@nine1ie/a2ui-vue-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +285 -0
- package/dist/index.js +524 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A2UI Vue contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
interface DynamicString {
|
|
2
|
+
literalString?: string;
|
|
3
|
+
path?: string;
|
|
4
|
+
functionCall?: FunctionCall;
|
|
5
|
+
}
|
|
6
|
+
interface DynamicNumber {
|
|
7
|
+
literalNumber?: number;
|
|
8
|
+
path?: string;
|
|
9
|
+
functionCall?: FunctionCall;
|
|
10
|
+
}
|
|
11
|
+
interface DynamicBoolean {
|
|
12
|
+
literalBoolean?: boolean;
|
|
13
|
+
path?: string;
|
|
14
|
+
functionCall?: FunctionCall;
|
|
15
|
+
}
|
|
16
|
+
interface DynamicStringList {
|
|
17
|
+
literalArray?: string[];
|
|
18
|
+
path?: string;
|
|
19
|
+
functionCall?: FunctionCall;
|
|
20
|
+
}
|
|
21
|
+
type DynamicValue = DynamicString | DynamicNumber | DynamicBoolean | DynamicStringList;
|
|
22
|
+
interface FunctionCall {
|
|
23
|
+
call: string;
|
|
24
|
+
args?: Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
interface ExplicitChildList {
|
|
27
|
+
explicitList: string[];
|
|
28
|
+
}
|
|
29
|
+
interface TemplateChildList {
|
|
30
|
+
template: {
|
|
31
|
+
componentId: string;
|
|
32
|
+
dataBinding: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
type ChildList = ExplicitChildList | TemplateChildList;
|
|
36
|
+
declare function isTemplateChildList(child: ChildList): child is TemplateChildList;
|
|
37
|
+
interface ComponentDef {
|
|
38
|
+
id: string;
|
|
39
|
+
component: string;
|
|
40
|
+
[key: string]: any;
|
|
41
|
+
}
|
|
42
|
+
interface ActionEvent {
|
|
43
|
+
event: {
|
|
44
|
+
name: string;
|
|
45
|
+
context?: Record<string, any>;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
interface ActionFunctionCall {
|
|
49
|
+
functionCall: {
|
|
50
|
+
call: string;
|
|
51
|
+
args?: Record<string, any>;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
type Action = ActionEvent | ActionFunctionCall;
|
|
55
|
+
interface ValidationCheck {
|
|
56
|
+
call: string;
|
|
57
|
+
args?: Record<string, any>;
|
|
58
|
+
message?: string;
|
|
59
|
+
}
|
|
60
|
+
interface Theme {
|
|
61
|
+
primaryColor?: string;
|
|
62
|
+
iconUrl?: string;
|
|
63
|
+
agentDisplayName?: string;
|
|
64
|
+
}
|
|
65
|
+
interface CreateSurfaceMessage {
|
|
66
|
+
surfaceId: string;
|
|
67
|
+
catalogId: string;
|
|
68
|
+
theme?: Theme;
|
|
69
|
+
sendDataModel?: boolean;
|
|
70
|
+
}
|
|
71
|
+
interface UpdateComponentsMessage {
|
|
72
|
+
surfaceId: string;
|
|
73
|
+
components: ComponentDef[];
|
|
74
|
+
}
|
|
75
|
+
interface UpdateDataModelMessage {
|
|
76
|
+
surfaceId?: string;
|
|
77
|
+
path?: string;
|
|
78
|
+
value?: any;
|
|
79
|
+
}
|
|
80
|
+
interface DeleteSurfaceMessage {
|
|
81
|
+
surfaceId: string;
|
|
82
|
+
}
|
|
83
|
+
interface A2UIServerMessage {
|
|
84
|
+
createSurface?: CreateSurfaceMessage;
|
|
85
|
+
updateComponents?: UpdateComponentsMessage;
|
|
86
|
+
updateDataModel?: UpdateDataModelMessage;
|
|
87
|
+
deleteSurface?: DeleteSurfaceMessage;
|
|
88
|
+
}
|
|
89
|
+
interface ActionMessage {
|
|
90
|
+
name: string;
|
|
91
|
+
surfaceId: string;
|
|
92
|
+
sourceComponentId: string;
|
|
93
|
+
timestamp: string;
|
|
94
|
+
context: Record<string, any>;
|
|
95
|
+
}
|
|
96
|
+
interface ErrorMessage {
|
|
97
|
+
code: string;
|
|
98
|
+
surfaceId?: string;
|
|
99
|
+
path?: string;
|
|
100
|
+
message: string;
|
|
101
|
+
}
|
|
102
|
+
interface A2UIClientCapabilities {
|
|
103
|
+
supportedCatalogIds: string[];
|
|
104
|
+
inlineCatalogs?: any[];
|
|
105
|
+
}
|
|
106
|
+
interface ValidationResult {
|
|
107
|
+
valid: boolean;
|
|
108
|
+
errors: ValidationError[];
|
|
109
|
+
}
|
|
110
|
+
interface ValidationError {
|
|
111
|
+
code: string;
|
|
112
|
+
surfaceId?: string;
|
|
113
|
+
path?: string;
|
|
114
|
+
message: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface RowProps {
|
|
118
|
+
alignment?: 'start' | 'center' | 'end' | 'space-between' | 'space-around';
|
|
119
|
+
children?: ChildList;
|
|
120
|
+
}
|
|
121
|
+
interface ColumnProps {
|
|
122
|
+
alignment?: 'start' | 'center' | 'end' | 'space-between' | 'space-around';
|
|
123
|
+
children?: ChildList;
|
|
124
|
+
}
|
|
125
|
+
interface ListProps {
|
|
126
|
+
children?: ChildList;
|
|
127
|
+
}
|
|
128
|
+
interface TextProps {
|
|
129
|
+
text: DynamicString;
|
|
130
|
+
usageHint?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'body' | 'caption';
|
|
131
|
+
}
|
|
132
|
+
interface ImageProps {
|
|
133
|
+
url: DynamicString;
|
|
134
|
+
alt?: DynamicString;
|
|
135
|
+
}
|
|
136
|
+
interface IconProps {
|
|
137
|
+
icon: DynamicString;
|
|
138
|
+
}
|
|
139
|
+
interface VideoProps {
|
|
140
|
+
url: DynamicString;
|
|
141
|
+
}
|
|
142
|
+
interface AudioPlayerProps {
|
|
143
|
+
url: DynamicString;
|
|
144
|
+
}
|
|
145
|
+
interface DividerProps {
|
|
146
|
+
}
|
|
147
|
+
interface CardProps {
|
|
148
|
+
child: string;
|
|
149
|
+
}
|
|
150
|
+
interface TabItem {
|
|
151
|
+
title: string;
|
|
152
|
+
child: string;
|
|
153
|
+
}
|
|
154
|
+
interface TabsProps {
|
|
155
|
+
tabs: TabItem[];
|
|
156
|
+
}
|
|
157
|
+
interface ModalProps {
|
|
158
|
+
trigger: string;
|
|
159
|
+
child: string;
|
|
160
|
+
}
|
|
161
|
+
interface ButtonProps {
|
|
162
|
+
label: DynamicString;
|
|
163
|
+
action?: Action;
|
|
164
|
+
variant?: 'primary' | 'borderless' | 'default';
|
|
165
|
+
checks?: ValidationCheck[];
|
|
166
|
+
}
|
|
167
|
+
interface TextFieldProps {
|
|
168
|
+
label: DynamicString;
|
|
169
|
+
value: DynamicString;
|
|
170
|
+
placeholder?: DynamicString;
|
|
171
|
+
checks?: ValidationCheck[];
|
|
172
|
+
}
|
|
173
|
+
interface CheckBoxProps {
|
|
174
|
+
label: DynamicString;
|
|
175
|
+
value: DynamicBoolean;
|
|
176
|
+
}
|
|
177
|
+
interface ChoiceOption {
|
|
178
|
+
label: string;
|
|
179
|
+
value: string;
|
|
180
|
+
}
|
|
181
|
+
interface ChoicePickerProps {
|
|
182
|
+
options: ChoiceOption[];
|
|
183
|
+
value: DynamicString | DynamicStringList;
|
|
184
|
+
multi?: boolean;
|
|
185
|
+
}
|
|
186
|
+
interface SliderProps {
|
|
187
|
+
min: DynamicNumber;
|
|
188
|
+
max: DynamicNumber;
|
|
189
|
+
step?: DynamicNumber;
|
|
190
|
+
value: DynamicNumber;
|
|
191
|
+
}
|
|
192
|
+
interface DateTimeInputProps {
|
|
193
|
+
mode: 'date' | 'time' | 'datetime';
|
|
194
|
+
value: DynamicString;
|
|
195
|
+
label?: DynamicString;
|
|
196
|
+
}
|
|
197
|
+
interface ComponentTypeMap {
|
|
198
|
+
Text: TextProps;
|
|
199
|
+
Image: ImageProps;
|
|
200
|
+
Icon: IconProps;
|
|
201
|
+
Video: VideoProps;
|
|
202
|
+
AudioPlayer: AudioPlayerProps;
|
|
203
|
+
Divider: DividerProps;
|
|
204
|
+
Row: RowProps;
|
|
205
|
+
Column: ColumnProps;
|
|
206
|
+
List: ListProps;
|
|
207
|
+
Card: CardProps;
|
|
208
|
+
Tabs: TabsProps;
|
|
209
|
+
Modal: ModalProps;
|
|
210
|
+
Button: ButtonProps;
|
|
211
|
+
TextField: TextFieldProps;
|
|
212
|
+
CheckBox: CheckBoxProps;
|
|
213
|
+
ChoicePicker: ChoicePickerProps;
|
|
214
|
+
Slider: SliderProps;
|
|
215
|
+
DateTimeInput: DateTimeInputProps;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
declare function parseMessage(json: string): A2UIServerMessage;
|
|
219
|
+
declare function parseMessageStream(chunk: string): A2UIServerMessage[];
|
|
220
|
+
declare function validateMessage(message: A2UIServerMessage): ValidationResult;
|
|
221
|
+
declare class A2UIParseError extends Error {
|
|
222
|
+
constructor(message: string);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
declare function getByPointer(obj: any, pointer: string): any;
|
|
226
|
+
declare function setByPointer(obj: any, pointer: string, value: any): void;
|
|
227
|
+
declare function deleteByPointer(obj: any, pointer: string): void;
|
|
228
|
+
declare function deepMerge(target: any, source: any): any;
|
|
229
|
+
interface Scope {
|
|
230
|
+
currentItem: any;
|
|
231
|
+
index: number;
|
|
232
|
+
parent?: Scope;
|
|
233
|
+
}
|
|
234
|
+
declare function isDynamicValue(value: any): value is DynamicValue;
|
|
235
|
+
declare function resolvePath(path: string, dataModel: any, scope?: Scope): any;
|
|
236
|
+
declare function setPath(path: string, dataModel: any, value: any, scope?: Scope): void;
|
|
237
|
+
declare function resolveLiteral(dynamic: DynamicValue): any;
|
|
238
|
+
|
|
239
|
+
declare class ComponentRegistry {
|
|
240
|
+
private components;
|
|
241
|
+
updateComponents(components: ComponentDef[]): void;
|
|
242
|
+
getComponent(id: string): ComponentDef | undefined;
|
|
243
|
+
hasComponent(id: string): boolean;
|
|
244
|
+
get size(): number;
|
|
245
|
+
resolveChildren(id: string, dataModel?: any, scope?: Scope): ComponentDef[];
|
|
246
|
+
private resolveTemplate;
|
|
247
|
+
getAll(): ComponentDef[];
|
|
248
|
+
clear(): void;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
interface SurfaceInstance {
|
|
252
|
+
id: string;
|
|
253
|
+
catalogId: string;
|
|
254
|
+
theme?: Theme;
|
|
255
|
+
sendDataModel: boolean;
|
|
256
|
+
componentRegistry: ComponentRegistry;
|
|
257
|
+
dataModel: Record<string, any>;
|
|
258
|
+
createdAt: number;
|
|
259
|
+
_version: number;
|
|
260
|
+
}
|
|
261
|
+
type ActionHandler = (action: ActionMessage) => void;
|
|
262
|
+
declare class SurfaceManager {
|
|
263
|
+
private surfaces;
|
|
264
|
+
private actionHandler?;
|
|
265
|
+
createSurface(msg: CreateSurfaceMessage): SurfaceInstance;
|
|
266
|
+
deleteSurface(surfaceId: string): void;
|
|
267
|
+
getSurface(surfaceId: string): SurfaceInstance | undefined;
|
|
268
|
+
hasSurface(surfaceId: string): boolean;
|
|
269
|
+
getAllSurfaces(): SurfaceInstance[];
|
|
270
|
+
clear(): void;
|
|
271
|
+
updateDataModel(surfaceId: string, path: string | undefined, value: any): void;
|
|
272
|
+
onAction(handler: ActionHandler): void;
|
|
273
|
+
dispatchAction(action: ActionMessage): void;
|
|
274
|
+
getDataModelSnapshot(): Record<string, Record<string, any>>;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
type FunctionImpl = (args: Record<string, any>, dataModel: any, scope?: Scope) => any;
|
|
278
|
+
declare function executeFunction(functionCall: {
|
|
279
|
+
call: string;
|
|
280
|
+
args?: Record<string, any>;
|
|
281
|
+
}, dataModel: any, scope?: Scope): any;
|
|
282
|
+
declare function registerFunction(name: string, impl: FunctionImpl): void;
|
|
283
|
+
declare function getFunction(name: string): FunctionImpl | undefined;
|
|
284
|
+
|
|
285
|
+
export { type A2UIClientCapabilities, A2UIParseError, type A2UIServerMessage, type Action, type ActionEvent, type ActionFunctionCall, type ActionHandler, type ActionMessage, type AudioPlayerProps, type ButtonProps, type CardProps, type CheckBoxProps, type ChildList, type ChoiceOption, type ChoicePickerProps, type ColumnProps, type ComponentDef, ComponentRegistry, type ComponentTypeMap, type CreateSurfaceMessage, type DateTimeInputProps, type DeleteSurfaceMessage, type DividerProps, type DynamicBoolean, type DynamicNumber, type DynamicString, type DynamicStringList, type DynamicValue, type ErrorMessage, type ExplicitChildList, type FunctionCall, type FunctionImpl, type IconProps, type ImageProps, type ListProps, type ModalProps, type RowProps, type Scope, type SliderProps, type SurfaceInstance, SurfaceManager, type TabItem, type TabsProps, type TemplateChildList, type TextFieldProps, type TextProps, type Theme, type UpdateComponentsMessage, type UpdateDataModelMessage, type ValidationCheck, type ValidationError, type ValidationResult, type VideoProps, deepMerge, deleteByPointer, executeFunction, getByPointer, getFunction, isDynamicValue, isTemplateChildList, parseMessage, parseMessageStream, registerFunction, resolveLiteral, resolvePath, setByPointer, setPath, validateMessage };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
// src/types/protocol.ts
|
|
2
|
+
function isTemplateChildList(child) {
|
|
3
|
+
return "template" in child;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/parser.ts
|
|
7
|
+
function parseMessage(json) {
|
|
8
|
+
let parsed;
|
|
9
|
+
try {
|
|
10
|
+
parsed = JSON.parse(json);
|
|
11
|
+
} catch (e) {
|
|
12
|
+
throw new A2UIParseError(`Invalid JSON: ${e.message}`);
|
|
13
|
+
}
|
|
14
|
+
const message = {};
|
|
15
|
+
if ("createSurface" in parsed) {
|
|
16
|
+
message.createSurface = parsed.createSurface;
|
|
17
|
+
} else if ("updateComponents" in parsed) {
|
|
18
|
+
message.updateComponents = parsed.updateComponents;
|
|
19
|
+
} else if ("updateDataModel" in parsed) {
|
|
20
|
+
message.updateDataModel = parsed.updateDataModel;
|
|
21
|
+
} else if ("deleteSurface" in parsed) {
|
|
22
|
+
message.deleteSurface = parsed.deleteSurface;
|
|
23
|
+
} else {
|
|
24
|
+
throw new A2UIParseError("Unknown message type. Expected one of: createSurface, updateComponents, updateDataModel, deleteSurface");
|
|
25
|
+
}
|
|
26
|
+
return message;
|
|
27
|
+
}
|
|
28
|
+
function parseMessageStream(chunk) {
|
|
29
|
+
const lines = chunk.split("\n").filter((line) => line.trim());
|
|
30
|
+
return lines.map((line) => parseMessage(line));
|
|
31
|
+
}
|
|
32
|
+
function validateMessage(message) {
|
|
33
|
+
const errors = [];
|
|
34
|
+
if (message.createSurface) {
|
|
35
|
+
validateCreateSurface(message.createSurface, errors);
|
|
36
|
+
} else if (message.updateComponents) {
|
|
37
|
+
validateUpdateComponents(message.updateComponents, errors);
|
|
38
|
+
} else if (message.updateDataModel) {
|
|
39
|
+
validateUpdateDataModel(message.updateDataModel, errors);
|
|
40
|
+
} else if (message.deleteSurface) {
|
|
41
|
+
validateDeleteSurface(message.deleteSurface, errors);
|
|
42
|
+
}
|
|
43
|
+
return { valid: errors.length === 0, errors };
|
|
44
|
+
}
|
|
45
|
+
function validateCreateSurface(msg, errors) {
|
|
46
|
+
if (!msg.surfaceId) {
|
|
47
|
+
errors.push({ code: "VALIDATION_FAILED", path: "/createSurface/surfaceId", message: "surfaceId is required" });
|
|
48
|
+
}
|
|
49
|
+
if (!msg.catalogId) {
|
|
50
|
+
errors.push({ code: "VALIDATION_FAILED", path: "/createSurface/catalogId", message: "catalogId is required" });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function validateUpdateComponents(msg, errors) {
|
|
54
|
+
if (!msg.surfaceId) {
|
|
55
|
+
errors.push({ code: "VALIDATION_FAILED", surfaceId: msg.surfaceId, path: "/updateComponents/surfaceId", message: "surfaceId is required" });
|
|
56
|
+
}
|
|
57
|
+
if (!Array.isArray(msg.components)) {
|
|
58
|
+
errors.push({ code: "VALIDATION_FAILED", surfaceId: msg.surfaceId, path: "/updateComponents/components", message: "components must be an array" });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const ids = /* @__PURE__ */ new Set();
|
|
62
|
+
for (let i = 0; i < msg.components.length; i++) {
|
|
63
|
+
const comp = msg.components[i];
|
|
64
|
+
if (!comp.id) {
|
|
65
|
+
errors.push({
|
|
66
|
+
code: "VALIDATION_FAILED",
|
|
67
|
+
surfaceId: msg.surfaceId,
|
|
68
|
+
path: `/updateComponents/components/${i}/id`,
|
|
69
|
+
message: "Component id is required"
|
|
70
|
+
});
|
|
71
|
+
} else if (ids.has(comp.id)) {
|
|
72
|
+
errors.push({
|
|
73
|
+
code: "VALIDATION_FAILED",
|
|
74
|
+
surfaceId: msg.surfaceId,
|
|
75
|
+
path: `/updateComponents/components/${i}/id`,
|
|
76
|
+
message: `Duplicate component id: ${comp.id}`
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
ids.add(comp.id);
|
|
80
|
+
}
|
|
81
|
+
if (!comp.component) {
|
|
82
|
+
errors.push({
|
|
83
|
+
code: "VALIDATION_FAILED",
|
|
84
|
+
surfaceId: msg.surfaceId,
|
|
85
|
+
path: `/updateComponents/components/${i}/component`,
|
|
86
|
+
message: "Component type is required"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function validateUpdateDataModel(msg, errors) {
|
|
92
|
+
if (!msg.surfaceId) {
|
|
93
|
+
errors.push({ code: "VALIDATION_FAILED", path: "/updateDataModel/surfaceId", message: "surfaceId is required" });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function validateDeleteSurface(msg, errors) {
|
|
97
|
+
if (!msg.surfaceId) {
|
|
98
|
+
errors.push({ code: "VALIDATION_FAILED", path: "/deleteSurface/surfaceId", message: "surfaceId is required" });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
var A2UIParseError = class extends Error {
|
|
102
|
+
constructor(message) {
|
|
103
|
+
super(message);
|
|
104
|
+
this.name = "A2UIParseError";
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/data-model.ts
|
|
109
|
+
function decodePointerSegment(segment) {
|
|
110
|
+
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
111
|
+
}
|
|
112
|
+
function getByPointer(obj, pointer) {
|
|
113
|
+
if (!pointer || pointer === "/") return obj;
|
|
114
|
+
const parts = pointer.split("/").filter(Boolean);
|
|
115
|
+
let current = obj;
|
|
116
|
+
for (const part of parts) {
|
|
117
|
+
if (current == null) return void 0;
|
|
118
|
+
current = current[decodePointerSegment(part)];
|
|
119
|
+
}
|
|
120
|
+
return current;
|
|
121
|
+
}
|
|
122
|
+
function setByPointer(obj, pointer, value) {
|
|
123
|
+
if (!pointer || pointer === "/") return;
|
|
124
|
+
const parts = pointer.split("/").filter(Boolean);
|
|
125
|
+
let current = obj;
|
|
126
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
127
|
+
if (current == null) return;
|
|
128
|
+
const decoded = decodePointerSegment(parts[i]);
|
|
129
|
+
if (typeof current[decoded] !== "object" || current[decoded] === null) {
|
|
130
|
+
current[decoded] = {};
|
|
131
|
+
}
|
|
132
|
+
current = current[decoded];
|
|
133
|
+
}
|
|
134
|
+
if (current != null) {
|
|
135
|
+
const lastKey = decodePointerSegment(parts[parts.length - 1]);
|
|
136
|
+
current[lastKey] = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function deleteByPointer(obj, pointer) {
|
|
140
|
+
if (!pointer || pointer === "/") return;
|
|
141
|
+
const parts = pointer.split("/").filter(Boolean);
|
|
142
|
+
let current = obj;
|
|
143
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
144
|
+
if (current == null) return;
|
|
145
|
+
current = current[decodePointerSegment(parts[i])];
|
|
146
|
+
}
|
|
147
|
+
if (current != null) {
|
|
148
|
+
const lastKey = decodePointerSegment(parts[parts.length - 1]);
|
|
149
|
+
if (Array.isArray(current)) {
|
|
150
|
+
current.splice(Number(lastKey), 1);
|
|
151
|
+
} else {
|
|
152
|
+
delete current[lastKey];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function deepMerge(target, source) {
|
|
157
|
+
if (source === void 0) return target;
|
|
158
|
+
if (typeof source !== "object" || source === null) return source;
|
|
159
|
+
if (typeof target !== "object" || target === null) return structuredClone(source);
|
|
160
|
+
const result = { ...target };
|
|
161
|
+
for (const key of Object.keys(source)) {
|
|
162
|
+
if (source[key] === void 0) {
|
|
163
|
+
delete result[key];
|
|
164
|
+
} else {
|
|
165
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
function isDynamicValue(value) {
|
|
171
|
+
if (typeof value !== "object" || value === null) return false;
|
|
172
|
+
return "literalString" in value || "literalNumber" in value || "literalBoolean" in value || "literalArray" in value || "path" in value || "functionCall" in value;
|
|
173
|
+
}
|
|
174
|
+
function resolvePath(path, dataModel, scope) {
|
|
175
|
+
if (path.startsWith("/")) {
|
|
176
|
+
return getByPointer(dataModel, path);
|
|
177
|
+
}
|
|
178
|
+
let current = scope;
|
|
179
|
+
const parts = path.split("/");
|
|
180
|
+
let result = void 0;
|
|
181
|
+
for (let i = 0; i < parts.length; i++) {
|
|
182
|
+
if (i === 0) {
|
|
183
|
+
result = current?.currentItem?.[parts[0]];
|
|
184
|
+
current = current?.parent;
|
|
185
|
+
} else {
|
|
186
|
+
result = result?.[parts[i]];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
function setPath(path, dataModel, value, scope) {
|
|
192
|
+
if (path.startsWith("/")) {
|
|
193
|
+
setByPointer(dataModel, path, value);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const parts = path.split("/").filter(Boolean);
|
|
197
|
+
if (parts.length === 0) return;
|
|
198
|
+
let current = scope?.currentItem ?? dataModel;
|
|
199
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
200
|
+
if (current == null) return;
|
|
201
|
+
const key = parts[i];
|
|
202
|
+
if (typeof current[key] !== "object" || current[key] === null) {
|
|
203
|
+
current[key] = {};
|
|
204
|
+
}
|
|
205
|
+
current = current[key];
|
|
206
|
+
}
|
|
207
|
+
if (current != null) {
|
|
208
|
+
current[parts[parts.length - 1]] = value;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function resolveLiteral(dynamic) {
|
|
212
|
+
if ("literalString" in dynamic) return dynamic.literalString;
|
|
213
|
+
if ("literalNumber" in dynamic) return dynamic.literalNumber;
|
|
214
|
+
if ("literalBoolean" in dynamic) return dynamic.literalBoolean;
|
|
215
|
+
if ("literalArray" in dynamic) return dynamic.literalArray;
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/component-registry.ts
|
|
220
|
+
var ComponentRegistry = class {
|
|
221
|
+
components = /* @__PURE__ */ new Map();
|
|
222
|
+
// Merge new component definitions (upsert)
|
|
223
|
+
updateComponents(components) {
|
|
224
|
+
for (const comp of components) {
|
|
225
|
+
this.components.set(comp.id, comp);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
getComponent(id) {
|
|
229
|
+
return this.components.get(id);
|
|
230
|
+
}
|
|
231
|
+
hasComponent(id) {
|
|
232
|
+
return this.components.has(id);
|
|
233
|
+
}
|
|
234
|
+
get size() {
|
|
235
|
+
return this.components.size;
|
|
236
|
+
}
|
|
237
|
+
// Resolve children of a component, returns ordered ComponentDef list
|
|
238
|
+
resolveChildren(id, dataModel, scope) {
|
|
239
|
+
const comp = this.components.get(id);
|
|
240
|
+
if (!comp) return [];
|
|
241
|
+
const childList = comp.children;
|
|
242
|
+
if (!childList) return [];
|
|
243
|
+
if (isTemplateChildList(childList)) {
|
|
244
|
+
return this.resolveTemplate(childList, dataModel, scope);
|
|
245
|
+
}
|
|
246
|
+
return (childList.explicitList ?? []).map((childId) => this.components.get(childId)).filter((c) => c != null);
|
|
247
|
+
}
|
|
248
|
+
// Resolve template-based children (data-bound lists)
|
|
249
|
+
resolveTemplate(templateChild, dataModel, scope) {
|
|
250
|
+
if (!dataModel) return [];
|
|
251
|
+
const { componentId, dataBinding } = templateChild.template;
|
|
252
|
+
const array = getByPointer(dataModel, dataBinding);
|
|
253
|
+
if (!Array.isArray(array)) return [];
|
|
254
|
+
return array.map((_, index) => {
|
|
255
|
+
const base = this.components.get(componentId);
|
|
256
|
+
return {
|
|
257
|
+
...base,
|
|
258
|
+
id: `${componentId}__${index}`,
|
|
259
|
+
_templateIndex: index,
|
|
260
|
+
_templateSource: componentId
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// Get all components as a flat array
|
|
265
|
+
getAll() {
|
|
266
|
+
return Array.from(this.components.values());
|
|
267
|
+
}
|
|
268
|
+
// Clear all components
|
|
269
|
+
clear() {
|
|
270
|
+
this.components.clear();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// src/surface.ts
|
|
275
|
+
var SurfaceManager = class {
|
|
276
|
+
surfaces = /* @__PURE__ */ new Map();
|
|
277
|
+
actionHandler;
|
|
278
|
+
createSurface(msg) {
|
|
279
|
+
const surface = {
|
|
280
|
+
id: msg.surfaceId,
|
|
281
|
+
catalogId: msg.catalogId,
|
|
282
|
+
theme: msg.theme,
|
|
283
|
+
sendDataModel: msg.sendDataModel ?? false,
|
|
284
|
+
componentRegistry: new ComponentRegistry(),
|
|
285
|
+
dataModel: {},
|
|
286
|
+
createdAt: Date.now(),
|
|
287
|
+
_version: 0
|
|
288
|
+
};
|
|
289
|
+
this.surfaces.set(msg.surfaceId, surface);
|
|
290
|
+
return surface;
|
|
291
|
+
}
|
|
292
|
+
deleteSurface(surfaceId) {
|
|
293
|
+
this.surfaces.delete(surfaceId);
|
|
294
|
+
}
|
|
295
|
+
getSurface(surfaceId) {
|
|
296
|
+
return this.surfaces.get(surfaceId);
|
|
297
|
+
}
|
|
298
|
+
hasSurface(surfaceId) {
|
|
299
|
+
return this.surfaces.has(surfaceId);
|
|
300
|
+
}
|
|
301
|
+
getAllSurfaces() {
|
|
302
|
+
return Array.from(this.surfaces.values());
|
|
303
|
+
}
|
|
304
|
+
clear() {
|
|
305
|
+
this.surfaces.clear();
|
|
306
|
+
}
|
|
307
|
+
// Update data model with upsert semantics
|
|
308
|
+
updateDataModel(surfaceId, path, value) {
|
|
309
|
+
const surface = this.surfaces.get(surfaceId);
|
|
310
|
+
if (!surface) return;
|
|
311
|
+
if (!path || path === "/") {
|
|
312
|
+
surface.dataModel = value ?? {};
|
|
313
|
+
} else if (value === void 0) {
|
|
314
|
+
deleteByPointer(surface.dataModel, path);
|
|
315
|
+
} else {
|
|
316
|
+
const existing = getByPointer(surface.dataModel, path);
|
|
317
|
+
if (typeof value === "object" && value !== null && typeof existing === "object" && existing !== null) {
|
|
318
|
+
setByPointer(surface.dataModel, path, deepMerge(existing, value));
|
|
319
|
+
} else {
|
|
320
|
+
setByPointer(surface.dataModel, path, value);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Set action handler for user interactions
|
|
325
|
+
onAction(handler) {
|
|
326
|
+
this.actionHandler = handler;
|
|
327
|
+
}
|
|
328
|
+
// Dispatch user action
|
|
329
|
+
dispatchAction(action) {
|
|
330
|
+
this.actionHandler?.(action);
|
|
331
|
+
}
|
|
332
|
+
// Get data model snapshot for capability exchange
|
|
333
|
+
getDataModelSnapshot() {
|
|
334
|
+
const snapshot = {};
|
|
335
|
+
for (const [id, surface] of this.surfaces) {
|
|
336
|
+
snapshot[id] = surface.dataModel;
|
|
337
|
+
}
|
|
338
|
+
return snapshot;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// src/functions/validation.ts
|
|
343
|
+
var validationFunctions = {
|
|
344
|
+
required: (args) => {
|
|
345
|
+
const value = args.value;
|
|
346
|
+
return value != null && value !== "";
|
|
347
|
+
},
|
|
348
|
+
regex: (args) => {
|
|
349
|
+
const { value, pattern } = args;
|
|
350
|
+
if (value == null || !pattern) return false;
|
|
351
|
+
return new RegExp(pattern).test(String(value));
|
|
352
|
+
},
|
|
353
|
+
length: (args) => {
|
|
354
|
+
const { value, min, max } = args;
|
|
355
|
+
if (value == null) return false;
|
|
356
|
+
const len = String(value).length;
|
|
357
|
+
if (min != null && len < min) return false;
|
|
358
|
+
if (max != null && len > max) return false;
|
|
359
|
+
return true;
|
|
360
|
+
},
|
|
361
|
+
numeric: (args) => {
|
|
362
|
+
const { value, min, max } = args;
|
|
363
|
+
if (value == null) return false;
|
|
364
|
+
const num = Number(value);
|
|
365
|
+
if (isNaN(num)) return false;
|
|
366
|
+
if (min != null && num < min) return false;
|
|
367
|
+
if (max != null && num > max) return false;
|
|
368
|
+
return true;
|
|
369
|
+
},
|
|
370
|
+
email: (args) => {
|
|
371
|
+
const { value } = args;
|
|
372
|
+
if (value == null) return false;
|
|
373
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value));
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// src/functions/format.ts
|
|
378
|
+
var formatFunctions = {
|
|
379
|
+
formatString: (args, dataModel, scope) => {
|
|
380
|
+
const template = String(args.template ?? args.value ?? "");
|
|
381
|
+
return template.replace(/\$\{(.*?)\}/g, (_, expr) => {
|
|
382
|
+
if (expr.startsWith("{")) return "${" + expr.slice(1);
|
|
383
|
+
if (!expr.includes("(")) {
|
|
384
|
+
const value = resolvePath(expr.trim(), dataModel, scope);
|
|
385
|
+
return value != null ? String(value) : "";
|
|
386
|
+
}
|
|
387
|
+
return "";
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
formatNumber: (args) => {
|
|
391
|
+
const { value, precision, grouping } = args;
|
|
392
|
+
if (value == null) return "";
|
|
393
|
+
const num = Number(value);
|
|
394
|
+
if (isNaN(num)) return "";
|
|
395
|
+
return num.toLocaleString(void 0, {
|
|
396
|
+
minimumFractionDigits: precision ?? 0,
|
|
397
|
+
maximumFractionDigits: precision ?? 0,
|
|
398
|
+
useGrouping: grouping !== false
|
|
399
|
+
});
|
|
400
|
+
},
|
|
401
|
+
formatCurrency: (args) => {
|
|
402
|
+
const { value, currency, precision } = args;
|
|
403
|
+
if (value == null) return "";
|
|
404
|
+
const num = Number(value);
|
|
405
|
+
if (isNaN(num)) return "";
|
|
406
|
+
return num.toLocaleString(void 0, {
|
|
407
|
+
style: "currency",
|
|
408
|
+
currency: currency ?? "USD",
|
|
409
|
+
minimumFractionDigits: precision ?? 2,
|
|
410
|
+
maximumFractionDigits: precision ?? 2
|
|
411
|
+
});
|
|
412
|
+
},
|
|
413
|
+
formatDate: (args) => {
|
|
414
|
+
const { value, format } = args;
|
|
415
|
+
if (value == null) return "";
|
|
416
|
+
const date = new Date(value);
|
|
417
|
+
if (isNaN(date.getTime())) return "";
|
|
418
|
+
const tokens = {
|
|
419
|
+
yyyy: String(date.getFullYear()),
|
|
420
|
+
MM: String(date.getMonth() + 1).padStart(2, "0"),
|
|
421
|
+
dd: String(date.getDate()).padStart(2, "0"),
|
|
422
|
+
HH: String(date.getHours()).padStart(2, "0"),
|
|
423
|
+
mm: String(date.getMinutes()).padStart(2, "0"),
|
|
424
|
+
ss: String(date.getSeconds()).padStart(2, "0")
|
|
425
|
+
};
|
|
426
|
+
let result = format ?? "yyyy-MM-dd";
|
|
427
|
+
for (const [token, replacement] of Object.entries(tokens)) {
|
|
428
|
+
result = result.replace(token, replacement);
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// src/functions/logic.ts
|
|
435
|
+
var logicFunctions = {
|
|
436
|
+
and: (args) => {
|
|
437
|
+
const values = args.values ?? args.value ?? [];
|
|
438
|
+
if (!Array.isArray(values)) return Boolean(values);
|
|
439
|
+
return values.every(Boolean);
|
|
440
|
+
},
|
|
441
|
+
or: (args) => {
|
|
442
|
+
const values = args.values ?? args.value ?? [];
|
|
443
|
+
if (!Array.isArray(values)) return Boolean(values);
|
|
444
|
+
return values.some(Boolean);
|
|
445
|
+
},
|
|
446
|
+
not: (args) => {
|
|
447
|
+
return !Boolean(args.value);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// src/functions/pluralize.ts
|
|
452
|
+
var pluralizeFunction = (args) => {
|
|
453
|
+
const { count, zero, one, other } = args;
|
|
454
|
+
const n = Number(count) || 0;
|
|
455
|
+
if (n === 0 && zero != null) return zero;
|
|
456
|
+
if (n === 1 && one != null) return one;
|
|
457
|
+
return other ?? "";
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// src/functions/index.ts
|
|
461
|
+
var builtinFunctions = {
|
|
462
|
+
...validationFunctions,
|
|
463
|
+
...formatFunctions,
|
|
464
|
+
...logicFunctions,
|
|
465
|
+
pluralize: pluralizeFunction,
|
|
466
|
+
openUrl: (args) => {
|
|
467
|
+
if (args.url) window.open(String(args.url), "_blank");
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
function resolveArgs(args, dataModel, scope) {
|
|
471
|
+
if (!args) return {};
|
|
472
|
+
const resolved = {};
|
|
473
|
+
for (const [key, value] of Object.entries(args)) {
|
|
474
|
+
if (isDynamicValue(value)) {
|
|
475
|
+
if ("path" in value && value.path) {
|
|
476
|
+
resolved[key] = resolvePath(value.path, dataModel, scope);
|
|
477
|
+
} else if ("functionCall" in value && value.functionCall) {
|
|
478
|
+
resolved[key] = executeFunction(value.functionCall, dataModel, scope);
|
|
479
|
+
} else {
|
|
480
|
+
resolved[key] = resolveLiteral(value);
|
|
481
|
+
}
|
|
482
|
+
} else if (typeof value === "object" && value !== null && "call" in value) {
|
|
483
|
+
resolved[key] = executeFunction(value, dataModel, scope);
|
|
484
|
+
} else {
|
|
485
|
+
resolved[key] = value;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return resolved;
|
|
489
|
+
}
|
|
490
|
+
function executeFunction(functionCall, dataModel, scope) {
|
|
491
|
+
const impl = builtinFunctions[functionCall.call];
|
|
492
|
+
if (!impl) {
|
|
493
|
+
console.warn(`[A2UI] Unknown function: ${functionCall.call}`);
|
|
494
|
+
return void 0;
|
|
495
|
+
}
|
|
496
|
+
const resolvedArgs = resolveArgs(functionCall.args, dataModel, scope);
|
|
497
|
+
return impl(resolvedArgs, dataModel, scope);
|
|
498
|
+
}
|
|
499
|
+
function registerFunction(name, impl) {
|
|
500
|
+
builtinFunctions[name] = impl;
|
|
501
|
+
}
|
|
502
|
+
function getFunction(name) {
|
|
503
|
+
return builtinFunctions[name];
|
|
504
|
+
}
|
|
505
|
+
export {
|
|
506
|
+
A2UIParseError,
|
|
507
|
+
ComponentRegistry,
|
|
508
|
+
SurfaceManager,
|
|
509
|
+
deepMerge,
|
|
510
|
+
deleteByPointer,
|
|
511
|
+
executeFunction,
|
|
512
|
+
getByPointer,
|
|
513
|
+
getFunction,
|
|
514
|
+
isDynamicValue,
|
|
515
|
+
isTemplateChildList,
|
|
516
|
+
parseMessage,
|
|
517
|
+
parseMessageStream,
|
|
518
|
+
registerFunction,
|
|
519
|
+
resolveLiteral,
|
|
520
|
+
resolvePath,
|
|
521
|
+
setByPointer,
|
|
522
|
+
setPath,
|
|
523
|
+
validateMessage
|
|
524
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nine1ie/a2ui-vue-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A2UI v0.9 protocol core: parser, data model, client functions",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./types": {
|
|
15
|
+
"types": "./dist/types/index.d.ts",
|
|
16
|
+
"import": "./dist/types/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.0.0",
|
|
24
|
+
"typescript": "^5.7.0",
|
|
25
|
+
"vitest": "^3.0.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup src/index.ts --format esm --dts --outDir dist",
|
|
29
|
+
"dev": "tsup src/index.ts --format esm --dts --outDir dist --watch",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"typecheck": "vue-tsc --noEmit"
|
|
32
|
+
}
|
|
33
|
+
}
|