@c9up/rune 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/MessagesProvider.d.ts +5 -0
  2. package/dist/MessagesProvider.d.ts.map +1 -1
  3. package/dist/MessagesProvider.js +1 -1
  4. package/dist/MessagesProvider.js.map +1 -1
  5. package/dist/Schema.d.ts +775 -31
  6. package/dist/Schema.d.ts.map +1 -1
  7. package/dist/Schema.js +2254 -129
  8. package/dist/Schema.js.map +1 -1
  9. package/dist/date.d.ts +36 -0
  10. package/dist/date.d.ts.map +1 -0
  11. package/dist/date.js +275 -0
  12. package/dist/date.js.map +1 -0
  13. package/dist/errors.d.ts +10 -0
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +10 -0
  16. package/dist/errors.js.map +1 -1
  17. package/dist/formats.d.ts +149 -0
  18. package/dist/formats.d.ts.map +1 -0
  19. package/dist/formats.js +612 -0
  20. package/dist/formats.js.map +1 -0
  21. package/dist/index.d.ts +149 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +179 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/magic.d.ts +30 -0
  26. package/dist/magic.d.ts.map +1 -0
  27. package/dist/magic.js +154 -0
  28. package/dist/magic.js.map +1 -0
  29. package/dist/types.d.ts +15 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +12 -0
  32. package/dist/types.js.map +1 -0
  33. package/index.darwin-arm64.node +0 -0
  34. package/index.darwin-x64.node +0 -0
  35. package/index.linux-arm64-gnu.node +0 -0
  36. package/index.linux-x64-gnu.node +0 -0
  37. package/index.win32-x64-msvc.node +0 -0
  38. package/package.json +9 -1
  39. package/src/MessagesProvider.ts +1 -1
  40. package/src/Schema.ts +3260 -172
  41. package/src/date.ts +320 -0
  42. package/src/errors.ts +11 -0
  43. package/src/formats.ts +721 -0
  44. package/src/index.ts +262 -0
  45. package/src/magic.ts +181 -0
  46. package/src/types.ts +55 -0
package/dist/Schema.d.ts CHANGED
@@ -3,6 +3,9 @@
3
3
  *
4
4
  * @implements FR38, FR39, FR40, FR41
5
5
  */
6
+ import { type CompareUnit, type DateFormat } from "./date.js";
7
+ import { RuneValidationError } from "./errors.js";
8
+ import { type AlphaOptions, type EmailOptions, type NormalizeEmailOptions, type NormalizeUrlOptions, type UrlOptions, type VatOptions } from "./formats.js";
6
9
  import type { MessagesProviderContract } from "./MessagesProvider.js";
7
10
  export type ValidationMessageParams = Record<string, string | number | boolean>;
8
11
  export type ValidationTranslator = (key: string, params?: ValidationMessageParams) => string | undefined;
@@ -34,8 +37,26 @@ export interface FieldContext {
34
37
  meta: Record<string, unknown>;
35
38
  /** `true` while no error has been reported for this field yet. */
36
39
  isValid: boolean;
37
- /** Report a validation failure for this field. */
38
- report(message: string, rule: string): void;
40
+ /** Last path segment `city` for `address.city` (VineJS `name`). */
41
+ name: string;
42
+ /** Dotted path with numeric segments replaced by `*` (`tags.*.name`). */
43
+ wildCardPath: string;
44
+ /** `true` when this value sits inside an array. */
45
+ isArrayMember: boolean;
46
+ /** `true` when the value is neither `undefined` nor `null`. */
47
+ isDefined: boolean;
48
+ /** `true` when the value passed its type rule. */
49
+ isValidDataType: boolean;
50
+ /** The full dotted path — same value as {@link field}, VineJS spelling. */
51
+ getFieldPath(): string;
52
+ /** Replace the value under validation (VineJS `mutate`). */
53
+ mutate(newValue: unknown): void;
54
+ /**
55
+ * Report a validation failure. `field` and `args` are optional (VineJS
56
+ * passes four arguments); omitting them reports against this field with no
57
+ * interpolation data.
58
+ */
59
+ report(message: string, rule: string, field?: string | FieldContext, args?: Record<string, unknown>): void;
39
60
  }
40
61
  /**
41
62
  * A `.use()` rule validator — VineJS shape `(value, options, field)`. Report
@@ -45,6 +66,13 @@ export type RuleValidator<Options = undefined> = (value: unknown, options: Optio
45
66
  /** A compiled `.use()` rule produced by {@link createRule}. */
46
67
  export interface CompiledRule {
47
68
  readonly __rune: "rule";
69
+ /** Run even on `undefined`/`null` (VineJS implicit rules). */
70
+ readonly implicit?: boolean;
71
+ readonly name?: string;
72
+ /** Modifier applied to this field's JSON Schema node. */
73
+ readonly toJSONSchema?: JsonSchemaModifier;
74
+ /** The options the rule was built with, handed to {@link toJSONSchema}. */
75
+ readonly ruleOptions?: unknown;
48
76
  run(value: unknown, field: FieldContext): void;
49
77
  }
50
78
  /**
@@ -62,8 +90,61 @@ export interface CompiledRule {
62
90
  * passwordConfirmation: rules.string().use(sameAs('password')),
63
91
  * })
64
92
  */
65
- export declare function createRule(validator: RuleValidator<undefined>): () => CompiledRule;
66
- export declare function createRule<Options>(validator: RuleValidator<Options>): (options: Options) => CompiledRule;
93
+ /**
94
+ * Options accepted by {@link createRule} / {@link createAsyncRule} VineJS
95
+ * `vine.createRule(fn, { implicit, isAsync })`.
96
+ */
97
+ export interface CreateRuleOptions {
98
+ /**
99
+ * Run the rule even when the value is `undefined` or `null`. Non-implicit
100
+ * rules are skipped on an absent value, which is why a `required`-style
101
+ * custom rule could not be written before.
102
+ */
103
+ implicit?: boolean;
104
+ /** Rule name reported in errors when the validator does not pass one. */
105
+ name?: string;
106
+ /**
107
+ * VineJS `toJSONSchema?: JsonSchemaModifier` — a FUNCTION receiving the node
108
+ * built so far (plus the rule's options) and returning the modified node.
109
+ * A static fragment could only ever add keys; a modifier can also narrow or
110
+ * replace what the base rules produced.
111
+ */
112
+ toJSONSchema?: JsonSchemaModifier;
113
+ /**
114
+ * Declare the rule asynchronous (VineJS `{ isAsync: true }`).
115
+ * {@link createAsyncRule} sets it; passing it to {@link createRule} routes
116
+ * the rule to the async builder instead of silently producing a sync rule
117
+ * whose Promise nobody awaits.
118
+ */
119
+ isAsync?: boolean;
120
+ }
121
+ export declare function createRule(validator: RuleValidator<undefined>, options?: CreateRuleOptions): () => CompiledRule;
122
+ export declare function createRule<Options>(validator: RuleValidator<Options>, options?: CreateRuleOptions): (options: Options) => CompiledRule;
123
+ /**
124
+ * An async `.useAsync()` rule validator — same shape as {@link RuleValidator}
125
+ * but may return a Promise. Runs only under {@link ValidationSchema.validateResultAsync}.
126
+ */
127
+ export type AsyncRuleValidator<Options = undefined> = (value: unknown, options: Options, field: FieldContext) => void | Promise<void>;
128
+ /** A compiled async rule produced by {@link createAsyncRule}. */
129
+ export interface AsyncCompiledRule {
130
+ readonly __rune: "asyncRule";
131
+ /** Run even on `undefined`/`null` (VineJS implicit rules). */
132
+ readonly implicit?: boolean;
133
+ readonly name?: string;
134
+ /** Modifier applied to this field's JSON Schema node. */
135
+ readonly toJSONSchema?: JsonSchemaModifier;
136
+ /** The options the rule was built with, handed to {@link toJSONSchema}. */
137
+ readonly ruleOptions?: unknown;
138
+ run(value: unknown, field: FieldContext): Promise<void>;
139
+ }
140
+ /**
141
+ * Async counterpart of {@link createRule} — for rules that must await (DB lookups
142
+ * etc.). Attach with `chain.useAsync(rule(options))`; the schema must then be run
143
+ * with `validateResultAsync`. This is how DB-backed `unique`/`exists` rules are built
144
+ * (the validator does the query), keeping rune framework-agnostic.
145
+ */
146
+ export declare function createAsyncRule(validator: AsyncRuleValidator<undefined>, options?: CreateRuleOptions): () => AsyncCompiledRule;
147
+ export declare function createAsyncRule<Options>(validator: AsyncRuleValidator<Options>, options?: CreateRuleOptions): (options: Options) => AsyncCompiledRule;
67
148
  /**
68
149
  * Validation result — discriminated union that narrows `data` to the schema's
69
150
  * `T` when `valid` is `true`, removing the need for callers to cast or guard
@@ -88,17 +169,105 @@ export interface ValidateOptions {
88
169
  * provider takes precedence over a globally bound translator).
89
170
  */
90
171
  messagesProvider?: MessagesProviderContract;
172
+ /**
173
+ * VineJS `errorReporter: () => ErrorReporterContract` — a FACTORY returning a
174
+ * reporter, so a transcribed Adonis reporter works as-is. A plain
175
+ * `(error) => void` observer is also accepted.
176
+ *
177
+ * The two are told apart by ARITY (a factory takes no argument), never by
178
+ * calling one speculatively to see what comes back.
179
+ *
180
+ * Either way the reporter OBSERVES: the validation result is never changed by
181
+ * it, so a reporter cannot mask a failure.
182
+ */
183
+ errorReporter?: ErrorReporterFactory | ((error: ValidationError) => void);
91
184
  }
185
+ /**
186
+ * VineJS `JsonSchemaModifier`: receives the JSON Schema node assembled from the
187
+ * declarative rules and returns the node to use instead.
188
+ */
189
+ export type JsonSchemaModifier = (node: Record<string, unknown>, options?: unknown) => Record<string, unknown>;
190
+ /** VineJS `ErrorReporterContract`. */
191
+ export interface ErrorReporterContract {
192
+ /** `true` once at least one error has been reported. */
193
+ hasErrors: boolean;
194
+ /** Build the exception a caller may throw. */
195
+ createError(): Error;
196
+ /** Report one failure. */
197
+ report(message: string, rule: string, field: FieldContext | string, args?: Record<string, unknown>): unknown;
198
+ }
199
+ /** A zero-argument factory producing a fresh {@link ErrorReporterContract}. */
200
+ export type ErrorReporterFactory = () => ErrorReporterContract;
92
201
  export interface ValidationSchema<T = Record<string, unknown>> {
93
202
  fields: Record<string, RuleChain>;
94
- /** Result-based validation (superset) — never throws. */
95
- validate(data: unknown, options?: ValidateOptions): ValidationResult<T>;
203
+ /**
204
+ * The object schema the validator was built from — always a chain, so
205
+ * `validator.schema.partial()` / `.pick()` / `.omit()` work as in VineJS
206
+ * whichever form `create()` received. The raw field map stays on
207
+ * {@link fields}.
208
+ */
209
+ schema: RuleChain;
210
+ /**
211
+ * Standard Schema v1 contract, so a consumer can validate through the
212
+ * vendor-neutral protocol instead of rune's own API.
213
+ */
214
+ "~standard": {
215
+ version: 1;
216
+ vendor: string;
217
+ /** Standard JSON Schema v1 props (VineJS 4.3+). */
218
+ jsonSchema: {
219
+ input(): Record<string, unknown>;
220
+ output(): Record<string, unknown>;
221
+ };
222
+ validate(value: unknown): Promise<{
223
+ value: T;
224
+ } | {
225
+ issues: ReadonlyArray<{
226
+ message: string;
227
+ path: string[];
228
+ }>;
229
+ }>;
230
+ };
231
+ /**
232
+ * Error reporter for this validator (VineJS `validator.errorReporter`). A
233
+ * per-call option still wins; this wins over the process-wide one.
234
+ */
235
+ errorReporter: ErrorReporterFactory | ((error: ValidationError) => void) | null;
236
+ /** Introspection of the compiled schema — VineJS `{ schema, refs }` shape. */
237
+ toJSON(): {
238
+ schema: SchemaIntrospection;
239
+ refs: string[];
240
+ };
241
+ /** JSON Schema for the compiled validator (VineJS `toJSONSchema`). */
242
+ toJSONSchema(): Record<string, unknown>;
243
+ /**
244
+ * Validate and return the payload, throwing {@link RuneValidationError} on
245
+ * failure — the VineJS/Adonis contract (`validator.validate(data)`), async
246
+ * so a schema carrying `unique`/`exists` behaves like any other.
247
+ *
248
+ * The never-throwing, synchronous form rune also offers is
249
+ * {@link validateResult}.
250
+ */
251
+ validate(data: unknown, options?: ValidateOptions): Promise<T>;
252
+ /** Result-based validation (rune superset) — synchronous, never throws. */
253
+ validateResult(data: unknown, options?: ValidateOptions): ValidationResult<T>;
254
+ /** Result-based validation awaiting async rules — never throws. */
255
+ validateResultAsync(data: unknown, options?: ValidateOptions): Promise<ValidationResult<T>>;
96
256
  /**
97
257
  * Throwing validation (VineJS/Adonis parity). Returns the validated data on
98
258
  * success; throws {@link RuneValidationError} (`E_VALIDATION_ERROR`, HTTP 422)
99
259
  * with a structured `.messages` array on failure.
100
260
  */
101
261
  validateOrThrow(data: unknown, options?: ValidateOptions): T;
262
+ /**
263
+ * Non-throwing validation returning `[error, null] | [null, data]`
264
+ * (VineJS `tryValidate`).
265
+ */
266
+ tryValidate(data: unknown, options?: ValidateOptions): Promise<[RuneValidationError, null] | [null, T]>;
267
+ /** Synchronous counterpart of {@link tryValidate} (rune superset). */
268
+ tryValidateSync(data: unknown, options?: ValidateOptions): [RuneValidationError, null] | [null, T];
269
+ /** Throwing async validation (see {@link validateResultAsync} + {@link validateOrThrow}). */
270
+ validateOrThrowAsync(data: unknown, options?: ValidateOptions): Promise<T>;
102
271
  }
103
272
  /** Context threaded through validation so field rules can reach root/parent/meta. */
104
273
  interface RunContext {
@@ -106,7 +275,161 @@ interface RunContext {
106
275
  parent: Record<string, unknown> | unknown[];
107
276
  meta: Record<string, unknown>;
108
277
  messagesProvider?: MessagesProviderContract;
278
+ errorReporter?: (error: ValidationError) => void;
109
279
  }
280
+ /**
281
+ * An async rule run deferred by the (synchronous) traversal and awaited by
282
+ * `validateResultAsync`. Collected at EVERY depth — top-level fields, nested object
283
+ * fields and array items alike.
284
+ */
285
+ interface PendingAsync {
286
+ chain: RuleChain;
287
+ field: string;
288
+ value: unknown;
289
+ ctx: RunContext;
290
+ }
291
+ /** Bind (or clear) the process-wide error reporter. */
292
+ export declare function setGlobalErrorReporter(reporter: ErrorReporterFactory | ((error: ValidationError) => void) | null): void;
293
+ /** Read the process-wide error reporter. */
294
+ export declare function getGlobalErrorReporter(): ErrorReporterFactory | ((error: ValidationError) => void) | null;
295
+ /** Host lookup seam backing `activeUrl()` — see that rule's note on why. */
296
+ export interface HostResolver {
297
+ /** Resolve `true` when the hostname resolves (DNS, or whatever you decide). */
298
+ resolves(hostname: string): Promise<boolean>;
299
+ }
300
+ /** Toggle the global `"" -> null` conversion (VineJS `convertEmptyStringsToNull`). */
301
+ export declare function setConvertEmptyStringsToNull(enabled: boolean): void;
302
+ /** Read the global `"" -> null` conversion flag. */
303
+ export declare function getConvertEmptyStringsToNull(): boolean;
304
+ /** Bind (or clear, with `null`) the resolver backing `activeUrl()`. */
305
+ export declare function bindHostResolver(resolver: HostResolver | null): void;
306
+ /** Bind (or clear) the process-wide messages provider. */
307
+ export declare function setGlobalMessagesProvider(provider: MessagesProviderContract | null): void;
308
+ /** Read the process-wide messages provider. */
309
+ export declare function getGlobalMessagesProvider(): MessagesProviderContract | null;
310
+ /** Bind (or clear, with `null`) the global `rules.date()` output mapper. */
311
+ export declare function setDateTransform(fn: ((value: Date) => unknown) | null): void;
312
+ /**
313
+ * A database lookup seam for the Lucid-style `unique` / `exists` rules.
314
+ *
315
+ * rune stays framework-agnostic, so it never imports a driver: the host binds
316
+ * one resolver at boot (as `bindRosetta` does for translations) and the rules
317
+ * then take Lucid's `{ table, column, where }` options instead of a hand-written
318
+ * callback. The callback form is kept — it is what the resolver is built from.
319
+ */
320
+ export interface DatabaseResolver {
321
+ /** Resolve `true` when at least one row matches. */
322
+ exists(query: DatabaseLookup): Promise<boolean>;
323
+ }
324
+ /** The lookup handed to a {@link DatabaseResolver} (Lucid `unique`/`exists`). */
325
+ export interface DatabaseLookup {
326
+ table: string;
327
+ column: string;
328
+ value: unknown;
329
+ /** Extra equality filters, e.g. `{ tenant_id: 3 }` (Lucid `where`). */
330
+ where?: Record<string, unknown>;
331
+ /** Rows to ignore, e.g. `{ id: 7 }` when updating (Lucid `whereNot`). */
332
+ whereNot?: Record<string, unknown>;
333
+ }
334
+ /** Bind (or clear, with `null`) the resolver backing `unique()` / `exists()`. */
335
+ export declare function bindDatabase(resolver: DatabaseResolver | null): void;
336
+ /** Options form of `unique()` / `exists()` — Lucid's shape. */
337
+ export interface DatabaseRuleOptions {
338
+ table: string;
339
+ column?: string;
340
+ where?: Record<string, unknown>;
341
+ whereNot?: Record<string, unknown>;
342
+ }
343
+ /** Options accepted by every date comparison (VineJS `{ compare, format }`). */
344
+ export interface DateCompareOptions {
345
+ /** Granularity of the comparison. Defaults to `"day"`, like VineJS. */
346
+ compare?: CompareUnit;
347
+ /** Format used to parse the operand / sibling, when it is a string. */
348
+ format?: string;
349
+ }
350
+ /**
351
+ * The structural shape `file()` accepts. An Adonis bodyparser `MultipartFile`
352
+ * satisfies it without rune having to know the type.
353
+ */
354
+ export interface FileLike {
355
+ size: number;
356
+ /**
357
+ * MIME type as REPORTED by the upload. Trust it only with
358
+ * `verifyContent()`, which checks it against the real bytes.
359
+ */
360
+ type?: string;
361
+ /** Adonis bodyparser's temp path — a byte source for `verifyContent()`. */
362
+ tmpPath?: string;
363
+ /** Alternative byte-source paths. */
364
+ filePath?: string;
365
+ path?: string;
366
+ /** In-memory bytes, when the upload was buffered. */
367
+ buffer?: Uint8Array;
368
+ extname?: string | null;
369
+ clientName?: string;
370
+ name?: string;
371
+ }
372
+ /**
373
+ * Parse a size limit — a byte count, or Adonis's `"2mb"` / `"512kb"` spelling.
374
+ * Throws on an unreadable unit rather than falling back to "unlimited": a cap
375
+ * that silently stops capping is worse than no cap at all.
376
+ */
377
+ export declare function parseByteSize(size: number | string): number;
378
+ /**
379
+ * What a `parse()` callback receives besides the value — VineJS's
380
+ * `ParseFn = (value, ctx: Pick<FieldContext, 'data' | 'parent' | 'meta'>)`.
381
+ */
382
+ export type ParseContext = Pick<FieldContext, "data" | "parent" | "meta">;
383
+ /**
384
+ * A conditional set of properties merged into an object (VineJS `vine.group`).
385
+ * The first branch whose predicate matches contributes its shape; `otherwise`
386
+ * is the unconditional fallback.
387
+ */
388
+ export interface ConditionalGroup {
389
+ readonly __rune: "group";
390
+ branches: ReadonlyArray<{
391
+ predicate: ((data: Record<string, unknown>) => boolean) | null;
392
+ shape: Record<string, RuleChain>;
393
+ }>;
394
+ }
395
+ /** Per-field introspection returned inside `toJSON().schema`. */
396
+ export type SchemaIntrospection = Record<string, {
397
+ rules: string[];
398
+ optional: boolean;
399
+ nullable: boolean;
400
+ }>;
401
+ /** Build a conditional group (VineJS `vine.group([...])`). */
402
+ export declare function group(branches: ReadonlyArray<{
403
+ predicate: ((data: Record<string, unknown>) => boolean) | null;
404
+ shape: Record<string, RuleChain>;
405
+ }>): ConditionalGroup;
406
+ /** A predicate-guarded group branch (`vine.group.if`). */
407
+ export declare function groupIf(predicate: (data: Record<string, unknown>) => boolean, shape: Record<string, RuleChain>): {
408
+ predicate: (data: Record<string, unknown>) => boolean;
409
+ shape: Record<string, RuleChain>;
410
+ };
411
+ /** The unconditional fallback branch (`vine.group.else` / `.otherwise`). */
412
+ export declare function groupElse(shape: Record<string, RuleChain>): {
413
+ predicate: null;
414
+ shape: Record<string, RuleChain>;
415
+ };
416
+ /** A union branch guarded by a predicate — `vine.union.if(...)`. */
417
+ export interface ConditionalBranch {
418
+ /** `null` for an unconditional branch (`union.else`). */
419
+ predicate: ((value: unknown, field: FieldContext) => boolean) | null;
420
+ chain: RuleChain;
421
+ }
422
+ /** What `union()` accepts: a bare chain, or a guarded branch. */
423
+ export type UnionBranch = RuleChain | ConditionalBranch;
424
+ /**
425
+ * Guarded union branch (VineJS `vine.union.if`). The predicate picks the branch;
426
+ * the chosen branch's OWN errors are reported, which is what makes a union
427
+ * diagnosable — "matches nothing" tells the caller nothing about which shape it
428
+ * nearly matched.
429
+ */
430
+ export declare function unionIf(predicate: (value: unknown, field: FieldContext) => boolean, chain: RuleChain): ConditionalBranch;
431
+ /** Fallback union branch (VineJS `vine.union.else`). */
432
+ export declare function unionElse(chain: RuleChain): ConditionalBranch;
110
433
  /** Extract the phantom output type of a chain. */
111
434
  type OutputOf<C> = C extends RuleChain<infer O> ? O : never;
112
435
  /** Keys whose output includes `undefined` become optional in the inferred shape. */
@@ -144,8 +467,19 @@ export type Infer<S> = Prettify<{
144
467
  * An explicit generic is still accepted for back-compat
145
468
  * (`schema<MyType>({ ... })`), overriding inference.
146
469
  */
147
- export declare function schema<S extends Record<string, RuleChain>>(fields: S): ValidationSchema<Infer<S>>;
148
- export declare function schema<T = Record<string, unknown>>(fields: Record<string, RuleChain>): ValidationSchema<T>;
470
+ export declare function schema<S extends Record<string, RuleChain>>(fields: S, objectChain?: RuleChain): ValidationSchema<Infer<S>>;
471
+ export declare function schema<T = Record<string, unknown>>(fields: Record<string, RuleChain>, objectChain?: RuleChain): ValidationSchema<T>;
472
+ /**
473
+ * VineJS's `vine.create(...)`. Same thing as {@link schema} — the Adonis
474
+ * spelling is provided so a validator reads the same in both frameworks.
475
+ */
476
+ /**
477
+ * VineJS's `vine.create(...)`. Accepts either a map of fields (rune's native
478
+ * spelling) or the `RuleChain` produced by `rune.object({...})`, because
479
+ * `vine.create(vine.object({...}))` is the form Adonis documents.
480
+ */
481
+ export declare function create<S extends Record<string, RuleChain>>(fields: S): ValidationSchema<Infer<S>>;
482
+ export declare function create(chain: RuleChain): ValidationSchema;
149
483
  export declare function setValidationTranslator(translator?: ValidationTranslator): void;
150
484
  export declare function bindRosetta(rosetta: {
151
485
  t(key: string, params?: ValidationMessageParams): string;
@@ -160,6 +494,15 @@ export interface RuleDef {
160
494
  message: string;
161
495
  /** Set when `.message()` overrode this rule's default text. */
162
496
  hasCustomMessage?: boolean;
497
+ /**
498
+ * Keep this rule off the Rust path even though its NAME is in
499
+ * {@link NATIVE_RULES}. Set by options the native engine does not know about
500
+ * (`uuid({ version })`, a callback list for `in` / `notIn`): the engine would
501
+ * run the rule without them and silently answer a different question.
502
+ */
503
+ tsOnly?: boolean;
504
+ /** Modifier this rule applies to its field's JSON Schema node. */
505
+ toJSONSchema?: JsonSchemaModifier;
163
506
  }
164
507
  /** A conditional-required condition (VineJS `requiredWhen` family). */
165
508
  interface RequiredCondition {
@@ -171,6 +514,11 @@ interface RequiredCondition {
171
514
  /** Phantom brand carrying the inferred output type (never assigned at runtime). */
172
515
  declare const OUTPUT: unique symbol;
173
516
  /** Rule chain — fluent, phantom-typed validation builder. */
517
+ /**
518
+ * The value list accepted by `in` / `notIn` / `enum` — static, or computed at
519
+ * validation time (VineJS parity).
520
+ */
521
+ export type AllowedValues = ReadonlyArray<string | number | boolean> | (() => ReadonlyArray<string | number | boolean>);
174
522
  export declare class RuleChain<Output = unknown> {
175
523
  #private;
176
524
  /** Phantom output type — drives {@link Infer}; never read at runtime. */
@@ -186,8 +534,30 @@ export declare class RuleChain<Output = unknown> {
186
534
  }>;
187
535
  /** Public read access to `.use()` rules (used to keep such schemas off the native path). */
188
536
  get useRules(): readonly CompiledRule[];
537
+ /** Public read access to async rules (`unique`/`exists`/`useAsync`) — run by `validateResultAsync`. */
538
+ get asyncRules(): readonly AsyncCompiledRule[];
539
+ /**
540
+ * Does this chain — or anything nested under it (object fields, array items) —
541
+ * carry async rules? The schema-level detection used to inspect only the
542
+ * top-level chains, so a nested `unique`/`exists` was invisible: `validate()`
543
+ * did not throw and the async pass never ran the rule, silently accepting
544
+ * an unchecked value.
545
+ */
546
+ get hasAsyncRulesDeep(): boolean;
547
+ /** Does this object keep keys its shape does not declare? */
548
+ get allowsUnknown(): boolean;
549
+ /** Free-form JSON Schema metadata attached with `meta()`. */
550
+ get metadata(): Record<string, unknown> | null;
551
+ /** The item chain of an `array()`, if declared. */
552
+ get arrayItem(): RuleChain | null;
553
+ /** The positional chains of a `tuple()`, if declared. */
554
+ get tupleItems(): RuleChain[] | null;
555
+ /** The value chain of a `record()`, if declared. */
556
+ get recordValue(): RuleChain | null;
557
+ /** Whether this chain stops at its first failing rule (VineJS `bail`). */
558
+ get bails(): boolean;
189
559
  /** Public read access to `.parse()` pre-transforms (kept off the native path). */
190
- get preTransforms(): ReadonlyArray<(value: unknown) => unknown>;
560
+ get preTransforms(): ReadonlyArray<(value: unknown, ctx: ParseContext) => unknown>;
191
561
  /** Whether this chain carries a `requiredWhen`-family condition. */
192
562
  get hasConditionalRequired(): boolean;
193
563
  /** Mark field as optional (absent / `undefined` allowed). */
@@ -204,10 +574,176 @@ export declare class RuleChain<Output = unknown> {
204
574
  array<Item extends RuleChain>(itemChain?: Item): RuleChain<OutputOf<Item>[]>;
205
575
  /** Must be a string. */
206
576
  string(): RuleChain<string>;
207
- /** Must be a number. */
208
- number(): RuleChain<number>;
209
- /** Must be a boolean. */
210
- boolean(): RuleChain<boolean>;
577
+ /**
578
+ * Must be a number. Like VineJS, a numeric STRING is coerced (`"32"` → `32`)
579
+ * HTML form bodies and query strings carry numbers as text, so requiring
580
+ * `typeof v === "number"` rejected the values Adonis accepts. Pass
581
+ * `{ strict: true }` to refuse anything that is not already a number.
582
+ */
583
+ number(options?: {
584
+ strict?: boolean;
585
+ }): RuleChain<number>;
586
+ /**
587
+ * Must be a boolean. Like VineJS, `"true"`, `"false"`, `"on"`, `"off"`,
588
+ * `"1"`, `"0"`, `1` and `0` are coerced; `{ strict: true }` refuses them.
589
+ */
590
+ boolean(options?: {
591
+ strict?: boolean;
592
+ }): RuleChain<boolean>;
593
+ /**
594
+ * Must be a date (VineJS `vine.date()`). ISO 8601 by default; pass `formats`
595
+ * for unix timestamps (`x` = ms, `X` = seconds) or a token format such as
596
+ * `DD/MM/YYYY`. Parsing is calendar-strict — `2026-02-31` is rejected.
597
+ *
598
+ * The validated output is a `Date`; bind {@link setDateTransform} to map it
599
+ * to your own type once at boot.
600
+ */
601
+ date(options?: {
602
+ formats?: DateFormat[];
603
+ }): RuleChain<Date>;
604
+ /** Must be strictly after `operand` (`'today'`, an ISO string, or a `Date`). */
605
+ after(operand: unknown, options?: DateCompareOptions): this;
606
+ /** Must be strictly before `operand`. */
607
+ before(operand: unknown, options?: DateCompareOptions): this;
608
+ /** Must be after `operand`, or equal to it. */
609
+ afterOrEqual(operand: unknown, options?: DateCompareOptions): this;
610
+ /** Must be before `operand`, or equal to it. */
611
+ beforeOrEqual(operand: unknown, options?: DateCompareOptions): this;
612
+ /** Must be after the date held by a sibling field (VineJS `afterField`). */
613
+ afterField(otherField: string, options?: DateCompareOptions): this;
614
+ /** Must be before the date held by a sibling field. */
615
+ beforeField(otherField: string, options?: DateCompareOptions): this;
616
+ /** Must be the same instant as `operand` (VineJS `equals`). */
617
+ equals(operand: unknown, options?: DateCompareOptions): this;
618
+ /** Must be after the sibling's date, or the same instant (VineJS `afterOrSameAs`). */
619
+ afterOrSameAs(otherField: string, options?: DateCompareOptions): this;
620
+ /** Must be before the sibling's date, or the same instant. */
621
+ beforeOrSameAs(otherField: string, options?: DateCompareOptions): this;
622
+ /** Must fall on a Saturday or Sunday (VineJS `weekend`). */
623
+ weekend(): this;
624
+ /** Must fall on a Monday-to-Friday day (VineJS `weekday`). */
625
+ weekday(): this;
626
+ /**
627
+ * Keep keys the object shape does not declare (VineJS
628
+ * `allowUnknownProperties`). Off by default: dropping undeclared keys is what
629
+ * makes a validated payload safe to hand to a mass assignment.
630
+ */
631
+ allowUnknownProperties(): this;
632
+ /**
633
+ * Convert the object's KEYS to camelCase in the output (VineJS
634
+ * `object.toCamelCase()`), so a snake_case payload hydrates camelCase
635
+ * properties. Distinct from the string `toCamelCase()`, which rewrites a
636
+ * VALUE — that one was never a substitute for this.
637
+ */
638
+ toCamelCaseKeys(): this;
639
+ /**
640
+ * Merge extra properties into this object's shape (VineJS `merge`). Accepts a
641
+ * plain shape or a {@link ConditionalGroup} whose branch is chosen per
642
+ * payload — `vine.group` in VineJS.
643
+ */
644
+ merge(extra: Record<string, RuleChain> | ConditionalGroup): this;
645
+ /** The nested shape declared by `object()`, if any (VineJS `getProperties`). */
646
+ getProperties(): Record<string, RuleChain> | null;
647
+ /** Independent copy of this chain (VineJS `clone`). */
648
+ clone(): RuleChain<Output>;
649
+ /**
650
+ * A CLONED subset of the object's properties (VineJS `pick`).
651
+ *
652
+ * Returns a properties record, not a schema — VineJS types it
653
+ * `Pick<Properties, Keys>` precisely so it composes by spread:
654
+ * `rules.any().object({ ...userShape.pick(["id"]) })`. Returning a chain here
655
+ * broke that idiom.
656
+ */
657
+ pick<K extends string>(keys: readonly K[]): Record<string, RuleChain>;
658
+ /** A cloned copy of the properties EXCLUDING `keys` (VineJS `omit`). */
659
+ omit<K extends string>(keys: readonly K[]): Record<string, RuleChain>;
660
+ /** Make every property of an object shape optional (VineJS `partial`). */
661
+ partial(keys?: readonly string[]): RuleChain<Output>;
662
+ /**
663
+ * Must be an "accepted" value — `true`, `1`, `"1"`, `"on"`, `"yes"`,
664
+ * `"true"` (VineJS `accepted`, for checkbox-style consent fields).
665
+ */
666
+ accepted(): RuleChain<true>;
667
+ /**
668
+ * Object with arbitrary keys, every value validated by `valueChain`
669
+ * (VineJS `record`).
670
+ */
671
+ record<Item extends RuleChain>(valueChain: Item): RuleChain<Record<string, OutputOf<Item>>>;
672
+ /**
673
+ * Fixed-length array with a schema per position (VineJS `tuple`). Extra
674
+ * items are rejected — a tuple that silently ignores a trailing element is
675
+ * how unvalidated data slips through.
676
+ */
677
+ tuple<const Items extends readonly RuleChain[]>(items: Items): RuleChain<{
678
+ [K in keyof Items]: OutputOf<Items[K]>;
679
+ }>;
680
+ /**
681
+ * Value must satisfy at least one of `chains`.
682
+ *
683
+ * Two forms, both supported:
684
+ *
685
+ * - guarded (VineJS parity): `union([rules.union.if(pred, chain), …,
686
+ * rules.union.else(fallback)])` — the predicate SELECTS the branch and
687
+ * that branch's own errors are reported, so a failure says which shape was
688
+ * meant and why it did not fit.
689
+ * - bare chains: tried in order, first match wins, and a total miss reports a
690
+ * single `union` error rather than every losing branch's noise.
691
+ */
692
+ union(chains: readonly UnionBranch[]): this;
693
+ /**
694
+ * Must be an uploaded file (VineJS/Adonis `vine.file()`).
695
+ *
696
+ * Named deviation: Adonis validates a bodyparser `MultipartFile`, which rune
697
+ * cannot import and stay agnostic. It checks the STRUCTURE instead — any
698
+ * object exposing `size` and a name/extension — so an Adonis MultipartFile
699
+ * satisfies it, and so does any other upload representation.
700
+ *
701
+ * `size` is a byte count; `extnames` are compared lowercase, without the dot.
702
+ */
703
+ file(options?: {
704
+ size?: number | string;
705
+ extnames?: readonly string[];
706
+ /**
707
+ * Skip the magic-number check. Default `true` — Adonis derives `extname`
708
+ * from the real bytes before validation, so trusting the declaration is
709
+ * NOT the safe default: a `.exe` renamed `.png` satisfies every
710
+ * declarative check, since all of them come from the uploader.
711
+ */
712
+ verifyContent?: boolean;
713
+ }): RuleChain<FileLike>;
714
+ /**
715
+ * Uploaded file with VineJS `nativeFile` options — `minSize`, `maxSize`,
716
+ * `mimeTypes`. Same structural contract as {@link file}: rune never reads
717
+ * bytes, so the MIME type is the one the upload REPORTS.
718
+ */
719
+ nativeFile(options?: {
720
+ minSize?: number | string;
721
+ maxSize?: number | string;
722
+ mimeTypes?: readonly string[];
723
+ }): RuleChain<FileLike>;
724
+ /** Minimum upload size (VineJS `nativeFile().minSize()`). */
725
+ minSize(size: number | string): this;
726
+ /** Maximum upload size (VineJS `nativeFile().maxSize()`). */
727
+ maxSize(size: number | string): this;
728
+ /**
729
+ * Allowed MIME types (VineJS `nativeFile().mimeTypes()`). The type is the one
730
+ * the upload REPORTS — rune never reads bytes, see {@link file}.
731
+ */
732
+ mimeTypes(types: readonly string[]): this;
733
+ /**
734
+ * Verify the file's REAL type against its magic number (Adonis parity).
735
+ *
736
+ * A `.exe` renamed `.jpg` passes every declarative check — size, extension,
737
+ * reported MIME — because all three come from the uploader. This reads the
738
+ * leading bytes and refuses a mismatch.
739
+ *
740
+ * Async by nature (it touches the filesystem), so the schema must run with
741
+ * `validateResultAsync` / `validate`. Needs a byte source on the file object
742
+ * (`buffer`, `tmpPath`, `filePath` or `path`) — an Adonis `MultipartFile`
743
+ * carries `tmpPath`. With NO source it FAILS: a content check that cannot
744
+ * run must never look like one that passed.
745
+ */
746
+ verifyContent(): this;
211
747
  /** Must equal one of `values` (enum). Narrows the output to the union. */
212
748
  enum<const V extends readonly (string | number | boolean)[]>(values: V): RuleChain<V[number]>;
213
749
  /** Must equal a literal value. */
@@ -223,25 +759,128 @@ export declare class RuleChain<Output = unknown> {
223
759
  /** Exact length for a string or array (VineJS `fixedLength`). */
224
760
  fixedLength(n: number): this;
225
761
  /** Must be a valid email. */
226
- email(): this;
762
+ email(options?: EmailOptions): this;
227
763
  /** Must match a regular expression (TS-only — never dispatched to Rust). */
228
764
  regex(pattern: RegExp): this;
229
765
  /** Must be a valid URL (TS-only — uses the WHATWG URL parser). */
230
- url(): this;
231
- /** Must be a valid UUID. */
232
- uuid(): this;
766
+ url(options?: UrlOptions): this;
767
+ /**
768
+ * The host must actually resolve (VineJS `activeUrl`).
769
+ *
770
+ * The only rule needing the network, which rune cannot do and stay agnostic
771
+ * and zero-dependency — so it runs through a resolver bound once at boot,
772
+ * exactly like `unique()`. Async by nature: run the schema with
773
+ * `validateResultAsync`. Unbound it THROWS, rather than passing a host nobody
774
+ * checked.
775
+ */
776
+ activeUrl(): this;
777
+ /**
778
+ * Must be a valid UUID, optionally restricted to given versions
779
+ * (VineJS `uuid({ version: [4] })`, versions 1 through 8).
780
+ */
781
+ uuid(options?: {
782
+ version?: number | number[];
783
+ }): this;
784
+ /** Must be a ULID (VineJS `ulid`). */
785
+ ulid(): this;
786
+ /** Must be a JSON Web Token — three dot-separated base64url segments. */
787
+ jwt(): this;
788
+ /** Must contain only ASCII characters (VineJS `ascii`). */
789
+ ascii(): this;
790
+ /** Must be a CSS hex colour code, with or without the leading `#`. */
791
+ hexCode(): this;
792
+ /** Must be an IP address. Pass `version` to require v4 or v6 specifically. */
793
+ ipAddress(options?: {
794
+ version?: 4 | 6;
795
+ }): this;
796
+ /** Must pass the Luhn checksum (VineJS `creditCard`). */
797
+ creditCard(): this;
798
+ /** Must be an IBAN passing the ISO 13616 mod-97 check. */
799
+ iban(): this;
800
+ /** Must be a `"lat,lng"` pair within the valid ranges. */
801
+ coordinates(): this;
802
+ /**
803
+ * Must be a mobile number in E.164 form. Named deviation from VineJS: rune
804
+ * carries no per-locale numbering plans, so there is no `locale` option.
805
+ */
806
+ mobile(options?: {
807
+ locale?: string | string[];
808
+ strictMode?: boolean;
809
+ }): this;
810
+ /**
811
+ * Must be a postal code for `countryCode`. Throws for a country rune has no
812
+ * pattern for, rather than accepting the value unchecked.
813
+ */
814
+ postalCode(options: {
815
+ countryCode: string | string[];
816
+ } | ((field: FieldContext) => {
817
+ countryCode: string | string[];
818
+ })): this;
819
+ /**
820
+ * Must be a valid VAT number (VineJS 4.2 `vat`). Accepts a country list or a
821
+ * callback resolving it per payload.
822
+ *
823
+ * Checksums are run where the country defines a short, well-defined one
824
+ * (BE, DE, NL, IT, PT, LU, CH); the others are FORMAT-only, which is stated
825
+ * rather than implied. An unknown country LEVES rather than accepting the
826
+ * value unchecked.
827
+ */
828
+ vat(options: VatOptions | ((field: FieldContext) => VatOptions)): this;
829
+ /** Must differ from a sibling field (VineJS `notSameAs`). */
830
+ notSameAs(otherField: string): this;
831
+ /** Array items must be unique — optionally compared on `field` (VineJS `distinct`). */
832
+ distinct(field?: string | string[]): this;
833
+ /** Must be less than or equal to zero (VineJS `nonPositive`). */
834
+ nonPositive(): this;
835
+ /** Array must hold at least one item (VineJS `notEmpty`). */
836
+ notEmpty(): this;
837
+ /** Drop `null`, `undefined` and `""` items before the item rules run. */
838
+ compact(): this;
839
+ /** Number must have no fractional part (VineJS `withoutDecimals`). */
840
+ withoutDecimals(): this;
841
+ /** Must be a passport number for `countryCode`. Throws for an uncovered country. */
842
+ passport(options: {
843
+ countryCode: string | string[];
844
+ }): this;
845
+ /** Lowercase the value (VineJS `toLowerCase`). */
846
+ toLowerCase(): this;
847
+ /** Uppercase the value (VineJS `toUpperCase`). */
848
+ toUpperCase(): this;
849
+ /**
850
+ * VineJS `toCamelCase()`, on both shapes it exists for:
851
+ *
852
+ * - on an `object()` chain it camelCases the object's KEYS
853
+ * (`VineObject.toCamelCase`);
854
+ * - on any other chain it camelCases the string VALUE (`VineString`).
855
+ *
856
+ * One name, because Vine has one name. Dispatching on whether a nested shape
857
+ * was declared is what keeps a transcribed validator behaving the same.
858
+ */
859
+ toCamelCase(): this;
860
+ /** HTML-escape `& < > " '` (VineJS `escape`). */
861
+ escape(): this;
862
+ /** Normalise an email address (VineJS `normalizeEmail`). */
863
+ normalizeEmail(options?: NormalizeEmailOptions): this;
864
+ /** Normalise a URL (VineJS `normalizeUrl`). */
865
+ normalizeUrl(options?: NormalizeUrlOptions): this;
233
866
  /** Must contain only ASCII letters. */
234
- alpha(): this;
867
+ alpha(options?: AlphaOptions): this;
235
868
  /** Must contain only ASCII letters and digits. */
236
- alphaNumeric(): this;
869
+ alphaNumeric(options?: AlphaOptions): this;
237
870
  /** String must start with `substring`. */
238
871
  startsWith(substring: string): this;
239
872
  /** String must end with `substring`. */
240
873
  endsWith(substring: string): this;
241
- /** Value must be one of `values`. */
242
- in(values: ReadonlyArray<string | number | boolean>): this;
874
+ /**
875
+ * Value must be one of `values`.
876
+ *
877
+ * VineJS also accepts a callback so the list can be computed at validation
878
+ * time (tenant-scoped roles, values read from config…). A static array is
879
+ * snapshotted; a callback is invoked on every check.
880
+ */
881
+ in(values: AllowedValues): this;
243
882
  /** Value must NOT be one of `values`. */
244
- notIn(values: ReadonlyArray<string | number | boolean>): this;
883
+ notIn(values: AllowedValues): this;
245
884
  /** Number must be positive (> 0) and finite. */
246
885
  positive(): this;
247
886
  /** Number must be negative (< 0) and finite. */
@@ -249,13 +888,14 @@ export declare class RuleChain<Output = unknown> {
249
888
  /** Number must be >= 0 and finite. */
250
889
  nonNegative(): this;
251
890
  /** Number must fall within `[min, max]` (inclusive). */
252
- range(min: number, max: number): this;
891
+ range(bounds: [min: number, max: number]): this;
253
892
  /** Number must have at most `digits` decimal places (TS-only). */
254
- decimal(digits: number): this;
893
+ decimal(digits: number | [number, number]): this;
255
894
  /** Must equal a sibling field (VineJS `sameAs`). Cross-field → TS-only. */
256
895
  sameAs(otherField: string): this;
257
896
  /** Must equal its `<field>_confirmation` sibling (VineJS `confirmed`). */
258
897
  confirmed(options?: {
898
+ as?: string;
259
899
  confirmationField?: string;
260
900
  }): this;
261
901
  /** Required only when `otherField` is present (non-null) — else optional. */
@@ -273,7 +913,7 @@ export declare class RuleChain<Output = unknown> {
273
913
  */
274
914
  transform<U>(fn: (value: unknown, field: FieldContext) => U): RuleChain<U>;
275
915
  /** Pre-validation transform of the raw input (VineJS `parse`). */
276
- parse(fn: (value: unknown) => unknown): this;
916
+ parse(fn: (value: unknown, ctx: ParseContext) => unknown): this;
277
917
  /** Custom validation rule. */
278
918
  custom(name: string, validate: (value: unknown) => boolean, message?: string): this;
279
919
  /**
@@ -281,14 +921,72 @@ export declare class RuleChain<Output = unknown> {
281
921
  * receives a {@link FieldContext} with the root `data` and `parent`, so it can
282
922
  * validate across fields. Runs after this field's type/value rules.
283
923
  */
284
- use(rule: CompiledRule): this;
285
- /** Set custom error message for the last rule. */
924
+ use(rule: CompiledRule | AsyncCompiledRule): this;
925
+ /**
926
+ * Attach an async rule (from {@link createAsyncRule}). The schema must then be
927
+ * run with `validateResultAsync` — sync `validate()` throws for such a schema.
928
+ */
929
+ useAsync(rule: AsyncCompiledRule): this;
930
+ /**
931
+ * DB-backed uniqueness rule (Adonis Lucid `unique`). `check(value, field)`
932
+ * resolves `true` when the value is unique (valid). rune stays agnostic — the
933
+ * check does the query (e.g. against atlas). Requires the async path (`validateResultAsync` / `validate`).
934
+ *
935
+ * rules.string().email().unique(async (value) => {
936
+ * const row = await db.from('users').where('email', value).first()
937
+ * return !row
938
+ * })
939
+ */
940
+ unique(check: (value: unknown, field: FieldContext) => boolean | Promise<boolean>, message?: string): this;
941
+ unique(options: DatabaseRuleOptions, message?: string): this;
942
+ /**
943
+ * DB-backed existence rule (Adonis Lucid `exists`). `check(value, field)`
944
+ * resolves `true` when a matching row exists (valid). Requires the async path (`validateResultAsync` / `validate`).
945
+ */
946
+ exists(check: (value: unknown, field: FieldContext) => boolean | Promise<boolean>, message?: string): this;
947
+ exists(options: DatabaseRuleOptions, message?: string): this;
948
+ /**
949
+ * Attach free-form JSON Schema metadata (VineJS `meta()`) — `title`,
950
+ * `description`, `examples`, `deprecated`… Merged verbatim into the field's
951
+ * node by `toJSONSchema()`.
952
+ */
953
+ meta(metadata: Record<string, unknown>): this;
954
+ /**
955
+ * Set a custom error message for the rule that was just added.
956
+ *
957
+ * "The last rule" spans all three registers: value rules (`#rules`),
958
+ * cross-field `.use()` rules (`sameAs`, `confirmed`, `afterField`,
959
+ * `notSameAs`) and async rules (`unique`, `exists`, `useAsync`). Targeting
960
+ * `#rules` alone silently retargeted the PREVIOUS value rule — or threw
961
+ * `NO_RULE` — whenever the preceding call was a cross-field or async rule.
962
+ */
286
963
  message(msg: string): this;
287
- /** Internal: validate a field value and return errors + transformed value. */
288
- _validateWithTransform(field: string, rawValue: unknown, ctx?: RunContext): {
964
+ /**
965
+ * Register a TYPE rule from outside the chain — used by the `optional()` and
966
+ * `null()` factories, which are types in their own right.
967
+ * @internal
968
+ */
969
+ pushTypeRule(rule: RuleDef): void;
970
+ /** Re-type this chain in place, without cloning. @internal */
971
+ retypeTo<U>(): RuleChain<U>;
972
+ /**
973
+ * Internal: validate a field value and return errors + transformed value.
974
+ *
975
+ * `pending` is the async-rule collector. The traversal itself stays sync (it
976
+ * is shared with `validate()`); when a collector is supplied, every chain in
977
+ * the tree that carries async rules and passed its sync rules records itself
978
+ * for the async path to await. Without it, nested async rules never ran.
979
+ */
980
+ _validateWithTransform(field: string, rawValue: unknown, ctx?: RunContext, pending?: PendingAsync[]): {
289
981
  errors: ValidationError[];
290
982
  transformed: unknown;
291
983
  };
984
+ /**
985
+ * Run this chain's async rules on the (already sync-validated) value, awaiting
986
+ * each in order. Returns the errors they reported. Used by `validateResultAsync`.
987
+ * @internal
988
+ */
989
+ _runAsyncRules(field: string, transformed: unknown, ctx: RunContext): Promise<ValidationError[]>;
292
990
  /** Internal: validate a field value against all rules. */
293
991
  _validate(field: string, value: unknown): ValidationError[];
294
992
  /** Internal: apply transforms. */
@@ -296,12 +994,58 @@ export declare class RuleChain<Output = unknown> {
296
994
  }
297
995
  /** No-op alias of {@link schema} — VineJS `vine.compile()` API parity. */
298
996
  export declare function compile<T extends ValidationSchema>(s: T): T;
997
+ export declare function compile(chain: RuleChain): ValidationSchema;
299
998
  /** Entry point for building rules. */
300
999
  export declare const rules: {
301
1000
  string: () => RuleChain<string>;
302
- number: () => RuleChain<number>;
303
- boolean: () => RuleChain<boolean>;
1001
+ number: (options?: {
1002
+ strict?: boolean;
1003
+ }) => RuleChain<number>;
1004
+ boolean: (options?: {
1005
+ strict?: boolean;
1006
+ }) => RuleChain<boolean>;
304
1007
  any: () => RuleChain<unknown>;
1008
+ date: (options?: {
1009
+ formats?: DateFormat[];
1010
+ }) => RuleChain<Date>;
1011
+ accepted: () => RuleChain<true>;
1012
+ file: (options?: {
1013
+ size?: number | string;
1014
+ extnames?: readonly string[];
1015
+ verifyContent?: boolean;
1016
+ }) => RuleChain<FileLike>;
1017
+ nativeFile: (options?: {
1018
+ minSize?: number | string;
1019
+ maxSize?: number | string;
1020
+ mimeTypes?: readonly string[];
1021
+ }) => RuleChain<FileLike>;
1022
+ record: <Item extends RuleChain>(valueChain: Item) => RuleChain<Record<string, OutputOf<Item>>>;
1023
+ tuple: <const Items extends readonly RuleChain[]>(items: Items) => RuleChain<{ [K in keyof Items]: OutputOf<Items[K]>; }>;
1024
+ union: ((chains: readonly UnionBranch[]) => RuleChain) & {
1025
+ if: typeof unionIf;
1026
+ else: typeof unionElse;
1027
+ otherwise: typeof unionElse;
1028
+ };
1029
+ /**
1030
+ * Union discriminated by the value's TYPE (VineJS `unionOfTypes`): the first
1031
+ * branch whose own type rule accepts the value wins.
1032
+ */
1033
+ /**
1034
+ * Make every property of a shape optional (VineJS `vine.helpers.optional`).
1035
+ * A properties TRANSFORMER, like `pick`/`omit` — it returns a record to
1036
+ * spread, not a schema.
1037
+ */
1038
+ /**
1039
+ * A field that must be ABSENT (VineJS `vine.optional()` → `VineOptional`,
1040
+ * `builder.d.ts:135`). Mostly a `unionOfTypes` branch. Distinct from
1041
+ * `.optional()` on a chain, which relaxes an existing type — this one IS the
1042
+ * type. The properties transformer that used to squat this name moved to
1043
+ * `helpers.optional`, where VineJS keeps it.
1044
+ */
1045
+ optional: () => RuleChain<undefined>;
1046
+ /** A field that must be `null` (VineJS `vine.null()` → `VineNull`). */
1047
+ null: () => RuleChain<null>;
1048
+ unionOfTypes: (chains: readonly RuleChain[]) => RuleChain;
305
1049
  object: <Sh extends Record<string, RuleChain>>(shape: Sh) => RuleChain<Infer<Sh>>;
306
1050
  array: <Item extends RuleChain>(item?: Item) => RuleChain<OutputOf<Item>[]>;
307
1051
  enum: <const V extends readonly (string | number | boolean)[]>(values: V) => RuleChain<V[number]>;