@substrat-run/contracts 0.66.0 → 0.67.0

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/dist/index.d.ts CHANGED
@@ -34,4 +34,5 @@ export * from './deploy.js';
34
34
  export * from './ci.js';
35
35
  export * from './money.js';
36
36
  export * from './attachments.js';
37
+ export * from './model.js';
37
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -34,4 +34,5 @@ export * from './deploy.js';
34
34
  export * from './ci.js';
35
35
  export * from './money.js';
36
36
  export * from './attachments.js';
37
+ export * from './model.js';
37
38
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * The entity registry (#697).
3
+ *
4
+ * The manifest describes permissions, events, guards, schedules, attachment
5
+ * targets, entity relations, searchables and UI contributions. It does not
6
+ * describe **entities**: `migrations` is a pointer (`journalDir` +
7
+ * `compatibleFrom`), the tables live in raw SQL the manifest never sees, and
8
+ * entity *type names* appear only as bare `z.string().min(1)` fragments across
9
+ * four unrelated, individually optional features.
10
+ *
11
+ * Nothing checks those four against each other or against the tables. A typo'd
12
+ * `parentType` in `entityRelations` parses cleanly and produces an edge that
13
+ * permission never flows along — the tuple evaluator walks a relation that does
14
+ * not exist, and a grant that should reach a child silently does not.
15
+ *
16
+ * This module gives them something to be checked against.
17
+ *
18
+ * ## What it is not
19
+ *
20
+ * Migrations do not move in here, and nothing about how tables are created
21
+ * changes. Whether the model becomes the source that migrations are *derived*
22
+ * from is #680/#685's question; the registry is a prerequisite either way.
23
+ */
24
+ import { z } from 'zod';
25
+ /**
26
+ * One entity: the table it lives in, its field schema, and its place in the
27
+ * permission graph.
28
+ */
29
+ export interface EntityDef<Names extends string = string> {
30
+ /** The physical table. Owned by this module — never another's (rule 4). */
31
+ readonly table: string;
32
+ /** The row shape. Field names are what `key`, `searchables` and events check against. */
33
+ readonly fields: z.ZodObject<z.ZodRawShape>;
34
+ /**
35
+ * The parent entity permission flows along (design doc §4.2 rule 3).
36
+ * Checked against the declared entities — a typo is a compile error, where
37
+ * today it is a silently dead edge.
38
+ */
39
+ readonly parent?: Names;
40
+ /** Natural key, if any. Must name fields that exist. */
41
+ readonly key?: readonly string[];
42
+ /** Fields an erasure must be able to reach (§12). Must name fields that exist. */
43
+ readonly erasable?: readonly string[];
44
+ }
45
+ /** The field names of one entity, read off its own `fields` schema. */
46
+ export type EntityFields<E> = E extends {
47
+ fields: infer F;
48
+ } ? F extends z.ZodObject<z.ZodRawShape> ? keyof z.infer<F> & string : never : never;
49
+ /**
50
+ * Declare a module's entities.
51
+ *
52
+ * The constraint is self-referential — `parent` is checked against the map's own
53
+ * keys, and `key`/`erasable` against each entity's own fields — which is what
54
+ * makes the checks bite per-entity rather than as a union across all of them.
55
+ * Written the obvious way (an erased supertype) every one of them compiles clean
56
+ * and enforces nothing; see `test/model.test.ts`, which exists to prove they
57
+ * still bite.
58
+ */
59
+ export declare function defineEntities<T extends {
60
+ readonly [K in keyof T]: EntityDef<keyof T & string> & {
61
+ key?: readonly EntityFields<T[K]>[];
62
+ erasable?: readonly EntityFields<T[K]>[];
63
+ };
64
+ }>(entities: T): T;
65
+ /** The declared entity names. */
66
+ export type EntityName<T> = keyof T & string;
67
+ /**
68
+ * The serialisable form — the artifact of record.
69
+ *
70
+ * Everything downstream (migrations, the manifest, the route table, an ER
71
+ * diagram, a diff classifier) reads THIS, never the TypeScript. That is what
72
+ * keeps the authoring notation swappable: a different authoring layer is a new
73
+ * emitter writing the same JSON, and nothing downstream notices.
74
+ *
75
+ * Field schemas are rendered with `z.toJSONSchema`, the same conversion the
76
+ * OpenAPI builder already uses — so there is no second schema language anywhere
77
+ * in the pipeline.
78
+ */
79
+ export interface EmittedEntity {
80
+ readonly table: string;
81
+ readonly fields: Record<string, unknown>;
82
+ readonly parent?: string;
83
+ readonly key?: readonly string[];
84
+ readonly erasable?: readonly string[];
85
+ }
86
+ export interface EmittedModel {
87
+ readonly entities: Record<string, EmittedEntity>;
88
+ }
89
+ /**
90
+ * Render the registry to plain JSON. Deterministic: entities and their fields
91
+ * are emitted in sorted order, so the checked-in artifact diffs cleanly and a
92
+ * reordered declaration is not a spurious change.
93
+ */
94
+ export declare function emitModel<T extends Record<string, EntityDef>>(entities: T): EmittedModel;
95
+ /**
96
+ * `entityRelations` derived from the `parent` declarations, rather than written
97
+ * a second time by hand.
98
+ *
99
+ * Two descriptions of one fact is how they come to disagree — and the disagreement
100
+ * here is invisible, because a relation naming an entity that does not exist is a
101
+ * permission edge that silently never resolves.
102
+ */
103
+ export declare function entityRelationsOf<T extends Record<string, EntityDef>>(entities: T): {
104
+ entityType: string;
105
+ parentType: string;
106
+ }[];
107
+ /**
108
+ * The entity-referencing half of a manifest, narrowed to declared entities.
109
+ *
110
+ * Entity-name positions are written `keyof T & string` inline rather than as
111
+ * `EntityName<T>`. A type ALIAS is printed unresolved in diagnostics — the error
112
+ * names the alias and inlines the whole entity map — where the inline form lists
113
+ * the actual names:
114
+ *
115
+ * Type '"bkie"' is not assignable to type '"bike" | "customer"'.
116
+ */
117
+ type EntityRefs<T extends Record<string, EntityDef>, M> = {
118
+ readonly attachmentTargets?: readonly {
119
+ readonly entityType: keyof T & string;
120
+ readonly readPermission: string;
121
+ readonly writePermission?: string;
122
+ }[];
123
+ /**
124
+ * `fields` is checked against the NAMED entity's own fields — the only place
125
+ * in the manifest today where a field name appears at all, and nothing
126
+ * checked it.
127
+ */
128
+ readonly searchables?: M extends {
129
+ searchables: infer S;
130
+ } ? {
131
+ readonly [I in keyof S]: S[I] extends {
132
+ entityType: infer N;
133
+ } ? N extends keyof T & string ? {
134
+ readonly entityType: N;
135
+ readonly fields: readonly EntityFields<T[N]>[];
136
+ } : never : never;
137
+ } : never;
138
+ readonly entityViews?: readonly {
139
+ readonly entityType: keyof T & string;
140
+ readonly view: string;
141
+ }[];
142
+ /**
143
+ * Parent edges whose CHILD this module does not own.
144
+ *
145
+ * A vertical legitimately declares these: an engine is entity-agnostic, so
146
+ * only the vertical knows that a work order hangs off a bike, or a protocol
147
+ * off a work order. The child name belongs to an engine and cannot be checked
148
+ * here — but the PARENT often can be, and throwing that away would be giving
149
+ * up a check we hold.
150
+ *
151
+ * So the parent is checked whenever it is one of this module's own entities:
152
+ *
153
+ * ```ts
154
+ * foreignChildren: [
155
+ * { entityType: 'workorder', parentType: 'bike' }, // parent CHECKED
156
+ * { entityType: 'protocol', parentType: 'workorder' }, // parent foreign too
157
+ * ]
158
+ * ```
159
+ *
160
+ * `parentType` accepts a declared entity name or an arbitrary string, and a
161
+ * typo that happens to look like neither is still accepted — TypeScript cannot
162
+ * express "one of these, or any other string, but tell me which". What it does
163
+ * buy is autocomplete on the local names and a compile error on the mixed edge
164
+ * once `foreignParents` (below) is used instead.
165
+ *
166
+ * Both become fully checkable when engines export their entity-type constants
167
+ * (#696 item 3), at which point these two fields collapse back into `parent`.
168
+ */
169
+ readonly foreignChildren?: readonly {
170
+ readonly entityType: string;
171
+ readonly parentType: (keyof T & string) | (string & {});
172
+ }[];
173
+ /**
174
+ * The mixed edge, stated so the checkable half IS checked: a child this module
175
+ * does not own, hanging off a parent it does. `parentType` here is strictly a
176
+ * declared entity — a typo is a compile error.
177
+ *
178
+ * `bike_shop`'s `workorder → bike` is the case: `workorder` is the engine's,
179
+ * `bike` is the vertical's, and the vertical is the only place that knows the
180
+ * edge exists.
181
+ */
182
+ readonly foreignChildOf?: readonly {
183
+ readonly entityType: string;
184
+ readonly parentType: keyof T & string;
185
+ }[];
186
+ };
187
+ /**
188
+ * Compose the entity-referencing manifest fragments against the registry.
189
+ *
190
+ * `entityRelations` is absent by design: it is DERIVED from the entities'
191
+ * `parent` declarations (`entityRelationsOf`) rather than written a second time.
192
+ *
193
+ * Spread the result into the module's manifest:
194
+ *
195
+ * ```ts
196
+ * export const manifest = moduleManifest.parse({
197
+ * id: '@acme/vertical',
198
+ * …,
199
+ * ...manifestEntities(entities, {
200
+ * attachmentTargets: [{ entityType: 'contract', readPermission: 'x:read' }],
201
+ * searchables: [{ entityType: 'customer', fields: ['name'] }],
202
+ * }),
203
+ * });
204
+ * ```
205
+ *
206
+ * A typo in any `entityType` is now a compile error naming the declared
207
+ * entities, and a `searchables` field that the entity does not have is too.
208
+ */
209
+ export declare function manifestEntities<const T extends Record<string, EntityDef>, const M extends EntityRefs<T, M>>(entities: T, refs: M): {
210
+ attachmentTargets: NonNullable<M['attachmentTargets']> | [];
211
+ searchables: M['searchables'];
212
+ entityRelations: {
213
+ entityType: string;
214
+ parentType: string;
215
+ }[];
216
+ ui: {
217
+ entityViews: M['entityViews'];
218
+ };
219
+ };
220
+ export {};
221
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
package/dist/model.js ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The entity registry (#697).
3
+ *
4
+ * The manifest describes permissions, events, guards, schedules, attachment
5
+ * targets, entity relations, searchables and UI contributions. It does not
6
+ * describe **entities**: `migrations` is a pointer (`journalDir` +
7
+ * `compatibleFrom`), the tables live in raw SQL the manifest never sees, and
8
+ * entity *type names* appear only as bare `z.string().min(1)` fragments across
9
+ * four unrelated, individually optional features.
10
+ *
11
+ * Nothing checks those four against each other or against the tables. A typo'd
12
+ * `parentType` in `entityRelations` parses cleanly and produces an edge that
13
+ * permission never flows along — the tuple evaluator walks a relation that does
14
+ * not exist, and a grant that should reach a child silently does not.
15
+ *
16
+ * This module gives them something to be checked against.
17
+ *
18
+ * ## What it is not
19
+ *
20
+ * Migrations do not move in here, and nothing about how tables are created
21
+ * changes. Whether the model becomes the source that migrations are *derived*
22
+ * from is #680/#685's question; the registry is a prerequisite either way.
23
+ */
24
+ import { z } from 'zod';
25
+ /**
26
+ * Declare a module's entities.
27
+ *
28
+ * The constraint is self-referential — `parent` is checked against the map's own
29
+ * keys, and `key`/`erasable` against each entity's own fields — which is what
30
+ * makes the checks bite per-entity rather than as a union across all of them.
31
+ * Written the obvious way (an erased supertype) every one of them compiles clean
32
+ * and enforces nothing; see `test/model.test.ts`, which exists to prove they
33
+ * still bite.
34
+ */
35
+ export function defineEntities(entities) {
36
+ return entities;
37
+ }
38
+ /**
39
+ * Render the registry to plain JSON. Deterministic: entities and their fields
40
+ * are emitted in sorted order, so the checked-in artifact diffs cleanly and a
41
+ * reordered declaration is not a spurious change.
42
+ */
43
+ export function emitModel(entities) {
44
+ const out = {};
45
+ for (const name of Object.keys(entities).sort()) {
46
+ const e = entities[name];
47
+ if (!e)
48
+ continue;
49
+ const { $schema: _drop, ...fields } = z.toJSONSchema(e.fields, { io: 'output', target: 'draft-2020-12' });
50
+ out[name] = {
51
+ table: e.table,
52
+ fields,
53
+ ...(e.parent ? { parent: e.parent } : {}),
54
+ ...(e.key ? { key: [...e.key].sort() } : {}),
55
+ ...(e.erasable ? { erasable: [...e.erasable].sort() } : {}),
56
+ };
57
+ }
58
+ return { entities: out };
59
+ }
60
+ /**
61
+ * `entityRelations` derived from the `parent` declarations, rather than written
62
+ * a second time by hand.
63
+ *
64
+ * Two descriptions of one fact is how they come to disagree — and the disagreement
65
+ * here is invisible, because a relation naming an entity that does not exist is a
66
+ * permission edge that silently never resolves.
67
+ */
68
+ export function entityRelationsOf(entities) {
69
+ return Object.keys(entities)
70
+ .sort()
71
+ .flatMap((name) => {
72
+ const parent = entities[name]?.parent;
73
+ return parent ? [{ entityType: name, parentType: parent }] : [];
74
+ });
75
+ }
76
+ /**
77
+ * Compose the entity-referencing manifest fragments against the registry.
78
+ *
79
+ * `entityRelations` is absent by design: it is DERIVED from the entities'
80
+ * `parent` declarations (`entityRelationsOf`) rather than written a second time.
81
+ *
82
+ * Spread the result into the module's manifest:
83
+ *
84
+ * ```ts
85
+ * export const manifest = moduleManifest.parse({
86
+ * id: '@acme/vertical',
87
+ * …,
88
+ * ...manifestEntities(entities, {
89
+ * attachmentTargets: [{ entityType: 'contract', readPermission: 'x:read' }],
90
+ * searchables: [{ entityType: 'customer', fields: ['name'] }],
91
+ * }),
92
+ * });
93
+ * ```
94
+ *
95
+ * A typo in any `entityType` is now a compile error naming the declared
96
+ * entities, and a `searchables` field that the entity does not have is too.
97
+ */
98
+ export function manifestEntities(entities, refs) {
99
+ return {
100
+ attachmentTargets: (refs.attachmentTargets ?? []),
101
+ searchables: refs.searchables,
102
+ // Derived edges first, then the ones this module cannot check.
103
+ entityRelations: [
104
+ ...entityRelationsOf(entities),
105
+ // Checked parent, foreign child — the mixed edge.
106
+ ...(refs.foreignChildOf ?? []),
107
+ // Neither side checkable here.
108
+ ...(refs.foreignChildren ?? []),
109
+ ],
110
+ ui: { entityViews: refs.entityViews },
111
+ };
112
+ }
113
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA8BxB;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAO5B,QAAW;IACX,OAAO,QAAQ,CAAC;AAClB,CAAC;AA6BD;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAsC,QAAW;IACxE,MAAM,GAAG,GAAkC,EAAE,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,CAGvG,CAAC;QACF,GAAG,CAAC,IAAI,CAAC,GAAG;YACV,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,MAAM;YACN,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5D,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAW;IAEX,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;SACzB,IAAI,EAAE;SACN,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAChB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACtC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,CAAC,CAAC,CAAC;AACP,CAAC;AAsFD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,gBAAgB,CAI9B,QAAW,EACX,IAAO;IAOP,OAAO;QACL,iBAAiB,EAAE,CAAC,IAAI,CAAC,iBAAiB,IAAI,EAAE,CAA6C;QAC7F,WAAW,EAAE,IAAI,CAAC,WAA+B;QACjD,+DAA+D;QAC/D,eAAe,EAAE;YACf,GAAG,iBAAiB,CAAC,QAAQ,CAAC;YAC9B,kDAAkD;YAClD,GAAG,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC;YAC9B,+BAA+B;YAC/B,GAAG,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC;SAChC;QACD,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAA+B,EAAE;KAC1D,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@substrat-run/contracts",
3
- "version": "0.66.0",
3
+ "version": "0.67.0",
4
4
  "description": "Substrat kernel contract schemas — Zod is the source of truth (master plan D-22); OAS/JSON Schema are emitted artifacts",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -25,13 +25,15 @@
25
25
  "zod": "^4.4.3"
26
26
  },
27
27
  "devDependencies": {
28
- "typescript": "^7.0.0"
28
+ "typescript": "^7.0.0",
29
+ "vitest": "^3.0.0"
29
30
  },
30
31
  "publishConfig": {
31
32
  "access": "public"
32
33
  },
33
34
  "scripts": {
34
35
  "build": "tsc -p tsconfig.json",
35
- "typecheck": "tsc -p tsconfig.json --noEmit"
36
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
37
+ "test": "vitest run"
36
38
  }
37
39
  }