@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.
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/entity.d.ts +117 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/entity.js +285 -0
- package/dist/entity.js.map +1 -0
- package/dist/field/boundary.d.ts +75 -0
- package/dist/field/boundary.d.ts.map +1 -0
- package/dist/field/boundary.js +76 -0
- package/dist/field/boundary.js.map +1 -0
- package/dist/field/field.d.ts +64 -0
- package/dist/field/field.d.ts.map +1 -0
- package/dist/field/field.js +28 -0
- package/dist/field/field.js.map +1 -0
- package/dist/field/index.d.ts +7 -0
- package/dist/field/index.d.ts.map +1 -0
- package/dist/field/index.js +9 -0
- package/dist/field/index.js.map +1 -0
- package/dist/field/lifecycle.d.ts +40 -0
- package/dist/field/lifecycle.d.ts.map +1 -0
- package/dist/field/lifecycle.js +27 -0
- package/dist/field/lifecycle.js.map +1 -0
- package/dist/field/meta.d.ts +9 -0
- package/dist/field/meta.d.ts.map +1 -0
- package/dist/field/meta.js +7 -0
- package/dist/field/meta.js.map +1 -0
- package/dist/field/role.d.ts +54 -0
- package/dist/field/role.d.ts.map +1 -0
- package/dist/field/role.js +55 -0
- package/dist/field/role.js.map +1 -0
- package/dist/field/shape.d.ts +77 -0
- package/dist/field/shape.d.ts.map +1 -0
- package/dist/field/shape.js +49 -0
- package/dist/field/shape.js.map +1 -0
- package/dist/hints.d.ts +33 -0
- package/dist/hints.d.ts.map +1 -0
- package/dist/hints.js +2 -0
- package/dist/hints.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/projections/card.d.ts +117 -0
- package/dist/projections/card.d.ts.map +1 -0
- package/dist/projections/card.js +8 -0
- package/dist/projections/card.js.map +1 -0
- package/dist/projections/describe.d.ts +25 -0
- package/dist/projections/describe.d.ts.map +1 -0
- package/dist/projections/describe.js +129 -0
- package/dist/projections/describe.js.map +1 -0
- package/dist/projections/encode.d.ts +11 -0
- package/dist/projections/encode.d.ts.map +1 -0
- package/dist/projections/encode.js +26 -0
- package/dist/projections/encode.js.map +1 -0
- package/dist/projections/io.d.ts +30 -0
- package/dist/projections/io.d.ts.map +1 -0
- package/dist/projections/io.js +52 -0
- package/dist/projections/io.js.map +1 -0
- package/dist/projections/lifecycle.d.ts +43 -0
- package/dist/projections/lifecycle.d.ts.map +1 -0
- package/dist/projections/lifecycle.js +115 -0
- package/dist/projections/lifecycle.js.map +1 -0
- package/dist/projections/reconstruct.d.ts +36 -0
- package/dist/projections/reconstruct.d.ts.map +1 -0
- package/dist/projections/reconstruct.js +149 -0
- package/dist/projections/reconstruct.js.map +1 -0
- package/dist/projections/source.d.ts +41 -0
- package/dist/projections/source.d.ts.map +1 -0
- package/dist/projections/source.js +37 -0
- package/dist/projections/source.js.map +1 -0
- package/dist/projections/standard.d.ts +41 -0
- package/dist/projections/standard.d.ts.map +1 -0
- package/dist/projections/standard.js +10 -0
- package/dist/projections/standard.js.map +1 -0
- package/dist/projections/typescript.d.ts +53 -0
- package/dist/projections/typescript.d.ts.map +1 -0
- package/dist/projections/typescript.js +166 -0
- package/dist/projections/typescript.js.map +1 -0
- package/dist/projections/validation.d.ts +49 -0
- package/dist/projections/validation.d.ts.map +1 -0
- package/dist/projections/validation.js +140 -0
- package/dist/projections/validation.js.map +1 -0
- package/dist/unique.d.ts +65 -0
- package/dist/unique.d.ts.map +1 -0
- package/dist/unique.js +80 -0
- package/dist/unique.js.map +1 -0
- package/dist/vocabulary/auto.d.ts +8 -0
- package/dist/vocabulary/auto.d.ts.map +1 -0
- package/dist/vocabulary/auto.js +13 -0
- package/dist/vocabulary/auto.js.map +1 -0
- package/dist/vocabulary/bool.d.ts +6 -0
- package/dist/vocabulary/bool.d.ts.map +1 -0
- package/dist/vocabulary/bool.js +9 -0
- package/dist/vocabulary/bool.js.map +1 -0
- package/dist/vocabulary/date.d.ts +3 -0
- package/dist/vocabulary/date.d.ts.map +1 -0
- package/dist/vocabulary/date.js +5 -0
- package/dist/vocabulary/date.js.map +1 -0
- package/dist/vocabulary/email.d.ts +5 -0
- package/dist/vocabulary/email.d.ts.map +1 -0
- package/dist/vocabulary/email.js +6 -0
- package/dist/vocabulary/email.js.map +1 -0
- package/dist/vocabulary/immutable.d.ts +9 -0
- package/dist/vocabulary/immutable.d.ts.map +1 -0
- package/dist/vocabulary/immutable.js +11 -0
- package/dist/vocabulary/immutable.js.map +1 -0
- package/dist/vocabulary/indexed.d.ts +19 -0
- package/dist/vocabulary/indexed.d.ts.map +1 -0
- package/dist/vocabulary/indexed.js +21 -0
- package/dist/vocabulary/indexed.js.map +1 -0
- package/dist/vocabulary/json.d.ts +18 -0
- package/dist/vocabulary/json.d.ts.map +1 -0
- package/dist/vocabulary/json.js +16 -0
- package/dist/vocabulary/json.js.map +1 -0
- package/dist/vocabulary/list.d.ts +21 -0
- package/dist/vocabulary/list.d.ts.map +1 -0
- package/dist/vocabulary/list.js +21 -0
- package/dist/vocabulary/list.js.map +1 -0
- package/dist/vocabulary/many.d.ts +8 -0
- package/dist/vocabulary/many.d.ts.map +1 -0
- package/dist/vocabulary/many.js +12 -0
- package/dist/vocabulary/many.js.map +1 -0
- package/dist/vocabulary/nullable.d.ts +9 -0
- package/dist/vocabulary/nullable.d.ts.map +1 -0
- package/dist/vocabulary/nullable.js +13 -0
- package/dist/vocabulary/nullable.js.map +1 -0
- package/dist/vocabulary/number.d.ts +10 -0
- package/dist/vocabulary/number.d.ts.map +1 -0
- package/dist/vocabulary/number.js +9 -0
- package/dist/vocabulary/number.js.map +1 -0
- package/dist/vocabulary/oneOf.d.ts +16 -0
- package/dist/vocabulary/oneOf.d.ts.map +1 -0
- package/dist/vocabulary/oneOf.js +13 -0
- package/dist/vocabulary/oneOf.js.map +1 -0
- package/dist/vocabulary/optional.d.ts +10 -0
- package/dist/vocabulary/optional.d.ts.map +1 -0
- package/dist/vocabulary/optional.js +15 -0
- package/dist/vocabulary/optional.js.map +1 -0
- package/dist/vocabulary/primary.d.ts +22 -0
- package/dist/vocabulary/primary.d.ts.map +1 -0
- package/dist/vocabulary/primary.js +31 -0
- package/dist/vocabulary/primary.js.map +1 -0
- package/dist/vocabulary/readOnly.d.ts +10 -0
- package/dist/vocabulary/readOnly.d.ts.map +1 -0
- package/dist/vocabulary/readOnly.js +12 -0
- package/dist/vocabulary/readOnly.js.map +1 -0
- package/dist/vocabulary/ref.d.ts +12 -0
- package/dist/vocabulary/ref.d.ts.map +1 -0
- package/dist/vocabulary/ref.js +20 -0
- package/dist/vocabulary/ref.js.map +1 -0
- package/dist/vocabulary/text.d.ts +12 -0
- package/dist/vocabulary/text.d.ts.map +1 -0
- package/dist/vocabulary/text.js +9 -0
- package/dist/vocabulary/text.js.map +1 -0
- package/dist/vocabulary/unique.d.ts +23 -0
- package/dist/vocabulary/unique.d.ts.map +1 -0
- package/dist/vocabulary/unique.js +25 -0
- package/dist/vocabulary/unique.js.map +1 -0
- package/dist/vocabulary/updated.d.ts +8 -0
- package/dist/vocabulary/updated.d.ts.map +1 -0
- package/dist/vocabulary/updated.js +12 -0
- package/dist/vocabulary/updated.js.map +1 -0
- package/dist/vocabulary/url.d.ts +5 -0
- package/dist/vocabulary/url.d.ts.map +1 -0
- package/dist/vocabulary/url.js +6 -0
- package/dist/vocabulary/url.js.map +1 -0
- package/dist/vocabulary/writeOnly.d.ts +9 -0
- package/dist/vocabulary/writeOnly.d.ts.map +1 -0
- package/dist/vocabulary/writeOnly.js +11 -0
- package/dist/vocabulary/writeOnly.js.map +1 -0
- 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":"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"}
|
|
@@ -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"}
|