@wcstack/state 1.29.0 → 1.31.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.
- package/README.ja.md +142 -0
- package/README.md +142 -0
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +50 -2
- package/dist/index.esm.js +466 -73
- package/dist/index.esm.js.map +1 -1
- package/dist/parser.d.ts +1 -1
- package/dist/parser.esm.js +6 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -351,14 +351,7 @@ function getCustomElement(node) {
|
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
|
|
355
|
-
* Resolve the registry at operation time so importing the runtime remains safe
|
|
356
|
-
* when browser globals are absent. The owner hook is reserved for scoped
|
|
357
|
-
* registries; current callers fall back to the global registry.
|
|
358
|
-
*/
|
|
359
|
-
function getCustomElementRegistry(owner = null) {
|
|
360
|
-
const globalRegistry = globalThis.customElements;
|
|
361
|
-
const registry = owner?.customElements ?? globalRegistry;
|
|
354
|
+
function toAdapter(registry) {
|
|
362
355
|
if (typeof registry !== "object" || registry === null)
|
|
363
356
|
return null;
|
|
364
357
|
const candidate = registry;
|
|
@@ -367,6 +360,33 @@ function getCustomElementRegistry(owner = null) {
|
|
|
367
360
|
}
|
|
368
361
|
return candidate;
|
|
369
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Resolve the registry that governs `owner` at operation time, so importing the
|
|
365
|
+
* runtime stays safe when browser globals are absent.
|
|
366
|
+
*
|
|
367
|
+
* Pass the node the operation is about (the bound element, its shadow root):
|
|
368
|
+
* with scoped custom element registries the same tag name can resolve to a
|
|
369
|
+
* different constructor per tree, so "is this tag defined?" is only meaningful
|
|
370
|
+
* relative to a node. Nodes on platforms without scoped registries report
|
|
371
|
+
* `undefined` and fall back to the global registry, which keeps every existing
|
|
372
|
+
* caller on today's behaviour.
|
|
373
|
+
*/
|
|
374
|
+
function getCustomElementRegistry(owner = null) {
|
|
375
|
+
if (owner !== null && typeof owner !== "undefined") {
|
|
376
|
+
const { customElementRegistry: scoped, customElements: owned } = owner;
|
|
377
|
+
// A node in a null-registry subtree resolves to no registry at all. Falling
|
|
378
|
+
// back to the global one would report globally-defined tags as usable and
|
|
379
|
+
// let us write own properties onto elements that are still un-upgraded --
|
|
380
|
+
// exactly the accessor shadowing the deferred-apply path exists to avoid.
|
|
381
|
+
if (scoped === null)
|
|
382
|
+
return null;
|
|
383
|
+
if (typeof scoped !== "undefined")
|
|
384
|
+
return toAdapter(scoped);
|
|
385
|
+
if (typeof owned !== "undefined")
|
|
386
|
+
return toAdapter(owned);
|
|
387
|
+
}
|
|
388
|
+
return toAdapter(globalThis.customElements);
|
|
389
|
+
}
|
|
370
390
|
function upgradeCustomElement(registry, root) {
|
|
371
391
|
registry.upgrade?.(root);
|
|
372
392
|
}
|
|
@@ -544,7 +564,7 @@ function expandSpread(node, results, options = {}) {
|
|
|
544
564
|
if (tagName === null) {
|
|
545
565
|
raiseError(`Spread binding "${result.statePathName}" requires a custom element with wcBindable, but <${element.tagName.toLowerCase()}> is not a custom element.`);
|
|
546
566
|
}
|
|
547
|
-
const registry = getCustomElementRegistry();
|
|
567
|
+
const registry = getCustomElementRegistry(element);
|
|
548
568
|
if (registry === null) {
|
|
549
569
|
raiseError(`CustomElementRegistry is unavailable for <${tagName}>.`);
|
|
550
570
|
}
|
|
@@ -2302,6 +2322,23 @@ function setLoopContextByNode(node, loopContext) {
|
|
|
2302
2322
|
loopContextByNode.set(node, loopContext);
|
|
2303
2323
|
}
|
|
2304
2324
|
|
|
2325
|
+
/**
|
|
2326
|
+
* devtools/sink.ts
|
|
2327
|
+
*
|
|
2328
|
+
* 計装点が参照するホットパス唯一の接点。依存ゼロの葉モジュールにすることで、
|
|
2329
|
+
* 計装される側(stateElementByName / setByAddress / binding / token)と
|
|
2330
|
+
* bridge の間の循環 import を避ける。
|
|
2331
|
+
*
|
|
2332
|
+
* コスト規範(protocol §1-1): フック未接続時、計装点のコストは
|
|
2333
|
+
* `devtoolsSink !== null` の分岐 1 個。イベントオブジェクトの生成は
|
|
2334
|
+
* 必ずこのチェックの内側で行うこと。
|
|
2335
|
+
*/
|
|
2336
|
+
/** live binding としてエクスポート。計装点は `if (devtoolsSink !== null)` で参照する */
|
|
2337
|
+
let devtoolsSink = null;
|
|
2338
|
+
function setDevtoolsSink(sink) {
|
|
2339
|
+
devtoolsSink = sink;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2305
2342
|
const lastListValueByAbsoluteStateAddress = new WeakMap();
|
|
2306
2343
|
function getLastListValueByAbsoluteStateAddress(address) {
|
|
2307
2344
|
return lastListValueByAbsoluteStateAddress.get(address) ?? [];
|
|
@@ -2576,23 +2613,6 @@ function clearAbsoluteStateAddressByBinding(binding) {
|
|
|
2576
2613
|
absoluteStateAddressByBinding.delete(binding);
|
|
2577
2614
|
}
|
|
2578
2615
|
|
|
2579
|
-
/**
|
|
2580
|
-
* devtools/sink.ts
|
|
2581
|
-
*
|
|
2582
|
-
* 計装点が参照するホットパス唯一の接点。依存ゼロの葉モジュールにすることで、
|
|
2583
|
-
* 計装される側(stateElementByName / setByAddress / binding / token)と
|
|
2584
|
-
* bridge の間の循環 import を避ける。
|
|
2585
|
-
*
|
|
2586
|
-
* コスト規範(protocol §1-1): フック未接続時、計装点のコストは
|
|
2587
|
-
* `devtoolsSink !== null` の分岐 1 個。イベントオブジェクトの生成は
|
|
2588
|
-
* 必ずこのチェックの内側で行うこと。
|
|
2589
|
-
*/
|
|
2590
|
-
/** live binding としてエクスポート。計装点は `if (devtoolsSink !== null)` で参照する */
|
|
2591
|
-
let devtoolsSink = null;
|
|
2592
|
-
function setDevtoolsSink(sink) {
|
|
2593
|
-
devtoolsSink = sink;
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
2616
|
/**
|
|
2597
2617
|
* 絶対アドレス → 登録 binding の台帳。
|
|
2598
2618
|
*
|
|
@@ -3100,7 +3120,7 @@ function attachEventTokenHandler(binding) {
|
|
|
3100
3120
|
const element = binding.node;
|
|
3101
3121
|
// カスタム要素が未定義なら定義後に再試行(wcBindable が必要なため)。
|
|
3102
3122
|
const customTagName = getCustomElement(element);
|
|
3103
|
-
const registry = getCustomElementRegistry();
|
|
3123
|
+
const registry = getCustomElementRegistry(element);
|
|
3104
3124
|
if (customTagName !== null && registry?.get(customTagName) === undefined) {
|
|
3105
3125
|
if (registry === null) {
|
|
3106
3126
|
raiseError(`CustomElementRegistry is unavailable for <${customTagName}>.`);
|
|
@@ -3396,7 +3416,7 @@ function isPossibleTwoWay(node, propName) {
|
|
|
3396
3416
|
}
|
|
3397
3417
|
const customTagName = getCustomElement(element);
|
|
3398
3418
|
if (customTagName !== null) {
|
|
3399
|
-
const customClass = getCustomElementRegistry()?.get(customTagName);
|
|
3419
|
+
const customClass = getCustomElementRegistry(element)?.get(customTagName);
|
|
3400
3420
|
if (typeof customClass === "undefined") {
|
|
3401
3421
|
raiseError(`Custom element <${customTagName}> is not defined. Cannot determine if property "${propName}" is suitable for two-way binding.`);
|
|
3402
3422
|
}
|
|
@@ -3579,7 +3599,7 @@ function getEventName(binding) {
|
|
|
3579
3599
|
// 2.wcBindable protocol
|
|
3580
3600
|
const customTagName = getCustomElement(binding.node);
|
|
3581
3601
|
if (customTagName !== null) {
|
|
3582
|
-
const customClass = getCustomElementRegistry()?.get(customTagName);
|
|
3602
|
+
const customClass = getCustomElementRegistry(binding.node)?.get(customTagName);
|
|
3583
3603
|
if (typeof customClass === "undefined") {
|
|
3584
3604
|
raiseError(`Custom element <${customTagName}> is not defined. Cannot determine event name for two-way binding.`);
|
|
3585
3605
|
}
|
|
@@ -3751,7 +3771,7 @@ function addTwowayValueObserver(node, propName, observer) {
|
|
|
3751
3771
|
function attachTwowayEventHandler(binding) {
|
|
3752
3772
|
const customTagName = getCustomElement(binding.node);
|
|
3753
3773
|
if (customTagName !== null) {
|
|
3754
|
-
const registry = getCustomElementRegistry();
|
|
3774
|
+
const registry = getCustomElementRegistry(binding.node);
|
|
3755
3775
|
const customClass = registry?.get(customTagName);
|
|
3756
3776
|
if (typeof customClass === "undefined") {
|
|
3757
3777
|
if (registry === null) {
|
|
@@ -3777,7 +3797,7 @@ function attachTwowayEventHandler(binding) {
|
|
|
3777
3797
|
function detachTwowayEventHandler(binding) {
|
|
3778
3798
|
const customTagName = getCustomElement(binding.node);
|
|
3779
3799
|
if (customTagName !== null) {
|
|
3780
|
-
const registry = getCustomElementRegistry();
|
|
3800
|
+
const registry = getCustomElementRegistry(binding.node);
|
|
3781
3801
|
const customClass = registry?.get(customTagName);
|
|
3782
3802
|
if (typeof customClass === "undefined") {
|
|
3783
3803
|
if (registry === null) {
|
|
@@ -4162,7 +4182,9 @@ function propagateListPathToOuterState(innerStateElement, innerPath) {
|
|
|
4162
4182
|
if (outerAbsPathInfo === null || outerAbsPathInfo.stateElement === innerStateElement) {
|
|
4163
4183
|
return;
|
|
4164
4184
|
}
|
|
4165
|
-
|
|
4185
|
+
// source="internal": 翻訳済みの外側パスであり、書き手が書いた文字列ではない。
|
|
4186
|
+
// 存在検査に掛けても直せる相手が居ないので掛けない(pathDiagnostics.ts)。
|
|
4187
|
+
outerAbsPathInfo.stateElement.setPathInfo(outerAbsPathInfo.pathInfo.path, "for", "internal");
|
|
4166
4188
|
}
|
|
4167
4189
|
/**
|
|
4168
4190
|
* 子スコープのリスト行パス(`items.*.name`)に対応する親スコープの絶対パス情報を返す。
|
|
@@ -4799,7 +4821,7 @@ class BindingSession {
|
|
|
4799
4821
|
return true;
|
|
4800
4822
|
}
|
|
4801
4823
|
deferUntilDefined(node, tagName, callback, reject = () => undefined) {
|
|
4802
|
-
const registry = getCustomElementRegistry();
|
|
4824
|
+
const registry = getCustomElementRegistry(node);
|
|
4803
4825
|
if (registry === null) {
|
|
4804
4826
|
raiseError(`CustomElementRegistry is unavailable for <${tagName}>.`);
|
|
4805
4827
|
}
|
|
@@ -5253,7 +5275,7 @@ class BindingSession {
|
|
|
5253
5275
|
attach();
|
|
5254
5276
|
return;
|
|
5255
5277
|
}
|
|
5256
|
-
const registry = getCustomElementRegistry();
|
|
5278
|
+
const registry = getCustomElementRegistry(record.info.node);
|
|
5257
5279
|
if (registry === null) {
|
|
5258
5280
|
raiseError(`CustomElementRegistry is unavailable for <${tagName}>.`);
|
|
5259
5281
|
}
|
|
@@ -5626,7 +5648,7 @@ function getWcBindable(element) {
|
|
|
5626
5648
|
if (customTagName === null) {
|
|
5627
5649
|
return null;
|
|
5628
5650
|
}
|
|
5629
|
-
const customClass = getCustomElementRegistry()?.get(customTagName);
|
|
5651
|
+
const customClass = getCustomElementRegistry(element)?.get(customTagName);
|
|
5630
5652
|
if (typeof customClass === "undefined") {
|
|
5631
5653
|
raiseError(`Custom element <${customTagName}> is not defined for command binding.`);
|
|
5632
5654
|
}
|
|
@@ -6969,7 +6991,7 @@ function getInputAttributeMirror(element, propName) {
|
|
|
6969
6991
|
if (customTagName === null) {
|
|
6970
6992
|
return null;
|
|
6971
6993
|
}
|
|
6972
|
-
const customClass = getCustomElementRegistry()?.get(customTagName);
|
|
6994
|
+
const customClass = getCustomElementRegistry(element)?.get(customTagName);
|
|
6973
6995
|
if (typeof customClass === "undefined") {
|
|
6974
6996
|
return null;
|
|
6975
6997
|
}
|
|
@@ -7329,6 +7351,246 @@ function applyChangeToWebComponent(binding, _context, _newValue) {
|
|
|
7329
7351
|
});
|
|
7330
7352
|
}
|
|
7331
7353
|
|
|
7354
|
+
/**
|
|
7355
|
+
* pathDiagnostics.ts — バインド / `$watch` 対象パスの存在検査(silent failure の可視化)。
|
|
7356
|
+
*
|
|
7357
|
+
* なぜ必要か:
|
|
7358
|
+
* `getByAddress` は「親が null / undefined のパスの読み」を undefined で返し、
|
|
7359
|
+
* undefined はプロパティ書き込みがスキップされる値なので、`user.nmae` のような
|
|
7360
|
+
* 打ち間違いは**エラーも警告も出さずに DOM が更新されない**だけになる。一方で
|
|
7361
|
+
* トップレベルの打ち間違い(`cout`)は parentAddress を辿れず raiseError で落ちる。
|
|
7362
|
+
* 同じ「パスを打ち間違えた」という 1 つの失敗が、パスの深さで silent / loud に
|
|
7363
|
+
* 割れており、書き手からは区別がつかない。ここはその silent 側を埋める。
|
|
7364
|
+
*
|
|
7365
|
+
* 精度方針(過小近似):
|
|
7366
|
+
* 「確実に存在しない」と言い切れる場合にだけ報告する。getter の戻り値の先・
|
|
7367
|
+
* 空配列・null 親・mapped な `bind-component` など、静的に決められない形はすべて
|
|
7368
|
+
* `"unknown"` に倒して黙る(偽陽性ゼロ優先。docs/static-wiring-dx-design.md D7 /
|
|
7369
|
+
* [ADR-06](../../docs/architecture-hardening/06-path-type-safety.md) の精度哲学)。
|
|
7370
|
+
*
|
|
7371
|
+
* 診断 code はコンソール → lint → IDE の三面で共有する(errorGuidance.ts の規約)。
|
|
7372
|
+
*/
|
|
7373
|
+
const UNKNOWN = Object.freeze({
|
|
7374
|
+
existence: "unknown",
|
|
7375
|
+
missingSegment: "",
|
|
7376
|
+
candidates: Object.freeze([]),
|
|
7377
|
+
});
|
|
7378
|
+
const EXISTS = Object.freeze({
|
|
7379
|
+
existence: "exists",
|
|
7380
|
+
missingSegment: "",
|
|
7381
|
+
candidates: Object.freeze([]),
|
|
7382
|
+
});
|
|
7383
|
+
/**
|
|
7384
|
+
* `obj` 自身+プロトタイプチェーン(Object.prototype 手前まで)から descriptor を引く。
|
|
7385
|
+
* 打ち切り位置は getAllPropertyDescriptors と同じ — 「state が宣言したもの」だけを
|
|
7386
|
+
* 存在とみなし、`toString` 等の Object.prototype 由来を存在扱いしない。
|
|
7387
|
+
*/
|
|
7388
|
+
function findDescriptor(obj, key) {
|
|
7389
|
+
let proto = obj;
|
|
7390
|
+
while (proto !== null && proto !== Object.prototype) {
|
|
7391
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
|
|
7392
|
+
if (typeof descriptor !== "undefined") {
|
|
7393
|
+
return descriptor;
|
|
7394
|
+
}
|
|
7395
|
+
proto = Object.getPrototypeOf(proto);
|
|
7396
|
+
}
|
|
7397
|
+
return undefined;
|
|
7398
|
+
}
|
|
7399
|
+
/** `obj` 自身+プロトタイプチェーンのキー名(did-you-mean の候補集合) */
|
|
7400
|
+
function ownKeys(obj) {
|
|
7401
|
+
const keys = [];
|
|
7402
|
+
let proto = obj;
|
|
7403
|
+
while (proto !== null && proto !== Object.prototype) {
|
|
7404
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
7405
|
+
keys.push(key);
|
|
7406
|
+
}
|
|
7407
|
+
proto = Object.getPrototypeOf(proto);
|
|
7408
|
+
}
|
|
7409
|
+
return keys;
|
|
7410
|
+
}
|
|
7411
|
+
/**
|
|
7412
|
+
* 失敗した階層の兄弟候補。生オブジェクトのキーに加え、その階層にフラット宣言
|
|
7413
|
+
* (ドットパス getter)されているものも混ぜる — `cart.items.*.subtotl` の正解
|
|
7414
|
+
* `subtotal` は行オブジェクトには無く getterPaths にしか居ないため。
|
|
7415
|
+
*/
|
|
7416
|
+
function collectCandidates(container, parentPrefix, declaredPaths) {
|
|
7417
|
+
const candidates = ownKeys(container);
|
|
7418
|
+
const prefix = parentPrefix.length > 0 ? parentPrefix + DELIMITER : "";
|
|
7419
|
+
for (const declared of declaredPaths) {
|
|
7420
|
+
if (prefix.length > 0 && !declared.startsWith(prefix)) {
|
|
7421
|
+
continue;
|
|
7422
|
+
}
|
|
7423
|
+
const rest = declared.slice(prefix.length);
|
|
7424
|
+
// 直下の 1 セグメントだけを候補にする(孫は別階層の名前なので提案しない)
|
|
7425
|
+
if (rest.length > 0 && rest.indexOf(DELIMITER) === -1) {
|
|
7426
|
+
candidates.push(rest);
|
|
7427
|
+
}
|
|
7428
|
+
}
|
|
7429
|
+
return candidates;
|
|
7430
|
+
}
|
|
7431
|
+
/**
|
|
7432
|
+
* `target` に対して `path` が解決しうるかを、値を読まずに(getter を評価せずに)判定する。
|
|
7433
|
+
*
|
|
7434
|
+
* 解決の順序は `getByAddress` の実装に合わせる: まず「パス文字列そのものがキーか」
|
|
7435
|
+
* (ドットパス getter がこれ)、次にセグメントを 1 つずつ降りる。
|
|
7436
|
+
*/
|
|
7437
|
+
function resolvePathExistence(target, path, declaredPaths) {
|
|
7438
|
+
// ドットパス getter / フラットキーの完全一致(`get "users.*.fullName"()` 等)
|
|
7439
|
+
if (findDescriptor(target, path) !== undefined) {
|
|
7440
|
+
return EXISTS;
|
|
7441
|
+
}
|
|
7442
|
+
const segments = getPathInfo(path).segments;
|
|
7443
|
+
let current = target;
|
|
7444
|
+
let prefix = "";
|
|
7445
|
+
for (let i = 0; i < segments.length; i++) {
|
|
7446
|
+
const segment = segments[i];
|
|
7447
|
+
const parentPrefix = prefix;
|
|
7448
|
+
prefix = i === 0 ? segment : prefix + DELIMITER + segment;
|
|
7449
|
+
// 途中のプレフィックスがフラット宣言されている(`cart.totalPrice` が getter で、
|
|
7450
|
+
// その戻り値のサブプロパティを読む形)。戻り値の形は評価しないと分からない
|
|
7451
|
+
if (i > 0 && i < segments.length - 1 && findDescriptor(target, prefix) !== undefined) {
|
|
7452
|
+
return UNKNOWN;
|
|
7453
|
+
}
|
|
7454
|
+
// null / undefined / primitive より深い読みは実行時 undefined 解決 = 判定不能。
|
|
7455
|
+
// 「初期値 null のオブジェクトに後から代入する」形を偽陽性で潰さないため
|
|
7456
|
+
if (Object(current) !== current) {
|
|
7457
|
+
return UNKNOWN;
|
|
7458
|
+
}
|
|
7459
|
+
if (segment === WILDCARD) {
|
|
7460
|
+
// 行の形は「いま入っている要素」からしか分からない。空配列・非配列は判定不能
|
|
7461
|
+
if (!Array.isArray(current) || current.length === 0) {
|
|
7462
|
+
return UNKNOWN;
|
|
7463
|
+
}
|
|
7464
|
+
current = current[0];
|
|
7465
|
+
continue;
|
|
7466
|
+
}
|
|
7467
|
+
const descriptor = findDescriptor(current, segment);
|
|
7468
|
+
if (typeof descriptor === "undefined") {
|
|
7469
|
+
return {
|
|
7470
|
+
existence: "missing",
|
|
7471
|
+
missingSegment: segment,
|
|
7472
|
+
candidates: collectCandidates(current, parentPrefix, declaredPaths),
|
|
7473
|
+
};
|
|
7474
|
+
}
|
|
7475
|
+
if (typeof descriptor.get === "function") {
|
|
7476
|
+
// getter の戻り値の先は評価しないと分からない(末尾なら存在は確定)
|
|
7477
|
+
return i === segments.length - 1 ? EXISTS : UNKNOWN;
|
|
7478
|
+
}
|
|
7479
|
+
current = descriptor.value;
|
|
7480
|
+
}
|
|
7481
|
+
return EXISTS;
|
|
7482
|
+
}
|
|
7483
|
+
/** 診断 code は lint / IDE と同一語彙(errorGuidance.ts の三面共有規約) */
|
|
7484
|
+
const DIAGNOSTIC_CODE = {
|
|
7485
|
+
binding: "wcs/binding-path-missing",
|
|
7486
|
+
watch: "wcs/watch-path-missing",
|
|
7487
|
+
};
|
|
7488
|
+
const SUBJECT = {
|
|
7489
|
+
binding: "Bound path",
|
|
7490
|
+
watch: "$watch path",
|
|
7491
|
+
};
|
|
7492
|
+
/**
|
|
7493
|
+
* ルート直下(単一セグメント)のパスが state に無いときのエラーメッセージ。
|
|
7494
|
+
*
|
|
7495
|
+
* この形だけは親アドレスを辿れないので読み取りが throw する = 元から loud だが、
|
|
7496
|
+
* 文面が `address.parentAddress is undefined path: cout` という内部実装の言葉で、
|
|
7497
|
+
* 「打ち間違い」だと分からず did-you-mean も lint 誘導も無かった。深いパスの
|
|
7498
|
+
* `console.warn` と同じ語彙に揃える。
|
|
7499
|
+
*/
|
|
7500
|
+
function missingRootPathMessage(stateName, path, target, declaredPaths) {
|
|
7501
|
+
return `[${DIAGNOSTIC_CODE.binding}] Path "${path}" does not exist on state "${stateName}".` +
|
|
7502
|
+
`${didYouMean(path, collectCandidates(target, "", declaredPaths))}${LINT_HINT}`;
|
|
7503
|
+
}
|
|
7504
|
+
/**
|
|
7505
|
+
* `$resolve` / `$getAll` に渡した添字の本数がワイルドカードの本数と噛み合わない。
|
|
7506
|
+
*
|
|
7507
|
+
* 不足(`$resolve`)は元から throw していたが、**超過は両 API とも黙って無視**され、
|
|
7508
|
+
* 取り違えた添字のまま「もっともらしい値」を返していた。本数はパス文字列から
|
|
7509
|
+
* 決まるので、噛み合わないことは常にプログラマのミス。
|
|
7510
|
+
*/
|
|
7511
|
+
function indexArityMessage(api, path, wildcardCount, actual) {
|
|
7512
|
+
const requirement = api === "$resolve"
|
|
7513
|
+
? `exactly ${wildcardCount}`
|
|
7514
|
+
: `at most ${wildcardCount}`;
|
|
7515
|
+
return `[wcs/index-arity] ${api}("${path}") requires ${requirement} index(es) ` +
|
|
7516
|
+
`("*" appears ${wildcardCount} time(s) in the path) but got ${actual}.${LINT_HINT}`;
|
|
7517
|
+
}
|
|
7518
|
+
/**
|
|
7519
|
+
* ワイルドカードを解決するループ文脈が足りない(=パスの階数 > スコープの階数)。
|
|
7520
|
+
*
|
|
7521
|
+
* `matrix.*.*` を 1 段の `for` の中で読む、`$2` を 1 段のループの中で読む、といった
|
|
7522
|
+
* 取り違えがこれ。元の文面は `address.listIndex?.index is undefined path: matrix.*` /
|
|
7523
|
+
* `Index not found at position 1 for loopContext:` という内部実装の言葉で、
|
|
7524
|
+
* **何を間違えたのかが書かれていなかった**。
|
|
7525
|
+
*/
|
|
7526
|
+
function wildcardScopeMessage(subject, needed, available) {
|
|
7527
|
+
return `[wcs/wildcard-rank] ${subject} needs ${needed} enclosing loop level(s) but the current ` +
|
|
7528
|
+
`scope provides ${available}. Wrap it in that many "for" templates, or use $resolve(path, indexes) ` +
|
|
7529
|
+
`to name the row explicitly.${LINT_HINT}`;
|
|
7530
|
+
}
|
|
7531
|
+
/** 同じ (state 要素, パス) の報告は 1 回だけにする台帳 */
|
|
7532
|
+
const reportedPathsByStateElement = new WeakMap();
|
|
7533
|
+
function alreadyReported(stateElement, path) {
|
|
7534
|
+
let reported = reportedPathsByStateElement.get(stateElement);
|
|
7535
|
+
if (typeof reported === "undefined") {
|
|
7536
|
+
reported = new Set();
|
|
7537
|
+
reportedPathsByStateElement.set(stateElement, reported);
|
|
7538
|
+
}
|
|
7539
|
+
if (reported.has(path)) {
|
|
7540
|
+
return true;
|
|
7541
|
+
}
|
|
7542
|
+
reported.add(path);
|
|
7543
|
+
return false;
|
|
7544
|
+
}
|
|
7545
|
+
/**
|
|
7546
|
+
* バインド確立時 / `$watch` 宣言時にパスの存在を検査し、確実に存在しないものだけ報告する。
|
|
7547
|
+
*
|
|
7548
|
+
* 報告は `console.warn` に留める(`raiseError` にしない):
|
|
7549
|
+
* 判定は過小近似とはいえ動的にキーが生える形まで排除できたわけではなく、
|
|
7550
|
+
* 既存ページを起動不能にする代償に見合わない。silent を破ることが目的であり、
|
|
7551
|
+
* 停止させることではない。
|
|
7552
|
+
*/
|
|
7553
|
+
function checkDeclaredPath(stateElement, state, path, source) {
|
|
7554
|
+
if (source === "internal" || typeof state === "undefined") {
|
|
7555
|
+
return;
|
|
7556
|
+
}
|
|
7557
|
+
// `$command` / `$streamStatus` / `$1` 等の予約名前空間は raw state に実体を持たない
|
|
7558
|
+
if (path.startsWith("$")) {
|
|
7559
|
+
return;
|
|
7560
|
+
}
|
|
7561
|
+
// mapped な bind-component の子スコープはパスの正本を持たない(親側で解決される)
|
|
7562
|
+
if (stateElement.hasMappedComponentState === true) {
|
|
7563
|
+
return;
|
|
7564
|
+
}
|
|
7565
|
+
// 単一セグメントのバインディングは読み取り時に raiseError で loud に落ちるので、
|
|
7566
|
+
// ここで二重に報告しない。`$watch` は落ちずに黙って発火しないだけなので検査する
|
|
7567
|
+
const segments = getPathInfo(path).segments;
|
|
7568
|
+
if (source === "binding" && segments.length < 2) {
|
|
7569
|
+
return;
|
|
7570
|
+
}
|
|
7571
|
+
if (alreadyReported(stateElement, path)) {
|
|
7572
|
+
return;
|
|
7573
|
+
}
|
|
7574
|
+
const result = resolvePathExistence(state, path, stateElement.getterPaths);
|
|
7575
|
+
if (result.existence !== "missing") {
|
|
7576
|
+
return;
|
|
7577
|
+
}
|
|
7578
|
+
// 接頭辞は raiseError と同じ `[@wcstack/state] [wcs/...]` の並び(コンソールの
|
|
7579
|
+
// grep 単位をパッケージで揃える)
|
|
7580
|
+
console.warn(`[@wcstack/state] [${DIAGNOSTIC_CODE[source]}] ${SUBJECT[source]} "${path}" does not resolve on state "${stateElement.name}": ` +
|
|
7581
|
+
`"${result.missingSegment}" is not declared.${didYouMean(result.missingSegment, result.candidates)}` +
|
|
7582
|
+
` Updates to this path will be silently dropped.${LINT_HINT}`);
|
|
7583
|
+
if (devtoolsSink !== null) {
|
|
7584
|
+
devtoolsSink({
|
|
7585
|
+
type: "state:path-unresolved",
|
|
7586
|
+
source,
|
|
7587
|
+
stateName: stateElement.name,
|
|
7588
|
+
path,
|
|
7589
|
+
missingSegment: result.missingSegment,
|
|
7590
|
+
});
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
|
|
7332
7594
|
// indexName ... $1, $2, ...
|
|
7333
7595
|
function getIndexValueByLoopContext(loopContext, indexName) {
|
|
7334
7596
|
if (loopContext.listIndex === null) {
|
|
@@ -7340,7 +7602,10 @@ function getIndexValueByLoopContext(loopContext, indexName) {
|
|
|
7340
7602
|
}
|
|
7341
7603
|
const listIndex = listIndexAtWildcard(loopContext.listIndex, indexPos, loopContext.pathInfo.wildcardCount);
|
|
7342
7604
|
if (listIndex === null) {
|
|
7343
|
-
|
|
7605
|
+
// 位置が範囲外 = `$2` を 1 段のループの中で読んだ、という取り違え。
|
|
7606
|
+
// 元の文面(`Index not found at position 1 for loopContext:`)は内部の言葉で、
|
|
7607
|
+
// 何段必要で何段あるのかが書かれていなかった。
|
|
7608
|
+
raiseError(wildcardScopeMessage(`"${indexName}"`, indexPos + 1, loopContext.pathInfo.wildcardCount));
|
|
7344
7609
|
}
|
|
7345
7610
|
return listIndex.index;
|
|
7346
7611
|
}
|
|
@@ -7392,7 +7657,7 @@ function scheduleDeferredApply(binding, tagName) {
|
|
|
7392
7657
|
return;
|
|
7393
7658
|
}
|
|
7394
7659
|
// Compatibility fallback for direct applyChange() callers outside a session.
|
|
7395
|
-
const registry = getCustomElementRegistry();
|
|
7660
|
+
const registry = getCustomElementRegistry(binding.replaceNode);
|
|
7396
7661
|
if (registry === null) {
|
|
7397
7662
|
scheduledBindings.delete(binding);
|
|
7398
7663
|
reportFailure(tagName, new Error("CustomElementRegistry is unavailable."));
|
|
@@ -7536,7 +7801,7 @@ function applyChange(binding, context) {
|
|
|
7536
7801
|
if (definedApplyVerifiedByBinding.get(binding) !== true) {
|
|
7537
7802
|
const customTag = getCustomElement(binding.replaceNode);
|
|
7538
7803
|
if (customTag) {
|
|
7539
|
-
if (getCustomElementRegistry()?.get(customTag) === undefined) {
|
|
7804
|
+
if (getCustomElementRegistry(binding.replaceNode)?.get(customTag) === undefined) {
|
|
7540
7805
|
// 未 define のカスタム要素へは今は適用できない(accessor 未確立の要素に
|
|
7541
7806
|
// 素の own property を書くと upgrade 後に class accessor を隠してしまう)。
|
|
7542
7807
|
// whenDefined 後に最新 state 値で再適用する(two-way attach / deferred
|
|
@@ -7587,6 +7852,26 @@ function applyChange(binding, context) {
|
|
|
7587
7852
|
}
|
|
7588
7853
|
}
|
|
7589
7854
|
|
|
7855
|
+
/**
|
|
7856
|
+
* バインディング 1 本の適用失敗を報告する(握り潰しではない)。
|
|
7857
|
+
*
|
|
7858
|
+
* `console.error` だけだと devtools からは「静かに握られた失敗」が見えないため、
|
|
7859
|
+
* 同じ地点から sink にも流す(`state:watch-error` と同じ位置づけ)。
|
|
7860
|
+
* 値と DOM は巻き戻さない — 伝播 hop 上限超過・watch 連鎖打ち切りと同じ姿勢。
|
|
7861
|
+
*/
|
|
7862
|
+
function reportBindingApplyError(binding, error) {
|
|
7863
|
+
console.error(`[@wcstack/state] binding "${binding.bindingType}: ${binding.statePathName}" failed to apply; ` +
|
|
7864
|
+
`the rest of this batch continues.`, { node: binding.node, error });
|
|
7865
|
+
if (devtoolsSink !== null) {
|
|
7866
|
+
devtoolsSink({
|
|
7867
|
+
type: "state:binding-apply-error",
|
|
7868
|
+
stateName: binding.stateName,
|
|
7869
|
+
path: binding.statePathName,
|
|
7870
|
+
bindingType: binding.bindingType,
|
|
7871
|
+
error,
|
|
7872
|
+
});
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7590
7875
|
/**
|
|
7591
7876
|
* バインディング情報の配列を処理し、各バインディングに対して状態の変更を適用する。
|
|
7592
7877
|
*
|
|
@@ -7642,7 +7927,16 @@ function applyChangeFromBindings(bindings, propagationContextByBinding) {
|
|
|
7642
7927
|
propagationContextByBinding: propagationContextByBinding,
|
|
7643
7928
|
};
|
|
7644
7929
|
do {
|
|
7645
|
-
|
|
7930
|
+
// 1 本の失敗を 1 本に閉じ込める(§ エラー隔離)。隔離しないと、stale な
|
|
7931
|
+
// アドレスを読んだ 1 本の throw がバッチの残り・$updatedCallback・drain
|
|
7932
|
+
// リスナー($watch / $streams restart)まで道連れにし、「値は新しいのに
|
|
7933
|
+
// DOM は途中まで」という再現困難な半端状態を作る。
|
|
7934
|
+
try {
|
|
7935
|
+
applyChange(binding, context);
|
|
7936
|
+
}
|
|
7937
|
+
catch (error) {
|
|
7938
|
+
reportBindingApplyError(binding, error);
|
|
7939
|
+
}
|
|
7646
7940
|
bindingIndex++;
|
|
7647
7941
|
const nextBindingInfo = bindings[bindingIndex];
|
|
7648
7942
|
if (!nextBindingInfo)
|
|
@@ -7658,7 +7952,12 @@ function applyChangeFromBindings(bindings, propagationContextByBinding) {
|
|
|
7658
7952
|
// applyChangeToProperty は propagationContextByBinding 以外の context を
|
|
7659
7953
|
// 参照しないため、遅延分は最小 context を渡す
|
|
7660
7954
|
for (const { binding, value } of deferredSelectBindings) {
|
|
7661
|
-
|
|
7955
|
+
try {
|
|
7956
|
+
applyChangeToProperty(binding, { propagationContextByBinding }, value);
|
|
7957
|
+
}
|
|
7958
|
+
catch (error) {
|
|
7959
|
+
reportBindingApplyError(binding, error);
|
|
7960
|
+
}
|
|
7662
7961
|
}
|
|
7663
7962
|
for (const [absAddress, newListValue] of newListValueByAbsAddress.entries()) {
|
|
7664
7963
|
setLastListValueByAbsoluteStateAddress(absAddress, newListValue);
|
|
@@ -8109,7 +8408,13 @@ function collectStructuralFragments(rootNode, walkRoot, forPath) {
|
|
|
8109
8408
|
async function waitForStateInitialize(root) {
|
|
8110
8409
|
const elements = root.querySelectorAll(config.tagNames.state);
|
|
8111
8410
|
const promises = [];
|
|
8112
|
-
|
|
8411
|
+
const registry = getCustomElementRegistry(root);
|
|
8412
|
+
if (registry === null) {
|
|
8413
|
+
// null レジストリのサブツリーでは <wcs-state> が upgrade されないので
|
|
8414
|
+
// initializePromise が生えず、待っても永久に初期化されない。
|
|
8415
|
+
raiseError(`CustomElementRegistry is unavailable for <${config.tagNames.state}>.`);
|
|
8416
|
+
}
|
|
8417
|
+
await registry.whenDefined(config.tagNames.state);
|
|
8113
8418
|
for (const element of elements) {
|
|
8114
8419
|
// Light DOM の mapped コンポーネントの state は待たない。それはこの root の
|
|
8115
8420
|
// バインディングが張られてからでないと初期化できず(自分を束ねるホスト binding を
|
|
@@ -8148,7 +8453,7 @@ async function buildBindings(root) {
|
|
|
8148
8453
|
}
|
|
8149
8454
|
}
|
|
8150
8455
|
|
|
8151
|
-
var version = "1.
|
|
8456
|
+
var version = "1.31.0";
|
|
8152
8457
|
var pkg = {
|
|
8153
8458
|
version: version};
|
|
8154
8459
|
|
|
@@ -9166,17 +9471,27 @@ class Updater {
|
|
|
9166
9471
|
}
|
|
9167
9472
|
}
|
|
9168
9473
|
}
|
|
9169
|
-
// context が無い場合は従来どおり 1 引数で呼ぶ(呼び出し契約の互換維持)
|
|
9170
|
-
if (propagationContextByBinding.size > 0) {
|
|
9171
|
-
applyChangeFromBindings(processBindings, propagationContextByBinding);
|
|
9172
|
-
}
|
|
9173
|
-
else {
|
|
9174
|
-
applyChangeFromBindings(processBindings);
|
|
9175
|
-
}
|
|
9176
9474
|
// drain 終了フック: binding 適用後に dedup 済みバッチを通知する(設計書 §3-2)。
|
|
9177
9475
|
// testApplyChange も同じ _applyChange を通るため、テストから同期に駆動できる。
|
|
9178
9476
|
// quarantine された address も state 値は適用済みのため通知対象に含める。
|
|
9179
|
-
|
|
9477
|
+
//
|
|
9478
|
+
// try/finally なのは、適用側が throw しても `$watch` / `$streams` restart を
|
|
9479
|
+
// 落とさないため。binding 1 本の失敗は applyChangeFromBindings が隔離するので
|
|
9480
|
+
// ここへ来るのは $updatedCallback の throw(契約どおり loud に伝播させる)等に
|
|
9481
|
+
// 限られるが、そのとき drain フックまで道連れにすると「機構間の順序は固定」
|
|
9482
|
+
// (README の 3 層表)が黙って破れる。例外は握らない = 伝播は維持する。
|
|
9483
|
+
try {
|
|
9484
|
+
// context が無い場合は従来どおり 1 引数で呼ぶ(呼び出し契約の互換維持)
|
|
9485
|
+
if (propagationContextByBinding.size > 0) {
|
|
9486
|
+
applyChangeFromBindings(processBindings, propagationContextByBinding);
|
|
9487
|
+
}
|
|
9488
|
+
else {
|
|
9489
|
+
applyChangeFromBindings(processBindings);
|
|
9490
|
+
}
|
|
9491
|
+
}
|
|
9492
|
+
finally {
|
|
9493
|
+
notifyUpdateBatchListeners(new Set(contextByAbsoluteAddress.keys()));
|
|
9494
|
+
}
|
|
9180
9495
|
}
|
|
9181
9496
|
}
|
|
9182
9497
|
const updater = new Updater();
|
|
@@ -9470,6 +9785,12 @@ function createStateElementSummary(element) {
|
|
|
9470
9785
|
// protocol v1 追補(additive)— 配線カバレッジの宣言面(設計 §4)。
|
|
9471
9786
|
// 旧 IStateElement 実装(optional)では undefined になりうるため null に畳む。
|
|
9472
9787
|
watchPaths: element.watchPaths ?? null,
|
|
9788
|
+
// ワイルドカード行 watch の発火前提「for バインド or $listKeys 宣言」の後者。
|
|
9789
|
+
// ListKeyMap のキー(リストパス)だけを出す — キー指定(文字列/関数)は
|
|
9790
|
+
// カバレッジ判定に不要で、関数はシリアライズ境界にも載せない。
|
|
9791
|
+
keyedListPaths: element.listKeys === undefined || element.listKeys === null
|
|
9792
|
+
? null
|
|
9793
|
+
: new Set(element.listKeys.keys()),
|
|
9473
9794
|
};
|
|
9474
9795
|
}
|
|
9475
9796
|
function requireStateElement(name, rootNode) {
|
|
@@ -10982,7 +11303,9 @@ function processWatchDeclaration(stateElement, state) {
|
|
|
10982
11303
|
paths.add(path);
|
|
10983
11304
|
// 依存グラフ登録(§8)。"for" 以外の bindingType は親 → 子の staticDependency
|
|
10984
11305
|
// チェーンを生やすだけで listPaths / elementPaths を触らない(State.setPathInfo 参照)。
|
|
10985
|
-
|
|
11306
|
+
// source="watch" は存在検査の診断 code を `wcs/watch-path-missing` に切り替える
|
|
11307
|
+
// (watch キーの miss は raiseError にも掛からず、黙って発火しないだけになる)。
|
|
11308
|
+
stateElement.setPathInfo(path, "prop", "watch");
|
|
10986
11309
|
}
|
|
10987
11310
|
setWatchEntries(stateElement, entries);
|
|
10988
11311
|
return paths.size > 0 ? paths : null;
|
|
@@ -11573,11 +11896,18 @@ function defineDCC(hostElement, shadowRoot, state) {
|
|
|
11573
11896
|
if (!tagName.includes("-")) {
|
|
11574
11897
|
raiseError(`DCC: "${tagName}" is not a valid custom element name (must contain a hyphen).`);
|
|
11575
11898
|
}
|
|
11576
|
-
|
|
11899
|
+
// 定義先は「定義元ホストを支配するレジストリ」。scoped registry を持つツリーで
|
|
11900
|
+
// global に define すると、その定義は自分の兄弟にすら適用されない。
|
|
11901
|
+
const definitionRegistry = getCustomElementRegistry(hostElement);
|
|
11902
|
+
if (definitionRegistry === null || typeof definitionRegistry.define !== "function") {
|
|
11903
|
+
raiseError(`DCC: CustomElementRegistry is unavailable for "${tagName}".`);
|
|
11904
|
+
}
|
|
11905
|
+
if (definitionRegistry.get(tagName)) {
|
|
11577
11906
|
// 重複定義は authoring error として落とす。従来は warn してスキップしていたが、
|
|
11578
11907
|
// 先勝ちで別テンプレートのインスタンスが生えるため「動いているように見えて中身が違う」
|
|
11579
11908
|
// 状態になる。state 名の重複(stateElementByName)が raiseError なのと作法を揃える
|
|
11580
11909
|
// (docs/architecture-hardening/15-state-component-mechanism-consistency.md §3.4)。
|
|
11910
|
+
// 一意性はレジストリ単位なので、別スコープの同名 DCC は衝突しない。
|
|
11581
11911
|
raiseError(`DCC: "${tagName}" is already registered. A custom element name can only be defined once.`);
|
|
11582
11912
|
}
|
|
11583
11913
|
// ShadowRoot は cloneNode 不可のため、template 経由で内容をクローン
|
|
@@ -11632,7 +11962,7 @@ function defineDCC(hostElement, shadowRoot, state) {
|
|
|
11632
11962
|
// カスタム要素として upgrade されていない。ホストが接続済みなら appendChild の時点で
|
|
11633
11963
|
// upgrade されるが、未接続の shadow に挿した場合は upgrade 契機が無く、内側の
|
|
11634
11964
|
// <wcs-state> が素の HTMLElement のまま残って createState が生えない。明示的に upgrade する。
|
|
11635
|
-
const registry = getCustomElementRegistry();
|
|
11965
|
+
const registry = getCustomElementRegistry(this._shadow);
|
|
11636
11966
|
if (registry !== null) {
|
|
11637
11967
|
upgradeCustomElement(registry, this._shadow);
|
|
11638
11968
|
}
|
|
@@ -11699,7 +12029,7 @@ function defineDCC(hostElement, shadowRoot, state) {
|
|
|
11699
12029
|
});
|
|
11700
12030
|
}
|
|
11701
12031
|
// カスタム要素登録
|
|
11702
|
-
|
|
12032
|
+
definitionRegistry.define(tagName, DCCElement);
|
|
11703
12033
|
}
|
|
11704
12034
|
|
|
11705
12035
|
/**
|
|
@@ -12071,7 +12401,10 @@ function _getByAddress(target, address, receiver, handler, stateElement) {
|
|
|
12071
12401
|
}
|
|
12072
12402
|
}
|
|
12073
12403
|
else {
|
|
12074
|
-
|
|
12404
|
+
// 親アドレスが無い = 単一セグメントのパスが state に存在しない。ここは元から
|
|
12405
|
+
// throw していたが、文面が内部実装の言葉だったので打ち間違いだと分からなかった。
|
|
12406
|
+
// 深いパスの console.warn(pathDiagnostics.checkDeclaredPath)と語彙を揃える。
|
|
12407
|
+
const parentAddress = address.parentAddress ?? raiseError(missingRootPathMessage(stateElement.name, address.pathInfo.path, target, stateElement.getterPaths));
|
|
12075
12408
|
const parentValue = getByAddress(target, parentAddress, receiver, handler);
|
|
12076
12409
|
// 親が居ないパスの読みは undefined(=「state に意見が無い」)。`Reflect.get` に
|
|
12077
12410
|
// そのまま渡すと生の `TypeError: Reflect.get called on non-object` になり、
|
|
@@ -12089,7 +12422,11 @@ function _getByAddress(target, address, receiver, handler, stateElement) {
|
|
|
12089
12422
|
}
|
|
12090
12423
|
const lastSegment = address.pathInfo.segments[address.pathInfo.segments.length - 1];
|
|
12091
12424
|
if (lastSegment === WILDCARD) {
|
|
12092
|
-
|
|
12425
|
+
// listIndex が無いまま末尾ワイルドカードに到達 = そのパスの階数を満たす
|
|
12426
|
+
// ループ文脈が無い(`matrix.*.*` を 1 段の `for` の中で読む等)。元の文面は
|
|
12427
|
+
// 内部の言葉(address.listIndex?.index is undefined)で、何段必要なのかが
|
|
12428
|
+
// 書かれていなかった(pathDiagnostics.ts)。
|
|
12429
|
+
const index = address.listIndex?.index ?? raiseError(wildcardScopeMessage(`path "${address.pathInfo.path}"`, address.pathInfo.wildcardCount, address.listIndex?.length ?? 0));
|
|
12093
12430
|
return Reflect.get(parentValue, index);
|
|
12094
12431
|
}
|
|
12095
12432
|
else {
|
|
@@ -12885,7 +13222,8 @@ function _setByAddress(target, address, absAddress, value, receiver, handler, ke
|
|
|
12885
13222
|
const parentValue = getByAddress(target, parentAddress, receiver, handler);
|
|
12886
13223
|
const lastSegment = address.pathInfo.segments[address.pathInfo.segments.length - 1];
|
|
12887
13224
|
if (lastSegment === WILDCARD) {
|
|
12888
|
-
|
|
13225
|
+
// 読み取り側(getByAddress)と同じ取り違え。書き込みでも何段必要かを言う。
|
|
13226
|
+
const index = address.listIndex?.index ?? raiseError(wildcardScopeMessage(`path "${address.pathInfo.path}"`, address.pathInfo.wildcardCount, address.listIndex?.length ?? 0));
|
|
12889
13227
|
return Reflect.set(parentValue, index, value);
|
|
12890
13228
|
}
|
|
12891
13229
|
else {
|
|
@@ -13054,7 +13392,9 @@ function setByAddressCore(target, address, value, receiver, handler, keyedMergeP
|
|
|
13054
13392
|
recordWatchPrevValue(stateElement, path, absAddress, devOldValue, devHasOldValue);
|
|
13055
13393
|
try {
|
|
13056
13394
|
if (key === undefined) {
|
|
13057
|
-
|
|
13395
|
+
// fast path 版の同じ取り違え(末尾ワイルドカードに listIndex が無い)。
|
|
13396
|
+
// 通常経路と同じ語彙で「何段必要か」を言う(pathDiagnostics.ts)。
|
|
13397
|
+
raiseError(wildcardScopeMessage(`path "${path}"`, address.pathInfo.wildcardCount, address.listIndex?.length ?? 0));
|
|
13058
13398
|
}
|
|
13059
13399
|
return Reflect.set(parentValue, key, value);
|
|
13060
13400
|
}
|
|
@@ -13153,8 +13493,12 @@ function resolve(target, _prop, receiver, handler) {
|
|
|
13153
13493
|
}
|
|
13154
13494
|
}
|
|
13155
13495
|
}
|
|
13156
|
-
|
|
13157
|
-
|
|
13496
|
+
// 添字の本数はワイルドカードの本数と**厳密に一致**する必要がある。
|
|
13497
|
+
// 不足は元から throw していたが、超過は黙って無視されていた(余分な要素を
|
|
13498
|
+
// 誰も読まないため)= `$resolve("items.*.price", [row, col])` のような
|
|
13499
|
+
// 「1 本しか無いのに 2 本渡す」取り違えが、間違った値を返したまま通っていた。
|
|
13500
|
+
if (indexes.length !== pathInfo.wildcardParentPathInfos.length) {
|
|
13501
|
+
raiseError(indexArityMessage("$resolve", path, pathInfo.wildcardParentPathInfos.length, indexes.length));
|
|
13158
13502
|
}
|
|
13159
13503
|
// ワイルドカード階層ごとにListIndexを解決していく
|
|
13160
13504
|
let listIndex = null;
|
|
@@ -13207,6 +13551,14 @@ function getAll(target, prop, receiver, handler) {
|
|
|
13207
13551
|
}
|
|
13208
13552
|
}
|
|
13209
13553
|
}
|
|
13554
|
+
// 明示的に渡された添字だけを検査する。`$getAll` の添字は**前方一致の接頭辞**で、
|
|
13555
|
+
// 足りない分は「その階層を全部展開する」という正しい意味を持つ(README の
|
|
13556
|
+
// `$getAll("scores.*", [])` がこれ)。一方**超過は意味を持たず黙って捨てられ**、
|
|
13557
|
+
// ワイルドカードの本数を取り違えたまま部分集合が返っていた。
|
|
13558
|
+
// 省略時に下で導出する添字は文脈由来なので、この検査には掛けない。
|
|
13559
|
+
if (typeof indexes !== "undefined" && indexes.length > pathInfo.wildcardParentPathInfos.length) {
|
|
13560
|
+
raiseError(indexArityMessage("$getAll", path, pathInfo.wildcardParentPathInfos.length, indexes.length));
|
|
13561
|
+
}
|
|
13210
13562
|
if (typeof indexes === "undefined") {
|
|
13211
13563
|
for (let i = 0; i < pathInfo.wildcardParentPathInfos.length; i++) {
|
|
13212
13564
|
const wildcardPattern = pathInfo.wildcardParentPathInfos[i];
|
|
@@ -13713,6 +14065,8 @@ function set(target, prop, value, receiver, handler) {
|
|
|
13713
14065
|
}
|
|
13714
14066
|
}
|
|
13715
14067
|
|
|
14068
|
+
/** 循環報告に載せるスタック末尾の段数(当事者が見える最小限) */
|
|
14069
|
+
const CYCLE_REPORT_DEPTH = 8;
|
|
13716
14070
|
class StateHandler {
|
|
13717
14071
|
_stateElement;
|
|
13718
14072
|
_stateName;
|
|
@@ -13753,12 +14107,36 @@ class StateHandler {
|
|
|
13753
14107
|
return this._loopContext;
|
|
13754
14108
|
}
|
|
13755
14109
|
pushAddress(address) {
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
14110
|
+
// 上限判定は **increment より前**に行う。後にすると、深さ超過で throw した時点で
|
|
14111
|
+
// `_addressStackIndex` だけが進み `_addressStack[index]` は未代入のまま残る。
|
|
14112
|
+
// 呼び出し側(getByAddress)は `pushAddress` を try の外で呼ぶので自分では pop
|
|
14113
|
+
// しないが、外側フレームの finally が順に pop していき、その 1 本目が未代入の枠を
|
|
14114
|
+
// 引いて `Address stack at index N is undefined.` を投げる = **本来の
|
|
14115
|
+
// 「無限ループの疑い」という診断が巻き戻しの最中に上書きされて消える**。
|
|
14116
|
+
// getter の相互参照(`get a(){return this.b}` / `get b(){return this.a}`)は
|
|
14117
|
+
// 実際にこれを踏み、原因と無関係な文面だけが残っていた。
|
|
14118
|
+
if (this._addressStackIndex + 1 >= MAX_LOOP_DEPTH) {
|
|
14119
|
+
raiseError(`Exceeded maximum address stack depth of ${MAX_LOOP_DEPTH}. ` +
|
|
14120
|
+
`Possible circular dependency between path getters: ${this._describeAddressCycle()}`);
|
|
13759
14121
|
}
|
|
14122
|
+
this._addressStackIndex++;
|
|
13760
14123
|
this._addressStack[this._addressStackIndex] = address;
|
|
13761
14124
|
}
|
|
14125
|
+
/**
|
|
14126
|
+
* スタック末尾の繰り返し区間をパス名で示す(循環の当事者だけを見せる)。
|
|
14127
|
+
* 上限に達したときのみ呼ばれるので、コストは異常系に閉じている。
|
|
14128
|
+
*/
|
|
14129
|
+
_describeAddressCycle() {
|
|
14130
|
+
const paths = [];
|
|
14131
|
+
for (let i = this._addressStackIndex; i >= 0 && paths.length < CYCLE_REPORT_DEPTH; i--) {
|
|
14132
|
+
const entry = this._addressStack[i];
|
|
14133
|
+
if (entry) {
|
|
14134
|
+
paths.push(entry.pathInfo.path);
|
|
14135
|
+
}
|
|
14136
|
+
}
|
|
14137
|
+
const unique = Array.from(new Set(paths));
|
|
14138
|
+
return `${unique.reverse().join(" -> ")} -> ...`;
|
|
14139
|
+
}
|
|
13762
14140
|
popAddress() {
|
|
13763
14141
|
if (this._addressStackIndex < 0) {
|
|
13764
14142
|
return null;
|
|
@@ -14347,7 +14725,13 @@ class State extends HTMLElementBase {
|
|
|
14347
14725
|
raiseError(`"bind-component" cannot be combined with ${conflicting.join(", ")}. The component's "${this.getAttribute("bind-component")}" property is the only state source.`);
|
|
14348
14726
|
}
|
|
14349
14727
|
const boundComponentStateProp = this.getAttribute("bind-component");
|
|
14350
|
-
|
|
14728
|
+
const componentRegistry = getCustomElementRegistry(boundComponent);
|
|
14729
|
+
if (componentRegistry === null) {
|
|
14730
|
+
// null レジストリのサブツリーではホストは永久に upgrade されない。
|
|
14731
|
+
// whenDefined を待つと無言でウェッジするので落とす。
|
|
14732
|
+
raiseError(`CustomElementRegistry is unavailable for <${customTagName}>.`);
|
|
14733
|
+
}
|
|
14734
|
+
await componentRegistry.whenDefined(customTagName.toLowerCase());
|
|
14351
14735
|
// data-wcs属性がある場合は、上位の状態によりbinding情報の設定が完了するまで待機する
|
|
14352
14736
|
if (boundComponent.hasAttribute(config.bindAttributeName)) {
|
|
14353
14737
|
await waitInitializeBinding(boundComponent);
|
|
@@ -14721,7 +15105,7 @@ class State extends HTMLElementBase {
|
|
|
14721
15105
|
addStaticDependency(sourcePath, targetPath) {
|
|
14722
15106
|
return this._addDependency(this._staticDependency, sourcePath, targetPath);
|
|
14723
15107
|
}
|
|
14724
|
-
setPathInfo(path, bindingType) {
|
|
15108
|
+
setPathInfo(path, bindingType, source = "binding") {
|
|
14725
15109
|
if (bindingType === "for") {
|
|
14726
15110
|
const isNewListPath = !this._listPaths.has(path);
|
|
14727
15111
|
this._listPaths.add(path);
|
|
@@ -14736,6 +15120,10 @@ class State extends HTMLElementBase {
|
|
|
14736
15120
|
if (!this._pathSet.has(path)) {
|
|
14737
15121
|
const pathInfo = getPathInfo(path);
|
|
14738
15122
|
this._pathSet.add(path);
|
|
15123
|
+
// 存在しないパスへの配線は「黙って更新されない」だけで終わるため、
|
|
15124
|
+
// 新規パスを 1 回だけ検査して確実な miss を報告する(pathDiagnostics.ts)。
|
|
15125
|
+
// パスごとに 1 回・バインド確立時のみで、更新のホットパスには乗らない。
|
|
15126
|
+
checkDeclaredPath(this, this.__state, path, source);
|
|
14739
15127
|
if (pathInfo.parentPath !== null) {
|
|
14740
15128
|
let currentPathInfo = pathInfo;
|
|
14741
15129
|
while (currentPathInfo.parentPath !== null) {
|
|
@@ -14797,20 +15185,25 @@ class State extends HTMLElementBase {
|
|
|
14797
15185
|
}
|
|
14798
15186
|
}
|
|
14799
15187
|
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
|
|
15188
|
+
/**
|
|
15189
|
+
* Register this package's tags. Pass a scoped `CustomElementRegistry` to define
|
|
15190
|
+
* them for a single shadow tree -- scoped registries do not inherit the global
|
|
15191
|
+
* one, so a tree using one needs its own definitions.
|
|
15192
|
+
*/
|
|
15193
|
+
function registerComponents(registry = customElements) {
|
|
15194
|
+
if (!registry.get(config.tagNames.ssr)) {
|
|
15195
|
+
registry.define(config.tagNames.ssr, Ssr);
|
|
14803
15196
|
}
|
|
14804
|
-
if (!
|
|
14805
|
-
|
|
15197
|
+
if (!registry.get(config.tagNames.state)) {
|
|
15198
|
+
registry.define(config.tagNames.state, State);
|
|
14806
15199
|
}
|
|
14807
15200
|
}
|
|
14808
15201
|
|
|
14809
|
-
function bootstrapState(config) {
|
|
15202
|
+
function bootstrapState(config, registry) {
|
|
14810
15203
|
if (config) {
|
|
14811
15204
|
setConfig(config);
|
|
14812
15205
|
}
|
|
14813
|
-
registerComponents();
|
|
15206
|
+
registerComponents(registry);
|
|
14814
15207
|
// DevTools Hook Protocol への source 登録(SSR では no-op・冪等)
|
|
14815
15208
|
registerDevtoolsSource();
|
|
14816
15209
|
}
|