@wcstack/state 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.ja.md +47 -17
  2. package/README.md +47 -17
  3. package/dist/auto.min.js +1 -1
  4. package/dist/auto.min.js.map +1 -1
  5. package/dist/define.d.ts +11 -7
  6. package/dist/index.d.ts +15 -7
  7. package/dist/index.esm.js +1 -1
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/manifest.d.ts +14 -1
  10. package/dist/manifest.esm.js +91 -35
  11. package/dist/parser.esm.js +25 -0
  12. package/dist/split/chunks/binder.js +1 -1
  13. package/dist/split/chunks/binder.js.map +1 -1
  14. package/dist/split/chunks/checkDependency.js +1 -1
  15. package/dist/split/chunks/declarationAliases.js +2 -0
  16. package/dist/split/chunks/declarationAliases.js.map +1 -0
  17. package/dist/split/chunks/filterRegistry.js +1 -1
  18. package/dist/split/chunks/filterRegistry.js.map +1 -1
  19. package/dist/split/chunks/mount.js +1 -1
  20. package/dist/split/chunks/mount.js.map +1 -1
  21. package/dist/split/chunks/pathDiagnostics.js +1 -1
  22. package/dist/split/chunks/pathDiagnostics.js.map +1 -1
  23. package/dist/split/chunks/registerComponents.js +1 -1
  24. package/dist/split/chunks/registerComponents.js.map +1 -1
  25. package/dist/split/chunks/setAll.js +1 -1
  26. package/dist/split/chunks/version.js +1 -1
  27. package/dist/split/chunks/watchRuntime.js +1 -1
  28. package/dist/split/core.d.ts +11 -7
  29. package/dist/split/core.js +1 -1
  30. package/dist/split/core.js.map +1 -1
  31. package/dist/split/features/devtools.js +1 -1
  32. package/dist/split/features/devtools.js.map +1 -1
  33. package/dist/split/features/diagnostics.js +1 -1
  34. package/dist/split/features/formats.js +1 -1
  35. package/dist/split/features/formats.js.map +1 -1
  36. package/dist/split/features/recursion.js +1 -1
  37. package/dist/split/features/scopes.js +1 -1
  38. package/dist/split/features/scopes.js.map +1 -1
  39. package/dist/split/features/ssr.js +1 -1
  40. package/dist/split/features/ssr.js.map +1 -1
  41. package/dist/split/features/temporal.js +1 -1
  42. package/dist/split/features/temporal.js.map +1 -1
  43. package/dist/wcs-manifest.json +65 -25
  44. package/package.json +1 -1
@@ -1 +1 @@
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 { 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 const filters = [...info.inFilters, ...info.outFilters]\n .map((f) => `${f.filterName}(${f.args.join(\",\")})`)\n .join(\"|\");\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 // `$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 */\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 readonly inFilters: readonly { readonly filterName: string; readonly args: readonly string[] }[];\n readonly outFilters: readonly { readonly filterName: string; readonly args: readonly string[] }[];\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\";\nimport { getMountRecordsForStateElement } from \"../webComponent/mount\";\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 const element = requireStateElement(rootNode);\n return getMountRecordsForStateElement(element).map((record) => ({\n marker: record.marker,\n componentTag: record.component.tagName.toLowerCase(),\n stateProp: record.stateProp,\n mountTable: record.entries.map((entry) => ({\n inner: entry.innerSegments.join(\".\"),\n outer: entry.outerPathInfo.path,\n })),\n delta: record.delta,\n privateKeys: Object.keys(record.privateSnapshot),\n getterKeys: [...record.getterKeys],\n exports: [...record.exports.keys()],\n }));\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","DEVTOOLS_HOOK_GLOBAL","getOrCreateHookRegistry","globals","globalThis","existing","undefined","version","console","warn","registry","sources","Map","listeners","Set","applySink","source","size","_setSink","sourceId","id","event","listener","onEvent","register","has","set","onSourceRegistered","unregister","get","delete","onSourceUnregistered","addListener","add","values","createMinimalRegistry","onUpdateBatch","batch","devtoolsSink","type","addresses","setSink","sink","wasActive","setDevtoolsSink","isActive","registerUpdateBatchListener","DEVTOOLS_LISTENER_PRIORITY","unregisterUpdateBatchListener","createStateElementSummary","element","rootNode","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","push","overlays","getMountRecordsForStateElement","map","record","marker","componentTag","component","tagName","toLowerCase","stateProp","mountTable","entries","entry","inner","innerSegments","join","outer","outerPathInfo","path","delta","privateKeys","Object","privateSnapshot","getterKeys","exports","keyedSubscriptions","stateElement","view","getKeyedLedgerView","byPath","tracked","watchers","sort","keyMap","rows","lists","lastValue","collectKeyedSubscriptions","result","createState","state","key","includes","startsWith","read","indexes","write","value","length","getDeclaredBindings","out","seenKeys","visitedFragments","info","filters","f","filterName","args","declarationKey","visitFragment","uuid","fragmentInfo","getFragmentInfoByUUID","nodeInfo","nodeInfos","parseBindTextResults","walker","ownerDocument","createTreeWalker","NodeFilter","SHOW_ELEMENT","SHOW_COMMENT","nextNode","nodeType","Node","ELEMENT_NODE","bindText","getAttribute","config","bindAttributeName","results","expandSpread","parseBindTextsForElement","match","exec","data","trim","keyword","commentTextPrefix","parseBindTextForEmbeddedNode","parseBindTextResult","collectDeclaredBindings","devtools","name","install"],"mappings":"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"}
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 { 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 const filters = [...info.inFilters, ...info.outFilters]\n .map((f) => `${f.filterName}(${f.args.join(\",\")})`)\n .join(\"|\");\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 // `$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 */\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 readonly inFilters: readonly { readonly filterName: string; readonly args: readonly string[] }[];\n readonly outFilters: readonly { readonly filterName: string; readonly args: readonly string[] }[];\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\";\nimport { getMountRecordsForStateElement } from \"../webComponent/mount\";\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 const element = requireStateElement(rootNode);\n return getMountRecordsForStateElement(element).map((record) => ({\n marker: record.marker,\n componentTag: record.component.tagName.toLowerCase(),\n stateProp: record.stateProp,\n mountTable: record.entries.map((entry) => ({\n inner: entry.innerSegments.join(\".\"),\n outer: entry.outerPathInfo.path,\n })),\n delta: record.delta,\n privateKeys: Object.keys(record.privateSnapshot),\n getterKeys: [...record.getterKeys],\n exports: [...record.exports.keys()],\n }));\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","DEVTOOLS_HOOK_GLOBAL","getOrCreateHookRegistry","globals","globalThis","existing","undefined","version","console","warn","registry","sources","Map","listeners","Set","applySink","source","size","_setSink","sourceId","id","event","listener","onEvent","register","has","set","onSourceRegistered","unregister","get","delete","onSourceUnregistered","addListener","add","values","createMinimalRegistry","onUpdateBatch","batch","devtoolsSink","type","addresses","setSink","sink","wasActive","setDevtoolsSink","isActive","registerUpdateBatchListener","DEVTOOLS_LISTENER_PRIORITY","unregisterUpdateBatchListener","createStateElementSummary","element","rootNode","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","push","overlays","getMountRecordsForStateElement","map","record","marker","componentTag","component","tagName","toLowerCase","stateProp","mountTable","entries","entry","inner","innerSegments","join","outer","outerPathInfo","path","delta","privateKeys","Object","privateSnapshot","getterKeys","exports","keyedSubscriptions","stateElement","view","getKeyedLedgerView","byPath","tracked","watchers","sort","keyMap","rows","lists","lastValue","collectKeyedSubscriptions","result","createState","state","key","includes","startsWith","read","indexes","write","value","length","getDeclaredBindings","out","seenKeys","visitedFragments","info","filters","f","filterName","args","declarationKey","visitFragment","uuid","fragmentInfo","getFragmentInfoByUUID","nodeInfo","nodeInfos","parseBindTextResults","walker","ownerDocument","createTreeWalker","NodeFilter","SHOW_ELEMENT","SHOW_COMMENT","nextNode","nodeType","Node","ELEMENT_NODE","bindText","getAttribute","config","bindAttributeName","results","expandSpread","parseBindTextsForElement","match","exec","data","trim","keyword","commentTextPrefix","parseBindTextForEmbeddedNode","parseBindTextResult","collectDeclaredBindings","devtools","name","install"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{s as e}from"../chunks/diagnosticsHooks.js";import{g as t,B as n,W as s,a7 as i,a8 as r,a9 as o,o as a,D as c}from"../chunks/pathDiagnostics.js";import{d,L as u}from"../chunks/errorGuidance.js";const g=Object.freeze({existence:"unknown",missingSegment:"",candidates:Object.freeze([])}),f=Object.freeze({existence:"exists",missingSegment:"",candidates:Object.freeze([])});const l=new WeakMap;const m=new WeakMap,h=new WeakSet,p=new WeakMap;function k(e,t,n,s){console.warn(`[@wcstack/state] [${r[n]}] ${o[n]} "${t}" does not resolve on the state tree: "${s.missingSegment}" is not declared.${d(s.missingSegment,s.candidates)} Updates to this path will be silently dropped.${u}`),null!==a&&a({type:"state:path-unresolved",source:n,path:t,missingSegment:s.missingSegment})}const v={check:function(e,r,o,a){if("internal"===a||void 0===r)return;if(o.startsWith("$"))return;if(-1!==o.indexOf("#"))return;const d=e.addressHooks;if(d){const t=d.suppressPathDiagnostic;for(let n=0;n<t.length;n++)if(t[n](e,o))return}const u=t(o).segments;if("binding"===a&&u.length<2)return;if(function(e,t){let n=l.get(e);return void 0===n&&(n=new Set,l.set(e,n)),!!n.has(t)||(n.add(t),!1)}(e,o))return;if(!0===e.hasRecursion&&null!==e.recursionRegistry.recursiveGetterOwning(o))return;const v=function(e,r,o){if(void 0!==n(e,r))return f;const a=t(r).segments;let d=e,u="";for(let t=0;t<a.length;t++){const r=a[t],l=u;if(u=0===t?r:u+c+r,t>0&&t<a.length-1&&void 0!==n(e,u))return g;if(Object(d)!==d)return g;if(r===s){if(!Array.isArray(d)||0===d.length)return g;d=d[0];continue}const m=n(d,r);if(void 0===m)return{existence:"missing",missingSegment:r,candidates:i(d,l,o)};if("function"==typeof m.get)return t===a.length-1?f:g;d=m.value}return f}(r,o,e.getterPaths);"missing"===v.existence&&(function(e,t){return!0===p.get(e)?.has(t)}(e,o)||("binding"!==a?k(e,o,a,v):function(e,t,n){let s=m.get(e);void 0===s&&(s=new Map,m.set(e,s));if(s.set(t,n),h.has(e))return;h.add(e),setTimeout(()=>function(e){h.delete(e);const t=m.get(e);if(void 0===t)return;m.delete(e);for(const[n,s]of t)k(e,n,"binding",s)}(e),0)}(e,o,v)))},reset:function(e){l.delete(e),m.delete(e)},markExported:function(e,t){let n=p.get(e);void 0===n&&(n=new Set,p.set(e,n)),n.add(t),m.get(e)?.delete(t)}};let w=!1;const S={name:"diagnostics",install(){w||(w=!0,e(v))}};export{S as default,S as diagnostics};
1
+ import{s as e}from"../chunks/diagnosticsHooks.js";import{g as t,C as n,W as s,a8 as i,a9 as r,aa as o,p as a,D as c}from"../chunks/pathDiagnostics.js";import{d,L as u}from"../chunks/errorGuidance.js";const g=Object.freeze({existence:"unknown",missingSegment:"",candidates:Object.freeze([])}),f=Object.freeze({existence:"exists",missingSegment:"",candidates:Object.freeze([])});const l=new WeakMap;const m=new WeakMap,h=new WeakSet,p=new WeakMap;function k(e,t,n,s){console.warn(`[@wcstack/state] [${r[n]}] ${o[n]} "${t}" does not resolve on the state tree: "${s.missingSegment}" is not declared.${d(s.missingSegment,s.candidates)} Updates to this path will be silently dropped.${u}`),null!==a&&a({type:"state:path-unresolved",source:n,path:t,missingSegment:s.missingSegment})}const v={check:function(e,r,o,a){if("internal"===a||void 0===r)return;if(o.startsWith("$"))return;if(-1!==o.indexOf("#"))return;const d=e.addressHooks;if(d){const t=d.suppressPathDiagnostic;for(let n=0;n<t.length;n++)if(t[n](e,o))return}const u=t(o).segments;if("binding"===a&&u.length<2)return;if(function(e,t){let n=l.get(e);return void 0===n&&(n=new Set,l.set(e,n)),!!n.has(t)||(n.add(t),!1)}(e,o))return;if(!0===e.hasRecursion&&null!==e.recursionRegistry.recursiveGetterOwning(o))return;const v=function(e,r,o){if(void 0!==n(e,r))return f;const a=t(r).segments;let d=e,u="";for(let t=0;t<a.length;t++){const r=a[t],l=u;if(u=0===t?r:u+c+r,t>0&&t<a.length-1&&void 0!==n(e,u))return g;if(Object(d)!==d)return g;if(r===s){if(!Array.isArray(d)||0===d.length)return g;d=d[0];continue}const m=n(d,r);if(void 0===m)return{existence:"missing",missingSegment:r,candidates:i(d,l,o)};if("function"==typeof m.get)return t===a.length-1?f:g;d=m.value}return f}(r,o,e.getterPaths);"missing"===v.existence&&(function(e,t){return!0===p.get(e)?.has(t)}(e,o)||("binding"!==a?k(e,o,a,v):function(e,t,n){let s=m.get(e);void 0===s&&(s=new Map,m.set(e,s));if(s.set(t,n),h.has(e))return;h.add(e),setTimeout(()=>function(e){h.delete(e);const t=m.get(e);if(void 0===t)return;m.delete(e);for(const[n,s]of t)k(e,n,"binding",s)}(e),0)}(e,o,v)))},reset:function(e){l.delete(e),m.delete(e)},markExported:function(e,t){let n=p.get(e);void 0===n&&(n=new Set,p.set(e,n)),n.add(t),m.get(e)?.delete(t)}};let w=!1;const S={name:"diagnostics",install(){w||(w=!0,e(v))}};export{S as default,S as diagnostics};
2
2
  //# sourceMappingURL=diagnostics.js.map
@@ -1,2 +1,2 @@
1
- import{c as t,b as e}from"../chunks/filterRegistry.js";import{r}from"../chunks/errorGuidance.js";function n(t){r(`filter ${t} requires at least one option`)}function o(t){r(`filter ${t} requires a number as option`)}function u(t){r(`filter ${t} requires a number value`)}function s(t){r(`filter ${t} requires a date value`)}function a(t){return!(!t||isNaN(Number(t)))}const c={eq:(t,e)=>{const r=t?.[0]??n("eq"),u=void 0!==e&&e.length>0?e[0]:r;return t=>"number"==typeof t?(a(r)||o("eq"),t===Number(r)):"string"==typeof t?t===r:t===u},ne:(t,e)=>{const r=t?.[0]??n("ne"),u=void 0!==e&&e.length>0?e[0]:r;return t=>"number"==typeof t?(a(r)||o("ne"),t!==Number(r)):"string"==typeof t?t!==r:t!==u},not:t=>t=>("boolean"!=typeof t&&r(`filter ${"not"} requires a boolean value`),!t),lt:t=>{const e=t?.[0]??n("lt");return a(e)||o("lt"),t=>("number"!=typeof t&&u("lt"),t<Number(e))},le:t=>{const e=t?.[0]??n("le");return a(e)||o("le"),t=>("number"!=typeof t&&u("le"),t<=Number(e))},gt:t=>{const e=t?.[0]??n("gt");return a(e)||o("gt"),t=>("number"!=typeof t&&u("gt"),t>Number(e))},ge:t=>{const e=t?.[0]??n("ge");return a(e)||o("ge"),t=>("number"!=typeof t&&u("ge"),t>=Number(e))},inc:t=>{const e=t?.[0]??n("inc");return a(e)||o("inc"),t=>("number"!=typeof t&&u("inc"),t+Number(e))},dec:t=>{const e=t?.[0]??n("dec");return a(e)||o("dec"),t=>("number"!=typeof t&&u("dec"),t-Number(e))},mul:t=>{const e=t?.[0]??n("mul");return a(e)||o("mul"),t=>("number"!=typeof t&&u("mul"),t*Number(e))},div:t=>{const e=t?.[0]??n("div");return a(e)||o("div"),t=>("number"!=typeof t&&u("div"),t/Number(e))},mod:t=>{const e=t?.[0]??n("mod");return a(e)||o("mod"),t=>("number"!=typeof t&&u("mod"),t%Number(e))},abs:t=>t=>("number"!=typeof t&&u("abs"),Math.abs(t)),clamp:t=>{const e=t?.[0]??n("clamp");a(e)||o("clamp");const r=t?.[1]??n("clamp");a(r)||o("clamp");const s=Number(e),c=Number(r);return t=>("number"!=typeof t&&u("clamp"),Math.min(Math.max(t,s),c))},fix:t=>{const e=t?.[0]??"0";return a(e)||o("fix"),t=>("number"!=typeof t&&u("fix"),t.toFixed(Number(e)))},locale:e=>{const r=e?.[0];return e=>("number"!=typeof e&&u("locale"),e.toLocaleString(r??t.locale))},uc:t=>t=>String(t).toUpperCase(),lc:t=>t=>String(t).toLowerCase(),cap:t=>t=>{const e=String(t);return 0===e.length?e:1===e.length?e.toUpperCase():e.charAt(0).toUpperCase()+e.slice(1)},trim:t=>t=>String(t).trim(),slice:t=>{const e=[],r=t?.[0]??n("slice");a(r)||o("slice"),e.push(Number(r));const u=t?.[1];return void 0!==u&&(a(u)||o("slice"),e.push(Number(u))),t=>String(t).slice(...e)},substr:t=>{const e=t?.[0]??n("substr");a(e)||o("substr");const r=t?.[1]??n("substr");return a(r)||o("substr"),t=>String(t).substr(Number(e),Number(r))},pad:t=>{const e=t?.[0]??n("pad");a(e)||o("pad");const r=t?.[1]??"0";return t=>String(t).padStart(Number(e),r)},rep:t=>{const e=t?.[0]??n("rep");return a(e)||o("rep"),t=>String(t).repeat(Number(e))},rev:t=>t=>String(t).split("").reverse().join(""),truncate:t=>{const e=t?.[0]??n("truncate");a(e)||o("truncate");const r=Number(e),u=t?.[1]??"…";return t=>{const e=String(t);return e.length<=r?e:e.slice(0,r)+u}},join:t=>{const e=t?.[0]??", ";return t=>(Array.isArray(t)||r(`filter ${"join"} requires an array value`),t.join(e))},int:t=>t=>parseInt(String(t),10),float:t=>t=>parseFloat(String(t)),round:t=>{const e=t?.[0]??"0";return a(e)||o("round"),t=>{"number"!=typeof t&&u("round");const r=Math.pow(10,Number(e));return Math.round(t*r)/r}},floor:t=>{const e=t?.[0]??"0";return a(e)||o("floor"),t=>{"number"!=typeof t&&u("floor");const r=Math.pow(10,Number(e));return Math.floor(t*r)/r}},ceil:t=>{const e=t?.[0]??"0";return a(e)||o("ceil"),t=>{"number"!=typeof t&&u("ceil");const r=Math.pow(10,Number(e));return Math.ceil(t*r)/r}},percent:t=>{const e=t?.[0]??"0";return a(e)||o("percent"),t=>("number"!=typeof t&&u("percent"),`${(100*t).toFixed(Number(e))}%`)},unit:t=>{const e=t?.[0]??n("unit");return t=>null==t?t:String(t)+e},date:e=>{const r=e?.[0];return e=>(e instanceof Date||s("date"),e.toLocaleDateString(r??t.locale))},time:e=>{const r=e?.[0];return e=>(e instanceof Date||s("time"),e.toLocaleTimeString(r??t.locale))},datetime:e=>{const r=e?.[0];return e=>(e instanceof Date||s("datetime"),e.toLocaleString(r??t.locale))},ymd:t=>{const e=t?.[0]??"-";return t=>{t instanceof Date||s("ymd");const r=t.getFullYear().toString(),n=(t.getMonth()+1).toString().padStart(2,"0"),o=t.getDate().toString().padStart(2,"0");return`${r}${e}${n}${e}${o}`}},hms:t=>{const e=t?.[0]??":";return t=>{t instanceof Date||s("hms");const r=t.getHours().toString().padStart(2,"0"),n=t.getMinutes().toString().padStart(2,"0"),o=t.getSeconds().toString().padStart(2,"0");return`${r}${e}${n}${e}${o}`}},falsy:t=>t=>!t,truthy:t=>t=>!!t,defaults:(t,e)=>{const r=t?.[0]??n("defaults"),o=void 0!==e&&e.length>0?e[0]:r;return t=>t||o},boolean:t=>t=>Boolean(t),number:t=>t=>Number(t),string:t=>t=>String(t),null:t=>t=>""===t?null:t},i={eq:[1,1],not:[0,0],ne:[1,1],lt:[1,1],le:[1,1],gt:[1,1],ge:[1,1],inc:[0,1],dec:[0,1],mul:[1,1],div:[1,1],mod:[1,1],abs:[0,0],clamp:[2,2],fix:[0,1],locale:[0,1],uc:[0,0],lc:[0,0],cap:[0,0],trim:[0,0],slice:[1,2],substr:[1,2],pad:[1,2],rep:[1,1],rev:[0,0],truncate:[1,2],join:[0,1],int:[0,0],float:[0,0],round:[0,1],floor:[0,1],ceil:[0,1],percent:[0,1],unit:[1,1],date:[0,0],time:[0,0],datetime:[0,0],ymd:[0,1],hms:[0,1],falsy:[0,0],truthy:[0,0],defaults:[1,1],boolean:[0,0],number:[0,0],string:[0,0],null:[0,0]},l=c,m=c;let p=!1;const b={name:"formats",install(){p||(p=!0,e("input",m,i),e("output",l,i))}};export{b as default,b as formats};
1
+ import{c as t,b as e}from"../chunks/filterRegistry.js";import{r}from"../chunks/errorGuidance.js";function n(t){r(`filter ${t} requires at least one option`)}function o(t){r(`filter ${t} requires a number as option`)}function u(t){r(`filter ${t} requires a number value`)}function a(t){r(`filter ${t} requires a date value`)}const s={inc:"add",dec:"sub",fix:"toFixed",uc:"upper",lc:"lower",cap:"capitalize",rep:"repeat",rev:"reverse",pad:"padStart",null:"nullIfEmpty"};function i(t){return!(!t||isNaN(Number(t)))}const c={eq:(t,e)=>{const r=t?.[0]??n("eq"),u=void 0!==e&&e.length>0?e[0]:r;return t=>"number"==typeof t?(i(r)||o("eq"),t===Number(r)):"string"==typeof t?t===r:t===u},ne:(t,e)=>{const r=t?.[0]??n("ne"),u=void 0!==e&&e.length>0?e[0]:r;return t=>"number"==typeof t?(i(r)||o("ne"),t!==Number(r)):"string"==typeof t?t!==r:t!==u},not:t=>t=>("boolean"!=typeof t&&r(`filter ${"not"} requires a boolean value`),!t),lt:t=>{const e=t?.[0]??n("lt");return i(e)||o("lt"),t=>("number"!=typeof t&&u("lt"),t<Number(e))},le:t=>{const e=t?.[0]??n("le");return i(e)||o("le"),t=>("number"!=typeof t&&u("le"),t<=Number(e))},gt:t=>{const e=t?.[0]??n("gt");return i(e)||o("gt"),t=>("number"!=typeof t&&u("gt"),t>Number(e))},ge:t=>{const e=t?.[0]??n("ge");return i(e)||o("ge"),t=>("number"!=typeof t&&u("ge"),t>=Number(e))},add:t=>{const e=t?.[0]??n("add");return i(e)||o("add"),t=>("number"!=typeof t&&u("add"),t+Number(e))},sub:t=>{const e=t?.[0]??n("sub");return i(e)||o("sub"),t=>("number"!=typeof t&&u("sub"),t-Number(e))},mul:t=>{const e=t?.[0]??n("mul");return i(e)||o("mul"),t=>("number"!=typeof t&&u("mul"),t*Number(e))},div:t=>{const e=t?.[0]??n("div");return i(e)||o("div"),t=>("number"!=typeof t&&u("div"),t/Number(e))},mod:t=>{const e=t?.[0]??n("mod");return i(e)||o("mod"),t=>("number"!=typeof t&&u("mod"),t%Number(e))},abs:t=>t=>("number"!=typeof t&&u("abs"),Math.abs(t)),clamp:t=>{const e=t?.[0]??n("clamp");i(e)||o("clamp");const r=t?.[1]??n("clamp");i(r)||o("clamp");const a=Number(e),s=Number(r);return t=>("number"!=typeof t&&u("clamp"),Math.min(Math.max(t,a),s))},toFixed:t=>{const e=t?.[0]??"0";return i(e)||o("toFixed"),t=>("number"!=typeof t&&u("toFixed"),t.toFixed(Number(e)))},locale:e=>{const r=e?.[0];return e=>("number"!=typeof e&&u("locale"),e.toLocaleString(r??t.locale))},upper:t=>t=>String(t).toUpperCase(),lower:t=>t=>String(t).toLowerCase(),capitalize:t=>t=>{const e=String(t);return 0===e.length?e:1===e.length?e.toUpperCase():e.charAt(0).toUpperCase()+e.slice(1)},trim:t=>t=>String(t).trim(),slice:t=>{const e=[],r=t?.[0]??n("slice");i(r)||o("slice"),e.push(Number(r));const u=t?.[1];return void 0!==u&&(i(u)||o("slice"),e.push(Number(u))),t=>String(t).slice(...e)},substr:t=>{const e=t?.[0]??n("substr");i(e)||o("substr");const r=t?.[1]??n("substr");return i(r)||o("substr"),t=>String(t).substr(Number(e),Number(r))},padStart:t=>{const e=t?.[0]??n("padStart");i(e)||o("padStart");const r=t?.[1]??"0";return t=>String(t).padStart(Number(e),r)},padEnd:t=>{const e=t?.[0]??n("padEnd");i(e)||o("padEnd");const r=t?.[1]??" ";return t=>String(t).padEnd(Number(e),r)},repeat:t=>{const e=t?.[0]??n("repeat");return i(e)||o("repeat"),t=>String(t).repeat(Number(e))},reverse:t=>t=>String(t).split("").reverse().join(""),truncate:t=>{const e=t?.[0]??n("truncate");i(e)||o("truncate");const r=Number(e),u=t?.[1]??"…";return t=>{const e=String(t);return e.length<=r?e:e.slice(0,r)+u}},join:t=>{const e=t?.[0]??", ";return t=>(Array.isArray(t)||r(`filter ${"join"} requires an array value`),t.join(e))},int:t=>t=>parseInt(String(t),10),float:t=>t=>parseFloat(String(t)),round:t=>{const e=t?.[0]??"0";return i(e)||o("round"),t=>{"number"!=typeof t&&u("round");const r=Math.pow(10,Number(e));return Math.round(t*r)/r}},floor:t=>{const e=t?.[0]??"0";return i(e)||o("floor"),t=>{"number"!=typeof t&&u("floor");const r=Math.pow(10,Number(e));return Math.floor(t*r)/r}},ceil:t=>{const e=t?.[0]??"0";return i(e)||o("ceil"),t=>{"number"!=typeof t&&u("ceil");const r=Math.pow(10,Number(e));return Math.ceil(t*r)/r}},percent:t=>{const e=t?.[0]??"0";return i(e)||o("percent"),t=>("number"!=typeof t&&u("percent"),`${(100*t).toFixed(Number(e))}%`)},unit:t=>{const e=t?.[0]??n("unit");return t=>null==t?t:String(t)+e},date:e=>{const r=e?.[0];return e=>(e instanceof Date||a("date"),e.toLocaleDateString(r??t.locale))},time:e=>{const r=e?.[0];return e=>(e instanceof Date||a("time"),e.toLocaleTimeString(r??t.locale))},datetime:e=>{const r=e?.[0];return e=>(e instanceof Date||a("datetime"),e.toLocaleString(r??t.locale))},ymd:t=>{const e=t?.[0]??"-";return t=>{t instanceof Date||a("ymd");const r=t.getFullYear().toString(),n=(t.getMonth()+1).toString().padStart(2,"0"),o=t.getDate().toString().padStart(2,"0");return`${r}${e}${n}${e}${o}`}},hms:t=>{const e=t?.[0]??":";return t=>{t instanceof Date||a("hms");const r=t.getHours().toString().padStart(2,"0"),n=t.getMinutes().toString().padStart(2,"0"),o=t.getSeconds().toString().padStart(2,"0");return`${r}${e}${n}${e}${o}`}},falsy:t=>t=>!t,truthy:t=>t=>!!t,defaults:(t,e)=>{const r=t?.[0]??n("defaults"),o=void 0!==e&&e.length>0?e[0]:r;return t=>t||o},coalesce:(t,e)=>{const r=t?.[0]??n("coalesce"),o=void 0!==e&&e.length>0?e[0]:r;return t=>t??o},boolean:t=>t=>Boolean(t),number:t=>t=>Number(t),string:t=>t=>String(t),nullIfEmpty:t=>t=>""===t?null:t},l={eq:[1,1],not:[0,0],ne:[1,1],lt:[1,1],le:[1,1],gt:[1,1],ge:[1,1],add:[1,1],sub:[1,1],mul:[1,1],div:[1,1],mod:[1,1],abs:[0,0],clamp:[2,2],toFixed:[0,1],locale:[0,1],upper:[0,0],lower:[0,0],capitalize:[0,0],trim:[0,0],slice:[1,2],substr:[1,2],padStart:[1,2],padEnd:[1,2],repeat:[1,1],reverse:[0,0],truncate:[1,2],join:[0,1],int:[0,0],float:[0,0],round:[0,1],floor:[0,1],ceil:[0,1],percent:[0,1],unit:[1,1],date:[0,0],time:[0,0],datetime:[0,0],ymd:[0,1],hms:[0,1],falsy:[0,0],truthy:[0,0],defaults:[1,1],coalesce:[1,1],boolean:[0,0],number:[0,0],string:[0,0],nullIfEmpty:[0,0]},m=c,p=c;let d=!1;const b={name:"formats",install(){d||(d=!0,e("input",p,l,s),e("output",m,l,s))}};export{b as default,b as formats};
2
2
  //# sourceMappingURL=formats.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"formats.js","sources":["../../../../../src/formats/errorMessages.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 * - valueMustBeBoolean: Error when value is not boolean\n * - valueMustBeDate: Error when value is not a Date\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 numeric value but non-number provided.\n * \n * @param fnName - Name of the filter function\n * @returns Never returns (always throws)\n */\nexport function valueMustBeString(fnName:string): never {\n raiseError(`filter ${fnName} requires a string value`);\n}\n\n/**\n * Throws error when filter requires boolean value but non-boolean provided.\n * \n * @param fnName - Name of the filter function\n * @returns Never returns (always throws)\n */\nexport function valueMustBeBoolean(fnName:string): never {\n raiseError(`filter ${fnName} requires a boolean 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 * 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, valueMustBeBoolean, valueMustBeDate, valueMustBeNumber } from \"./errorMessages.js\";\nimport { FilterFn, FilterWithOptions } from \"../filters/types\";\n\nfunction validateNumberString(value: string): boolean {\n if (!value || isNaN(Number(value))) {\n return false;\n }\n return true;\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 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 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 boolean value.\n * \n * @param options - Unused\n * @returns Filter function that returns inverted boolean\n */\nconst not = (_options?:string[]): FilterFn<boolean> => {\n return (value: unknown): boolean => {\n if (typeof value !== 'boolean') {valueMustBeBoolean('not');}\n return !value;\n }\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 = options?.[0] ?? optionsRequired('lt');\n if (!validateNumberString(opt)) {optionMustBeNumber('lt');}\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('lt');}\n return value < Number(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 = options?.[0] ?? optionsRequired('le');\n if (!validateNumberString(opt)) {optionMustBeNumber('le');}\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('le');}\n return value <= Number(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 = options?.[0] ?? optionsRequired('gt');\n if (!validateNumberString(opt)) {optionMustBeNumber('gt');}\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('gt');}\n return value > Number(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 = options?.[0] ?? optionsRequired('ge');\n if (!validateNumberString(opt)) {optionMustBeNumber('ge');}\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('ge');}\n return value >= Number(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 = options?.[0] ?? optionsRequired('inc');\n if (!validateNumberString(opt)) {optionMustBeNumber('inc');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('inc');}\n return value + Number(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 = options?.[0] ?? optionsRequired('dec');\n if (!validateNumberString(opt)) {optionMustBeNumber('dec');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('dec');}\n return value - Number(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 = options?.[0] ?? optionsRequired('mul');\n if (!validateNumberString(opt)) {optionMustBeNumber('mul');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('mul');}\n return value * Number(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 = options?.[0] ?? optionsRequired('div');\n if (!validateNumberString(opt)) {optionMustBeNumber('div');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('div');}\n return value / Number(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 = options?.[0] ?? optionsRequired('mod');\n if (!validateNumberString(opt)) {optionMustBeNumber('mod');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('mod');}\n return value % Number(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 opt1 = options?.[0] ?? optionsRequired('clamp');\n if (!validateNumberString(opt1)) {optionMustBeNumber('clamp');}\n const opt2 = options?.[1] ?? optionsRequired('clamp');\n if (!validateNumberString(opt2)) {optionMustBeNumber('clamp');}\n const min = Number(opt1);\n const max = Number(opt2);\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 = options?.[0] ?? \"0\";\n if (!validateNumberString(opt)) {optionMustBeNumber('fix');}\n return (value: unknown): string => {\n if (typeof value !== 'number') {valueMustBeNumber('fix');}\n return value.toFixed(Number(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[] = [];\n const opt1 = options?.[0] ?? optionsRequired('slice');\n if (!validateNumberString(opt1)) {optionMustBeNumber('slice');}\n numberedOpts.push(Number(opt1));\n const opt2 = options?.[1];\n if (typeof opt2 !== 'undefined') {\n if (!validateNumberString(opt2)) {optionMustBeNumber('slice');}\n numberedOpts.push(Number(opt2));\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 const opt1 = options?.[0] ?? optionsRequired('substr');\n if (!validateNumberString(opt1)) {optionMustBeNumber('substr');}\n const opt2 = options?.[1] ?? optionsRequired('substr');\n if (!validateNumberString(opt2)) {optionMustBeNumber('substr');}\n return (value: unknown): string => {\n return String(value).substr(Number(opt1), Number(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 = options?.[0] ?? optionsRequired('pad');\n if (!validateNumberString(opt1)) {optionMustBeNumber('pad');}\n const opt2 = options?.[1] ?? '0';\n return (value: unknown): string => {\n return String(value).padStart(Number(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 = options?.[0] ?? optionsRequired('rep');\n if (!validateNumberString(opt)) {optionMustBeNumber('rep');}\n return (value: unknown): string => {\n return String(value).repeat(Number(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 opt = options?.[0] ?? '0';\n if (!validateNumberString(opt)) {optionMustBeNumber('round');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('round');}\n const optValue = Math.pow(10, Number(opt));\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 opt = options?.[0] ?? '0';\n if (!validateNumberString(opt)) {optionMustBeNumber('floor');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('floor');}\n const optValue = Math.pow(10, Number(opt));\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 opt = options?.[0] ?? '0';\n if (!validateNumberString(opt)) {optionMustBeNumber('ceil');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('ceil');}\n const optValue = Math.pow(10, Number(opt));\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 = options?.[0] ?? '0';\n if (!validateNumberString(opt)) {optionMustBeNumber('percent');}\n return (value: unknown): string => {\n if (typeof value !== 'number') {valueMustBeNumber('percent');}\n return `${(value * 100).toFixed(Number(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\"`,\n * so the binding layer's \"undefined skips the write, null clears\" semantics survive.\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<unknown> => {\n const opt = options?.[0] ?? optionsRequired('unit');\n return (value: unknown): unknown => {\n if (value === null || typeof value === 'undefined') {return value;}\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 opt1 = options?.[0] ?? optionsRequired('truncate');\n if (!validateNumberString(opt1)) {optionMustBeNumber('truncate');}\n const maxLength = Number(opt1);\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 * 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 \"inc\": inc,\n \"dec\": dec,\n \"mul\": mul,\n \"div\": div,\n \"mod\": mod,\n \"abs\": abs,\n \"clamp\": clamp,\n\n \"fix\": fix,\n \"locale\": locale,\n \"uc\": uc,\n \"lc\": lc,\n \"cap\": cap,\n \"trim\": trim,\n \"slice\": slice,\n \"substr\": substr,\n \"pad\": pad,\n \"rep\": rep,\n \"rev\": rev,\n \"truncate\": 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\": 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\n \"boolean\": boolean,\n \"number\": number,\n \"string\": string,\n \"null\": _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 inc: [0, 1],\n dec: [0, 1],\n mul: [1, 1],\n div: [1, 1],\n mod: [1, 1],\n abs: [0, 0],\n clamp: [2, 2],\n fix: [0, 1],\n locale: [0, 1],\n uc: [0, 0],\n lc: [0, 0],\n cap: [0, 0],\n trim: [0, 0],\n slice: [1, 2],\n substr: [1, 2],\n pad: [1, 2],\n rep: [1, 1],\n rev: [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 date: [0, 0],\n time: [0, 0],\n datetime: [0, 0],\n ymd: [0, 1],\n hms: [0, 1],\n falsy: [0, 0],\n truthy: [0, 0],\n defaults: [1, 1],\n boolean: [0, 0],\n number: [0, 0],\n string: [0, 0],\n null: [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 * @param name - Filter 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 const filter = filters[name];\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\";\n\nlet installed = false;\n/** 冪等。full / auto では `bootstrapState()` が呼ぶ */\nexport function installFormats(): void {\n if (installed) return;\n installed = true;\n registerFilters(\"input\", inputBuiltinFilters, builtinFilterArity);\n registerFilters(\"output\", outputBuiltinFilters, builtinFilterArity);\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","validateNumberString","value","isNaN","Number","builtinFilters","eq","options","literals","opt","literal","undefined","length","ne","not","_options","lt","le","gt","ge","inc","dec","mul","div","mod","abs","Math","clamp","opt1","opt2","min","max","fix","toFixed","locale","explicit","toLocaleString","config","uc","String","toUpperCase","lc","toLowerCase","cap","v","charAt","slice","trim","numberedOpts","push","substr","pad","padStart","rep","repeat","rev","split","reverse","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","boolean","Boolean","number","string","null","builtinFilterArity","outputBuiltinFilters","inputBuiltinFilters","installed","formats","name","install","registerFilters"],"mappings":"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"}
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 * - valueMustBeBoolean: Error when value is not boolean\n * - valueMustBeDate: Error when value is not a Date\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 numeric value but non-number provided.\n * \n * @param fnName - Name of the filter function\n * @returns Never returns (always throws)\n */\nexport function valueMustBeString(fnName:string): never {\n raiseError(`filter ${fnName} requires a string value`);\n}\n\n/**\n * Throws error when filter requires boolean value but non-boolean provided.\n * \n * @param fnName - Name of the filter function\n * @returns Never returns (always throws)\n */\nexport function valueMustBeBoolean(fnName:string): never {\n raiseError(`filter ${fnName} requires a boolean 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, valueMustBeBoolean, valueMustBeDate, valueMustBeNumber } from \"./errorMessages.js\";\nimport { FilterFn, FilterWithOptions } from \"../filters/types\";\nimport { builtinFilterAliases } from \"../filters/filterAliases\";\n\nfunction validateNumberString(value: string): boolean {\n if (!value || isNaN(Number(value))) {\n return false;\n }\n return true;\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 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 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 boolean value.\n * \n * @param options - Unused\n * @returns Filter function that returns inverted boolean\n */\nconst not = (_options?:string[]): FilterFn<boolean> => {\n return (value: unknown): boolean => {\n if (typeof value !== 'boolean') {valueMustBeBoolean('not');}\n return !value;\n }\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 = options?.[0] ?? optionsRequired('lt');\n if (!validateNumberString(opt)) {optionMustBeNumber('lt');}\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('lt');}\n return value < Number(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 = options?.[0] ?? optionsRequired('le');\n if (!validateNumberString(opt)) {optionMustBeNumber('le');}\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('le');}\n return value <= Number(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 = options?.[0] ?? optionsRequired('gt');\n if (!validateNumberString(opt)) {optionMustBeNumber('gt');}\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('gt');}\n return value > Number(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 = options?.[0] ?? optionsRequired('ge');\n if (!validateNumberString(opt)) {optionMustBeNumber('ge');}\n return (value: unknown): boolean => {\n if (typeof value !== 'number') {valueMustBeNumber('ge');}\n return value >= Number(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 = options?.[0] ?? optionsRequired('add');\n if (!validateNumberString(opt)) {optionMustBeNumber('add');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('add');}\n return value + Number(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 = options?.[0] ?? optionsRequired('sub');\n if (!validateNumberString(opt)) {optionMustBeNumber('sub');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('sub');}\n return value - Number(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 = options?.[0] ?? optionsRequired('mul');\n if (!validateNumberString(opt)) {optionMustBeNumber('mul');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('mul');}\n return value * Number(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 = options?.[0] ?? optionsRequired('div');\n if (!validateNumberString(opt)) {optionMustBeNumber('div');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('div');}\n return value / Number(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 = options?.[0] ?? optionsRequired('mod');\n if (!validateNumberString(opt)) {optionMustBeNumber('mod');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('mod');}\n return value % Number(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 opt1 = options?.[0] ?? optionsRequired('clamp');\n if (!validateNumberString(opt1)) {optionMustBeNumber('clamp');}\n const opt2 = options?.[1] ?? optionsRequired('clamp');\n if (!validateNumberString(opt2)) {optionMustBeNumber('clamp');}\n const min = Number(opt1);\n const max = Number(opt2);\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 = options?.[0] ?? \"0\";\n if (!validateNumberString(opt)) {optionMustBeNumber('toFixed');}\n return (value: unknown): string => {\n if (typeof value !== 'number') {valueMustBeNumber('toFixed');}\n return value.toFixed(Number(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[] = [];\n const opt1 = options?.[0] ?? optionsRequired('slice');\n if (!validateNumberString(opt1)) {optionMustBeNumber('slice');}\n numberedOpts.push(Number(opt1));\n const opt2 = options?.[1];\n if (typeof opt2 !== 'undefined') {\n if (!validateNumberString(opt2)) {optionMustBeNumber('slice');}\n numberedOpts.push(Number(opt2));\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 const opt1 = options?.[0] ?? optionsRequired('substr');\n if (!validateNumberString(opt1)) {optionMustBeNumber('substr');}\n const opt2 = options?.[1] ?? optionsRequired('substr');\n if (!validateNumberString(opt2)) {optionMustBeNumber('substr');}\n return (value: unknown): string => {\n return String(value).substr(Number(opt1), Number(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 = options?.[0] ?? optionsRequired('padStart');\n if (!validateNumberString(opt1)) {optionMustBeNumber('padStart');}\n const opt2 = options?.[1] ?? '0';\n return (value: unknown): string => {\n return String(value).padStart(Number(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 = options?.[0] ?? optionsRequired('padEnd');\n if (!validateNumberString(opt1)) {optionMustBeNumber('padEnd');}\n const opt2 = options?.[1] ?? ' ';\n return (value: unknown): string => {\n return String(value).padEnd(Number(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 = options?.[0] ?? optionsRequired('repeat');\n if (!validateNumberString(opt)) {optionMustBeNumber('repeat');}\n return (value: unknown): string => {\n return String(value).repeat(Number(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 opt = options?.[0] ?? '0';\n if (!validateNumberString(opt)) {optionMustBeNumber('round');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('round');}\n const optValue = Math.pow(10, Number(opt));\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 opt = options?.[0] ?? '0';\n if (!validateNumberString(opt)) {optionMustBeNumber('floor');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('floor');}\n const optValue = Math.pow(10, Number(opt));\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 opt = options?.[0] ?? '0';\n if (!validateNumberString(opt)) {optionMustBeNumber('ceil');}\n return (value: unknown): number => {\n if (typeof value !== 'number') {valueMustBeNumber('ceil');}\n const optValue = Math.pow(10, Number(opt));\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 = options?.[0] ?? '0';\n if (!validateNumberString(opt)) {optionMustBeNumber('percent');}\n return (value: unknown): string => {\n if (typeof value !== 'number') {valueMustBeNumber('percent');}\n return `${(value * 100).toFixed(Number(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\"`,\n * so the binding layer's \"undefined skips the write, null clears\" semantics survive.\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<unknown> => {\n const opt = options?.[0] ?? optionsRequired('unit');\n return (value: unknown): unknown => {\n if (value === null || typeof value === 'undefined') {return value;}\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 opt1 = options?.[0] ?? optionsRequired('truncate');\n if (!validateNumberString(opt1)) {optionMustBeNumber('truncate');}\n const maxLength = Number(opt1);\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 \"upper\": uc,\n \"lower\": lc,\n \"capitalize\": cap,\n \"trim\": trim,\n \"slice\": slice,\n \"substr\": substr,\n \"padStart\": pad,\n \"padEnd\": padEnd,\n \"repeat\": rep,\n \"reverse\": rev,\n \"truncate\": 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\": 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: [1, 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 date: [0, 0],\n time: [0, 0],\n datetime: [0, 0],\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 * @param name - Filter 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 const filter = filters[name] ?? filters[builtinFilterAliases[name]];\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","builtinFilters","eq","options","literals","opt","literal","undefined","length","ne","not","_options","lt","le","gt","ge","add","sub","mul","div","mod","abs","Math","clamp","opt1","opt2","min","max","toFixed","locale","explicit","toLocaleString","config","upper","String","toUpperCase","lower","toLowerCase","capitalize","v","charAt","slice","trim","numberedOpts","push","substr","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":"iGAwBM,SAAUA,EAAgBC,GAC9BC,EAAW,UAAUD,iCACvB,CAQM,SAAUE,EAAmBF,GACjCC,EAAW,UAAUD,gCACvB,CAQM,SAAUG,EAAkBH,GAChCC,EAAW,UAAUD,4BACvB,CA4BM,SAAUI,EAAgBJ,GAC9BC,EAAW,UAAUD,0BACvB,CCrEO,MAAMK,EAAyD,CACpEC,IAAK,MACLC,IAAK,MACLC,IAAK,UACLC,GAAI,QACJC,GAAI,QACJC,IAAK,aACLC,IAAK,SACLC,IAAK,UACLC,IAAK,WACLC,KAAM,eCMR,SAASC,EAAqBC,GAC5B,SAAKA,GAASC,MAAMC,OAAOF,IAI7B,CAQA,MA4uBMG,EAAoC,CACxCC,GA7uBS,CAACC,EAAmBC,KAC7B,MAAMC,EAAMF,IAAU,IAAMvB,EAAgB,MAEtC0B,OAAuBC,IAAbH,GAA0BA,EAASI,OAAS,EAAIJ,EAAS,GAAKC,EAC9E,OAAQP,GAEe,iBAAVA,GACJD,EAAqBQ,IAAOtB,EAAmB,MAC7Ce,IAAUE,OAAOK,IAEL,iBAAVP,EACFA,IAAUO,EAGZP,IAAUQ,GAguBnBG,GAttBS,CAACN,EAAmBC,KAC7B,MAAMC,EAAMF,IAAU,IAAMvB,EAAgB,MACtC0B,OAAuBC,IAAbH,GAA0BA,EAASI,OAAS,EAAIJ,EAAS,GAAKC,EAC9E,OAAQP,GAEe,iBAAVA,GACJD,EAAqBQ,IAAOtB,EAAmB,MAC7Ce,IAAUE,OAAOK,IAEL,iBAAVP,EACFA,IAAUO,EAGZP,IAAUQ,GA0sBnBI,IAhsBWC,GACHb,IACe,kBAAVA,GFpBbhB,EAAW,UEoB2C,mCAC5CgB,GA+rBVc,GArrBUT,IACV,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,MAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,MAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,MAC3Cc,EAAQE,OAAOK,KAirBxBQ,GAvqBUV,IACV,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,MAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,MAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,MAC3Cc,GAASE,OAAOK,KAmqBzBS,GAzpBUX,IACV,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,MAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,MAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,MAC3Cc,EAAQE,OAAOK,KAqpBxBU,GA3oBUZ,IACV,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,MAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,MAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,MAC3Cc,GAASE,OAAOK,KAwoBzBW,IA9nBWb,IACX,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,OAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,OAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,OAC3Cc,EAAQE,OAAOK,KA0nBxBY,IAhnBWd,IACX,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,OAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,OAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,OAC3Cc,EAAQE,OAAOK,KA4mBxBa,IAlmBWf,IACX,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,OAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,OAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,OAC3Cc,EAAQE,OAAOK,KA8lBxBc,IAplBWhB,IACX,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,OAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,OAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,OAC3Cc,EAAQE,OAAOK,KAglBxBe,IAtkBWjB,IACX,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,OAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,OAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,OAC3Cc,EAAQE,OAAOK,KAkkBxBgB,IAxjBWV,GACHb,IACe,iBAAVA,GAAqBd,EAAkB,OAC3CsC,KAAKD,IAAIvB,IAsjBlByB,MAxiBapB,IACb,MAAMqB,EAAOrB,IAAU,IAAMvB,EAAgB,SACxCiB,EAAqB2B,IAAQzC,EAAmB,SACrD,MAAM0C,EAAOtB,IAAU,IAAMvB,EAAgB,SACxCiB,EAAqB4B,IAAQ1C,EAAmB,SACrD,MAAM2C,EAAM1B,OAAOwB,GACbG,EAAM3B,OAAOyB,GACnB,OAAQ3B,IACe,iBAAVA,GAAqBd,EAAkB,SAC3CsC,KAAKI,IAAIJ,KAAKK,IAAI7B,EAAO4B,GAAMC,KAiiBxCC,QAvhBWzB,IACX,MAAME,EAAMF,IAAU,IAAM,IAE5B,OADKN,EAAqBQ,IAAOtB,EAAmB,WAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,WAC3Cc,EAAM8B,QAAQ5B,OAAOK,MAmhB9BwB,OAxfc1B,IACd,MAAM2B,EAAW3B,IAAU,GAC3B,OAAQL,IACe,iBAAVA,GAAqBd,EAAkB,UAC3Cc,EAAMiC,eAAeD,GAAYE,EAAOH,UAqfjDI,MA3eUtB,GACFb,GACCoC,OAAOpC,GAAOqC,cA0evBC,MAheUzB,GACFb,GACCoC,OAAOpC,GAAOuC,cA+dvBC,WArdW3B,GACHb,IACN,MAAMyC,EAAIL,OAAOpC,GACjB,OAAiB,IAAbyC,EAAE/B,OAAsB+B,EACX,IAAbA,EAAE/B,OAAsB+B,EAAEJ,cACvBI,EAAEC,OAAO,GAAGL,cAAgBI,EAAEE,MAAM,IAid7CC,KAvcY/B,GACJb,GACCoC,OAAOpC,GAAO4C,OAscvBD,MA5batC,IACb,MAAMwC,EAAyB,GACzBnB,EAAOrB,IAAU,IAAMvB,EAAgB,SACxCiB,EAAqB2B,IAAQzC,EAAmB,SACrD4D,EAAaC,KAAK5C,OAAOwB,IACzB,MAAMC,EAAOtB,IAAU,GAKvB,YAJoB,IAATsB,IACJ5B,EAAqB4B,IAAQ1C,EAAmB,SACrD4D,EAAaC,KAAK5C,OAAOyB,KAEnB3B,GACCoC,OAAOpC,GAAO2C,SAASE,IAkbhCE,OAxac1C,IACd,MAAMqB,EAAOrB,IAAU,IAAMvB,EAAgB,UACxCiB,EAAqB2B,IAAQzC,EAAmB,UACrD,MAAM0C,EAAOtB,IAAU,IAAMvB,EAAgB,UAE7C,OADKiB,EAAqB4B,IAAQ1C,EAAmB,UAC7Ce,GACCoC,OAAOpC,GAAO+C,OAAO7C,OAAOwB,GAAOxB,OAAOyB,KAmanDqB,SAzZW3C,IACX,MAAMqB,EAAOrB,IAAU,IAAMvB,EAAgB,YACxCiB,EAAqB2B,IAAQzC,EAAmB,YACrD,MAAM0C,EAAOtB,IAAU,IAAM,IAC7B,OAAQL,GACCoC,OAAOpC,GAAOgD,SAAS9C,OAAOwB,GAAOC,IAqZ9CsB,OA3Yc5C,IACd,MAAMqB,EAAOrB,IAAU,IAAMvB,EAAgB,UACxCiB,EAAqB2B,IAAQzC,EAAmB,UACrD,MAAM0C,EAAOtB,IAAU,IAAM,IAC7B,OAAQL,GACCoC,OAAOpC,GAAOiD,OAAO/C,OAAOwB,GAAOC,IAuY5CuB,OA7XW7C,IACX,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,UAE5C,OADKiB,EAAqBQ,IAAOtB,EAAmB,UAC5Ce,GACCoC,OAAOpC,GAAOkD,OAAOhD,OAAOK,KA0XrC4C,QAhXWtC,GACHb,GACCoC,OAAOpC,GAAOoD,MAAM,IAAID,UAAUE,KAAK,IA+WhDC,SA/NgBjD,IAChB,MAAMqB,EAAOrB,IAAU,IAAMvB,EAAgB,YACxCiB,EAAqB2B,IAAQzC,EAAmB,YACrD,MAAMsE,EAAYrD,OAAOwB,GACnB8B,EAASnD,IAAU,IAAM,IAC/B,OAAQL,IACN,MAAMyC,EAAIL,OAAOpC,GACjB,OAAIyC,EAAE/B,QAAU6C,EAAmBd,EAC5BA,EAAEE,MAAM,EAAGY,GAAaC,IAwNjCH,KAlPYhD,IACZ,MAAME,EAAMF,IAAU,IAAM,KAC5B,OAAQL,IACDyD,MAAMC,QAAQ1D,IF3erBhB,EAAW,UE2eoC,kCACtCgB,EAAMqD,KAAK9C,KAgPpBoD,IAxWW9C,GACHb,GACC4D,SAASxB,OAAOpC,GAAQ,IAuWjC6D,MA7VahD,GACLb,GACC8D,WAAW1B,OAAOpC,IA4V3B+D,MAlVa1D,IACb,MAAME,EAAMF,IAAU,IAAM,IAE5B,OADKN,EAAqBQ,IAAOtB,EAAmB,SAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,SAClD,MAAM8E,EAAWxC,KAAKyC,IAAI,GAAI/D,OAAOK,IACrC,OAAOiB,KAAKuC,MAAM/D,EAAQgE,GAAYA,IA6UxCE,MAnUa7D,IACb,MAAME,EAAMF,IAAU,IAAM,IAE5B,OADKN,EAAqBQ,IAAOtB,EAAmB,SAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,SAClD,MAAM8E,EAAWxC,KAAKyC,IAAI,GAAI/D,OAAOK,IACrC,OAAOiB,KAAK0C,MAAMlE,EAAQgE,GAAYA,IA8TxCG,KApTY9D,IACZ,MAAME,EAAMF,IAAU,IAAM,IAE5B,OADKN,EAAqBQ,IAAOtB,EAAmB,QAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,QAClD,MAAM8E,EAAWxC,KAAKyC,IAAI,GAAI/D,OAAOK,IACrC,OAAOiB,KAAK2C,KAAKnE,EAAQgE,GAAYA,IA+SvCI,QArSe/D,IACf,MAAME,EAAMF,IAAU,IAAM,IAE5B,OADKN,EAAqBQ,IAAOtB,EAAmB,WAC5Ce,IACe,iBAAVA,GAAqBd,EAAkB,WAC3C,IAAY,IAARc,GAAa8B,QAAQ5B,OAAOK,SAiSzC8D,KA3QYhE,IACZ,MAAME,EAAMF,IAAU,IAAMvB,EAAgB,QAC5C,OAAQkB,GACFA,QAAwDA,EACrDoC,OAAOpC,GAASO,GAyQzB+D,KAxNYjE,IAEZ,MAAM2B,EAAW3B,IAAU,GAC3B,OAAQL,IACAA,aAAiBuE,MAASpF,EAAgB,QACzCa,EAAMwE,mBAAmBxC,GAAYE,EAAOH,UAoNrD0C,KA1MYpE,IAEZ,MAAM2B,EAAW3B,IAAU,GAC3B,OAAQL,IACAA,aAAiBuE,MAAQpF,EAAgB,QACxCa,EAAM0E,mBAAmB1C,GAAYE,EAAOH,UAsMrD4C,SA5LgBtE,IAEhB,MAAM2B,EAAW3B,IAAU,GAC3B,OAAQL,IACAA,aAAiBuE,MAAQpF,EAAgB,YACxCa,EAAMiC,eAAeD,GAAYE,EAAOH,UAwLjD6C,IA9KWvE,IACX,MAAME,EAAMF,IAAU,IAAM,IAC5B,OAAQL,IACAA,aAAiBuE,MAAQpF,EAAgB,OAC/C,MAAM0F,EAAO7E,EAAM8E,cAAcC,WAC3BC,GAAShF,EAAMiF,WAAa,GAAGF,WAAW/B,SAAS,EAAG,KACtDkC,EAAMlF,EAAMmF,UAAUJ,WAAW/B,SAAS,EAAG,KACnD,MAAO,GAAG6B,IAAOtE,IAAMyE,IAAQzE,IAAM2E,MAwKvCE,IA3JW/E,IACX,MAAME,EAAMF,IAAU,IAAM,IAC5B,OAAQL,IACAA,aAAiBuE,MAAQpF,EAAgB,OAC/C,MAAMkG,EAAQrF,EAAMsF,WAAWP,WAAW/B,SAAS,EAAG,KAChDuC,EAAUvF,EAAMwF,aAAaT,WAAW/B,SAAS,EAAG,KACpDyC,EAAUzF,EAAM0F,aAAaX,WAAW/B,SAAS,EAAG,KAC1D,MAAO,GAAGqC,IAAQ9E,IAAMgF,IAAUhF,IAAMkF,MAsJ1CE,MA3Ia9E,GACLb,IAA6BA,EA2IrC4F,OAlIc/E,GAENb,KAA8BA,EAiItC6F,SAxHe,CAACxF,EAAmBC,KACnC,MAAMC,EAAMF,IAAU,IAAMvB,EAAgB,YAEtCgH,OAAwBrF,IAAbH,GAA0BA,EAASI,OAAS,EAAIJ,EAAS,GAAKC,EAC/E,OAAQP,GACDA,GAAe8F,GAoHtBC,SAxGe,CAAC1F,EAAmBC,KACnC,MAAMC,EAAMF,IAAU,IAAMvB,EAAgB,YACtCgH,OAAwBrF,IAAbH,GAA0BA,EAASI,OAAS,EAAIJ,EAAS,GAAKC,EAC/E,OAAQP,GAA4BA,GAAS8F,GAuG7CE,QA9FenF,GACPb,GACCiG,QAAQjG,GA6FjBkG,OAnFcrF,GACNb,GACCE,OAAOF,GAkFhBmG,OAxEctF,GACNb,GACCoC,OAAOpC,GAuEhBoG,YA7DavF,GACLb,GACY,KAAVA,EAAgB,KAAOA,GAoEtBqG,EAA0E,CACrFjG,GAAI,CAAC,EAAG,GACRQ,IAAK,CAAC,EAAG,GACTD,GAAI,CAAC,EAAG,GACRG,GAAI,CAAC,EAAG,GACRC,GAAI,CAAC,EAAG,GACRC,GAAI,CAAC,EAAG,GACRC,GAAI,CAAC,EAAG,GACRC,IAAK,CAAC,EAAG,GACTC,IAAK,CAAC,EAAG,GACTC,IAAK,CAAC,EAAG,GACTC,IAAK,CAAC,EAAG,GACTC,IAAK,CAAC,EAAG,GACTC,IAAK,CAAC,EAAG,GACTE,MAAO,CAAC,EAAG,GACXK,QAAS,CAAC,EAAG,GACbC,OAAQ,CAAC,EAAG,GACZI,MAAO,CAAC,EAAG,GACXG,MAAO,CAAC,EAAG,GACXE,WAAY,CAAC,EAAG,GAChBI,KAAM,CAAC,EAAG,GACVD,MAAO,CAAC,EAAG,GACXI,OAAQ,CAAC,EAAG,GACZC,SAAU,CAAC,EAAG,GACdC,OAAQ,CAAC,EAAG,GACZC,OAAQ,CAAC,EAAG,GACZC,QAAS,CAAC,EAAG,GACbG,SAAU,CAAC,EAAG,GACdD,KAAM,CAAC,EAAG,GACVM,IAAK,CAAC,EAAG,GACTE,MAAO,CAAC,EAAG,GACXE,MAAO,CAAC,EAAG,GACXG,MAAO,CAAC,EAAG,GACXC,KAAM,CAAC,EAAG,GACVC,QAAS,CAAC,EAAG,GACbC,KAAM,CAAC,EAAG,GACVC,KAAM,CAAC,EAAG,GACVG,KAAM,CAAC,EAAG,GACVE,SAAU,CAAC,EAAG,GACdC,IAAK,CAAC,EAAG,GACTQ,IAAK,CAAC,EAAG,GACTO,MAAO,CAAC,EAAG,GACXC,OAAQ,CAAC,EAAG,GACZC,SAAU,CAAC,EAAG,GACdE,SAAU,CAAC,EAAG,GACdC,QAAS,CAAC,EAAG,GACbE,OAAQ,CAAC,EAAG,GACZC,OAAQ,CAAC,EAAG,GACZC,YAAa,CAAC,EAAG,IAGNE,EAAuBnG,EACvBoG,EAAsBpG,ECz3BnC,IAAIqG,GAAY,ECJT,MAAMC,EAAyB,CACpCC,KAAM,UACN,OAAAC,GDKIH,IACJA,GAAY,EACZI,EAAgB,QAASL,EAAqBF,EAAoBjH,GAClEwH,EAAgB,SAAUN,EAAsBD,EAAoBjH,GCNpE"}