@c9up/rune 0.1.7 → 0.1.9

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 (51) 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 +831 -35
  6. package/dist/Schema.d.ts.map +1 -1
  7. package/dist/Schema.js +2342 -140
  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 +148 -0
  18. package/dist/formats.d.ts.map +1 -0
  19. package/dist/formats.js +671 -0
  20. package/dist/formats.js.map +1 -0
  21. package/dist/index.d.ts +150 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +180 -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/native.d.ts +18 -6
  30. package/dist/native.d.ts.map +1 -1
  31. package/dist/native.js +34 -19
  32. package/dist/native.js.map +1 -1
  33. package/dist/types.d.ts +15 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/types.js +12 -0
  36. package/dist/types.js.map +1 -0
  37. package/index.darwin-arm64.node +0 -0
  38. package/index.darwin-x64.node +0 -0
  39. package/index.linux-arm64-gnu.node +0 -0
  40. package/index.linux-x64-gnu.node +0 -0
  41. package/index.win32-x64-msvc.node +0 -0
  42. package/package.json +9 -1
  43. package/src/MessagesProvider.ts +1 -1
  44. package/src/Schema.ts +3389 -177
  45. package/src/date.ts +320 -0
  46. package/src/errors.ts +11 -0
  47. package/src/formats.ts +776 -0
  48. package/src/index.ts +269 -0
  49. package/src/magic.ts +181 -0
  50. package/src/native.ts +36 -21
  51. 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,171 @@ interface RunContext {
106
275
  parent: Record<string, unknown> | unknown[];
107
276
  meta: Record<string, unknown>;
108
277
  messagesProvider?: MessagesProviderContract;
278
+ errorReporter?: (error: ValidationError) => void;
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>;
109
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
+ /**
396
+ * Called when no union branch matched (VineJS `UnionNoMatchCallback`). Report
397
+ * through the field context; reporting nothing suppresses the generic error.
398
+ */
399
+ export type UnionNoMatchCallback = (value: unknown, field: FieldContext) => void;
400
+ /**
401
+ * Called with a record's keys (VineJS `RecordKeysCallback`). Report through the
402
+ * field context; reporting nothing accepts the key set.
403
+ */
404
+ export type RecordKeysCallback = (keys: string[], field: FieldContext) => void;
405
+ /** Per-field introspection returned inside `toJSON().schema`. */
406
+ export type SchemaIntrospection = Record<string, {
407
+ rules: string[];
408
+ optional: boolean;
409
+ nullable: boolean;
410
+ }>;
411
+ /** Build a conditional group (VineJS `vine.group([...])`). */
412
+ export declare function group(branches: ReadonlyArray<{
413
+ predicate: ((data: Record<string, unknown>) => boolean) | null;
414
+ shape: Record<string, RuleChain>;
415
+ }>): ConditionalGroup;
416
+ /** A predicate-guarded group branch (`vine.group.if`). */
417
+ export declare function groupIf(predicate: (data: Record<string, unknown>) => boolean, shape: Record<string, RuleChain>): {
418
+ predicate: (data: Record<string, unknown>) => boolean;
419
+ shape: Record<string, RuleChain>;
420
+ };
421
+ /** The unconditional fallback branch (`vine.group.else` / `.otherwise`). */
422
+ export declare function groupElse(shape: Record<string, RuleChain>): {
423
+ predicate: null;
424
+ shape: Record<string, RuleChain>;
425
+ };
426
+ /** A union branch guarded by a predicate — `vine.union.if(...)`. */
427
+ export interface ConditionalBranch {
428
+ /** `null` for an unconditional branch (`union.else`). */
429
+ predicate: ((value: unknown, field: FieldContext) => boolean) | null;
430
+ chain: RuleChain;
431
+ }
432
+ /** What `union()` accepts: a bare chain, or a guarded branch. */
433
+ export type UnionBranch = RuleChain | ConditionalBranch;
434
+ /**
435
+ * Guarded union branch (VineJS `vine.union.if`). The predicate picks the branch;
436
+ * the chosen branch's OWN errors are reported, which is what makes a union
437
+ * diagnosable — "matches nothing" tells the caller nothing about which shape it
438
+ * nearly matched.
439
+ */
440
+ export declare function unionIf(predicate: (value: unknown, field: FieldContext) => boolean, chain: RuleChain): ConditionalBranch;
441
+ /** Fallback union branch (VineJS `vine.union.else`). */
442
+ export declare function unionElse(chain: RuleChain): ConditionalBranch;
110
443
  /** Extract the phantom output type of a chain. */
111
444
  type OutputOf<C> = C extends RuleChain<infer O> ? O : never;
112
445
  /** Keys whose output includes `undefined` become optional in the inferred shape. */
@@ -144,8 +477,19 @@ export type Infer<S> = Prettify<{
144
477
  * An explicit generic is still accepted for back-compat
145
478
  * (`schema<MyType>({ ... })`), overriding inference.
146
479
  */
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>;
480
+ export declare function schema<S extends Record<string, RuleChain>>(fields: S, objectChain?: RuleChain): ValidationSchema<Infer<S>>;
481
+ export declare function schema<T = Record<string, unknown>>(fields: Record<string, RuleChain>, objectChain?: RuleChain): ValidationSchema<T>;
482
+ /**
483
+ * VineJS's `vine.create(...)`. Same thing as {@link schema} — the Adonis
484
+ * spelling is provided so a validator reads the same in both frameworks.
485
+ */
486
+ /**
487
+ * VineJS's `vine.create(...)`. Accepts either a map of fields (rune's native
488
+ * spelling) or the `RuleChain` produced by `rune.object({...})`, because
489
+ * `vine.create(vine.object({...}))` is the form Adonis documents.
490
+ */
491
+ export declare function create<S extends Record<string, RuleChain>>(fields: S): ValidationSchema<Infer<S>>;
492
+ export declare function create(chain: RuleChain): ValidationSchema;
149
493
  export declare function setValidationTranslator(translator?: ValidationTranslator): void;
150
494
  export declare function bindRosetta(rosetta: {
151
495
  t(key: string, params?: ValidationMessageParams): string;
@@ -156,10 +500,24 @@ export interface RuleDef {
156
500
  param?: number;
157
501
  /** Interpolation args exposed to i18n/messages providers (e.g. `{ min: 3 }`). */
158
502
  args?: Record<string, unknown>;
159
- validate: (value: unknown) => boolean;
503
+ /**
504
+ * `field` is the context VineJS hands a rule — a callback-valued rule
505
+ * (`in`, `notIn`, `enum`) reads `meta`/`parent` off it to compute its list
506
+ * per request. Rules that do not need it simply declare one parameter.
507
+ */
508
+ validate: (value: unknown, field: FieldContext) => boolean;
160
509
  message: string;
161
510
  /** Set when `.message()` overrode this rule's default text. */
162
511
  hasCustomMessage?: boolean;
512
+ /**
513
+ * Keep this rule off the Rust path even though its NAME is in
514
+ * {@link NATIVE_RULES}. Set by options the native engine does not know about
515
+ * (`uuid({ version })`, a callback list for `in` / `notIn`): the engine would
516
+ * run the rule without them and silently answer a different question.
517
+ */
518
+ tsOnly?: boolean;
519
+ /** Modifier this rule applies to its field's JSON Schema node. */
520
+ toJSONSchema?: JsonSchemaModifier;
163
521
  }
164
522
  /** A conditional-required condition (VineJS `requiredWhen` family). */
165
523
  interface RequiredCondition {
@@ -171,6 +529,11 @@ interface RequiredCondition {
171
529
  /** Phantom brand carrying the inferred output type (never assigned at runtime). */
172
530
  declare const OUTPUT: unique symbol;
173
531
  /** Rule chain — fluent, phantom-typed validation builder. */
532
+ /**
533
+ * The value list accepted by `in` / `notIn` / `enum` — static, or computed at
534
+ * validation time (VineJS parity).
535
+ */
536
+ export type AllowedValues = ReadonlyArray<string | number | boolean> | ((field: FieldContext) => ReadonlyArray<string | number | boolean>);
174
537
  export declare class RuleChain<Output = unknown> {
175
538
  #private;
176
539
  /** Phantom output type — drives {@link Infer}; never read at runtime. */
@@ -186,8 +549,30 @@ export declare class RuleChain<Output = unknown> {
186
549
  }>;
187
550
  /** Public read access to `.use()` rules (used to keep such schemas off the native path). */
188
551
  get useRules(): readonly CompiledRule[];
552
+ /** Public read access to async rules (`unique`/`exists`/`useAsync`) — run by `validateResultAsync`. */
553
+ get asyncRules(): readonly AsyncCompiledRule[];
554
+ /**
555
+ * Does this chain — or anything nested under it (object fields, array items) —
556
+ * carry async rules? The schema-level detection used to inspect only the
557
+ * top-level chains, so a nested `unique`/`exists` was invisible: `validate()`
558
+ * did not throw and the async pass never ran the rule, silently accepting
559
+ * an unchecked value.
560
+ */
561
+ get hasAsyncRulesDeep(): boolean;
562
+ /** Does this object keep keys its shape does not declare? */
563
+ get allowsUnknown(): boolean;
564
+ /** Free-form JSON Schema metadata attached with `meta()`. */
565
+ get metadata(): Record<string, unknown> | null;
566
+ /** The item chain of an `array()`, if declared. */
567
+ get arrayItem(): RuleChain | null;
568
+ /** The positional chains of a `tuple()`, if declared. */
569
+ get tupleItems(): RuleChain[] | null;
570
+ /** The value chain of a `record()`, if declared. */
571
+ get recordValue(): RuleChain | null;
572
+ /** Whether this chain stops at its first failing rule (VineJS `bail`). */
573
+ get bails(): boolean;
189
574
  /** Public read access to `.parse()` pre-transforms (kept off the native path). */
190
- get preTransforms(): ReadonlyArray<(value: unknown) => unknown>;
575
+ get preTransforms(): ReadonlyArray<(value: unknown, ctx: ParseContext) => unknown>;
191
576
  /** Whether this chain carries a `requiredWhen`-family condition. */
192
577
  get hasConditionalRequired(): boolean;
193
578
  /** Mark field as optional (absent / `undefined` allowed). */
@@ -204,12 +589,210 @@ export declare class RuleChain<Output = unknown> {
204
589
  array<Item extends RuleChain>(itemChain?: Item): RuleChain<OutputOf<Item>[]>;
205
590
  /** Must be a string. */
206
591
  string(): RuleChain<string>;
207
- /** Must be a number. */
208
- number(): RuleChain<number>;
209
- /** Must be a boolean. */
210
- boolean(): RuleChain<boolean>;
211
- /** Must equal one of `values` (enum). Narrows the output to the union. */
212
- enum<const V extends readonly (string | number | boolean)[]>(values: V): RuleChain<V[number]>;
592
+ /**
593
+ * Must be a number. Like VineJS, a numeric STRING is coerced (`"32"` → `32`)
594
+ * HTML form bodies and query strings carry numbers as text, so requiring
595
+ * `typeof v === "number"` rejected the values Adonis accepts. Pass
596
+ * `{ strict: true }` to refuse anything that is not already a number.
597
+ */
598
+ number(options?: {
599
+ strict?: boolean;
600
+ }): RuleChain<number>;
601
+ /**
602
+ * Must be a boolean. Like VineJS, `"true"`, `"false"`, `"on"`, `"off"`,
603
+ * `"1"`, `"0"`, `1` and `0` are coerced; `{ strict: true }` refuses them.
604
+ */
605
+ boolean(options?: {
606
+ strict?: boolean;
607
+ }): RuleChain<boolean>;
608
+ /**
609
+ * Must be a date (VineJS `vine.date()`). ISO 8601 by default; pass `formats`
610
+ * for unix timestamps (`x` = ms, `X` = seconds) or a token format such as
611
+ * `DD/MM/YYYY`. Parsing is calendar-strict — `2026-02-31` is rejected.
612
+ *
613
+ * The validated output is a `Date`; bind {@link setDateTransform} to map it
614
+ * to your own type once at boot.
615
+ */
616
+ date(options?: {
617
+ formats?: DateFormat[];
618
+ }): RuleChain<Date>;
619
+ /** Must be strictly after `operand` (`'today'`, an ISO string, or a `Date`). */
620
+ after(operand: unknown, options?: DateCompareOptions): this;
621
+ /** Must be strictly before `operand`. */
622
+ before(operand: unknown, options?: DateCompareOptions): this;
623
+ /** Must be after `operand`, or equal to it. */
624
+ afterOrEqual(operand: unknown, options?: DateCompareOptions): this;
625
+ /** Must be before `operand`, or equal to it. */
626
+ beforeOrEqual(operand: unknown, options?: DateCompareOptions): this;
627
+ /** Must be after the date held by a sibling field (VineJS `afterField`). */
628
+ afterField(otherField: string, options?: DateCompareOptions): this;
629
+ /** Must be before the date held by a sibling field. */
630
+ beforeField(otherField: string, options?: DateCompareOptions): this;
631
+ /** Must be the same instant as `operand` (VineJS `equals`). */
632
+ equals(operand: unknown, options?: DateCompareOptions): this;
633
+ /** Must be after the sibling's date, or the same instant (VineJS `afterOrSameAs`). */
634
+ afterOrSameAs(otherField: string, options?: DateCompareOptions): this;
635
+ /** Must be before the sibling's date, or the same instant. */
636
+ beforeOrSameAs(otherField: string, options?: DateCompareOptions): this;
637
+ /** Must fall on a Saturday or Sunday (VineJS `weekend`). */
638
+ weekend(): this;
639
+ /** Must fall on a Monday-to-Friday day (VineJS `weekday`). */
640
+ weekday(): this;
641
+ /**
642
+ * Keep keys the object shape does not declare (VineJS
643
+ * `allowUnknownProperties`). Off by default: dropping undeclared keys is what
644
+ * makes a validated payload safe to hand to a mass assignment.
645
+ */
646
+ allowUnknownProperties(): this;
647
+ /**
648
+ * Convert the object's KEYS to camelCase in the output (VineJS
649
+ * `object.toCamelCase()`), so a snake_case payload hydrates camelCase
650
+ * properties. Distinct from the string `toCamelCase()`, which rewrites a
651
+ * VALUE — that one was never a substitute for this.
652
+ */
653
+ toCamelCaseKeys(): this;
654
+ /**
655
+ * Merge extra properties into this object's shape (VineJS `merge`). Accepts a
656
+ * plain shape or a {@link ConditionalGroup} whose branch is chosen per
657
+ * payload — `vine.group` in VineJS.
658
+ */
659
+ merge(extra: Record<string, RuleChain> | ConditionalGroup): this;
660
+ /** The nested shape declared by `object()`, if any (VineJS `getProperties`). */
661
+ getProperties(): Record<string, RuleChain> | null;
662
+ /** Independent copy of this chain (VineJS `clone`). */
663
+ clone(): RuleChain<Output>;
664
+ /**
665
+ * A CLONED subset of the object's properties (VineJS `pick`).
666
+ *
667
+ * Returns a properties record, not a schema — VineJS types it
668
+ * `Pick<Properties, Keys>` precisely so it composes by spread:
669
+ * `rules.any().object({ ...userShape.pick(["id"]) })`. Returning a chain here
670
+ * broke that idiom.
671
+ */
672
+ pick<K extends string>(keys: readonly K[]): Record<string, RuleChain>;
673
+ /** A cloned copy of the properties EXCLUDING `keys` (VineJS `omit`). */
674
+ omit<K extends string>(keys: readonly K[]): Record<string, RuleChain>;
675
+ /** Make every property of an object shape optional (VineJS `partial`). */
676
+ partial(keys?: readonly string[]): RuleChain<Output>;
677
+ /**
678
+ * Must be an "accepted" value — `true`, `1`, `"1"`, `"on"`, `"yes"`,
679
+ * `"true"` (VineJS `accepted`, for checkbox-style consent fields).
680
+ */
681
+ accepted(): RuleChain<true>;
682
+ /**
683
+ * Object with arbitrary keys, every value validated by `valueChain`
684
+ * (VineJS `record`).
685
+ */
686
+ record<Item extends RuleChain>(valueChain: Item): RuleChain<Record<string, OutputOf<Item>>>;
687
+ /**
688
+ * Check the record's KEYS, not its values (VineJS `record().validateKeys()`).
689
+ *
690
+ * The callback receives every key at once and reports through the field
691
+ * context — the set is what matters when keys must be exclusive, exhaustive,
692
+ * or drawn from a list only known at runtime.
693
+ */
694
+ validateKeys(callback: RecordKeysCallback): this;
695
+ /**
696
+ * Fixed-length array with a schema per position (VineJS `tuple`). Extra
697
+ * items are rejected — a tuple that silently ignores a trailing element is
698
+ * how unvalidated data slips through.
699
+ */
700
+ tuple<const Items extends readonly RuleChain[]>(items: Items): RuleChain<{
701
+ [K in keyof Items]: OutputOf<Items[K]>;
702
+ }>;
703
+ /**
704
+ * Value must satisfy at least one of `chains`.
705
+ *
706
+ * Two forms, both supported:
707
+ *
708
+ * - guarded (VineJS parity): `union([rules.union.if(pred, chain), …,
709
+ * rules.union.else(fallback)])` — the predicate SELECTS the branch and
710
+ * that branch's own errors are reported, so a failure says which shape was
711
+ * meant and why it did not fit.
712
+ * - bare chains: tried in order, first match wins, and a total miss reports a
713
+ * single `union` error rather than every losing branch's noise.
714
+ */
715
+ /**
716
+ * What to do when NO union branch matched (VineJS `union().otherwise()`).
717
+ *
718
+ * The callback receives the value and the field, and reports the error it
719
+ * wants — the point being that "matches nothing" is a useless message when
720
+ * the caller knows which shapes were on offer. Reporting nothing from the
721
+ * callback suppresses the generic error entirely, which is how a union
722
+ * folded into a larger check stays quiet.
723
+ */
724
+ otherwise(callback: UnionNoMatchCallback): this;
725
+ union(chains: readonly UnionBranch[]): this;
726
+ /**
727
+ * Must be an uploaded file (VineJS/Adonis `vine.file()`).
728
+ *
729
+ * Named deviation: Adonis validates a bodyparser `MultipartFile`, which rune
730
+ * cannot import and stay agnostic. It checks the STRUCTURE instead — any
731
+ * object exposing `size` and a name/extension — so an Adonis MultipartFile
732
+ * satisfies it, and so does any other upload representation.
733
+ *
734
+ * `size` is a byte count; `extnames` are compared lowercase, without the dot.
735
+ */
736
+ file(options?: {
737
+ size?: number | string;
738
+ extnames?: readonly string[];
739
+ /**
740
+ * Skip the magic-number check. Default `true` — Adonis derives `extname`
741
+ * from the real bytes before validation, so trusting the declaration is
742
+ * NOT the safe default: a `.exe` renamed `.png` satisfies every
743
+ * declarative check, since all of them come from the uploader.
744
+ */
745
+ verifyContent?: boolean;
746
+ }): RuleChain<FileLike>;
747
+ /**
748
+ * Uploaded file with VineJS `nativeFile` options — `minSize`, `maxSize`,
749
+ * `mimeTypes`. Same structural contract as {@link file}: rune never reads
750
+ * bytes, so the MIME type is the one the upload REPORTS.
751
+ */
752
+ nativeFile(options?: {
753
+ minSize?: number | string;
754
+ maxSize?: number | string;
755
+ mimeTypes?: readonly string[];
756
+ }): RuleChain<FileLike>;
757
+ /** Minimum upload size (VineJS `nativeFile().minSize()`). */
758
+ minSize(size: number | string): this;
759
+ /** Maximum upload size (VineJS `nativeFile().maxSize()`). */
760
+ maxSize(size: number | string): this;
761
+ /**
762
+ * Allowed MIME types (VineJS `nativeFile().mimeTypes()`). The type is the one
763
+ * the upload REPORTS — rune never reads bytes, see {@link file}.
764
+ */
765
+ mimeTypes(types: readonly string[]): this;
766
+ /**
767
+ * Verify the file's REAL type against its magic number (Adonis parity).
768
+ *
769
+ * A `.exe` renamed `.jpg` passes every declarative check — size, extension,
770
+ * reported MIME — because all three come from the uploader. This reads the
771
+ * leading bytes and refuses a mismatch.
772
+ *
773
+ * Async by nature (it touches the filesystem), so the schema must run with
774
+ * `validateResultAsync` / `validate`. Needs a byte source on the file object
775
+ * (`buffer`, `tmpPath`, `filePath` or `path`) — an Adonis `MultipartFile`
776
+ * carries `tmpPath`. With NO source it FAILS: a content check that cannot
777
+ * run must never look like one that passed.
778
+ */
779
+ verifyContent(): this;
780
+ /**
781
+ * Must equal one of `values` (enum). Narrows the output to the union.
782
+ *
783
+ * `values` may be a callback receiving the field, which is how a list that
784
+ * depends on the request — the roles this tenant allows, the statuses this
785
+ * user may set — is computed per validation instead of frozen at import.
786
+ */
787
+ enum<const V extends readonly (string | number | boolean)[]>(values: V | ((field: FieldContext) => V)): RuleChain<V[number]>;
788
+ /**
789
+ * The choices this enum was declared with (VineJS `getChoices()`) — the list
790
+ * itself, or the callback when it is computed per request.
791
+ *
792
+ * Reading them back is what lets a form render the same options the
793
+ * validator will accept, from one declaration instead of two.
794
+ */
795
+ getChoices(): ReadonlyArray<string | number | boolean> | ((field: FieldContext) => ReadonlyArray<string | number | boolean>) | undefined;
213
796
  /** Must equal a literal value. */
214
797
  literal<V extends string | number | boolean>(value: V): RuleChain<V>;
215
798
  /** Minimum length (string) or minimum value (number). Alias of min/minLength. */
@@ -223,25 +806,133 @@ export declare class RuleChain<Output = unknown> {
223
806
  /** Exact length for a string or array (VineJS `fixedLength`). */
224
807
  fixedLength(n: number): this;
225
808
  /** Must be a valid email. */
226
- email(): this;
809
+ email(options?: EmailOptions): this;
227
810
  /** Must match a regular expression (TS-only — never dispatched to Rust). */
228
811
  regex(pattern: RegExp): this;
229
812
  /** Must be a valid URL (TS-only — uses the WHATWG URL parser). */
230
- url(): this;
231
- /** Must be a valid UUID. */
232
- uuid(): this;
813
+ url(options?: UrlOptions): this;
814
+ /**
815
+ * The host must actually resolve (VineJS `activeUrl`).
816
+ *
817
+ * The only rule needing the network, which rune cannot do and stay agnostic
818
+ * and zero-dependency — so it runs through a resolver bound once at boot,
819
+ * exactly like `unique()`. Async by nature: run the schema with
820
+ * `validateResultAsync`. Unbound it THROWS, rather than passing a host nobody
821
+ * checked.
822
+ */
823
+ activeUrl(): this;
824
+ /**
825
+ * Must be a valid UUID, optionally restricted to given versions
826
+ * (VineJS `uuid({ version: [4] })`, versions 1 through 8).
827
+ */
828
+ uuid(options?: {
829
+ version?: number | number[];
830
+ }): this;
831
+ /** Must be a ULID (VineJS `ulid`). */
832
+ ulid(): this;
833
+ /** Must be a JSON Web Token — three dot-separated base64url segments. */
834
+ jwt(): this;
835
+ /** Must contain only ASCII characters (VineJS `ascii`). */
836
+ ascii(): this;
837
+ /** Must be a CSS hex colour code, with or without the leading `#`. */
838
+ hexCode(): this;
839
+ /** Must be an IP address. Pass `version` to require v4 or v6 specifically. */
840
+ ipAddress(options?: {
841
+ version?: 4 | 6;
842
+ }): this;
843
+ /** Must pass the Luhn checksum (VineJS `creditCard`). */
844
+ creditCard(): this;
845
+ /** Must be an IBAN passing the ISO 13616 mod-97 check. */
846
+ iban(): this;
847
+ /** Must be a `"lat,lng"` pair within the valid ranges. */
848
+ coordinates(): this;
849
+ /**
850
+ * Must be a mobile number (VineJS/Adonis `mobile()`).
851
+ *
852
+ * With no `locale`, the number must be in E.164 form. With one or more,
853
+ * it must match one of their numbering plans, as in VineJS. rune carries
854
+ * its own plans rather than validator.js', so it knows fewer locales — an
855
+ * unknown one raises `UNSUPPORTED_LOCALE` at schema build, naming the ones
856
+ * it does know, rather than silently accepting anything at request time.
857
+ */
858
+ mobile(options?: {
859
+ locale?: string | string[];
860
+ strictMode?: boolean;
861
+ }): this;
862
+ /**
863
+ * Must be a postal code for `countryCode`. Throws for a country rune has no
864
+ * pattern for, rather than accepting the value unchecked.
865
+ */
866
+ postalCode(options: {
867
+ countryCode: string | string[];
868
+ } | ((field: FieldContext) => {
869
+ countryCode: string | string[];
870
+ })): this;
871
+ /**
872
+ * Must be a valid VAT number (VineJS 4.2 `vat`). Accepts a country list or a
873
+ * callback resolving it per payload.
874
+ *
875
+ * Checksums are run where the country defines a short, well-defined one
876
+ * (BE, DE, NL, IT, PT, LU, CH); the others are FORMAT-only, which is stated
877
+ * rather than implied. An unknown country LEVES rather than accepting the
878
+ * value unchecked.
879
+ */
880
+ vat(options: VatOptions | ((field: FieldContext) => VatOptions)): this;
881
+ /** Must differ from a sibling field (VineJS `notSameAs`). */
882
+ notSameAs(otherField: string): this;
883
+ /** Array items must be unique — optionally compared on `field` (VineJS `distinct`). */
884
+ distinct(field?: string | string[]): this;
885
+ /** Must be less than or equal to zero (VineJS `nonPositive`). */
886
+ nonPositive(): this;
887
+ /** Array must hold at least one item (VineJS `notEmpty`). */
888
+ notEmpty(): this;
889
+ /** Drop `null`, `undefined` and `""` items before the item rules run. */
890
+ compact(): this;
891
+ /** Number must have no fractional part (VineJS `withoutDecimals`). */
892
+ withoutDecimals(): this;
893
+ /** Must be a passport number for `countryCode`. Throws for an uncovered country. */
894
+ passport(options: {
895
+ countryCode: string | string[];
896
+ }): this;
897
+ /** Lowercase the value (VineJS `toLowerCase`). */
898
+ toLowerCase(): this;
899
+ /** Uppercase the value (VineJS `toUpperCase`). */
900
+ toUpperCase(): this;
901
+ /**
902
+ * VineJS `toCamelCase()`, on both shapes it exists for:
903
+ *
904
+ * - on an `object()` chain it camelCases the object's KEYS
905
+ * (`VineObject.toCamelCase`);
906
+ * - on any other chain it camelCases the string VALUE (`VineString`).
907
+ *
908
+ * One name, because Vine has one name. Dispatching on whether a nested shape
909
+ * was declared is what keeps a transcribed validator behaving the same.
910
+ */
911
+ toCamelCase(): this;
912
+ /** HTML-escape `& < > " '` (VineJS `escape`). */
913
+ escape(): this;
914
+ /** Normalise an email address (VineJS `normalizeEmail`). */
915
+ normalizeEmail(options?: NormalizeEmailOptions): this;
916
+ /** Normalise a URL (VineJS `normalizeUrl`). */
917
+ normalizeUrl(options?: NormalizeUrlOptions): this;
233
918
  /** Must contain only ASCII letters. */
234
- alpha(): this;
919
+ alpha(options?: AlphaOptions): this;
235
920
  /** Must contain only ASCII letters and digits. */
236
- alphaNumeric(): this;
921
+ alphaNumeric(options?: AlphaOptions): this;
237
922
  /** String must start with `substring`. */
238
923
  startsWith(substring: string): this;
239
924
  /** String must end with `substring`. */
240
925
  endsWith(substring: string): this;
241
- /** Value must be one of `values`. */
242
- in(values: ReadonlyArray<string | number | boolean>): this;
926
+ /**
927
+ * Value must be one of `values`.
928
+ *
929
+ * VineJS also accepts a callback so the list can be computed at validation
930
+ * time (tenant-scoped roles, values read from config…). A static array is
931
+ * snapshotted; a callback is invoked on every check.
932
+ */
933
+ in(values: AllowedValues): this;
243
934
  /** Value must NOT be one of `values`. */
244
- notIn(values: ReadonlyArray<string | number | boolean>): this;
935
+ notIn(values: AllowedValues): this;
245
936
  /** Number must be positive (> 0) and finite. */
246
937
  positive(): this;
247
938
  /** Number must be negative (< 0) and finite. */
@@ -249,13 +940,14 @@ export declare class RuleChain<Output = unknown> {
249
940
  /** Number must be >= 0 and finite. */
250
941
  nonNegative(): this;
251
942
  /** Number must fall within `[min, max]` (inclusive). */
252
- range(min: number, max: number): this;
943
+ range(bounds: [min: number, max: number]): this;
253
944
  /** Number must have at most `digits` decimal places (TS-only). */
254
- decimal(digits: number): this;
945
+ decimal(digits: number | [number, number]): this;
255
946
  /** Must equal a sibling field (VineJS `sameAs`). Cross-field → TS-only. */
256
947
  sameAs(otherField: string): this;
257
948
  /** Must equal its `<field>_confirmation` sibling (VineJS `confirmed`). */
258
949
  confirmed(options?: {
950
+ as?: string;
259
951
  confirmationField?: string;
260
952
  }): this;
261
953
  /** Required only when `otherField` is present (non-null) — else optional. */
@@ -273,7 +965,7 @@ export declare class RuleChain<Output = unknown> {
273
965
  */
274
966
  transform<U>(fn: (value: unknown, field: FieldContext) => U): RuleChain<U>;
275
967
  /** Pre-validation transform of the raw input (VineJS `parse`). */
276
- parse(fn: (value: unknown) => unknown): this;
968
+ parse(fn: (value: unknown, ctx: ParseContext) => unknown): this;
277
969
  /** Custom validation rule. */
278
970
  custom(name: string, validate: (value: unknown) => boolean, message?: string): this;
279
971
  /**
@@ -281,14 +973,72 @@ export declare class RuleChain<Output = unknown> {
281
973
  * receives a {@link FieldContext} with the root `data` and `parent`, so it can
282
974
  * validate across fields. Runs after this field's type/value rules.
283
975
  */
284
- use(rule: CompiledRule): this;
285
- /** Set custom error message for the last rule. */
976
+ use(rule: CompiledRule | AsyncCompiledRule): this;
977
+ /**
978
+ * Attach an async rule (from {@link createAsyncRule}). The schema must then be
979
+ * run with `validateResultAsync` — sync `validate()` throws for such a schema.
980
+ */
981
+ useAsync(rule: AsyncCompiledRule): this;
982
+ /**
983
+ * DB-backed uniqueness rule (Adonis Lucid `unique`). `check(value, field)`
984
+ * resolves `true` when the value is unique (valid). rune stays agnostic — the
985
+ * check does the query (e.g. against atlas). Requires the async path (`validateResultAsync` / `validate`).
986
+ *
987
+ * rules.string().email().unique(async (value) => {
988
+ * const row = await db.from('users').where('email', value).first()
989
+ * return !row
990
+ * })
991
+ */
992
+ unique(check: (value: unknown, field: FieldContext) => boolean | Promise<boolean>, message?: string): this;
993
+ unique(options: DatabaseRuleOptions, message?: string): this;
994
+ /**
995
+ * DB-backed existence rule (Adonis Lucid `exists`). `check(value, field)`
996
+ * resolves `true` when a matching row exists (valid). Requires the async path (`validateResultAsync` / `validate`).
997
+ */
998
+ exists(check: (value: unknown, field: FieldContext) => boolean | Promise<boolean>, message?: string): this;
999
+ exists(options: DatabaseRuleOptions, message?: string): this;
1000
+ /**
1001
+ * Attach free-form JSON Schema metadata (VineJS `meta()`) — `title`,
1002
+ * `description`, `examples`, `deprecated`… Merged verbatim into the field's
1003
+ * node by `toJSONSchema()`.
1004
+ */
1005
+ meta(metadata: Record<string, unknown>): this;
1006
+ /**
1007
+ * Set a custom error message for the rule that was just added.
1008
+ *
1009
+ * "The last rule" spans all three registers: value rules (`#rules`),
1010
+ * cross-field `.use()` rules (`sameAs`, `confirmed`, `afterField`,
1011
+ * `notSameAs`) and async rules (`unique`, `exists`, `useAsync`). Targeting
1012
+ * `#rules` alone silently retargeted the PREVIOUS value rule — or threw
1013
+ * `NO_RULE` — whenever the preceding call was a cross-field or async rule.
1014
+ */
286
1015
  message(msg: string): this;
287
- /** Internal: validate a field value and return errors + transformed value. */
288
- _validateWithTransform(field: string, rawValue: unknown, ctx?: RunContext): {
1016
+ /**
1017
+ * Register a TYPE rule from outside the chain — used by the `optional()` and
1018
+ * `null()` factories, which are types in their own right.
1019
+ * @internal
1020
+ */
1021
+ pushTypeRule(rule: RuleDef): void;
1022
+ /** Re-type this chain in place, without cloning. @internal */
1023
+ retypeTo<U>(): RuleChain<U>;
1024
+ /**
1025
+ * Internal: validate a field value and return errors + transformed value.
1026
+ *
1027
+ * `pending` is the async-rule collector. The traversal itself stays sync (it
1028
+ * is shared with `validate()`); when a collector is supplied, every chain in
1029
+ * the tree that carries async rules and passed its sync rules records itself
1030
+ * for the async path to await. Without it, nested async rules never ran.
1031
+ */
1032
+ _validateWithTransform(field: string, rawValue: unknown, ctx?: RunContext, pending?: PendingAsync[]): {
289
1033
  errors: ValidationError[];
290
1034
  transformed: unknown;
291
1035
  };
1036
+ /**
1037
+ * Run this chain's async rules on the (already sync-validated) value, awaiting
1038
+ * each in order. Returns the errors they reported. Used by `validateResultAsync`.
1039
+ * @internal
1040
+ */
1041
+ _runAsyncRules(field: string, transformed: unknown, ctx: RunContext): Promise<ValidationError[]>;
292
1042
  /** Internal: validate a field value against all rules. */
293
1043
  _validate(field: string, value: unknown): ValidationError[];
294
1044
  /** Internal: apply transforms. */
@@ -296,15 +1046,61 @@ export declare class RuleChain<Output = unknown> {
296
1046
  }
297
1047
  /** No-op alias of {@link schema} — VineJS `vine.compile()` API parity. */
298
1048
  export declare function compile<T extends ValidationSchema>(s: T): T;
1049
+ export declare function compile(chain: RuleChain): ValidationSchema;
299
1050
  /** Entry point for building rules. */
300
1051
  export declare const rules: {
301
1052
  string: () => RuleChain<string>;
302
- number: () => RuleChain<number>;
303
- boolean: () => RuleChain<boolean>;
1053
+ number: (options?: {
1054
+ strict?: boolean;
1055
+ }) => RuleChain<number>;
1056
+ boolean: (options?: {
1057
+ strict?: boolean;
1058
+ }) => RuleChain<boolean>;
304
1059
  any: () => RuleChain<unknown>;
1060
+ date: (options?: {
1061
+ formats?: DateFormat[];
1062
+ }) => RuleChain<Date>;
1063
+ accepted: () => RuleChain<true>;
1064
+ file: (options?: {
1065
+ size?: number | string;
1066
+ extnames?: readonly string[];
1067
+ verifyContent?: boolean;
1068
+ }) => RuleChain<FileLike>;
1069
+ nativeFile: (options?: {
1070
+ minSize?: number | string;
1071
+ maxSize?: number | string;
1072
+ mimeTypes?: readonly string[];
1073
+ }) => RuleChain<FileLike>;
1074
+ record: <Item extends RuleChain>(valueChain: Item) => RuleChain<Record<string, OutputOf<Item>>>;
1075
+ tuple: <const Items extends readonly RuleChain[]>(items: Items) => RuleChain<{ [K in keyof Items]: OutputOf<Items[K]>; }>;
1076
+ union: ((chains: readonly UnionBranch[]) => RuleChain) & {
1077
+ if: typeof unionIf;
1078
+ else: typeof unionElse;
1079
+ otherwise: typeof unionElse;
1080
+ };
1081
+ /**
1082
+ * Union discriminated by the value's TYPE (VineJS `unionOfTypes`): the first
1083
+ * branch whose own type rule accepts the value wins.
1084
+ */
1085
+ /**
1086
+ * Make every property of a shape optional (VineJS `vine.helpers.optional`).
1087
+ * A properties TRANSFORMER, like `pick`/`omit` — it returns a record to
1088
+ * spread, not a schema.
1089
+ */
1090
+ /**
1091
+ * A field that must be ABSENT (VineJS `vine.optional()` → `VineOptional`,
1092
+ * `builder.d.ts:135`). Mostly a `unionOfTypes` branch. Distinct from
1093
+ * `.optional()` on a chain, which relaxes an existing type — this one IS the
1094
+ * type. The properties transformer that used to squat this name moved to
1095
+ * `helpers.optional`, where VineJS keeps it.
1096
+ */
1097
+ optional: () => RuleChain<undefined>;
1098
+ /** A field that must be `null` (VineJS `vine.null()` → `VineNull`). */
1099
+ null: () => RuleChain<null>;
1100
+ unionOfTypes: (chains: readonly RuleChain[]) => RuleChain;
305
1101
  object: <Sh extends Record<string, RuleChain>>(shape: Sh) => RuleChain<Infer<Sh>>;
306
1102
  array: <Item extends RuleChain>(item?: Item) => RuleChain<OutputOf<Item>[]>;
307
- enum: <const V extends readonly (string | number | boolean)[]>(values: V) => RuleChain<V[number]>;
1103
+ enum: <const V extends readonly (string | number | boolean)[]>(values: V | ((field: FieldContext) => V)) => RuleChain<V[number]>;
308
1104
  literal: <V extends string | number | boolean>(value: V) => RuleChain<V>;
309
1105
  };
310
1106
  export {};