@adecore/intelligent-ui 0.21.1 → 0.21.2
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/compiler.js +52 -45
- package/package.json +1 -1
- package/src/compiler.ts +61 -47
package/dist/compiler.js
CHANGED
|
@@ -20,6 +20,50 @@ export function uiMayReferenceHost(text) {
|
|
|
20
20
|
.map(([name]) => name);
|
|
21
21
|
return new RegExp(`<(?:${links.join('|')})\\b`).test(text);
|
|
22
22
|
}
|
|
23
|
+
/* A spent budget ends the whole block; every other failure only marks the node or declaration it came from. */
|
|
24
|
+
function rethrowBudget(error) {
|
|
25
|
+
if (error instanceof UiFailure && error.code === 'budget_exceeded') {
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function isQuery(expression) {
|
|
30
|
+
return expression.kind === 'call' && expression.name === 'Query';
|
|
31
|
+
}
|
|
32
|
+
/* Throws when a query argument reads anything but declared local state. */
|
|
33
|
+
function checkQueryReferences(part, defaults) {
|
|
34
|
+
if (!part || typeof part !== 'object') {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if ('kind' in part && part.kind === 'reference' && 'name' in part && !Object.hasOwn(defaults, String(part.name))) {
|
|
38
|
+
throw new UiFailure('invalid_query', 'Query arguments may reference only declared local state.');
|
|
39
|
+
}
|
|
40
|
+
for (const value of Object.values(part)) {
|
|
41
|
+
checkQueryReferences(value, defaults);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function compileQuery(expression, block, budget, querySchemas) {
|
|
45
|
+
if (expression.args.length !== 2 || !isConstant(expression.args[0]) || expression.args[1].kind !== 'object') {
|
|
46
|
+
throw new UiFailure('invalid_query', '@Query needs a literal source and an argument record.');
|
|
47
|
+
}
|
|
48
|
+
const querySource = evaluateUiExpression(expression.args[0], {}, budget);
|
|
49
|
+
const argument = expression.args[1];
|
|
50
|
+
checkQueryReferences(argument, block.defaults);
|
|
51
|
+
const args = evaluateUiExpression(argument, block.defaults, budget);
|
|
52
|
+
if (typeof querySource !== 'string' || !args || Array.isArray(args) || typeof args !== 'object') {
|
|
53
|
+
throw new UiFailure('invalid_query', 'Invalid query source or argument record.');
|
|
54
|
+
}
|
|
55
|
+
if (!querySchemas || !Object.hasOwn(querySchemas, querySource)) {
|
|
56
|
+
throw new UiFailure('unknown_query', 'The host did not register this query source.');
|
|
57
|
+
}
|
|
58
|
+
const checked = querySchemas[querySource].safeParse(args);
|
|
59
|
+
if (!checked.success) {
|
|
60
|
+
throw new UiFailure('invalid_query', 'Query arguments do not match the host schema.');
|
|
61
|
+
}
|
|
62
|
+
if (Object.keys(block.queries).length >= UI_HOST_LIMITS.queries) {
|
|
63
|
+
throw new UiFailure('budget_exceeded', `A block may declare at most ${UI_HOST_LIMITS.queries} queries.`);
|
|
64
|
+
}
|
|
65
|
+
return { source: querySource, args, ...(!isConstant(argument) ? { expression: argument } : {}) };
|
|
66
|
+
}
|
|
23
67
|
function isConstant(expression) {
|
|
24
68
|
switch (expression.kind) {
|
|
25
69
|
case 'literal':
|
|
@@ -84,9 +128,7 @@ export function compileUiBlock(source, options) {
|
|
|
84
128
|
node.fallback = uiPlainText(evaluateUiExpression(syntax.expression, block.defaults, budget));
|
|
85
129
|
}
|
|
86
130
|
catch (error) {
|
|
87
|
-
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
131
|
+
rethrowBudget(error);
|
|
90
132
|
}
|
|
91
133
|
}
|
|
92
134
|
else {
|
|
@@ -150,9 +192,7 @@ export function compileUiBlock(source, options) {
|
|
|
150
192
|
evaluated[key] = evaluateUiExpression(expression, block.defaults, budget);
|
|
151
193
|
}
|
|
152
194
|
catch (error) {
|
|
153
|
-
|
|
154
|
-
throw error;
|
|
155
|
-
}
|
|
195
|
+
rethrowBudget(error);
|
|
156
196
|
unresolved = true;
|
|
157
197
|
}
|
|
158
198
|
}
|
|
@@ -165,9 +205,7 @@ export function compileUiBlock(source, options) {
|
|
|
165
205
|
}
|
|
166
206
|
}
|
|
167
207
|
catch (error) {
|
|
168
|
-
|
|
169
|
-
throw error;
|
|
170
|
-
}
|
|
208
|
+
rethrowBudget(error);
|
|
171
209
|
node.error = error instanceof UiFailure ? error.code : 'invalid_props';
|
|
172
210
|
diagnose(error, syntax.start, syntax.end, syntax.id);
|
|
173
211
|
}
|
|
@@ -182,41 +220,12 @@ export function compileUiBlock(source, options) {
|
|
|
182
220
|
try {
|
|
183
221
|
const syntax = parseUiSyntax(source, options.id, options.final, budget);
|
|
184
222
|
block.diagnostics = syntax.diagnostics;
|
|
185
|
-
|
|
223
|
+
// Defaults first, so a query argument can read every one of them.
|
|
224
|
+
const declarations = Object.entries(syntax.declarations).sort(([, left], [, right]) => Number(isQuery(left)) - Number(isQuery(right)));
|
|
225
|
+
for (const [name, expression] of declarations) {
|
|
186
226
|
try {
|
|
187
227
|
if (expression.kind === 'call' && expression.name === 'Query') {
|
|
188
|
-
|
|
189
|
-
throw new UiFailure('invalid_query', '@Query needs a literal source and an argument record.');
|
|
190
|
-
}
|
|
191
|
-
const querySource = evaluateUiExpression(expression.args[0], {}, budget);
|
|
192
|
-
const argument = expression.args[1];
|
|
193
|
-
const references = (part) => {
|
|
194
|
-
if (!part || typeof part !== 'object') {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if ('kind' in part && part.kind === 'reference' && 'name' in part && !Object.hasOwn(block.defaults, String(part.name))) {
|
|
198
|
-
throw new UiFailure('invalid_query', 'Query arguments may reference only declared local state.');
|
|
199
|
-
}
|
|
200
|
-
for (const value of Object.values(part)) {
|
|
201
|
-
references(value);
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
references(argument);
|
|
205
|
-
const args = evaluateUiExpression(argument, block.defaults, budget);
|
|
206
|
-
if (typeof querySource !== 'string' || !args || Array.isArray(args) || typeof args !== 'object') {
|
|
207
|
-
throw new UiFailure('invalid_query', 'Invalid query source or argument record.');
|
|
208
|
-
}
|
|
209
|
-
if (!options.querySchemas || !Object.hasOwn(options.querySchemas, querySource)) {
|
|
210
|
-
throw new UiFailure('unknown_query', 'The host did not register this query source.');
|
|
211
|
-
}
|
|
212
|
-
const checked = options.querySchemas[querySource].safeParse(args);
|
|
213
|
-
if (!checked.success) {
|
|
214
|
-
throw new UiFailure('invalid_query', 'Query arguments do not match the host schema.');
|
|
215
|
-
}
|
|
216
|
-
if (Object.keys(block.queries).length >= UI_HOST_LIMITS.queries) {
|
|
217
|
-
throw new UiFailure('budget_exceeded', `A block may declare at most ${UI_HOST_LIMITS.queries} queries.`);
|
|
218
|
-
}
|
|
219
|
-
block.queries[name] = { source: querySource, args, ...(!isConstant(argument) ? { expression: argument } : {}) };
|
|
228
|
+
block.queries[name] = compileQuery(expression, block, budget, options.querySchemas);
|
|
220
229
|
}
|
|
221
230
|
else {
|
|
222
231
|
if (!isConstant(expression)) {
|
|
@@ -226,9 +235,7 @@ export function compileUiBlock(source, options) {
|
|
|
226
235
|
}
|
|
227
236
|
}
|
|
228
237
|
catch (error) {
|
|
229
|
-
|
|
230
|
-
throw error;
|
|
231
|
-
}
|
|
238
|
+
rethrowBudget(error);
|
|
232
239
|
diagnose(error, 0, source.length);
|
|
233
240
|
}
|
|
234
241
|
}
|
package/package.json
CHANGED
package/src/compiler.ts
CHANGED
|
@@ -70,6 +70,59 @@ export interface UiCompileOptions {
|
|
|
70
70
|
now?: () => number;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/* A spent budget ends the whole block; every other failure only marks the node or declaration it came from. */
|
|
74
|
+
function rethrowBudget(error: unknown): void {
|
|
75
|
+
if (error instanceof UiFailure && error.code === 'budget_exceeded') {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isQuery(expression: UiExpression): boolean {
|
|
81
|
+
return expression.kind === 'call' && expression.name === 'Query';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Throws when a query argument reads anything but declared local state. */
|
|
85
|
+
function checkQueryReferences(part: unknown, defaults: Readonly<Record<string, UiValue>>): void {
|
|
86
|
+
if (!part || typeof part !== 'object') {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if ('kind' in part && part.kind === 'reference' && 'name' in part && !Object.hasOwn(defaults, String(part.name))) {
|
|
90
|
+
throw new UiFailure('invalid_query', 'Query arguments may reference only declared local state.');
|
|
91
|
+
}
|
|
92
|
+
for (const value of Object.values(part)) {
|
|
93
|
+
checkQueryReferences(value, defaults);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function compileQuery(
|
|
98
|
+
expression: Extract<UiExpression, { kind: 'call' }>,
|
|
99
|
+
block: UiBlock,
|
|
100
|
+
budget: UiBudget,
|
|
101
|
+
querySchemas: UiCompileOptions['querySchemas']
|
|
102
|
+
): UiQuery {
|
|
103
|
+
if (expression.args.length !== 2 || !isConstant(expression.args[0]) || expression.args[1].kind !== 'object') {
|
|
104
|
+
throw new UiFailure('invalid_query', '@Query needs a literal source and an argument record.');
|
|
105
|
+
}
|
|
106
|
+
const querySource = evaluateUiExpression(expression.args[0], {}, budget);
|
|
107
|
+
const argument = expression.args[1];
|
|
108
|
+
checkQueryReferences(argument, block.defaults);
|
|
109
|
+
const args = evaluateUiExpression(argument, block.defaults, budget);
|
|
110
|
+
if (typeof querySource !== 'string' || !args || Array.isArray(args) || typeof args !== 'object') {
|
|
111
|
+
throw new UiFailure('invalid_query', 'Invalid query source or argument record.');
|
|
112
|
+
}
|
|
113
|
+
if (!querySchemas || !Object.hasOwn(querySchemas, querySource)) {
|
|
114
|
+
throw new UiFailure('unknown_query', 'The host did not register this query source.');
|
|
115
|
+
}
|
|
116
|
+
const checked = querySchemas[querySource].safeParse(args);
|
|
117
|
+
if (!checked.success) {
|
|
118
|
+
throw new UiFailure('invalid_query', 'Query arguments do not match the host schema.');
|
|
119
|
+
}
|
|
120
|
+
if (Object.keys(block.queries).length >= UI_HOST_LIMITS.queries) {
|
|
121
|
+
throw new UiFailure('budget_exceeded', `A block may declare at most ${UI_HOST_LIMITS.queries} queries.`);
|
|
122
|
+
}
|
|
123
|
+
return { source: querySource, args, ...(!isConstant(argument) ? { expression: argument } : {}) };
|
|
124
|
+
}
|
|
125
|
+
|
|
73
126
|
function isConstant(expression: UiExpression): boolean {
|
|
74
127
|
switch (expression.kind) {
|
|
75
128
|
case 'literal':
|
|
@@ -134,9 +187,7 @@ export function compileUiBlock(source: string, options: UiCompileOptions): UiBlo
|
|
|
134
187
|
try {
|
|
135
188
|
node.fallback = uiPlainText(evaluateUiExpression(syntax.expression, block.defaults, budget));
|
|
136
189
|
} catch (error) {
|
|
137
|
-
|
|
138
|
-
throw error;
|
|
139
|
-
}
|
|
190
|
+
rethrowBudget(error);
|
|
140
191
|
}
|
|
141
192
|
} else {
|
|
142
193
|
node.props.text = syntax.text ?? '';
|
|
@@ -199,9 +250,7 @@ export function compileUiBlock(source: string, options: UiCompileOptions): UiBlo
|
|
|
199
250
|
try {
|
|
200
251
|
evaluated[key] = evaluateUiExpression(expression, block.defaults, budget);
|
|
201
252
|
} catch (error) {
|
|
202
|
-
|
|
203
|
-
throw error;
|
|
204
|
-
}
|
|
253
|
+
rethrowBudget(error);
|
|
205
254
|
unresolved = true;
|
|
206
255
|
}
|
|
207
256
|
}
|
|
@@ -213,9 +262,7 @@ export function compileUiBlock(source: string, options: UiCompileOptions): UiBlo
|
|
|
213
262
|
}
|
|
214
263
|
}
|
|
215
264
|
} catch (error) {
|
|
216
|
-
|
|
217
|
-
throw error;
|
|
218
|
-
}
|
|
265
|
+
rethrowBudget(error);
|
|
219
266
|
node.error = error instanceof UiFailure ? error.code : 'invalid_props';
|
|
220
267
|
diagnose(error, syntax.start, syntax.end, syntax.id);
|
|
221
268
|
}
|
|
@@ -230,43 +277,12 @@ export function compileUiBlock(source: string, options: UiCompileOptions): UiBlo
|
|
|
230
277
|
try {
|
|
231
278
|
const syntax = parseUiSyntax(source, options.id, options.final, budget);
|
|
232
279
|
block.diagnostics = syntax.diagnostics;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
)
|
|
280
|
+
// Defaults first, so a query argument can read every one of them.
|
|
281
|
+
const declarations = Object.entries(syntax.declarations).sort(([, left], [, right]) => Number(isQuery(left)) - Number(isQuery(right)));
|
|
282
|
+
for (const [name, expression] of declarations) {
|
|
236
283
|
try {
|
|
237
284
|
if (expression.kind === 'call' && expression.name === 'Query') {
|
|
238
|
-
|
|
239
|
-
throw new UiFailure('invalid_query', '@Query needs a literal source and an argument record.');
|
|
240
|
-
}
|
|
241
|
-
const querySource = evaluateUiExpression(expression.args[0], {}, budget);
|
|
242
|
-
const argument = expression.args[1];
|
|
243
|
-
const references = (part: unknown): void => {
|
|
244
|
-
if (!part || typeof part !== 'object') {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if ('kind' in part && part.kind === 'reference' && 'name' in part && !Object.hasOwn(block.defaults, String(part.name))) {
|
|
248
|
-
throw new UiFailure('invalid_query', 'Query arguments may reference only declared local state.');
|
|
249
|
-
}
|
|
250
|
-
for (const value of Object.values(part)) {
|
|
251
|
-
references(value);
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
references(argument);
|
|
255
|
-
const args = evaluateUiExpression(argument, block.defaults, budget);
|
|
256
|
-
if (typeof querySource !== 'string' || !args || Array.isArray(args) || typeof args !== 'object') {
|
|
257
|
-
throw new UiFailure('invalid_query', 'Invalid query source or argument record.');
|
|
258
|
-
}
|
|
259
|
-
if (!options.querySchemas || !Object.hasOwn(options.querySchemas, querySource)) {
|
|
260
|
-
throw new UiFailure('unknown_query', 'The host did not register this query source.');
|
|
261
|
-
}
|
|
262
|
-
const checked = options.querySchemas[querySource].safeParse(args);
|
|
263
|
-
if (!checked.success) {
|
|
264
|
-
throw new UiFailure('invalid_query', 'Query arguments do not match the host schema.');
|
|
265
|
-
}
|
|
266
|
-
if (Object.keys(block.queries).length >= UI_HOST_LIMITS.queries) {
|
|
267
|
-
throw new UiFailure('budget_exceeded', `A block may declare at most ${UI_HOST_LIMITS.queries} queries.`);
|
|
268
|
-
}
|
|
269
|
-
block.queries[name] = { source: querySource, args, ...(!isConstant(argument) ? { expression: argument } : {}) };
|
|
285
|
+
block.queries[name] = compileQuery(expression, block, budget, options.querySchemas);
|
|
270
286
|
} else {
|
|
271
287
|
if (!isConstant(expression)) {
|
|
272
288
|
throw new UiFailure('invalid_default', 'State defaults must be literal JSON values.');
|
|
@@ -274,9 +290,7 @@ export function compileUiBlock(source: string, options: UiCompileOptions): UiBlo
|
|
|
274
290
|
block.defaults[name] = evaluateUiExpression(expression, {}, budget);
|
|
275
291
|
}
|
|
276
292
|
} catch (error) {
|
|
277
|
-
|
|
278
|
-
throw error;
|
|
279
|
-
}
|
|
293
|
+
rethrowBudget(error);
|
|
280
294
|
diagnose(error, 0, source.length);
|
|
281
295
|
}
|
|
282
296
|
}
|