@maroonedog/luq 2.5.0 → 2.7.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.
Files changed (47) hide show
  1. package/README.md +35 -8
  2. package/dist/chain/slot-type-guard.js +20 -6
  3. package/dist/chain/slot-type-guard.mjs +20 -6
  4. package/dist/compile/resolve-presence.js +36 -5
  5. package/dist/compile/resolve-presence.mjs +36 -5
  6. package/dist/json-schema/assert-supported-dialect.d.ts +37 -0
  7. package/dist/json-schema/assert-supported-dialect.js +113 -0
  8. package/dist/json-schema/assert-supported-dialect.mjs +109 -0
  9. package/dist/json-schema/build-from-schema.d.ts +22 -3
  10. package/dist/json-schema/build-from-schema.js +27 -5
  11. package/dist/json-schema/build-from-schema.mjs +27 -5
  12. package/dist/json-schema/declare-value-keywords.d.ts +0 -2
  13. package/dist/json-schema/declare-value-keywords.js +7 -10
  14. package/dist/json-schema/declare-value-keywords.mjs +7 -9
  15. package/dist/json-schema/extensions/json-schema/index.d.ts +2 -2
  16. package/dist/json-schema/extensions/json-schema/index.js +3 -1
  17. package/dist/json-schema/extensions/json-schema/index.mjs +1 -1
  18. package/dist/json-schema/extensions/json-schema/json-schema.d.ts +2 -2
  19. package/dist/json-schema/extensions/json-schema/json-schema.js +3 -0
  20. package/dist/json-schema/extensions/json-schema/json-schema.mjs +4 -1
  21. package/dist/json-schema/extensions/json-schema-full-feature/bundle-coverage.js +6 -1
  22. package/dist/json-schema/extensions/json-schema-full-feature/bundle-coverage.mjs +6 -1
  23. package/dist/json-schema/extensions/json-schema-full-feature/index.d.ts +3 -1
  24. package/dist/json-schema/extensions/json-schema-full-feature/index.js +18 -1
  25. package/dist/json-schema/extensions/json-schema-full-feature/index.mjs +11 -0
  26. package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.d.ts +9 -1
  27. package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.js +9 -2
  28. package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.mjs +9 -2
  29. package/dist/json-schema/index.d.ts +3 -0
  30. package/dist/json-schema/index.js +7 -1
  31. package/dist/json-schema/index.mjs +2 -0
  32. package/dist/json-schema/keyword-map-core.js +3 -1
  33. package/dist/json-schema/keyword-map-core.mjs +3 -1
  34. package/dist/json-schema/keyword-map.js +3 -0
  35. package/dist/json-schema/keyword-map.mjs +3 -0
  36. package/dist/json-schema/schema-to-declarations.js +1 -4
  37. package/dist/json-schema/schema-to-declarations.mjs +2 -5
  38. package/dist/json-schema/unsupported-dialect-error.d.ts +21 -0
  39. package/dist/json-schema/unsupported-dialect-error.js +58 -0
  40. package/dist/json-schema/unsupported-dialect-error.mjs +54 -0
  41. package/dist/runtime/create-issue.d.ts +9 -0
  42. package/dist/runtime/create-issue.js +9 -2
  43. package/dist/runtime/create-issue.mjs +9 -2
  44. package/dist/runtime/run-field.js +1 -0
  45. package/dist/runtime/run-field.mjs +1 -0
  46. package/dist/types/index.d.ts +12 -0
  47. package/package.json +20 -8
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ // ===========================================================================
3
+ // L8 src/json-schema/unsupported-dialect-error.ts
4
+ //
5
+ // The third refusal, and the only one with no keyword to hang on.
6
+ // UnsupportedKeywordError refuses a keyword NAME; MalformedSchemaError refuses
7
+ // a keyword VALUE. Both need the document to write something recognisable, and
8
+ // there is one divergence that writes nothing at all:
9
+ //
10
+ // {"$schema": "https://json-schema.org/draft/2020-12/schema",
11
+ // "$defs": {"name": {"type": "string"}},
12
+ // "properties": {"nick": {"$ref": "#/$defs/name", "minLength": 5}}}
13
+ //
14
+ // Every keyword there is a Draft-07 keyword spelled the Draft-07 way, so the
15
+ // keyword table sees nothing to refuse. The meaning is not Draft-07: §8.3 has
16
+ // `$ref` REPLACE the object it appears in, and from 2019-09 on `$ref` is an
17
+ // ordinary applicator whose siblings are applied. Read as Draft-07 the document
18
+ // above builds a validator that accepts `{"nick":"ab"}` — the `minLength` is
19
+ // gone, silently, and nothing in the result says a constraint went missing.
20
+ // `$schema` is the only signal the document offers, so it is the one read.
21
+ //
22
+ // It stays a separate class from the other two, neither extending the other,
23
+ // for the reason written in malformed-schema-error.ts: each answers a different
24
+ // question. "Luq cannot honour this keyword", "this document is not valid
25
+ // Draft-07", and now "this document is not Draft-07 AT ALL" are three distinct
26
+ // facts, and a caller catching one must not silently catch another.
27
+ // ===========================================================================
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.UnsupportedDialectError = exports.DRAFT07_DIALECT_URI = void 0;
30
+ /** The dialect this library implements, in the spelling the draft publishes. */
31
+ exports.DRAFT07_DIALECT_URI = "http://json-schema.org/draft-07/schema#";
32
+ /**
33
+ * Raised when a document's root `$schema` names a dialect Luq does not
34
+ * implement. Thrown at build time, before a validator that would read the
35
+ * document under the wrong dialect's rules can exist.
36
+ *
37
+ * `reason` follows the convention the other two refusals use: a sentence
38
+ * fragment with no terminal punctuation, saying what the dialect does that
39
+ * Draft-07 does not, so that two refusals from two different dialects read as
40
+ * one library rather than as two unrelated messages.
41
+ */
42
+ class UnsupportedDialectError extends Error {
43
+ constructor(declared, reason) {
44
+ super(`JSON Schema dialect "${declared}" is not supported: ${reason}. Luq ` +
45
+ `implements Draft-07 (${exports.DRAFT07_DIALECT_URI}) and would read this ` +
46
+ "document under Draft-07 rules, which can enforce less than it " +
47
+ "states. Convert the document to Draft-07, or pass " +
48
+ "`{ assumeDraft07: true }` to take the Draft-07 reading deliberately.");
49
+ /** The dialect Luq reads every document under. */
50
+ this.implemented = exports.DRAFT07_DIALECT_URI;
51
+ this.name = "UnsupportedDialectError";
52
+ this.declared = declared;
53
+ this.reason = reason;
54
+ // Without this, `instanceof` fails when the package is compiled to ES5.
55
+ Object.setPrototypeOf(this, UnsupportedDialectError.prototype);
56
+ }
57
+ }
58
+ exports.UnsupportedDialectError = UnsupportedDialectError;
@@ -0,0 +1,54 @@
1
+ // ===========================================================================
2
+ // L8 src/json-schema/unsupported-dialect-error.ts
3
+ //
4
+ // The third refusal, and the only one with no keyword to hang on.
5
+ // UnsupportedKeywordError refuses a keyword NAME; MalformedSchemaError refuses
6
+ // a keyword VALUE. Both need the document to write something recognisable, and
7
+ // there is one divergence that writes nothing at all:
8
+ //
9
+ // {"$schema": "https://json-schema.org/draft/2020-12/schema",
10
+ // "$defs": {"name": {"type": "string"}},
11
+ // "properties": {"nick": {"$ref": "#/$defs/name", "minLength": 5}}}
12
+ //
13
+ // Every keyword there is a Draft-07 keyword spelled the Draft-07 way, so the
14
+ // keyword table sees nothing to refuse. The meaning is not Draft-07: §8.3 has
15
+ // `$ref` REPLACE the object it appears in, and from 2019-09 on `$ref` is an
16
+ // ordinary applicator whose siblings are applied. Read as Draft-07 the document
17
+ // above builds a validator that accepts `{"nick":"ab"}` — the `minLength` is
18
+ // gone, silently, and nothing in the result says a constraint went missing.
19
+ // `$schema` is the only signal the document offers, so it is the one read.
20
+ //
21
+ // It stays a separate class from the other two, neither extending the other,
22
+ // for the reason written in malformed-schema-error.ts: each answers a different
23
+ // question. "Luq cannot honour this keyword", "this document is not valid
24
+ // Draft-07", and now "this document is not Draft-07 AT ALL" are three distinct
25
+ // facts, and a caller catching one must not silently catch another.
26
+ // ===========================================================================
27
+ /** The dialect this library implements, in the spelling the draft publishes. */
28
+ export const DRAFT07_DIALECT_URI = "http://json-schema.org/draft-07/schema#";
29
+ /**
30
+ * Raised when a document's root `$schema` names a dialect Luq does not
31
+ * implement. Thrown at build time, before a validator that would read the
32
+ * document under the wrong dialect's rules can exist.
33
+ *
34
+ * `reason` follows the convention the other two refusals use: a sentence
35
+ * fragment with no terminal punctuation, saying what the dialect does that
36
+ * Draft-07 does not, so that two refusals from two different dialects read as
37
+ * one library rather than as two unrelated messages.
38
+ */
39
+ export class UnsupportedDialectError extends Error {
40
+ constructor(declared, reason) {
41
+ super(`JSON Schema dialect "${declared}" is not supported: ${reason}. Luq ` +
42
+ `implements Draft-07 (${DRAFT07_DIALECT_URI}) and would read this ` +
43
+ "document under Draft-07 rules, which can enforce less than it " +
44
+ "states. Convert the document to Draft-07, or pass " +
45
+ "`{ assumeDraft07: true }` to take the Draft-07 reading deliberately.");
46
+ /** The dialect Luq reads every document under. */
47
+ this.implemented = DRAFT07_DIALECT_URI;
48
+ this.name = "UnsupportedDialectError";
49
+ this.declared = declared;
50
+ this.reason = reason;
51
+ // Without this, `instanceof` fails when the package is compiled to ES5.
52
+ Object.setPrototypeOf(this, UnsupportedDialectError.prototype);
53
+ }
54
+ }
@@ -13,6 +13,15 @@ export interface IssueRequest {
13
13
  * sees one call shape and needs no rule kind to dispatch on.
14
14
  */
15
15
  render(context: MessageContext): string;
16
+ /**
17
+ * The branch failures behind a composite, when the rule that failed is one.
18
+ *
19
+ * Passed rather than read off the detail because this layer never sees a
20
+ * rule kind: a presence policy closes over nothing and a check closes over
21
+ * its own IssueDetail, and keeping that asymmetry out of here is what lets
22
+ * one call shape serve both.
23
+ */
24
+ readonly causes?: readonly ValidationIssue[] | undefined;
16
25
  }
17
26
  /**
18
27
  * A plugin whose message factory throws must not take the validation down
@@ -17,12 +17,19 @@ function createIssue(request) {
17
17
  value: request.value,
18
18
  code: request.code,
19
19
  };
20
- return Object.freeze({
20
+ const issue = {
21
21
  path: request.path,
22
22
  code: request.code,
23
23
  message: renderMessageOrFallback(request, context),
24
24
  severity: request.severity,
25
- });
25
+ };
26
+ // Spread CONDITIONALLY. Writing `causes: request.causes` unconditionally
27
+ // would put the key on every issue the library reports, with the value
28
+ // undefined — and `"causes" in issue` would then answer true for a plain
29
+ // stringMin failure, which is exactly the question a caller asks it.
30
+ return Object.freeze(request.causes === undefined || request.causes.length === 0
31
+ ? issue
32
+ : { ...issue, causes: request.causes });
26
33
  }
27
34
  function renderMessageOrFallback(request, context) {
28
35
  try {
@@ -13,12 +13,19 @@ export function createIssue(request) {
13
13
  value: request.value,
14
14
  code: request.code,
15
15
  };
16
- return Object.freeze({
16
+ const issue = {
17
17
  path: request.path,
18
18
  code: request.code,
19
19
  message: renderMessageOrFallback(request, context),
20
20
  severity: request.severity,
21
- });
21
+ };
22
+ // Spread CONDITIONALLY. Writing `causes: request.causes` unconditionally
23
+ // would put the key on every issue the library reports, with the value
24
+ // undefined — and `"causes" in issue` would then answer true for a plain
25
+ // stringMin failure, which is exactly the question a caller asks it.
26
+ return Object.freeze(request.causes === undefined || request.causes.length === 0
27
+ ? issue
28
+ : { ...issue, causes: request.causes });
22
29
  }
23
30
  function renderMessageOrFallback(request, context) {
24
31
  try {
@@ -78,6 +78,7 @@ function reportCheckFailure(check, detail, value, ruleContext, context) {
78
78
  severity: check.severity,
79
79
  value,
80
80
  render: (ctx) => check.describe(detail, ctx),
81
+ causes: detail.causes,
81
82
  }));
82
83
  }
83
84
  function runChecks(field, value, ruleContext, context, mark) {
@@ -74,6 +74,7 @@ function reportCheckFailure(check, detail, value, ruleContext, context) {
74
74
  severity: check.severity,
75
75
  value,
76
76
  render: (ctx) => check.describe(detail, ctx),
77
+ causes: detail.causes,
77
78
  }));
78
79
  }
79
80
  function runChecks(field, value, ruleContext, context, mark) {
@@ -26,6 +26,18 @@ export interface ValidationIssue {
26
26
  /** Never optional: the chain resolves it once at build time, so a reader
27
27
  * never has to re-implement the "absent means error" fallback. */
28
28
  readonly severity: IssueSeverity;
29
+ /**
30
+ * Why a composite failed, when the failing rule is one.
31
+ *
32
+ * `allOf`, `anyOf` and `oneOf` report their own code, which says WHICH
33
+ * applicator failed and never why. The branch failures behind it land here,
34
+ * so a caller can reach "minLength" without re-running the sub-schemas.
35
+ *
36
+ * ABSENT, not undefined, on an ordinary issue. A key present everywhere
37
+ * would make `"causes" in issue` answer true for every issue the library
38
+ * reports, which is the opposite of what it is for.
39
+ */
40
+ readonly causes?: readonly ValidationIssue[];
29
41
  }
30
42
  /** branch / index / causes let a composite failure explain itself. */
31
43
  export interface IssueDetail {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maroonedog/luq",
3
- "version": "2.5.0",
4
- "description": "Universal Model & API Definition Platform - TypeScript validation library evolving into cross-language code generation",
3
+ "version": "2.7.0",
4
+ "description": "Validate the TypeScript types you already have. Rules are declared against your own type's field paths, so a rule that does not apply to its field is a compile error.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
@@ -459,18 +459,20 @@
459
459
  "typecheck:src": "tsc --noEmit -p tsconfig.json",
460
460
  "typecheck:scripts": "tsc --noEmit -p scripts/tsconfig.json",
461
461
  "typecheck:bench": "tsc --noEmit -p bench/tsconfig.json",
462
- "generate": "npm run generate:sources && npm run generate:exports && npm run generate:lock",
462
+ "generate": "npm run generate:sources && npm run generate:exports && npm run generate:lock && npm run generate:issue-codes",
463
463
  "generate:sources": "npm run generate:manifest && npm run generate:barrel && npm run generate:slot-catalog",
464
464
  "generate:slot-catalog": "npx ts-node --project scripts/tsconfig.json scripts/generate-slot-catalog.ts",
465
465
  "generate:manifest": "npx ts-node --project scripts/tsconfig.json scripts/generate-plugin-manifest.ts",
466
466
  "generate:barrel": "npx ts-node --project scripts/tsconfig.json scripts/generate-plugin-barrel.ts",
467
467
  "generate:exports": "npx ts-node --project scripts/tsconfig.json scripts/generate-package-exports.ts",
468
468
  "generate:lock": "npx ts-node --project scripts/tsconfig.json scripts/generate-plugin-catalog.ts",
469
+ "generate:issue-codes": "npx ts-node --project scripts/tsconfig.json scripts/generate-issue-code-catalog.ts",
469
470
  "check:exports": "npx ts-node --project scripts/tsconfig.json scripts/check-exports.ts --mode=exact",
470
471
  "check:catalog-lock": "npx ts-node --project scripts/tsconfig.json scripts/check-catalog-lock.ts",
472
+ "check:issue-code-lock": "npx ts-node --project scripts/tsconfig.json scripts/check-issue-code-lock.ts",
471
473
  "check:plugin-isolation": "npx ts-node --project scripts/tsconfig.json scripts/check-plugin-isolation.ts",
472
474
  "check:plugin-uniqueness": "npx ts-node --project scripts/tsconfig.json scripts/check-plugin-uniqueness.ts",
473
- "check:catalog": "npm run check:plugin-uniqueness && npm run check:plugin-isolation && npm run check:exports && npm run check:catalog-lock",
475
+ "check:catalog": "npm run check:plugin-uniqueness && npm run check:plugin-isolation && npm run check:exports && npm run check:catalog-lock && npm run check:issue-code-lock",
474
476
  "check:size": "npx ts-node --project scripts/tsconfig.json scripts/measure-bundle-size.ts",
475
477
  "check:barrel-equivalence": "npx ts-node --project scripts/tsconfig.json scripts/check-barrel-equivalence.ts",
476
478
  "check:no-dynamic-code": "npx ts-node --project scripts/tsconfig.json scripts/check-no-dynamic-code.ts",
@@ -493,6 +495,7 @@
493
495
  "check:conformance-figures": "npx ts-node --project scripts/tsconfig.json scripts/check-conformance-figures.ts",
494
496
  "bench:competitors": "npx ts-node --project bench/tsconfig.json bench/competitors/report-competitors.ts",
495
497
  "bench:competitors:check": "npx ts-node --project bench/tsconfig.json bench/competitors/check-competitors.ts",
498
+ "bench:megamorphism": "npx ts-node --project bench/tsconfig.json bench/megamorphism/report-megamorphism.ts",
496
499
  "lint:ox": "oxlint -c .oxlintrc.json src bench",
497
500
  "lint:naming": "eslint \"src/**/*.ts\" \"bench/**/*.ts\"",
498
501
  "generate:perf-figures": "npx ts-node --project scripts/tsconfig.json scripts/check-perf-figures.ts --write",
@@ -507,13 +510,22 @@
507
510
  "url": "https://github.com/maroonedog/luq/issues"
508
511
  },
509
512
  "keywords": [
510
- "form",
511
- "validation",
512
513
  "typescript",
514
+ "validation",
513
515
  "validator",
514
- "library"
516
+ "schema",
517
+ "json-schema",
518
+ "draft-07",
519
+ "openapi",
520
+ "standard-schema",
521
+ "standardschema",
522
+ "type-safe",
523
+ "form-validation",
524
+ "runtime-validation",
525
+ "content-security-policy",
526
+ "zero-dependencies"
515
527
  ],
516
- "author": "",
528
+ "author": "maroonedog (https://github.com/maroonedog)",
517
529
  "license": "MIT",
518
530
  "files": [
519
531
  "dist"