@warlock.js/seal 5.15.0 → 5.17.0
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/CHANGELOG.md +7 -0
- package/README.md +22 -0
- package/cjs/index.cjs +953 -6977
- package/cjs/index.cjs.map +1 -1
- package/cjs/object-_2YHdBxJ.cjs +613 -0
- package/cjs/object-_2YHdBxJ.cjs.map +1 -0
- package/cjs/object.cjs +10 -0
- package/cjs/required-methods-GwIEE6hP.cjs +5868 -0
- package/cjs/required-methods-GwIEE6hP.cjs.map +1 -0
- package/cjs/string-6m5Y83pJ.cjs +943 -0
- package/cjs/string-6m5Y83pJ.cjs.map +1 -0
- package/cjs/string.cjs +10 -0
- package/esm/factory/validators.d.mts +5 -0
- package/esm/factory/validators.d.mts.map +1 -1
- package/esm/factory/validators.mjs +4 -3
- package/esm/factory/validators.mjs.map +1 -1
- package/esm/helpers/index.mjs +1 -1
- package/esm/helpers/validation-helpers.mjs +1 -1
- package/esm/helpers/validation-helpers.mjs.map +1 -1
- package/esm/index.mjs +1 -1
- package/esm/object.d.mts +15 -0
- package/esm/object.d.mts.map +1 -0
- package/esm/object.mjs +13 -0
- package/esm/object.mjs.map +1 -0
- package/esm/rules/common/equals-field-rules.mjs +1 -1
- package/esm/rules/conditional/forbidden-if-rules.mjs +2 -2
- package/esm/rules/conditional/present-if-rules.mjs +2 -2
- package/esm/rules/conditional/present-unless-rules.mjs +1 -1
- package/esm/rules/conditional/present-with-rules.mjs +1 -1
- package/esm/rules/conditional/present-without-rules.mjs +1 -1
- package/esm/rules/conditional/required-if-rules.mjs +2 -2
- package/esm/rules/conditional/required-unless-rules.mjs +2 -2
- package/esm/rules/conditional/required-when-rule.mjs +1 -1
- package/esm/rules/conditional/required-with-rules.mjs +2 -2
- package/esm/rules/conditional/required-without-rules.mjs +2 -2
- package/esm/rules/core/forbidden.mjs +1 -1
- package/esm/rules/core/required.mjs +1 -1
- package/esm/rules/date/date-field-comparison-rules.mjs +1 -1
- package/esm/rules/date/date.mjs +1 -1
- package/esm/rules/file/file-size.mjs +1 -1
- package/esm/rules/scalar/accepted-rule.mjs +2 -2
- package/esm/rules/scalar/declined-rule.mjs +2 -2
- package/esm/string.d.mts +13 -0
- package/esm/string.d.mts.map +1 -0
- package/esm/string.mjs +13 -0
- package/esm/string.mjs.map +1 -0
- package/esm/types/context-types.d.mts +2 -1
- package/esm/types/context-types.d.mts.map +1 -1
- package/esm/validators/base-validator.d.mts.map +1 -1
- package/esm/validators/base-validator.mjs +0 -1
- package/esm/validators/base-validator.mjs.map +1 -1
- package/esm/validators/object-validator.d.mts.map +1 -1
- package/esm/validators/object-validator.mjs +0 -3
- package/esm/validators/object-validator.mjs.map +1 -1
- package/esm/validators/primitive-validator.mjs +0 -1
- package/esm/validators/primitive-validator.mjs.map +1 -1
- package/esm/validators/string-validator.d.mts.map +1 -1
- package/esm/validators/string-validator.mjs +2 -2
- package/esm/validators/string-validator.mjs.map +1 -1
- package/llms-full.txt +22 -0
- package/package.json +21 -1
- package/skills/seal-basics/SKILL.md +22 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
const require_required_methods = require('./required-methods-GwIEE6hP.cjs');
|
|
2
|
+
let _mongez_reinforcements = require("@mongez/reinforcements");
|
|
3
|
+
let _mongez_supportive_is = require("@mongez/supportive-is");
|
|
4
|
+
|
|
5
|
+
//#region ../seal/src/mutators/object-mutators.ts
|
|
6
|
+
/** Strip unknown keys from object */
|
|
7
|
+
const stripUnknownMutator = async (value, context) => {
|
|
8
|
+
const allowedKeys = [...context?.ctx.schema ? Object.keys(context.ctx.schema) : [], ...context?.options.allowedKeys ?? []];
|
|
9
|
+
const result = {};
|
|
10
|
+
for (const key in value) if (allowedKeys.includes(key)) result[key] = value[key];
|
|
11
|
+
return result;
|
|
12
|
+
};
|
|
13
|
+
/** Trim all string values in object */
|
|
14
|
+
const objectTrimMutator = async (value, context) => {
|
|
15
|
+
if (!(0, _mongez_supportive_is.isPlainObject)(value)) return value;
|
|
16
|
+
const result = {};
|
|
17
|
+
const recursive = context?.options.recursive ?? false;
|
|
18
|
+
for (const key in value) {
|
|
19
|
+
const item = value[key];
|
|
20
|
+
if (recursive) if (Array.isArray(item)) result[key] = await Promise.all(item.map(async (i) => typeof i === "string" ? (0, _mongez_reinforcements.trim)(i) : await objectTrimMutator(i, context)));
|
|
21
|
+
else if ((0, _mongez_supportive_is.isPlainObject)(item)) result[key] = await objectTrimMutator(item, context);
|
|
22
|
+
else result[key] = typeof item === "string" ? (0, _mongez_reinforcements.trim)(item) : item;
|
|
23
|
+
else result[key] = typeof item === "string" ? (0, _mongez_reinforcements.trim)(item) : item;
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
/** Parse JSON string */
|
|
28
|
+
const jsonMutator = async (value) => {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(value);
|
|
31
|
+
} catch {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region ../seal/src/validators/computed-validator.ts
|
|
38
|
+
/**
|
|
39
|
+
* Computed field validator
|
|
40
|
+
*
|
|
41
|
+
* Computes a value based on other validated fields in the schema.
|
|
42
|
+
* The computed value is persisted and can optionally be validated.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* // Basic computed field
|
|
47
|
+
* const schema = v.object({
|
|
48
|
+
* title: v.string().required(),
|
|
49
|
+
* slug: v.computed(data => slugify(data.title)),
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* // With result validation
|
|
53
|
+
* const schema = v.object({
|
|
54
|
+
* title: v.string().required(),
|
|
55
|
+
* slug: v.computed(
|
|
56
|
+
* data => slugify(data.title),
|
|
57
|
+
* v.string().minLength(3)
|
|
58
|
+
* ),
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* // Async computation
|
|
62
|
+
* const schema = v.object({
|
|
63
|
+
* image: v.string().url(),
|
|
64
|
+
* thumbnail: v.computed(async data => {
|
|
65
|
+
* return await generateThumbnail(data.image);
|
|
66
|
+
* }),
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
var ComputedValidator = class extends require_required_methods.BaseValidator {
|
|
71
|
+
/**
|
|
72
|
+
* Create a new computed field validator
|
|
73
|
+
*
|
|
74
|
+
* @param callback - Function to compute the value from validated data
|
|
75
|
+
* @param resultValidator - Optional validator to validate the computed result
|
|
76
|
+
*/
|
|
77
|
+
constructor(callback, resultValidator) {
|
|
78
|
+
super();
|
|
79
|
+
this.callback = callback;
|
|
80
|
+
this.resultValidator = resultValidator;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Execute the callback and optionally validate the result
|
|
84
|
+
*/
|
|
85
|
+
async validate(data, context) {
|
|
86
|
+
try {
|
|
87
|
+
const result = await this.callback(data, context);
|
|
88
|
+
if (this.resultValidator) {
|
|
89
|
+
const validation = await this.resultValidator.validate(result, context);
|
|
90
|
+
if (!validation.isValid) return {
|
|
91
|
+
isValid: false,
|
|
92
|
+
errors: validation.errors,
|
|
93
|
+
data: void 0
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
isValid: true,
|
|
97
|
+
errors: [],
|
|
98
|
+
data: validation.data
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
isValid: true,
|
|
103
|
+
errors: [],
|
|
104
|
+
data: result
|
|
105
|
+
};
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
isValid: false,
|
|
109
|
+
errors: [{
|
|
110
|
+
type: "computed",
|
|
111
|
+
error: error instanceof Error ? error.message : "Computed field callback failed",
|
|
112
|
+
input: context.path
|
|
113
|
+
}],
|
|
114
|
+
data: void 0
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Clone this validator with all its configuration
|
|
120
|
+
* Critical for ObjectValidator.clone(), extend(), merge(), etc.
|
|
121
|
+
*/
|
|
122
|
+
clone() {
|
|
123
|
+
const cloned = super.clone();
|
|
124
|
+
cloned.callback = this.callback;
|
|
125
|
+
cloned.resultValidator = this.resultValidator?.clone();
|
|
126
|
+
return cloned;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Computed fields don't have a specific type to match
|
|
130
|
+
*/
|
|
131
|
+
matchesType(value) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* @inheritdoc
|
|
136
|
+
*
|
|
137
|
+
* Computed fields are server-side runtime values — they have no input schema
|
|
138
|
+
* and cannot be represented in JSON Schema. Calling this method is always a
|
|
139
|
+
* programming error.
|
|
140
|
+
*
|
|
141
|
+
* @throws Error Always throws — computed/managed fields have no JSON Schema representation.
|
|
142
|
+
*/
|
|
143
|
+
toJsonSchema(_target = "draft-2020-12") {
|
|
144
|
+
throw new Error("[Seal] toJsonSchema() is not supported on ComputedValidator / ManagedValidator. Computed fields are runtime-only and have no input JSON Schema representation. ObjectValidator.toJsonSchema() automatically skips computed fields — do not call toJsonSchema() on a computed validator directly.");
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region ../seal/src/validators/object-validator.ts
|
|
150
|
+
/**
|
|
151
|
+
* Object validator class with generic schema type for proper type inference.
|
|
152
|
+
*
|
|
153
|
+
* Threads two distinct inferred shapes into `BaseValidator`'s `TInput`/`TOutput`:
|
|
154
|
+
*
|
|
155
|
+
* - `TInput` = `InferInputObjectShape<TSchema>` — what `~standard.validate()`
|
|
156
|
+
* accepts (caller's pre-validation payload; defaults/catches make
|
|
157
|
+
* keys optional)
|
|
158
|
+
* - `TOutput` = `InferOutputObjectShape<TSchema>` — what `~standard.validate()`
|
|
159
|
+
* returns on success (post-default, post-catch shape; guaranteed
|
|
160
|
+
* keys are required)
|
|
161
|
+
*
|
|
162
|
+
* Both helpers are used directly (not the full `Infer.*` walker) to avoid a
|
|
163
|
+
* recursive class-base evaluation that would otherwise occur when the
|
|
164
|
+
* brand-aware widening tries to inspect the class type.
|
|
165
|
+
*/
|
|
166
|
+
var ObjectValidator = class ObjectValidator extends require_required_methods.BaseValidator {
|
|
167
|
+
constructor(schema, errorMessage) {
|
|
168
|
+
super();
|
|
169
|
+
this.schema = schema;
|
|
170
|
+
this.shouldAllowUnknown = false;
|
|
171
|
+
this.allowedKeys = [];
|
|
172
|
+
this.hasUnknownKeyRule = false;
|
|
173
|
+
this.addMutableRule(require_required_methods.objectRule, errorMessage);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Check if value is an object type (plain object, not array or date)
|
|
177
|
+
*/
|
|
178
|
+
matchesType(value) {
|
|
179
|
+
return (0, _mongez_supportive_is.isPlainObject)(value);
|
|
180
|
+
}
|
|
181
|
+
/** Strip unknown keys from the data */
|
|
182
|
+
stripUnknown() {
|
|
183
|
+
const validator = this.instance;
|
|
184
|
+
return validator.addMutator(stripUnknownMutator, { get allowedKeys() {
|
|
185
|
+
return validator.allowedKeys;
|
|
186
|
+
} });
|
|
187
|
+
}
|
|
188
|
+
/** Add list of allowed keys that could be in the data but not necessarily validated */
|
|
189
|
+
allow(...keys) {
|
|
190
|
+
const validator = this.instance;
|
|
191
|
+
validator.allowedKeys.push(...keys);
|
|
192
|
+
return validator;
|
|
193
|
+
}
|
|
194
|
+
/** Trim values of the object properties */
|
|
195
|
+
trim(recursive = true) {
|
|
196
|
+
return this.instance.addMutator(objectTrimMutator, { recursive });
|
|
197
|
+
}
|
|
198
|
+
/** Whether to allow unknown properties
|
|
199
|
+
* Please note it will allow only unknown direct children keys, not nested children keys
|
|
200
|
+
*/
|
|
201
|
+
allowUnknown(allow = true) {
|
|
202
|
+
const validator = this.instance;
|
|
203
|
+
validator.shouldAllowUnknown = allow;
|
|
204
|
+
return validator;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Create a copy of this object validator with the same configuration
|
|
208
|
+
* Copies schema, rules, mutators, transformers, and object-specific settings
|
|
209
|
+
*
|
|
210
|
+
* @returns A new ObjectValidator instance with copied configuration
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* const baseUser = v.object({ name: v.string() }).allowUnknown();
|
|
215
|
+
* const userCopy = baseUser.clone();
|
|
216
|
+
* // userCopy has the same schema and allowUnknown setting
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
clone(keys) {
|
|
220
|
+
const cloned = super.clone();
|
|
221
|
+
const newSchema = {};
|
|
222
|
+
for (const [key, validator] of Object.entries(this.schema)) {
|
|
223
|
+
if (keys && !keys.includes(key)) continue;
|
|
224
|
+
newSchema[key] = validator.clone();
|
|
225
|
+
}
|
|
226
|
+
cloned.schema = newSchema;
|
|
227
|
+
cloned.shouldAllowUnknown = this.shouldAllowUnknown;
|
|
228
|
+
cloned.allowedKeys = [...this.allowedKeys];
|
|
229
|
+
return cloned;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Extend this schema with additional fields
|
|
233
|
+
* Clones the current validator and adds new fields to the schema
|
|
234
|
+
* **Keeps original configuration** (allowUnknown, stripUnknown, etc.)
|
|
235
|
+
*
|
|
236
|
+
* If an ObjectValidator is provided, only its schema is used - its configuration is ignored.
|
|
237
|
+
* This is useful for creating reusable field collections that can be added to different schemas.
|
|
238
|
+
*
|
|
239
|
+
* @param schemaOrValidator - Plain schema object or ObjectValidator to extend with
|
|
240
|
+
* @returns A new ObjectValidator with merged schema and original configuration
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* // Extend with plain schema
|
|
245
|
+
* const baseUser = v.object({
|
|
246
|
+
* name: v.string().required(),
|
|
247
|
+
* email: v.string().email().required()
|
|
248
|
+
* }).allowUnknown();
|
|
249
|
+
*
|
|
250
|
+
* const adminUser = baseUser.extend({
|
|
251
|
+
* role: v.string().in(['admin', 'superadmin']).required()
|
|
252
|
+
* });
|
|
253
|
+
* // adminUser has: name, email, role
|
|
254
|
+
* // adminUser keeps: allowUnknown() from base ✅
|
|
255
|
+
*
|
|
256
|
+
* // Extend with ObjectValidator (only schema is used)
|
|
257
|
+
* const auditFields = v.object({
|
|
258
|
+
* createdAt: v.date().required(),
|
|
259
|
+
* updatedAt: v.date().required()
|
|
260
|
+
* }).stripUnknown(); // This config is ignored!
|
|
261
|
+
*
|
|
262
|
+
* const fullUser = baseUser.extend(auditFields);
|
|
263
|
+
* // fullUser has: name, email, createdAt, updatedAt
|
|
264
|
+
* // fullUser keeps: allowUnknown() from base (NOT stripUnknown from auditFields) ✅
|
|
265
|
+
*
|
|
266
|
+
* // Chain multiple extends
|
|
267
|
+
* const complexSchema = baseUser
|
|
268
|
+
* .extend(auditFields)
|
|
269
|
+
* .extend({ metadata: v.object({}) });
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
extend(schemaOrValidator) {
|
|
273
|
+
const extended = this.clone();
|
|
274
|
+
const schemaToAdd = schemaOrValidator instanceof ObjectValidator ? schemaOrValidator.schema : schemaOrValidator;
|
|
275
|
+
for (const [key, validator] of Object.entries(schemaToAdd)) extended.schema[key] = validator.clone();
|
|
276
|
+
return extended;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Merge with another ObjectValidator
|
|
280
|
+
* Clones current validator, merges schemas, and **overrides configuration** with other validator's config
|
|
281
|
+
*
|
|
282
|
+
* Unlike extend(), merge() combines both schemas AND configurations.
|
|
283
|
+
* The other validator's configuration (allowUnknown, stripUnknown, etc.) takes precedence.
|
|
284
|
+
*
|
|
285
|
+
* @param validator - Another ObjectValidator to merge with
|
|
286
|
+
* @returns A new ObjectValidator with merged schema and configuration
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```ts
|
|
290
|
+
* const baseUser = v.object({
|
|
291
|
+
* name: v.string().required()
|
|
292
|
+
* }).allowUnknown();
|
|
293
|
+
*
|
|
294
|
+
* const timestamps = v.object({
|
|
295
|
+
* createdAt: v.date().required(),
|
|
296
|
+
* updatedAt: v.date().required()
|
|
297
|
+
* }).stripUnknown();
|
|
298
|
+
*
|
|
299
|
+
* const merged = baseUser.merge(timestamps);
|
|
300
|
+
* // merged has: name, createdAt, updatedAt
|
|
301
|
+
* // merged config: stripUnknown() from timestamps (overrides allowUnknown) ✅
|
|
302
|
+
*
|
|
303
|
+
* // Chain multiple merges
|
|
304
|
+
* const full = baseUser.merge(timestamps).merge(softDeleteSchema);
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
merge(validator) {
|
|
308
|
+
const merged = this.clone();
|
|
309
|
+
for (const [key, schemaValidator] of Object.entries(validator.schema)) merged.schema[key] = schemaValidator.clone();
|
|
310
|
+
merged.shouldAllowUnknown = validator.shouldAllowUnknown;
|
|
311
|
+
merged.allowedKeys = [...merged.allowedKeys, ...validator.allowedKeys];
|
|
312
|
+
merged.rules.push(...validator.rules);
|
|
313
|
+
merged.mutators.push(...validator.mutators);
|
|
314
|
+
merged.dataTransformers.push(...validator.dataTransformers);
|
|
315
|
+
merged.attributesText = {
|
|
316
|
+
...merged.attributesText,
|
|
317
|
+
...validator.attributesText
|
|
318
|
+
};
|
|
319
|
+
merged.translatedAttributes = {
|
|
320
|
+
...merged.translatedAttributes,
|
|
321
|
+
...validator.translatedAttributes
|
|
322
|
+
};
|
|
323
|
+
return merged;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Create a new schema with only the specified fields
|
|
327
|
+
* Clones the current validator and keeps only the selected fields
|
|
328
|
+
* **Preserves all configuration** (allowUnknown, stripUnknown, etc.)
|
|
329
|
+
*
|
|
330
|
+
* @param keys - Field names to keep in the schema
|
|
331
|
+
* @returns A new ObjectValidator with only the picked fields
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```ts
|
|
335
|
+
* const fullUser = v.object({
|
|
336
|
+
* id: v.int().required(),
|
|
337
|
+
* name: v.string().required(),
|
|
338
|
+
* email: v.string().email().required(),
|
|
339
|
+
* password: v.string().required(),
|
|
340
|
+
* role: v.string()
|
|
341
|
+
* }).allowUnknown();
|
|
342
|
+
*
|
|
343
|
+
* // For login - only need email and password
|
|
344
|
+
* const loginSchema = fullUser.pick('email', 'password');
|
|
345
|
+
* // loginSchema has: { email, password }
|
|
346
|
+
* // loginSchema keeps: allowUnknown() ✅
|
|
347
|
+
*
|
|
348
|
+
* // For public profile
|
|
349
|
+
* const publicSchema = fullUser.pick('id', 'name', 'role');
|
|
350
|
+
* // publicSchema has: { id, name, role }
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
pick(...keys) {
|
|
354
|
+
const picked = this.clone();
|
|
355
|
+
const newSchema = {};
|
|
356
|
+
for (const key of keys) if (key in picked.schema) newSchema[key] = picked.schema[key];
|
|
357
|
+
picked.schema = newSchema;
|
|
358
|
+
return picked;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Mark all or the given schema fields as optional
|
|
362
|
+
*/
|
|
363
|
+
partial(...keys) {
|
|
364
|
+
const validationSchema = this.clone();
|
|
365
|
+
if (keys.length === 0) keys = Object.keys(validationSchema.schema);
|
|
366
|
+
for (const [key, validator] of Object.entries(validationSchema.schema)) if (keys.length === 0 || keys.includes(key)) validationSchema.schema[key] = validator.optional();
|
|
367
|
+
return validationSchema;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Make the all or the given fields as required
|
|
371
|
+
*/
|
|
372
|
+
requiredFields(...keys) {
|
|
373
|
+
const validationSchema = this.clone();
|
|
374
|
+
if (keys.length === 0) keys = Object.keys(validationSchema.schema);
|
|
375
|
+
for (const [key, validator] of Object.entries(validationSchema.schema)) if (keys.length === 0 || keys.includes(key)) validationSchema.schema[key] = validator.required();
|
|
376
|
+
return validationSchema;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Create a new schema excluding the specified fields
|
|
380
|
+
* Clones the current validator and removes the specified fields
|
|
381
|
+
* **Preserves all configuration** (allowUnknown, stripUnknown, etc.)
|
|
382
|
+
*
|
|
383
|
+
* @param keys - Field names to exclude from the schema
|
|
384
|
+
* @returns A new ObjectValidator without the excluded fields
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```ts
|
|
388
|
+
* const fullUser = v.object({
|
|
389
|
+
* id: v.int().required(),
|
|
390
|
+
* name: v.string().required(),
|
|
391
|
+
* email: v.string().email().required(),
|
|
392
|
+
* password: v.string().required(),
|
|
393
|
+
* role: v.string()
|
|
394
|
+
* }).allowUnknown();
|
|
395
|
+
*
|
|
396
|
+
* // For updates - exclude id
|
|
397
|
+
* const updateSchema = fullUser.without('id');
|
|
398
|
+
* // updateSchema has: { name, email, password, role }
|
|
399
|
+
* // updateSchema keeps: allowUnknown() ✅
|
|
400
|
+
*
|
|
401
|
+
* // For public API - exclude sensitive fields
|
|
402
|
+
* const publicSchema = fullUser.without('password', 'role');
|
|
403
|
+
* // publicSchema has: { id, name, email }
|
|
404
|
+
*
|
|
405
|
+
* // Combine with other methods
|
|
406
|
+
* const patchSchema = fullUser.without('id', 'password');
|
|
407
|
+
* // patchSchema has: { name, email, role }
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
without(...keys) {
|
|
411
|
+
const filtered = this.clone();
|
|
412
|
+
const newSchema = {};
|
|
413
|
+
for (const key in filtered.schema) if (!keys.includes(key)) newSchema[key] = filtered.schema[key];
|
|
414
|
+
filtered.schema = newSchema;
|
|
415
|
+
return filtered;
|
|
416
|
+
}
|
|
417
|
+
/** Mutate the data */
|
|
418
|
+
mutate(data, context) {
|
|
419
|
+
if (!(0, _mongez_supportive_is.isPlainObject)(data)) return data;
|
|
420
|
+
return super.mutate({ ...data }, context);
|
|
421
|
+
}
|
|
422
|
+
/** Validate the data */
|
|
423
|
+
async validate(data, context = { path: "" }) {
|
|
424
|
+
context.schema = this.schema;
|
|
425
|
+
const mutatedData = await this.mutate(data, context);
|
|
426
|
+
if (this.shouldAllowUnknown === false && !this.hasUnknownKeyRule) {
|
|
427
|
+
this.hasUnknownKeyRule = true;
|
|
428
|
+
const rule = this.addMutableRule(require_required_methods.unknownKeyRule, void 0, {
|
|
429
|
+
allowedKeys: this.allowedKeys,
|
|
430
|
+
schema: this.schema
|
|
431
|
+
});
|
|
432
|
+
this.setRuleAttributesList(rule);
|
|
433
|
+
}
|
|
434
|
+
const result = await super.validate(mutatedData, context);
|
|
435
|
+
if (result.isValid === false) return result;
|
|
436
|
+
if (data === void 0) return result;
|
|
437
|
+
const errors = [];
|
|
438
|
+
const validatedData = {};
|
|
439
|
+
const validationPromises = Object.entries(this.schema).filter(([, validator]) => !this.isComputedValidator(validator)).map(async ([key, validator]) => {
|
|
440
|
+
const value = mutatedData?.[key] !== void 0 ? mutatedData[key] : validator.getDefaultValue();
|
|
441
|
+
const childContext = {
|
|
442
|
+
...context,
|
|
443
|
+
parent: mutatedData,
|
|
444
|
+
value,
|
|
445
|
+
key,
|
|
446
|
+
path: require_required_methods.setKeyPath(context.path, key)
|
|
447
|
+
};
|
|
448
|
+
const childResult = await validator.validate(value, childContext);
|
|
449
|
+
if (childResult.data !== void 0 && !validator.isOmitted()) validatedData[key] = childResult.data;
|
|
450
|
+
if (childResult.isValid === false) errors.push(...childResult.errors);
|
|
451
|
+
});
|
|
452
|
+
await Promise.all(validationPromises);
|
|
453
|
+
if (errors.length > 0) return {
|
|
454
|
+
isValid: false,
|
|
455
|
+
errors,
|
|
456
|
+
data: void 0
|
|
457
|
+
};
|
|
458
|
+
const computedFields = this.getComputedFields();
|
|
459
|
+
const computedPromises = Object.entries(computedFields).map(async ([key, validator]) => {
|
|
460
|
+
const childContext = {
|
|
461
|
+
...context,
|
|
462
|
+
parent: validatedData,
|
|
463
|
+
value: void 0
|
|
464
|
+
};
|
|
465
|
+
const childResult = await validator.validate(validatedData, childContext);
|
|
466
|
+
if (childResult.data !== void 0 && !validator.isOmitted()) validatedData[key] = childResult.data;
|
|
467
|
+
if (childResult.isValid === false) errors.push(...childResult.errors);
|
|
468
|
+
});
|
|
469
|
+
await Promise.all(computedPromises);
|
|
470
|
+
if (errors.length > 0) return {
|
|
471
|
+
isValid: false,
|
|
472
|
+
errors,
|
|
473
|
+
data: void 0
|
|
474
|
+
};
|
|
475
|
+
const cleanedData = removeUndefinedValues(validatedData);
|
|
476
|
+
const transformedData = await this.startTransformationPipeline(cleanedData, context);
|
|
477
|
+
return {
|
|
478
|
+
isValid: true,
|
|
479
|
+
errors: [],
|
|
480
|
+
data: this.shouldAllowUnknown === false ? transformedData : {
|
|
481
|
+
...transformedData,
|
|
482
|
+
...(0, _mongez_reinforcements.except)(mutatedData, Object.keys(this.schema))
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Check if a validator is a computed or managed field
|
|
488
|
+
* ManagedValidator extends ComputedValidator, so instanceof catches both
|
|
489
|
+
*/
|
|
490
|
+
isComputedValidator(validator) {
|
|
491
|
+
return validator instanceof ComputedValidator;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Get all computed/managed fields from the schema
|
|
495
|
+
*/
|
|
496
|
+
getComputedFields() {
|
|
497
|
+
const computed = {};
|
|
498
|
+
for (const [key, validator] of Object.entries(this.schema)) if (validator instanceof ComputedValidator) computed[key] = validator;
|
|
499
|
+
return computed;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* @inheritdoc
|
|
503
|
+
*
|
|
504
|
+
* Recursively generates JSON Schema for all input fields in the schema.
|
|
505
|
+
* Computed/managed fields are skipped — they have no input representation.
|
|
506
|
+
*
|
|
507
|
+
* **Standard targets** (`draft-2020-12`, `draft-07`, `openapi-3.0`):
|
|
508
|
+
* - Fields marked `.optional()` are excluded from `required`.
|
|
509
|
+
*
|
|
510
|
+
* **`openai-strict` target** (OpenAI Structured Outputs):
|
|
511
|
+
* - ALL fields appear in `required` — OpenAI rejects schemas with optional fields.
|
|
512
|
+
* - Optional fields are instead expressed as nullable types:
|
|
513
|
+
* `{ type: ["string", "null"] }` so the model can output `null` for them.
|
|
514
|
+
* - Recursively applies `openai-strict` to all nested objects.
|
|
515
|
+
*
|
|
516
|
+
* @example
|
|
517
|
+
* ```ts
|
|
518
|
+
* v.object({
|
|
519
|
+
* name: v.string().required(),
|
|
520
|
+
* age: v.int().optional(),
|
|
521
|
+
* }).toJsonSchema("draft-2020-12")
|
|
522
|
+
* // → { type: "object",
|
|
523
|
+
* // properties: { name: { type: "string" }, age: { type: "integer" } },
|
|
524
|
+
* // required: ["name"], additionalProperties: false }
|
|
525
|
+
*
|
|
526
|
+
* v.object({
|
|
527
|
+
* name: v.string().required(),
|
|
528
|
+
* age: v.int().optional(),
|
|
529
|
+
* }).toJsonSchema("openai-strict")
|
|
530
|
+
* // → { type: "object",
|
|
531
|
+
* // properties: { name: { type: "string" }, age: { type: ["integer", "null"] } },
|
|
532
|
+
* // required: ["name", "age"], ← all fields
|
|
533
|
+
* // additionalProperties: false }
|
|
534
|
+
* ```
|
|
535
|
+
*/
|
|
536
|
+
toJsonSchema(target = "draft-2020-12") {
|
|
537
|
+
const properties = {};
|
|
538
|
+
const required = [];
|
|
539
|
+
const isOpenAIStrict = target === "openai-strict";
|
|
540
|
+
for (const [key, validator] of Object.entries(this.schema)) {
|
|
541
|
+
if (validator instanceof ComputedValidator) continue;
|
|
542
|
+
let fieldSchema = validator.toJsonSchema(target);
|
|
543
|
+
if (isOpenAIStrict) {
|
|
544
|
+
if (validator.isOptional) fieldSchema = require_required_methods.wrapNullableStrict(fieldSchema);
|
|
545
|
+
required.push(key);
|
|
546
|
+
} else if (!validator.isOptional) required.push(key);
|
|
547
|
+
properties[key] = fieldSchema;
|
|
548
|
+
}
|
|
549
|
+
const schema = {
|
|
550
|
+
type: "object",
|
|
551
|
+
properties
|
|
552
|
+
};
|
|
553
|
+
if (required.length > 0) schema.required = required;
|
|
554
|
+
if (!this.shouldAllowUnknown) schema.additionalProperties = false;
|
|
555
|
+
if (this.isNullable) require_required_methods.applyNullable(schema, target);
|
|
556
|
+
return schema;
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
/** Recursively remove undefined values from an object */
|
|
560
|
+
function removeUndefinedValues(obj, visited = /* @__PURE__ */ new WeakMap()) {
|
|
561
|
+
if (obj === null) return obj;
|
|
562
|
+
if (Array.isArray(obj)) return obj.map((item) => removeUndefinedValues(item, visited));
|
|
563
|
+
if (!(0, _mongez_supportive_is.isPlainObject)(obj)) return obj;
|
|
564
|
+
if (visited.has(obj)) return visited.get(obj);
|
|
565
|
+
const result = {};
|
|
566
|
+
visited.set(obj, result);
|
|
567
|
+
for (const [key, value] of Object.entries(obj)) if (value !== void 0) result[key] = removeUndefinedValues(value, visited);
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region ../seal/src/object.ts
|
|
573
|
+
/** Create an object schema without importing the complete `v` factory. */
|
|
574
|
+
const object = (schema, errorMessage) => new ObjectValidator(schema, errorMessage);
|
|
575
|
+
|
|
576
|
+
//#endregion
|
|
577
|
+
Object.defineProperty(exports, 'ComputedValidator', {
|
|
578
|
+
enumerable: true,
|
|
579
|
+
get: function () {
|
|
580
|
+
return ComputedValidator;
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
Object.defineProperty(exports, 'ObjectValidator', {
|
|
584
|
+
enumerable: true,
|
|
585
|
+
get: function () {
|
|
586
|
+
return ObjectValidator;
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
Object.defineProperty(exports, 'jsonMutator', {
|
|
590
|
+
enumerable: true,
|
|
591
|
+
get: function () {
|
|
592
|
+
return jsonMutator;
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
Object.defineProperty(exports, 'object', {
|
|
596
|
+
enumerable: true,
|
|
597
|
+
get: function () {
|
|
598
|
+
return object;
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
Object.defineProperty(exports, 'objectTrimMutator', {
|
|
602
|
+
enumerable: true,
|
|
603
|
+
get: function () {
|
|
604
|
+
return objectTrimMutator;
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
Object.defineProperty(exports, 'stripUnknownMutator', {
|
|
608
|
+
enumerable: true,
|
|
609
|
+
get: function () {
|
|
610
|
+
return stripUnknownMutator;
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
//# sourceMappingURL=object-_2YHdBxJ.cjs.map
|