@a2ui-sdk/utils 0.0.3 → 0.1.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.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Path utility functions for A2UI data model operations.
3
+ */
4
+ /**
5
+ * Gets a value from the data model by path.
6
+ *
7
+ * @param dataModel - The data model to read from
8
+ * @param path - The path to the value (e.g., "/user/name")
9
+ * @returns The value at the path, or undefined if not found
10
+ *
11
+ * @example
12
+ * const model = { user: { name: "John" } };
13
+ * getValueByPath(model, "/user/name"); // "John"
14
+ * getValueByPath(model, "/user/age"); // undefined
15
+ */
16
+ export function getValueByPath(dataModel, path) {
17
+ if (!path || path === '/') {
18
+ return dataModel;
19
+ }
20
+ // Split path: "/user/name" -> ["user", "name"]
21
+ const keys = path.split('/').filter(Boolean);
22
+ let current = dataModel;
23
+ for (const key of keys) {
24
+ if (current === null || current === undefined) {
25
+ return undefined;
26
+ }
27
+ if (typeof current !== 'object') {
28
+ return undefined;
29
+ }
30
+ current = current[key];
31
+ }
32
+ return current;
33
+ }
34
+ /**
35
+ * Sets a value in the data model by path, returning a new data model.
36
+ * This function is immutable - it does not modify the original data model.
37
+ *
38
+ * @param dataModel - The data model to update
39
+ * @param path - The path to set (e.g., "/user/name")
40
+ * @param value - The value to set
41
+ * @returns A new data model with the value set
42
+ *
43
+ * @example
44
+ * const model = { user: { name: "John" } };
45
+ * setValueByPath(model, "/user/name", "Jane");
46
+ * // Returns: { user: { name: "Jane" } }
47
+ */
48
+ export function setValueByPath(dataModel, path, value) {
49
+ if (!path || path === '/') {
50
+ // Replace the entire data model
51
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
52
+ return { ...dataModel, ...value };
53
+ }
54
+ return dataModel;
55
+ }
56
+ // Split path: "/user/name" -> ["user", "name"]
57
+ const keys = path.split('/').filter(Boolean);
58
+ // Recursive helper to build the new object structure
59
+ function setNested(obj, remainingKeys, val) {
60
+ if (remainingKeys.length === 0) {
61
+ // This shouldn't happen, but handle gracefully
62
+ if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
63
+ return { ...obj, ...val };
64
+ }
65
+ return obj;
66
+ }
67
+ const [key, ...rest] = remainingKeys;
68
+ if (rest.length === 0) {
69
+ // Last key - set the value
70
+ return {
71
+ ...obj,
72
+ [key]: val,
73
+ };
74
+ }
75
+ // Intermediate key - recurse
76
+ const existingValue = obj[key];
77
+ const nestedObj = typeof existingValue === 'object' &&
78
+ existingValue !== null &&
79
+ !Array.isArray(existingValue)
80
+ ? existingValue
81
+ : {};
82
+ return {
83
+ ...obj,
84
+ [key]: setNested(nestedObj, rest, val),
85
+ };
86
+ }
87
+ return setNested(dataModel, keys, value);
88
+ }
89
+ /**
90
+ * Merges data into the data model at a given path.
91
+ * This is used for dataModelUpdate messages where contents are merged.
92
+ *
93
+ * @param dataModel - The data model to update
94
+ * @param path - The path to merge at (e.g., "/form")
95
+ * @param data - The data to merge
96
+ * @returns A new data model with the data merged
97
+ */
98
+ export function mergeAtPath(dataModel, path, data) {
99
+ if (!path || path === '/') {
100
+ // Merge at root
101
+ return { ...dataModel, ...data };
102
+ }
103
+ // Get current value at path
104
+ const current = getValueByPath(dataModel, path);
105
+ const currentObj = typeof current === 'object' && current !== null && !Array.isArray(current)
106
+ ? current
107
+ : {};
108
+ // Merge and set
109
+ const merged = { ...currentObj, ...data };
110
+ return setValueByPath(dataModel, path, merged);
111
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * pathUtils Tests
3
+ *
4
+ * Tests for path utility functions used in A2UI data model operations.
5
+ */
6
+ export {};
@@ -0,0 +1,211 @@
1
+ /**
2
+ * pathUtils Tests
3
+ *
4
+ * Tests for path utility functions used in A2UI data model operations.
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { getValueByPath, setValueByPath, mergeAtPath } from './pathUtils.js';
8
+ describe('pathUtils', () => {
9
+ describe('getValueByPath', () => {
10
+ const testModel = {
11
+ user: {
12
+ name: 'John',
13
+ age: 30,
14
+ profile: {
15
+ email: 'john@example.com',
16
+ active: true,
17
+ },
18
+ },
19
+ items: ['a', 'b', 'c'],
20
+ count: 42,
21
+ };
22
+ it('should return entire data model for empty path', () => {
23
+ expect(getValueByPath(testModel, '')).toEqual(testModel);
24
+ });
25
+ it('should return entire data model for root path', () => {
26
+ expect(getValueByPath(testModel, '/')).toEqual(testModel);
27
+ });
28
+ it('should get top-level string value', () => {
29
+ const model = { name: 'test' };
30
+ expect(getValueByPath(model, '/name')).toBe('test');
31
+ });
32
+ it('should get top-level number value', () => {
33
+ expect(getValueByPath(testModel, '/count')).toBe(42);
34
+ });
35
+ it('should get top-level array value', () => {
36
+ expect(getValueByPath(testModel, '/items')).toEqual(['a', 'b', 'c']);
37
+ });
38
+ it('should get nested object value', () => {
39
+ expect(getValueByPath(testModel, '/user')).toEqual({
40
+ name: 'John',
41
+ age: 30,
42
+ profile: {
43
+ email: 'john@example.com',
44
+ active: true,
45
+ },
46
+ });
47
+ });
48
+ it('should get deeply nested string value', () => {
49
+ expect(getValueByPath(testModel, '/user/name')).toBe('John');
50
+ });
51
+ it('should get deeply nested number value', () => {
52
+ expect(getValueByPath(testModel, '/user/age')).toBe(30);
53
+ });
54
+ it('should get deeply nested object value', () => {
55
+ expect(getValueByPath(testModel, '/user/profile')).toEqual({
56
+ email: 'john@example.com',
57
+ active: true,
58
+ });
59
+ });
60
+ it('should get very deeply nested value', () => {
61
+ expect(getValueByPath(testModel, '/user/profile/email')).toBe('john@example.com');
62
+ expect(getValueByPath(testModel, '/user/profile/active')).toBe(true);
63
+ });
64
+ it('should return undefined for non-existent path', () => {
65
+ expect(getValueByPath(testModel, '/nonexistent')).toBeUndefined();
66
+ });
67
+ it('should return undefined for non-existent nested path', () => {
68
+ expect(getValueByPath(testModel, '/user/nonexistent')).toBeUndefined();
69
+ });
70
+ it('should return undefined for path through non-object', () => {
71
+ expect(getValueByPath(testModel, '/count/nested')).toBeUndefined();
72
+ });
73
+ it('should return undefined when intermediate value is null', () => {
74
+ const model = { user: null };
75
+ expect(getValueByPath(model, '/user/name')).toBeUndefined();
76
+ });
77
+ it('should return undefined when intermediate value is undefined', () => {
78
+ const model = { user: undefined };
79
+ expect(getValueByPath(model, '/user/name')).toBeUndefined();
80
+ });
81
+ it('should handle paths without leading slash', () => {
82
+ expect(getValueByPath(testModel, 'user/name')).toBe('John');
83
+ });
84
+ it('should handle empty data model', () => {
85
+ expect(getValueByPath({}, '/user/name')).toBeUndefined();
86
+ });
87
+ });
88
+ describe('setValueByPath', () => {
89
+ it('should return merged object for empty path with object value', () => {
90
+ const model = { a: 1 };
91
+ const result = setValueByPath(model, '', { b: 2 });
92
+ expect(result).toEqual({ a: 1, b: 2 });
93
+ });
94
+ it('should return merged object for root path with object value', () => {
95
+ const model = { a: 1 };
96
+ const result = setValueByPath(model, '/', { b: 2 });
97
+ expect(result).toEqual({ a: 1, b: 2 });
98
+ });
99
+ it('should return original model for root path with non-object value', () => {
100
+ const model = { a: 1 };
101
+ const result = setValueByPath(model, '/', 'string');
102
+ expect(result).toEqual({ a: 1 });
103
+ });
104
+ it('should return original model for root path with array value', () => {
105
+ const model = { a: 1 };
106
+ const result = setValueByPath(model, '/', [1, 2, 3]);
107
+ expect(result).toEqual({ a: 1 });
108
+ });
109
+ it('should set top-level value', () => {
110
+ const model = { a: 1 };
111
+ const result = setValueByPath(model, '/b', 2);
112
+ expect(result).toEqual({ a: 1, b: 2 });
113
+ });
114
+ it('should update existing top-level value', () => {
115
+ const model = { a: 1 };
116
+ const result = setValueByPath(model, '/a', 2);
117
+ expect(result).toEqual({ a: 2 });
118
+ });
119
+ it('should set nested value in existing object', () => {
120
+ const model = { user: { name: 'John' } };
121
+ const result = setValueByPath(model, '/user/age', 30);
122
+ expect(result).toEqual({ user: { name: 'John', age: 30 } });
123
+ });
124
+ it('should update existing nested value', () => {
125
+ const model = { user: { name: 'John' } };
126
+ const result = setValueByPath(model, '/user/name', 'Jane');
127
+ expect(result).toEqual({ user: { name: 'Jane' } });
128
+ });
129
+ it('should create nested structure if not exists', () => {
130
+ const model = {};
131
+ const result = setValueByPath(model, '/user/profile/email', 'test@test.com');
132
+ expect(result).toEqual({
133
+ user: { profile: { email: 'test@test.com' } },
134
+ });
135
+ });
136
+ it('should replace non-object with object when setting nested path', () => {
137
+ const model = { user: 'string' };
138
+ const result = setValueByPath(model, '/user/name', 'John');
139
+ expect(result).toEqual({ user: { name: 'John' } });
140
+ });
141
+ it('should be immutable - not modify original model', () => {
142
+ const model = { user: { name: 'John' } };
143
+ const result = setValueByPath(model, '/user/name', 'Jane');
144
+ expect(model).toEqual({ user: { name: 'John' } });
145
+ expect(result).toEqual({ user: { name: 'Jane' } });
146
+ });
147
+ it('should handle setting null value', () => {
148
+ const model = { user: { name: 'John' } };
149
+ const result = setValueByPath(model, '/user/name', null);
150
+ expect(result).toEqual({ user: { name: null } });
151
+ });
152
+ it('should handle setting array value', () => {
153
+ const model = { items: [] };
154
+ const result = setValueByPath(model, '/items', ['a', 'b']);
155
+ expect(result).toEqual({ items: ['a', 'b'] });
156
+ });
157
+ it('should handle setting object value', () => {
158
+ const model = { user: null };
159
+ const result = setValueByPath(model, '/user', { name: 'John' });
160
+ expect(result).toEqual({ user: { name: 'John' } });
161
+ });
162
+ });
163
+ describe('mergeAtPath', () => {
164
+ it('should merge at root for empty path', () => {
165
+ const model = { a: 1 };
166
+ const result = mergeAtPath(model, '', { b: 2 });
167
+ expect(result).toEqual({ a: 1, b: 2 });
168
+ });
169
+ it('should merge at root for root path', () => {
170
+ const model = { a: 1 };
171
+ const result = mergeAtPath(model, '/', { b: 2 });
172
+ expect(result).toEqual({ a: 1, b: 2 });
173
+ });
174
+ it('should overwrite existing keys at root', () => {
175
+ const model = { a: 1, b: 2 };
176
+ const result = mergeAtPath(model, '/', { b: 3, c: 4 });
177
+ expect(result).toEqual({ a: 1, b: 3, c: 4 });
178
+ });
179
+ it('should merge at nested path', () => {
180
+ const model = { user: { name: 'John' } };
181
+ const result = mergeAtPath(model, '/user', { age: 30 });
182
+ expect(result).toEqual({ user: { name: 'John', age: 30 } });
183
+ });
184
+ it('should overwrite existing keys at nested path', () => {
185
+ const model = { user: { name: 'John', age: 25 } };
186
+ const result = mergeAtPath(model, '/user', { age: 30 });
187
+ expect(result).toEqual({ user: { name: 'John', age: 30 } });
188
+ });
189
+ it('should create path if not exists', () => {
190
+ const model = {};
191
+ const result = mergeAtPath(model, '/user', { name: 'John' });
192
+ expect(result).toEqual({ user: { name: 'John' } });
193
+ });
194
+ it('should replace non-object at path with merged result', () => {
195
+ const model = { user: 'string' };
196
+ const result = mergeAtPath(model, '/user', { name: 'John' });
197
+ expect(result).toEqual({ user: { name: 'John' } });
198
+ });
199
+ it('should handle array at path by treating as non-object', () => {
200
+ const model = { items: [1, 2, 3] };
201
+ const result = mergeAtPath(model, '/items', { a: 1 });
202
+ expect(result).toEqual({ items: { a: 1 } });
203
+ });
204
+ it('should be immutable - not modify original model', () => {
205
+ const model = { user: { name: 'John' } };
206
+ const result = mergeAtPath(model, '/user', { age: 30 });
207
+ expect(model).toEqual({ user: { name: 'John' } });
208
+ expect(result).toEqual({ user: { name: 'John', age: 30 } });
209
+ });
210
+ });
211
+ });
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Data binding utility functions for A2UI 0.9.
3
+ *
4
+ * Handles resolving dynamic values with the simplified 0.9 format:
5
+ * - Literal values: `"string"`, `42`, `true`
6
+ * - Path bindings: `{"path": "/user/name"}` or `{"path": "relative"}`
7
+ * - Function calls: `{"call": "functionName", "args": {...}}`
8
+ */
9
+ import type { DynamicValue, DynamicString, DataModel, FormBindableValue } from '@a2ui-sdk/types/0.9';
10
+ /**
11
+ * Type guard to check if a value is a path binding.
12
+ *
13
+ * @param value - The value to check
14
+ * @returns True if the value is a path binding object
15
+ *
16
+ * @example
17
+ * isPathBinding({ path: "/user/name" }); // true
18
+ * isPathBinding("literal"); // false
19
+ * isPathBinding({ call: "required" }); // false
20
+ */
21
+ export declare function isPathBinding(value: FormBindableValue | undefined | null): value is {
22
+ path: string;
23
+ };
24
+ /**
25
+ * Type guard to check if a value is a function call.
26
+ *
27
+ * @param value - The value to check
28
+ * @returns True if the value is a function call object
29
+ */
30
+ export declare function isFunctionCall(value: FormBindableValue | undefined | null): value is {
31
+ call: string;
32
+ args?: Record<string, DynamicValue>;
33
+ };
34
+ /**
35
+ * Resolves a dynamic value to its actual value.
36
+ *
37
+ * Resolution order:
38
+ * 1. If value is a path binding object → resolve from data model
39
+ * 2. If value is a function call → return undefined (function calls not evaluated here)
40
+ * 3. Otherwise → return literal value
41
+ *
42
+ * @param value - The dynamic value (literal, path binding, or function call)
43
+ * @param dataModel - The data model for path lookups
44
+ * @param basePath - Optional base path for relative path resolution
45
+ * @param defaultValue - Default value if undefined or path not found
46
+ * @returns The resolved value
47
+ *
48
+ * @example
49
+ * // Literal values
50
+ * resolveValue("Hello", {}); // "Hello"
51
+ * resolveValue(42, {}); // 42
52
+ * resolveValue(true, {}); // true
53
+ *
54
+ * // Path bindings
55
+ * const model = { user: { name: "John" } };
56
+ * resolveValue({ path: "/user/name" }, model); // "John"
57
+ * resolveValue({ path: "name" }, model, "/user"); // "John" (relative)
58
+ * resolveValue({ path: "/nonexistent" }, model, null, "default"); // "default"
59
+ */
60
+ export declare function resolveValue<T = unknown>(value: FormBindableValue | undefined | null, dataModel: DataModel, basePath?: string | null, defaultValue?: T): T;
61
+ /**
62
+ * Resolves a DynamicString value to a string.
63
+ * This is a convenience wrapper for string values.
64
+ *
65
+ * Supports interpolation: if the string contains `${...}` expressions,
66
+ * they will be replaced with values from the data model.
67
+ *
68
+ * @param value - The dynamic string value
69
+ * @param dataModel - The data model for path lookups
70
+ * @param basePath - Optional base path for relative path resolution
71
+ * @param defaultValue - Default value if undefined or path not found
72
+ * @returns The resolved string
73
+ *
74
+ * @example
75
+ * // Simple string
76
+ * resolveString("Hello", {}); // "Hello"
77
+ *
78
+ * // Path binding
79
+ * resolveString({ path: "/user/name" }, { user: { name: "John" } }); // "John"
80
+ *
81
+ * // Interpolation
82
+ * resolveString("Hello, ${/user/name}!", { user: { name: "John" } }); // "Hello, John!"
83
+ */
84
+ export declare function resolveString(value: DynamicString | undefined | null, dataModel: DataModel, basePath?: string | null, defaultValue?: string): string;
85
+ /**
86
+ * Resolves action context values to a plain object.
87
+ * All path bindings are resolved to their actual values.
88
+ *
89
+ * @param context - Object with dynamic values
90
+ * @param dataModel - The data model for path lookups
91
+ * @param basePath - Optional base path for relative path resolution
92
+ * @returns A plain object with resolved context values
93
+ *
94
+ * @example
95
+ * const context = {
96
+ * name: { path: "/user/name" },
97
+ * action: "submit",
98
+ * count: 42
99
+ * };
100
+ * resolveContext(context, { user: { name: "John" } });
101
+ * // Returns: { name: "John", action: "submit", count: 42 }
102
+ */
103
+ export declare function resolveContext(context: Record<string, DynamicValue> | undefined, dataModel: DataModel, basePath?: string | null): Record<string, unknown>;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Data binding utility functions for A2UI 0.9.
3
+ *
4
+ * Handles resolving dynamic values with the simplified 0.9 format:
5
+ * - Literal values: `"string"`, `42`, `true`
6
+ * - Path bindings: `{"path": "/user/name"}` or `{"path": "relative"}`
7
+ * - Function calls: `{"call": "functionName", "args": {...}}`
8
+ */
9
+ import { getValueByPath, resolvePath } from './pathUtils.js';
10
+ import { interpolate } from '@a2ui-sdk/utils/0.9';
11
+ /**
12
+ * Type guard to check if a value is a path binding.
13
+ *
14
+ * @param value - The value to check
15
+ * @returns True if the value is a path binding object
16
+ *
17
+ * @example
18
+ * isPathBinding({ path: "/user/name" }); // true
19
+ * isPathBinding("literal"); // false
20
+ * isPathBinding({ call: "required" }); // false
21
+ */
22
+ export function isPathBinding(value) {
23
+ return (value !== undefined &&
24
+ value !== null &&
25
+ typeof value === 'object' &&
26
+ !Array.isArray(value) &&
27
+ 'path' in value &&
28
+ typeof value.path === 'string');
29
+ }
30
+ /**
31
+ * Type guard to check if a value is a function call.
32
+ *
33
+ * @param value - The value to check
34
+ * @returns True if the value is a function call object
35
+ */
36
+ export function isFunctionCall(value) {
37
+ return (value !== undefined &&
38
+ value !== null &&
39
+ typeof value === 'object' &&
40
+ !Array.isArray(value) &&
41
+ 'call' in value &&
42
+ typeof value.call === 'string');
43
+ }
44
+ /**
45
+ * Resolves a dynamic value to its actual value.
46
+ *
47
+ * Resolution order:
48
+ * 1. If value is a path binding object → resolve from data model
49
+ * 2. If value is a function call → return undefined (function calls not evaluated here)
50
+ * 3. Otherwise → return literal value
51
+ *
52
+ * @param value - The dynamic value (literal, path binding, or function call)
53
+ * @param dataModel - The data model for path lookups
54
+ * @param basePath - Optional base path for relative path resolution
55
+ * @param defaultValue - Default value if undefined or path not found
56
+ * @returns The resolved value
57
+ *
58
+ * @example
59
+ * // Literal values
60
+ * resolveValue("Hello", {}); // "Hello"
61
+ * resolveValue(42, {}); // 42
62
+ * resolveValue(true, {}); // true
63
+ *
64
+ * // Path bindings
65
+ * const model = { user: { name: "John" } };
66
+ * resolveValue({ path: "/user/name" }, model); // "John"
67
+ * resolveValue({ path: "name" }, model, "/user"); // "John" (relative)
68
+ * resolveValue({ path: "/nonexistent" }, model, null, "default"); // "default"
69
+ */
70
+ export function resolveValue(value, dataModel, basePath = null, defaultValue) {
71
+ if (value === undefined || value === null) {
72
+ return defaultValue;
73
+ }
74
+ // Path binding
75
+ if (isPathBinding(value)) {
76
+ const resolvedPath = resolvePath(value.path, basePath);
77
+ const result = getValueByPath(dataModel, resolvedPath);
78
+ if (result === undefined) {
79
+ return defaultValue;
80
+ }
81
+ return result;
82
+ }
83
+ // Function call - not evaluated here, return default
84
+ if (isFunctionCall(value)) {
85
+ return defaultValue;
86
+ }
87
+ // Literal value
88
+ return value;
89
+ }
90
+ /**
91
+ * Resolves a DynamicString value to a string.
92
+ * This is a convenience wrapper for string values.
93
+ *
94
+ * Supports interpolation: if the string contains `${...}` expressions,
95
+ * they will be replaced with values from the data model.
96
+ *
97
+ * @param value - The dynamic string value
98
+ * @param dataModel - The data model for path lookups
99
+ * @param basePath - Optional base path for relative path resolution
100
+ * @param defaultValue - Default value if undefined or path not found
101
+ * @returns The resolved string
102
+ *
103
+ * @example
104
+ * // Simple string
105
+ * resolveString("Hello", {}); // "Hello"
106
+ *
107
+ * // Path binding
108
+ * resolveString({ path: "/user/name" }, { user: { name: "John" } }); // "John"
109
+ *
110
+ * // Interpolation
111
+ * resolveString("Hello, ${/user/name}!", { user: { name: "John" } }); // "Hello, John!"
112
+ */
113
+ export function resolveString(value, dataModel, basePath = null, defaultValue = '') {
114
+ // Handle undefined/null
115
+ if (value === undefined || value === null) {
116
+ return defaultValue;
117
+ }
118
+ // Handle path binding
119
+ if (isPathBinding(value)) {
120
+ const resolvedPath = resolvePath(value.path, basePath);
121
+ const result = getValueByPath(dataModel, resolvedPath);
122
+ if (result === undefined || result === null) {
123
+ return defaultValue;
124
+ }
125
+ return String(result);
126
+ }
127
+ // Handle function call (not evaluated here)
128
+ if (isFunctionCall(value)) {
129
+ return defaultValue;
130
+ }
131
+ // Handle string literal - perform interpolation
132
+ if (typeof value === 'string') {
133
+ return interpolate(value, dataModel, basePath);
134
+ }
135
+ // Other types (number, boolean) - convert to string
136
+ return String(value);
137
+ }
138
+ /**
139
+ * Resolves action context values to a plain object.
140
+ * All path bindings are resolved to their actual values.
141
+ *
142
+ * @param context - Object with dynamic values
143
+ * @param dataModel - The data model for path lookups
144
+ * @param basePath - Optional base path for relative path resolution
145
+ * @returns A plain object with resolved context values
146
+ *
147
+ * @example
148
+ * const context = {
149
+ * name: { path: "/user/name" },
150
+ * action: "submit",
151
+ * count: 42
152
+ * };
153
+ * resolveContext(context, { user: { name: "John" } });
154
+ * // Returns: { name: "John", action: "submit", count: 42 }
155
+ */
156
+ export function resolveContext(context, dataModel, basePath = null) {
157
+ if (!context) {
158
+ return {};
159
+ }
160
+ const result = {};
161
+ for (const [key, value] of Object.entries(context)) {
162
+ result[key] = resolveValue(value, dataModel, basePath);
163
+ }
164
+ return result;
165
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * dataBinding Tests
3
+ *
4
+ * Tests for data binding utility functions used in A2UI 0.9.
5
+ */
6
+ export {};