@maroonedog/luq 2.1.0 → 2.3.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 (161) hide show
  1. package/README.md +92 -430
  2. package/dist/builder/builder-surface.types.d.ts +1 -0
  3. package/dist/builder/compile-declarations.d.ts +7 -1
  4. package/dist/builder/compile-declarations.js +18 -8
  5. package/dist/builder/compile-declarations.mjs +18 -8
  6. package/dist/builder/create-builder.js +8 -0
  7. package/dist/builder/create-builder.mjs +8 -0
  8. package/dist/builder/create-field-builder.js +13 -1
  9. package/dist/builder/create-field-builder.mjs +13 -1
  10. package/dist/builder/declared-calls-store.d.ts +9 -0
  11. package/dist/builder/declared-calls-store.js +19 -0
  12. package/dist/builder/declared-calls-store.mjs +15 -0
  13. package/dist/builder/field-builder.types.d.ts +13 -0
  14. package/dist/builder/field-declared-calls.types.d.ts +6 -0
  15. package/dist/builder/field-declared-calls.types.js +2 -0
  16. package/dist/builder/field-declared-calls.types.mjs +1 -0
  17. package/dist/builder/field-entry.types.d.ts +9 -3
  18. package/dist/builder/field-options.types.d.ts +26 -0
  19. package/dist/chain/bundle-paths.types.d.ts +23 -0
  20. package/dist/chain/bundle-paths.types.js +2 -0
  21. package/dist/chain/bundle-paths.types.mjs +1 -0
  22. package/dist/chain/chain-method.types.d.ts +8 -3
  23. package/dist/chain/chain-node-store.d.ts +5 -0
  24. package/dist/chain/chain-node-store.js +15 -0
  25. package/dist/chain/chain-node-store.mjs +11 -0
  26. package/dist/chain/collect-field-rules.d.ts +13 -2
  27. package/dist/chain/collect-field-rules.js +10 -3
  28. package/dist/chain/collect-field-rules.mjs +10 -3
  29. package/dist/chain/create-chain-node.js +19 -9
  30. package/dist/chain/create-chain-node.mjs +19 -9
  31. package/dist/chain/declaration-recorder.port.d.ts +31 -0
  32. package/dist/chain/declaration-recorder.port.js +17 -0
  33. package/dist/chain/declaration-recorder.port.mjs +13 -0
  34. package/dist/chain/declared-call.types.d.ts +15 -0
  35. package/dist/chain/declared-call.types.js +2 -0
  36. package/dist/chain/declared-call.types.mjs +1 -0
  37. package/dist/chain/index.d.ts +3 -1
  38. package/dist/chain/resolve-args.types.d.ts +4 -2
  39. package/dist/compile/compile-array-node.d.ts +0 -7
  40. package/dist/compile/compile-array-node.js +5 -0
  41. package/dist/compile/compile-array-node.mjs +5 -0
  42. package/dist/compile/compile-field.d.ts +1 -0
  43. package/dist/compile/compile-field.js +13 -2
  44. package/dist/compile/compile-field.mjs +13 -2
  45. package/dist/compile/compile-schema.js +4 -0
  46. package/dist/compile/compile-schema.mjs +4 -0
  47. package/dist/compile/group-array-fields.d.ts +1 -0
  48. package/dist/compile/split-rules-by-kind.js +27 -7
  49. package/dist/compile/split-rules-by-kind.mjs +27 -7
  50. package/dist/compile/validation-plan.types.d.ts +36 -0
  51. package/dist/core/type-erasure.d.ts +36 -30
  52. package/dist/core/type-erasure.js +36 -30
  53. package/dist/core/type-erasure.mjs +36 -30
  54. package/dist/json-schema/build-from-schema.js +8 -1
  55. package/dist/json-schema/build-from-schema.mjs +8 -1
  56. package/dist/json-schema/declare-additional-properties.d.ts +7 -7
  57. package/dist/json-schema/declare-additional-properties.js +7 -7
  58. package/dist/json-schema/declare-additional-properties.mjs +7 -7
  59. package/dist/json-schema/declare-object-keywords.js +4 -4
  60. package/dist/json-schema/declare-object-keywords.mjs +4 -4
  61. package/dist/json-schema/flatten-schema.js +1 -1
  62. package/dist/json-schema/flatten-schema.mjs +1 -1
  63. package/dist/json-schema/follow-json-pointer.d.ts +7 -6
  64. package/dist/json-schema/follow-json-pointer.js +24 -24
  65. package/dist/json-schema/follow-json-pointer.mjs +24 -24
  66. package/dist/json-schema/ref-resolution-error.js +3 -3
  67. package/dist/json-schema/ref-resolution-error.mjs +3 -3
  68. package/dist/json-schema/schema-registry.js +12 -11
  69. package/dist/json-schema/schema-registry.mjs +12 -11
  70. package/dist/json-schema/uri-reference.js +12 -12
  71. package/dist/json-schema/uri-reference.mjs +12 -12
  72. package/dist/path/create-value-writer.js +12 -12
  73. package/dist/path/create-value-writer.mjs +12 -12
  74. package/dist/path/reserved-segment.d.ts +16 -16
  75. package/dist/path/reserved-segment.js +17 -21
  76. package/dist/path/reserved-segment.mjs +17 -21
  77. package/dist/plugin-kit/marker.types.d.ts +18 -0
  78. package/dist/plugins/index.generated.d.ts +1 -0
  79. package/dist/plugins/index.generated.js +6 -4
  80. package/dist/plugins/index.generated.mjs +3 -2
  81. package/dist/plugins/manifest.generated.js +3 -2
  82. package/dist/plugins/manifest.generated.mjs +3 -2
  83. package/dist/plugins/object-additional-properties/select-additional-keys.d.ts +8 -8
  84. package/dist/plugins/object-additional-properties/select-additional-keys.js +16 -16
  85. package/dist/plugins/object-additional-properties/select-additional-keys.mjs +16 -16
  86. package/dist/plugins/stitch/stitch.d.ts +24 -8
  87. package/dist/plugins/stitch-with/index.d.ts +2 -0
  88. package/dist/plugins/stitch-with/index.js +5 -0
  89. package/dist/plugins/stitch-with/index.mjs +1 -0
  90. package/dist/plugins/stitch-with/stitch-with.d.ts +12 -0
  91. package/dist/plugins/stitch-with/stitch-with.js +92 -0
  92. package/dist/plugins/stitch-with/stitch-with.mjs +89 -0
  93. package/dist/plugins/stitchWith.d.ts +1 -0
  94. package/dist/plugins/stitchWith.js +2 -0
  95. package/dist/plugins/stitchWith.mjs +1 -0
  96. package/dist/plugins/string-min/string-min.js +19 -3
  97. package/dist/plugins/string-min/string-min.mjs +20 -4
  98. package/dist/presets/index.d.ts +1 -0
  99. package/dist/presets/index.js +9 -0
  100. package/dist/presets/index.mjs +1 -0
  101. package/dist/presets/presets.d.ts +164 -0
  102. package/dist/presets/presets.js +79 -0
  103. package/dist/presets/presets.mjs +76 -0
  104. package/dist/runtime/create-field-validator.js +22 -8
  105. package/dist/runtime/create-field-validator.mjs +22 -8
  106. package/dist/runtime/create-validator.js +38 -8
  107. package/dist/runtime/create-validator.mjs +38 -8
  108. package/dist/runtime/field-rule-context.d.ts +32 -0
  109. package/dist/runtime/field-rule-context.js +48 -0
  110. package/dist/runtime/field-rule-context.mjs +44 -0
  111. package/dist/runtime/index-stack.d.ts +29 -4
  112. package/dist/runtime/index-stack.js +76 -15
  113. package/dist/runtime/index-stack.mjs +76 -15
  114. package/dist/runtime/output-writer.js +5 -1
  115. package/dist/runtime/output-writer.mjs +5 -1
  116. package/dist/runtime/run-array-node.js +22 -10
  117. package/dist/runtime/run-array-node.mjs +22 -10
  118. package/dist/runtime/run-field.js +47 -18
  119. package/dist/runtime/run-field.mjs +47 -18
  120. package/dist/runtime/run-plan.js +5 -1
  121. package/dist/runtime/run-plan.mjs +5 -1
  122. package/dist/standard-schema/assemble-json-schema.d.ts +4 -0
  123. package/dist/standard-schema/assemble-json-schema.js +95 -0
  124. package/dist/standard-schema/assemble-json-schema.mjs +92 -0
  125. package/dist/standard-schema/declaration-recorder.d.ts +6 -0
  126. package/dist/standard-schema/declaration-recorder.js +30 -0
  127. package/dist/standard-schema/declaration-recorder.mjs +27 -0
  128. package/dist/standard-schema/declarations-unavailable-error.d.ts +4 -0
  129. package/dist/standard-schema/declarations-unavailable-error.js +32 -0
  130. package/dist/standard-schema/declarations-unavailable-error.mjs +28 -0
  131. package/dist/standard-schema/emit-field-schema.d.ts +9 -0
  132. package/dist/standard-schema/emit-field-schema.js +68 -0
  133. package/dist/standard-schema/emit-field-schema.mjs +65 -0
  134. package/dist/standard-schema/index.d.ts +5 -0
  135. package/dist/standard-schema/index.js +9 -1
  136. package/dist/standard-schema/index.mjs +4 -0
  137. package/dist/standard-schema/json-schema-target.d.ts +6 -0
  138. package/dist/standard-schema/json-schema-target.js +44 -0
  139. package/dist/standard-schema/json-schema-target.mjs +39 -0
  140. package/dist/standard-schema/plugin-keyword-map.d.ts +3 -0
  141. package/dist/standard-schema/plugin-keyword-map.js +93 -0
  142. package/dist/standard-schema/plugin-keyword-map.mjs +90 -0
  143. package/dist/standard-schema/split-issue-path.d.ts +6 -4
  144. package/dist/standard-schema/split-issue-path.js +15 -13
  145. package/dist/standard-schema/split-issue-path.mjs +15 -13
  146. package/dist/standard-schema/standard-schema.types.d.ts +8 -7
  147. package/dist/standard-schema/standard-schema.types.js +6 -6
  148. package/dist/standard-schema/standard-schema.types.mjs +6 -6
  149. package/dist/standard-schema/to-standard-json-schema.d.ts +19 -0
  150. package/dist/standard-schema/to-standard-json-schema.js +36 -0
  151. package/dist/standard-schema/to-standard-json-schema.mjs +33 -0
  152. package/dist/standard-schema/to-standard-schema.d.ts +16 -15
  153. package/dist/standard-schema/to-standard-schema.js +15 -22
  154. package/dist/standard-schema/to-standard-schema.mjs +15 -22
  155. package/dist/standard-schema/unrepresentable-rule-error.d.ts +15 -0
  156. package/dist/standard-schema/unrepresentable-rule-error.js +43 -0
  157. package/dist/standard-schema/unrepresentable-rule-error.mjs +38 -0
  158. package/dist/types/index.d.ts +19 -7
  159. package/dist/types/index.js +7 -7
  160. package/dist/types/index.mjs +7 -7
  161. package/package.json +35 -19
@@ -0,0 +1,28 @@
1
+ // ===========================================================================
2
+ // L10 src/standard-schema/declarations-unavailable-error.ts
3
+ //
4
+ // "Nothing was declared" and "what was declared is not known" are different
5
+ // answers, and only one of them can be written out.
6
+ //
7
+ // Collapsing them into an empty list produces a schema that permits everything
8
+ // and looks entirely confident about it. This refusal exists to stop that.
9
+ //
10
+ // It outranks the unrepresentable policy: `omit` is permission to drop a
11
+ // declaration that cannot be written, not permission to write without knowing
12
+ // what was declared.
13
+ // ===========================================================================
14
+ export class DeclarationsUnavailableError extends Error {
15
+ constructor(fieldPath) {
16
+ super((fieldPath === undefined
17
+ ? "This validator carries no declarations"
18
+ : `"${fieldPath}" carries no declarations`) +
19
+ ", so no JSON Schema can be emitted from it. Two things cause " +
20
+ "this. A validator from fromJsonSchema() was assembled from rules " +
21
+ "directly and never went through the builder chain. Otherwise the " +
22
+ 'build() ran before "@maroonedog/luq/standard-schema" was loaded — ' +
23
+ "import it from the module that builds the validator, or from one " +
24
+ "evaluated before it.");
25
+ this.fieldPath = fieldPath;
26
+ this.name = "DeclarationsUnavailableError";
27
+ }
28
+ }
@@ -0,0 +1,9 @@
1
+ import type { DeclaredCall } from "../chain/declared-call.types";
2
+ import { type UnrepresentablePolicy } from "./unrepresentable-rule-error";
3
+ /** One emitted field. `required` is separate: the parent assembles it. */
4
+ export interface EmittedField {
5
+ readonly schema: Record<string, unknown>;
6
+ readonly isRequired: boolean;
7
+ }
8
+ /** Makes one field's schema from that field's declared calls. */
9
+ export declare function emitFieldSchema(fieldPath: string, calls: readonly DeclaredCall[], policy: UnrepresentablePolicy): EmittedField;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emitFieldSchema = emitFieldSchema;
4
+ const plugin_keyword_map_1 = require("./plugin-keyword-map");
5
+ const unrepresentable_rule_error_1 = require("./unrepresentable-rule-error");
6
+ /** Slot to JSON Schema type name; undefined when it cannot be decided. */
7
+ const TYPE_OF_SLOT = Object.freeze({
8
+ string: "string",
9
+ number: "number",
10
+ boolean: "boolean",
11
+ array: "array",
12
+ object: "object",
13
+ // date is not a JSON type. Draft-07 convention writes it as a string with
14
+ // `format: "date-time"`, but the date slot judges Date instances, which
15
+ // are not JSON values. Counted as unwritable rather than conflated.
16
+ });
17
+ function typeOf(calls) {
18
+ const slots = new Set(calls.map((call) => call.slot));
19
+ slots.delete("any");
20
+ if (slots.size !== 1)
21
+ return undefined;
22
+ const [slot] = [...slots];
23
+ return slot === undefined ? undefined : TYPE_OF_SLOT[slot];
24
+ }
25
+ /**
26
+ * How `type` is spelled. Draft-07 expresses `.nullable()` as a list of
27
+ * types (`{"type": ["string", "null"]}`). `{"nullable": true}` is the
28
+ * OpenAPI 3.0 spelling and is not in the JSON Schema vocabulary.
29
+ */
30
+ function typeKeyword(calls, fieldPath, policy) {
31
+ const isInteger = calls.some((call) => call.pluginName === "numberInteger");
32
+ const isNullable = calls.some((call) => call.pluginName === "nullable");
33
+ const base = isInteger ? "integer" : typeOf(calls);
34
+ if (base === undefined) {
35
+ if (policy === "throw") {
36
+ throw new unrepresentable_rule_error_1.UnrepresentableRuleError(fieldPath, "the chain", "its declarations do not settle on one JSON type");
37
+ }
38
+ return {};
39
+ }
40
+ return { type: isNullable ? [base, "null"] : base };
41
+ }
42
+ /** Makes one field's schema from that field's declared calls. */
43
+ function emitFieldSchema(fieldPath, calls, policy) {
44
+ if (calls.length === 0 && policy === "throw") {
45
+ throw new unrepresentable_rule_error_1.UnrepresentableRuleError(fieldPath, "this field", "no declaration was recorded for it (it was not built through the " +
46
+ "builder chain)");
47
+ }
48
+ const schema = typeKeyword(calls, fieldPath, policy);
49
+ for (const call of calls) {
50
+ const toKeywords = plugin_keyword_map_1.PLUGIN_KEYWORDS[call.pluginName];
51
+ if (toKeywords === undefined) {
52
+ if (policy === "throw") {
53
+ throw new unrepresentable_rule_error_1.UnrepresentableRuleError(fieldPath, call.pluginName, "no JSON Schema keyword expresses it");
54
+ }
55
+ continue;
56
+ }
57
+ const keywords = toKeywords(call.args);
58
+ if (keywords === null)
59
+ continue;
60
+ Object.assign(schema, keywords);
61
+ }
62
+ return {
63
+ schema,
64
+ // `.optional()` counts wherever it appears in the chain, not only first.
65
+ isRequired: calls.some((call) => call.pluginName === "required") &&
66
+ !calls.some((call) => call.pluginName === "optional"),
67
+ };
68
+ }
@@ -0,0 +1,65 @@
1
+ import { PLUGIN_KEYWORDS } from "./plugin-keyword-map.mjs";
2
+ import { UnrepresentableRuleError, } from "./unrepresentable-rule-error.mjs";
3
+ /** Slot to JSON Schema type name; undefined when it cannot be decided. */
4
+ const TYPE_OF_SLOT = Object.freeze({
5
+ string: "string",
6
+ number: "number",
7
+ boolean: "boolean",
8
+ array: "array",
9
+ object: "object",
10
+ // date is not a JSON type. Draft-07 convention writes it as a string with
11
+ // `format: "date-time"`, but the date slot judges Date instances, which
12
+ // are not JSON values. Counted as unwritable rather than conflated.
13
+ });
14
+ function typeOf(calls) {
15
+ const slots = new Set(calls.map((call) => call.slot));
16
+ slots.delete("any");
17
+ if (slots.size !== 1)
18
+ return undefined;
19
+ const [slot] = [...slots];
20
+ return slot === undefined ? undefined : TYPE_OF_SLOT[slot];
21
+ }
22
+ /**
23
+ * How `type` is spelled. Draft-07 expresses `.nullable()` as a list of
24
+ * types (`{"type": ["string", "null"]}`). `{"nullable": true}` is the
25
+ * OpenAPI 3.0 spelling and is not in the JSON Schema vocabulary.
26
+ */
27
+ function typeKeyword(calls, fieldPath, policy) {
28
+ const isInteger = calls.some((call) => call.pluginName === "numberInteger");
29
+ const isNullable = calls.some((call) => call.pluginName === "nullable");
30
+ const base = isInteger ? "integer" : typeOf(calls);
31
+ if (base === undefined) {
32
+ if (policy === "throw") {
33
+ throw new UnrepresentableRuleError(fieldPath, "the chain", "its declarations do not settle on one JSON type");
34
+ }
35
+ return {};
36
+ }
37
+ return { type: isNullable ? [base, "null"] : base };
38
+ }
39
+ /** Makes one field's schema from that field's declared calls. */
40
+ export function emitFieldSchema(fieldPath, calls, policy) {
41
+ if (calls.length === 0 && policy === "throw") {
42
+ throw new UnrepresentableRuleError(fieldPath, "this field", "no declaration was recorded for it (it was not built through the " +
43
+ "builder chain)");
44
+ }
45
+ const schema = typeKeyword(calls, fieldPath, policy);
46
+ for (const call of calls) {
47
+ const toKeywords = PLUGIN_KEYWORDS[call.pluginName];
48
+ if (toKeywords === undefined) {
49
+ if (policy === "throw") {
50
+ throw new UnrepresentableRuleError(fieldPath, call.pluginName, "no JSON Schema keyword expresses it");
51
+ }
52
+ continue;
53
+ }
54
+ const keywords = toKeywords(call.args);
55
+ if (keywords === null)
56
+ continue;
57
+ Object.assign(schema, keywords);
58
+ }
59
+ return {
60
+ schema,
61
+ // `.optional()` counts wherever it appears in the chain, not only first.
62
+ isRequired: calls.some((call) => call.pluginName === "required") &&
63
+ !calls.some((call) => call.pluginName === "optional"),
64
+ };
65
+ }
@@ -4,3 +4,8 @@ export type { IssuePathSegments } from "./split-issue-path";
4
4
  export { splitIssuePath } from "./split-issue-path";
5
5
  export type { StandardLuqSchema } from "./to-standard-schema";
6
6
  export { toStandardSchema } from "./to-standard-schema";
7
+ export type { JsonSchemaOptions, StandardJsonSchemaLuqSchema, } from "./to-standard-json-schema";
8
+ export { toStandardJsonSchema } from "./to-standard-json-schema";
9
+ export { DeclarationsUnavailableError } from "./declarations-unavailable-error";
10
+ export { UnsupportedJsonSchemaTargetError } from "./json-schema-target";
11
+ export { UnrepresentableRuleError, type UnrepresentablePolicy, } from "./unrepresentable-rule-error";
@@ -1,7 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toStandardSchema = exports.splitIssuePath = void 0;
3
+ exports.UnrepresentableRuleError = exports.UnsupportedJsonSchemaTargetError = exports.DeclarationsUnavailableError = exports.toStandardJsonSchema = exports.toStandardSchema = exports.splitIssuePath = void 0;
4
4
  var split_issue_path_1 = require("./split-issue-path");
5
5
  Object.defineProperty(exports, "splitIssuePath", { enumerable: true, get: function () { return split_issue_path_1.splitIssuePath; } });
6
6
  var to_standard_schema_1 = require("./to-standard-schema");
7
7
  Object.defineProperty(exports, "toStandardSchema", { enumerable: true, get: function () { return to_standard_schema_1.toStandardSchema; } });
8
+ var to_standard_json_schema_1 = require("./to-standard-json-schema");
9
+ Object.defineProperty(exports, "toStandardJsonSchema", { enumerable: true, get: function () { return to_standard_json_schema_1.toStandardJsonSchema; } });
10
+ var declarations_unavailable_error_1 = require("./declarations-unavailable-error");
11
+ Object.defineProperty(exports, "DeclarationsUnavailableError", { enumerable: true, get: function () { return declarations_unavailable_error_1.DeclarationsUnavailableError; } });
12
+ var json_schema_target_1 = require("./json-schema-target");
13
+ Object.defineProperty(exports, "UnsupportedJsonSchemaTargetError", { enumerable: true, get: function () { return json_schema_target_1.UnsupportedJsonSchemaTargetError; } });
14
+ var unrepresentable_rule_error_1 = require("./unrepresentable-rule-error");
15
+ Object.defineProperty(exports, "UnrepresentableRuleError", { enumerable: true, get: function () { return unrepresentable_rule_error_1.UnrepresentableRuleError; } });
@@ -1,2 +1,6 @@
1
1
  export { splitIssuePath } from "./split-issue-path.mjs";
2
2
  export { toStandardSchema } from "./to-standard-schema.mjs";
3
+ export { toStandardJsonSchema } from "./to-standard-json-schema.mjs";
4
+ export { DeclarationsUnavailableError } from "./declarations-unavailable-error.mjs";
5
+ export { UnsupportedJsonSchemaTargetError } from "./json-schema-target.mjs";
6
+ export { UnrepresentableRuleError, } from "./unrepresentable-rule-error.mjs";
@@ -0,0 +1,6 @@
1
+ export declare class UnsupportedJsonSchemaTargetError extends Error {
2
+ readonly target: string;
3
+ constructor(target: string);
4
+ }
5
+ /** Returns the `$schema` for a supported target; throws for anything else. */
6
+ export declare function resolveJsonSchemaTarget(target: string): string;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ // ===========================================================================
3
+ // L10 src/standard-schema/json-schema-target.ts
4
+ //
5
+ // Takes the spec's `target` and admits only what is actually supported.
6
+ //
7
+ // The spec says to throw on an unsupported target, and it is right to: writing
8
+ // draft-07 silently when 2020-12 was asked for hands the caller a document
9
+ // they will read under the wrong rules.
10
+ //
11
+ // Two are supported, draft-2020-12 and draft-07 — the two the spec names as
12
+ // widely used and strongly recommends implementing. openapi-3.0 descends from
13
+ // draft-04 and is a different lineage, so it is not admitted on a guess.
14
+ //
15
+ // Across the vocabulary currently emitted, the two differ only in `$schema`:
16
+ // every keyword written today is spelled and means the same in both. Tuples
17
+ // would differ, and are not emitted. This is where the branch goes if that
18
+ // changes.
19
+ // ===========================================================================
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.UnsupportedJsonSchemaTargetError = void 0;
22
+ exports.resolveJsonSchemaTarget = resolveJsonSchemaTarget;
23
+ /** The supported targets, and the `$schema` each one announces. */
24
+ const SCHEMA_URI = Object.freeze({
25
+ "draft-2020-12": "https://json-schema.org/draft/2020-12/schema",
26
+ "draft-07": "http://json-schema.org/draft-07/schema#",
27
+ });
28
+ class UnsupportedJsonSchemaTargetError extends Error {
29
+ constructor(target) {
30
+ super(`Luq does not emit JSON Schema for the target "${target}". ` +
31
+ `Supported targets: ${Object.keys(SCHEMA_URI).join(", ")}.`);
32
+ this.target = target;
33
+ this.name = "UnsupportedJsonSchemaTargetError";
34
+ }
35
+ }
36
+ exports.UnsupportedJsonSchemaTargetError = UnsupportedJsonSchemaTargetError;
37
+ /** Returns the `$schema` for a supported target; throws for anything else. */
38
+ function resolveJsonSchemaTarget(target) {
39
+ const schemaUri = SCHEMA_URI[target];
40
+ if (schemaUri === undefined) {
41
+ throw new UnsupportedJsonSchemaTargetError(target);
42
+ }
43
+ return schemaUri;
44
+ }
@@ -0,0 +1,39 @@
1
+ // ===========================================================================
2
+ // L10 src/standard-schema/json-schema-target.ts
3
+ //
4
+ // Takes the spec's `target` and admits only what is actually supported.
5
+ //
6
+ // The spec says to throw on an unsupported target, and it is right to: writing
7
+ // draft-07 silently when 2020-12 was asked for hands the caller a document
8
+ // they will read under the wrong rules.
9
+ //
10
+ // Two are supported, draft-2020-12 and draft-07 — the two the spec names as
11
+ // widely used and strongly recommends implementing. openapi-3.0 descends from
12
+ // draft-04 and is a different lineage, so it is not admitted on a guess.
13
+ //
14
+ // Across the vocabulary currently emitted, the two differ only in `$schema`:
15
+ // every keyword written today is spelled and means the same in both. Tuples
16
+ // would differ, and are not emitted. This is where the branch goes if that
17
+ // changes.
18
+ // ===========================================================================
19
+ /** The supported targets, and the `$schema` each one announces. */
20
+ const SCHEMA_URI = Object.freeze({
21
+ "draft-2020-12": "https://json-schema.org/draft/2020-12/schema",
22
+ "draft-07": "http://json-schema.org/draft-07/schema#",
23
+ });
24
+ export class UnsupportedJsonSchemaTargetError extends Error {
25
+ constructor(target) {
26
+ super(`Luq does not emit JSON Schema for the target "${target}". ` +
27
+ `Supported targets: ${Object.keys(SCHEMA_URI).join(", ")}.`);
28
+ this.target = target;
29
+ this.name = "UnsupportedJsonSchemaTargetError";
30
+ }
31
+ }
32
+ /** Returns the `$schema` for a supported target; throws for anything else. */
33
+ export function resolveJsonSchemaTarget(target) {
34
+ const schemaUri = SCHEMA_URI[target];
35
+ if (schemaUri === undefined) {
36
+ throw new UnsupportedJsonSchemaTargetError(target);
37
+ }
38
+ return schemaUri;
39
+ }
@@ -0,0 +1,3 @@
1
+ /** A keyword fragment made from the arguments; null means type or presence handles it. */
2
+ export type ToKeywords = (args: readonly unknown[]) => Record<string, unknown> | null;
3
+ export declare const PLUGIN_KEYWORDS: Readonly<Record<string, ToKeywords>>;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ // ===========================================================================
3
+ // L10 src/standard-schema/plugin-keyword-map.ts
4
+ //
5
+ // "This plugin, called with these arguments, means this JSON Schema keyword."
6
+ // The reading direction of keyword-map-*.ts, reusing the correspondence those
7
+ // files already settled rather than inventing a second one.
8
+ //
9
+ // Plugin names are written as STRINGS. Importing the plugins themselves would
10
+ // drag every one of them into the bundle of anyone who emits a schema, which
11
+ // does not fit "you only ship what you used". A test cross-checks each string
12
+ // against the real `plugin.name`, so the names cannot drift without the cost.
13
+ //
14
+ // A plugin that is not listed here cannot be written out. A plugin the user
15
+ // wrote themselves is never listed, so it never can. That is a consequence of
16
+ // the design rather than an oversight, and it surfaces as
17
+ // UnrepresentableRuleError instead of being hidden.
18
+ // ===========================================================================
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.PLUGIN_KEYWORDS = void 0;
21
+ const numberAt = (args, index) => typeof args[index] === "number" ? args[index] : Number.NaN;
22
+ /**
23
+ * A second argument makes `.min(n)` exclusive. The same boundary the
24
+ * reading direction draws when it maps `exclusiveMinimum`.
25
+ */
26
+ const boundOf = (inclusive, exclusive) => (args) => ({
27
+ [args[1] === true ? exclusive : inclusive]: numberAt(args, 0),
28
+ });
29
+ /** A format with no arguments: the name is the `format` value verbatim. */
30
+ const format = (name) => () => ({ format: name });
31
+ exports.PLUGIN_KEYWORDS = Object.freeze({
32
+ // --- strings ----------------------------------------------------------
33
+ stringMin: (args) => ({ minLength: numberAt(args, 0) }),
34
+ stringMax: (args) => ({ maxLength: numberAt(args, 0) }),
35
+ stringExactLength: (args) => ({
36
+ minLength: numberAt(args, 0),
37
+ maxLength: numberAt(args, 0),
38
+ }),
39
+ // Draft-07's `pattern` is an ECMA-262 SOURCE string. Flags cannot be
40
+ // spelled there, and dropping them changes the meaning, so a flagged
41
+ // RegExp counts as unwritable rather than being silently narrowed.
42
+ stringPattern: (args) => args[0] instanceof RegExp ? { pattern: args[0].source } : null,
43
+ stringContentEncoding: (args) => ({ contentEncoding: args[0] }),
44
+ stringContentMediaType: (args) => ({ contentMediaType: args[0] }),
45
+ // --- formats (names taken verbatim from the format map) ---------------
46
+ stringDatetime: format("date-time"),
47
+ stringDate: format("date"),
48
+ stringTime: format("time"),
49
+ stringDuration: format("duration"),
50
+ stringEmail: format("email"),
51
+ stringIdnEmail: format("idn-email"),
52
+ stringHostname: format("hostname"),
53
+ stringIdnHostname: format("idn-hostname"),
54
+ stringIpv4: format("ipv4"),
55
+ stringIpv6: format("ipv6"),
56
+ // `url` is not a registered JSON Schema format name. Both spellings map
57
+ // to this plugin on the reading side, but a writer has to pick one, so it
58
+ // picks the registered `uri`.
59
+ stringUrl: format("uri"),
60
+ stringUriReference: format("uri-reference"),
61
+ stringIri: format("iri"),
62
+ stringIriReference: format("iri-reference"),
63
+ stringUriTemplate: format("uri-template"),
64
+ stringJsonPointer: format("json-pointer"),
65
+ stringRelativeJsonPointer: format("relative-json-pointer"),
66
+ stringRegex: format("regex"),
67
+ uuid: format("uuid"),
68
+ // --- numbers ----------------------------------------------------------
69
+ numberMin: boundOf("minimum", "exclusiveMinimum"),
70
+ numberMax: boundOf("maximum", "exclusiveMaximum"),
71
+ numberMultipleOf: (args) => ({ multipleOf: numberAt(args, 0) }),
72
+ numberRange: (args) => ({
73
+ minimum: numberAt(args, 0),
74
+ maximum: numberAt(args, 1),
75
+ }),
76
+ numberPositive: () => ({ exclusiveMinimum: 0 }),
77
+ numberNegative: () => ({ exclusiveMaximum: 0 }),
78
+ // --- arrays -----------------------------------------------------------
79
+ arrayMinLength: (args) => ({ minItems: numberAt(args, 0) }),
80
+ arrayMaxLength: (args) => ({ maxItems: numberAt(args, 0) }),
81
+ arrayUnique: () => ({ uniqueItems: true }),
82
+ // --- values -----------------------------------------------------------
83
+ literal: (args) => ({ const: args[0] }),
84
+ oneOf: (args) => (Array.isArray(args[0]) ? { enum: args[0] } : null),
85
+ // --- handled by type or presence, adding no keyword -------------------
86
+ // numberInteger becomes `type: "integer"`, so it feeds the type decision.
87
+ numberInteger: () => null,
88
+ required: () => null,
89
+ optional: () => null,
90
+ nullable: () => null,
91
+ // Changes the order things run in, not the set of accepted values.
92
+ skip: () => null,
93
+ });
@@ -0,0 +1,90 @@
1
+ // ===========================================================================
2
+ // L10 src/standard-schema/plugin-keyword-map.ts
3
+ //
4
+ // "This plugin, called with these arguments, means this JSON Schema keyword."
5
+ // The reading direction of keyword-map-*.ts, reusing the correspondence those
6
+ // files already settled rather than inventing a second one.
7
+ //
8
+ // Plugin names are written as STRINGS. Importing the plugins themselves would
9
+ // drag every one of them into the bundle of anyone who emits a schema, which
10
+ // does not fit "you only ship what you used". A test cross-checks each string
11
+ // against the real `plugin.name`, so the names cannot drift without the cost.
12
+ //
13
+ // A plugin that is not listed here cannot be written out. A plugin the user
14
+ // wrote themselves is never listed, so it never can. That is a consequence of
15
+ // the design rather than an oversight, and it surfaces as
16
+ // UnrepresentableRuleError instead of being hidden.
17
+ // ===========================================================================
18
+ const numberAt = (args, index) => typeof args[index] === "number" ? args[index] : Number.NaN;
19
+ /**
20
+ * A second argument makes `.min(n)` exclusive. The same boundary the
21
+ * reading direction draws when it maps `exclusiveMinimum`.
22
+ */
23
+ const boundOf = (inclusive, exclusive) => (args) => ({
24
+ [args[1] === true ? exclusive : inclusive]: numberAt(args, 0),
25
+ });
26
+ /** A format with no arguments: the name is the `format` value verbatim. */
27
+ const format = (name) => () => ({ format: name });
28
+ export const PLUGIN_KEYWORDS = Object.freeze({
29
+ // --- strings ----------------------------------------------------------
30
+ stringMin: (args) => ({ minLength: numberAt(args, 0) }),
31
+ stringMax: (args) => ({ maxLength: numberAt(args, 0) }),
32
+ stringExactLength: (args) => ({
33
+ minLength: numberAt(args, 0),
34
+ maxLength: numberAt(args, 0),
35
+ }),
36
+ // Draft-07's `pattern` is an ECMA-262 SOURCE string. Flags cannot be
37
+ // spelled there, and dropping them changes the meaning, so a flagged
38
+ // RegExp counts as unwritable rather than being silently narrowed.
39
+ stringPattern: (args) => args[0] instanceof RegExp ? { pattern: args[0].source } : null,
40
+ stringContentEncoding: (args) => ({ contentEncoding: args[0] }),
41
+ stringContentMediaType: (args) => ({ contentMediaType: args[0] }),
42
+ // --- formats (names taken verbatim from the format map) ---------------
43
+ stringDatetime: format("date-time"),
44
+ stringDate: format("date"),
45
+ stringTime: format("time"),
46
+ stringDuration: format("duration"),
47
+ stringEmail: format("email"),
48
+ stringIdnEmail: format("idn-email"),
49
+ stringHostname: format("hostname"),
50
+ stringIdnHostname: format("idn-hostname"),
51
+ stringIpv4: format("ipv4"),
52
+ stringIpv6: format("ipv6"),
53
+ // `url` is not a registered JSON Schema format name. Both spellings map
54
+ // to this plugin on the reading side, but a writer has to pick one, so it
55
+ // picks the registered `uri`.
56
+ stringUrl: format("uri"),
57
+ stringUriReference: format("uri-reference"),
58
+ stringIri: format("iri"),
59
+ stringIriReference: format("iri-reference"),
60
+ stringUriTemplate: format("uri-template"),
61
+ stringJsonPointer: format("json-pointer"),
62
+ stringRelativeJsonPointer: format("relative-json-pointer"),
63
+ stringRegex: format("regex"),
64
+ uuid: format("uuid"),
65
+ // --- numbers ----------------------------------------------------------
66
+ numberMin: boundOf("minimum", "exclusiveMinimum"),
67
+ numberMax: boundOf("maximum", "exclusiveMaximum"),
68
+ numberMultipleOf: (args) => ({ multipleOf: numberAt(args, 0) }),
69
+ numberRange: (args) => ({
70
+ minimum: numberAt(args, 0),
71
+ maximum: numberAt(args, 1),
72
+ }),
73
+ numberPositive: () => ({ exclusiveMinimum: 0 }),
74
+ numberNegative: () => ({ exclusiveMaximum: 0 }),
75
+ // --- arrays -----------------------------------------------------------
76
+ arrayMinLength: (args) => ({ minItems: numberAt(args, 0) }),
77
+ arrayMaxLength: (args) => ({ maxItems: numberAt(args, 0) }),
78
+ arrayUnique: () => ({ uniqueItems: true }),
79
+ // --- values -----------------------------------------------------------
80
+ literal: (args) => ({ const: args[0] }),
81
+ oneOf: (args) => (Array.isArray(args[0]) ? { enum: args[0] } : null),
82
+ // --- handled by type or presence, adding no keyword -------------------
83
+ // numberInteger becomes `type: "integer"`, so it feeds the type decision.
84
+ numberInteger: () => null,
85
+ required: () => null,
86
+ optional: () => null,
87
+ nullable: () => null,
88
+ // Changes the order things run in, not the set of accepted values.
89
+ skip: () => null,
90
+ });
@@ -1,9 +1,11 @@
1
- /** 開いた結果。オブジェクトのキーは string、配列の添字は number。 */
1
+ /** The opened path: object keys as strings, array indices as numbers. */
2
2
  export type IssuePathSegments = readonly (string | number)[];
3
3
  /**
4
- * ルート ("") は空配列。空配列は仕様上「ルート自身への issue」を意味する。
4
+ * The root ("") is the empty list, which the spec reads as an issue on the
5
+ * root itself.
5
6
  *
6
- * 解釈できない形が来たら、握り潰さずに path 全体を1つの文字列セグメントとして
7
- * 返す。issue を落とすより、開けなかったことが分かる形で渡すほうがまし。
7
+ * A shape that cannot be interpreted comes back as the whole path in one
8
+ * string segment rather than being swallowed. Handing over something visibly
9
+ * unopened beats dropping the issue.
8
10
  */
9
11
  export declare function splitIssuePath(path: string): IssuePathSegments;
@@ -2,26 +2,28 @@
2
2
  // ===========================================================================
3
3
  // L10 src/standard-schema/split-issue-path.ts
4
4
  //
5
- // Luq issue path ("items[1].productId") Standard Schema path
6
- // (["items", 1, "productId"]) に開く。
5
+ // Opens an issue path ("items[1].productId") into the spec's segment list
6
+ // (["items", 1, "productId"]).
7
7
  //
8
- // 方向に注意: これは formatIssuePath の逆で、宣言パス (parseFieldPath
9
- // 読む "items[*].productId") のパーサではない。issue path には [*]
10
- // 決して現れず、代わりに実インデックスが入る。二つの文法を一つの関数で
11
- // 扱おうとすると、[*] 0 と読むような取り違えが静かに入るので分けてある。
8
+ // Mind the direction: this reverses issue-path FORMATTING, and is not a parser
9
+ // for declared paths. An issue path never contains [*] — it carries the real
10
+ // index instead. Handling both grammars in one function is how [*] quietly
11
+ // starts being read as 0, so they are kept apart.
12
12
  //
13
- // 配列インデックスは number として出す。仕様の PropertyKey
14
- // string | number | symbol を許し、消費側 (フォームライブラリ)
15
- // 添字を number として受け取る前提で書かれているため。
13
+ // Array indices come out as numbers. The spec's PropertyKey allows
14
+ // string | number | symbol, and the consumers — form libraries — are written
15
+ // expecting a number.
16
16
  // ===========================================================================
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.splitIssuePath = splitIssuePath;
19
19
  const INDEX_PATTERN = /^\[(\d+)\]/;
20
20
  /**
21
- * ルート ("") は空配列。空配列は仕様上「ルート自身への issue」を意味する。
21
+ * The root ("") is the empty list, which the spec reads as an issue on the
22
+ * root itself.
22
23
  *
23
- * 解釈できない形が来たら、握り潰さずに path 全体を1つの文字列セグメントとして
24
- * 返す。issue を落とすより、開けなかったことが分かる形で渡すほうがまし。
24
+ * A shape that cannot be interpreted comes back as the whole path in one
25
+ * string segment rather than being swallowed. Handing over something visibly
26
+ * unopened beats dropping the issue.
25
27
  */
26
28
  function splitIssuePath(path) {
27
29
  if (path === "")
@@ -54,7 +56,7 @@ function splitIssuePath(path) {
54
56
  }
55
57
  return expectKey ? [path] : segments;
56
58
  }
57
- /** キーは "." "[" の手前まで。どちらも無ければ末尾まで。 */
59
+ /** A key runs to the next "." or "[", or to the end when there is neither. */
58
60
  function findKeyEnd(rest) {
59
61
  const dot = rest.indexOf(".");
60
62
  const bracket = rest.indexOf("[");
@@ -1,24 +1,26 @@
1
1
  // ===========================================================================
2
2
  // L10 src/standard-schema/split-issue-path.ts
3
3
  //
4
- // Luq issue path ("items[1].productId") Standard Schema path
5
- // (["items", 1, "productId"]) に開く。
4
+ // Opens an issue path ("items[1].productId") into the spec's segment list
5
+ // (["items", 1, "productId"]).
6
6
  //
7
- // 方向に注意: これは formatIssuePath の逆で、宣言パス (parseFieldPath
8
- // 読む "items[*].productId") のパーサではない。issue path には [*]
9
- // 決して現れず、代わりに実インデックスが入る。二つの文法を一つの関数で
10
- // 扱おうとすると、[*] 0 と読むような取り違えが静かに入るので分けてある。
7
+ // Mind the direction: this reverses issue-path FORMATTING, and is not a parser
8
+ // for declared paths. An issue path never contains [*] — it carries the real
9
+ // index instead. Handling both grammars in one function is how [*] quietly
10
+ // starts being read as 0, so they are kept apart.
11
11
  //
12
- // 配列インデックスは number として出す。仕様の PropertyKey
13
- // string | number | symbol を許し、消費側 (フォームライブラリ)
14
- // 添字を number として受け取る前提で書かれているため。
12
+ // Array indices come out as numbers. The spec's PropertyKey allows
13
+ // string | number | symbol, and the consumers — form libraries — are written
14
+ // expecting a number.
15
15
  // ===========================================================================
16
16
  const INDEX_PATTERN = /^\[(\d+)\]/;
17
17
  /**
18
- * ルート ("") は空配列。空配列は仕様上「ルート自身への issue」を意味する。
18
+ * The root ("") is the empty list, which the spec reads as an issue on the
19
+ * root itself.
19
20
  *
20
- * 解釈できない形が来たら、握り潰さずに path 全体を1つの文字列セグメントとして
21
- * 返す。issue を落とすより、開けなかったことが分かる形で渡すほうがまし。
21
+ * A shape that cannot be interpreted comes back as the whole path in one
22
+ * string segment rather than being swallowed. Handing over something visibly
23
+ * unopened beats dropping the issue.
22
24
  */
23
25
  export function splitIssuePath(path) {
24
26
  if (path === "")
@@ -51,7 +53,7 @@ export function splitIssuePath(path) {
51
53
  }
52
54
  return expectKey ? [path] : segments;
53
55
  }
54
- /** キーは "." "[" の手前まで。どちらも無ければ末尾まで。 */
56
+ /** A key runs to the next "." or "[", or to the end when there is neither. */
55
57
  function findKeyEnd(rest) {
56
58
  const dot = rest.indexOf(".");
57
59
  const bracket = rest.indexOf("[");