@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 CHANGED
@@ -1,3 +1,11 @@
1
- # Temporary Holding Version
1
+ # @longzai-intelligence-agent-ui-render/render
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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`
@@ -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.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }