@lunora/values 0.0.0 → 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +105 -0
- package/README.md +109 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/index.d.mts +417 -0
- package/dist/index.d.ts +417 -0
- package/dist/index.mjs +9 -0
- package/dist/packem_shared/ValidationError-DWWcFe37.mjs +60 -0
- package/dist/packem_shared/argsToJsonSchema-DdHmmamC.mjs +22 -0
- package/dist/packem_shared/isOrWrapsFromValidator-DJMAE0l9.mjs +412 -0
- package/dist/packem_shared/jsonSchemaFromNode-weszUOQG.mjs +82 -0
- package/dist/packem_shared/parseValidatorMap-CCjevE5Z.mjs +30 -0
- package/package.json +37 -17
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
type ValidationPath = ReadonlyArray<number | string>;
|
|
3
|
+
/**
|
|
4
|
+
* Thrown by `validator.parse` (or returned inside `safeParse`) when input does
|
|
5
|
+
* not match the validator's shape. `path` walks from the root to the offending
|
|
6
|
+
* value, e.g. `["users", 0, "email"]`.
|
|
7
|
+
*/
|
|
8
|
+
declare class ValidationError extends Error {
|
|
9
|
+
readonly path: ValidationPath;
|
|
10
|
+
readonly expected: string;
|
|
11
|
+
readonly received: string;
|
|
12
|
+
constructor(message: string, options: {
|
|
13
|
+
expected: string;
|
|
14
|
+
path: ValidationPath;
|
|
15
|
+
received: string;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Render a short, diagnostic description of a runtime value for the `received`
|
|
20
|
+
* field of a {@link ValidationError}. Primitives carry their concrete (length-
|
|
21
|
+
* capped) literal so messages distinguish `string "7"` from `number 7`;
|
|
22
|
+
* non-plain objects carry their constructor name (e.g. `Date`) so a class
|
|
23
|
+
* instance is not flattened to a bare `"object"`.
|
|
24
|
+
*/
|
|
25
|
+
declare const describeValue: (value: unknown) => string;
|
|
26
|
+
declare const formatPath: (path: ValidationPath) => string;
|
|
27
|
+
/** Branded id type, e.g. `Id<"users">`. */
|
|
28
|
+
type Id<TableName extends string> = string & {
|
|
29
|
+
readonly __table: TableName;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A JSON Schema fragment (Draft 2020-12 / OpenAPI 3.1 compatible). Intentionally
|
|
33
|
+
* a loose bag — a `.check()`/`.meta()` caller contributes keywords like
|
|
34
|
+
* `minLength`/`pattern`/`minimum` that `toJsonSchema` shallow-merges onto the
|
|
35
|
+
* node for the enclosing validator. Mirrors the `JsonSchema` shape exported by
|
|
36
|
+
* `./to-json-schema`; kept structurally identical and local so `v.ts` never
|
|
37
|
+
* imports the converter and the two files stay decoupled.
|
|
38
|
+
*/
|
|
39
|
+
interface JsonSchemaFragment {
|
|
40
|
+
[keyword: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Options for a {@link Validator.check} refinement. Lets a predicate carry both
|
|
44
|
+
* a human-facing `message` and an introspectable JSON Schema `schema` fragment
|
|
45
|
+
* (e.g. `{ minLength: 1 }`) so the constraint flows into `toJsonSchema`. The
|
|
46
|
+
* legacy `.check(pred, "message")` string form remains supported.
|
|
47
|
+
*/
|
|
48
|
+
interface CheckOptions {
|
|
49
|
+
/** Failure message thrown on the `ValidationError` (default `"value matching refinement"`). */
|
|
50
|
+
message?: string;
|
|
51
|
+
/** JSON Schema fragment merged onto this validator's node by `toJsonSchema`. */
|
|
52
|
+
schema?: JsonSchemaFragment;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Options for {@link Validator.meta} — pure metadata with no runtime parsing
|
|
56
|
+
* effect, used to enrich the emitted JSON Schema node (description + constraint
|
|
57
|
+
* keywords) without attaching a predicate.
|
|
58
|
+
*/
|
|
59
|
+
interface MetaOptions {
|
|
60
|
+
/** A human description merged onto this validator's JSON Schema node. */
|
|
61
|
+
description?: string;
|
|
62
|
+
/** JSON Schema fragment merged onto this validator's node by `toJsonSchema`. */
|
|
63
|
+
schema?: JsonSchemaFragment;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Runtime "kind" tag attached to every validator. Codegen and reflective tools
|
|
67
|
+
* use this to inspect the shape without crawling the closure.
|
|
68
|
+
*/
|
|
69
|
+
type ValidatorKind = "any" | "array" | "bigint" | "boolean" | "bytes" | "date" | "from" | "id" | "literal" | "null" | "number" | "object" | "optional" | "record" | "storage" | "string" | "timestamp" | "union";
|
|
70
|
+
interface Validator<T = unknown> extends StandardSchemaV1<T, T> {
|
|
71
|
+
readonly __type: T;
|
|
72
|
+
/**
|
|
73
|
+
* Attach a refinement predicate. The returned validator parses with the
|
|
74
|
+
* original rules first; if the result satisfies `predicate` it passes
|
|
75
|
+
* through, otherwise it throws a {@link ValidationError} carrying
|
|
76
|
+
* `message` (default: `"value matching refinement"`). Multiple `.check()`
|
|
77
|
+
* calls chain — every predicate must return true.
|
|
78
|
+
*
|
|
79
|
+
* The second argument may be a plain message string (legacy form) or a
|
|
80
|
+
* {@link CheckOptions} object that additionally carries a JSON Schema
|
|
81
|
+
* `schema` fragment (e.g. `{ minLength: 1 }`) reflected by `toJsonSchema`.
|
|
82
|
+
*
|
|
83
|
+
* Works in any context — argument validators, column validators, or
|
|
84
|
+
* standalone — so it can encode invariants like
|
|
85
|
+
* `v.number().check(n => n >= 0)` or
|
|
86
|
+
* `v.string().check(s => s.length > 0, { message: "non-empty", schema: { minLength: 1 } })`.
|
|
87
|
+
*/
|
|
88
|
+
check: (predicate: (value: T) => boolean, options?: CheckOptions | string) => Validator<T>;
|
|
89
|
+
readonly kind: ValidatorKind;
|
|
90
|
+
/**
|
|
91
|
+
* Attach pure metadata (description + JSON Schema constraint fragment) with
|
|
92
|
+
* no effect on runtime parsing. The fragment is shallow-merged onto this
|
|
93
|
+
* validator's emitted JSON Schema node, composing with any `.check()`
|
|
94
|
+
* `schema` fragments (later wins on conflicting keys).
|
|
95
|
+
*/
|
|
96
|
+
meta: (options: MetaOptions) => Validator<T>;
|
|
97
|
+
parse: (value: unknown) => T;
|
|
98
|
+
safeParse: (value: unknown) => {
|
|
99
|
+
error: ValidationError;
|
|
100
|
+
ok: false;
|
|
101
|
+
} | {
|
|
102
|
+
ok: true;
|
|
103
|
+
value: T;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** Extract the TS type a validator describes (the **select** type). */
|
|
107
|
+
type Infer<V> = V extends Validator<infer T> ? T : never;
|
|
108
|
+
/**
|
|
109
|
+
* Column constraints/defaults collected from the `v.*` modifier chain used
|
|
110
|
+
* inside `defineTable`. Inert in argument position. Persisted on the
|
|
111
|
+
* validator's internal `_meta.column` and mirrored into codegen IR.
|
|
112
|
+
*/
|
|
113
|
+
interface ColumnMeta {
|
|
114
|
+
/** `.$defaultFn(fn)` — default factory; field is optional on insert. */
|
|
115
|
+
defaultFn?: () => unknown;
|
|
116
|
+
/** `.default(value)` — literal default; field is optional on insert. */
|
|
117
|
+
defaultValue?: unknown;
|
|
118
|
+
/** Default `true`; `.nullable()` flips it to `false`. */
|
|
119
|
+
notNull: boolean;
|
|
120
|
+
/** `.$onUpdateFn(fn)` — recomputed on every patch/replace. */
|
|
121
|
+
onUpdateFn?: () => unknown;
|
|
122
|
+
/**
|
|
123
|
+
* `.serverDefault(fn)` — a SERVER-trusted value factory. Unlike
|
|
124
|
+
* `.$defaultFn` (which only fills an absent field), this runs on every
|
|
125
|
+
* insert/update and SILENTLY OVERWRITES any client-supplied value with
|
|
126
|
+
* `fn({ auth })`, so the column is never client-controllable (e.g.
|
|
127
|
+
* `ownerId`/`tenantId` stamped from `auth.userId`). Field is optional on
|
|
128
|
+
* insert. The factory runs server-side with the resolved request auth.
|
|
129
|
+
*/
|
|
130
|
+
serverDefault?: (context: ServerDefaultContext) => unknown;
|
|
131
|
+
/** `.unique()` — synthesizes a UNIQUE index. */
|
|
132
|
+
unique?: boolean;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Context handed to a `.serverDefault(fn)` factory at write time. Carries the
|
|
136
|
+
* resolved request identity so a column can be stamped from the caller
|
|
137
|
+
* (`auth.userId`) rather than trusted from the client. Structurally mirrors the
|
|
138
|
+
* `auth` slice of the server's procedure context without depending on
|
|
139
|
+
* `@lunora/server`.
|
|
140
|
+
*/
|
|
141
|
+
interface ServerDefaultContext {
|
|
142
|
+
readonly auth: {
|
|
143
|
+
/** The raw identity claims, or `null` for the anonymous/no-resolver case. */
|
|
144
|
+
readonly identity: Record<string, unknown> | null;
|
|
145
|
+
/** The resolved caller id, or `null` when unauthenticated. */
|
|
146
|
+
readonly userId: null | string;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Phantom carrier of a column's select/insert types. Never present at runtime;
|
|
151
|
+
* `defineTable` reads it to derive `$inferSelect` / `$inferInsert`.
|
|
152
|
+
*/
|
|
153
|
+
interface Column<TSelect, TInsert> {
|
|
154
|
+
/** Phantom carrier — type-only, never present at runtime. */
|
|
155
|
+
readonly __column: {
|
|
156
|
+
insert: TInsert;
|
|
157
|
+
select: TSelect;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* A {@link Validator} carrying the chainable column-modifier API. The factories
|
|
162
|
+
* (`v.string()`, …) return this so modifiers are available inside `defineTable`.
|
|
163
|
+
* `TSelect` is the read type; `TInsert` is the write type (modifiers may make it
|
|
164
|
+
* `| undefined`, marking the field optional on insert).
|
|
165
|
+
*/
|
|
166
|
+
interface ColumnValidator<TSelect, TInsert> extends Column<TSelect, TInsert>, Validator<TSelect> {
|
|
167
|
+
/** Default factory applied in the write layer; field becomes optional on insert. */
|
|
168
|
+
$defaultFn: (function_: () => TSelect) => ColumnValidator<TSelect, TInsert | undefined>;
|
|
169
|
+
/** Recompute the field on every patch/replace when not explicitly provided. */
|
|
170
|
+
$onUpdateFn: (function_: () => TSelect) => ColumnValidator<TSelect, TInsert>;
|
|
171
|
+
/** Override the inferred select/insert type without changing runtime parsing (e.g. `v.string().$type<Id<"users">>()`). */
|
|
172
|
+
$type: <TOverride>() => ColumnValidator<TOverride, TOverride>;
|
|
173
|
+
/** Refinement predicate run after parsing — see {@link Validator.check}. Chainable; preserves column modifiers. */
|
|
174
|
+
check: (predicate: (value: TSelect) => boolean, options?: CheckOptions | string) => ColumnValidator<TSelect, TInsert>;
|
|
175
|
+
/** Literal default applied in the write layer; field becomes optional on insert. */
|
|
176
|
+
default: (value: TSelect) => ColumnValidator<TSelect, TInsert | undefined>;
|
|
177
|
+
/** Attach JSON Schema metadata — see {@link Validator.meta}. Chainable; preserves column modifiers. */
|
|
178
|
+
meta: (options: MetaOptions) => ColumnValidator<TSelect, TInsert>;
|
|
179
|
+
/** Allow SQL NULL — widens the select type to `T | null`. */
|
|
180
|
+
nullable: () => ColumnValidator<null | TSelect, null | TInsert>;
|
|
181
|
+
/**
|
|
182
|
+
* Stamp this column SERVER-side from the request auth on every write,
|
|
183
|
+
* overwriting any client-supplied value. The field becomes optional on
|
|
184
|
+
* insert (the server fills it). Use for owner/tenant columns that must never
|
|
185
|
+
* be client-controllable — e.g. `v.string().serverDefault(({ auth }) => auth.userId)`.
|
|
186
|
+
*/
|
|
187
|
+
serverDefault: (function_: (context: ServerDefaultContext) => TSelect) => ColumnValidator<TSelect, TInsert | undefined>;
|
|
188
|
+
/** Enforce a UNIQUE constraint (synthesizes a unique index). */
|
|
189
|
+
unique: () => ColumnValidator<TSelect, TInsert>;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A time-valued {@link ColumnValidator} (epoch milliseconds). Adds
|
|
193
|
+
* {@link TimestampColumnValidator.defaultNow} so the field can default to the
|
|
194
|
+
* insert-time clock.
|
|
195
|
+
*/
|
|
196
|
+
interface TimestampColumnValidator extends ColumnValidator<number, number> {
|
|
197
|
+
/** Default to the current epoch-ms (`Date.now()`) at insert time; field becomes optional on insert. */
|
|
198
|
+
defaultNow: () => ColumnValidator<number, number | undefined>;
|
|
199
|
+
}
|
|
200
|
+
/** The type a validator/column presents on **select** (reads). */
|
|
201
|
+
type InferSelect<V> = V extends Validator<infer T> ? T : never;
|
|
202
|
+
/** The type a validator/column accepts on **insert** (writes). */
|
|
203
|
+
type InferInsert<V> = V extends Column<unknown, infer I> ? I : V extends Validator<infer T> ? T : never;
|
|
204
|
+
/** Derive the read shape of a table's column map. */
|
|
205
|
+
type SelectShape<S extends Record<string, Validator>> = { [K in keyof S]: InferSelect<S[K]> };
|
|
206
|
+
/**
|
|
207
|
+
* Derive the write shape of a table's column map. Columns whose insert type
|
|
208
|
+
* includes `undefined` (via `.default()` / `.$defaultFn()` / `v.optional`)
|
|
209
|
+
* become optional keys.
|
|
210
|
+
*/
|
|
211
|
+
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]> };
|
|
212
|
+
declare const string: () => ColumnValidator<string, string>;
|
|
213
|
+
declare const number: () => ColumnValidator<number, number>;
|
|
214
|
+
/** Epoch-millisecond timestamp (`number`). Pair with `.defaultNow()` for an insert-time clock. */
|
|
215
|
+
declare const timestamp: () => TimestampColumnValidator;
|
|
216
|
+
/** Calendar date stored as an epoch-millisecond `number`. Pair with `.defaultNow()` for an insert-time clock. */
|
|
217
|
+
declare const date: () => TimestampColumnValidator;
|
|
218
|
+
declare const boolean: () => ColumnValidator<boolean, boolean>;
|
|
219
|
+
declare const bigintValidator: () => ColumnValidator<bigint, bigint>;
|
|
220
|
+
declare const nullValidator: () => ColumnValidator<null, null>;
|
|
221
|
+
declare const bytes: () => ColumnValidator<ArrayBuffer, ArrayBuffer>;
|
|
222
|
+
declare const id: <TableName extends string>(tableName: TableName) => ColumnValidator<Id<TableName>, Id<TableName>>;
|
|
223
|
+
/**
|
|
224
|
+
* A reference to a stored R2 object: the column holds the object's **key** (a
|
|
225
|
+
* string), the same key `@lunora/storage` puts/gets by. Functionally it parses
|
|
226
|
+
* like `v.string()`, but the distinct `"storage"` kind lets codegen and the
|
|
227
|
+
* studio join the data model to R2 — the file browser uses it to show which
|
|
228
|
+
* record owns a file and to flag orphaned objects no row references. The
|
|
229
|
+
* optional `bucket` names the typed bucket the key lives in (for app-context
|
|
230
|
+
* signed URLs); omit it for the app's default bucket.
|
|
231
|
+
*/
|
|
232
|
+
declare const storage: (bucket?: string) => ColumnValidator<string, string>;
|
|
233
|
+
declare const literal: <T extends bigint | boolean | number | string | null>(literalValue: T) => ColumnValidator<T, T>;
|
|
234
|
+
declare const array: <V extends Validator>(inner: V) => ColumnValidator<Infer<V>[], Infer<V>[]>;
|
|
235
|
+
type ObjectShape = Record<string, Validator>;
|
|
236
|
+
type ObjectShapeType<S extends ObjectShape> = { [K in keyof S as undefined extends Infer<S[K]> ? K : never]?: Infer<S[K]> } & { [K in keyof S as undefined extends Infer<S[K]> ? never : K]: Infer<S[K]> };
|
|
237
|
+
declare const objectValidator: <S extends ObjectShape>(shape: S) => ColumnValidator<ObjectShapeType<S>, ObjectShapeType<S>>;
|
|
238
|
+
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>>>;
|
|
239
|
+
declare const union: <Vs extends ReadonlyArray<Validator>>(...members: Vs) => ColumnValidator<Infer<Vs[number]>, Infer<Vs[number]>>;
|
|
240
|
+
declare const optional: <V extends Validator>(inner: V) => ColumnValidator<Infer<V> | undefined, Infer<V> | undefined>;
|
|
241
|
+
declare const any: () => ColumnValidator<unknown, unknown>;
|
|
242
|
+
/**
|
|
243
|
+
* Infer the output type of a Standard Schema v1 object. When the schema omits
|
|
244
|
+
* `~standard.types` (it is optional in the spec), falls back to `unknown` so
|
|
245
|
+
* callers always get a usable type rather than `never`.
|
|
246
|
+
*/
|
|
247
|
+
type InferStandardOutput<S extends StandardSchemaV1> = S["~standard"]["types"] extends {
|
|
248
|
+
output: infer O;
|
|
249
|
+
} ? O : unknown;
|
|
250
|
+
/**
|
|
251
|
+
* Wrap any Standard Schema v1 validator (`zod`, `valibot`, `arktype`, …) so it
|
|
252
|
+
* can be used as an **args** validator in `query`/`mutation`/`action`. The
|
|
253
|
+
* wrapped validator's output type is inferred from `~standard.types.output`
|
|
254
|
+
* when declared; falls back to `unknown` when the schema omits the types field.
|
|
255
|
+
*
|
|
256
|
+
* **Args-only.** `v.from(...)` validators must not be used as table columns —
|
|
257
|
+
* `defineTable` checks the `kind` and throws a clear error if you try.
|
|
258
|
+
*
|
|
259
|
+
* **Sync-only.** Standard Schema allows async `validate`; Lunora args
|
|
260
|
+
* validation is synchronous and throws when a Promise is returned.
|
|
261
|
+
*/
|
|
262
|
+
declare const from: <S extends StandardSchemaV1>(schema: S) => ColumnValidator<InferStandardOutput<S>, InferStandardOutput<S>>;
|
|
263
|
+
/**
|
|
264
|
+
* True when `validator` is `v.from(...)` or structurally wraps one through
|
|
265
|
+
* `v.optional` / `v.array` / `v.object` / `v.record` / `v.union`. `defineTable`
|
|
266
|
+
* uses it to reject Standard-Schema-backed validators anywhere in a column —
|
|
267
|
+
* not just at the top level — since they are args-only and have no SQL column
|
|
268
|
+
* type. The nested children live on the validator's `_meta` (`inner`, `shape`,
|
|
269
|
+
* `members`, `keyValidator`/`valueValidator`) and are themselves validators.
|
|
270
|
+
*/
|
|
271
|
+
declare const isOrWrapsFromValidator: (validator: Validator) => boolean;
|
|
272
|
+
/**
|
|
273
|
+
* The inner validator wrapped by `v.optional(inner)`, or `undefined` for any
|
|
274
|
+
* other validator. The nested child lives on the validator's internal `_meta`
|
|
275
|
+
* bag; this accessor keeps that knowledge inside `@lunora/values` (the package
|
|
276
|
+
* that owns validator internals) so consumers don't reach into `_meta`
|
|
277
|
+
* themselves. Used by `@lunora/server`'s `defineEnv` to coerce through a leading
|
|
278
|
+
* `v.optional(...)`.
|
|
279
|
+
* @returns The inner validator if `v.optional(...)`, otherwise `undefined`.
|
|
280
|
+
*/
|
|
281
|
+
declare const optionalInner: (validator: Validator) => Validator | undefined;
|
|
282
|
+
/**
|
|
283
|
+
* Validator/codec namespace. Each factory returns a {@link Validator} with a
|
|
284
|
+
* runtime `parse`/`safeParse` plus a phantom `__type` field for inference.
|
|
285
|
+
*/
|
|
286
|
+
declare const v: {
|
|
287
|
+
any: typeof any;
|
|
288
|
+
array: typeof array;
|
|
289
|
+
bigint: typeof bigintValidator;
|
|
290
|
+
boolean: typeof boolean;
|
|
291
|
+
bytes: typeof bytes;
|
|
292
|
+
date: typeof date;
|
|
293
|
+
from: typeof from;
|
|
294
|
+
id: typeof id;
|
|
295
|
+
literal: typeof literal;
|
|
296
|
+
null: typeof nullValidator;
|
|
297
|
+
number: typeof number;
|
|
298
|
+
object: typeof objectValidator;
|
|
299
|
+
optional: typeof optional;
|
|
300
|
+
record: typeof record;
|
|
301
|
+
storage: typeof storage;
|
|
302
|
+
string: typeof string;
|
|
303
|
+
timestamp: typeof timestamp;
|
|
304
|
+
union: typeof union;
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* A JSON Schema node (Draft 2020-12 / OpenAPI 3.1 compatible). Intentionally a
|
|
308
|
+
* loose bag — Lunora only emits a known subset, but consumers (OpenAPI/OpenRPC
|
|
309
|
+
* builders, Swagger UI, form generators) treat it as an opaque schema object.
|
|
310
|
+
*/
|
|
311
|
+
interface JsonSchema {
|
|
312
|
+
[keyword: string]: unknown;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Structural reader over a validator-like node. The shared mapping algorithm
|
|
316
|
+
* ({@link jsonSchemaFromNode}) is parameterized by this interface so the same
|
|
317
|
+
* switch/recursion serves both inputs Lunora maps to JSON Schema: the runtime
|
|
318
|
+
* `@lunora/values` validator (children + metadata live on `_meta`), and the
|
|
319
|
+
* build-time validator IR consumed by codegen (children are plain fields, with
|
|
320
|
+
* no runtime metadata — so `constraints`/`isNullable` may be inert there).
|
|
321
|
+
*
|
|
322
|
+
* A reader normalizes a `TNode` to the small set of children/leaves the mapper
|
|
323
|
+
* recurses over. Composite accessors (`inner`/`shape`/`members`/`valueChild`)
|
|
324
|
+
* return the same `TNode` type so the mapper can recurse uniformly; leaf concerns
|
|
325
|
+
* that differ between the two sources — how a literal's `const` is computed,
|
|
326
|
+
* whether a `.check()`/`.meta()` constraint fragment exists, whether `.nullable()`
|
|
327
|
+
* was applied — are delegated wholesale to the reader.
|
|
328
|
+
*/
|
|
329
|
+
interface SchemaNodeReader<TNode> {
|
|
330
|
+
/**
|
|
331
|
+
* The `.check()`/`.meta()` JSON Schema fragment to shallow-merge onto the
|
|
332
|
+
* node, or `undefined` when none (the IR side never carries one). Constraint
|
|
333
|
+
* keys win over the base on conflict so a refinement can tighten — never
|
|
334
|
+
* silently weaken — the schema.
|
|
335
|
+
*/
|
|
336
|
+
constraints: (node: TNode) => JsonSchema | undefined;
|
|
337
|
+
/** Inner child of an `array`/`optional` node. May be absent on the IR side. */
|
|
338
|
+
inner: (node: TNode) => TNode | undefined;
|
|
339
|
+
/** Whether `.nullable()` was applied (runtime: `_meta.column.notNull === false`). */
|
|
340
|
+
isNullable: (node: TNode) => boolean;
|
|
341
|
+
/** Discriminating validator kind. */
|
|
342
|
+
kind: (node: TNode) => ValidatorKind;
|
|
343
|
+
/**
|
|
344
|
+
* The JSON Schema `const` fragment for a `literal` node. Computed differently
|
|
345
|
+
* per source — runtime reads the live `_meta.value`; the IR parses verbatim
|
|
346
|
+
* source text — so the reader owns it entirely.
|
|
347
|
+
*/
|
|
348
|
+
literalSchema: (node: TNode) => JsonSchema;
|
|
349
|
+
/** Member nodes of a `union`. */
|
|
350
|
+
members: (node: TNode) => ReadonlyArray<TNode>;
|
|
351
|
+
/** Property nodes of an `object`, keyed by property name. */
|
|
352
|
+
shape: (node: TNode) => Record<string, TNode>;
|
|
353
|
+
/** Target table name of an `id` node. */
|
|
354
|
+
tableName: (node: TNode) => unknown;
|
|
355
|
+
/** Value-child of a `record` node. May be absent on the IR side. */
|
|
356
|
+
valueChild: (node: TNode) => TNode | undefined;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* The single validator→JSON-Schema mapping algorithm, shared by the runtime
|
|
360
|
+
* `toJsonSchema` (over `@lunora/values` validators) and codegen's IR-backed
|
|
361
|
+
* mapper. It walks a node recursively via the supplied {@link SchemaNodeReader},
|
|
362
|
+
* so nested objects/arrays/unions/records are fully expanded (never collapsed to
|
|
363
|
+
* one level).
|
|
364
|
+
*
|
|
365
|
+
* `date`/`timestamp` are epoch-millisecond numbers in Lunora (not ISO strings),
|
|
366
|
+
* so they schema as integers; `bigint` schemas as an int64 (JSON has no bigint
|
|
367
|
+
* type, so `format: int64` is the conventional OpenAPI carrier); `bytes` is an
|
|
368
|
+
* `ArrayBuffer`, surfaced as base64 per JSON Schema 2020-12 content encoding.
|
|
369
|
+
*
|
|
370
|
+
* A `.check()`/`.meta()` JSON Schema fragment (when the reader exposes one) is
|
|
371
|
+
* shallow-merged onto the node — constraint keys win on conflict. A `.nullable()`
|
|
372
|
+
* node widens to also accept `null`; constraints describe the underlying value,
|
|
373
|
+
* so they ride inside the non-null branch rather than on the wrapping `anyOf`.
|
|
374
|
+
*/
|
|
375
|
+
declare const jsonSchemaFromNode: <TNode>(node: TNode, reader: SchemaNodeReader<TNode>) => JsonSchema;
|
|
376
|
+
/**
|
|
377
|
+
* Build `{ type: "object", properties, required, additionalProperties: false }`
|
|
378
|
+
* from a node shape. A `v.optional(...)` property is the only thing that drops
|
|
379
|
+
* out of `required`; every other property is required.
|
|
380
|
+
*/
|
|
381
|
+
declare const objectSchemaFromNodes: <TNode>(shape: Record<string, TNode>, reader: SchemaNodeReader<TNode>) => JsonSchema;
|
|
382
|
+
/**
|
|
383
|
+
* Convert a single `@lunora/values` validator to a JSON Schema node (Draft
|
|
384
|
+
* 2020-12 / OpenAPI 3.1). A thin wrapper over the shared {@link jsonSchemaFromNode}
|
|
385
|
+
* core with the runtime {@link validatorReader}; see that core for the full
|
|
386
|
+
* kind→schema mapping (date/timestamp → epoch-ms integer, bigint → int64, bytes →
|
|
387
|
+
* base64, id → annotated string, literal → `const`, optionality via the parent
|
|
388
|
+
* `required` list, `.nullable()` widening, `.check()`/`.meta()` constraint merge).
|
|
389
|
+
*/
|
|
390
|
+
declare const toJsonSchema: (validator: Validator) => JsonSchema;
|
|
391
|
+
/**
|
|
392
|
+
* Convert a function's argument validators (a name-to-validator map) into a
|
|
393
|
+
* single JSON Schema object. Non-`optional` arguments are `required`; the result
|
|
394
|
+
* is the request `params`/`args` schema an OpenAPI operation or OpenRPC method
|
|
395
|
+
* advertises. An empty arg map yields an empty (but valid) object schema.
|
|
396
|
+
*/
|
|
397
|
+
declare const argsToJsonSchema: (args: Record<string, Validator>) => JsonSchema;
|
|
398
|
+
/** Map of validators describing a record of named fields (a function's args, a step's args, an HTTP query/body/params). */
|
|
399
|
+
type ValidatorMap = Record<string, Validator>;
|
|
400
|
+
/** Infer the object type from a {@link ValidatorMap} — optional validators (`v.optional`) become optional keys. */
|
|
401
|
+
type InferValidatorMap<A extends ValidatorMap> = { [K in keyof A as undefined extends Infer<A[K]> ? K : never]?: Infer<A[K]> } & { [K in keyof A as undefined extends Infer<A[K]> ? never : K]: Infer<A[K]> };
|
|
402
|
+
/**
|
|
403
|
+
* Validate each declared field of `source` through its validator, re-wrapping
|
|
404
|
+
* any {@link ValidationError} with a `label.<key>:` prefix and the rebuilt path
|
|
405
|
+
* `[key, ...error.path]` so the failure points at the offending field. Optional
|
|
406
|
+
* fields absent from the source are skipped (so `v.optional` passes and a
|
|
407
|
+
* required validator fails on `undefined`).
|
|
408
|
+
*
|
|
409
|
+
* The single arg-/field-parsing implementation shared across the framework — the
|
|
410
|
+
* procedure builder (label `args`), the HTTP route builder (`searchParams` /
|
|
411
|
+
* `body` / `params`), and `@lunora/workflow`'s reusable steps (`step args`) — so
|
|
412
|
+
* the error-prefixing and optional-skip semantics can't drift apart. The `label`
|
|
413
|
+
* is the only thing each caller varies.
|
|
414
|
+
*/
|
|
415
|
+
declare const parseValidatorMap: (validators: ValidatorMap, source: Record<string, unknown>, label: string) => Record<string, unknown>;
|
|
416
|
+
declare const VERSION = "0.0.0";
|
|
417
|
+
export { type CheckOptions, type Column, type ColumnMeta, type ColumnValidator, 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, VERSION, ValidationError, type ValidationPath, type Validator, type ValidatorKind, type ValidatorMap, argsToJsonSchema, describeValue, formatPath, isOrWrapsFromValidator, jsonSchemaFromNode, objectSchemaFromNodes, optionalInner, parseValidatorMap, toJsonSchema, v };
|