convex-helpers 0.1.69 → 0.1.70

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/README.md CHANGED
@@ -600,20 +600,23 @@ await ctx.runMutation(internal.users.update, {
600
600
  When using validators for defining database schema or function arguments,
601
601
  these validators help:
602
602
 
603
- 1. Add a `Table` utility that defines a table and keeps references to the fields
604
- to avoid re-defining validators. To learn more about sharing validators, read
605
- [this article](https://stack.convex.dev/argument-validation-without-repetition),
606
- an extension of [this article](https://stack.convex.dev/types-cookbook).
607
- 2. Add utilties for `partial`, `pick` and `omit` to match the TypeScript type
608
- utilities.
609
- 3. Add shorthand for a union of `literals`, a `nullable` field, a `deprecated`
610
- field, and `brandedString`. To learn more about branded strings see
603
+ 1. Add shorthand for a union of `literals`, a `nullable` field, a `deprecated`
604
+ field, a `partial` object, and `brandedString`.
605
+ To learn more about branded strings see
611
606
  [this article](https://stack.convex.dev/using-branded-types-in-validators).
607
+ 2. A `validate(validator, data)` function validates a value against a validator.
608
+ Warning: this does not validate that the value of v.id is an ID for the given table.
609
+ 3. Add utilties for `partial`, `pick` and `omit` to match the TypeScript type
610
+ utilities.
612
611
  4. Add a `doc(schema, "tableName")` helper to validate a document with system
613
612
  fields included.
614
613
  5. Add a `typedV(schema)` helper that is a `v` replacement that also has:
615
614
  - `doc("tableName")` that works like `doc` above.
616
615
  - `id("tableName")` that is typed to tables in your schema.
616
+ 6. Add a `Table` utility that defines a table and keeps references to the fields
617
+ to avoid re-defining validators. To learn more about sharing validators, read
618
+ [this article](https://stack.convex.dev/argument-validation-without-repetition),
619
+ an extension of [this article](https://stack.convex.dev/types-cookbook).
617
620
 
618
621
  Example:
619
622
 
@@ -666,6 +669,11 @@ const balanceAndEmail = pick(vv.doc("accounts").fields, ["balance", "email"]);
666
669
 
667
670
  // A validator for all the fields except balance.
668
671
  const accountWithoutBalance = omit(vv.doc("accounts").fields, ["balance"]);
672
+
673
+ // Validate against a validator. Can optionally throw on error.
674
+ validate(balanceAndEmail, { balance: 123n, email: "test@example.com" });
675
+ // Warning: this only validates that `accountId` is a string.
676
+ validate(vv.id("accounts"), accountId);
669
677
  ```
670
678
 
671
679
  ## Filter
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "convex-helpers",
3
- "version": "0.1.69",
3
+ "version": "0.1.70",
4
4
  "description": "A collection of useful code to complement the official convex package.",
5
5
  "type": "module",
6
6
  "bin": {
package/validators.d.ts CHANGED
@@ -55,7 +55,7 @@ export declare const null_: import("convex/values").VNull<null, "required">;
55
55
  /** Re-export values from v without having to do v.* */
56
56
  export declare const id: <TableName extends string>(tableName: TableName) => import("convex/values").VId<import("convex/values").GenericId<TableName>, "required">, object: <T_2 extends PropertyValidators>(fields: T_2) => VObject<import("convex/server").Expand<{ [Property in { [Property_1 in keyof T_2]: T_2[Property_1]["isOptional"] extends "optional" ? Property_1 : never; }[keyof T_2]]?: Exclude<import("convex/values").Infer<T_2[Property]>, undefined> | undefined; } & { [Property_1 in Exclude<keyof T_2, { [Property in keyof T_2]: T_2[Property]["isOptional"] extends "optional" ? Property : never; }[keyof T_2]>]: import("convex/values").Infer<T_2[Property_1]>; }>, T_2, "required", { [Property_2 in keyof T_2]: Property_2 | `${Property_2 & string}.${T_2[Property_2]["fieldPaths"]}`; }[keyof T_2] & string>, array: <T_1 extends Validator<any, "required", any>>(element: T_1) => import("convex/values").VArray<T_1["type"][], T_1, "required">, bytes: () => import("convex/values").VBytes<ArrayBuffer, "required">, literal: <T extends string | number | bigint | boolean>(literal: T) => import("convex/values").VLiteral<T, "required">, optional: <T_4 extends GenericValidator>(value: T_4) => VOptional<T_4>, union: <T_3 extends Validator<any, "required", any>[]>(...members: T_3) => VUnion<T_3[number]["type"], T_3, "required", T_3[number]["fieldPaths"]>;
57
57
  /** ArrayBuffer validator. */
58
- export declare const arrayBuffer: () => import("convex/values").VBytes<ArrayBuffer, "required">;
58
+ export declare const arrayBuffer: import("convex/values").VBytes<ArrayBuffer, "required">;
59
59
  /**
60
60
  * Utility to get the validators for fields associated with a table.
61
61
  * e.g. for systemFields("users") it would return:
@@ -215,4 +215,25 @@ export declare const pretend: <T extends GenericValidator>(_typeToImmitate: T) =
215
215
  * });
216
216
  */
217
217
  export declare const pretendRequired: <T extends Validator<any, "required", any>>(optionalType: T) => T;
218
+ export declare class ValidationError extends Error {
219
+ expected: string;
220
+ got: string;
221
+ path?: string | undefined;
222
+ constructor(expected: string, got: string, path?: string | undefined);
223
+ }
224
+ /**
225
+ * Validate a value against a validator.
226
+ *
227
+ * WARNING: This does not validate that v.id is an ID for the given table.
228
+ * It only validates that the ID is a string. Function `args`, `returns` and
229
+ * schema definitions will validate that the ID is an ID for the given table.
230
+ *
231
+ * @param validator The validator to validate against.
232
+ * @param value The value to validate.
233
+ * @returns Whether the value is valid against the validator.
234
+ */
235
+ export declare function validate<T extends Validator<any, any, any>>(validator: T, value: unknown, opts?: {
236
+ throw?: boolean;
237
+ pathPrefix?: string;
238
+ }): value is T["type"];
218
239
  //# sourceMappingURL=validators.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["validators.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,OAAO,EACP,SAAS,EACT,OAAO,EACP,MAAM,EACN,SAAS,EAEV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EACL,6BAA6B,EAC7B,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,QAAQ,GACnB,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAC5C,CAAC,SAAS,CAAC,EAAE,WAEJ,CAAC,KACT,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,CAMvB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,iNAClD,CAAC;AAEvB;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,kBAAkB,OAAO,CAAC,KAMrD,GACF,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAElC,CAAC;AAIF,wBAAwB;AACxB,eAAO,MAAM,MAAM,6BAAa,CAAC;AACjC,mEAAmE;AACnE,eAAO,MAAM,MAAM,sDAAc,CAAC;AAClC,mEAAmE;AACnE,eAAO,MAAM,OAAO,sDAAc,CAAC;AACnC,+DAA+D;AAC/D,eAAO,MAAM,OAAO,uDAAc,CAAC;AACnC,yDAAyD;AACzD,eAAO,MAAM,MAAM,oDAAY,CAAC;AAChC,yDAAyD;AACzD,eAAO,MAAM,KAAK,oDAAY,CAAC;AAC/B,uBAAuB;AACvB,eAAO,MAAM,GAAG,uDAAU,CAAC;AAC3B,sEAAsE;AACtE,eAAO,MAAM,KAAK,iDAAW,CAAC;AAC9B,uDAAuD;AACvD,eAAO,MAAQ,EAAE,6IAAE,MAAM,0NASH,CAAC,+WAOJ,eAGnB,0CAnB2B,KAAK,iIAAE,KAAK,iEAAE,OAAO,iHAAE,QAAQ,gEAAE,KAAK,6EA2C/D,WAAU,2BAGZ,WAAY,eA9C2D,CAAC;AACxE,6BAA6B;AAC7B,eAAO,MAAM,WAAW,+DAAQ,CAAC;AAEjC;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,SAAS,MAAM,aACxC,SAAS;;;CAIpB,CAAC;AAEH,MAAM,MAAM,YAAY,CAAC,SAAS,SAAS,MAAM,IAAI,UAAU,CAC7D,OAAO,YAAY,CAAC,SAAS,CAAC,CAC/B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,SAAS,MAAM,EACxB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,aAE/B,SAAS,UACZ,CAAC,KAMJ,MAAM,CAAC,CAAC;;;CAAgB,CAC9B,CAAC;AAEF,MAAM,MAAM,oBAAoB,CAC9B,CAAC,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAClC,MAAM,SAAS,kBAAkB,IAEjC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,GACxC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,GAC9D,SAAS,CACP,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,EACtC,CAAC,CAAC,YAAY,CAAC,EACf,CAAC,CAAC,YAAY,CAAC,GACb;KACG,QAAQ,IAAI,MAAM,MAAM,GAAG,MAAM,GAC9B,GAAG,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,EAAE,GAC/C,QAAQ;CACb,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GACxB,MAAM,CACT,CAAC;AAER,eAAO,MAAM,GAAG,GACd,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,EAC7C,SAAS,SAAS,qBAAqB,CACrC,6BAA6B,CAAC,MAAM,CAAC,CACtC,UAEO,MAAM,aACH,SAAS,KACnB,oBAAoB,CACrB,CAAC,OAAO,MAAM,EAAE,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,EACjD,YAAY,CAAC,SAAS,CAAC,CAmBxB,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,MAAM,CAAC,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,EAClE,MAAM,EAAE,MAAM;IAIZ;;;;OAIG;SAED,SAAS,SAAS,qBAAqB,CACrC,6BAA6B,CAAC,MAAM,CAAC,CACtC,aAEU,SAAS;IAEtB;;;;;;;;OAQG;UAED,SAAS,SAAS,qBAAqB,CACrC,6BAA6B,CAAC,MAAM,CAAC,CACtC,aAEU,SAAS,KACnB,oBAAoB,CACrB,CAAC,OAAO,MAAM,EAAE,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,EACjD,YAAY,CAAC,SAAS,CAAC,CACxB;;;;;;;;;;;oOA3IiB,CAAC,+WAOJ,eAGnB;;2DAsBI,GAAG,wBAEL,WAAU,2BAGZ,WAAY;;;EAwGX;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,MAAM,UAAU,CAAC,KACzC,OAAO,CAAC,MAAM,GAAG;IAAE,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC;AAE3C,6EAA6E;AAC7E,eAAO,MAAM,UAAU,EAA0B,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,gBAAgB,mBAAmB,CAAC,KAAG,CAC/C,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,CAAC,gBACzD,CAAC,KACd,CAA6C,CAAC"}
1
+ {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["validators.ts"],"names":[],"mappings":"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"}
package/validators.js CHANGED
@@ -64,7 +64,7 @@ export const null_ = v.null();
64
64
  /** Re-export values from v without having to do v.* */
65
65
  export const { id, object, array, bytes, literal, optional, union } = v;
66
66
  /** ArrayBuffer validator. */
67
- export const arrayBuffer = bytes;
67
+ export const arrayBuffer = bytes();
68
68
  /**
69
69
  * Utility to get the validators for fields associated with a table.
70
70
  * e.g. for systemFields("users") it would return:
@@ -223,3 +223,187 @@ export const pretend = (_typeToImmitate) => v.optional(v.any());
223
223
  * });
224
224
  */
225
225
  export const pretendRequired = (optionalType) => v.optional(optionalType);
226
+ export class ValidationError extends Error {
227
+ expected;
228
+ got;
229
+ path;
230
+ constructor(expected, got, path) {
231
+ const message = `Validator error${path ? ` for ${path}` : ""}: Expected \`${expected}\`, got \`${got}\``;
232
+ super(message);
233
+ this.expected = expected;
234
+ this.got = got;
235
+ this.path = path;
236
+ this.name = "ValidationError";
237
+ }
238
+ }
239
+ /**
240
+ * Validate a value against a validator.
241
+ *
242
+ * WARNING: This does not validate that v.id is an ID for the given table.
243
+ * It only validates that the ID is a string. Function `args`, `returns` and
244
+ * schema definitions will validate that the ID is an ID for the given table.
245
+ *
246
+ * @param validator The validator to validate against.
247
+ * @param value The value to validate.
248
+ * @returns Whether the value is valid against the validator.
249
+ */
250
+ export function validate(validator, value, opts) {
251
+ let valid = true;
252
+ let expected = validator.kind;
253
+ if (value === undefined) {
254
+ if (validator.isOptional !== "optional") {
255
+ valid = false;
256
+ }
257
+ }
258
+ else {
259
+ switch (validator.kind) {
260
+ case "null": {
261
+ if (value !== null) {
262
+ valid = false;
263
+ }
264
+ break;
265
+ }
266
+ case "float64": {
267
+ if (typeof value !== "number") {
268
+ expected = "number";
269
+ valid = false;
270
+ }
271
+ break;
272
+ }
273
+ case "int64": {
274
+ if (typeof value !== "bigint") {
275
+ expected = "bigint";
276
+ valid = false;
277
+ }
278
+ break;
279
+ }
280
+ case "boolean": {
281
+ if (typeof value !== "boolean") {
282
+ valid = false;
283
+ }
284
+ break;
285
+ }
286
+ case "string": {
287
+ if (typeof value !== "string") {
288
+ valid = false;
289
+ }
290
+ break;
291
+ }
292
+ case "bytes": {
293
+ if (!(value instanceof ArrayBuffer)) {
294
+ valid = false;
295
+ }
296
+ break;
297
+ }
298
+ case "any": {
299
+ break;
300
+ }
301
+ case "literal": {
302
+ if (value !== validator.value) {
303
+ valid = false;
304
+ }
305
+ break;
306
+ }
307
+ case "id": {
308
+ if (typeof value !== "string") {
309
+ valid = false;
310
+ }
311
+ break;
312
+ }
313
+ case "array": {
314
+ if (!Array.isArray(value)) {
315
+ valid = false;
316
+ break;
317
+ }
318
+ for (const [index, v] of value.entries()) {
319
+ const path = `${opts?.pathPrefix ?? ""}[${index}]`;
320
+ valid = validate(validator.element, v, { ...opts, pathPrefix: path });
321
+ if (!valid) {
322
+ expected = validator.element.kind;
323
+ break;
324
+ }
325
+ }
326
+ break;
327
+ }
328
+ case "object": {
329
+ if (typeof value !== "object" || value === null) {
330
+ valid = false;
331
+ break;
332
+ }
333
+ const prototype = Object.getPrototypeOf(value);
334
+ const isSimple = prototype === null ||
335
+ prototype === Object.prototype ||
336
+ // Objects generated from other contexts (e.g. across Node.js `vm` modules) will not satisfy the previous
337
+ // conditions but are still simple objects.
338
+ prototype?.constructor?.name === "Object";
339
+ if (!isSimple) {
340
+ expected =
341
+ prototype?.constructor?.name ?? typeof prototype ?? "object";
342
+ valid = false;
343
+ break;
344
+ }
345
+ for (const [k, fieldValidator] of Object.entries(validator.fields)) {
346
+ valid = validate(fieldValidator, value[k], {
347
+ ...opts,
348
+ pathPrefix: appendPath(opts, k),
349
+ });
350
+ if (!valid) {
351
+ break;
352
+ }
353
+ }
354
+ for (const k of Object.keys(value)) {
355
+ if (validator.fields[k] === undefined) {
356
+ if (opts?.throw) {
357
+ throw new ValidationError("nothing", typeof value[k], appendPath(opts, k));
358
+ }
359
+ valid = false;
360
+ break;
361
+ }
362
+ }
363
+ break;
364
+ }
365
+ case "union": {
366
+ valid = false;
367
+ for (const member of validator.members) {
368
+ if (validate(member, value, opts)) {
369
+ valid = true;
370
+ break;
371
+ }
372
+ }
373
+ break;
374
+ }
375
+ case "record": {
376
+ if (typeof value !== "object" || value === null) {
377
+ valid = false;
378
+ break;
379
+ }
380
+ for (const [k, fieldValue] of Object.entries(value)) {
381
+ valid = validate(validator.key, k, {
382
+ ...opts,
383
+ pathPrefix: appendPath(opts, k),
384
+ });
385
+ if (!valid) {
386
+ expected = validator.key.kind;
387
+ break;
388
+ }
389
+ valid = validate(validator.value, fieldValue, {
390
+ ...opts,
391
+ pathPrefix: appendPath(opts, k),
392
+ });
393
+ if (!valid) {
394
+ expected = validator.value.kind;
395
+ break;
396
+ }
397
+ }
398
+ break;
399
+ }
400
+ }
401
+ }
402
+ if (!valid && opts?.throw) {
403
+ throw new ValidationError(expected, typeof value, opts?.pathPrefix);
404
+ }
405
+ return valid;
406
+ }
407
+ function appendPath(opts, path) {
408
+ return opts?.pathPrefix ? `${opts.pathPrefix}.${path}` : path;
409
+ }
package/validators.ts CHANGED
@@ -96,7 +96,7 @@ export const null_ = v.null();
96
96
  /** Re-export values from v without having to do v.* */
97
97
  export const { id, object, array, bytes, literal, optional, union } = v;
98
98
  /** ArrayBuffer validator. */
99
- export const arrayBuffer = bytes;
99
+ export const arrayBuffer = bytes();
100
100
 
101
101
  /**
102
102
  * Utility to get the validators for fields associated with a table.
@@ -329,3 +329,200 @@ export const pretend = <T extends GenericValidator>(_typeToImmitate: T): T =>
329
329
  export const pretendRequired = <T extends Validator<any, "required", any>>(
330
330
  optionalType: T,
331
331
  ): T => v.optional(optionalType) as unknown as T;
332
+
333
+ export class ValidationError extends Error {
334
+ constructor(
335
+ public expected: string,
336
+ public got: string,
337
+ public path?: string,
338
+ ) {
339
+ const message = `Validator error${path ? ` for ${path}` : ""}: Expected \`${expected}\`, got \`${got}\``;
340
+ super(message);
341
+ this.name = "ValidationError";
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Validate a value against a validator.
347
+ *
348
+ * WARNING: This does not validate that v.id is an ID for the given table.
349
+ * It only validates that the ID is a string. Function `args`, `returns` and
350
+ * schema definitions will validate that the ID is an ID for the given table.
351
+ *
352
+ * @param validator The validator to validate against.
353
+ * @param value The value to validate.
354
+ * @returns Whether the value is valid against the validator.
355
+ */
356
+ export function validate<T extends Validator<any, any, any>>(
357
+ validator: T,
358
+ value: unknown,
359
+ opts?: {
360
+ throw?: boolean;
361
+ pathPrefix?: string;
362
+ },
363
+ ): value is T["type"] {
364
+ let valid = true;
365
+ let expected: string = validator.kind;
366
+ if (value === undefined) {
367
+ if (validator.isOptional !== "optional") {
368
+ valid = false;
369
+ }
370
+ } else {
371
+ switch (validator.kind) {
372
+ case "null": {
373
+ if (value !== null) {
374
+ valid = false;
375
+ }
376
+ break;
377
+ }
378
+ case "float64": {
379
+ if (typeof value !== "number") {
380
+ expected = "number";
381
+ valid = false;
382
+ }
383
+ break;
384
+ }
385
+ case "int64": {
386
+ if (typeof value !== "bigint") {
387
+ expected = "bigint";
388
+ valid = false;
389
+ }
390
+ break;
391
+ }
392
+ case "boolean": {
393
+ if (typeof value !== "boolean") {
394
+ valid = false;
395
+ }
396
+ break;
397
+ }
398
+ case "string": {
399
+ if (typeof value !== "string") {
400
+ valid = false;
401
+ }
402
+ break;
403
+ }
404
+ case "bytes": {
405
+ if (!(value instanceof ArrayBuffer)) {
406
+ valid = false;
407
+ }
408
+ break;
409
+ }
410
+ case "any": {
411
+ break;
412
+ }
413
+ case "literal": {
414
+ if (value !== validator.value) {
415
+ valid = false;
416
+ }
417
+ break;
418
+ }
419
+ case "id": {
420
+ if (typeof value !== "string") {
421
+ valid = false;
422
+ }
423
+ break;
424
+ }
425
+ case "array": {
426
+ if (!Array.isArray(value)) {
427
+ valid = false;
428
+ break;
429
+ }
430
+ for (const [index, v] of value.entries()) {
431
+ const path = `${opts?.pathPrefix ?? ""}[${index}]`;
432
+ valid = validate(validator.element, v, { ...opts, pathPrefix: path });
433
+ if (!valid) {
434
+ expected = validator.element.kind;
435
+ break;
436
+ }
437
+ }
438
+ break;
439
+ }
440
+ case "object": {
441
+ if (typeof value !== "object" || value === null) {
442
+ valid = false;
443
+ break;
444
+ }
445
+ const prototype = Object.getPrototypeOf(value);
446
+ const isSimple =
447
+ prototype === null ||
448
+ prototype === Object.prototype ||
449
+ // Objects generated from other contexts (e.g. across Node.js `vm` modules) will not satisfy the previous
450
+ // conditions but are still simple objects.
451
+ prototype?.constructor?.name === "Object";
452
+
453
+ if (!isSimple) {
454
+ expected =
455
+ prototype?.constructor?.name ?? typeof prototype ?? "object";
456
+ valid = false;
457
+ break;
458
+ }
459
+ for (const [k, fieldValidator] of Object.entries(validator.fields)) {
460
+ valid = validate(fieldValidator, (value as any)[k], {
461
+ ...opts,
462
+ pathPrefix: appendPath(opts, k),
463
+ });
464
+ if (!valid) {
465
+ break;
466
+ }
467
+ }
468
+ for (const k of Object.keys(value)) {
469
+ if (validator.fields[k] === undefined) {
470
+ if (opts?.throw) {
471
+ throw new ValidationError(
472
+ "nothing",
473
+ typeof (value as any)[k],
474
+ appendPath(opts, k),
475
+ );
476
+ }
477
+ valid = false;
478
+ break;
479
+ }
480
+ }
481
+ break;
482
+ }
483
+ case "union": {
484
+ valid = false;
485
+ for (const member of validator.members) {
486
+ if (validate(member, value, opts)) {
487
+ valid = true;
488
+ break;
489
+ }
490
+ }
491
+ break;
492
+ }
493
+ case "record": {
494
+ if (typeof value !== "object" || value === null) {
495
+ valid = false;
496
+ break;
497
+ }
498
+ for (const [k, fieldValue] of Object.entries(value)) {
499
+ valid = validate(validator.key, k, {
500
+ ...opts,
501
+ pathPrefix: appendPath(opts, k),
502
+ });
503
+ if (!valid) {
504
+ expected = validator.key.kind;
505
+ break;
506
+ }
507
+ valid = validate(validator.value, fieldValue, {
508
+ ...opts,
509
+ pathPrefix: appendPath(opts, k),
510
+ });
511
+ if (!valid) {
512
+ expected = validator.value.kind;
513
+ break;
514
+ }
515
+ }
516
+ break;
517
+ }
518
+ }
519
+ }
520
+ if (!valid && opts?.throw) {
521
+ throw new ValidationError(expected, typeof value, opts?.pathPrefix);
522
+ }
523
+ return valid;
524
+ }
525
+
526
+ function appendPath(opts: { pathPrefix?: string } | undefined, path: string) {
527
+ return opts?.pathPrefix ? `${opts.pathPrefix}.${path}` : path;
528
+ }