@lunora/values 1.0.0-alpha.7 → 1.0.0-alpha.71
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/dist/index.d.mts +518 -283
- package/dist/index.d.ts +518 -283
- package/dist/index.mjs +1 -5
- package/dist/packem_shared/DEFER_VALIDATION-C0nIiPG-.mjs +1 -0
- package/dist/packem_shared/ValidationError-BdVoGndu.mjs +1 -0
- package/dist/packem_shared/acceptsAbsent-Biqy6mRN.mjs +1 -0
- package/dist/packem_shared/argsToJsonSchema-ATrQFtr_.mjs +1 -0
- package/dist/packem_shared/cyrb53-CFiior4X.mjs +1 -0
- package/dist/packem_shared/isOrWrapsFromValidator-Bf5Axakp.mjs +1 -0
- package/package.json +3 -2
- package/dist/packem_shared/DEFER_VALIDATION-CIKr17sW.mjs +0 -42
- package/dist/packem_shared/ValidationError-CoyFtkxj.mjs +0 -65
- package/dist/packem_shared/argsToJsonSchema-DdHmmamC.mjs +0 -22
- package/dist/packem_shared/isOrWrapsFromValidator-UlWeMhFL.mjs +0 -413
- package/dist/packem_shared/jsonSchemaFromNode-weszUOQG.mjs +0 -82
package/dist/index.d.mts
CHANGED
|
@@ -2,15 +2,15 @@ import { LunoraError } from '@lunora/errors';
|
|
|
2
2
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
3
|
type ValidationPath = ReadonlyArray<number | string>;
|
|
4
4
|
/**
|
|
5
|
-
* Thrown by `validator.parse` (or returned inside `safeParse`) when input does
|
|
6
|
-
* not match the validator's shape. `path` walks from the root to the offending
|
|
7
|
-
* value, e.g. `["users", 0, "email"]`.
|
|
8
|
-
*
|
|
9
|
-
* A `LunoraError` subclass: carries `code: "VALIDATION_ERROR"` and `status: 400`
|
|
10
|
-
* so the runtime/DO transport mappers surface it structurally (a request that
|
|
11
|
-
* fails validation is a 400), while keeping the `name: "ValidationError"` and the
|
|
12
|
-
* `path`/`expected`/`received` diagnostics.
|
|
13
|
-
*/
|
|
5
|
+
* Thrown by `validator.parse` (or returned inside `safeParse`) when input does
|
|
6
|
+
* not match the validator's shape. `path` walks from the root to the offending
|
|
7
|
+
* value, e.g. `["users", 0, "email"]`.
|
|
8
|
+
*
|
|
9
|
+
* A `LunoraError` subclass: carries `code: "VALIDATION_ERROR"` and `status: 400`
|
|
10
|
+
* so the runtime/DO transport mappers surface it structurally (a request that
|
|
11
|
+
* fails validation is a 400), while keeping the `name: "ValidationError"` and the
|
|
12
|
+
* `path`/`expected`/`received` diagnostics.
|
|
13
|
+
*/
|
|
14
14
|
declare class ValidationError extends LunoraError {
|
|
15
15
|
readonly path: ValidationPath;
|
|
16
16
|
readonly expected: string;
|
|
@@ -22,43 +22,50 @@ declare class ValidationError extends LunoraError {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Render a short, diagnostic description of a runtime value for the `received`
|
|
26
|
-
* field of a {@link ValidationError}. Primitives carry their concrete (length-
|
|
27
|
-
* capped) literal so messages distinguish `string "7"` from `number 7`;
|
|
28
|
-
* non-plain objects carry their constructor name (e.g. `Date`) so a class
|
|
29
|
-
* instance is not flattened to a bare `"object"`.
|
|
30
|
-
*
|
|
31
|
-
* Pass `{ literal: false }` to suppress the concrete primitive literal and
|
|
32
|
-
* return only the type tag (`"string"`, `"number"`, `"bigint"`, …). This is used
|
|
33
|
-
* on `.check()` refinement failures — where the value already passed its type
|
|
34
|
-
* check — so a secret-bearing field (password, token) never surfaces its value
|
|
35
|
-
* in the `ValidationError.message`/`received` that goes to the wire and logs.
|
|
36
|
-
*/
|
|
25
|
+
* Render a short, diagnostic description of a runtime value for the `received`
|
|
26
|
+
* field of a {@link ValidationError}. Primitives carry their concrete (length-
|
|
27
|
+
* capped) literal so messages distinguish `string "7"` from `number 7`;
|
|
28
|
+
* non-plain objects carry their constructor name (e.g. `Date`) so a class
|
|
29
|
+
* instance is not flattened to a bare `"object"`.
|
|
30
|
+
*
|
|
31
|
+
* Pass `{ literal: false }` to suppress the concrete primitive literal and
|
|
32
|
+
* return only the type tag (`"string"`, `"number"`, `"bigint"`, …). This is used
|
|
33
|
+
* on `.check()` refinement failures — where the value already passed its type
|
|
34
|
+
* check — so a secret-bearing field (password, token) never surfaces its value
|
|
35
|
+
* in the `ValidationError.message`/`received` that goes to the wire and logs.
|
|
36
|
+
*/
|
|
37
37
|
declare const describeValue: (value: unknown, options?: {
|
|
38
38
|
literal?: boolean;
|
|
39
39
|
}) => string;
|
|
40
40
|
declare const formatPath: (path: ValidationPath) => string;
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* A cyrb53 string hash — fast, well-distributed, 53-bit, with good avalanche
|
|
43
|
+
* behaviour. Public-domain (bryc). Two independent accumulators seeded from
|
|
44
|
+
* `seed` are mixed and combined; we down-fold the 53-bit result to an unsigned
|
|
45
|
+
* 32-bit integer for faker's seed.
|
|
46
|
+
*/
|
|
47
|
+
declare const cyrb53: (text: string, seed?: number) => number;
|
|
48
|
+
/** Branded id type, e.g. `Id<"users">`. */
|
|
42
49
|
type Id<TableName extends string> = string & {
|
|
43
50
|
readonly __table: TableName;
|
|
44
51
|
};
|
|
45
52
|
/**
|
|
46
|
-
* A JSON Schema fragment (Draft 2020-12 / OpenAPI 3.1 compatible). Intentionally
|
|
47
|
-
* a loose bag — a `.check()`/`.meta()` caller contributes keywords like
|
|
48
|
-
* `minLength`/`pattern`/`minimum` that `toJsonSchema` shallow-merges onto the
|
|
49
|
-
* node for the enclosing validator. Mirrors the `JsonSchema` shape exported by
|
|
50
|
-
* `./to-json-schema`; kept structurally identical and local so `v.ts` never
|
|
51
|
-
* imports the converter and the two files stay decoupled.
|
|
52
|
-
*/
|
|
53
|
+
* A JSON Schema fragment (Draft 2020-12 / OpenAPI 3.1 compatible). Intentionally
|
|
54
|
+
* a loose bag — a `.check()`/`.meta()` caller contributes keywords like
|
|
55
|
+
* `minLength`/`pattern`/`minimum` that `toJsonSchema` shallow-merges onto the
|
|
56
|
+
* node for the enclosing validator. Mirrors the `JsonSchema` shape exported by
|
|
57
|
+
* `./to-json-schema`; kept structurally identical and local so `v.ts` never
|
|
58
|
+
* imports the converter and the two files stay decoupled.
|
|
59
|
+
*/
|
|
53
60
|
interface JsonSchemaFragment {
|
|
54
61
|
[keyword: string]: unknown;
|
|
55
62
|
}
|
|
56
63
|
/**
|
|
57
|
-
* Options for a {@link Validator.check} refinement. Lets a predicate carry both
|
|
58
|
-
* a human-facing `message` and an introspectable JSON Schema `schema` fragment
|
|
59
|
-
* (e.g. `{ minLength: 1 }`) so the constraint flows into `toJsonSchema`. The
|
|
60
|
-
* legacy `.check(pred, "message")` string form remains supported.
|
|
61
|
-
*/
|
|
64
|
+
* Options for a {@link Validator.check} refinement. Lets a predicate carry both
|
|
65
|
+
* a human-facing `message` and an introspectable JSON Schema `schema` fragment
|
|
66
|
+
* (e.g. `{ minLength: 1 }`) so the constraint flows into `toJsonSchema`. The
|
|
67
|
+
* legacy `.check(pred, "message")` string form remains supported.
|
|
68
|
+
*/
|
|
62
69
|
interface CheckOptions {
|
|
63
70
|
/** Failure message thrown on the `ValidationError` (default `"value matching refinement"`). */
|
|
64
71
|
message?: string;
|
|
@@ -66,10 +73,10 @@ interface CheckOptions {
|
|
|
66
73
|
schema?: JsonSchemaFragment;
|
|
67
74
|
}
|
|
68
75
|
/**
|
|
69
|
-
* Options for {@link Validator.meta} — pure metadata with no runtime parsing
|
|
70
|
-
* effect, used to enrich the emitted JSON Schema node (description + constraint
|
|
71
|
-
* keywords) without attaching a predicate.
|
|
72
|
-
*/
|
|
76
|
+
* Options for {@link Validator.meta} — pure metadata with no runtime parsing
|
|
77
|
+
* effect, used to enrich the emitted JSON Schema node (description + constraint
|
|
78
|
+
* keywords) without attaching a predicate.
|
|
79
|
+
*/
|
|
73
80
|
interface MetaOptions {
|
|
74
81
|
/** A human description merged onto this validator's JSON Schema node. */
|
|
75
82
|
description?: string;
|
|
@@ -77,39 +84,47 @@ interface MetaOptions {
|
|
|
77
84
|
schema?: JsonSchemaFragment;
|
|
78
85
|
}
|
|
79
86
|
/**
|
|
80
|
-
* Runtime "kind" tag attached to every validator. Codegen and reflective tools
|
|
81
|
-
* use this to inspect the shape without crawling the closure.
|
|
82
|
-
*/
|
|
83
|
-
type ValidatorKind = "any" | "array" | "bigint" | "boolean" | "bytes" | "date" | "from" | "id" | "literal" | "null" | "number" | "object" | "optional" | "record" | "storage" | "string" | "timestamp" | "union";
|
|
87
|
+
* Runtime "kind" tag attached to every validator. Codegen and reflective tools
|
|
88
|
+
* use this to inspect the shape without crawling the closure.
|
|
89
|
+
*/
|
|
90
|
+
type ValidatorKind = "any" | "array" | "bigint" | "boolean" | "bytes" | "date" | "from" | "geoPoint" | "id" | "literal" | "null" | "number" | "object" | "optional" | "record" | "storage" | "string" | "timestamp" | "union";
|
|
84
91
|
interface Validator<T = unknown> extends StandardSchemaV1<T, T> {
|
|
85
92
|
readonly __type: T;
|
|
86
93
|
/**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
* Attach a refinement predicate. The returned validator parses with the
|
|
95
|
+
* original rules first; if the result satisfies `predicate` it passes
|
|
96
|
+
* through, otherwise it throws a {@link ValidationError} carrying
|
|
97
|
+
* `message` (default: `"value matching refinement"`). Multiple `.check()`
|
|
98
|
+
* calls chain — every predicate must return true.
|
|
99
|
+
*
|
|
100
|
+
* The second argument may be a plain message string (legacy form) or a
|
|
101
|
+
* {@link CheckOptions} object that additionally carries a JSON Schema
|
|
102
|
+
* `schema` fragment (e.g. `{ minLength: 1 }`) reflected by `toJsonSchema`.
|
|
103
|
+
*
|
|
104
|
+
* Works in any context — argument validators, column validators, or
|
|
105
|
+
* standalone — so it can encode invariants like
|
|
106
|
+
* `v.number().check(n => n >= 0)`.
|
|
107
|
+
*
|
|
108
|
+
* For the common length/format/range cases, prefer the named shortcuts
|
|
109
|
+
* (`v.string().min(1)`, `.max(n)`, `.length(n)`, `.pattern(re)`, `.email()`,
|
|
110
|
+
* `.url()`; `v.number().min(n)`, `.max(n)`, `.int()`, `.positive()`;
|
|
111
|
+
* `v.array(...).min(n)`, `.max(n)`) — each is sugar over this same
|
|
112
|
+
* `.check(predicate, { schema })` path, so the predicate and the JSON Schema
|
|
113
|
+
* keyword are set together and can never drift apart. `.check()` remains the
|
|
114
|
+
* escape hatch for anything the shortcuts don't cover, e.g.
|
|
115
|
+
* `v.string().check(s => s.length > 0, { message: "non-empty", schema: { minLength: 1 } })`.
|
|
116
|
+
*/
|
|
102
117
|
check: (predicate: (value: T) => boolean, options?: CheckOptions | string) => Validator<T>;
|
|
103
118
|
readonly kind: ValidatorKind;
|
|
104
119
|
/**
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
* Attach pure metadata (description + JSON Schema constraint fragment) with
|
|
121
|
+
* no effect on runtime parsing. The fragment is shallow-merged onto this
|
|
122
|
+
* validator's emitted JSON Schema node, composing with any `.check()`
|
|
123
|
+
* `schema` fragments (later wins on conflicting keys).
|
|
124
|
+
*/
|
|
110
125
|
meta: (options: MetaOptions) => Validator<T>;
|
|
111
|
-
parse: (value: unknown) => T;
|
|
112
|
-
safeParse: (value: unknown) => {
|
|
126
|
+
parse: (value: unknown, options?: ParseOptions) => T;
|
|
127
|
+
safeParse: (value: unknown, options?: ParseOptions) => {
|
|
113
128
|
error: ValidationError;
|
|
114
129
|
ok: false;
|
|
115
130
|
} | {
|
|
@@ -120,10 +135,10 @@ interface Validator<T = unknown> extends StandardSchemaV1<T, T> {
|
|
|
120
135
|
/** Extract the TS type a validator describes (the **select** type). */
|
|
121
136
|
type Infer<V> = V extends Validator<infer T> ? T : never;
|
|
122
137
|
/**
|
|
123
|
-
* Column constraints/defaults collected from the `v.*` modifier chain used
|
|
124
|
-
* inside `defineTable`. Inert in argument position. Persisted on the
|
|
125
|
-
* validator's internal `_meta.column` and mirrored into codegen IR.
|
|
126
|
-
*/
|
|
138
|
+
* Column constraints/defaults collected from the `v.*` modifier chain used
|
|
139
|
+
* inside `defineTable`. Inert in argument position. Persisted on the
|
|
140
|
+
* validator's internal `_meta.column` and mirrored into codegen IR.
|
|
141
|
+
*/
|
|
127
142
|
interface ColumnMeta {
|
|
128
143
|
/** `.$defaultFn(fn)` — default factory; field is optional on insert. */
|
|
129
144
|
defaultFn?: () => unknown;
|
|
@@ -134,24 +149,24 @@ interface ColumnMeta {
|
|
|
134
149
|
/** `.$onUpdateFn(fn)` — recomputed on every patch/replace. */
|
|
135
150
|
onUpdateFn?: () => unknown;
|
|
136
151
|
/**
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
* `.serverDefault(fn)` — a SERVER-trusted value factory. Unlike
|
|
153
|
+
* `.$defaultFn` (which only fills an absent field), this runs on every
|
|
154
|
+
* insert/update and SILENTLY OVERWRITES any client-supplied value with
|
|
155
|
+
* `fn({ auth })`, so the column is never client-controllable (e.g.
|
|
156
|
+
* `ownerId`/`tenantId` stamped from `auth.userId`). Field is optional on
|
|
157
|
+
* insert. The factory runs server-side with the resolved request auth.
|
|
158
|
+
*/
|
|
144
159
|
serverDefault?: (context: ServerDefaultContext) => unknown;
|
|
145
160
|
/** `.unique()` — synthesizes a UNIQUE index. */
|
|
146
161
|
unique?: boolean;
|
|
147
162
|
}
|
|
148
163
|
/**
|
|
149
|
-
* Context handed to a `.serverDefault(fn)` factory at write time. Carries the
|
|
150
|
-
* resolved request identity so a column can be stamped from the caller
|
|
151
|
-
* (`auth.userId`) rather than trusted from the client. Structurally mirrors the
|
|
152
|
-
* `auth` slice of the server's procedure context without depending on
|
|
153
|
-
* `@lunora/server`.
|
|
154
|
-
*/
|
|
164
|
+
* Context handed to a `.serverDefault(fn)` factory at write time. Carries the
|
|
165
|
+
* resolved request identity so a column can be stamped from the caller
|
|
166
|
+
* (`auth.userId`) rather than trusted from the client. Structurally mirrors the
|
|
167
|
+
* `auth` slice of the server's procedure context without depending on
|
|
168
|
+
* `@lunora/server`.
|
|
169
|
+
*/
|
|
155
170
|
interface ServerDefaultContext {
|
|
156
171
|
readonly auth: {
|
|
157
172
|
/** The raw identity claims, or `null` for the anonymous/no-resolver case. */
|
|
@@ -161,9 +176,9 @@ interface ServerDefaultContext {
|
|
|
161
176
|
};
|
|
162
177
|
}
|
|
163
178
|
/**
|
|
164
|
-
* Phantom carrier of a column's select/insert types. Never present at runtime;
|
|
165
|
-
* `defineTable` reads it to derive `$inferSelect` / `$inferInsert`.
|
|
166
|
-
*/
|
|
179
|
+
* Phantom carrier of a column's select/insert types. Never present at runtime;
|
|
180
|
+
* `defineTable` reads it to derive `$inferSelect` / `$inferInsert`.
|
|
181
|
+
*/
|
|
167
182
|
interface Column<TSelect, TInsert> {
|
|
168
183
|
/** Phantom carrier — type-only, never present at runtime. */
|
|
169
184
|
readonly __column: {
|
|
@@ -172,17 +187,24 @@ interface Column<TSelect, TInsert> {
|
|
|
172
187
|
};
|
|
173
188
|
}
|
|
174
189
|
/**
|
|
175
|
-
* A {@link Validator} carrying the chainable column-modifier API. The factories
|
|
176
|
-
* (`v.string()`, …) return this so modifiers are available inside `defineTable`.
|
|
177
|
-
* `TSelect` is the read type; `TInsert` is the write type (modifiers may make it
|
|
178
|
-
* `| undefined`, marking the field optional on insert).
|
|
179
|
-
*/
|
|
190
|
+
* A {@link Validator} carrying the chainable column-modifier API. The factories
|
|
191
|
+
* (`v.string()`, …) return this so modifiers are available inside `defineTable`.
|
|
192
|
+
* `TSelect` is the read type; `TInsert` is the write type (modifiers may make it
|
|
193
|
+
* `| undefined`, marking the field optional on insert).
|
|
194
|
+
*/
|
|
180
195
|
interface ColumnValidator<TSelect, TInsert> extends Column<TSelect, TInsert>, Validator<TSelect> {
|
|
181
196
|
/** Default factory applied in the write layer; field becomes optional on insert. */
|
|
182
197
|
$defaultFn: (function_: () => TSelect) => ColumnValidator<TSelect, TInsert | undefined>;
|
|
183
198
|
/** Recompute the field on every patch/replace when not explicitly provided. */
|
|
184
199
|
$onUpdateFn: (function_: () => TSelect) => ColumnValidator<TSelect, TInsert>;
|
|
185
|
-
/**
|
|
200
|
+
/**
|
|
201
|
+
* Override the type `Infer<>` gives this validator without changing runtime
|
|
202
|
+
* parsing (e.g. `v.string().$type<Brand>()`). An unchecked assertion: the
|
|
203
|
+
* parser still accepts any value of the base kind, so codegen's `Doc_*` /
|
|
204
|
+
* `Insert_*` / procedure-arg types keep the base type (`string`) — the one
|
|
205
|
+
* the stored value is actually guaranteed to have. For a document id use
|
|
206
|
+
* `v.id("users")`, which is checked.
|
|
207
|
+
*/
|
|
186
208
|
$type: <TOverride>() => ColumnValidator<TOverride, TOverride>;
|
|
187
209
|
/** Refinement predicate run after parsing — see {@link Validator.check}. Chainable; preserves column modifiers. */
|
|
188
210
|
check: (predicate: (value: TSelect) => boolean, options?: CheckOptions | string) => ColumnValidator<TSelect, TInsert>;
|
|
@@ -193,38 +215,118 @@ interface ColumnValidator<TSelect, TInsert> extends Column<TSelect, TInsert>, Va
|
|
|
193
215
|
/** Allow SQL NULL — widens the select type to `T | null`. */
|
|
194
216
|
nullable: () => ColumnValidator<null | TSelect, null | TInsert>;
|
|
195
217
|
/**
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
218
|
+
* Stamp this column SERVER-side from the request auth on every write,
|
|
219
|
+
* overwriting any client-supplied value. The field becomes optional on
|
|
220
|
+
* insert (the server fills it). Use for owner/tenant columns that must never
|
|
221
|
+
* be client-controllable — e.g. `v.string().serverDefault(({ auth }) => auth.userId)`.
|
|
222
|
+
*/
|
|
201
223
|
serverDefault: (function_: (context: ServerDefaultContext) => TSelect) => ColumnValidator<TSelect, TInsert | undefined>;
|
|
202
224
|
/** Enforce a UNIQUE constraint (synthesizes a unique index). */
|
|
203
225
|
unique: () => ColumnValidator<TSelect, TInsert>;
|
|
204
226
|
}
|
|
205
227
|
/**
|
|
206
|
-
* A time-valued {@link ColumnValidator} (epoch milliseconds). Adds
|
|
207
|
-
* {@link TimestampColumnValidator.defaultNow} so the field can default to the
|
|
208
|
-
* insert-time clock.
|
|
209
|
-
*/
|
|
228
|
+
* A time-valued {@link ColumnValidator} (epoch milliseconds). Adds
|
|
229
|
+
* {@link TimestampColumnValidator.defaultNow} so the field can default to the
|
|
230
|
+
* insert-time clock.
|
|
231
|
+
*/
|
|
210
232
|
interface TimestampColumnValidator extends ColumnValidator<number, number> {
|
|
211
233
|
/** Default to the current epoch-ms (`Date.now()`) at insert time; field becomes optional on insert. */
|
|
212
234
|
defaultNow: () => ColumnValidator<number, number | undefined>;
|
|
213
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* A {@link ColumnValidator} for `v.string()` with ergonomic refinement
|
|
238
|
+
* shortcuts. Each method is sugar over `.check(predicate, { schema })` — it
|
|
239
|
+
* sets the runtime predicate AND the matching JSON Schema keyword in one call
|
|
240
|
+
* so the two can never drift (see the module-level `.check()` docstring for
|
|
241
|
+
* the underlying two-part mechanism). Chainable among each other; `.check()`/
|
|
242
|
+
* `.meta()` compose after them but the return narrows to the base
|
|
243
|
+
* {@link ColumnValidator} (no further refinement shortcuts after that point).
|
|
244
|
+
*/
|
|
245
|
+
interface StringColumnValidator extends ColumnValidator<string, string> {
|
|
246
|
+
/** Require a valid email address (`format: "email"`). Uses a pragmatic (non-RFC-5322-exhaustive) pattern. */
|
|
247
|
+
email: () => StringColumnValidator;
|
|
248
|
+
/** Require exactly `length` characters (`minLength`/`maxLength` both set to `length`). */
|
|
249
|
+
length: (length: number) => StringColumnValidator;
|
|
250
|
+
/** Require at most `max` characters (`maxLength`). */
|
|
251
|
+
max: (max: number) => StringColumnValidator;
|
|
252
|
+
/** Require at least `min` characters (`minLength`). */
|
|
253
|
+
min: (min: number) => StringColumnValidator;
|
|
254
|
+
/**
|
|
255
|
+
* Require the value to match `pattern` (JSON Schema `pattern` set from
|
|
256
|
+
* `pattern.source`; emitted `pattern` does not encode `pattern.flags` — a
|
|
257
|
+
* case-insensitive `/i` regex, for instance, emits a flag-less JSON Schema
|
|
258
|
+
* pattern). A `g`/`y`-flagged `pattern` is tested statelessly (its
|
|
259
|
+
* `lastIndex` is never consulted or advanced), so a single validator
|
|
260
|
+
* instance gives the same answer for the same input on every call.
|
|
261
|
+
*/
|
|
262
|
+
pattern: (pattern: RegExp) => StringColumnValidator;
|
|
263
|
+
/**
|
|
264
|
+
* Require a valid `http:`/`https:` URL (`format: "uri"`). Parseable by the
|
|
265
|
+
* WHATWG `URL` constructor is necessary but not sufficient — schemes such as
|
|
266
|
+
* `javascript:`, `data:`, `file:`, and `vbscript:` all parse successfully but
|
|
267
|
+
* are rejected here, since accepting them lets a validated "link" field carry
|
|
268
|
+
* an XSS payload straight into an anchor's `href` or `window.location` at
|
|
269
|
+
* render time.
|
|
270
|
+
*/
|
|
271
|
+
url: () => StringColumnValidator;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* A {@link ColumnValidator} for `v.number()` with ergonomic refinement
|
|
275
|
+
* shortcuts — see {@link StringColumnValidator} for the delegation pattern.
|
|
276
|
+
*/
|
|
277
|
+
interface NumberColumnValidator extends ColumnValidator<number, number> {
|
|
278
|
+
/** Require an integer value (`Number.isInteger`); JSON Schema `type` narrows to `"integer"`. */
|
|
279
|
+
int: () => NumberColumnValidator;
|
|
280
|
+
/** Require at most `max` (`maximum`). */
|
|
281
|
+
max: (max: number) => NumberColumnValidator;
|
|
282
|
+
/** Require at least `min` (`minimum`). */
|
|
283
|
+
min: (min: number) => NumberColumnValidator;
|
|
284
|
+
/** Require a value strictly greater than zero (`exclusiveMinimum: 0`). */
|
|
285
|
+
positive: () => NumberColumnValidator;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* A {@link ColumnValidator} for `v.object(...)`, carrying the `.strip()` opt-out.
|
|
289
|
+
*/
|
|
290
|
+
interface ObjectColumnValidator<T> extends ColumnValidator<T, T> {
|
|
291
|
+
/**
|
|
292
|
+
* Drop keys this shape does not declare, even under `.output()`.
|
|
293
|
+
*
|
|
294
|
+
* Only meaningful there — input parsing strips already. Say it when the
|
|
295
|
+
* narrowing is deliberate (trimming an internal field off a row before it
|
|
296
|
+
* reaches a client); without it, an undeclared key on the way OUT is an
|
|
297
|
+
* error, because the alternative is deleting server data silently.
|
|
298
|
+
*/
|
|
299
|
+
strip: () => ObjectColumnValidator<T>;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* A {@link ColumnValidator} for `v.array(...)` with ergonomic length-refinement
|
|
303
|
+
* shortcuts — see {@link StringColumnValidator} for the delegation pattern.
|
|
304
|
+
*/
|
|
305
|
+
interface ArrayColumnValidator<TItem> extends ColumnValidator<TItem[], TItem[]> {
|
|
306
|
+
/** Require at most `max` items (`maxItems`). */
|
|
307
|
+
max: (max: number) => ArrayColumnValidator<TItem>;
|
|
308
|
+
/** Require at least `min` items (`minItems`). */
|
|
309
|
+
min: (min: number) => ArrayColumnValidator<TItem>;
|
|
310
|
+
}
|
|
214
311
|
/** The type a validator/column presents on **select** (reads). */
|
|
215
312
|
type InferSelect<V> = V extends Validator<infer T> ? T : never;
|
|
216
313
|
/** The type a validator/column accepts on **insert** (writes). */
|
|
217
314
|
type InferInsert<V> = V extends Column<unknown, infer I> ? I : V extends Validator<infer T> ? T : never;
|
|
218
315
|
/** Derive the read shape of a table's column map. */
|
|
219
|
-
type SelectShape<S extends Record<string, Validator>> = { [K in keyof S]: InferSelect<S[K]
|
|
220
|
-
/**
|
|
221
|
-
* Derive the write shape of a table's column map. Columns whose insert type
|
|
222
|
-
* includes `undefined` (via `.default()` / `.$defaultFn()` / `v.optional`)
|
|
223
|
-
* become optional keys.
|
|
224
|
-
*/
|
|
225
|
-
type InsertShape<S extends Record<string, Validator>> = { [K in keyof S as undefined extends InferInsert<S[K]> ? K : never]?: Exclude<InferInsert<S[K]>, undefined
|
|
226
|
-
|
|
227
|
-
|
|
316
|
+
type SelectShape<S extends Record<string, Validator>> = { [K in keyof S]: InferSelect<S[K]>; };
|
|
317
|
+
/**
|
|
318
|
+
* Derive the write shape of a table's column map. Columns whose insert type
|
|
319
|
+
* includes `undefined` (via `.default()` / `.$defaultFn()` / `v.optional`)
|
|
320
|
+
* become optional keys.
|
|
321
|
+
*/
|
|
322
|
+
type InsertShape<S extends Record<string, Validator>> = { [K in keyof S as undefined extends InferInsert<S[K]> ? K : never]?: Exclude<InferInsert<S[K]>, undefined>; } & { [K in keyof S as undefined extends InferInsert<S[K]> ? never : K]: InferInsert<S[K]>; };
|
|
323
|
+
/** Options for {@link Validator.parse} / {@link Validator.safeParse}. */
|
|
324
|
+
interface ParseOptions {
|
|
325
|
+
/** See {@link ParseContext.rejectUnknownKeys}. */
|
|
326
|
+
rejectUnknownKeys?: boolean;
|
|
327
|
+
}
|
|
328
|
+
declare const string: () => StringColumnValidator;
|
|
329
|
+
declare const number: () => NumberColumnValidator;
|
|
228
330
|
/** Epoch-millisecond timestamp (`number`). Pair with `.defaultNow()` for an insert-time clock. */
|
|
229
331
|
declare const timestamp: () => TimestampColumnValidator;
|
|
230
332
|
/** Calendar date stored as an epoch-millisecond `number`. Pair with `.defaultNow()` for an insert-time clock. */
|
|
@@ -235,77 +337,174 @@ declare const nullValidator: () => ColumnValidator<null, null>;
|
|
|
235
337
|
declare const bytes: () => ColumnValidator<ArrayBuffer, ArrayBuffer>;
|
|
236
338
|
declare const id: <TableName extends string>(tableName: TableName) => ColumnValidator<Id<TableName>, Id<TableName>>;
|
|
237
339
|
/**
|
|
238
|
-
* A reference to a stored R2 object: the column holds the object's **key** (a
|
|
239
|
-
* string), the same key `@lunora/storage` puts/gets by. Functionally it parses
|
|
240
|
-
* like `v.string()`, but the distinct `"storage"` kind lets codegen and the
|
|
241
|
-
* studio join the data model to R2 — the file browser uses it to show which
|
|
242
|
-
* record owns a file and to flag orphaned objects no row references. The
|
|
243
|
-
* optional `bucket` names the typed bucket the key lives in (for app-context
|
|
244
|
-
* signed URLs); omit it for the app's default bucket.
|
|
245
|
-
*/
|
|
340
|
+
* A reference to a stored R2 object: the column holds the object's **key** (a
|
|
341
|
+
* string), the same key `@lunora/storage` puts/gets by. Functionally it parses
|
|
342
|
+
* like `v.string()`, but the distinct `"storage"` kind lets codegen and the
|
|
343
|
+
* studio join the data model to R2 — the file browser uses it to show which
|
|
344
|
+
* record owns a file and to flag orphaned objects no row references. The
|
|
345
|
+
* optional `bucket` names the typed bucket the key lives in (for app-context
|
|
346
|
+
* signed URLs); omit it for the app's default bucket.
|
|
347
|
+
*/
|
|
246
348
|
declare const storage: (bucket?: string) => ColumnValidator<string, string>;
|
|
349
|
+
/**
|
|
350
|
+
* A geographic point — latitude/longitude in decimal degrees (WGS84). The value
|
|
351
|
+
* a `v.geoPoint()` column reads/writes. Stored as a JSON object alongside the
|
|
352
|
+
* row; a `.geoIndex(name, { field })` on the table maintains a geohash companion
|
|
353
|
+
* so `withGeoIndex(name, q => q.near(point, radius) | q.within(bbox))` can answer
|
|
354
|
+
* proximity/bounding-box reads.
|
|
355
|
+
*/
|
|
356
|
+
interface GeoPoint {
|
|
357
|
+
/** Latitude in decimal degrees, `-90 … 90`. */
|
|
358
|
+
lat: number;
|
|
359
|
+
/** Longitude in decimal degrees, `-180 … 180`. */
|
|
360
|
+
lng: number;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* A latitude/longitude point (WGS84 decimal degrees). Parses an object with
|
|
364
|
+
* finite `lat` ∈ `[-90, 90]` and `lng` ∈ `[-180, 180]`; any other shape or an
|
|
365
|
+
* out-of-range coordinate throws a {@link ValidationError}. Pair with a table's
|
|
366
|
+
* `.geoIndex(name, { field })` to enable `near` / `within` reads.
|
|
367
|
+
*/
|
|
368
|
+
declare const geoPoint: () => ColumnValidator<GeoPoint, GeoPoint>;
|
|
247
369
|
declare const literal: <T extends bigint | boolean | number | string | null>(literalValue: T) => ColumnValidator<T, T>;
|
|
248
|
-
declare const array: <V extends Validator>(inner: V) =>
|
|
249
|
-
/**
|
|
250
|
-
* Split a value-type map into optional + required keys: any member whose value
|
|
251
|
-
* type includes `undefined` becomes an optional key. The single optionality rule
|
|
252
|
-
* shared by object-shape inference ({@link ObjectShapeType}) and args-map
|
|
253
|
-
* inference (`InferValidatorMap` in `./validator-map`), so the two can never
|
|
254
|
-
* drift. (`InsertShape` stays separate — it additionally `Exclude`s `undefined`
|
|
255
|
-
* from the optional value, a deliberate insert-type difference.)
|
|
256
|
-
|
|
257
|
-
|
|
370
|
+
declare const array: <V extends Validator>(inner: V) => ArrayColumnValidator<Infer<V>>;
|
|
371
|
+
/**
|
|
372
|
+
* Split a value-type map into optional + required keys: any member whose value
|
|
373
|
+
* type includes `undefined` becomes an optional key. The single optionality rule
|
|
374
|
+
* shared by object-shape inference ({@link ObjectShapeType}) and args-map
|
|
375
|
+
* inference (`InferValidatorMap` in `./validator-map`), so the two can never
|
|
376
|
+
* drift. (`InsertShape` stays separate — it additionally `Exclude`s `undefined`
|
|
377
|
+
* from the optional value, a deliberate insert-type difference.)
|
|
378
|
+
*
|
|
379
|
+
* `undefined extends M[K]` is the type-level spelling of "the parser accepts
|
|
380
|
+
* this field absent" — the same question `acceptsAbsent` in
|
|
381
|
+
* `./json-schema-core` answers structurally. It is broader than
|
|
382
|
+
* `v.optional(...)` on purpose: `v.any()` returns its input unchanged, so it
|
|
383
|
+
* parses an absent field happily, and a `v.union(...)` with an `any`/optional
|
|
384
|
+
* member does too. Anything narrower would mark a key required that the runtime
|
|
385
|
+
* does not require — which is how a generated `_generated/api.ts` came to reject
|
|
386
|
+
* args the server accepts (issue #688).
|
|
387
|
+
*/
|
|
388
|
+
type OptionalizeShape<M> = { [K in keyof M as undefined extends M[K] ? K : never]?: M[K]; } & { [K in keyof M as undefined extends M[K] ? never : K]: M[K]; };
|
|
258
389
|
type ObjectShape = Record<string, Validator>;
|
|
259
|
-
type ObjectShapeType<S extends ObjectShape> = OptionalizeShape<{ [K in keyof S]: Infer<S[K]
|
|
260
|
-
declare const objectValidator: <S extends ObjectShape>(shape: S) =>
|
|
390
|
+
type ObjectShapeType<S extends ObjectShape> = OptionalizeShape<{ [K in keyof S]: Infer<S[K]>; }>;
|
|
391
|
+
declare const objectValidator: <S extends ObjectShape>(shape: S, stripUnknown?: boolean) => ObjectColumnValidator<ObjectShapeType<S>>;
|
|
261
392
|
declare const record: <K extends Validator<string>, V extends Validator>(keyValidator: K, valueValidator: V) => ColumnValidator<Record<Infer<K>, Infer<V>>, Record<Infer<K>, Infer<V>>>;
|
|
262
393
|
declare const union: <Vs extends ReadonlyArray<Validator>>(...members: Vs) => ColumnValidator<Infer<Vs[number]>, Infer<Vs[number]>>;
|
|
263
394
|
declare const optional: <V extends Validator>(inner: V) => ColumnValidator<Infer<V> | undefined, Infer<V> | undefined>;
|
|
395
|
+
/** Every member of a shape made optional — the result of {@link partial}. */
|
|
396
|
+
type PartialShape<S extends ObjectShape> = { [K in keyof S]: ColumnValidator<Infer<S[K]> | undefined, Infer<S[K]> | undefined>; };
|
|
397
|
+
/**
|
|
398
|
+
* Wrap every member of a shape in {@link optional}.
|
|
399
|
+
*
|
|
400
|
+
* A patch-style procedure takes "any subset of these columns" — the same shape
|
|
401
|
+
* the table declares, with nothing required. Spelling that out by hand means a
|
|
402
|
+
* second copy of the shape that has to be kept in step with the first, and the
|
|
403
|
+
* failure when it drifts is quiet: a column added to the table is simply not
|
|
404
|
+
* accepted by the patch, and nothing typechecks it against the table.
|
|
405
|
+
*
|
|
406
|
+
* Takes and returns a *shape record* rather than a `v.object(...)`, so the one
|
|
407
|
+
* function serves both positions — a procedure's args map (`.input(...)`) and a
|
|
408
|
+
* nested object (`v.object(v.partial(shape))`).
|
|
409
|
+
*
|
|
410
|
+
* A member that is already `v.optional(...)` is passed through rather than
|
|
411
|
+
* double-wrapped, so this is idempotent.
|
|
412
|
+
*
|
|
413
|
+
* **Pass the fields a caller may patch, not a whole table's shape.** Handing this
|
|
414
|
+
* `schema.tables.x.shape` makes every present and *future* column client-writable:
|
|
415
|
+
* add `ownerId`, `role`, or `isVerified` to the table later and it silently joins
|
|
416
|
+
* the patch, with no diff on the procedure to review. Row-level policies do not
|
|
417
|
+
* close that — they decide which ROW you may write, not which fields — so the
|
|
418
|
+
* allow-list has to be here.
|
|
419
|
+
* @example
|
|
420
|
+
* ```ts
|
|
421
|
+
* const editable = { body: v.string(), title: v.string() };
|
|
422
|
+
*
|
|
423
|
+
* mutation.input({ id: v.id("threads"), ...v.partial(editable) }).mutation(…)
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
declare const partial: <S extends ObjectShape>(shape: S) => PartialShape<S>;
|
|
264
427
|
declare const any: () => ColumnValidator<unknown, unknown>;
|
|
265
428
|
/**
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* `
|
|
293
|
-
|
|
429
|
+
* The type a Standard Schema v1 object validates TO — what a read gets back.
|
|
430
|
+
*
|
|
431
|
+
* Defer to the spec's own helper rather than matching `~standard.types`: the spec
|
|
432
|
+
* declares that property optional (it is a phantom that never exists at runtime),
|
|
433
|
+
* so every real library types it as a union with `undefined`, and a hand-written
|
|
434
|
+
* `extends { output: infer O }` misses all of them. A schema that declares no
|
|
435
|
+
* `types` still resolves through the constraint to `unknown`.
|
|
436
|
+
*/
|
|
437
|
+
type InferStandardOutput<S extends StandardSchemaV1> = StandardSchemaV1.InferOutput<S>;
|
|
438
|
+
/**
|
|
439
|
+
* The type a Standard Schema v1 object validates FROM — what a write supplies.
|
|
440
|
+
*
|
|
441
|
+
* Distinct from {@link InferStandardOutput} only for a transforming schema
|
|
442
|
+
* (`z.string().transform(…)`, `z.coerce.number()`), where the value handed in is
|
|
443
|
+
* not the value stored. Identical for everything else.
|
|
444
|
+
*/
|
|
445
|
+
type InferStandardInput<S extends StandardSchemaV1> = StandardSchemaV1.InferInput<S>;
|
|
446
|
+
/**
|
|
447
|
+
* Wrap any Standard Schema v1 validator (`zod`, `valibot`, `arktype`, …) so it
|
|
448
|
+
* can be used as an args validator in `query`/`mutation`/`action`, or as a table
|
|
449
|
+
* column. The wrapped validator's output type is inferred from
|
|
450
|
+
* `~standard.types.output` when declared; falls back to `unknown` when the
|
|
451
|
+
* schema omits the types field.
|
|
452
|
+
*
|
|
453
|
+
* **Columns are stored by the value's runtime type.** A shard row is a JSON
|
|
454
|
+
* document, so a `v.from()` column needs no SQL type of its own. For a
|
|
455
|
+
* `.global()` table it maps to a TEXT column holding whatever the encoder
|
|
456
|
+
* produces: a scalar is written verbatim (a `v.from(z.string())` column holds a
|
|
457
|
+
* bare `hello`, not `"hello"`), and an object or array is JSON-encoded. That is
|
|
458
|
+
* the same rule `v.union` and `v.any` follow, and it is why the column cannot be
|
|
459
|
+
* a Postgres/MySQL `JSON` column — a bare `hello` is not valid JSON.
|
|
460
|
+
*
|
|
461
|
+
* The consequence worth knowing: a stored *string* that itself looks like JSON
|
|
462
|
+
* (`'{"a":1}'`) is ambiguous on read and decodes to the parsed object. Declare
|
|
463
|
+
* the column with a concrete `v.*` type when the plain column type matters — for
|
|
464
|
+
* a comparison index, or to avoid that ambiguity.
|
|
465
|
+
*
|
|
466
|
+
* **Not seedable.** `@lunora/seed` cannot introspect an external schema to
|
|
467
|
+
* invent a valid value, so it refuses a `v.from()` column with an actionable
|
|
468
|
+
* error rather than generating one that fails validation on insert.
|
|
469
|
+
*
|
|
470
|
+
* **Sync-only.** Standard Schema allows async `validate`; Lunora validation is
|
|
471
|
+
* synchronous and throws when a Promise is returned.
|
|
472
|
+
*
|
|
473
|
+
* **Reads and writes can differ.** A write supplies the schema's INPUT and a read
|
|
474
|
+
* gets its OUTPUT back, because what is stored is `validate()`'s result. The two
|
|
475
|
+
* coincide for every non-transforming schema; they part for `z.coerce.number()`
|
|
476
|
+
* and friends, where typing the insert side as the output would demand the
|
|
477
|
+
* post-transform value from a caller whose value the validator is there to
|
|
478
|
+
* transform.
|
|
479
|
+
*/
|
|
480
|
+
declare const from: <S extends StandardSchemaV1>(schema: S) => ColumnValidator<InferStandardOutput<S>, InferStandardInput<S>>;
|
|
481
|
+
/**
|
|
482
|
+
* True when `validator` is `v.from(...)` or structurally wraps one through
|
|
483
|
+
* `v.optional` / `v.array` / `v.object` / `v.record` / `v.union`. The nested
|
|
484
|
+
* children live on the validator's `_meta` (`inner`, `shape`, `members`,
|
|
485
|
+
* `keyValidator`/`valueValidator`) and are themselves validators.
|
|
486
|
+
*
|
|
487
|
+
* For tooling that must know whether a value is validated by an external
|
|
488
|
+
* Standard Schema rather than a concrete `v.*` type — a seeder that cannot
|
|
489
|
+
* invent a conforming value, a JSON Schema exporter that has nothing to
|
|
490
|
+
* describe. `defineTable` no longer calls it: a `v.from()` column is allowed
|
|
491
|
+
* and stores JSON, see {@link from}.
|
|
492
|
+
*/
|
|
294
493
|
declare const isOrWrapsFromValidator: (validator: Validator) => boolean;
|
|
295
494
|
/**
|
|
296
|
-
* The inner validator wrapped by `v.optional(inner)`, or `undefined` for any
|
|
297
|
-
* other validator. The nested child lives on the validator's internal `_meta`
|
|
298
|
-
* bag; this accessor keeps that knowledge inside `@lunora/values` (the package
|
|
299
|
-
* that owns validator internals) so consumers don't reach into `_meta`
|
|
300
|
-
* themselves. Used by `@lunora/server`'s `defineEnv` to coerce through a leading
|
|
301
|
-
* `v.optional(...)`.
|
|
302
|
-
* @returns The inner validator if `v.optional(...)`, otherwise `undefined`.
|
|
303
|
-
*/
|
|
495
|
+
* The inner validator wrapped by `v.optional(inner)`, or `undefined` for any
|
|
496
|
+
* other validator. The nested child lives on the validator's internal `_meta`
|
|
497
|
+
* bag; this accessor keeps that knowledge inside `@lunora/values` (the package
|
|
498
|
+
* that owns validator internals) so consumers don't reach into `_meta`
|
|
499
|
+
* themselves. Used by `@lunora/server`'s `defineEnv` to coerce through a leading
|
|
500
|
+
* `v.optional(...)`.
|
|
501
|
+
* @returns The inner validator if `v.optional(...)`, otherwise `undefined`.
|
|
502
|
+
*/
|
|
304
503
|
declare const optionalInner: (validator: Validator) => Validator | undefined;
|
|
305
504
|
/**
|
|
306
|
-
* Validator/codec namespace. Each factory returns a {@link Validator} with a
|
|
307
|
-
* runtime `parse`/`safeParse` plus a phantom `__type` field for inference.
|
|
308
|
-
*/
|
|
505
|
+
* Validator/codec namespace. Each factory returns a {@link Validator} with a
|
|
506
|
+
* runtime `parse`/`safeParse` plus a phantom `__type` field for inference.
|
|
507
|
+
*/
|
|
309
508
|
declare const v: {
|
|
310
509
|
any: typeof any;
|
|
311
510
|
array: typeof array;
|
|
@@ -314,12 +513,14 @@ declare const v: {
|
|
|
314
513
|
bytes: typeof bytes;
|
|
315
514
|
date: typeof date;
|
|
316
515
|
from: typeof from;
|
|
516
|
+
geoPoint: typeof geoPoint;
|
|
317
517
|
id: typeof id;
|
|
318
518
|
literal: typeof literal;
|
|
319
519
|
null: typeof nullValidator;
|
|
320
520
|
number: typeof number;
|
|
321
521
|
object: typeof objectValidator;
|
|
322
522
|
optional: typeof optional;
|
|
523
|
+
partial: typeof partial;
|
|
323
524
|
record: typeof record;
|
|
324
525
|
storage: typeof storage;
|
|
325
526
|
string: typeof string;
|
|
@@ -327,47 +528,53 @@ declare const v: {
|
|
|
327
528
|
union: typeof union;
|
|
328
529
|
};
|
|
329
530
|
/**
|
|
330
|
-
* A JSON Schema node (Draft 2020-12 / OpenAPI 3.1 compatible). Intentionally a
|
|
331
|
-
* loose bag — Lunora only emits a known subset, but consumers (OpenAPI/OpenRPC
|
|
332
|
-
* builders, Swagger UI, form generators) treat it as an opaque schema object.
|
|
333
|
-
*/
|
|
531
|
+
* A JSON Schema node (Draft 2020-12 / OpenAPI 3.1 compatible). Intentionally a
|
|
532
|
+
* loose bag — Lunora only emits a known subset, but consumers (OpenAPI/OpenRPC
|
|
533
|
+
* builders, Swagger UI, form generators) treat it as an opaque schema object.
|
|
534
|
+
*/
|
|
334
535
|
interface JsonSchema {
|
|
335
536
|
[keyword: string]: unknown;
|
|
336
537
|
}
|
|
337
538
|
/**
|
|
338
|
-
* Structural reader over a validator-like node. The shared mapping algorithm
|
|
339
|
-
* ({@link jsonSchemaFromNode}) is parameterized by this interface so the same
|
|
340
|
-
* switch/recursion serves both inputs Lunora maps to JSON Schema: the runtime
|
|
341
|
-
* `@lunora/values` validator (children + metadata live on `_meta`), and the
|
|
342
|
-
* build-time validator IR consumed by codegen (children are plain fields, with
|
|
343
|
-
* no runtime metadata — so `constraints`/`isNullable` may be inert there).
|
|
344
|
-
*
|
|
345
|
-
* A reader normalizes a `TNode` to the small set of children/leaves the mapper
|
|
346
|
-
* recurses over. Composite accessors (`inner`/`shape`/`members`/`valueChild`)
|
|
347
|
-
* return the same `TNode` type so the mapper can recurse uniformly; leaf concerns
|
|
348
|
-
* that differ between the two sources — how a literal's `const` is computed,
|
|
349
|
-
* whether a `.check()`/`.meta()` constraint fragment exists, whether `.nullable()`
|
|
350
|
-
* was applied — are delegated wholesale to the reader.
|
|
351
|
-
*/
|
|
539
|
+
* Structural reader over a validator-like node. The shared mapping algorithm
|
|
540
|
+
* ({@link jsonSchemaFromNode}) is parameterized by this interface so the same
|
|
541
|
+
* switch/recursion serves both inputs Lunora maps to JSON Schema: the runtime
|
|
542
|
+
* `@lunora/values` validator (children + metadata live on `_meta`), and the
|
|
543
|
+
* build-time validator IR consumed by codegen (children are plain fields, with
|
|
544
|
+
* no runtime metadata — so `constraints`/`isNullable` may be inert there).
|
|
545
|
+
*
|
|
546
|
+
* A reader normalizes a `TNode` to the small set of children/leaves the mapper
|
|
547
|
+
* recurses over. Composite accessors (`inner`/`shape`/`members`/`keyChild`/`valueChild`)
|
|
548
|
+
* return the same `TNode` type so the mapper can recurse uniformly; leaf concerns
|
|
549
|
+
* that differ between the two sources — how a literal's `const` is computed,
|
|
550
|
+
* whether a `.check()`/`.meta()` constraint fragment exists, whether `.nullable()`
|
|
551
|
+
* was applied — are delegated wholesale to the reader.
|
|
552
|
+
*/
|
|
352
553
|
interface SchemaNodeReader<TNode> {
|
|
353
554
|
/**
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
555
|
+
* The `.check()`/`.meta()` JSON Schema fragment to shallow-merge onto the
|
|
556
|
+
* node, or `undefined` when none (the IR side never carries one). Constraint
|
|
557
|
+
* keys win over the base on conflict so a refinement can tighten — never
|
|
558
|
+
* silently weaken — the schema.
|
|
559
|
+
*/
|
|
359
560
|
constraints: (node: TNode) => JsonSchema | undefined;
|
|
360
561
|
/** Inner child of an `array`/`optional` node. May be absent on the IR side. */
|
|
361
562
|
inner: (node: TNode) => TNode | undefined;
|
|
362
563
|
/** Whether `.nullable()` was applied (runtime: `_meta.column.notNull === false`). */
|
|
363
564
|
isNullable: (node: TNode) => boolean;
|
|
565
|
+
/**
|
|
566
|
+
* Key-child of a `record` node, mapped to `propertyNames`. May be absent on
|
|
567
|
+
* the IR side (and on a `v.record(v.string(), …)` whose key validator adds
|
|
568
|
+
* no constraint beyond `type: "string"` it is merely redundant, not wrong).
|
|
569
|
+
*/
|
|
570
|
+
keyChild: (node: TNode) => TNode | undefined;
|
|
364
571
|
/** Discriminating validator kind. */
|
|
365
572
|
kind: (node: TNode) => ValidatorKind;
|
|
366
573
|
/**
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
574
|
+
* The JSON Schema `const` fragment for a `literal` node. Computed differently
|
|
575
|
+
* per source — runtime reads the live `_meta.value`; the IR parses verbatim
|
|
576
|
+
* source text — so the reader owns it entirely.
|
|
577
|
+
*/
|
|
371
578
|
literalSchema: (node: TNode) => JsonSchema;
|
|
372
579
|
/** Member nodes of a `union`. */
|
|
373
580
|
members: (node: TNode) => ReadonlyArray<TNode>;
|
|
@@ -379,109 +586,137 @@ interface SchemaNodeReader<TNode> {
|
|
|
379
586
|
valueChild: (node: TNode) => TNode | undefined;
|
|
380
587
|
}
|
|
381
588
|
/**
|
|
382
|
-
* The single validator→JSON-Schema mapping algorithm, shared by the runtime
|
|
383
|
-
* `toJsonSchema` (over `@lunora/values` validators) and codegen's IR-backed
|
|
384
|
-
* mapper. It walks a node recursively via the supplied {@link SchemaNodeReader},
|
|
385
|
-
* so nested objects/arrays/unions/records are fully expanded (never collapsed to
|
|
386
|
-
* one level).
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
|
|
589
|
+
* The single validator→JSON-Schema mapping algorithm, shared by the runtime
|
|
590
|
+
* `toJsonSchema` (over `@lunora/values` validators) and codegen's IR-backed
|
|
591
|
+
* mapper. It walks a node recursively via the supplied {@link SchemaNodeReader},
|
|
592
|
+
* so nested objects/arrays/unions/records are fully expanded (never collapsed to
|
|
593
|
+
* one level).
|
|
594
|
+
*
|
|
595
|
+
* Each node names the JSON CARRIER, not the decoded JS type — the transport
|
|
596
|
+
* decodes before the parser sees a value, so describing the JS type would
|
|
597
|
+
* advertise shapes no JSON body can hold. `date`/`timestamp` are
|
|
598
|
+
* epoch-millisecond numbers in Lunora (not ISO strings), so they schema as
|
|
599
|
+
* integers; `bigint` rides as an int64 decimal string, the same carrier the
|
|
600
|
+
* wire codec and `v.literal(5n)` use; `bytes` is an `ArrayBuffer`, surfaced as
|
|
601
|
+
* base64 per JSON Schema 2020-12 content encoding.
|
|
602
|
+
*
|
|
603
|
+
* A `.check()`/`.meta()` JSON Schema fragment (when the reader exposes one) is
|
|
604
|
+
* shallow-merged onto the node — constraint keys win on conflict. A `.nullable()`
|
|
605
|
+
* node widens to also accept `null`; constraints describe the underlying value,
|
|
606
|
+
* so they ride inside the non-null branch rather than on the wrapping `anyOf`.
|
|
607
|
+
*/
|
|
398
608
|
declare const jsonSchemaFromNode: <TNode>(node: TNode, reader: SchemaNodeReader<TNode>) => JsonSchema;
|
|
399
609
|
/**
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
|
|
610
|
+
* True when the parser accepts this node's field ABSENT — the only honest test
|
|
611
|
+
* for whether a key is optional.
|
|
612
|
+
*
|
|
613
|
+
* `kind === "optional"` used to be the whole test, which called two kinds
|
|
614
|
+
* required that parse `{}` happily: `v.any()` (its parser returns `undefined`
|
|
615
|
+
* unchanged) and a `v.union(...)` with an optional or `any` member (the union
|
|
616
|
+
* tries members, one of which accepts `undefined`). A contract that requires a
|
|
617
|
+
* field the server does not cannot be satisfied by a generated client.
|
|
618
|
+
*
|
|
619
|
+
* Shared beyond JSON Schema's `required`: `@lunora/codegen`'s api emitter runs it
|
|
620
|
+
* over the build-time validator IR to decide `key?: T` vs `key: T`, so the
|
|
621
|
+
* generated `_generated/api.ts` argument types, the emitted OpenAPI/OpenRPC
|
|
622
|
+
* specs, and `Infer`'s `undefined extends …` optionality rule all answer this
|
|
623
|
+
* one question the same way.
|
|
624
|
+
* @returns `true` when an absent value parses.
|
|
625
|
+
*/
|
|
626
|
+
declare const acceptsAbsent: <TNode>(node: TNode, reader: SchemaNodeReader<TNode>) => boolean;
|
|
627
|
+
/**
|
|
628
|
+
* Build `{ type: "object", properties, required }` from a node shape.
|
|
629
|
+
*
|
|
630
|
+
* Deliberately NOT `additionalProperties: false`: `v.object` STRIPS an
|
|
631
|
+
* undeclared key rather than refusing it (only `.output()`'s
|
|
632
|
+
* `rejectUnknownKeys` mode refuses one, and it is not what this schema
|
|
633
|
+
* describes), so closing the object had a spec-validating client reject bodies
|
|
634
|
+
* the server accepts. Absent means allowed, which is what the parser does.
|
|
635
|
+
*
|
|
636
|
+
* `required` lists every key whose field cannot be absent — see
|
|
637
|
+
* {@link acceptsAbsent}.
|
|
638
|
+
*/
|
|
404
639
|
declare const objectSchemaFromNodes: <TNode>(shape: Record<string, TNode>, reader: SchemaNodeReader<TNode>) => JsonSchema;
|
|
405
640
|
/**
|
|
406
|
-
* Convert a single `@lunora/values` validator to a JSON Schema node (Draft
|
|
407
|
-
* 2020-12 / OpenAPI 3.1). A thin wrapper over the shared {@link jsonSchemaFromNode}
|
|
408
|
-
* core with the runtime {@link validatorReader}; see that core for the full
|
|
409
|
-
* kind→schema mapping (date/timestamp → epoch-ms integer, bigint → int64, bytes →
|
|
410
|
-
* base64, id → annotated string, literal → `const`, optionality via the parent
|
|
411
|
-
* `required` list, `.nullable()` widening, `.check()`/`.meta()` constraint merge).
|
|
412
|
-
*/
|
|
641
|
+
* Convert a single `@lunora/values` validator to a JSON Schema node (Draft
|
|
642
|
+
* 2020-12 / OpenAPI 3.1). A thin wrapper over the shared {@link jsonSchemaFromNode}
|
|
643
|
+
* core with the runtime {@link validatorReader}; see that core for the full
|
|
644
|
+
* kind→schema mapping (date/timestamp → epoch-ms integer, bigint → int64, bytes →
|
|
645
|
+
* base64, id → annotated string, literal → `const`, optionality via the parent
|
|
646
|
+
* `required` list, `.nullable()` widening, `.check()`/`.meta()` constraint merge).
|
|
647
|
+
*/
|
|
413
648
|
declare const toJsonSchema: (validator: Validator) => JsonSchema;
|
|
414
649
|
/**
|
|
415
|
-
* Convert a function's argument validators (a name-to-validator map) into a
|
|
416
|
-
* single JSON Schema object. Non-`optional` arguments are `required`; the result
|
|
417
|
-
* is the request `params`/`args` schema an OpenAPI operation or OpenRPC method
|
|
418
|
-
* advertises. An empty arg map yields an empty (but valid) object schema.
|
|
419
|
-
*/
|
|
650
|
+
* Convert a function's argument validators (a name-to-validator map) into a
|
|
651
|
+
* single JSON Schema object. Non-`optional` arguments are `required`; the result
|
|
652
|
+
* is the request `params`/`args` schema an OpenAPI operation or OpenRPC method
|
|
653
|
+
* advertises. An empty arg map yields an empty (but valid) object schema.
|
|
654
|
+
*/
|
|
420
655
|
declare const argsToJsonSchema: (args: Record<string, Validator>) => JsonSchema;
|
|
421
656
|
/** Map of validators describing a record of named fields (a function's args, a step's args, an HTTP query/body/params). */
|
|
422
657
|
type ValidatorMap = Record<string, Validator>;
|
|
423
658
|
/**
|
|
424
|
-
* Infer the object type from a {@link ValidatorMap} — optional validators
|
|
425
|
-
* (`v.optional`) become optional keys. Shares the single optionality rule with
|
|
426
|
-
* `ObjectShapeType` via {@link OptionalizeShape}, so args-map and object-shape
|
|
427
|
-
* inference can never drift.
|
|
428
|
-
*/
|
|
429
|
-
type InferValidatorMap<A extends ValidatorMap> = OptionalizeShape<{ [K in keyof A]: Infer<A[K]
|
|
430
|
-
/**
|
|
431
|
-
* A precompiled fast-path parser for one {@link ValidatorMap}. Returns the fully
|
|
432
|
-
* built, validated record on a confident success, or the {@link DEFER_VALIDATION}
|
|
433
|
-
* sentinel to hand the input back to the interpreted parser.
|
|
434
|
-
*
|
|
435
|
-
* The contract is soundness, not completeness: a compiled parser may return
|
|
436
|
-
* {@link DEFER_VALIDATION} for any input it is not certain about (the interpreted
|
|
437
|
-
* path then runs and either succeeds or throws the canonical error), but it must
|
|
438
|
-
* NEVER return a built record for input the interpreted parser would reject, and
|
|
439
|
-
* the record it returns must be byte-for-byte what the interpreted parser would
|
|
440
|
-
* have produced. This lets `@lunora/codegen` emit zero-allocation structural
|
|
441
|
-
* checks (the common case) while every error message and every tricky validator
|
|
442
|
-
* still flows through the single interpreted implementation below — so error
|
|
443
|
-
* contracts can never drift.
|
|
444
|
-
*
|
|
445
|
-
* The `source` parameter is intentionally `any`: the codegen-emitted body is
|
|
446
|
-
* plain JavaScript (no type annotations — it must also be loadable via
|
|
447
|
-
* `new Function` in the compiler's differential tests) that index-walks the input
|
|
448
|
-
* to arbitrary depth, which strict TypeScript forbids on `unknown`/`object`. An
|
|
449
|
-
* `any` input lets the emitted structural checks type-check cleanly while the
|
|
450
|
-
* RESULT stays strongly typed; soundness is enforced by the differential test
|
|
451
|
-
* harness, not the input type.
|
|
452
|
-
*/
|
|
659
|
+
* Infer the object type from a {@link ValidatorMap} — optional validators
|
|
660
|
+
* (`v.optional`) become optional keys. Shares the single optionality rule with
|
|
661
|
+
* `ObjectShapeType` via {@link OptionalizeShape}, so args-map and object-shape
|
|
662
|
+
* inference can never drift.
|
|
663
|
+
*/
|
|
664
|
+
type InferValidatorMap<A extends ValidatorMap> = OptionalizeShape<{ [K in keyof A]: Infer<A[K]>; }>;
|
|
665
|
+
/**
|
|
666
|
+
* A precompiled fast-path parser for one {@link ValidatorMap}. Returns the fully
|
|
667
|
+
* built, validated record on a confident success, or the {@link DEFER_VALIDATION}
|
|
668
|
+
* sentinel to hand the input back to the interpreted parser.
|
|
669
|
+
*
|
|
670
|
+
* The contract is soundness, not completeness: a compiled parser may return
|
|
671
|
+
* {@link DEFER_VALIDATION} for any input it is not certain about (the interpreted
|
|
672
|
+
* path then runs and either succeeds or throws the canonical error), but it must
|
|
673
|
+
* NEVER return a built record for input the interpreted parser would reject, and
|
|
674
|
+
* the record it returns must be byte-for-byte what the interpreted parser would
|
|
675
|
+
* have produced. This lets `@lunora/codegen` emit zero-allocation structural
|
|
676
|
+
* checks (the common case) while every error message and every tricky validator
|
|
677
|
+
* still flows through the single interpreted implementation below — so error
|
|
678
|
+
* contracts can never drift.
|
|
679
|
+
*
|
|
680
|
+
* The `source` parameter is intentionally `any`: the codegen-emitted body is
|
|
681
|
+
* plain JavaScript (no type annotations — it must also be loadable via
|
|
682
|
+
* `new Function` in the compiler's differential tests) that index-walks the input
|
|
683
|
+
* to arbitrary depth, which strict TypeScript forbids on `unknown`/`object`. An
|
|
684
|
+
* `any` input lets the emitted structural checks type-check cleanly while the
|
|
685
|
+
* RESULT stays strongly typed; soundness is enforced by the differential test
|
|
686
|
+
* harness, not the input type.
|
|
687
|
+
*/
|
|
453
688
|
type CompiledValidatorMap = (source: any) => Record<string, unknown> | typeof DEFER_VALIDATION;
|
|
454
689
|
/**
|
|
455
|
-
* Sentinel a {@link CompiledValidatorMap} returns to defer to the interpreted
|
|
456
|
-
* parser. A unique symbol (never a valid parse result — {@link parseValidatorMap}
|
|
457
|
-
* always yields a record) so the seam can distinguish "compiled handled it" from
|
|
458
|
-
* "compiled bailed" with a single identity check and no per-call allocation.
|
|
459
|
-
*/
|
|
690
|
+
* Sentinel a {@link CompiledValidatorMap} returns to defer to the interpreted
|
|
691
|
+
* parser. A unique symbol (never a valid parse result — {@link parseValidatorMap}
|
|
692
|
+
* always yields a record) so the seam can distinguish "compiled handled it" from
|
|
693
|
+
* "compiled bailed" with a single identity check and no per-call allocation.
|
|
694
|
+
*/
|
|
460
695
|
declare const DEFER_VALIDATION: unique symbol;
|
|
461
696
|
/**
|
|
462
|
-
* Install a compiled fast-path parser for `validators`. Idempotent-ish: a second
|
|
463
|
-
* install overwrites the first (codegen emits each map once, so this only matters
|
|
464
|
-
* if a host installs by hand). See {@link CompiledValidatorMap} for the contract
|
|
465
|
-
* the parser must honour.
|
|
466
|
-
*/
|
|
697
|
+
* Install a compiled fast-path parser for `validators`. Idempotent-ish: a second
|
|
698
|
+
* install overwrites the first (codegen emits each map once, so this only matters
|
|
699
|
+
* if a host installs by hand). See {@link CompiledValidatorMap} for the contract
|
|
700
|
+
* the parser must honour.
|
|
701
|
+
*/
|
|
467
702
|
declare const installCompiledValidatorMap: (validators: object, compiled: CompiledValidatorMap) => void;
|
|
468
703
|
/**
|
|
469
|
-
* Validate each declared field of `source` through its validator, re-wrapping
|
|
470
|
-
* any {@link ValidationError} with a `label
|
|
471
|
-
* `[key, ...error.path]` so the failure points at the offending field. Optional
|
|
472
|
-
* fields absent from the source are skipped (so `v.optional` passes and a
|
|
473
|
-
* required validator fails on `undefined`).
|
|
474
|
-
*
|
|
475
|
-
* The single arg-/field-parsing implementation shared across the framework — the
|
|
476
|
-
* procedure builder (label `args`), the HTTP route builder (`searchParams` /
|
|
477
|
-
* `body` / `params`), and `@lunora/workflow`'s reusable steps (`step args`) — so
|
|
478
|
-
* the error-prefixing and optional-skip semantics can't drift apart. The `label`
|
|
479
|
-
* is the only thing each caller varies.
|
|
480
|
-
*
|
|
481
|
-
* When a codegen-emitted {@link CompiledValidatorMap} is installed for this exact
|
|
482
|
-
* `validators` object, the fast path runs first; it either returns the finished
|
|
483
|
-
* record (a confident success — the common case) or {@link DEFER_VALIDATION}, in
|
|
484
|
-
* which case the interpreted loop below runs and owns the result (and any error).
|
|
485
|
-
*/
|
|
704
|
+
* Validate each declared field of `source` through its validator, re-wrapping
|
|
705
|
+
* any {@link ValidationError} with a `label.<key>:` prefix and the rebuilt path
|
|
706
|
+
* `[key, ...error.path]` so the failure points at the offending field. Optional
|
|
707
|
+
* fields absent from the source are skipped (so `v.optional` passes and a
|
|
708
|
+
* required validator fails on `undefined`).
|
|
709
|
+
*
|
|
710
|
+
* The single arg-/field-parsing implementation shared across the framework — the
|
|
711
|
+
* procedure builder (label `args`), the HTTP route builder (`searchParams` /
|
|
712
|
+
* `body` / `params`), and `@lunora/workflow`'s reusable steps (`step args`) — so
|
|
713
|
+
* the error-prefixing and optional-skip semantics can't drift apart. The `label`
|
|
714
|
+
* is the only thing each caller varies.
|
|
715
|
+
*
|
|
716
|
+
* When a codegen-emitted {@link CompiledValidatorMap} is installed for this exact
|
|
717
|
+
* `validators` object, the fast path runs first; it either returns the finished
|
|
718
|
+
* record (a confident success — the common case) or {@link DEFER_VALIDATION}, in
|
|
719
|
+
* which case the interpreted loop below runs and owns the result (and any error).
|
|
720
|
+
*/
|
|
486
721
|
declare const parseValidatorMap: (validators: ValidatorMap, source: Record<string, unknown>, label: string) => Record<string, unknown>;
|
|
487
|
-
export { type CheckOptions, type Column, type ColumnMeta, type ColumnValidator, type CompiledValidatorMap, DEFER_VALIDATION, type Id, type Infer, type InferInsert, type InferSelect, type InferStandardOutput, type InferValidatorMap, type InsertShape, type JsonSchema, type JsonSchemaFragment, type MetaOptions, type SchemaNodeReader, type SelectShape, type ServerDefaultContext, type TimestampColumnValidator, ValidationError, type ValidationPath, type Validator, type ValidatorKind, type ValidatorMap, argsToJsonSchema, describeValue, formatPath, installCompiledValidatorMap, isOrWrapsFromValidator, jsonSchemaFromNode, objectSchemaFromNodes, optionalInner, parseValidatorMap, toJsonSchema, v };
|
|
722
|
+
export { type ArrayColumnValidator, type CheckOptions, type Column, type ColumnMeta, type ColumnValidator, type CompiledValidatorMap, DEFER_VALIDATION, type GeoPoint, type Id, type Infer, type InferInsert, type InferSelect, type InferStandardInput, type InferStandardOutput, type InferValidatorMap, type InsertShape, type JsonSchema, type JsonSchemaFragment, type MetaOptions, type NumberColumnValidator, type ObjectColumnValidator, type ParseOptions, type SchemaNodeReader, type SelectShape, type ServerDefaultContext, type StringColumnValidator, type TimestampColumnValidator, ValidationError, type ValidationPath, type Validator, type ValidatorKind, type ValidatorMap, acceptsAbsent, argsToJsonSchema, cyrb53, describeValue, formatPath, installCompiledValidatorMap, isOrWrapsFromValidator, jsonSchemaFromNode, objectSchemaFromNodes, optionalInner, parseValidatorMap, toJsonSchema, v };
|