(ast, model, links = {})
| 598 | * @returns {{schemaVersion: number, commands: object[], events: object[], types: object, domains: object}} The schema. |
| 599 | */ |
| 600 | export function projectSchema(ast, model, links = {}) { |
| 601 | const types = {} |
| 602 | for (const def of normalizeAst(ast)) { |
| 603 | if (!def?.Name) continue |
| 604 | const node = projectType(def) |
| 605 | // Types the normalizer minted for anonymous CDDL constructs (hoisted enums / |
| 606 | // inline records, union arms) carry their decomposition so a binding can name |
| 607 | // or nest them idiomatically without parsing the synthetic key. `owner` is the |
| 608 | // type the construct was lifted out of; `label` is the member name within it. |
| 609 | if (def['x-selenium-synthetic']) { |
| 610 | node.synthetic = true |
| 611 | node.owner = def['x-selenium-owner'] |
| 612 | node.label = def['x-selenium-label'] |
| 613 | } |
| 614 | // Link to the type's definition in the live spec, when the index covers it — |
| 615 | // the readable prose section where one exists, else the CDDL production. |
| 616 | // Synthetic types have no spec definition and are (correctly) never in it. |
| 617 | const typeHref = links.types?.[def.Name.toLowerCase()] |
| 618 | if (typeHref) node.specHref = typeHref |
| 619 | types[def.Name] = node |
| 620 | } |
| 621 | for (const [name, node] of Object.entries(types)) |
| 622 | if (node.kind === 'union') node.selector = unionSelector(name, types) |
| 623 | // Override the result-grouping unions: they are dispatched by request id, so a |
| 624 | // payload selector for them is meaningless (and would be empty/ambiguous). |
| 625 | for (const name of correlatedUnions(types)) types[name].selector = { correlated: true } |
| 626 | // A first-class union whose every arm is an object rejects a non-object payload |
| 627 | // instead of passing it through. (An alias-union like input.Origin, which carries |
| 628 | // bare-string arms, is intentionally left unflagged so those arms still pass through.) |
| 629 | for (const node of Object.values(types)) |
| 630 | if (node.kind === 'union' && node.variants.every((v) => variantIsObject(v, types))) node.objectOnly = true |
| 631 | |
| 632 | const commands = [] |
| 633 | const events = [] |
| 634 | const envelopeParams = commandEnvelopeParams(types) |
| 635 | // Commands and events link to their own prose section (`#command-*` / `#event-*`), |
| 636 | // keyed by the wire method; domains to their `#module-*` section. Present-only-when-known. |
| 637 | const link = (map, key) => (typeof key === 'string' ? map?.[key.toLowerCase()] : undefined) |
| 638 | for (const [domain, entry] of Object.entries(model)) { |
| 639 | for (const c of entry.commands ?? []) { |
| 640 | const cmd = { |
| 641 | domain, |
| 642 | method: c.method, |
| 643 | name: c.name, |
| 644 | // Prefer the envelope's params (it captures inline params the model drops). |
| 645 | params: typeRef(envelopeParams.get(c.method) ?? c.params), |
| 646 | result: typeRef(c.result), |
| 647 | } |
| 648 | const href = link(links.commands, c.method) |
| 649 | if (href) cmd.specHref = href |
| 650 | commands.push(cmd) |
| 651 | } |
| 652 | for (const e of entry.events ?? []) { |
| 653 | const ev = { domain, method: e.method, name: e.name, params: typeRef(envelopeParams.get(e.method) ?? e.params) } |
| 654 | const href = link(links.events, e.method) |
| 655 | if (href) ev.specHref = href |
| 656 | events.push(ev) |
| 657 | } |
no test coverage detected