@fougere/schema 0.1.0-alpha.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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/dist/entity.d.ts +117 -0
  4. package/dist/entity.d.ts.map +1 -0
  5. package/dist/entity.js +285 -0
  6. package/dist/entity.js.map +1 -0
  7. package/dist/field/boundary.d.ts +75 -0
  8. package/dist/field/boundary.d.ts.map +1 -0
  9. package/dist/field/boundary.js +76 -0
  10. package/dist/field/boundary.js.map +1 -0
  11. package/dist/field/field.d.ts +64 -0
  12. package/dist/field/field.d.ts.map +1 -0
  13. package/dist/field/field.js +28 -0
  14. package/dist/field/field.js.map +1 -0
  15. package/dist/field/index.d.ts +7 -0
  16. package/dist/field/index.d.ts.map +1 -0
  17. package/dist/field/index.js +9 -0
  18. package/dist/field/index.js.map +1 -0
  19. package/dist/field/lifecycle.d.ts +40 -0
  20. package/dist/field/lifecycle.d.ts.map +1 -0
  21. package/dist/field/lifecycle.js +27 -0
  22. package/dist/field/lifecycle.js.map +1 -0
  23. package/dist/field/meta.d.ts +9 -0
  24. package/dist/field/meta.d.ts.map +1 -0
  25. package/dist/field/meta.js +7 -0
  26. package/dist/field/meta.js.map +1 -0
  27. package/dist/field/role.d.ts +54 -0
  28. package/dist/field/role.d.ts.map +1 -0
  29. package/dist/field/role.js +55 -0
  30. package/dist/field/role.js.map +1 -0
  31. package/dist/field/shape.d.ts +77 -0
  32. package/dist/field/shape.d.ts.map +1 -0
  33. package/dist/field/shape.js +49 -0
  34. package/dist/field/shape.js.map +1 -0
  35. package/dist/hints.d.ts +33 -0
  36. package/dist/hints.d.ts.map +1 -0
  37. package/dist/hints.js +2 -0
  38. package/dist/hints.js.map +1 -0
  39. package/dist/index.d.ts +36 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +46 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/projections/card.d.ts +117 -0
  44. package/dist/projections/card.d.ts.map +1 -0
  45. package/dist/projections/card.js +8 -0
  46. package/dist/projections/card.js.map +1 -0
  47. package/dist/projections/describe.d.ts +25 -0
  48. package/dist/projections/describe.d.ts.map +1 -0
  49. package/dist/projections/describe.js +129 -0
  50. package/dist/projections/describe.js.map +1 -0
  51. package/dist/projections/encode.d.ts +11 -0
  52. package/dist/projections/encode.d.ts.map +1 -0
  53. package/dist/projections/encode.js +26 -0
  54. package/dist/projections/encode.js.map +1 -0
  55. package/dist/projections/io.d.ts +30 -0
  56. package/dist/projections/io.d.ts.map +1 -0
  57. package/dist/projections/io.js +52 -0
  58. package/dist/projections/io.js.map +1 -0
  59. package/dist/projections/lifecycle.d.ts +43 -0
  60. package/dist/projections/lifecycle.d.ts.map +1 -0
  61. package/dist/projections/lifecycle.js +115 -0
  62. package/dist/projections/lifecycle.js.map +1 -0
  63. package/dist/projections/reconstruct.d.ts +36 -0
  64. package/dist/projections/reconstruct.d.ts.map +1 -0
  65. package/dist/projections/reconstruct.js +149 -0
  66. package/dist/projections/reconstruct.js.map +1 -0
  67. package/dist/projections/source.d.ts +41 -0
  68. package/dist/projections/source.d.ts.map +1 -0
  69. package/dist/projections/source.js +37 -0
  70. package/dist/projections/source.js.map +1 -0
  71. package/dist/projections/standard.d.ts +41 -0
  72. package/dist/projections/standard.d.ts.map +1 -0
  73. package/dist/projections/standard.js +10 -0
  74. package/dist/projections/standard.js.map +1 -0
  75. package/dist/projections/typescript.d.ts +53 -0
  76. package/dist/projections/typescript.d.ts.map +1 -0
  77. package/dist/projections/typescript.js +166 -0
  78. package/dist/projections/typescript.js.map +1 -0
  79. package/dist/projections/validation.d.ts +49 -0
  80. package/dist/projections/validation.d.ts.map +1 -0
  81. package/dist/projections/validation.js +140 -0
  82. package/dist/projections/validation.js.map +1 -0
  83. package/dist/unique.d.ts +65 -0
  84. package/dist/unique.d.ts.map +1 -0
  85. package/dist/unique.js +80 -0
  86. package/dist/unique.js.map +1 -0
  87. package/dist/vocabulary/auto.d.ts +8 -0
  88. package/dist/vocabulary/auto.d.ts.map +1 -0
  89. package/dist/vocabulary/auto.js +13 -0
  90. package/dist/vocabulary/auto.js.map +1 -0
  91. package/dist/vocabulary/bool.d.ts +6 -0
  92. package/dist/vocabulary/bool.d.ts.map +1 -0
  93. package/dist/vocabulary/bool.js +9 -0
  94. package/dist/vocabulary/bool.js.map +1 -0
  95. package/dist/vocabulary/date.d.ts +3 -0
  96. package/dist/vocabulary/date.d.ts.map +1 -0
  97. package/dist/vocabulary/date.js +5 -0
  98. package/dist/vocabulary/date.js.map +1 -0
  99. package/dist/vocabulary/email.d.ts +5 -0
  100. package/dist/vocabulary/email.d.ts.map +1 -0
  101. package/dist/vocabulary/email.js +6 -0
  102. package/dist/vocabulary/email.js.map +1 -0
  103. package/dist/vocabulary/immutable.d.ts +9 -0
  104. package/dist/vocabulary/immutable.d.ts.map +1 -0
  105. package/dist/vocabulary/immutable.js +11 -0
  106. package/dist/vocabulary/immutable.js.map +1 -0
  107. package/dist/vocabulary/indexed.d.ts +19 -0
  108. package/dist/vocabulary/indexed.d.ts.map +1 -0
  109. package/dist/vocabulary/indexed.js +21 -0
  110. package/dist/vocabulary/indexed.js.map +1 -0
  111. package/dist/vocabulary/json.d.ts +18 -0
  112. package/dist/vocabulary/json.d.ts.map +1 -0
  113. package/dist/vocabulary/json.js +16 -0
  114. package/dist/vocabulary/json.js.map +1 -0
  115. package/dist/vocabulary/list.d.ts +21 -0
  116. package/dist/vocabulary/list.d.ts.map +1 -0
  117. package/dist/vocabulary/list.js +21 -0
  118. package/dist/vocabulary/list.js.map +1 -0
  119. package/dist/vocabulary/many.d.ts +8 -0
  120. package/dist/vocabulary/many.d.ts.map +1 -0
  121. package/dist/vocabulary/many.js +12 -0
  122. package/dist/vocabulary/many.js.map +1 -0
  123. package/dist/vocabulary/nullable.d.ts +9 -0
  124. package/dist/vocabulary/nullable.d.ts.map +1 -0
  125. package/dist/vocabulary/nullable.js +13 -0
  126. package/dist/vocabulary/nullable.js.map +1 -0
  127. package/dist/vocabulary/number.d.ts +10 -0
  128. package/dist/vocabulary/number.d.ts.map +1 -0
  129. package/dist/vocabulary/number.js +9 -0
  130. package/dist/vocabulary/number.js.map +1 -0
  131. package/dist/vocabulary/oneOf.d.ts +16 -0
  132. package/dist/vocabulary/oneOf.d.ts.map +1 -0
  133. package/dist/vocabulary/oneOf.js +13 -0
  134. package/dist/vocabulary/oneOf.js.map +1 -0
  135. package/dist/vocabulary/optional.d.ts +10 -0
  136. package/dist/vocabulary/optional.d.ts.map +1 -0
  137. package/dist/vocabulary/optional.js +15 -0
  138. package/dist/vocabulary/optional.js.map +1 -0
  139. package/dist/vocabulary/primary.d.ts +22 -0
  140. package/dist/vocabulary/primary.d.ts.map +1 -0
  141. package/dist/vocabulary/primary.js +31 -0
  142. package/dist/vocabulary/primary.js.map +1 -0
  143. package/dist/vocabulary/readOnly.d.ts +10 -0
  144. package/dist/vocabulary/readOnly.d.ts.map +1 -0
  145. package/dist/vocabulary/readOnly.js +12 -0
  146. package/dist/vocabulary/readOnly.js.map +1 -0
  147. package/dist/vocabulary/ref.d.ts +12 -0
  148. package/dist/vocabulary/ref.d.ts.map +1 -0
  149. package/dist/vocabulary/ref.js +20 -0
  150. package/dist/vocabulary/ref.js.map +1 -0
  151. package/dist/vocabulary/text.d.ts +12 -0
  152. package/dist/vocabulary/text.d.ts.map +1 -0
  153. package/dist/vocabulary/text.js +9 -0
  154. package/dist/vocabulary/text.js.map +1 -0
  155. package/dist/vocabulary/unique.d.ts +23 -0
  156. package/dist/vocabulary/unique.d.ts.map +1 -0
  157. package/dist/vocabulary/unique.js +25 -0
  158. package/dist/vocabulary/unique.js.map +1 -0
  159. package/dist/vocabulary/updated.d.ts +8 -0
  160. package/dist/vocabulary/updated.d.ts.map +1 -0
  161. package/dist/vocabulary/updated.js +12 -0
  162. package/dist/vocabulary/updated.js.map +1 -0
  163. package/dist/vocabulary/url.d.ts +5 -0
  164. package/dist/vocabulary/url.d.ts.map +1 -0
  165. package/dist/vocabulary/url.js +6 -0
  166. package/dist/vocabulary/url.js.map +1 -0
  167. package/dist/vocabulary/writeOnly.d.ts +9 -0
  168. package/dist/vocabulary/writeOnly.d.ts.map +1 -0
  169. package/dist/vocabulary/writeOnly.js +11 -0
  170. package/dist/vocabulary/writeOnly.js.map +1 -0
  171. package/package.json +40 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The lifecycle axis, realized — once, for every storage.
3
+ *
4
+ * `validation.ts` states the split: the judge answers "is this value legal?" and never
5
+ * fills a hole; filling it — stamp `'now'`, apply `{ value }`, call `{ generate }` — is
6
+ * the storage's role at the point of persistence. The split is right. What was missing
7
+ * is that the framework shipped the RULE and never its realization, so each storage
8
+ * rewrote it and they drifted:
9
+ *
10
+ * - `schema-sql` realized `{ generate }` and `'now'` in its ORM, and `{ value }` in its
11
+ * DDL — as a column `DEFAULT` the database fills. A mechanism only SQL has.
12
+ * - The Nuxt module's fallback ORM realized none of the three: its signature is
13
+ * `(_entity, _name)`, both ignored, so it forces the name `id` and a random uuid.
14
+ * - A third-party adapter (MongoDB, written outside the repo on 2026-08-08) found the
15
+ * gap the only way available: a test that expected `'draft'` and got `undefined`.
16
+ *
17
+ * Measured, one entity, `oneOf('draft','published',{ default:'draft' })`: SQLite answered
18
+ * `'draft'`, MongoDB answered `undefined`. Same declaration, same port, two answers.
19
+ *
20
+ * So the rule and its realization live together now, and a storage adapter calls this
21
+ * instead of re-deriving it. `update: 'forbidden'` is NOT here: refusing a value is a
22
+ * judgment, and the façade already does it (`validation.ts`, patch mode).
23
+ */
24
+ import { resolveCustomGenerator } from '../field/index.js';
25
+ import { createId } from '@paralleldrive/cuid2';
26
+ /**
27
+ * Resolve a generator TOKEN to a function: a custom name registered via
28
+ * `registerGenerator` wins, then the built-ins. An unknown name throws — loud and
29
+ * local, instead of a silent divergence between two storages.
30
+ *
31
+ * The built-ins used to live storage-side, which put the inversion in plain sight: a
32
+ * generator YOU invent travelled to every adapter, and the three the framework ships
33
+ * did not.
34
+ */
35
+ function generatorFor(ref) {
36
+ const custom = resolveCustomGenerator(ref);
37
+ if (custom)
38
+ return custom;
39
+ switch (ref) {
40
+ case 'cuid2': return createId;
41
+ case 'uuid': return () => globalThis.crypto.randomUUID();
42
+ case 'nanoid': {
43
+ return () => {
44
+ const bytes = new Uint8Array(21);
45
+ globalThis.crypto.getRandomValues(bytes);
46
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
47
+ return Array.from(bytes, (b) => alphabet[b & 63]).join('');
48
+ };
49
+ }
50
+ default:
51
+ throw new Error(`Unknown generator '${ref}' — register it with registerGenerator('${ref}', fn)`);
52
+ }
53
+ }
54
+ /**
55
+ * Fill what the entity says the system writes at creation.
56
+ *
57
+ * A value the caller supplied is never touched, including `null` — presence is the
58
+ * test, not truthiness, so `archivedAt: null` stays null rather than being re-filled.
59
+ *
60
+ * `'now'` produces a `Date`, the value the field declares. The storage converts it if
61
+ * its driver needs something else (`schema-sql/src/values.ts` does exactly that) —
62
+ * which is the same direction every other value travels.
63
+ */
64
+ export function applyCreate(fields, input) {
65
+ const out = { ...input };
66
+ const instant = Date.now();
67
+ for (const [name, field] of Object.entries(fields)) {
68
+ if (name in out)
69
+ continue;
70
+ const create = field.lifecycle?.create;
71
+ if (create === 'now') {
72
+ // One instant, one Date PER FIELD. `auto()` and `updated()` on the same entity
73
+ // would otherwise hold the same object, so mutating `updatedAt` would move an
74
+ // immutable `createdAt` with it — invisible where a storage serializes on write
75
+ // (SQL), lasting where it does not (an in-memory store keeps the row as handed).
76
+ out[name] = new Date(instant);
77
+ }
78
+ else if (typeof create === 'object' && create !== null) {
79
+ if ('value' in create)
80
+ out[name] = freshValue(create.value);
81
+ else if ('generate' in create)
82
+ out[name] = generatorFor(String(create.generate))();
83
+ }
84
+ // `'optional'` and an absent rule both mean: the system writes nothing here.
85
+ }
86
+ return out;
87
+ }
88
+ /**
89
+ * A declared default is written into every row, so handing out the declaration itself
90
+ * would alias every row to it — mutate one, mutate the field and all its siblings.
91
+ *
92
+ * Every default the vocabulary can express is a primitive (`text`, `number`, `bool`,
93
+ * `oneOf`; `list` and `json` take none), so this only pays for itself under the escape
94
+ * hatch — `createField({ lifecycle: { create: { value: {…} } } })`. Cheap insurance
95
+ * against the one failure mode nobody would ever debug from the symptom.
96
+ */
97
+ function freshValue(value) {
98
+ if (value === null || typeof value !== 'object')
99
+ return value;
100
+ return structuredClone(value);
101
+ }
102
+ /**
103
+ * Fill what the entity says the system writes at every update — `updated()`, and
104
+ * nothing else. A supplied value wins, same rule as create.
105
+ */
106
+ export function applyUpdate(fields, patch) {
107
+ const out = { ...patch };
108
+ const instant = Date.now();
109
+ for (const [name, field] of Object.entries(fields)) {
110
+ if (field.lifecycle?.update === 'now' && !(name in out))
111
+ out[name] = new Date(instant);
112
+ }
113
+ return out;
114
+ }
115
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/projections/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { type Field, type Fields } from '../field/index.js';
2
+ import { type SchemaConstructor, type SchemaViewInfer } from '../entity.js';
3
+ import { type SchemaBundle, type SchemaDescriptor } from './card.js';
4
+ /**
5
+ * The field map a stated row shape implies.
6
+ *
7
+ * Every member is marked auto-at-creation, so `CtorInput` asks for nothing: a card
8
+ * describes rows as they are READ, and "what a caller must supply at creation" is a
9
+ * different question — one `required` answers, and one a synced consumer never asks,
10
+ * since it calls the host rather than constructing.
11
+ */
12
+ type FieldsOf<T> = {
13
+ [K in keyof T]-?: Field<T[K], true>;
14
+ };
15
+ /**
16
+ * Read a lone card back into a working schema — THE single reconstructor. The result
17
+ * carries its `~standard` (live validation) rebuilt locally: the descriptor crosses
18
+ * the wire as data, the behaviour is reconstituted here. Relations stay name stand-ins
19
+ * (no set to resolve against — use {@link reconstructSet} for live targets).
20
+ *
21
+ * `T` states the shape of a row, and that is what makes a rebuilt schema a CLASS rather
22
+ * than a type and a value declared side by side: `class Post extends reconstruct<{…}>(card) {}`
23
+ * is one declaration carrying both, exactly like `class Post extends entity({…}) {}`.
24
+ * Without it the instance type is an index signature, so a synced entity validated
25
+ * perfectly and taught the compiler nothing — `post.titel` compiled.
26
+ */
27
+ export declare function reconstruct<T = SchemaViewInfer<Fields>>(descriptor: SchemaDescriptor): SchemaConstructor<FieldsOf<T>>;
28
+ /**
29
+ * Read a whole bundle back into live schemas, keyed by name — THE set reconstructor.
30
+ * Every entity is rebuilt against a shared `$defs` resolver, so a relation's `to()`
31
+ * hands back the real reconstructed target (feeds adapters), not a name stand-in.
32
+ * Targets absent from the set (external/cross-frond `$ref`) keep the stand-in.
33
+ */
34
+ export declare function reconstructSet(bundle: SchemaBundle): Record<string, SchemaConstructor<Fields>>;
35
+ export {};
36
+ //# sourceMappingURL=reconstruct.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconstruct.d.ts","sourceRoot":"","sources":["../../src/projections/reconstruct.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,KAAK,EACV,KAAK,MAAM,EAGZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA2B,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACrG,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,WAAW,CAAC;AAsHnB;;;;;;;GAOG;AACH,KAAK,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;CAAE,CAAC;AAE3D;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,EACrD,UAAU,EAAE,gBAAgB,GAC3B,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAEhC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAc9F"}
@@ -0,0 +1,149 @@
1
+ import { createField, } from '../field/index.js';
2
+ import { createSchemaConstructor } from '../entity.js';
3
+ import { clean, } from './card.js';
4
+ // ─── reconstruct — card → schema (the single reconstructor) ────────
5
+ /**
6
+ * Read a JSON Schema property back into a Fougère shape. The `type` travels
7
+ * VERBATIM — nullability is the `[T,'null']` union on both sides, so there is
8
+ * nothing to split; only the keyword subset of the base type is picked.
9
+ */
10
+ function reconstructShape(prop) {
11
+ const types = Array.isArray(prop.type) ? prop.type : prop.type ? [prop.type] : [];
12
+ const base = types.find((t) => t !== 'null');
13
+ if (base === undefined)
14
+ return undefined;
15
+ const type = prop.type;
16
+ // `array` is two distinct things: with `items` it is a value list (a real shape);
17
+ // without, it is a bare `many` relation marker — no shape, the role carries it.
18
+ if (base === 'array') {
19
+ if (!prop.items)
20
+ return undefined;
21
+ const items = reconstructShape(prop.items);
22
+ return clean({ type, items, minItems: prop.minItems, maxItems: prop.maxItems });
23
+ }
24
+ switch (base) {
25
+ case 'string':
26
+ return clean({ type, minLength: prop.minLength, maxLength: prop.maxLength, pattern: prop.pattern, enum: prop.enum, format: prop.format });
27
+ case 'number':
28
+ case 'integer':
29
+ return clean({ type, minimum: prop.minimum, maximum: prop.maximum });
30
+ case 'boolean':
31
+ return { type };
32
+ default: // 'object' or anything unrecognised → opaque value (nesting travels verbatim)
33
+ return clean({ type, properties: prop.properties, required: prop.required });
34
+ }
35
+ }
36
+ function reconstructRole(role, resolve) {
37
+ const out = {};
38
+ if (role.primary)
39
+ out.primary = true;
40
+ // Members arrive spelled out and stay that way — a single-member group read back as the
41
+ // empty self-reference would re-describe identically but lose the distinction for no
42
+ // gain. `uniqueMembers` treats a named group as already resolved.
43
+ if (role.unique?.length)
44
+ out.unique = role.unique.map((group) => [...group]);
45
+ if (role.index)
46
+ out.index = true;
47
+ if (role.relation) {
48
+ const name = role.relation.to;
49
+ out.relation = {
50
+ // `to` is the `$ref` (a name). With a resolver (a bundle), it hands back the real
51
+ // reconstructed target — adapters can read its fields. Lazy, so circular relations
52
+ // resolve fine. Without one (a lone card), a name-only stand-in: enough to validate
53
+ // (`kind`) and re-describe identically, but not to feed an adapter.
54
+ to: () => resolve?.(name) ?? { name },
55
+ kind: role.relation.kind,
56
+ ...(role.relation.onDelete ? { onDelete: role.relation.onDelete } : {}),
57
+ };
58
+ }
59
+ return out;
60
+ }
61
+ function reconstructField(prop, resolve) {
62
+ const ext = prop['x-fougere'];
63
+ return createField({
64
+ shape: reconstructShape(prop),
65
+ role: ext?.role ? reconstructRole(ext.role, resolve) : undefined,
66
+ // The normal forms are pure JSON — they travelled verbatim, they read back verbatim.
67
+ lifecycle: ext?.lifecycle,
68
+ boundary: ext?.boundary,
69
+ meta: prop.description !== undefined ? { description: prop.description } : undefined,
70
+ });
71
+ }
72
+ /**
73
+ * The entity-level groups implied by the fields — the inverse of
74
+ * `projectUniqueOntoFields`. Only groups of more than one member: a lone `unique(slug)`
75
+ * is fully stated by the field's own role, and listing it here would make `getUnique()`
76
+ * answer a composite the author never declared.
77
+ */
78
+ function compositeFromFields(fields) {
79
+ const seen = new Map();
80
+ for (const field of Object.values(fields)) {
81
+ for (const group of field.role?.unique ?? []) {
82
+ if (group.length < 2)
83
+ continue;
84
+ // A key that cannot collide with a field name — `JSON.stringify` rather than a
85
+ // separator byte: a NUL in the source made every `grep` read this whole file as
86
+ // binary and skip it silently, a poor price for a de-duplication key.
87
+ seen.set(JSON.stringify(group), [...group]);
88
+ }
89
+ }
90
+ return seen.size ? [...seen.values()] : undefined;
91
+ }
92
+ /** Build a live schema from a card; `resolve` wires relation targets when in a bundle. */
93
+ function buildSchema(descriptor, resolve) {
94
+ const fields = {};
95
+ for (const [key, prop] of Object.entries(descriptor.properties)) {
96
+ fields[key] = reconstructField(prop, resolve);
97
+ }
98
+ // Recover the entity-level declaration from what the members carry. The card holds the
99
+ // fact once per member; a group of two arrives twice, so the set is de-duplicated —
100
+ // `getUnique()` then answers what the original author wrote, and the DDL on this side
101
+ // emits the same constraint as the DDL on the other.
102
+ const schema = createSchemaConstructor(fields, undefined, undefined, {}, compositeFromFields(fields));
103
+ // The name is the identity everything downstream keys on — the registration key, the
104
+ // table, the GraphQL type, what a relation's `to` points at. `describe` writes it as
105
+ // `title`; dropping it here left a rebuilt schema called `Schema`, so a card could not
106
+ // round-trip and an adapter standing on one had no entity name to work from.
107
+ // `reconstructSet` still overrides with the bundle key, which is the more specific truth.
108
+ if (descriptor.title) {
109
+ Object.defineProperty(schema, 'name', { value: descriptor.title, configurable: true });
110
+ }
111
+ return schema;
112
+ }
113
+ /**
114
+ * Read a lone card back into a working schema — THE single reconstructor. The result
115
+ * carries its `~standard` (live validation) rebuilt locally: the descriptor crosses
116
+ * the wire as data, the behaviour is reconstituted here. Relations stay name stand-ins
117
+ * (no set to resolve against — use {@link reconstructSet} for live targets).
118
+ *
119
+ * `T` states the shape of a row, and that is what makes a rebuilt schema a CLASS rather
120
+ * than a type and a value declared side by side: `class Post extends reconstruct<{…}>(card) {}`
121
+ * is one declaration carrying both, exactly like `class Post extends entity({…}) {}`.
122
+ * Without it the instance type is an index signature, so a synced entity validated
123
+ * perfectly and taught the compiler nothing — `post.titel` compiled.
124
+ */
125
+ export function reconstruct(descriptor) {
126
+ return buildSchema(descriptor);
127
+ }
128
+ /**
129
+ * Read a whole bundle back into live schemas, keyed by name — THE set reconstructor.
130
+ * Every entity is rebuilt against a shared `$defs` resolver, so a relation's `to()`
131
+ * hands back the real reconstructed target (feeds adapters), not a name stand-in.
132
+ * Targets absent from the set (external/cross-frond `$ref`) keep the stand-in.
133
+ */
134
+ export function reconstructSet(bundle) {
135
+ const map = {};
136
+ const resolve = (name) => map[name.toLowerCase()];
137
+ const out = {};
138
+ for (const [name, descriptor] of Object.entries(bundle.$defs)) {
139
+ const schema = buildSchema(descriptor, resolve);
140
+ // Name the rebuilt class after its key so re-describing it yields the same `to` name
141
+ // (describe reads `relation.to().name`). Lazy `to` means the map need only be full
142
+ // before any `.to()` call — true once this loop ends.
143
+ Object.defineProperty(schema, 'name', { value: name, configurable: true });
144
+ map[name.toLowerCase()] = schema;
145
+ out[name] = schema;
146
+ }
147
+ return out;
148
+ }
149
+ //# sourceMappingURL=reconstruct.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconstruct.js","sourceRoot":"","sources":["../../src/projections/reconstruct.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,GAOZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,uBAAuB,EAAgD,MAAM,cAAc,CAAC;AACrG,OAAO,EACL,KAAK,GAKN,MAAM,WAAW,CAAC;AAEnB,sEAAsE;AAEtE;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,IAAqB;IAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAqB,CAAC;IACxC,kFAAkF;IAClF,gFAAgF;IAChF,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAClC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,OAAO,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAqB,CAAC;IACtG,CAAC;IACD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ;YACX,OAAO,KAAK,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAU,CAAC;QACrJ,KAAK,QAAQ,CAAC;QACd,KAAK,SAAS;YACZ,OAAO,KAAK,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAU,CAAC;QAChF,KAAK,SAAS;YACZ,OAAO,EAAE,IAAI,EAAW,CAAC;QAC3B,SAAS,8EAA8E;YACrF,OAAO,KAAK,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAqB,CAAC;IACrG,CAAC;AACH,CAAC;AAQD,SAAS,eAAe,CAAC,IAAoB,EAAE,OAAkB;IAC/D,MAAM,GAAG,GAAS,EAAE,CAAC;IACrB,IAAI,IAAI,CAAC,OAAO;QAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;IACrC,wFAAwF;IACxF,qFAAqF;IACrF,kEAAkE;IAClE,IAAI,IAAI,CAAC,MAAM,EAAE,MAAM;QAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAC7E,IAAI,IAAI,CAAC,KAAK;QAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC;IACjC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,GAAG,CAAC,QAAQ,GAAG;YACb,kFAAkF;YAClF,mFAAmF;YACnF,oFAAoF;YACpF,oEAAoE;YACpE,EAAE,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAK,EAAE,IAAI,EAAmC;YACvE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI;YACxB,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAqB,EAAE,OAAkB;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IAC9B,OAAO,WAAW,CAAC;QACjB,KAAK,EAAE,gBAAgB,CAAC,IAAI,CAAC;QAC7B,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;QAChE,qFAAqF;QACrF,SAAS,EAAE,GAAG,EAAE,SAAS;QACzB,QAAQ,EAAE,GAAG,EAAE,QAAQ;QACvB,IAAI,EAAE,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;KACrF,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,MAAc;IACzC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAoB,CAAC;IACzC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC;YAC7C,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YAC/B,+EAA+E;YAC/E,gFAAgF;YAChF,sEAAsE;YACtE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACpD,CAAC;AAED,0FAA0F;AAC1F,SAAS,WAAW,CAAC,UAA4B,EAAE,OAAkB;IACnE,MAAM,MAAM,GAAW,EAAE,CAAC;IAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAChE,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IACD,uFAAuF;IACvF,oFAAoF;IACpF,sFAAsF;IACtF,qDAAqD;IACrD,MAAM,MAAM,GAAG,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC;IAEtG,qFAAqF;IACrF,qFAAqF;IACrF,uFAAuF;IACvF,6EAA6E;IAC7E,0FAA0F;IAC1F,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAYD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CACzB,UAA4B;IAE5B,OAAO,WAAW,CAAC,UAAU,CAA8C,CAAC;AAC9E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,MAAM,GAAG,GAAsC,EAAE,CAAC;IAClD,MAAM,OAAO,GAAa,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5D,MAAM,GAAG,GAA8C,EAAE,CAAC;IAC1D,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAChD,qFAAqF;QACrF,mFAAmF;QACnF,sDAAsD;QACtD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3E,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,GAAG,MAAsC,CAAC;QACjE,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;IACrB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { Fields, SchemaLike } from '../field/index.js';
2
+ import type { SchemaDescriptor } from './card.js';
3
+ /**
4
+ * The two forms a schema reaches an adapter in — and the single reader that takes both.
5
+ *
6
+ * An adapter has never needed the class, only its fields. In-process it gets the class
7
+ * because the class is there; from a remote or foreign frond it gets the card, because a
8
+ * class does not cross a wire. Naming that union here is what lets one projection serve
9
+ * both, instead of each adapter growing its own branch.
10
+ *
11
+ * The card was built for exactly this and had no in-repo reader: every adapter called
12
+ * `getFields()` on the live class, so writing a Fougere adapter meant being inside the
13
+ * repo, in TypeScript, with the class in hand.
14
+ */
15
+ export type SchemaSource = SchemaLike | SchemaDescriptor;
16
+ /**
17
+ * Normalize a source to a live schema — reconstructing a card, passing a class through.
18
+ *
19
+ * This is what an adapter reaching for the schema MORE THAN ONCE should call, once, at its
20
+ * boundary: `reconstruct` runs per call (6.5 µs for a 17-field entity), so a component that
21
+ * reads fields in two places would otherwise rebuild the schema twice and hold two
22
+ * unrelated field objects. Normalize at the edge, then nothing downstream knows the
23
+ * difference — which is the point.
24
+ */
25
+ export declare function schemaOf(source: SchemaSource): SchemaLike;
26
+ /**
27
+ * The fields an adapter projects from, whichever form it was handed — {@link schemaOf}
28
+ * for the single-read case.
29
+ *
30
+ * Cost: nothing for a class. For a card, one `reconstruct` — measured at 6.5 µs for a
31
+ * 17-field entity, paid once per entity when the adapter builds, never per request. Read
32
+ * twice and you pay twice: hold the result, or normalize with `schemaOf` instead.
33
+ */
34
+ export declare function fieldsOf(source: SchemaSource): Fields;
35
+ /**
36
+ * The composite unique groups, whichever form. A bare wrapper (a view mid-derivation)
37
+ * carries no `getUnique` and answers `undefined`; a card's groups are recovered from what
38
+ * its members carry, so `reconstruct` has already restored them by the time this reads.
39
+ */
40
+ export declare function uniqueOf(source: SchemaSource): ReadonlyArray<ReadonlyArray<string>> | undefined;
41
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/projections/source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAOzD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,CAEzD;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAE/F"}
@@ -0,0 +1,37 @@
1
+ import { reconstruct } from './reconstruct.js';
2
+ /** A card is the plain document — no behaviour, so no `getFields`. */
3
+ function isDescriptor(source) {
4
+ return typeof source.getFields !== 'function';
5
+ }
6
+ /**
7
+ * Normalize a source to a live schema — reconstructing a card, passing a class through.
8
+ *
9
+ * This is what an adapter reaching for the schema MORE THAN ONCE should call, once, at its
10
+ * boundary: `reconstruct` runs per call (6.5 µs for a 17-field entity), so a component that
11
+ * reads fields in two places would otherwise rebuild the schema twice and hold two
12
+ * unrelated field objects. Normalize at the edge, then nothing downstream knows the
13
+ * difference — which is the point.
14
+ */
15
+ export function schemaOf(source) {
16
+ return isDescriptor(source) ? reconstruct(source) : source;
17
+ }
18
+ /**
19
+ * The fields an adapter projects from, whichever form it was handed — {@link schemaOf}
20
+ * for the single-read case.
21
+ *
22
+ * Cost: nothing for a class. For a card, one `reconstruct` — measured at 6.5 µs for a
23
+ * 17-field entity, paid once per entity when the adapter builds, never per request. Read
24
+ * twice and you pay twice: hold the result, or normalize with `schemaOf` instead.
25
+ */
26
+ export function fieldsOf(source) {
27
+ return schemaOf(source).getFields();
28
+ }
29
+ /**
30
+ * The composite unique groups, whichever form. A bare wrapper (a view mid-derivation)
31
+ * carries no `getUnique` and answers `undefined`; a card's groups are recovered from what
32
+ * its members carry, so `reconstruct` has already restored them by the time this reads.
33
+ */
34
+ export function uniqueOf(source) {
35
+ return schemaOf(source).getUnique?.();
36
+ }
37
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/projections/source.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAgB/C,sEAAsE;AACtE,SAAS,YAAY,CAAC,MAAoB;IACxC,OAAO,OAAQ,MAAqB,CAAC,SAAS,KAAK,UAAU,CAAC;AAChE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAoB;IAC3C,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAC7D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAoB;IAC3C,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAoB;IAC3C,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC;AACxC,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Standard Schema v1 — types inlined from @standard-schema/spec.
3
+ *
4
+ * We inline instead of depending on the package to keep @fougere/schema zero-dep.
5
+ * The spec is stable (v1) and unlikely to change.
6
+ *
7
+ * @see https://standardschema.dev/
8
+ */
9
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
10
+ readonly '~standard': StandardSchemaV1.Props<Input, Output>;
11
+ }
12
+ export declare namespace StandardSchemaV1 {
13
+ interface Props<Input = unknown, Output = Input> {
14
+ readonly version: 1;
15
+ readonly vendor: string;
16
+ readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
17
+ readonly types?: Types<Input, Output> | undefined;
18
+ }
19
+ type Result<Output> = SuccessResult<Output> | FailureResult;
20
+ interface SuccessResult<Output> {
21
+ readonly value: Output;
22
+ readonly issues?: undefined;
23
+ }
24
+ interface FailureResult {
25
+ readonly issues: ReadonlyArray<Issue>;
26
+ }
27
+ interface Issue {
28
+ readonly message: string;
29
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
30
+ }
31
+ interface PathSegment {
32
+ readonly key: PropertyKey;
33
+ }
34
+ interface Types<Input = unknown, Output = Input> {
35
+ readonly input: Input;
36
+ readonly output: Output;
37
+ }
38
+ type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['input'];
39
+ type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['output'];
40
+ }
41
+ //# sourceMappingURL=standard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standard.d.ts","sourceRoot":"","sources":["../../src/projections/standard.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,gBAAgB,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;IAC/D,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;CAC7D;AAED,MAAM,CAAC,OAAO,WAAW,gBAAgB,CAAC;IACxC,UAAU,KAAK,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;QAC7C,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,OAAO,KACX,MAAM,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;KACnD;IAED,KAAK,MAAM,CAAC,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC;IAE5D,UAAU,aAAa,CAAC,MAAM;QAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;KAC7B;IAED,UAAU,aAAa;QACrB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;KACvC;IAED,UAAU,KAAK;QACb,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,SAAS,CAAC;KACtE;IAED,UAAU,WAAW;QACnB,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;KAC3B;IAED,UAAU,KAAK,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;QAC7C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB;IAED,KAAK,UAAU,CAAC,MAAM,SAAS,gBAAgB,IAAI,WAAW,CAC5D,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAC7B,CAAC,OAAO,CAAC,CAAC;IAEX,KAAK,WAAW,CAAC,MAAM,SAAS,gBAAgB,IAAI,WAAW,CAC7D,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAC7B,CAAC,QAAQ,CAAC,CAAC;CACb"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Standard Schema v1 — types inlined from @standard-schema/spec.
3
+ *
4
+ * We inline instead of depending on the package to keep @fougere/schema zero-dep.
5
+ * The spec is stable (v1) and unlikely to change.
6
+ *
7
+ * @see https://standardschema.dev/
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=standard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standard.js","sourceRoot":"","sources":["../../src/projections/standard.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,53 @@
1
+ import type { SchemaDescriptor } from './card.js';
2
+ export interface TypeSourceOptions {
3
+ /** Name of the emitted interface. Defaults to the card's `title`, capitalized. */
4
+ name?: string;
5
+ /** Emit `export` before the declaration. Default: true. */
6
+ exported?: boolean;
7
+ }
8
+ /**
9
+ * The anonymous object type one card describes — the members, nothing around them.
10
+ *
11
+ * Every property is present: `required` on a Fougère card answers "what must a CALLER
12
+ * supply at creation", not "what is always there when read" — an `id` the system
13
+ * generates is absent from `required` and always present on a row. Typing the read
14
+ * shape from the create rule would make `post.id` possibly-undefined for everyone.
15
+ */
16
+ export declare function shapeTypeOf(descriptor: SchemaDescriptor, indent?: string): string;
17
+ /**
18
+ * Emit the entity a card describes — ONE class, judge and shape together.
19
+ *
20
+ * An entity is a class here (`class Post extends entity({…}) {}`), and a class is the
21
+ * language's own answer to "a name that is both a value and a type". Emitting an
22
+ * interface next to a const said the same thing twice and relied on declaration
23
+ * merging to look like a class; this IS one.
24
+ *
25
+ * The card travels inline, so the rebuilt judge is exact — the shape written above it
26
+ * is read off that same card, never a second source to keep in step.
27
+ *
28
+ * Requires `reconstruct` in scope: the caller writes the import, since only it knows
29
+ * whether the consumer says `@fougere/schema` or a path.
30
+ */
31
+ export declare function entitySourceOf(descriptor: SchemaDescriptor, options?: TypeSourceOptions): string;
32
+ /** One operation, as much of it as a card can say. */
33
+ export interface OpDescriptor {
34
+ name: string;
35
+ description?: string;
36
+ output?: SchemaDescriptor;
37
+ /** How much `output` describes. Absent means the card did not say. */
38
+ cardinality?: 'one' | 'maybe' | 'many' | 'page' | 'none';
39
+ }
40
+ /**
41
+ * Emit the type of a façade — every operation an entity serves, as a caller meets it.
42
+ *
43
+ * This is what `Facade<T>` needs and what no consumer in another repository could have:
44
+ * writing `Facade<ArticleHandler>` used to require importing the handler's class, which
45
+ * `sync` does not carry and should not. The card carries the operations; this reads them.
46
+ *
47
+ * Each op takes an optional invocation and returns a promise — the door's signature, not
48
+ * the handler's. A handler's method takes positional arguments; its door takes the call.
49
+ */
50
+ export declare function facadeTypeSourceOf(ops: readonly OpDescriptor[], options?: TypeSourceOptions & {
51
+ rowType?: string;
52
+ }): string;
53
+ //# sourceMappingURL=typescript.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typescript.d.ts","sourceRoot":"","sources":["../../src/projections/typescript.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAwEnE,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAK,GAAG,MAAM,CAS7E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,gBAAgB,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CASpG;AAmBD,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,sEAAsE;IACtE,WAAW,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CAC1D;AAyBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,SAAS,YAAY,EAAE,EAC5B,OAAO,GAAE,iBAAiB,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD,MAAM,CAYR"}