@maroonedog/luq 2.0.0 → 2.1.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 (109) hide show
  1. package/README.md +100 -10
  2. package/dist/chain/create-chain-node.js +22 -1
  3. package/dist/chain/create-chain-node.mjs +22 -1
  4. package/dist/compile/resolve-conditional-presence.js +1 -0
  5. package/dist/compile/resolve-conditional-presence.mjs +1 -0
  6. package/dist/compile/resolve-presence.js +4 -0
  7. package/dist/compile/resolve-presence.mjs +4 -0
  8. package/dist/compile/validation-plan.types.d.ts +10 -0
  9. package/dist/json-schema/build-from-schema.js +2 -3
  10. package/dist/json-schema/build-from-schema.mjs +2 -3
  11. package/dist/json-schema/collect-definitions.d.ts +30 -0
  12. package/dist/json-schema/collect-definitions.js +47 -3
  13. package/dist/json-schema/collect-definitions.mjs +46 -4
  14. package/dist/json-schema/collect-sub-schema-rules.d.ts +12 -1
  15. package/dist/json-schema/collect-sub-schema-rules.js +37 -7
  16. package/dist/json-schema/collect-sub-schema-rules.mjs +38 -8
  17. package/dist/json-schema/create-structural-context.d.ts +13 -3
  18. package/dist/json-schema/create-structural-context.js +72 -6
  19. package/dist/json-schema/create-structural-context.mjs +72 -7
  20. package/dist/json-schema/declare-additional-properties.d.ts +19 -0
  21. package/dist/json-schema/declare-additional-properties.js +42 -0
  22. package/dist/json-schema/declare-additional-properties.mjs +37 -0
  23. package/dist/json-schema/declare-object-keywords.d.ts +0 -2
  24. package/dist/json-schema/declare-object-keywords.js +6 -13
  25. package/dist/json-schema/declare-object-keywords.mjs +7 -13
  26. package/dist/json-schema/declare-presence.d.ts +9 -3
  27. package/dist/json-schema/declare-presence.js +12 -6
  28. package/dist/json-schema/declare-presence.mjs +12 -6
  29. package/dist/json-schema/extensions/json-schema/index.d.ts +1 -0
  30. package/dist/json-schema/extensions/json-schema/json-schema.d.ts +17 -2
  31. package/dist/json-schema/extensions/json-schema/json-schema.js +15 -7
  32. package/dist/json-schema/extensions/json-schema/json-schema.mjs +16 -8
  33. package/dist/json-schema/extensions/json-schema-full-feature/index.d.ts +1 -0
  34. package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.d.ts +2 -1
  35. package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.js +3 -1
  36. package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.mjs +3 -1
  37. package/dist/json-schema/follow-json-pointer.d.ts +23 -0
  38. package/dist/json-schema/follow-json-pointer.js +87 -0
  39. package/dist/json-schema/follow-json-pointer.mjs +82 -0
  40. package/dist/json-schema/index.d.ts +7 -2
  41. package/dist/json-schema/index.js +10 -2
  42. package/dist/json-schema/index.mjs +5 -2
  43. package/dist/json-schema/keyword-map-object.d.ts +1 -1
  44. package/dist/json-schema/ref-resolution-error.d.ts +4 -0
  45. package/dist/json-schema/ref-resolution-error.js +20 -0
  46. package/dist/json-schema/ref-resolution-error.mjs +16 -0
  47. package/dist/json-schema/ref-scope.d.ts +18 -0
  48. package/dist/json-schema/ref-scope.js +37 -0
  49. package/dist/json-schema/ref-scope.mjs +33 -0
  50. package/dist/json-schema/resolve-ref.d.ts +14 -9
  51. package/dist/json-schema/resolve-ref.js +89 -59
  52. package/dist/json-schema/resolve-ref.mjs +86 -55
  53. package/dist/json-schema/schema-registry.d.ts +30 -0
  54. package/dist/json-schema/schema-registry.js +142 -0
  55. package/dist/json-schema/schema-registry.mjs +139 -0
  56. package/dist/json-schema/schema-to-declarations.js +2 -1
  57. package/dist/json-schema/schema-to-declarations.mjs +2 -1
  58. package/dist/json-schema/structural-expansion.types.d.ts +8 -1
  59. package/dist/json-schema/uri-reference.d.ts +29 -0
  60. package/dist/json-schema/uri-reference.js +118 -0
  61. package/dist/json-schema/uri-reference.mjs +111 -0
  62. package/dist/path/create-value-writer.js +27 -2
  63. package/dist/path/create-value-writer.mjs +27 -2
  64. package/dist/path/reserved-segment.d.ts +22 -4
  65. package/dist/path/reserved-segment.js +27 -7
  66. package/dist/path/reserved-segment.mjs +27 -7
  67. package/dist/plugin-kit/compiled-rule.d.ts +13 -0
  68. package/dist/plugin-kit/create-rule.d.ts +11 -0
  69. package/dist/plugin-kit/create-rule.js +1 -0
  70. package/dist/plugin-kit/create-rule.mjs +1 -0
  71. package/dist/plugin-kit/plugin-definition.d.ts +15 -0
  72. package/dist/plugin-kit/plugin-definition.js +1 -0
  73. package/dist/plugin-kit/plugin-definition.mjs +1 -0
  74. package/dist/plugins/object-additional-properties/object-additional-properties-schema.d.ts +1 -1
  75. package/dist/plugins/object-additional-properties/object-additional-properties-schema.js +4 -2
  76. package/dist/plugins/object-additional-properties/object-additional-properties-schema.mjs +4 -2
  77. package/dist/plugins/object-additional-properties/object-additional-properties.d.ts +1 -1
  78. package/dist/plugins/object-additional-properties/object-additional-properties.js +4 -2
  79. package/dist/plugins/object-additional-properties/object-additional-properties.mjs +4 -2
  80. package/dist/plugins/object-additional-properties/select-additional-keys.d.ts +15 -0
  81. package/dist/plugins/object-additional-properties/select-additional-keys.js +51 -0
  82. package/dist/plugins/object-additional-properties/select-additional-keys.mjs +47 -0
  83. package/dist/plugins/string-exact-length/string-exact-length.js +2 -2
  84. package/dist/plugins/string-exact-length/string-exact-length.mjs +3 -3
  85. package/dist/plugins/string-max/string-max.js +2 -2
  86. package/dist/plugins/string-max/string-max.mjs +3 -3
  87. package/dist/plugins/string-min/string-min.js +2 -2
  88. package/dist/plugins/string-min/string-min.mjs +3 -3
  89. package/dist/runtime/decide-presence.js +2 -1
  90. package/dist/runtime/decide-presence.mjs +2 -1
  91. package/dist/standard-schema/index.d.ts +6 -0
  92. package/dist/standard-schema/index.js +7 -0
  93. package/dist/standard-schema/index.mjs +2 -0
  94. package/dist/standard-schema/split-issue-path.d.ts +9 -0
  95. package/dist/standard-schema/split-issue-path.js +66 -0
  96. package/dist/standard-schema/split-issue-path.mjs +63 -0
  97. package/dist/standard-schema/standard-schema.types.d.ts +46 -0
  98. package/dist/standard-schema/standard-schema.types.js +13 -0
  99. package/dist/standard-schema/standard-schema.types.mjs +12 -0
  100. package/dist/standard-schema/to-standard-schema.d.ts +41 -0
  101. package/dist/standard-schema/to-standard-schema.js +60 -0
  102. package/dist/standard-schema/to-standard-schema.mjs +57 -0
  103. package/dist/standard-schema.d.ts +1 -0
  104. package/dist/standard-schema.js +2 -0
  105. package/dist/standard-schema.mjs +1 -0
  106. package/dist/types/index.d.ts +12 -0
  107. package/dist/types/index.js +18 -0
  108. package/dist/types/index.mjs +17 -0
  109. package/package.json +10 -3
@@ -5,14 +5,29 @@ import type { Unchanged } from "../../../plugin-kit/marker.types";
5
5
  import { type JsonSchemaBag } from "../../index";
6
6
  /** The label the whole document carries inside the composite it becomes. */
7
7
  export declare const SCHEMA_BRANCH_LABEL = "schema";
8
+ /**
9
+ * What the converter may reach beyond the document itself.
10
+ *
11
+ * `externalDocuments` is a MAP, deliberately, and not a loader function:
12
+ * * Luq never opens a socket, so a schema cannot make the process fetch a
13
+ * URL it names (SSRF) — what can be read is written in the caller's code;
14
+ * * conversion stays synchronous, so `build()` keeps returning a validator
15
+ * rather than a promise;
16
+ * * nothing is evaluated, so the CSP guarantee is untouched.
17
+ * The caller fetches, reads from disk, or bundles — whichever is right for
18
+ * their deployment — and hands over what they already have.
19
+ */
20
+ export interface JsonSchemaOptions {
21
+ readonly externalDocuments?: Readonly<Record<string, unknown>> | undefined;
22
+ }
8
23
  /**
9
24
  * The document as a rule list over ONE subject. Exported because it is the
10
25
  * only part of this plugin the full-feature bundle needs, and because a test
11
26
  * can then count the rules a document produces without building a validator.
12
27
  */
13
- export declare function collectDocumentRules(ctx: RuleBuildContext<MessageContextExtra>, document: unknown, bag: JsonSchemaBag): readonly Rule[];
28
+ export declare function collectDocumentRules(ctx: RuleBuildContext<MessageContextExtra>, document: unknown, bag: JsonSchemaBag, options?: JsonSchemaOptions): readonly Rule[];
14
29
  export declare const jsonSchemaPlugin: import("../../../plugin-kit/plugin-definition").PluginDefinition<"jsonSchema", "jsonSchema", readonly TypeName[], {
15
- args: readonly [document: unknown, bag: JsonSchemaBag];
30
+ args: readonly [document: unknown, bag: JsonSchemaBag, options?: JsonSchemaOptions];
16
31
  out: Unchanged;
17
32
  context: MessageContextExtra;
18
33
  }>;
@@ -57,41 +57,49 @@ const SCHEMA_SLOTS = [
57
57
  ];
58
58
  /** The label the whole document carries inside the composite it becomes. */
59
59
  exports.SCHEMA_BRANCH_LABEL = "schema";
60
+ const NO_EXTERNAL_DOCUMENTS = Object.freeze({});
60
61
  /**
61
62
  * The document as a rule list over ONE subject. Exported because it is the
62
63
  * only part of this plugin the full-feature bundle needs, and because a test
63
64
  * can then count the rules a document produces without building a validator.
64
65
  */
65
- function collectDocumentRules(ctx, document, bag) {
66
+ function collectDocumentRules(ctx, document, bag, options = {}) {
66
67
  if (!(0, index_1.isDraft07Schema)(document))
67
68
  throw new index_1.NotASchemaError(document);
68
69
  const root = document;
70
+ const scope = (0, index_1.createDocumentScope)(root, options.externalDocuments ?? NO_EXTERNAL_DOCUMENTS);
69
71
  // The seed's `chain` is a ChainBuildContext, whose three members a
70
72
  // RuleBuildContext already carries. Written inline because naming the type
71
73
  // would mean importing src/chain, which tier `extension` forbids.
72
74
  const seed = {
73
75
  bag,
74
- root,
76
+ scope,
75
77
  chain: {
76
78
  fieldPath: ctx.fieldPath,
77
79
  declaredSiblingKeys: ctx.declaredSiblingKeys,
78
80
  config: ctx.config,
79
81
  },
80
82
  };
81
- const node = (0, index_1.resolveSchemaNode)(root, root);
82
- const context = (0, index_1.createStructuralContext)(seed, node, []);
83
- return (0, index_1.collectSubSchemaRules)(root, context);
83
+ const resolved = (0, index_1.resolveSchemaNodeInScope)(root, scope);
84
+ const context = (0, index_1.createStructuralContext)(seed, resolved.node, [], resolved.scope);
85
+ // The branch subject carries its own null policy, like every sub-schema
86
+ // subject: collectSubSchemaRules puts it there. `judgesNull` on the plugin
87
+ // gets null as far as this composite; that policy gets it the rest of the
88
+ // way, into the document's own rules.
89
+ return (0, index_1.collectSubSchemaRules)(resolved.node, context);
84
90
  }
85
91
  exports.jsonSchemaPlugin = (0, plugin_definition_1.definePlugin)()({
86
92
  name: "jsonSchema",
93
+ // The document decides whether null is allowed, so null has to reach it.
94
+ judgesNull: true,
87
95
  method: "jsonSchema",
88
96
  slots: SCHEMA_SLOTS,
89
- build: (ctx, document, bag) => (0, create_rule_1.composite)({
97
+ build: (ctx, document, bag, options) => (0, create_rule_1.composite)({
90
98
  code: ctx.code,
91
99
  messageFactory: ctx.messageFactory,
92
100
  severity: ctx.severity,
93
101
  branches: [
94
- (0, create_rule_1.branch)(exports.SCHEMA_BRANCH_LABEL, collectDocumentRules(ctx, document, bag)),
102
+ (0, create_rule_1.branch)(exports.SCHEMA_BRANCH_LABEL, collectDocumentRules(ctx, document, bag, options)),
95
103
  ],
96
104
  combine: (runners) => (value, runContext) => runners[0]?.run(value, runContext) ?? types_1.PASS,
97
105
  describe: () => "Value does not match the JSON Schema document",
@@ -38,7 +38,7 @@
38
38
  import { branch, composite } from "../../../plugin-kit/create-rule.mjs";
39
39
  import { definePlugin } from "../../../plugin-kit/plugin-definition.mjs";
40
40
  import { PASS } from "../../../types/index.mjs";
41
- import { NotASchemaError, collectSubSchemaRules, createStructuralContext, isDraft07Schema, resolveSchemaNode, } from "../../index.mjs";
41
+ import { NotASchemaError, collectSubSchemaRules, createStructuralContext, createDocumentScope, isDraft07Schema, resolveSchemaNodeInScope, } from "../../index.mjs";
42
42
  /** Every slot: a JSON Schema document constrains a value of any shape. */
43
43
  const SCHEMA_SLOTS = [
44
44
  "string",
@@ -53,41 +53,49 @@ const SCHEMA_SLOTS = [
53
53
  ];
54
54
  /** The label the whole document carries inside the composite it becomes. */
55
55
  export const SCHEMA_BRANCH_LABEL = "schema";
56
+ const NO_EXTERNAL_DOCUMENTS = Object.freeze({});
56
57
  /**
57
58
  * The document as a rule list over ONE subject. Exported because it is the
58
59
  * only part of this plugin the full-feature bundle needs, and because a test
59
60
  * can then count the rules a document produces without building a validator.
60
61
  */
61
- export function collectDocumentRules(ctx, document, bag) {
62
+ export function collectDocumentRules(ctx, document, bag, options = {}) {
62
63
  if (!isDraft07Schema(document))
63
64
  throw new NotASchemaError(document);
64
65
  const root = document;
66
+ const scope = createDocumentScope(root, options.externalDocuments ?? NO_EXTERNAL_DOCUMENTS);
65
67
  // The seed's `chain` is a ChainBuildContext, whose three members a
66
68
  // RuleBuildContext already carries. Written inline because naming the type
67
69
  // would mean importing src/chain, which tier `extension` forbids.
68
70
  const seed = {
69
71
  bag,
70
- root,
72
+ scope,
71
73
  chain: {
72
74
  fieldPath: ctx.fieldPath,
73
75
  declaredSiblingKeys: ctx.declaredSiblingKeys,
74
76
  config: ctx.config,
75
77
  },
76
78
  };
77
- const node = resolveSchemaNode(root, root);
78
- const context = createStructuralContext(seed, node, []);
79
- return collectSubSchemaRules(root, context);
79
+ const resolved = resolveSchemaNodeInScope(root, scope);
80
+ const context = createStructuralContext(seed, resolved.node, [], resolved.scope);
81
+ // The branch subject carries its own null policy, like every sub-schema
82
+ // subject: collectSubSchemaRules puts it there. `judgesNull` on the plugin
83
+ // gets null as far as this composite; that policy gets it the rest of the
84
+ // way, into the document's own rules.
85
+ return collectSubSchemaRules(resolved.node, context);
80
86
  }
81
87
  export const jsonSchemaPlugin = /*#__PURE__*/ definePlugin()({
82
88
  name: "jsonSchema",
89
+ // The document decides whether null is allowed, so null has to reach it.
90
+ judgesNull: true,
83
91
  method: "jsonSchema",
84
92
  slots: SCHEMA_SLOTS,
85
- build: (ctx, document, bag) => composite({
93
+ build: (ctx, document, bag, options) => composite({
86
94
  code: ctx.code,
87
95
  messageFactory: ctx.messageFactory,
88
96
  severity: ctx.severity,
89
97
  branches: [
90
- branch(SCHEMA_BRANCH_LABEL, collectDocumentRules(ctx, document, bag)),
98
+ branch(SCHEMA_BRANCH_LABEL, collectDocumentRules(ctx, document, bag, options)),
91
99
  ],
92
100
  combine: (runners) => (value, runContext) => runners[0]?.run(value, runContext) ?? PASS,
93
101
  describe: () => "Value does not match the JSON Schema document",
@@ -1,4 +1,5 @@
1
1
  export { fromJsonSchema, jsonSchemaFullFeaturePlugin, } from "./json-schema-full-feature";
2
2
  export { jsonSchemaBag } from "./bundled-plugins";
3
3
  export { UNBOUND_BUNDLED_PLUGIN_NAMES, findStaleUnboundDeclarations, findUnbundledBoundPlugins, findUnexplainedBundledPlugins, listBundledPluginNames, } from "./bundle-coverage";
4
+ export type { JsonSchemaOptions } from "../json-schema";
4
5
  export type { JsonSchemaBag } from "../../index";
@@ -1,8 +1,9 @@
1
1
  import type { MessageContextExtra, TypeName } from "../../../types";
2
2
  import type { Unchanged } from "../../../plugin-kit/marker.types";
3
+ import type { JsonSchemaOptions } from "../json-schema";
3
4
  import type { GlobalConfig } from "../../../types/global-config";
4
5
  export declare const jsonSchemaFullFeaturePlugin: import("../../../plugin-kit/plugin-definition").PluginDefinition<"jsonSchemaFullFeature", "jsonSchemaFullFeature", readonly TypeName[], {
5
- args: readonly [document: unknown];
6
+ args: readonly [document: unknown, options?: JsonSchemaOptions];
6
7
  out: Unchanged;
7
8
  context: MessageContextExtra;
8
9
  }>;
@@ -41,9 +41,11 @@ const SCHEMA_SLOTS = [
41
41
  ];
42
42
  exports.jsonSchemaFullFeaturePlugin = (0, plugin_definition_1.definePlugin)()({
43
43
  name: "jsonSchemaFullFeature",
44
+ // The document decides whether null is allowed, so null has to reach it.
45
+ judgesNull: true,
44
46
  method: "jsonSchemaFullFeature",
45
47
  slots: SCHEMA_SLOTS,
46
- build: (ctx, document) => json_schema_1.jsonSchemaPlugin.build(ctx, document, bundled_plugins_1.jsonSchemaBag),
48
+ build: (ctx, document, options) => json_schema_1.jsonSchemaPlugin.build(ctx, document, bundled_plugins_1.jsonSchemaBag, options),
47
49
  });
48
50
  /**
49
51
  * The documented one-import route:
@@ -37,9 +37,11 @@ const SCHEMA_SLOTS = [
37
37
  ];
38
38
  export const jsonSchemaFullFeaturePlugin = /*#__PURE__*/ definePlugin()({
39
39
  name: "jsonSchemaFullFeature",
40
+ // The document decides whether null is allowed, so null has to reach it.
41
+ judgesNull: true,
40
42
  method: "jsonSchemaFullFeature",
41
43
  slots: SCHEMA_SLOTS,
42
- build: (ctx, document) => jsonSchemaPlugin.build(ctx, document, jsonSchemaBag),
44
+ build: (ctx, document, options) => jsonSchemaPlugin.build(ctx, document, jsonSchemaBag, options),
43
45
  });
44
46
  /**
45
47
  * The documented one-import route:
@@ -0,0 +1,23 @@
1
+ /** `fragment` is what came AFTER the "#", so there is no prefix to strip. */
2
+ export declare function toPointerTokens(fragment: string): readonly string[];
3
+ /**
4
+ * `definitions` and `$defs` are the same container to a pointer: a Draft-07
5
+ * document spells it one way, a 2019-09 document the other, and a schema that
6
+ * mixes them (they exist) must still resolve. A real `definitions` member
7
+ * always wins, so a property literally named "definitions" is unaffected.
8
+ */
9
+ export declare function stepInto(current: unknown, token: string): unknown;
10
+ /**
11
+ * ポインタを辿りながら、**途中で跨いだ `$id` を数える**。
12
+ *
13
+ * `#/definitions/baz/definitions/bar` の `baz` が `$id: "folder/"` を持つとき、
14
+ * `bar` の中に書かれた相対 `$ref` は folder/ の下で解決されなければならない。
15
+ * 着地したノードの `$id` だけを見ると、通過したノードの分が落ちる。
16
+ *
17
+ * ベースをどう進めるかは呼び出し側が渡す (`advance`)。この関数は「どのノードを
18
+ * どの順に跨いだか」だけを知っていればよく、URI の演算は持たない。
19
+ */
20
+ export declare function walkPointer<TScope>(fragment: string, document: unknown, scope: TScope, advance: (scope: TScope, node: unknown) => TScope, onMissing: (token: string) => never): {
21
+ readonly node: unknown;
22
+ readonly scope: TScope;
23
+ };
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toPointerTokens = toPointerTokens;
4
+ exports.stepInto = stepInto;
5
+ exports.walkPointer = walkPointer;
6
+ // ===========================================================================
7
+ // L8 src/json-schema/follow-json-pointer.ts — RFC 6901 のポインタを辿る。
8
+ //
9
+ // `$ref` のうち **場所** を表す部分だけを担う。どの文書を見るかは
10
+ // schema-registry.ts と resolve-ref.ts の仕事で、ここは「その文書の中の
11
+ // どこか」だけを答える。分けてあるのは、`$id` によるベース URI の話と
12
+ // ポインタの復号の話が別物であり、混ぜると 200 行を超えて両方読みにくく
13
+ // なるからである。
14
+ // ===========================================================================
15
+ const types_1 = require("../types");
16
+ /** RFC 6901: `~1` is "/" and `~0` is "~", decoded in that order. */
17
+ function decodePointerToken(token) {
18
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
19
+ }
20
+ /**
21
+ * `$ref` は URI で、ポインタはそのフラグメント。RFC 6901 §6 は
22
+ * 「フラグメントの規則でパーセント符号化されている」と定めるので、
23
+ * **スラッシュで割る前にフラグメント全体を復号する**。
24
+ *
25
+ * 順序が意味を持つ。`#/definitions/percent%25field` は復号して
26
+ * `/definitions/percent%field` になり、そこで割ってトークンを得る。
27
+ * 先に割ってからトークンごとに復号すると `%25` は復号されるが、
28
+ * `%2F` が「区切りとしてのスラッシュ」に戻る仕様どおりの挙動にならない。
29
+ *
30
+ * 壊れたパーセント列 (`%zz`) は decodeURIComponent が投げるので、
31
+ * 復号できないポインタはそのまま扱う。ここで投げると、ポインタが1つ
32
+ * 壊れているだけで文書全体が読めなくなる。
33
+ */
34
+ function decodeFragment(pointer) {
35
+ try {
36
+ return decodeURIComponent(pointer);
37
+ }
38
+ catch {
39
+ return pointer;
40
+ }
41
+ }
42
+ /** `fragment` is what came AFTER the "#", so there is no prefix to strip. */
43
+ function toPointerTokens(fragment) {
44
+ const pointer = decodeFragment(fragment);
45
+ if (pointer === "")
46
+ return [];
47
+ // "/" は「ルート直下の空文字キー」であって空のトークン列ではない。
48
+ // ここを [] にすると `{"": ...}` を指すポインタがルートに化ける。
49
+ return pointer.split("/").slice(1).map(decodePointerToken);
50
+ }
51
+ /**
52
+ * `definitions` and `$defs` are the same container to a pointer: a Draft-07
53
+ * document spells it one way, a 2019-09 document the other, and a schema that
54
+ * mixes them (they exist) must still resolve. A real `definitions` member
55
+ * always wins, so a property literally named "definitions" is unaffected.
56
+ */
57
+ function stepInto(current, token) {
58
+ if ((0, types_1.isArray)(current))
59
+ return current[Number(token)];
60
+ if (!(0, types_1.isPlainObject)(current))
61
+ return undefined;
62
+ if (token === "definitions" || token === "$defs") {
63
+ return current["definitions"] ?? current["$defs"];
64
+ }
65
+ return current[token];
66
+ }
67
+ /**
68
+ * ポインタを辿りながら、**途中で跨いだ `$id` を数える**。
69
+ *
70
+ * `#/definitions/baz/definitions/bar` の `baz` が `$id: "folder/"` を持つとき、
71
+ * `bar` の中に書かれた相対 `$ref` は folder/ の下で解決されなければならない。
72
+ * 着地したノードの `$id` だけを見ると、通過したノードの分が落ちる。
73
+ *
74
+ * ベースをどう進めるかは呼び出し側が渡す (`advance`)。この関数は「どのノードを
75
+ * どの順に跨いだか」だけを知っていればよく、URI の演算は持たない。
76
+ */
77
+ function walkPointer(fragment, document, scope, advance, onMissing) {
78
+ let current = document;
79
+ let here = scope;
80
+ for (const token of toPointerTokens(fragment)) {
81
+ current = stepInto(current, token);
82
+ if (current === undefined)
83
+ onMissing(token);
84
+ here = advance(here, current);
85
+ }
86
+ return { node: current, scope: here };
87
+ }
@@ -0,0 +1,82 @@
1
+ // ===========================================================================
2
+ // L8 src/json-schema/follow-json-pointer.ts — RFC 6901 のポインタを辿る。
3
+ //
4
+ // `$ref` のうち **場所** を表す部分だけを担う。どの文書を見るかは
5
+ // schema-registry.ts と resolve-ref.ts の仕事で、ここは「その文書の中の
6
+ // どこか」だけを答える。分けてあるのは、`$id` によるベース URI の話と
7
+ // ポインタの復号の話が別物であり、混ぜると 200 行を超えて両方読みにくく
8
+ // なるからである。
9
+ // ===========================================================================
10
+ import { isArray, isPlainObject } from "../types/index.mjs";
11
+ /** RFC 6901: `~1` is "/" and `~0` is "~", decoded in that order. */
12
+ function decodePointerToken(token) {
13
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
14
+ }
15
+ /**
16
+ * `$ref` は URI で、ポインタはそのフラグメント。RFC 6901 §6 は
17
+ * 「フラグメントの規則でパーセント符号化されている」と定めるので、
18
+ * **スラッシュで割る前にフラグメント全体を復号する**。
19
+ *
20
+ * 順序が意味を持つ。`#/definitions/percent%25field` は復号して
21
+ * `/definitions/percent%field` になり、そこで割ってトークンを得る。
22
+ * 先に割ってからトークンごとに復号すると `%25` は復号されるが、
23
+ * `%2F` が「区切りとしてのスラッシュ」に戻る仕様どおりの挙動にならない。
24
+ *
25
+ * 壊れたパーセント列 (`%zz`) は decodeURIComponent が投げるので、
26
+ * 復号できないポインタはそのまま扱う。ここで投げると、ポインタが1つ
27
+ * 壊れているだけで文書全体が読めなくなる。
28
+ */
29
+ function decodeFragment(pointer) {
30
+ try {
31
+ return decodeURIComponent(pointer);
32
+ }
33
+ catch {
34
+ return pointer;
35
+ }
36
+ }
37
+ /** `fragment` is what came AFTER the "#", so there is no prefix to strip. */
38
+ export function toPointerTokens(fragment) {
39
+ const pointer = decodeFragment(fragment);
40
+ if (pointer === "")
41
+ return [];
42
+ // "/" は「ルート直下の空文字キー」であって空のトークン列ではない。
43
+ // ここを [] にすると `{"": ...}` を指すポインタがルートに化ける。
44
+ return pointer.split("/").slice(1).map(decodePointerToken);
45
+ }
46
+ /**
47
+ * `definitions` and `$defs` are the same container to a pointer: a Draft-07
48
+ * document spells it one way, a 2019-09 document the other, and a schema that
49
+ * mixes them (they exist) must still resolve. A real `definitions` member
50
+ * always wins, so a property literally named "definitions" is unaffected.
51
+ */
52
+ export function stepInto(current, token) {
53
+ if (isArray(current))
54
+ return current[Number(token)];
55
+ if (!isPlainObject(current))
56
+ return undefined;
57
+ if (token === "definitions" || token === "$defs") {
58
+ return current["definitions"] ?? current["$defs"];
59
+ }
60
+ return current[token];
61
+ }
62
+ /**
63
+ * ポインタを辿りながら、**途中で跨いだ `$id` を数える**。
64
+ *
65
+ * `#/definitions/baz/definitions/bar` の `baz` が `$id: "folder/"` を持つとき、
66
+ * `bar` の中に書かれた相対 `$ref` は folder/ の下で解決されなければならない。
67
+ * 着地したノードの `$id` だけを見ると、通過したノードの分が落ちる。
68
+ *
69
+ * ベースをどう進めるかは呼び出し側が渡す (`advance`)。この関数は「どのノードを
70
+ * どの順に跨いだか」だけを知っていればよく、URI の演算は持たない。
71
+ */
72
+ export function walkPointer(fragment, document, scope, advance, onMissing) {
73
+ let current = document;
74
+ let here = scope;
75
+ for (const token of toPointerTokens(fragment)) {
76
+ current = stepInto(current, token);
77
+ if (current === undefined)
78
+ onMissing(token);
79
+ here = advance(here, current);
80
+ }
81
+ return { node: current, scope: here };
82
+ }
@@ -1,5 +1,5 @@
1
1
  export { NotASchemaError, buildFieldEntries, buildFromSchema, fromJsonSchema, } from "./build-from-schema";
2
- export { DEFINITION_CONTAINERS, collectDefinitionNames, isDefinitionContainer, readRefPointer, resolveSchemaNode, toSchemaObject, } from "./collect-definitions";
2
+ export { DEFINITION_CONTAINERS, collectDefinitionNames, isDefinitionContainer, readRefPointer, resolveSchemaNode, resolveSchemaNodeInScope, toSchemaObject, } from "./collect-definitions";
3
3
  export { flattenSchema, joinDeclaredPath } from "./flatten-schema";
4
4
  export type { ReadChildSchemas, SchemaFieldDeclaration, } from "./flatten-schema";
5
5
  export { STRUCTURAL_EXPANSIONS, expandSchemaRules, isStructuralKeyword, readChildSchemas, } from "./schema-to-declarations";
@@ -10,7 +10,12 @@ export { declarePresenceRules } from "./declare-presence";
10
10
  export type { PresenceDeclaration } from "./declare-presence";
11
11
  export type { ConversionSeed } from "./create-structural-context";
12
12
  export type { ChildSchema, StructuralConsumer, StructuralContext, StructuralExpansion, StructuralExpansionTable, StructuralKeyword, } from "./structural-expansion.types";
13
- export { RefResolutionError, isResolvableRef, resolveRef } from "./resolve-ref";
13
+ export { RefResolutionError } from "./ref-resolution-error";
14
+ export { isResolvableRef, resolveRef, resolveRefInScope } from "./resolve-ref";
15
+ export type { RefScope } from "./ref-scope";
16
+ export { createDocumentScope, createLocalScope } from "./ref-scope";
17
+ export type { SchemaRegistry } from "./schema-registry";
18
+ export { createSchemaRegistry } from "./schema-registry";
14
19
  export { UnsupportedKeywordError } from "./unsupported-keyword-error";
15
20
  export { isDraft07Schema, isSchemaObject } from "./draft07.types";
16
21
  export type { Draft07Schema, Draft07SchemaObject, Draft07TypeKeyword, } from "./draft07.types";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.listFormatNames = exports.findFormatHandling = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Keyword = exports.findKeywordHandling = exports.countKeywordHandlings = exports.assertKeywordSupported = exports.isSchemaObject = exports.isDraft07Schema = exports.UnsupportedKeywordError = exports.resolveRef = exports.isResolvableRef = exports.RefResolutionError = exports.declarePresenceRules = exports.declareRequiredProperties = exports.createStructuralContext = exports.toSchemaBranch = exports.collectSubSchemaRules = exports.readChildSchemas = exports.isStructuralKeyword = exports.expandSchemaRules = exports.STRUCTURAL_EXPANSIONS = exports.joinDeclaredPath = exports.flattenSchema = exports.toSchemaObject = exports.resolveSchemaNode = exports.readRefPointer = exports.isDefinitionContainer = exports.collectDefinitionNames = exports.DEFINITION_CONTAINERS = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.NotASchemaError = void 0;
3
+ exports.listFormatNames = exports.findFormatHandling = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Keyword = exports.findKeywordHandling = exports.countKeywordHandlings = exports.assertKeywordSupported = exports.isSchemaObject = exports.isDraft07Schema = exports.UnsupportedKeywordError = exports.createSchemaRegistry = exports.createLocalScope = exports.createDocumentScope = exports.resolveRefInScope = exports.resolveRef = exports.isResolvableRef = exports.RefResolutionError = exports.declarePresenceRules = exports.declareRequiredProperties = exports.createStructuralContext = exports.toSchemaBranch = exports.collectSubSchemaRules = exports.readChildSchemas = exports.isStructuralKeyword = exports.expandSchemaRules = exports.STRUCTURAL_EXPANSIONS = exports.joinDeclaredPath = exports.flattenSchema = exports.toSchemaObject = exports.resolveSchemaNodeInScope = exports.resolveSchemaNode = exports.readRefPointer = exports.isDefinitionContainer = exports.collectDefinitionNames = exports.DEFINITION_CONTAINERS = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.NotASchemaError = void 0;
4
4
  // ===========================================================================
5
5
  // L8 src/json-schema/index.ts — re-exports only. Nothing is defined here.
6
6
  //
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "collectDefinitionNames", { enumerable: true, get
18
18
  Object.defineProperty(exports, "isDefinitionContainer", { enumerable: true, get: function () { return collect_definitions_1.isDefinitionContainer; } });
19
19
  Object.defineProperty(exports, "readRefPointer", { enumerable: true, get: function () { return collect_definitions_1.readRefPointer; } });
20
20
  Object.defineProperty(exports, "resolveSchemaNode", { enumerable: true, get: function () { return collect_definitions_1.resolveSchemaNode; } });
21
+ Object.defineProperty(exports, "resolveSchemaNodeInScope", { enumerable: true, get: function () { return collect_definitions_1.resolveSchemaNodeInScope; } });
21
22
  Object.defineProperty(exports, "toSchemaObject", { enumerable: true, get: function () { return collect_definitions_1.toSchemaObject; } });
22
23
  var flatten_schema_1 = require("./flatten-schema");
23
24
  Object.defineProperty(exports, "flattenSchema", { enumerable: true, get: function () { return flatten_schema_1.flattenSchema; } });
@@ -36,10 +37,17 @@ var declare_required_properties_1 = require("./declare-required-properties");
36
37
  Object.defineProperty(exports, "declareRequiredProperties", { enumerable: true, get: function () { return declare_required_properties_1.declareRequiredProperties; } });
37
38
  var declare_presence_1 = require("./declare-presence");
38
39
  Object.defineProperty(exports, "declarePresenceRules", { enumerable: true, get: function () { return declare_presence_1.declarePresenceRules; } });
40
+ var ref_resolution_error_1 = require("./ref-resolution-error");
41
+ Object.defineProperty(exports, "RefResolutionError", { enumerable: true, get: function () { return ref_resolution_error_1.RefResolutionError; } });
39
42
  var resolve_ref_1 = require("./resolve-ref");
40
- Object.defineProperty(exports, "RefResolutionError", { enumerable: true, get: function () { return resolve_ref_1.RefResolutionError; } });
41
43
  Object.defineProperty(exports, "isResolvableRef", { enumerable: true, get: function () { return resolve_ref_1.isResolvableRef; } });
42
44
  Object.defineProperty(exports, "resolveRef", { enumerable: true, get: function () { return resolve_ref_1.resolveRef; } });
45
+ Object.defineProperty(exports, "resolveRefInScope", { enumerable: true, get: function () { return resolve_ref_1.resolveRefInScope; } });
46
+ var ref_scope_1 = require("./ref-scope");
47
+ Object.defineProperty(exports, "createDocumentScope", { enumerable: true, get: function () { return ref_scope_1.createDocumentScope; } });
48
+ Object.defineProperty(exports, "createLocalScope", { enumerable: true, get: function () { return ref_scope_1.createLocalScope; } });
49
+ var schema_registry_1 = require("./schema-registry");
50
+ Object.defineProperty(exports, "createSchemaRegistry", { enumerable: true, get: function () { return schema_registry_1.createSchemaRegistry; } });
43
51
  var unsupported_keyword_error_1 = require("./unsupported-keyword-error");
44
52
  Object.defineProperty(exports, "UnsupportedKeywordError", { enumerable: true, get: function () { return unsupported_keyword_error_1.UnsupportedKeywordError; } });
45
53
  var draft07_types_1 = require("./draft07.types");
@@ -5,14 +5,17 @@
5
5
  // the only json-schema surface anything outside src/json-schema/ may reach.
6
6
  // ===========================================================================
7
7
  export { NotASchemaError, buildFieldEntries, buildFromSchema, fromJsonSchema, } from "./build-from-schema.mjs";
8
- export { DEFINITION_CONTAINERS, collectDefinitionNames, isDefinitionContainer, readRefPointer, resolveSchemaNode, toSchemaObject, } from "./collect-definitions.mjs";
8
+ export { DEFINITION_CONTAINERS, collectDefinitionNames, isDefinitionContainer, readRefPointer, resolveSchemaNode, resolveSchemaNodeInScope, toSchemaObject, } from "./collect-definitions.mjs";
9
9
  export { flattenSchema, joinDeclaredPath } from "./flatten-schema.mjs";
10
10
  export { STRUCTURAL_EXPANSIONS, expandSchemaRules, isStructuralKeyword, readChildSchemas, } from "./schema-to-declarations.mjs";
11
11
  export { collectSubSchemaRules, toSchemaBranch, } from "./collect-sub-schema-rules.mjs";
12
12
  export { createStructuralContext } from "./create-structural-context.mjs";
13
13
  export { declareRequiredProperties } from "./declare-required-properties.mjs";
14
14
  export { declarePresenceRules } from "./declare-presence.mjs";
15
- export { RefResolutionError, isResolvableRef, resolveRef } from "./resolve-ref.mjs";
15
+ export { RefResolutionError } from "./ref-resolution-error.mjs";
16
+ export { isResolvableRef, resolveRef, resolveRefInScope } from "./resolve-ref.mjs";
17
+ export { createDocumentScope, createLocalScope } from "./ref-scope.mjs";
18
+ export { createSchemaRegistry } from "./schema-registry.mjs";
16
19
  export { UnsupportedKeywordError } from "./unsupported-keyword-error.mjs";
17
20
  export { isDraft07Schema, isSchemaObject } from "./draft07.types.mjs";
18
21
  export { assertKeywordSupported, countKeywordHandlings, findKeywordHandling, isDraft07Keyword, listBoundPluginNames, listDraft07Keywords, } from "./keyword-map.mjs";
@@ -13,5 +13,5 @@ export declare const requiredBinding: import("./keyword-binding.types").KeywordB
13
13
  * to the declared sibling keys, so `additionalProperties: false` no longer
14
14
  * rejects every property the way 1.x's empty `allowedProperties` default did.
15
15
  */
16
- export declare const additionalPropertiesBinding: import("./keyword-binding.types").KeywordBinding<"object", "additionalProperties", "objectAdditionalProperties", readonly [allowed: boolean, allowedProperties?: readonly string[] | undefined], boolean>;
16
+ export declare const additionalPropertiesBinding: import("./keyword-binding.types").KeywordBinding<"object", "additionalProperties", "objectAdditionalProperties", readonly [allowed: boolean, allowedProperties?: readonly string[] | undefined, allowedPatterns?: readonly string[] | undefined], boolean>;
17
17
  export declare const objectKeywordMap: KeywordTable<Draft07ObjectKeyword>;
@@ -0,0 +1,4 @@
1
+ export declare class RefResolutionError extends Error {
2
+ readonly ref: string;
3
+ constructor(ref: string, reason: string);
4
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ // ===========================================================================
3
+ // L8 src/json-schema/ref-resolution-error.ts
4
+ //
5
+ // `$ref` が解けなかったことを表す例外。呼び出し側が **identity で捕まえられる**
6
+ // ことが要件なので、独立したクラスであり、独立したモジュールである
7
+ // (resolve-ref.ts から import すると、その巨大な依存を一緒に引く)。
8
+ // ===========================================================================
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.RefResolutionError = void 0;
11
+ class RefResolutionError extends Error {
12
+ constructor(ref, reason) {
13
+ super(`Cannot resolve $ref "${ref}": ${reason}`);
14
+ this.name = "RefResolutionError";
15
+ this.ref = ref;
16
+ // Without this, `instanceof` fails when the package is compiled to ES5.
17
+ Object.setPrototypeOf(this, RefResolutionError.prototype);
18
+ }
19
+ }
20
+ exports.RefResolutionError = RefResolutionError;
@@ -0,0 +1,16 @@
1
+ // ===========================================================================
2
+ // L8 src/json-schema/ref-resolution-error.ts
3
+ //
4
+ // `$ref` が解けなかったことを表す例外。呼び出し側が **identity で捕まえられる**
5
+ // ことが要件なので、独立したクラスであり、独立したモジュールである
6
+ // (resolve-ref.ts から import すると、その巨大な依存を一緒に引く)。
7
+ // ===========================================================================
8
+ export class RefResolutionError extends Error {
9
+ constructor(ref, reason) {
10
+ super(`Cannot resolve $ref "${ref}": ${reason}`);
11
+ this.name = "RefResolutionError";
12
+ this.ref = ref;
13
+ // Without this, `instanceof` fails when the package is compiled to ES5.
14
+ Object.setPrototypeOf(this, RefResolutionError.prototype);
15
+ }
16
+ }
@@ -0,0 +1,18 @@
1
+ import type { Draft07Schema } from "./draft07.types";
2
+ import type { SchemaRegistry } from "./schema-registry";
3
+ export interface RefScope {
4
+ /** The resource a fragment-only reference (`#/definitions/x`) resolves in. */
5
+ readonly document: Draft07Schema;
6
+ /** The base URI in force here, "" for a document that declares no `$id`. */
7
+ readonly baseUri: string;
8
+ /** Everything a URI can name: `$id` bases, anchors, supplied documents. */
9
+ readonly registry: SchemaRegistry;
10
+ }
11
+ /**
12
+ * The scope of a document that declares no `$id` and reaches nothing else.
13
+ * Used by callers that hold a root and nothing more, and by every test that
14
+ * is about pointers rather than about identity.
15
+ */
16
+ export declare function createLocalScope(root: Draft07Schema): RefScope;
17
+ /** A scope over a root plus the documents the caller already loaded. */
18
+ export declare function createDocumentScope(root: Draft07Schema, externalDocuments: Readonly<Record<string, unknown>>): RefScope;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLocalScope = createLocalScope;
4
+ exports.createDocumentScope = createDocumentScope;
5
+ const draft07_types_1 = require("./draft07.types");
6
+ const schema_registry_1 = require("./schema-registry");
7
+ const uri_reference_1 = require("./uri-reference");
8
+ /**
9
+ * A document's own `$id` is where its base URI starts. Beginning at "" and
10
+ * ignoring it is how `{"$id":"http://host/a","properties":{"x":{"$ref":"b.json"}}}`
11
+ * ends up looking for a document literally named "b.json".
12
+ */
13
+ function baseUriOf(root) {
14
+ if (!(0, draft07_types_1.isSchemaObject)(root) || typeof root.$id !== "string")
15
+ return "";
16
+ return (0, uri_reference_1.nextBaseUri)("", root.$id);
17
+ }
18
+ /**
19
+ * The scope of a document that declares no `$id` and reaches nothing else.
20
+ * Used by callers that hold a root and nothing more, and by every test that
21
+ * is about pointers rather than about identity.
22
+ */
23
+ function createLocalScope(root) {
24
+ return {
25
+ document: root,
26
+ baseUri: baseUriOf(root),
27
+ registry: (0, schema_registry_1.createSchemaRegistry)(root),
28
+ };
29
+ }
30
+ /** A scope over a root plus the documents the caller already loaded. */
31
+ function createDocumentScope(root, externalDocuments) {
32
+ return {
33
+ document: root,
34
+ baseUri: baseUriOf(root),
35
+ registry: (0, schema_registry_1.createSchemaRegistry)(root, externalDocuments),
36
+ };
37
+ }
@@ -0,0 +1,33 @@
1
+ import { isSchemaObject } from "./draft07.types.mjs";
2
+ import { createSchemaRegistry } from "./schema-registry.mjs";
3
+ import { nextBaseUri } from "./uri-reference.mjs";
4
+ /**
5
+ * A document's own `$id` is where its base URI starts. Beginning at "" and
6
+ * ignoring it is how `{"$id":"http://host/a","properties":{"x":{"$ref":"b.json"}}}`
7
+ * ends up looking for a document literally named "b.json".
8
+ */
9
+ function baseUriOf(root) {
10
+ if (!isSchemaObject(root) || typeof root.$id !== "string")
11
+ return "";
12
+ return nextBaseUri("", root.$id);
13
+ }
14
+ /**
15
+ * The scope of a document that declares no `$id` and reaches nothing else.
16
+ * Used by callers that hold a root and nothing more, and by every test that
17
+ * is about pointers rather than about identity.
18
+ */
19
+ export function createLocalScope(root) {
20
+ return {
21
+ document: root,
22
+ baseUri: baseUriOf(root),
23
+ registry: createSchemaRegistry(root),
24
+ };
25
+ }
26
+ /** A scope over a root plus the documents the caller already loaded. */
27
+ export function createDocumentScope(root, externalDocuments) {
28
+ return {
29
+ document: root,
30
+ baseUri: baseUriOf(root),
31
+ registry: createSchemaRegistry(root, externalDocuments),
32
+ };
33
+ }