@longzai-intelligence-agent-ui-render/render 0.0.0-stage → 0.0.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.md +10 -2
- package/dist/index.d.ts +608 -0
- package/dist/index.js +1 -0
- package/package.json +56 -4
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @longzai-intelligence-agent-ui-render/render
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
渲染端口包。承载 `RendererPort` 与流编译器端口、`SpecStreamCompiler` 与表达式与可见性与 watch 三类求值器、json-render 适配器与注册表构建器、`createRendererPort` 与 `createRendererCapability` 装配工厂,保留 headless 与带注册表两形态。
|
|
4
|
+
|
|
5
|
+
## 导出面
|
|
6
|
+
|
|
7
|
+
- 端口:`RendererPort`、`SpecStreamCompilerPort` 及注入令牌
|
|
8
|
+
- 纯逻辑件:`SpecStreamCompiler`、`ExpressionEvaluator`、`VisibilityEvaluator`、`WatchEvaluator`
|
|
9
|
+
- 基础设施件:`createJsonRenderAdapter`、`RegistryBuilder`、`Registry`
|
|
10
|
+
- 装配工厂:`createRendererPort`、`createRendererCapability`
|
|
11
|
+
- 错误面:`RendererError`、`SpecValidationError`、`UnknownComponentError`
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import { DomainError } from "@longzai-intelligence-error/core";
|
|
2
|
+
import { Catalog, Spec, SpecStreamPatch } from "@longzai-intelligence-agent-ui-render/core";
|
|
3
|
+
import { ComponentType } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* The result of a render operation.
|
|
6
|
+
*
|
|
7
|
+
* Either carries the serialized HTML output of the rendered Spec tree,
|
|
8
|
+
* or an error message describing why rendering failed.
|
|
9
|
+
*/
|
|
10
|
+
type RenderResult = {
|
|
11
|
+
/**
|
|
12
|
+
* The serialized HTML output of the rendered tree (present on success).
|
|
13
|
+
*/
|
|
14
|
+
html?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The error message (present on failure).
|
|
17
|
+
*/
|
|
18
|
+
error?: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Options for a single-shot render operation.
|
|
22
|
+
*
|
|
23
|
+
* The renderer takes a fully-assembled Spec plus optional initial state
|
|
24
|
+
* and an optional Catalog snapshot, and produces a RenderResult.
|
|
25
|
+
*/
|
|
26
|
+
type RenderOptions = {
|
|
27
|
+
/**
|
|
28
|
+
* The fully-assembled Spec tree to render.
|
|
29
|
+
*/
|
|
30
|
+
spec: Spec;
|
|
31
|
+
/**
|
|
32
|
+
* Optional initial state values merged on top of the Spec's state.
|
|
33
|
+
*/
|
|
34
|
+
state?: Record<string, unknown>;
|
|
35
|
+
/**
|
|
36
|
+
* Optional Catalog snapshot used for component whitelist resolution.
|
|
37
|
+
*/
|
|
38
|
+
catalog?: Catalog;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Options for a streaming render operation.
|
|
42
|
+
*
|
|
43
|
+
* The renderer consumes a sequence of SpecStreamPatch values, progressively
|
|
44
|
+
* building the Spec tree, and yields the intermediate Spec after each patch
|
|
45
|
+
* is applied. This enables progressive rendering of AI-generated UIs.
|
|
46
|
+
*/
|
|
47
|
+
type StreamRenderOptions = {
|
|
48
|
+
/**
|
|
49
|
+
* The sequence of SpecStreamPatch values to apply.
|
|
50
|
+
*/
|
|
51
|
+
patches: SpecStreamPatch[];
|
|
52
|
+
/**
|
|
53
|
+
* Optional initial state values merged into the initial Spec state.
|
|
54
|
+
*/
|
|
55
|
+
initialState?: Record<string, unknown>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Renderer domain service interface (Port).
|
|
59
|
+
*
|
|
60
|
+
* Defines the standard operations for the rendering runtime:
|
|
61
|
+
* single-shot rendering of a fully-assembled Spec, and progressive
|
|
62
|
+
* streaming render of SpecStream patches.
|
|
63
|
+
*
|
|
64
|
+
* **Architecture**:
|
|
65
|
+
*
|
|
66
|
+
* - Defined in the Contract package, consumed by Implementation and external packages.
|
|
67
|
+
* - The Implementation package provides the SpecStreamCompiler and expression evaluators.
|
|
68
|
+
* - The Infrastructure package provides the concrete json-render adapter implementation.
|
|
69
|
+
*/
|
|
70
|
+
type RendererPort = {
|
|
71
|
+
/**
|
|
72
|
+
* Render a fully-assembled Spec tree.
|
|
73
|
+
*
|
|
74
|
+
* @param options - Render options (spec + optional state and catalog)
|
|
75
|
+
* @returns The render result (HTML output or error message)
|
|
76
|
+
*/
|
|
77
|
+
render(options: RenderOptions): Promise<RenderResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Progressively render a stream of SpecStream patches.
|
|
80
|
+
*
|
|
81
|
+
* Yields the intermediate Spec after each patch is applied,
|
|
82
|
+
* enabling progressive rendering of AI-generated UIs.
|
|
83
|
+
*
|
|
84
|
+
* @param options - Stream render options (patches + optional initial state)
|
|
85
|
+
* @returns An async generator yielding the intermediate Spec after each patch
|
|
86
|
+
*/
|
|
87
|
+
renderStream(options: StreamRenderOptions): AsyncGenerator<Spec, void, unknown>;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* RendererPort dependency injection token.
|
|
91
|
+
*/
|
|
92
|
+
declare const RENDERER_PORT_TOKEN: unique symbol;
|
|
93
|
+
/**
|
|
94
|
+
* 流编译器端口
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
97
|
+
* 以无状态方式把补丁序列应用到初始 Spec,返回渐进装配后的 Spec 树
|
|
98
|
+
*/
|
|
99
|
+
type SpecStreamCompilerPort = {
|
|
100
|
+
/**
|
|
101
|
+
* 编译一段补丁序列为 Spec 树
|
|
102
|
+
*
|
|
103
|
+
* @param patches - 待应用的 SpecStream 补丁序列
|
|
104
|
+
* @param initialSpec - 可选的初始 Spec,缺省为空 Spec
|
|
105
|
+
* @returns 装配完成的 Spec 树
|
|
106
|
+
*/
|
|
107
|
+
compile(patches: SpecStreamPatch[], initialSpec?: Spec): Spec;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* 流编译器端口依赖注入令牌
|
|
111
|
+
*/
|
|
112
|
+
declare const SPEC_STREAM_COMPILER_TOKEN: unique symbol;
|
|
113
|
+
/**
|
|
114
|
+
* Generic renderer error.
|
|
115
|
+
*
|
|
116
|
+
* Thrown when a rendering operation fails for a reason not covered by
|
|
117
|
+
* the more specific error subclasses (e.g. an unexpected exception
|
|
118
|
+
* during expression evaluation or component rendering).
|
|
119
|
+
*/
|
|
120
|
+
declare class RendererError extends DomainError {
|
|
121
|
+
/**
|
|
122
|
+
* Create a RendererError instance.
|
|
123
|
+
*
|
|
124
|
+
* @param message - The error message describing the failure
|
|
125
|
+
*/
|
|
126
|
+
constructor(message: string);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Spec validation error.
|
|
130
|
+
*
|
|
131
|
+
* Thrown when a Spec or SpecStreamPatch fails schema validation
|
|
132
|
+
* (e.g. missing root element, unknown patch operation, malformed
|
|
133
|
+
* element definition).
|
|
134
|
+
*/
|
|
135
|
+
declare class SpecValidationError extends DomainError {
|
|
136
|
+
/**
|
|
137
|
+
* The validation error messages.
|
|
138
|
+
*/
|
|
139
|
+
readonly errors: string[];
|
|
140
|
+
/**
|
|
141
|
+
* Create a SpecValidationError instance.
|
|
142
|
+
*
|
|
143
|
+
* @param errors - The validation error messages
|
|
144
|
+
*/
|
|
145
|
+
constructor(errors: string[]);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Unknown component error.
|
|
149
|
+
*
|
|
150
|
+
* Thrown when a Spec element references a component type that is not
|
|
151
|
+
* present in the Catalog or Registry (i.e. no React implementation is
|
|
152
|
+
* registered for the component name).
|
|
153
|
+
*/
|
|
154
|
+
declare class UnknownComponentError extends DomainError {
|
|
155
|
+
/**
|
|
156
|
+
* The unknown component type name.
|
|
157
|
+
*/
|
|
158
|
+
readonly componentType: string;
|
|
159
|
+
/**
|
|
160
|
+
* Create an UnknownComponentError instance.
|
|
161
|
+
*
|
|
162
|
+
* @param componentType - The unknown component type name
|
|
163
|
+
*/
|
|
164
|
+
constructor(componentType: string);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* SpecStream compiler.
|
|
168
|
+
*
|
|
169
|
+
* Applies a sequence of SpecStreamPatch values onto an initial Spec,
|
|
170
|
+
* producing the progressively-built Spec tree. The compiler is
|
|
171
|
+
* stateless: each `compile` call returns a fresh result derived from
|
|
172
|
+
* the supplied initial Spec (or an empty Spec when omitted).
|
|
173
|
+
*/
|
|
174
|
+
declare class SpecStreamCompiler {
|
|
175
|
+
/**
|
|
176
|
+
* Compile a sequence of SpecStreamPatch values into a Spec.
|
|
177
|
+
*
|
|
178
|
+
* @param patches - The sequence of SpecStreamPatch values to apply
|
|
179
|
+
* @param initialSpec - Optional initial Spec to apply patches onto
|
|
180
|
+
* @returns The fully-assembled Spec tree
|
|
181
|
+
*/
|
|
182
|
+
compile(patches: SpecStreamPatch[], initialSpec?: Spec): Spec;
|
|
183
|
+
/**
|
|
184
|
+
* Apply a single SpecStreamPatch onto the working Spec.
|
|
185
|
+
*
|
|
186
|
+
* @param spec - The working Spec (mutated in place)
|
|
187
|
+
* @param patch - The patch to apply
|
|
188
|
+
* @throws {@link SpecValidationError} The patch operation is unknown
|
|
189
|
+
*/
|
|
190
|
+
private applyPatch;
|
|
191
|
+
/**
|
|
192
|
+
* Parse a JSON-pointer-like path into decoded segments.
|
|
193
|
+
*
|
|
194
|
+
* @param path - The JSON-pointer-like path (e.g. "/elements/btn1/props/label")
|
|
195
|
+
* @returns The decoded path segments (e.g. ["elements", "btn1", "props", "label"])
|
|
196
|
+
* @throws {@link SpecValidationError} A path segment is malformed
|
|
197
|
+
*/
|
|
198
|
+
private parsePath;
|
|
199
|
+
/**
|
|
200
|
+
* Apply an `add` patch.
|
|
201
|
+
*
|
|
202
|
+
* Creates a new element or nested field. When targeting `/elements/<id>`,
|
|
203
|
+
* the value is treated as a full SpecElement. When targeting `/root`,
|
|
204
|
+
* the value is the root element id string. When targeting `/state`,
|
|
205
|
+
* the value is the initial state object.
|
|
206
|
+
*
|
|
207
|
+
* @param spec - The working Spec (mutated in place)
|
|
208
|
+
* @param segments - The decoded path segments
|
|
209
|
+
* @param value - The patch payload value
|
|
210
|
+
* @throws {@link SpecValidationError} The add target is invalid
|
|
211
|
+
*/
|
|
212
|
+
private applyAdd;
|
|
213
|
+
/**
|
|
214
|
+
* Add an element or a nested field on an existing element.
|
|
215
|
+
*
|
|
216
|
+
* @param spec - The working Spec (mutated in place)
|
|
217
|
+
* @param rest - The path segments after "/elements"
|
|
218
|
+
* @param value - The patch payload value
|
|
219
|
+
* @throws {@link SpecValidationError} The element target or value is invalid
|
|
220
|
+
*/
|
|
221
|
+
private addElement;
|
|
222
|
+
/**
|
|
223
|
+
* Add a state field (or nested state path).
|
|
224
|
+
*
|
|
225
|
+
* @param spec - The working Spec (mutated in place)
|
|
226
|
+
* @param rest - The path segments after "/state"
|
|
227
|
+
* @param value - The patch payload value
|
|
228
|
+
* @throws {@link SpecValidationError} The state value is not an object
|
|
229
|
+
*/
|
|
230
|
+
private addStateField;
|
|
231
|
+
/**
|
|
232
|
+
* Apply an `update` patch.
|
|
233
|
+
*
|
|
234
|
+
* Replaces a field value in place. Behaves like `replace` but is
|
|
235
|
+
* conventionally used for partial field updates.
|
|
236
|
+
*
|
|
237
|
+
* @param spec - The working Spec (mutated in place)
|
|
238
|
+
* @param segments - The decoded path segments
|
|
239
|
+
* @param value - The patch payload value
|
|
240
|
+
* @throws {@link SpecValidationError} Delegated to applyReplace when the target is invalid
|
|
241
|
+
*/
|
|
242
|
+
private applyUpdate;
|
|
243
|
+
/**
|
|
244
|
+
* Apply a `remove` patch.
|
|
245
|
+
*
|
|
246
|
+
* Deletes an element or nested field.
|
|
247
|
+
*
|
|
248
|
+
* @param spec - The working Spec (mutated in place)
|
|
249
|
+
* @param segments - The decoded path segments
|
|
250
|
+
* @throws {@link SpecValidationError} The remove target is invalid
|
|
251
|
+
*/
|
|
252
|
+
private applyRemove;
|
|
253
|
+
/**
|
|
254
|
+
* Apply a `replace` patch.
|
|
255
|
+
*
|
|
256
|
+
* Wholesale replaces an element or subtree.
|
|
257
|
+
*
|
|
258
|
+
* @param spec - The working Spec (mutated in place)
|
|
259
|
+
* @param segments - The decoded path segments
|
|
260
|
+
* @param value - The patch payload value
|
|
261
|
+
* @throws {@link SpecValidationError} The replace target or value is invalid
|
|
262
|
+
*/
|
|
263
|
+
private applyReplace;
|
|
264
|
+
/**
|
|
265
|
+
* Set a nested field on a SpecElement (e.g. props.label, children).
|
|
266
|
+
*
|
|
267
|
+
* @param element - The element to mutate
|
|
268
|
+
* @param segments - The path segments relative to the element
|
|
269
|
+
* @param value - The value to set
|
|
270
|
+
* @throws {@link SpecValidationError} The field path is invalid
|
|
271
|
+
*/
|
|
272
|
+
private setNestedField;
|
|
273
|
+
/**
|
|
274
|
+
* Remove a nested field on a SpecElement.
|
|
275
|
+
*
|
|
276
|
+
* @param element - The element to mutate
|
|
277
|
+
* @param segments - The path segments relative to the element
|
|
278
|
+
* @throws {@link SpecValidationError} The field path is invalid
|
|
279
|
+
*/
|
|
280
|
+
private removeNestedField;
|
|
281
|
+
/**
|
|
282
|
+
* Set a nested field on a plain record, creating intermediate objects
|
|
283
|
+
* as needed.
|
|
284
|
+
*
|
|
285
|
+
* @param record - The record to mutate
|
|
286
|
+
* @param segments - The path segments
|
|
287
|
+
* @param value - The value to set
|
|
288
|
+
*/
|
|
289
|
+
private setNestedRecord;
|
|
290
|
+
/**
|
|
291
|
+
* Remove a nested field from a plain record.
|
|
292
|
+
*
|
|
293
|
+
* @param record - The record to mutate
|
|
294
|
+
* @param segments - The path segments
|
|
295
|
+
*/
|
|
296
|
+
private removeNestedRecord;
|
|
297
|
+
/**
|
|
298
|
+
* Determine whether a value is a valid SpecElement.
|
|
299
|
+
*
|
|
300
|
+
* @param value - The value to test
|
|
301
|
+
* @returns True if the value narrows to SpecElement
|
|
302
|
+
*/
|
|
303
|
+
private isSpecElement;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Expression evaluator.
|
|
307
|
+
*
|
|
308
|
+
* Evaluates the json-render expression DSL against a state snapshot.
|
|
309
|
+
* The supported expression directives are:
|
|
310
|
+
*
|
|
311
|
+
* - `$state` — read a value from state by dot path
|
|
312
|
+
* - `$cond` — conditional (if / then / else)
|
|
313
|
+
* - `$template` — string interpolation with `{{path}}` placeholders
|
|
314
|
+
* - `$computed` — call a registered function with arguments
|
|
315
|
+
* - `$bindState` — two-way binding path (returns the binding path string)
|
|
316
|
+
*/
|
|
317
|
+
/**
|
|
318
|
+
* A registered computed function. Receives the resolved arguments and
|
|
319
|
+
* the current state, and returns a value of any type.
|
|
320
|
+
*/
|
|
321
|
+
type ComputedFunction = (args: unknown[], state: Record<string, unknown>) => unknown;
|
|
322
|
+
/**
|
|
323
|
+
* Expression evaluator.
|
|
324
|
+
*
|
|
325
|
+
* Recursively walks an expression tree, resolving json-render
|
|
326
|
+
* directives against the supplied state. Plain objects without a
|
|
327
|
+
* recognized directive key are walked deeply, so nested expressions
|
|
328
|
+
* inside props are resolved.
|
|
329
|
+
*/
|
|
330
|
+
declare class ExpressionEvaluator {
|
|
331
|
+
/**
|
|
332
|
+
* The registered computed functions, keyed by function name.
|
|
333
|
+
*/
|
|
334
|
+
private readonly computed;
|
|
335
|
+
/**
|
|
336
|
+
* Create an ExpressionEvaluator instance.
|
|
337
|
+
*
|
|
338
|
+
* @param computed - Optional map of registered computed functions
|
|
339
|
+
*/
|
|
340
|
+
constructor(computed?: Map<string, ComputedFunction>);
|
|
341
|
+
/**
|
|
342
|
+
* Evaluate an expression against a state snapshot.
|
|
343
|
+
*
|
|
344
|
+
* @param expr - The expression value (any type)
|
|
345
|
+
* @param state - The current state snapshot
|
|
346
|
+
* @returns The resolved value
|
|
347
|
+
*/
|
|
348
|
+
evaluateExpression(expr: unknown, state: Record<string, unknown>): unknown;
|
|
349
|
+
/**
|
|
350
|
+
* Evaluate a `$state` directive.
|
|
351
|
+
*
|
|
352
|
+
* Reads a value from state by dot path. A non-string operand is
|
|
353
|
+
* returned as-is.
|
|
354
|
+
*
|
|
355
|
+
* @param operand - The directive operand (a dot path string)
|
|
356
|
+
* @param state - The current state snapshot
|
|
357
|
+
* @returns The state value at the path, or undefined if not found
|
|
358
|
+
*/
|
|
359
|
+
private evaluateState;
|
|
360
|
+
/**
|
|
361
|
+
* Evaluate a `$cond` directive.
|
|
362
|
+
*
|
|
363
|
+
* Expects an operand with `if`, `then`, and optional `else` fields.
|
|
364
|
+
* The `if` condition is evaluated as a boolean; truthy yields `then`,
|
|
365
|
+
* otherwise `else` (or undefined when `else` is omitted).
|
|
366
|
+
*
|
|
367
|
+
* @param operand - The directive operand
|
|
368
|
+
* @param state - The current state snapshot
|
|
369
|
+
* @returns The resolved branch value
|
|
370
|
+
*/
|
|
371
|
+
private evaluateCond;
|
|
372
|
+
/**
|
|
373
|
+
* Evaluate a `$template` directive.
|
|
374
|
+
*
|
|
375
|
+
* Interpolates `{{path}}` placeholders in a string with state values
|
|
376
|
+
* read by dot path.
|
|
377
|
+
*
|
|
378
|
+
* @param operand - The template string
|
|
379
|
+
* @param state - The current state snapshot
|
|
380
|
+
* @returns The interpolated string
|
|
381
|
+
*/
|
|
382
|
+
private evaluateTemplate;
|
|
383
|
+
/**
|
|
384
|
+
* Evaluate a `$computed` directive.
|
|
385
|
+
*
|
|
386
|
+
* Expects an operand with `fn` (the function name) and optional
|
|
387
|
+
* `args` (an array of expressions to evaluate before calling).
|
|
388
|
+
*
|
|
389
|
+
* @param operand - The directive operand
|
|
390
|
+
* @param state - The current state snapshot
|
|
391
|
+
* @returns The computed function's return value
|
|
392
|
+
*/
|
|
393
|
+
private evaluateComputed;
|
|
394
|
+
/**
|
|
395
|
+
* Evaluate a `$bindState` directive.
|
|
396
|
+
*
|
|
397
|
+
* Returns the binding path string as-is, enabling the renderer to
|
|
398
|
+
* wire two-way bindings between a component prop and a state path.
|
|
399
|
+
*
|
|
400
|
+
* @param operand - The binding path string
|
|
401
|
+
* @returns The binding path string
|
|
402
|
+
*/
|
|
403
|
+
private evaluateBindState;
|
|
404
|
+
/**
|
|
405
|
+
* Read a value from a record by dot path.
|
|
406
|
+
*
|
|
407
|
+
* @param record - The record to read from
|
|
408
|
+
* @param path - The dot path (e.g. "user.profile.name")
|
|
409
|
+
* @returns The value at the path, or undefined if not found
|
|
410
|
+
*/
|
|
411
|
+
private readPath;
|
|
412
|
+
/**
|
|
413
|
+
* Determine whether a value is truthy for $cond evaluation.
|
|
414
|
+
*
|
|
415
|
+
* @param value - The value to test
|
|
416
|
+
* @returns True if the value is truthy
|
|
417
|
+
*/
|
|
418
|
+
private isTruthy;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Visibility evaluator.
|
|
422
|
+
*
|
|
423
|
+
* Delegates expression resolution to the {@link ExpressionEvaluator}
|
|
424
|
+
* and coerces the result to a boolean. A missing `visible` field
|
|
425
|
+
* means the element is visible.
|
|
426
|
+
*/
|
|
427
|
+
declare class VisibilityEvaluator {
|
|
428
|
+
/**
|
|
429
|
+
* The expression evaluator used to resolve expression-typed visible
|
|
430
|
+
* values.
|
|
431
|
+
*/
|
|
432
|
+
private readonly expressionEvaluator;
|
|
433
|
+
/**
|
|
434
|
+
* Create a VisibilityEvaluator instance.
|
|
435
|
+
*
|
|
436
|
+
* @param expressionEvaluator - The expression evaluator to delegate to
|
|
437
|
+
*/
|
|
438
|
+
constructor(expressionEvaluator: ExpressionEvaluator);
|
|
439
|
+
/**
|
|
440
|
+
* Evaluate a `visible` value against a state snapshot.
|
|
441
|
+
*
|
|
442
|
+
* @param visible - The visible value (boolean, expression, or undefined)
|
|
443
|
+
* @param state - The current state snapshot
|
|
444
|
+
* @returns True if the element should be rendered
|
|
445
|
+
*/
|
|
446
|
+
evaluate(visible: unknown, state: Record<string, unknown>): boolean;
|
|
447
|
+
/**
|
|
448
|
+
* Coerce a resolved value to a boolean for visibility decisions.
|
|
449
|
+
*
|
|
450
|
+
* @param value - The resolved value
|
|
451
|
+
* @returns The boolean coercion
|
|
452
|
+
*/
|
|
453
|
+
private coerceBoolean;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Watch evaluator.
|
|
457
|
+
*
|
|
458
|
+
* Evaluates the `watch` field of a SpecElement against a state
|
|
459
|
+
* transition. Each watch entry is keyed by a state path; when the
|
|
460
|
+
* value at that path changes between the previous and current state
|
|
461
|
+
* snapshots, the entry's action is emitted.
|
|
462
|
+
*/
|
|
463
|
+
/**
|
|
464
|
+
* A triggered watch action. Emitted when a watched state path changes
|
|
465
|
+
* between two state snapshots.
|
|
466
|
+
*/
|
|
467
|
+
type WatchAction = {
|
|
468
|
+
/**
|
|
469
|
+
* The action name to invoke.
|
|
470
|
+
*/
|
|
471
|
+
action: string;
|
|
472
|
+
/**
|
|
473
|
+
* The action parameters (carried verbatim from the watch entry).
|
|
474
|
+
*/
|
|
475
|
+
params: unknown;
|
|
476
|
+
};
|
|
477
|
+
/**
|
|
478
|
+
* Watch evaluator.
|
|
479
|
+
*
|
|
480
|
+
* Iterates over a SpecElement's `watch` map, comparing the previous
|
|
481
|
+
* and current state values at each watched path. When a change is
|
|
482
|
+
* detected, the entry's action is added to the emitted list.
|
|
483
|
+
*/
|
|
484
|
+
declare class WatchEvaluator {
|
|
485
|
+
/**
|
|
486
|
+
* Evaluate the watch map of a SpecElement.
|
|
487
|
+
*
|
|
488
|
+
* @param watch - The watch map (keyed by state path)
|
|
489
|
+
* @param state - The current state snapshot
|
|
490
|
+
* @param prevState - The previous state snapshot
|
|
491
|
+
* @returns The list of triggered actions
|
|
492
|
+
*/
|
|
493
|
+
evaluateWatches(watch: Record<string, unknown>, state: Record<string, unknown>, prevState: Record<string, unknown>): WatchAction[];
|
|
494
|
+
/**
|
|
495
|
+
* Extract an action from a watch entry.
|
|
496
|
+
*
|
|
497
|
+
* Accepts either a plain action string, or an object with `action`
|
|
498
|
+
* and optional `params` fields.
|
|
499
|
+
*
|
|
500
|
+
* @param entry - The watch entry value
|
|
501
|
+
* @returns The extracted action, or null if no action is present
|
|
502
|
+
*/
|
|
503
|
+
private extractAction;
|
|
504
|
+
/**
|
|
505
|
+
* Read a value from a record by dot path.
|
|
506
|
+
*
|
|
507
|
+
* @param record - The record to read from
|
|
508
|
+
* @param path - The dot path (e.g. "form.username")
|
|
509
|
+
* @returns The value at the path, or undefined if not found
|
|
510
|
+
*/
|
|
511
|
+
private readPath;
|
|
512
|
+
/**
|
|
513
|
+
* Determine whether two values are equal for watch change detection.
|
|
514
|
+
*
|
|
515
|
+
* Uses reference equality for objects and arrays, and value equality
|
|
516
|
+
* for primitives.
|
|
517
|
+
*
|
|
518
|
+
* @param a - The previous value
|
|
519
|
+
* @param b - The current value
|
|
520
|
+
* @returns True if the values are considered equal
|
|
521
|
+
*/
|
|
522
|
+
private isEqual;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* A runtime component registry mapping component names to their React
|
|
526
|
+
* implementations.
|
|
527
|
+
*/
|
|
528
|
+
type Registry = Map<string, ComponentType>;
|
|
529
|
+
/**
|
|
530
|
+
* Registry builder.
|
|
531
|
+
*
|
|
532
|
+
* Walks the Catalog's component map and pairs each entry with the
|
|
533
|
+
* corresponding React component from the supplied components map.
|
|
534
|
+
* Components not present in the components map are silently skipped,
|
|
535
|
+
* enabling partial registration during incremental adoption.
|
|
536
|
+
*/
|
|
537
|
+
declare class RegistryBuilder {
|
|
538
|
+
/**
|
|
539
|
+
* Build a Registry from a Catalog and a components map.
|
|
540
|
+
*
|
|
541
|
+
* @param catalog - The Catalog whitelist defining component names
|
|
542
|
+
* @param components - A record of component name → React implementation
|
|
543
|
+
* @returns The populated Registry
|
|
544
|
+
*/
|
|
545
|
+
build(catalog: Catalog, components: Record<string, ComponentType>): Registry;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Options for the json-render adapter factory.
|
|
549
|
+
*/
|
|
550
|
+
type JsonRenderAdapterOptions = {
|
|
551
|
+
/**
|
|
552
|
+
* The stream compiler used to progressively build Spec trees from
|
|
553
|
+
* SpecStreamPatch sequences. Injected via the contract port so the
|
|
554
|
+
* infrastructure package does not depend on the implementation
|
|
555
|
+
* package.
|
|
556
|
+
*/
|
|
557
|
+
compiler: SpecStreamCompilerPort;
|
|
558
|
+
/**
|
|
559
|
+
* Optional pre-built component registry. When omitted, the adapter
|
|
560
|
+
* operates in a headless mode (stream compilation only) and
|
|
561
|
+
* single-shot render returns a serialized spec representation.
|
|
562
|
+
*/
|
|
563
|
+
registry?: Registry;
|
|
564
|
+
/**
|
|
565
|
+
* Optional components map used for single-shot rendering when no
|
|
566
|
+
* pre-built registry is supplied.
|
|
567
|
+
*/
|
|
568
|
+
components?: Record<string, ComponentType>;
|
|
569
|
+
};
|
|
570
|
+
/**
|
|
571
|
+
* Create a json-render-based RendererPort implementation.
|
|
572
|
+
*
|
|
573
|
+
* The adapter wraps @json-render/react's Renderer and Registry. For
|
|
574
|
+
* single-shot rendering, it validates the Spec, resolves the component
|
|
575
|
+
* registry, and renders the tree. For streaming rendering, it uses
|
|
576
|
+
* the injected SpecStreamCompilerPort to progressively build the Spec
|
|
577
|
+
* and yields each intermediate state.
|
|
578
|
+
*
|
|
579
|
+
* @param options - Adapter configuration (compiler + optional registry / components)
|
|
580
|
+
* @returns A RendererPort implementation
|
|
581
|
+
*/
|
|
582
|
+
declare function createJsonRenderAdapter(options: JsonRenderAdapterOptions): RendererPort;
|
|
583
|
+
/**
|
|
584
|
+
* renderer 域端口装配配置
|
|
585
|
+
*
|
|
586
|
+
* @remarks
|
|
587
|
+
* 即基础设施适配器配置去掉编译器项,编译器由本工厂注入
|
|
588
|
+
*/
|
|
589
|
+
type RendererPortFactoryOptions = Omit<JsonRenderAdapterOptions, 'compiler'>;
|
|
590
|
+
/**
|
|
591
|
+
* 创建 renderer 域端口
|
|
592
|
+
*
|
|
593
|
+
* @remarks
|
|
594
|
+
* 以自有 `SpecStreamCompiler` 满足流编译器端口,组件注册面经可选项透传
|
|
595
|
+
* @param options - 端口装配配置,缺省为无注册表的 headless 形态
|
|
596
|
+
* @returns renderer 域端口
|
|
597
|
+
*/
|
|
598
|
+
declare function createRendererPort(options?: RendererPortFactoryOptions): RendererPort;
|
|
599
|
+
/**
|
|
600
|
+
* 创建渲染开箱即用能力
|
|
601
|
+
*
|
|
602
|
+
* @remarks
|
|
603
|
+
* 零配置即得 headless 渲染端口,携组件注册表形态请经 `createRendererPort` 传入注册面配置
|
|
604
|
+
* @param options - 端口装配配置
|
|
605
|
+
* @returns 渲染端口
|
|
606
|
+
*/
|
|
607
|
+
declare function createRendererCapability(options?: RendererPortFactoryOptions): RendererPort;
|
|
608
|
+
export { type ComputedFunction, ExpressionEvaluator, type JsonRenderAdapterOptions, RENDERER_PORT_TOKEN, type Registry, RegistryBuilder, type RenderOptions, type RenderResult, RendererError, type RendererPort, type RendererPortFactoryOptions, SPEC_STREAM_COMPILER_TOKEN, SpecStreamCompiler, type SpecStreamCompilerPort, SpecValidationError, type StreamRenderOptions, UnknownComponentError, VisibilityEvaluator, type WatchAction, WatchEvaluator, createJsonRenderAdapter, createRendererCapability, createRendererPort };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DomainError as e}from"@longzai-intelligence-error/core";import{SpecSchema as t}from"@longzai-intelligence-agent-ui-render/core";import{ensureError as n}from"@longzai-intelligence-error/utils";const r=Symbol(`RendererPort`),i=Symbol(`SpecStreamCompilerPort`);var a=class extends e{constructor(e){super(e,`RENDERER_ERROR`,{message:e}),this.name=`RendererError`}},o=class extends e{errors;constructor(e){super(`Spec validation failed: ${e.join(`, `)}`,`SPEC_VALIDATION_ERROR`,{errors:e}),this.name=`SpecValidationError`,this.errors=e}},s=class extends e{componentType;constructor(e){super(`Unknown component type: ${e}`,`UNKNOWN_COMPONENT`,{componentType:e}),this.name=`UnknownComponentError`,this.componentType=e}};function c(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function l(e){return typeof e==`object`&&!!e}const u={root:``,elements:{}};var d=class{compile(e,t){let n={root:t?.root??u.root,elements:{...t?.elements??u.elements},...t?.state===void 0?{}:{state:{...t.state}}};for(let t of e)this.applyPatch(n,t);return n}applyPatch(e,t){let n=this.parsePath(t.path);switch(t.op){case`add`:this.applyAdd(e,n,t.value);break;case`update`:this.applyUpdate(e,n,t.value);break;case`remove`:this.applyRemove(e,n);break;case`replace`:this.applyReplace(e,n,t.value);break;default:throw new o([`Unknown patch operation: ${t.op}`])}}parsePath(e){return e.length===0||e===`/`?[]:(e.startsWith(`/`)?e.slice(1):e).split(`/`).map(e=>e.replace(/~1/g,`/`).replace(/~0/g,`~`))}applyAdd(e,t,n){if(t.length===0)throw new o([`Cannot 'add' the Spec root itself`]);let[r,...i]=t;if(r===`elements`)this.addElement(e,i,n);else if(r===`root`){if(i.length>0)throw new o([`Cannot 'add' nested fields under /root`]);if(typeof n!=`string`)throw new o([`/root value must be a string element id`]);e.root=n}else if(r===`state`)this.addStateField(e,i,n);else throw new o([`Unknown top-level field: ${r}`])}addElement(e,t,n){if(t.length===0)throw new o([`Cannot 'add' to /elements without an element id`]);let[r,...i]=t;if(r===void 0)throw new o([`Cannot 'add' to /elements without an element id`]);if(i.length===0){if(!this.isSpecElement(n))throw new o([`Invalid element value for /elements/${r}`]);e.elements[r]={...n};return}let a=e.elements[r];if(!a)throw new o([`Element not found: ${r}`]);this.setNestedField(a,i,n)}addStateField(e,t,n){if(e.state||={},t.length===0){if(!c(n))throw new o([`/state value must be an object`]);e.state={...n}}else this.setNestedRecord(e.state,t,n)}applyUpdate(e,t,n){this.applyReplace(e,t,n)}applyRemove(e,t){if(t.length===0)throw new o([`Cannot 'remove' the Spec root itself`]);let[n,...r]=t;if(n===`elements`){if(r.length===0)throw new o([`Cannot 'remove' all elements`]);let[t,...n]=r;if(t===void 0)throw new o([`Cannot 'remove' /elements without an element id`]);if(n.length===0){delete e.elements[t],e.root===t&&(e.root=``);return}let i=e.elements[t];if(!i)throw new o([`Element not found: ${t}`]);this.removeNestedField(i,n)}else if(n===`root`)e.root=``;else if(n===`state`){if(!e.state)return;if(r.length===0){delete e.state;return}this.removeNestedRecord(e.state,r)}else throw new o([`Unknown top-level field: ${n}`])}applyReplace(e,t,n){if(t.length===0)throw new o([`Cannot 'replace' the Spec root itself`]);let[r,...i]=t;if(r===`elements`){if(i.length===0)throw new o([`Cannot 'replace' all elements`]);let[t,...r]=i;if(t===void 0)throw new o([`Cannot 'replace' /elements without an element id`]);if(r.length===0){if(!this.isSpecElement(n))throw new o([`Invalid element value for /elements/${t}`]);e.elements[t]={...n};return}let a=e.elements[t];if(!a)throw new o([`Element not found: ${t}`]);this.setNestedField(a,r,n)}else if(r===`root`){if(i.length>0)throw new o([`Cannot 'replace' nested fields under /root`]);if(typeof n!=`string`)throw new o([`/root value must be a string element id`]);e.root=n}else if(r===`state`){if(i.length===0){if(!c(n))throw new o([`/state value must be an object`]);e.state={...n};return}e.state||={},this.setNestedRecord(e.state,i,n)}else throw new o([`Unknown top-level field: ${r}`])}setNestedField(e,t,n){let[r,...i]=t;if(r===`props`){if(e.props||={},i.length===0){if(!c(n))throw new o([`/props value must be an object`]);e.props={...n};return}this.setNestedRecord(e.props,i,n)}else if(r===`children`){if(i.length>0)throw new o([`Cannot set nested fields under /children`]);if(!Array.isArray(n))throw new o([`/children value must be an array`]);e.children=[...n]}else if(r===`visible`){if(i.length>0)throw new o([`Cannot set nested fields under /visible`]);e.visible=n}else if(r===`watch`){if(e.watch||={},i.length===0){if(!c(n))throw new o([`/watch value must be an object`]);e.watch={...n};return}this.setNestedRecord(e.watch,i,n)}else if(r===`type`){if(i.length>0)throw new o([`Cannot set nested fields under /type`]);if(typeof n!=`string`)throw new o([`/type value must be a string`]);e.type=n}else if(r===`id`){if(i.length>0)throw new o([`Cannot set nested fields under /id`]);if(typeof n!=`string`)throw new o([`/id value must be a string`]);e.id=n}else throw new o([`Unknown element field: ${r}`])}removeNestedField(e,t){let[n,...r]=t;if(n===`props`){if(!e.props)return;if(r.length===0){delete e.props;return}this.removeNestedRecord(e.props,r)}else if(n===`children`)delete e.children;else if(n===`visible`)delete e.visible;else if(n===`watch`){if(!e.watch)return;if(r.length===0){delete e.watch;return}this.removeNestedRecord(e.watch,r)}else throw n===`type`||n===`id`?new o([`Cannot remove required field: ${n}`]):new o([`Unknown element field: ${n}`])}setNestedRecord(e,t,n){let r=t.at(-1);if(r===void 0)return;let i=e;for(let e of t.slice(0,-1)){let t=i[e];if(l(t))i=t;else{let t={};i[e]=t,i=t}}i[r]=n}removeNestedRecord(e,t){let n=t.at(-1);if(n===void 0)return;let r=e;for(let e of t.slice(0,-1)){let t=r[e];if(!l(t))return;r=t}delete r[n]}isSpecElement(e){return c(e)?typeof e.id==`string`&&typeof e.type==`string`:!1}};function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e){return typeof e==`object`&&!!e}var m=class{computed;constructor(e=/* @__PURE__ */ new Map){this.computed=e}evaluateExpression(e,t){if(typeof e!=`object`||!e)return e;if(Array.isArray(e))return e.map(e=>this.evaluateExpression(e,t));if(!f(e))return e;let n=e;if(`$state`in n)return this.evaluateState(n.$state,t);if(`$cond`in n)return this.evaluateCond(n.$cond,t);if(`$template`in n)return this.evaluateTemplate(n.$template,t);if(`$computed`in n)return this.evaluateComputed(n.$computed,t);if(`$bindState`in n)return this.evaluateBindState(n.$bindState);let r={};for(let[e,i]of Object.entries(n))r[e]=this.evaluateExpression(i,t);return r}evaluateState(e,t){return typeof e==`string`?this.readPath(t,e):e}evaluateCond(e,t){if(!f(e))return;let n=e,r=this.evaluateExpression(n.if,t);if(this.isTruthy(r))return this.evaluateExpression(n.then,t);if(`else`in n)return this.evaluateExpression(n.else,t)}evaluateTemplate(e,t){return typeof e==`string`?e.replace(/\{\{([^}]+)\}\}/g,(e,n)=>{let r=String(n).trim();if(r.length===0)return e;let i=this.readPath(t,r);return i==null?``:typeof i==`string`?i:String(i)}):e}evaluateComputed(e,t){if(!f(e))return;let n=e,r=n.fn;if(typeof r!=`string`)return;let i=this.computed.get(r);if(i)return i((Array.isArray(n.args)?n.args:[]).map(e=>this.evaluateExpression(e,t)),t)}evaluateBindState(e){return e}readPath(e,t){let n=t.split(`.`),r=e;for(let e of n){if(!p(r))return;r=r[e]}return r}isTruthy(e){return e==null?!1:typeof e==`boolean`?e:typeof e==`number`?e!==0&&!Number.isNaN(e):typeof e==`string`||Array.isArray(e)?e.length>0:typeof e==`object`&&e?Object.keys(e).length>0:!!e}},h=class{expressionEvaluator;constructor(e){this.expressionEvaluator=e}evaluate(e,t){if(e==null)return!0;if(typeof e==`boolean`)return e;let n=this.expressionEvaluator.evaluateExpression(e,t);return this.coerceBoolean(n)}coerceBoolean(e){return typeof e==`boolean`?e:e==null?!1:typeof e==`number`?e!==0&&!Number.isNaN(e):typeof e==`string`||Array.isArray(e)?e.length>0:typeof e==`object`&&e?Object.keys(e).length>0:!!e}};function g(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function _(e){return typeof e==`object`&&!!e}var v=class{evaluateWatches(e,t,n){let r=[];for(let[i,a]of Object.entries(e)){let e=this.readPath(n,i),o=this.readPath(t,i);if(!this.isEqual(e,o)){let e=this.extractAction(a);e!==null&&r.push(e)}}return r}extractAction(e){return typeof e==`string`?{action:e,params:void 0}:g(e)&&typeof e.action==`string`?{action:e.action,params:e.params}:null}readPath(e,t){let n=t.split(`.`),r=e;for(let e of n){if(!_(r))return;r=r[e]}return r}isEqual(e,t){return e===t||(e===null||t===null||typeof e==typeof t&&typeof e!=`object`)&&e===t}};function y(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function b(e){return typeof e==`function`}function x(e){let r=e.compiler;return{async render(r){try{let n=t.safeParse(r.spec);if(!n.success)return{error:`Spec validation failed: ${n.error.message}`};let i=n.data;return{html:await C(i,{...i.state,...r.state},e)}}catch(e){return{error:n(e).message}}},async*renderStream(e){let t={root:``,elements:{},...e.initialState?{state:{...e.initialState}}:{}};for(let n of e.patches)t=S(r,t,n),yield t}}}function S(e,t,n){return e.compile([n],t)}async function C(e,t,n){let r=n.registry,i=n.components;if(!r&&!i)return E(e,t);let a=await w();if(!a)return E(e,t);let{Renderer:o}=await import(`@json-render/react`);return a({type:o,props:{spec:e,state:t,registry:r??T(i??{})}})}async function w(){try{let e=await import(`react-dom/server`);if(y(e)){let t=e.renderToString;if(b(t))return t}return null}catch{return null}}function T(e){let t=/* @__PURE__ */ new Map;for(let[n,r]of Object.entries(e))t.set(n,r);return t}function E(e,t){return`<div data-renderer="headless" data-root="${e.root||`(empty)`}" data-elements="${Object.keys(e.elements).length}" data-state="${JSON.stringify(t)}"></div>`}var D=class{build(e,t){let n=/* @__PURE__ */ new Map;for(let r of Object.keys(e.components)){let e=t[r];e&&n.set(r,e)}return n}};function O(e={}){return x({compiler:new d,...e})}function k(e={}){return O(e)}export{m as ExpressionEvaluator,r as RENDERER_PORT_TOKEN,D as RegistryBuilder,a as RendererError,i as SPEC_STREAM_COMPILER_TOKEN,d as SpecStreamCompiler,o as SpecValidationError,s as UnknownComponentError,h as VisibilityEvaluator,v as WatchEvaluator,x as createJsonRenderAdapter,k as createRendererCapability,O as createRendererPort};
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longzai-intelligence-agent-ui-render/render",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Longzai Intelligence Agent UI 渲染端口与 json-render 适配器包",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"adapter",
|
|
7
|
+
"agent-ui",
|
|
8
|
+
"longzai-intelligence",
|
|
9
|
+
"renderer",
|
|
10
|
+
"spec-stream"
|
|
11
|
+
],
|
|
12
|
+
"license": "UNLICENSED",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "lzi-builder",
|
|
25
|
+
"build:prod": "NODE_ENV=production bun run build",
|
|
26
|
+
"prepublishOnly": "bun run build:prod",
|
|
27
|
+
"dev": "lzi-builder --watch",
|
|
28
|
+
"clean": "lzi-dev-tools clean",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest",
|
|
31
|
+
"test:coverage": "vitest run --coverage",
|
|
32
|
+
"lint": "lzi-lint lint",
|
|
33
|
+
"lint:fix": "lzi-lint fix",
|
|
34
|
+
"typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
|
|
35
|
+
"typecheck:app": "lzi-tsgo typecheck tsconfig/app.json",
|
|
36
|
+
"typecheck:node": "lzi-tsgo typecheck tsconfig/node.json",
|
|
37
|
+
"typecheck:test": "lzi-tsgo typecheck tsconfig/test.json"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@json-render/core": "^0.1.0",
|
|
41
|
+
"@json-render/react": "^0.1.0",
|
|
42
|
+
"@longzai-intelligence-agent-ui-render/core": "0.0.1",
|
|
43
|
+
"@longzai-intelligence-error/core": "^0.2.0",
|
|
44
|
+
"@longzai-intelligence-error/utils": "^0.2.0",
|
|
45
|
+
"zod": "^4.4.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@longzai-intelligence-testing/utils": "^0.0.3",
|
|
49
|
+
"@longzai-intelligence-vitest/config": "^0.0.12",
|
|
50
|
+
"@types/react": "^19.2.17",
|
|
51
|
+
"react": "^19.2.7",
|
|
52
|
+
"vitest": "^4.1.4"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": ">=18.0.0"
|
|
56
|
+
},
|
|
57
|
+
"packageManager": "bun@1.4.3"
|
|
58
|
+
}
|