@milehimikey/em 1.22.0 → 1.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
- package/.claude/skills/event-modeling-design/SKILL.md +5 -2
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-review/SKILL.md +1 -1
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +36 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/cli/api.d.ts +60 -17
- package/dist/cli/api.js +154 -34
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/scopeInputs.js +2 -2
- package/dist/cli/scopeInputs.js.map +1 -1
- package/dist/emit/api.d.ts +18 -0
- package/dist/emit/api.js +117 -3
- package/dist/emit/api.js.map +1 -1
- package/dist/emit/apiSurfaceJson.d.ts +1 -1
- package/dist/emit/apiSurfaceJson.js +18 -1
- package/dist/emit/apiSurfaceJson.js.map +1 -1
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +20 -1
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/json.d.ts +42 -1
- package/dist/emit/json.js +65 -6
- package/dist/emit/json.js.map +1 -1
- package/dist/mcp/server.js +7 -6
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/diff.d.ts +19 -1
- package/dist/model/diff.js +99 -0
- package/dist/model/diff.js.map +1 -1
- package/dist/model/model.d.ts +88 -4
- package/dist/model/model.js +98 -3
- package/dist/model/model.js.map +1 -1
- package/dist/model/refs.d.ts +4 -0
- package/dist/model/refs.js +35 -1
- package/dist/model/refs.js.map +1 -1
- package/dist/model/rules.d.ts +61 -0
- package/dist/model/rules.js +72 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/validate.js +137 -4
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +30 -0
- package/dist/parser/parser.js +67 -1
- package/dist/parser/parser.js.map +1 -1
- package/dist/render/sliceDiagram.js +6 -0
- package/dist/render/sliceDiagram.js.map +1 -1
- package/dist/render/storylineDiagram.js +6 -0
- package/dist/render/storylineDiagram.js.map +1 -1
- package/dist/system/adaptation.d.ts +5 -2
- package/dist/system/adaptation.js +5 -3
- package/dist/system/adaptation.js.map +1 -1
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/shared/reference/em-dsl.md +36 -0
- package/plugin/skills/conform/SKILL.md +1 -1
- package/plugin/skills/design/SKILL.md +5 -2
- package/plugin/skills/discover/SKILL.md +1 -1
- package/plugin/skills/engagement/SKILL.md +1 -1
- package/plugin/skills/event-modeling/SKILL.md +1 -1
- package/plugin/skills/implement/SKILL.md +1 -1
- package/plugin/skills/review/SKILL.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling
|
|
3
|
-
em-version: 1.22.
|
|
3
|
+
em-version: 1.22.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when the user runs `/event-modeling` (with or without a phase name), or wants to resume an
|
|
6
6
|
event-modeling session without specifying which part of the process applies. Reads the model's
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-conform
|
|
3
|
-
em-version: 1.22.
|
|
3
|
+
em-version: 1.22.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when checking a ratified event model (and its slice docs) for drift against the codebase
|
|
6
6
|
that implements it — advisory only, never a gate, never an unprompted edit — or when walking
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-design
|
|
3
|
-
em-version: 1.22.
|
|
3
|
+
em-version: 1.22.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when structuring a draft event model into swimlanes and the four patterns (State Change,
|
|
6
6
|
State View, Automation, Translation), evaluating a model's structural completeness, or writing
|
|
@@ -234,7 +234,10 @@ For each slice:
|
|
|
234
234
|
1. Hold a Socratic deep-dive to fill every section of `../event-modeling-shared/templates/slice.md`:
|
|
235
235
|
intent, trigger/actor,
|
|
236
236
|
command fields (types & rules), event(s) + payload (mark immutable facts) — field names and
|
|
237
|
-
types go into the `.em`, the doc's tables are generated from it
|
|
237
|
+
types go into the `.em`, the doc's tables are generated from it (on a `public` element, a
|
|
238
|
+
field's type must be the fixed scalar table, a declared `type`, a declared identity `scalar`,
|
|
239
|
+
or a declared `enum` — see `em-dsl.md`'s Strict public types/Identity scalars/Enums
|
|
240
|
+
sections) — invariants
|
|
238
241
|
(give each a stable ID — see **Invariants live in the model** below), Given/When/Then scenarios (happy path + rule boundaries + edge cases),
|
|
239
242
|
alternate/error flows (retries, idempotency, compensations), non-functional requirements
|
|
240
243
|
(security/authz, PII/compliance, performance/SLA), read models affected, open questions. Park
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-discover
|
|
3
|
-
em-version: 1.22.
|
|
3
|
+
em-version: 1.22.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when starting a brand-new event model from scratch — greenfield discovery: brainstorming
|
|
6
6
|
past-tense events, storyboarding the happy path, finding the commands and read models — or when
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-engagement
|
|
3
|
-
em-version: 1.22.
|
|
3
|
+
em-version: 1.22.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when leading an engagement: building a named set of ratified event-modeling slices
|
|
6
6
|
(`em engagement`) into one independent PR per slice, each against main, by dispatching the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-implement
|
|
3
|
-
em-version: 1.22.
|
|
3
|
+
em-version: 1.22.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when building a single ratified event-modeling slice (status: ready-to-implement) into
|
|
6
6
|
merged, tested code — the one phase where the agent builds rather than facilitates. Gates on
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-review
|
|
3
|
-
em-version: 1.22.
|
|
3
|
+
em-version: 1.22.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when starting em's live browser viewer (`em watch --serve`) so a team can watch a model
|
|
6
6
|
update in real time, or facilitating a SCHEDULED stakeholder walkthrough that steps through
|
|
@@ -352,6 +352,8 @@ slice "Name" [source "url"] { # one vertical time step (a column); source is
|
|
|
352
352
|
arrow From Element -> To Element # explicit cross-slice edge (overrides inferred flow)
|
|
353
353
|
|
|
354
354
|
type Name { field: Type, ... } # named structured type, reusable from any field (see Named types)
|
|
355
|
+
scalar Name: base # identity alias over a public scalar table entry (see Named types)
|
|
356
|
+
enum Name { member, ... } # closed named value set, reusable from any field (see Named types)
|
|
355
357
|
```
|
|
356
358
|
|
|
357
359
|
### Element kinds (8 keywords, nothing else)
|
|
@@ -620,6 +622,31 @@ how tree/recursive data (categories, org charts, comment threads, BOMs) gets exp
|
|
|
620
622
|
fields and ordinary element fields alike) — `{ name, ref, array }` when resolved, `null`
|
|
621
623
|
otherwise. `em diff` tracks types added/removed and field changes on surviving types.
|
|
622
624
|
|
|
625
|
+
**Identity scalars** (GH #477): `scalar Name: base` declares an identity alias over one public
|
|
626
|
+
scalar table entry (`base`, case-insensitive) — so a `public` field can carry a domain name on
|
|
627
|
+
the wire (`orderId: OrderId`) instead of degrading to the bare table type (`uuid`) to satisfy
|
|
628
|
+
[Strict public types](#strict-public-types). Reference it bare or `Name[]`, same as a declared
|
|
629
|
+
`type`; on a `public` element it satisfies the strict table directly, on an internal element it
|
|
630
|
+
resolves opportunistically. `em validate` errors on an unknown `base`, a redeclared name, or a
|
|
631
|
+
name colliding with a declared `type` (either direction). `em api generate` declares a used
|
|
632
|
+
scalar once, after the table scalars, extending the formatted table scalar
|
|
633
|
+
(`scalar OrderId extends Uuid;`) or the core type (`scalar Sku extends string;`) — a model with
|
|
634
|
+
no `scalar` emits unchanged. `em export` adds `model.scalars[]` and an additive `scalarRef` field
|
|
635
|
+
key, mutually exclusive with `typeRef`.
|
|
636
|
+
|
|
637
|
+
**Enums** (GH #478): `enum Name { member, … }` declares a closed, named value set — members are
|
|
638
|
+
bare identifiers or quoted strings, in declaration order. Reference it bare or `Name[]`, same
|
|
639
|
+
as a declared `type`/`scalar`; on a `public` element it satisfies the strict table directly, on
|
|
640
|
+
an internal element it resolves opportunistically. `em validate` errors on an empty enum, a
|
|
641
|
+
case-insensitive duplicate member, a redeclared name, or a name colliding with a declared
|
|
642
|
+
`type`/`scalar` (either direction). `em api generate` declares a used enum once, members in
|
|
643
|
+
declaration order (`enum OrderStatus { placed, shipped }`), after the scalar declarations and
|
|
644
|
+
before type blocks — a member that isn't a bare TypeSpec identifier emits as a quoted string
|
|
645
|
+
literal; a model with no `enum` emits unchanged. `em export` adds `model.enums[]` and an
|
|
646
|
+
additive `enumRef` field key, mutually exclusive with both `typeRef` and `scalarRef`. `em api
|
|
647
|
+
check --base` judges a surviving enum's member changes by role: removed/renamed is breaking on
|
|
648
|
+
an output, added is breaking on an input, additive on a pure output.
|
|
649
|
+
|
|
623
650
|
### Swimlane band order (top → bottom)
|
|
624
651
|
Header row → **Automation** (only if used) → **persona** rows (in declared order) → **API**
|
|
625
652
|
(commands + views share this lane) → **context** rows (in declared order).
|
|
@@ -950,11 +977,17 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
950
977
|
| `doc-names-unfed-event` | warning | Doc names an event its view does not read | Add the event to the view's `from` in the model (if the edge is real), or fix the doc's Source column / scenario. |
|
|
951
978
|
| `doc-omits-fed-event` | warning | Doc omits an event its view reads | Mention the event in the view's Source column (run `em slice sync`), or drop it from the view's `from`. |
|
|
952
979
|
| `duplicate-element-ref` | warning | Duplicate element ref | Rename the element so its export ref is unique. |
|
|
980
|
+
| `duplicate-enum-ref` | warning | Duplicate enum ref | Rename the enum so its export ref is unique. |
|
|
953
981
|
| `duplicate-model-key` | warning | Duplicate model key | Give each model a unique `model "Name"` so its key (and every cross-model ref) is stable. |
|
|
954
982
|
| `duplicate-name` | warning | Duplicate name | Rename one of the duplicates. |
|
|
983
|
+
| `duplicate-scalar-ref` | warning | Duplicate scalar ref | Rename the scalar so its export ref is unique. |
|
|
955
984
|
| `duplicate-slice-name` | warning | Duplicate slice name | Rename the slice so its export key is unique. |
|
|
956
985
|
| `duplicate-type-name` | warning | Duplicate type name | Rename one of the duplicate `type` declarations. |
|
|
957
986
|
| `duplicate-type-ref` | warning | Duplicate type ref | Rename the type so its export ref is unique. |
|
|
987
|
+
| `enum-duplicate-member` | error | Enum duplicate member | Remove or rename the duplicate member — members are compared case-insensitively. |
|
|
988
|
+
| `enum-duplicate-name` | error | Enum duplicate name | Declare the enum once; rename or remove the duplicate `enum` block. |
|
|
989
|
+
| `enum-empty` | error | Empty enum | Give the enum at least one member, or remove the declaration. |
|
|
990
|
+
| `enum-name-collision` | error | Enum/type/scalar name collision | Rename the `enum` or the colliding `type`/`scalar` so the names no longer collide — a name can't mean both at once. |
|
|
958
991
|
| `fields-completeness/event-field-no-source` | warning | Event field not from a command | Add the field to a command that records the event (in any slice), mark it assigned if the handler sets it, or remove it from the event. |
|
|
959
992
|
| `fields-completeness/view-field-no-source` | warning | View field with no source | Add the field to a source event, or remove it from the view. |
|
|
960
993
|
| `frontmatter-coherence-implemented-without-link` | warning | Implemented without a link | Add `implementedIn` once the slice ships. |
|
|
@@ -987,6 +1020,9 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
987
1020
|
| `reaction-from-future-view` | error | Backward timeline (reaction reads a future view) | Declare the view in or before the reaction's slice. |
|
|
988
1021
|
| `reaction-from-unresolved` | error | Unknown read-model source | Project the event into a view first, or fix the `from` reference. |
|
|
989
1022
|
| `retry-slice-of` | warning | Slice only retries another command | Re-fire the same reaction via `view … again from` the retry-scheduled event; a different actor's override is its own slice with its own command (docs/patterns.md#automation). |
|
|
1023
|
+
| `scalar-duplicate-name` | error | Scalar duplicate name | Declare the scalar once; rename or remove the duplicate `scalar` line. |
|
|
1024
|
+
| `scalar-type-name-collision` | error | Scalar/type name collision | Rename the `scalar` or the `type` so the two names no longer collide — a name can't mean both at once. |
|
|
1025
|
+
| `scalar-unknown-base` | error | Scalar unknown base | Point `base` at one of the public scalar table's names (string, text, int, long, decimal, boolean, uuid, date, datetime, duration, bytes), case-insensitive. |
|
|
990
1026
|
| `scenario-then-not-outcome` | warning | Scenario's Then names no event or rejection | Make the Then bullets name the event(s) the command records, or the rejection/error it returns. |
|
|
991
1027
|
| `scenario-when-not-command` | warning | Scenario's When does not name the slice's command | Make the When bullet name the slice's command (for a reaction slice: the command, the reaction element, or a view of the slice) — each scenario exercises exactly one command. For a Translation, naming the translation element satisfies it. |
|
|
992
1028
|
| `seam-consumer-not-reaction` | error | Seam consumer is not a reaction | Point `to` at a translation/automation element (or a slice containing exactly one). |
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "em-1-22-
|
|
2
|
+
"name": "em-1-22-1",
|
|
3
3
|
"owner": {
|
|
4
4
|
"name": "Mike Key"
|
|
5
5
|
},
|
|
6
|
-
"description": "em v1.22.
|
|
6
|
+
"description": "em v1.22.1: the Event Modeling skill bundle and MCP server as a Claude Code plugin.",
|
|
7
7
|
"plugins": [
|
|
8
8
|
{
|
|
9
9
|
"name": "em",
|
package/dist/cli/api.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NormalizedModel } from "../model/model.js";
|
|
2
2
|
import { Diagnostic } from "../model/validate.js";
|
|
3
3
|
import { compile } from "../pipeline.js";
|
|
4
|
-
import { ElementExport, FieldExport, TypeExport } from "../emit/json.js";
|
|
4
|
+
import { ElementExport, EnumExport, FieldExport, TypeExport } from "../emit/json.js";
|
|
5
5
|
import { RefsResult } from "../model/refs.js";
|
|
6
6
|
import { GitRunner } from "./diff-inputs.js";
|
|
7
7
|
/** `<modelDir>/contracts/<modelKey>.tsp` (R6), built from the model path as given. */
|
|
@@ -30,7 +30,7 @@ export interface ApiCheckChange {
|
|
|
30
30
|
}
|
|
31
31
|
type Role = "input" | "output";
|
|
32
32
|
interface SurfaceEntry {
|
|
33
|
-
/** command | event | view | type */
|
|
33
|
+
/** command | event | view | type | enum */
|
|
34
34
|
kind: string;
|
|
35
35
|
name: string;
|
|
36
36
|
/** Normalized identity key: kind + normalizeName(name). */
|
|
@@ -38,11 +38,13 @@ interface SurfaceEntry {
|
|
|
38
38
|
fields: FieldExport[];
|
|
39
39
|
renamedFrom: string[];
|
|
40
40
|
roles: Set<Role>;
|
|
41
|
-
/** Model-declared invariants (MIL-265), declaration order; `[]` for a type. */
|
|
41
|
+
/** Model-declared invariants (MIL-265), declaration order; `[]` for a type/enum. */
|
|
42
42
|
invariants: {
|
|
43
43
|
id: string;
|
|
44
44
|
rule: string | null;
|
|
45
45
|
}[];
|
|
46
|
+
/** GH #478: an enum's own members, in declaration order — `[]` for every non-enum kind. */
|
|
47
|
+
members: string[];
|
|
46
48
|
}
|
|
47
49
|
interface Surface {
|
|
48
50
|
entries: SurfaceEntry[];
|
|
@@ -51,14 +53,29 @@ interface Surface {
|
|
|
51
53
|
allElements: Set<string>;
|
|
52
54
|
}
|
|
53
55
|
/** Build the public surface of one compiled model: public commands (input), events and views
|
|
54
|
-
* (output), and every declared type reachable from them (carrying the roles that reach
|
|
56
|
+
* (output), and every declared type/enum reachable from them (carrying the roles that reach
|
|
57
|
+
* it — GH #478 extends the existing type-reach walk to also track an `enumRef` field,
|
|
58
|
+
* directly or through a reachable type's own fields, the same way `reachableEnumRefs`
|
|
59
|
+
* (emit/api.ts) tracks it for `em api generate`). `enums` defaults to `[]` so every existing
|
|
60
|
+
* caller (and the one test helper that built a `Surface` before enums existed) keeps compiling
|
|
61
|
+
* without a required third argument at a call site that never has one. */
|
|
55
62
|
export declare function surfaceOf(slices: {
|
|
56
63
|
elements: ElementExport[];
|
|
57
|
-
}[], types: TypeExport[]): Surface;
|
|
58
|
-
/** A field's type as the contract sees it: a declared type's
|
|
59
|
-
* normalized, with its array arity. */
|
|
60
|
-
export declare function typeLabel(f: Pick<FieldExport, "type" | "typeRef">): string;
|
|
61
|
-
|
|
64
|
+
}[], types: TypeExport[], enums?: EnumExport[]): Surface;
|
|
65
|
+
/** A field's type as the contract sees it: a declared type's, identity scalar's or enum's
|
|
66
|
+
* name, or the raw string, normalized, with its array arity. */
|
|
67
|
+
export declare function typeLabel(f: Pick<FieldExport, "type" | "typeRef" | "scalarRef" | "enumRef">): string;
|
|
68
|
+
/**
|
|
69
|
+
* GH #477: a `scalarRef` folds its `base` into the identity string too, not just `ref`/`array` —
|
|
70
|
+
* so a surviving scalar DECLARATION's base changing (`OrderId: uuid` -> `OrderId: string`)
|
|
71
|
+
* registers as a type change on every field that uses it, even though the scalar's own ref and
|
|
72
|
+
* the field's array arity are unchanged. This reuses `diffFields`' existing per-field
|
|
73
|
+
* `compare()` rather than adding a second comparison path — see its `scalarRef`-aware branch
|
|
74
|
+
* below for the breaking-change message this produces. GH #478: `enumRef` needs no equivalent
|
|
75
|
+
* fold — an enum's MEMBER changes are diffed on the enum's own `SurfaceEntry` (`diffEnumMembers`
|
|
76
|
+
* below), not per using field, so `ref`/`array` alone identify a field's enum reference here.
|
|
77
|
+
*/
|
|
78
|
+
export declare function typeIdentity(f: Pick<FieldExport, "type" | "typeRef" | "scalarRef" | "enumRef">): string;
|
|
62
79
|
/** Diff two public surfaces. `base === null` means the model did not exist at the base
|
|
63
80
|
* revision: every HEAD element is an additive addition. */
|
|
64
81
|
export declare function diffSurfaces(base: Surface | null, head: Surface): ApiCheckChange[];
|
|
@@ -96,13 +113,21 @@ export type ApiCheckOutcome = {
|
|
|
96
113
|
export declare function runApiCheck(file: string, base: string | undefined, runGit?: GitRunner): ApiCheckOutcome;
|
|
97
114
|
/** The text report: one status line, then one `additive:`/`breaking:` line per change. */
|
|
98
115
|
export declare function formatApiCheckText(r: ApiCheckReport): string[];
|
|
99
|
-
/** A public field's resolved type (R7's fixed table
|
|
100
|
-
* `resolvePublicType` (model/model.ts) computes —
|
|
101
|
-
* key and the TypeSpec core scalar `em api
|
|
102
|
-
*
|
|
116
|
+
/** A public field's resolved type (R7's fixed table, a declared identity scalar, a declared
|
|
117
|
+
* enum, or a declared type), the same shape `resolvePublicType` (model/model.ts) computes —
|
|
118
|
+
* `scalar` carries the matched table entry's key and the TypeSpec core scalar `em api
|
|
119
|
+
* generate` emits for it; `declared` carries the reachable type's own name (see
|
|
120
|
+
* `ApiSurfaceType` below for its full field shape); `scalar-alias` (GH #477, schema 1.1)
|
|
121
|
+
* carries the declared identity scalar's own name in `tsType` (what actually appears in the
|
|
122
|
+
* generated contract, same convention `declared` already uses) and the table key it extends
|
|
123
|
+
* in `base` — `scalar`/`declared` leave `base` `null`; `enum` (GH #478, schema 1.2) carries
|
|
124
|
+
* the declared enum's own name in `tsType`, same convention — find its members under the
|
|
125
|
+
* report's own `enums` list. */
|
|
103
126
|
export interface ApiSurfaceFieldType {
|
|
104
|
-
kind: "scalar" | "declared";
|
|
127
|
+
kind: "scalar" | "declared" | "scalar-alias" | "enum";
|
|
105
128
|
scalar: string | null;
|
|
129
|
+
/** The `PUBLIC_SCALAR_TYPES` key a `scalar-alias` extends; `null` for every other kind. */
|
|
130
|
+
base: string | null;
|
|
106
131
|
tsType: string;
|
|
107
132
|
array: boolean;
|
|
108
133
|
}
|
|
@@ -133,6 +158,20 @@ export interface ApiSurfaceType {
|
|
|
133
158
|
slug: string;
|
|
134
159
|
fields: ApiSurfaceField[];
|
|
135
160
|
}
|
|
161
|
+
/** One declared `scalar Name: base` identity alias reachable from a public command/event/view's
|
|
162
|
+
* fields (GH #477, schema 1.1) — `base` is the `PUBLIC_SCALAR_TYPES` key it extends. */
|
|
163
|
+
export interface ApiSurfaceScalar {
|
|
164
|
+
name: string;
|
|
165
|
+
slug: string;
|
|
166
|
+
base: string;
|
|
167
|
+
}
|
|
168
|
+
/** One declared `enum Name { member, … }` reachable from a public command/event/view's fields
|
|
169
|
+
* (GH #478, schema 1.2) — `members` in declaration order, raw (unquoted). */
|
|
170
|
+
export interface ApiSurfaceEnum {
|
|
171
|
+
name: string;
|
|
172
|
+
slug: string;
|
|
173
|
+
members: string[];
|
|
174
|
+
}
|
|
136
175
|
export interface ApiSurfaceReport {
|
|
137
176
|
file: string;
|
|
138
177
|
modelKey: string;
|
|
@@ -140,6 +179,8 @@ export interface ApiSurfaceReport {
|
|
|
140
179
|
events: ApiSurfaceElement[];
|
|
141
180
|
views: ApiSurfaceElement[];
|
|
142
181
|
types: ApiSurfaceType[];
|
|
182
|
+
scalars: ApiSurfaceScalar[];
|
|
183
|
+
enums: ApiSurfaceEnum[];
|
|
143
184
|
}
|
|
144
185
|
export type ApiSurfaceOutcome = {
|
|
145
186
|
ok: true;
|
|
@@ -154,9 +195,11 @@ export type ApiSurfaceOutcome = {
|
|
|
154
195
|
* Build the `em api surface` report for an already-validated (error-free) model: the `public`
|
|
155
196
|
* commands/events/views (`publicSurfaceOf`, same as `em api generate`/`em api check`) plus every
|
|
156
197
|
* declared type reachable from their fields (`reachableTypeRefs`, same walk `em api generate`'s
|
|
157
|
-
* contract body uses),
|
|
158
|
-
*
|
|
159
|
-
* `
|
|
198
|
+
* contract body uses), every declared identity scalar reachable the same way
|
|
199
|
+
* (`reachableScalarRefs`, GH #477), and every declared enum reachable the same way
|
|
200
|
+
* (`reachableEnumRefs`, GH #478), each field's type resolved through `resolvePublicType` — the
|
|
201
|
+
* one notion of the public surface every em surface shares, so a companion tool never re-derives
|
|
202
|
+
* it from `em export --json` and risks drifting from it.
|
|
160
203
|
*/
|
|
161
204
|
export declare function buildApiSurfaceReport(model: NormalizedModel, refs: RefsResult, diagnostics: Diagnostic[], source: string, file: string): ApiSurfaceReport;
|
|
162
205
|
/**
|
package/dist/cli/api.js
CHANGED
|
@@ -14,7 +14,7 @@ import { hasErrors } from "../model/validate.js";
|
|
|
14
14
|
import { compile, compileModelFile } from "../pipeline.js";
|
|
15
15
|
import { ModelLoadError } from "../model/loadModel.js";
|
|
16
16
|
import { ParseError } from "../parser/parser.js";
|
|
17
|
-
import { buildApiContract, publicSurfaceOf, reachableTypeRefs } from "../emit/api.js";
|
|
17
|
+
import { buildApiContract, publicSurfaceOf, reachableEnumRefs, reachableScalarRefs, reachableTypeRefs } from "../emit/api.js";
|
|
18
18
|
import { buildExportDoc } from "../emit/json.js";
|
|
19
19
|
import { kebabSlug } from "../util/slug.js";
|
|
20
20
|
import { realGit, resolveRevision } from "./diff-inputs.js";
|
|
@@ -36,14 +36,25 @@ export function generateContract(file, source, compiled, outPath) {
|
|
|
36
36
|
}
|
|
37
37
|
const keyOf = (kind, name) => `${kind}:${normalizeName(name)}`;
|
|
38
38
|
/** Build the public surface of one compiled model: public commands (input), events and views
|
|
39
|
-
* (output), and every declared type reachable from them (carrying the roles that reach
|
|
40
|
-
|
|
39
|
+
* (output), and every declared type/enum reachable from them (carrying the roles that reach
|
|
40
|
+
* it — GH #478 extends the existing type-reach walk to also track an `enumRef` field,
|
|
41
|
+
* directly or through a reachable type's own fields, the same way `reachableEnumRefs`
|
|
42
|
+
* (emit/api.ts) tracks it for `em api generate`). `enums` defaults to `[]` so every existing
|
|
43
|
+
* caller (and the one test helper that built a `Surface` before enums existed) keeps compiling
|
|
44
|
+
* without a required third argument at a call site that never has one. */
|
|
45
|
+
export function surfaceOf(slices, types, enums = []) {
|
|
41
46
|
const s = publicSurfaceOf(slices);
|
|
42
47
|
const entries = [];
|
|
43
48
|
const typesByRef = new Map(types.map((t) => [t.ref, t]));
|
|
44
49
|
const typeRoles = new Map();
|
|
50
|
+
const enumRoles = new Map();
|
|
45
51
|
const reach = (fields, role) => {
|
|
46
52
|
for (const f of fields ?? []) {
|
|
53
|
+
if (f.enumRef) {
|
|
54
|
+
const roles = enumRoles.get(f.enumRef.ref) ?? new Set();
|
|
55
|
+
roles.add(role);
|
|
56
|
+
enumRoles.set(f.enumRef.ref, roles);
|
|
57
|
+
}
|
|
47
58
|
if (!f.typeRef)
|
|
48
59
|
continue;
|
|
49
60
|
const roles = typeRoles.get(f.typeRef.ref) ?? new Set();
|
|
@@ -63,6 +74,7 @@ export function surfaceOf(slices, types) {
|
|
|
63
74
|
renamedFrom: el.renamedFrom ?? [],
|
|
64
75
|
roles: new Set([role]),
|
|
65
76
|
invariants: el.invariants ?? [],
|
|
77
|
+
members: [],
|
|
66
78
|
});
|
|
67
79
|
reach(el.fields, role);
|
|
68
80
|
};
|
|
@@ -76,7 +88,13 @@ export function surfaceOf(slices, types) {
|
|
|
76
88
|
const roles = typeRoles.get(t.ref);
|
|
77
89
|
if (!roles)
|
|
78
90
|
continue;
|
|
79
|
-
entries.push({ kind: "type", name: t.name, key: keyOf("type", t.name), fields: t.fields, renamedFrom: [], roles, invariants: [] });
|
|
91
|
+
entries.push({ kind: "type", name: t.name, key: keyOf("type", t.name), fields: t.fields, renamedFrom: [], roles, invariants: [], members: [] });
|
|
92
|
+
}
|
|
93
|
+
for (const e of enums) {
|
|
94
|
+
const roles = enumRoles.get(e.ref);
|
|
95
|
+
if (!roles)
|
|
96
|
+
continue;
|
|
97
|
+
entries.push({ kind: "enum", name: e.name, key: keyOf("enum", e.name), fields: [], renamedFrom: [], roles, invariants: [], members: e.members });
|
|
80
98
|
}
|
|
81
99
|
const allElements = new Set();
|
|
82
100
|
for (const slice of slices)
|
|
@@ -84,18 +102,38 @@ export function surfaceOf(slices, types) {
|
|
|
84
102
|
allElements.add(keyOf(el.kind, el.name));
|
|
85
103
|
for (const t of types)
|
|
86
104
|
allElements.add(keyOf("type", t.name));
|
|
105
|
+
for (const e of enums)
|
|
106
|
+
allElements.add(keyOf("enum", e.name));
|
|
87
107
|
return { entries, allElements };
|
|
88
108
|
}
|
|
89
|
-
/** A field's type as the contract sees it: a declared type's
|
|
90
|
-
* normalized, with its array arity. */
|
|
109
|
+
/** A field's type as the contract sees it: a declared type's, identity scalar's or enum's
|
|
110
|
+
* name, or the raw string, normalized, with its array arity. */
|
|
91
111
|
export function typeLabel(f) {
|
|
92
112
|
if (f.typeRef)
|
|
93
113
|
return `${f.typeRef.name}${f.typeRef.array ? "[]" : ""}`;
|
|
114
|
+
if (f.scalarRef)
|
|
115
|
+
return `${f.scalarRef.name}${f.scalarRef.array ? "[]" : ""}`;
|
|
116
|
+
if (f.enumRef)
|
|
117
|
+
return `${f.enumRef.name}${f.enumRef.array ? "[]" : ""}`;
|
|
94
118
|
return f.type ?? "(none)";
|
|
95
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* GH #477: a `scalarRef` folds its `base` into the identity string too, not just `ref`/`array` —
|
|
122
|
+
* so a surviving scalar DECLARATION's base changing (`OrderId: uuid` -> `OrderId: string`)
|
|
123
|
+
* registers as a type change on every field that uses it, even though the scalar's own ref and
|
|
124
|
+
* the field's array arity are unchanged. This reuses `diffFields`' existing per-field
|
|
125
|
+
* `compare()` rather than adding a second comparison path — see its `scalarRef`-aware branch
|
|
126
|
+
* below for the breaking-change message this produces. GH #478: `enumRef` needs no equivalent
|
|
127
|
+
* fold — an enum's MEMBER changes are diffed on the enum's own `SurfaceEntry` (`diffEnumMembers`
|
|
128
|
+
* below), not per using field, so `ref`/`array` alone identify a field's enum reference here.
|
|
129
|
+
*/
|
|
96
130
|
export function typeIdentity(f) {
|
|
97
131
|
if (f.typeRef)
|
|
98
132
|
return `ref:${f.typeRef.ref}:${f.typeRef.array}`;
|
|
133
|
+
if (f.scalarRef)
|
|
134
|
+
return `ref:${f.scalarRef.ref}:${f.scalarRef.array}:${f.scalarRef.base}`;
|
|
135
|
+
if (f.enumRef)
|
|
136
|
+
return `enum-ref:${f.enumRef.ref}:${f.enumRef.array}`;
|
|
99
137
|
return `raw:${(f.type ?? "").replace(/\s+/g, "").toLowerCase()}`;
|
|
100
138
|
}
|
|
101
139
|
function diffFields(base, head, element, out) {
|
|
@@ -120,7 +158,25 @@ function diffFields(base, head, element, out) {
|
|
|
120
158
|
}
|
|
121
159
|
const compare = (b, h) => {
|
|
122
160
|
if (typeIdentity(b) !== typeIdentity(h)) {
|
|
123
|
-
|
|
161
|
+
// GH #477: same scalar declaration, same array arity, different `base` — the field's own
|
|
162
|
+
// reference didn't change, but what it resolves to on the wire did (every consumer
|
|
163
|
+
// deserializing this field as the old base now gets the new one). Phrased distinctly from
|
|
164
|
+
// an ordinary type change so the report doesn't read as "OrderId -> OrderId" (no visible
|
|
165
|
+
// difference) when the real story is the scalar's declaration.
|
|
166
|
+
if (b.scalarRef && h.scalarRef &&
|
|
167
|
+
b.scalarRef.ref === h.scalarRef.ref &&
|
|
168
|
+
b.scalarRef.array === h.scalarRef.array &&
|
|
169
|
+
b.scalarRef.base !== h.scalarRef.base) {
|
|
170
|
+
out.push({
|
|
171
|
+
kind: "breaking",
|
|
172
|
+
element,
|
|
173
|
+
field: h.name,
|
|
174
|
+
what: `scalar "${h.scalarRef.name}" base changed ${b.scalarRef.base} → ${h.scalarRef.base}`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
out.push({ kind: "breaking", element, field: h.name, what: `type changed ${typeLabel(b)} → ${typeLabel(h)}` });
|
|
179
|
+
}
|
|
124
180
|
}
|
|
125
181
|
if (b.optional !== h.optional) {
|
|
126
182
|
if (h.optional) {
|
|
@@ -160,6 +216,37 @@ function diffFields(base, head, element, out) {
|
|
|
160
216
|
out.push({ kind, element, field: h.name, what: `added (${required ? "required" : "optional"})` });
|
|
161
217
|
}
|
|
162
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* GH #478: an enum's own member set, base vs HEAD, diffed by role (the same `isInput`/
|
|
221
|
+
* `isOutput` union `diffFields` computes above) — the issue's pinned severity matrix:
|
|
222
|
+
* - member REMOVED (or, since there's no rename clause, the removal half of what reads as a
|
|
223
|
+
* rename) is breaking wherever the enum reaches an OUTPUT (an existing receiver may still
|
|
224
|
+
* expect that known value) — not conditioned on input reach at all.
|
|
225
|
+
* - member ADDED is breaking wherever the enum reaches an INPUT (existing senders are
|
|
226
|
+
* unaffected; a strict receiver built against the old member set rejects the new value) —
|
|
227
|
+
* additive on a PURE output (`isInput` false): a receiver tolerates an unknown value.
|
|
228
|
+
* A rename is never detected as such (same "no rename detection" posture `em diff` already
|
|
229
|
+
* takes for a `type`/element rename, model/diff.ts) — it reads as remove+add, each half scored
|
|
230
|
+
* independently by the rules above. Base order first (removals), then HEAD-only (additions),
|
|
231
|
+
* same convention `diffFields` uses.
|
|
232
|
+
*/
|
|
233
|
+
function diffEnumMembers(base, head, element, out) {
|
|
234
|
+
const roles = new Set([...base.roles, ...head.roles]);
|
|
235
|
+
const isInput = roles.has("input");
|
|
236
|
+
const isOutput = roles.has("output");
|
|
237
|
+
const baseKeys = new Set(base.members.map(normalizeName));
|
|
238
|
+
const headKeys = new Set(head.members.map(normalizeName));
|
|
239
|
+
for (const b of base.members) {
|
|
240
|
+
if (headKeys.has(normalizeName(b)))
|
|
241
|
+
continue;
|
|
242
|
+
out.push({ kind: isOutput ? "breaking" : "additive", element, field: null, what: `member "${b}" removed` });
|
|
243
|
+
}
|
|
244
|
+
for (const h of head.members) {
|
|
245
|
+
if (baseKeys.has(normalizeName(h)))
|
|
246
|
+
continue;
|
|
247
|
+
out.push({ kind: isInput ? "breaking" : "additive", element, field: null, what: `member "${h}" added` });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
163
250
|
/** MIL-265: a public element's invariants, base vs HEAD, matched by ID. Added or rule text
|
|
164
251
|
* changed = breaking (the contract got tighter); removed = additive (looser). HEAD order for
|
|
165
252
|
* additions/changes, then base order for removals. */
|
|
@@ -195,7 +282,10 @@ export function diffSurfaces(base, head) {
|
|
|
195
282
|
const same = baseByKey.get(h.key);
|
|
196
283
|
if (same) {
|
|
197
284
|
matchedBase.add(same.key);
|
|
198
|
-
|
|
285
|
+
if (h.kind === "enum")
|
|
286
|
+
diffEnumMembers(same, h, element, out);
|
|
287
|
+
else
|
|
288
|
+
diffFields(same, h, element, out);
|
|
199
289
|
diffInvariants(same, h, element, out);
|
|
200
290
|
continue;
|
|
201
291
|
}
|
|
@@ -205,11 +295,14 @@ export function diffSurfaces(base, head) {
|
|
|
205
295
|
if (renamedFrom) {
|
|
206
296
|
matchedBase.add(renamedFrom.key);
|
|
207
297
|
out.push({ kind: "breaking", element, field: null, what: `renamed from "${renamedFrom.name}"` });
|
|
208
|
-
|
|
298
|
+
if (h.kind === "enum")
|
|
299
|
+
diffEnumMembers(renamedFrom, h, element, out);
|
|
300
|
+
else
|
|
301
|
+
diffFields(renamedFrom, h, element, out);
|
|
209
302
|
diffInvariants(renamedFrom, h, element, out);
|
|
210
303
|
continue;
|
|
211
304
|
}
|
|
212
|
-
if (h.kind !== "type" && base.allElements.has(h.key)) {
|
|
305
|
+
if (h.kind !== "type" && h.kind !== "enum" && base.allElements.has(h.key)) {
|
|
213
306
|
out.push({ kind: "additive", element, field: null, what: "marked public" });
|
|
214
307
|
}
|
|
215
308
|
else {
|
|
@@ -220,7 +313,7 @@ export function diffSurfaces(base, head) {
|
|
|
220
313
|
if (matchedBase.has(b.key))
|
|
221
314
|
continue;
|
|
222
315
|
const element = elementId(b);
|
|
223
|
-
if (b.kind !== "type" && head.allElements.has(b.key)) {
|
|
316
|
+
if (b.kind !== "type" && b.kind !== "enum" && head.allElements.has(b.key)) {
|
|
224
317
|
out.push({ kind: "breaking", element, field: null, what: "`public` dropped" });
|
|
225
318
|
}
|
|
226
319
|
else {
|
|
@@ -274,7 +367,7 @@ export function runApiCheck(file, base, runGit = realGit) {
|
|
|
274
367
|
return { ok: false, message: `em api check: unknown revision "${base}" (needed for --base)`, diagnostics: head.diagnostics };
|
|
275
368
|
}
|
|
276
369
|
const headDoc = buildExportDoc(head.model, head.refs, head.diagnostics, head.source, file).doc;
|
|
277
|
-
const headSurface = surfaceOf(headDoc.model.slices, headDoc.model.types);
|
|
370
|
+
const headSurface = surfaceOf(headDoc.model.slices, headDoc.model.types, headDoc.model.enums);
|
|
278
371
|
const atBase = resolveRevision(file, base, runGit);
|
|
279
372
|
let baseSurface = null;
|
|
280
373
|
if (atBase.ok) {
|
|
@@ -301,7 +394,7 @@ export function runApiCheck(file, base, runGit = realGit) {
|
|
|
301
394
|
// Base-side validate errors (e.g. a pre-1.14 free-text public type) do not block the
|
|
302
395
|
// structural diff — the diff reads shapes, not strictness.
|
|
303
396
|
const baseDoc = buildExportDoc(baseCompiled.model, baseCompiled.refs, baseCompiled.diagnostics, atBase.content, file).doc;
|
|
304
|
-
baseSurface = surfaceOf(baseDoc.model.slices, baseDoc.model.types);
|
|
397
|
+
baseSurface = surfaceOf(baseDoc.model.slices, baseDoc.model.types, baseDoc.model.enums);
|
|
305
398
|
}
|
|
306
399
|
else if (!/is not tracked by git/.test(atBase.message)) {
|
|
307
400
|
return { ok: false, message: atBase.message.replace(/^em diff:/, "em api check:"), diagnostics: head.diagnostics };
|
|
@@ -334,35 +427,41 @@ export function formatApiCheckText(r) {
|
|
|
334
427
|
return lines;
|
|
335
428
|
}
|
|
336
429
|
/** Resolve one field's public type exactly as `em api generate`'s own `fieldType()` does
|
|
337
|
-
* (emit/api.ts) — declared types win (same precedence `resolvePublicType` already gives),
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
|
|
342
|
-
|
|
430
|
+
* (emit/api.ts) — declared types win (same precedence `resolvePublicType` already gives), then
|
|
431
|
+
* a declared identity scalar (GH #477), then a declared enum (GH #478), else the fixed scalar
|
|
432
|
+
* table. A model with errors never reaches here (both CLI callers refuse first), so an
|
|
433
|
+
* unresolved type is a programming error, not a user-facing case — same posture as
|
|
434
|
+
* `buildApiContract`'s own throw. */
|
|
435
|
+
function surfaceFieldType(f, typesByName, scalarsByName, enumsByName, owner) {
|
|
436
|
+
const r = resolvePublicType(f.type ?? undefined, typesByName, scalarsByName, enumsByName);
|
|
343
437
|
if (!r) {
|
|
344
438
|
throw new Error(`em api surface: ${owner} field "${f.name}" has no public type — em validate's ` +
|
|
345
439
|
"public-field-type-unresolved should have refused this model");
|
|
346
440
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
441
|
+
if (r.kind === "scalar")
|
|
442
|
+
return { kind: "scalar", scalar: r.scalar, base: null, tsType: r.tsType, array: r.array };
|
|
443
|
+
if (r.kind === "identity") {
|
|
444
|
+
return { kind: "scalar-alias", scalar: null, base: r.base, tsType: r.scalarDecl.name, array: r.array };
|
|
445
|
+
}
|
|
446
|
+
if (r.kind === "enum")
|
|
447
|
+
return { kind: "enum", scalar: null, base: null, tsType: r.enumDecl.name, array: r.array };
|
|
448
|
+
return { kind: "declared", scalar: null, base: null, tsType: r.typeDecl.name, array: r.array };
|
|
350
449
|
}
|
|
351
|
-
function surfaceField(f, typesByName, owner) {
|
|
450
|
+
function surfaceField(f, typesByName, scalarsByName, enumsByName, owner) {
|
|
352
451
|
return {
|
|
353
452
|
name: f.name,
|
|
354
|
-
type: surfaceFieldType(f, typesByName, owner),
|
|
453
|
+
type: surfaceFieldType(f, typesByName, scalarsByName, enumsByName, owner),
|
|
355
454
|
optional: f.optional,
|
|
356
455
|
tag: f.tag,
|
|
357
456
|
assigned: f.assigned,
|
|
358
457
|
renamedFrom: f.renamedFrom,
|
|
359
458
|
};
|
|
360
459
|
}
|
|
361
|
-
function surfaceElement(el, typesByName) {
|
|
460
|
+
function surfaceElement(el, typesByName, scalarsByName, enumsByName) {
|
|
362
461
|
return {
|
|
363
462
|
name: el.name,
|
|
364
463
|
slug: kebabSlug(el.name),
|
|
365
|
-
fields: (el.fields ?? []).map((f) => surfaceField(f, typesByName, `${el.kind} "${el.name}"`)),
|
|
464
|
+
fields: (el.fields ?? []).map((f) => surfaceField(f, typesByName, scalarsByName, enumsByName, `${el.kind} "${el.name}"`)),
|
|
366
465
|
invariants: el.invariants ?? null,
|
|
367
466
|
};
|
|
368
467
|
}
|
|
@@ -370,28 +469,38 @@ function surfaceElement(el, typesByName) {
|
|
|
370
469
|
* Build the `em api surface` report for an already-validated (error-free) model: the `public`
|
|
371
470
|
* commands/events/views (`publicSurfaceOf`, same as `em api generate`/`em api check`) plus every
|
|
372
471
|
* declared type reachable from their fields (`reachableTypeRefs`, same walk `em api generate`'s
|
|
373
|
-
* contract body uses),
|
|
374
|
-
*
|
|
375
|
-
* `
|
|
472
|
+
* contract body uses), every declared identity scalar reachable the same way
|
|
473
|
+
* (`reachableScalarRefs`, GH #477), and every declared enum reachable the same way
|
|
474
|
+
* (`reachableEnumRefs`, GH #478), each field's type resolved through `resolvePublicType` — the
|
|
475
|
+
* one notion of the public surface every em surface shares, so a companion tool never re-derives
|
|
476
|
+
* it from `em export --json` and risks drifting from it.
|
|
376
477
|
*/
|
|
377
478
|
export function buildApiSurfaceReport(model, refs, diagnostics, source, file) {
|
|
378
479
|
const { doc } = buildExportDoc(model, refs, diagnostics, source, file);
|
|
379
480
|
const surface = publicSurfaceOf(doc.model.slices);
|
|
380
481
|
const typesByRef = new Map(doc.model.types.map((t) => [t.ref, t]));
|
|
381
482
|
const reachable = reachableTypeRefs([...surface.commands, ...surface.events, ...surface.views], typesByRef);
|
|
483
|
+
const reachableScalars = reachableScalarRefs([...surface.commands, ...surface.events, ...surface.views], typesByRef);
|
|
484
|
+
const reachableEnums = reachableEnumRefs([...surface.commands, ...surface.events, ...surface.views], typesByRef);
|
|
382
485
|
return {
|
|
383
486
|
file,
|
|
384
487
|
modelKey: doc.model.key,
|
|
385
|
-
commands: surface.commands.map((el) => surfaceElement(el, model.typesByName)),
|
|
386
|
-
events: surface.events.map((el) => surfaceElement(el, model.typesByName)),
|
|
387
|
-
views: surface.views.map((el) => surfaceElement(el, model.typesByName)),
|
|
488
|
+
commands: surface.commands.map((el) => surfaceElement(el, model.typesByName, model.scalarsByName, model.enumsByName)),
|
|
489
|
+
events: surface.events.map((el) => surfaceElement(el, model.typesByName, model.scalarsByName, model.enumsByName)),
|
|
490
|
+
views: surface.views.map((el) => surfaceElement(el, model.typesByName, model.scalarsByName, model.enumsByName)),
|
|
388
491
|
types: doc.model.types
|
|
389
492
|
.filter((t) => reachable.has(t.ref))
|
|
390
493
|
.map((t) => ({
|
|
391
494
|
name: t.name,
|
|
392
495
|
slug: kebabSlug(t.name),
|
|
393
|
-
fields: t.fields.map((f) => surfaceField(f, model.typesByName, `type "${t.name}"`)),
|
|
496
|
+
fields: t.fields.map((f) => surfaceField(f, model.typesByName, model.scalarsByName, model.enumsByName, `type "${t.name}"`)),
|
|
394
497
|
})),
|
|
498
|
+
scalars: doc.model.scalars
|
|
499
|
+
.filter((s) => reachableScalars.has(s.ref))
|
|
500
|
+
.map((s) => ({ name: s.name, slug: kebabSlug(s.name), base: s.base })),
|
|
501
|
+
enums: doc.model.enums
|
|
502
|
+
.filter((e) => reachableEnums.has(e.ref))
|
|
503
|
+
.map((e) => ({ name: e.name, slug: kebabSlug(e.name), members: e.members })),
|
|
395
504
|
};
|
|
396
505
|
}
|
|
397
506
|
/**
|
|
@@ -441,7 +550,18 @@ export function formatApiSurfaceText(r) {
|
|
|
441
550
|
section("events", r.events);
|
|
442
551
|
section("views", r.views);
|
|
443
552
|
section("types", r.types);
|
|
444
|
-
if (r.
|
|
553
|
+
if (r.scalars.length > 0) {
|
|
554
|
+
lines.push("scalars:");
|
|
555
|
+
for (const s of r.scalars)
|
|
556
|
+
lines.push(` ${s.slug} extends ${s.base}`);
|
|
557
|
+
}
|
|
558
|
+
if (r.enums.length > 0) {
|
|
559
|
+
lines.push("enums:");
|
|
560
|
+
for (const e of r.enums)
|
|
561
|
+
lines.push(` ${e.slug} { ${e.members.join(", ")} }`);
|
|
562
|
+
}
|
|
563
|
+
if (r.commands.length === 0 && r.events.length === 0 && r.views.length === 0 &&
|
|
564
|
+
r.types.length === 0 && r.scalars.length === 0 && r.enums.length === 0) {
|
|
445
565
|
lines.push("(no public surface)");
|
|
446
566
|
}
|
|
447
567
|
return lines;
|