@qilin-agent/client-ui-slots 0.0.0-stage → 3.1.4

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/lib/index.js ADDED
@@ -0,0 +1,587 @@
1
+ //#region lib/types/renderer.js
2
+ /**
3
+ * Convert one standard source name to its rendered Hook prop name.
4
+ * @param name - registered fixed or keyed source name.
5
+ * @returns the `use<Name>` prop exposed to Slot components.
6
+ */
7
+ function standardHookPropName(name) {
8
+ return `use${name[0]?.toUpperCase() ?? ""}${name.slice(1)}`;
9
+ }
10
+ /** Thrown when a retained renderSlot binding is invoked after its declaring entry was disposed. */
11
+ var StaleAuthorizationError = class extends Error {};
12
+ /**
13
+ * Thrown when a renderSlot binding is invoked for a key outside its entry's
14
+ * children declaration (plain-JS backstop; typed callers are narrowed
15
+ * statically).
16
+ */
17
+ var SlotOwnershipError = class extends Error {};
18
+ //#endregion
19
+ //#region lib/types/index.js
20
+ /**
21
+ * Resolve a possibly-thunked list label at read time (thunks follow the
22
+ * active locale; owners projecting ledger rows call this instead of reading
23
+ * `options.label` raw).
24
+ * @param label - the stored label.
25
+ * @returns the display string, or undefined when the entry declared none.
26
+ */
27
+ function resolveSlotLabel(label) {
28
+ return typeof label === "function" ? label() : label;
29
+ }
30
+ const NO_ENTRIES = Object.freeze([]);
31
+ /**
32
+ * Pure slot registry (no cordis; event emission and the renderer installation contract
33
+ * live in the runtime Service wrapper).
34
+ *
35
+ * The 'root' slot is the one a-priori declaration, seeded at construction
36
+ * (single/root, declared by the framework) — the render tree's root hole.
37
+ *
38
+ * Change propagation contract: versions bump and {@link SlotCore.onMutate}
39
+ * fires synchronously per mutation (registry state is consistent when they
40
+ * fire); {@link SlotCore.subscribeDeclaration} fires synchronously for each
41
+ * declaration lifetime boundary; {@link SlotCore.subscribe} notifications
42
+ * batch per microtask, so N same-tick mutations produce one notification per
43
+ * touched key. Entry crash reports ({@link SlotCore.reportEntryError}) ride
44
+ * the same mutation channel when they abdicate, then notify
45
+ * {@link SlotCore.onEntryError} synchronously.
46
+ */
47
+ var SlotCore = class {
48
+ records = /* @__PURE__ */ new Map();
49
+ factories = /* @__PURE__ */ new Map();
50
+ mutateListeners = /* @__PURE__ */ new Set();
51
+ /** Shared-handle scope ledger: handle → the scope it first mounted under + live mount count. */
52
+ handleScopes = /* @__PURE__ */ new Map();
53
+ dirty = /* @__PURE__ */ new Set();
54
+ flushScheduled = false;
55
+ /**
56
+ * Entries retired by an abdicating crash report
57
+ * ({@link SlotCore.reportEntryError}): excluded from
58
+ * {@link SlotCore.entriesOfSlot} projections for the rest of their
59
+ * registration's life, while the registration itself stays on the ledger
60
+ * (disposal authority remains with the registrant).
61
+ */
62
+ abdicated = /* @__PURE__ */ new WeakSet();
63
+ entryErrorListeners = /* @__PURE__ */ new Set();
64
+ constructor() {
65
+ const root = this.record("root");
66
+ root.spec = {
67
+ kind: "single",
68
+ scope: "root"
69
+ };
70
+ root.declaredBy = "(built-in)";
71
+ root.declarationEpoch = 1;
72
+ }
73
+ /** Register one reusable Factory definition. */
74
+ registerFactory = ((rawOptions, component) => {
75
+ const options = rawOptions;
76
+ const record = this.factoryRecord(options.name);
77
+ if (record.definition !== void 0) throw new Error(`slot factory "${options.name}" already has a definition`);
78
+ for (const childKey of Object.keys(options.children ?? {})) {
79
+ const childRecord = this.records.get(childKey);
80
+ if (childRecord?.spec !== void 0) throw new Error(`slot "${childKey}" is already declared (by ${childRecord.declaredBy ?? "an unknown entry"})`);
81
+ }
82
+ if (options.store !== void 0 && typeof options.store !== "function") {
83
+ const pinned = this.handleScopes.get(options.store);
84
+ if (pinned !== void 0 && pinned.scope !== options.scope) throw new Error(`store handle mounted under factory "${options.name}" (scope "${options.scope}") is already mounted under scope "${pinned.scope}" — one handle, one scope`);
85
+ if (pinned !== void 0) pinned.count += 1;
86
+ else this.handleScopes.set(options.store, {
87
+ scope: options.scope,
88
+ count: 1
89
+ });
90
+ }
91
+ const definition = {
92
+ name: options.name,
93
+ component,
94
+ scope: options.scope,
95
+ ...options.children === void 0 ? {} : { children: options.children },
96
+ ...options.store === void 0 ? {} : { store: options.store },
97
+ ...options.inject === void 0 ? {} : { inject: options.inject },
98
+ ...options.locale === void 0 ? {} : { locale: options.locale },
99
+ ...options.slots === void 0 ? {} : { slots: options.slots },
100
+ ...options.registrant === void 0 ? {} : { registrant: options.registrant }
101
+ };
102
+ record.definition = definition;
103
+ this.markFactoryDirty(record);
104
+ const declarations = [];
105
+ for (const [childKey, childSpec] of Object.entries(options.children ?? {})) {
106
+ const childRecord = this.record(childKey);
107
+ childRecord.spec = childSpec;
108
+ childRecord.declaredBy = `factory "${options.name}"${options.registrant ? ` (${options.registrant})` : ""}`;
109
+ childRecord.parent = `factory:${options.name}`;
110
+ childRecord.declarationEpoch += 1;
111
+ declarations.push([childKey, childRecord]);
112
+ }
113
+ for (const [childKey, childRecord] of declarations) this.markDirty(childKey, childRecord);
114
+ for (const [, childRecord] of declarations) this.notifyDeclaration(childRecord);
115
+ return () => {
116
+ if (record.definition !== definition) return;
117
+ record.definition = void 0;
118
+ this.markFactoryDirty(record);
119
+ if (definition.store !== void 0 && typeof definition.store !== "function") {
120
+ const pinned = this.handleScopes.get(definition.store);
121
+ if (pinned !== void 0 && --pinned.count === 0) this.handleScopes.delete(definition.store);
122
+ }
123
+ this.releaseChildren(definition.children);
124
+ };
125
+ });
126
+ /**
127
+ * Read one registered Factory definition.
128
+ * @param name - Factory name.
129
+ * @returns the live definition, or `undefined` when absent.
130
+ */
131
+ factory(name) {
132
+ return this.factories.get(name)?.definition;
133
+ }
134
+ /**
135
+ * Read the monotonic definition version for one Factory name.
136
+ * @param name - Factory name.
137
+ * @returns the current version.
138
+ */
139
+ factoryVersion(name) {
140
+ return this.factories.get(name)?.version ?? 0;
141
+ }
142
+ /**
143
+ * Subscribe to one Factory definition's registration lifetime.
144
+ * @param name - Factory name.
145
+ * @param listener - callback notified after a definition change.
146
+ * @returns the unsubscribe function.
147
+ */
148
+ subscribeFactory(name, listener) {
149
+ const record = this.factoryRecord(name);
150
+ record.listeners.add(listener);
151
+ return () => {
152
+ record.listeners.delete(listener);
153
+ };
154
+ }
155
+ /**
156
+ * Return whether a retained Factory definition is still registered.
157
+ * @param definition - retained definition identity.
158
+ * @returns whether that exact definition remains live.
159
+ */
160
+ isFactoryLive(definition) {
161
+ return this.factories.get(definition.name)?.definition === definition;
162
+ }
163
+ register(options, component) {
164
+ const rec = this.records.get(options.name);
165
+ if (!rec?.spec) throw new Error(`slot "${options.name}" is not declared (a parent entry's children table must declare it)`);
166
+ const spec = rec.spec;
167
+ const priority = options.priority ?? 0;
168
+ const occupantHint = (occupant) => `at priority ${priority}${occupant.registrant !== void 0 ? ` (registered by ${occupant.registrant})` : ""} — register at a different priority to shadow it (lowest renders)`;
169
+ switch (spec.kind) {
170
+ case "single": {
171
+ const occupant = rec.entries.find((e) => (e.options.priority ?? 0) === priority);
172
+ if (occupant) throw new Error(`single slot "${options.name}" already has a registration ${occupantHint(occupant)}`);
173
+ break;
174
+ }
175
+ case "keyed": {
176
+ if (options.key === void 0) throw new Error(`keyed slot "${options.name}" requires options.key`);
177
+ const occupant = rec.entries.find((e) => e.options.key === options.key && (e.options.priority ?? 0) === priority);
178
+ if (occupant) throw new Error(`keyed slot "${options.name}" already has an entry for key "${options.key}" ${occupantHint(occupant)}`);
179
+ break;
180
+ }
181
+ case "list": {
182
+ if (options.id === void 0) throw new Error(`list slot "${options.name}" requires options.id`);
183
+ const occupant = rec.entries.find((e) => e.options.id === options.id && (e.options.priority ?? 0) === priority);
184
+ if (occupant) throw new Error(`list slot "${options.name}" already has an entry with id "${options.id}" ${occupantHint(occupant)}`);
185
+ break;
186
+ }
187
+ case "chain":
188
+ if (options.select === void 0) throw new Error(`chain slot "${options.name}" requires options.select`);
189
+ break;
190
+ }
191
+ const rendersExisting = options.rendersExistingChildren === true;
192
+ if (options.children) for (const childKey of Object.keys(options.children)) {
193
+ const childRec = this.records.get(childKey);
194
+ if (childRec?.spec) {
195
+ if (rendersExisting) continue;
196
+ throw new Error(`slot "${childKey}" is already declared (by ${childRec.declaredBy ?? "an unknown entry"})`);
197
+ }
198
+ }
199
+ if (options.store !== void 0 && typeof options.store !== "function") {
200
+ const pinned = this.handleScopes.get(options.store);
201
+ if (pinned && pinned.scope !== spec.scope) throw new Error(`store handle mounted under "${options.name}" (scope "${spec.scope}") is already mounted under scope "${pinned.scope}" — one handle, one scope`);
202
+ if (pinned) pinned.count += 1;
203
+ else this.handleScopes.set(options.store, {
204
+ scope: spec.scope,
205
+ count: 1
206
+ });
207
+ }
208
+ const entry = {
209
+ component,
210
+ options: {
211
+ ...options.key !== void 0 ? { key: options.key } : {},
212
+ ...options.id !== void 0 ? { id: options.id } : {},
213
+ ...options.order !== void 0 ? { order: options.order } : {},
214
+ ...options.label !== void 0 ? { label: options.label } : {},
215
+ ...options.section !== void 0 ? { section: options.section } : {},
216
+ ...options.priority !== void 0 ? { priority: options.priority } : {}
217
+ },
218
+ ...options.select !== void 0 ? { select: options.select } : {},
219
+ ...options.inject !== void 0 ? { inject: options.inject } : {},
220
+ ...options.children !== void 0 ? { children: options.children } : {},
221
+ ...options.store !== void 0 ? { store: options.store } : {},
222
+ ...options.locale !== void 0 ? { locale: options.locale } : {},
223
+ ...options.registrant !== void 0 ? { registrant: options.registrant } : {}
224
+ };
225
+ const next = [...rec.entries, entry];
226
+ next.sort(spec.kind === "list" ? (a, b) => (a.options.priority ?? 0) - (b.options.priority ?? 0) || (a.options.order ?? 0) - (b.options.order ?? 0) : (a, b) => (a.options.priority ?? 0) - (b.options.priority ?? 0));
227
+ rec.entries = next;
228
+ this.markDirty(options.name, rec);
229
+ if (options.children) {
230
+ const declarations = [];
231
+ const owned = /* @__PURE__ */ new Set();
232
+ for (const [childKey, childSpec] of Object.entries(options.children)) {
233
+ const childRec = this.record(childKey);
234
+ if (rendersExisting && childRec.spec !== void 0) continue;
235
+ childRec.spec = childSpec;
236
+ childRec.declaredBy = `an entry in "${options.name}"${options.registrant ? ` (${options.registrant})` : ""}`;
237
+ childRec.parent = options.name;
238
+ childRec.declarationEpoch += 1;
239
+ declarations.push([childKey, childRec]);
240
+ owned.add(childKey);
241
+ }
242
+ if (rendersExisting) entry.ownedChildren = owned;
243
+ for (const [childKey, childRec] of declarations) this.markDirty(childKey, childRec);
244
+ for (const [, childRec] of declarations) this.notifyDeclaration(childRec);
245
+ }
246
+ return () => {
247
+ if (!rec.entries.includes(entry)) return;
248
+ rec.entries = rec.entries.filter((e) => e !== entry);
249
+ this.markDirty(options.name, rec);
250
+ this.releaseEntry(entry);
251
+ };
252
+ }
253
+ /**
254
+ * Whether a previously obtained entry is still registered (the render
255
+ * machinery's stale-authorization probe: a retained renderSlot binding
256
+ * whose entry left the ledger must not render).
257
+ * @param entry - a previously read entry.
258
+ * @returns false once the entry's registration was disposed.
259
+ */
260
+ isLive(entry) {
261
+ for (const rec of this.records.values()) if (rec.entries.includes(entry)) return true;
262
+ return false;
263
+ }
264
+ /**
265
+ * Snapshot the registered entries for a key. Returns the cached array
266
+ * reference (stable between mutations — safe as a uSES getSnapshot source);
267
+ * empty for keys not (or no longer) declared, so renderers may probe ahead
268
+ * of plugin load order.
269
+ * @param key - slot key (dynamic: the render machinery holds keys as strings).
270
+ * @returns entries in registration (list: order) sequence.
271
+ */
272
+ entries(key) {
273
+ return this.records.get(key)?.entries ?? NO_ENTRIES;
274
+ }
275
+ /**
276
+ * Project a key's entries to its shadowing winners: the first live
277
+ * (non-abdicated) entry of each cell in priority order — single: the slot
278
+ * is one cell; keyed: one cell per `key`; list: one cell per `id` (winners
279
+ * keep ledger sequence; list renderers still refine display by `order`).
280
+ * Chain keys return the raw entries unchanged: election consumes every
281
+ * entry, shadowing does not apply. The raw {@link SlotCore.entries} view
282
+ * stays the inspection surface. Builds a fresh array per call — a render
283
+ * body read, not a uSES getSnapshot source.
284
+ * @param key - slot key (dynamic: the render machinery holds keys as strings).
285
+ * @returns the winning entry per occupied cell (empty while undeclared).
286
+ */
287
+ entriesOfSlot(key) {
288
+ const rec = this.records.get(key);
289
+ if (!rec?.spec) return NO_ENTRIES;
290
+ const kind = rec.spec.kind;
291
+ if (kind === "chain") return rec.entries;
292
+ const heads = [];
293
+ const seenCells = /* @__PURE__ */ new Set();
294
+ for (const entry of rec.entries) {
295
+ if (this.abdicated.has(entry)) continue;
296
+ const cell = kind === "keyed" ? entry.options.key : kind === "list" ? entry.options.id : void 0;
297
+ if (seenCells.has(cell)) continue;
298
+ seenCells.add(cell);
299
+ heads.push(entry);
300
+ }
301
+ return heads;
302
+ }
303
+ /**
304
+ * Look up a slot's declared spec, narrowed by the SlotMap key.
305
+ * @param key - SlotMap key.
306
+ * @returns the spec, or undefined while undeclared.
307
+ */
308
+ spec(key) {
309
+ return this.records.get(key)?.spec;
310
+ }
311
+ /**
312
+ * Dynamic-key escape hatch for spec lookup — renderers resolving keys they
313
+ * only hold as strings (generic dispatch) use this wide form; statically
314
+ * keyed callers use {@link SlotCore.spec}.
315
+ * @param key - candidate slot key.
316
+ * @returns the wide-typed spec, or undefined while undeclared.
317
+ */
318
+ specDynamic(key) {
319
+ return this.records.get(key)?.spec;
320
+ }
321
+ /**
322
+ * Export the current declaration topology without components or executable hooks.
323
+ * Factory definitions appear as `factory:<name>` parents of their ordinary
324
+ * child Slots, matching the parent/child topology of ordinary registrations.
325
+ * @param root - exact Slot or `factory:<name>` key to select; omitted returns every live root.
326
+ * @returns selected live Slot trees, or an empty array when `root` is unavailable.
327
+ */
328
+ snapshot(root) {
329
+ const buildSlot = (name, seen) => {
330
+ const record = this.records.get(name);
331
+ if (record?.spec === void 0 || seen.has(name)) return void 0;
332
+ const branch = new Set(seen);
333
+ branch.add(name);
334
+ const active = new Set(this.entriesOfSlot(name));
335
+ const children = [...this.records.entries()].filter(([, candidate]) => candidate.spec !== void 0 && candidate.parent === name).flatMap(([child]) => {
336
+ const node = buildSlot(child, branch);
337
+ return node === void 0 ? [] : [node];
338
+ });
339
+ return {
340
+ type: "slot",
341
+ name,
342
+ kind: record.spec.kind,
343
+ scope: record.spec.scope,
344
+ ...record.declaredBy === void 0 ? {} : { declaredBy: record.declaredBy },
345
+ occupants: record.entries.map((entry) => ({
346
+ ...entry.registrant === void 0 ? {} : { registrant: entry.registrant },
347
+ ...entry.options.key === void 0 ? {} : { key: entry.options.key },
348
+ ...entry.options.id === void 0 ? {} : { id: entry.options.id },
349
+ ...entry.options.order === void 0 ? {} : { order: entry.options.order },
350
+ priority: entry.options.priority ?? 0,
351
+ active: active.has(entry)
352
+ })),
353
+ children
354
+ };
355
+ };
356
+ const buildFactory = (name) => {
357
+ const definition = this.factories.get(name)?.definition;
358
+ if (definition === void 0) return void 0;
359
+ const nodeName = `factory:${name}`;
360
+ const children = [...this.records.entries()].filter(([, candidate]) => candidate.spec !== void 0 && candidate.parent === nodeName).flatMap(([child]) => {
361
+ const node = buildSlot(child, new Set([nodeName]));
362
+ return node === void 0 ? [] : [node];
363
+ });
364
+ return {
365
+ type: "factory",
366
+ name,
367
+ scope: definition.scope,
368
+ ...definition.registrant === void 0 ? {} : { registrant: definition.registrant },
369
+ children
370
+ };
371
+ };
372
+ if (root !== void 0) {
373
+ const node = root.startsWith("factory:") ? buildFactory(root.slice(8)) : buildSlot(root, /* @__PURE__ */ new Set());
374
+ return node === void 0 ? [] : [node];
375
+ }
376
+ const slots = [...this.records.entries()].filter(([, record]) => record.spec !== void 0 && record.parent === void 0).flatMap(([name]) => {
377
+ const node = buildSlot(name, /* @__PURE__ */ new Set());
378
+ return node === void 0 ? [] : [node];
379
+ });
380
+ const factories = [...this.factories.keys()].flatMap((name) => {
381
+ const node = buildFactory(name);
382
+ return node === void 0 ? [] : [node];
383
+ });
384
+ return [...slots, ...factories];
385
+ }
386
+ /**
387
+ * Read the declaration lifetime of a key. Entry additions and removals do
388
+ * not change it; declaration creation and collapse each advance it.
389
+ * @param key - slot key.
390
+ * @returns monotonic epoch (0 before the first declaration).
391
+ */
392
+ declarationEpoch(key) {
393
+ return this.records.get(key)?.declarationEpoch ?? 0;
394
+ }
395
+ /**
396
+ * Subscribe to registration changes for a key (microtask-batched).
397
+ * Subscribing ahead of declaration is allowed; the declaration notifies.
398
+ * @param key - slot key.
399
+ * @param fn - change callback.
400
+ * @returns unsubscribe.
401
+ */
402
+ subscribe(key, fn) {
403
+ const rec = this.record(key);
404
+ rec.listeners.add(fn);
405
+ return () => {
406
+ rec.listeners.delete(fn);
407
+ };
408
+ }
409
+ /**
410
+ * Subscribe to declaration lifetime boundaries for a key. Notifications
411
+ * are synchronous so declaration teardown finishes before a subsequent
412
+ * same-tick registration can observe stale resources. Ordinary entry
413
+ * mutations do not notify this surface. A children table commits every
414
+ * sibling declaration before its first notification.
415
+ * @param key - slot key.
416
+ * @param fn - declaration or collapse callback.
417
+ * @returns unsubscribe.
418
+ */
419
+ subscribeDeclaration(key, fn) {
420
+ const rec = this.record(key);
421
+ rec.declarationListeners.add(fn);
422
+ return () => {
423
+ rec.declarationListeners.delete(fn);
424
+ };
425
+ }
426
+ /**
427
+ * Monotonic version for a key, bumped synchronously per mutation so a
428
+ * uSES getSnapshot read is never stale when its batched notification lands.
429
+ * @param key - slot key.
430
+ * @returns current version (0 for untouched keys).
431
+ */
432
+ getVersion(key) {
433
+ return this.records.get(key)?.version ?? 0;
434
+ }
435
+ /**
436
+ * Hook every mutation (the runtime Service wrapper bridges this to ctx.emit).
437
+ * Fires synchronously per mutation, unbatched — event semantics need one
438
+ * emission per change.
439
+ * @param fn - called with the mutated key.
440
+ * @returns unsubscribe.
441
+ */
442
+ onMutate(fn) {
443
+ this.mutateListeners.add(fn);
444
+ return () => {
445
+ this.mutateListeners.delete(fn);
446
+ };
447
+ }
448
+ /**
449
+ * Renderer crash report from an entry boundary. Always notifies
450
+ * {@link SlotCore.onEntryError} listeners; with `info.abdicate` set (the
451
+ * shadowing kinds — single/keyed/list) it first retires the entry from its
452
+ * cell, one-shot: the record's version bumps through the ordinary mutation
453
+ * channel so outlets re-project onto the cell's next survivor, and a
454
+ * repeat abdicating report no-ops entirely. Chain crashes report with
455
+ * `abdicate: false` — election alternatives resolve at select time, so the
456
+ * entry keeps its cell and only the notification fires. The registration
457
+ * itself stays on the ledger either way — raw {@link SlotCore.entries}
458
+ * still lists the entry and its disposer keeps working.
459
+ * @param key - slot key the entry rendered under.
460
+ * @param entry - the crashed entry.
461
+ * @param error - the crash cause, forwarded to listeners verbatim.
462
+ * @param info - `abdicate`: whether the crash retires the entry from its cell.
463
+ */
464
+ reportEntryError(key, entry, error, info) {
465
+ if (info.abdicate) {
466
+ if (this.abdicated.has(entry)) return;
467
+ this.abdicated.add(entry);
468
+ const rec = this.records.get(key);
469
+ if (rec !== void 0) this.markDirty(key, rec);
470
+ }
471
+ for (const fn of [...this.entryErrorListeners]) fn(key, entry, error, { abdicated: info.abdicate });
472
+ }
473
+ /**
474
+ * Report a contained Factory occurrence crash through the ordinary entry
475
+ * supervision channel without retiring the shared definition.
476
+ * @param name - Factory name whose occurrence crashed.
477
+ * @param registration - Factory definition or caller registration that owns the crashing Component.
478
+ * @param error - the crash cause, forwarded to listeners verbatim.
479
+ */
480
+ reportFactoryError(name, registration, error) {
481
+ for (const fn of [...this.entryErrorListeners]) fn(`factory:${name}`, registration, error, { abdicated: false });
482
+ }
483
+ /**
484
+ * Observe ordinary entry and Factory occurrence crashes. Fires synchronously
485
+ * per report, after any ordinary-entry abdication mutation. Factory failures
486
+ * never retire their shared definition.
487
+ * @param fn - called with the Slot or `factory:<name>` key, the crashed
488
+ * registration, the cause, and whether an ordinary entry was retired.
489
+ * @returns unsubscribe.
490
+ */
491
+ onEntryError(fn) {
492
+ this.entryErrorListeners.add(fn);
493
+ return () => {
494
+ this.entryErrorListeners.delete(fn);
495
+ };
496
+ }
497
+ /**
498
+ * Cascade for a removed entry: release its store mount and collapse every
499
+ * child slot it declared — specs clear, contributions empty (their stale
500
+ * disposers no-op), recursively down the declaration tree. One lifecycle
501
+ * axis: ledger rows, slots, contributions, and store mounts die together.
502
+ */
503
+ releaseEntry(entry) {
504
+ if (entry.store !== void 0 && typeof entry.store !== "function") {
505
+ const pinned = this.handleScopes.get(entry.store);
506
+ if (pinned && --pinned.count === 0) this.handleScopes.delete(entry.store);
507
+ }
508
+ this.releaseChildren(entry.children, entry.ownedChildren);
509
+ }
510
+ releaseChildren(children, ownedChildren) {
511
+ if (children === void 0) return;
512
+ for (const childKey of Object.keys(children)) {
513
+ if (ownedChildren !== void 0 && !ownedChildren.has(childKey)) continue;
514
+ const childRec = this.records.get(childKey);
515
+ /* v8 ignore next -- defensive: declaring always creates the record */
516
+ if (!childRec) continue;
517
+ const doomed = childRec.entries;
518
+ childRec.spec = void 0;
519
+ childRec.declaredBy = void 0;
520
+ childRec.parent = void 0;
521
+ childRec.declarationEpoch += 1;
522
+ childRec.entries = NO_ENTRIES;
523
+ this.markDirty(childKey, childRec);
524
+ this.notifyDeclaration(childRec);
525
+ for (const dead of doomed) this.releaseEntry(dead);
526
+ }
527
+ }
528
+ record(key) {
529
+ let rec = this.records.get(key);
530
+ if (!rec) {
531
+ rec = {
532
+ spec: void 0,
533
+ declaredBy: void 0,
534
+ parent: void 0,
535
+ declarationEpoch: 0,
536
+ entries: NO_ENTRIES,
537
+ version: 0,
538
+ listeners: /* @__PURE__ */ new Set(),
539
+ declarationListeners: /* @__PURE__ */ new Set()
540
+ };
541
+ this.records.set(key, rec);
542
+ }
543
+ return rec;
544
+ }
545
+ factoryRecord(name) {
546
+ let record = this.factories.get(name);
547
+ if (record === void 0) {
548
+ record = {
549
+ definition: void 0,
550
+ version: 0,
551
+ listeners: /* @__PURE__ */ new Set()
552
+ };
553
+ this.factories.set(name, record);
554
+ }
555
+ return record;
556
+ }
557
+ markFactoryDirty(record) {
558
+ record.version += 1;
559
+ queueMicrotask(() => {
560
+ for (const listener of [...record.listeners]) listener();
561
+ });
562
+ }
563
+ markDirty(key, rec) {
564
+ rec.version += 1;
565
+ for (const fn of [...this.mutateListeners]) fn(key);
566
+ this.dirty.add(rec);
567
+ if (!this.flushScheduled) {
568
+ this.flushScheduled = true;
569
+ queueMicrotask(() => {
570
+ this.flush();
571
+ });
572
+ }
573
+ }
574
+ notifyDeclaration(rec) {
575
+ for (const fn of [...rec.declarationListeners]) fn();
576
+ }
577
+ flush() {
578
+ this.flushScheduled = false;
579
+ const dirty = [...this.dirty];
580
+ this.dirty.clear();
581
+ for (const rec of dirty) for (const fn of [...rec.listeners]) fn();
582
+ }
583
+ };
584
+ //#endregion
585
+ export { SlotCore, SlotOwnershipError, StaleAuthorizationError, resolveSlotLabel, standardHookPropName };
586
+
587
+ //# sourceMappingURL=index.js.map