@jarenjs/linq 0.46.5 → 0.56.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 (77) hide show
  1. package/ARCHITECTURE.md +217 -0
  2. package/README.md +566 -17
  3. package/docs/APP-PEN.md +1143 -0
  4. package/docs/CONTRACT-PEN.md +1217 -0
  5. package/docs/DB-CLIENT.md +814 -0
  6. package/docs/FLOW-PEN.md +1026 -0
  7. package/docs/FORMS-PEN.md +940 -0
  8. package/docs/JSLT-PEN.md +955 -0
  9. package/docs/LINQ-FORMAT.md +774 -384
  10. package/docs/MIGRATION-PEN.md +781 -0
  11. package/docs/MODEL-PEN.md +1083 -0
  12. package/docs/QUERY-PEN.md +1636 -0
  13. package/docs/SCHEMA-PEN.md +1218 -0
  14. package/package.json +57 -4
  15. package/src/app/action.js +255 -0
  16. package/src/app/capture.js +63 -0
  17. package/src/app/define.js +260 -0
  18. package/src/app/index.js +20 -0
  19. package/src/app/patch.js +277 -0
  20. package/src/app/sub.js +106 -0
  21. package/src/async.js +329 -75
  22. package/src/capture-root.js +82 -0
  23. package/src/concurrency.js +9 -4
  24. package/src/contract/define.js +269 -0
  25. package/src/contract/http.js +247 -0
  26. package/src/contract/index.js +23 -0
  27. package/src/contract/operation.js +342 -0
  28. package/src/db/handle.js +86 -0
  29. package/src/db/include.js +316 -0
  30. package/src/db/index.js +19 -0
  31. package/src/db/live.js +43 -0
  32. package/src/db/membership.js +37 -0
  33. package/src/db/open.js +82 -0
  34. package/src/document.js +143 -13
  35. package/src/effect.js +65 -0
  36. package/src/errors.js +69 -6
  37. package/src/expression.js +532 -39
  38. package/src/flow/capture.js +33 -0
  39. package/src/flow/dag.js +302 -0
  40. package/src/flow/fsm.js +328 -0
  41. package/src/flow/index.js +22 -0
  42. package/src/forms/index.js +43 -0
  43. package/src/forms/rules.js +170 -0
  44. package/src/forms/submit.js +177 -0
  45. package/src/index.js +4 -2
  46. package/src/jslt/body.js +226 -0
  47. package/src/jslt/index.js +18 -0
  48. package/src/jslt/rules.js +207 -0
  49. package/src/json-boundary.js +90 -0
  50. package/src/migration/define.js +323 -0
  51. package/src/migration/index.js +15 -0
  52. package/src/migration/steps.js +248 -0
  53. package/src/model/collection.js +171 -0
  54. package/src/model/define.js +125 -0
  55. package/src/model/entity.js +307 -0
  56. package/src/model/index.js +47 -0
  57. package/src/model/relation.js +85 -0
  58. package/src/provider.js +137 -20
  59. package/src/schema/brand.js +31 -0
  60. package/src/schema/builders.js +526 -0
  61. package/src/schema/check.js +29 -0
  62. package/src/schema/emit.js +394 -0
  63. package/src/schema/factories.js +239 -0
  64. package/src/schema/index.js +37 -0
  65. package/src/schema-of.js +24 -0
  66. package/src/sequence.js +233 -103
  67. package/src/sources.js +10 -3
  68. package/types/app.d.ts +293 -0
  69. package/types/contract.d.ts +371 -0
  70. package/types/db.d.ts +188 -0
  71. package/types/flow.d.ts +285 -0
  72. package/types/forms.d.ts +253 -0
  73. package/types/index.d.ts +389 -41
  74. package/types/jslt.d.ts +193 -0
  75. package/types/migration.d.ts +201 -0
  76. package/types/model.d.ts +493 -0
  77. package/types/schema.d.ts +494 -0
@@ -0,0 +1,493 @@
1
+ /**
2
+ * Hand-authored declarations for `@jarenjs/linq/model` — the schema
3
+ * pen's every name, from subclasses that carry the `x-entity`
4
+ * vocabulary, plus relation members, collections, indexes and
5
+ * `defineModel()`. `InferMeta<>` reads the model document's phantom the
6
+ * way `entityEmitModel` reads the document itself: closed entity
7
+ * interfaces (nested shapes included), relation members as optional
8
+ * references, date-formatted strings as `DateTime`, an input variant
9
+ * with store-written members optional, to-one/to-many projections
10
+ * dropped and many-to-many members as key-or-document arrays, a key of
11
+ * the key member's primitive (or the composite object), and the
12
+ * relations map. The agreement is pinned: `InferMeta<>` of the fixture
13
+ * model rebuilt through the pen equals the generated `EntityMetaMap`.
14
+ */
15
+
16
+ import type { DateTime, Expr } from './index.js';
17
+ import type {
18
+ Annotations, BuilderLike, CheckRule, Flag, FlagsOf, Infer, Input, Json, JsonSchema,
19
+ NamedLike, Simplify, SchemaBuilder, StringBuilder, NumberBuilder, BooleanBuilder,
20
+ NullBuilder, ArrayBuilder, TupleBuilder, ObjectBuilder, NamedBuilder, WhenBuilder,
21
+ NeverBuilder,
22
+ } from './schema.js';
23
+
24
+ type AnyBuilder = BuilderLike<any, any, any>;
25
+ type Props = Record<string, AnyBuilder>;
26
+ type Nullify<T, N extends boolean> = N extends true ? T | null : T;
27
+
28
+ /** A store-written default marks the member `generated`; `key()` marks it `key`. */
29
+ type Generated<F extends Flag> = F | 'generated';
30
+
31
+ /**
32
+ * The closed `x-entity` vocabulary (MODEL-FORMAT §9.2), as `entity()`
33
+ * writes it. A member outside this set is `JL0102`: the store refuses a
34
+ * mapping directive it cannot read rather than ignoring it, so the pen
35
+ * refuses it first. The named methods below are the way to spell each of
36
+ * these — they also carry the FLAGS `InferMeta<>` reads, which the
37
+ * untyped primitive cannot.
38
+ */
39
+ export interface EntityBlock {
40
+ /** (Part of) the primary key. */
41
+ readonly key?: true;
42
+ /** A unique index over the member's column. */
43
+ readonly unique?: true;
44
+ /** A non-unique index over the member's column. */
45
+ readonly index?: true;
46
+ /** The optimistic-concurrency token: one plain integer column (§11.5). */
47
+ readonly version?: true;
48
+ /** An epoch column beside a date string, or stay in the document (§9.3). */
49
+ readonly column?: 'integer' | 'json';
50
+ /** Applied on write, in JavaScript (§9.6). */
51
+ readonly default?: 'now' | 'updated' | 'uuid' | 'auto'
52
+ | { readonly value: Json }
53
+ | { readonly query: { readonly [keyword: string]: unknown } };
54
+ /** A relation, as `rel.*` spells one (§9.4). */
55
+ readonly relation?: {
56
+ readonly to: string;
57
+ readonly many?: true;
58
+ readonly via?: string;
59
+ readonly through?: string;
60
+ readonly onDelete?: 'cascade' | 'restrict' | 'setNull';
61
+ };
62
+ }
63
+
64
+ // ————— the entity-aware builders —————
65
+
66
+ /** The base every untyped kind is built from, plus the vocabulary. */
67
+ export class EntityBuilder<Out = unknown, In = Out, F extends Flag = never> extends SchemaBuilder<Out, In, F> {
68
+ optional(): EntityBuilder<Out, In, F | 'optional'>;
69
+ nullable(): EntityBuilder<Out | null, In | null, F>;
70
+ default(value: Out): EntityBuilder<Out, In, F | 'defaulted'>;
71
+ /** `key: true` — (part of) the primary key. */
72
+ key(): EntityBuilder<Out, In, F | 'key'>;
73
+ /** `unique: true` — a unique index over the column. */
74
+ unique(): this;
75
+ /** `index: true` — a non-unique index over the column. */
76
+ index(): this;
77
+ /** `column: 'json'` keeps a scalar in the document. */
78
+ column(storage: 'json'): this;
79
+ /** `default: { value }` — a literal, filled when absent. */
80
+ fill(value: Out): EntityBuilder<Out, In, Generated<F>>;
81
+ /** `default: { query }` — over the document being written (`$`); no externals. */
82
+ compute(rule: ComputeRule): EntityBuilder<Out, In, Generated<F>>;
83
+ /** Merge into the `x-entity` block — the primitive every method above
84
+ * writes through, held to the closed vocabulary. It carries no flag, so
85
+ * `key()`/`identity()`/`fill()` and the rest stay the way to spell one. */
86
+ entity(patch: EntityBlock): this;
87
+ /** As in the schema pen, but `x-entity` is owned here. */
88
+ meta(annotations: Annotations & { readonly 'x-entity'?: never }): this;
89
+ }
90
+
91
+ export class EntityStringBuilder<Out = string, In = Out, F extends Flag = never> extends StringBuilder<Out, In, F> {
92
+ optional(): EntityStringBuilder<Out, In, F | 'optional'>;
93
+ nullable(): EntityStringBuilder<Out | null, In | null, F>;
94
+ default(value: Out): EntityStringBuilder<Out, In, F | 'defaulted'>;
95
+ coerce(): EntityStringBuilder<Out, In | number | boolean, F>;
96
+ format(name: 'date-time' | 'date'): EntityStringBuilder<DateTime, DateTime, F>;
97
+ format(name: string): this;
98
+ enumOf<const V extends readonly string[]>(values: V): EntityStringBuilder<V[number], V[number], F>;
99
+ key(): EntityStringBuilder<Out, In, F | 'key'>;
100
+ unique(): this;
101
+ index(): this;
102
+ /** `column: 'integer'` — an epoch column, on a `datetime()`/`date()` only. */
103
+ column(storage: 'json'): this;
104
+ column(this: EntityStringBuilder<DateTime, any, any>, storage: 'integer'): this;
105
+ /** A store-allocated key: `key: true` + `default: 'uuid'` (`crypto.randomUUID()`). */
106
+ identity(kind: 'uuid'): EntityStringBuilder<Out, In, Generated<F> | 'key'>;
107
+ /** `default: 'now'` — an RFC 3339 stamp on insert, when absent. */
108
+ now(): EntityStringBuilder<Out, In, Generated<F>>;
109
+ /** `default: 'updated'` — a stamp on insert and on every update. */
110
+ updated(): EntityStringBuilder<Out, In, Generated<F>>;
111
+ fill(value: Out): EntityStringBuilder<Out, In, Generated<F>>;
112
+ compute(rule: ComputeRule): EntityStringBuilder<Out, In, Generated<F>>;
113
+ /** Merge into the `x-entity` block — the primitive every method above
114
+ * writes through, held to the closed vocabulary. It carries no flag, so
115
+ * `key()`/`identity()`/`fill()` and the rest stay the way to spell one. */
116
+ entity(patch: EntityBlock): this;
117
+ meta(annotations: Annotations & { readonly 'x-entity'?: never }): this;
118
+ }
119
+
120
+ export class EntityNumberBuilder<Out = number, In = Out, F extends Flag = never> extends NumberBuilder<Out, In, F> {
121
+ optional(): EntityNumberBuilder<Out, In, F | 'optional'>;
122
+ nullable(): EntityNumberBuilder<Out | null, In | null, F>;
123
+ default(value: Out): EntityNumberBuilder<Out, In, F | 'defaulted'>;
124
+ coerce(): EntityNumberBuilder<Out, In | string, F>;
125
+ enumOf<const V extends readonly number[]>(values: V): EntityNumberBuilder<V[number], V[number] | Exclude<In, number>, F>;
126
+ key(): EntityNumberBuilder<Out, In, F | 'key'>;
127
+ unique(): this;
128
+ index(): this;
129
+ /** `version: true` — the optimistic-concurrency token (an integer). */
130
+ version(): this;
131
+ column(storage: 'json'): this;
132
+ /** A store-allocated key: `key: true` + `default: 'auto'` (the database allocates an integer). */
133
+ identity(kind: 'auto'): EntityNumberBuilder<Out, In, Generated<F> | 'key'>;
134
+ fill(value: Out): EntityNumberBuilder<Out, In, Generated<F>>;
135
+ compute(rule: ComputeRule): EntityNumberBuilder<Out, In, Generated<F>>;
136
+ /** Merge into the `x-entity` block — the primitive every method above
137
+ * writes through, held to the closed vocabulary. It carries no flag, so
138
+ * `key()`/`identity()`/`fill()` and the rest stay the way to spell one. */
139
+ entity(patch: EntityBlock): this;
140
+ meta(annotations: Annotations & { readonly 'x-entity'?: never }): this;
141
+ }
142
+
143
+ declare class EntityBooleanBuilder<Out = boolean, In = Out, F extends Flag = never> extends BooleanBuilder<Out, In, F> {
144
+ optional(): EntityBooleanBuilder<Out, In, F | 'optional'>;
145
+ nullable(): EntityBooleanBuilder<Out | null, In | null, F>;
146
+ default(value: Out): EntityBooleanBuilder<Out, In, F | 'defaulted'>;
147
+ coerce(): EntityBooleanBuilder<Out, In | string, F>;
148
+ key(): EntityBooleanBuilder<Out, In, F | 'key'>;
149
+ unique(): this;
150
+ index(): this;
151
+ column(storage: 'json'): this;
152
+ fill(value: Out): EntityBooleanBuilder<Out, In, Generated<F>>;
153
+ compute(rule: ComputeRule): EntityBooleanBuilder<Out, In, Generated<F>>;
154
+ /** Merge into the `x-entity` block — the primitive every method above
155
+ * writes through, held to the closed vocabulary. It carries no flag, so
156
+ * `key()`/`identity()`/`fill()` and the rest stay the way to spell one. */
157
+ entity(patch: EntityBlock): this;
158
+ meta(annotations: Annotations & { readonly 'x-entity'?: never }): this;
159
+ }
160
+
161
+ declare class EntityNullBuilder<Out = null, In = Out, F extends Flag = never> extends NullBuilder<Out, In, F> {
162
+ optional(): EntityNullBuilder<Out, In, F | 'optional'>;
163
+ default(value: Out): EntityNullBuilder<Out, In, F | 'defaulted'>;
164
+ coerce(): EntityNullBuilder<Out, In | string, F>;
165
+ column(storage: 'json'): this;
166
+ fill(value: Out): EntityNullBuilder<Out, In, Generated<F>>;
167
+ compute(rule: ComputeRule): EntityNullBuilder<Out, In, Generated<F>>;
168
+ /** Merge into the `x-entity` block — the primitive every method above
169
+ * writes through, held to the closed vocabulary. It carries no flag, so
170
+ * `key()`/`identity()`/`fill()` and the rest stay the way to spell one. */
171
+ entity(patch: EntityBlock): this;
172
+ meta(annotations: Annotations & { readonly 'x-entity'?: never }): this;
173
+ }
174
+
175
+ export class EntityArrayBuilder<Out = unknown[], In = Out, F extends Flag = never> extends ArrayBuilder<Out, In, F> {
176
+ optional(): EntityArrayBuilder<Out, In, F | 'optional'>;
177
+ nullable(): EntityArrayBuilder<Out | null, In | null, F>;
178
+ default(value: Out): EntityArrayBuilder<Out, In, F | 'defaulted'>;
179
+ fill(value: Out): EntityArrayBuilder<Out, In, Generated<F>>;
180
+ compute(rule: ComputeRule): EntityArrayBuilder<Out, In, Generated<F>>;
181
+ /** Merge into the `x-entity` block — the primitive every method above
182
+ * writes through, held to the closed vocabulary. It carries no flag, so
183
+ * `key()`/`identity()`/`fill()` and the rest stay the way to spell one. */
184
+ entity(patch: EntityBlock): this;
185
+ meta(annotations: Annotations & { readonly 'x-entity'?: never }): this;
186
+ }
187
+
188
+ export class EntityTupleBuilder<
189
+ T extends readonly AnyBuilder[], R = unknown, RIn = R,
190
+ N extends boolean = false, F extends Flag = never,
191
+ > extends TupleBuilder<T, R, RIn, N, F> {
192
+ optional(): EntityTupleBuilder<T, R, RIn, N, F | 'optional'>;
193
+ nullable(): EntityTupleBuilder<T, R, RIn, true, F>;
194
+ rest<B extends AnyBuilder>(builder: B): EntityTupleBuilder<T, Infer<B>, Input<B>, N, F>;
195
+ /** Merge into the `x-entity` block — the primitive every method above
196
+ * writes through, held to the closed vocabulary. It carries no flag, so
197
+ * `key()`/`identity()`/`fill()` and the rest stay the way to spell one. */
198
+ entity(patch: EntityBlock): this;
199
+ meta(annotations: Annotations & { readonly 'x-entity'?: never }): this;
200
+ }
201
+
202
+ export class EntityObjectBuilder<
203
+ P extends Props, Open extends boolean = false, PV = never, PVIn = PV,
204
+ N extends boolean = false, F extends Flag = never,
205
+ > extends ObjectBuilder<P, Open, PV, PVIn, N, F> {
206
+ optional(): EntityObjectBuilder<P, Open, PV, PVIn, N, F | 'optional'>;
207
+ nullable(): EntityObjectBuilder<P, Open, PV, PVIn, true, F>;
208
+ open(): EntityObjectBuilder<P, true, PV, PVIn, N, F>;
209
+ patternProperties<M extends Props>(map: M): EntityObjectBuilder<P, Open, Infer<M[keyof M]>, Input<M[keyof M]>, N, F>;
210
+ extend<Q extends Props>(props: Q): EntityObjectBuilder<Simplify<Omit<P, keyof Q> & Q>, Open, PV, PVIn, N, F>;
211
+ pick<K extends keyof P & string>(keys: readonly K[]): EntityObjectBuilder<Pick<P, K>, Open, PV, PVIn, N, F>;
212
+ omit<K extends keyof P & string>(keys: readonly K[]): EntityObjectBuilder<Omit<P, K>, Open, PV, PVIn, N, F>;
213
+ /** `x-rename`: this entity (or collection) was previously named `name`. */
214
+ renamedFrom(name: string): this;
215
+ /** `column: 'json'` keeps the object in the document (where it lives anyway). */
216
+ column(storage: 'json'): this;
217
+ /** Merge into the `x-entity` block — the primitive every method above
218
+ * writes through, held to the closed vocabulary. It carries no flag, so
219
+ * `key()`/`identity()`/`fill()` and the rest stay the way to spell one. */
220
+ entity(patch: EntityBlock): this;
221
+ meta(annotations: Annotations & { readonly 'x-entity'?: never }): this;
222
+ }
223
+
224
+ declare class EntityNamedBuilder<Out, In = Out, F extends Flag = never> extends NamedBuilder<Out, In, F> {
225
+ optional(): EntityNamedBuilder<Out, In, F | 'optional'>;
226
+ nullable(): EntityNamedBuilder<Out | null, In | null, F>;
227
+ /** Merge into the `x-entity` block — the primitive every method above
228
+ * writes through, held to the closed vocabulary. It carries no flag, so
229
+ * `key()`/`identity()`/`fill()` and the rest stay the way to spell one. */
230
+ entity(patch: EntityBlock): this;
231
+ meta(annotations: Annotations & { readonly 'x-entity'?: never }): this;
232
+ }
233
+
234
+ export class EntityWhenBuilder<F extends Flag = never> extends WhenBuilder<F> {
235
+ optional(): EntityWhenBuilder<F | 'optional'>;
236
+ entity(patch: EntityBlock): this;
237
+ then(builder: AnyBuilder): EntityWhenBuilder<F>;
238
+ else(builder: AnyBuilder): EntityWhenBuilder<F>;
239
+ }
240
+
241
+ /**
242
+ * `never()` on this pen. `false` carries no keywords, so the entity
243
+ * vocabulary cannot be written on it: `entity()`, `key()`, `unique()`,
244
+ * `index()`, `version()`, `column()`, `now()`, `updated()`, `fill()`,
245
+ * `compute()` and `meta()` all raise `JL0102` and `identity()` raises
246
+ * `JL0101`. None of the entity vocabulary is declared here, and `meta()`
247
+ * is inherited from `NeverBuilder` with a `never` parameter, so none of
248
+ * those calls compiles either — and `never()` ANSWERS this class, so a
249
+ * caller meets the refusal without narrowing to it. `renamedFrom()` is
250
+ * the one that writes outside the schema and so survives.
251
+ */
252
+ export class EntityNeverBuilder<Out = never, In = Out, F extends Flag = never> extends NeverBuilder<Out, In, F> {
253
+ optional(): EntityNeverBuilder<Out, In, F | 'optional'>;
254
+ /** As on the schema pen: widening to admit `null` is what makes the
255
+ * vocabulary writable, so it answers this pen's base builder. */
256
+ nullable(): EntityBuilder<Out | null, In | null, F>;
257
+ /** The migration hint: a rename is recorded beside the schema, not in it. */
258
+ renamedFrom(name: string): this;
259
+ }
260
+
261
+ /** A `compute()` rule: the document being written at `$`, no externals. */
262
+ export type ComputeRule =
263
+ | ((doc: Expr<Record<string, unknown>>, externals: Record<string, never>) => unknown)
264
+ | { readonly [keyword: string]: unknown };
265
+
266
+ // ————— relations —————
267
+
268
+ export type RelationKind = 'oneToMany' | 'oneToOne' | 'manyToMany';
269
+
270
+ /** A relation member: no type of its own, optional by construction; the
271
+ * phantom `__relation` is what `InferMeta` and `defineModel` read. */
272
+ declare class RelationBuilder<To extends string, Many extends boolean, Kind extends RelationKind>
273
+ extends SchemaBuilder<unknown, unknown, 'optional'> {
274
+ readonly __relation: { readonly to: To; readonly many: Many; readonly kind: Kind };
275
+ }
276
+
277
+ /**
278
+ * A TYPE, not a value: a relation member is a plain builder over an
279
+ * `any` schema at run time, so there is no class to export. It is met
280
+ * through `rel.hasMany()`, `rel.hasOne()` and `rel.belongsToMany()`,
281
+ * which are how one is ever made.
282
+ */
283
+ export type { RelationBuilder };
284
+
285
+ export interface ForeignKeyOptions {
286
+ /** The foreign-key property name. */
287
+ via: string;
288
+ /** Required wherever a foreign key is created — never defaulted silently. */
289
+ onDelete: 'cascade' | 'restrict' | 'setNull';
290
+ }
291
+
292
+ export const rel: {
293
+ /** One-to-many: `{ to, many: true, via, onDelete }`, the FK on the TARGET. */
294
+ hasMany<To extends string>(to: To, options: ForeignKeyOptions): RelationBuilder<To, true, 'oneToMany'>;
295
+ /** One-to-one (and the many-to-one side): `{ to, via, onDelete }`, the FK on the DECLARING entity. */
296
+ hasOne<To extends string>(to: To, options: ForeignKeyOptions): RelationBuilder<To, false, 'oneToOne'>;
297
+ /** Many-to-many: `{ to, many: true, through? }` — a join table. */
298
+ belongsToMany<To extends string>(to: To, options?: { through?: string }): RelationBuilder<To, true, 'manyToMany'>;
299
+ };
300
+
301
+ // ————— collections —————
302
+
303
+ export interface IndexOptions {
304
+ name?: string;
305
+ unique?: boolean;
306
+ derive?: 'geohash' | 'bbox' | 'vector';
307
+ precision?: number;
308
+ dims?: number;
309
+ physical?: 'columns' | 'rtree';
310
+ }
311
+
312
+ /** One index declaration; `D` is the document shape its paths are checked against. */
313
+ export interface IndexSpec<D = unknown> {
314
+ readonly __doc?: D;
315
+ readonly name: string;
316
+ readonly path: string | readonly string[];
317
+ readonly unique?: boolean;
318
+ readonly derive?: 'geohash' | 'bbox' | 'vector';
319
+ readonly precision?: number;
320
+ readonly dims?: number;
321
+ readonly physical?: 'columns' | 'rtree';
322
+ }
323
+
324
+ /** A member path: a captured lambda, a JSONPath string, or a composite of either. */
325
+ export type IndexPath<D> = ((doc: Expr<D>) => unknown) | string | readonly (((doc: Expr<D>) => unknown) | string)[];
326
+
327
+ /** One index over a singular path (`(p) => p.embedding` → `$.embedding`),
328
+ * a composite, or a JSONPath string; default name `by_<segments>`. */
329
+ export function index<D = unknown>(path: IndexPath<D>, options?: IndexOptions): IndexSpec<D>;
330
+
331
+ export interface CollectionOptions<D> {
332
+ /** An RFC 6901 pointer, a captured member path (`(d) => d.id` → `/id`), or `null` (the store allocates). */
333
+ key?: string | ((doc: Expr<D>) => unknown) | null;
334
+ identity?: 'caller' | 'uuid' | 'integer';
335
+ indexes?: readonly IndexSpec<D>[];
336
+ renamedFrom?: string;
337
+ }
338
+
339
+ /** One collection declaration for `defineModel`. */
340
+ export interface CollectionSpec<D = unknown> {
341
+ readonly __doc?: D;
342
+ readonly schema: JsonSchema | boolean;
343
+ readonly key?: string | null;
344
+ readonly identity?: 'caller' | 'uuid' | 'integer';
345
+ readonly indexes?: readonly IndexSpec<D>[];
346
+ readonly 'x-rename'?: string;
347
+ }
348
+
349
+ export function collection<B extends AnyBuilder>(schema: B, options?: CollectionOptions<Infer<B>>): CollectionSpec<Infer<B>>;
350
+
351
+ // ————— the model —————
352
+
353
+ /** An entity declaration whose relation targets are all declared names.
354
+ * (Each member carries `__out`, so the check is never a weak-type one.) */
355
+ type EntityFor<Names> = BuilderLike & {
356
+ readonly __props: { readonly [member: string]: { readonly __out: unknown; readonly __relation?: { readonly to: Names } } };
357
+ };
358
+
359
+ /** The `$model` 0.1 document, carrying the entity builders — and the
360
+ * collection specs — as phantoms: what `InferMeta<>` and the migration
361
+ * pen's name and shape checks read. */
362
+ export interface ModelDocument<E = unknown, C = unknown> {
363
+ readonly __entities?: E;
364
+ readonly __collections?: C;
365
+ readonly $model: '0.1';
366
+ readonly entities?: { readonly [name: string]: { readonly schema: JsonSchema | boolean; readonly 'x-rename'?: string } };
367
+ readonly collections?: { readonly [name: string]: CollectionSpec };
368
+ }
369
+
370
+ /** One `$model` 0.1 document that `openStore` accepts unchanged. */
371
+ export function defineModel<
372
+ E extends { [K in keyof E]: EntityFor<keyof E & string> } = {},
373
+ C extends Record<string, CollectionSpec<any>> = {},
374
+ >(spec: { entities?: E; collections?: C }): ModelDocument<E, C>;
375
+
376
+ // ————— InferMeta —————
377
+
378
+ type PropsOf<B> = B extends { readonly __props: infer P } ? P : never;
379
+ type RelationOf<B> = B extends { readonly __relation: infer R } ? R : never;
380
+ type IsRelation<B> = B extends { readonly __relation: any } ? true : false;
381
+
382
+ /** A closed reading of a value type: every index signature stripped,
383
+ * recursively — what `entityEmitModel` asks emit for (`openObjects: 'closed'`). */
384
+ export type Unopen<T> =
385
+ T extends string | number | boolean | null | undefined ? T :
386
+ T extends readonly unknown[] ? { [I in keyof T]: Unopen<T[I]> } :
387
+ T extends object ? { [K in keyof T as string extends K ? never : K]: Unopen<T[K]> } :
388
+ T;
389
+
390
+ /** Present in the document unless `optional()` — the schema's own `required`. */
391
+ type DocRequired<B> = 'optional' extends FlagsOf<B> ? false : true;
392
+ /** Accepted by `create()`/`add()` unless `optional()` or store-written. */
393
+ type InputRequired<B> = 'optional' extends FlagsOf<B> ? false : 'generated' extends FlagsOf<B> ? false : true;
394
+ type IsKey<B> = 'key' extends FlagsOf<B> ? true : false;
395
+
396
+ type PlainKeys<P> = { [K in keyof P]: IsRelation<P[K]> extends true ? never : K }[keyof P];
397
+ type RelationKeys<P> = { [K in keyof P]: IsRelation<P[K]> extends true ? K : never }[keyof P];
398
+ type KeyKeys<P> = { [K in keyof P]: IsKey<P[K]> extends true ? K : never }[keyof P];
399
+ /** (Guarded by `IsRelation`: a conditional over `never` is not distributive, and `never` extends anything.) */
400
+ type ManyToManyKeys<P> = { [K in keyof P]: IsRelation<P[K]> extends true ? (RelationOf<P[K]> extends { kind: 'manyToMany' } ? K : never) : never }[keyof P];
401
+
402
+ /** A DateTime read is a plain string write (the brand discriminates
403
+ * expressions, never blocks a caller's literal); anything else is itself. */
404
+ type InputMember<T> = [T] extends [DateTime] ? ([DateTime] extends [T] ? string : Unopen<T>) : Unopen<T>;
405
+
406
+ type RelationDoc<E, R> = R extends { to: infer To extends keyof E & string; many: infer M }
407
+ ? (M extends true ? EntityDoc<E, To>[] : EntityDoc<E, To>)
408
+ : never;
409
+
410
+ /** The document shape: closed, relation members optional references. */
411
+ export type EntityDoc<E, K extends keyof E> = Simplify<
412
+ { [P in PlainKeys<PropsOf<E[K]>> as DocRequired<PropsOf<E[K]>[P]> extends true ? P : never]: Unopen<Infer<PropsOf<E[K]>[P]>> } &
413
+ { [P in PlainKeys<PropsOf<E[K]>> as DocRequired<PropsOf<E[K]>[P]> extends true ? never : P]?: Unopen<Infer<PropsOf<E[K]>[P]>> } &
414
+ { [P in RelationKeys<PropsOf<E[K]>>]?: RelationDoc<E, RelationOf<PropsOf<E[K]>[P]>> }>;
415
+
416
+ /** The primitive of a key member, as the store hands it back. */
417
+ type KeyPrimitive<B> = Infer<B> extends string ? string : number;
418
+
419
+ /** The key: one member's primitive, or the composite object. */
420
+ export type EntityKey<E, K extends keyof E> =
421
+ [KeyKeys<PropsOf<E[K]>>] extends [never] ? never
422
+ : IsUnion<KeyKeys<PropsOf<E[K]>>> extends true
423
+ ? { [P in KeyKeys<PropsOf<E[K]>>]: KeyPrimitive<PropsOf<E[K]>[P]> }
424
+ : KeyPrimitive<PropsOf<E[K]>[KeyKeys<PropsOf<E[K]>>]>;
425
+
426
+ type IsUnion<T, U = T> = T extends unknown ? ([U] extends [T] ? false : true) : never;
427
+
428
+ /** What `create()`/`add()` accept: store-written members optional,
429
+ * to-one/to-many projections dropped, many-to-many as key-or-document arrays. */
430
+ export type EntityInput<E, K extends keyof E> = Simplify<
431
+ { [P in PlainKeys<PropsOf<E[K]>> as InputRequired<PropsOf<E[K]>[P]> extends true ? P : never]: InputMember<Infer<PropsOf<E[K]>[P]>> } &
432
+ { [P in PlainKeys<PropsOf<E[K]>> as InputRequired<PropsOf<E[K]>[P]> extends true ? never : P]?: InputMember<Infer<PropsOf<E[K]>[P]>> } &
433
+ { [P in ManyToManyKeys<PropsOf<E[K]>>]?: RelationOf<PropsOf<E[K]>[P]> extends { to: infer To extends keyof E & string }
434
+ ? Array<EntityKey<E, To> | EntityDoc<E, To>> : never }>;
435
+
436
+ /** The relations map: name → `{ entity, doc, many }`. */
437
+ export type EntityRelations<E, K extends keyof E> = {
438
+ [P in RelationKeys<PropsOf<E[K]>>]: RelationOf<PropsOf<E[K]>[P]> extends { to: infer To extends keyof E & string; kind: infer Kind }
439
+ ? { entity: To; doc: EntityDoc<E, To>; many: Kind extends 'oneToOne' ? false : true }
440
+ : never;
441
+ };
442
+
443
+ /** The `EntityMetaMap` a model's phantom implies — `typedStore<InferMeta<typeof model>>(store)`. */
444
+ export type InferMeta<M> = M extends ModelDocument<infer E> ? {
445
+ [K in keyof E]: {
446
+ doc: EntityDoc<E, K>;
447
+ input: EntityInput<E, K>;
448
+ key: EntityKey<E, K>;
449
+ relations: EntityRelations<E, K>;
450
+ };
451
+ } : never;
452
+
453
+ // ————— the factories, entity-aware —————
454
+
455
+ export function string(): EntityStringBuilder;
456
+ export function number(): EntityNumberBuilder;
457
+ export function integer(): EntityNumberBuilder;
458
+ export function boolean(): EntityBooleanBuilder;
459
+ export function nil(): EntityNullBuilder;
460
+ export function literal<const V extends Json>(value: V): EntityBuilder<V, V>;
461
+ export function enumOf<const V extends readonly Json[]>(values: V): EntityBuilder<V[number], V[number]>;
462
+ export function object<P extends Props>(props: P): EntityObjectBuilder<P>;
463
+ export function array<B extends AnyBuilder>(items: B): EntityArrayBuilder<Infer<B>[], Input<B>[]>;
464
+ export function tuple<T extends readonly AnyBuilder[]>(items: readonly [...T]): EntityTupleBuilder<T>;
465
+ export function record<B extends AnyBuilder>(values: B):
466
+ EntityBuilder<{ [key: string]: Infer<B> }, { [key: string]: Input<B> }>;
467
+ export function union<T extends readonly AnyBuilder[]>(options: readonly [...T]):
468
+ EntityBuilder<Infer<T[number]>, Input<T[number]>>;
469
+ export function discriminated<K extends string, T extends readonly BuilderLike<Record<K, unknown>, any, any>[]>(
470
+ key: K, options: readonly [...T]): EntityBuilder<Infer<T[number]>, Input<T[number]>>;
471
+ export function intersection<T extends readonly AnyBuilder[]>(parts: readonly [...T]):
472
+ EntityBuilder<Intersect<{ [I in keyof T]: Infer<T[I]> }>, Intersect<{ [I in keyof T]: Input<T[I]> }>>;
473
+ type Intersect<T extends readonly unknown[]> = T extends readonly [infer H, ...infer R] ? H & Intersect<R> : unknown;
474
+ export function named<B extends AnyBuilder>(name: string, builder: B): EntityNamedBuilder<Infer<B>, Input<B>>;
475
+ export function ref<T = unknown>(name: string): EntityBuilder<T, T>;
476
+ export function lazy<T, I = T>(thunk: () => NamedLike<T, I>): EntityBuilder<T, I>;
477
+ export function any(): EntityBuilder<unknown, unknown>;
478
+ export function never(): EntityNeverBuilder;
479
+ export function when(cond: AnyBuilder): EntityWhenBuilder;
480
+ export function from<T = unknown>(json: JsonSchema | boolean): EntityBuilder<T, T>;
481
+ export function document(root: AnyBuilder, options?: { draft?: '2020-12' }): JsonSchema | boolean;
482
+ export function datetime(): EntityStringBuilder<DateTime, DateTime>;
483
+ export function date(): EntityStringBuilder<DateTime, DateTime>;
484
+ export function time(): EntityStringBuilder;
485
+ export function duration(): EntityStringBuilder;
486
+
487
+ /** The mixin the classes above are built with: a NEW class carrying the vocabulary. */
488
+ export function withEntity<B extends new (...args: any[]) => any>(Base: B): B;
489
+
490
+ export const SCHEMA_BUILDER: unique symbol;
491
+ export function isSchemaBuilder(value: unknown): value is BuilderLike;
492
+ export function schemaOf(value: unknown): unknown;
493
+ export type { CheckRule };