@maroonedog/luq 2.0.0 → 2.2.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 (165) hide show
  1. package/README.md +364 -57
  2. package/dist/builder/builder-surface.types.d.ts +1 -0
  3. package/dist/builder/create-builder.js +9 -0
  4. package/dist/builder/create-builder.mjs +9 -0
  5. package/dist/builder/field-builder.types.d.ts +13 -0
  6. package/dist/chain/bundle-paths.types.d.ts +26 -0
  7. package/dist/chain/bundle-paths.types.js +2 -0
  8. package/dist/chain/bundle-paths.types.mjs +1 -0
  9. package/dist/chain/chain-method.types.d.ts +8 -3
  10. package/dist/chain/create-chain-node.js +22 -1
  11. package/dist/chain/create-chain-node.mjs +22 -1
  12. package/dist/chain/resolve-args.types.d.ts +4 -2
  13. package/dist/compile/compile-array-node.d.ts +0 -7
  14. package/dist/compile/compile-array-node.js +4 -0
  15. package/dist/compile/compile-array-node.mjs +4 -0
  16. package/dist/compile/compile-field.js +10 -1
  17. package/dist/compile/compile-field.mjs +10 -1
  18. package/dist/compile/resolve-conditional-presence.js +1 -0
  19. package/dist/compile/resolve-conditional-presence.mjs +1 -0
  20. package/dist/compile/resolve-presence.js +4 -0
  21. package/dist/compile/resolve-presence.mjs +4 -0
  22. package/dist/compile/split-rules-by-kind.js +31 -7
  23. package/dist/compile/split-rules-by-kind.mjs +31 -7
  24. package/dist/compile/validation-plan.types.d.ts +33 -0
  25. package/dist/json-schema/build-from-schema.js +2 -3
  26. package/dist/json-schema/build-from-schema.mjs +2 -3
  27. package/dist/json-schema/collect-definitions.d.ts +30 -0
  28. package/dist/json-schema/collect-definitions.js +47 -3
  29. package/dist/json-schema/collect-definitions.mjs +46 -4
  30. package/dist/json-schema/collect-sub-schema-rules.d.ts +12 -1
  31. package/dist/json-schema/collect-sub-schema-rules.js +37 -7
  32. package/dist/json-schema/collect-sub-schema-rules.mjs +38 -8
  33. package/dist/json-schema/create-structural-context.d.ts +13 -3
  34. package/dist/json-schema/create-structural-context.js +72 -6
  35. package/dist/json-schema/create-structural-context.mjs +72 -7
  36. package/dist/json-schema/declare-additional-properties.d.ts +19 -0
  37. package/dist/json-schema/declare-additional-properties.js +42 -0
  38. package/dist/json-schema/declare-additional-properties.mjs +37 -0
  39. package/dist/json-schema/declare-object-keywords.d.ts +0 -2
  40. package/dist/json-schema/declare-object-keywords.js +6 -13
  41. package/dist/json-schema/declare-object-keywords.mjs +7 -13
  42. package/dist/json-schema/declare-presence.d.ts +9 -3
  43. package/dist/json-schema/declare-presence.js +12 -6
  44. package/dist/json-schema/declare-presence.mjs +12 -6
  45. package/dist/json-schema/extensions/json-schema/index.d.ts +1 -0
  46. package/dist/json-schema/extensions/json-schema/json-schema.d.ts +17 -2
  47. package/dist/json-schema/extensions/json-schema/json-schema.js +15 -7
  48. package/dist/json-schema/extensions/json-schema/json-schema.mjs +16 -8
  49. package/dist/json-schema/extensions/json-schema-full-feature/index.d.ts +1 -0
  50. package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.d.ts +2 -1
  51. package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.js +3 -1
  52. package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.mjs +3 -1
  53. package/dist/json-schema/flatten-schema.js +1 -1
  54. package/dist/json-schema/flatten-schema.mjs +1 -1
  55. package/dist/json-schema/follow-json-pointer.d.ts +23 -0
  56. package/dist/json-schema/follow-json-pointer.js +87 -0
  57. package/dist/json-schema/follow-json-pointer.mjs +82 -0
  58. package/dist/json-schema/index.d.ts +7 -2
  59. package/dist/json-schema/index.js +10 -2
  60. package/dist/json-schema/index.mjs +5 -2
  61. package/dist/json-schema/keyword-map-object.d.ts +1 -1
  62. package/dist/json-schema/ref-resolution-error.d.ts +4 -0
  63. package/dist/json-schema/ref-resolution-error.js +20 -0
  64. package/dist/json-schema/ref-resolution-error.mjs +16 -0
  65. package/dist/json-schema/ref-scope.d.ts +18 -0
  66. package/dist/json-schema/ref-scope.js +37 -0
  67. package/dist/json-schema/ref-scope.mjs +33 -0
  68. package/dist/json-schema/resolve-ref.d.ts +14 -9
  69. package/dist/json-schema/resolve-ref.js +89 -59
  70. package/dist/json-schema/resolve-ref.mjs +86 -55
  71. package/dist/json-schema/schema-registry.d.ts +30 -0
  72. package/dist/json-schema/schema-registry.js +142 -0
  73. package/dist/json-schema/schema-registry.mjs +139 -0
  74. package/dist/json-schema/schema-to-declarations.js +2 -1
  75. package/dist/json-schema/schema-to-declarations.mjs +2 -1
  76. package/dist/json-schema/structural-expansion.types.d.ts +8 -1
  77. package/dist/json-schema/uri-reference.d.ts +29 -0
  78. package/dist/json-schema/uri-reference.js +118 -0
  79. package/dist/json-schema/uri-reference.mjs +111 -0
  80. package/dist/path/create-value-writer.js +27 -2
  81. package/dist/path/create-value-writer.mjs +27 -2
  82. package/dist/path/reserved-segment.d.ts +22 -4
  83. package/dist/path/reserved-segment.js +27 -7
  84. package/dist/path/reserved-segment.mjs +27 -7
  85. package/dist/plugin-kit/compiled-rule.d.ts +13 -0
  86. package/dist/plugin-kit/create-rule.d.ts +11 -0
  87. package/dist/plugin-kit/create-rule.js +1 -0
  88. package/dist/plugin-kit/create-rule.mjs +1 -0
  89. package/dist/plugin-kit/marker.types.d.ts +18 -0
  90. package/dist/plugin-kit/plugin-definition.d.ts +15 -0
  91. package/dist/plugin-kit/plugin-definition.js +1 -0
  92. package/dist/plugin-kit/plugin-definition.mjs +1 -0
  93. package/dist/plugins/index.generated.d.ts +1 -0
  94. package/dist/plugins/index.generated.js +4 -2
  95. package/dist/plugins/index.generated.mjs +1 -0
  96. package/dist/plugins/manifest.generated.js +1 -0
  97. package/dist/plugins/manifest.generated.mjs +1 -0
  98. package/dist/plugins/object-additional-properties/object-additional-properties-schema.d.ts +1 -1
  99. package/dist/plugins/object-additional-properties/object-additional-properties-schema.js +4 -2
  100. package/dist/plugins/object-additional-properties/object-additional-properties-schema.mjs +4 -2
  101. package/dist/plugins/object-additional-properties/object-additional-properties.d.ts +1 -1
  102. package/dist/plugins/object-additional-properties/object-additional-properties.js +4 -2
  103. package/dist/plugins/object-additional-properties/object-additional-properties.mjs +4 -2
  104. package/dist/plugins/object-additional-properties/select-additional-keys.d.ts +15 -0
  105. package/dist/plugins/object-additional-properties/select-additional-keys.js +51 -0
  106. package/dist/plugins/object-additional-properties/select-additional-keys.mjs +47 -0
  107. package/dist/plugins/stitch/stitch.d.ts +28 -8
  108. package/dist/plugins/stitch-with/index.d.ts +2 -0
  109. package/dist/plugins/stitch-with/index.js +5 -0
  110. package/dist/plugins/stitch-with/index.mjs +1 -0
  111. package/dist/plugins/stitch-with/stitch-with.d.ts +12 -0
  112. package/dist/plugins/stitch-with/stitch-with.js +94 -0
  113. package/dist/plugins/stitch-with/stitch-with.mjs +91 -0
  114. package/dist/plugins/stitchWith.d.ts +1 -0
  115. package/dist/plugins/stitchWith.js +2 -0
  116. package/dist/plugins/stitchWith.mjs +1 -0
  117. package/dist/plugins/string-exact-length/string-exact-length.js +2 -2
  118. package/dist/plugins/string-exact-length/string-exact-length.mjs +3 -3
  119. package/dist/plugins/string-max/string-max.js +2 -2
  120. package/dist/plugins/string-max/string-max.mjs +3 -3
  121. package/dist/plugins/string-min/string-min.js +21 -3
  122. package/dist/plugins/string-min/string-min.mjs +21 -3
  123. package/dist/presets/index.d.ts +1 -0
  124. package/dist/presets/index.js +9 -0
  125. package/dist/presets/index.mjs +1 -0
  126. package/dist/presets/presets.d.ts +165 -0
  127. package/dist/presets/presets.js +80 -0
  128. package/dist/presets/presets.mjs +77 -0
  129. package/dist/runtime/create-field-validator.js +24 -8
  130. package/dist/runtime/create-field-validator.mjs +24 -8
  131. package/dist/runtime/create-validator.js +38 -8
  132. package/dist/runtime/create-validator.mjs +38 -8
  133. package/dist/runtime/decide-presence.js +2 -1
  134. package/dist/runtime/decide-presence.mjs +2 -1
  135. package/dist/runtime/field-rule-context.d.ts +32 -0
  136. package/dist/runtime/field-rule-context.js +48 -0
  137. package/dist/runtime/field-rule-context.mjs +44 -0
  138. package/dist/runtime/index-stack.d.ts +29 -4
  139. package/dist/runtime/index-stack.js +76 -15
  140. package/dist/runtime/index-stack.mjs +76 -15
  141. package/dist/runtime/run-array-node.js +22 -10
  142. package/dist/runtime/run-array-node.mjs +22 -10
  143. package/dist/runtime/run-field.js +47 -17
  144. package/dist/runtime/run-field.mjs +47 -17
  145. package/dist/runtime/run-plan.js +5 -1
  146. package/dist/runtime/run-plan.mjs +5 -1
  147. package/dist/standard-schema/index.d.ts +6 -0
  148. package/dist/standard-schema/index.js +7 -0
  149. package/dist/standard-schema/index.mjs +2 -0
  150. package/dist/standard-schema/split-issue-path.d.ts +9 -0
  151. package/dist/standard-schema/split-issue-path.js +66 -0
  152. package/dist/standard-schema/split-issue-path.mjs +63 -0
  153. package/dist/standard-schema/standard-schema.types.d.ts +46 -0
  154. package/dist/standard-schema/standard-schema.types.js +13 -0
  155. package/dist/standard-schema/standard-schema.types.mjs +12 -0
  156. package/dist/standard-schema/to-standard-schema.d.ts +41 -0
  157. package/dist/standard-schema/to-standard-schema.js +60 -0
  158. package/dist/standard-schema/to-standard-schema.mjs +57 -0
  159. package/dist/standard-schema.d.ts +1 -0
  160. package/dist/standard-schema.js +2 -0
  161. package/dist/standard-schema.mjs +1 -0
  162. package/dist/types/index.d.ts +24 -0
  163. package/dist/types/index.js +18 -0
  164. package/dist/types/index.mjs +17 -0
  165. package/package.json +42 -19
@@ -1,6 +1,16 @@
1
+ import type { CrossFieldOutcome } from "../../types";
1
2
  import type { PickPaths } from "../../path/index";
2
- import type { FieldRefs, Unchanged } from "../../plugin-kit/marker.types";
3
- /** The bundle handed to the check, keyed by the path exactly as declared. */
3
+ import type { FieldRefs, StitchOut } from "../../plugin-kit/marker.types";
4
+ /**
5
+ * The bundle handed to the check at RUN TIME, keyed by the path exactly as
6
+ * declared.
7
+ *
8
+ * 呼び出し側がこれを見ることはもう無い。`.stitch(["price"], ...)` と書いた
9
+ * 時点でパスの集合は分かっているので、述語が受け取る束の型は
10
+ * `PickPaths<TRoot, F>` として組まれる (src/chain/chain-method.types.ts の
11
+ * StitchOut の腕)。この型が残っているのは、実行時に集める側が「キーは
12
+ * パス文字列」という事実を書き留めておく場所だからである。
13
+ */
4
14
  export type StitchFieldValues = Readonly<Record<string, unknown>>;
5
15
  /**
6
16
  * PickPaths applied to a declared tuple: `StitchFieldsOf<Order, ["price",
@@ -11,11 +21,21 @@ export type StitchFieldValues = Readonly<Record<string, unknown>>;
11
21
  * the same alternative-2 move readExternalContext makes for async context.
12
22
  */
13
23
  export type StitchFieldsOf<TRoot, TFields extends readonly string[]> = PickPaths<TRoot, TFields>;
14
- /** 1.x's `{ valid, message? }`, unchanged. */
15
- export interface StitchOutcome {
16
- readonly valid: boolean;
17
- readonly message?: string;
18
- }
24
+ /**
25
+ * 1.x の `{ valid, message? }`、名前も形もそのまま。実体は src/types の
26
+ * CrossFieldOutcome で、チェーン層が呼び出し側の型を組むのに参照する —
27
+ * L3 から L7 を import しないための置き場である。
28
+ */
29
+ export type StitchOutcome = CrossFieldOutcome;
30
+ /**
31
+ * 実行時に build() が受け取る形。**呼び出し側が見る型ではない。**
32
+ *
33
+ * `.stitch(["price", "quantity"], ...)` と書いた時点でパスの集合は分かって
34
+ * いるので、述語が受け取る束は `PickPaths<TRoot, F>` として組まれ、キーごとに
35
+ * 値の型が付く (src/chain/chain-method.types.ts の StitchOut の腕)。
36
+ * ここが `Record<string, unknown>` のままだったのが、その腕を足すまでの
37
+ * stitch である。
38
+ */
19
39
  export type StitchCheck = (fieldValues: StitchFieldValues, value: unknown, root: unknown) => StitchOutcome;
20
40
  /** 1.x's messageFactory context for this plugin, minus the re-run. */
21
41
  export interface StitchExtra {
@@ -25,6 +45,6 @@ export interface StitchExtra {
25
45
  }
26
46
  export declare const stitchPlugin: import("../../plugin-kit/plugin-definition").PluginDefinition<"stitch", "stitch", readonly ["string", "number", "boolean", "date", "object", "array", "tuple", "union"], {
27
47
  args: readonly [fields: FieldRefs, check: StitchCheck];
28
- out: Unchanged;
48
+ out: StitchOut;
29
49
  context: StitchExtra;
30
50
  }>;
@@ -0,0 +1,2 @@
1
+ export { stitchWithPlugin } from "./stitch-with";
2
+ export type { BundleAliasMap, StitchWithExtra } from "./stitch-with";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stitchWithPlugin = void 0;
4
+ var stitch_with_1 = require("./stitch-with");
5
+ Object.defineProperty(exports, "stitchWithPlugin", { enumerable: true, get: function () { return stitch_with_1.stitchWithPlugin; } });
@@ -0,0 +1 @@
1
+ export { stitchWithPlugin } from "./stitch-with.mjs";
@@ -0,0 +1,12 @@
1
+ import type { MessageContextExtra } from "../../types";
2
+ import type { BundleOut, NarrowedChain } from "../../plugin-kit/marker.types";
3
+ /** 別名 -> ルートのパス。実行時はただの文字列の対応表である。 */
4
+ export type BundleAliasMap = Readonly<Record<string, string>>;
5
+ export interface StitchWithExtra extends MessageContextExtra {
6
+ readonly aliases: readonly string[];
7
+ }
8
+ export declare const stitchWithPlugin: import("../../plugin-kit/plugin-definition").PluginDefinition<"stitchWith", "stitchWith", readonly ["string", "number", "boolean", "date", "object", "array", "tuple", "union"], {
9
+ args: readonly [fields: BundleAliasMap, define: NarrowedChain];
10
+ out: BundleOut;
11
+ context: StitchWithExtra;
12
+ }>;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stitchWithPlugin = void 0;
4
+ // ===========================================================================
5
+ // L7 src/plugins/stitch-with/stitch-with.ts — EXPERIMENTAL.
6
+ // `stitch` の型付き後継。クロスフィールド検証のためのメソッドである。
7
+ //
8
+ // stitch の核は「**複数のフィールドを1つの判定にまとめる**」ことなので、
9
+ // 主体は束そのものであって、別名ごとではない。別名ごとにルールを並べる形も
10
+ // 書けるが、それでは `total === price * quantity` が書けず、stitch では
11
+ // なくなる。
12
+ //
13
+ // stitch との違いは一点だけである。stitch は束を
14
+ // `Readonly<Record<string, unknown>>` として手書きの述語に渡すので、束の中身に
15
+ // ついて型が何も言わない。ここでは束が対応表から組まれて **型が付く**:
16
+ //
17
+ // .v("total", (b) => b.number.stitchWith(
18
+ // { cost: "price", count: "quantity" },
19
+ // (f) => f.object.custom((bundle) => bundle.cost * bundle.count === 100)
20
+ // ))
21
+ //
22
+ // `bundle` は `{ cost: number; count: number }` であって Record ではない。
23
+ // メンバー名を綴り違えれば、型を取り違えれば、コンパイルエラーになる。
24
+ //
25
+ // なぜ別名を経由するのか。束をパス文字列でキーすると、その文字列は宣言の場で
26
+ // **パスとして** 解釈される: `"user.name"` は束の中の `user.name` を探しに
27
+ // 行き、束は平たいので見つからない (実測して分かった)。別名は素の識別子なので
28
+ // その衝突が起きず、参照できるのは宣言した別名だけになる。
29
+ //
30
+ // このファイルに判定は無い。サブチェーンは NarrowedChain と同じ経路で
31
+ // `readonly Rule[]` に解決され、branch がそれを枝にし、エンジンが走らせる。
32
+ // 束専用の収集器をコアに置く案も作って動かしたが、実測でコアが 220 B 増えた
33
+ // (7,590 -> 7,810 B)。stitchWith を使わない利用者が払う形なので採らなかった。
34
+ // 既存の経路に乗せると追加は 0 B である。
35
+ //
36
+ // 1.x が同じ責務の実装を3つ持っていたのは、ここで「もう1つ書く」を選んだ
37
+ // からである。
38
+ // ===========================================================================
39
+ const types_1 = require("../../types");
40
+ const create_rule_1 = require("../../plugin-kit/create-rule");
41
+ const plugin_definition_1 = require("../../plugin-kit/plugin-definition");
42
+ const index_1 = require("../../path/index");
43
+ const STITCH_WITH_SLOTS = [
44
+ "string",
45
+ "number",
46
+ "boolean",
47
+ "date",
48
+ "object",
49
+ "array",
50
+ "tuple",
51
+ "union",
52
+ ];
53
+ const BUNDLE_BRANCH_LABEL = "bundle";
54
+ function readMembers(aliasMap) {
55
+ return Object.freeze(Object.entries(aliasMap).map(([alias, path]) => ({
56
+ alias,
57
+ read: (0, index_1.createValueReader)((0, index_1.parseFieldPath)(path)),
58
+ })));
59
+ }
60
+ /** ルートから束を組む。枝の主体はこのオブジェクトになる。 */
61
+ function collectBundle(members, root) {
62
+ const bundle = {};
63
+ for (const member of members)
64
+ bundle[member.alias] = member.read(root);
65
+ return bundle;
66
+ }
67
+ exports.stitchWithPlugin = (0, plugin_definition_1.definePlugin)()({
68
+ name: "stitchWith",
69
+ method: "stitchWith",
70
+ slots: STITCH_WITH_SLOTS,
71
+ build: (ctx, aliasMap, rules) => {
72
+ const members = readMembers((0, types_1.isPlainObject)(aliasMap) ? aliasMap : {});
73
+ const aliases = Object.freeze(members.map((member) => member.alias));
74
+ return (0, create_rule_1.composite)({
75
+ code: ctx.code,
76
+ messageFactory: ctx.messageFactory,
77
+ severity: ctx.severity,
78
+ branches: [(0, create_rule_1.branch)(BUNDLE_BRANCH_LABEL, rules)],
79
+ // 主体の値は見ない。見るのはルートから組んだ束だけである。
80
+ combine: (runners) => {
81
+ const runner = runners[0];
82
+ if (runner === undefined)
83
+ return () => types_1.PASS;
84
+ return (_value, runCtx) => {
85
+ const outcome = runner.run(collectBundle(members, runCtx.root), runCtx);
86
+ return outcome.ok ? types_1.PASS : (0, types_1.fail)({ ...outcome.detail });
87
+ };
88
+ },
89
+ describe: (_detail, msgCtx) => `Cross-field validation failed for ${msgCtx.path}`,
90
+ buildMessageContext: () => ({ aliases }),
91
+ });
92
+ },
93
+ subChainArguments: [1],
94
+ });
@@ -0,0 +1,91 @@
1
+ // ===========================================================================
2
+ // L7 src/plugins/stitch-with/stitch-with.ts — EXPERIMENTAL.
3
+ // `stitch` の型付き後継。クロスフィールド検証のためのメソッドである。
4
+ //
5
+ // stitch の核は「**複数のフィールドを1つの判定にまとめる**」ことなので、
6
+ // 主体は束そのものであって、別名ごとではない。別名ごとにルールを並べる形も
7
+ // 書けるが、それでは `total === price * quantity` が書けず、stitch では
8
+ // なくなる。
9
+ //
10
+ // stitch との違いは一点だけである。stitch は束を
11
+ // `Readonly<Record<string, unknown>>` として手書きの述語に渡すので、束の中身に
12
+ // ついて型が何も言わない。ここでは束が対応表から組まれて **型が付く**:
13
+ //
14
+ // .v("total", (b) => b.number.stitchWith(
15
+ // { cost: "price", count: "quantity" },
16
+ // (f) => f.object.custom((bundle) => bundle.cost * bundle.count === 100)
17
+ // ))
18
+ //
19
+ // `bundle` は `{ cost: number; count: number }` であって Record ではない。
20
+ // メンバー名を綴り違えれば、型を取り違えれば、コンパイルエラーになる。
21
+ //
22
+ // なぜ別名を経由するのか。束をパス文字列でキーすると、その文字列は宣言の場で
23
+ // **パスとして** 解釈される: `"user.name"` は束の中の `user.name` を探しに
24
+ // 行き、束は平たいので見つからない (実測して分かった)。別名は素の識別子なので
25
+ // その衝突が起きず、参照できるのは宣言した別名だけになる。
26
+ //
27
+ // このファイルに判定は無い。サブチェーンは NarrowedChain と同じ経路で
28
+ // `readonly Rule[]` に解決され、branch がそれを枝にし、エンジンが走らせる。
29
+ // 束専用の収集器をコアに置く案も作って動かしたが、実測でコアが 220 B 増えた
30
+ // (7,590 -> 7,810 B)。stitchWith を使わない利用者が払う形なので採らなかった。
31
+ // 既存の経路に乗せると追加は 0 B である。
32
+ //
33
+ // 1.x が同じ責務の実装を3つ持っていたのは、ここで「もう1つ書く」を選んだ
34
+ // からである。
35
+ // ===========================================================================
36
+ import { PASS, fail, isPlainObject } from "../../types/index.mjs";
37
+ import { branch, composite } from "../../plugin-kit/create-rule.mjs";
38
+ import { definePlugin } from "../../plugin-kit/plugin-definition.mjs";
39
+ import { createValueReader, parseFieldPath } from "../../path/index.mjs";
40
+ const STITCH_WITH_SLOTS = [
41
+ "string",
42
+ "number",
43
+ "boolean",
44
+ "date",
45
+ "object",
46
+ "array",
47
+ "tuple",
48
+ "union",
49
+ ];
50
+ const BUNDLE_BRANCH_LABEL = "bundle";
51
+ function readMembers(aliasMap) {
52
+ return Object.freeze(Object.entries(aliasMap).map(([alias, path]) => ({
53
+ alias,
54
+ read: createValueReader(parseFieldPath(path)),
55
+ })));
56
+ }
57
+ /** ルートから束を組む。枝の主体はこのオブジェクトになる。 */
58
+ function collectBundle(members, root) {
59
+ const bundle = {};
60
+ for (const member of members)
61
+ bundle[member.alias] = member.read(root);
62
+ return bundle;
63
+ }
64
+ export const stitchWithPlugin = /*#__PURE__*/ definePlugin()({
65
+ name: "stitchWith",
66
+ method: "stitchWith",
67
+ slots: STITCH_WITH_SLOTS,
68
+ build: (ctx, aliasMap, rules) => {
69
+ const members = readMembers(isPlainObject(aliasMap) ? aliasMap : {});
70
+ const aliases = Object.freeze(members.map((member) => member.alias));
71
+ return composite({
72
+ code: ctx.code,
73
+ messageFactory: ctx.messageFactory,
74
+ severity: ctx.severity,
75
+ branches: [branch(BUNDLE_BRANCH_LABEL, rules)],
76
+ // 主体の値は見ない。見るのはルートから組んだ束だけである。
77
+ combine: (runners) => {
78
+ const runner = runners[0];
79
+ if (runner === undefined)
80
+ return () => PASS;
81
+ return (_value, runCtx) => {
82
+ const outcome = runner.run(collectBundle(members, runCtx.root), runCtx);
83
+ return outcome.ok ? PASS : fail({ ...outcome.detail });
84
+ };
85
+ },
86
+ describe: (_detail, msgCtx) => `Cross-field validation failed for ${msgCtx.path}`,
87
+ buildMessageContext: () => ({ aliases }),
88
+ });
89
+ },
90
+ subChainArguments: [1],
91
+ });
@@ -0,0 +1 @@
1
+ export * from "./stitch-with/index";
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ module.exports = require("./stitch-with/index");
@@ -0,0 +1 @@
1
+ export * from "./stitch-with/index.mjs";
@@ -20,9 +20,9 @@ exports.stringExactLengthPlugin = (0, plugin_definition_1.definePlugin)()({
20
20
  code: ctx.code,
21
21
  messageFactory: ctx.messageFactory,
22
22
  severity: ctx.severity,
23
- run: (value) => !(0, types_1.isString)(value) || value.length === expected
23
+ run: (value) => !(0, types_1.isString)(value) || (0, types_1.countCodePoints)(value) === expected
24
24
  ? types_1.PASS
25
- : (0, types_1.fail)({ expected, actual: value.length }),
25
+ : (0, types_1.fail)({ expected, actual: (0, types_1.countCodePoints)(value) }),
26
26
  describe: (detail) => `String must have exactly ${String(expected)} characters, but got ` +
27
27
  `${String(detail.actual)}`,
28
28
  buildMessageContext: (detail) => ({
@@ -4,7 +4,7 @@
4
4
  // ===========================================================================
5
5
  import { check } from "../../plugin-kit/create-rule.mjs";
6
6
  import { PluginArgumentError, definePlugin, } from "../../plugin-kit/plugin-definition.mjs";
7
- import { PASS, fail, isNumber, isString } from "../../types/index.mjs";
7
+ import { PASS, fail, isNumber, countCodePoints, isString } from "../../types/index.mjs";
8
8
  export const stringExactLengthPlugin = /*#__PURE__*/ definePlugin()({
9
9
  name: "stringExactLength",
10
10
  method: "exactLength",
@@ -17,9 +17,9 @@ export const stringExactLengthPlugin = /*#__PURE__*/ definePlugin()({
17
17
  code: ctx.code,
18
18
  messageFactory: ctx.messageFactory,
19
19
  severity: ctx.severity,
20
- run: (value) => !isString(value) || value.length === expected
20
+ run: (value) => !isString(value) || countCodePoints(value) === expected
21
21
  ? PASS
22
- : fail({ expected, actual: value.length }),
22
+ : fail({ expected, actual: countCodePoints(value) }),
23
23
  describe: (detail) => `String must have exactly ${String(expected)} characters, but got ` +
24
24
  `${String(detail.actual)}`,
25
25
  buildMessageContext: (detail) => ({
@@ -20,9 +20,9 @@ exports.stringMaxPlugin = (0, plugin_definition_1.definePlugin)()({
20
20
  code: ctx.code,
21
21
  messageFactory: ctx.messageFactory,
22
22
  severity: ctx.severity,
23
- run: (value) => !(0, types_1.isString)(value) || value.length <= max
23
+ run: (value) => !(0, types_1.isString)(value) || (0, types_1.countCodePoints)(value) <= max
24
24
  ? types_1.PASS
25
- : (0, types_1.fail)({ expected: max, actual: value.length }),
25
+ : (0, types_1.fail)({ expected: max, actual: (0, types_1.countCodePoints)(value) }),
26
26
  describe: (detail) => `String must have at most ${String(max)} characters, but got ` +
27
27
  `${String(detail.actual)}`,
28
28
  buildMessageContext: (detail) => ({
@@ -4,7 +4,7 @@
4
4
  // ===========================================================================
5
5
  import { check } from "../../plugin-kit/create-rule.mjs";
6
6
  import { PluginArgumentError, definePlugin, } from "../../plugin-kit/plugin-definition.mjs";
7
- import { PASS, fail, isNumber, isString } from "../../types/index.mjs";
7
+ import { PASS, fail, isNumber, countCodePoints, isString } from "../../types/index.mjs";
8
8
  export const stringMaxPlugin = /*#__PURE__*/ definePlugin()({
9
9
  name: "stringMax",
10
10
  method: "max",
@@ -17,9 +17,9 @@ export const stringMaxPlugin = /*#__PURE__*/ definePlugin()({
17
17
  code: ctx.code,
18
18
  messageFactory: ctx.messageFactory,
19
19
  severity: ctx.severity,
20
- run: (value) => !isString(value) || value.length <= max
20
+ run: (value) => !isString(value) || countCodePoints(value) <= max
21
21
  ? PASS
22
- : fail({ expected: max, actual: value.length }),
22
+ : fail({ expected: max, actual: countCodePoints(value) }),
23
23
  describe: (detail) => `String must have at most ${String(max)} characters, but got ` +
24
24
  `${String(detail.actual)}`,
25
25
  buildMessageContext: (detail) => ({
@@ -23,9 +23,27 @@ exports.stringMinPlugin = (0, plugin_definition_1.definePlugin)()({
23
23
  code: ctx.code,
24
24
  messageFactory: ctx.messageFactory,
25
25
  severity: ctx.severity,
26
- run: (value) => !(0, types_1.isString)(value) || value.length >= min
27
- ? types_1.PASS
28
- : (0, types_1.fail)({ expected: min, actual: value.length }),
26
+ // min に届いた時点で止める。全長を数える必要があるのは**落ちる**
27
+ // ときだけで、そのときは値が min より短いのだから走査も短い。
28
+ // 元は countCodePoints を最大二度呼び、どちらも文字列を最後まで
29
+ // 歩いていた。符号位置で数えるのは変えない (UTF-16 単位ではない) —
30
+ // 変えているのは、いつ止めるかだけである。受理パスで 12.7%。
31
+ run: (value) => {
32
+ if (!(0, types_1.isString)(value))
33
+ return types_1.PASS;
34
+ // min が 0 なら空文字列も通る。ループは空文字列で一度も回らないので、
35
+ // この行が無いと `""` が actual 0 で落ちる — 早期脱出に書き換えた
36
+ // ときに実際に開いた穴で、既存のテストは一件も気づかなかった。
37
+ if (min === 0)
38
+ return types_1.PASS;
39
+ let count = 0;
40
+ for (const _character of value) {
41
+ count += 1;
42
+ if (count >= min)
43
+ return types_1.PASS;
44
+ }
45
+ return (0, types_1.fail)({ expected: min, actual: count });
46
+ },
29
47
  describe: (detail) => `String must have at least ${String(min)} characters, but got ` +
30
48
  `${String(detail.actual)}`,
31
49
  buildMessageContext: (detail) => ({
@@ -20,9 +20,27 @@ export const stringMinPlugin = /*#__PURE__*/ definePlugin()({
20
20
  code: ctx.code,
21
21
  messageFactory: ctx.messageFactory,
22
22
  severity: ctx.severity,
23
- run: (value) => !isString(value) || value.length >= min
24
- ? PASS
25
- : fail({ expected: min, actual: value.length }),
23
+ // min に届いた時点で止める。全長を数える必要があるのは**落ちる**
24
+ // ときだけで、そのときは値が min より短いのだから走査も短い。
25
+ // 元は countCodePoints を最大二度呼び、どちらも文字列を最後まで
26
+ // 歩いていた。符号位置で数えるのは変えない (UTF-16 単位ではない) —
27
+ // 変えているのは、いつ止めるかだけである。受理パスで 12.7%。
28
+ run: (value) => {
29
+ if (!isString(value))
30
+ return PASS;
31
+ // min が 0 なら空文字列も通る。ループは空文字列で一度も回らないので、
32
+ // この行が無いと `""` が actual 0 で落ちる — 早期脱出に書き換えた
33
+ // ときに実際に開いた穴で、既存のテストは一件も気づかなかった。
34
+ if (min === 0)
35
+ return PASS;
36
+ let count = 0;
37
+ for (const _character of value) {
38
+ count += 1;
39
+ if (count >= min)
40
+ return PASS;
41
+ }
42
+ return fail({ expected: min, actual: count });
43
+ },
26
44
  describe: (detail) => `String must have at least ${String(min)} characters, but got ` +
27
45
  `${String(detail.actual)}`,
28
46
  buildMessageContext: (detail) => ({
@@ -0,0 +1 @@
1
+ export { arrays, everydayRules, numbers, presence, strings } from "./presets";
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.strings = exports.presence = exports.numbers = exports.everydayRules = exports.arrays = void 0;
4
+ var presets_1 = require("./presets");
5
+ Object.defineProperty(exports, "arrays", { enumerable: true, get: function () { return presets_1.arrays; } });
6
+ Object.defineProperty(exports, "everydayRules", { enumerable: true, get: function () { return presets_1.everydayRules; } });
7
+ Object.defineProperty(exports, "numbers", { enumerable: true, get: function () { return presets_1.numbers; } });
8
+ Object.defineProperty(exports, "presence", { enumerable: true, get: function () { return presets_1.presence; } });
9
+ Object.defineProperty(exports, "strings", { enumerable: true, get: function () { return presets_1.strings; } });
@@ -0,0 +1 @@
1
+ export { arrays, everydayRules, numbers, presence, strings } from "./presets.mjs";
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 在る・無い・null。ほぼ全ての宣言がこの三つのどれかを使う。
3
+ * 使用実績: required 138 / optional 26 / nullable 12。
4
+ */
5
+ export declare const presence: Readonly<{
6
+ required: import("..").PluginDefinition<"required", "required", readonly import("..").TypeName[], {
7
+ args: readonly [];
8
+ out: import("../plugin-kit").PresenceShift<"excludeMissing">;
9
+ context: import("..").MessageContextExtra;
10
+ }>;
11
+ optional: import("..").PluginDefinition<"optional", "optional", readonly import("..").TypeName[], {
12
+ args: readonly [];
13
+ out: import("../plugin-kit").PresenceShift<"excludeNull">;
14
+ context: import("..").MessageContextExtra;
15
+ }>;
16
+ nullable: import("..").PluginDefinition<"nullable", "nullable", readonly import("..").TypeName[], {
17
+ args: readonly [];
18
+ out: import("../plugin-kit").PresenceShift<"allowNull">;
19
+ context: import("..").MessageContextExtra;
20
+ }>;
21
+ }>;
22
+ /** 文字列の定番。使用実績: stringMin 95 / stringEmail 33 / stringPattern 12。 */
23
+ export declare const strings: Readonly<{
24
+ stringMin: import("..").PluginDefinition<"stringMin", "min", readonly ["string"], {
25
+ args: readonly [min: number];
26
+ out: import("../plugin-kit").Unchanged;
27
+ context: import("../plugins/string-min/string-min").StringMinContext;
28
+ }>;
29
+ stringMax: import("..").PluginDefinition<"stringMax", "max", readonly ["string"], {
30
+ args: readonly [max: number];
31
+ out: import("../plugin-kit").Unchanged;
32
+ context: import("../plugins/string-max/string-max").StringMaxContext;
33
+ }>;
34
+ stringPattern: import("..").PluginDefinition<"stringPattern", "pattern", readonly ["string"], {
35
+ args: readonly [pattern: RegExp];
36
+ out: import("../plugin-kit").Unchanged;
37
+ context: {
38
+ readonly pattern: string;
39
+ };
40
+ }>;
41
+ stringEmail: import("..").PluginDefinition<"stringEmail", "email", readonly ["string"], {
42
+ args: readonly [options?: import("../plugins/string-email/string-email").EmailFormatOptions];
43
+ out: import("../plugin-kit").Unchanged;
44
+ context: {
45
+ readonly reason: string;
46
+ };
47
+ }>;
48
+ }>;
49
+ /** 数値の定番。使用実績: numberMin 43 / numberMax 8。 */
50
+ export declare const numbers: Readonly<{
51
+ numberMin: import("..").PluginDefinition<"numberMin", "min", readonly ["number"], {
52
+ args: readonly [min: number, exclusive?: boolean];
53
+ out: import("../plugin-kit").Unchanged;
54
+ context: import("../plugins/number-min").NumberMinContext;
55
+ }>;
56
+ numberMax: import("..").PluginDefinition<"numberMax", "max", readonly ["number"], {
57
+ args: readonly [max: number, exclusive?: boolean];
58
+ out: import("../plugin-kit").Unchanged;
59
+ context: import("../plugins/number-max").NumberMaxContext;
60
+ }>;
61
+ numberInteger: import("..").PluginDefinition<"numberInteger", "integer", readonly ["number"], {
62
+ args: readonly [];
63
+ out: import("../plugin-kit").Unchanged;
64
+ context: import("..").MessageContextExtra;
65
+ }>;
66
+ }>;
67
+ /** 配列の定番。要素ごとの規則は arrayEach が運ぶ。 */
68
+ export declare const arrays: Readonly<{
69
+ arrayMinLength: import("..").PluginDefinition<"arrayMinLength", "minLength", readonly ["array", "tuple"], {
70
+ args: readonly [min: number];
71
+ out: import("../plugin-kit").Unchanged;
72
+ context: import("../plugins/array-min-length").ArrayMinLengthContext;
73
+ }>;
74
+ arrayMaxLength: import("..").PluginDefinition<"arrayMaxLength", "maxLength", readonly ["array", "tuple"], {
75
+ args: readonly [max: number];
76
+ out: import("../plugin-kit").Unchanged;
77
+ context: import("../plugins/array-max-length").ArrayMaxLengthContext;
78
+ }>;
79
+ arrayEach: import("..").PluginDefinition<"arrayEach", "each", readonly ["array", "tuple"], {
80
+ args: readonly [element: import("../plugin-kit").ElementChain];
81
+ out: import("../plugin-kit").Unchanged;
82
+ context: import("..").MessageContextExtra;
83
+ }>;
84
+ }>;
85
+ /**
86
+ * 上の四つを合わせたもの。13プラグイン、gzip で +1,452 B。
87
+ *
88
+ * 名前が「common」でないのは、それが何も言っていないからである
89
+ * (lint の禁止語彙にも入っている)。入っているのは presence と、文字列・数値・
90
+ * 配列それぞれの定番で、毎日書くのはこの範囲だ、という主張がこの名前である。
91
+ *
92
+ * これでも「全部入り」ではない。全部入りが要るなら `@maroonedog/luq/plugins`
93
+ * のバレルがあり、そちらは 77 個ぶん、gzip で +17,986 B を払う。
94
+ */
95
+ export declare const everydayRules: Readonly<{
96
+ arrayMinLength: import("..").PluginDefinition<"arrayMinLength", "minLength", readonly ["array", "tuple"], {
97
+ args: readonly [min: number];
98
+ out: import("../plugin-kit").Unchanged;
99
+ context: import("../plugins/array-min-length").ArrayMinLengthContext;
100
+ }>;
101
+ arrayMaxLength: import("..").PluginDefinition<"arrayMaxLength", "maxLength", readonly ["array", "tuple"], {
102
+ args: readonly [max: number];
103
+ out: import("../plugin-kit").Unchanged;
104
+ context: import("../plugins/array-max-length").ArrayMaxLengthContext;
105
+ }>;
106
+ arrayEach: import("..").PluginDefinition<"arrayEach", "each", readonly ["array", "tuple"], {
107
+ args: readonly [element: import("../plugin-kit").ElementChain];
108
+ out: import("../plugin-kit").Unchanged;
109
+ context: import("..").MessageContextExtra;
110
+ }>;
111
+ numberMin: import("..").PluginDefinition<"numberMin", "min", readonly ["number"], {
112
+ args: readonly [min: number, exclusive?: boolean];
113
+ out: import("../plugin-kit").Unchanged;
114
+ context: import("../plugins/number-min").NumberMinContext;
115
+ }>;
116
+ numberMax: import("..").PluginDefinition<"numberMax", "max", readonly ["number"], {
117
+ args: readonly [max: number, exclusive?: boolean];
118
+ out: import("../plugin-kit").Unchanged;
119
+ context: import("../plugins/number-max").NumberMaxContext;
120
+ }>;
121
+ numberInteger: import("..").PluginDefinition<"numberInteger", "integer", readonly ["number"], {
122
+ args: readonly [];
123
+ out: import("../plugin-kit").Unchanged;
124
+ context: import("..").MessageContextExtra;
125
+ }>;
126
+ stringMin: import("..").PluginDefinition<"stringMin", "min", readonly ["string"], {
127
+ args: readonly [min: number];
128
+ out: import("../plugin-kit").Unchanged;
129
+ context: import("../plugins/string-min/string-min").StringMinContext;
130
+ }>;
131
+ stringMax: import("..").PluginDefinition<"stringMax", "max", readonly ["string"], {
132
+ args: readonly [max: number];
133
+ out: import("../plugin-kit").Unchanged;
134
+ context: import("../plugins/string-max/string-max").StringMaxContext;
135
+ }>;
136
+ stringPattern: import("..").PluginDefinition<"stringPattern", "pattern", readonly ["string"], {
137
+ args: readonly [pattern: RegExp];
138
+ out: import("../plugin-kit").Unchanged;
139
+ context: {
140
+ readonly pattern: string;
141
+ };
142
+ }>;
143
+ stringEmail: import("..").PluginDefinition<"stringEmail", "email", readonly ["string"], {
144
+ args: readonly [options?: import("../plugins/string-email/string-email").EmailFormatOptions];
145
+ out: import("../plugin-kit").Unchanged;
146
+ context: {
147
+ readonly reason: string;
148
+ };
149
+ }>;
150
+ required: import("..").PluginDefinition<"required", "required", readonly import("..").TypeName[], {
151
+ args: readonly [];
152
+ out: import("../plugin-kit").PresenceShift<"excludeMissing">;
153
+ context: import("..").MessageContextExtra;
154
+ }>;
155
+ optional: import("..").PluginDefinition<"optional", "optional", readonly import("..").TypeName[], {
156
+ args: readonly [];
157
+ out: import("../plugin-kit").PresenceShift<"excludeNull">;
158
+ context: import("..").MessageContextExtra;
159
+ }>;
160
+ nullable: import("..").PluginDefinition<"nullable", "nullable", readonly import("..").TypeName[], {
161
+ args: readonly [];
162
+ out: import("../plugin-kit").PresenceShift<"allowNull">;
163
+ context: import("..").MessageContextExtra;
164
+ }>;
165
+ }>;