@c9up/rune 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Schema.d.ts +112 -0
- package/dist/Schema.d.ts.map +1 -0
- package/dist/Schema.js +425 -0
- package/dist/Schema.js.map +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/native.d.ts +27 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +68 -0
- package/dist/native.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
- package/src/Schema.ts +25 -21
- package/src/native.ts +26 -0
package/dist/Schema.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rune Validation Schema — fluent validation rules.
|
|
3
|
+
*
|
|
4
|
+
* @implements FR38, FR39, FR40, FR41
|
|
5
|
+
*/
|
|
6
|
+
export type ValidationMessageParams = Record<string, string | number | boolean>;
|
|
7
|
+
export type ValidationTranslator = (key: string, params?: ValidationMessageParams) => string | undefined;
|
|
8
|
+
export interface ValidationError {
|
|
9
|
+
field: string;
|
|
10
|
+
rule: string;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validation result — discriminated union that narrows `data` to the schema's
|
|
15
|
+
* `T` when `valid` is `true`, removing the need for callers to cast or guard
|
|
16
|
+
* `data` separately.
|
|
17
|
+
*/
|
|
18
|
+
export type ValidationResult<T = Record<string, unknown>> = {
|
|
19
|
+
valid: true;
|
|
20
|
+
errors: ValidationError[];
|
|
21
|
+
data: T;
|
|
22
|
+
} | {
|
|
23
|
+
valid: false;
|
|
24
|
+
errors: ValidationError[];
|
|
25
|
+
data?: undefined;
|
|
26
|
+
};
|
|
27
|
+
export interface ValidationSchema<T = Record<string, unknown>> {
|
|
28
|
+
fields: Record<string, RuleChain>;
|
|
29
|
+
validate(data: unknown): ValidationResult<T>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a validation schema.
|
|
33
|
+
*
|
|
34
|
+
* Pass `T` explicitly when the caller wants `result.data` typed as a concrete
|
|
35
|
+
* shape after `result.valid === true` narrows the union — runtime validation
|
|
36
|
+
* is unchanged, the generic only types the success branch.
|
|
37
|
+
*
|
|
38
|
+
* const RegisterValidator = schema<{ email: string; password: string }>({
|
|
39
|
+
* email: rules.string().email(),
|
|
40
|
+
* password: rules.string().min(8),
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* The default `Record<string, unknown>` matches the historical untyped surface
|
|
44
|
+
* so existing call sites that read `result.data` field-by-field with their
|
|
45
|
+
* own narrowing continue to compile.
|
|
46
|
+
*/
|
|
47
|
+
export declare function schema<T = Record<string, unknown>>(fields: Record<string, RuleChain>): ValidationSchema<T>;
|
|
48
|
+
export declare function setValidationTranslator(translator?: ValidationTranslator): void;
|
|
49
|
+
export declare function bindRosetta(rosetta: {
|
|
50
|
+
t(key: string, params?: ValidationMessageParams): string;
|
|
51
|
+
}): void;
|
|
52
|
+
/** Rule definition stored on a chain. */
|
|
53
|
+
export interface RuleDef {
|
|
54
|
+
name: string;
|
|
55
|
+
param?: number;
|
|
56
|
+
validate: (value: unknown) => boolean;
|
|
57
|
+
message: string;
|
|
58
|
+
}
|
|
59
|
+
/** Rule chain — fluent validation builder. */
|
|
60
|
+
export declare class RuleChain {
|
|
61
|
+
#private;
|
|
62
|
+
/** Public read access to rules (for OpenAPI generation, Rust bridge). */
|
|
63
|
+
get rules(): readonly RuleDef[];
|
|
64
|
+
get isOptionalField(): boolean;
|
|
65
|
+
get transforms(): ReadonlyArray<{
|
|
66
|
+
name: string;
|
|
67
|
+
fn: (value: unknown) => unknown;
|
|
68
|
+
}>;
|
|
69
|
+
/** Mark field as optional. */
|
|
70
|
+
optional(): this;
|
|
71
|
+
/** Must be an object matching a nested schema. */
|
|
72
|
+
object(shape: Record<string, RuleChain>): this;
|
|
73
|
+
/** Must be an array. Items validated by the provided chain. */
|
|
74
|
+
array(itemChain?: RuleChain): this;
|
|
75
|
+
/** Must be a string. */
|
|
76
|
+
string(): this;
|
|
77
|
+
/** Must be a number. */
|
|
78
|
+
number(): this;
|
|
79
|
+
/** Must be a boolean. */
|
|
80
|
+
boolean(): this;
|
|
81
|
+
/** Minimum length (string) or minimum value (number). */
|
|
82
|
+
min(n: number): this;
|
|
83
|
+
/** Maximum length (string) or maximum value (number). */
|
|
84
|
+
max(n: number): this;
|
|
85
|
+
/** Must be a valid email. */
|
|
86
|
+
email(): this;
|
|
87
|
+
/** Must be positive (> 0) and finite. */
|
|
88
|
+
positive(): this;
|
|
89
|
+
/** Trim whitespace (transform). */
|
|
90
|
+
trim(): this;
|
|
91
|
+
/** Custom validation rule. */
|
|
92
|
+
custom(name: string, validate: (value: unknown) => boolean, message?: string): this;
|
|
93
|
+
/** Set custom error message for the last rule. */
|
|
94
|
+
message(msg: string): this;
|
|
95
|
+
/** Internal: validate a field value and return errors + transformed value. */
|
|
96
|
+
_validateWithTransform(field: string, value: unknown): {
|
|
97
|
+
errors: ValidationError[];
|
|
98
|
+
transformed: unknown;
|
|
99
|
+
};
|
|
100
|
+
/** Internal: validate a field value against all rules. */
|
|
101
|
+
_validate(field: string, value: unknown): ValidationError[];
|
|
102
|
+
/** Internal: apply transforms. */
|
|
103
|
+
_transform(value: unknown): unknown;
|
|
104
|
+
}
|
|
105
|
+
/** Entry point for building rules. */
|
|
106
|
+
export declare const rules: {
|
|
107
|
+
string: () => RuleChain;
|
|
108
|
+
number: () => RuleChain;
|
|
109
|
+
boolean: () => RuleChain;
|
|
110
|
+
any: () => RuleChain;
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=Schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Schema.d.ts","sourceRoot":"","sources":["../src/Schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAChF,MAAM,MAAM,oBAAoB,GAAG,CAClC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,uBAAuB,KAC5B,MAAM,GAAG,SAAS,CAAC;AAExB,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACrD;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACnD;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AAEjE,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;CAC7C;AA4FD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAC/B,gBAAgB,CAAC,CAAC,CAAC,CAiDrB;AAED,wBAAgB,uBAAuB,CACtC,UAAU,CAAC,EAAE,oBAAoB,GAC/B,IAAI,CAEN;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACpC,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,uBAAuB,GAAG,MAAM,CAAC;CACzD,GAAG,IAAI,CAEP;AAED,yCAAyC;AACzC,MAAM,WAAW,OAAO;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,8CAA8C;AAC9C,qBAAa,SAAS;;IAOrB,yEAAyE;IACzE,IAAI,KAAK,IAAI,SAAS,OAAO,EAAE,CAE9B;IACD,IAAI,eAAe,IAAI,OAAO,CAE7B;IACD,IAAI,UAAU,IAAI,aAAa,CAAC;QAC/B,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;KAChC,CAAC,CAED;IAED,8BAA8B;IAC9B,QAAQ,IAAI,IAAI;IAKhB,kDAAkD;IAClD,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,IAAI;IAU9C,+DAA+D;IAC/D,KAAK,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI;IAUlC,wBAAwB;IACxB,MAAM,IAAI,IAAI;IASd,wBAAwB;IACxB,MAAM,IAAI,IAAI;IAUd,yBAAyB;IACzB,OAAO,IAAI,IAAI;IASf,yDAAyD;IACzD,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAepB,yDAAyD;IACzD,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAepB,6BAA6B;IAC7B,KAAK,IAAI,IAAI;IAcb,yCAAyC;IACzC,QAAQ,IAAI,IAAI;IAShB,mCAAmC;IACnC,IAAI,IAAI,IAAI;IAQZ,8BAA8B;IAC9B,MAAM,CACL,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,EACrC,OAAO,CAAC,EAAE,MAAM,GACd,IAAI;IASP,kDAAkD;IAClD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAQ1B,8EAA8E;IAC9E,sBAAsB,CACrB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,GACZ;QAAE,MAAM,EAAE,eAAe,EAAE,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE;IAiGtD,0DAA0D;IAC1D,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE;IAI3D,kCAAkC;IAClC,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;CAWnC;AAED,sCAAsC;AACtC,eAAO,MAAM,KAAK;;;;;CAKjB,CAAC"}
|
package/dist/Schema.js
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rune Validation Schema — fluent validation rules.
|
|
3
|
+
*
|
|
4
|
+
* @implements FR38, FR39, FR40, FR41
|
|
5
|
+
*/
|
|
6
|
+
import { RuneError } from "./errors.js";
|
|
7
|
+
import { isNativeAvailable, validateNative, warnNativeUnavailableOnce, } from "./native.js";
|
|
8
|
+
/** Type guard: narrows `unknown` to a plain object (non-null, non-array, typeof 'object'). */
|
|
9
|
+
function isPlainObject(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
/** Rules the Rust validation engine can handle natively. */
|
|
13
|
+
const STANDARD_RULES = new Set([
|
|
14
|
+
"string",
|
|
15
|
+
"number",
|
|
16
|
+
"boolean",
|
|
17
|
+
"min",
|
|
18
|
+
"max",
|
|
19
|
+
"email",
|
|
20
|
+
"positive",
|
|
21
|
+
]);
|
|
22
|
+
/** Default messages for standard rules — used to detect custom-message overrides. */
|
|
23
|
+
const STANDARD_MSGS = {
|
|
24
|
+
string: "Must be a string",
|
|
25
|
+
number: "Must be a number",
|
|
26
|
+
boolean: "Must be a boolean",
|
|
27
|
+
min: "Minimum",
|
|
28
|
+
max: "Maximum",
|
|
29
|
+
email: "Must be a valid email",
|
|
30
|
+
positive: "Must be positive",
|
|
31
|
+
};
|
|
32
|
+
const TYPE_RULE_NAMES = new Set([
|
|
33
|
+
"string",
|
|
34
|
+
"number",
|
|
35
|
+
"boolean",
|
|
36
|
+
"object",
|
|
37
|
+
"array",
|
|
38
|
+
]);
|
|
39
|
+
let validationTranslator;
|
|
40
|
+
function hasDefaultMessage(rule) {
|
|
41
|
+
if (rule.name === "min" || rule.name === "max") {
|
|
42
|
+
if (typeof rule.param !== "number")
|
|
43
|
+
return false;
|
|
44
|
+
const expected = `${rule.name === "min" ? "Minimum" : "Maximum"} ${rule.param}`;
|
|
45
|
+
return rule.message === expected;
|
|
46
|
+
}
|
|
47
|
+
const defaultMsg = STANDARD_MSGS[rule.name];
|
|
48
|
+
return defaultMsg !== undefined && rule.message === defaultMsg;
|
|
49
|
+
}
|
|
50
|
+
function resolveValidationMessage(key, fallback, params) {
|
|
51
|
+
const translated = validationTranslator?.(key, params);
|
|
52
|
+
if (typeof translated === "string" && translated.length > 0) {
|
|
53
|
+
return translated;
|
|
54
|
+
}
|
|
55
|
+
return fallback;
|
|
56
|
+
}
|
|
57
|
+
function resolveRuleMessage(field, rule) {
|
|
58
|
+
const fallback = rule.message;
|
|
59
|
+
if (!STANDARD_RULES.has(rule.name)) {
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
if (!hasDefaultMessage(rule)) {
|
|
63
|
+
return fallback;
|
|
64
|
+
}
|
|
65
|
+
const params = { field };
|
|
66
|
+
if (rule.name === "min" && typeof rule.param === "number") {
|
|
67
|
+
params.min = rule.param;
|
|
68
|
+
}
|
|
69
|
+
if (rule.name === "max" && typeof rule.param === "number") {
|
|
70
|
+
params.max = rule.param;
|
|
71
|
+
}
|
|
72
|
+
return resolveValidationMessage(`validation.${rule.name}`, fallback, params);
|
|
73
|
+
}
|
|
74
|
+
/** Compute once: does any field rule prevent dispatching to Rust? */
|
|
75
|
+
function detectHasCustomRules(fields) {
|
|
76
|
+
return Object.values(fields).some((chain) => {
|
|
77
|
+
return chain.rules.some((r) => {
|
|
78
|
+
if (!STANDARD_RULES.has(r.name))
|
|
79
|
+
return true; // custom rule
|
|
80
|
+
if (!hasDefaultMessage(r))
|
|
81
|
+
return true; // custom message
|
|
82
|
+
return false;
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create a validation schema.
|
|
88
|
+
*
|
|
89
|
+
* Pass `T` explicitly when the caller wants `result.data` typed as a concrete
|
|
90
|
+
* shape after `result.valid === true` narrows the union — runtime validation
|
|
91
|
+
* is unchanged, the generic only types the success branch.
|
|
92
|
+
*
|
|
93
|
+
* const RegisterValidator = schema<{ email: string; password: string }>({
|
|
94
|
+
* email: rules.string().email(),
|
|
95
|
+
* password: rules.string().min(8),
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* The default `Record<string, unknown>` matches the historical untyped surface
|
|
99
|
+
* so existing call sites that read `result.data` field-by-field with their
|
|
100
|
+
* own narrowing continue to compile.
|
|
101
|
+
*/
|
|
102
|
+
export function schema(fields) {
|
|
103
|
+
// Computed once at construction time, not per validate() call.
|
|
104
|
+
const hasCustomRules = detectHasCustomRules(fields);
|
|
105
|
+
return {
|
|
106
|
+
fields,
|
|
107
|
+
validate(data) {
|
|
108
|
+
if (!isPlainObject(data)) {
|
|
109
|
+
return {
|
|
110
|
+
valid: false,
|
|
111
|
+
errors: [
|
|
112
|
+
{
|
|
113
|
+
field: "_root",
|
|
114
|
+
rule: "type",
|
|
115
|
+
message: "Input must be an object",
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (!hasCustomRules && !validationTranslator) {
|
|
121
|
+
if (isNativeAvailable()) {
|
|
122
|
+
return validateWithRust(fields, data);
|
|
123
|
+
}
|
|
124
|
+
// This schema would have used the native engine, but it isn't
|
|
125
|
+
// loaded — surface the platform-dependent TS fallback once instead
|
|
126
|
+
// of diverging silently. (Schemas with custom rules / a translator
|
|
127
|
+
// always run on TS by design and don't warn.)
|
|
128
|
+
warnNativeUnavailableOnce();
|
|
129
|
+
}
|
|
130
|
+
const errors = [];
|
|
131
|
+
const validated = {};
|
|
132
|
+
for (const [field, chain] of Object.entries(fields)) {
|
|
133
|
+
const value = data[field];
|
|
134
|
+
const result = chain._validateWithTransform(field, value);
|
|
135
|
+
errors.push(...result.errors);
|
|
136
|
+
if (result.errors.length === 0 && value !== undefined) {
|
|
137
|
+
validated[field] = result.transformed;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (errors.length === 0) {
|
|
141
|
+
return { valid: true, errors, data: validated };
|
|
142
|
+
}
|
|
143
|
+
return { valid: false, errors };
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export function setValidationTranslator(translator) {
|
|
148
|
+
validationTranslator = translator;
|
|
149
|
+
}
|
|
150
|
+
export function bindRosetta(rosetta) {
|
|
151
|
+
setValidationTranslator((key, params) => rosetta.t(key, params));
|
|
152
|
+
}
|
|
153
|
+
/** Rule chain — fluent validation builder. */
|
|
154
|
+
export class RuleChain {
|
|
155
|
+
#rules = [];
|
|
156
|
+
#isOptional = false;
|
|
157
|
+
#transforms = [];
|
|
158
|
+
#nestedSchema = null;
|
|
159
|
+
#arrayItemChain = null;
|
|
160
|
+
/** Public read access to rules (for OpenAPI generation, Rust bridge). */
|
|
161
|
+
get rules() {
|
|
162
|
+
return this.#rules;
|
|
163
|
+
}
|
|
164
|
+
get isOptionalField() {
|
|
165
|
+
return this.#isOptional;
|
|
166
|
+
}
|
|
167
|
+
get transforms() {
|
|
168
|
+
return this.#transforms;
|
|
169
|
+
}
|
|
170
|
+
/** Mark field as optional. */
|
|
171
|
+
optional() {
|
|
172
|
+
this.#isOptional = true;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
/** Must be an object matching a nested schema. */
|
|
176
|
+
object(shape) {
|
|
177
|
+
this.#rules.push({
|
|
178
|
+
name: "object",
|
|
179
|
+
validate: (v) => typeof v === "object" && v !== null && !Array.isArray(v),
|
|
180
|
+
message: "Must be an object",
|
|
181
|
+
});
|
|
182
|
+
this.#nestedSchema = shape;
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
/** Must be an array. Items validated by the provided chain. */
|
|
186
|
+
array(itemChain) {
|
|
187
|
+
this.#rules.push({
|
|
188
|
+
name: "array",
|
|
189
|
+
validate: (v) => Array.isArray(v),
|
|
190
|
+
message: "Must be an array",
|
|
191
|
+
});
|
|
192
|
+
this.#arrayItemChain = itemChain ?? null;
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
/** Must be a string. */
|
|
196
|
+
string() {
|
|
197
|
+
this.#rules.push({
|
|
198
|
+
name: "string",
|
|
199
|
+
validate: (v) => typeof v === "string",
|
|
200
|
+
message: "Must be a string",
|
|
201
|
+
});
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
/** Must be a number. */
|
|
205
|
+
number() {
|
|
206
|
+
this.#rules.push({
|
|
207
|
+
name: "number",
|
|
208
|
+
validate: (v) => typeof v === "number" && !Number.isNaN(v) && Number.isFinite(v),
|
|
209
|
+
message: "Must be a number",
|
|
210
|
+
});
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
/** Must be a boolean. */
|
|
214
|
+
boolean() {
|
|
215
|
+
this.#rules.push({
|
|
216
|
+
name: "boolean",
|
|
217
|
+
validate: (v) => typeof v === "boolean",
|
|
218
|
+
message: "Must be a boolean",
|
|
219
|
+
});
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
/** Minimum length (string) or minimum value (number). */
|
|
223
|
+
min(n) {
|
|
224
|
+
this.#rules.push({
|
|
225
|
+
name: "min",
|
|
226
|
+
param: n,
|
|
227
|
+
validate: (v) => typeof v === "string"
|
|
228
|
+
? [...v].length >= n
|
|
229
|
+
: typeof v === "number"
|
|
230
|
+
? v >= n
|
|
231
|
+
: false,
|
|
232
|
+
message: `Minimum ${n}`,
|
|
233
|
+
});
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
/** Maximum length (string) or maximum value (number). */
|
|
237
|
+
max(n) {
|
|
238
|
+
this.#rules.push({
|
|
239
|
+
name: "max",
|
|
240
|
+
param: n,
|
|
241
|
+
validate: (v) => typeof v === "string"
|
|
242
|
+
? [...v].length <= n
|
|
243
|
+
: typeof v === "number"
|
|
244
|
+
? v <= n
|
|
245
|
+
: false,
|
|
246
|
+
message: `Maximum ${n}`,
|
|
247
|
+
});
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
/** Must be a valid email. */
|
|
251
|
+
email() {
|
|
252
|
+
this.#rules.push({
|
|
253
|
+
name: "email",
|
|
254
|
+
// Mirror the Rust engine's regex exactly (crates/rune-engine/src/engine.rs)
|
|
255
|
+
// so the SAME schema validates identically whether or not the native
|
|
256
|
+
// binary loaded: no whitespace anywhere (the old TS rule rejected only
|
|
257
|
+
// \r\n, silently accepting interior spaces), a single @, dotted domain.
|
|
258
|
+
validate: (v) => typeof v === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
|
|
259
|
+
message: "Must be a valid email",
|
|
260
|
+
});
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
/** Must be positive (> 0) and finite. */
|
|
264
|
+
positive() {
|
|
265
|
+
this.#rules.push({
|
|
266
|
+
name: "positive",
|
|
267
|
+
validate: (v) => typeof v === "number" && Number.isFinite(v) && v > 0,
|
|
268
|
+
message: "Must be positive",
|
|
269
|
+
});
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
/** Trim whitespace (transform). */
|
|
273
|
+
trim() {
|
|
274
|
+
this.#transforms.push({
|
|
275
|
+
name: "trim",
|
|
276
|
+
fn: (v) => (typeof v === "string" ? v.trim() : v),
|
|
277
|
+
});
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
/** Custom validation rule. */
|
|
281
|
+
custom(name, validate, message) {
|
|
282
|
+
this.#rules.push({
|
|
283
|
+
name,
|
|
284
|
+
validate,
|
|
285
|
+
message: message ?? `Failed custom rule: ${name}`,
|
|
286
|
+
});
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
/** Set custom error message for the last rule. */
|
|
290
|
+
message(msg) {
|
|
291
|
+
if (this.#rules.length === 0) {
|
|
292
|
+
throw new RuneError("NO_RULE", "message() must be called after a rule");
|
|
293
|
+
}
|
|
294
|
+
this.#rules[this.#rules.length - 1].message = msg;
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
/** Internal: validate a field value and return errors + transformed value. */
|
|
298
|
+
_validateWithTransform(field, value) {
|
|
299
|
+
if (value === undefined || value === null) {
|
|
300
|
+
if (this.#isOptional)
|
|
301
|
+
return { errors: [], transformed: value };
|
|
302
|
+
return { errors: [this.#requiredError(field)], transformed: value };
|
|
303
|
+
}
|
|
304
|
+
// 1. Type rules first on the raw value — bail on type mismatch.
|
|
305
|
+
const typeError = this.#runTypeRules(field, value);
|
|
306
|
+
if (typeError)
|
|
307
|
+
return { errors: [typeError], transformed: value };
|
|
308
|
+
// 2. Apply transforms (trim, etc.), then run value rules on the result.
|
|
309
|
+
let transformed = this.#applyTransformsTo(value);
|
|
310
|
+
const errors = this.#runValueRules(field, transformed);
|
|
311
|
+
// 4. Nested object validation (only if type check passed — not arrays)
|
|
312
|
+
if (this.#nestedSchema &&
|
|
313
|
+
typeof transformed === "object" &&
|
|
314
|
+
transformed !== null &&
|
|
315
|
+
!Array.isArray(transformed)) {
|
|
316
|
+
transformed = { ...transformed };
|
|
317
|
+
for (const [nestedField, chain] of Object.entries(this.#nestedSchema)) {
|
|
318
|
+
const nestedValue = transformed[nestedField];
|
|
319
|
+
const nestedResult = chain._validateWithTransform(`${field}.${nestedField}`, nestedValue);
|
|
320
|
+
errors.push(...nestedResult.errors);
|
|
321
|
+
if (nestedResult.transformed !== undefined) {
|
|
322
|
+
transformed[nestedField] =
|
|
323
|
+
nestedResult.transformed;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// 5. Array item validation
|
|
328
|
+
if (this.#arrayItemChain && Array.isArray(transformed)) {
|
|
329
|
+
transformed = [...transformed];
|
|
330
|
+
for (let i = 0; i < transformed.length; i++) {
|
|
331
|
+
const itemResult = this.#arrayItemChain._validateWithTransform(`${field}.${i}`, transformed[i]);
|
|
332
|
+
errors.push(...itemResult.errors);
|
|
333
|
+
if (itemResult.transformed !== undefined) {
|
|
334
|
+
transformed[i] = itemResult.transformed;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return { errors, transformed };
|
|
339
|
+
}
|
|
340
|
+
#requiredError(field) {
|
|
341
|
+
return {
|
|
342
|
+
field,
|
|
343
|
+
rule: "required",
|
|
344
|
+
message: resolveValidationMessage("validation.required", `${field} is required`, { field }),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/** Run the type rules (string/number/…) on the raw value; first failure bails. */
|
|
348
|
+
#runTypeRules(field, value) {
|
|
349
|
+
for (const rule of this.#rules) {
|
|
350
|
+
if (TYPE_RULE_NAMES.has(rule.name) && !rule.validate(value)) {
|
|
351
|
+
return {
|
|
352
|
+
field,
|
|
353
|
+
rule: rule.name,
|
|
354
|
+
message: resolveRuleMessage(field, rule),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
/** Run the non-type rules (min/max/email/…) on the transformed value. */
|
|
361
|
+
#runValueRules(field, transformed) {
|
|
362
|
+
const errors = [];
|
|
363
|
+
for (const rule of this.#rules) {
|
|
364
|
+
if (!TYPE_RULE_NAMES.has(rule.name) && !rule.validate(transformed)) {
|
|
365
|
+
errors.push({
|
|
366
|
+
field,
|
|
367
|
+
rule: rule.name,
|
|
368
|
+
message: resolveRuleMessage(field, rule),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return errors;
|
|
373
|
+
}
|
|
374
|
+
/** Internal: validate a field value against all rules. */
|
|
375
|
+
_validate(field, value) {
|
|
376
|
+
return this._validateWithTransform(field, value).errors;
|
|
377
|
+
}
|
|
378
|
+
/** Internal: apply transforms. */
|
|
379
|
+
_transform(value) {
|
|
380
|
+
return this.#applyTransformsTo(value);
|
|
381
|
+
}
|
|
382
|
+
#applyTransformsTo(value) {
|
|
383
|
+
let result = value;
|
|
384
|
+
for (const transform of this.#transforms) {
|
|
385
|
+
result = transform.fn(result);
|
|
386
|
+
}
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/** Entry point for building rules. */
|
|
391
|
+
export const rules = {
|
|
392
|
+
string: () => new RuleChain().string(),
|
|
393
|
+
number: () => new RuleChain().number(),
|
|
394
|
+
boolean: () => new RuleChain().boolean(),
|
|
395
|
+
any: () => new RuleChain(),
|
|
396
|
+
};
|
|
397
|
+
/** Serialize schema + data and validate via Rust NAPI. */
|
|
398
|
+
function validateWithRust(fields, data) {
|
|
399
|
+
const schemaDesc = {};
|
|
400
|
+
for (const [field, chain] of Object.entries(fields)) {
|
|
401
|
+
const rules = chain.rules.map((r) => ({
|
|
402
|
+
name: r.name,
|
|
403
|
+
// Serialize THIS rule's own param. Previously a find-first lookup by
|
|
404
|
+
// rule name returned the first matching rule's param, so min(3).min(5)
|
|
405
|
+
// sent both Rust entries as min=3, dropping the 5 bound (audit 2026-06-13).
|
|
406
|
+
params: r.name === "min"
|
|
407
|
+
? { min: r.param }
|
|
408
|
+
: r.name === "max"
|
|
409
|
+
? { max: r.param }
|
|
410
|
+
: null,
|
|
411
|
+
}));
|
|
412
|
+
schemaDesc[field] = {
|
|
413
|
+
rules,
|
|
414
|
+
optional: chain.isOptionalField,
|
|
415
|
+
transforms: chain.transforms.map((t) => t.name),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
const request = JSON.stringify({ schema: schemaDesc, data });
|
|
419
|
+
const native = validateNative(request);
|
|
420
|
+
if (native.valid && native.data !== undefined) {
|
|
421
|
+
return { valid: true, errors: native.errors, data: native.data };
|
|
422
|
+
}
|
|
423
|
+
return { valid: false, errors: native.errors };
|
|
424
|
+
}
|
|
425
|
+
//# sourceMappingURL=Schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Schema.js","sourceRoot":"","sources":["../src/Schema.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuneError — structured error for Rune validation.
|
|
3
|
+
*/
|
|
4
|
+
export declare class RuneError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly hint?: string;
|
|
7
|
+
constructor(code: string, message: string, options?: {
|
|
8
|
+
hint?: string;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEX,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;CAMtE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuneError — structured error for Rune validation.
|
|
3
|
+
*/
|
|
4
|
+
export class RuneError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
hint;
|
|
7
|
+
constructor(code, message, options) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "RuneError";
|
|
10
|
+
this.code = `RUNE_${code}`;
|
|
11
|
+
this.hint = options?.hint;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAC1B,IAAI,CAAS;IACb,IAAI,CAAU;IAEvB,YAAY,IAAY,EAAE,OAAe,EAAE,OAA2B;QACrE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,QAAQ,IAAI,EAAE,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC;IAC3B,CAAC;CACD"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @c9up/rune
|
|
3
|
+
* @description Rune — Validation engine for the Ream framework
|
|
4
|
+
* @implements FR38, FR39, FR40, FR41, FR42
|
|
5
|
+
*/
|
|
6
|
+
export { RuneError } from "./errors.js";
|
|
7
|
+
export type { RuleChain, ValidationError, ValidationMessageParams, ValidationResult, ValidationSchema, ValidationTranslator, } from "./Schema.js";
|
|
8
|
+
export { bindRosetta, rules, schema, setValidationTranslator, } from "./Schema.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EACX,SAAS,EACT,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,WAAW,EACX,KAAK,EACL,MAAM,EACN,uBAAuB,GACvB,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @c9up/rune
|
|
3
|
+
* @description Rune — Validation engine for the Ream framework
|
|
4
|
+
* @implements FR38, FR39, FR40, FR41, FR42
|
|
5
|
+
*/
|
|
6
|
+
export { RuneError } from "./errors.js";
|
|
7
|
+
export { bindRosetta, rules, schema, setValidationTranslator, } from "./Schema.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AASxC,OAAO,EACN,WAAW,EACX,KAAK,EACL,MAAM,EACN,uBAAuB,GACvB,MAAM,aAAa,CAAC"}
|
package/dist/native.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native validation loader — loads the Rust NAPI binary.
|
|
3
|
+
*
|
|
4
|
+
* @implements FR40
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Validate data via the Rust NAPI engine.
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateNative(requestJson: string): {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
errors: Array<{
|
|
12
|
+
field: string;
|
|
13
|
+
rule: string;
|
|
14
|
+
message: string;
|
|
15
|
+
}>;
|
|
16
|
+
data?: Record<string, unknown>;
|
|
17
|
+
};
|
|
18
|
+
export declare function isNativeAvailable(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Surface — exactly once per process — that the native engine is unavailable so
|
|
21
|
+
* the schema is falling back to the TypeScript validator. The two paths can
|
|
22
|
+
* differ subtly, so a silent fallback makes validation results depend on whether
|
|
23
|
+
* the prebuilt binary loaded (platform-dependent). Callers invoke this only when
|
|
24
|
+
* they WOULD have used the native engine (no custom rules / translator).
|
|
25
|
+
*/
|
|
26
|
+
export declare function warnNativeUnavailableOnce(): void;
|
|
27
|
+
//# sourceMappingURL=native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA8BH;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG;IACpD,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAaA;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAID;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAehD"}
|
package/dist/native.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native validation loader — loads the Rust NAPI binary.
|
|
3
|
+
*
|
|
4
|
+
* @implements FR40
|
|
5
|
+
*/
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { arch, platform } from "node:process";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
const require2 = createRequire(import.meta.url);
|
|
11
|
+
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const platformMap = {
|
|
13
|
+
"linux-x64": "linux-x64-gnu",
|
|
14
|
+
"linux-arm64": "linux-arm64-gnu",
|
|
15
|
+
"darwin-x64": "darwin-x64",
|
|
16
|
+
"darwin-arm64": "darwin-arm64",
|
|
17
|
+
"win32-x64": "win32-x64-msvc",
|
|
18
|
+
};
|
|
19
|
+
let native;
|
|
20
|
+
let loadError;
|
|
21
|
+
try {
|
|
22
|
+
const suffix = platformMap[`${platform}-${arch}`];
|
|
23
|
+
if (suffix) {
|
|
24
|
+
native = require2(join(__dirname2, `../index.${suffix}.node`));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
loadError = e;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate data via the Rust NAPI engine.
|
|
32
|
+
*/
|
|
33
|
+
export function validateNative(requestJson) {
|
|
34
|
+
if (!native) {
|
|
35
|
+
throw new Error(`[RUNE_NAPI_NOT_FOUND] Rust validation engine not available: ${loadError ?? "binary not found"}`);
|
|
36
|
+
}
|
|
37
|
+
const result = JSON.parse(native.validate(requestJson));
|
|
38
|
+
if (typeof result.valid !== "boolean" || !Array.isArray(result.errors)) {
|
|
39
|
+
throw new Error(`[RUNE_NAPI_INVALID_RESPONSE] Rust engine returned unexpected shape: ${JSON.stringify(result)}`);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
export function isNativeAvailable() {
|
|
44
|
+
return native !== undefined;
|
|
45
|
+
}
|
|
46
|
+
let warnedFallback = false;
|
|
47
|
+
/**
|
|
48
|
+
* Surface — exactly once per process — that the native engine is unavailable so
|
|
49
|
+
* the schema is falling back to the TypeScript validator. The two paths can
|
|
50
|
+
* differ subtly, so a silent fallback makes validation results depend on whether
|
|
51
|
+
* the prebuilt binary loaded (platform-dependent). Callers invoke this only when
|
|
52
|
+
* they WOULD have used the native engine (no custom rules / translator).
|
|
53
|
+
*/
|
|
54
|
+
export function warnNativeUnavailableOnce() {
|
|
55
|
+
if (warnedFallback)
|
|
56
|
+
return;
|
|
57
|
+
warnedFallback = true;
|
|
58
|
+
const target = `${platform}-${arch}`;
|
|
59
|
+
const reason = loadError !== undefined
|
|
60
|
+
? `failed to load (${loadError instanceof Error ? loadError.message : String(loadError)})`
|
|
61
|
+
: platformMap[target] !== undefined
|
|
62
|
+
? "binary not found"
|
|
63
|
+
: `no prebuilt binary for ${target}`;
|
|
64
|
+
console.warn(`[rune] native validation engine unavailable — ${reason}. Falling back to the ` +
|
|
65
|
+
"TypeScript validator, whose results can differ subtly from the Rust engine — " +
|
|
66
|
+
"validation is platform-dependent for this process.");
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChD,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE3D,MAAM,WAAW,GAA2B;IAC3C,WAAW,EAAE,eAAe;IAC5B,aAAa,EAAE,iBAAiB;IAChC,YAAY,EAAE,YAAY;IAC1B,cAAc,EAAE,cAAc;IAC9B,WAAW,EAAE,gBAAgB;CAC7B,CAAC;AAEF,IAAI,MAA0D,CAAC;AAC/D,IAAI,SAAkB,CAAC;AAEvB,IAAI,CAAC;IACJ,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,QAAQ,IAAI,IAAI,EAAE,CAAC,CAAC;IAClD,IAAI,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,MAAM,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;AACF,CAAC;AAAC,OAAO,CAAC,EAAE,CAAC;IACZ,SAAS,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB;IAKjD,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACd,+DAA+D,SAAS,IAAI,kBAAkB,EAAE,CAChG,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;IACxD,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CACd,uEAAuE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAC/F,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB;IAChC,OAAO,MAAM,KAAK,SAAS,CAAC;AAC7B,CAAC;AAED,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB;IACxC,IAAI,cAAc;QAAE,OAAO;IAC3B,cAAc,GAAG,IAAI,CAAC;IACtB,MAAM,MAAM,GAAG,GAAG,QAAQ,IAAI,IAAI,EAAE,CAAC;IACrC,MAAM,MAAM,GACX,SAAS,KAAK,SAAS;QACtB,CAAC,CAAC,mBAAmB,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG;QAC1F,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,SAAS;YAClC,CAAC,CAAC,kBAAkB;YACpB,CAAC,CAAC,0BAA0B,MAAM,EAAE,CAAC;IACxC,OAAO,CAAC,IAAI,CACX,iDAAiD,MAAM,wBAAwB;QAC9E,+EAA+E;QAC/E,oDAAoD,CACrD,CAAC;AACH,CAAC"}
|
package/index.darwin-arm64.node
CHANGED
|
Binary file
|
package/index.darwin-x64.node
CHANGED
|
Binary file
|
|
Binary file
|
package/index.linux-x64-gnu.node
CHANGED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/src/Schema.ts
CHANGED
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { RuneError } from "./errors.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
isNativeAvailable,
|
|
10
|
+
validateNative,
|
|
11
|
+
warnNativeUnavailableOnce,
|
|
12
|
+
} from "./native.js";
|
|
9
13
|
|
|
10
14
|
export type ValidationMessageParams = Record<string, string | number | boolean>;
|
|
11
15
|
export type ValidationTranslator = (
|
|
@@ -161,8 +165,15 @@ export function schema<T = Record<string, unknown>>(
|
|
|
161
165
|
};
|
|
162
166
|
}
|
|
163
167
|
|
|
164
|
-
if (
|
|
165
|
-
|
|
168
|
+
if (!hasCustomRules && !validationTranslator) {
|
|
169
|
+
if (isNativeAvailable()) {
|
|
170
|
+
return validateWithRust<T>(fields, data);
|
|
171
|
+
}
|
|
172
|
+
// This schema would have used the native engine, but it isn't
|
|
173
|
+
// loaded — surface the platform-dependent TS fallback once instead
|
|
174
|
+
// of diverging silently. (Schemas with custom rules / a translator
|
|
175
|
+
// always run on TS by design and don't warn.)
|
|
176
|
+
warnNativeUnavailableOnce();
|
|
166
177
|
}
|
|
167
178
|
|
|
168
179
|
const errors: ValidationError[] = [];
|
|
@@ -322,15 +333,12 @@ export class RuleChain {
|
|
|
322
333
|
email(): this {
|
|
323
334
|
this.#rules.push({
|
|
324
335
|
name: "email",
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const dot = domain.lastIndexOf(".");
|
|
332
|
-
return dot > 0 && dot < domain.length - 1;
|
|
333
|
-
},
|
|
336
|
+
// Mirror the Rust engine's regex exactly (crates/rune-engine/src/engine.rs)
|
|
337
|
+
// so the SAME schema validates identically whether or not the native
|
|
338
|
+
// binary loaded: no whitespace anywhere (the old TS rule rejected only
|
|
339
|
+
// \r\n, silently accepting interior spaces), a single @, dotted domain.
|
|
340
|
+
validate: (v) =>
|
|
341
|
+
typeof v === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
|
|
334
342
|
message: "Must be a valid email",
|
|
335
343
|
});
|
|
336
344
|
return this;
|
|
@@ -523,11 +531,14 @@ function validateWithRust<T>(
|
|
|
523
531
|
for (const [field, chain] of Object.entries(fields)) {
|
|
524
532
|
const rules = chain.rules.map((r) => ({
|
|
525
533
|
name: r.name,
|
|
534
|
+
// Serialize THIS rule's own param. Previously a find-first lookup by
|
|
535
|
+
// rule name returned the first matching rule's param, so min(3).min(5)
|
|
536
|
+
// sent both Rust entries as min=3, dropping the 5 bound (audit 2026-06-13).
|
|
526
537
|
params:
|
|
527
538
|
r.name === "min"
|
|
528
|
-
? { min:
|
|
539
|
+
? { min: r.param }
|
|
529
540
|
: r.name === "max"
|
|
530
|
-
? { max:
|
|
541
|
+
? { max: r.param }
|
|
531
542
|
: null,
|
|
532
543
|
}));
|
|
533
544
|
schemaDesc[field] = {
|
|
@@ -544,10 +555,3 @@ function validateWithRust<T>(
|
|
|
544
555
|
}
|
|
545
556
|
return { valid: false, errors: native.errors };
|
|
546
557
|
}
|
|
547
|
-
|
|
548
|
-
function extractParam(chain: RuleChain, ruleName: string): number | undefined {
|
|
549
|
-
const rule = chain.rules.find((r) => r.name === ruleName);
|
|
550
|
-
if (!rule) return undefined;
|
|
551
|
-
// Use stored param directly (no longer parsed from message text)
|
|
552
|
-
return rule.param;
|
|
553
|
-
}
|
package/src/native.ts
CHANGED
|
@@ -57,3 +57,29 @@ export function validateNative(requestJson: string): {
|
|
|
57
57
|
export function isNativeAvailable(): boolean {
|
|
58
58
|
return native !== undefined;
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
let warnedFallback = false;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Surface — exactly once per process — that the native engine is unavailable so
|
|
65
|
+
* the schema is falling back to the TypeScript validator. The two paths can
|
|
66
|
+
* differ subtly, so a silent fallback makes validation results depend on whether
|
|
67
|
+
* the prebuilt binary loaded (platform-dependent). Callers invoke this only when
|
|
68
|
+
* they WOULD have used the native engine (no custom rules / translator).
|
|
69
|
+
*/
|
|
70
|
+
export function warnNativeUnavailableOnce(): void {
|
|
71
|
+
if (warnedFallback) return;
|
|
72
|
+
warnedFallback = true;
|
|
73
|
+
const target = `${platform}-${arch}`;
|
|
74
|
+
const reason =
|
|
75
|
+
loadError !== undefined
|
|
76
|
+
? `failed to load (${loadError instanceof Error ? loadError.message : String(loadError)})`
|
|
77
|
+
: platformMap[target] !== undefined
|
|
78
|
+
? "binary not found"
|
|
79
|
+
: `no prebuilt binary for ${target}`;
|
|
80
|
+
console.warn(
|
|
81
|
+
`[rune] native validation engine unavailable — ${reason}. Falling back to the ` +
|
|
82
|
+
"TypeScript validator, whose results can differ subtly from the Rust engine — " +
|
|
83
|
+
"validation is platform-dependent for this process.",
|
|
84
|
+
);
|
|
85
|
+
}
|