@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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +221 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +113 -0
- package/dist/model.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"}
|
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"}
|
package/dist/model.d.ts
ADDED
|
@@ -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.
|
|
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
|
}
|