@deepseek-ai/dsh-client-ui-slots 0.1.6-alpha.1 → 0.1.7-alpha.1
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.i18n.yaml +2 -2
- package/README.md +14 -7
- package/README.zh.md +14 -7
- package/lib/index.js +159 -15
- package/lib/types/index.d.ts +236 -25
- package/lib/types/renderer.d.ts +54 -6
- package/package.json +5 -5
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-slots/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: d82b303d5d6efaebbccaab00d06d874d79165fb1
|
|
6
|
+
README.zh.md: 2a9d0364eda148ddf8db8cc61daf1747381c4875
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Slot registry pure core for the dsh web client:
|
|
2
|
+
description: "Slot registry pure core for the dsh web client: ordinary extension slots, reusable Component Factories, derived props types, store seats, and the renderer install contract."
|
|
3
3
|
kind: "package-library"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
`dsh-client-ui-slots` lets web client plugins define and compose typed UI regions.
|
|
12
|
+
`dsh-client-ui-slots` lets web client plugins define and compose typed UI regions. Ordinary Slots provide parent-owned extension positions; Component Factories provide reusable assemblies with caller-selected local Components. Both APIs derive scoped state, injection, locale, and child-render props from declaration-merged types and report conflicting definitions during plugin loading. Pair this React-free package with `ui-renderer` when the client needs rendering.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -27,9 +27,15 @@ English | [中文](README.zh.md)
|
|
|
27
27
|
|
|
28
28
|
Compose UI through this package whenever you write a client plugin: register a component into a slot your parent declared, or declare child slots your component renders. The four kinds cover the composition shapes — `single` (one occupant), `list` (ordered entries), `keyed` (dispatch by a key), and `chain` (entries elect themselves).
|
|
29
29
|
|
|
30
|
-
###
|
|
30
|
+
### Reusable Component Factories
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Use a Component Factory when one package defines an assembly that unrelated parents render independently. Declare its complete type in `SlotFactoryMap`, install the definition with `ctx.slots.registerFactory()`, render occurrences through the injected `renderFactorySlot()`, and select each declared local Component through the call's `slots` option. The definition reads that choice through `useFactorySlot(name, fallback)`.
|
|
33
|
+
|
|
34
|
+
Factory `children` remain ordinary global Slots and must match `SlotMap`, while local `slots` select one Component per occurrence. An occurrence inherits its render-position scope; `renderFactorySlot()` does not accept a Session identity. Shared Store handles use ordinary scope resolution. A Store factory stays lazy until an occurrence first materializes, then creates one handle for that render position and rejects a persistent Store spec whose key would collide across occurrences.
|
|
35
|
+
|
|
36
|
+
### The five framework props shares
|
|
37
|
+
|
|
38
|
+
Every registered component receives props composed from five framework shares: the runtime share (`owner` from the parent's render call site, plus the session standard kit and global seat), the child-render share (`renderSlot` statically narrowed to declared children), the Factory-render share (`renderFactorySlot`), the store share (the declared handle's selector hook and draft-stripped actions), and the business share (inferred from `inject`). Components reference the derived props aliases; they never re-type a share locally.
|
|
33
39
|
|
|
34
40
|
### Store seats
|
|
35
41
|
|
|
@@ -47,15 +53,15 @@ Declaring a slot is claiming it: the registering entry becomes the only entry al
|
|
|
47
53
|
<details>
|
|
48
54
|
<summary>Implementation internals — click to expand</summary>
|
|
49
55
|
|
|
50
|
-
The design is one table: declaration = render authorization = runtime spec. `SlotMap` is declared empty here and merged by consumers via `declare module` augmentation, exactly like the standard-kit interfaces (`SessionStandardProps`, `GlobalStandardProps`)
|
|
56
|
+
The ordinary Slot design is one table: declaration = render authorization = runtime spec. `SlotMap` is declared empty here and merged by consumers via `declare module` augmentation, exactly like `SlotFactoryMap` and the standard-kit interfaces (`SessionStandardProps`, `GlobalStandardProps`). Factory definitions use a separate single-definition ledger because their occurrences have no parent declaration.
|
|
51
57
|
|
|
52
58
|
### Registration and routing
|
|
53
59
|
|
|
54
|
-
`SlotCore` seeds the a-priori `'root'` slot at construction and enforces load-time validation. `ChainSelect` selectors run in ascending `priority` order (ties in registration order); the first non-null return elects its entry and becomes the component's `matched` prop, and all-null falls to the owner's `renderSlotChain` fallback (`ChainRenderOpts`). Each key carries a declaration epoch that advances only on declaration and collapse; `ui-renderer` uses it for `ctx.slots.inject`, independently from ordinary entry versions.
|
|
60
|
+
`SlotCore` seeds the a-priori `'root'` slot at construction and enforces load-time validation. `ChainSelect` selectors run in ascending `priority` order (ties in registration order); the first non-null return elects its entry and becomes the component's `matched` prop, and all-null falls to the owner's `renderSlotChain` fallback (`ChainRenderOpts`). Each key carries a declaration epoch that advances only on declaration and collapse; `ui-renderer` uses it for `ctx.slots.inject`, independently from ordinary entry versions. Live inspection uses strict `type: 'slot' | 'factory'` nodes and nests Factory-owned child Slots under their definition.
|
|
55
61
|
|
|
56
62
|
### The renderer contract
|
|
57
63
|
|
|
58
|
-
`renderer.ts` carries the installation contract (`SlotRenderer`, `SlotRendererHost`) plus `StaleAuthorizationError`/`SlotOwnershipError`; ui-renderer owns both the implementation and its plugin-lifecycle installation. Engine products and the renderer host contract carry bare snapshot sources (`getSnapshot`/`subscribe`), never React hooks — hook binding belongs to the render machinery.
|
|
64
|
+
`renderer.ts` carries the installation contract (`SlotRenderer`, `SlotRendererHost`) plus `StaleAuthorizationError`/`SlotOwnershipError`; ui-renderer owns both the implementation and its plugin-lifecycle installation. Engine products and the renderer host contract carry bare snapshot sources (`getSnapshot`/`subscribe`), never React hooks — hook binding belongs to the render machinery. Factory crashes use the ordinary supervision channel, and an idempotent effect retains per-position Store handles only after commit.
|
|
59
65
|
|
|
60
66
|
</details>
|
|
61
67
|
|
|
@@ -68,6 +74,7 @@ These pages cover the engine, the renderer, and the composition model.
|
|
|
68
74
|
|
|
69
75
|
- [ui-renderer](../ui-renderer/README.md) — the React slot renderer implementing this package's install contract.
|
|
70
76
|
- [Slot system standard](../../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md) — the definitive composition model.
|
|
77
|
+
- [Component Factories](../../../.agents/notes/implemented/architecture/2026-09-10-component-factories-and-local-slots.md) — reusable definitions, local Component selection, and occurrence lifetimes.
|
|
71
78
|
- [Web client architecture](../../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md) — the loading chain and object layer this registry plugs into.
|
|
72
79
|
|
|
73
80
|
-----
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "dsh Web 客户端的 slot
|
|
2
|
+
description: "dsh Web 客户端的 slot 注册表纯核心:普通扩展 slots、可复用 Component Factory、推导 props 类型、store 席位与渲染器安装约定。"
|
|
3
3
|
kind: "package-library"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ kind: "package-library"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
`dsh-client-ui-slots` 让 Web 客户端插件定义并组合带类型检查的 UI
|
|
12
|
+
`dsh-client-ui-slots` 让 Web 客户端插件定义并组合带类型检查的 UI 区域。普通 Slots 提供 parent-owned 扩展位置;Component Factory 提供带调用方所选局部 Component 的可复用装配。两套 API 都从声明合并类型推导 scoped state、injection、locale 与 child-render props,并在插件加载期间报告冲突 definition。客户端需要渲染时,将这个不依赖 React 的包与 `ui-renderer` 配合使用。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -27,9 +27,15 @@ kind: "package-library"
|
|
|
27
27
|
|
|
28
28
|
编写客户端插件时都通过本包组合 UI:把组件注册进父级已声明的 slot,或声明组件将要渲染的子 slot。四种 kind 覆盖组合形态——`single`(单个占位者)、`list`(有序条目)、`keyed`(按键分派)与 `chain`(条目自行提名)。
|
|
29
29
|
|
|
30
|
-
###
|
|
30
|
+
### 可复用 Component Factory
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
当一个包定义装配、而互不相关的 parents 需要独立渲染它时,使用 Component Factory。在 `SlotFactoryMap` 中声明完整类型,通过 `ctx.slots.registerFactory()` 安装 definition,通过注入的 `renderFactorySlot()` 渲染 occurrences,并通过调用的 `slots` 选项选择每个已声明的局部 Component。definition 通过 `useFactorySlot(name, fallback)` 读取该选择。
|
|
33
|
+
|
|
34
|
+
Factory `children` 仍是普通全局 Slots 且必须与 `SlotMap` 匹配,而局部 `slots` 为每个 occurrence 选择一个 Component。occurrence 继承其渲染位置的 scope;`renderFactorySlot()` 不接受 Session identity。共享 Store handle 使用普通 scope 解析。Store factory 保持 lazy,直到 occurrence 首次物化时才为该渲染位置创建一个 handle;若持久化 Store spec 会让 persistence key 在 occurrences 之间冲突,renderer 会拒绝它。
|
|
35
|
+
|
|
36
|
+
### 五个框架 props share
|
|
37
|
+
|
|
38
|
+
每个已注册组件都会收到由五个框架 share 组合而成的 props:运行时 share(父级 render 调用点的 `owner`,加上会话标准工具包与全局席位)、child render share(静态缩窄到已声明 children 的 `renderSlot`)、Factory render share(`renderFactorySlot`)、store share(已声明 handle 的 selector 钩子与移除 draft 的 actions),以及业务 share(从 `inject` 推导)。组件引用推导出的 props 别名;它们绝不在本地重新定义任何 share 的类型。
|
|
33
39
|
|
|
34
40
|
### Store 席位
|
|
35
41
|
|
|
@@ -47,15 +53,15 @@ register 调用可以用 `store: defineStore(...)` 声明 store 席位:`init`
|
|
|
47
53
|
<details>
|
|
48
54
|
<summary>实现细节——点击展开</summary>
|
|
49
55
|
|
|
50
|
-
设计就是一张表:声明 = 渲染授权 = 运行时规范。`SlotMap` 在这里声明为空,由消费方通过 `declare module`
|
|
56
|
+
普通 Slot 设计就是一张表:声明 = 渲染授权 = 运行时规范。`SlotMap` 在这里声明为空,由消费方通过 `declare module` 增补合并;`SlotFactoryMap` 和标准工具包接口(`SessionStandardProps`、`GlobalStandardProps`)也采用同一方式。Factory definition 使用独立的单 definition ledger,因为其 occurrences 没有 parent 声明。
|
|
51
57
|
|
|
52
58
|
### 注册与路由
|
|
53
59
|
|
|
54
|
-
`SlotCore` 在构造时预置 `'root'` slot,并强制执行加载时验证。`ChainSelect` selector 按升序 `priority` 运行(相同值按注册顺序);第一个非 null 返回值选中其条目,并成为组件的 `matched` prop;全部返回 null 时使用 owner 的 `renderSlotChain` fallback(`ChainRenderOpts`)。每个 key 都携带一个 declaration epoch,它只在声明与移除时递增;`ui-renderer` 将其用于 `ctx.slots.inject
|
|
60
|
+
`SlotCore` 在构造时预置 `'root'` slot,并强制执行加载时验证。`ChainSelect` selector 按升序 `priority` 运行(相同值按注册顺序);第一个非 null 返回值选中其条目,并成为组件的 `matched` prop;全部返回 null 时使用 owner 的 `renderSlotChain` fallback(`ChainRenderOpts`)。每个 key 都携带一个 declaration epoch,它只在声明与移除时递增;`ui-renderer` 将其用于 `ctx.slots.inject`,且与普通条目版本相互独立。实时检查使用严格的 `type: 'slot' | 'factory'` 节点,并把 Factory-owned child Slots 嵌套在其 definition 下。
|
|
55
61
|
|
|
56
62
|
### 渲染器约定
|
|
57
63
|
|
|
58
|
-
`renderer.ts` 携带安装约定(`SlotRenderer`、`SlotRendererHost`)以及 `StaleAuthorizationError`/`SlotOwnershipError`;ui-renderer 负责实现,并在其插件生命周期中完成安装。引擎产物与渲染器宿主约定携带裸快照 source(`getSnapshot`/`subscribe`),绝不携带 React 钩子——钩子绑定属于渲染机制。
|
|
64
|
+
`renderer.ts` 携带安装约定(`SlotRenderer`、`SlotRendererHost`)以及 `StaleAuthorizationError`/`SlotOwnershipError`;ui-renderer 负责实现,并在其插件生命周期中完成安装。引擎产物与渲染器宿主约定携带裸快照 source(`getSnapshot`/`subscribe`),绝不携带 React 钩子——钩子绑定属于渲染机制。Factory 崩溃使用普通监督通道,幂等 effect 仅在 commit 后保留逐渲染位置 Store handle。
|
|
59
65
|
|
|
60
66
|
</details>
|
|
61
67
|
|
|
@@ -68,6 +74,7 @@ register 调用可以用 `store: defineStore(...)` 声明 store 席位:`init`
|
|
|
68
74
|
|
|
69
75
|
- [ui-renderer](../ui-renderer/README.zh.md)——实现本包安装约定的 React slot 渲染器。
|
|
70
76
|
- [slot 系统标准](../../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.zh.md)——权威组合模型。
|
|
77
|
+
- [Component Factory](../../../.agents/notes/implemented/architecture/2026-09-10-component-factories-and-local-slots.zh.md)——可复用 definitions、局部 Component 选择与 occurrence 生命周期。
|
|
71
78
|
- [Web 客户端架构](../../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md)——本注册表接入的加载链与对象层。
|
|
72
79
|
|
|
73
80
|
-----
|
package/lib/index.js
CHANGED
|
@@ -46,6 +46,7 @@ const NO_ENTRIES = Object.freeze([]);
|
|
|
46
46
|
*/
|
|
47
47
|
var SlotCore = class {
|
|
48
48
|
records = /* @__PURE__ */ new Map();
|
|
49
|
+
factories = /* @__PURE__ */ new Map();
|
|
49
50
|
mutateListeners = /* @__PURE__ */ new Set();
|
|
50
51
|
/** Shared-handle scope ledger: handle → the scope it first mounted under + live mount count. */
|
|
51
52
|
handleScopes = /* @__PURE__ */ new Map();
|
|
@@ -69,6 +70,96 @@ var SlotCore = class {
|
|
|
69
70
|
root.declaredBy = "(built-in)";
|
|
70
71
|
root.declarationEpoch = 1;
|
|
71
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
|
+
}
|
|
72
163
|
register(options, component) {
|
|
73
164
|
const rec = this.records.get(options.name);
|
|
74
165
|
if (!rec?.spec) throw new Error(`slot "${options.name}" is not declared (a parent entry's children table must declare it)`);
|
|
@@ -220,21 +311,24 @@ var SlotCore = class {
|
|
|
220
311
|
}
|
|
221
312
|
/**
|
|
222
313
|
* Export the current declaration topology without components or executable hooks.
|
|
223
|
-
*
|
|
314
|
+
* Factory definitions appear as `factory:<name>` parents of their ordinary
|
|
315
|
+
* child Slots, matching the parent/child topology of ordinary registrations.
|
|
316
|
+
* @param root - exact Slot or `factory:<name>` key to select; omitted returns every live root.
|
|
224
317
|
* @returns selected live Slot trees, or an empty array when `root` is unavailable.
|
|
225
318
|
*/
|
|
226
319
|
snapshot(root) {
|
|
227
|
-
const
|
|
320
|
+
const buildSlot = (name, seen) => {
|
|
228
321
|
const record = this.records.get(name);
|
|
229
322
|
if (record?.spec === void 0 || seen.has(name)) return void 0;
|
|
230
323
|
const branch = new Set(seen);
|
|
231
324
|
branch.add(name);
|
|
232
325
|
const active = new Set(this.entriesOfSlot(name));
|
|
233
326
|
const children = [...this.records.entries()].filter(([, candidate]) => candidate.spec !== void 0 && candidate.parent === name).flatMap(([child]) => {
|
|
234
|
-
const node =
|
|
327
|
+
const node = buildSlot(child, branch);
|
|
235
328
|
return node === void 0 ? [] : [node];
|
|
236
329
|
});
|
|
237
330
|
return {
|
|
331
|
+
type: "slot",
|
|
238
332
|
name,
|
|
239
333
|
kind: record.spec.kind,
|
|
240
334
|
scope: record.spec.scope,
|
|
@@ -250,14 +344,35 @@ var SlotCore = class {
|
|
|
250
344
|
children
|
|
251
345
|
};
|
|
252
346
|
};
|
|
347
|
+
const buildFactory = (name) => {
|
|
348
|
+
const definition = this.factories.get(name)?.definition;
|
|
349
|
+
if (definition === void 0) return void 0;
|
|
350
|
+
const nodeName = `factory:${name}`;
|
|
351
|
+
const children = [...this.records.entries()].filter(([, candidate]) => candidate.spec !== void 0 && candidate.parent === nodeName).flatMap(([child]) => {
|
|
352
|
+
const node = buildSlot(child, new Set([nodeName]));
|
|
353
|
+
return node === void 0 ? [] : [node];
|
|
354
|
+
});
|
|
355
|
+
return {
|
|
356
|
+
type: "factory",
|
|
357
|
+
name,
|
|
358
|
+
scope: definition.scope,
|
|
359
|
+
...definition.registrant === void 0 ? {} : { registrant: definition.registrant },
|
|
360
|
+
children
|
|
361
|
+
};
|
|
362
|
+
};
|
|
253
363
|
if (root !== void 0) {
|
|
254
|
-
const node =
|
|
364
|
+
const node = root.startsWith("factory:") ? buildFactory(root.slice(8)) : buildSlot(root, /* @__PURE__ */ new Set());
|
|
255
365
|
return node === void 0 ? [] : [node];
|
|
256
366
|
}
|
|
257
|
-
|
|
258
|
-
const node =
|
|
367
|
+
const slots = [...this.records.entries()].filter(([, record]) => record.spec !== void 0 && record.parent === void 0).flatMap(([name]) => {
|
|
368
|
+
const node = buildSlot(name, /* @__PURE__ */ new Set());
|
|
369
|
+
return node === void 0 ? [] : [node];
|
|
370
|
+
});
|
|
371
|
+
const factories = [...this.factories.keys()].flatMap((name) => {
|
|
372
|
+
const node = buildFactory(name);
|
|
259
373
|
return node === void 0 ? [] : [node];
|
|
260
374
|
});
|
|
375
|
+
return [...slots, ...factories];
|
|
261
376
|
}
|
|
262
377
|
/**
|
|
263
378
|
* Read the declaration lifetime of a key. Entry additions and removals do
|
|
@@ -347,13 +462,21 @@ var SlotCore = class {
|
|
|
347
462
|
for (const fn of [...this.entryErrorListeners]) fn(key, entry, error, { abdicated: info.abdicate });
|
|
348
463
|
}
|
|
349
464
|
/**
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
|
|
356
|
-
|
|
465
|
+
* Report a contained Factory occurrence crash through the ordinary entry
|
|
466
|
+
* supervision channel without retiring the shared definition.
|
|
467
|
+
* @param name - Factory name whose occurrence crashed.
|
|
468
|
+
* @param registration - Factory definition or caller registration that owns the crashing Component.
|
|
469
|
+
* @param error - the crash cause, forwarded to listeners verbatim.
|
|
470
|
+
*/
|
|
471
|
+
reportFactoryError(name, registration, error) {
|
|
472
|
+
for (const fn of [...this.entryErrorListeners]) fn(`factory:${name}`, registration, error, { abdicated: false });
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Observe ordinary entry and Factory occurrence crashes. Fires synchronously
|
|
476
|
+
* per report, after any ordinary-entry abdication mutation. Factory failures
|
|
477
|
+
* never retire their shared definition.
|
|
478
|
+
* @param fn - called with the Slot or `factory:<name>` key, the crashed
|
|
479
|
+
* registration, the cause, and whether an ordinary entry was retired.
|
|
357
480
|
* @returns unsubscribe.
|
|
358
481
|
*/
|
|
359
482
|
onEntryError(fn) {
|
|
@@ -373,8 +496,11 @@ var SlotCore = class {
|
|
|
373
496
|
const pinned = this.handleScopes.get(entry.store);
|
|
374
497
|
if (pinned && --pinned.count === 0) this.handleScopes.delete(entry.store);
|
|
375
498
|
}
|
|
376
|
-
|
|
377
|
-
|
|
499
|
+
this.releaseChildren(entry.children);
|
|
500
|
+
}
|
|
501
|
+
releaseChildren(children) {
|
|
502
|
+
if (children === void 0) return;
|
|
503
|
+
for (const childKey of Object.keys(children)) {
|
|
378
504
|
const childRec = this.records.get(childKey);
|
|
379
505
|
/* v8 ignore next -- defensive: declaring always creates the record */
|
|
380
506
|
if (!childRec) continue;
|
|
@@ -406,6 +532,24 @@ var SlotCore = class {
|
|
|
406
532
|
}
|
|
407
533
|
return rec;
|
|
408
534
|
}
|
|
535
|
+
factoryRecord(name) {
|
|
536
|
+
let record = this.factories.get(name);
|
|
537
|
+
if (record === void 0) {
|
|
538
|
+
record = {
|
|
539
|
+
definition: void 0,
|
|
540
|
+
version: 0,
|
|
541
|
+
listeners: /* @__PURE__ */ new Set()
|
|
542
|
+
};
|
|
543
|
+
this.factories.set(name, record);
|
|
544
|
+
}
|
|
545
|
+
return record;
|
|
546
|
+
}
|
|
547
|
+
markFactoryDirty(record) {
|
|
548
|
+
record.version += 1;
|
|
549
|
+
queueMicrotask(() => {
|
|
550
|
+
for (const listener of [...record.listeners]) listener();
|
|
551
|
+
});
|
|
552
|
+
}
|
|
409
553
|
markDirty(key, rec) {
|
|
410
554
|
rec.version += 1;
|
|
411
555
|
for (const fn of [...this.mutateListeners]) fn(key);
|
package/lib/types/index.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ export * from './renderer.ts';
|
|
|
16
16
|
/** Slot contract table. Owners extend via declaration merging; entries are {@link SlotEntryDef}. */
|
|
17
17
|
export interface SlotMap {
|
|
18
18
|
}
|
|
19
|
+
/** Reusable Component Factory contract table, extended through declaration merging. */
|
|
20
|
+
export interface SlotFactoryMap {
|
|
21
|
+
}
|
|
19
22
|
/**
|
|
20
23
|
* Locale namespace table. Dictionary owners extend via declaration merging
|
|
21
24
|
* (exactly like {@link SlotMap}, and declared in this entry module for the
|
|
@@ -80,12 +83,15 @@ export type PropsLocale<N> = N extends keyof LocaleNamespaceMap & string ? {
|
|
|
80
83
|
export type SlotKind = 'single' | 'list' | 'keyed' | 'chain';
|
|
81
84
|
/** Slot data context: global, current-session-optional, or strict session-bound. */
|
|
82
85
|
export type SlotScope = 'root' | 'session-maybe' | 'session';
|
|
86
|
+
/** Declaration-merged explicit target types for non-root scope Providers. */
|
|
87
|
+
export interface SlotScopeTargetMap {
|
|
88
|
+
}
|
|
83
89
|
/**
|
|
84
90
|
* One SlotMap entry: kind/scope axes plus the optional owner-supplied props
|
|
85
91
|
* share (`owner` is what the parent passes at its renderSlot call site; the
|
|
86
92
|
* framework standard kit and the registrant's injected share never enter this
|
|
87
|
-
* table — full component props compose at the component
|
|
88
|
-
*
|
|
93
|
+
* table — full component props compose at the component from the framework
|
|
94
|
+
* shares, see {@link ComposedProps}).
|
|
89
95
|
*/
|
|
90
96
|
export interface SlotEntryDef {
|
|
91
97
|
kind: SlotKind;
|
|
@@ -110,6 +116,21 @@ export interface SlotEntryDef {
|
|
|
110
116
|
*/
|
|
111
117
|
inject?: object;
|
|
112
118
|
}
|
|
119
|
+
/** One caller-selected Component position inside a reusable Factory. */
|
|
120
|
+
export interface FactoryLocalSlotDef {
|
|
121
|
+
scope: SlotScope;
|
|
122
|
+
props?: object;
|
|
123
|
+
}
|
|
124
|
+
/** Complete static definition of one reusable Component Factory. */
|
|
125
|
+
export interface SlotFactoryDef {
|
|
126
|
+
scope: SlotScope;
|
|
127
|
+
props?: object;
|
|
128
|
+
children?: ChildrenDecl;
|
|
129
|
+
store?: StoreDecl;
|
|
130
|
+
inject?: object;
|
|
131
|
+
locale?: keyof LocaleNamespaceMap & string;
|
|
132
|
+
slots?: Record<string, FactoryLocalSlotDef>;
|
|
133
|
+
}
|
|
113
134
|
/**
|
|
114
135
|
* Runtime dispatch spec for one slot, recorded from a register call's
|
|
115
136
|
* `children` value. The literal is compile-time checked against the SlotMap
|
|
@@ -170,10 +191,10 @@ export type ScopeOf<K extends keyof SlotMap & string> = SlotMap[K]['scope'];
|
|
|
170
191
|
export interface SessionStandardProps {
|
|
171
192
|
}
|
|
172
193
|
/**
|
|
173
|
-
* Framework standard kit delivered to
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
194
|
+
* Framework standard kit delivered to session-optional slots. Its hooks stay
|
|
195
|
+
* callable while no session is selected and return `undefined` until one
|
|
196
|
+
* becomes current; `ui-session` and domain UI adapters merge the concrete
|
|
197
|
+
* members.
|
|
177
198
|
*/
|
|
178
199
|
export interface SessionMaybeStandardProps {
|
|
179
200
|
}
|
|
@@ -192,11 +213,13 @@ export interface GlobalStandardProps {
|
|
|
192
213
|
export type SessionIdOf = SessionStandardProps extends {
|
|
193
214
|
sessionId: infer S;
|
|
194
215
|
} ? S : string;
|
|
216
|
+
/** Standard props selected by one declared scope. */
|
|
217
|
+
export type ScopeStandardProps<S extends SlotScope> = (S extends 'session' ? SessionStandardProps : S extends 'session-maybe' ? SessionMaybeStandardProps : object) & GlobalStandardProps;
|
|
195
218
|
/**
|
|
196
219
|
* Runtime props share for a slot key: owner share (parent's renderSlot call
|
|
197
220
|
* site) + session standard kit (session scope only) + the global seat.
|
|
198
221
|
*/
|
|
199
|
-
export type PropsRuntime<K extends keyof SlotMap & string, EntryKey extends EntryKeyOf<K> = EntryKeyOf<K>> = OwnerOf<K> & KeyPropsOf<K, EntryKey> & SlotInjectFace<SlotInjectOf<K>> &
|
|
222
|
+
export type PropsRuntime<K extends keyof SlotMap & string, EntryKey extends EntryKeyOf<K> = EntryKeyOf<K>> = OwnerOf<K> & KeyPropsOf<K, EntryKey> & SlotInjectFace<SlotInjectOf<K>> & ScopeStandardProps<ScopeOf<K>>;
|
|
200
223
|
/** renderSlot dispatch options: keyed dispatch key, list filtering, and empty fallback. */
|
|
201
224
|
export interface RenderOpts<EntryKey extends string = string> {
|
|
202
225
|
entryKey?: EntryKey;
|
|
@@ -262,15 +285,20 @@ export type MatchedShare<E extends SlotEntryDef, M> = E['kind'] extends 'chain'
|
|
|
262
285
|
} : object;
|
|
263
286
|
/** Props of the standard-kit SessionProvider seat. */
|
|
264
287
|
export interface SessionAreaProps {
|
|
288
|
+
/**
|
|
289
|
+
* Explicit Session-scope target. Omit this property to inherit the surrounding
|
|
290
|
+
* binding; pass `undefined` to establish an explicitly absent binding.
|
|
291
|
+
*/
|
|
292
|
+
readonly session?: SlotScopeTargetMap[keyof SlotScopeTargetMap & 'session'] | undefined;
|
|
265
293
|
/** No-session body (also covers a current id whose session cannot be resolved). */
|
|
266
294
|
empty?: (() => ReactNode) | undefined;
|
|
267
|
-
/** Session body;
|
|
295
|
+
/** Session body; scoped entries apply their declared remount semantics. */
|
|
268
296
|
children: ReactNode;
|
|
269
297
|
}
|
|
270
298
|
/**
|
|
271
299
|
* Framework-wired session area component. `ui-session` supplies the current
|
|
272
300
|
* Controller binding through the renderer scope adapter; entries that declare
|
|
273
|
-
* session-
|
|
301
|
+
* `session` or `session-maybe` children receive this component without importing it.
|
|
274
302
|
*/
|
|
275
303
|
export type SessionProviderComponent = (props: SessionAreaProps) => ReactNode;
|
|
276
304
|
/**
|
|
@@ -304,15 +332,76 @@ export type PropsRenderSlots<S extends keyof SlotMap & string> = {
|
|
|
304
332
|
* @returns rendered node(s).
|
|
305
333
|
*/
|
|
306
334
|
renderSlotChain: <K extends ChainKeysOf<S>>(key: K, owner: OwnerOf<K>, opts?: ChainRenderOpts) => ReactNode;
|
|
307
|
-
}) & ('session'
|
|
335
|
+
}) & ([Extract<ScopeOf<S>, 'session' | 'session-maybe'>] extends [never] ? object : {
|
|
308
336
|
SessionProvider: SessionProviderComponent;
|
|
309
|
-
}
|
|
337
|
+
});
|
|
310
338
|
/**
|
|
311
339
|
* Registration-position component shape: the bare call signature, so composed
|
|
312
340
|
* constraints check through clean parameter contravariance (FC statics add
|
|
313
341
|
* covariant noise rejecting legitimate narrowings).
|
|
314
342
|
*/
|
|
315
343
|
export type SlotComponent<P> = (props: P) => ReactNode;
|
|
344
|
+
type FactoryDefOf<F extends keyof SlotFactoryMap & string> = SlotFactoryMap[F] & SlotFactoryDef;
|
|
345
|
+
type FactoryInputPropsOf<F extends keyof SlotFactoryMap & string> = SlotFactoryMap[F] extends {
|
|
346
|
+
props: infer P extends object;
|
|
347
|
+
} ? P : object;
|
|
348
|
+
type FactoryChildrenOf<F extends keyof SlotFactoryMap & string> = SlotFactoryMap[F] extends {
|
|
349
|
+
children: infer D extends Record<string, unknown>;
|
|
350
|
+
} ? D : Record<never, never>;
|
|
351
|
+
type FactoryStoreOf<F extends keyof SlotFactoryMap & string> = SlotFactoryMap[F] extends {
|
|
352
|
+
store: infer H extends StoreDecl;
|
|
353
|
+
} ? HandleOf<H> : undefined;
|
|
354
|
+
type FactoryInjectOf<F extends keyof SlotFactoryMap & string> = SlotFactoryMap[F] extends {
|
|
355
|
+
inject: infer I extends object;
|
|
356
|
+
} ? I : object;
|
|
357
|
+
type FactoryLocaleOf<F extends keyof SlotFactoryMap & string> = SlotFactoryMap[F] extends {
|
|
358
|
+
locale: infer N extends keyof LocaleNamespaceMap & string;
|
|
359
|
+
} ? N : undefined;
|
|
360
|
+
type FactoryLocalSlotsOf<F extends keyof SlotFactoryMap & string> = SlotFactoryMap[F] extends {
|
|
361
|
+
slots: infer S extends Record<string, FactoryLocalSlotDef>;
|
|
362
|
+
} ? S : Record<never, never>;
|
|
363
|
+
type FactoryLocalNameOf<F extends keyof SlotFactoryMap & string> = keyof FactoryLocalSlotsOf<F> & string;
|
|
364
|
+
type FactoryLocalDefOf<F extends keyof SlotFactoryMap & string, N extends FactoryLocalNameOf<F>> = FactoryLocalSlotsOf<F>[N] & FactoryLocalSlotDef;
|
|
365
|
+
type FactoryLocalInputPropsOf<F extends keyof SlotFactoryMap & string, N extends FactoryLocalNameOf<F>> = FactoryLocalDefOf<F, N> extends {
|
|
366
|
+
props: infer P extends object;
|
|
367
|
+
} ? P : object;
|
|
368
|
+
type FactoryRenderPropsOf<F extends keyof SlotFactoryMap & string> = [
|
|
369
|
+
keyof FactoryChildrenOf<F> & keyof SlotMap & string
|
|
370
|
+
] extends [never] ? object : PropsRenderSlots<keyof FactoryChildrenOf<F> & keyof SlotMap & string>;
|
|
371
|
+
/** Framework-derived props shared by a Factory definition and its local Components. */
|
|
372
|
+
export type FactoryRegistrationPropsOf<F extends keyof SlotFactoryMap & string> = FactoryRenderPropsOf<F> & PropsStore<FactoryStoreOf<F>> & InjectFace<FactoryInjectOf<F>> & PropsLocale<FactoryLocaleOf<F>> & PropsRenderFactories;
|
|
373
|
+
/** Complete props received by a registered Factory Component. */
|
|
374
|
+
export type FactoryComponentPropsOf<F extends keyof SlotFactoryMap & string> = FactoryInputPropsOf<F> & FactoryRegistrationPropsOf<F> & ScopeStandardProps<FactoryDefOf<F>['scope']> & {
|
|
375
|
+
useFactorySlot: UseFactorySlot<F>;
|
|
376
|
+
};
|
|
377
|
+
/** Complete props received by one caller-selected or fallback local Component. */
|
|
378
|
+
export type FactoryLocalComponentPropsOf<F extends keyof SlotFactoryMap & string, N extends FactoryLocalNameOf<F>> = FactoryLocalInputPropsOf<F, N> & FactoryRegistrationPropsOf<F> & ScopeStandardProps<FactoryLocalDefOf<F, N>['scope']>;
|
|
379
|
+
/** Component accepted for one declared local Factory position. */
|
|
380
|
+
export type FactoryLocalComponent<F extends keyof SlotFactoryMap & string, N extends FactoryLocalNameOf<F>> = SlotComponent<FactoryLocalComponentPropsOf<F, N>>;
|
|
381
|
+
/**
|
|
382
|
+
* Hook exposed only to a Factory definition for selecting a local Component.
|
|
383
|
+
* @param name - declared local position.
|
|
384
|
+
* @param fallback - Component used when the occurrence caller makes no selection.
|
|
385
|
+
* @returns a stable Component accepting only the local occurrence props.
|
|
386
|
+
*/
|
|
387
|
+
export type UseFactorySlot<F extends keyof SlotFactoryMap & string> = <N extends FactoryLocalNameOf<F>>(name: N, fallback: FactoryLocalComponent<F, N>) => SlotComponent<FactoryLocalInputPropsOf<F, N>>;
|
|
388
|
+
/**
|
|
389
|
+
* Render one independently keyed occurrence of a registered Factory.
|
|
390
|
+
* @param name - declaration-merged Factory name.
|
|
391
|
+
* @param props - caller-owned occurrence input.
|
|
392
|
+
* @param options - local Component selections and the missing-definition fallback.
|
|
393
|
+
* @returns the Factory occurrence or fallback.
|
|
394
|
+
*/
|
|
395
|
+
export type RenderFactorySlot = <F extends keyof SlotFactoryMap & string>(name: F, props: FactoryInputPropsOf<F>, options?: {
|
|
396
|
+
slots?: Partial<{
|
|
397
|
+
[N in FactoryLocalNameOf<F>]: FactoryLocalComponent<F, N>;
|
|
398
|
+
}>;
|
|
399
|
+
fallback?: ReactNode;
|
|
400
|
+
}) => ReactNode;
|
|
401
|
+
/** Factory rendering capability supplied to every renderer-created Component. */
|
|
402
|
+
export interface PropsRenderFactories {
|
|
403
|
+
renderFactorySlot: RenderFactorySlot;
|
|
404
|
+
}
|
|
316
405
|
/**
|
|
317
406
|
* Registrant hooks compartment: bare observable sources (getSnapshot +
|
|
318
407
|
* subscribe pairs) supplied under the reserved `hooks` key of an entry's
|
|
@@ -382,7 +471,7 @@ export type InjectFace<I extends object> = I extends {
|
|
|
382
471
|
* {@link PropsLocale}). Each share derives from its single source of truth;
|
|
383
472
|
* components reference this composition, never re-type it.
|
|
384
473
|
*/
|
|
385
|
-
export type ComposedProps<K extends keyof SlotMap & string, EntryKey extends EntryKeyOf<K>, S extends keyof SlotMap & string, H, I extends object, M = never, N = undefined> = PropsRuntime<K, EntryKey> & PropsRenderSlots<S> & PropsStore<H> & InjectFace<I> & MatchedShare<SlotMap[K], M> & PropsLocale<N>;
|
|
474
|
+
export type ComposedProps<K extends keyof SlotMap & string, EntryKey extends EntryKeyOf<K>, S extends keyof SlotMap & string, H, I extends object, M = never, N = undefined> = PropsRuntime<K, EntryKey> & PropsRenderSlots<S> & PropsRenderFactories & PropsStore<H> & InjectFace<I> & MatchedShare<SlotMap[K], M> & PropsLocale<N>;
|
|
386
475
|
/**
|
|
387
476
|
* Inject factory parameter list, derived from the registration's declaration:
|
|
388
477
|
* strict session slots receive a definite framework-resolved `sessionId`;
|
|
@@ -392,6 +481,72 @@ export type ComposedProps<K extends keyof SlotMap & string, EntryKey extends Ent
|
|
|
392
481
|
* object parameter exists.
|
|
393
482
|
*/
|
|
394
483
|
export type InjectParams<K extends keyof SlotMap & string, H> = ScopeOf<K> extends 'session' ? ([H] extends [StoreDecl] ? [sessionId: SessionIdOf, actions: BoundActions<HandleOf<H>>] : [sessionId: SessionIdOf]) : ScopeOf<K> extends 'session-maybe' ? ([H] extends [StoreDecl] ? [sessionId: SessionIdOf | undefined, actions: BoundActions<HandleOf<H>> | undefined] : [sessionId: SessionIdOf | undefined]) : ([H] extends [StoreDecl] ? [actions: BoundActions<HandleOf<H>>] : []);
|
|
484
|
+
/** Factory inject parameters use the same scope/store matrix as ordinary registrations. */
|
|
485
|
+
export type FactoryInjectParams<F extends keyof SlotFactoryMap & string> = FactoryDefOf<F>['scope'] extends 'session' ? ([FactoryStoreOf<F>] extends [StoreDecl] ? [sessionId: SessionIdOf, actions: BoundActions<FactoryStoreOf<F>>] : [sessionId: SessionIdOf]) : FactoryDefOf<F>['scope'] extends 'session-maybe' ? ([FactoryStoreOf<F>] extends [StoreDecl] ? [sessionId: SessionIdOf | undefined, actions: BoundActions<FactoryStoreOf<F>> | undefined] : [sessionId: SessionIdOf | undefined]) : ([FactoryStoreOf<F>] extends [StoreDecl] ? [actions: BoundActions<FactoryStoreOf<F>>] : []);
|
|
486
|
+
type FactoryField<F extends keyof SlotFactoryMap & string, K extends keyof SlotFactoryDef, Value> = K extends keyof SlotFactoryMap[F] ? {
|
|
487
|
+
[P in K]-?: Value;
|
|
488
|
+
} : {
|
|
489
|
+
[P in K]?: never;
|
|
490
|
+
};
|
|
491
|
+
type RuntimeFactorySlots<F extends keyof SlotFactoryMap & string> = {
|
|
492
|
+
[N in FactoryLocalNameOf<F>]: {
|
|
493
|
+
scope: FactoryLocalDefOf<F, N>['scope'];
|
|
494
|
+
};
|
|
495
|
+
};
|
|
496
|
+
type FactoryInjectCollisionKeys<F extends keyof SlotFactoryMap & string> = Extract<keyof InjectFace<FactoryInjectOf<F>>, keyof (FactoryRenderPropsOf<F> & PropsStore<FactoryStoreOf<F>> & PropsLocale<FactoryLocaleOf<F>> & PropsRenderFactories & ScopeStandardProps<FactoryDefOf<F>['scope']> & {
|
|
497
|
+
useFactorySlot: UseFactorySlot<F>;
|
|
498
|
+
})>;
|
|
499
|
+
type FactoryInputCollisionKeys<F extends keyof SlotFactoryMap & string> = Extract<keyof FactoryInputPropsOf<F>, keyof (FactoryRegistrationPropsOf<F> & ScopeStandardProps<FactoryDefOf<F>['scope']> & {
|
|
500
|
+
useFactorySlot: UseFactorySlot<F>;
|
|
501
|
+
})>;
|
|
502
|
+
type FactoryLocalCollisionKeys<F extends keyof SlotFactoryMap & string> = {
|
|
503
|
+
[N in FactoryLocalNameOf<F>]: Extract<keyof FactoryLocalInputPropsOf<F, N>, keyof (FactoryRegistrationPropsOf<F> & ScopeStandardProps<FactoryLocalDefOf<F, N>['scope']>)>;
|
|
504
|
+
}[FactoryLocalNameOf<F>];
|
|
505
|
+
type FactoryRegistrationLocalScopeCollisionKeys<F extends keyof SlotFactoryMap & string> = {
|
|
506
|
+
[N in FactoryLocalNameOf<F>]: Extract<keyof FactoryRegistrationPropsOf<F>, keyof ScopeStandardProps<FactoryLocalDefOf<F, N>['scope']>>;
|
|
507
|
+
}[FactoryLocalNameOf<F>];
|
|
508
|
+
type FactoryCollisionCheck<F extends keyof SlotFactoryMap & string> = [
|
|
509
|
+
FactoryInjectCollisionKeys<F> | FactoryInputCollisionKeys<F> | FactoryLocalCollisionKeys<F> | FactoryRegistrationLocalScopeCollisionKeys<F>
|
|
510
|
+
] extends [never] ? unknown : {
|
|
511
|
+
'Factory declaration has overlapping prop ownership': FactoryInjectCollisionKeys<F> | FactoryInputCollisionKeys<F> | FactoryLocalCollisionKeys<F> | FactoryRegistrationLocalScopeCollisionKeys<F>;
|
|
512
|
+
};
|
|
513
|
+
type FactoryChildMismatchKeys<F extends keyof SlotFactoryMap & string> = {
|
|
514
|
+
[K in keyof FactoryChildrenOf<F>]: K extends keyof SlotMap ? FactoryChildrenOf<F>[K] extends SlotSpec<SlotMap[K]> ? never : K : K;
|
|
515
|
+
}[keyof FactoryChildrenOf<F>];
|
|
516
|
+
type FactoryChildrenCheck<F extends keyof SlotFactoryMap & string> = [
|
|
517
|
+
FactoryChildMismatchKeys<F>
|
|
518
|
+
] extends [never] ? unknown : {
|
|
519
|
+
'Factory children must match SlotMap': FactoryChildMismatchKeys<F>;
|
|
520
|
+
};
|
|
521
|
+
/** Registration options checked against the complete declaration-merged Factory definition. */
|
|
522
|
+
export type RegisterFactoryOptions<F extends keyof SlotFactoryMap & string> = {
|
|
523
|
+
name: F;
|
|
524
|
+
scope: FactoryDefOf<F>['scope'];
|
|
525
|
+
} & FactoryField<F, 'children', FactoryChildrenOf<F>> & FactoryField<F, 'store', FactoryStoreOf<F> | (() => FactoryStoreOf<F>)> & FactoryField<F, 'inject', (...args: FactoryInjectParams<F>) => FactoryInjectOf<F>> & FactoryField<F, 'locale', FactoryLocaleOf<F>> & FactoryField<F, 'slots', RuntimeFactorySlots<F>> & FactoryCollisionCheck<F> & FactoryChildrenCheck<F>;
|
|
526
|
+
/**
|
|
527
|
+
* Typed registration method implemented by the renderer-owned SlotRegistry service.
|
|
528
|
+
* @param options - runtime values checked against the Factory declaration.
|
|
529
|
+
* @param component - reusable definition Component.
|
|
530
|
+
* @returns an idempotent definition disposer.
|
|
531
|
+
*/
|
|
532
|
+
export interface RegisterFactory {
|
|
533
|
+
<F extends keyof SlotFactoryMap & string>(options: RegisterFactoryOptions<F>, component: SlotComponent<FactoryComponentPropsOf<F>>): () => void;
|
|
534
|
+
}
|
|
535
|
+
/** Type-erased Factory definition stored by the runtime registry. */
|
|
536
|
+
export interface StoredFactory {
|
|
537
|
+
readonly name: string;
|
|
538
|
+
readonly component: unknown;
|
|
539
|
+
readonly scope: SlotScope;
|
|
540
|
+
readonly children?: Readonly<Record<string, SlotSpec<SlotEntryDef>>> | undefined;
|
|
541
|
+
readonly store?: StoreDecl | undefined;
|
|
542
|
+
readonly inject?: ((...args: never[]) => Record<string, unknown>) | undefined;
|
|
543
|
+
readonly locale?: string | undefined;
|
|
544
|
+
readonly slots?: Readonly<Record<string, {
|
|
545
|
+
scope: SlotScope;
|
|
546
|
+
}>> | undefined;
|
|
547
|
+
/** Diagnostics label of who registered the definition. */
|
|
548
|
+
readonly registrant?: string | undefined;
|
|
549
|
+
}
|
|
395
550
|
/**
|
|
396
551
|
* A list-entry display label: a plain string, or a thunk re-evaluated per
|
|
397
552
|
* read so registration-time text (nav rows, tabs) follows the active locale
|
|
@@ -506,8 +661,10 @@ export interface LiveSlotOccupant {
|
|
|
506
661
|
/** Whether the renderer currently selects this entry. */
|
|
507
662
|
active: boolean;
|
|
508
663
|
}
|
|
509
|
-
/** JSON-safe live
|
|
664
|
+
/** JSON-safe live Slot declaration tree. */
|
|
510
665
|
export interface LiveSlotNode {
|
|
666
|
+
/** Discriminant for an ordinary Slot declaration. */
|
|
667
|
+
type: 'slot';
|
|
511
668
|
/** Exact SlotMap key. */
|
|
512
669
|
name: string;
|
|
513
670
|
/** Slot cardinality. */
|
|
@@ -521,6 +678,21 @@ export interface LiveSlotNode {
|
|
|
521
678
|
/** Slots declared by entries mounted in this slot. */
|
|
522
679
|
children: LiveSlotNode[];
|
|
523
680
|
}
|
|
681
|
+
/** JSON-safe live Factory definition with its ordinary child Slot tree. */
|
|
682
|
+
export interface LiveFactoryNode {
|
|
683
|
+
/** Discriminant for a reusable Factory definition. */
|
|
684
|
+
type: 'factory';
|
|
685
|
+
/** Exact SlotFactoryMap key. */
|
|
686
|
+
name: string;
|
|
687
|
+
/** Runtime data scope inherited by each occurrence. */
|
|
688
|
+
scope: SlotScope;
|
|
689
|
+
/** Plugin or package that registered the definition, when known. */
|
|
690
|
+
registrant?: string;
|
|
691
|
+
/** Ordinary Slots declared by the Factory definition. */
|
|
692
|
+
children: LiveSlotNode[];
|
|
693
|
+
}
|
|
694
|
+
/** One root in the live Slot/Factory composition topology. */
|
|
695
|
+
export type LiveCompositionNode = LiveSlotNode | LiveFactoryNode;
|
|
524
696
|
/**
|
|
525
697
|
* Pure slot registry (no cordis; event emission and the renderer installation contract
|
|
526
698
|
* live in the runtime Service wrapper).
|
|
@@ -539,6 +711,7 @@ export interface LiveSlotNode {
|
|
|
539
711
|
*/
|
|
540
712
|
export declare class SlotCore {
|
|
541
713
|
private records;
|
|
714
|
+
private factories;
|
|
542
715
|
private mutateListeners;
|
|
543
716
|
/** Shared-handle scope ledger: handle → the scope it first mounted under + live mount count. */
|
|
544
717
|
private handleScopes;
|
|
@@ -554,6 +727,33 @@ export declare class SlotCore {
|
|
|
554
727
|
private abdicated;
|
|
555
728
|
private entryErrorListeners;
|
|
556
729
|
constructor();
|
|
730
|
+
/** Register one reusable Factory definition. */
|
|
731
|
+
readonly registerFactory: RegisterFactory;
|
|
732
|
+
/**
|
|
733
|
+
* Read one registered Factory definition.
|
|
734
|
+
* @param name - Factory name.
|
|
735
|
+
* @returns the live definition, or `undefined` when absent.
|
|
736
|
+
*/
|
|
737
|
+
factory(name: string): StoredFactory | undefined;
|
|
738
|
+
/**
|
|
739
|
+
* Read the monotonic definition version for one Factory name.
|
|
740
|
+
* @param name - Factory name.
|
|
741
|
+
* @returns the current version.
|
|
742
|
+
*/
|
|
743
|
+
factoryVersion(name: string): number;
|
|
744
|
+
/**
|
|
745
|
+
* Subscribe to one Factory definition's registration lifetime.
|
|
746
|
+
* @param name - Factory name.
|
|
747
|
+
* @param listener - callback notified after a definition change.
|
|
748
|
+
* @returns the unsubscribe function.
|
|
749
|
+
*/
|
|
750
|
+
subscribeFactory(name: string, listener: () => void): () => void;
|
|
751
|
+
/**
|
|
752
|
+
* Return whether a retained Factory definition is still registered.
|
|
753
|
+
* @param definition - retained definition identity.
|
|
754
|
+
* @returns whether that exact definition remains live.
|
|
755
|
+
*/
|
|
756
|
+
isFactoryLive(definition: StoredFactory): boolean;
|
|
557
757
|
/**
|
|
558
758
|
* Contribute a component to a declared slot and (optionally) declare child
|
|
559
759
|
* slots, a store seat, and the registrant's business face.
|
|
@@ -581,7 +781,7 @@ export declare class SlotCore {
|
|
|
581
781
|
* @param options - registration options: target `name`, `children`
|
|
582
782
|
* declaration table, `store` seat, `inject` business-face factory, kind
|
|
583
783
|
* shape fields (keyed `key`; list `id`/`order`/`label`).
|
|
584
|
-
* @param component - component honoring the
|
|
784
|
+
* @param component - component honoring the five-share composed props
|
|
585
785
|
* contract ({@link ComposedProps}); checked at this call site.
|
|
586
786
|
* @returns disposer removing the registration and its declarations
|
|
587
787
|
* (idempotent; stale disposers after a cascade are no-ops).
|
|
@@ -595,7 +795,7 @@ export declare class SlotCore {
|
|
|
595
795
|
* factory's return and joins the component's composed-props constraint
|
|
596
796
|
* (factory parameters derive from the declaration, {@link InjectParams}).
|
|
597
797
|
* @param options - registration options plus the `inject` business-face factory.
|
|
598
|
-
* @param component - component honoring the
|
|
798
|
+
* @param component - component honoring the five-share composed props
|
|
599
799
|
* contract including the inject share `I`.
|
|
600
800
|
* @returns disposer removing the registration and its declarations.
|
|
601
801
|
*/
|
|
@@ -648,10 +848,12 @@ export declare class SlotCore {
|
|
|
648
848
|
specDynamic(key: string): SlotSpec<SlotEntryDef> | undefined;
|
|
649
849
|
/**
|
|
650
850
|
* Export the current declaration topology without components or executable hooks.
|
|
651
|
-
*
|
|
851
|
+
* Factory definitions appear as `factory:<name>` parents of their ordinary
|
|
852
|
+
* child Slots, matching the parent/child topology of ordinary registrations.
|
|
853
|
+
* @param root - exact Slot or `factory:<name>` key to select; omitted returns every live root.
|
|
652
854
|
* @returns selected live Slot trees, or an empty array when `root` is unavailable.
|
|
653
855
|
*/
|
|
654
|
-
snapshot(root?: string):
|
|
856
|
+
snapshot(root?: string): LiveCompositionNode[];
|
|
655
857
|
/**
|
|
656
858
|
* Read the declaration lifetime of a key. Entry additions and removals do
|
|
657
859
|
* not change it; declaration creation and collapse each advance it.
|
|
@@ -713,16 +915,22 @@ export declare class SlotCore {
|
|
|
713
915
|
abdicate: boolean;
|
|
714
916
|
}): void;
|
|
715
917
|
/**
|
|
716
|
-
*
|
|
717
|
-
*
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
|
|
722
|
-
|
|
918
|
+
* Report a contained Factory occurrence crash through the ordinary entry
|
|
919
|
+
* supervision channel without retiring the shared definition.
|
|
920
|
+
* @param name - Factory name whose occurrence crashed.
|
|
921
|
+
* @param registration - Factory definition or caller registration that owns the crashing Component.
|
|
922
|
+
* @param error - the crash cause, forwarded to listeners verbatim.
|
|
923
|
+
*/
|
|
924
|
+
reportFactoryError(name: string, registration: StoredEntry | StoredFactory, error: unknown): void;
|
|
925
|
+
/**
|
|
926
|
+
* Observe ordinary entry and Factory occurrence crashes. Fires synchronously
|
|
927
|
+
* per report, after any ordinary-entry abdication mutation. Factory failures
|
|
928
|
+
* never retire their shared definition.
|
|
929
|
+
* @param fn - called with the Slot or `factory:<name>` key, the crashed
|
|
930
|
+
* registration, the cause, and whether an ordinary entry was retired.
|
|
723
931
|
* @returns unsubscribe.
|
|
724
932
|
*/
|
|
725
|
-
onEntryError(fn: (key: string,
|
|
933
|
+
onEntryError(fn: (key: string, registration: StoredEntry | StoredFactory, error: unknown, info: {
|
|
726
934
|
abdicated: boolean;
|
|
727
935
|
}) => void): () => void;
|
|
728
936
|
/**
|
|
@@ -732,7 +940,10 @@ export declare class SlotCore {
|
|
|
732
940
|
* axis: ledger rows, slots, contributions, and store mounts die together.
|
|
733
941
|
*/
|
|
734
942
|
private releaseEntry;
|
|
943
|
+
private releaseChildren;
|
|
735
944
|
private record;
|
|
945
|
+
private factoryRecord;
|
|
946
|
+
private markFactoryDirty;
|
|
736
947
|
private markDirty;
|
|
737
948
|
private notifyDeclaration;
|
|
738
949
|
private flush;
|
package/lib/types/renderer.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
4
|
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
5
|
-
import type { SessionAreaProps, SlotEntryDef, SlotScope, SlotSpec, StoredEntry, Translate } from './index.ts';
|
|
5
|
+
import type { SessionAreaProps, SlotEntryDef, SlotScope, SlotSpec, StoredEntry, StoredFactory, Translate } from './index.ts';
|
|
6
6
|
/**
|
|
7
7
|
* The locale face the render machinery consumes: namespace binding plus an
|
|
8
8
|
* observable revision (getSnapshot/subscribe pair — the same HostObservable
|
|
@@ -78,14 +78,14 @@ export interface ScopedStandardSourceBinding extends StandardSourceBinding {
|
|
|
78
78
|
}
|
|
79
79
|
/** One installed source of bindings for a non-root Slot scope. */
|
|
80
80
|
export interface SlotScopeAdapter {
|
|
81
|
-
/**
|
|
81
|
+
/** Default binding inherited by scoped entries, including its absent projection. */
|
|
82
82
|
readonly current: HostObservable<StandardSourceBinding>;
|
|
83
83
|
/**
|
|
84
|
-
* Resolve an
|
|
85
|
-
* @param
|
|
86
|
-
* @returns the
|
|
84
|
+
* Resolve a stable observable for an explicit scope target or explicit absence.
|
|
85
|
+
* @param target - domain-owned Provider target, or absence.
|
|
86
|
+
* @returns the target's current standard-source binding.
|
|
87
87
|
*/
|
|
88
|
-
|
|
88
|
+
bindingSource(target: SessionAreaProps['session']): HostObservable<StandardSourceBinding>;
|
|
89
89
|
/**
|
|
90
90
|
* Render the scope owner's area seat over the current binding. The renderer
|
|
91
91
|
* binds this function to the standard `SessionProvider` prop without owning
|
|
@@ -154,6 +154,13 @@ export interface SlotRendererHost {
|
|
|
154
154
|
reportEntryError(key: string, entry: StoredEntry, error: unknown, info: {
|
|
155
155
|
abdicate: boolean;
|
|
156
156
|
}): void;
|
|
157
|
+
/**
|
|
158
|
+
* Report a contained Factory occurrence crash without retiring its shared definition.
|
|
159
|
+
* @param name - Factory name whose occurrence crashed.
|
|
160
|
+
* @param registration - Factory definition or caller registration that owns the crashing Component.
|
|
161
|
+
* @param error - the crash cause.
|
|
162
|
+
*/
|
|
163
|
+
reportFactoryError(name: string, registration: StoredEntry | StoredFactory, error: unknown): void;
|
|
157
164
|
/**
|
|
158
165
|
* Declared runtime spec from the declarations ledger.
|
|
159
166
|
* @param key - slot key.
|
|
@@ -174,6 +181,47 @@ export interface SlotRendererHost {
|
|
|
174
181
|
* @returns the instance, or undefined when the entry declares no store.
|
|
175
182
|
*/
|
|
176
183
|
storeOf(entry: StoredEntry, scopeBinding: ScopedStandardSourceBinding | undefined): StoreInstanceLike | undefined;
|
|
184
|
+
/**
|
|
185
|
+
* Resolve a Factory Store for one render occurrence and inherited scope.
|
|
186
|
+
* @param definition - live Factory definition.
|
|
187
|
+
* @param scopeBinding - exact Session binding for scoped Factories, undefined for root scope.
|
|
188
|
+
* @param occurrence - identity token owned by the render position.
|
|
189
|
+
* @returns the occurrence Store instance, or undefined without a Store declaration.
|
|
190
|
+
*/
|
|
191
|
+
factoryStoreOf(definition: StoredFactory, scopeBinding: ScopedStandardSourceBinding | undefined, occurrence: object): StoreInstanceLike | undefined;
|
|
192
|
+
/**
|
|
193
|
+
* Retain an exclusive Factory Store occurrence after React commits it.
|
|
194
|
+
* Repeated setup and cleanup preserve the occurrence's Store identity.
|
|
195
|
+
* @param definition - the live Factory definition.
|
|
196
|
+
* @param occurrence - identity token owned by the mounted render position.
|
|
197
|
+
* @returns an idempotent cleanup function.
|
|
198
|
+
*/
|
|
199
|
+
retainFactoryOccurrence(definition: StoredFactory, occurrence: object): () => void;
|
|
200
|
+
/**
|
|
201
|
+
* Subscribe to one Factory definition's registration lifetime.
|
|
202
|
+
* @param name - Factory name.
|
|
203
|
+
* @param fn - change callback.
|
|
204
|
+
* @returns the unsubscribe function.
|
|
205
|
+
*/
|
|
206
|
+
subscribeFactory(name: string, fn: () => void): () => void;
|
|
207
|
+
/**
|
|
208
|
+
* Read the monotonic version for one Factory definition.
|
|
209
|
+
* @param name - Factory name.
|
|
210
|
+
* @returns the current definition version.
|
|
211
|
+
*/
|
|
212
|
+
getFactoryVersion(name: string): number;
|
|
213
|
+
/**
|
|
214
|
+
* Read one live Factory definition.
|
|
215
|
+
* @param name - Factory name.
|
|
216
|
+
* @returns the definition, or undefined while unregistered.
|
|
217
|
+
*/
|
|
218
|
+
factoryOf(name: string): StoredFactory | undefined;
|
|
219
|
+
/**
|
|
220
|
+
* Check retained Factory render authority.
|
|
221
|
+
* @param definition - a previously resolved Factory definition.
|
|
222
|
+
* @returns whether that exact definition remains live.
|
|
223
|
+
*/
|
|
224
|
+
isFactoryLive(definition: StoredFactory): boolean;
|
|
177
225
|
/** Root standard data assembled from domain-owned contributions. */
|
|
178
226
|
readonly root: HostObservable<StandardSourceBinding>;
|
|
179
227
|
/** Monotonic source updated whenever the installed scope-adapter roster changes. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-slots",
|
|
3
|
-
"description": "Slot registry pure core:
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "Slot registry pure core: typed ordinary Slots and reusable Component Factories, derived props, Store seats, and renderer installation",
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/react": "~18.3.1",
|
|
27
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
28
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
27
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
28
|
+
"@deepseek-ai/dsh-client-store": "^0.1.7-alpha.1"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"lib/index.js",
|
|
32
32
|
"lib/types/**/*.d.ts"
|
|
33
33
|
],
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
35
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
36
36
|
}
|
|
37
37
|
}
|