@a2ui-sdk/utils 0.0.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/dist/0.9/index.d.ts +1 -0
- package/dist/0.9/index.js +1 -0
- package/dist/0.9/interpolation/evaluator.d.ts +15 -0
- package/dist/0.9/interpolation/evaluator.js +95 -0
- package/dist/0.9/interpolation/evaluator.test.d.ts +4 -0
- package/dist/0.9/interpolation/evaluator.test.js +699 -0
- package/dist/0.9/interpolation/index.d.ts +70 -0
- package/dist/0.9/interpolation/index.js +84 -0
- package/dist/0.9/interpolation/lexer.d.ts +18 -0
- package/dist/0.9/interpolation/lexer.js +250 -0
- package/dist/0.9/interpolation/lexer.test.d.ts +4 -0
- package/dist/0.9/interpolation/lexer.test.js +360 -0
- package/dist/0.9/interpolation/parser.d.ts +14 -0
- package/dist/0.9/interpolation/parser.js +236 -0
- package/dist/0.9/interpolation/parser.test.d.ts +4 -0
- package/dist/0.9/interpolation/parser.test.js +314 -0
- package/dist/0.9/interpolation/types.d.ts +124 -0
- package/dist/0.9/interpolation/types.js +36 -0
- package/dist/0.9/interpolation.test.d.ts +5 -0
- package/dist/0.9/interpolation.test.js +154 -0
- package/dist/0.9/pathUtils.d.ts +115 -0
- package/dist/0.9/pathUtils.js +256 -0
- package/dist/0.9/pathUtils.test.d.ts +6 -0
- package/dist/0.9/pathUtils.test.js +330 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path utility functions for A2UI 0.9 data model operations.
|
|
3
|
+
*
|
|
4
|
+
* Implements JSON Pointer (RFC 6901) parsing and resolution with
|
|
5
|
+
* support for relative paths within collection scopes.
|
|
6
|
+
*/
|
|
7
|
+
import type { DataModel } from '@a2ui-sdk/types/0.9';
|
|
8
|
+
/**
|
|
9
|
+
* Parses a JSON Pointer path into segments.
|
|
10
|
+
*
|
|
11
|
+
* @param path - The JSON Pointer path (e.g., "/user/name")
|
|
12
|
+
* @returns Array of path segments
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* parseJsonPointer("/user/name"); // ["user", "name"]
|
|
16
|
+
* parseJsonPointer("/items/0"); // ["items", "0"]
|
|
17
|
+
* parseJsonPointer("/"); // []
|
|
18
|
+
* parseJsonPointer("/a~1b"); // ["a/b"] (escaped slash)
|
|
19
|
+
* parseJsonPointer("/m~0n"); // ["m~n"] (escaped tilde)
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseJsonPointer(path: string): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Creates a JSON Pointer path from segments.
|
|
24
|
+
*
|
|
25
|
+
* @param segments - Array of path segments
|
|
26
|
+
* @returns JSON Pointer path string
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* createJsonPointer(["user", "name"]); // "/user/name"
|
|
30
|
+
* createJsonPointer(["a/b"]); // "/a~1b"
|
|
31
|
+
* createJsonPointer([]); // "/"
|
|
32
|
+
*/
|
|
33
|
+
export declare function createJsonPointer(segments: string[]): string;
|
|
34
|
+
/**
|
|
35
|
+
* Gets a value from the data model by JSON Pointer path.
|
|
36
|
+
*
|
|
37
|
+
* @param dataModel - The data model to read from
|
|
38
|
+
* @param path - The JSON Pointer path (e.g., "/user/name")
|
|
39
|
+
* @returns The value at the path, or undefined if not found
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const model = { user: { name: "John" }, items: ["a", "b"] };
|
|
43
|
+
* getValueByPath(model, "/user/name"); // "John"
|
|
44
|
+
* getValueByPath(model, "/items/0"); // "a"
|
|
45
|
+
* getValueByPath(model, "/nonexistent"); // undefined
|
|
46
|
+
*/
|
|
47
|
+
export declare function getValueByPath(dataModel: DataModel, path: string): unknown;
|
|
48
|
+
/**
|
|
49
|
+
* Sets a value in the data model by JSON Pointer path, returning a new data model.
|
|
50
|
+
* This function is immutable - it does not modify the original data model.
|
|
51
|
+
*
|
|
52
|
+
* @param dataModel - The data model to update
|
|
53
|
+
* @param path - The JSON Pointer path (e.g., "/user/name")
|
|
54
|
+
* @param value - The value to set (undefined to delete)
|
|
55
|
+
* @returns A new data model with the value set
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* const model = { user: { name: "John" } };
|
|
59
|
+
* setValueByPath(model, "/user/name", "Jane"); // { user: { name: "Jane" } }
|
|
60
|
+
* setValueByPath(model, "/user/age", 30); // { user: { name: "John", age: 30 } }
|
|
61
|
+
* setValueByPath(model, "/user/name", undefined); // { user: {} }
|
|
62
|
+
*/
|
|
63
|
+
export declare function setValueByPath(dataModel: DataModel, path: string, value: unknown): DataModel;
|
|
64
|
+
/**
|
|
65
|
+
* Normalizes a path to ensure it starts with "/" and has no trailing "/".
|
|
66
|
+
*
|
|
67
|
+
* @param path - The path to normalize
|
|
68
|
+
* @returns The normalized path
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* normalizePath("user/name"); // "/user/name"
|
|
72
|
+
* normalizePath("/user/name/"); // "/user/name"
|
|
73
|
+
*/
|
|
74
|
+
export declare function normalizePath(path: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Checks if a path is absolute (starts with "/").
|
|
77
|
+
*
|
|
78
|
+
* @param path - The path to check
|
|
79
|
+
* @returns True if the path is absolute
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* isAbsolutePath("/user/name"); // true
|
|
83
|
+
* isAbsolutePath("name"); // false
|
|
84
|
+
* isAbsolutePath(""); // false
|
|
85
|
+
*/
|
|
86
|
+
export declare function isAbsolutePath(path: string): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Resolves a path against a base path (scope).
|
|
89
|
+
*
|
|
90
|
+
* Absolute paths (starting with "/") are returned as-is.
|
|
91
|
+
* Relative paths are resolved against the base path.
|
|
92
|
+
*
|
|
93
|
+
* @param path - The path to resolve
|
|
94
|
+
* @param basePath - The base path (scope), or null for root scope
|
|
95
|
+
* @returns The resolved absolute path
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* resolvePath("/user/name", "/items/0"); // "/user/name" (absolute)
|
|
99
|
+
* resolvePath("name", "/items/0"); // "/items/0/name" (relative)
|
|
100
|
+
* resolvePath("name", null); // "/name" (root scope)
|
|
101
|
+
*/
|
|
102
|
+
export declare function resolvePath(path: string, basePath: string | null): string;
|
|
103
|
+
/**
|
|
104
|
+
* Joins two paths together.
|
|
105
|
+
*
|
|
106
|
+
* @param basePath - The base path
|
|
107
|
+
* @param relativePath - The relative path to join
|
|
108
|
+
* @returns The joined path
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* joinPaths("/user", "name"); // "/user/name"
|
|
112
|
+
* joinPaths("/user", "/name"); // "/user/name"
|
|
113
|
+
* joinPaths("/user/", "/name/"); // "/user/name"
|
|
114
|
+
*/
|
|
115
|
+
export declare function joinPaths(basePath: string, relativePath: string): string;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path utility functions for A2UI 0.9 data model operations.
|
|
3
|
+
*
|
|
4
|
+
* Implements JSON Pointer (RFC 6901) parsing and resolution with
|
|
5
|
+
* support for relative paths within collection scopes.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parses a JSON Pointer path into segments.
|
|
9
|
+
*
|
|
10
|
+
* @param path - The JSON Pointer path (e.g., "/user/name")
|
|
11
|
+
* @returns Array of path segments
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* parseJsonPointer("/user/name"); // ["user", "name"]
|
|
15
|
+
* parseJsonPointer("/items/0"); // ["items", "0"]
|
|
16
|
+
* parseJsonPointer("/"); // []
|
|
17
|
+
* parseJsonPointer("/a~1b"); // ["a/b"] (escaped slash)
|
|
18
|
+
* parseJsonPointer("/m~0n"); // ["m~n"] (escaped tilde)
|
|
19
|
+
*/
|
|
20
|
+
export function parseJsonPointer(path) {
|
|
21
|
+
if (!path || path === '/') {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
// Remove leading slash and split
|
|
25
|
+
const segments = path.startsWith('/')
|
|
26
|
+
? path.slice(1).split('/')
|
|
27
|
+
: path.split('/');
|
|
28
|
+
// Unescape JSON Pointer special characters (~1 -> /, ~0 -> ~)
|
|
29
|
+
return segments
|
|
30
|
+
.filter((s) => s !== '')
|
|
31
|
+
.map((segment) => segment.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Creates a JSON Pointer path from segments.
|
|
35
|
+
*
|
|
36
|
+
* @param segments - Array of path segments
|
|
37
|
+
* @returns JSON Pointer path string
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* createJsonPointer(["user", "name"]); // "/user/name"
|
|
41
|
+
* createJsonPointer(["a/b"]); // "/a~1b"
|
|
42
|
+
* createJsonPointer([]); // "/"
|
|
43
|
+
*/
|
|
44
|
+
export function createJsonPointer(segments) {
|
|
45
|
+
if (segments.length === 0) {
|
|
46
|
+
return '/';
|
|
47
|
+
}
|
|
48
|
+
// Escape special characters (~ -> ~0, / -> ~1)
|
|
49
|
+
const escaped = segments.map((segment) => segment.replace(/~/g, '~0').replace(/\//g, '~1'));
|
|
50
|
+
return '/' + escaped.join('/');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Gets a value from the data model by JSON Pointer path.
|
|
54
|
+
*
|
|
55
|
+
* @param dataModel - The data model to read from
|
|
56
|
+
* @param path - The JSON Pointer path (e.g., "/user/name")
|
|
57
|
+
* @returns The value at the path, or undefined if not found
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const model = { user: { name: "John" }, items: ["a", "b"] };
|
|
61
|
+
* getValueByPath(model, "/user/name"); // "John"
|
|
62
|
+
* getValueByPath(model, "/items/0"); // "a"
|
|
63
|
+
* getValueByPath(model, "/nonexistent"); // undefined
|
|
64
|
+
*/
|
|
65
|
+
export function getValueByPath(dataModel, path) {
|
|
66
|
+
if (!path || path === '/') {
|
|
67
|
+
return dataModel;
|
|
68
|
+
}
|
|
69
|
+
const segments = parseJsonPointer(path);
|
|
70
|
+
let current = dataModel;
|
|
71
|
+
for (const segment of segments) {
|
|
72
|
+
if (current === null || current === undefined) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(current)) {
|
|
76
|
+
const index = parseInt(segment, 10);
|
|
77
|
+
if (isNaN(index)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
current = current[index];
|
|
81
|
+
}
|
|
82
|
+
else if (typeof current === 'object') {
|
|
83
|
+
current = current[segment];
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return current;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Sets a value in the data model by JSON Pointer path, returning a new data model.
|
|
93
|
+
* This function is immutable - it does not modify the original data model.
|
|
94
|
+
*
|
|
95
|
+
* @param dataModel - The data model to update
|
|
96
|
+
* @param path - The JSON Pointer path (e.g., "/user/name")
|
|
97
|
+
* @param value - The value to set (undefined to delete)
|
|
98
|
+
* @returns A new data model with the value set
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* const model = { user: { name: "John" } };
|
|
102
|
+
* setValueByPath(model, "/user/name", "Jane"); // { user: { name: "Jane" } }
|
|
103
|
+
* setValueByPath(model, "/user/age", 30); // { user: { name: "John", age: 30 } }
|
|
104
|
+
* setValueByPath(model, "/user/name", undefined); // { user: {} }
|
|
105
|
+
*/
|
|
106
|
+
export function setValueByPath(dataModel, path, value) {
|
|
107
|
+
// Replace entire data model
|
|
108
|
+
if (!path || path === '/') {
|
|
109
|
+
if (value === undefined) {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
return dataModel;
|
|
116
|
+
}
|
|
117
|
+
const segments = parseJsonPointer(path);
|
|
118
|
+
// Deep clone to ensure immutability
|
|
119
|
+
const result = structuredClone(dataModel);
|
|
120
|
+
// Navigate to parent
|
|
121
|
+
let current = result;
|
|
122
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
123
|
+
const segment = segments[i];
|
|
124
|
+
if (Array.isArray(current)) {
|
|
125
|
+
const index = parseInt(segment, 10);
|
|
126
|
+
if (isNaN(index)) {
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
// Ensure array element exists as object
|
|
130
|
+
if (current[index] === null || current[index] === undefined) {
|
|
131
|
+
current[index] = {};
|
|
132
|
+
}
|
|
133
|
+
current = current[index];
|
|
134
|
+
}
|
|
135
|
+
else if (typeof current === 'object' && current !== null) {
|
|
136
|
+
const obj = current;
|
|
137
|
+
// Create intermediate object if needed
|
|
138
|
+
if (obj[segment] === null || obj[segment] === undefined) {
|
|
139
|
+
obj[segment] = {};
|
|
140
|
+
}
|
|
141
|
+
else if (typeof obj[segment] !== 'object') {
|
|
142
|
+
obj[segment] = {};
|
|
143
|
+
}
|
|
144
|
+
current = obj[segment];
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Set or delete at final segment
|
|
151
|
+
const lastSegment = segments[segments.length - 1];
|
|
152
|
+
if (Array.isArray(current)) {
|
|
153
|
+
const index = parseInt(lastSegment, 10);
|
|
154
|
+
if (!isNaN(index)) {
|
|
155
|
+
if (value === undefined) {
|
|
156
|
+
current.splice(index, 1);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
current[index] = value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (typeof current === 'object' && current !== null) {
|
|
164
|
+
const obj = current;
|
|
165
|
+
if (value === undefined) {
|
|
166
|
+
delete obj[lastSegment];
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
obj[lastSegment] = value;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Normalizes a path to ensure it starts with "/" and has no trailing "/".
|
|
176
|
+
*
|
|
177
|
+
* @param path - The path to normalize
|
|
178
|
+
* @returns The normalized path
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* normalizePath("user/name"); // "/user/name"
|
|
182
|
+
* normalizePath("/user/name/"); // "/user/name"
|
|
183
|
+
*/
|
|
184
|
+
export function normalizePath(path) {
|
|
185
|
+
let normalized = path.trim();
|
|
186
|
+
// Ensure starts with /
|
|
187
|
+
if (!normalized.startsWith('/')) {
|
|
188
|
+
normalized = '/' + normalized;
|
|
189
|
+
}
|
|
190
|
+
// Remove trailing / (except for root path)
|
|
191
|
+
if (normalized.length > 1 && normalized.endsWith('/')) {
|
|
192
|
+
normalized = normalized.slice(0, -1);
|
|
193
|
+
}
|
|
194
|
+
return normalized;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Checks if a path is absolute (starts with "/").
|
|
198
|
+
*
|
|
199
|
+
* @param path - The path to check
|
|
200
|
+
* @returns True if the path is absolute
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* isAbsolutePath("/user/name"); // true
|
|
204
|
+
* isAbsolutePath("name"); // false
|
|
205
|
+
* isAbsolutePath(""); // false
|
|
206
|
+
*/
|
|
207
|
+
export function isAbsolutePath(path) {
|
|
208
|
+
return path.startsWith('/');
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Resolves a path against a base path (scope).
|
|
212
|
+
*
|
|
213
|
+
* Absolute paths (starting with "/") are returned as-is.
|
|
214
|
+
* Relative paths are resolved against the base path.
|
|
215
|
+
*
|
|
216
|
+
* @param path - The path to resolve
|
|
217
|
+
* @param basePath - The base path (scope), or null for root scope
|
|
218
|
+
* @returns The resolved absolute path
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* resolvePath("/user/name", "/items/0"); // "/user/name" (absolute)
|
|
222
|
+
* resolvePath("name", "/items/0"); // "/items/0/name" (relative)
|
|
223
|
+
* resolvePath("name", null); // "/name" (root scope)
|
|
224
|
+
*/
|
|
225
|
+
export function resolvePath(path, basePath) {
|
|
226
|
+
if (isAbsolutePath(path)) {
|
|
227
|
+
return normalizePath(path);
|
|
228
|
+
}
|
|
229
|
+
if (basePath === null || basePath === '/') {
|
|
230
|
+
return normalizePath(path);
|
|
231
|
+
}
|
|
232
|
+
return joinPaths(basePath, path);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Joins two paths together.
|
|
236
|
+
*
|
|
237
|
+
* @param basePath - The base path
|
|
238
|
+
* @param relativePath - The relative path to join
|
|
239
|
+
* @returns The joined path
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* joinPaths("/user", "name"); // "/user/name"
|
|
243
|
+
* joinPaths("/user", "/name"); // "/user/name"
|
|
244
|
+
* joinPaths("/user/", "/name/"); // "/user/name"
|
|
245
|
+
*/
|
|
246
|
+
export function joinPaths(basePath, relativePath) {
|
|
247
|
+
const base = normalizePath(basePath);
|
|
248
|
+
const relative = relativePath.trim().replace(/^\/+/, '').replace(/\/+$/, '');
|
|
249
|
+
if (!relative) {
|
|
250
|
+
return base;
|
|
251
|
+
}
|
|
252
|
+
if (base === '/') {
|
|
253
|
+
return '/' + relative;
|
|
254
|
+
}
|
|
255
|
+
return base + '/' + relative;
|
|
256
|
+
}
|