@jarenjs/linq 0.49.2 → 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.
- package/ARCHITECTURE.md +217 -0
- package/README.md +559 -17
- package/docs/APP-PEN.md +1143 -0
- package/docs/CONTRACT-PEN.md +1217 -0
- package/docs/DB-CLIENT.md +814 -0
- package/docs/FLOW-PEN.md +1026 -0
- package/docs/FORMS-PEN.md +940 -0
- package/docs/JSLT-PEN.md +955 -0
- package/docs/LINQ-FORMAT.md +771 -383
- package/docs/MIGRATION-PEN.md +781 -0
- package/docs/MODEL-PEN.md +1083 -0
- package/docs/QUERY-PEN.md +1636 -0
- package/docs/SCHEMA-PEN.md +1218 -0
- package/package.json +57 -4
- package/src/app/action.js +255 -0
- package/src/app/capture.js +63 -0
- package/src/app/define.js +260 -0
- package/src/app/index.js +20 -0
- package/src/app/patch.js +277 -0
- package/src/app/sub.js +106 -0
- package/src/async.js +329 -75
- package/src/capture-root.js +82 -0
- package/src/concurrency.js +9 -4
- package/src/contract/define.js +269 -0
- package/src/contract/http.js +247 -0
- package/src/contract/index.js +23 -0
- package/src/contract/operation.js +342 -0
- package/src/db/handle.js +86 -0
- package/src/db/include.js +316 -0
- package/src/db/index.js +19 -0
- package/src/db/live.js +43 -0
- package/src/db/membership.js +37 -0
- package/src/db/open.js +82 -0
- package/src/document.js +143 -13
- package/src/effect.js +65 -0
- package/src/errors.js +69 -6
- package/src/expression.js +437 -36
- package/src/flow/capture.js +33 -0
- package/src/flow/dag.js +302 -0
- package/src/flow/fsm.js +328 -0
- package/src/flow/index.js +22 -0
- package/src/forms/index.js +43 -0
- package/src/forms/rules.js +170 -0
- package/src/forms/submit.js +177 -0
- package/src/index.js +4 -2
- package/src/jslt/body.js +226 -0
- package/src/jslt/index.js +18 -0
- package/src/jslt/rules.js +207 -0
- package/src/json-boundary.js +90 -0
- package/src/migration/define.js +323 -0
- package/src/migration/index.js +15 -0
- package/src/migration/steps.js +248 -0
- package/src/model/collection.js +171 -0
- package/src/model/define.js +125 -0
- package/src/model/entity.js +307 -0
- package/src/model/index.js +47 -0
- package/src/model/relation.js +85 -0
- package/src/provider.js +137 -20
- package/src/schema/brand.js +31 -0
- package/src/schema/builders.js +526 -0
- package/src/schema/check.js +29 -0
- package/src/schema/emit.js +394 -0
- package/src/schema/factories.js +239 -0
- package/src/schema/index.js +37 -0
- package/src/schema-of.js +24 -0
- package/src/sequence.js +233 -103
- package/src/sources.js +10 -3
- package/types/app.d.ts +293 -0
- package/types/contract.d.ts +371 -0
- package/types/db.d.ts +188 -0
- package/types/flow.d.ts +285 -0
- package/types/forms.d.ts +253 -0
- package/types/index.d.ts +231 -26
- package/types/jslt.d.ts +193 -0
- package/types/migration.d.ts +201 -0
- package/types/model.d.ts +493 -0
- package/types/schema.d.ts +494 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-authored declarations for `@jarenjs/linq/schema` — the schema
|
|
3
|
+
* pen's type contract, kept to the same line as `index.d.ts`: the
|
|
4
|
+
* common path is precisely typed, the exotic path is honestly
|
|
5
|
+
* `unknown`, nothing is ever a WRONG type. Every builder carries two
|
|
6
|
+
* phantoms — `Out`, the shape a document describes AFTER
|
|
7
|
+
* normalization, and `In`, the shape a caller may hand in BEFORE it —
|
|
8
|
+
* and a flag set that decides whether an object member is required on
|
|
9
|
+
* each side. `Infer<>` and `Input<>` read them.
|
|
10
|
+
*
|
|
11
|
+
* Every rule here is emit's reading of the EMITTED document
|
|
12
|
+
* (EMIT-FORMAT §5–§7): a closed object has no index signature, an open
|
|
13
|
+
* one carries `[k: string]: unknown`, a tuple keeps an open rest until
|
|
14
|
+
* `.rest(never())`, a `default()`ed member is present after
|
|
15
|
+
* normalization and optional before it, a `coerce()`d scalar accepts
|
|
16
|
+
* its transport forms on the way in, a constraint (`min`, `pattern`,
|
|
17
|
+
* `format`) never narrows a type, and a date-formatted string is the
|
|
18
|
+
* `DateTime` brand. The three-way agreement — pen type ≡ emit's
|
|
19
|
+
* declaration ≡ the validator's verdicts — is pinned for every corpus
|
|
20
|
+
* entry in `test/consumer/linq-schema.ts` (types) and
|
|
21
|
+
* `test/linq/schema-pen.test.js` (runtime).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { BoolExpr, DateTime, Expr, ObjectExpr, StringExpr, UnknownExpr } from './index.js';
|
|
25
|
+
|
|
26
|
+
/** A JSON value. */
|
|
27
|
+
export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
|
|
28
|
+
|
|
29
|
+
/** A JSON Schema document as the pen writes it: keywords, verbatim. */
|
|
30
|
+
export interface JsonSchema {
|
|
31
|
+
readonly [keyword: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The flags a member carries: left out of `required`; carrying a JSON
|
|
35
|
+
* Schema `default`; and, for the model pen, a store-written default
|
|
36
|
+
* (`generated`) or a `key()` mark. */
|
|
37
|
+
export type Flag = 'optional' | 'defaulted' | 'generated' | 'key';
|
|
38
|
+
|
|
39
|
+
/** Flatten an intersection into one object type (what emit prints). */
|
|
40
|
+
export type Simplify<T> = { [K in keyof T]: T[K] };
|
|
41
|
+
|
|
42
|
+
/** `T`, or `T | null` when the builder is nullable. */
|
|
43
|
+
type Nullify<T, N extends boolean> = N extends true ? T | null : T;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* What every builder looks like to a type that only needs its phantoms
|
|
47
|
+
* — the constraint every builder-taking position uses. Structural on
|
|
48
|
+
* purpose: a builder class is compared by its four carriers, never by
|
|
49
|
+
* its methods, so a subclass with a narrower `check()` parameter still
|
|
50
|
+
* fits wherever "a builder" is asked for.
|
|
51
|
+
*/
|
|
52
|
+
export interface BuilderLike<Out = unknown, In = unknown, F extends Flag = Flag> {
|
|
53
|
+
readonly __out: Out;
|
|
54
|
+
readonly __in: In;
|
|
55
|
+
readonly __flags: F;
|
|
56
|
+
readonly schema: JsonSchema | boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A named builder, by its phantom: what `lazy()` demands. */
|
|
60
|
+
export interface NamedLike<Out = unknown, In = unknown> extends BuilderLike<Out, In, Flag> {
|
|
61
|
+
readonly __named: true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type AnyBuilder = BuilderLike<any, any, any>;
|
|
65
|
+
|
|
66
|
+
/** `Expr<any>` would pick the first conditional arm; the honest top instead. */
|
|
67
|
+
type ValueExpr<Out> = 0 extends (1 & Out) ? UnknownExpr : Expr<Out>;
|
|
68
|
+
|
|
69
|
+
/** The output shape of a builder. */
|
|
70
|
+
export type Infer<B> = B extends BuilderLike<infer O, any, any> ? O : never;
|
|
71
|
+
|
|
72
|
+
/** The accepted (input) shape of a builder — `Infer` with `default()`ed
|
|
73
|
+
* members optional and `coerce()`d scalars widened to their transport
|
|
74
|
+
* forms, exactly as the normalizer's accepted twin reads. */
|
|
75
|
+
export type Input<B> = B extends BuilderLike<any, infer I, any> ? I : never;
|
|
76
|
+
|
|
77
|
+
/** The document type a builder writes. */
|
|
78
|
+
export type SchemaOf<B> = B extends BuilderLike<any, any, any> ? B['schema'] : never;
|
|
79
|
+
|
|
80
|
+
export type FlagsOf<B> = B extends BuilderLike<any, any, infer F> ? F : never;
|
|
81
|
+
|
|
82
|
+
/** Required after normalization: unless `optional()` and not `default()`ed. */
|
|
83
|
+
type OutRequired<B> = 'optional' extends FlagsOf<B> ? ('defaulted' extends FlagsOf<B> ? true : false) : true;
|
|
84
|
+
/** Required before normalization: unless `optional()` or `default()`ed. */
|
|
85
|
+
type InRequired<B> = 'optional' extends FlagsOf<B> ? false : 'defaulted' extends FlagsOf<B> ? false : true;
|
|
86
|
+
|
|
87
|
+
type Props = Record<string, AnyBuilder>;
|
|
88
|
+
|
|
89
|
+
type Members<P extends Props> = Simplify<
|
|
90
|
+
{ [K in keyof P as OutRequired<P[K]> extends true ? K : never]: Infer<P[K]> } &
|
|
91
|
+
{ [K in keyof P as OutRequired<P[K]> extends true ? never : K]?: Infer<P[K]> }>;
|
|
92
|
+
type MembersIn<P extends Props> = Simplify<
|
|
93
|
+
{ [K in keyof P as InRequired<P[K]> extends true ? K : never]: Input<P[K]> } &
|
|
94
|
+
{ [K in keyof P as InRequired<P[K]> extends true ? never : K]?: Input<P[K]> }>;
|
|
95
|
+
|
|
96
|
+
/** Emit widens an index signature to cover every declared member. */
|
|
97
|
+
type Indexed<M, V> = Simplify<M & { [key: string]: V | Required<M>[keyof M] }>;
|
|
98
|
+
|
|
99
|
+
type ObjectShape<P extends Props, Open extends boolean, PV, M> =
|
|
100
|
+
Open extends true ? Simplify<M & { [key: string]: unknown }>
|
|
101
|
+
: [keyof P] extends [never] ? ([PV] extends [never] ? Record<string, never> : { [key: string]: PV })
|
|
102
|
+
: [PV] extends [never] ? M : Indexed<M, PV>;
|
|
103
|
+
|
|
104
|
+
type ObjectOut<P extends Props, Open extends boolean, PV> = ObjectShape<P, Open, PV, Members<P>>;
|
|
105
|
+
type ObjectIn<P extends Props, Open extends boolean, PV> = ObjectShape<P, Open, PV, MembersIn<P>>;
|
|
106
|
+
|
|
107
|
+
type Outs<T extends readonly AnyBuilder[]> = { [I in keyof T]: Infer<T[I]> };
|
|
108
|
+
type Ins<T extends readonly AnyBuilder[]> = { [I in keyof T]: Input<T[I]> };
|
|
109
|
+
|
|
110
|
+
type TupleOf<T extends readonly unknown[], R> = [R] extends [never] ? [...T] : [...T, ...R[]];
|
|
111
|
+
|
|
112
|
+
type Intersect<T extends readonly unknown[]> =
|
|
113
|
+
T extends readonly [infer H, ...infer R] ? H & Intersect<R> : unknown;
|
|
114
|
+
|
|
115
|
+
/** The externals a `check()` rule may name — the two the validator binds. */
|
|
116
|
+
export interface CheckExternals {
|
|
117
|
+
/** The instance root (`$root`): an object whose members are the honest top. */
|
|
118
|
+
readonly root: ObjectExpr<Record<string, unknown>>;
|
|
119
|
+
/** The current location as a JSON pointer string (`$path`). */
|
|
120
|
+
readonly path: StringExpr;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A `check()` rule: a captured callback, or a query document verbatim. */
|
|
124
|
+
export type CheckRule<Out> =
|
|
125
|
+
| ((value: ValueExpr<Out>, externals: CheckExternals) => BoolExpr | boolean)
|
|
126
|
+
| { readonly [keyword: string]: unknown };
|
|
127
|
+
|
|
128
|
+
/** Annotations `meta()` writes verbatim: any key but the ones the pen owns. */
|
|
129
|
+
export type Annotations = { readonly [keyword: string]: Json } & {
|
|
130
|
+
readonly [K in OwnedKeyword]?: never;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** The keywords the pen writes itself (`JL0104` through `meta()`). */
|
|
134
|
+
export type OwnedKeyword =
|
|
135
|
+
| '$schema' | '$id' | '$ref' | '$defs' | 'definitions' | '$anchor' | '$dynamicRef'
|
|
136
|
+
| '$dynamicAnchor' | '$recursiveRef' | '$recursiveAnchor' | '$vocabulary' | '$query'
|
|
137
|
+
| '$data' | 'data' | 'type' | 'nullable' | 'const' | 'enum' | 'properties' | 'required'
|
|
138
|
+
| 'additionalProperties' | 'patternProperties' | 'propertyNames' | 'minProperties'
|
|
139
|
+
| 'maxProperties' | 'dependentRequired' | 'dependentSchemas' | 'dependencies'
|
|
140
|
+
| 'unevaluatedProperties' | 'items' | 'prefixItems' | 'additionalItems' | 'contains'
|
|
141
|
+
| 'minContains' | 'maxContains' | 'minItems' | 'maxItems' | 'uniqueItems'
|
|
142
|
+
| 'unevaluatedItems' | 'minLength' | 'maxLength' | 'pattern' | 'format'
|
|
143
|
+
| 'contentEncoding' | 'contentMediaType' | 'contentSchema' | 'minimum' | 'maximum'
|
|
144
|
+
| 'exclusiveMinimum' | 'exclusiveMaximum' | 'multipleOf' | 'formatMinimum'
|
|
145
|
+
| 'formatMaximum' | 'formatExclusiveMinimum' | 'formatExclusiveMaximum' | 'anyOf'
|
|
146
|
+
| 'oneOf' | 'allOf' | 'not' | 'if' | 'then' | 'else' | 'default' | 'title'
|
|
147
|
+
| 'description' | 'examples' | 'errorMessage' | 'x-coerce' | 'x-trim';
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The immutable base every builder shares. `Out` is the shape after
|
|
151
|
+
* normalization, `In` the shape before it, `F` the member flags.
|
|
152
|
+
*/
|
|
153
|
+
export class SchemaBuilder<Out = unknown, In = Out, F extends Flag = never> {
|
|
154
|
+
protected constructor();
|
|
155
|
+
/** Phantoms: declared, never present at runtime (a builder is never
|
|
156
|
+
* constructed by hand, so nothing ever has to supply them). */
|
|
157
|
+
readonly __out: Out;
|
|
158
|
+
readonly __in: In;
|
|
159
|
+
readonly __flags: F;
|
|
160
|
+
/** The document: assembled once, deep-frozen, `$defs` hoisted. */
|
|
161
|
+
readonly schema: JsonSchema | boolean;
|
|
162
|
+
/** `JSON.stringify(builder)` is the document. */
|
|
163
|
+
toJSON(): JsonSchema | boolean;
|
|
164
|
+
/** As an object member: left out of `required`. */
|
|
165
|
+
optional(): SchemaBuilder<Out, In, F | 'optional'>;
|
|
166
|
+
/** Admit `null`. */
|
|
167
|
+
nullable(): SchemaBuilder<Out | null, In | null, F>;
|
|
168
|
+
/** `default`: present after normalization, optional before it. */
|
|
169
|
+
default(value: Out): SchemaBuilder<Out, In, F | 'defaulted'>;
|
|
170
|
+
/** `description`. */
|
|
171
|
+
describe(text: string): this;
|
|
172
|
+
/** `title`. */
|
|
173
|
+
title(text: string): this;
|
|
174
|
+
/** One more entry of `examples`. */
|
|
175
|
+
example(value: Out): this;
|
|
176
|
+
/** Annotations written verbatim; a pen-owned keyword is refused (`JL0104`). */
|
|
177
|
+
meta(annotations: Annotations): this;
|
|
178
|
+
/** `errorMessage`: the validator's author-supplied message spec. */
|
|
179
|
+
message(spec: Json): this;
|
|
180
|
+
/** A cross-field rule as `$query` (a captured callback, or a document). */
|
|
181
|
+
check(rule: CheckRule<Out>): this;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** `{ type: 'string' }` and the string constraints. */
|
|
185
|
+
export class StringBuilder<Out = string, In = Out, F extends Flag = never> extends SchemaBuilder<Out, In, F> {
|
|
186
|
+
optional(): StringBuilder<Out, In, F | 'optional'>;
|
|
187
|
+
nullable(): StringBuilder<Out | null, In | null, F>;
|
|
188
|
+
default(value: Out): StringBuilder<Out, In, F | 'defaulted'>;
|
|
189
|
+
/** `x-coerce`: numbers and booleans are accepted and become their text. */
|
|
190
|
+
coerce(): StringBuilder<Out, In | number | boolean, F>;
|
|
191
|
+
/** `x-trim`: leading and trailing whitespace is removed before validation. */
|
|
192
|
+
trim(): this;
|
|
193
|
+
/** `minLength`. */
|
|
194
|
+
min(n: number): this;
|
|
195
|
+
/** `maxLength`. */
|
|
196
|
+
max(n: number): this;
|
|
197
|
+
/** `minLength` and `maxLength` together. */
|
|
198
|
+
length(n: number): this;
|
|
199
|
+
/** `pattern`: a regular expression source (a flagless `RegExp` is taken by its source). */
|
|
200
|
+
pattern(source: string | RegExp): this;
|
|
201
|
+
/** `format`: `date-time` and `date` carry the `DateTime` brand. */
|
|
202
|
+
format(name: 'date-time' | 'date'): StringBuilder<DateTime, DateTime, F>;
|
|
203
|
+
format(name: string): this;
|
|
204
|
+
/** `format: 'email'`. */
|
|
205
|
+
email(): this;
|
|
206
|
+
/** `format: 'uuid'`. */
|
|
207
|
+
uuid(): this;
|
|
208
|
+
/** `format: 'uri'`. */
|
|
209
|
+
uri(): this;
|
|
210
|
+
/** `enum` beside `type: 'string'` — a typed enum, the literal union. */
|
|
211
|
+
enumOf<const V extends readonly string[]>(values: V): StringBuilder<V[number], V[number], F>;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** `{ type: 'number' | 'integer' }` and the numeric constraints. */
|
|
215
|
+
export class NumberBuilder<Out = number, In = Out, F extends Flag = never> extends SchemaBuilder<Out, In, F> {
|
|
216
|
+
optional(): NumberBuilder<Out, In, F | 'optional'>;
|
|
217
|
+
nullable(): NumberBuilder<Out | null, In | null, F>;
|
|
218
|
+
default(value: Out): NumberBuilder<Out, In, F | 'defaulted'>;
|
|
219
|
+
/** `x-coerce`: a numeric string is accepted and becomes the number. */
|
|
220
|
+
coerce(): NumberBuilder<Out, In | string, F>;
|
|
221
|
+
/** `minimum`. */
|
|
222
|
+
min(n: number): this;
|
|
223
|
+
/** `maximum`. */
|
|
224
|
+
max(n: number): this;
|
|
225
|
+
/** `exclusiveMinimum`. */
|
|
226
|
+
gt(n: number): this;
|
|
227
|
+
/** `exclusiveMaximum`. */
|
|
228
|
+
lt(n: number): this;
|
|
229
|
+
/** `multipleOf`. */
|
|
230
|
+
multipleOf(n: number): this;
|
|
231
|
+
/** `type: 'integer'` — integer-ness is a documented widening, the type stays `number`. */
|
|
232
|
+
int(): this;
|
|
233
|
+
/** `enum` beside the numeric type — a typed enum, the literal union;
|
|
234
|
+
* `coerce()` then widens `Input` by `string` (emit's accepted reading). */
|
|
235
|
+
enumOf<const V extends readonly number[]>(values: V): NumberBuilder<V[number], V[number] | Exclude<In, number>, F>;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** `{ type: 'boolean' }`. */
|
|
239
|
+
declare class BooleanBuilder<Out = boolean, In = Out, F extends Flag = never> extends SchemaBuilder<Out, In, F> {
|
|
240
|
+
optional(): BooleanBuilder<Out, In, F | 'optional'>;
|
|
241
|
+
nullable(): BooleanBuilder<Out | null, In | null, F>;
|
|
242
|
+
default(value: Out): BooleanBuilder<Out, In, F | 'defaulted'>;
|
|
243
|
+
/** `x-coerce`: `'true'`/`'false'` are accepted and become the boolean. */
|
|
244
|
+
coerce(): BooleanBuilder<Out, In | string, F>;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** `{ type: 'null' }`. */
|
|
248
|
+
declare class NullBuilder<Out = null, In = Out, F extends Flag = never> extends SchemaBuilder<Out, In, F> {
|
|
249
|
+
optional(): NullBuilder<Out, In, F | 'optional'>;
|
|
250
|
+
default(value: Out): NullBuilder<Out, In, F | 'defaulted'>;
|
|
251
|
+
/** `x-coerce`: the empty string is accepted and becomes `null`. */
|
|
252
|
+
coerce(): NullBuilder<Out, In | string, F>;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** `{ type: 'array', items }` and the array constraints. */
|
|
256
|
+
export class ArrayBuilder<Out = unknown[], In = Out, F extends Flag = never> extends SchemaBuilder<Out, In, F> {
|
|
257
|
+
optional(): ArrayBuilder<Out, In, F | 'optional'>;
|
|
258
|
+
nullable(): ArrayBuilder<Out | null, In | null, F>;
|
|
259
|
+
default(value: Out): ArrayBuilder<Out, In, F | 'defaulted'>;
|
|
260
|
+
/** `minItems`. */
|
|
261
|
+
min(n: number): this;
|
|
262
|
+
/** `maxItems`. */
|
|
263
|
+
max(n: number): this;
|
|
264
|
+
/** `minItems` and `maxItems` together. */
|
|
265
|
+
length(n: number): this;
|
|
266
|
+
/** `uniqueItems: true`. */
|
|
267
|
+
unique(): this;
|
|
268
|
+
/** `contains`. */
|
|
269
|
+
contains(builder: AnyBuilder): this;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** `{ type: 'array', prefixItems, minItems }` — every position required,
|
|
273
|
+
* the rest open until `.rest(never())`. */
|
|
274
|
+
export class TupleBuilder<
|
|
275
|
+
T extends readonly AnyBuilder[],
|
|
276
|
+
R = unknown, RIn = R,
|
|
277
|
+
N extends boolean = false, F extends Flag = never,
|
|
278
|
+
> extends SchemaBuilder<Nullify<TupleOf<Outs<T>, R>, N>, Nullify<TupleOf<Ins<T>, RIn>, N>, F> {
|
|
279
|
+
optional(): TupleBuilder<T, R, RIn, N, F | 'optional'>;
|
|
280
|
+
nullable(): TupleBuilder<T, R, RIn, true, F>;
|
|
281
|
+
default(value: Nullify<TupleOf<Outs<T>, R>, N>): TupleBuilder<T, R, RIn, N, F | 'defaulted'>;
|
|
282
|
+
/** `items`: what may follow the positions; `never()` closes the tuple. */
|
|
283
|
+
rest<B extends AnyBuilder>(builder: B): TupleBuilder<T, Infer<B>, Input<B>, N, F>;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** `{ type: 'object', properties, required, additionalProperties: false }`. */
|
|
287
|
+
export class ObjectBuilder<
|
|
288
|
+
P extends Props,
|
|
289
|
+
Open extends boolean = false,
|
|
290
|
+
PV = never, PVIn = PV,
|
|
291
|
+
N extends boolean = false, F extends Flag = never,
|
|
292
|
+
> extends SchemaBuilder<Nullify<ObjectOut<P, Open, PV>, N>, Nullify<ObjectIn<P, Open, PVIn>, N>, F> {
|
|
293
|
+
/** The members, as builders — what a pen over this one reads. */
|
|
294
|
+
readonly __props: P;
|
|
295
|
+
optional(): ObjectBuilder<P, Open, PV, PVIn, N, F | 'optional'>;
|
|
296
|
+
nullable(): ObjectBuilder<P, Open, PV, PVIn, true, F>;
|
|
297
|
+
default(value: Nullify<ObjectOut<P, Open, PV>, N>): ObjectBuilder<P, Open, PV, PVIn, N, F | 'defaulted'>;
|
|
298
|
+
/** Drop `additionalProperties: false`: other members are admitted (`[k: string]: unknown`). */
|
|
299
|
+
open(): ObjectBuilder<P, true, PV, PVIn, N, F>;
|
|
300
|
+
/** `minProperties`. */
|
|
301
|
+
minProperties(n: number): this;
|
|
302
|
+
/** `maxProperties`. */
|
|
303
|
+
maxProperties(n: number): this;
|
|
304
|
+
/** `patternProperties`: on a closed object the index signature carries
|
|
305
|
+
* the pattern values (widened over the members, as emit reads it). */
|
|
306
|
+
patternProperties<M extends Props>(map: M):
|
|
307
|
+
ObjectBuilder<P, Open, Infer<M[keyof M]>, Input<M[keyof M]>, N, F>;
|
|
308
|
+
/** `propertyNames`. */
|
|
309
|
+
propertyNames(builder: AnyBuilder): this;
|
|
310
|
+
/** `dependentRequired`. */
|
|
311
|
+
dependentRequired(map: { readonly [member: string]: readonly string[] }): this;
|
|
312
|
+
/** More members; a later spelling of a name replaces the earlier one. */
|
|
313
|
+
extend<Q extends Props>(props: Q): ObjectBuilder<Simplify<Omit<P, keyof Q> & Q>, Open, PV, PVIn, N, F>;
|
|
314
|
+
/** Only these members. */
|
|
315
|
+
pick<K extends keyof P & string>(keys: readonly K[]): ObjectBuilder<Pick<P, K>, Open, PV, PVIn, N, F>;
|
|
316
|
+
/** All but these members. */
|
|
317
|
+
omit<K extends keyof P & string>(keys: readonly K[]): ObjectBuilder<Omit<P, K>, Open, PV, PVIn, N, F>;
|
|
318
|
+
/** Every member optional. */
|
|
319
|
+
partial(): ObjectBuilder<{ [K in keyof P]: AsOptional<P[K]> }, Open, PV, PVIn, N, F>;
|
|
320
|
+
/** These members (every member, when none are named) required again. */
|
|
321
|
+
required<K extends keyof P & string>(keys?: readonly K[]):
|
|
322
|
+
ObjectBuilder<{ [J in keyof P]: J extends K ? AsRequired<P[J]> : P[J] }, Open, PV, PVIn, N, F>;
|
|
323
|
+
required(): ObjectBuilder<{ [K in keyof P]: AsRequired<P[K]> }, Open, PV, PVIn, N, F>;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
type AsOptional<B> = B extends BuilderLike<infer O, infer I, infer G> ? BuilderLike<O, I, G | 'optional'> : never;
|
|
327
|
+
type AsRequired<B> = B extends BuilderLike<infer O, infer I, infer G> ? BuilderLike<O, I, Exclude<G, 'optional'>> : never;
|
|
328
|
+
|
|
329
|
+
/** A definition: hoisted to `$defs`, referenced where used. The
|
|
330
|
+
* phantom `__named` is what `lazy()` demands. */
|
|
331
|
+
declare class NamedBuilder<Out, In = Out, F extends Flag = never> extends SchemaBuilder<Out, In, F> {
|
|
332
|
+
readonly __named: true;
|
|
333
|
+
optional(): NamedBuilder<Out, In, F | 'optional'>;
|
|
334
|
+
nullable(): NamedBuilder<Out | null, In | null, F>;
|
|
335
|
+
default(value: Out): NamedBuilder<Out, In, F | 'defaulted'>;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* The three builders above are TYPES, not values: `boolean()`, `nil()`
|
|
340
|
+
* and `named()` all answer a plain `SchemaBuilder` at run time, so
|
|
341
|
+
* there is no class to export and importing one as a value would
|
|
342
|
+
* resolve to `undefined`. They are reached through the factory that
|
|
343
|
+
* answers them and extended by the model and forms pens; the census in
|
|
344
|
+
* `test/linq/types.test.js` holds the value half of this file equal to
|
|
345
|
+
* what the module actually exports.
|
|
346
|
+
*/
|
|
347
|
+
export type { BooleanBuilder, NullBuilder, NamedBuilder };
|
|
348
|
+
|
|
349
|
+
/** `{ if, then, else }` — typed `unknown`, as emit reads a conditional. */
|
|
350
|
+
export class WhenBuilder<F extends Flag = never> extends SchemaBuilder<unknown, unknown, F> {
|
|
351
|
+
optional(): WhenBuilder<F | 'optional'>;
|
|
352
|
+
/** The `then` branch. Never `await` a `when()` builder: this method
|
|
353
|
+
* makes it thenable-shaped, and a promise resolution is refused (`JL0101`). */
|
|
354
|
+
then(builder: AnyBuilder): WhenBuilder<F>;
|
|
355
|
+
/** The `else` branch. */
|
|
356
|
+
else(builder: AnyBuilder): WhenBuilder<F>;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* `false` — the schema nothing satisfies. While that IS the document it
|
|
361
|
+
* carries no keywords at all, so every method that would write one is
|
|
362
|
+
* refused with `JL0102` rather than dropped. The signatures below say so
|
|
363
|
+
* on BOTH sides: the return type is `never`, and the parameter is `never`
|
|
364
|
+
* too, so the call does not compile either — a method declared only
|
|
365
|
+
* `: never` still type-checks at its call site, which would leave the
|
|
366
|
+
* refusal a run-time surprise for a caller who narrowed to this class.
|
|
367
|
+
* `nullable()` is the way out and the refusals name it: the node
|
|
368
|
+
* becomes `{ anyOf: [false, { type: 'null' }] }`, which carries keywords
|
|
369
|
+
* like any other, so it answers the base builder rather than this class.
|
|
370
|
+
* `never()` answers one, and this class is how a caller recognises it
|
|
371
|
+
* (`instanceof`).
|
|
372
|
+
*/
|
|
373
|
+
export class NeverBuilder<Out = never, In = Out, F extends Flag = never> extends SchemaBuilder<Out, In, F> {
|
|
374
|
+
/** `false` carries no members, so the boolean schema is the document. */
|
|
375
|
+
readonly schema: false;
|
|
376
|
+
optional(): NeverBuilder<Out, In, F | 'optional'>;
|
|
377
|
+
/** The one method that widens what `false` admits — and so the one
|
|
378
|
+
* that hands back a builder the annotations below are legal on. */
|
|
379
|
+
nullable(): SchemaBuilder<Out | null, In | null, F>;
|
|
380
|
+
/** Refused (`JL0102`) — `false` carries no `default`. */
|
|
381
|
+
default(value: never): never;
|
|
382
|
+
/** Refused (`JL0102`) — `false` carries no `description`. */
|
|
383
|
+
describe(text: never): never;
|
|
384
|
+
/** Refused (`JL0102`) — `false` carries no `title`. */
|
|
385
|
+
title(text: never): never;
|
|
386
|
+
/** Refused (`JL0102`) — `false` carries no `examples`. */
|
|
387
|
+
example(value: never): never;
|
|
388
|
+
/** Refused (`JL0102`) — `false` carries no annotation of any name. */
|
|
389
|
+
meta(annotations: never): never;
|
|
390
|
+
/** Refused (`JL0102`) — `false` carries no `errorMessage`. */
|
|
391
|
+
message(spec: never): never;
|
|
392
|
+
/** Refused (`JL0102`) — nothing reaches a check on `false`. */
|
|
393
|
+
check(rule: never): never;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** `{ type: 'string' }`. */
|
|
397
|
+
export function string(): StringBuilder;
|
|
398
|
+
/** `{ type: 'number' }`. */
|
|
399
|
+
export function number(): NumberBuilder;
|
|
400
|
+
/** `{ type: 'integer' }` — the type is `number`; integer-ness is a documented widening. */
|
|
401
|
+
export function integer(): NumberBuilder;
|
|
402
|
+
/** `{ type: 'boolean' }`. */
|
|
403
|
+
export function boolean(): BooleanBuilder;
|
|
404
|
+
/** `{ type: 'null' }`. */
|
|
405
|
+
export function nil(): NullBuilder;
|
|
406
|
+
/** `{ const: value }`. */
|
|
407
|
+
export function literal<const V extends Json>(value: V): SchemaBuilder<V, V>;
|
|
408
|
+
/** `{ enum: values }`. */
|
|
409
|
+
export function enumOf<const V extends readonly Json[]>(values: V): SchemaBuilder<V[number], V[number]>;
|
|
410
|
+
/** A closed object (`additionalProperties: false`) of named members. */
|
|
411
|
+
export function object<P extends Props>(props: P): ObjectBuilder<P>;
|
|
412
|
+
/** `{ type: 'array', items }`. */
|
|
413
|
+
export function array<B extends AnyBuilder>(items: B): ArrayBuilder<Infer<B>[], Input<B>[]>;
|
|
414
|
+
/** `{ type: 'array', prefixItems, minItems }`. */
|
|
415
|
+
export function tuple<T extends readonly AnyBuilder[]>(items: readonly [...T]): TupleBuilder<T>;
|
|
416
|
+
/** `{ type: 'object', additionalProperties: values }`. */
|
|
417
|
+
export function record<B extends AnyBuilder>(values: B):
|
|
418
|
+
SchemaBuilder<{ [key: string]: Infer<B> }, { [key: string]: Input<B> }>;
|
|
419
|
+
/** `{ anyOf: options }`. A hand-written option is `from<T>(json)`. */
|
|
420
|
+
export function union<T extends readonly AnyBuilder[]>(options: readonly [...T]):
|
|
421
|
+
SchemaBuilder<Outs<T>[number], Ins<T>[number]>;
|
|
422
|
+
/** `{ oneOf: options }`: objects each declaring `key` as a `literal()` or `enumOf()` member. */
|
|
423
|
+
export function discriminated<K extends string, T extends readonly BuilderLike<Record<K, unknown>, any, any>[]>(
|
|
424
|
+
key: K, options: readonly [...T]): SchemaBuilder<Outs<T>[number], Ins<T>[number]>;
|
|
425
|
+
/** `{ allOf: parts }` — object parts must be `open()` (`JL0102` otherwise). */
|
|
426
|
+
export function intersection<T extends readonly AnyBuilder[]>(parts: readonly [...T]):
|
|
427
|
+
SchemaBuilder<Intersect<Outs<T>>, Intersect<Ins<T>>>;
|
|
428
|
+
/** A definition: hoisted to `$defs` and referenced wherever it is used. */
|
|
429
|
+
export function named<B extends AnyBuilder>(name: string, builder: B): NamedBuilder<Infer<B>, Input<B>>;
|
|
430
|
+
/** A reference to a definition by name; `T` is caller-asserted (a
|
|
431
|
+
* name carries no type) — `lazy()` is the inferred spelling. */
|
|
432
|
+
export function ref<T = unknown>(name: string): SchemaBuilder<T, T>;
|
|
433
|
+
/** A deferred reference to a NAMED builder — the recursion spelling.
|
|
434
|
+
* Annotate the recursive constant (`const Node: SchemaBuilder<Node> = …`),
|
|
435
|
+
* as any recursive inference needs. */
|
|
436
|
+
export function lazy<T, I = T>(thunk: () => NamedLike<T, I>): SchemaBuilder<T, I>;
|
|
437
|
+
/** `{}` — anything. */
|
|
438
|
+
export function any(): SchemaBuilder<unknown, unknown>;
|
|
439
|
+
/** `false` — nothing; it carries no annotations (`JL0102`). */
|
|
440
|
+
export function never(): NeverBuilder;
|
|
441
|
+
/** `{ if: cond }`, extended by `.then()`/`.else()`. */
|
|
442
|
+
export function when(cond: AnyBuilder): WhenBuilder;
|
|
443
|
+
/** A hand-written JSON Schema embedded verbatim; `T` is caller-asserted
|
|
444
|
+
* (a JSON literal is never inferred). */
|
|
445
|
+
export function from<T = unknown>(json: JsonSchema | boolean): SchemaBuilder<T, T>;
|
|
446
|
+
/** A standalone document: `$schema` first when a draft is named. */
|
|
447
|
+
export function document(root: AnyBuilder, options?: { draft?: '2020-12' }): JsonSchema | boolean;
|
|
448
|
+
/** `{ type: 'string', format: 'date-time' }` — the `DateTime` brand. */
|
|
449
|
+
export function datetime(): StringBuilder<DateTime, DateTime>;
|
|
450
|
+
/** `{ type: 'string', format: 'date' }` — the `DateTime` brand. */
|
|
451
|
+
export function date(): StringBuilder<DateTime, DateTime>;
|
|
452
|
+
/** `{ type: 'string', format: 'time' }`. */
|
|
453
|
+
export function time(): StringBuilder;
|
|
454
|
+
/** `{ type: 'string', format: 'duration' }`. */
|
|
455
|
+
export function duration(): StringBuilder;
|
|
456
|
+
|
|
457
|
+
/** The brand key every builder answers `true` under. */
|
|
458
|
+
export const SCHEMA_BUILDER: unique symbol;
|
|
459
|
+
/** Whether `value` is a schema builder. */
|
|
460
|
+
export function isSchemaBuilder(value: unknown): value is BuilderLike;
|
|
461
|
+
/** A builder's document, or the value as given. */
|
|
462
|
+
export function schemaOf(value: unknown): unknown;
|
|
463
|
+
/** The JSON boundary every value entering a document crosses (`JL0101`). */
|
|
464
|
+
export function requireJson<T>(value: T, what: string): T;
|
|
465
|
+
|
|
466
|
+
/** A builder class as the factory wiring receives it — the pen supplies
|
|
467
|
+
* eight, and every factory answers an instance of the one it was handed.
|
|
468
|
+
* A pen's classes guard their constructors (`protected`: a builder is
|
|
469
|
+
* never constructed by hand), and no construct signature accepts one, so
|
|
470
|
+
* what this asks for is the class object itself. */
|
|
471
|
+
type BuilderClass = { readonly prototype: unknown };
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Build the named factories above for ONE set of builder classes. The
|
|
475
|
+
* schema pen calls it with the classes in this file; `./model` and
|
|
476
|
+
* `./forms` call it with their subclasses, so the wiring exists exactly
|
|
477
|
+
* once and no subpath patches another's prototype.
|
|
478
|
+
*
|
|
479
|
+
* The answer is typed as the record it is, not as the twenty-six
|
|
480
|
+
* signatures above: what each factory returns depends on the classes
|
|
481
|
+
* handed in, which no signature here can name. A caller building a pen
|
|
482
|
+
* of their own destructures it and annotates what they re-export — the
|
|
483
|
+
* way `./model` and `./forms` do.
|
|
484
|
+
*/
|
|
485
|
+
export function createFactories(classes: {
|
|
486
|
+
readonly Base: BuilderClass;
|
|
487
|
+
readonly String: BuilderClass;
|
|
488
|
+
readonly Number: BuilderClass;
|
|
489
|
+
readonly Array: BuilderClass;
|
|
490
|
+
readonly Tuple: BuilderClass;
|
|
491
|
+
readonly Object: BuilderClass;
|
|
492
|
+
readonly When: BuilderClass;
|
|
493
|
+
readonly Never: BuilderClass;
|
|
494
|
+
}): Readonly<Record<string, (...args: any[]) => unknown>>;
|