regor 1.4.8 → 1.5.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/README.md CHANGED
@@ -152,11 +152,13 @@ pval.isBoolean
152
152
  pval.isClass(MyClass)
153
153
  pval.optional(pval.isString)
154
154
  pval.nullable(pval.isNumber)
155
+ pval.or(pval.isString, pval.isNumber)
155
156
  pval.oneOf(['create', 'edit'] as const)
156
157
  pval.arrayOf(pval.isString)
157
158
  pval.shape({ title: pval.isString, count: pval.isNumber })
158
159
  pval.refOf(pval.isString)
159
- pval.fail('title', 'expected non-empty string')
160
+ pval.describe('value')
161
+ pval.fail('title', 'expected non-empty string, got string ("")')
160
162
  ```
161
163
 
162
164
  ### Dynamic bindings and refs
@@ -204,7 +206,7 @@ import { pval, type PropValidator } from 'regor'
204
206
 
205
207
  const isNonEmptyString: PropValidator<string> = (value, name) => {
206
208
  if (typeof value !== 'string' || value.trim() === '') {
207
- pval.fail(name, 'expected non-empty string')
209
+ pval.fail(name, `expected non-empty string, ${pval.describe(value)}`)
208
210
  }
209
211
  }
210
212
 
@@ -219,7 +221,7 @@ Custom validators can also use the third `head` argument:
219
221
  const startsWithPrefix: PropValidator<string> = (value, name, head) => {
220
222
  const ctx = head.requireContext(AppServices)
221
223
  if (typeof value !== 'string' || !value.startsWith(ctx.prefix)) {
222
- pval.fail(name, 'expected prefixed value')
224
+ pval.fail(name, `expected prefixed value, ${pval.describe(value)}`)
223
225
  }
224
226
  }
225
227
  ```
package/dist/regor.d.ts CHANGED
@@ -13,6 +13,12 @@
13
13
  */
14
14
  export type PropValidator<TValue = unknown> = (value: unknown, name: string, head: ComponentHead<any>) => asserts value is TValue;
15
15
  export type ValidationSchemaLike = Record<string, PropValidator<any>>;
16
+ export type ValidatorTuple = readonly [
17
+ PropValidator<any>,
18
+ PropValidator<any>,
19
+ ...PropValidator<any>[]
20
+ ];
21
+ export type InferValidatorValue<TValidator> = TValidator extends PropValidator<infer TValue> ? TValue : never;
16
22
  /**
17
23
  * Validation schema shape suggested by `ComponentHead<T>.props`.
18
24
  *
@@ -34,8 +40,8 @@ export type InferPropValidationSchema<TSchema extends Record<string, unknown>> =
34
40
  * Built-in prop-validator namespace used with `head.validateProps(...)`.
35
41
  *
36
42
  * This namespace includes both ready-made validators and composition helpers.
37
- * Custom validators can also use `pval.fail(...)` to produce the same
38
- * structured failure shape as Regor's built-in validators.
43
+ * Custom validators can also use `pval.fail(...)` and `pval.describe(...)`
44
+ * to produce messages in the same style as Regor's built-in validators.
39
45
  *
40
46
  * Example:
41
47
  * ```ts
@@ -50,12 +56,14 @@ export type InferPropValidationSchema<TSchema extends Record<string, unknown>> =
50
56
  */
51
57
  export declare const pval: {
52
58
  readonly fail: (name: string, message: string) => never;
59
+ readonly describe: (value: unknown) => string;
53
60
  readonly isString: PropValidator<string>;
54
61
  readonly isNumber: PropValidator<number>;
55
62
  readonly isBoolean: PropValidator<boolean>;
56
63
  readonly isClass: <TValue extends object>(ctor: abstract new (...args: any[]) => TValue) => PropValidator<TValue>;
57
64
  readonly optional: <TValue>(validator: PropValidator<TValue>) => PropValidator<TValue | undefined>;
58
65
  readonly nullable: <TValue>(validator: PropValidator<TValue>) => PropValidator<TValue | null>;
66
+ readonly or: <TValidators extends ValidatorTuple>(...validators: TValidators) => PropValidator<InferValidatorValue<TValidators[number]>>;
59
67
  readonly oneOf: <const TValue extends readonly unknown[]>(values: TValue) => PropValidator<TValue[number]>;
60
68
  readonly arrayOf: <TValue>(validator: PropValidator<TValue>) => PropValidator<TValue[]>;
61
69
  readonly shape: <TSchema extends ValidationSchemaLike>(schema: TSchema) => PropValidator<InferPropValidationSchema<TSchema>>;
@@ -301,6 +301,9 @@ var describeValue = (value) => {
301
301
  var _a;
302
302
  if (value === null) return "null";
303
303
  if (value === void 0) return "undefined";
304
+ if (isRef(value)) {
305
+ return `ref<${describeValue(value())}>`;
306
+ }
304
307
  if (typeof value === "string") return "string";
305
308
  if (typeof value === "number") return "number";
306
309
  if (typeof value === "boolean") return "boolean";
@@ -315,6 +318,64 @@ var describeValue = (value) => {
315
318
  const ctorName = (_a = value == null ? void 0 : value.constructor) == null ? void 0 : _a.name;
316
319
  return ctorName && ctorName !== "Object" ? ctorName : "object";
317
320
  };
321
+ var trimPreview = (value) => {
322
+ return value.length > 60 ? `${value.slice(0, 57)}...` : value;
323
+ };
324
+ var formatPreview = (value, depth = 0) => {
325
+ const maxPreviewDepth = 1;
326
+ const maxArrayItems = 5;
327
+ const maxObjectEntries = 5;
328
+ if (depth > maxPreviewDepth) return "unknown";
329
+ if (isRef(value)) {
330
+ const refValue = value();
331
+ return `ref(${formatPreview(refValue, depth + 1)})`;
332
+ }
333
+ if (typeof value === "string") return trimPreview(JSON.stringify(value));
334
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
335
+ return String(value);
336
+ }
337
+ if (typeof value === "symbol") return String(value);
338
+ if (value === null) return "null";
339
+ if (value === void 0) return "undefined";
340
+ if (value instanceof Date) return value.toISOString();
341
+ if (value instanceof RegExp) return String(value);
342
+ if (isArray(value)) {
343
+ const items = value.slice(0, maxArrayItems).map((x) => formatPreview(x, depth + 1)).join(", ");
344
+ return value.length > maxArrayItems ? `[${items}, ...]` : `[${items}]`;
345
+ }
346
+ if (isObject(value)) {
347
+ const entries = Object.entries(value).slice(0, maxObjectEntries);
348
+ if (entries.length === 0) return "{}";
349
+ const text = entries.map(([key, entry]) => {
350
+ const preview = formatPreview(entry, depth + 1);
351
+ return `${key}: ${preview}`;
352
+ }).join(", ");
353
+ return Object.keys(value).length > maxObjectEntries ? `{ ${text}, ... }` : `{ ${text} }`;
354
+ }
355
+ return "unknown";
356
+ };
357
+ var describe = (value) => {
358
+ const type = describeValue(value);
359
+ const preview = formatPreview(value);
360
+ return `got ${type} (${preview})`;
361
+ };
362
+ var getErrorDetail = (error) => {
363
+ if (error instanceof PropValidationError) return error.detail;
364
+ if (error instanceof Error) return error.message;
365
+ return String(error);
366
+ };
367
+ var formatOrDetail = (errors2, value) => {
368
+ const received = `, ${describe(value)}.`;
369
+ const reasons = [];
370
+ for (const error of errors2) {
371
+ const detail = getErrorDetail(error);
372
+ const reason = detail.endsWith(received) ? detail.slice(0, -received.length) : detail;
373
+ if (!reasons.includes(reason)) reasons.push(reason);
374
+ }
375
+ if (reasons.length === 0) return describe(value);
376
+ if (reasons.length === 1) return `${reasons[0]}, ${describe(value)}`;
377
+ return `${reasons.join(" or ")}, ${describe(value)}`;
378
+ };
318
379
  var formatLiteral = (value) => {
319
380
  if (typeof value === "string") return `"${value}"`;
320
381
  if (typeof value === "number" || typeof value === "boolean") {
@@ -325,18 +386,24 @@ var formatLiteral = (value) => {
325
386
  return describeValue(value);
326
387
  };
327
388
  var isString2 = (value, name) => {
328
- if (typeof value !== "string") fail(name, "expected string");
389
+ if (typeof value !== "string")
390
+ fail(name, `expected string, ${describe(value)}`);
329
391
  };
330
392
  var isNumber = (value, name) => {
331
- if (typeof value !== "number") fail(name, "expected number");
393
+ if (typeof value !== "number")
394
+ fail(name, `expected number, ${describe(value)}`);
332
395
  };
333
396
  var isBoolean = (value, name) => {
334
- if (typeof value !== "boolean") fail(name, "expected boolean");
397
+ if (typeof value !== "boolean")
398
+ fail(name, `expected boolean, ${describe(value)}`);
335
399
  };
336
400
  var isClass = (ctor) => {
337
401
  return (value, name) => {
338
402
  if (!(value instanceof ctor)) {
339
- fail(name, `expected instance of ${ctor.name || "provided class"}`);
403
+ fail(
404
+ name,
405
+ `expected instance of ${ctor.name || "provided class"}, ${describe(value)}`
406
+ );
340
407
  }
341
408
  };
342
409
  };
@@ -352,18 +419,32 @@ var nullable = (validator) => {
352
419
  validator(value, name, head);
353
420
  };
354
421
  };
422
+ var or = (...validators) => {
423
+ return (value, name, head) => {
424
+ const errors2 = [];
425
+ for (const validator of validators) {
426
+ try {
427
+ validator(value, name, head);
428
+ return;
429
+ } catch (error) {
430
+ errors2.push(error);
431
+ }
432
+ }
433
+ fail(name, formatOrDetail(errors2, value));
434
+ };
435
+ };
355
436
  var oneOf = (values) => {
356
437
  return (value, name) => {
357
438
  if (values.includes(value)) return;
358
439
  fail(
359
440
  name,
360
- `expected one of ${values.map((x) => formatLiteral(x)).join(", ")}`
441
+ `expected one of ${values.map((x) => formatLiteral(x)).join(", ")}, ${describe(value)}`
361
442
  );
362
443
  };
363
444
  };
364
445
  var arrayOf = (validator) => {
365
446
  return (value, name, head) => {
366
- if (!isArray(value)) fail(name, "expected array");
447
+ if (!isArray(value)) fail(name, `expected array, ${describe(value)}`);
367
448
  const items = value;
368
449
  for (let i = 0; i < items.length; ++i) {
369
450
  validator(items[i], `${name}[${i}]`, head);
@@ -372,7 +453,7 @@ var arrayOf = (validator) => {
372
453
  };
373
454
  var shape = (schema) => {
374
455
  return (value, name, head) => {
375
- if (!isObject(value)) fail(name, "expected object");
456
+ if (!isObject(value)) fail(name, `expected object, ${describe(value)}`);
376
457
  const record = value;
377
458
  for (const key in schema) {
378
459
  const validator = schema[key];
@@ -382,19 +463,23 @@ var shape = (schema) => {
382
463
  };
383
464
  var refOf = (validator) => {
384
465
  return (value, name, head) => {
385
- if (!isRef(value)) fail(name, "expected ref");
386
- const refValue = value;
387
- validator(refValue(), `${name}.value`, head);
466
+ if (isRef(value)) {
467
+ validator(value(), `${name}.value`, head);
468
+ return;
469
+ }
470
+ fail(name, `expected ref, ${describe(value)}`);
388
471
  };
389
472
  };
390
473
  var pval = {
391
474
  fail,
475
+ describe,
392
476
  isString: isString2,
393
477
  isNumber,
394
478
  isBoolean,
395
479
  isClass,
396
480
  optional,
397
481
  nullable,
482
+ or,
398
483
  oneOf,
399
484
  arrayOf,
400
485
  shape,