@c9up/rune 0.1.6 → 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 +45 -0
  2. package/dist/MessagesProvider.d.ts.map +1 -0
  3. package/dist/MessagesProvider.js +77 -0
  4. package/dist/MessagesProvider.js.map +1 -0
  5. package/dist/Schema.d.ts +912 -38
  6. package/dist/Schema.d.ts.map +1 -1
  7. package/dist/Schema.js +2841 -219
  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 +42 -0
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +37 -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 +152 -3
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +181 -2
  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 +11 -1
  39. package/src/MessagesProvider.ts +115 -0
  40. package/src/Schema.ts +3970 -215
  41. package/src/date.ts +320 -0
  42. package/src/errors.ts +59 -0
  43. package/src/formats.ts +721 -0
  44. package/src/index.ts +266 -1
  45. package/src/magic.ts +181 -0
  46. package/src/types.ts +55 -0
package/dist/Schema.d.ts CHANGED
@@ -3,12 +3,20 @@
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";
9
+ import type { MessagesProviderContract } from "./MessagesProvider.js";
6
10
  export type ValidationMessageParams = Record<string, string | number | boolean>;
7
11
  export type ValidationTranslator = (key: string, params?: ValidationMessageParams) => string | undefined;
8
12
  export interface ValidationError {
9
13
  field: string;
10
14
  rule: string;
11
15
  message: string;
16
+ /** Array index when the field is an array item (VineJS parity). */
17
+ index?: number;
18
+ /** Rule metadata carried for reporters/i18n (e.g. `{ min: 3 }`). */
19
+ meta?: Record<string, unknown>;
12
20
  }
13
21
  /**
14
22
  * Field context handed to `.use()` rules — mirrors VineJS's field context. It
@@ -29,8 +37,26 @@ export interface FieldContext {
29
37
  meta: Record<string, unknown>;
30
38
  /** `true` while no error has been reported for this field yet. */
31
39
  isValid: boolean;
32
- /** Report a validation failure for this field. */
33
- 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;
34
60
  }
35
61
  /**
36
62
  * A `.use()` rule validator — VineJS shape `(value, options, field)`. Report
@@ -40,6 +66,13 @@ export type RuleValidator<Options = undefined> = (value: unknown, options: Optio
40
66
  /** A compiled `.use()` rule produced by {@link createRule}. */
41
67
  export interface CompiledRule {
42
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;
43
76
  run(value: unknown, field: FieldContext): void;
44
77
  }
45
78
  /**
@@ -57,8 +90,61 @@ export interface CompiledRule {
57
90
  * passwordConfirmation: rules.string().use(sameAs('password')),
58
91
  * })
59
92
  */
60
- export declare function createRule(validator: RuleValidator<undefined>): () => CompiledRule;
61
- 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;
62
148
  /**
63
149
  * Validation result — discriminated union that narrows `data` to the schema's
64
150
  * `T` when `valid` is `true`, removing the need for callers to cast or guard
@@ -77,34 +163,323 @@ export type ValidationResult<T = Record<string, unknown>> = {
77
163
  export interface ValidateOptions {
78
164
  /** Runtime metadata exposed to `.use()` rules via `field.meta` (VineJS parity). */
79
165
  meta?: Record<string, unknown>;
166
+ /**
167
+ * VineJS-style messages provider. When supplied, default rule messages are
168
+ * resolved through it (custom `.message()` overrides still win, and the
169
+ * provider takes precedence over a globally bound translator).
170
+ */
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);
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;
80
198
  }
199
+ /** A zero-argument factory producing a fresh {@link ErrorReporterContract}. */
200
+ export type ErrorReporterFactory = () => ErrorReporterContract;
81
201
  export interface ValidationSchema<T = Record<string, unknown>> {
82
202
  fields: Record<string, RuleChain>;
83
- 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>>;
256
+ /**
257
+ * Throwing validation (VineJS/Adonis parity). Returns the validated data on
258
+ * success; throws {@link RuneValidationError} (`E_VALIDATION_ERROR`, HTTP 422)
259
+ * with a structured `.messages` array on failure.
260
+ */
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>;
84
271
  }
85
272
  /** Context threaded through validation so field rules can reach root/parent/meta. */
86
273
  interface RunContext {
87
274
  data: Record<string, unknown>;
88
275
  parent: Record<string, unknown> | unknown[];
89
276
  meta: Record<string, unknown>;
277
+ messagesProvider?: MessagesProviderContract;
278
+ errorReporter?: (error: ValidationError) => void;
90
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;
433
+ /** Extract the phantom output type of a chain. */
434
+ type OutputOf<C> = C extends RuleChain<infer O> ? O : never;
435
+ /** Keys whose output includes `undefined` become optional in the inferred shape. */
436
+ type OptionalKeys<S> = {
437
+ [K in keyof S]: undefined extends OutputOf<S[K]> ? K : never;
438
+ }[keyof S];
439
+ /** Flatten an intersection into a single readable object type. */
440
+ type Prettify<T> = {
441
+ [K in keyof T]: T[K];
442
+ } & unknown;
443
+ /**
444
+ * Infer the validated data shape from a schema's field map — the type
445
+ * `result.data` carries once `result.valid === true`. `rules.string()` →
446
+ * `string`, `.optional()` → an optional key, `.nullable()` → `T | null`,
447
+ * `.object(shape)`/`.array(item)` recurse.
448
+ */
449
+ export type Infer<S> = Prettify<{
450
+ [K in Exclude<keyof S, OptionalKeys<S>>]: OutputOf<S[K]>;
451
+ } & {
452
+ [K in OptionalKeys<S>]?: Exclude<OutputOf<S[K]>, undefined>;
453
+ }>;
91
454
  /**
92
455
  * Create a validation schema.
93
456
  *
94
- * Pass `T` explicitly when the caller wants `result.data` typed as a concrete
95
- * shape after `result.valid === true` narrows the union — runtime validation
96
- * is unchanged, the generic only types the success branch.
457
+ * The field map's rule chains are phantom-typed, so `result.data` is inferred
458
+ * automatically `schema({ email: rules.string(), age: rules.number() })`
459
+ * types `data` as `{ email: string; age: number }` with no manual generic.
97
460
  *
98
- * const RegisterValidator = schema<{ email: string; password: string }>({
461
+ * const RegisterValidator = schema({
99
462
  * email: rules.string().email(),
100
- * password: rules.string().min(8),
463
+ * age: rules.number().optional(),
101
464
  * });
465
+ * // Infer<typeof RegisterValidator> not needed — result.data is typed.
102
466
  *
103
- * The default `Record<string, unknown>` matches the historical untyped surface
104
- * so existing call sites that read `result.data` field-by-field with their
105
- * own narrowing continue to compile.
467
+ * An explicit generic is still accepted for back-compat
468
+ * (`schema<MyType>({ ... })`), overriding inference.
469
+ */
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.
106
480
  */
107
- export declare function schema<T = Record<string, unknown>>(fields: Record<string, RuleChain>): ValidationSchema<T>;
481
+ export declare function create<S extends Record<string, RuleChain>>(fields: S): ValidationSchema<Infer<S>>;
482
+ export declare function create(chain: RuleChain): ValidationSchema;
108
483
  export declare function setValidationTranslator(translator?: ValidationTranslator): void;
109
484
  export declare function bindRosetta(rosetta: {
110
485
  t(key: string, params?: ValidationMessageParams): string;
@@ -113,43 +488,432 @@ export declare function bindRosetta(rosetta: {
113
488
  export interface RuleDef {
114
489
  name: string;
115
490
  param?: number;
491
+ /** Interpolation args exposed to i18n/messages providers (e.g. `{ min: 3 }`). */
492
+ args?: Record<string, unknown>;
116
493
  validate: (value: unknown) => boolean;
117
494
  message: string;
495
+ /** Set when `.message()` overrode this rule's default text. */
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;
506
+ }
507
+ /** A conditional-required condition (VineJS `requiredWhen` family). */
508
+ interface RequiredCondition {
509
+ kind: "exists" | "missing" | "when";
510
+ otherField: string;
511
+ operator?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "in" | "notIn";
512
+ value?: unknown;
118
513
  }
119
- /** Rule chain fluent validation builder. */
120
- export declare class RuleChain {
514
+ /** Phantom brand carrying the inferred output type (never assigned at runtime). */
515
+ declare const OUTPUT: unique symbol;
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>);
522
+ export declare class RuleChain<Output = unknown> {
121
523
  #private;
524
+ /** Phantom output type — drives {@link Infer}; never read at runtime. */
525
+ readonly [OUTPUT]: Output;
122
526
  /** Public read access to rules (for OpenAPI generation, Rust bridge). */
123
527
  get rules(): readonly RuleDef[];
124
528
  get isOptionalField(): boolean;
529
+ /** Public read access to the `.nullable()` flag (keeps such schemas off the native path). */
530
+ get isNullable(): boolean;
125
531
  get transforms(): ReadonlyArray<{
126
532
  name: string;
127
- fn: (value: unknown) => unknown;
533
+ fn: (value: unknown, field: FieldContext) => unknown;
128
534
  }>;
129
535
  /** Public read access to `.use()` rules (used to keep such schemas off the native path). */
130
536
  get useRules(): readonly CompiledRule[];
131
- /** Mark field as optional. */
132
- optional(): this;
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;
559
+ /** Public read access to `.parse()` pre-transforms (kept off the native path). */
560
+ get preTransforms(): ReadonlyArray<(value: unknown, ctx: ParseContext) => unknown>;
561
+ /** Whether this chain carries a `requiredWhen`-family condition. */
562
+ get hasConditionalRequired(): boolean;
563
+ /** Mark field as optional (absent / `undefined` allowed). */
564
+ optional(): RuleChain<Output | undefined>;
565
+ /** Mark field as nullable (`null` allowed, kept in the output). */
566
+ nullable(): RuleChain<Output | null>;
567
+ /** Mark field as both optional and nullable. */
568
+ nullish(): RuleChain<Output | null | undefined>;
569
+ /** Stop at the first failing rule for this field (VineJS bail). */
570
+ bail(enabled?: boolean): this;
133
571
  /** Must be an object matching a nested schema. */
134
- object(shape: Record<string, RuleChain>): this;
572
+ object<Sh extends Record<string, RuleChain>>(shape: Sh): RuleChain<Infer<Sh>>;
135
573
  /** Must be an array. Items validated by the provided chain. */
136
- array(itemChain?: RuleChain): this;
574
+ array<Item extends RuleChain>(itemChain?: Item): RuleChain<OutputOf<Item>[]>;
137
575
  /** Must be a string. */
138
- string(): this;
139
- /** Must be a number. */
140
- number(): this;
141
- /** Must be a boolean. */
142
- boolean(): this;
143
- /** Minimum length (string) or minimum value (number). */
576
+ string(): RuleChain<string>;
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;
747
+ /** Must equal one of `values` (enum). Narrows the output to the union. */
748
+ enum<const V extends readonly (string | number | boolean)[]>(values: V): RuleChain<V[number]>;
749
+ /** Must equal a literal value. */
750
+ literal<V extends string | number | boolean>(value: V): RuleChain<V>;
751
+ /** Minimum length (string) or minimum value (number). Alias of min/minLength. */
144
752
  min(n: number): this;
145
- /** Maximum length (string) or maximum value (number). */
753
+ /** Maximum length (string) or maximum value (number). Alias of max/maxLength. */
146
754
  max(n: number): this;
755
+ /** Minimum length for a string or array (VineJS `minLength`). */
756
+ minLength(n: number): this;
757
+ /** Maximum length for a string or array (VineJS `maxLength`). */
758
+ maxLength(n: number): this;
759
+ /** Exact length for a string or array (VineJS `fixedLength`). */
760
+ fixedLength(n: number): this;
147
761
  /** Must be a valid email. */
148
- email(): this;
149
- /** Must be positive (> 0) and finite. */
762
+ email(options?: EmailOptions): this;
763
+ /** Must match a regular expression (TS-only never dispatched to Rust). */
764
+ regex(pattern: RegExp): this;
765
+ /** Must be a valid URL (TS-only — uses the WHATWG URL parser). */
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;
866
+ /** Must contain only ASCII letters. */
867
+ alpha(options?: AlphaOptions): this;
868
+ /** Must contain only ASCII letters and digits. */
869
+ alphaNumeric(options?: AlphaOptions): this;
870
+ /** String must start with `substring`. */
871
+ startsWith(substring: string): this;
872
+ /** String must end with `substring`. */
873
+ endsWith(substring: string): 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;
882
+ /** Value must NOT be one of `values`. */
883
+ notIn(values: AllowedValues): this;
884
+ /** Number must be positive (> 0) and finite. */
150
885
  positive(): this;
886
+ /** Number must be negative (< 0) and finite. */
887
+ negative(): this;
888
+ /** Number must be >= 0 and finite. */
889
+ nonNegative(): this;
890
+ /** Number must fall within `[min, max]` (inclusive). */
891
+ range(bounds: [min: number, max: number]): this;
892
+ /** Number must have at most `digits` decimal places (TS-only). */
893
+ decimal(digits: number | [number, number]): this;
894
+ /** Must equal a sibling field (VineJS `sameAs`). Cross-field → TS-only. */
895
+ sameAs(otherField: string): this;
896
+ /** Must equal its `<field>_confirmation` sibling (VineJS `confirmed`). */
897
+ confirmed(options?: {
898
+ as?: string;
899
+ confirmationField?: string;
900
+ }): this;
901
+ /** Required only when `otherField` is present (non-null) — else optional. */
902
+ requiredIfExists(otherField: string): this;
903
+ /** Required only when `otherField` is absent/null — else optional. */
904
+ requiredIfMissing(otherField: string): this;
905
+ /** Required only when `otherField <op> value` holds — else optional. */
906
+ requiredWhen(otherField: string, operator: RequiredCondition["operator"], value: unknown): this;
151
907
  /** Trim whitespace (transform). */
152
908
  trim(): this;
909
+ /**
910
+ * Post-validation transform changing the output type (VineJS `transform`).
911
+ * `value` is `unknown` — narrow it in the callback (the no-cast rule forbids
912
+ * lying about a dynamically-produced value's static type).
913
+ */
914
+ transform<U>(fn: (value: unknown, field: FieldContext) => U): RuleChain<U>;
915
+ /** Pre-validation transform of the raw input (VineJS `parse`). */
916
+ parse(fn: (value: unknown, ctx: ParseContext) => unknown): this;
153
917
  /** Custom validation rule. */
154
918
  custom(name: string, validate: (value: unknown) => boolean, message?: string): this;
155
919
  /**
@@ -157,25 +921,135 @@ export declare class RuleChain {
157
921
  * receives a {@link FieldContext} with the root `data` and `parent`, so it can
158
922
  * validate across fields. Runs after this field's type/value rules.
159
923
  */
160
- use(rule: CompiledRule): this;
161
- /** 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
+ */
162
963
  message(msg: string): this;
163
- /** Internal: validate a field value and return errors + transformed value. */
164
- _validateWithTransform(field: string, value: 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[]): {
165
981
  errors: ValidationError[];
166
982
  transformed: unknown;
167
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[]>;
168
990
  /** Internal: validate a field value against all rules. */
169
991
  _validate(field: string, value: unknown): ValidationError[];
170
992
  /** Internal: apply transforms. */
171
993
  _transform(value: unknown): unknown;
172
994
  }
995
+ /** No-op alias of {@link schema} — VineJS `vine.compile()` API parity. */
996
+ export declare function compile<T extends ValidationSchema>(s: T): T;
997
+ export declare function compile(chain: RuleChain): ValidationSchema;
173
998
  /** Entry point for building rules. */
174
999
  export declare const rules: {
175
- string: () => RuleChain;
176
- number: () => RuleChain;
177
- boolean: () => RuleChain;
178
- any: () => RuleChain;
1000
+ string: () => RuleChain<string>;
1001
+ number: (options?: {
1002
+ strict?: boolean;
1003
+ }) => RuleChain<number>;
1004
+ boolean: (options?: {
1005
+ strict?: boolean;
1006
+ }) => RuleChain<boolean>;
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;
1049
+ object: <Sh extends Record<string, RuleChain>>(shape: Sh) => RuleChain<Infer<Sh>>;
1050
+ array: <Item extends RuleChain>(item?: Item) => RuleChain<OutputOf<Item>[]>;
1051
+ enum: <const V extends readonly (string | number | boolean)[]>(values: V) => RuleChain<V[number]>;
1052
+ literal: <V extends string | number | boolean>(value: V) => RuleChain<V>;
179
1053
  };
180
1054
  export {};
181
1055
  //# sourceMappingURL=Schema.d.ts.map