@substrat-run/contracts 0.66.0 → 0.68.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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +231 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +111 -0
- package/dist/model.js.map +1 -0
- package/dist/operations.d.ts +223 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +81 -0
- package/dist/operations.js.map +1 -0
- package/package.json +5 -3
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -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;AAC3B,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
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;AAC3B,cAAc,iBAAiB,CAAC"}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
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 types permission may flow along (design doc §4.2 rule 3).
|
|
36
|
+
* Checked against the declared entities — a typo is a compile error, where it
|
|
37
|
+
* used to be a silently dead edge.
|
|
38
|
+
*
|
|
39
|
+
* **Plural, and an array even for one.** `entityRelations` is an ALLOWLIST,
|
|
40
|
+
* not an assertion: the kernel accumulates permitted parents into a *set* per
|
|
41
|
+
* entity type and `ctx.link` checks membership. `reservation` already hangs
|
|
42
|
+
* off both `resource` and `member`; `protocol` off both `workorder` and
|
|
43
|
+
* `employee`. Singular `parent` said "the parent", which is not what the
|
|
44
|
+
* kernel means and cannot express the real cases.
|
|
45
|
+
*/
|
|
46
|
+
readonly parents?: readonly Names[];
|
|
47
|
+
/** Natural key, if any. Must name fields that exist. */
|
|
48
|
+
readonly key?: readonly string[];
|
|
49
|
+
/** Fields an erasure must be able to reach (§12). Must name fields that exist. */
|
|
50
|
+
readonly erasable?: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
/** The field names of one entity, read off its own `fields` schema. */
|
|
53
|
+
export type EntityFields<E> = E extends {
|
|
54
|
+
fields: infer F;
|
|
55
|
+
} ? F extends z.ZodObject<z.ZodRawShape> ? keyof z.infer<F> & string : never : never;
|
|
56
|
+
/**
|
|
57
|
+
* Declare a module's entities.
|
|
58
|
+
*
|
|
59
|
+
* The constraint is self-referential — `parent` is checked against the map's own
|
|
60
|
+
* keys, and `key`/`erasable` against each entity's own fields — which is what
|
|
61
|
+
* makes the checks bite per-entity rather than as a union across all of them.
|
|
62
|
+
* Written the obvious way (an erased supertype) every one of them compiles clean
|
|
63
|
+
* and enforces nothing; see `test/model.test.ts`, which exists to prove they
|
|
64
|
+
* still bite.
|
|
65
|
+
*/
|
|
66
|
+
export declare function defineEntities<T extends {
|
|
67
|
+
readonly [K in keyof T]: EntityDef<keyof T & string> & {
|
|
68
|
+
key?: readonly EntityFields<T[K]>[];
|
|
69
|
+
erasable?: readonly EntityFields<T[K]>[];
|
|
70
|
+
};
|
|
71
|
+
}>(entities: T): T;
|
|
72
|
+
/** The declared entity names. */
|
|
73
|
+
export type EntityName<T> = keyof T & string;
|
|
74
|
+
/**
|
|
75
|
+
* The serialisable form — the artifact of record.
|
|
76
|
+
*
|
|
77
|
+
* Everything downstream (migrations, the manifest, the route table, an ER
|
|
78
|
+
* diagram, a diff classifier) reads THIS, never the TypeScript. That is what
|
|
79
|
+
* keeps the authoring notation swappable: a different authoring layer is a new
|
|
80
|
+
* emitter writing the same JSON, and nothing downstream notices.
|
|
81
|
+
*
|
|
82
|
+
* Field schemas are rendered with `z.toJSONSchema`, the same conversion the
|
|
83
|
+
* OpenAPI builder already uses — so there is no second schema language anywhere
|
|
84
|
+
* in the pipeline.
|
|
85
|
+
*/
|
|
86
|
+
export interface EmittedEntity {
|
|
87
|
+
readonly table: string;
|
|
88
|
+
readonly fields: Record<string, unknown>;
|
|
89
|
+
/** The permitted parent types, sorted. One shape, always. */
|
|
90
|
+
readonly parents?: readonly string[];
|
|
91
|
+
readonly key?: readonly string[];
|
|
92
|
+
readonly erasable?: readonly string[];
|
|
93
|
+
}
|
|
94
|
+
export interface EmittedModel {
|
|
95
|
+
readonly entities: Record<string, EmittedEntity>;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Render the registry to plain JSON. Deterministic: entities and their fields
|
|
99
|
+
* are emitted in sorted order, so the checked-in artifact diffs cleanly and a
|
|
100
|
+
* reordered declaration is not a spurious change.
|
|
101
|
+
*/
|
|
102
|
+
export declare function emitModel<T extends Record<string, EntityDef>>(entities: T): EmittedModel;
|
|
103
|
+
/**
|
|
104
|
+
* `entityRelations` derived from the `parent` declarations, rather than written
|
|
105
|
+
* a second time by hand.
|
|
106
|
+
*
|
|
107
|
+
* Two descriptions of one fact is how they come to disagree — and the disagreement
|
|
108
|
+
* here is invisible, because a relation naming an entity that does not exist is a
|
|
109
|
+
* permission edge that silently never resolves.
|
|
110
|
+
*/
|
|
111
|
+
export declare function entityRelationsOf<T extends Record<string, EntityDef>>(entities: T): {
|
|
112
|
+
entityType: string;
|
|
113
|
+
parentType: string;
|
|
114
|
+
}[];
|
|
115
|
+
/**
|
|
116
|
+
* The entity-referencing half of a manifest, narrowed to declared entities.
|
|
117
|
+
*
|
|
118
|
+
* Entity-name positions are written `keyof T & string` inline rather than as
|
|
119
|
+
* `EntityName<T>`. A type ALIAS is printed unresolved in diagnostics — the error
|
|
120
|
+
* names the alias and inlines the whole entity map — where the inline form lists
|
|
121
|
+
* the actual names:
|
|
122
|
+
*
|
|
123
|
+
* Type '"bkie"' is not assignable to type '"bike" | "customer"'.
|
|
124
|
+
*/
|
|
125
|
+
type EntityRefs<T extends Record<string, EntityDef>, M> = {
|
|
126
|
+
readonly attachmentTargets?: readonly {
|
|
127
|
+
readonly entityType: keyof T & string;
|
|
128
|
+
readonly readPermission: string;
|
|
129
|
+
readonly writePermission?: string;
|
|
130
|
+
}[];
|
|
131
|
+
/**
|
|
132
|
+
* `fields` is checked against the NAMED entity's own fields — the only place
|
|
133
|
+
* in the manifest today where a field name appears at all, and nothing
|
|
134
|
+
* checked it.
|
|
135
|
+
*/
|
|
136
|
+
readonly searchables?: M extends {
|
|
137
|
+
searchables: infer S;
|
|
138
|
+
} ? {
|
|
139
|
+
readonly [I in keyof S]: S[I] extends {
|
|
140
|
+
entityType: infer N;
|
|
141
|
+
} ? N extends keyof T & string ? {
|
|
142
|
+
readonly entityType: N;
|
|
143
|
+
readonly fields: readonly EntityFields<T[N]>[];
|
|
144
|
+
} : never : never;
|
|
145
|
+
} : never;
|
|
146
|
+
readonly entityViews?: readonly {
|
|
147
|
+
readonly entityType: keyof T & string;
|
|
148
|
+
readonly view: string;
|
|
149
|
+
}[];
|
|
150
|
+
/**
|
|
151
|
+
* The engine registries this module composes, so relation edges naming their
|
|
152
|
+
* entities can be checked.
|
|
153
|
+
*/
|
|
154
|
+
readonly engines?: readonly Record<string, EntityDef>[];
|
|
155
|
+
/**
|
|
156
|
+
* Parent edges involving an entity this module does not own.
|
|
157
|
+
*
|
|
158
|
+
* A vertical legitimately declares these: an engine is entity-agnostic, so
|
|
159
|
+
* only the vertical knows that a work order hangs off a bike, or a protocol
|
|
160
|
+
* off a work order. **Both sides are checked** against the local entities plus
|
|
161
|
+
* every entity of every registry in `engines`.
|
|
162
|
+
*
|
|
163
|
+
* Local-to-local edges do not belong here — they are DERIVED from the
|
|
164
|
+
* entities' own `parents`, and declaring one twice is how two descriptions of
|
|
165
|
+
* a fact come to disagree.
|
|
166
|
+
*
|
|
167
|
+
* This replaces the `foreignChildOf` / `foreignChildren` pair, which existed
|
|
168
|
+
* only because foreign names were uncheckable. They are now, so the split has
|
|
169
|
+
* nothing left to say.
|
|
170
|
+
*/
|
|
171
|
+
readonly relations?: readonly {
|
|
172
|
+
readonly entityType: (keyof T & string) | (M extends {
|
|
173
|
+
engines: readonly (infer R)[];
|
|
174
|
+
} ? NamesOf<R> : never);
|
|
175
|
+
readonly parentType: (keyof T & string) | (M extends {
|
|
176
|
+
engines: readonly (infer R)[];
|
|
177
|
+
} ? NamesOf<R> : never);
|
|
178
|
+
}[];
|
|
179
|
+
};
|
|
180
|
+
/** Every entity name in one registry. */
|
|
181
|
+
type NamesOf<R> = R extends Record<string, EntityDef> ? keyof R & string : never;
|
|
182
|
+
/**
|
|
183
|
+
* Compose the entity-referencing manifest fragments against the registry.
|
|
184
|
+
*
|
|
185
|
+
* `entityRelations` is absent by design: it is DERIVED from the entities'
|
|
186
|
+
* `parent` declarations (`entityRelationsOf`) rather than written a second time.
|
|
187
|
+
*
|
|
188
|
+
* Spread the result into the module's manifest:
|
|
189
|
+
*
|
|
190
|
+
* ```ts
|
|
191
|
+
* export const manifest = moduleManifest.parse({
|
|
192
|
+
* id: '@acme/vertical',
|
|
193
|
+
* …,
|
|
194
|
+
* ...manifestEntities(entities, {
|
|
195
|
+
* attachmentTargets: [{ entityType: 'contract', readPermission: 'x:read' }],
|
|
196
|
+
* searchables: [{ entityType: 'customer', fields: ['name'] }],
|
|
197
|
+
* }),
|
|
198
|
+
* });
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* A typo in any `entityType` is now a compile error naming the declared
|
|
202
|
+
* entities, and a `searchables` field that the entity does not have is too.
|
|
203
|
+
*/
|
|
204
|
+
export declare function manifestEntities<const T extends Record<string, EntityDef>, const M extends EntityRefs<T, M>>(entities: T, refs: M): {
|
|
205
|
+
attachmentTargets: NonNullable<M['attachmentTargets']> | [];
|
|
206
|
+
searchables: M['searchables'];
|
|
207
|
+
entityRelations: {
|
|
208
|
+
entityType: string;
|
|
209
|
+
parentType: string;
|
|
210
|
+
}[];
|
|
211
|
+
ui: {
|
|
212
|
+
entityViews: M['entityViews'];
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* The row type of a declared entity — what `ctx.sql.query` returns for it.
|
|
217
|
+
*
|
|
218
|
+
* `ctx.sql.query` leaves `T` to the vertical, so every handler writes its own
|
|
219
|
+
* row interface and the schema ends up described three times: the DDL, the
|
|
220
|
+
* registry, and a hand-written `interface CustomerRow`. This collapses the
|
|
221
|
+
* third into the second.
|
|
222
|
+
*
|
|
223
|
+
* ```ts
|
|
224
|
+
* export type CustomerRow = EntityRow<typeof calloutEntities, 'customer'>;
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
export type EntityRow<T extends Record<string, EntityDef>, K extends keyof T> = T[K] extends {
|
|
228
|
+
fields: infer F;
|
|
229
|
+
} ? F extends z.ZodObject<z.ZodRawShape> ? z.infer<F> : never : never;
|
|
230
|
+
export {};
|
|
231
|
+
//# 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,111 @@
|
|
|
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.parents?.length ? { parents: [...e.parents].sort() } : {}),
|
|
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 parents = entities[name]?.parents;
|
|
73
|
+
if (!parents?.length)
|
|
74
|
+
return [];
|
|
75
|
+
return [...parents].sort().map((parentType) => ({ entityType: name, parentType }));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Compose the entity-referencing manifest fragments against the registry.
|
|
80
|
+
*
|
|
81
|
+
* `entityRelations` is absent by design: it is DERIVED from the entities'
|
|
82
|
+
* `parent` declarations (`entityRelationsOf`) rather than written a second time.
|
|
83
|
+
*
|
|
84
|
+
* Spread the result into the module's manifest:
|
|
85
|
+
*
|
|
86
|
+
* ```ts
|
|
87
|
+
* export const manifest = moduleManifest.parse({
|
|
88
|
+
* id: '@acme/vertical',
|
|
89
|
+
* …,
|
|
90
|
+
* ...manifestEntities(entities, {
|
|
91
|
+
* attachmentTargets: [{ entityType: 'contract', readPermission: 'x:read' }],
|
|
92
|
+
* searchables: [{ entityType: 'customer', fields: ['name'] }],
|
|
93
|
+
* }),
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* A typo in any `entityType` is now a compile error naming the declared
|
|
98
|
+
* entities, and a `searchables` field that the entity does not have is too.
|
|
99
|
+
*/
|
|
100
|
+
export function manifestEntities(entities, refs) {
|
|
101
|
+
return {
|
|
102
|
+
attachmentTargets: (refs.attachmentTargets ?? []),
|
|
103
|
+
searchables: refs.searchables,
|
|
104
|
+
// Derived edges first, then the ones this module cannot check.
|
|
105
|
+
// Local edges are derived from the entities' own `parents`; edges involving
|
|
106
|
+
// a composed engine's entity are declared, and both sides are checked.
|
|
107
|
+
entityRelations: [...entityRelationsOf(entities), ...(refs.relations ?? [])],
|
|
108
|
+
ui: { entityViews: refs.entityViews },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# 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;AAqCxB;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAO5B,QAAW;IACX,OAAO,QAAQ,CAAC;AAClB,CAAC;AA8BD;;;;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,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,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,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,MAAM;YAAE,OAAO,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;AACP,CAAC;AAuED;;;;;;;;;;;;;;;;;;;;;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,4EAA4E;QAC5E,uEAAuE;QACvE,eAAe,EAAE,CAAC,GAAG,iBAAiB,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QAC5E,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAA+B,EAAE;KAC1D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operation surface of the model (#707).
|
|
3
|
+
*
|
|
4
|
+
* #697 declared the entities. This declares what can be *done* to them, and
|
|
5
|
+
* checks the joins that today are unchecked strings: which permission an
|
|
6
|
+
* operation requires, which output field an event takes its subject from,
|
|
7
|
+
* whether a payload carries something an erasure must be able to reach.
|
|
8
|
+
*
|
|
9
|
+
* ## A composer, not a second `defineModel`
|
|
10
|
+
*
|
|
11
|
+
* `defineOperations` sits beside `defineEntities` rather than swallowing it.
|
|
12
|
+
* Each half stays independently adoptable — which is what let the entity half
|
|
13
|
+
* ship and be taken up by two verticals before this existed. A vertical adopts
|
|
14
|
+
* operations when it is ready, not as the price of adopting entities.
|
|
15
|
+
*
|
|
16
|
+
* ## `input`, and the transcription that is not here
|
|
17
|
+
*
|
|
18
|
+
* `input` is the Zod object the handler already parses — the same object, not a
|
|
19
|
+
* description of it. That is the whole reason the model is TypeScript (#680): a
|
|
20
|
+
* schema language would need the shape written twice, and transcription is what
|
|
21
|
+
* produced 40 wrong argument names in the one app where this was measured.
|
|
22
|
+
*/
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
import type { EntityDef } from './model.js';
|
|
25
|
+
/** `{var}` names in a literal path. */
|
|
26
|
+
type PathParams<S extends string> = S extends `${string}{${infer P}}${infer Rest}` ? P | PathParams<Rest> : never;
|
|
27
|
+
type InputKeys<O> = O extends {
|
|
28
|
+
input: infer I;
|
|
29
|
+
} ? (I extends z.ZodType ? keyof z.infer<I> & string : never) : never;
|
|
30
|
+
type OutputKeys<O> = O extends {
|
|
31
|
+
output: infer R;
|
|
32
|
+
} ? (R extends z.ZodType ? keyof z.infer<R> & string : never) : never;
|
|
33
|
+
/** Every `{var}` must name an input field, or the path type collapses. */
|
|
34
|
+
type CheckedPath<O> = O extends {
|
|
35
|
+
http: {
|
|
36
|
+
path: infer P;
|
|
37
|
+
};
|
|
38
|
+
} ? P extends string ? [PathParams<P>] extends [InputKeys<O>] ? P : never : never : string;
|
|
39
|
+
/**
|
|
40
|
+
* The erasable fields OF THE ENTITY THIS EVENT IS ABOUT.
|
|
41
|
+
*
|
|
42
|
+
* Resolving through `emits.entity` rather than matching field names across all
|
|
43
|
+
* entities is what makes the §12 check exact. A `contactPerson.email` marked
|
|
44
|
+
* erasable must not stop an event about an `office` carrying its own `email` —
|
|
45
|
+
* a rule that refuses correct code trains people to route around it, which is
|
|
46
|
+
* how a PII rule stops being obeyed.
|
|
47
|
+
*/
|
|
48
|
+
type ErasableOf<Entities, O> = O extends {
|
|
49
|
+
emits: {
|
|
50
|
+
entity: infer N;
|
|
51
|
+
};
|
|
52
|
+
} ? N extends keyof Entities ? Entities[N] extends {
|
|
53
|
+
erasable: readonly (infer F)[];
|
|
54
|
+
} ? F & string : never : never : never;
|
|
55
|
+
/**
|
|
56
|
+
* The platform's own event invariant, moved from runtime to compile time.
|
|
57
|
+
* `contracts/events.ts` enforces it with a `superRefine`: *"subjectId is
|
|
58
|
+
* required when piiClass is 'direct' — crypto-shredding must be able to key the
|
|
59
|
+
* erasure"*. Classification is mandatory here for the same reason it is there:
|
|
60
|
+
* an unclassified event type cannot be declared.
|
|
61
|
+
*/
|
|
62
|
+
type PiiShape<O, OutKeys extends string> = O extends {
|
|
63
|
+
emits: {
|
|
64
|
+
piiClass: 'none';
|
|
65
|
+
};
|
|
66
|
+
} ? {
|
|
67
|
+
readonly piiClass: 'none';
|
|
68
|
+
readonly subjectId?: never;
|
|
69
|
+
} : {
|
|
70
|
+
readonly piiClass: 'pseudonymous' | 'direct';
|
|
71
|
+
readonly subjectId: OutKeys;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* An operation carries a leading `permission` OR `narrows` with a reason, never
|
|
75
|
+
* both and never neither (rule 5 / CRM-EFF's check 14). `narrows` is the
|
|
76
|
+
* per-row proof walk: a salesperson listing their own customers must get their
|
|
77
|
+
* list, not a denial.
|
|
78
|
+
*/
|
|
79
|
+
type OpAuthority<O, PermKey extends string> = O extends {
|
|
80
|
+
narrows: unknown;
|
|
81
|
+
} ? {
|
|
82
|
+
readonly narrows: {
|
|
83
|
+
readonly reason: string;
|
|
84
|
+
};
|
|
85
|
+
readonly permission?: never;
|
|
86
|
+
} : {
|
|
87
|
+
readonly permission: PermKey;
|
|
88
|
+
readonly narrows?: never;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The per-operation constraint, self-referential in `O`.
|
|
92
|
+
*
|
|
93
|
+
* Each operation is checked against ITS OWN declared input and output rather
|
|
94
|
+
* than an erased supertype. Written the obvious way every check below compiles
|
|
95
|
+
* clean and enforces nothing — see `test/operations.test.ts`, which exists to
|
|
96
|
+
* prove they still bite.
|
|
97
|
+
*/
|
|
98
|
+
type OperationShape<O, Entities, PermKey extends string> = {
|
|
99
|
+
/** One line, imperative — what invoking this does. Feeds the API document. */
|
|
100
|
+
readonly summary: string;
|
|
101
|
+
/**
|
|
102
|
+
* The request body — the SAME Zod object the handler parses.
|
|
103
|
+
*
|
|
104
|
+
* **Omitted means no body at all**, and the handler then takes `undefined`.
|
|
105
|
+
* Found by the first adopter: three of Callout's six operations take no input,
|
|
106
|
+
* and a required `z.object({})` cannot say so — a handler accepting only
|
|
107
|
+
* `undefined` is not assignable to one accepting `{}`.
|
|
108
|
+
*
|
|
109
|
+
* This mirrors `ApiOperationDoc.input` ("Omit = no body") rather than
|
|
110
|
+
* inventing a second vocabulary for the same fact.
|
|
111
|
+
*/
|
|
112
|
+
readonly input?: z.ZodObject<z.ZodRawShape>;
|
|
113
|
+
/** True when the handler accepts a body but also accepts none (filter-style reads). */
|
|
114
|
+
readonly inputOptional?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Declared, not inferred (#695 Ask 2). Inference documents accidents: one
|
|
117
|
+
* inferred return carried `contacts?: undefined`, an artefact of an early
|
|
118
|
+
* return, which generation would have cemented into the published API.
|
|
119
|
+
*
|
|
120
|
+
* Declare a return where a caller branches on it — a UI lane is a caller that
|
|
121
|
+
* branches, which is why #682/#683 depend on this.
|
|
122
|
+
*/
|
|
123
|
+
readonly output: z.ZodType;
|
|
124
|
+
readonly http?: {
|
|
125
|
+
readonly method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
126
|
+
readonly path: CheckedPath<O>;
|
|
127
|
+
};
|
|
128
|
+
readonly emits?: {
|
|
129
|
+
/** The entity the event is about — a declared entity. */
|
|
130
|
+
readonly entity: keyof Entities & string;
|
|
131
|
+
/**
|
|
132
|
+
* Which OUTPUT field carries that entity's id.
|
|
133
|
+
*
|
|
134
|
+
* The #695 defect: 18 operations emitted `entityId: String(result.id)` on
|
|
135
|
+
* objects that answer with `contractId` / `runId` / `instanceId`. For a
|
|
136
|
+
* mutation writing a child the event is about the PARENT, so the two differ
|
|
137
|
+
* and nothing downstream would ever have flagged it.
|
|
138
|
+
*/
|
|
139
|
+
readonly entityIdFrom: OutputKeys<O>;
|
|
140
|
+
readonly type: string;
|
|
141
|
+
readonly schemaVersion: number;
|
|
142
|
+
/**
|
|
143
|
+
* Fat payload, drawn from the output — minus anything the entity marks
|
|
144
|
+
* `erasable`. Immutable events are the one place in a scope an erasure
|
|
145
|
+
* cannot reach.
|
|
146
|
+
*/
|
|
147
|
+
readonly payload?: readonly Exclude<OutputKeys<O>, ErasableOf<Entities, O>>[];
|
|
148
|
+
} & PiiShape<O, OutputKeys<O>>;
|
|
149
|
+
/**
|
|
150
|
+
* Per-field permission on the projection: omission, not denial. The caller
|
|
151
|
+
* still gets the row, without the fields they may not see.
|
|
152
|
+
*/
|
|
153
|
+
readonly gates?: {
|
|
154
|
+
readonly [F in OutputKeys<O>]?: PermKey;
|
|
155
|
+
};
|
|
156
|
+
} & OpAuthority<O, PermKey>;
|
|
157
|
+
/**
|
|
158
|
+
* Declare a module's operations against its entities and permission keys.
|
|
159
|
+
*
|
|
160
|
+
* Curried so the entities and permissions are given explicitly while each
|
|
161
|
+
* operation still infers its own input and output — a callback parameter cannot
|
|
162
|
+
* be contextually typed by a generic being inferred from the object containing
|
|
163
|
+
* it.
|
|
164
|
+
*
|
|
165
|
+
* ```ts
|
|
166
|
+
* export const ops = defineOperations(calloutEntities, PERMISSIONS)({
|
|
167
|
+
* 'customer/create': {
|
|
168
|
+
* summary: 'Register a customer',
|
|
169
|
+
* permission: 'customer:manage',
|
|
170
|
+
* input: z.object({ name: z.string() }),
|
|
171
|
+
* output: z.object({ id: z.string(), number: z.string() }),
|
|
172
|
+
* http: { method: 'POST', path: '/customers' },
|
|
173
|
+
* emits: {
|
|
174
|
+
* entity: 'customer', entityIdFrom: 'id',
|
|
175
|
+
* type: 'callout.customer-created', schemaVersion: 1, piiClass: 'none',
|
|
176
|
+
* },
|
|
177
|
+
* },
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export declare function defineOperations<const Entities extends Record<string, EntityDef>, const Perms extends readonly string[]>(_entities: Entities, _permissions: Perms): <const Ops extends { readonly [K in keyof Ops]: OperationShape<Ops[K], Entities, Perms[number]>; }>(operations: Ops) => Ops;
|
|
182
|
+
/**
|
|
183
|
+
* The permission keys an operation set actually requires, for the manifest.
|
|
184
|
+
*
|
|
185
|
+
* Read structurally rather than through a `{ permission?: string }` parameter:
|
|
186
|
+
* a `narrows` operation has neither `permission` nor `emits`, and TypeScript's
|
|
187
|
+
* weak-type rule rejects an object sharing no properties with the parameter.
|
|
188
|
+
*/
|
|
189
|
+
export declare function permissionsUsedBy(operations: Readonly<Record<string, object>>): string[];
|
|
190
|
+
/** The event types an operation set emits, for `manifest.events.emits`. */
|
|
191
|
+
export declare function eventsEmittedBy(operations: Readonly<Record<string, object>>): {
|
|
192
|
+
type: string;
|
|
193
|
+
schemaVersion: number;
|
|
194
|
+
}[];
|
|
195
|
+
/**
|
|
196
|
+
* The handler map a declared operation set requires — CRM-EFF's `satisfies Impl`
|
|
197
|
+
* seam, which is what makes the declaration BINDING rather than decorative.
|
|
198
|
+
*
|
|
199
|
+
* ```ts
|
|
200
|
+
* export const operations = { … } satisfies OperationImpl<typeof calloutOps, OperationContext>;
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* Four things become compile errors at the exact method: a handler whose input
|
|
204
|
+
* disagrees with the declared `input`, one whose return disagrees with the
|
|
205
|
+
* declared `output`, an operation declared and not implemented, and one
|
|
206
|
+
* implemented and not declared.
|
|
207
|
+
*
|
|
208
|
+
* `Ctx` is a parameter rather than `OperationContext` because contracts is below
|
|
209
|
+
* the kernel and must not import it. The vertical supplies it.
|
|
210
|
+
*/
|
|
211
|
+
export type OperationImpl<Ops, Ctx> = {
|
|
212
|
+
[K in keyof Ops]: Ops[K] extends {
|
|
213
|
+
output: infer O;
|
|
214
|
+
} ? O extends z.ZodType ? (ctx: Ctx, input: ImplInput<Ops[K]>) => z.infer<O> | Promise<z.infer<O>> : never : never;
|
|
215
|
+
};
|
|
216
|
+
/** No declared `input` means the handler takes `undefined`. */
|
|
217
|
+
type ImplInput<O> = O extends {
|
|
218
|
+
input: infer I;
|
|
219
|
+
} ? I extends z.ZodType ? O extends {
|
|
220
|
+
inputOptional: true;
|
|
221
|
+
} ? z.infer<I> | undefined : z.infer<I> : undefined : undefined;
|
|
222
|
+
export {};
|
|
223
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operation surface of the model (#707).
|
|
3
|
+
*
|
|
4
|
+
* #697 declared the entities. This declares what can be *done* to them, and
|
|
5
|
+
* checks the joins that today are unchecked strings: which permission an
|
|
6
|
+
* operation requires, which output field an event takes its subject from,
|
|
7
|
+
* whether a payload carries something an erasure must be able to reach.
|
|
8
|
+
*
|
|
9
|
+
* ## A composer, not a second `defineModel`
|
|
10
|
+
*
|
|
11
|
+
* `defineOperations` sits beside `defineEntities` rather than swallowing it.
|
|
12
|
+
* Each half stays independently adoptable — which is what let the entity half
|
|
13
|
+
* ship and be taken up by two verticals before this existed. A vertical adopts
|
|
14
|
+
* operations when it is ready, not as the price of adopting entities.
|
|
15
|
+
*
|
|
16
|
+
* ## `input`, and the transcription that is not here
|
|
17
|
+
*
|
|
18
|
+
* `input` is the Zod object the handler already parses — the same object, not a
|
|
19
|
+
* description of it. That is the whole reason the model is TypeScript (#680): a
|
|
20
|
+
* schema language would need the shape written twice, and transcription is what
|
|
21
|
+
* produced 40 wrong argument names in the one app where this was measured.
|
|
22
|
+
*/
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// The composer.
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Declare a module's operations against its entities and permission keys.
|
|
29
|
+
*
|
|
30
|
+
* Curried so the entities and permissions are given explicitly while each
|
|
31
|
+
* operation still infers its own input and output — a callback parameter cannot
|
|
32
|
+
* be contextually typed by a generic being inferred from the object containing
|
|
33
|
+
* it.
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* export const ops = defineOperations(calloutEntities, PERMISSIONS)({
|
|
37
|
+
* 'customer/create': {
|
|
38
|
+
* summary: 'Register a customer',
|
|
39
|
+
* permission: 'customer:manage',
|
|
40
|
+
* input: z.object({ name: z.string() }),
|
|
41
|
+
* output: z.object({ id: z.string(), number: z.string() }),
|
|
42
|
+
* http: { method: 'POST', path: '/customers' },
|
|
43
|
+
* emits: {
|
|
44
|
+
* entity: 'customer', entityIdFrom: 'id',
|
|
45
|
+
* type: 'callout.customer-created', schemaVersion: 1, piiClass: 'none',
|
|
46
|
+
* },
|
|
47
|
+
* },
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function defineOperations(_entities, _permissions) {
|
|
52
|
+
return (operations) => operations;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The permission keys an operation set actually requires, for the manifest.
|
|
56
|
+
*
|
|
57
|
+
* Read structurally rather than through a `{ permission?: string }` parameter:
|
|
58
|
+
* a `narrows` operation has neither `permission` nor `emits`, and TypeScript's
|
|
59
|
+
* weak-type rule rejects an object sharing no properties with the parameter.
|
|
60
|
+
*/
|
|
61
|
+
export function permissionsUsedBy(operations) {
|
|
62
|
+
const keys = Object.values(operations).flatMap((op) => {
|
|
63
|
+
const permission = op.permission;
|
|
64
|
+
return typeof permission === 'string' ? [permission] : [];
|
|
65
|
+
});
|
|
66
|
+
return [...new Set(keys)].sort();
|
|
67
|
+
}
|
|
68
|
+
/** The event types an operation set emits, for `manifest.events.emits`. */
|
|
69
|
+
export function eventsEmittedBy(operations) {
|
|
70
|
+
const seen = new Map();
|
|
71
|
+
for (const op of Object.values(operations)) {
|
|
72
|
+
const emits = op.emits;
|
|
73
|
+
if (typeof emits?.type === 'string' && typeof emits.schemaVersion === 'number') {
|
|
74
|
+
seen.set(emits.type, emits.schemaVersion);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return [...seen.entries()]
|
|
78
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
79
|
+
.map(([type, schemaVersion]) => ({ type, schemaVersion }));
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiIxB,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,gBAAgB,CAG9B,SAAmB,EAAE,YAAmB;IACxC,OAAO,CAKL,UAAe,EACV,EAAE,CAAC,UAAU,CAAC;AACvB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAA4C;IAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QACpD,MAAM,UAAU,GAAI,EAA+B,CAAC,UAAU,CAAC;QAC/D,OAAO,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACnC,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,eAAe,CAC7B,UAA4C;IAE5C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAI,EAA8D,CAAC,KAAK,CAAC;QACpF,IAAI,OAAO,KAAK,EAAE,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ,EAAE,CAAC;YAC/E,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;SACvB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;SACtC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;AAC/D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@substrat-run/contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.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
|
}
|