@wcstack/state 3.5.4 → 4.0.0-rc.2

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 (82) hide show
  1. package/README.ja.md +669 -428
  2. package/README.md +667 -426
  3. package/dist/auto.min.js +1 -2
  4. package/dist/define.d.ts +4 -8
  5. package/dist/define.js +1 -1
  6. package/dist/index.d.ts +139 -1483
  7. package/dist/index.esm.js +1 -2
  8. package/dist/manifest.d.ts +18 -59
  9. package/dist/manifest.esm.js +542 -1202
  10. package/dist/parser.d.ts +49 -103
  11. package/dist/parser.esm.js +662 -831
  12. package/dist/split/auto.js +1 -0
  13. package/dist/split/chunks/chunk-732CB4QN.js +1 -0
  14. package/dist/split/chunks/chunk-7PN5YNZZ.js +1 -0
  15. package/dist/split/chunks/chunk-FF6KHTYS.js +1 -0
  16. package/dist/split/chunks/chunk-HFKGUDDJ.js +1 -0
  17. package/dist/split/chunks/chunk-PKDBVBAP.js +1 -0
  18. package/dist/split/chunks/chunk-QI7P7YPP.js +1 -0
  19. package/dist/split/chunks/chunk-QQH6N5CJ.js +1 -0
  20. package/dist/split/chunks/chunk-UM3O6QXD.js +1 -0
  21. package/dist/split/chunks/chunk-WEL3YSG7.js +1 -0
  22. package/dist/split/chunks/chunk-WWJNAZVP.js +1 -0
  23. package/dist/split/chunks/chunk-YOPPVWPQ.js +1 -0
  24. package/dist/split/core.d.ts +114 -1168
  25. package/dist/split/core.js +1 -2
  26. package/dist/split/features/devtools.d.ts +23 -3
  27. package/dist/split/features/devtools.js +1 -2
  28. package/dist/split/features/diagnostics.d.ts +27 -5
  29. package/dist/split/features/diagnostics.js +1 -2
  30. package/dist/split/features/formats.d.ts +21 -5
  31. package/dist/split/features/formats.js +1 -2
  32. package/dist/split/features/list-keys.d.ts +24 -0
  33. package/dist/split/features/list-keys.js +1 -0
  34. package/dist/split/features/recursion.d.ts +23 -3
  35. package/dist/split/features/recursion.js +1 -2
  36. package/dist/split/features/scopes.d.ts +25 -4
  37. package/dist/split/features/scopes.js +1 -2
  38. package/dist/split/features/ssr.d.ts +23 -3
  39. package/dist/split/features/ssr.js +1 -2
  40. package/dist/split/features/temporal.d.ts +19 -4
  41. package/dist/split/features/temporal.js +1 -2
  42. package/dist/wcs-manifest.json +43 -49
  43. package/package.json +15 -17
  44. package/dist/auto.min.js.map +0 -1
  45. package/dist/index.esm.js.map +0 -1
  46. package/dist/split/chunks/binder.js +0 -2
  47. package/dist/split/chunks/binder.js.map +0 -1
  48. package/dist/split/chunks/checkDependency.js +0 -2
  49. package/dist/split/chunks/checkDependency.js.map +0 -1
  50. package/dist/split/chunks/declarationAliases.js +0 -2
  51. package/dist/split/chunks/declarationAliases.js.map +0 -1
  52. package/dist/split/chunks/declarationHooks.js +0 -2
  53. package/dist/split/chunks/declarationHooks.js.map +0 -1
  54. package/dist/split/chunks/diagnosticsHooks.js +0 -2
  55. package/dist/split/chunks/diagnosticsHooks.js.map +0 -1
  56. package/dist/split/chunks/errorGuidance.js +0 -2
  57. package/dist/split/chunks/errorGuidance.js.map +0 -1
  58. package/dist/split/chunks/featureBridge.js +0 -2
  59. package/dist/split/chunks/featureBridge.js.map +0 -1
  60. package/dist/split/chunks/features.d.ts +0 -29
  61. package/dist/split/chunks/filterRegistry.js +0 -2
  62. package/dist/split/chunks/filterRegistry.js.map +0 -1
  63. package/dist/split/chunks/getAllPropertyDescriptors.js +0 -2
  64. package/dist/split/chunks/getAllPropertyDescriptors.js.map +0 -1
  65. package/dist/split/chunks/lifecycleHooks.js +0 -2
  66. package/dist/split/chunks/lifecycleHooks.js.map +0 -1
  67. package/dist/split/chunks/pathDiagnostics.js +0 -2
  68. package/dist/split/chunks/pathDiagnostics.js.map +0 -1
  69. package/dist/split/chunks/registerComponents.js +0 -2
  70. package/dist/split/chunks/registerComponents.js.map +0 -1
  71. package/dist/split/chunks/setAll.js +0 -2
  72. package/dist/split/chunks/setAll.js.map +0 -1
  73. package/dist/split/chunks/version.js +0 -2
  74. package/dist/split/chunks/version.js.map +0 -1
  75. package/dist/split/core.js.map +0 -1
  76. package/dist/split/features/devtools.js.map +0 -1
  77. package/dist/split/features/diagnostics.js.map +0 -1
  78. package/dist/split/features/formats.js.map +0 -1
  79. package/dist/split/features/recursion.js.map +0 -1
  80. package/dist/split/features/scopes.js.map +0 -1
  81. package/dist/split/features/ssr.js.map +0 -1
  82. package/dist/split/features/temporal.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"devtools.js","sources":["../../../../../src/devtools/declaredBindings.ts","../../../../../src/devtools/types.ts","../../../../../src/devtools/bridge.ts","../../../../../src/devtools/keyedSubscriptions.ts","../../../../../src/features/devtools.ts"],"sourcesContent":["/**\n * devtools/declaredBindings.ts — 宣言レベルのバインディング列挙\n * (IDevtoolsSource.getDeclaredBindings の実装・protocol v1 追補)。\n *\n * devtools 側の DOM 再スキャン(declaredScan — 「正本 bindTextParser に非追随」と\n * 自己申告する簡易パーサ)を置き換える正本実装。ランタイム自身のパーサと\n * fragment レジストリで答えるため:\n * - パース結果は実行時解釈と**同一**(複製パーサのドリフトが構造的に消える)\n * - DOM から引き上げられた構造テンプレート内部(for/if の中身)も列挙できる\n * (DOM 再スキャンでは原理的に不可視だった領域)\n *\n * **戻り値は「宣言の集合」であり、インスタンスの multiset ではない**:\n * レンダリング済みページでは行クローンが `data-wcs` 属性とネストアンカー\n * (同一 UUID)を保持したまま live DOM に入るため、素朴な走査は宣言 1 件を\n * 行数分だけ重複列挙してしまう。ここでは宣言タプル(path / prop /\n * bindingType / フィルタ列)で dedupe し、宣言 1 件 = エントリ 1 件を保証する。\n * インスタンス粒度(どの行のどのノードか)は live の binding 台帳\n * (state:binding-added)の守備範囲で、こちらには持たせない。\n * 同一タプルの独立した静的宣言(同じバインディングを書いた別要素)も 1 件に\n * 畳まれる — node は最初に見つかったものを代表として持つ。\n *\n * fragment(構造テンプレート内部)は **rootNode 配下の live アンカーから到達可能な\n * UUID の推移閉包**として列挙する。ページ大域のレジストリを直接舐めないため、\n * 別 root の fragment や破棄済みビューの stale fragment は載らない。\n *\n * spread(`...:`)は attribute 起点では live Element の wcBindable から展開する\n * (設計 §5-1)。対象カスタム要素が未定義なら既存契約どおり spread のまま残す。\n *\n * **既知の盲点(時系列依存)**: 構造テンプレート外のテキストバインディングは、\n * binding start がコメントアンカーを空 Text に差し替えるため、**活性化後の DOM\n * から消えて列挙に現れない**(mustache は変換前も素の Text で不可視)。早期\n * アタッチ時は live 台帳(binding-added)が同領域を持つ。遅延アタッチでは\n * declared / live のどちらからも見えない — protocol §6 の既知非対称の一部。\n *\n * エラーパス以外もフック接続時にしか呼ばれない pull API であり、ホットパスには\n * 乗らない。壊れた宣言(不正 bindText)はエントリを捨てて先へ進む(検査は lint の\n * 責務 — ここは観測面)。\n */\n\nimport { config } from \"../config\";\nimport { parseBindTextsForElement } from \"../bindTextParser/parseBindTextsForElement\";\nimport { parseBindTextForEmbeddedNode } from \"../bindTextParser/parseBindTextForEmbeddedNode\";\nimport { expandSpread } from \"../bindTextParser/expandSpread\";\nimport { getFragmentInfoByUUID } from \"../structural/fragmentInfoByUUID\";\nimport { filterListKey } from \"../binding/filterKey\";\nimport { ParseBindTextResult } from \"../bindTextParser/types\";\nimport { IDeclaredBindingInfo } from \"./types\";\n\n// bindings/parseCommentNode.ts の EMBEDDED_REGEX と同形(keyword を自前で保持する\n// ために複製する — parseCommentNode は keyword を捨てて値しか返さないため、\n// 「未登録の構造アンカー(SSR pre-hydration ウィンドウ等)」と「埋め込みテキスト」\n// を区別できず、UUID をパスとして誤パースした bogus エントリが生まれる)。\nconst COMMENT_PATTERN = /^\\s*@@\\s*(.*?)\\s*:\\s*(.+?)\\s*$/;\n\nfunction declarationKey(info: IDeclaredBindingInfo): string {\n // 引数は型付きの値で書き出す(要件 B9)— `defaults(0)` と `defaults('0')` を同じ宣言に畳まない。\n // 入力フィルタと出力フィルタは**別の成分**にする: 連結すると `value|trim: memo`(入力)と\n // `value: memo|trim`(出力)が同じ鍵になり、片方が宣言集合から黙って落ちる\n const filters = `${filterListKey(info.inFilters)}\\u0000${filterListKey(info.outFilters)}`;\n return [info.statePathName, info.propName, info.bindingType, filters].join(\"\\u0000\");\n}\n\nfunction toInfo(\n node: Node | null,\n parsed: ParseBindTextResult,\n origin: IDeclaredBindingInfo[\"origin\"],\n raw: string,\n): IDeclaredBindingInfo {\n return {\n node,\n propName: parsed.propName,\n statePathName: parsed.statePathName,\n bindingType: parsed.bindingType,\n inFilters: parsed.inFilters,\n outFilters: parsed.outFilters,\n origin,\n raw,\n };\n}\n\n/**\n * rootNode 配下の live DOM と、そこから到達可能な fragment から宣言集合を列挙する。\n * ヘッダの規定(宣言集合 / 到達閉包 / spread 展開 / テキストの盲点)を参照。\n */\nexport function collectDeclaredBindings(rootNode: Node): IDeclaredBindingInfo[] {\n const out: IDeclaredBindingInfo[] = [];\n const seenKeys = new Set<string>();\n const visitedFragments = new Set<string>();\n\n const push = (info: IDeclaredBindingInfo): void => {\n const key = declarationKey(info);\n if (seenKeys.has(key)) return;\n seenKeys.add(key);\n out.push(info);\n };\n\n /** fragment の内部宣言を emit し、ネストした構造アンカーへ再帰する(到達閉包)。 */\n const visitFragment = (uuid: string): void => {\n if (visitedFragments.has(uuid)) return;\n visitedFragments.add(uuid);\n const fragmentInfo = getFragmentInfoByUUID(uuid);\n if (fragmentInfo === null) return;\n for (const nodeInfo of fragmentInfo.nodeInfos) {\n for (const parsed of nodeInfo.parseBindTextResults) {\n push(toInfo(null, parsed, \"fragment\", \"\"));\n if (parsed.uuid != null) {\n visitFragment(parsed.uuid);\n }\n }\n }\n };\n\n const doc = rootNode.ownerDocument ?? (rootNode as Document);\n const walker = doc.createTreeWalker(\n rootNode,\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,\n );\n\n for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element;\n const bindText = element.getAttribute(config.bindAttributeName);\n if (bindText === null || bindText.length === 0) continue;\n try {\n // spread は live Element の wcBindable から展開(未定義要素は spread のまま = 既存契約)\n const results = expandSpread(element, parseBindTextsForElement(bindText));\n for (const parsed of results) {\n push(toInfo(element, parsed, \"attribute\", bindText));\n }\n } catch {\n // 不正な宣言は観測面では捨てる(検査は lint / ランタイム raiseError の責務)\n }\n continue;\n }\n // コメントアンカー: 構造ディレクティブ(UUID → fragment 登録済み)または\n // 埋め込みテキストバインディング。\n const match = COMMENT_PATTERN.exec((node as Comment).data.trim());\n if (match === null) continue;\n const keyword = match[1] || config.commentTextPrefix;\n const value = match[2];\n const fragmentInfo = getFragmentInfoByUUID(value);\n if (fragmentInfo !== null) {\n push(toInfo(node, fragmentInfo.parseBindTextResult, \"comment\", value));\n visitFragment(value);\n continue;\n }\n if (keyword !== config.commentTextPrefix) {\n // 構造アンカーなのに fragment 未登録(SSR の pre-hydration ウィンドウ等)、\n // または未知 keyword — UUID や不明値をパスとして誤パースしない。\n continue;\n }\n try {\n push(toInfo(node, parseBindTextForEmbeddedNode(value), \"comment\", value));\n } catch {\n // 同上\n }\n }\n\n return out;\n}\n","/**\n * devtools/types.ts\n *\n * DevTools Hook Protocol (docs/devtools-hook-protocol.md) の型定義。\n *\n * イベント payload はランタイム内部オブジェクト(IAbsoluteStateAddress /\n * IBindingInfo 等)への生参照を含む(同一 realm・オーバーレイ前提、protocol 原則 4)。\n * 消費者はこれらを変異してはならない。\n */\n\nimport type { IAbsoluteStateAddress } from \"../address/types\";\nimport type { IBindingInfo } from \"../binding/types\";\nimport type { IStateElement } from \"../components/types\";\n\n/** グローバル registry のプロパティ名 */\nexport const DEVTOOLS_HOOK_GLOBAL = \"__WCSTACK_DEVTOOLS_HOOK__\";\n\n/** プロトコル版。additive change では上げない(protocol §2) */\nexport const DEVTOOLS_PROTOCOL_VERSION = 2;\n\nexport type DevtoolsEvent =\n | {\n readonly type: \"state:element-registered\";\n readonly rootNode: Node;\n readonly element: IStateElement;\n }\n | {\n readonly type: \"state:element-unregistered\";\n readonly rootNode: Node;\n readonly element: IStateElement;\n }\n | {\n readonly type: \"state:write\";\n readonly absoluteAddress: IAbsoluteStateAddress;\n readonly value: unknown;\n /** same-value guard が既に取得していた場合のみ意味を持つ(protocol §4.2) */\n readonly oldValue: unknown;\n readonly hasOldValue: boolean;\n }\n | {\n readonly type: \"state:update-batch\";\n readonly addresses: ReadonlySet<IAbsoluteStateAddress>;\n }\n | {\n readonly type: \"state:binding-added\";\n readonly absoluteAddress: IAbsoluteStateAddress;\n readonly binding: IBindingInfo;\n }\n | {\n readonly type: \"state:binding-removed\";\n readonly absoluteAddress: IAbsoluteStateAddress;\n readonly binding: IBindingInfo;\n }\n | {\n readonly type: \"state:binding-cleared\";\n readonly absoluteAddress: IAbsoluteStateAddress;\n }\n | {\n readonly type: \"state:token-emit\";\n readonly kind: \"command\" | \"event\";\n readonly tokenName: string;\n readonly args: readonly unknown[];\n readonly subscriberCount: number;\n /**\n * 発火元ツリーの state 要素(protocol v2 追補 2026-09-05・additive)。\n * registry(getOrCreate*Token)経由で作られた token だけが持つ — 直接生成された\n * token や旧ランタイムの payload には無い(optional)。無い emit の実測は\n * ツリー別に分けられないため、消費側は全ツリーの照会へ合算で残す。\n */\n readonly stateElement?: IStateElement;\n }\n | {\n // `$watch` の実行中に throw した(docs/state-watch-hook-design.md §7-1)。\n // watch は 1 バッチで N 個走り drain フックを他機能と共有するため、例外は\n // watch 側で閉じる(console.error のみ)。それだと devtools から\n // 「静かに握られた失敗」が見えないので、同じ地点からここにも流す。\n readonly type: \"state:watch-error\";\n /**\n * throw 元。cur の評価(getter)とハンドラ本体では原因も直し方も違う。\n * `$scan`(scan/scanReport.ts)は `evaluate`(source / 出力の読み)・`fold`(fold の throw・\n * Promise の戻り値・接ぎ木で getter になった from)・`write`(出力の書き込み)を使う\n */\n readonly phase: \"prime\" | \"evaluate\" | \"handler\" | \"fold\" | \"write\";\n /** `$watch` の宣言キー(ワイルドカードを含む生のパス)。`$scan` は `$scan.<出力名>` */\n readonly path: string;\n readonly error: unknown;\n }\n | {\n // watch 起点の書き込み連鎖が深さ上限で打ち切られた(設計書 §7-2)。\n // 値と binding 適用は巻き戻さない = propagation:hop-limit と同じ姿勢。\n readonly type: \"state:watch-chain-limit\";\n readonly maxDepth: number;\n /** 打ち切ったバッチに載っていたアドレスのパス(報告用) */\n readonly paths: readonly string[];\n }\n | {\n // 描画起点の書き込み連鎖(binding の適用中の書き込みが次のバッチを起こす)が深さ上限で\n // 打ち切られた(#338・updater/updater.ts)。そのバッチの binding は適用せず、値は巻き戻さない。\n readonly type: \"state:render-chain-limit\";\n readonly maxDepth: number;\n /** 打ち切ったバッチに載っていたアドレスのパス(= 直前の描画の中で書かれたパス) */\n readonly paths: readonly string[];\n }\n | {\n // `$watch` ハンドラの正常発火(state-watch-hook-design.md §11 で予約済み・\n // static-wiring-dx-design.md §4 の配線カバレッジが消費)。値は載せない —\n // 「宣言したのに一度も発火しない」の検出には発火の事実だけで足りる。\n readonly type: \"state:watch-fired\";\n /** `$watch` の宣言キー(ワイルドカードを含む生のパス) */\n readonly path: string;\n /**\n * 発火元ツリーの state 要素(protocol v2 追補 2026-09-05・additive)。\n * 複数ツリーが同名の watch パスを宣言するページで実測台帳をツリー別に\n * 分けるための識別。旧ランタイムの payload には無い(optional)— 無い発火は\n * 消費側が全ツリーの照会へ合算で残す。値を載せない契約(§4.3.1)は不変。\n */\n readonly stateElement?: IStateElement;\n }\n | {\n // バインド / `$watch` の対象パスが state 上で解決しないと確定した\n // (pathDiagnostics.ts)。ランタイムは console.warn に留めて続行するため、\n // 「配線したのに黙って死んでいる」を devtools からも見えるようにする。\n readonly type: \"state:path-unresolved\";\n /** 書き手が書いた面。診断 code が binding / watch で変わる */\n readonly source: \"binding\" | \"watch\" | \"scan\";\n /** 宣言されたパス(ワイルドカードを含む生の文字列) */\n readonly path: string;\n /** 解決に失敗したセグメント */\n readonly missingSegment: string;\n }\n | {\n // binding 適用が throw したが、バッチの残りは続行した(apply/applyChangeFromBindings)。\n // 例外を握らずに隔離した事実を観測可能にする(state:watch-error と同じ位置づけ)。\n readonly type: \"state:binding-apply-error\";\n /** バインディングの state パス(ワイルドカードを含む生の文字列) */\n readonly path: string;\n readonly bindingType: string;\n readonly error: unknown;\n }\n | {\n readonly type: \"propagation:suppressed\";\n readonly reason: \"confirmation\" | \"visited-edge\";\n readonly transactionId: number;\n readonly edgeId: number;\n readonly node: Node;\n readonly member: string;\n }\n | {\n readonly type: \"propagation:coalesced\";\n readonly absoluteAddress: IAbsoluteStateAddress;\n readonly droppedTransactionId: number;\n readonly winnerTransactionId: number;\n }\n | {\n readonly type: \"propagation:hop-limit\";\n readonly absoluteAddress: IAbsoluteStateAddress;\n readonly transactionId: number;\n readonly hop: number;\n }\n // --- contract analyzer (Phase 5b, §6 contract category) ---\n | {\n // sidecar manifest から 1 コンポーネント契約を読んだ(dev-time analyzer)。\n readonly type: \"contract:manifest-read\";\n readonly tag: string;\n /** 実行時に該当タグが登録済みか(未登録なら drift の起点)。 */\n readonly loaded: boolean;\n }\n | {\n // manifest の未知 namespace / extension(runtime analyzer が解釈しない)。\n readonly type: \"contract:unsupported-extension\";\n readonly namespace: string;\n }\n | {\n // sidecar と live wcBindable 宣言の drift。live 宣言が正本。\n readonly type: \"contract:drift\";\n readonly reason: \"component-not-loaded\" | \"missing-member\" | \"event-mismatch\";\n readonly tag: string;\n readonly member?: string;\n /** event-mismatch のとき: sidecar 宣言 event / live event。 */\n readonly sidecarEvent?: string;\n readonly liveEvent?: string;\n };\n\nexport type DevtoolsSink = (event: DevtoolsEvent) => void;\n\n/** contract analyzer(Phase 5b)が生成しうる event だけの狭い union(公開 API の戻り型)。 */\nexport type ContractEvent = Extract<\n DevtoolsEvent,\n { readonly type: \"contract:manifest-read\" | \"contract:unsupported-extension\" | \"contract:drift\" }\n>;\n\n/** マウント記録 1 件の要約(overlays — protocol v2)。 */\nexport interface IMountOverlaySummary {\n /** D20 の予約セグメント(`#m<id>`) */\n readonly marker: string;\n /** マウントされたコンポーネントのタグ名(小文字) */\n readonly componentTag: string;\n readonly stateProp: string;\n /** マウント表: 内側接頭辞(空 = ルートエントリ)→ 外側パス */\n readonly mountTable: readonly { readonly inner: string; readonly outer: string }[];\n /** `$n` 補正の Δ(ルート接頭辞のワイルドカード数) */\n readonly delta: number;\n /** 私有キー(作者の own data key — オーバーレイ空間に住む) */\n readonly privateKeys: readonly string[];\n /** 公開 getter の公開パス(親スコープから読める・docs/state-overlay-export-design.md) */\n readonly exports: readonly string[];\n /** マーカーパスに載る getter のキー */\n readonly getterKeys: readonly string[];\n}\n\n/**\n * 鍵付き購読(`$eq` / `$eqPath` / `$eqIndex`)の path 1 つ分の要約(keyedSubscriptions の要素 —\n * protocol v2 追補・additive、要件 D17)。取り出した時点の数えで、台帳への参照は持たない。\n */\nexport interface IKeyedSubscriptionSummary {\n /** `$eq` 系の第 1 引数(選択を持つパス) */\n readonly path: string;\n /**\n * `path` が getter かその下にあるので鍵付きで購読できず、追跡付きの読みに落ちた。`path` が\n * 変わるたびに、それを読む getter がすべて再評価される(README「鍵付き選択」)\n */\n readonly tracked: boolean;\n /** 行ごとの購読の数(`$eq` / `$eqPath`、`$eqIndex` の外側の段、行の外の getter) */\n readonly rows: number;\n /** 行ごとの購読が使っている鍵の数 */\n readonly keys: number;\n /** `$eqIndex` の最内段が置くリスト単位の監視の数(行ごとの購読を持たない) */\n readonly lists: number;\n /** `path` の最後に見た値(登録時か書き込み時)。見ていなければ undefined。生値(protocol 原則 4) */\n readonly lastValue: unknown;\n}\n\nexport interface IStateElementSummary {\n readonly rootNode: Node;\n readonly element: IStateElement;\n readonly paths: {\n readonly list: ReadonlySet<string>;\n readonly element: ReadonlySet<string>;\n readonly getter: ReadonlySet<string>;\n readonly setter: ReadonlySet<string>;\n };\n readonly commandTokenNames: ReadonlySet<string>;\n readonly eventTokenNames: ReadonlySet<string>;\n readonly staticDependency: ReadonlyMap<string, readonly string[]>;\n readonly dynamicDependency: ReadonlyMap<string, readonly string[]>;\n /**\n * `$watch` の宣言パス集合(宣言なしは null)。protocol v1 追補(additive)—\n * 配線カバレッジの「宣言面」(static-wiring-dx-design.md §4)。\n */\n readonly watchPaths: ReadonlySet<string> | null;\n /**\n * `$listKeys` で宣言されたリストパス集合(宣言なしは null)。protocol v1 追補\n * (additive)— ワイルドカード行 watch に**リスト書き込み**が届く前提は\n * 「for バインド or $listKeys 宣言」(state-watch-hook-design.md §6-3)なので、\n * paths.list(for 由来)と合わせて初めてカバレッジの前提判定が正確になる\n *(明示 index 書き込みは前提に依らず発火し得る)。\n */\n readonly keyedListPaths: ReadonlySet<string> | null;\n}\n\n/**\n * 宣言レベルのバインディング 1 件(getDeclaredBindings の要素)。\n * 正本パーサ(bindTextParser)の結果をそのまま流す — devtools 側の簡易パーサ\n * (declaredScan)が「bindTextParser 非追随」と自己申告していたドリフトの恒久解消。\n * filters は IFilterInfo の構造的サブセット(filterName / args)として読める。\n *\n * getDeclaredBindings の戻り値は**宣言の集合**(宣言タプルで dedupe 済み)であり、\n * レンダリング行数に比例したインスタンス列ではない。インスタンス粒度は live の\n * binding 台帳(state:binding-added)の守備範囲。\n */\n/** 宣言されたフィルタ 1 つ(`IParsedFilter` の devtools 向けの写し) */\nexport interface IDeclaredFilter {\n readonly filterName: string;\n readonly args: readonly string[];\n /** 引数の型付きの値(要件 B9)。省略されたら `args` で代用される */\n readonly literals?: readonly unknown[];\n}\n\nexport interface IDeclaredBindingInfo {\n /**\n * 宣言を代表するノード(要素またはコメントアンカー。同一宣言の複数出現は\n * 最初に見つかったノード)。構造テンプレート内部の宣言(origin: 'fragment')は\n * live DOM にノードを持たないため null。\n */\n readonly node: Node | null;\n readonly propName: string;\n readonly statePathName: string;\n readonly bindingType: string;\n /**\n * 宣言されたフィルタ。`literals`(引数の型付きの値・要件 B9)は宣言の同一判定\n * (`binding/filterKey.ts`)が読む。型から落とすと、型どおりのオブジェクトを渡した瞬間に\n * `defaults(0)` と `defaults('0')` が同じ宣言に畳まれる(`literals ?? args` のフォールバックで\n * 黙って原文比較へ落ちるため)。\n */\n readonly inFilters: readonly IDeclaredFilter[];\n readonly outFilters: readonly IDeclaredFilter[];\n readonly origin: \"attribute\" | \"comment\" | \"fragment\";\n /**\n * 宣言の原文。構造ディレクティブのアンカー(origin: 'comment')は原文が DOM に\n * 残らないためレジストリ UUID、fragment 由来は空文字。\n */\n readonly raw: string;\n}\n\nexport interface IDevtoolsSource {\n readonly id: string;\n readonly kind: \"state\";\n readonly packageVersion: string;\n getStateElements(): IStateElementSummary[];\n /**\n * state のトップレベルキー(データプロパティ + 実行可能な getter)を列挙する。\n * メソッド・`$` 始まり・ワイルドカードを含むキーは除外。\n * 状態ツリー UI の描画起点(protocol §3)。\n */\n keys(rootNode: Node): string[];\n /**\n * rootNode のツリーに載っているマウント記録の列挙(protocol v2 — D20 の可視化)。\n * マーカー(`#m<id>`)ごとに、マウント表と私有面(オーバーレイ専用アドレス空間に\n * 住むキー)を出す。マウントが無ければ空配列。\n */\n overlays(rootNode: Node): IMountOverlaySummary[];\n /**\n * rootNode のツリーの鍵付き購読を path ごとに要約する(protocol v2 追補・additive、要件 D17)。\n * 購読は依存グラフの動的な変化なので、イベントではなくここで取る(protocol §4.6)。\n * 一度も `$eq` 系が評価されていなければ空配列。\n */\n keyedSubscriptions(rootNode: Node): IKeyedSubscriptionSummary[];\n read(rootNode: Node, path: string, indexes?: number[]): unknown;\n write(rootNode: Node, path: string, value: unknown, indexes?: number[]): void;\n /**\n * rootNode 配下の宣言レベルのバインディングを正本パーサで列挙する\n * (protocol v1 追補・additive)。live DOM(属性 + コメントアンカー)に加え、\n * DOM から引き上げられた構造テンプレート内部(fragment レジストリ)も含む —\n * DOM 再スキャン方式の declared ビューでは原理的に見えなかった領域。\n */\n getDeclaredBindings(rootNode: Node): IDeclaredBindingInfo[];\n /** registry 専用。listener の有無に応じて registry が差し替える */\n _setSink(sink: DevtoolsSink | null): void;\n}\n\nexport interface IDevtoolsListener {\n onSourceRegistered?(source: IDevtoolsSource): void;\n onSourceUnregistered?(sourceId: string): void;\n onEvent?(sourceId: string, event: DevtoolsEvent): void;\n}\n\nexport interface IDevtoolsHookRegistry {\n readonly version: number;\n readonly sources: Map<string, IDevtoolsSource>;\n register(source: IDevtoolsSource): void;\n unregister(sourceId: string): void;\n /** 戻り値は解除関数。既登録 source は onSourceRegistered で即時リプレイされる */\n addListener(listener: IDevtoolsListener): () => void;\n}\n","/**\n * devtools/bridge.ts\n *\n * DevTools Hook Protocol (docs/devtools-hook-protocol.md) の state 側実装。\n *\n * - registry 最小実装: `globalThis.__WCSTACK_DEVTOOLS_HOOK__` を create-if-missing で\n * 確保する(ロード順非依存・先勝ち。devtools 側 client も同一仕様の実装を持つ)。\n * - source: この state モジュールコピーを 1 source として登録する。同一ページに\n * コピーが複数あれば複数 source になる(正常系、protocol §5)。\n * - sink 切替: listener の有無に応じて registry が `_setSink` を呼び、ここで\n * updater の drain リスナー登録/解除も連動させる(protocol §4.3)。\n */\n\nimport { inSsr } from \"../config\";\nimport { DEVTOOLS_LISTENER_PRIORITY } from \"../define\";\nimport { getStateElement, getLiveStateElements } from \"../stateElementByName\";\n// スコープ機能のコードは静的 import しない(要件 B13 — devtools だけのページに\n// `webComponent/*` が乗る)。受け口は bridge/featureBridge.ts で、scopes の install が置く\nimport { getMountOverlays } from \"../bridge/featureBridge\";\nimport { registerUpdateBatchListener, unregisterUpdateBatchListener, UpdateBatchListener } from \"../updater/updater\";\nimport { raiseError } from \"../raiseError\";\nimport { VERSION } from \"../version\";\nimport { IStateElement } from \"../components/types\";\nimport { collectDeclaredBindings } from \"./declaredBindings\";\nimport { collectKeyedSubscriptions } from \"./keyedSubscriptions\";\nimport { devtoolsSink, setDevtoolsSink } from \"../platform/devtoolsSink\";\nimport {\n DEVTOOLS_HOOK_GLOBAL,\n DEVTOOLS_PROTOCOL_VERSION,\n DevtoolsSink,\n IDevtoolsHookRegistry,\n IDevtoolsListener,\n IDevtoolsSource,\n IStateElementSummary,\n IMountOverlaySummary,\n} from \"./types\";\n\n/**\n * registry の最小実装(protocol §2)。30 行程度に抑え、振る舞いは\n * 「source/listener の管理と sink の配線」のみ。台帳・整形は devtools 側の責務。\n */\nfunction createMinimalRegistry(): IDevtoolsHookRegistry {\n const sources = new Map<string, IDevtoolsSource>();\n const listeners = new Set<IDevtoolsListener>();\n const applySink = (source: IDevtoolsSource): void => {\n if (listeners.size === 0) {\n source._setSink(null);\n return;\n }\n const sourceId = source.id;\n source._setSink((event) => {\n for (const listener of listeners) {\n listener.onEvent?.(sourceId, event);\n }\n });\n };\n return {\n version: DEVTOOLS_PROTOCOL_VERSION,\n sources,\n register(source: IDevtoolsSource): void {\n if (sources.has(source.id)) {\n return;\n }\n sources.set(source.id, source);\n applySink(source);\n for (const listener of listeners) {\n listener.onSourceRegistered?.(source);\n }\n },\n unregister(sourceId: string): void {\n const source = sources.get(sourceId);\n if (source === undefined) {\n return;\n }\n source._setSink(null);\n sources.delete(sourceId);\n for (const listener of listeners) {\n listener.onSourceUnregistered?.(sourceId);\n }\n },\n addListener(listener: IDevtoolsListener): () => void {\n listeners.add(listener);\n // 既登録 source をリプレイ(遅延アタッチの起点、protocol §6)\n for (const source of sources.values()) {\n applySink(source);\n listener.onSourceRegistered?.(source);\n }\n return () => {\n if (!listeners.delete(listener)) {\n return;\n }\n for (const source of sources.values()) {\n applySink(source);\n }\n };\n },\n };\n}\n\nexport function getOrCreateHookRegistry(): IDevtoolsHookRegistry {\n const globals = globalThis as unknown as Record<string, unknown>;\n const existing = globals[DEVTOOLS_HOOK_GLOBAL] as IDevtoolsHookRegistry | undefined;\n if (existing !== undefined) {\n if (existing.version !== DEVTOOLS_PROTOCOL_VERSION) {\n // 先勝ち固定。振る舞いは差し替えない(protocol §2)\n console.warn(\n `[wcstack/state] devtools hook registry version mismatch: found ${existing.version}, expected ${DEVTOOLS_PROTOCOL_VERSION}. Keeping the existing registry (first-wins).`\n );\n }\n return existing;\n }\n const registry = createMinimalRegistry();\n globals[DEVTOOLS_HOOK_GLOBAL] = registry;\n return registry;\n}\n\n/**\n * drain 終了バッチの転送リスナー。sink 接続中のみ updater に登録される。\n */\nconst onUpdateBatch: UpdateBatchListener = (batch) => {\n if (devtoolsSink !== null) {\n devtoolsSink({ type: \"state:update-batch\", addresses: batch });\n }\n};\n\n/**\n * registry からの sink 差し替え。updater の drain リスナー登録/解除を連動させる。\n * detach 時に登録が残らないこと(protocol §7-2)。\n */\nfunction setSink(sink: DevtoolsSink | null): void {\n const wasActive = devtoolsSink !== null;\n setDevtoolsSink(sink);\n const isActive = sink !== null;\n if (isActive && !wasActive) {\n // `$watch` / `$streams` restart より先に流す(protocol §4.3)。優先度を省略しても\n // 既定 0 で結果は同じだが、それは偶然なので定数で意図を固定する。\n registerUpdateBatchListener(onUpdateBatch, DEVTOOLS_LISTENER_PRIORITY);\n } else if (!isActive && wasActive) {\n unregisterUpdateBatchListener(onUpdateBatch);\n }\n}\n\nfunction createStateElementSummary(element: IStateElement): IStateElementSummary {\n return {\n rootNode: element.rootNode,\n element,\n paths: {\n list: element.listPaths,\n element: element.elementPaths,\n getter: element.getterPaths,\n setter: element.setterPaths,\n },\n commandTokenNames: element.commandTokenNames,\n eventTokenNames: element.eventTokenNames,\n staticDependency: element.staticDependency,\n dynamicDependency: element.dynamicDependency,\n // protocol v1 追補(additive)— 配線カバレッジの宣言面(設計 §4)。\n // 旧 IStateElement 実装(optional)では undefined になりうるため null に畳む。\n watchPaths: element.watchPaths ?? null,\n // ワイルドカード行 watch の発火前提「for バインド or $listKeys 宣言」の後者。\n // ListKeyMap のキー(リストパス)だけを出す — キー指定(文字列/関数)は\n // カバレッジ判定に不要で、関数はシリアライズ境界にも載せない。\n keyedListPaths:\n element.listKeys === undefined || element.listKeys === null\n ? null\n : new Set(element.listKeys.keys()),\n };\n}\n\nfunction requireStateElement(rootNode: Node): IStateElement {\n return getStateElement(rootNode) ??\n raiseError(`devtools: no state tree on this root`);\n}\n\nfunction createSourceId(): string {\n // getUUID() はモジュールローカル連番のため、state コピーが複数ある\n // ページで source id が衝突する。ランダム採番で回避する。\n return \"state:\" + Math.random().toString(36).slice(2, 10);\n}\n\nlet registeredSource: IDevtoolsSource | null = null;\n\n/**\n * この state ランタイムを 1 source として registry に登録する。\n * bootstrapState() から呼ばれる。冪等・SSR では何もしない(protocol 原則 6)。\n */\nexport function registerDevtoolsSource(): void {\n if (inSsr()) {\n return;\n }\n if (registeredSource !== null) {\n return;\n }\n const source: IDevtoolsSource = {\n id: createSourceId(),\n kind: \"state\",\n packageVersion: VERSION,\n getStateElements(): IStateElementSummary[] {\n const summaries: IStateElementSummary[] = [];\n for (const element of getLiveStateElements()) {\n summaries.push(createStateElementSummary(element));\n }\n return summaries;\n },\n overlays(rootNode: Node): IMountOverlaySummary[] {\n // スコープ機能が入っていないページにマウントは存在しえない(受け口が空なら空配列)\n return getMountOverlays(requireStateElement(rootNode));\n },\n keyedSubscriptions(rootNode: Node) {\n return collectKeyedSubscriptions(requireStateElement(rootNode));\n },\n keys(rootNode: Node): string[] {\n const element = requireStateElement(rootNode);\n const result: string[] = [];\n element.createState(\"readonly\", (state) => {\n // Object.keys は Proxy の ownKeys 経由で target の own key を返す。\n // メソッド判別の typeof アクセスは getter を 1 回実行する副作用があるため、\n // ループ文脈依存で throw する getter は catch して「キーとしては存在する」\n // 側に倒す(値の表示可否は UI 側の責務)。\n for (const key of Object.keys(state)) {\n if (key.includes(\"*\") || key.startsWith(\"$\")) {\n continue;\n }\n try {\n if (typeof (state as unknown as Record<string, unknown>)[key] === \"function\") {\n continue;\n }\n } catch {\n // 読めない getter もキーとしては列挙する\n }\n result.push(key);\n }\n });\n return result;\n },\n read(rootNode: Node, path: string, indexes?: number[]): unknown {\n const element = requireStateElement(rootNode);\n let result: unknown;\n element.createState(\"readonly\", (state) => {\n result = (state as unknown as Record<string, (p: string, i: number[]) => unknown>)[\"$resolve\"](path, indexes ?? []);\n });\n return result;\n },\n write(rootNode: Node, path: string, value: unknown, indexes?: number[]): void {\n const element = requireStateElement(rootNode);\n element.createState(\"writable\", (state) => {\n if (indexes !== undefined && indexes.length > 0) {\n // 3 引数の $resolve は値が undefined でも書き(引数の個数で読み書きを分ける — 要件 B7)\n (state as unknown as Record<string, (p: string, i: number[], v: unknown) => void>)[\"$resolve\"](path, indexes, value);\n } else {\n (state as unknown as Record<string, unknown>)[path] = value;\n }\n });\n },\n getDeclaredBindings(rootNode: Node) {\n return collectDeclaredBindings(rootNode);\n },\n _setSink: setSink,\n };\n registeredSource = source;\n getOrCreateHookRegistry().register(source);\n}\n\n/**\n * テスト専用: sink を解除し source 登録状態をリセットする。\n * グローバル registry 自体の掃除(delete globalThis[DEVTOOLS_HOOK_GLOBAL])は\n * テスト側の責務。アプリケーションからの利用は想定しない。\n */\nexport function __resetDevtoolsBridgeForTest(): void {\n setSink(null);\n registeredSource = null;\n}\n\n/** テスト専用: 現在登録済みの source を返す */\nexport function __getRegisteredSourceForTest(): IDevtoolsSource | null {\n return registeredSource;\n}\n","/**\n * devtools/keyedSubscriptions.ts\n *\n * DevTools Hook Protocol の `keyedSubscriptions(rootNode)`(protocol v2 追補・要件 D17)。\n * 鍵付き購読(`$eq` / `$eqPath` / `$eqIndex`)の台帳を path ごとに数えて返す。\n *\n * 購読は描画のたびに行単位で増減する依存グラフの動的な変化なので、イベントでは流さず\n * pull で取る(protocol §4.6。1 万行の描画で 1 万件のイベントにしない)。数えるのは\n * 呼ばれた時点だけで、台帳への参照は返さない。このモジュールは devtools 機能だけが\n * import する — core は台帳を読ませるアクセサしか持たない。\n */\n\nimport { IStateElement } from \"../components/types\";\nimport { getKeyedLedgerView } from \"../dependency/keyedDependency\";\nimport { IKeyedSubscriptionSummary } from \"./types\";\n\nexport function collectKeyedSubscriptions(stateElement: IStateElement): IKeyedSubscriptionSummary[] {\n const view = getKeyedLedgerView(stateElement);\n if (view === null) {\n return [];\n }\n const paths = new Set<string>([...view.byPath.keys(), ...view.tracked]);\n if (typeof view.watchers !== \"undefined\") {\n for (const path of view.watchers.keys()) {\n paths.add(path);\n }\n }\n const summaries: IKeyedSubscriptionSummary[] = [];\n for (const path of [...paths].sort()) {\n const keyMap = view.byPath.get(path);\n let rows = 0;\n if (typeof keyMap !== \"undefined\") {\n for (const addresses of keyMap.values()) {\n rows += addresses.size;\n }\n }\n summaries.push({\n path,\n tracked: view.tracked.has(path),\n rows,\n keys: keyMap?.size ?? 0,\n lists: view.watchers?.get(path)?.size ?? 0,\n lastValue: view.lastValue.get(path),\n });\n }\n return summaries;\n}\n","/**\n * features/devtools.ts — DevTools Hook Protocol への source 登録(@wcstack/state/features/devtools)。\n */\nimport type { IStateFeature } from \"../core/features\";\nimport { registerDevtoolsSource } from \"../devtools/bridge\";\n\nexport const devtools: IStateFeature = {\n name: \"devtools\",\n install(): void {\n registerDevtoolsSource();\n },\n};\nexport default devtools;\n"],"names":["COMMENT_PATTERN","toInfo","node","parsed","origin","raw","propName","statePathName","bindingType","inFilters","outFilters","collectDeclaredBindings","rootNode","out","seenKeys","Set","visitedFragments","push","info","key","filters","filterListKey","join","declarationKey","has","add","visitFragment","uuid","fragmentInfo","getFragmentInfoByUUID","nodeInfo","nodeInfos","parseBindTextResults","walker","ownerDocument","createTreeWalker","NodeFilter","SHOW_ELEMENT","SHOW_COMMENT","nextNode","nodeType","Node","ELEMENT_NODE","element","bindText","getAttribute","config","bindAttributeName","length","results","expandSpread","parseBindTextsForElement","match","exec","data","trim","keyword","commentTextPrefix","value","parseBindTextForEmbeddedNode","parseBindTextResult","DEVTOOLS_HOOK_GLOBAL","getOrCreateHookRegistry","globals","globalThis","existing","undefined","version","console","warn","registry","sources","Map","listeners","applySink","source","size","_setSink","sourceId","id","event","listener","onEvent","register","set","onSourceRegistered","unregister","get","delete","onSourceUnregistered","addListener","values","createMinimalRegistry","onUpdateBatch","batch","devtoolsSink","type","addresses","setSink","sink","wasActive","setDevtoolsSink","isActive","registerUpdateBatchListener","DEVTOOLS_LISTENER_PRIORITY","unregisterUpdateBatchListener","createStateElementSummary","paths","list","listPaths","elementPaths","getter","getterPaths","setter","setterPaths","commandTokenNames","eventTokenNames","staticDependency","dynamicDependency","watchPaths","keyedListPaths","listKeys","keys","requireStateElement","getStateElement","raiseError","registeredSource","registerDevtoolsSource","inSsr","Math","random","toString","slice","kind","packageVersion","VERSION","getStateElements","summaries","getLiveStateElements","overlays","getMountOverlays","keyedSubscriptions","stateElement","view","getKeyedLedgerView","byPath","tracked","watchers","path","sort","keyMap","rows","lists","lastValue","collectKeyedSubscriptions","result","createState","state","Object","includes","startsWith","read","indexes","write","getDeclaredBindings","devtools","name","install"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"diagnostics.js","sources":["../../../../../src/diagnostics/pathChecks.ts","../../../../../src/diagnostics/install.ts","../../../../../src/features/diagnostics.ts"],"sourcesContent":["/**\n * diagnostics/pathChecks.ts — バインド / `$watch` / `$scan` 対象パスの存在検査\n * (@wcstack/state/features/diagnostics。silent failure の可視化)。\n *\n * なぜ必要か:\n * `getByAddress` は「親が null / undefined のパスの読み」を undefined で返し、\n * undefined はプロパティ書き込みがスキップされる値なので、`user.nmae` のような\n * 打ち間違いは**エラーも警告も出さずに DOM が更新されない**だけになる。一方で\n * トップレベルの打ち間違い(`cout`)は parentAddress を辿れず raiseError で落ちる。\n * 同じ「パスを打ち間違えた」という 1 つの失敗が、パスの深さで silent / loud に\n * 割れており、書き手からは区別がつかない。ここはその silent 側を埋める。\n *\n * 開発時の診断なので機能として分けてある(full / auto は入れる。`@wcstack/state/core` だけの\n * ページは入れなければ警告を出さない — 本番向けの軽量形)。core の受け口は\n * core/diagnosticsHooks.ts。\n *\n * 精度方針(過小近似):\n * 「確実に存在しない」と言い切れる場合にだけ報告する。getter の戻り値の先・\n * 空配列・null 親・mapped な `bind-component` など、静的に決められない形はすべて\n * `\"unknown\"` に倒して黙る(偽陽性ゼロ優先。docs/static-wiring-dx-design.md D7 /\n * [ADR-06](../../../docs/architecture-hardening/06-path-type-safety.md) の精度哲学)。\n */\n\nimport { getPathInfo } from \"../address/PathInfo\";\nimport type { IStateElement } from \"../components/types\";\nimport { DELIMITER, WILDCARD } from \"../define\";\nimport { devtoolsSink } from \"../platform/devtoolsSink\";\nimport { didYouMean, LINT_HINT } from \"../errorGuidance\";\nimport {\n collectCandidates, DIAGNOSTIC_CODE, findDescriptor, indexPathRowPaths, PathInfoSource, SUBJECT,\n} from \"../pathDiagnostics\";\n\nexport type PathExistence = \"exists\" | \"missing\" | \"unknown\";\n\nexport interface IPathExistenceResult {\n readonly existence: PathExistence;\n /** `\"missing\"` のとき、解決に失敗したセグメント */\n readonly missingSegment: string;\n /** `\"missing\"` のとき、その階層に実在する兄弟キー(did-you-mean 用) */\n readonly candidates: readonly string[];\n}\n\nconst UNKNOWN: IPathExistenceResult = Object.freeze({\n existence: \"unknown\" as const,\n missingSegment: \"\",\n candidates: Object.freeze([]) as readonly string[],\n});\n\nconst EXISTS: IPathExistenceResult = Object.freeze({\n existence: \"exists\" as const,\n missingSegment: \"\",\n candidates: Object.freeze([]) as readonly string[],\n});\n\n/**\n * `target` に対して `path` が解決しうるかを、値を読まずに(getter を評価せずに)判定する。\n *\n * 解決の順序は `getByAddress` の実装に合わせる: まず「パス文字列そのものがキーか」\n * (ドットパス getter がこれ)、次にセグメントを 1 つずつ降りる。\n *\n * `indexPath` は数値添字の束縛(address/indexPathAccessor.ts の isIndexPath・#332): いまその位置にある\n * 行を読むので、親が配列なら数値の区切りを `*` として降りる(`items.0.double` は行 getter\n * `items.*.double` を見る。行の形は `*` と同じく先頭の要素で見る)。負の添字のような行になりえない\n * 区切りと、配列でない親(`sales.2024.total`)は素のキーのまま検査する。\n */\nexport function resolvePathExistence(\n target: object,\n path: string,\n declaredPaths: Iterable<string>,\n indexPath?: boolean,\n): IPathExistenceResult {\n // ドットパス getter / フラットキーの完全一致(`get \"users.*.fullName\"()` 等)\n if (findDescriptor(target, path) !== undefined) {\n return EXISTS;\n }\n const segments = getPathInfo(path).segments;\n let current: unknown = target;\n let prefix = \"\";\n for (let i = 0; i < segments.length; i++) {\n const segment = indexPath && Array.isArray(current) && +segments[i] >= 0 ? WILDCARD : segments[i];\n const parentPrefix = prefix;\n prefix = i === 0 ? segment : prefix + DELIMITER + segment;\n // 途中のプレフィックスがフラット宣言されている(`cart.totalPrice` が getter で、\n // その戻り値のサブプロパティを読む形)。戻り値の形は評価しないと分からない。\n // 末尾で当たるのは `*` へ読み替えたパスだけ(読み替えの無いパスは冒頭の完全一致で済んでいる)。\n // 数値添字の束縛の暗黙の getter(`for: groups.0.items` が生やす — #332)は作者の宣言ではない: いまその\n // 位置にある行を読むだけなので、それが読む行のパス(`groups.*.items`)の宣言に読み替え、宣言が無ければ\n // そのままデータを辿る(#388)。作者の宣言は接頭辞そのものの宣言\n const declared = i > 0 && findDescriptor(target, prefix);\n const rowPath = declared && indexPathRowPaths.get(declared.get as object);\n if (declared && (!rowPath || findDescriptor(target, rowPath))) {\n return i < segments.length - 1 ? UNKNOWN : EXISTS;\n }\n // null / undefined / primitive より深い読みは実行時 undefined 解決 = 判定不能。\n // 「初期値 null のオブジェクトに後から代入する」形を偽陽性で潰さないため\n if (Object(current) !== current) {\n return UNKNOWN;\n }\n if (segment === WILDCARD) {\n // 行の形は「いま入っている要素」からしか分からない。空配列・非配列は判定不能\n if (!Array.isArray(current) || current.length === 0) {\n return UNKNOWN;\n }\n current = current[0];\n continue;\n }\n const descriptor = findDescriptor(current as object, segment);\n if (typeof descriptor === \"undefined\") {\n return {\n existence: \"missing\",\n missingSegment: segment,\n candidates: collectCandidates(current as object, parentPrefix, declaredPaths, target),\n };\n }\n if (typeof descriptor.get === \"function\") {\n // getter の戻り値の先は評価しないと分からない(末尾なら存在は確定)\n return i === segments.length - 1 ? EXISTS : UNKNOWN;\n }\n current = descriptor.value;\n }\n return EXISTS;\n}\n\n/** 同じ (state 要素, パス) の報告は 1 回だけにする台帳 */\nconst reportedPathsByStateElement: WeakMap<IStateElement, Set<string>> = new WeakMap();\n\n/** テスト間の分離用(本番経路からは呼ばれない) */\nexport function clearReportedPaths(stateElement: IStateElement): void {\n reportedPathsByStateElement.delete(stateElement);\n}\n\n/**\n * state の世代が進んだ(再セット)ときに、この要素の検査済みの印と遅延中の報告を捨てる(#270)。\n *\n * 「パスごとに 1 回」は同じ誤りを更新のたびに報告し続けないための台帳で、世代をまたいで\n * 持ち越す理由は無い。持ち越すと、第 1 世代で検査済みのパスが第 2 世代の state から消えても\n * 無言のままになる。遅延中の報告は前の世代の state で判定した結果なので、あわせて捨てる\n * (呼び手の経路情報の作り直しが、新しい世代で判定し直して積み直す)。公開 getter の登録\n * (`markExportedPath`)はマウント記録の寿命に属するので触らない。\n */\nexport function resetPathDiagnostics(stateElement: IStateElement): void {\n reportedPathsByStateElement.delete(stateElement);\n deferredReportsByStateElement.delete(stateElement);\n}\n\nfunction alreadyReported(stateElement: IStateElement, path: string): boolean {\n let reported = reportedPathsByStateElement.get(stateElement);\n if (typeof reported === \"undefined\") {\n reported = new Set<string>();\n reportedPathsByStateElement.set(stateElement, reported);\n }\n if (reported.has(path)) {\n return true;\n }\n reported.add(path);\n return false;\n}\n\n/**\n * バインド確立時 / `$watch` 宣言時にパスの存在を検査し、確実に存在しないものだけ報告する。\n *\n * 報告は `console.warn` に留める(`raiseError` にしない):\n * 判定は過小近似とはいえ動的にキーが生える形まで排除できたわけではなく、\n * 既存ページを起動不能にする代償に見合わない。silent を破ることが目的であり、\n * 停止させることではない。\n */\nexport function checkDeclaredPath(\n stateElement: IStateElement,\n state: object | undefined,\n path: string,\n source: PathInfoSource,\n indexPath?: boolean,\n): void {\n if (source === \"internal\" || typeof state === \"undefined\") {\n return;\n }\n // `$command` / `$streamStatus` / `$1` 等の予約名前空間は raw state に実体を持たない\n if (path.startsWith(\"$\")) {\n return;\n }\n // マウントの予約セグメント(`users.*.#m1.editing` — D20)はオーバーレイに実体があり\n // raw state には無い。`#else`(構造プレースホルダ)も同様(webComponent/mount.ts)\n if (path.indexOf(\"#\") !== -1) {\n return;\n }\n // 機能が黙らせる領域(予約済みボリュームスロットの配下 — D22。webComponent/addressHooks.ts)は\n // suppressPathDiagnostic hook に聞く。hook の無い state は判定 1 個で抜ける\n const hooks = stateElement.addressHooks;\n if (hooks) {\n const suppress = hooks.suppressPathDiagnostic;\n for (let i = 0; i < suppress.length; i++) {\n if (suppress[i](stateElement, path)) {\n return;\n }\n }\n }\n\n // 単一セグメントのバインディングは読み取り時に raiseError で loud に落ちるので、\n // ここで二重に報告しない。`$watch` は落ちずに黙って発火しないだけなので検査する\n const segments = getPathInfo(path).segments;\n if (source === \"binding\" && segments.length < 2) {\n return;\n }\n if (alreadyReported(stateElement, path)) {\n return;\n }\n // 再帰 getter の展開形は、バインド確立の時点ではまだ生えていない(読む直前に\n // 遅延実体化する — recursion/registry.ts)。素の存在検査では必ず「解決できない」に\n // なるので、宣言済みの `**` getter に合致するかを先に見る。実体化はしない。\n // 展開形の**値の内側**(`nodes.*.stats.count` で `get \"nodes.**.stats\"()` がオブジェクトを\n // 返す形)も同じ — 通常の getter なら下の「途中のプレフィックスがフラット宣言」で\n // UNKNOWN に倒れるところ、未実体化のアクセサは findDescriptor に見えないのでここで畳む。\n if (stateElement.hasRecursion === true && stateElement.recursionRegistry!.recursiveGetterOwning(path) !== null) {\n return;\n }\n const result = resolvePathExistence(state, path, stateElement.getterPaths, indexPath);\n if (result.existence !== \"missing\") {\n return;\n }\n if (isExportedPath(stateElement, path)) {\n return;\n }\n if (source === \"binding\") {\n // 遅延報告(docs/state-overlay-export-design.md X7): バインド確立時点では、その位置に\n // マウントされるコンポーネントの getter(公開 getter)がまだ登録されていない。\n // 1 マクロタスク待って、登録で解消しなかったものだけを報告する\n deferReport(stateElement, path, result);\n return;\n }\n reportMissing(stateElement, path, source, result);\n}\n\ninterface IMissingResult {\n readonly missingSegment: string;\n readonly candidates: readonly string[];\n}\n\nconst deferredReportsByStateElement = new WeakMap<IStateElement, Map<string, IMissingResult>>();\nconst flushScheduled = new WeakSet<IStateElement>();\nconst exportedPathsByStateElement = new WeakMap<IStateElement, Set<string>>();\n\n/** 公開 getter の登録(webComponent/exportIndex.ts)— このパスは「存在しない」ではない */\nexport function markExportedPath(stateElement: IStateElement, path: string): void {\n let paths = exportedPathsByStateElement.get(stateElement);\n if (typeof paths === \"undefined\") {\n paths = new Set<string>();\n exportedPathsByStateElement.set(stateElement, paths);\n }\n paths.add(path);\n deferredReportsByStateElement.get(stateElement)?.delete(path);\n}\n\nfunction isExportedPath(stateElement: IStateElement, path: string): boolean {\n return exportedPathsByStateElement.get(stateElement)?.has(path) === true;\n}\n\nfunction deferReport(stateElement: IStateElement, path: string, result: IMissingResult): void {\n let pending = deferredReportsByStateElement.get(stateElement);\n if (typeof pending === \"undefined\") {\n pending = new Map();\n deferredReportsByStateElement.set(stateElement, pending);\n }\n pending.set(path, result);\n if (flushScheduled.has(stateElement)) {\n return;\n }\n flushScheduled.add(stateElement);\n setTimeout(() => flushDeferredPathReports(stateElement), 0);\n}\n\n/** 遅延中の報告を今すぐ流す(タイマー到達時・テスト用) */\nexport function flushDeferredPathReports(stateElement: IStateElement): void {\n flushScheduled.delete(stateElement);\n const pending = deferredReportsByStateElement.get(stateElement);\n if (typeof pending === \"undefined\") {\n return;\n }\n deferredReportsByStateElement.delete(stateElement);\n // 登録で解消したものは markExportedPath が pending から消している\n for (const [path, result] of pending) {\n reportMissing(stateElement, path, \"binding\", result);\n }\n}\n\nfunction reportMissing(\n stateElement: IStateElement,\n path: string,\n source: PathInfoSource & (\"binding\" | \"watch\" | \"scan\"),\n result: IMissingResult,\n): void {\n // 接頭辞は raiseError と同じ `[@wcstack/state] [wcs/...]` の並び(コンソールの\n // grep 単位をパッケージで揃える)\n console.warn(\n `[@wcstack/state] [${DIAGNOSTIC_CODE[source]}] ${SUBJECT[source]} \"${path}\" does not resolve on the state tree: ` +\n `\"${result.missingSegment}\" is not declared.${didYouMean(result.missingSegment, result.candidates)}` +\n ` Updates to this path will be silently dropped.${LINT_HINT}`,\n );\n if (devtoolsSink !== null) {\n devtoolsSink({\n type: \"state:path-unresolved\",\n source,\n path,\n missingSegment: result.missingSegment,\n });\n }\n}\n","/**\n * diagnostics/install.ts — 開発時の診断の install(@wcstack/state/features/diagnostics)。\n * core の受け口(core/diagnosticsHooks.ts)へ、束縛時のパス存在検査を置く。\n */\nimport { IPathDiagnostics, setPathDiagnostics } from \"../core/diagnosticsHooks\";\nimport { checkDeclaredPath, markExportedPath, resetPathDiagnostics } from \"./pathChecks\";\n\n/** 受け口へ置く実装。外へ出す口は `installDiagnostics` だけなので export しない */\nconst pathDiagnosticsFeature: IPathDiagnostics = {\n check: checkDeclaredPath,\n reset: resetPathDiagnostics,\n markExported: markExportedPath,\n};\n\nlet installed = false;\n/** 冪等。full / auto では `bootstrapState()` が呼ぶ */\nexport function installDiagnostics(): void {\n if (installed) return;\n installed = true;\n setPathDiagnostics(pathDiagnosticsFeature);\n}\n","/**\n * features/diagnostics.ts — 開発時の診断(@wcstack/state/features/diagnostics)。\n * 束縛・`$watch`・`$scan` のパスが state に無いときの console.warn(did-you-mean 付き)。\n * 入れなければ何も出さない — `@wcstack/state/core` だけの本番ページの軽量形。\n */\nimport type { IStateFeature } from \"../core/features\";\nimport { installDiagnostics } from \"../diagnostics/install\";\n\nexport const diagnostics: IStateFeature = {\n name: \"diagnostics\",\n install(): void {\n installDiagnostics();\n },\n};\nexport default diagnostics;\n"],"names":["UNKNOWN","Object","freeze","existence","missingSegment","candidates","EXISTS","reportedPathsByStateElement","WeakMap","deferredReportsByStateElement","flushScheduled","WeakSet","exportedPathsByStateElement","reportMissing","stateElement","path","source","result","console","warn","DIAGNOSTIC_CODE","SUBJECT","didYouMean","LINT_HINT","devtoolsSink","type","pathDiagnosticsFeature","check","state","indexPath","startsWith","indexOf","hooks","addressHooks","suppress","suppressPathDiagnostic","i","length","segments","getPathInfo","reported","get","Set","set","has","add","alreadyReported","hasRecursion","recursionRegistry","recursiveGetterOwning","target","declaredPaths","undefined","findDescriptor","current","prefix","segment","Array","isArray","WILDCARD","parentPrefix","DELIMITER","declared","rowPath","indexPathRowPaths","descriptor","collectCandidates","value","resolvePathExistence","getterPaths","isExportedPath","pending","Map","setTimeout","delete","flushDeferredPathReports","deferReport","reset","markExported","paths","installed","diagnostics","name","install","setPathDiagnostics"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"formats.js","sources":["../../../../../src/formats/errorMessages.ts","../../../../../src/filters/filterAliases.ts","../../../../../src/formats/builtinFilters.ts","../../../../../src/formats/install.ts","../../../../../src/features/formats.ts"],"sourcesContent":["/**\n * errorMessages.ts\n *\n * Error message generation utilities used by filter functions.\n *\n * Main responsibilities:\n * - Throws clear error messages when filter options or value type checks fail\n * - Takes function name as argument to specify which filter caused the error\n *\n * Design points:\n * - optionsRequired: Error when required option is not specified\n * - optionMustBeNumber: Error when option value is not a number\n * - valueMustBeNumber: Error when value is not a number\n * - valueMustBeDate: Error when value is not a Date\n * - valueMustBeArray: Error when value is not an array\n */\nimport { raiseError } from \"../raiseError\";\n\n/**\n * Throws error when filter requires at least one option but none provided.\n * \n * @param fnName - Name of the filter function\n * @returns Never returns (always throws)\n */\nexport function optionsRequired(fnName:string): never {\n raiseError(`filter ${fnName} requires at least one option`);\n}\n\n/**\n * Throws error when filter option must be a number but invalid value provided.\n * \n * @param fnName - Name of the filter function\n * @returns Never returns (always throws)\n */\nexport function optionMustBeNumber(fnName:string): never {\n raiseError(`filter ${fnName} requires a number as option`);\n}\n\n/**\n * Throws error when filter requires numeric value but non-number provided.\n * \n * @param fnName - Name of the filter function\n * @returns Never returns (always throws)\n */\nexport function valueMustBeNumber(fnName:string): never {\n raiseError(`filter ${fnName} requires a number value`);\n}\n\n/**\n * Throws error when filter requires Date value but non-Date provided.\n *\n * @param fnName - Name of the filter function\n * @returns Never returns (always throws)\n */\nexport function valueMustBeDate(fnName:string): never {\n raiseError(`filter ${fnName} requires a date value`);\n}\n\n/**\n * Throws error when filter requires array value but non-array provided.\n *\n * @param fnName - Name of the filter function\n * @returns Never returns (always throws)\n */\nexport function valueMustBeArray(fnName:string): never {\n raiseError(`filter ${fnName} requires an array value`);\n}","/**\n * filters/filterAliases.ts — 組み込みフィルタの旧名 → 正式名(要件 B12・docs/state-3x-naming.ja.md V1〜V9)。\n *\n * 旧名は 3.x の間エイリアスとして残り、4.0 で外す(D4)。解決は登録簿(core/filterRegistry)が行い、\n * 実装・引数の個数・メタデータは正式名だけが持つ。formats の install と manifest(tooling)の両方が読むので、\n * 実装にもメタデータにも依存しない小さな表として独立させている。\n */\nexport const builtinFilterAliases: Readonly<Record<string, string>> = {\n inc: \"add\",\n dec: \"sub\",\n fix: \"toFixed\",\n uc: \"upper\",\n lc: \"lower\",\n cap: \"capitalize\",\n rep: \"repeat\",\n rev: \"reverse\",\n pad: \"padStart\",\n null: \"nullIfEmpty\",\n};\n","/**\n * builtinFilters.ts\n *\n * Implementation file for built-in filter functions available in Structive.\n *\n * Main responsibilities:\n * - Provides filters for conversion, comparison, formatting, and validation of numbers, strings, dates, booleans, etc.\n * - Defines functions with options for each filter name, enabling flexible use during binding\n * - Designed for common use as both input and output filters\n *\n * Design points:\n * - Comprehensive coverage of diverse filters: eq, ne, lt, gt, inc, abs, clamp, fix, locale, uc, lc, cap, trim, slice, pad, truncate, join, int, float, round, percent, unit, date, time, ymd, hms, falsy, truthy, defaults, boolean, number, string, null, etc.\n * - Rich type checking and error handling for option values\n * - Centralized management of filter functions with FilterWithOptions type, easy to extend\n * - Dynamic retrieval of filter functions from filter names and options via builtinFilterFn\n */\nimport { config } from \"../config.js\";\nimport { didYouMean, LINT_HINT } from \"../errorGuidance.js\";\nimport { raiseError } from \"../raiseError.js\";\nimport { optionMustBeNumber, optionsRequired, valueMustBeArray, valueMustBeDate, valueMustBeNumber } from \"./errorMessages.js\";\nimport { FilterFn, FilterWithOptions, FilterWithOptionsFn } from \"../filters/types\";\nimport { builtinFilterAliases } from \"../filters/filterAliases\";\nimport { v4Migration } from \"../core/v4MigrationHooks\";\n\nfunction validateNumberString(value: string): boolean {\n if (!value || isNaN(Number(value))) {\n return false;\n }\n return true;\n}\n\n/**\n * Reads one numeric option **at construction time** and returns the number itself.\n *\n * Every numeric filter used to write out the same three lines (`options?.[i] ?? optionsRequired`,\n * `validateNumberString`, `optionMustBeNumber`) and then call `Number(opt)` again on **every\n * apply**. Folding it here keeps the option checks in one place and takes the conversion off the\n * hot path.\n */\nfunction numberOption(value: string, fnName: string): number {\n if (!validateNumberString(value)) {optionMustBeNumber(fnName);}\n return Number(value);\n}\n\n/** A required numeric option: missing → `optionsRequired`, non-numeric → `optionMustBeNumber`. */\nfunction requiredNumberOption(options: string[] | undefined, index: number, fnName: string): number {\n return numberOption(options?.[index] ?? optionsRequired(fnName), fnName);\n}\n\n/**\n * Extends the display-side empty-value contract (requirement B8) to the formatting filters.\n *\n * A filter that builds its result with `String(value)` turns `undefined` / `null` into the\n * *characters* `\"undefined\"` / `\"null\"`, so `attr.title: x|trim` wrote `title=\"undefined\"` where\n * the unfiltered `attr.title: x` correctly removes the attribute — one filter was enough to undo\n * what B8 established. This family therefore passes an absent value straight through and leaves\n * the decision to the apply side (attribute removed, class cleared, style cleared, text emptied).\n *\n * Deliberately **not** wrapped:\n * - filters for which an absent value *is* the input: `defaults` / `coalesce` / `nullIfEmpty` /\n * `boolean` / `truthy` / `falsy` / `not` / `eq` / `ne`;\n * - the conversions, whose whole job is to convert: `int` / `float` / `number` / `string`;\n * - the filters that reject a value of the wrong type (the number, date and array families).\n * Their throw is confined to the one binding and reported through `$errorCallback`\n * (`apply/applyChangeFromBindings.ts`), which is a different quality of failure from silently\n * painting the word \"undefined\" into the page.\n */\nconst nullishPassthrough = (factory: (options?: string[]) => FilterFn<string>): FilterWithOptionsFn =>\n (options?: string[]): FilterFn => {\n const filterFn = factory(options);\n // `== null` は null と undefined の両方(意図的な緩い比較)\n return (value: unknown): unknown => (value == null ? value : filterFn(value));\n };\n\n/**\n * Equality filter - compares value with option.\n * \n * @param options - Array with comparison value as first element\n * @returns Filter function that returns boolean\n */\nconst eq = (options?:string[], literals?: readonly unknown[]): FilterFn<boolean> => {\n const opt = options?.[0] ?? optionsRequired('eq');\n // The typed literal (B9): unquoted true / false / null / numbers are typed, quoted arguments are strings\n const literal = literals !== undefined && literals.length > 0 ? literals[0] : opt;\n return (value: unknown): boolean => {\n // Align types for comparison\n if (typeof value === 'number') {\n // A typed literal that is not a string compares as itself (B9). A number is never equal to\n // `true` / `false` / `null`, so `selectedId|eq(null)` is simply false once the id is a\n // number — it used to throw `optionMustBeNumber` on **every apply**, which broke every\n // binding on a path that is sometimes null and sometimes numeric. Only an unquoted\n // non-number (a bare string such as `eq(abc)`) still reports the type mismatch, and it has\n // to stay inside the closure: `eq` accepts values of any type, so `status|eq(active)` is\n // perfectly valid and cannot be rejected at construction the way `lt` / `add` can.\n if (typeof literal !== 'string') {return value === literal;}\n if (!validateNumberString(opt)) {optionMustBeNumber('eq');}\n return value === Number(opt);\n }\n if (typeof value === 'string') {\n return value === opt;\n }\n // Booleans, null and the rest compare with the typed literal: eq(true) matches true (B9)\n return value === literal;\n }\n}\n\n/**\n * Inequality filter - compares value with option.\n * \n * @param options - Array with comparison value as first element\n * @returns Filter function that returns boolean\n */\nconst ne = (options?:string[], literals?: readonly unknown[]): FilterFn<boolean> => {\n const opt = options?.[0] ?? optionsRequired('ne');\n const literal = literals !== undefined && literals.length > 0 ? literals[0] : opt;\n return (value: unknown): boolean => {\n // Align types for comparison\n if (typeof value === 'number') {\n // Same as `eq`: a typed `true` / `false` / `null` compares as itself instead of being\n // forced through `Number()` (B9)\n if (typeof literal !== 'string') {return value !== literal;}\n if (!validateNumberString(opt)) {optionMustBeNumber('ne');}\n return value !== Number(opt);\n }\n if (typeof value === 'string') {\n return value !== opt;\n }\n // Booleans, null and the rest compare with the typed literal (B9)\n return value !== literal;\n }\n}\n\n/**\n * Boolean NOT filter - inverts the truthiness of the value.\n *\n * Deliberately the same lenient `!value` as the engine-owned copy in\n * `core/filterRegistry.ts` (`CORE_FILTERS.not`). `if:` / `else:` are built as one\n * `if` parse result plus an engine-injected `not` (`structural/createNotFilter.ts`),\n * and `apply/applyChangeToIf.ts` coerces the `if` side with `Boolean()`. A strict\n * `not` therefore made `else:` throw — and render neither branch — whenever the\n * condition was a falsy non-boolean (`0` / `\"\"` / `undefined` / `null`). Keeping the\n * two implementations identical also means a page behaves the same whether or not\n * `features/formats` is installed (the split core entry registers no `not`).\n *\n * @param options - Unused\n * @returns Filter function that returns the inverted truthiness\n */\nconst not = (_options?:string[]): FilterFn<boolean> => {\n return (value: unknown): boolean => !value;\n}\n\n/**\n * Less than filter - checks if value is less than option.\n * \n * @param options - Array with comparison number as first element\n * @returns Filter function that returns boolean\n */\nconst lt = (options?:string[]): FilterFn<boolean> => {\n const opt = requiredNumberOption(options, 0, 'lt');\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('lt');}\n return value < opt;\n }\n}\n\n/**\n * Less than or equal filter - checks if value is less than or equal to option.\n * \n * @param options - Array with comparison number as first element\n * @returns Filter function that returns boolean\n */\nconst le = (options?:string[]): FilterFn<boolean> => {\n const opt = requiredNumberOption(options, 0, 'le');\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('le');}\n return value <= opt;\n }\n}\n\n/**\n * Greater than filter - checks if value is greater than option.\n * \n * @param options - Array with comparison number as first element\n * @returns Filter function that returns boolean\n */\nconst gt = (options?:string[]): FilterFn<boolean> => {\n const opt = requiredNumberOption(options, 0, 'gt');\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('gt');}\n return value > opt;\n }\n}\n\n/**\n * Greater than or equal filter - checks if value is greater than or equal to option.\n * \n * @param options - Array with comparison number as first element\n * @returns Filter function that returns boolean\n */\nconst ge = (options?:string[]): FilterFn<boolean> => {\n const opt = requiredNumberOption(options, 0, 'ge');\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('ge');}\n return value >= opt;\n }\n}\n\n/**\n * Increment filter - adds option value to input value.\n * \n * @param options - Array with increment number as first element\n * @returns Filter function that returns incremented number\n */\nconst inc = (options?:string[]): FilterFn<number> => {\n const opt = requiredNumberOption(options, 0, 'add');\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('add');}\n return value + opt;\n }\n}\n\n/**\n * Decrement filter - subtracts option value from input value.\n * \n * @param options - Array with decrement number as first element\n * @returns Filter function that returns decremented number\n */\nconst dec = (options?:string[]): FilterFn<number> => {\n const opt = requiredNumberOption(options, 0, 'sub');\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('sub');}\n return value - opt;\n }\n}\n\n/**\n * Multiply filter - multiplies value by option.\n * \n * @param options - Array with multiplier number as first element\n * @returns Filter function that returns multiplied number\n */\nconst mul = (options?:string[]): FilterFn<number> => {\n const opt = requiredNumberOption(options, 0, 'mul');\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('mul');}\n return value * opt;\n }\n}\n\n/**\n * Divide filter - divides value by option.\n * \n * @param options - Array with divisor number as first element\n * @returns Filter function that returns divided number\n */\nconst div = (options?:string[]): FilterFn<number> => {\n const opt = requiredNumberOption(options, 0, 'div');\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('div');}\n return value / opt;\n }\n}\n\n/**\n * Modulo filter - returns remainder of division.\n * \n * @param options - Array with divisor number as first element\n * @returns Filter function that returns remainder\n */\nconst mod = (options?:string[]): FilterFn<number> => {\n const opt = requiredNumberOption(options, 0, 'mod');\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('mod');}\n return value % opt;\n }\n}\n\n/**\n * Absolute value filter - returns the magnitude of a number.\n *\n * @param options - Unused\n * @returns Filter function that returns the absolute value\n */\nconst abs = (_options?:string[]): FilterFn<number> => {\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('abs');}\n return Math.abs(value);\n }\n}\n\n/**\n * Clamp filter - constrains a number to the inclusive range [min, max].\n *\n * Saturating conversion in the same family as round/floor/ceil, so it stays on\n * the wire rather than in state. Pairs with `unit` for style bindings:\n * `style.width: ratio|clamp(0,1)|percent(0)`.\n *\n * @param options - Array with minimum as first element and maximum as second (both required)\n * @returns Filter function that returns the clamped number\n */\nconst clamp = (options?:string[]): FilterFn<number> => {\n const min = requiredNumberOption(options, 0, 'clamp');\n const max = requiredNumberOption(options, 1, 'clamp');\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('clamp');}\n return Math.min(Math.max(value, min), max);\n }\n}\n\n/**\n * Fixed decimal filter - formats number to fixed decimal places.\n *\n * @param options - Array with decimal places as first element (default: 0)\n * @returns Filter function that returns formatted string\n */\nconst fix = (options?:string[]): FilterFn<string> => {\n const opt = numberOption(options?.[0] ?? \"0\", 'toFixed');\n return (value: unknown): string => {\n if (typeof value !== 'number') {valueMustBeNumber('toFixed');}\n return value.toFixed(opt);\n }\n}\n\n/**\n * Locale number filter - formats number according to locale.\n *\n * ロケール依存フィルタ(`locale` / `date` / `time` / `datetime`)は\n * **明示引数だけを構築時に確定し、既定の `config.locale` は適用のたびに読む**。\n *\n * 以前は `options?.[0] ?? config.locale` を返り値の関数の**外**で解決していた。\n * フィルタ関数はバインド構築時に一度だけ作られるので、これはロケールを\n * クロージャに焼き込むことを意味する。`config.locale` の確定がバインド構築より\n * 遅れると、それ以降どう直しても「同じページの中で日付だけ既定ロケール」が\n * 永続し、しかも `config.locale` は依存グラフに載らないので再描画で回復もしない。\n * 症状(日付だけ英語)は原因(起動順序)から遠く、追いにくい。\n *\n * 適用のたびに読めば、少なくとも**再適用されたバインドは回復する**。ロケールは\n * 起動時に確定する前提(docs/i18n-design.md D1)なので通常この差は現れず、\n * これは順序事故から復帰できるようにするための保険である。\n *\n * 明示引数(`|date(ja-JP)`)は構築時に固定でよい — バインド式の一部であり、\n * 実行中に変わらない。\n *\n * @param options - Array with locale string as first element (default: config.locale)\n * @returns Filter function that returns localized number string\n */\nconst locale = (options?:string[]): FilterFn<string> => {\n const explicit = options?.[0];\n return (value: unknown): string => {\n if (typeof value !== 'number') {valueMustBeNumber('locale');}\n return value.toLocaleString(explicit ?? config.locale);\n }\n}\n\n/**\n * Uppercase filter - converts string to uppercase.\n * \n * @param options - Unused\n * @returns Filter function that returns uppercase string\n */\nconst uc = (_options?:string[]): FilterFn<string> => {\n return (value: unknown): string => {\n return String(value).toUpperCase();\n }\n}\n\n/**\n * Lowercase filter - converts string to lowercase.\n * \n * @param options - Unused\n * @returns Filter function that returns lowercase string\n */\nconst lc = (_options?:string[]): FilterFn<string> => {\n return (value: unknown): string => {\n return String(value).toLowerCase();\n }\n}\n\n/**\n * Capitalize filter - capitalizes first character of string.\n * \n * @param options - Unused\n * @returns Filter function that returns capitalized string\n */\nconst cap = (_options?:string[]): FilterFn<string> => {\n return (value: unknown): string => {\n const v = String(value);\n if (v.length === 0) {return v;}\n if (v.length === 1) {return v.toUpperCase();}\n return v.charAt(0).toUpperCase() + v.slice(1);\n }\n}\n\n/**\n * Trim filter - removes whitespace from both ends of string.\n * \n * @param options - Unused\n * @returns Filter function that returns trimmed string\n */\nconst trim = (_options?:string[]): FilterFn<string> => {\n return (value: unknown): string => {\n return String(value).trim();\n }\n}\n\n/**\n * Slice filter - extracts portion of string from specified index.\n * \n * @param options - Array with start index and optional end index\n * @returns Filter function that returns sliced string\n */\nconst slice = (options?:string[]): FilterFn<string> => {\n const numberedOpts: number[] = [requiredNumberOption(options, 0, 'slice')];\n const opt2 = options?.[1];\n if (typeof opt2 !== 'undefined') {\n numberedOpts.push(numberOption(opt2, 'slice'));\n }\n return (value: unknown): string => {\n return String(value).slice(...numberedOpts);\n }\n}\n\n/**\n * Substring filter - extracts substring from specified position and length.\n * \n * @param options - Array with start index and length\n * @returns Filter function that returns substring\n */\nconst substr = (options?:string[]): FilterFn<string> => {\n // 4.0 removes `substr` in favour of `slice` (3.5 notice, D39). The registry caches the resolved\n // function per argument list, so this factory runs once per distinct use\n v4Migration?.removed('substr');\n const opt1 = requiredNumberOption(options, 0, 'substr');\n const opt2 = requiredNumberOption(options, 1, 'substr');\n return (value: unknown): string => {\n return String(value).substr(opt1, opt2);\n }\n}\n\n/**\n * Pad filter - pads string to specified length from start.\n * \n * @param options - Array with target length and pad string (default: '0')\n * @returns Filter function that returns padded string\n */\nconst pad = (options?:string[]): FilterFn<string> => {\n const opt1 = requiredNumberOption(options, 0, 'padStart');\n const opt2 = options?.[1] ?? '0';\n return (value: unknown): string => {\n return String(value).padStart(opt1, opt2);\n }\n}\n\n/**\n * padEnd filter - pads string to specified length from the end (the pair of `padStart`, requirement B12).\n *\n * @param options - Array with target length and pad string (default: ' ')\n * @returns Filter function that returns padded string\n */\nconst padEnd = (options?:string[]): FilterFn<string> => {\n const opt1 = requiredNumberOption(options, 0, 'padEnd');\n const opt2 = options?.[1] ?? ' ';\n return (value: unknown): string => {\n return String(value).padEnd(opt1, opt2);\n }\n}\n\n/**\n * Repeat filter - repeats string specified number of times.\n * \n * @param options - Array with repeat count as first element\n * @returns Filter function that returns repeated string\n */\nconst rep = (options?:string[]): FilterFn<string> => {\n const opt = requiredNumberOption(options, 0, 'repeat');\n return (value: unknown): string => {\n return String(value).repeat(opt);\n }\n}\n\n/**\n * Reverse filter - reverses character order in string.\n * \n * @param options - Unused\n * @returns Filter function that returns reversed string\n */\nconst rev = (_options?:string[]): FilterFn<string> => {\n return (value: unknown): string => {\n return String(value).split('').reverse().join('');\n }\n}\n\n/**\n * Integer filter - parses value to integer.\n * \n * @param options - Unused\n * @returns Filter function that returns integer\n */\nconst int = (_options?:string[]): FilterFn<number> => {\n return (value: unknown): number => {\n return parseInt(String(value), 10);\n }\n}\n\n/**\n * Float filter - parses value to floating point number.\n * \n * @param options - Unused\n * @returns Filter function that returns float\n */\nconst float = (_options?:string[]): FilterFn<number> => {\n return (value: unknown): number => {\n return parseFloat(String(value));\n }\n}\n\n/**\n * Round filter - rounds number to specified decimal places.\n * \n * @param options - Array with decimal places as first element (default: 0)\n * @returns Filter function that returns rounded number\n */\nconst round = (options?:string[]): FilterFn<number> => {\n const optValue = Math.pow(10, numberOption(options?.[0] ?? '0', 'round'));\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('round');}\n return Math.round(value * optValue) / optValue;\n }\n}\n\n/**\n * Floor filter - rounds number down to specified decimal places.\n * \n * @param options - Array with decimal places as first element (default: 0)\n * @returns Filter function that returns floored number\n */\nconst floor = (options?:string[]): FilterFn<number> => {\n const optValue = Math.pow(10, numberOption(options?.[0] ?? '0', 'floor'));\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('floor');}\n return Math.floor(value * optValue) / optValue;\n }\n}\n\n/**\n * Ceiling filter - rounds number up to specified decimal places.\n * \n * @param options - Array with decimal places as first element (default: 0)\n * @returns Filter function that returns ceiled number\n */\nconst ceil = (options?:string[]): FilterFn<number> => {\n const optValue = Math.pow(10, numberOption(options?.[0] ?? '0', 'ceil'));\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('ceil');}\n return Math.ceil(value * optValue) / optValue;\n }\n}\n\n/**\n * Percent filter - formats number as percentage string.\n * \n * @param options - Array with decimal places as first element (default: 0)\n * @returns Filter function that returns percentage string with '%'\n */\nconst percent = (options?:string[]): FilterFn<string> => {\n const opt = numberOption(options?.[0] ?? '0', 'percent');\n return (value: unknown): string => {\n if (typeof value !== 'number') {valueMustBeNumber('percent');}\n return `${(value * 100).toFixed(opt)}%`;\n }\n}\n\n/**\n * Unit filter - appends a CSS unit (or any suffix) to the value.\n *\n * A number alone does nothing in CSS, so without this the unit has to be built in\n * state — which drags presentation into the source of truth, and in the worst case\n * forces a whole derived array just to carry `\"42%\"` strings.\n * `style.height: samples.*.cpu|clamp(0,100)|fix(0)|unit(%)` keeps it on the wire.\n *\n * Accepts strings as well as numbers **on purpose**: the useful chains run through\n * `fix` / `percent`, which already return strings. Rejecting non-numbers here would\n * break exactly the combination this filter exists for.\n *\n * `null` / `undefined` pass through untouched rather than becoming `\"undefinedpx\"`, so the\n * binding layer's \"undefined skips the write, null clears\" semantics survive. That guard is no\n * longer written here: it is `nullishPassthrough`, shared with the rest of the `String(value)`\n * family, which used to paint `\"undefined\"` into the page for exactly the same reason.\n *\n * @param options - Array with the unit/suffix as first element (required)\n * @returns Filter function that returns the value with the unit appended\n */\nconst unit = (options?:string[]): FilterFn<string> => {\n const opt = options?.[0] ?? optionsRequired('unit');\n return (value: unknown): string => {\n return String(value) + opt;\n }\n}\n\n/**\n * Join filter - joins array elements into a string.\n *\n * The default separator is `\", \"` rather than `\",\"`: a bare comma is what `String()`\n * already produces without any filter, so defaulting to it would make `|join` a no-op.\n *\n * @param options - Array with separator as first element (default: ', ')\n * @returns Filter function that returns the joined string\n */\nconst join = (options?:string[]): FilterFn<string> => {\n const opt = options?.[0] ?? ', ';\n return (value: unknown): string => {\n if (!Array.isArray(value)) {valueMustBeArray('join');}\n return value.join(opt);\n }\n}\n\n/**\n * Truncate filter - shortens a string and appends an ellipsis.\n *\n * The length option counts **kept characters**, not the total including the suffix,\n * matching the existing `slice(0, n)` reading. A string at or below the limit is\n * returned untouched (no suffix).\n *\n * @param options - Array with max kept length as first element and suffix as second (default: '…')\n * @returns Filter function that returns the truncated string\n */\nconst truncate = (options?:string[]): FilterFn<string> => {\n const maxLength = requiredNumberOption(options, 0, 'truncate');\n const suffix = options?.[1] ?? '…';\n return (value: unknown): string => {\n const v = String(value);\n if (v.length <= maxLength) {return v;}\n return v.slice(0, maxLength) + suffix;\n }\n}\n\n/**\n * Date filter - formats Date object as localized date string.\n *\n * @param options - Array with locale string as first element (default: config.locale)\n * @returns Filter function that returns date string\n */\nconst date = (options?:string[]): FilterFn<string> => {\n // 既定ロケールは適用のたびに読む(`locale` フィルタの注記を参照)\n const explicit = options?.[0];\n return (value: unknown): string => {\n if (!(value instanceof Date)) {valueMustBeDate('date');}\n return value.toLocaleDateString(explicit ?? config.locale);\n }\n}\n\n/**\n * Time filter - formats Date object as localized time string.\n * \n * @param options - Array with locale string as first element (default: config.locale)\n * @returns Filter function that returns time string\n */\nconst time = (options?:string[]): FilterFn<string> => {\n // 既定ロケールは適用のたびに読む(`locale` フィルタの注記を参照)\n const explicit = options?.[0];\n return (value: unknown): string => {\n if (!(value instanceof Date)) {valueMustBeDate('time');}\n return value.toLocaleTimeString(explicit ?? config.locale);\n }\n}\n\n/**\n * DateTime filter - formats Date object as localized date and time string.\n * \n * @param options - Array with locale string as first element (default: config.locale)\n * @returns Filter function that returns datetime string\n */\nconst datetime = (options?:string[]): FilterFn<string> => {\n // 既定ロケールは適用のたびに読む(`locale` フィルタの注記を参照)\n const explicit = options?.[0];\n return (value: unknown): string => {\n if (!(value instanceof Date)) {valueMustBeDate('datetime');}\n return value.toLocaleString(explicit ?? config.locale);\n }\n}\n\n/**\n * Year-Month-Day filter - formats Date object as YYYY-MM-DD string.\n * \n * @param options - Array with separator string as first element (default: '-')\n * @returns Filter function that returns formatted date string\n */\nconst ymd = (options?:string[]): FilterFn<string> => {\n const opt = options?.[0] ?? '-';\n return (value: unknown): string => {\n if (!(value instanceof Date)) {valueMustBeDate('ymd');}\n const year = value.getFullYear().toString();\n const month = (value.getMonth() + 1).toString().padStart(2, '0');\n const day = value.getDate().toString().padStart(2, '0');\n return `${year}${opt}${month}${opt}${day}`;\n }\n}\n\n/**\n * Hour-Minute-Second filter - formats Date object as HH:MM:SS string.\n *\n * The counterpart of `ymd`: a fixed, zero-padded, locale-independent rendering with a\n * configurable separator, for when `time` (locale-formatted) is not stable enough.\n *\n * @param options - Array with separator string as first element (default: ':')\n * @returns Filter function that returns formatted time string\n */\nconst hms = (options?:string[]): FilterFn<string> => {\n const opt = options?.[0] ?? ':';\n return (value: unknown): string => {\n if (!(value instanceof Date)) {valueMustBeDate('hms');}\n const hours = value.getHours().toString().padStart(2, '0');\n const minutes = value.getMinutes().toString().padStart(2, '0');\n const seconds = value.getSeconds().toString().padStart(2, '0');\n return `${hours}${opt}${minutes}${opt}${seconds}`;\n }\n}\n\n/**\n * Falsy filter - checks if value is falsy, by JavaScript's own truthiness (`!value`: false, null,\n * undefined, 0, -0, 0n, '' and NaN). Before 3.0 the list was spelled out and missed 0n (B10).\n *\n * @param options - Unused\n * @returns Filter function that returns true for falsy values\n */\nconst falsy = (_options?:string[]): FilterFn<boolean> => {\n return (value: unknown): boolean => !value;\n}\n\n/**\n * Truthy filter - checks if value is truthy.\n * \n * @param options - Unused\n * @returns Filter function that returns true for non-falsy values\n */\nconst truthy = (_options?:string[]): FilterFn<boolean> => {\n // JavaScript's truthiness, the same as the `boolean` filter (B10)\n return (value: unknown): boolean => !!value;\n}\n\n/**\n * Default filter - returns default value if input is falsy (JavaScript's truthiness, as `falsy`).\n * \n * @param options - Array with default value as first element\n * @returns Filter function that returns value or default\n */\nconst defaults = (options?:string[], literals?: readonly unknown[]): FilterFn<unknown> => {\n const opt = options?.[0] ?? optionsRequired('defaults');\n // The fallback is the typed literal (B9): defaults(0) gives 0, defaults(null) gives null, defaults('0') gives \"0\"\n const fallback = literals !== undefined && literals.length > 0 ? literals[0] : opt;\n return (value: unknown): unknown => {\n if (!value) {return fallback;}\n return value;\n }\n}\n\n/**\n * coalesce filter - returns the default only for null / undefined (`defaults` also replaces 0, false and \"\").\n * Requirement B12: the nullish counterpart of `defaults`, read like SQL COALESCE.\n *\n * @param options - Array with default value as first element\n * @returns Filter function that returns value or default\n */\nconst coalesce = (options?:string[], literals?: readonly unknown[]): FilterFn<unknown> => {\n const opt = options?.[0] ?? optionsRequired('coalesce');\n const fallback = literals !== undefined && literals.length > 0 ? literals[0] : opt;\n return (value: unknown): unknown => value ?? fallback;\n}\n\n/**\n * Boolean filter - converts value to boolean.\n * \n * @param options - Unused\n * @returns Filter function that returns boolean\n */\nconst boolean = (_options?:string[]): FilterFn<boolean> => {\n return (value: unknown): boolean => {\n return Boolean(value);\n }\n}\n\n/**\n * Number filter - converts value to number.\n * \n * @param options - Unused\n * @returns Filter function that returns number\n */\nconst number = (_options?:string[]): FilterFn<number> => {\n return (value: unknown): number => {\n return Number(value);\n }\n}\n\n/**\n * String filter - converts value to string.\n * \n * @param options - Unused\n * @returns Filter function that returns string\n */\nconst string = (_options?:string[]): FilterFn<string> => {\n return (value: unknown): string => {\n return String(value);\n }\n}\n\n/**\n * Null filter - converts empty string to null.\n * \n * @param options - Unused\n * @returns Filter function that returns null for empty string, otherwise original value\n */\nconst _null = (_options?:string[]): FilterFn<unknown> => {\n return (value: unknown): unknown => {\n return (value === \"\") ? null : value;\n } \n}\n\nconst builtinFilters: FilterWithOptions = {\n \"eq\": eq,\n \"ne\": ne,\n \"not\": not,\n\n \"lt\": lt,\n \"le\": le,\n \"gt\": gt,\n \"ge\": ge,\n\n \"add\": inc,\n \"sub\": dec,\n \"mul\": mul,\n \"div\": div,\n \"mod\": mod,\n \"abs\": abs,\n \"clamp\": clamp,\n\n \"toFixed\": fix,\n \"locale\": locale,\n // `String(value)` で組み立てる族は、値が無いときに素通しする(要件 B8 — nullishPassthrough を参照)\n \"upper\": nullishPassthrough(uc),\n \"lower\": nullishPassthrough(lc),\n \"capitalize\": nullishPassthrough(cap),\n \"trim\": nullishPassthrough(trim),\n \"slice\": nullishPassthrough(slice),\n \"substr\": nullishPassthrough(substr),\n \"padStart\": nullishPassthrough(pad),\n \"padEnd\": nullishPassthrough(padEnd),\n \"repeat\": nullishPassthrough(rep),\n \"reverse\": nullishPassthrough(rev),\n \"truncate\": nullishPassthrough(truncate),\n \"join\": join,\n\n \"int\": int,\n \"float\": float,\n \"round\": round,\n \"floor\": floor,\n \"ceil\": ceil,\n \"percent\": percent,\n \"unit\": nullishPassthrough(unit),\n\n \"date\": date,\n \"time\": time,\n \"datetime\": datetime,\n \"ymd\": ymd,\n \"hms\": hms,\n\n \"falsy\": falsy,\n \"truthy\": truthy,\n \"defaults\": defaults,\n \"coalesce\": coalesce,\n\n \"boolean\": boolean,\n \"number\": number,\n \"string\": string,\n \"nullIfEmpty\": _null,\n};\n\n/**\n * The argument count each built-in accepts, [min, max] — checked when the bindings are planned\n * (`[wcs/filter-arity]`, requirement B3), with the same bounds lint uses. It is the same data as\n * `builtinFilterMeta` (filters/filterMeta.ts) without the descriptions, so that installing the formats\n * does not pull the metadata into the page; a test keeps the two equal.\n */\nexport const builtinFilterArity: Readonly<Record<string, readonly [number, number]>> = {\n eq: [1, 1],\n not: [0, 0],\n ne: [1, 1],\n lt: [1, 1],\n le: [1, 1],\n gt: [1, 1],\n ge: [1, 1],\n add: [1, 1],\n sub: [1, 1],\n mul: [1, 1],\n div: [1, 1],\n mod: [1, 1],\n abs: [0, 0],\n clamp: [2, 2],\n toFixed: [0, 1],\n locale: [0, 1],\n upper: [0, 0],\n lower: [0, 0],\n capitalize: [0, 0],\n trim: [0, 0],\n slice: [1, 2],\n // `substr` は長さも必須(実装が両方読む・README も `substr(start, length)`)。\n // [1, 2] だったころは `substr(0)` が引数の個数の検査を素通りし、工場の\n // 「requires at least one option」という的外れな文言で落ちていた\n substr: [2, 2],\n padStart: [1, 2],\n padEnd: [1, 2],\n repeat: [1, 1],\n reverse: [0, 0],\n truncate: [1, 2],\n join: [0, 1],\n int: [0, 0],\n float: [0, 0],\n round: [0, 1],\n floor: [0, 1],\n ceil: [0, 1],\n percent: [0, 1],\n unit: [1, 1],\n // ロケール依存の 3 つは `locale` と同じく引数 1 つ(`date(ja-JP)`)を受ける。\n // [0, 0] だったころは、README が規範として書いている `timestamp|date(ja-JP)` が\n // 束縛計画の段で必ず `[wcs/filter-arity]` になっていた(2.x には検査自体が無かった)\n date: [0, 1],\n time: [0, 1],\n datetime: [0, 1],\n ymd: [0, 1],\n hms: [0, 1],\n falsy: [0, 0],\n truthy: [0, 0],\n defaults: [1, 1],\n coalesce: [1, 1],\n boolean: [0, 0],\n number: [0, 0],\n string: [0, 0],\n nullIfEmpty: [0, 0],\n};\n\nexport const outputBuiltinFilters = builtinFilters;\nexport const inputBuiltinFilters = builtinFilters;\n\nexport const builtinFiltersByFilterIOType = {\n \"input\": inputBuiltinFilters,\n \"output\": outputBuiltinFilters,\n} as const;\n\n/**\n * Retrieves built-in filter function by name and options.\n *\n * **Test / tooling only.** The binding pipeline never goes through here: it resolves\n * filters from the registry with `core/filterRegistry.ts#resolveFilterFn`, which also\n * checks `builtinFilterArity` and passes the **typed literals** (requirement B9).\n * This helper takes raw option strings only, so `builtinFilterFn(\"coalesce\", [\"0\"])`\n * yields the string `\"0\"` where the runtime yields the number `0`. Prefer\n * `resolveFilterFn` in any new code, and treat a difference between the two as a\n * limitation of this helper rather than of the runtime.\n *\n * @param name - Filter name (a 3.x alias such as `uc` resolves to its canonical name)\n * @param options - Array of option strings\n * @returns Function that takes FilterWithOptions and returns filter function\n */\nexport const builtinFilterFn = (name:string, options: string[]) => (filters: FilterWithOptions) => {\n // 自前のキーだけを引く。素のオブジェクトへのブラケット参照だと `Object.prototype` のメンバが\n // フィルタとして通り(`toString` / `constructor` / `valueOf`)、`[wcs/filter-unknown]` の代わりに\n // `\"[object Undefined]\"` や素の TypeError が出ていた。登録簿(core/filterRegistry.ts)が\n // まさにこの穴のために `Map` を採っているのと同じ規準\n const own = Object.prototype.hasOwnProperty;\n const canonical = own.call(builtinFilterAliases, name) ? builtinFilterAliases[name] : name;\n const filter = own.call(filters, canonical) ? filters[canonical] : undefined;\n if (!filter) {\n // lint の wcs/filter-unknown と同じ語彙・同じ did-you-mean 規準(三面同語彙)。\n raiseError(`[wcs/filter-unknown] filter not found: ${name}.${didYouMean(name, Object.keys(filters))}${LINT_HINT}`);\n }\n return filter(options);\n}\n\n","/**\n * formats/install.ts — 書式フィルタ群の install(設計案 §4、要件 D16)。\n *\n * `uc` / `date` / `round` / `truncate` … の実装を core の登録簿へ置く。core 自身が持つのは\n * エンジンが差し込む `not` だけなので、この機能を入れないページでは、宣言されたフィルタが\n * 束縛計画の段で `[wcs/filter-unknown]` として名指しで落ちる(黙って素通ししない)。\n */\nimport { registerFilters } from \"../core/filterRegistry\";\nimport { builtinFilterArity, inputBuiltinFilters, outputBuiltinFilters } from \"./builtinFilters\";\nimport { builtinFilterAliases } from \"../filters/filterAliases\";\n\nlet installed = false;\n/** 冪等。full / auto では `bootstrapState()` が呼ぶ */\nexport function installFormats(): void {\n if (installed) return;\n installed = true;\n registerFilters(\"input\", inputBuiltinFilters, builtinFilterArity, builtinFilterAliases);\n registerFilters(\"output\", outputBuiltinFilters, builtinFilterArity, builtinFilterAliases);\n}\n","/**\n * features/formats.ts — 書式フィルタ群(@wcstack/state/features/formats)。\n * `|uc` `|date(…)` `|round(…)` のような宣言が要求する実関数を core の登録簿へ置く。\n */\nimport type { IStateFeature } from \"../core/features\";\nimport { installFormats } from \"../formats/install\";\n\nexport const formats: IStateFeature = {\n name: \"formats\",\n install(): void {\n installFormats();\n },\n};\nexport default formats;\n"],"names":["optionsRequired","fnName","raiseError","optionMustBeNumber","valueMustBeNumber","valueMustBeDate","builtinFilterAliases","inc","dec","fix","uc","lc","cap","rep","rev","pad","null","validateNumberString","value","isNaN","Number","numberOption","requiredNumberOption","options","index","nullishPassthrough","factory","filterFn","builtinFilters","eq","literals","opt","literal","undefined","length","ne","not","_options","lt","le","gt","ge","add","sub","mul","div","mod","abs","Math","clamp","min","max","toFixed","locale","explicit","toLocaleString","config","upper","String","toUpperCase","lower","toLowerCase","capitalize","v","charAt","slice","trim","numberedOpts","opt2","push","substr","opt1","padStart","padEnd","repeat","reverse","split","join","truncate","maxLength","suffix","Array","isArray","int","parseInt","float","parseFloat","round","optValue","pow","floor","ceil","percent","unit","date","Date","toLocaleDateString","time","toLocaleTimeString","datetime","ymd","year","getFullYear","toString","month","getMonth","day","getDate","hms","hours","getHours","minutes","getMinutes","seconds","getSeconds","falsy","truthy","defaults","fallback","coalesce","boolean","Boolean","number","string","nullIfEmpty","builtinFilterArity","outputBuiltinFilters","inputBuiltinFilters","installed","formats","name","install","registerFilters"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"recursion.js","sources":["../../../../../src/recursion/expand.ts","../../../../../src/recursion/bind.ts","../../../../../src/recursion/walk.ts","../../../../../src/recursion/setAllRecursive.ts","../../../../../src/recursion/addressHooks.ts","../../../../../src/recursion/getAllRecursive.ts","../../../../../src/recursion/declaration.ts","../../../../../src/recursion/generation.ts","../../../../../src/recursion/registry.ts","../../../../../src/recursion/declarations.ts","../../../../../src/features/recursion.ts"],"sourcesContent":["/**\n * recursion/expand.ts\n *\n * `**` を含むオーサリング層のパスと、エンジンが扱う具体パスの相互変換。\n * **純関数だけ**を置く(state も proxy も触らない)。\n *\n * 変換は 1 対 1 ではなく 1 対多である。`nodes.**.total` は深さごとに\n * `nodes.*.total` / `nodes.*.children.*.total` / … という無限の族を表し、\n * エンジンが見るのは常にそのうちの 1 本だけ(設計書 D2)。\n */\n\nimport { DELIMITER, MAX_WILDCARD_DEPTH, RECURSION_WILDCARD, WILDCARD } from \"../define\";\nimport { raiseError } from \"../raiseError\";\nimport { IRecursionSpec } from \"./types\";\n\n/** `**` を含むか(含まない大多数のパスを 1 回の indexOf で抜ける)。 */\nexport function hasRecursionWildcard(path: string): boolean {\n return path.indexOf(RECURSION_WILDCARD) !== -1;\n}\n\n/**\n * `**` を含むパスを宣言と突き合わせ、接尾辞(`**` より後ろ。無ければ空文字)を返す。\n * 宣言に合致しない `**` は「宣言なしの `**`」として呼び出し側が診断する(null を返す)。\n */\nexport function splitRecursivePath(spec: IRecursionSpec, path: string): string | null {\n if (path === spec.recursiveAnchor) {\n return \"\";\n }\n const prefix = spec.recursiveAnchor + DELIMITER;\n if (!path.startsWith(prefix)) {\n return null;\n }\n const suffix = path.slice(spec.recursiveAnchor.length);\n // 接尾辞に 2 つ目の `**` があるのは初版では未対応(複数の再帰点)。\n if (hasRecursionWildcard(suffix)) {\n return null;\n }\n // 接尾辞は整形されたパスでなければならない: 空セグメント(`nodes.**.` / `nodes.**..x`)と\n // `**` 直後の素の `*`(`nodes.**.*` — 展開すると `nodes.*.*` でアンカー行そのもの)は\n // 受理しない。`assertNodePath` / `$watch` が同じ形を拒否するのと対称(第 4 サイクルで実測:\n // 受理すると `[undefined×n]` や生の `Reflect.set called on non-object` になっていた)。\n const segments = suffix.slice(DELIMITER.length).split(DELIMITER);\n if (segments[0] === WILDCARD || segments.some((segment) => segment.length === 0)) {\n return null;\n }\n return suffix;\n}\n\n/**\n * 接尾辞(`.` で始まる)の添字セグメントだけを `*` に畳む。先頭の空セグメントは区切りの\n * 都合なので畳まない(`indexSegmentsToWildcard` に丸ごと渡すと `Number(\"\") === 0` で `*` になる)。\n * `**` パスの検査(構造・読み取り専用)と `**` getter キーの検査が共有する。\n */\nexport function foldSuffixIndexes(suffix: string): string {\n return suffix.length === 0 ? suffix : DELIMITER + indexSegmentsToWildcard(suffix.slice(DELIMITER.length));\n}\n\n/**\n * 2 つの接尾辞が**同じ具体パス族**を指すか。片方がもう片方の末尾で、差分が反復語の\n * 整数倍(0 回を含む)のとき真。`nodes.**.total` と `nodes.**.children.*.total` は\n * 深さ k と k+1 で同じ `nodes.*.children.*.total` になる、という関係を捉える。\n * 静的側の `recursionPaths.sameFamily` と同じ純関数。\n */\nexport function sameFamily(spec: IRecursionSpec, a: string, b: string): boolean {\n const unit = DELIMITER + spec.repeat;\n const shorter = a.length <= b.length ? a : b;\n const longer = a.length <= b.length ? b : a;\n if (!longer.endsWith(shorter)) {\n return false;\n }\n const gap = longer.slice(0, longer.length - shorter.length);\n if (gap.length === 0) {\n return true;\n }\n if (gap.length % unit.length !== 0) {\n return false;\n }\n for (let cursor = 0; cursor < gap.length; cursor += unit.length) {\n if (!gap.startsWith(unit, cursor)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * 接尾辞 `suffix` が、`**` getter の接尾辞 `familySuffix` の族そのもの、またはその値の内側を\n * 指しているか。`.` 境界で切った各接頭辞 `p`(全体を含む)について `sameFamily(familySuffix, p)`\n * を見る — `startsWith(familySuffix + \".\")` だけでは、反復語ぶんずれた展開形の値の内側\n * (`nodes.**.children.*.total.x` で `nodes.**.total`)を取りこぼす(第 4 サイクルで実測)。\n */\nexport function coversSuffix(spec: IRecursionSpec, familySuffix: string, suffix: string): boolean {\n for (let end = suffix.length; end > 0; end = suffix.lastIndexOf(DELIMITER, end - 1)) {\n if (sameFamily(spec, familySuffix, suffix.slice(0, end))) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * 添字セグメント(`nodes.1.total` の `1`)を `*` に畳む。判定は `address/ResolvedAddress.ts`\n * と同じ「`Number()` が NaN でない区切り」。API のパス引数(`$getAll` / `$setAll` / `$resolve`)と\n * `**` パスの接尾辞は set トラップと違って `getResolvedAddress` の正規化を経ないので、\n * 再帰の検査(読み取り専用・構造)に掛ける前にここで畳む。\n */\nexport function indexSegmentsToWildcard(path: string): string {\n const segments = path.split(DELIMITER);\n for (let i = 0; i < segments.length; i++) {\n if (segments[i] !== WILDCARD && !Number.isNaN(Number(segments[i]))) {\n segments[i] = WILDCARD;\n }\n }\n return segments.join(DELIMITER);\n}\n\n/**\n * `**` パスの接尾辞が「再帰の構造そのもの」を名指しているか。\n *\n * ノード自身(`nodes.**` / `nodes.**.children.*`)・子リスト(`nodes.**.children`)・その\n * `length`(`arr.length = 0` は配列を切り詰める)・多段の反復サブパスなら子リストへ至る\n * 途中のオブジェクト(`nodes.**.branch`)。書き側(`setAllRecursive`)は確定済みの\n * 子アドレスを壊すので拒否し、宣言側(`**` getter のキー)は生成 getter が実データの\n * 子リストを影にするので拒否する — 同じ述語を両方が使う。\n *\n * 反復サブパスを**途中まで**名指す形もすべて構造。`\".\" + repeatList` との完全一致だけを\n * 見ると、多段の repeat で途中のオブジェクトが素通りし、深さ 0 の `branch` を置き換えた\n * 瞬間に確定済みの深さ 1 のアドレスが宙に浮く(着地後レビューで実測。実装計画 §7-3)。\n */\nexport function isStructuralSuffix(spec: IRecursionSpec, suffix: string): boolean {\n const repeatSegments = spec.repeat.split(DELIMITER);\n const unit = DELIMITER + spec.repeat;\n let rest = suffix;\n while (rest.startsWith(unit)) {\n rest = rest.slice(unit.length);\n }\n if (rest.length === 0 || rest === DELIMITER + spec.repeatList + DELIMITER + \"length\") {\n return true;\n }\n for (let i = 1; i < repeatSegments.length; i++) {\n if (rest === DELIMITER + repeatSegments.slice(0, i).join(DELIMITER)) {\n return true;\n }\n }\n return false;\n}\n\n/** 深さ k の具体パスを作る。上限超過は生成前に throw する(設計書 D11)。 */\nexport function concretePathAt(spec: IRecursionSpec, suffix: string, depth: number): string {\n if (depth < 0) {\n raiseError(`Recursion depth must not be negative (got ${depth}).`);\n }\n let path = spec.anchor;\n for (let i = 0; i < depth; i++) {\n path += DELIMITER + spec.repeat;\n }\n const full = path + suffix;\n // ワイルドカード段数は展開後のパス全体で数える(アンカー・反復・接尾辞をすべて含む)。\n // intern(getPathInfo)より**前**に文字列から数える — 上限超過のパスを永続キャッシュ\n // (PathInfo の `_cache`)に残さない(設計書 D10「毎ノードの固有パスを intern しない」)。\n let wildcardCount = 0;\n for (const segment of full.split(DELIMITER)) {\n if (segment === WILDCARD) {\n wildcardCount++;\n }\n }\n if (wildcardCount > MAX_WILDCARD_DEPTH) {\n raiseError(\n `[wcs/recursion-depth-exceeded] Recursion on \"${spec.anchor}\" reached depth ${depth} ` +\n `(\"${full}\"), which needs ${wildcardCount} wildcard levels — the limit is ${MAX_WILDCARD_DEPTH}. ` +\n `Either the data nests deeper than the engine can address, or the tree contains a cycle.`\n );\n }\n return full;\n}\n\n/** 深さ k のノードパス(接尾辞なし)。リストパスの登録に使う(このモジュール内だけ)。 */\nfunction nodePathAt(spec: IRecursionSpec, depth: number): string {\n return concretePathAt(spec, \"\", depth);\n}\n\n/**\n * 深さ k のノードが持つ子リストのパス(`nodes.*.children` / `nodes.*.children.*.children` …)。\n * `listPaths` へ登録する対象(E4)。深さ 0 のアンカー自身のリスト(`nodes`)も含める。\n */\nexport function listPathsUpTo(spec: IRecursionSpec, depth: number): string[] {\n const paths: string[] = [];\n // アンカー自身のリスト(末尾の `.*` を落とした形)\n paths.push(spec.anchorList);\n for (let k = 0; k < depth; k++) {\n paths.push(nodePathAt(spec, k) + DELIMITER + spec.repeatList);\n }\n return paths;\n}\n\n/**\n * 具体パスが「その再帰 getter の深さ k の展開形」なら深さを返す。違えば null。\n *\n * 文字列中の反復語の出現数で数えない — 接尾辞が反復語と同じ綴りを含む場合に\n * 取り違える。前から `anchor`、後ろから `suffix` を確かめ、間が `repeat` の\n * 反復ちょうどであることを見る。\n */\nexport function depthOfConcretePath(spec: IRecursionSpec, suffix: string, path: string): number | null {\n if (!path.startsWith(spec.anchor)) {\n return null;\n }\n if (suffix.length > 0 && !path.endsWith(suffix)) {\n return null;\n }\n // 接頭辞と接尾辞が**重なって**はならない。重なると slice が空文字に畳まれて\n // 「深さ 0 で一致」に見え、アンカーそのもの(`nodes.*` — 実データの行)が\n // 生成 getter に隠される。接尾辞が `.*` の `nodes.**.*` で実際に踏んだ。\n if (path.length < spec.anchor.length + suffix.length) {\n return null;\n }\n const middle = path.slice(spec.anchor.length, path.length - suffix.length);\n if (middle.length === 0) {\n return 0;\n }\n const unit = DELIMITER + spec.repeat;\n let depth = 0;\n let cursor = 0;\n while (cursor < middle.length) {\n if (!middle.startsWith(unit, cursor)) {\n return null;\n }\n cursor += unit.length;\n depth++;\n }\n return depth;\n}\n","/**\n * recursion/bind.ts\n *\n * オーサリング層の `**` を「いま評価している深さ」へ束縛する。\n *\n * 深さの根拠は**生成アクセサのアドレス**であって、文字列中の反復語の出現数ではない。\n * `getByAddress` は getterPaths に載るパスを読むときアドレスをスタックへ積むので、\n * 深さ k の再帰 getter の本体を評価している最中は、スタック先頭がその具体パスの\n * アドレスになっている。そこから深さを復元する(実装計画 §1-2)。\n *\n * 見るのは**スタック先頭だけ**である。添字(ListIndex)を供給する `getContextListIndex` /\n * `$getAll` の省略形も先頭しか見ないので、深さだけを外側のフレームから拾うと「深さは\n * 束縛されたが行は無い」という定義にない状態になる — `**` getter が別の素の getter を\n * 経由して `**` を読む形(`get \"nodes.**.x\"() { return this.helper }` /\n * `get helper() { return this[\"nodes.**.value\"] }`)がそれで、直接読みは生の\n * `ListIndex not found`、`$getAll` の省略形は「束縛した深さ × 全行」という値を無言で\n * 返していた。深さと行は同じフレームから取る。\n */\n\nimport { IStateElement } from \"../components/types\";\nimport { DELIMITER } from \"../define\";\nimport { IStateHandler } from \"../proxy/types\";\nimport { recursionAnchorMismatchMessage } from \"../pathDiagnostics\";\nimport { raiseError } from \"../raiseError\";\nimport { splitRecursivePath } from \"./expand\";\nimport type { RecursionRegistry } from \"./registry\";\n\n/**\n * 具体パスの**接頭辞**として最深のノードパスを見つけ、その深さを返す。\n * `nodes.*.children.*.label` のような行 getter の文脈から深さ 1 を取り出す用。\n */\nfunction depthOfConcretePathPrefix(registry: RecursionRegistry, path: string): number | null {\n const spec = registry.spec;\n if (!path.startsWith(spec.anchor)) {\n return null;\n }\n const unit = DELIMITER + spec.repeat;\n let depth = 0;\n let cursor = spec.anchor.length;\n while (path.startsWith(unit, cursor)) {\n cursor += unit.length;\n depth++;\n }\n // 接頭辞の直後はパス境界(末尾、または `.`)でなければならない。\n if (cursor !== path.length && path.charCodeAt(cursor) !== 46 /* '.' */) {\n return null;\n }\n return depth;\n}\n\n/**\n * 評価中のアドレス(スタック先頭)から再帰の深さを求める。再帰文脈でなければ null。\n * 先頭が null(ループ文脈の無いイベントハンドラ・初期同期)も再帰文脈ではない。\n */\nexport function currentRecursionDepth(handler: IStateHandler, registry: RecursionRegistry): number | null {\n if (handler.addressStackLength === 0) {\n return null;\n }\n const address = handler.lastAddressStack;\n if (address === null) {\n return null;\n }\n const accessor = registry.accessorFor(address.pathInfo.path);\n if (accessor !== null) {\n return accessor.depth;\n }\n // 生成アクセサでなくても、宣言に合致する具体パス(行 getter・行のイベントハンドラが\n // 積むループのアドレス)ならそこから深さを取れる。\n return depthOfConcretePathPrefix(registry, address.pathInfo.path);\n}\n\n/**\n * `**` を含むパスを、いま評価している深さの具体パスへ書き換える。\n * 再帰文脈が無いところで `**` を直接読むのは、深さが決まらないので診断する。\n */\nexport function bindRecursivePath(\n stateElement: IStateElement,\n handler: IStateHandler,\n path: string,\n): string {\n // 呼び出し元は 2 つとも `hasRecursion === true` をゲートにしているので、\n // ここに来た時点でレジストリは必ずある。到達不能な `??` 分岐は置かない\n // (カバレッジ閾値に効く — walkDependency の `address.listIndex!` と同じ綴り)。\n const registry = stateElement.recursionRegistry!;\n // アンカー照合を先に行う。深さ解決を先にすると、綴り違いのアンカーが\n // 「文脈が無い」と報告されて原因に辿り着けない。\n const suffix = splitRecursivePath(registry.spec, path);\n if (suffix === null) {\n raiseError(recursionAnchorMismatchMessage(path, registry.spec.recursiveAnchor));\n }\n const depth = currentRecursionDepth(handler, registry);\n if (depth === null) {\n raiseError(\n `[wcs/recursion-context] \"${path}\" uses \"**\", which is bound to the depth of the recursive getter ` +\n `being evaluated, and there is no recursion context here. Read it from inside a recursive getter ` +\n `or a row getter under \"${registry.spec.anchor}\", or name a concrete depth ` +\n `(for example \"${registry.spec.anchor}${path.slice(registry.spec.recursiveAnchor.length)}\"). ` +\n `The depth comes from the innermost frame only: a plain getter reached from a recursive getter ` +\n `has no row of its own, so read \"**\" in the recursive getter and pass the value on.`\n );\n }\n // 照合済みの接尾辞をそのまま使う。具体パスは記憶付き(再帰 getter の評価ごとに通る経路)。\n return registry.concretePathAt(suffix, depth);\n}\n","/**\n * recursion/walk.ts\n *\n * 再帰アンカー配下を**全深さ**にわたって走査し、マッチする具体アドレスを列挙する。\n * `$getAll(path, [])` の合併形(設計書 §6-2)と `$setAll` のブロードキャストが共有する。\n *\n * 固定 arity の走査(`proxy/apis/wildcardIndexes.ts`)は「ワイルドカードの本数が\n * 静的に決まっている」ことに立脚しているので、そのままでは深さが動的な族を扱えない。\n * ここは深さ方向だけを自前で降り、**各深さの具体パスは固定 arity のまま**扱う\n * — つまりエンジンが見るパスは常に `**` を含まない普通のパスである(設計書 D2)。\n *\n * 順序は**深さ優先・行きがけ・添字昇順**(§1-2)。ノードを 1 つ出したら、その子へ\n * 降りきってから次の兄弟へ移る。読みと書きが同じ順序を使うことが `$setAll` の\n * 契約の前提になる。\n *\n * 走査が throw したとき、その走査が観測したリスト値は差分基準へ確定**しない**\n * (途中まで進めた基準を残すと、次の読みが「変化なし」と誤認しうる)。\n */\n\nimport { liftAddress } from \"../address/liftAddress\";\nimport { getPathInfo } from \"../address/PathInfo\";\nimport { createStateAddress } from \"../address/StateAddress\";\nimport { IAbsoluteStateAddress, IPathInfo, IStateAddress } from \"../address/types\";\nimport { DELIMITER } from \"../define\";\nimport { createListDiff } from \"../list/createListDiff\";\nimport { getStateListBaseline, setStateListBaseline } from \"../list/stateListBaseline\";\nimport { IListIndex } from \"../list/types\";\nimport { getByAddress } from \"../proxy/methods/getByAddress\";\nimport { IStateHandler } from \"../proxy/types\";\nimport { raiseError } from \"../raiseError\";\nimport type { RecursionRegistry } from \"./registry\";\n\n\n/** 深さごとに 1 回だけ決まるもの。ノードごとに作り直さない。 */\ninterface IDepthPaths {\n readonly nodePath: string;\n readonly concretePathInfo: IPathInfo;\n readonly childListPathInfo: IPathInfo;\n}\n\n/**\n * アンカー配下の全深さを列挙する。深さ優先・行きがけ・添字昇順。\n *\n * 終端は「その深さの子リストが空」。上限超過は `concretePathAt` が**その深さに実際に\n * ノードが居るときだけ**検査する(葉の 1 段先を投機的に見て落ちないように)。\n */\nexport function collectRecursiveAddresses(\n target: object,\n receiver: any,\n handler: IStateHandler,\n registry: RecursionRegistry,\n suffix: string,\n): IStateAddress[] {\n const spec = registry.spec;\n const results: IStateAddress[] = [];\n const observed: Map<IAbsoluteStateAddress, readonly unknown[]> = new Map();\n const pathsByDepth: IDepthPaths[] = [];\n const repeatList = spec.repeatList;\n const anchorList = spec.anchorList;\n\n /**\n * 「同じ配列インスタンスが 2 つ以上の親から到達可能」を**走査そのもの**で判定する\n * (設計書 D12・E6)。台帳の親(`newIndexes[0].parentListIndex`)で見てはならない\n * — 台帳はリスト配列の identity だけをキーにしていて、行オブジェクトを作り直す\n * ふつうのイミュータブル更新(`nodes.map(n => ({...n}))` は children を参照ごと\n * 引き継ぐ)でも親 ListIndex が別物になるため、正当な木を恒久的に拒否してしまう。\n *\n * 走査で見た配列を覚えておけば、共有も循環も「同じ配列に 2 度到達したか」で決まる。\n * 祖先の集合に居れば循環(自分より上へ戻る)、そうでなければ兄弟共有。\n * 空配列は行を持たないので別名化のしようがなく、追跡しない(`[]` の使い回しは正当)。\n */\n const ancestors: Set<readonly unknown[]> = new Set();\n const visited: Set<readonly unknown[]> = new Set();\n\n const guardShape = (\n listPath: string,\n value: unknown,\n seen: Set<readonly unknown[]>,\n ): readonly unknown[] | null => {\n if (!Array.isArray(value) || value.length === 0) {\n return null;\n }\n const list = value as readonly unknown[];\n if (ancestors.has(list)) {\n raiseError(\n `[wcs/recursion-cycle] \"${listPath}\" is reachable from itself: the recursion on ` +\n `\"${spec.anchor}\" walked into a list that one of its own ancestors already owns. ` +\n `The data contains a cycle, which this version does not support.`\n );\n }\n if (seen.has(list)) {\n raiseError(\n `[wcs/recursion-shared-list] \"${listPath}\" is the same array instance as a list reached ` +\n `from another node. The recursion on \"${spec.anchor}\" needs a tree: give each node its own ` +\n `\"${repeatList}\" array.`\n );\n }\n return list;\n };\n\n const pathsAt = (depth: number): IDepthPaths => {\n const known = pathsByDepth[depth];\n if (typeof known !== \"undefined\") {\n return known;\n }\n // 上限検査はここ(=その深さに実際にノードが居ると分かってから)。具体パスはレジストリの\n // 記憶(接尾辞 × 深さ)から引き、走査ごとに文字列連結をやり直さない。\n const concretePath = registry.concretePathAt(suffix, depth);\n const nodePath = suffix.length === 0 ? concretePath : registry.concretePathAt(\"\", depth);\n const paths: IDepthPaths = {\n nodePath,\n concretePathInfo: getPathInfo(concretePath),\n childListPathInfo: getPathInfo(nodePath + DELIMITER + repeatList),\n };\n pathsByDepth[depth] = paths;\n return paths;\n };\n\n /** 接尾辞側に残ったワイルドカード段だけを、行の ListIndex を起点に展開する。 */\n const expandSuffix = (concretePathInfo: IPathInfo, level: number, listIndex: IListIndex): void => {\n const parents = concretePathInfo.wildcardParentPathInfos;\n if (level >= parents.length) {\n results.push(createStateAddress(concretePathInfo, listIndex));\n return;\n }\n // 接尾辞側のリストは検査しない。接尾辞が反復語を含む形(`nodes.**.children.*.value`)\n // では、接尾辞の展開と深さ方向の降下が**同じ配列**を通る — 同じ族を 2 通りに綴れる\n // ことの帰結で、共有ではない。次元をまたいでも、同じ次元の中でも(深さ 0 の接尾辞\n // 展開と深さ 1 の接尾辞展開が同じ配列に当たる)自己衝突するので、共有の判定は\n // 深さ方向にだけ掛ける。\n //\n // 結果として `$setAll(\"nodes.**.tags\", [], arr)` のように**ブロードキャストが作った**\n // 配列共有は、ここでは捕まらない(`[wcs/wildcard-rank]` という無関係な文面で落ちる)。\n // 既知の制限として設計書に記録してある。\n const rows = readRows(parents[level], listIndex, null).rows;\n for (let i = 0; i < rows.length; i++) {\n expandSuffix(concretePathInfo, level + 1, rows[i]);\n }\n };\n\n /**\n * リストを 1 本読んで行と、追跡対象のリスト配列を返す。差分基準は state 側の\n * 共有正本(E1)から取り、観測値は走査の最後にまとめて確定する。`guardPath` が\n * 非 null のときだけ共有・循環の検査を掛ける(接尾辞側の普通のリストは再帰の\n * 対象ではない)。\n */\n function readRows(\n listPathInfo: IPathInfo,\n parentListIndex: IListIndex | null,\n seen: Set<readonly unknown[]> | null,\n ): { rows: IListIndex[], tracked: readonly unknown[] | null } {\n const listAddress = createStateAddress(listPathInfo, parentListIndex);\n const absAddress = liftAddress(handler.stateElement, listAddress);\n const value = getByAddress(target, listAddress, receiver, handler);\n const tracked = seen === null ? null : guardShape(listPathInfo.path, value, seen);\n const listDiff = createListDiff(\n parentListIndex, getStateListBaseline(absAddress), value);\n observed.set(absAddress, Array.isArray(value) ? value : []);\n if (tracked !== null && seen !== null) {\n seen.add(tracked);\n }\n return { rows: listDiff.newIndexes, tracked };\n }\n\n const descend = (depth: number, listPathInfo: IPathInfo, parentListIndex: IListIndex | null): void => {\n const { rows, tracked } = readRows(listPathInfo, parentListIndex, visited);\n if (rows.length === 0) {\n return;\n }\n const paths = pathsAt(depth);\n // 行が 1 つでもある ⟹ そのリストは非空配列だった ⟹ guardShape が追跡対象を返している\n // (非配列も空配列も createListDiff が空の行に畳むので、上の早期 return で抜ける)。\n // このリストは、いま降りている枝の祖先になる。子で同じ配列に当たれば循環。\n const branch = tracked as readonly unknown[];\n ancestors.add(branch);\n const flat = paths.concretePathInfo.wildcardCount === depth + 1;\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n if (flat) {\n // 接尾辞にワイルドカードが無い(大多数)。行の ListIndex がそのまま具体パスの\n // 連鎖長を満たすので、追加の走査は要らない。この行 ListIndex は createListDiff が\n // 台帳へ登録した正本そのものなので、`getListIndexByIndexes` で引き直す必要も無い。\n results.push(createStateAddress(paths.concretePathInfo, row));\n } else {\n expandSuffix(paths.concretePathInfo, depth + 1, row);\n }\n descend(depth + 1, paths.childListPathInfo, row);\n }\n ancestors.delete(branch);\n };\n\n descend(0, getPathInfo(anchorList), null);\n\n // 観測したリスト値を差分基準へ確定する。合併形の `$getAll` も再帰の `$setAll` も必ず確定する\n // (実装計画 §6 — cold な書き込みが ListIndex 世代を鋳造したまま基準を残さないと、次の\n // 構造変更で深い子台帳が孤児になる)。走査が throw したときは上の raise でここに来ない。\n for (const [address, value] of observed) {\n setStateListBaseline(address, value);\n }\n return results;\n}\n","/**\n * recursion/setAllRecursive.ts\n *\n * `$setAll(\"<anchor>.**.<suffix>\", [], value)` — **全深さへのブロードキャスト**\n * (設計書 D8 / §7-3)。\n *\n * 読み(`$getAll` の合併形)と同じ列挙を使い、同じ順序で書く。許すのは `[]` の\n * ブロードキャストだけで、mapper と `{ spread: true }` は受け付けない:\n *\n * - **mapper** の `(current, ...indexes)` は、深さごとに添字の本数が変わるので\n * そのままでは渡せない。深さを渡す別のシグネチャを決めてから入れる。\n * - **`{ spread: true }`** は「マッチ順に 1 件ずつ配る」形。順序は決定的に定義できるが、\n * 木に 1 次元配列を配るのは作者が走査順を知らないと使えず、実用にならない。\n *\n * 添字の省略も受け付けない。`$setAll` は「書き込み API に暗黙の文脈依存を持たせない」\n * という既存の決定(docs/state-set-all-design.md の D4)を継ぐので、読み側にある\n * 省略形(文脈束縛)の対応物を書き側には置かない。\n */\n\nimport { recursionAnchorMismatchMessage, setAllValueKindMessage } from \"../pathDiagnostics\";\nimport { setByAddress } from \"../proxy/methods/setByAddress\";\nimport { IStateHandler } from \"../proxy/types\";\nimport { raiseError } from \"../raiseError\";\nimport { assertWritable } from \"../proxy/assertWritable\";\nimport { foldSuffixIndexes, isStructuralSuffix, splitRecursivePath } from \"./expand\";\nimport { IRecursionSpec } from \"./types\";\nimport { collectRecursiveAddresses } from \"./walk\";\n\n/**\n * 接尾辞が「再帰の構造そのもの」を名指していないか(述語は expand.ts の `isStructuralSuffix`)。\n *\n * ノード自身(`nodes.**`)と子リスト(`nodes.**.children`)と子ノード\n * (`nodes.**.children.*`)と子リストの `length` は、書き換えると確定済みの子アドレスを壊す。\n * 初版は葉の属性の更新に限る(実装計画 §1-3)。判定対象は宣言から導出する。\n */\nfunction assertNotStructural(spec: IRecursionSpec, path: string, suffix: string): void {\n if (isStructuralSuffix(spec, suffix)) {\n raiseError(\n `[wcs/recursion-structural-write] \"${path}\" writes the recursion structure itself ` +\n `(a node, its \"${spec.repeatList}\" list or that list's length, or an object on the way to that list). ` +\n `This version broadcasts to leaf properties only — ` +\n `replacing a node would invalidate the child addresses already resolved for this write.`\n );\n }\n}\n\nexport function setAllRecursive(\n target: object,\n receiver: any,\n handler: IStateHandler,\n path: string,\n indexes: unknown,\n value: any,\n options: { readonly spread?: boolean } | undefined,\n): number {\n assertWritable(handler);\n // 呼び出し元(setAll.ts)は `hasRecursion === true` をゲートにしているので必ずある。\n const registry = handler.stateElement.recursionRegistry!;\n const suffix = splitRecursivePath(registry.spec, path);\n if (suffix === null) {\n raiseError(recursionAnchorMismatchMessage(path, registry.spec.recursiveAnchor));\n }\n // 検査には添字を `*` に畳んだ接尾辞を掛ける。`nodes.**.children.0`(子ノード)・\n // `nodes.**.children.0.children`(孫リスト)・`nodes.**.children.0.total`(getter の展開形)は\n // 添字綴りのままだと素の文字列一致をすり抜け、構造を置き換えたり部分書き込みの途中で\n // 生の TypeError になったりしていた(第 2 サイクルのレビューで実測)。列挙は綴りのまま\n // 行う — 接尾辞の添字は「その子だけ」を指す意味を持つ。\n const checkedSuffix = foldSuffixIndexes(suffix);\n\n // --- 形の検査は列挙より前(1 件も書かないことを保証する。設計 §7-3) ---\n if (!Array.isArray(indexes)) {\n raiseError(setAllValueKindMessage(\n path, `with \"**\" requires an explicit empty indexes array ([]) — the write API takes no context.`));\n }\n if (indexes.length > 0) {\n raiseError(\n `[wcs/recursion-setall-form] $setAll(\"${path}\", indexes, …) with \"**\" takes no partial prefix: ` +\n `a prefix cannot say which depth it applies to. Pass [] to broadcast to every depth.`\n );\n }\n if (typeof value === \"function\") {\n raiseError(setAllValueKindMessage(\n path, `with \"**\" does not take a mapper yet — the index tuple has a different length at each depth.`));\n }\n if (options?.spread === true) {\n raiseError(setAllValueKindMessage(\n path, `with \"**\" does not take { spread: true } — handing a flat array to a tree needs the author ` +\n `to know the walk order, which is not a usable contract.`));\n }\n assertNotStructural(registry.spec, path, checkedSuffix);\n const conflicting = registry.conflictingRecursiveGetter(checkedSuffix);\n if (conflicting !== null) {\n raiseError(\n `[wcs/recursion-readonly] \"${path}\" writes into the recursive getter \"${conflicting}\", which has ` +\n `no setter — the two name the same family of concrete paths (or this path points inside the value ` +\n `that getter derives). Write the values it derives from instead.`\n );\n }\n\n // --- 第 1 相: 書き込み先を全部確定する ---\n // **観測したリスト値は基準へ確定する**(走査が必ず行う — walk.ts)。固定 arity の\n // `$setAll` は `commitDiffBaseline: false` で走るが、あれは「読みの私有基準を書きから\n // 動かさない」という E1 以前の所有権モデルの話で、いまの基準は読み・描画・依存ウォークが\n // 共有する state 側の正本である(実装計画 §3-2 の E1)。\n //\n // 確定しないと cold(読みも描画も一度も走っていない)状態の `$setAll` が全深さぶんの\n // ListIndex 世代を鋳造したまま基準を残さず、次の構造変更でその世代が見えない diff が\n // 行を鋳造し直す。生き残った深い children の台帳だけが死んだ世代の親を指し、以後\n // 再帰 getter の読みが恒久的に落ちる(値の合併は動き続けるので無症状のまま進む)。\n // 1 件も書かない `undefined` のブロードキャストでも同じなので、「書き込み 0 件」は\n // 「状態が動いていない」を意味しない。\n const addresses = collectRecursiveAddresses(target, receiver, handler, registry, suffix);\n\n // --- 第 2 相: 確定したアドレスにだけ書く ---\n let written = 0;\n for (let i = 0; i < addresses.length; i++) {\n // undefined は常にスキップ(設計 §5)。クリアは null。\n if (typeof value === \"undefined\") {\n continue;\n }\n setByAddress(target, addresses[i], value, receiver, handler);\n written++;\n }\n return written;\n}\n","/**\n * recursion/addressHooks.ts — `$recursion` を宣言した state に付く hook(設計案 H1、S3)。\n * 従来 core(get トラップ・`$getAll` / `$setAll`・setByAddress)が直接 import していた `**` の\n * 束縛・合併形・書き込み禁止をここへ移し、宣言の無い state では一切走らない。\n * (`$trackDependency` の `**` 拒否は宣言の有無に関わらないので core に残る)\n */\nimport { getResolvedAddress } from \"../address/ResolvedAddress\";\nimport { createStateAddress } from \"../address/StateAddress\";\nimport { IAddressHooks, NOT_HANDLED, registerFeatureHooks } from \"../core/addressHooks\";\nimport { getAll } from \"../proxy/apis/getAll\";\nimport { ISetAllOptions, setAll } from \"../proxy/apis/setAll\";\nimport { getByAddress } from \"../proxy/methods/getByAddress\";\nimport { getListIndex } from \"../proxy/methods/getListIndex\";\nimport { raiseError } from \"../raiseError\";\nimport { bindRecursivePath } from \"./bind\";\nimport { hasRecursionWildcard } from \"./expand\";\nimport { getAllRecursive } from \"./getAllRecursive\";\nimport { setAllRecursive } from \"./setAllRecursive\";\n\n// hook は要素の寿命の間は付いたまま(再セットで `$recursion` が消えても外れない)ので、\n// 宣言が消えた後は `hasRecursion` の boolean 判定 1 個で core と同じ経路へ戻す\nexport const recursionAddressHooks: IAddressHooks = {\n // 再帰 getter の展開形(`nodes.*.children.*.total`)とその値の内側への書き込みは、`**` を\n // 含まないので `setAllRecursive` の読み取り専用検査を通らない。未実体化なら fast path が\n // 「親オブジェクトの未存在キー」として行オブジェクトへ素の値を書き、代入値を `dirty:false` で\n // キャッシュに載せる — ノードが汚れ、実体化後も getter が評価されず、深さ 0 の集計まで\n // 巻き込む(レビュー P18 で実測)。読み側の遅延実体化(getByAddress の E5)と対称に、書き側はここで止める\n write(stateElement, address) {\n if (stateElement.hasRecursion !== true) {\n return NOT_HANDLED;\n }\n const owner = stateElement.recursionRegistry!.recursiveGetterOwningPath(address.pathInfo);\n if (owner !== null) {\n raiseError(\n `[wcs/recursion-readonly] \"${address.pathInfo.path}\" writes into the recursive getter \"${owner}\" ` +\n `(this path is that getter at one depth, or a path inside the value it derives), which has ` +\n `no setter. Write the values it derives from instead.`\n );\n }\n return NOT_HANDLED;\n },\n get(handler, prop, receiver, target) {\n if (handler.stateElement.hasRecursion !== true) {\n return NOT_HANDLED;\n }\n if (prop === \"$getAll\") {\n // オーサリング層の `**`。省略形は「いま評価している深さ」に束縛し、`[]` 明示は\n // 全深さの合併になる(設計書 §6-2)。部分接頭辞は `**` に対して定義できない。\n return (path: string, indexes?: number[]): any[] => {\n if (hasRecursionWildcard(path)) {\n if (typeof indexes === \"undefined\") {\n path = bindRecursivePath(handler.stateElement, handler, path);\n } else {\n // アンカー照合と添字の形の検査は合併形の側で行う(判定順を静的側と揃えるため)\n return getAllRecursive(target, receiver, handler, path, indexes);\n }\n }\n return getAll(target, prop, receiver, handler)(path, indexes);\n };\n }\n if (prop === \"$setAll\") {\n // 書き側は `[]` のブロードキャストだけを受け付ける(形の検査は列挙より前に行い、\n // 1 件も書かないことを保証する。設計 §7-3)\n return (path: string, indexes: number[], value: any, options?: ISetAllOptions): number => {\n if (hasRecursionWildcard(path)) {\n return setAllRecursive(target, receiver, handler, path, indexes, value, options);\n }\n return setAll(target, prop, receiver, handler)(path, indexes, value, options);\n };\n }\n if (prop.charCodeAt(0) !== 36 /* '$' */ && hasRecursionWildcard(prop)) {\n // オーサリング層の `**` を、いま評価している再帰 getter の深さへ束縛して通常解決する\n const resolvedAddress = getResolvedAddress(bindRecursivePath(handler.stateElement, handler, prop));\n const listIndex = getListIndex(target, resolvedAddress, receiver, handler);\n return getByAddress(target, createStateAddress(resolvedAddress.pathInfo, listIndex), receiver, handler);\n }\n return NOT_HANDLED;\n },\n};\n\nlet installed = false;\n/** 冪等。full エントリでは State が `$recursion` の宣言時に呼ぶ(分割エントリでは `install` が担う) */\nexport function installRecursionHooks(): void {\n if (installed) return;\n installed = true;\n registerFeatureHooks(\"recursion\", recursionAddressHooks);\n}\n","/**\n * recursion/getAllRecursive.ts\n *\n * `$getAll(\"<anchor>.**.<suffix>\", [])` — **全深さの合併**(設計書 D7 / §6-2)。\n *\n * 省略形(文脈束縛)とは別経路にする。省略形は「いま評価している深さの 1 本」を\n * 具体パスに直して既存の固定 arity 走査へ渡すだけだが、合併形は深さそのものを\n * 走査対象にするので、返る添字タプルの長さが結果ごとに変わる。だから合併形は\n * **値の配列しか返さない**(`$resolve` への往復は保証しない。設計書 §7-2)。\n */\n\nimport { getByAddress } from \"../proxy/methods/getByAddress\";\nimport { IStateHandler } from \"../proxy/types\";\nimport { splitRecursivePath } from \"./expand\";\nimport { collectRecursiveAddresses } from \"./walk\";\nimport { recursionAnchorMismatchMessage } from \"../pathDiagnostics\";\nimport { raiseError } from \"../raiseError\";\n\nexport function getAllRecursive(\n target: object,\n receiver: any,\n handler: IStateHandler,\n path: string,\n indexes: unknown,\n): any[] {\n // 呼び出し元(getAll.ts)は `hasRecursion === true` をゲートにしている。\n const registry = handler.stateElement.recursionRegistry!;\n // 判定順はアンカー照合 → 添字の形。静的側(vscode-wcs recursionValidator)と同じ順に\n // しておかないと、綴り違いのアンカーに `[0]` を渡した呼び出しが片側では\n // `recursion-anchor`、もう片側では `recursion-getall-form` になる。\n const suffix = splitRecursivePath(registry.spec, path);\n if (suffix === null) {\n raiseError(recursionAnchorMismatchMessage(path, registry.spec.recursiveAnchor));\n }\n // 合併形の添字は `[]` だけ。`null` 等の非配列は素の TypeError にせず、形の診断にする。\n if (!Array.isArray(indexes)) {\n raiseError(\n `[wcs/recursion-getall-form] $getAll(\"${path}\", indexes) with \"**\" takes either no indexes ` +\n `(to read the depth of the recursive getter being evaluated) or [] (to walk every depth) — ` +\n `got ${indexes === null ? \"null\" : typeof indexes}.`\n );\n }\n if (indexes.length > 0) {\n raiseError(\n `[wcs/recursion-getall-form] $getAll(\"${path}\", indexes) with \"**\" takes no partial ` +\n `prefix: a prefix cannot say which depth it applies to. Omit the indexes to read the ` +\n `depth of the recursive getter being evaluated, or pass [] to walk every depth.`\n );\n }\n // 走査は観測したリスト値を差分基準へ確定する(再帰の `$setAll` も同じ。\n // 固定 arity の `$setAll` だけが確定しない — setAllRecursive.ts 第 1 相の注記)。\n const addresses = collectRecursiveAddresses(target, receiver, handler, registry, suffix);\n const values: any[] = [];\n for (let i = 0; i < addresses.length; i++) {\n // `**` は依存グラフに載らない(D2)。呼び出し元の getter は「触れた深さの\n // 具体パス」に依存する — その登録は getByAddress の checkDependency が行う\n // (他行読み取りの検出も含む)。ここで書き写すと untrack を見ない劣化版になる。\n values.push(getByAddress(target, addresses[i], receiver, handler));\n }\n return values;\n}\n","/**\n * recursion/declaration.ts\n *\n * `$recursion: { <anchor>: <repeat> }` 宣言を検証して仕様に落とす\n * (docs/state-recursive-path-impl-plan.md §1-1)。宣言が無ければ null で、\n * その state は再帰の経路にまったく入らない(`$listKeys` と同じゼロコスト規約)。\n *\n * 初版が受け付けるのは **単一の自己再帰** だけ。アンカーも反復サブパスも\n * 「固定プロパティ列 + 末尾の `.*` ひとつ」に限る。途中のワイルドカード・複数宣言・\n * 相互再帰は、黙って別の意味に解釈せず明示的に拒否する。\n */\n\nimport { DELIMITER, RECURSION_WILDCARD, STATE_RECURSION_NAME, WILDCARD } from \"../define\";\nimport { raiseError } from \"../raiseError\";\nimport type { IState } from \"../types\";\nimport { IRecursionSpec } from \"./types\";\n\n/** `a.b.*` の形(末尾だけがワイルドカード・空セグメント無し・`**` 無し・添字無し)か。 */\nfunction assertNodePath(kind: string, path: string): void {\n if (typeof path !== \"string\" || path.length === 0) {\n raiseError(`[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} ${kind} must be a non-empty string.`);\n }\n const segments = path.split(DELIMITER);\n if (segments.some((segment) => segment.length === 0)) {\n raiseError(`[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} ${kind} \"${path}\" must not contain empty path segments.`);\n }\n if (segments.length < 2) {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} ${kind} \"${path}\" must name a list element: ` +\n `a property path ending with \"${DELIMITER}${WILDCARD}\" (for example \"nodes${DELIMITER}${WILDCARD}\").`\n );\n }\n if (segments[segments.length - 1] !== WILDCARD) {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} ${kind} \"${path}\" must end with \"${DELIMITER}${WILDCARD}\" ` +\n `— it names the element of the list, not the list itself.`\n );\n }\n // 予約セグメント。マウントのマーカー(`#m1`)と `$` 名前空間は raw state に実体を\n // 持たないので、再帰のアンカーにはなり得ない(checkDeclaredPath が同じ 2 つで\n // 早期 return しているのと対称)。\n if (segments[0].charCodeAt(0) === 36 /* '$' */) {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} ${kind} \"${path}\" must not start with \"$\" — that namespace is reserved.`\n );\n }\n if (path.indexOf(\"#\") !== -1) {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} ${kind} \"${path}\" must not contain \"#\" — that segment is reserved for mounts.`\n );\n }\n for (let i = 0; i < segments.length - 1; i++) {\n if (segments[i] === WILDCARD) {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} ${kind} \"${path}\" must have exactly one \"${WILDCARD}\", at the end. ` +\n `Wildcards in the middle are not supported in this version.`\n );\n }\n if (segments[i] === RECURSION_WILDCARD) {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} ${kind} \"${path}\" must not contain \"${RECURSION_WILDCARD}\" — ` +\n `the declaration is what gives \"${RECURSION_WILDCARD}\" its meaning.`\n );\n }\n // 添字セグメント(`children.0.*`)。エンジンは具体パスの添字を `*` に畳むので\n // (`indexSegmentsToWildcard` — ResolvedAddress と同じ規則)、宣言の途中に書かれた\n // 添字は意味を持たない奇形になる。黙って `*` と同じに読み替えず、ここで落とす。\n if (!isNaN(Number(segments[i]))) {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} ${kind} \"${path}\" must not contain an index segment ` +\n `(\"${segments[i]}\") — the recursion is declared over the shape of the tree, not over one row.`\n );\n }\n }\n}\n\n/**\n * `$recursion` 宣言を検証して仕様にする。宣言が無ければ null(=ゼロコスト経路)。\n */\nexport function processRecursionDeclaration(state: IState): IRecursionSpec | null {\n const declared = (state as Record<string, unknown>)[STATE_RECURSION_NAME];\n if (typeof declared === \"undefined\") {\n return null;\n }\n if (typeof declared !== \"object\" || declared === null) {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} must be an object mapping one anchor path to its repeating sub-path ` +\n `(for example { \"nodes.*\": \"children.*\" }).`\n );\n }\n const entries = Object.entries(declared as Record<string, unknown>);\n if (entries.length === 0) {\n raiseError(`[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} must declare exactly one anchor; it is empty.`);\n }\n if (entries.length > 1) {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} declares ${entries.length} anchors (${entries.map(([k]) => `\"${k}\"`).join(\", \")}). ` +\n `This version supports exactly one self-recursive anchor per state.`\n );\n }\n const [anchor, repeat] = entries[0];\n assertNodePath(\"anchor\", anchor);\n if (typeof repeat !== \"string\") {\n raiseError(\n `[wcs/recursion-declaration-invalid] ${STATE_RECURSION_NAME} entry \"${anchor}\" must map to the repeating sub-path as a string ` +\n `(for example \"children${DELIMITER}${WILDCARD}\").`\n );\n }\n assertNodePath(\"repeating sub-path\", repeat);\n // 反復サブパスが相対か絶対かは**名前の形では判定できない**。`{ \"nodes.*\": \"nodes.*\" }`\n // は `{ nodes: [{ nodes: [...] }] }` という自己相似な木の最も自然な綴りなので、\n // 「アンカーと同じ語で始まる」ことを理由に拒否してはならない。\n const anchorList = anchor.slice(0, anchor.lastIndexOf(DELIMITER));\n const repeatList = repeat.slice(0, repeat.lastIndexOf(DELIMITER));\n const recursiveAnchor = anchorList + DELIMITER + RECURSION_WILDCARD;\n return Object.freeze({ anchor, repeat, recursiveAnchor, anchorList, repeatList });\n}\n","/**\n * recursion/generation.ts\n *\n * 再帰レジストリの**世代の後始末**。state の再セットで前世代のレジストリが捨てられるとき、\n * その世代が生やしたもの — own の生成アクセサ・依存表の辺・評価結果のキャッシュ — を忘れる。\n * レジストリ本体(宣言の検証・パス族の代数・遅延実体化)から切り出してある: ここだけが\n * 依存表・キャッシュ・台帳という state 全体の構造に触る。\n */\n\nimport { IStateElement } from \"../components/types\";\n\n/**\n * この機構が生やした getter。作者が手で書いた同名 getter と見分けるために使う\n * (前者は忘れてよい・後者は衝突として拒否する)。\n */\nconst generatedGetters: WeakSet<object> = new WeakSet();\n\nexport function markGeneratedGetter(getter: object): void {\n generatedGetters.add(getter);\n}\n\n/** この機構の生成物か(own に残った生成 getter)。 */\nexport function isGeneratedGetter(descriptor: PropertyDescriptor): boolean {\n return typeof descriptor.get === \"function\" && generatedGetters.has(descriptor.get);\n}\n\n/**\n * 前世代が生やしたものを忘れる(state の再セット時に呼ぶ)。忘れるのは 2 つ。\n *\n * **own の生成アクセサ。** 生成 getter は state オブジェクトの own プロパティとして残る。\n * 同じオブジェクトを `$recursion` 無し(または別アンカー)で再セットしたとき、残したままだと\n * `getStateInfo` が `getterPaths` に拾い直し、読むと作者が書いていない `nodes.**.value` を\n * 名指す `wcs/recursion-unsupported` になる(第 4 サイクルで実測)。`getStateInfo` の\n * **前**に消す。\n *\n * **依存辺。** `_state` のセッタは `_listPaths` / `_getterPaths` / `_pathSet` をクリアするが、\n * 依存表(`_staticDependency` / `_dynamicDependency`)は state の寿命を越えて残る。\n * 通常のパスはそれで正しい — 同じ綴りのパスは新しい state でも同じ意味を持つ。\n * だが**生成アクセサは違う**。新しい世代ではまだ実体化されておらず、それを指す辺だけが\n * 残ると、次の構造書き込みで依存ウォークが「アクセサの無い具体パス」へ降りて落ちる。\n * 依存表そのものをクリアしてはならない(既存バインディングの辺まで消えて、再セット後の\n * 集計が更新されなくなる — 実測済み)。この世代が作った辺だけを外す。\n *\n * **評価結果のキャッシュは忘れない。** 同じ state オブジェクト(または同じ配列)の再セットでは\n * 台帳が配列の identity をキーにしているので ListIndex も絶対アドレスも世代を跨いで同一のまま残り、\n * かつては旧世代の `dirty:false` の値がそのまま次の読みに返っていた(第 2・第 4 サイクルで実測)。\n * これは後から入った**世代印**(`cache/types.ts` の `generation`。`3bfb92a6`)が構造的に塞いでいる:\n * キャッシュを載せる 2 か所(`getByAddress` / `setByAddress`)が必ず現世代の印を付け、読みは\n * `cacheEntry.generation === stateElement.stateGeneration` の項目しかヒットにしない。この関数は\n * `runPreCommit` から呼ばれ、`_stateGeneration++` はその**直後**(`components/State.ts`)なので、\n * ここで消せる項目はどれみち必ず世代不一致で miss になる。掃き出しは `O(生成パス数 × 行数)` の\n * 純粋な無駄仕事だったので外した(番人は `integration.recursionGetter.test.ts` の再セット 4 本 —\n * 世代印を壊すとこの 4 本が落ちる)。\n */\nexport function forgetGeneration(\n stateElement: IStateElement,\n previousState: object,\n generatedPaths: ReadonlySet<string>,\n): void {\n if (generatedPaths.size === 0) {\n return;\n }\n for (const path of generatedPaths) {\n const descriptor = Object.getOwnPropertyDescriptor(previousState, path);\n if (typeof descriptor !== \"undefined\" && isGeneratedGetter(descriptor)) {\n delete (previousState as Record<string, unknown>)[path];\n }\n }\n for (const map of [stateElement.staticDependency, stateElement.dynamicDependency]) {\n for (const path of generatedPaths) {\n map.delete(path);\n }\n for (const [source, targets] of map) {\n let kept: string[] | null = null;\n for (let i = 0; i < targets.length; i++) {\n if (generatedPaths.has(targets[i])) {\n kept ??= targets.slice(0, i);\n continue;\n }\n kept?.push(targets[i]);\n }\n if (kept !== null) {\n if (kept.length === 0) {\n map.delete(source);\n } else {\n map.set(source, kept);\n }\n }\n }\n }\n}\n","/**\n * recursion/registry.ts\n *\n * state 1 つぶんの再帰レジストリ。宣言・`**` getter の定義・展開済みアクセサの台帳を\n * 持ち、「具体パスを読む直前に、その深さのアクセサを生やす」遅延実体化を担う。\n *\n * 遅延であることは実装の**不変条件**である(Phase A の A6/A7)。そのパスを一度でも\n * 読んでから生やしても、`isCacheable` が `wildcardCount > 0` だけでキャッシュ可を返す\n * ため `undefined` が `dirty:false` で固定され、以後どう書いても回復しない。\n * したがって実体化は `getByAddress` のキャッシュ参照**前**に置く(E5)。\n *\n * 寿命は state の世代と共にする。`_state` の再セットで `getterPaths` / `listPaths` は\n * クリアされるので、レジストリも作り直す(§1-3)。ただし**生やしたアクセサは state\n * オブジェクトの側に残る**ので、同じ state を再セットすると `getStateInfo` がそれを\n * `getterPaths` に復元する。そのとき「もう生えているから何もしない」と早期 return して\n * しまうと `listPaths` の登録だけが抜け落ちるため、生成物は WeakSet で見分けて\n * 登録だけをやり直す。\n */\n\nimport { IPathInfo } from \"../address/types\";\nimport { IStateElement } from \"../components/types\";\nimport { DELIMITER } from \"../define\";\nimport { getAllPropertyDescriptors } from \"../getAllPropertyDescriptors\";\nimport { recursionAnchorMismatchMessage } from \"../pathDiagnostics\";\nimport { raiseError } from \"../raiseError\";\nimport {\n concretePathAt, coversSuffix, depthOfConcretePath, foldSuffixIndexes, hasRecursionWildcard,\n indexSegmentsToWildcard, isStructuralSuffix, listPathsUpTo, sameFamily, splitRecursivePath,\n} from \"./expand\";\nimport { forgetGeneration, isGeneratedGetter, markGeneratedGetter } from \"./generation\";\nimport { IRecursionAccessor, IRecursionSpec } from \"./types\";\n\n/** 宣言時(構築時)の診断コード。lint(vscode-wcs)が同じコードで先に出す。 */\nconst DECLARATION_INVALID = \"[wcs/recursion-declaration-invalid]\";\n\n/** `**` getter 1 本ぶんの定義(本体は評価しない — descriptor だけを持つ)。 */\ninterface IRecursiveGetterDefinition {\n readonly recursivePath: string;\n readonly suffix: string;\n readonly get: () => unknown;\n}\n\nexport class RecursionRegistry {\n readonly spec: IRecursionSpec;\n private readonly _definitions: Map<string, IRecursiveGetterDefinition> = new Map();\n private readonly _accessors: Map<string, IRecursionAccessor> = new Map();\n /**\n * `recursiveGetterOwning` の記憶。キーは添字を `*` に畳んだ形(`nodes.1.total` と `nodes.2.total`\n * は 1 つ)、値は「その具体パスを展開形(またはその値の内側)として持つ `**` getter」、\n * 無ければ null。\n *\n * 有界である: キーは添字を畳んだワイルドカード形のパス文字列で、`PathInfo` が intern する集合\n * (バインディング・getter・API 引数に綴られたパスと、その展開形)の部分集合にしかならない。\n * intern 済みパスの集合が有界であることは D10 で受け入れ済みなので、ここも同じ上限に収まる。\n * 文字列は WeakSet に入らないので、寿命はレジストリ(= state の世代)と共にする。\n */\n private readonly _ownerByPath: Map<string, string | null> = new Map();\n /**\n * 書き込みのホットパス(`setByAddress`)向けの記憶。キーは intern 済みの `PathInfo` なので\n * 寿命と上限は PathInfo の intern 集合と同じ(WeakMap)。畳み(split + Number + join)は\n * miss のときだけ払う — 宣言のある state では**アンカー外を含む全書き込み**がここを通る\n * (第 4 サイクルで実測: 畳みを毎回払うと `s.counter = i` で +100ns/書き込み)。\n */\n private readonly _ownerByPathInfo: WeakMap<IPathInfo, string | null> = new WeakMap();\n /**\n * 読みのホットパス(`getByAddress`)向けの記憶。`_ownerByPathInfo` と対称で、キーは\n * intern 済みの `PathInfo`、値は「そのパスの展開アクセサ」、展開形でなければ null。\n * 宣言のある state では**アンカー外を含む全読み**(親ウォークの各段を含む)がここを\n * 通るので、文字列キーの `Map.get` + `Set.has` + `startsWith` を毎回払わせない\n * (第 5 サイクルで実測)。\n *\n * 読みの否定判定の記憶は**ここ 1 つ**(第 5 サイクル再検証で文字列キーの `_nonAccessors` を撤去 —\n * 前段にこの記憶を置いた後は、PathInfo とパス文字列が 1:1 なので二重に持つだけだった)。\n * 否定を記憶してよい根拠は、定義集合が state の世代内で不変であること — \n * 同じ `PathInfo` は同じパス文字列なので、いちど「展開形でない」と決まった PathInfo が\n * 後から実体化されることはない。実体化した側は `materializeForPathInfo` が\n * `_define` の戻り値でそのまま記憶を更新する(否定が実体化を隠さない)。\n */\n private readonly _accessorByPathInfo: WeakMap<IPathInfo, IRecursionAccessor | null> = new WeakMap();\n /** `concretePathAt` の記憶(接尾辞 → 深さ順の具体パス)。 */\n private readonly _concreteBySuffix: Map<string, string[]> = new Map();\n private readonly _registeredListPaths: Set<string> = new Set();\n\n constructor(spec: IRecursionSpec, state: object) {\n this.spec = spec;\n // getter 本体は実行しない。descriptor だけを見て `**` を含むキーを拾う。\n const descriptors = getAllPropertyDescriptors(state);\n for (const [key, descriptor] of Object.entries(descriptors)) {\n if (!hasRecursionWildcard(key)) {\n continue;\n }\n if (typeof descriptor.set === \"function\") {\n raiseError(\n `${DECLARATION_INVALID} Recursive setters are not supported in this version: \"${key}\". ` +\n `Declare a plain path setter, or write through the concrete path.`\n );\n }\n if (typeof descriptor.get !== \"function\") {\n raiseError(\n `${DECLARATION_INVALID} \"${key}\" contains \"**\" but is not a getter. ` +\n `The recursion wildcard only names a family of computed paths.`\n );\n }\n const suffix = splitRecursivePath(spec, key);\n if (suffix === null) {\n raiseError(recursionAnchorMismatchMessage(key, spec.recursiveAnchor));\n }\n if (suffix.length === 0) {\n // `get \"nodes.**\"` は展開すると `nodes.*` そのもの。`getByAddress` は\n // 「パスが target にあるか」を先に見るので、実データの行が丸ごと隠れる。\n raiseError(\n `${DECLARATION_INVALID} \"${key}\" names the recursive node itself. \"**\" names a computed path ` +\n `under a node (for example \"${spec.recursiveAnchor}${DELIMITER}total\"), not the node.`\n );\n }\n // 構造の判定は添字綴り(`get \"nodes.**.children.0\"()`)も畳んでから掛ける(書き側と同じ)\n if (isStructuralSuffix(spec, foldSuffixIndexes(suffix))) {\n // `get \"nodes.**.children\"` / `.children.*` / `.children.length` / 多段なら `.branch` は\n // 展開すると実データの子リスト(子ノード・その length・途中のオブジェクト)そのもの。\n // 生成 getter が `getByAddress` の「パスが target にあるか」で勝ち、実データの木を\n // 深さ 1 以下ごと無言で影にする(第 2 サイクルのレビューで実測: `$getAll(\"nodes.**.value\", [])`\n // が `[1, 2]` に縮んだ)。書き側が同じ形を `recursion-structural-write` で拒否するのと対称。\n raiseError(\n `${DECLARATION_INVALID} \"${key}\" names the recursion structure itself (a node, its ` +\n `\"${spec.repeatList}\" list or that list's length, or an ` +\n `object on the way to that list). A recursive getter would hide the real child list at every ` +\n `depth — \"**\" names a computed leaf under a node (for example \"${spec.recursiveAnchor}${DELIMITER}total\").`\n );\n }\n this._definitions.set(key, {\n recursivePath: key,\n suffix,\n get: descriptor.get as () => unknown,\n });\n }\n this._assertNoColliding();\n this._assertNoConcreteCollision(descriptors);\n }\n\n /**\n * 作者が手で書いた具体パス(`get \"nodes.*.children.*.total\"()` / データプロパティ)が、宣言済み\n * `**` getter の展開形と同名でないことを**構築時に**確かめる。\n *\n * `_define` の衝突検査は「その深さを最初に読んだとき」にしか走らないので、データが浅い間は\n * 通り、木が 1 段深くなった瞬間にバインディングが落ちていた(第 3 サイクルのレビューで実測)。\n * 前世代の生成物(own に残った生成 getter)は衝突ではない — 同じ state の再セットで必ず居る。\n */\n private _assertNoConcreteCollision(descriptors: Record<string, PropertyDescriptor>): void {\n for (const [key, descriptor] of Object.entries(descriptors)) {\n if (hasRecursionWildcard(key) || isGeneratedGetter(descriptor)) {\n continue;\n }\n const owner = this._matchExpansion(key);\n if (owner !== null) {\n raiseError(\n `${DECLARATION_INVALID} \"${key}\" is already defined on the state, so the recursive getter ` +\n `\"${owner}\" cannot expand to it. Rename one of them.`\n );\n }\n }\n }\n\n /**\n * 2 本の `**` getter が同じ具体パスへ展開しないことを、宣言だけから静的に確かめる。\n *\n * 衝突するのは「片方の接尾辞がもう片方の接尾辞の末尾で、差分が反復語の整数倍」の\n * ときだけ(`nodes.**.total` と `nodes.**.children.*.total` は深さ k と k+1 で\n * 同じ `nodes.*.children.*.total` になる)。検出しないと `_definitions` の挿入順で\n * 最初に一致した方が無言で勝つ。\n */\n private _assertNoColliding(): void {\n const definitions = Array.from(this._definitions.values());\n for (let i = 0; i < definitions.length; i++) {\n for (let j = i + 1; j < definitions.length; j++) {\n if (sameFamily(this.spec, definitions[i].suffix, definitions[j].suffix)) {\n raiseError(\n `${DECLARATION_INVALID} \"${definitions[i].recursivePath}\" and \"${definitions[j].recursivePath}\" ` +\n `expand to the same concrete path at different depths (they differ by whole repetitions of ` +\n `\"${this.spec.repeat}\"). Rename one of them.`\n );\n }\n }\n }\n }\n\n /**\n * `**` getter を 1 本でも宣言しているか。\n * **テスト・診断専用**(ランタイムの経路は `_definitions.size` を直接見る)。\n */\n get hasDefinitions(): boolean {\n return this._definitions.size > 0;\n }\n /**\n * その接尾辞が宣言済みの `**` getter と衝突するなら、その getter のパスを返す。\n *\n * 完全一致だけでは足りない。①反復語の整数倍だけ違う接尾辞は同じ族を指す\n * (`_assertNoColliding` が宣言どうしについて既に見ている条件)②getter の**下**を\n * 指す形(`nodes.**.total.x` / 反復語ぶんずれた `nodes.**.children.*.total.x`)は、\n * getter が返したオブジェクトへ書いてキャッシュを汚し、次の無効化で無言に戻る。\n * どちらも書き込みの入口(列挙より前)で止める — 述語は expand.ts の `coversSuffix`。\n */\n conflictingRecursiveGetter(suffix: string): string | null {\n for (const definition of this._definitions.values()) {\n if (coversSuffix(this.spec, definition.suffix, suffix)) {\n return definition.recursivePath;\n }\n }\n return null;\n }\n\n /**\n * `recursiveGetterOwning` の intern 済み `PathInfo` 版(書き込みのホットパス用)。\n * WeakMap の hit なら畳みも照合も払わない。\n */\n recursiveGetterOwningPath(pathInfo: IPathInfo): string | null {\n const known = this._ownerByPathInfo.get(pathInfo);\n if (typeof known !== \"undefined\") {\n return known;\n }\n const owner = this.recursiveGetterOwning(pathInfo.path);\n this._ownerByPathInfo.set(pathInfo, owner);\n return owner;\n }\n\n /**\n * 具体パスを展開形(またはその値の内側)として持つ `**` getter のパス。無ければ null。\n * **実体化はしない。**\n *\n * `conflictingRecursiveGetter` の**具体パス版**で、`**` を経ない 2 つの入口が使う:\n *\n * - バインド確立時のパス存在検査(`checkDeclaredPath`)。あの時点ではまだ生えて\n * いないので、素の存在検査では必ず「解決できない」になる。展開形そのもの\n * (`nodes.*.total`)だけでなく、その値の中を指す形(`nodes.*.stats.count` で\n * `get \"nodes.**.stats\"()` がオブジェクトを返す)も、通常の getter の下と同じく\n * 評価しないと分からないので黙る側に倒す。\n * - 書き込みの入口(`setByAddress`)。`$setAll(\"nodes.*.children.*.total\", [], v)` や\n * `this[\"nodes.1.total\"] = v` は `**` を含まないので `setAllRecursive` の\n * 読み取り専用検査を通らず、未実体化なら fast path が行オブジェクトへ素の\n * プロパティとして書いてしまう(ノードを汚し、代入値が `dirty:false` で載って\n * 以後 getter が評価されない)。展開形への書き込みは、実体化の前後に関わらず\n * `wcs/recursion-readonly` で止める。\n */\n recursiveGetterOwning(concretePath: string): string | null {\n const accessor = this._accessors.get(concretePath);\n if (typeof accessor !== \"undefined\") {\n return accessor.recursivePath;\n }\n // 添字綴り(`$setAll(\"nodes.1.total\", [], v)` — API のパス引数は set トラップと違って\n // getResolvedAddress の正規化を経ない)は、添字を `*` に畳んでから照合する。畳まないと\n // `nodes[1].total` へ素の値が書かれる(第 3 回レビューで実測)。**無条件に**畳む —\n // 「アンカーで始まらないときだけ」にすると、ワイルドカードと添字の混在綴り\n // (`nodes.*.children.0.total`)がアンカーで始まるせいで畳まれず、`depthOfConcretePath` が\n // `.children.0` を反復単位と認めずに素通りする(第 4 回レビューで実測)。\n // 記憶のキーは畳んだ形 — 添字綴りのまま記憶すると綴りの数だけ単調に増える。\n const pattern = indexSegmentsToWildcard(concretePath);\n const known = this._ownerByPath.get(pattern);\n if (typeof known !== \"undefined\") {\n return known;\n }\n let owner: string | null = null;\n if (pattern.startsWith(this.spec.anchor)) {\n // 展開形そのもの → その値の内側(`.` 境界で切った接頭辞を長い方から)の順に照合する。\n // 接頭辞はアンカーより長いものだけ — アンカー自身は接尾辞が空なので getter になり得ない。\n owner = this._matchExpansion(pattern);\n for (let end = pattern.lastIndexOf(DELIMITER);\n owner === null && end > this.spec.anchor.length;\n end = pattern.lastIndexOf(DELIMITER, end - 1)) {\n owner = this._matchExpansion(pattern.slice(0, end));\n }\n }\n // 定義集合は state の世代内で不変なので、判定は記憶してよい(アンカー外の否定も含む)。\n this._ownerByPath.set(pattern, owner);\n return owner;\n }\n\n /** 具体パスが宣言済み `**` getter の展開形そのものなら、その getter のパス。 */\n private _matchExpansion(concretePath: string): string | null {\n for (const definition of this._definitions.values()) {\n if (depthOfConcretePath(this.spec, definition.suffix, concretePath) !== null) {\n return definition.recursivePath;\n }\n }\n return null;\n }\n\n /**\n * `materializeFor` の `PathInfo` 版。**読みのホットパス(`getByAddress`)専用**で、\n * 判定そのものは `materializeFor` に委ね、結果(否定を含む)を PathInfo に記憶する。\n * 書き側の `recursiveGetterOwningPath` と対称。\n */\n materializeForPathInfo(stateElement: IStateElement, pathInfo: IPathInfo): IRecursionAccessor | null {\n // `**` getter の無い宣言(レジストリは空)は、記憶を作らずに抜ける\n if (this._definitions.size === 0) {\n return null;\n }\n const known = this._accessorByPathInfo.get(pathInfo);\n if (typeof known !== \"undefined\") {\n return known;\n }\n const accessor = this.materializeFor(stateElement, pathInfo.path);\n this._accessorByPathInfo.set(pathInfo, accessor);\n return accessor;\n }\n\n /**\n * 具体パスが再帰 getter の展開形なら、そのアクセサを(未登録なら生やして)返す。\n * 該当しなければ null。読みは `materializeForPathInfo` を通るので、ここへ来るのは\n * 記憶が外れたときだけ — 判定は接頭辞 1 回で抜け、ここでは否定を記憶しない(記憶は\n * `materializeForPathInfo` の PathInfo キーの 1 か所)。\n * (`**` getter の無い空レジストリを弾くのは呼び出し側の役目。)\n */\n materializeFor(stateElement: IStateElement, concretePath: string): IRecursionAccessor | null {\n const known = this._accessors.get(concretePath);\n if (typeof known !== \"undefined\") {\n return known;\n }\n if (!concretePath.startsWith(this.spec.anchor)) {\n return null;\n }\n let matched: IRecursiveGetterDefinition | null = null;\n let matchedDepth = 0;\n for (const definition of this._definitions.values()) {\n const depth = depthOfConcretePath(this.spec, definition.suffix, concretePath);\n if (depth === null) {\n continue;\n }\n if (matched !== null) {\n // コンストラクタの静的検査で弾いているはずの形。保険として先着を無言で採らない。\n raiseError(\n `\"${concretePath}\" matches both \"${matched.recursivePath}\" and \"${definition.recursivePath}\".`\n );\n }\n matched = definition;\n matchedDepth = depth;\n }\n if (matched === null) {\n return null;\n }\n return this._define(stateElement, matched, matchedDepth, concretePath);\n }\n\n private _define(\n stateElement: IStateElement,\n definition: IRecursiveGetterDefinition,\n depth: number,\n concretePath: string,\n ): IRecursionAccessor {\n // 作者が同じ具体パスを手で定義していないか。**プロトタイプチェーンまで**見る —\n // class 構文の getter は own ではなく prototype に載る(`getStateInfo` が\n // `getterPaths` に拾うのと同じ範囲)。own しか見ないと、生成アクセサが own に\n // 定義されて作者の getter を無言で影にする。前世代の生成物(own・WeakSet に載る\n // get)だけは上書きしてよい。\n const existing = stateElement.findStateDescriptor(concretePath);\n if (typeof existing !== \"undefined\" && !isGeneratedGetter(existing)) {\n raiseError(\n `\"${concretePath}\" is already defined on the state, so the recursive getter ` +\n `\"${definition.recursivePath}\" cannot expand to it. Rename one of them.`\n );\n }\n const accessor: IRecursionAccessor = Object.freeze({\n recursivePath: definition.recursivePath,\n depth,\n });\n const body = definition.get;\n const generated = function (this: unknown): unknown {\n return body.call(this);\n };\n markGeneratedGetter(generated);\n // 前世代の生成物が残っていても、登録(getterPaths / setPathInfo / listPaths)は\n // この世代でやり直す必要があるので、descriptor ごと定義し直す。\n stateElement.defineTreeAccessor(concretePath, {\n get: generated,\n enumerable: false,\n configurable: true,\n });\n this._registerListPaths(stateElement, depth);\n this._accessors.set(concretePath, accessor);\n return accessor;\n }\n\n /**\n * 経路上のリストパスを `listPaths` に載せる(E4)。`setPathInfo(path, \"for\")` は\n * 使えない — あちらは `elementPaths` にも入れて `setByAddress` の swap 経路\n * (`isSwappable`)を変えてしまう。ここで要るのは「依存ウォークがこのパスを\n * リストとして展開する」ことだけ。\n */\n private _registerListPaths(stateElement: IStateElement, depth: number): void {\n for (const listPath of listPathsUpTo(this.spec, depth)) {\n if (this._registeredListPaths.has(listPath)) {\n continue;\n }\n this._registeredListPaths.add(listPath);\n stateElement.addListPath(listPath);\n }\n }\n\n /**\n * この世代が生やしたもの(own の生成アクセサ・依存辺・キャッシュ)を忘れる(state の\n * 再セット時、`getStateInfo` の再収集より**前**に呼ぶ)。実体は generation.ts。\n */\n forgetGenerated(stateElement: IStateElement, previousState: object): ReadonlySet<string> {\n const generatedPaths: ReadonlySet<string> = new Set(this._accessors.keys());\n forgetGeneration(stateElement, previousState, generatedPaths);\n // 忘れた具体パスを返す。`_state` のセッタは経路情報を作り直すときにこれを除く —\n // この世代ではまだ実体化されていないので、辺だけ張り直してはならない。\n return generatedPaths;\n }\n\n /**\n * `**` 接尾辞の深さ `depth` の具体パス(`concretePathAt` の記憶付き版)。\n * 束縛形の読み(`this[\"nodes.**.value\"]` / 省略形 `$getAll`)は再帰 getter の評価ごとに\n * ここを通るので、深さぶんの文字列連結とワイルドカード数えを毎回やり直さない。\n * 上限は「接尾辞の種類 × 128」で有界(上限超過は `concretePathAt` が throw するので載らない)。\n */\n concretePathAt(suffix: string, depth: number): string {\n let byDepth = this._concreteBySuffix.get(suffix);\n if (typeof byDepth === \"undefined\") {\n byDepth = [];\n this._concreteBySuffix.set(suffix, byDepth);\n }\n let path = byDepth[depth];\n if (typeof path === \"undefined\") {\n path = concretePathAt(this.spec, suffix, depth);\n byDepth[depth] = path;\n }\n return path;\n }\n\n /** 展開済みアクセサのメタデータ(深さ解決・診断・テスト用)。 */\n accessorFor(concretePath: string): IRecursionAccessor | null {\n return this._accessors.get(concretePath) ?? null;\n }\n\n /**\n * これまでに実体化した具体パスの一覧。**テスト専用**(「読んだ深さだけが生える」という\n * 遅延実体化の不変条件を外から確かめる口。ランタイムはどの経路からも呼ばない)。\n */\n get materializedPaths(): ReadonlySet<string> {\n return new Set(this._accessors.keys());\n }\n}\n\n","/**\n * recursion/declarations.ts — `$recursion` の宣言(設計案 H4、S4)。\n *\n * 文脈袋の作り手でもある: 構築したレジストリを袋に置き、`$scan` の検証がそれを読む\n * (`**` getter の展開形を `from` に書いた形を落とすため — D5)。core は「ある宣言が別の宣言に\n * 値を渡す」ことを知らなくてよくなった。\n *\n * 段の位置は従来の `_state` セッターのまま:\n * validateEarly core 自身のトークン / `$listKeys` の解析より前(`value` しか読まない)\n * preCommit 旧世代の生成アクセサ・依存辺・キャッシュを忘れてから差し替える。世代を進める前\n * apply アンカーのリストパスの登録(`_listPaths` のクリアより後)\n */\nimport type { IState } from \"../types\";\nimport { IDeclarationHooks, registerDeclarationHooks } from \"../core/declarationHooks\";\nimport { STATE_RECURSION_NAME } from \"../define\";\nimport { installRecursionHooks } from \"./addressHooks\";\nimport { processRecursionDeclaration } from \"./declaration\";\nimport { RecursionRegistry } from \"./registry\";\nimport type { IRecursionSpec } from \"./types\";\n\nconst SPEC = \"recursionSpec\";\nconst REGISTRY = \"recursionRegistry\";\n\nexport const recursionDeclarationHooks: IDeclarationHooks = {\n // scan(8)より先に走り、袋にレジストリを置く\n order: 5,\n validateEarly(_element, value, ctx) {\n const spec = processRecursionDeclaration(value);\n ctx.set(SPEC, spec);\n ctx.set(REGISTRY, spec === null ? null : new RecursionRegistry(spec, value));\n },\n preCommit(element, _value, ctx) {\n // 旧世代の生成アクセサ(own)・それを指す依存辺・評価結果のキャッシュを忘れてから\n // 差し替える(recursion/generation.ts)。own の生成アクセサは、同じオブジェクトを再セットする\n // ときに `getStateInfo` が `getterPaths` へ拾い直す前に消えていなければならない。\n // 前世代の再帰レジストリが生やした具体パスは要素の除外集合に積む。経路情報の作り直し\n // (`_rebuildPathInfo`)はそこを除く — 新しい世代ではまだ実体化されていないため。\n const previous = element.recursionRegistry;\n if (previous !== null) {\n for (const path of previous.forgetGenerated(element, ctx.get<IState>(\"previousState\") as IState)) {\n element.addGeneratedPath?.(path);\n }\n }\n const registry = ctx.get<RecursionRegistry | null>(REGISTRY) ?? null;\n element.setRecursionRegistry?.(registry);\n if (registry !== null) {\n // `$recursion` を宣言した state にだけ再帰の hook(`**` の束縛・再帰 getter の書き込み禁止)を付ける\n element.attachAddressHooks?.(\"recursion\", STATE_RECURSION_NAME);\n }\n },\n apply(element, _value, ctx) {\n const spec = ctx.get<IRecursionSpec | null>(SPEC) ?? null;\n if (spec === null) {\n return;\n }\n // アンカーのリストパス(`nodes.*` なら `nodes`)は**宣言から静的に分かる**ので、\n // 展開を待たずに今すぐ登録する。\n //\n // これが無いと、再帰パスを一度読んだ後の再セットで構造書き込みが恒久的に落ちる。\n // 生成アクセサの `setPathInfo` が張った静的辺(`nodes` → `nodes.*`)は依存グラフに\n // 残るのに、`_listPaths` はセッターでクリアされ、次に再帰パスを読むまで張り直されない。\n // その隙間に構造書き込みが来ると `walkDependency` が「リストではないパス」として\n // `nodes.*` に到達し、listIndex を持たないアドレスで `Cannot expand dynamic dependency…`\n // になる(値は書かれるので、データと表示が乖離したまま自己回復しない)。\n element.addListPath(spec.anchorList);\n },\n};\n\nlet installed = false;\n/** 冪等。full / auto では `bootstrapState()` が呼ぶ */\nexport function installRecursionDeclarations(): void {\n if (installed) return;\n installed = true;\n installRecursionHooks();\n registerDeclarationHooks(\"recursion\", recursionDeclarationHooks);\n}\n","/**\n * features/recursion.ts — `$recursion` と `**`(@wcstack/state/features/recursion)。\n */\nimport type { IStateFeature } from \"../core/features\";\nimport { installRecursionDeclarations } from \"../recursion/declarations\";\n\nexport const recursion: IStateFeature = {\n name: \"recursion\",\n install(): void {\n installRecursionDeclarations();\n },\n};\nexport default recursion;\n"],"names":["hasRecursionWildcard","path","indexOf","RECURSION_WILDCARD","splitRecursivePath","spec","recursiveAnchor","prefix","DELIMITER","startsWith","suffix","slice","length","segments","split","WILDCARD","some","segment","foldSuffixIndexes","indexSegmentsToWildcard","sameFamily","a","b","unit","repeat","shorter","longer","endsWith","gap","cursor","coversSuffix","familySuffix","end","lastIndexOf","i","Number","isNaN","join","isStructuralSuffix","repeatSegments","rest","repeatList","concretePathAt","depth","raiseError","anchor","full","wildcardCount","MAX_WILDCARD_DEPTH","nodePathAt","depthOfConcretePath","middle","currentRecursionDepth","handler","registry","addressStackLength","address","lastAddressStack","accessor","accessorFor","pathInfo","charCodeAt","depthOfConcretePathPrefix","bindRecursivePath","stateElement","recursionRegistry","recursionAnchorMismatchMessage","collectRecursiveAddresses","target","receiver","results","observed","Map","pathsByDepth","anchorList","ancestors","Set","visited","expandSuffix","concretePathInfo","level","listIndex","parents","wildcardParentPathInfos","push","createStateAddress","rows","readRows","listPathInfo","parentListIndex","seen","listAddress","absAddress","liftAddress","value","getByAddress","tracked","listPath","Array","isArray","list","has","guardShape","listDiff","createListDiff","getStateListBaseline","set","add","newIndexes","descend","paths","known","concretePath","nodePath","getPathInfo","childListPathInfo","pathsAt","branch","flat","row","delete","setStateListBaseline","setAllRecursive","indexes","options","assertWritable","checkedSuffix","setAllValueKindMessage","spread","assertNotStructural","conflicting","conflictingRecursiveGetter","addresses","written","setByAddress","recursionAddressHooks","write","hasRecursion","NOT_HANDLED","owner","recursiveGetterOwningPath","get","prop","values","getAllRecursive","getAll","setAll","resolvedAddress","getResolvedAddress","getListIndex","installed","assertNodePath","kind","STATE_RECURSION_NAME","generatedGetters","WeakSet","isGeneratedGetter","descriptor","DECLARATION_INVALID","RecursionRegistry","_definitions","_accessors","_ownerByPath","_ownerByPathInfo","WeakMap","_accessorByPathInfo","_concreteBySuffix","_registeredListPaths","constructor","state","this","descriptors","getAllPropertyDescriptors","key","Object","entries","recursivePath","_assertNoColliding","_assertNoConcreteCollision","_matchExpansion","definitions","from","j","hasDefinitions","size","definition","recursiveGetterOwning","pattern","materializeForPathInfo","materializeFor","matched","matchedDepth","_define","existing","findStateDescriptor","freeze","body","generated","call","getter","defineTreeAccessor","enumerable","configurable","_registerListPaths","k","listPathsUpTo","addListPath","forgetGenerated","previousState","generatedPaths","keys","getOwnPropertyDescriptor","map","staticDependency","dynamicDependency","source","targets","kept","forgetGeneration","byDepth","materializedPaths","SPEC","REGISTRY","recursionDeclarationHooks","order","validateEarly","_element","ctx","declared","processRecursionDeclaration","preCommit","element","_value","previous","addGeneratedPath","setRecursionRegistry","attachAddressHooks","apply","installRecursionDeclarations","registerFeatureHooks","registerDeclarationHooks","recursion","name","install"],"mappings":"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"}