@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.
- package/ARCHITECTURE.md +217 -0
- package/README.md +566 -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 +774 -384
- 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 +532 -39
- 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 +389 -41
- 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
package/types/forms.d.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-authored declarations for `@jarenjs/linq/forms` — the schema
|
|
3
|
+
* pen's every name, from subclasses that carry the `x-form` vocabulary,
|
|
4
|
+
* plus `assertOnSubmit()`.
|
|
5
|
+
*
|
|
6
|
+
* A rule is an ANNOTATION, so nothing here changes what a builder
|
|
7
|
+
* INFERS: `Infer<>`, `Input<>` and the flags read exactly as they do on
|
|
8
|
+
* the schema pen, and the subclasses exist only so `form()` survives
|
|
9
|
+
* every chained method. What the rules are typed against is the rule
|
|
10
|
+
* CONTEXT: `c.root` is the whole form document, typed by annotation
|
|
11
|
+
* (`(c: RuleContext<Invoice>) => …`) because a member builder is
|
|
12
|
+
* written before the object that will hold it exists — the same limit
|
|
13
|
+
* the flow pen's `context` meets, and TypeScript's own.
|
|
14
|
+
*
|
|
15
|
+
* Every claim here has a runtime twin in `test/linq/forms-pen.test.js`
|
|
16
|
+
* and a compile-level pin in `test/consumer/linq-app.ts`; FORMS-PEN.md
|
|
17
|
+
* is the normative mapping table.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { BoolExpr, DateTime, MemberExpr, StringExpr } from './index.js';
|
|
21
|
+
import type {
|
|
22
|
+
Annotations, BuilderLike, Flag, Infer, Input, Json, JsonSchema, NamedLike, Simplify,
|
|
23
|
+
SchemaBuilder, StringBuilder, NumberBuilder, BooleanBuilder, NullBuilder, ArrayBuilder,
|
|
24
|
+
TupleBuilder, ObjectBuilder, NamedBuilder, WhenBuilder, NeverBuilder,
|
|
25
|
+
} from './schema.js';
|
|
26
|
+
|
|
27
|
+
type AnyBuilder = BuilderLike<any, any, any>;
|
|
28
|
+
type Props = Record<string, AnyBuilder>;
|
|
29
|
+
type Nullify<T, N extends boolean> = N extends true ? T | null : T;
|
|
30
|
+
|
|
31
|
+
// ————— the rule context —————
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The three names a rule query binds (the forms README, "The rule query
|
|
35
|
+
* context"): the whole document at `$`, the field's own value as the
|
|
36
|
+
* `$value` external and its pointer as `$pointer`. `Doc` is the honest
|
|
37
|
+
* top until the callback is annotated.
|
|
38
|
+
*/
|
|
39
|
+
export interface RuleContext<Doc = unknown, Value = unknown> {
|
|
40
|
+
/** The whole form document (`$`) — cross-field is the point. */
|
|
41
|
+
readonly root: MemberExpr<Doc>;
|
|
42
|
+
/** The field's current value (`$value`); an absent field binds `null`. */
|
|
43
|
+
readonly value: MemberExpr<Value>;
|
|
44
|
+
/** The field's data pointer (`$pointer`), `'/vatId'`. */
|
|
45
|
+
readonly pointer: StringExpr;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A rule: a callback captured over the context, or a query document. */
|
|
49
|
+
export type Rule<Doc = unknown, Value = unknown> =
|
|
50
|
+
| ((context: RuleContext<Doc, Value>) => unknown)
|
|
51
|
+
| { readonly [keyword: string]: unknown };
|
|
52
|
+
|
|
53
|
+
/** The message an `assert` failure renders: an inline template, or a
|
|
54
|
+
* catalog spec (the forms README, "MessageSpec in `x-form.message`"). */
|
|
55
|
+
export type MessageSpec =
|
|
56
|
+
| string
|
|
57
|
+
| { readonly $msgid?: string; readonly message?: string; readonly params?: Record<string, Json> };
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* What `form()` takes — exactly the members `x-form` defines. `preview`
|
|
61
|
+
* is absent on purpose: a field's preview hint is DERIVED from its
|
|
62
|
+
* format by the registry, never authored, and writing it is `JL0102`.
|
|
63
|
+
*/
|
|
64
|
+
export interface FormRules<Doc = unknown, Value = unknown> {
|
|
65
|
+
/** Should the field be shown? Asserted by effective boolean value; fails OPEN. */
|
|
66
|
+
readonly visible?: Rule<Doc, Value>;
|
|
67
|
+
/** Should the field accept input? EBV; fails OPEN. */
|
|
68
|
+
readonly enabled?: Rule<Doc, Value>;
|
|
69
|
+
/** A cross-field preemptive assertion. EBV; fails CLOSED. */
|
|
70
|
+
readonly assert?: Rule<Doc, Value>;
|
|
71
|
+
/** The field's derived value, mapped to plain JSON. */
|
|
72
|
+
readonly computed?: Rule<Doc, Value>;
|
|
73
|
+
/** Shown when `assert` fails. */
|
|
74
|
+
readonly message?: MessageSpec;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ————— the rule-aware builders —————
|
|
78
|
+
|
|
79
|
+
/** The base every untyped kind is built from, plus `form()`. */
|
|
80
|
+
export class FormBuilder<Out = unknown, In = Out, F extends Flag = never> extends SchemaBuilder<Out, In, F> {
|
|
81
|
+
optional(): FormBuilder<Out, In, F | 'optional'>;
|
|
82
|
+
nullable(): FormBuilder<Out | null, In | null, F>;
|
|
83
|
+
default(value: Out): FormBuilder<Out, In, F | 'defaulted'>;
|
|
84
|
+
/** One `x-form` annotation; a second call merges into the same one. */
|
|
85
|
+
form<Doc = unknown>(rules: FormRules<Doc, Out>): this;
|
|
86
|
+
/** As in the schema pen, but `x-form` is owned here. */
|
|
87
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class FormStringBuilder<Out = string, In = Out, F extends Flag = never> extends StringBuilder<Out, In, F> {
|
|
91
|
+
optional(): FormStringBuilder<Out, In, F | 'optional'>;
|
|
92
|
+
nullable(): FormStringBuilder<Out | null, In | null, F>;
|
|
93
|
+
default(value: Out): FormStringBuilder<Out, In, F | 'defaulted'>;
|
|
94
|
+
coerce(): FormStringBuilder<Out, In | number | boolean, F>;
|
|
95
|
+
format(name: 'date-time' | 'date'): FormStringBuilder<DateTime, DateTime, F>;
|
|
96
|
+
format(name: string): this;
|
|
97
|
+
enumOf<const V extends readonly string[]>(values: V): FormStringBuilder<V[number], V[number], F>;
|
|
98
|
+
form<Doc = unknown>(rules: FormRules<Doc, Out>): this;
|
|
99
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class FormNumberBuilder<Out = number, In = Out, F extends Flag = never> extends NumberBuilder<Out, In, F> {
|
|
103
|
+
optional(): FormNumberBuilder<Out, In, F | 'optional'>;
|
|
104
|
+
nullable(): FormNumberBuilder<Out | null, In | null, F>;
|
|
105
|
+
default(value: Out): FormNumberBuilder<Out, In, F | 'defaulted'>;
|
|
106
|
+
coerce(): FormNumberBuilder<Out, In | string, F>;
|
|
107
|
+
enumOf<const V extends readonly number[]>(values: V): FormNumberBuilder<V[number], V[number] | Exclude<In, number>, F>;
|
|
108
|
+
form<Doc = unknown>(rules: FormRules<Doc, Out>): this;
|
|
109
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
declare class FormBooleanBuilder<Out = boolean, In = Out, F extends Flag = never> extends BooleanBuilder<Out, In, F> {
|
|
113
|
+
optional(): FormBooleanBuilder<Out, In, F | 'optional'>;
|
|
114
|
+
nullable(): FormBooleanBuilder<Out | null, In | null, F>;
|
|
115
|
+
default(value: Out): FormBooleanBuilder<Out, In, F | 'defaulted'>;
|
|
116
|
+
coerce(): FormBooleanBuilder<Out, In | string, F>;
|
|
117
|
+
form<Doc = unknown>(rules: FormRules<Doc, Out>): this;
|
|
118
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
declare class FormNullBuilder<Out = null, In = Out, F extends Flag = never> extends NullBuilder<Out, In, F> {
|
|
122
|
+
optional(): FormNullBuilder<Out, In, F | 'optional'>;
|
|
123
|
+
default(value: Out): FormNullBuilder<Out, In, F | 'defaulted'>;
|
|
124
|
+
coerce(): FormNullBuilder<Out, In | string, F>;
|
|
125
|
+
form<Doc = unknown>(rules: FormRules<Doc, Out>): this;
|
|
126
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class FormArrayBuilder<Out = unknown[], In = Out, F extends Flag = never> extends ArrayBuilder<Out, In, F> {
|
|
130
|
+
optional(): FormArrayBuilder<Out, In, F | 'optional'>;
|
|
131
|
+
nullable(): FormArrayBuilder<Out | null, In | null, F>;
|
|
132
|
+
default(value: Out): FormArrayBuilder<Out, In, F | 'defaulted'>;
|
|
133
|
+
form<Doc = unknown>(rules: FormRules<Doc, Out>): this;
|
|
134
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export class FormTupleBuilder<
|
|
138
|
+
T extends readonly AnyBuilder[], R = unknown, RIn = R,
|
|
139
|
+
N extends boolean = false, F extends Flag = never,
|
|
140
|
+
> extends TupleBuilder<T, R, RIn, N, F> {
|
|
141
|
+
optional(): FormTupleBuilder<T, R, RIn, N, F | 'optional'>;
|
|
142
|
+
nullable(): FormTupleBuilder<T, R, RIn, true, F>;
|
|
143
|
+
rest<B extends AnyBuilder>(builder: B): FormTupleBuilder<T, Infer<B>, Input<B>, N, F>;
|
|
144
|
+
form<Doc = unknown>(rules: FormRules<Doc, unknown>): this;
|
|
145
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export class FormObjectBuilder<
|
|
149
|
+
P extends Props, Open extends boolean = false, PV = never, PVIn = PV,
|
|
150
|
+
N extends boolean = false, F extends Flag = never,
|
|
151
|
+
> extends ObjectBuilder<P, Open, PV, PVIn, N, F> {
|
|
152
|
+
optional(): FormObjectBuilder<P, Open, PV, PVIn, N, F | 'optional'>;
|
|
153
|
+
nullable(): FormObjectBuilder<P, Open, PV, PVIn, true, F>;
|
|
154
|
+
open(): FormObjectBuilder<P, true, PV, PVIn, N, F>;
|
|
155
|
+
patternProperties<M extends Props>(map: M): FormObjectBuilder<P, Open, Infer<M[keyof M]>, Input<M[keyof M]>, N, F>;
|
|
156
|
+
extend<Q extends Props>(props: Q): FormObjectBuilder<Simplify<Omit<P, keyof Q> & Q>, Open, PV, PVIn, N, F>;
|
|
157
|
+
pick<K extends keyof P & string>(keys: readonly K[]): FormObjectBuilder<Pick<P, K>, Open, PV, PVIn, N, F>;
|
|
158
|
+
omit<K extends keyof P & string>(keys: readonly K[]): FormObjectBuilder<Omit<P, K>, Open, PV, PVIn, N, F>;
|
|
159
|
+
/** A rule on the ROOT's `visible` is refused by `compileFormRules`:
|
|
160
|
+
* hiding the whole form would null the render tree and its summary. */
|
|
161
|
+
form<Doc = unknown>(rules: FormRules<Doc, unknown>): this;
|
|
162
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
declare class FormNamedBuilder<Out, In = Out, F extends Flag = never> extends NamedBuilder<Out, In, F> {
|
|
166
|
+
optional(): FormNamedBuilder<Out, In, F | 'optional'>;
|
|
167
|
+
nullable(): FormNamedBuilder<Out | null, In | null, F>;
|
|
168
|
+
form<Doc = unknown>(rules: FormRules<Doc, Out>): this;
|
|
169
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* `when()` on this pen. A conditional carries a rule like any other
|
|
174
|
+
* node: `buildFormModel` reads `x-form` off whatever schema it builds a
|
|
175
|
+
* field for, so a `when()` used as an object MEMBER answers a field whose
|
|
176
|
+
* rules evaluate — the runtime twin is in `test/linq/forms-pen.test.js`.
|
|
177
|
+
* `Value` is `unknown` here, as on the object and tuple builders: the
|
|
178
|
+
* node describes a shape rather than a value.
|
|
179
|
+
*/
|
|
180
|
+
export class FormWhenBuilder<F extends Flag = never> extends WhenBuilder<F> {
|
|
181
|
+
optional(): FormWhenBuilder<F | 'optional'>;
|
|
182
|
+
then(builder: AnyBuilder): FormWhenBuilder<F>;
|
|
183
|
+
else(builder: AnyBuilder): FormWhenBuilder<F>;
|
|
184
|
+
form<Doc = unknown>(rules: FormRules<Doc, unknown>): this;
|
|
185
|
+
meta(annotations: Annotations & { readonly 'x-form'?: never }): this;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* `never()` on this pen — and `never()` ANSWERS one, so a caller meets
|
|
190
|
+
* this class without narrowing to it. A rule is an annotation and
|
|
191
|
+
* `false` carries no annotation, so `form()` and `meta()` both raise
|
|
192
|
+
* `JL0102` here: `form()` is not declared at all, and `meta()` is
|
|
193
|
+
* inherited from `NeverBuilder` with a `never` parameter, so neither
|
|
194
|
+
* call compiles either. `nullable()` widens the node and hands back this
|
|
195
|
+
* pen's base builder, where both are legal again.
|
|
196
|
+
*/
|
|
197
|
+
export class FormNeverBuilder<Out = never, In = Out, F extends Flag = never> extends NeverBuilder<Out, In, F> {
|
|
198
|
+
optional(): FormNeverBuilder<Out, In, F | 'optional'>;
|
|
199
|
+
/** As on the schema pen: widening to admit `null` is what makes a rule
|
|
200
|
+
* writable, so it answers this pen's base builder. */
|
|
201
|
+
nullable(): FormBuilder<Out | null, In | null, F>;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ————— the named factories —————
|
|
205
|
+
|
|
206
|
+
export function string(): FormStringBuilder;
|
|
207
|
+
export function number(): FormNumberBuilder;
|
|
208
|
+
export function integer(): FormNumberBuilder;
|
|
209
|
+
export function boolean(): FormBooleanBuilder;
|
|
210
|
+
export function nil(): FormNullBuilder;
|
|
211
|
+
export function literal<const V extends Json>(value: V): FormBuilder<V, V>;
|
|
212
|
+
export function enumOf<const V extends readonly Json[]>(values: V): FormBuilder<V[number], V[number]>;
|
|
213
|
+
export function object<P extends Props>(props: P): FormObjectBuilder<P>;
|
|
214
|
+
export function array<B extends AnyBuilder>(items: B): FormArrayBuilder<Infer<B>[], Input<B>[]>;
|
|
215
|
+
export function tuple<T extends readonly AnyBuilder[]>(items: readonly [...T]): FormTupleBuilder<T>;
|
|
216
|
+
export function record<B extends AnyBuilder>(values: B):
|
|
217
|
+
FormBuilder<{ [key: string]: Infer<B> }, { [key: string]: Input<B> }>;
|
|
218
|
+
export function union<T extends readonly AnyBuilder[]>(options: readonly [...T]):
|
|
219
|
+
FormBuilder<Infer<T[number]>, Input<T[number]>>;
|
|
220
|
+
export function discriminated<K extends string, T extends readonly BuilderLike<Record<K, unknown>, any, any>[]>(
|
|
221
|
+
key: K, options: readonly [...T]): FormBuilder<Infer<T[number]>, Input<T[number]>>;
|
|
222
|
+
export function intersection<T extends readonly AnyBuilder[]>(parts: readonly [...T]):
|
|
223
|
+
FormBuilder<Intersect<{ [I in keyof T]: Infer<T[I]> }>, Intersect<{ [I in keyof T]: Input<T[I]> }>>;
|
|
224
|
+
type Intersect<T extends readonly unknown[]> = T extends readonly [infer H, ...infer R] ? H & Intersect<R> : unknown;
|
|
225
|
+
export function named<B extends AnyBuilder>(name: string, builder: B): FormNamedBuilder<Infer<B>, Input<B>>;
|
|
226
|
+
export function ref<T = unknown>(name: string): FormBuilder<T, T>;
|
|
227
|
+
export function lazy<T, I = T>(thunk: () => NamedLike<T, I>): FormBuilder<T, I>;
|
|
228
|
+
export function any(): FormBuilder<unknown, unknown>;
|
|
229
|
+
export function never(): FormNeverBuilder;
|
|
230
|
+
export function when(cond: AnyBuilder): FormWhenBuilder;
|
|
231
|
+
export function from<T = unknown>(json: JsonSchema | boolean): FormBuilder<T, T>;
|
|
232
|
+
export function document(root: AnyBuilder, options?: { draft?: '2020-12' }): JsonSchema | boolean;
|
|
233
|
+
export function datetime(): FormStringBuilder<DateTime, DateTime>;
|
|
234
|
+
export function date(): FormStringBuilder<DateTime, DateTime>;
|
|
235
|
+
export function time(): FormStringBuilder;
|
|
236
|
+
export function duration(): FormStringBuilder;
|
|
237
|
+
|
|
238
|
+
/** The mixin the classes above are built with: a NEW class carrying `form()`. */
|
|
239
|
+
export function withForm<B extends new (...args: any[]) => any>(Base: B): B;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* The submit twin of a document's `x-form.assert` rules (the forms
|
|
243
|
+
* README's layer 3): every assert copied onto the ROOT as its own
|
|
244
|
+
* `allOf` branch `{ $query, errorMessage }`, so the rule an author wrote
|
|
245
|
+
* once for per-keystroke feedback is what the compiled validator
|
|
246
|
+
* enforces. A document with no assert answers itself.
|
|
247
|
+
*/
|
|
248
|
+
export function assertOnSubmit(root: AnyBuilder | JsonSchema): JsonSchema;
|
|
249
|
+
|
|
250
|
+
export const SCHEMA_BUILDER: unique symbol;
|
|
251
|
+
export function isSchemaBuilder(value: unknown): value is BuilderLike;
|
|
252
|
+
export function schemaOf(value: unknown): unknown;
|
|
253
|
+
export type { BoolExpr };
|