@antglobal/copilot-cards-core 1.0.2 → 1.0.3
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/index.cjs +2403 -96
- package/dist/index.d.ts +192 -7
- package/dist/index.js +2383 -97
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -387,8 +387,14 @@ async function parseResponseBody(response) {
|
|
|
387
387
|
*
|
|
388
388
|
* @param silent - If true, only write to ctx.variables without calling
|
|
389
389
|
* setVariable (avoids triggering re-render during polling iterations).
|
|
390
|
+
* When variableWriter is present it owns the write and must synchronously
|
|
391
|
+
* update the bound variables draft while honoring this flag.
|
|
390
392
|
*/
|
|
391
393
|
function writeResponseVariable(ctx, responseKey, data, silent = false) {
|
|
394
|
+
if (ctx.variableWriter) {
|
|
395
|
+
ctx.variableWriter(responseKey, data, { silent, source: 'request' });
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
392
398
|
if (ctx.variables) {
|
|
393
399
|
ctx.variables[responseKey] = data;
|
|
394
400
|
}
|
|
@@ -571,6 +577,10 @@ const handleUrl = (step, ctx) => {
|
|
|
571
577
|
};
|
|
572
578
|
const handleSetVariable = (step, ctx) => {
|
|
573
579
|
const { key, value } = step.params;
|
|
580
|
+
if (ctx.variableWriter) {
|
|
581
|
+
ctx.variableWriter(key, value, { silent: false, source: 'setVariable' });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
574
584
|
if (ctx.setVariable) {
|
|
575
585
|
ctx.setVariable(key, value);
|
|
576
586
|
}
|
|
@@ -630,9 +640,15 @@ async function runActionStep(step, context = {}) {
|
|
|
630
640
|
return;
|
|
631
641
|
}
|
|
632
642
|
// Resolve expression variables in params (e.g. '${order_id}' → 'ORDER_666')
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
643
|
+
const resolutionContext = context.expressionContext ?? context.variables;
|
|
644
|
+
const resolvedParams = context.parameterResolver
|
|
645
|
+
? context.parameterResolver(step.params)
|
|
646
|
+
: resolutionContext
|
|
647
|
+
? resolveDeep(step.params, resolutionContext)
|
|
648
|
+
: step.params;
|
|
649
|
+
const resolvedStep = resolvedParams === step.params
|
|
650
|
+
? step
|
|
651
|
+
: { ...step, params: resolvedParams };
|
|
636
652
|
await handler(resolvedStep, context);
|
|
637
653
|
}
|
|
638
654
|
/**
|
|
@@ -1100,6 +1116,578 @@ function staticValue(value) {
|
|
|
1100
1116
|
return { type: 'static', value };
|
|
1101
1117
|
}
|
|
1102
1118
|
|
|
1119
|
+
const UNSAFE_POINTER_SEGMENTS = new Set([
|
|
1120
|
+
'__proto__',
|
|
1121
|
+
'constructor',
|
|
1122
|
+
'prototype',
|
|
1123
|
+
]);
|
|
1124
|
+
const ARRAY_INDEX_PATTERN = /^(0|[1-9]\d*)$/;
|
|
1125
|
+
const MAX_ARRAY_INDEX = 4294967294;
|
|
1126
|
+
/** A rejected pointer spelling or array location, rather than a data failure. */
|
|
1127
|
+
class JsonPointerPathError extends Error {
|
|
1128
|
+
constructor(code, message) {
|
|
1129
|
+
super(message);
|
|
1130
|
+
this.name = 'JsonPointerPathError';
|
|
1131
|
+
this.code = code;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function isRecord(value) {
|
|
1135
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1139
|
+
return prototype === Object.prototype || prototype === null;
|
|
1140
|
+
}
|
|
1141
|
+
function isContainer(value) {
|
|
1142
|
+
return Array.isArray(value) || isRecord(value);
|
|
1143
|
+
}
|
|
1144
|
+
function isReadableContainer(value) {
|
|
1145
|
+
return value !== null && typeof value === 'object';
|
|
1146
|
+
}
|
|
1147
|
+
function assertSafeSegment(segment) {
|
|
1148
|
+
if (UNSAFE_POINTER_SEGMENTS.has(segment)) {
|
|
1149
|
+
throw new JsonPointerPathError('UNSAFE_SEGMENT', `Unsafe JSON Pointer segment "${segment}"`);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function arrayIndex(segment) {
|
|
1153
|
+
if (!ARRAY_INDEX_PATTERN.test(segment))
|
|
1154
|
+
return undefined;
|
|
1155
|
+
const index = Number(segment);
|
|
1156
|
+
return index <= MAX_ARRAY_INDEX ? index : undefined;
|
|
1157
|
+
}
|
|
1158
|
+
function assertContainerSegment(container, segment) {
|
|
1159
|
+
if (Array.isArray(container) && arrayIndex(segment) === undefined) {
|
|
1160
|
+
throw new JsonPointerPathError('INVALID_ARRAY_INDEX', `Invalid array index "${segment}" in JSON Pointer`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function assertWritableContainerSegment(container, segment) {
|
|
1164
|
+
assertContainerSegment(container, segment);
|
|
1165
|
+
if (!Array.isArray(container))
|
|
1166
|
+
return;
|
|
1167
|
+
const index = arrayIndex(segment);
|
|
1168
|
+
if (index !== undefined && index > container.length) {
|
|
1169
|
+
throw new JsonPointerPathError('INVALID_ARRAY_INDEX', `Array index "${segment}" exceeds length ${container.length} in JSON Pointer`);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
function readOwnDataProperty(container, key) {
|
|
1173
|
+
const descriptor = Object.getOwnPropertyDescriptor(container, key);
|
|
1174
|
+
if (!descriptor)
|
|
1175
|
+
return { found: false };
|
|
1176
|
+
if (!('value' in descriptor)) {
|
|
1177
|
+
throw new TypeError(`Cannot traverse JSON Pointer accessor member "${key}"`);
|
|
1178
|
+
}
|
|
1179
|
+
return { found: true, value: descriptor.value };
|
|
1180
|
+
}
|
|
1181
|
+
function defineDataProperty(target, key, value, recordMutation) {
|
|
1182
|
+
assertWritableContainerSegment(target, key);
|
|
1183
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
1184
|
+
if (descriptor) {
|
|
1185
|
+
if ('value' in descriptor && descriptor.writable) {
|
|
1186
|
+
recordMutation?.(target, key);
|
|
1187
|
+
Object.defineProperty(target, key, { value });
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (!descriptor.configurable) {
|
|
1191
|
+
throw new Error(`Cannot write JSON Pointer member "${key}"`);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
else if (!Object.isExtensible(target)) {
|
|
1195
|
+
throw new Error(`Cannot create JSON Pointer member "${key}"`);
|
|
1196
|
+
}
|
|
1197
|
+
recordMutation?.(target, key);
|
|
1198
|
+
Object.defineProperty(target, key, {
|
|
1199
|
+
value,
|
|
1200
|
+
enumerable: true,
|
|
1201
|
+
configurable: true,
|
|
1202
|
+
writable: true,
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
function createDetachedBranch(segments, value) {
|
|
1206
|
+
let branch = value;
|
|
1207
|
+
for (let index = segments.length - 1; index >= 0; index -= 1) {
|
|
1208
|
+
const segment = segments[index];
|
|
1209
|
+
const child = arrayIndex(segment) === undefined ? {} : [];
|
|
1210
|
+
defineDataProperty(child, segment, branch);
|
|
1211
|
+
branch = child;
|
|
1212
|
+
}
|
|
1213
|
+
return branch;
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Decode one RFC 6901 JSON Pointer reference token.
|
|
1217
|
+
*
|
|
1218
|
+
* Only `~0` and `~1` are legal escapes. URI/percent decoding is deliberately
|
|
1219
|
+
* outside this helper's contract.
|
|
1220
|
+
*/
|
|
1221
|
+
function decodeJsonPointerSegment(segment) {
|
|
1222
|
+
let decoded = '';
|
|
1223
|
+
for (let index = 0; index < segment.length; index += 1) {
|
|
1224
|
+
const character = segment[index];
|
|
1225
|
+
if (character !== '~') {
|
|
1226
|
+
decoded += character;
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
const escape = segment[index + 1];
|
|
1230
|
+
if (escape === '0') {
|
|
1231
|
+
decoded += '~';
|
|
1232
|
+
}
|
|
1233
|
+
else if (escape === '1') {
|
|
1234
|
+
decoded += '/';
|
|
1235
|
+
}
|
|
1236
|
+
else {
|
|
1237
|
+
throw new JsonPointerPathError('SYNTAX', `Invalid JSON Pointer escape "~${escape ?? ''}"`);
|
|
1238
|
+
}
|
|
1239
|
+
index += 1;
|
|
1240
|
+
}
|
|
1241
|
+
return decoded;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Parse a strict RFC 6901 JSON Pointer.
|
|
1245
|
+
*
|
|
1246
|
+
* The canonical root is `''`; `/` names an empty-string property.
|
|
1247
|
+
*/
|
|
1248
|
+
function parseJsonPointer(pointer) {
|
|
1249
|
+
if (pointer === '')
|
|
1250
|
+
return [];
|
|
1251
|
+
if (typeof pointer !== 'string' || !pointer.startsWith('/')) {
|
|
1252
|
+
throw new JsonPointerPathError('SYNTAX', 'JSON Pointer must be empty or start with "/"');
|
|
1253
|
+
}
|
|
1254
|
+
return pointer.slice(1).split('/').map((encoded) => {
|
|
1255
|
+
const segment = decodeJsonPointerSegment(encoded);
|
|
1256
|
+
assertSafeSegment(segment);
|
|
1257
|
+
return segment;
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
/** Read an own-property value through a strict JSON Pointer. */
|
|
1261
|
+
function getByJsonPointer(root, pointer) {
|
|
1262
|
+
const segments = parseJsonPointer(pointer);
|
|
1263
|
+
let current = root;
|
|
1264
|
+
for (const segment of segments) {
|
|
1265
|
+
if (!isReadableContainer(current))
|
|
1266
|
+
return undefined;
|
|
1267
|
+
assertContainerSegment(current, segment);
|
|
1268
|
+
const property = readOwnDataProperty(current, segment);
|
|
1269
|
+
if (!property.found)
|
|
1270
|
+
return undefined;
|
|
1271
|
+
current = property.value;
|
|
1272
|
+
}
|
|
1273
|
+
return current;
|
|
1274
|
+
}
|
|
1275
|
+
function cloneJsonDataInternal(value, ancestors) {
|
|
1276
|
+
if (value === null ||
|
|
1277
|
+
typeof value === 'string' ||
|
|
1278
|
+
typeof value === 'boolean') {
|
|
1279
|
+
return value;
|
|
1280
|
+
}
|
|
1281
|
+
if (typeof value === 'number') {
|
|
1282
|
+
if (!Number.isFinite(value)) {
|
|
1283
|
+
throw new TypeError('JSON data numbers must be finite');
|
|
1284
|
+
}
|
|
1285
|
+
return value;
|
|
1286
|
+
}
|
|
1287
|
+
if (typeof value !== 'object') {
|
|
1288
|
+
throw new TypeError(`Unsupported JSON data value: ${typeof value}`);
|
|
1289
|
+
}
|
|
1290
|
+
if (ancestors.has(value)) {
|
|
1291
|
+
throw new TypeError('Cannot clone cyclic JSON data');
|
|
1292
|
+
}
|
|
1293
|
+
ancestors.add(value);
|
|
1294
|
+
try {
|
|
1295
|
+
if (Array.isArray(value)) {
|
|
1296
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
1297
|
+
if (ownKeys.some(key => typeof key === 'symbol')) {
|
|
1298
|
+
throw new TypeError('JSON data arrays cannot contain symbol keys');
|
|
1299
|
+
}
|
|
1300
|
+
const keys = ownKeys.filter(key => key !== 'length');
|
|
1301
|
+
if (keys.length !== value.length ||
|
|
1302
|
+
keys.some((key) => {
|
|
1303
|
+
const index = arrayIndex(key);
|
|
1304
|
+
return index === undefined || index >= value.length;
|
|
1305
|
+
})) {
|
|
1306
|
+
throw new TypeError('JSON data arrays must be dense and contain only indexed values');
|
|
1307
|
+
}
|
|
1308
|
+
const result = [];
|
|
1309
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1310
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
1311
|
+
if (!descriptor || !descriptor.enumerable) {
|
|
1312
|
+
throw new TypeError('JSON data arrays must be dense and contain only indexed values');
|
|
1313
|
+
}
|
|
1314
|
+
if (!('value' in descriptor)) {
|
|
1315
|
+
throw new TypeError('JSON data arrays cannot contain accessors');
|
|
1316
|
+
}
|
|
1317
|
+
result.push(cloneJsonDataInternal(descriptor.value, ancestors));
|
|
1318
|
+
}
|
|
1319
|
+
return result;
|
|
1320
|
+
}
|
|
1321
|
+
if (!isRecord(value)) {
|
|
1322
|
+
throw new TypeError('JSON data objects must be plain records');
|
|
1323
|
+
}
|
|
1324
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
1325
|
+
throw new TypeError('JSON data objects cannot contain symbol keys');
|
|
1326
|
+
}
|
|
1327
|
+
const result = {};
|
|
1328
|
+
for (const key of Object.keys(value)) {
|
|
1329
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1330
|
+
if (!descriptor || !('value' in descriptor)) {
|
|
1331
|
+
throw new TypeError('JSON data objects cannot contain accessors');
|
|
1332
|
+
}
|
|
1333
|
+
Object.defineProperty(result, key, {
|
|
1334
|
+
value: cloneJsonDataInternal(descriptor.value, ancestors),
|
|
1335
|
+
enumerable: true,
|
|
1336
|
+
configurable: true,
|
|
1337
|
+
writable: true,
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
return result;
|
|
1341
|
+
}
|
|
1342
|
+
finally {
|
|
1343
|
+
ancestors.delete(value);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
/** Deep-clone a value from the JSON data-model domain. */
|
|
1347
|
+
function cloneJsonData(value) {
|
|
1348
|
+
return cloneJsonDataInternal(value, new Set());
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Replace a root record's safe own contents while preserving its identity.
|
|
1352
|
+
*
|
|
1353
|
+
* For ordinary non-Proxy JSON records, validation and cloning finish before
|
|
1354
|
+
* the first mutation, so a rejected candidate leaves the target untouched.
|
|
1355
|
+
* JavaScript Proxy traps cannot be identified reliably and are outside this
|
|
1356
|
+
* atomicity guarantee.
|
|
1357
|
+
*/
|
|
1358
|
+
function replaceRootContentsInternal(root, next, recordMutation) {
|
|
1359
|
+
if (!isRecord(root) || !isRecord(next)) {
|
|
1360
|
+
throw new TypeError('JSON Pointer root replacement requires a record');
|
|
1361
|
+
}
|
|
1362
|
+
const nextKeys = Object.keys(next);
|
|
1363
|
+
for (const key of nextKeys) {
|
|
1364
|
+
if (UNSAFE_POINTER_SEGMENTS.has(key)) {
|
|
1365
|
+
throw new Error(`Unsafe root key "${key}"`);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
const cloned = cloneJsonData(next);
|
|
1369
|
+
const currentSafeKeys = Object.keys(root).filter(key => !UNSAFE_POINTER_SEGMENTS.has(key));
|
|
1370
|
+
for (const key of currentSafeKeys) {
|
|
1371
|
+
if (!Object.prototype.hasOwnProperty.call(cloned, key)) {
|
|
1372
|
+
const descriptor = Object.getOwnPropertyDescriptor(root, key);
|
|
1373
|
+
if (!descriptor?.configurable) {
|
|
1374
|
+
throw new Error(`Cannot remove non-configurable root key "${key}"`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
for (const key of nextKeys) {
|
|
1379
|
+
const descriptor = Object.getOwnPropertyDescriptor(root, key);
|
|
1380
|
+
if (!descriptor && !Object.isExtensible(root)) {
|
|
1381
|
+
throw new Error(`Cannot create root key "${key}"`);
|
|
1382
|
+
}
|
|
1383
|
+
if (descriptor &&
|
|
1384
|
+
!descriptor.configurable &&
|
|
1385
|
+
(!('value' in descriptor) || !descriptor.writable)) {
|
|
1386
|
+
throw new Error(`Cannot replace non-writable root key "${key}"`);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
for (const key of currentSafeKeys) {
|
|
1390
|
+
if (!Object.prototype.hasOwnProperty.call(cloned, key)) {
|
|
1391
|
+
recordMutation?.(root, key);
|
|
1392
|
+
delete root[key];
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
for (const key of nextKeys) {
|
|
1396
|
+
const descriptor = Object.getOwnPropertyDescriptor(root, key);
|
|
1397
|
+
recordMutation?.(root, key);
|
|
1398
|
+
if (descriptor && !descriptor.configurable) {
|
|
1399
|
+
Object.defineProperty(root, key, { value: cloned[key] });
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
Object.defineProperty(root, key, {
|
|
1403
|
+
value: cloned[key],
|
|
1404
|
+
enumerable: true,
|
|
1405
|
+
configurable: true,
|
|
1406
|
+
writable: true,
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
function replaceRootContents(root, next) {
|
|
1412
|
+
replaceRootContentsInternal(root, next);
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Transactionally write JSON data through a strict JSON Pointer when the
|
|
1416
|
+
* target is an ordinary non-Proxy JSON record. JavaScript Proxy traps are
|
|
1417
|
+
* outside the atomicity guarantee because they cannot be identified reliably.
|
|
1418
|
+
*
|
|
1419
|
+
* The root pointer `''` replaces the root record. Non-root values are cloned
|
|
1420
|
+
* before they become reachable from the target.
|
|
1421
|
+
*/
|
|
1422
|
+
function setByJsonPointerInternal(root, pointer, value, recordMutation) {
|
|
1423
|
+
if (!isRecord(root)) {
|
|
1424
|
+
throw new TypeError('JSON Pointer writes require a record root');
|
|
1425
|
+
}
|
|
1426
|
+
const segments = parseJsonPointer(pointer);
|
|
1427
|
+
if (segments.length === 0) {
|
|
1428
|
+
if (!isRecord(value)) {
|
|
1429
|
+
throw new TypeError('JSON Pointer root replacement requires a record value');
|
|
1430
|
+
}
|
|
1431
|
+
replaceRootContentsInternal(root, value, recordMutation);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const clonedValue = cloneJsonData(value);
|
|
1435
|
+
let current = root;
|
|
1436
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
1437
|
+
const segment = segments[index];
|
|
1438
|
+
assertWritableContainerSegment(current, segment);
|
|
1439
|
+
const property = readOwnDataProperty(current, segment);
|
|
1440
|
+
if (!property.found) {
|
|
1441
|
+
const branch = createDetachedBranch(segments.slice(index + 1), clonedValue);
|
|
1442
|
+
defineDataProperty(current, segment, branch, recordMutation);
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
const next = property.value;
|
|
1446
|
+
if (next === null || next === undefined) {
|
|
1447
|
+
const branch = createDetachedBranch(segments.slice(index + 1), clonedValue);
|
|
1448
|
+
defineDataProperty(current, segment, branch, recordMutation);
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
if (!isContainer(next)) {
|
|
1452
|
+
throw new Error(`Cannot traverse JSON Pointer member "${segment}"`);
|
|
1453
|
+
}
|
|
1454
|
+
current = next;
|
|
1455
|
+
}
|
|
1456
|
+
defineDataProperty(current, segments[segments.length - 1], clonedValue, recordMutation);
|
|
1457
|
+
}
|
|
1458
|
+
function setByJsonPointer(root, pointer, value) {
|
|
1459
|
+
setByJsonPointerInternal(root, pointer, value);
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Engine-internal variant that records only properties the strict writer is
|
|
1463
|
+
* about to mutate. It is intentionally not re-exported from the Core package.
|
|
1464
|
+
*/
|
|
1465
|
+
function setByJsonPointerWithMutationRecorder(root, pointer, value, recordMutation) {
|
|
1466
|
+
setByJsonPointerInternal(root, pointer, value, recordMutation);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function isPlainObject$1(value) {
|
|
1470
|
+
try {
|
|
1471
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
1472
|
+
return false;
|
|
1473
|
+
}
|
|
1474
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1475
|
+
return prototype === Object.prototype || prototype === null;
|
|
1476
|
+
}
|
|
1477
|
+
catch {
|
|
1478
|
+
return false;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
function getExactOwnDataValues(value, expected) {
|
|
1482
|
+
try {
|
|
1483
|
+
const keys = Reflect.ownKeys(value);
|
|
1484
|
+
if (keys.length !== expected.length
|
|
1485
|
+
|| !expected.every(key => Object.prototype.hasOwnProperty.call(value, key))) {
|
|
1486
|
+
return undefined;
|
|
1487
|
+
}
|
|
1488
|
+
const values = [];
|
|
1489
|
+
for (const key of expected) {
|
|
1490
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1491
|
+
if (!descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
1492
|
+
return undefined;
|
|
1493
|
+
}
|
|
1494
|
+
values.push(descriptor.value);
|
|
1495
|
+
}
|
|
1496
|
+
return values;
|
|
1497
|
+
}
|
|
1498
|
+
catch {
|
|
1499
|
+
return undefined;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function readDynamicChildList(value) {
|
|
1503
|
+
if (!isPlainObject$1(value))
|
|
1504
|
+
return undefined;
|
|
1505
|
+
const fields = getExactOwnDataValues(value, ['path', 'componentId']);
|
|
1506
|
+
if (!fields
|
|
1507
|
+
|| typeof fields[0] !== 'string'
|
|
1508
|
+
|| typeof fields[1] !== 'string') {
|
|
1509
|
+
return undefined;
|
|
1510
|
+
}
|
|
1511
|
+
return { path: fields[0], componentId: fields[1] };
|
|
1512
|
+
}
|
|
1513
|
+
/** Runtime guard for the exact dynamic ChildList common-type shape. */
|
|
1514
|
+
function isA2UIDynamicChildList(value) {
|
|
1515
|
+
return readDynamicChildList(value) !== undefined;
|
|
1516
|
+
}
|
|
1517
|
+
/** Runtime guard for either supported ChildList form. */
|
|
1518
|
+
function isA2UIChildList(value) {
|
|
1519
|
+
try {
|
|
1520
|
+
if (Array.isArray(value)) {
|
|
1521
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
|
|
1522
|
+
const length = lengthDescriptor?.value;
|
|
1523
|
+
if (!Number.isSafeInteger(length) || length < 0)
|
|
1524
|
+
return false;
|
|
1525
|
+
for (let index = 0; index < length; index += 1) {
|
|
1526
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
1527
|
+
if (!descriptor
|
|
1528
|
+
|| !Object.prototype.hasOwnProperty.call(descriptor, 'value')
|
|
1529
|
+
|| typeof descriptor.value !== 'string') {
|
|
1530
|
+
return false;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return true;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
catch {
|
|
1537
|
+
return false;
|
|
1538
|
+
}
|
|
1539
|
+
return isA2UIDynamicChildList(value);
|
|
1540
|
+
}
|
|
1541
|
+
/** Runtime guard for the exact A2UI DataBinding.path common-type shape. */
|
|
1542
|
+
function isA2UIPathBinding(value) {
|
|
1543
|
+
return readPathBinding(value) !== undefined;
|
|
1544
|
+
}
|
|
1545
|
+
function readPathBinding(value) {
|
|
1546
|
+
if (!isPlainObject$1(value))
|
|
1547
|
+
return undefined;
|
|
1548
|
+
const fields = getExactOwnDataValues(value, ['path']);
|
|
1549
|
+
if (!fields || typeof fields[0] !== 'string')
|
|
1550
|
+
return undefined;
|
|
1551
|
+
return { path: fields[0] };
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Resolve one A2UI path from the data-model root or the current item scope.
|
|
1555
|
+
*
|
|
1556
|
+
* Absolute paths use strict JSON Pointer syntax. Relative paths are an A2UI
|
|
1557
|
+
* extension and are appended to the current strict-pointer scope.
|
|
1558
|
+
*/
|
|
1559
|
+
function resolveA2UIPath(root, path, scopePath) {
|
|
1560
|
+
if (typeof path !== 'string' || typeof scopePath !== 'string') {
|
|
1561
|
+
throw new TypeError('[A2UI] path and scopePath must be strings');
|
|
1562
|
+
}
|
|
1563
|
+
const pointer = path.startsWith('/')
|
|
1564
|
+
? path
|
|
1565
|
+
: path === ''
|
|
1566
|
+
? scopePath
|
|
1567
|
+
: `${scopePath}/${path}`;
|
|
1568
|
+
return getByJsonPointer(root, pointer);
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Resolve every exact A2UI `{ path }` binding in a nested value.
|
|
1572
|
+
*
|
|
1573
|
+
* Plain records and arrays are cloned with their property descriptors, so
|
|
1574
|
+
* accessors are preserved but never invoked. Cycles in non-JSON direct API
|
|
1575
|
+
* inputs remain cycles in the cloned output.
|
|
1576
|
+
*/
|
|
1577
|
+
function resolveA2UIDeep(value, root, scopePath) {
|
|
1578
|
+
return resolveA2UIDeepInternal(value, root, scopePath, new WeakMap());
|
|
1579
|
+
}
|
|
1580
|
+
function resolveA2UIDeepInternal(value, root, scopePath, seen) {
|
|
1581
|
+
const binding = readPathBinding(value);
|
|
1582
|
+
if (binding) {
|
|
1583
|
+
return resolveA2UIPath(root, binding.path, scopePath);
|
|
1584
|
+
}
|
|
1585
|
+
if (typeof value !== 'object' || value === null)
|
|
1586
|
+
return value;
|
|
1587
|
+
const existing = seen.get(value);
|
|
1588
|
+
if (existing)
|
|
1589
|
+
return existing;
|
|
1590
|
+
let isArray;
|
|
1591
|
+
let prototype;
|
|
1592
|
+
let descriptors;
|
|
1593
|
+
try {
|
|
1594
|
+
isArray = Array.isArray(value);
|
|
1595
|
+
prototype = Object.getPrototypeOf(value);
|
|
1596
|
+
if (!isArray
|
|
1597
|
+
&& prototype !== Object.prototype
|
|
1598
|
+
&& prototype !== null) {
|
|
1599
|
+
return value;
|
|
1600
|
+
}
|
|
1601
|
+
descriptors = [];
|
|
1602
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
1603
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1604
|
+
if (!descriptor)
|
|
1605
|
+
return value;
|
|
1606
|
+
descriptors.push([key, descriptor]);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
catch {
|
|
1610
|
+
return value;
|
|
1611
|
+
}
|
|
1612
|
+
const output = isArray
|
|
1613
|
+
? []
|
|
1614
|
+
: Object.create(prototype);
|
|
1615
|
+
seen.set(value, output);
|
|
1616
|
+
let arrayLength;
|
|
1617
|
+
for (const [key, descriptor] of descriptors) {
|
|
1618
|
+
if (isArray && key === 'length') {
|
|
1619
|
+
arrayLength = descriptor;
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
const nextDescriptor = Object.prototype.hasOwnProperty.call(descriptor, 'value')
|
|
1623
|
+
? {
|
|
1624
|
+
...descriptor,
|
|
1625
|
+
value: resolveA2UIDeepInternal(descriptor.value, root, scopePath, seen),
|
|
1626
|
+
}
|
|
1627
|
+
: descriptor;
|
|
1628
|
+
Object.defineProperty(output, key, nextDescriptor);
|
|
1629
|
+
}
|
|
1630
|
+
if (isArray && arrayLength) {
|
|
1631
|
+
Object.defineProperty(output, 'length', {
|
|
1632
|
+
value: arrayLength.value,
|
|
1633
|
+
writable: arrayLength.writable,
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
return output;
|
|
1637
|
+
}
|
|
1638
|
+
/** Create an ActionRunner-compatible resolver bound to one item scope. */
|
|
1639
|
+
function createA2UIParameterResolver(root, scopePath) {
|
|
1640
|
+
return params => resolveA2UIDeep(params, root, scopePath);
|
|
1641
|
+
}
|
|
1642
|
+
const A2UI_CHILD_BINDING = Symbol('copilot-cards.a2ui-child-binding');
|
|
1643
|
+
/** Attach normalized dynamic-child metadata without widening native JSON. */
|
|
1644
|
+
function setA2UIChildBinding(slot, value) {
|
|
1645
|
+
const binding = readDynamicChildList(value);
|
|
1646
|
+
if (!binding) {
|
|
1647
|
+
throw new TypeError('[A2UI] Invalid dynamic ChildList binding');
|
|
1648
|
+
}
|
|
1649
|
+
slot[A2UI_CHILD_BINDING] = {
|
|
1650
|
+
dialect: 'a2ui',
|
|
1651
|
+
path: binding.path,
|
|
1652
|
+
templateId: binding.componentId,
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
/** Read adapter-owned dynamic-child metadata from a slot. */
|
|
1656
|
+
function getA2UIChildBinding(slot) {
|
|
1657
|
+
if (!((typeof slot === 'object' && slot !== null)
|
|
1658
|
+
|| typeof slot === 'function')) {
|
|
1659
|
+
return undefined;
|
|
1660
|
+
}
|
|
1661
|
+
try {
|
|
1662
|
+
const descriptor = Object.getOwnPropertyDescriptor(slot, A2UI_CHILD_BINDING);
|
|
1663
|
+
if (!descriptor
|
|
1664
|
+
|| !Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
1665
|
+
return undefined;
|
|
1666
|
+
}
|
|
1667
|
+
return descriptor.value;
|
|
1668
|
+
}
|
|
1669
|
+
catch {
|
|
1670
|
+
return undefined;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
const A2UI_BINDING_DIALECT = Symbol('copilot-cards.a2ui-binding-dialect');
|
|
1674
|
+
/** Mark an adapter-created element as using A2UI path semantics. */
|
|
1675
|
+
function markA2UIBindingDialect(element) {
|
|
1676
|
+
element[A2UI_BINDING_DIALECT] = true;
|
|
1677
|
+
}
|
|
1678
|
+
/** Whether this exact element owns the private adapter dialect marker. */
|
|
1679
|
+
function hasA2UIBindingDialect(element) {
|
|
1680
|
+
try {
|
|
1681
|
+
const descriptor = Object.getOwnPropertyDescriptor(element, A2UI_BINDING_DIALECT);
|
|
1682
|
+
return !!descriptor
|
|
1683
|
+
&& Object.prototype.hasOwnProperty.call(descriptor, 'value')
|
|
1684
|
+
&& descriptor.value === true;
|
|
1685
|
+
}
|
|
1686
|
+
catch {
|
|
1687
|
+
return false;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1103
1691
|
/**
|
|
1104
1692
|
* Schema Parser — resolves a CardSchema into a renderable tree.
|
|
1105
1693
|
*
|
|
@@ -1119,6 +1707,85 @@ function normalizeSchema(input) {
|
|
|
1119
1707
|
}
|
|
1120
1708
|
return input;
|
|
1121
1709
|
}
|
|
1710
|
+
/** Whether a schema contains native repeat or adapter-owned dynamic children. */
|
|
1711
|
+
function hasDynamicChildren(input) {
|
|
1712
|
+
const schema = normalizeSchema(input);
|
|
1713
|
+
return Object.values(schema.elements).some(element => elementSlotEntries(element).entries.some(([, slot]) => hasOwn(slot, 'repeat') || getA2UIChildBinding(slot) !== undefined));
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Whether a renderer must use the scoped binding/materialization path.
|
|
1717
|
+
*
|
|
1718
|
+
* Literal native/static schemas deliberately remain on the legacy parser path.
|
|
1719
|
+
* The private A2UI marker cannot be forged by JSON input.
|
|
1720
|
+
*/
|
|
1721
|
+
function requiresBindingMaterialization(input) {
|
|
1722
|
+
const schema = normalizeSchema(input);
|
|
1723
|
+
return hasDynamicChildren(schema)
|
|
1724
|
+
|| Object.values(schema.elements).some(hasA2UIBindingDialect);
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Stable fingerprint of the definition graph that controls binding topology.
|
|
1728
|
+
* Runtime data and materialized occurrence counts are intentionally excluded.
|
|
1729
|
+
*/
|
|
1730
|
+
function bindingTopologyFingerprint(input) {
|
|
1731
|
+
const schema = normalizeSchema(input);
|
|
1732
|
+
const topology = Object.keys(schema.elements)
|
|
1733
|
+
.sort()
|
|
1734
|
+
.map(id => {
|
|
1735
|
+
const element = schema.elements[id];
|
|
1736
|
+
const slotEntries = elementSlotEntries(element);
|
|
1737
|
+
const slots = slotEntries.entries
|
|
1738
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1739
|
+
.map(([slotName, slot]) => {
|
|
1740
|
+
const a2ui = getA2UIChildBinding(slot);
|
|
1741
|
+
const config = readOwnData(slot, 'config');
|
|
1742
|
+
const overlays = config.kind === 'value'
|
|
1743
|
+
? readOwnData(config.value, 'overlays')
|
|
1744
|
+
: config;
|
|
1745
|
+
const repeat = readOwnData(slot, 'repeat');
|
|
1746
|
+
const repeatFields = repeat.kind === 'value'
|
|
1747
|
+
&& isPlainObject(repeat.value)
|
|
1748
|
+
? {
|
|
1749
|
+
source: topologyValue(readOwnData(repeat.value, 'source')),
|
|
1750
|
+
template: topologyValue(readOwnData(repeat.value, 'template')),
|
|
1751
|
+
item: topologyValue(readOwnData(repeat.value, 'item')),
|
|
1752
|
+
index: topologyValue(readOwnData(repeat.value, 'index')),
|
|
1753
|
+
}
|
|
1754
|
+
: topologyValue(repeat);
|
|
1755
|
+
return {
|
|
1756
|
+
name: slotName,
|
|
1757
|
+
children: topologyValue(readOwnData(slot, 'children')),
|
|
1758
|
+
groups: topologyValue(readOwnData(slot, 'groups')),
|
|
1759
|
+
overlays: topologyValue(overlays),
|
|
1760
|
+
repeat: hasOwn(slot, 'repeat')
|
|
1761
|
+
? repeatFields
|
|
1762
|
+
: null,
|
|
1763
|
+
a2ui: a2ui
|
|
1764
|
+
? {
|
|
1765
|
+
path: a2ui.path,
|
|
1766
|
+
templateId: a2ui.templateId,
|
|
1767
|
+
}
|
|
1768
|
+
: null,
|
|
1769
|
+
};
|
|
1770
|
+
});
|
|
1771
|
+
for (const slotName of slotEntries.accessors.sort()) {
|
|
1772
|
+
slots.push({ name: slotName, opaque: '<accessor>' });
|
|
1773
|
+
}
|
|
1774
|
+
if (slotEntries.opaque) {
|
|
1775
|
+
slots.push({ name: '<opaque-slots>', opaque: '<opaque>' });
|
|
1776
|
+
}
|
|
1777
|
+
return {
|
|
1778
|
+
id,
|
|
1779
|
+
type: element.type,
|
|
1780
|
+
a2ui: hasA2UIBindingDialect(element),
|
|
1781
|
+
slots,
|
|
1782
|
+
};
|
|
1783
|
+
});
|
|
1784
|
+
return JSON.stringify({
|
|
1785
|
+
rootID: schema.rootID,
|
|
1786
|
+
topology,
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1122
1789
|
// ─── Parser ──────────────────────────────────────────────────────
|
|
1123
1790
|
/**
|
|
1124
1791
|
* Parse a CardSchema into a nested RenderTreeNode starting from `rootID`.
|
|
@@ -1155,74 +1822,1230 @@ function parseSchema(input) {
|
|
|
1155
1822
|
}
|
|
1156
1823
|
}
|
|
1157
1824
|
}
|
|
1158
|
-
if (slot.config?.overlays) {
|
|
1159
|
-
for (const overlay of slot.config.overlays) {
|
|
1160
|
-
if (overlay.children) {
|
|
1161
|
-
for (const childId of overlay.children) {
|
|
1162
|
-
children.push(buildNode(childId));
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1825
|
+
if (slot.config?.overlays) {
|
|
1826
|
+
for (const overlay of slot.config.overlays) {
|
|
1827
|
+
if (overlay.children) {
|
|
1828
|
+
for (const childId of overlay.children) {
|
|
1829
|
+
children.push(buildNode(childId));
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
visited.delete(id); // allow same element across different branches
|
|
1837
|
+
return {
|
|
1838
|
+
id,
|
|
1839
|
+
type: element.type,
|
|
1840
|
+
props: element.props,
|
|
1841
|
+
children,
|
|
1842
|
+
lifecycle: element.lifecycle,
|
|
1843
|
+
events: element.events,
|
|
1844
|
+
directives: element.directives,
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
return buildNode(rootID);
|
|
1848
|
+
}
|
|
1849
|
+
// ─── Validation ──────────────────────────────────────────────────
|
|
1850
|
+
/**
|
|
1851
|
+
* Validate a CardSchema and return any error messages.
|
|
1852
|
+
*/
|
|
1853
|
+
function validateSchema(input) {
|
|
1854
|
+
const schema = normalizeSchema(input);
|
|
1855
|
+
const errors = [];
|
|
1856
|
+
if (!schema.version) {
|
|
1857
|
+
errors.push('Missing "version" field');
|
|
1858
|
+
}
|
|
1859
|
+
if (typeof schema.rootID !== 'string') {
|
|
1860
|
+
errors.push('"rootID" field must be a string');
|
|
1861
|
+
}
|
|
1862
|
+
else if (!schema.rootID) {
|
|
1863
|
+
errors.push('Missing "rootID" field');
|
|
1864
|
+
}
|
|
1865
|
+
else if (!schema.elements[schema.rootID]) {
|
|
1866
|
+
errors.push(`Root element "${schema.rootID}" not found in elements`);
|
|
1867
|
+
}
|
|
1868
|
+
const allIds = new Set(Object.keys(schema.elements));
|
|
1869
|
+
for (const [id, element] of Object.entries(schema.elements)) {
|
|
1870
|
+
if (!element.type) {
|
|
1871
|
+
errors.push(`Element "${id}" is missing a "type" field`);
|
|
1872
|
+
}
|
|
1873
|
+
// Validate slot children and groups references without invoking accessors.
|
|
1874
|
+
const slots = elementSlotEntries(element);
|
|
1875
|
+
for (const slotName of slots.accessors) {
|
|
1876
|
+
errors.push(`Element "${id}" slot "${slotName}" must be an own data property`);
|
|
1877
|
+
}
|
|
1878
|
+
if (slots.opaque) {
|
|
1879
|
+
errors.push(`Element "${id}" slots could not be inspected safely`);
|
|
1880
|
+
}
|
|
1881
|
+
for (const [slotName, slot] of slots.entries) {
|
|
1882
|
+
if (!slot || typeof slot !== 'object') {
|
|
1883
|
+
errors.push(`Element "${id}" slot "${slotName}" must be an object`);
|
|
1884
|
+
continue;
|
|
1885
|
+
}
|
|
1886
|
+
const children = readOwnData(slot, 'children');
|
|
1887
|
+
if (children.kind === 'value') {
|
|
1888
|
+
const values = ownArrayDataValues(children.value);
|
|
1889
|
+
if (!values) {
|
|
1890
|
+
errors.push(`Element "${id}" slot "${slotName}" children must be an array`);
|
|
1891
|
+
}
|
|
1892
|
+
else {
|
|
1893
|
+
for (const childId of values) {
|
|
1894
|
+
if (typeof childId !== 'string' || childId.length === 0) {
|
|
1895
|
+
errors.push(`Element "${id}" slot "${slotName}" child IDs must be non-empty strings; received "${String(childId)}"`);
|
|
1896
|
+
}
|
|
1897
|
+
else if (!allIds.has(childId)) {
|
|
1898
|
+
errors.push(`Element "${id}" slot "${slotName}" references unknown child "${String(childId)}"`);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
else if (children.kind === 'accessor' || children.kind === 'opaque') {
|
|
1904
|
+
errors.push(`Element "${id}" slot "${slotName}" children must be an own data property`);
|
|
1905
|
+
}
|
|
1906
|
+
const groups = readOwnData(slot, 'groups');
|
|
1907
|
+
if (groups.kind === 'value') {
|
|
1908
|
+
const groupValues = ownArrayDataValues(groups.value);
|
|
1909
|
+
if (!groupValues) {
|
|
1910
|
+
errors.push(`Element "${id}" slot "${slotName}" groups must be an array`);
|
|
1911
|
+
}
|
|
1912
|
+
else {
|
|
1913
|
+
for (const group of groupValues) {
|
|
1914
|
+
const childrenInGroup = ownArrayDataValues(group);
|
|
1915
|
+
if (!childrenInGroup) {
|
|
1916
|
+
errors.push(`Element "${id}" slot "${slotName}" groups must contain arrays`);
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
for (const childId of childrenInGroup) {
|
|
1920
|
+
if (typeof childId !== 'string' || childId.length === 0) {
|
|
1921
|
+
errors.push(`Element "${id}" slot "${slotName}" group child IDs must be non-empty strings; received "${String(childId)}"`);
|
|
1922
|
+
}
|
|
1923
|
+
else if (!allIds.has(childId)) {
|
|
1924
|
+
errors.push(`Element "${id}" slot "${slotName}" group references unknown child "${String(childId)}"`);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
else if (groups.kind === 'accessor' || groups.kind === 'opaque') {
|
|
1931
|
+
errors.push(`Element "${id}" slot "${slotName}" groups must be an own data property`);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
validateRepeatBindings(schema, errors);
|
|
1936
|
+
return errors;
|
|
1937
|
+
}
|
|
1938
|
+
const REPEAT_SLOT_LAYOUTS = new Set([
|
|
1939
|
+
'default',
|
|
1940
|
+
'list',
|
|
1941
|
+
'grid',
|
|
1942
|
+
'horizontalScroll',
|
|
1943
|
+
'carousel',
|
|
1944
|
+
]);
|
|
1945
|
+
const ALIAS_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
1946
|
+
const RESERVED_ALIASES = new Set(['__proto__', 'constructor', 'prototype']);
|
|
1947
|
+
function hasOwn(value, key) {
|
|
1948
|
+
try {
|
|
1949
|
+
return ((typeof value === 'object' && value !== null) ||
|
|
1950
|
+
typeof value === 'function') && Object.prototype.hasOwnProperty.call(value, key);
|
|
1951
|
+
}
|
|
1952
|
+
catch {
|
|
1953
|
+
return false;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
function isPlainObject(value) {
|
|
1957
|
+
try {
|
|
1958
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
1959
|
+
return false;
|
|
1960
|
+
}
|
|
1961
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1962
|
+
return prototype === Object.prototype || prototype === null;
|
|
1963
|
+
}
|
|
1964
|
+
catch {
|
|
1965
|
+
return false;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
function readOwnData(value, key) {
|
|
1969
|
+
if (!((typeof value === 'object' && value !== null)
|
|
1970
|
+
|| typeof value === 'function')) {
|
|
1971
|
+
return { kind: 'missing' };
|
|
1972
|
+
}
|
|
1973
|
+
try {
|
|
1974
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1975
|
+
if (!descriptor)
|
|
1976
|
+
return { kind: 'missing' };
|
|
1977
|
+
if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
1978
|
+
return { kind: 'accessor' };
|
|
1979
|
+
}
|
|
1980
|
+
return { kind: 'value', value: descriptor.value };
|
|
1981
|
+
}
|
|
1982
|
+
catch {
|
|
1983
|
+
return { kind: 'opaque' };
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
function ownStringDataEntries(value) {
|
|
1987
|
+
const result = {
|
|
1988
|
+
entries: [],
|
|
1989
|
+
accessors: [],
|
|
1990
|
+
opaque: false,
|
|
1991
|
+
};
|
|
1992
|
+
if (!value || typeof value !== 'object')
|
|
1993
|
+
return result;
|
|
1994
|
+
try {
|
|
1995
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
1996
|
+
if (typeof key !== 'string')
|
|
1997
|
+
continue;
|
|
1998
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1999
|
+
if (!descriptor) {
|
|
2000
|
+
result.opaque = true;
|
|
2001
|
+
}
|
|
2002
|
+
else if (Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
2003
|
+
result.entries.push([key, descriptor.value]);
|
|
2004
|
+
}
|
|
2005
|
+
else {
|
|
2006
|
+
result.accessors.push(key);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
catch {
|
|
2011
|
+
result.opaque = true;
|
|
2012
|
+
}
|
|
2013
|
+
return result;
|
|
2014
|
+
}
|
|
2015
|
+
function ownArrayDataValues(value) {
|
|
2016
|
+
try {
|
|
2017
|
+
if (!Array.isArray(value))
|
|
2018
|
+
return undefined;
|
|
2019
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
|
|
2020
|
+
const length = lengthDescriptor?.value;
|
|
2021
|
+
if (!Number.isSafeInteger(length) || length < 0)
|
|
2022
|
+
return undefined;
|
|
2023
|
+
const output = [];
|
|
2024
|
+
for (let index = 0; index < length; index += 1) {
|
|
2025
|
+
const entry = readOwnData(value, String(index));
|
|
2026
|
+
if (entry.kind !== 'value')
|
|
2027
|
+
return undefined;
|
|
2028
|
+
output.push(entry.value);
|
|
2029
|
+
}
|
|
2030
|
+
return output;
|
|
2031
|
+
}
|
|
2032
|
+
catch {
|
|
2033
|
+
return undefined;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
function elementSlotEntries(element) {
|
|
2037
|
+
const slots = readOwnData(element.props, 'slots');
|
|
2038
|
+
return slots.kind === 'value'
|
|
2039
|
+
? ownStringDataEntries(slots.value)
|
|
2040
|
+
: {
|
|
2041
|
+
entries: [],
|
|
2042
|
+
accessors: slots.kind === 'accessor' ? ['slots'] : [],
|
|
2043
|
+
opaque: slots.kind === 'opaque',
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
function readRepeatBindingFields(value) {
|
|
2047
|
+
if (!isPlainObject(value))
|
|
2048
|
+
return undefined;
|
|
2049
|
+
const source = readOwnData(value, 'source');
|
|
2050
|
+
const template = readOwnData(value, 'template');
|
|
2051
|
+
const item = readOwnData(value, 'item');
|
|
2052
|
+
const index = readOwnData(value, 'index');
|
|
2053
|
+
return {
|
|
2054
|
+
source: source.kind === 'value' ? source.value : undefined,
|
|
2055
|
+
template: template.kind === 'value' ? template.value : undefined,
|
|
2056
|
+
item: item.kind === 'value' ? item.value : undefined,
|
|
2057
|
+
...(index.kind === 'value' ? { index: index.value } : {}),
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
function slotStaticReferences(slot) {
|
|
2061
|
+
const references = [];
|
|
2062
|
+
const children = readOwnData(slot, 'children');
|
|
2063
|
+
if (children.kind === 'value') {
|
|
2064
|
+
for (const child of ownArrayDataValues(children.value) ?? []) {
|
|
2065
|
+
if (typeof child === 'string')
|
|
2066
|
+
references.push(child);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
const groups = readOwnData(slot, 'groups');
|
|
2070
|
+
if (groups.kind === 'value') {
|
|
2071
|
+
for (const group of ownArrayDataValues(groups.value) ?? []) {
|
|
2072
|
+
for (const child of ownArrayDataValues(group) ?? []) {
|
|
2073
|
+
if (typeof child === 'string')
|
|
2074
|
+
references.push(child);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
const config = readOwnData(slot, 'config');
|
|
2079
|
+
const overlays = config.kind === 'value'
|
|
2080
|
+
? readOwnData(config.value, 'overlays')
|
|
2081
|
+
: { kind: 'missing' };
|
|
2082
|
+
if (overlays.kind === 'value') {
|
|
2083
|
+
for (const overlay of ownArrayDataValues(overlays.value) ?? []) {
|
|
2084
|
+
const overlayChildren = readOwnData(overlay, 'children');
|
|
2085
|
+
if (overlayChildren.kind !== 'value')
|
|
2086
|
+
continue;
|
|
2087
|
+
for (const child of ownArrayDataValues(overlayChildren.value) ?? []) {
|
|
2088
|
+
if (typeof child === 'string')
|
|
2089
|
+
references.push(child);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
return references;
|
|
2094
|
+
}
|
|
2095
|
+
function topologyValue(read, seen = new WeakSet()) {
|
|
2096
|
+
if (read.kind !== 'value') {
|
|
2097
|
+
if (read.kind === 'missing')
|
|
2098
|
+
return null;
|
|
2099
|
+
if (read.kind === 'accessor')
|
|
2100
|
+
return '<accessor>';
|
|
2101
|
+
return '<opaque>';
|
|
2102
|
+
}
|
|
2103
|
+
const value = read.value;
|
|
2104
|
+
if (!value || typeof value !== 'object')
|
|
2105
|
+
return value;
|
|
2106
|
+
if (seen.has(value))
|
|
2107
|
+
return '<cycle>';
|
|
2108
|
+
seen.add(value);
|
|
2109
|
+
const array = ownArrayDataValues(value);
|
|
2110
|
+
if (array) {
|
|
2111
|
+
return array.map(entry => topologyValue({ kind: 'value', value: entry }, seen));
|
|
2112
|
+
}
|
|
2113
|
+
if (!isPlainObject(value))
|
|
2114
|
+
return '<opaque>';
|
|
2115
|
+
const entries = ownStringDataEntries(value);
|
|
2116
|
+
if (entries.opaque)
|
|
2117
|
+
return '<opaque>';
|
|
2118
|
+
const output = {};
|
|
2119
|
+
for (const key of entries.accessors.sort())
|
|
2120
|
+
output[key] = '<accessor>';
|
|
2121
|
+
for (const [key, entry] of entries.entries.sort(([left], [right]) => left.localeCompare(right))) {
|
|
2122
|
+
output[key] = topologyValue({ kind: 'value', value: entry }, seen);
|
|
2123
|
+
}
|
|
2124
|
+
return output;
|
|
2125
|
+
}
|
|
2126
|
+
function getOwnElement(schema, id) {
|
|
2127
|
+
const element = readOwnData(schema.elements, id);
|
|
2128
|
+
return element.kind === 'value'
|
|
2129
|
+
? element.value
|
|
2130
|
+
: undefined;
|
|
2131
|
+
}
|
|
2132
|
+
function isCompleteExpression(source) {
|
|
2133
|
+
if (typeof source !== 'string')
|
|
2134
|
+
return false;
|
|
2135
|
+
const trimmed = source.trim();
|
|
2136
|
+
if (!trimmed.startsWith('${'))
|
|
2137
|
+
return false;
|
|
2138
|
+
if (!trimmed.slice(2, -1).trim())
|
|
2139
|
+
return false;
|
|
2140
|
+
let depth = 1;
|
|
2141
|
+
let quote;
|
|
2142
|
+
let escaped = false;
|
|
2143
|
+
for (let index = 2; index < trimmed.length; index += 1) {
|
|
2144
|
+
const character = trimmed[index];
|
|
2145
|
+
if (quote) {
|
|
2146
|
+
if (escaped) {
|
|
2147
|
+
escaped = false;
|
|
2148
|
+
}
|
|
2149
|
+
else if (character === '\\') {
|
|
2150
|
+
escaped = true;
|
|
2151
|
+
}
|
|
2152
|
+
else if (character === quote) {
|
|
2153
|
+
quote = undefined;
|
|
2154
|
+
}
|
|
2155
|
+
continue;
|
|
2156
|
+
}
|
|
2157
|
+
if (character === "'" || character === '"' || character === '`') {
|
|
2158
|
+
quote = character;
|
|
2159
|
+
}
|
|
2160
|
+
else if (character === '{') {
|
|
2161
|
+
depth += 1;
|
|
2162
|
+
}
|
|
2163
|
+
else if (character === '}') {
|
|
2164
|
+
depth -= 1;
|
|
2165
|
+
if (depth === 0)
|
|
2166
|
+
return index === trimmed.length - 1;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return false;
|
|
2170
|
+
}
|
|
2171
|
+
function elementReferences(element) {
|
|
2172
|
+
const references = [];
|
|
2173
|
+
for (const [, slot] of elementSlotEntries(element).entries) {
|
|
2174
|
+
references.push(...slotStaticReferences(slot));
|
|
2175
|
+
const repeat = readOwnData(slot, 'repeat');
|
|
2176
|
+
const binding = repeat.kind === 'value'
|
|
2177
|
+
? readRepeatBindingFields(repeat.value)
|
|
2178
|
+
: undefined;
|
|
2179
|
+
if (typeof binding?.template === 'string' && binding.template) {
|
|
2180
|
+
references.push(binding.template);
|
|
2181
|
+
}
|
|
2182
|
+
const a2ui = getA2UIChildBinding(slot);
|
|
2183
|
+
if (typeof a2ui?.templateId === 'string' && a2ui.templateId) {
|
|
2184
|
+
references.push(a2ui.templateId);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return references;
|
|
2188
|
+
}
|
|
2189
|
+
function validateRepeatBindings(schema, errors) {
|
|
2190
|
+
const dynamicBindings = [];
|
|
2191
|
+
for (const [id, element] of Object.entries(schema.elements)) {
|
|
2192
|
+
const elementRepeats = elementSlotEntries(element).entries
|
|
2193
|
+
.filter((entry) => hasOwn(entry[1], 'repeat')
|
|
2194
|
+
|| getA2UIChildBinding(entry[1]) !== undefined);
|
|
2195
|
+
if (elementRepeats.length > 1) {
|
|
2196
|
+
errors.push(`Element "${id}" has more than one dynamic slot`);
|
|
2197
|
+
}
|
|
2198
|
+
for (const [slotName, slot] of elementRepeats) {
|
|
2199
|
+
const a2ui = getA2UIChildBinding(slot);
|
|
2200
|
+
const repeat = readOwnData(slot, 'repeat');
|
|
2201
|
+
if (repeat.kind === 'missing' && a2ui) {
|
|
2202
|
+
dynamicBindings.push({
|
|
2203
|
+
owner: id,
|
|
2204
|
+
slotName,
|
|
2205
|
+
dialect: 'a2ui',
|
|
2206
|
+
template: a2ui.templateId,
|
|
2207
|
+
});
|
|
2208
|
+
if (typeof a2ui.templateId !== 'string'
|
|
2209
|
+
|| a2ui.templateId.length === 0) {
|
|
2210
|
+
errors.push(`Element "${id}" slot "${slotName}" dynamic children template must be a non-empty string`);
|
|
2211
|
+
}
|
|
2212
|
+
else if (!getOwnElement(schema, a2ui.templateId)) {
|
|
2213
|
+
errors.push(`Element "${id}" slot "${slotName}" dynamic children reference unknown template "${a2ui.templateId}"`);
|
|
2214
|
+
}
|
|
2215
|
+
continue;
|
|
2216
|
+
}
|
|
2217
|
+
const rawBinding = repeat.kind === 'value'
|
|
2218
|
+
? repeat.value
|
|
2219
|
+
: undefined;
|
|
2220
|
+
if (!isPlainObject(rawBinding)) {
|
|
2221
|
+
errors.push(`Element "${id}" slot "${slotName}" repeat must be a non-null plain object`);
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
const binding = readRepeatBindingFields(rawBinding);
|
|
2225
|
+
dynamicBindings.push({
|
|
2226
|
+
owner: id,
|
|
2227
|
+
slotName,
|
|
2228
|
+
dialect: 'native',
|
|
2229
|
+
template: binding.template,
|
|
2230
|
+
binding,
|
|
2231
|
+
});
|
|
2232
|
+
if (!REPEAT_SLOT_LAYOUTS.has(slotName)) {
|
|
2233
|
+
errors.push(`Element "${id}" slot "${slotName}" does not support repeat`);
|
|
2234
|
+
}
|
|
2235
|
+
const children = readOwnData(slot, 'children');
|
|
2236
|
+
const groups = readOwnData(slot, 'groups');
|
|
2237
|
+
if ((children.kind === 'value' && !!children.value)
|
|
2238
|
+
|| children.kind === 'accessor'
|
|
2239
|
+
|| children.kind === 'opaque'
|
|
2240
|
+
|| (groups.kind === 'value' && !!groups.value)
|
|
2241
|
+
|| groups.kind === 'accessor'
|
|
2242
|
+
|| groups.kind === 'opaque') {
|
|
2243
|
+
errors.push(`Element "${id}" slot "${slotName}" cannot combine repeat with children or groups`);
|
|
2244
|
+
}
|
|
2245
|
+
if (typeof binding.source !== 'string') {
|
|
2246
|
+
errors.push(`Element "${id}" slot "${slotName}" repeat source must be a string`);
|
|
2247
|
+
}
|
|
2248
|
+
else if (!isCompleteExpression(binding.source)) {
|
|
2249
|
+
errors.push(`Element "${id}" slot "${slotName}" repeat source must be one complete non-empty \${...} expression`);
|
|
2250
|
+
}
|
|
2251
|
+
if (typeof binding.template !== 'string') {
|
|
2252
|
+
errors.push(`Element "${id}" slot "${slotName}" repeat template must be a string`);
|
|
2253
|
+
}
|
|
2254
|
+
else if (!binding.template) {
|
|
2255
|
+
errors.push(`Element "${id}" slot "${slotName}" repeat template must be a non-empty string`);
|
|
2256
|
+
}
|
|
2257
|
+
else if (!getOwnElement(schema, binding.template)) {
|
|
2258
|
+
errors.push(`Element "${id}" slot "${slotName}" repeat references unknown template "${binding.template}"`);
|
|
2259
|
+
}
|
|
2260
|
+
validateAlias(id, slotName, 'item', binding.item, errors);
|
|
2261
|
+
if (binding.index !== undefined) {
|
|
2262
|
+
validateAlias(id, slotName, 'index', binding.index, errors);
|
|
2263
|
+
}
|
|
2264
|
+
if (binding.index !== undefined && binding.item === binding.index) {
|
|
2265
|
+
errors.push(`Element "${id}" slot "${slotName}" repeat item and index aliases must be different`);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
validateRepeatNesting(schema, dynamicBindings, errors);
|
|
2270
|
+
validateDynamicClosures(schema, dynamicBindings, errors);
|
|
2271
|
+
}
|
|
2272
|
+
function validateAlias(owner, slotName, kind, alias, errors) {
|
|
2273
|
+
if (typeof alias !== 'string') {
|
|
2274
|
+
errors.push(`Element "${owner}" slot "${slotName}" repeat ${kind} must be a string`);
|
|
2275
|
+
}
|
|
2276
|
+
else if (!ALIAS_PATTERN.test(alias)) {
|
|
2277
|
+
errors.push(`Element "${owner}" slot "${slotName}" repeat ${kind} alias "${alias}" is invalid`);
|
|
2278
|
+
}
|
|
2279
|
+
else if (RESERVED_ALIASES.has(alias)) {
|
|
2280
|
+
errors.push(`Element "${owner}" slot "${slotName}" repeat ${kind} alias "${alias}" is reserved`);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
function validateRepeatNesting(schema, bindings, errors) {
|
|
2284
|
+
const reportedCycles = new Set();
|
|
2285
|
+
const reportedCollisions = new Set();
|
|
2286
|
+
function visitElement(id, activeAliases, repeatPath, visitedStatic) {
|
|
2287
|
+
const element = getOwnElement(schema, id);
|
|
2288
|
+
if (!element || visitedStatic.has(id))
|
|
2289
|
+
return;
|
|
2290
|
+
const nextVisitedStatic = new Set(visitedStatic).add(id);
|
|
2291
|
+
for (const [slotName, slotValue] of elementSlotEntries(element).entries) {
|
|
2292
|
+
if (!slotValue || typeof slotValue !== 'object')
|
|
2293
|
+
continue;
|
|
2294
|
+
const slot = slotValue;
|
|
2295
|
+
const repeat = readOwnData(slot, 'repeat');
|
|
2296
|
+
const nativeBinding = repeat.kind === 'value'
|
|
2297
|
+
? readRepeatBindingFields(repeat.value)
|
|
2298
|
+
: undefined;
|
|
2299
|
+
const a2uiBinding = getA2UIChildBinding(slot);
|
|
2300
|
+
const template = nativeBinding?.template ?? a2uiBinding?.templateId;
|
|
2301
|
+
if (typeof template === 'string' && template) {
|
|
2302
|
+
const cycleAt = repeatPath.indexOf(template);
|
|
2303
|
+
if (cycleAt >= 0) {
|
|
2304
|
+
const cycle = [...repeatPath.slice(cycleAt), template].join(' -> ');
|
|
2305
|
+
if (!reportedCycles.has(cycle)) {
|
|
2306
|
+
reportedCycles.add(cycle);
|
|
2307
|
+
errors.push(nativeBinding
|
|
2308
|
+
? `Element "${id}" slot "${slotName}" has repeat template cycle: ${cycle}`
|
|
2309
|
+
: `Element "${id}" slot "${slotName}" has dynamic template cycle: ${cycle}`);
|
|
2310
|
+
}
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
const aliases = [nativeBinding?.item, nativeBinding?.index].filter((alias) => typeof alias === 'string');
|
|
2314
|
+
for (const alias of aliases) {
|
|
2315
|
+
if (activeAliases.has(alias)) {
|
|
2316
|
+
const key = `${id}:${slotName}:${alias}`;
|
|
2317
|
+
if (!reportedCollisions.has(key)) {
|
|
2318
|
+
reportedCollisions.add(key);
|
|
2319
|
+
errors.push(`Element "${id}" slot "${slotName}" repeat alias "${alias}" conflicts with an active repeat alias`);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
const nestedAliases = new Set(activeAliases);
|
|
2324
|
+
for (const alias of aliases)
|
|
2325
|
+
nestedAliases.add(alias);
|
|
2326
|
+
visitElement(template, nestedAliases, [...repeatPath, template], new Set());
|
|
2327
|
+
}
|
|
2328
|
+
for (const child of slotStaticReferences(slot)) {
|
|
2329
|
+
visitElement(child, activeAliases, repeatPath, nextVisitedStatic);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
for (const dynamic of bindings) {
|
|
2334
|
+
const { owner, binding, template } = dynamic;
|
|
2335
|
+
const aliases = new Set([binding?.item, binding?.index].filter((alias) => typeof alias === 'string'));
|
|
2336
|
+
if (typeof template === 'string' && template) {
|
|
2337
|
+
visitElement(template, aliases, [owner, template], new Set());
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
function validateDynamicClosures(schema, repeats, errors) {
|
|
2342
|
+
for (const { owner } of repeats) {
|
|
2343
|
+
const visited = new Set();
|
|
2344
|
+
function visit(id) {
|
|
2345
|
+
if (visited.has(id))
|
|
2346
|
+
return;
|
|
2347
|
+
visited.add(id);
|
|
2348
|
+
const element = getOwnElement(schema, id);
|
|
2349
|
+
if (!element)
|
|
2350
|
+
return;
|
|
2351
|
+
const lifecycle = readOwnData(element, 'lifecycle');
|
|
2352
|
+
if ((lifecycle.kind === 'value' && !!lifecycle.value)
|
|
2353
|
+
|| lifecycle.kind === 'accessor'
|
|
2354
|
+
|| lifecycle.kind === 'opaque') {
|
|
2355
|
+
errors.push(`Element "${id}" in dynamic patch closure owned by "${owner}" cannot use lifecycle`);
|
|
2356
|
+
}
|
|
2357
|
+
if (hasOwn(element.props, 'variableKey')) {
|
|
2358
|
+
errors.push(`Element "${id}" in dynamic patch closure owned by "${owner}" cannot use props.variableKey`);
|
|
2359
|
+
}
|
|
2360
|
+
for (const reference of elementReferences(element))
|
|
2361
|
+
visit(reference);
|
|
2362
|
+
}
|
|
2363
|
+
visit(owner);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
/** Build a live expression object whose own properties are the current aliases. */
|
|
2368
|
+
function createExpressionContext(scope) {
|
|
2369
|
+
const prototype = scope.parent
|
|
2370
|
+
? createExpressionContext(scope.parent)
|
|
2371
|
+
: scope.root;
|
|
2372
|
+
return Object.assign(Object.create(prototype), scope.locals);
|
|
2373
|
+
}
|
|
2374
|
+
function isBoundRenderTreeNode(value) {
|
|
2375
|
+
if (!value || typeof value !== 'object')
|
|
2376
|
+
return false;
|
|
2377
|
+
const node = value;
|
|
2378
|
+
return (typeof node.id === 'string' &&
|
|
2379
|
+
typeof node.sourceId === 'string' &&
|
|
2380
|
+
typeof node.instancePath === 'string' &&
|
|
2381
|
+
typeof node.dataPath === 'string' &&
|
|
2382
|
+
!!node.scope &&
|
|
2383
|
+
(node.bindingDialect === 'native' || node.bindingDialect === 'a2ui') &&
|
|
2384
|
+
Array.isArray(node.children));
|
|
2385
|
+
}
|
|
2386
|
+
function cloneValue(value, seen = new WeakMap()) {
|
|
2387
|
+
if (!value || typeof value !== 'object')
|
|
2388
|
+
return value;
|
|
2389
|
+
const existing = seen.get(value);
|
|
2390
|
+
if (existing)
|
|
2391
|
+
return existing;
|
|
2392
|
+
let isArray;
|
|
2393
|
+
let prototype;
|
|
2394
|
+
let descriptors;
|
|
2395
|
+
try {
|
|
2396
|
+
isArray = Array.isArray(value);
|
|
2397
|
+
prototype = Object.getPrototypeOf(value);
|
|
2398
|
+
descriptors = [];
|
|
2399
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
2400
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2401
|
+
if (descriptor)
|
|
2402
|
+
descriptors.push([key, descriptor]);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
catch {
|
|
2406
|
+
return value;
|
|
2407
|
+
}
|
|
2408
|
+
const result = isArray ? [] : Object.create(prototype);
|
|
2409
|
+
seen.set(value, result);
|
|
2410
|
+
for (const [key, descriptor] of descriptors) {
|
|
2411
|
+
if (isArray && key === 'length')
|
|
2412
|
+
continue;
|
|
2413
|
+
if (Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
2414
|
+
Object.defineProperty(result, key, {
|
|
2415
|
+
value: cloneValue(descriptor.value, seen),
|
|
2416
|
+
enumerable: descriptor.enumerable,
|
|
2417
|
+
configurable: true,
|
|
2418
|
+
writable: true,
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
else {
|
|
2422
|
+
Object.defineProperty(result, key, descriptor);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
return result;
|
|
2426
|
+
}
|
|
2427
|
+
function escapeRuntimePart(value) {
|
|
2428
|
+
return Array.from(value, character => character.codePointAt(0).toString(16)).join('-');
|
|
2429
|
+
}
|
|
2430
|
+
function appendOccurrence(outerPath, kind, ownerId, slot, position) {
|
|
2431
|
+
const segments = [
|
|
2432
|
+
kind === 'item' ? 'i' : 'c',
|
|
2433
|
+
ownerId,
|
|
2434
|
+
slot,
|
|
2435
|
+
position,
|
|
2436
|
+
];
|
|
2437
|
+
return outerPath + segments
|
|
2438
|
+
.map(segment => `${segment.length}:${segment}`)
|
|
2439
|
+
.join('');
|
|
2440
|
+
}
|
|
2441
|
+
function runtimeId(sourceId, instancePath) {
|
|
2442
|
+
return `r_${escapeRuntimePart(sourceId)}_${escapeRuntimePart(instancePath)}`;
|
|
2443
|
+
}
|
|
2444
|
+
function pointerSegment(value) {
|
|
2445
|
+
return value.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
2446
|
+
}
|
|
2447
|
+
function joinPointer(base, segments) {
|
|
2448
|
+
return `${base}${segments.map(segment => `/${pointerSegment(segment)}`).join('')}`;
|
|
2449
|
+
}
|
|
2450
|
+
function collectSlotReferences(slot) {
|
|
2451
|
+
const references = [];
|
|
2452
|
+
slot.children?.forEach((sourceId, index) => {
|
|
2453
|
+
references.push({
|
|
2454
|
+
sourceId,
|
|
2455
|
+
position: `children:${index}`,
|
|
2456
|
+
assign: id => { slot.children[index] = id; },
|
|
2457
|
+
});
|
|
2458
|
+
});
|
|
2459
|
+
slot.groups?.forEach((group, groupIndex) => {
|
|
2460
|
+
group.forEach((sourceId, childIndex) => {
|
|
2461
|
+
references.push({
|
|
2462
|
+
sourceId,
|
|
2463
|
+
position: `groups:${groupIndex}:${childIndex}`,
|
|
2464
|
+
assign: id => { slot.groups[groupIndex][childIndex] = id; },
|
|
2465
|
+
});
|
|
2466
|
+
});
|
|
2467
|
+
});
|
|
2468
|
+
const overlays = slot.config?.overlays;
|
|
2469
|
+
if (Array.isArray(overlays)) {
|
|
2470
|
+
overlays.forEach((overlay, overlayIndex) => {
|
|
2471
|
+
if (!Array.isArray(overlay?.children))
|
|
2472
|
+
return;
|
|
2473
|
+
overlay.children.forEach((sourceId, childIndex) => {
|
|
2474
|
+
references.push({
|
|
2475
|
+
sourceId,
|
|
2476
|
+
position: `overlays:${overlayIndex}:${childIndex}`,
|
|
2477
|
+
assign: id => { overlay.children[childIndex] = id; },
|
|
2478
|
+
});
|
|
2479
|
+
});
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
return references;
|
|
2483
|
+
}
|
|
2484
|
+
function isPointerAncestor(ancestor, descendant) {
|
|
2485
|
+
if (ancestor === '')
|
|
2486
|
+
return descendant !== '';
|
|
2487
|
+
return descendant.startsWith(`${ancestor}/`);
|
|
2488
|
+
}
|
|
2489
|
+
function pointersOverlap(left, right) {
|
|
2490
|
+
return (left === right ||
|
|
2491
|
+
isPointerAncestor(left, right) ||
|
|
2492
|
+
isPointerAncestor(right, left));
|
|
2493
|
+
}
|
|
2494
|
+
function ownersInInsertionOrder(card, selectedKeys) {
|
|
2495
|
+
const result = [];
|
|
2496
|
+
for (const [key, owner] of card.repeatOwners) {
|
|
2497
|
+
if (selectedKeys.has(key))
|
|
2498
|
+
result.push(owner);
|
|
2499
|
+
}
|
|
2500
|
+
return result;
|
|
2501
|
+
}
|
|
2502
|
+
function removeOwnersCoveredByAncestors(card, selectedKeys) {
|
|
2503
|
+
const result = new Set(selectedKeys);
|
|
2504
|
+
for (const key of selectedKeys) {
|
|
2505
|
+
let parentKey = card.repeatOwners.get(key)?.parentKey;
|
|
2506
|
+
while (parentKey) {
|
|
2507
|
+
if (selectedKeys.has(parentKey)) {
|
|
2508
|
+
result.delete(key);
|
|
2509
|
+
break;
|
|
2510
|
+
}
|
|
2511
|
+
parentKey = card.repeatOwners.get(parentKey)?.parentKey;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
return result;
|
|
2515
|
+
}
|
|
2516
|
+
/** Select repeat owners affected by a JSON Pointer data update. */
|
|
2517
|
+
function findAffectedRepeatOwners(card, updatePath) {
|
|
2518
|
+
if (card.unresolvedRepeatOwners.size > 0)
|
|
2519
|
+
return [];
|
|
2520
|
+
const selected = new Set();
|
|
2521
|
+
for (const [dependency, owners] of card.valueDependencies) {
|
|
2522
|
+
if (!dependency.startsWith('data:'))
|
|
2523
|
+
continue;
|
|
2524
|
+
const dependencyPath = dependency.slice('data:'.length);
|
|
2525
|
+
if (!pointersOverlap(updatePath, dependencyPath))
|
|
2526
|
+
continue;
|
|
2527
|
+
for (const owner of owners)
|
|
2528
|
+
selected.add(owner);
|
|
2529
|
+
}
|
|
2530
|
+
const exactStructuralOwners = new Set();
|
|
2531
|
+
const containingStructuralOwners = new Set();
|
|
2532
|
+
let nearestStructuralLength = -1;
|
|
2533
|
+
const nearestStructuralOwners = new Set();
|
|
2534
|
+
for (const owner of card.repeatOwners.values()) {
|
|
2535
|
+
const sourcePath = owner.sourcePath;
|
|
2536
|
+
if (sourcePath === undefined)
|
|
2537
|
+
continue;
|
|
2538
|
+
if (updatePath === sourcePath) {
|
|
2539
|
+
exactStructuralOwners.add(owner.key);
|
|
2540
|
+
continue;
|
|
2541
|
+
}
|
|
2542
|
+
if (isPointerAncestor(updatePath, sourcePath)) {
|
|
2543
|
+
containingStructuralOwners.add(owner.key);
|
|
2544
|
+
continue;
|
|
2545
|
+
}
|
|
2546
|
+
if (isPointerAncestor(sourcePath, updatePath)) {
|
|
2547
|
+
if (sourcePath.length > nearestStructuralLength) {
|
|
2548
|
+
nearestStructuralLength = sourcePath.length;
|
|
2549
|
+
nearestStructuralOwners.clear();
|
|
2550
|
+
}
|
|
2551
|
+
if (sourcePath.length === nearestStructuralLength) {
|
|
2552
|
+
nearestStructuralOwners.add(owner.key);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
if (exactStructuralOwners.size > 0) {
|
|
2557
|
+
for (const owner of exactStructuralOwners)
|
|
2558
|
+
selected.add(owner);
|
|
2559
|
+
for (const owner of containingStructuralOwners)
|
|
2560
|
+
selected.add(owner);
|
|
2561
|
+
}
|
|
2562
|
+
else {
|
|
2563
|
+
for (const owner of containingStructuralOwners)
|
|
2564
|
+
selected.add(owner);
|
|
2565
|
+
for (const owner of nearestStructuralOwners)
|
|
2566
|
+
selected.add(owner);
|
|
2567
|
+
}
|
|
2568
|
+
return ownersInInsertionOrder(card, removeOwnersCoveredByAncestors(card, selected));
|
|
2569
|
+
}
|
|
2570
|
+
/** Select repeat owners whose materialized closure contains a source definition. */
|
|
2571
|
+
function findTemplateRepeatOwners(card, sourceIds) {
|
|
2572
|
+
const selected = new Set();
|
|
2573
|
+
for (const sourceId of sourceIds) {
|
|
2574
|
+
const owners = card.dependencies.get(`component:${sourceId}`);
|
|
2575
|
+
if (!owners)
|
|
2576
|
+
continue;
|
|
2577
|
+
for (const owner of owners)
|
|
2578
|
+
selected.add(owner);
|
|
2579
|
+
}
|
|
2580
|
+
const runtimeOwnerCounts = new Map();
|
|
2581
|
+
for (const owner of card.repeatOwners.values()) {
|
|
2582
|
+
runtimeOwnerCounts.set(owner.runtimeOwnerId, (runtimeOwnerCounts.get(owner.runtimeOwnerId) ?? 0) + 1);
|
|
2583
|
+
}
|
|
2584
|
+
for (const key of selected) {
|
|
2585
|
+
const runtimeOwnerId = card.repeatOwners.get(key)?.runtimeOwnerId;
|
|
2586
|
+
if (runtimeOwnerId && (runtimeOwnerCounts.get(runtimeOwnerId) ?? 0) > 1) {
|
|
2587
|
+
return [];
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
return ownersInInsertionOrder(card, removeOwnersCoveredByAncestors(card, selected));
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Normalize a card and recursively turn native repeat declarations into a
|
|
2594
|
+
* renderer-ready tree with ordinary children and occurrence-specific IDs.
|
|
2595
|
+
*/
|
|
2596
|
+
function materializeCard(input, variables, options = {}) {
|
|
2597
|
+
const schema = normalizeSchema(input);
|
|
2598
|
+
const rootVariables = variables ?? schema.variables;
|
|
2599
|
+
const maxDepth = options.maxDepth ?? 3;
|
|
2600
|
+
const maxItems = options.maxItems ?? 100;
|
|
2601
|
+
const instances = new Map();
|
|
2602
|
+
const schemaSourceIds = new Set(Object.keys(schema.elements));
|
|
2603
|
+
const publicIds = new Set(schemaSourceIds);
|
|
2604
|
+
const repeatOwners = new Map();
|
|
2605
|
+
const dependencies = new Map();
|
|
2606
|
+
const valueDependencies = new Map();
|
|
2607
|
+
const unresolvedRepeatOwners = new Set();
|
|
2608
|
+
const scopeAliasPaths = new WeakMap();
|
|
2609
|
+
const scopeIndexAliases = new WeakMap();
|
|
2610
|
+
const runtimeOwnerKeys = new Map();
|
|
2611
|
+
const diagnostics = [];
|
|
2612
|
+
let repeatedItemCount = 0;
|
|
2613
|
+
let hasRepeat = false;
|
|
2614
|
+
const rootScope = {
|
|
2615
|
+
root: rootVariables,
|
|
2616
|
+
locals: {},
|
|
2617
|
+
dataPath: '',
|
|
2618
|
+
bindingDialect: 'native',
|
|
2619
|
+
};
|
|
2620
|
+
scopeAliasPaths.set(rootScope, new Map());
|
|
2621
|
+
scopeIndexAliases.set(rootScope, new Set());
|
|
2622
|
+
function addDependency(type, value, ownerKey) {
|
|
2623
|
+
const key = `${type}:${value}`;
|
|
2624
|
+
const owners = dependencies.get(key);
|
|
2625
|
+
if (owners) {
|
|
2626
|
+
owners.add(ownerKey);
|
|
2627
|
+
}
|
|
2628
|
+
else {
|
|
2629
|
+
dependencies.set(key, new Set([ownerKey]));
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
function addValueDependency(pointer, ownerKey) {
|
|
2633
|
+
addDependency('data', pointer, ownerKey);
|
|
2634
|
+
const key = `data:${pointer}`;
|
|
2635
|
+
const owners = valueDependencies.get(key);
|
|
2636
|
+
if (owners) {
|
|
2637
|
+
owners.add(ownerKey);
|
|
2638
|
+
}
|
|
2639
|
+
else {
|
|
2640
|
+
valueDependencies.set(key, new Set([ownerKey]));
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
function resolveDependencyPath(path, scope) {
|
|
2644
|
+
const segments = path.split('.');
|
|
2645
|
+
const alias = segments[0];
|
|
2646
|
+
let cursor = scope;
|
|
2647
|
+
while (cursor) {
|
|
2648
|
+
if (Object.prototype.hasOwnProperty.call(cursor.locals, alias)) {
|
|
2649
|
+
const aliases = scopeAliasPaths.get(cursor);
|
|
2650
|
+
if (!aliases?.has(alias))
|
|
2651
|
+
return undefined;
|
|
2652
|
+
const base = aliases.get(alias);
|
|
2653
|
+
if (base === undefined)
|
|
2654
|
+
return undefined;
|
|
2655
|
+
if (scopeIndexAliases.get(cursor)?.has(alias))
|
|
2656
|
+
return base;
|
|
2657
|
+
return joinPointer(base, segments.slice(1));
|
|
2658
|
+
}
|
|
2659
|
+
cursor = cursor.parent;
|
|
2660
|
+
}
|
|
2661
|
+
return joinPointer('', segments);
|
|
2662
|
+
}
|
|
2663
|
+
function resolveSimpleExpressionPath(expression, scope) {
|
|
2664
|
+
const match = expression.trim().match(/^\$\{\s*([a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*)\s*\}$/);
|
|
2665
|
+
return match ? resolveDependencyPath(match[1], scope) : undefined;
|
|
2666
|
+
}
|
|
2667
|
+
function resolveA2UIPointer(path, scopePath) {
|
|
2668
|
+
return path.startsWith('/')
|
|
2669
|
+
? path
|
|
2670
|
+
: path === ''
|
|
2671
|
+
? scopePath
|
|
2672
|
+
: `${scopePath}/${path}`;
|
|
2673
|
+
}
|
|
2674
|
+
function ownDataEntries(value) {
|
|
2675
|
+
try {
|
|
2676
|
+
const entries = [];
|
|
2677
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
2678
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2679
|
+
if (descriptor &&
|
|
2680
|
+
Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
2681
|
+
entries.push([key, descriptor.value]);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
return entries;
|
|
2685
|
+
}
|
|
2686
|
+
catch {
|
|
2687
|
+
return undefined;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
function isIndexAliasPath(path, scope) {
|
|
2691
|
+
const alias = path.split('.')[0];
|
|
2692
|
+
let cursor = scope;
|
|
2693
|
+
while (cursor) {
|
|
2694
|
+
if (Object.prototype.hasOwnProperty.call(cursor.locals, alias)) {
|
|
2695
|
+
return scopeIndexAliases.get(cursor)?.has(alias) ?? false;
|
|
2696
|
+
}
|
|
2697
|
+
cursor = cursor.parent;
|
|
2698
|
+
}
|
|
2699
|
+
return false;
|
|
2700
|
+
}
|
|
2701
|
+
function scanExpressionString(value, scope, ownerKey) {
|
|
2702
|
+
if (!value.includes('${'))
|
|
2703
|
+
return;
|
|
2704
|
+
const expressions = [...value.matchAll(/\$\{([^{}]*)\}/g)];
|
|
2705
|
+
const unmatched = value.replace(/\$\{[^{}]*\}/g, '').includes('${');
|
|
2706
|
+
if (expressions.length === 0 || unmatched) {
|
|
2707
|
+
unresolvedRepeatOwners.add(ownerKey);
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
for (const expression of expressions) {
|
|
2711
|
+
const path = expression[1].trim();
|
|
2712
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/.test(path)) {
|
|
2713
|
+
unresolvedRepeatOwners.add(ownerKey);
|
|
2714
|
+
continue;
|
|
2715
|
+
}
|
|
2716
|
+
const pointer = resolveDependencyPath(path, scope);
|
|
2717
|
+
if (pointer === undefined) {
|
|
2718
|
+
unresolvedRepeatOwners.add(ownerKey);
|
|
2719
|
+
}
|
|
2720
|
+
else if (isIndexAliasPath(path, scope)) {
|
|
2721
|
+
addDependency('data', pointer, ownerKey);
|
|
2722
|
+
}
|
|
2723
|
+
else {
|
|
2724
|
+
addValueDependency(pointer, ownerKey);
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
function scanDependencyValue(value, scope, ownerKey, visited) {
|
|
2729
|
+
if (typeof value === 'string') {
|
|
2730
|
+
scanExpressionString(value, scope, ownerKey);
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
if (!value || typeof value !== 'object')
|
|
2734
|
+
return;
|
|
2735
|
+
if (visited.has(value))
|
|
2736
|
+
return;
|
|
2737
|
+
visited.add(value);
|
|
2738
|
+
if (scope.bindingDialect === 'a2ui' && isA2UIPathBinding(value)) {
|
|
2739
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, 'path');
|
|
2740
|
+
const path = descriptor?.value;
|
|
2741
|
+
if (typeof path === 'string') {
|
|
2742
|
+
addValueDependency(resolveA2UIPointer(path, scope.dataPath), ownerKey);
|
|
2743
|
+
}
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
const entries = ownDataEntries(value);
|
|
2747
|
+
if (!entries) {
|
|
2748
|
+
unresolvedRepeatOwners.add(ownerKey);
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
for (const [key, child] of entries) {
|
|
2752
|
+
if (Array.isArray(value) && key === 'length')
|
|
2753
|
+
continue;
|
|
2754
|
+
scanDependencyValue(child, scope, ownerKey, visited);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
function scanProps(props, scope, ownerKey, visited) {
|
|
2758
|
+
if (visited.has(props))
|
|
2759
|
+
return;
|
|
2760
|
+
visited.add(props);
|
|
2761
|
+
const propEntries = ownDataEntries(props);
|
|
2762
|
+
if (!propEntries) {
|
|
2763
|
+
unresolvedRepeatOwners.add(ownerKey);
|
|
2764
|
+
return;
|
|
2765
|
+
}
|
|
2766
|
+
for (const [key, value] of propEntries) {
|
|
2767
|
+
if (key !== 'slots') {
|
|
2768
|
+
scanDependencyValue(value, scope, ownerKey, visited);
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
if (!value || typeof value !== 'object')
|
|
2772
|
+
continue;
|
|
2773
|
+
const slotEntries = ownDataEntries(value);
|
|
2774
|
+
if (!slotEntries) {
|
|
2775
|
+
unresolvedRepeatOwners.add(ownerKey);
|
|
2776
|
+
continue;
|
|
2777
|
+
}
|
|
2778
|
+
for (const [, slot] of slotEntries) {
|
|
2779
|
+
if (!slot || typeof slot !== 'object')
|
|
2780
|
+
continue;
|
|
2781
|
+
const entries = ownDataEntries(slot);
|
|
2782
|
+
if (!entries) {
|
|
2783
|
+
unresolvedRepeatOwners.add(ownerKey);
|
|
2784
|
+
continue;
|
|
2785
|
+
}
|
|
2786
|
+
for (const [slotKey, slotValue] of entries) {
|
|
2787
|
+
if (slotKey === 'repeat' || slotKey === A2UI_CHILD_BINDING)
|
|
2788
|
+
continue;
|
|
2789
|
+
scanDependencyValue(slotValue, scope, ownerKey, visited);
|
|
1166
2790
|
}
|
|
1167
2791
|
}
|
|
1168
2792
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
2793
|
+
}
|
|
2794
|
+
function allocateRuntimeId(sourceId, instancePath) {
|
|
2795
|
+
const base = runtimeId(sourceId, instancePath);
|
|
2796
|
+
let id = base;
|
|
2797
|
+
let suffix = 1;
|
|
2798
|
+
while (publicIds.has(id) || instances.has(id)) {
|
|
2799
|
+
id = `${base}_${suffix}`;
|
|
2800
|
+
suffix += 1;
|
|
2801
|
+
}
|
|
2802
|
+
publicIds.add(id);
|
|
2803
|
+
return id;
|
|
2804
|
+
}
|
|
2805
|
+
function allocateStaticInstanceKey(id) {
|
|
2806
|
+
const base = `s_${escapeRuntimePart(id)}`;
|
|
2807
|
+
let suffix = 1;
|
|
2808
|
+
let key = `${base}_${suffix}`;
|
|
2809
|
+
while (schemaSourceIds.has(key) ||
|
|
2810
|
+
publicIds.has(key) ||
|
|
2811
|
+
instances.has(key)) {
|
|
2812
|
+
suffix += 1;
|
|
2813
|
+
key = `${base}_${suffix}`;
|
|
2814
|
+
}
|
|
2815
|
+
return key;
|
|
2816
|
+
}
|
|
2817
|
+
function buildNode(sourceId, scope, instancePath, structuralPath, repeatedOccurrence, parentId, repeatDepth, nearestOwnerKey, activeDefinitions) {
|
|
2818
|
+
if (activeDefinitions.has(sourceId)) {
|
|
2819
|
+
throw new Error(`[CardMaterializer] Circular reference detected at "${sourceId}"`);
|
|
2820
|
+
}
|
|
2821
|
+
const nextActiveDefinitions = new Set(activeDefinitions).add(sourceId);
|
|
2822
|
+
const element = schema.elements[sourceId];
|
|
2823
|
+
if (!element) {
|
|
2824
|
+
throw new Error(`[CardMaterializer] Missing element for id "${sourceId}"`);
|
|
2825
|
+
}
|
|
2826
|
+
const bindingDialect = (scope.bindingDialect === 'a2ui' ||
|
|
2827
|
+
hasA2UIBindingDialect(element)) ? 'a2ui' : 'native';
|
|
2828
|
+
const activeScope = bindingDialect === scope.bindingDialect
|
|
2829
|
+
? scope
|
|
2830
|
+
: { ...scope, bindingDialect };
|
|
2831
|
+
if (activeScope !== scope) {
|
|
2832
|
+
scopeAliasPaths.set(activeScope, new Map(scopeAliasPaths.get(scope) ?? []));
|
|
2833
|
+
scopeIndexAliases.set(activeScope, new Set(scopeIndexAliases.get(scope) ?? []));
|
|
2834
|
+
}
|
|
2835
|
+
const id = repeatedOccurrence
|
|
2836
|
+
? allocateRuntimeId(sourceId, instancePath)
|
|
2837
|
+
: sourceId;
|
|
2838
|
+
const props = cloneValue(element.props);
|
|
2839
|
+
const node = {
|
|
1171
2840
|
id,
|
|
2841
|
+
sourceId,
|
|
2842
|
+
instancePath,
|
|
2843
|
+
dataPath: activeScope.dataPath,
|
|
2844
|
+
scope: activeScope,
|
|
2845
|
+
bindingDialect,
|
|
1172
2846
|
type: element.type,
|
|
1173
|
-
props
|
|
1174
|
-
children,
|
|
2847
|
+
props,
|
|
2848
|
+
children: [],
|
|
1175
2849
|
lifecycle: element.lifecycle,
|
|
1176
2850
|
events: element.events,
|
|
1177
2851
|
directives: element.directives,
|
|
1178
2852
|
};
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
// ─── Validation ──────────────────────────────────────────────────
|
|
1183
|
-
/**
|
|
1184
|
-
* Validate a CardSchema and return any error messages.
|
|
1185
|
-
*/
|
|
1186
|
-
function validateSchema(input) {
|
|
1187
|
-
const schema = normalizeSchema(input);
|
|
1188
|
-
const errors = [];
|
|
1189
|
-
if (!schema.version) {
|
|
1190
|
-
errors.push('Missing "version" field');
|
|
1191
|
-
}
|
|
1192
|
-
if (!schema.rootID) {
|
|
1193
|
-
errors.push('Missing "rootID" field');
|
|
1194
|
-
}
|
|
1195
|
-
else if (!schema.elements[schema.rootID]) {
|
|
1196
|
-
errors.push(`Root element "${schema.rootID}" not found in elements`);
|
|
1197
|
-
}
|
|
1198
|
-
const allIds = new Set(Object.keys(schema.elements));
|
|
1199
|
-
for (const [id, element] of Object.entries(schema.elements)) {
|
|
1200
|
-
if (!element.type) {
|
|
1201
|
-
errors.push(`Element "${id}" is missing a "type" field`);
|
|
2853
|
+
let instanceKey = id;
|
|
2854
|
+
if (!repeatedOccurrence && instances.has(instanceKey)) {
|
|
2855
|
+
instanceKey = allocateStaticInstanceKey(id);
|
|
1202
2856
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
2857
|
+
instances.set(instanceKey, { id, sourceId, parentId, node });
|
|
2858
|
+
const slotsDescriptor = Object.getOwnPropertyDescriptor(props, 'slots');
|
|
2859
|
+
const slots = (slotsDescriptor &&
|
|
2860
|
+
Object.prototype.hasOwnProperty.call(slotsDescriptor, 'value'))
|
|
2861
|
+
? slotsDescriptor.value
|
|
2862
|
+
: undefined;
|
|
2863
|
+
const slotOwners = new Map();
|
|
2864
|
+
if (slots) {
|
|
2865
|
+
for (const [rawSlotName, rawSlot] of ownDataEntries(slots) ?? []) {
|
|
2866
|
+
if (typeof rawSlotName !== 'string' || !rawSlot || typeof rawSlot !== 'object') {
|
|
2867
|
+
continue;
|
|
2868
|
+
}
|
|
2869
|
+
const slotName = rawSlotName;
|
|
2870
|
+
const slot = rawSlot;
|
|
2871
|
+
const repeatDescriptor = Object.getOwnPropertyDescriptor(slot, 'repeat');
|
|
2872
|
+
let binding;
|
|
2873
|
+
if (repeatDescriptor &&
|
|
2874
|
+
Object.prototype.hasOwnProperty.call(repeatDescriptor, 'value')) {
|
|
2875
|
+
const native = repeatDescriptor.value;
|
|
2876
|
+
if (!native || typeof native !== 'object') {
|
|
2877
|
+
throw new Error(`[CardMaterializer] Invalid repeat on "${sourceId}" slot "${slotName}"`);
|
|
1211
2878
|
}
|
|
2879
|
+
binding = {
|
|
2880
|
+
dialect: 'native',
|
|
2881
|
+
source: native.source,
|
|
2882
|
+
template: native.template,
|
|
2883
|
+
item: native.item,
|
|
2884
|
+
index: native.index,
|
|
2885
|
+
};
|
|
1212
2886
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
2887
|
+
else {
|
|
2888
|
+
const a2ui = getA2UIChildBinding(slot);
|
|
2889
|
+
if (a2ui) {
|
|
2890
|
+
binding = {
|
|
2891
|
+
dialect: 'a2ui',
|
|
2892
|
+
source: a2ui.path,
|
|
2893
|
+
template: a2ui.templateId,
|
|
2894
|
+
};
|
|
1220
2895
|
}
|
|
1221
2896
|
}
|
|
2897
|
+
if (!binding)
|
|
2898
|
+
continue;
|
|
2899
|
+
hasRepeat = true;
|
|
2900
|
+
const depth = repeatDepth + 1;
|
|
2901
|
+
const sourcePath = binding.dialect === 'a2ui'
|
|
2902
|
+
? resolveA2UIPointer(binding.source, activeScope.dataPath)
|
|
2903
|
+
: resolveSimpleExpressionPath(binding.source, activeScope);
|
|
2904
|
+
const ownerKey = `o_${escapeRuntimePart(id)}_${escapeRuntimePart(slotName)}_${escapeRuntimePart(structuralPath)}`;
|
|
2905
|
+
const owner = {
|
|
2906
|
+
key: ownerKey,
|
|
2907
|
+
...(nearestOwnerKey ? { parentKey: nearestOwnerKey } : {}),
|
|
2908
|
+
runtimeOwnerId: id,
|
|
2909
|
+
sourceOwnerId: sourceId,
|
|
2910
|
+
slot: slotName,
|
|
2911
|
+
templateId: binding.template,
|
|
2912
|
+
...(sourcePath === undefined ? {} : { sourcePath }),
|
|
2913
|
+
dataPath: activeScope.dataPath,
|
|
2914
|
+
instancePath,
|
|
2915
|
+
depth,
|
|
2916
|
+
};
|
|
2917
|
+
if (repeatOwners.has(ownerKey)) {
|
|
2918
|
+
throw new Error(`[CardMaterializer] Repeat owner key collision at "${ownerKey}"`);
|
|
2919
|
+
}
|
|
2920
|
+
repeatOwners.set(ownerKey, owner);
|
|
2921
|
+
slotOwners.set(slotName, { owner, binding, sourcePath, depth });
|
|
2922
|
+
addDependency('component', binding.template, ownerKey);
|
|
2923
|
+
const runtimeKeys = runtimeOwnerKeys.get(id) ?? [];
|
|
2924
|
+
if (runtimeKeys.length > 0) {
|
|
2925
|
+
for (const key of runtimeKeys)
|
|
2926
|
+
unresolvedRepeatOwners.add(key);
|
|
2927
|
+
unresolvedRepeatOwners.add(ownerKey);
|
|
2928
|
+
}
|
|
2929
|
+
runtimeKeys.push(ownerKey);
|
|
2930
|
+
runtimeOwnerKeys.set(id, runtimeKeys);
|
|
2931
|
+
if (sourcePath === undefined) {
|
|
2932
|
+
unresolvedRepeatOwners.add(ownerKey);
|
|
2933
|
+
}
|
|
2934
|
+
else {
|
|
2935
|
+
addDependency('data', sourcePath, ownerKey);
|
|
2936
|
+
}
|
|
1222
2937
|
}
|
|
1223
2938
|
}
|
|
2939
|
+
const ownOwnerKeys = [...slotOwners.values()].map(entry => entry.owner.key);
|
|
2940
|
+
const effectiveOwnerKey = ownOwnerKeys[0] ?? nearestOwnerKey;
|
|
2941
|
+
if (ownOwnerKeys.length > 1) {
|
|
2942
|
+
for (const key of ownOwnerKeys)
|
|
2943
|
+
unresolvedRepeatOwners.add(key);
|
|
2944
|
+
}
|
|
2945
|
+
if (effectiveOwnerKey) {
|
|
2946
|
+
addDependency('component', sourceId, effectiveOwnerKey);
|
|
2947
|
+
const visited = new WeakSet();
|
|
2948
|
+
scanProps(element.props, activeScope, effectiveOwnerKey, visited);
|
|
2949
|
+
scanDependencyValue(element.directives?.visible, activeScope, effectiveOwnerKey, visited);
|
|
2950
|
+
scanDependencyValue(element.directives?.disabled, activeScope, effectiveOwnerKey, visited);
|
|
2951
|
+
}
|
|
2952
|
+
if (!slots)
|
|
2953
|
+
return node;
|
|
2954
|
+
for (const [rawSlotName, rawSlot] of ownDataEntries(slots) ?? []) {
|
|
2955
|
+
if (typeof rawSlotName !== 'string' || !rawSlot || typeof rawSlot !== 'object') {
|
|
2956
|
+
continue;
|
|
2957
|
+
}
|
|
2958
|
+
const slotName = rawSlotName;
|
|
2959
|
+
const slot = rawSlot;
|
|
2960
|
+
const references = collectSlotReferences(slot);
|
|
2961
|
+
for (const reference of references) {
|
|
2962
|
+
const childStructuralPath = appendOccurrence(structuralPath, 'child', sourceId, slotName, reference.position);
|
|
2963
|
+
const childPath = repeatedOccurrence ? childStructuralPath : '';
|
|
2964
|
+
const child = buildNode(reference.sourceId, activeScope, childPath, childStructuralPath, repeatedOccurrence, id, repeatDepth, effectiveOwnerKey, nextActiveDefinitions);
|
|
2965
|
+
reference.assign(child.id);
|
|
2966
|
+
node.children.push(child);
|
|
2967
|
+
}
|
|
2968
|
+
const slotOwner = slotOwners.get(slotName);
|
|
2969
|
+
if (!slotOwner)
|
|
2970
|
+
continue;
|
|
2971
|
+
const { owner, binding, sourcePath, depth } = slotOwner;
|
|
2972
|
+
const value = binding.dialect === 'a2ui'
|
|
2973
|
+
? resolveA2UIPath(rootVariables, binding.source, activeScope.dataPath)
|
|
2974
|
+
: resolveExpression(binding.source, createExpressionContext(activeScope));
|
|
2975
|
+
let items;
|
|
2976
|
+
if (value == null) {
|
|
2977
|
+
items = [];
|
|
2978
|
+
}
|
|
2979
|
+
else if (!Array.isArray(value)) {
|
|
2980
|
+
const diagnostic = {
|
|
2981
|
+
code: 'REPEAT_SOURCE_NOT_ARRAY',
|
|
2982
|
+
message: `Repeat source "${binding.source}" on "${sourceId}" did not resolve to an array`,
|
|
2983
|
+
ownerId: id,
|
|
2984
|
+
source: binding.source,
|
|
2985
|
+
};
|
|
2986
|
+
diagnostics.push(diagnostic);
|
|
2987
|
+
options.onDiagnostic?.(diagnostic);
|
|
2988
|
+
items = [];
|
|
2989
|
+
}
|
|
2990
|
+
else {
|
|
2991
|
+
items = value;
|
|
2992
|
+
}
|
|
2993
|
+
if (items.length > 0 && depth > maxDepth) {
|
|
2994
|
+
throw new Error(`[CardMaterializer] maxDepth ${maxDepth} exceeded at "${sourceId}" slot "${slotName}"`);
|
|
2995
|
+
}
|
|
2996
|
+
if (repeatedItemCount + items.length > maxItems) {
|
|
2997
|
+
throw new Error(`[CardMaterializer] maxItems ${maxItems} exceeded at "${sourceId}" slot "${slotName}"`);
|
|
2998
|
+
}
|
|
2999
|
+
repeatedItemCount += items.length;
|
|
3000
|
+
Reflect.deleteProperty(slot, 'repeat');
|
|
3001
|
+
Reflect.deleteProperty(slot, A2UI_CHILD_BINDING);
|
|
3002
|
+
slot.children = [];
|
|
3003
|
+
items.forEach((item, index) => {
|
|
3004
|
+
const dataPath = sourcePath === undefined
|
|
3005
|
+
? activeScope.dataPath
|
|
3006
|
+
: joinPointer(sourcePath, [String(index)]);
|
|
3007
|
+
const locals = binding.dialect === 'native' && binding.item
|
|
3008
|
+
? {
|
|
3009
|
+
[binding.item]: item,
|
|
3010
|
+
...(binding.index ? { [binding.index]: index } : {}),
|
|
3011
|
+
}
|
|
3012
|
+
: {};
|
|
3013
|
+
const childScope = {
|
|
3014
|
+
root: rootVariables,
|
|
3015
|
+
locals,
|
|
3016
|
+
parent: activeScope,
|
|
3017
|
+
dataPath,
|
|
3018
|
+
bindingDialect: binding.dialect,
|
|
3019
|
+
};
|
|
3020
|
+
const aliases = new Map();
|
|
3021
|
+
if (binding.dialect === 'native' && binding.item) {
|
|
3022
|
+
aliases.set(binding.item, sourcePath === undefined ? undefined : dataPath);
|
|
3023
|
+
if (binding.index)
|
|
3024
|
+
aliases.set(binding.index, sourcePath);
|
|
3025
|
+
}
|
|
3026
|
+
scopeAliasPaths.set(childScope, aliases);
|
|
3027
|
+
scopeIndexAliases.set(childScope, new Set(binding.dialect === 'native' && binding.index
|
|
3028
|
+
? [binding.index]
|
|
3029
|
+
: []));
|
|
3030
|
+
const itemPath = appendOccurrence(structuralPath, 'item', sourceId, slotName, `${binding.template}:${index}`);
|
|
3031
|
+
const child = buildNode(binding.template, childScope, itemPath, itemPath, true, id, depth, owner.key, nextActiveDefinitions);
|
|
3032
|
+
slot.children.push(child.id);
|
|
3033
|
+
node.children.push(child);
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
3036
|
+
return node;
|
|
1224
3037
|
}
|
|
1225
|
-
|
|
3038
|
+
const root = buildNode(schema.rootID, rootScope, '', '', false, undefined, 0, undefined, new Set());
|
|
3039
|
+
return {
|
|
3040
|
+
root,
|
|
3041
|
+
hasRepeat,
|
|
3042
|
+
instances,
|
|
3043
|
+
repeatOwners,
|
|
3044
|
+
dependencies,
|
|
3045
|
+
valueDependencies,
|
|
3046
|
+
unresolvedRepeatOwners,
|
|
3047
|
+
diagnostics,
|
|
3048
|
+
};
|
|
1226
3049
|
}
|
|
1227
3050
|
|
|
1228
3051
|
/**
|
|
@@ -1363,12 +3186,29 @@ function isA2UIEnvelope(msg) {
|
|
|
1363
3186
|
*/
|
|
1364
3187
|
function a2uiComponentToElement(comp) {
|
|
1365
3188
|
const { id, component, children, slots, directives, events, lifecycle, ...props } = comp;
|
|
3189
|
+
if (children !== undefined && !isA2UIChildList(children)) {
|
|
3190
|
+
throw new Error(`[A2UI] Component "${id}" children must be a valid ChildList `
|
|
3191
|
+
+ '(string[] or exact { path, componentId } object)');
|
|
3192
|
+
}
|
|
1366
3193
|
const p = { ...props };
|
|
1367
|
-
if (children) {
|
|
3194
|
+
if (Array.isArray(children)) {
|
|
1368
3195
|
p.slots = { ...(p.slots ?? {}), default: { children } };
|
|
1369
3196
|
}
|
|
1370
3197
|
if (slots) {
|
|
1371
|
-
p.slots = {
|
|
3198
|
+
p.slots = {
|
|
3199
|
+
...(p.slots ?? {}),
|
|
3200
|
+
...(isA2UIDynamicChildList(children)
|
|
3201
|
+
? copyDynamicSlots(id, slots)
|
|
3202
|
+
: slots),
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
if (isA2UIDynamicChildList(children)) {
|
|
3206
|
+
p.slots = { ...(p.slots ?? {}) };
|
|
3207
|
+
const defaultSlot = Object.prototype.hasOwnProperty.call(p.slots, 'default')
|
|
3208
|
+
? cloneDynamicDefaultSlot(id, p.slots.default)
|
|
3209
|
+
: {};
|
|
3210
|
+
p.slots.default = defaultSlot;
|
|
3211
|
+
setA2UIChildBinding(defaultSlot, children);
|
|
1372
3212
|
}
|
|
1373
3213
|
const element = { id, type: component, props: p };
|
|
1374
3214
|
if (directives)
|
|
@@ -1377,8 +3217,121 @@ function a2uiComponentToElement(comp) {
|
|
|
1377
3217
|
element.events = events;
|
|
1378
3218
|
if (lifecycle)
|
|
1379
3219
|
element.lifecycle = lifecycle;
|
|
3220
|
+
if (isA2UIDynamicChildList(children)
|
|
3221
|
+
|| containsA2UIPathBinding$1(p)
|
|
3222
|
+
|| containsA2UIPathBinding$1(directives)
|
|
3223
|
+
|| containsA2UIPathBinding$1(events)
|
|
3224
|
+
|| containsA2UIPathBinding$1(lifecycle)) {
|
|
3225
|
+
markA2UIBindingDialect(element);
|
|
3226
|
+
}
|
|
1380
3227
|
return element;
|
|
1381
3228
|
}
|
|
3229
|
+
function containsA2UIPathBinding$1(value, seen = new WeakSet()) {
|
|
3230
|
+
if (isA2UIPathBinding(value))
|
|
3231
|
+
return true;
|
|
3232
|
+
if (typeof value !== 'object' || value === null)
|
|
3233
|
+
return false;
|
|
3234
|
+
if (seen.has(value))
|
|
3235
|
+
return false;
|
|
3236
|
+
seen.add(value);
|
|
3237
|
+
try {
|
|
3238
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
3239
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
3240
|
+
if (descriptor
|
|
3241
|
+
&& Object.prototype.hasOwnProperty.call(descriptor, 'value')
|
|
3242
|
+
&& containsA2UIPathBinding$1(descriptor.value, seen)) {
|
|
3243
|
+
return true;
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
catch {
|
|
3248
|
+
return false;
|
|
3249
|
+
}
|
|
3250
|
+
return false;
|
|
3251
|
+
}
|
|
3252
|
+
function isPlainRecord(value) {
|
|
3253
|
+
try {
|
|
3254
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
3255
|
+
return false;
|
|
3256
|
+
}
|
|
3257
|
+
const prototype = Object.getPrototypeOf(value);
|
|
3258
|
+
return prototype === Object.prototype || prototype === null;
|
|
3259
|
+
}
|
|
3260
|
+
catch {
|
|
3261
|
+
return false;
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
function copyDynamicSlots(componentId, slots) {
|
|
3265
|
+
const output = {};
|
|
3266
|
+
let keys;
|
|
3267
|
+
try {
|
|
3268
|
+
keys = Reflect.ownKeys(slots);
|
|
3269
|
+
}
|
|
3270
|
+
catch {
|
|
3271
|
+
throw new Error(`[A2UI] Component "${componentId}" slots must be a readable record`);
|
|
3272
|
+
}
|
|
3273
|
+
for (const key of keys) {
|
|
3274
|
+
let descriptor;
|
|
3275
|
+
try {
|
|
3276
|
+
descriptor = Object.getOwnPropertyDescriptor(slots, key);
|
|
3277
|
+
}
|
|
3278
|
+
catch {
|
|
3279
|
+
throw new Error(`[A2UI] Component "${componentId}" slots must be a readable record`);
|
|
3280
|
+
}
|
|
3281
|
+
if (!descriptor || !descriptor.enumerable)
|
|
3282
|
+
continue;
|
|
3283
|
+
if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
3284
|
+
throw new Error(`[A2UI] Component "${componentId}" slots must use data properties`);
|
|
3285
|
+
}
|
|
3286
|
+
Object.defineProperty(output, key, {
|
|
3287
|
+
configurable: true,
|
|
3288
|
+
enumerable: true,
|
|
3289
|
+
value: descriptor.value,
|
|
3290
|
+
writable: true,
|
|
3291
|
+
});
|
|
3292
|
+
}
|
|
3293
|
+
return output;
|
|
3294
|
+
}
|
|
3295
|
+
function cloneDynamicDefaultSlot(componentId, source) {
|
|
3296
|
+
if (!isPlainRecord(source)) {
|
|
3297
|
+
throw new Error(`[A2UI] Component "${componentId}" slots.default must be a plain record`);
|
|
3298
|
+
}
|
|
3299
|
+
let keys;
|
|
3300
|
+
try {
|
|
3301
|
+
keys = Reflect.ownKeys(source);
|
|
3302
|
+
}
|
|
3303
|
+
catch {
|
|
3304
|
+
throw new Error(`[A2UI] Component "${componentId}" slots.default must be a plain record`);
|
|
3305
|
+
}
|
|
3306
|
+
const conflicts = new Set(['children', 'groups', 'repeat']);
|
|
3307
|
+
const output = {};
|
|
3308
|
+
for (const key of keys) {
|
|
3309
|
+
if (conflicts.has(key)) {
|
|
3310
|
+
throw new Error(`[A2UI] Component "${componentId}" slots.default.${String(key)} `
|
|
3311
|
+
+ 'conflicts with dynamic children');
|
|
3312
|
+
}
|
|
3313
|
+
let descriptor;
|
|
3314
|
+
try {
|
|
3315
|
+
descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
3316
|
+
}
|
|
3317
|
+
catch {
|
|
3318
|
+
throw new Error(`[A2UI] Component "${componentId}" slots.default must be a plain record`);
|
|
3319
|
+
}
|
|
3320
|
+
if (!descriptor || !descriptor.enumerable)
|
|
3321
|
+
continue;
|
|
3322
|
+
if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
3323
|
+
throw new Error(`[A2UI] Component "${componentId}" slots.default must be a plain `
|
|
3324
|
+
+ 'record with data properties');
|
|
3325
|
+
}
|
|
3326
|
+
Object.defineProperty(output, key, {
|
|
3327
|
+
configurable: true,
|
|
3328
|
+
enumerable: true,
|
|
3329
|
+
value: descriptor.value,
|
|
3330
|
+
writable: true,
|
|
3331
|
+
});
|
|
3332
|
+
}
|
|
3333
|
+
return output;
|
|
3334
|
+
}
|
|
1382
3335
|
/**
|
|
1383
3336
|
* Convert an A2UI v0.9 envelope message into an internal StreamingCommand.
|
|
1384
3337
|
* Returns null for unrecognized messages.
|
|
@@ -1409,8 +3362,17 @@ function a2uiToCommand(msg) {
|
|
|
1409
3362
|
return { type: 'updateComponents', surfaceId, elements, ...(rootID ? { rootID } : {}) };
|
|
1410
3363
|
}
|
|
1411
3364
|
if (msg.updateDataModel) {
|
|
1412
|
-
const { surfaceId, path
|
|
1413
|
-
|
|
3365
|
+
const { surfaceId, path: envelopePath, value = null } = msg.updateDataModel;
|
|
3366
|
+
const path = envelopePath === undefined || envelopePath === '/'
|
|
3367
|
+
? ''
|
|
3368
|
+
: envelopePath;
|
|
3369
|
+
return {
|
|
3370
|
+
type: 'updateDataModel',
|
|
3371
|
+
surfaceId,
|
|
3372
|
+
path,
|
|
3373
|
+
pathDialect: 'a2ui',
|
|
3374
|
+
value,
|
|
3375
|
+
};
|
|
1414
3376
|
}
|
|
1415
3377
|
if (msg.appendContent) {
|
|
1416
3378
|
const { surfaceId, elementId, content } = msg.appendContent;
|
|
@@ -1555,6 +3517,109 @@ class StreamingParser {
|
|
|
1555
3517
|
* - Compute parent-child relationships for component changes
|
|
1556
3518
|
* - Emit typed events for the rendering layer
|
|
1557
3519
|
*/
|
|
3520
|
+
function cloneStreamingValue(value, seen = new WeakMap()) {
|
|
3521
|
+
if (value === null || typeof value !== 'object')
|
|
3522
|
+
return value;
|
|
3523
|
+
const cached = seen.get(value);
|
|
3524
|
+
if (cached !== undefined)
|
|
3525
|
+
return cached;
|
|
3526
|
+
const output = Array.isArray(value)
|
|
3527
|
+
? []
|
|
3528
|
+
: Object.create(Object.getPrototypeOf(value));
|
|
3529
|
+
seen.set(value, output);
|
|
3530
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
3531
|
+
if (Array.isArray(value) && key === 'length')
|
|
3532
|
+
continue;
|
|
3533
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
3534
|
+
if (!descriptor)
|
|
3535
|
+
continue;
|
|
3536
|
+
if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
3537
|
+
throw new TypeError(`Cannot clone streaming state accessor "${String(key)}"`);
|
|
3538
|
+
}
|
|
3539
|
+
Object.defineProperty(output, key, {
|
|
3540
|
+
value: cloneStreamingValue(descriptor.value, seen),
|
|
3541
|
+
enumerable: descriptor.enumerable,
|
|
3542
|
+
configurable: true,
|
|
3543
|
+
writable: true,
|
|
3544
|
+
});
|
|
3545
|
+
}
|
|
3546
|
+
return output;
|
|
3547
|
+
}
|
|
3548
|
+
function restoreOwnProperty(target, key) {
|
|
3549
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
3550
|
+
return descriptor
|
|
3551
|
+
? () => {
|
|
3552
|
+
Object.defineProperty(target, key, descriptor);
|
|
3553
|
+
}
|
|
3554
|
+
: () => {
|
|
3555
|
+
Reflect.deleteProperty(target, key);
|
|
3556
|
+
};
|
|
3557
|
+
}
|
|
3558
|
+
function arrayIndexWriteExtendsLength(target, key) {
|
|
3559
|
+
if (!Array.isArray(target))
|
|
3560
|
+
return false;
|
|
3561
|
+
const text = String(key);
|
|
3562
|
+
if (!/^(0|[1-9]\d*)$/.test(text))
|
|
3563
|
+
return false;
|
|
3564
|
+
const index = Number(text);
|
|
3565
|
+
return index <= 4294967294 && index >= target.length;
|
|
3566
|
+
}
|
|
3567
|
+
function writeProperty(target, key, value, journal) {
|
|
3568
|
+
if (!journal) {
|
|
3569
|
+
target[key] = value;
|
|
3570
|
+
return;
|
|
3571
|
+
}
|
|
3572
|
+
recordPropertyMutation(target, key, journal);
|
|
3573
|
+
target[key] = value;
|
|
3574
|
+
}
|
|
3575
|
+
function recordPropertyMutation(target, key, journal) {
|
|
3576
|
+
if (arrayIndexWriteExtendsLength(target, key)) {
|
|
3577
|
+
journal.push(restoreOwnProperty(target, 'length'));
|
|
3578
|
+
}
|
|
3579
|
+
journal.push(restoreOwnProperty(target, key));
|
|
3580
|
+
}
|
|
3581
|
+
function deleteProperty(target, key, journal) {
|
|
3582
|
+
if (!journal)
|
|
3583
|
+
return Reflect.deleteProperty(target, key);
|
|
3584
|
+
const undo = restoreOwnProperty(target, key);
|
|
3585
|
+
const deleted = Reflect.deleteProperty(target, key);
|
|
3586
|
+
if (deleted)
|
|
3587
|
+
journal.push(undo);
|
|
3588
|
+
return deleted;
|
|
3589
|
+
}
|
|
3590
|
+
function spliceOne(target, index, journal) {
|
|
3591
|
+
if (!journal) {
|
|
3592
|
+
target.splice(index, 1);
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
const removed = target[index];
|
|
3596
|
+
target.splice(index, 1);
|
|
3597
|
+
journal.push(() => {
|
|
3598
|
+
target.splice(index, 0, removed);
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
function rollbackMutations(journal) {
|
|
3602
|
+
for (let index = journal.length - 1; index >= 0; index -= 1) {
|
|
3603
|
+
journal[index]();
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
function containsA2UIPathBinding(value, seen = new WeakSet()) {
|
|
3607
|
+
if (isA2UIPathBinding(value))
|
|
3608
|
+
return true;
|
|
3609
|
+
if (value === null || typeof value !== 'object' || seen.has(value)) {
|
|
3610
|
+
return false;
|
|
3611
|
+
}
|
|
3612
|
+
seen.add(value);
|
|
3613
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
3614
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
3615
|
+
if (descriptor
|
|
3616
|
+
&& Object.prototype.hasOwnProperty.call(descriptor, 'value')
|
|
3617
|
+
&& containsA2UIPathBinding(descriptor.value, seen)) {
|
|
3618
|
+
return true;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
return false;
|
|
3622
|
+
}
|
|
1558
3623
|
// ─── Engine ──────────────────────────────────────────────────────
|
|
1559
3624
|
class StreamingEngine {
|
|
1560
3625
|
constructor(listeners) {
|
|
@@ -1630,25 +3695,90 @@ class StreamingEngine {
|
|
|
1630
3695
|
const schema = cmd.schema
|
|
1631
3696
|
? normalizeSchema(cmd.schema)
|
|
1632
3697
|
: { version: '1.0', rootID: 'root', elements: {}, variables: {} };
|
|
3698
|
+
const previousSchema = this.surfaces.get(cmd.surfaceId);
|
|
1633
3699
|
this.surfaces.set(cmd.surfaceId, schema);
|
|
1634
|
-
|
|
3700
|
+
try {
|
|
3701
|
+
this.listeners.onSurfaceCreated(cmd.surfaceId, cmd.schema ?? null);
|
|
3702
|
+
}
|
|
3703
|
+
catch (error) {
|
|
3704
|
+
if (previousSchema) {
|
|
3705
|
+
this.surfaces.set(cmd.surfaceId, previousSchema);
|
|
3706
|
+
}
|
|
3707
|
+
else {
|
|
3708
|
+
this.surfaces.delete(cmd.surfaceId);
|
|
3709
|
+
}
|
|
3710
|
+
throw error;
|
|
3711
|
+
}
|
|
1635
3712
|
}
|
|
1636
3713
|
handleUpdateComponents(cmd) {
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
3714
|
+
const previousSchema = this.surfaces.get(cmd.surfaceId);
|
|
3715
|
+
const baseSchema = previousSchema ?? {
|
|
3716
|
+
version: '1.0',
|
|
3717
|
+
rootID: cmd.rootID ?? 'root',
|
|
3718
|
+
elements: {},
|
|
3719
|
+
variables: {},
|
|
3720
|
+
};
|
|
3721
|
+
const previousRequiresBinding = requiresBindingMaterialization(baseSchema);
|
|
3722
|
+
const commandMayIntroduceBinding = this.commandMayIntroduceBinding(cmd);
|
|
3723
|
+
// Preserve the original mutation order, references, and event timing for
|
|
3724
|
+
// the overwhelmingly common static-to-static path.
|
|
3725
|
+
if (!previousRequiresBinding && !commandMayIntroduceBinding) {
|
|
3726
|
+
this.applyStaticComponentMutation(cmd, previousSchema, baseSchema);
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
const candidate = cloneStreamingValue(baseSchema);
|
|
3730
|
+
const candidateChanges = this.applyComponentMutation(candidate, cmd, true);
|
|
3731
|
+
const rootIDChanged = candidate.rootID !== baseSchema.rootID;
|
|
3732
|
+
const candidateRequiresBinding = requiresBindingMaterialization(candidate);
|
|
3733
|
+
// A command may briefly carry binding metadata and remove it again in the
|
|
3734
|
+
// same batch. The final candidate, not that intermediate form, decides
|
|
3735
|
+
// whether the legacy static path remains applicable.
|
|
3736
|
+
if (!previousRequiresBinding && !candidateRequiresBinding) {
|
|
3737
|
+
this.applyStaticComponentMutation(cmd, previousSchema, baseSchema);
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
if (candidateChanges.length === 0 && !rootIDChanged)
|
|
3741
|
+
return;
|
|
3742
|
+
this.surfaces.set(cmd.surfaceId, candidate);
|
|
3743
|
+
try {
|
|
3744
|
+
this.listeners.onComponentsUpdated(cmd.surfaceId, candidateChanges);
|
|
3745
|
+
}
|
|
3746
|
+
catch (error) {
|
|
3747
|
+
if (previousSchema) {
|
|
3748
|
+
this.surfaces.set(cmd.surfaceId, previousSchema);
|
|
3749
|
+
}
|
|
3750
|
+
else {
|
|
3751
|
+
this.surfaces.delete(cmd.surfaceId);
|
|
3752
|
+
}
|
|
3753
|
+
throw error;
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
applyStaticComponentMutation(cmd, previousSchema, schema) {
|
|
3757
|
+
const journal = [];
|
|
3758
|
+
if (!previousSchema)
|
|
1646
3759
|
this.surfaces.set(cmd.surfaceId, schema);
|
|
3760
|
+
try {
|
|
3761
|
+
const changes = this.applyComponentMutation(schema, cmd, false, journal);
|
|
3762
|
+
if (changes.length > 0) {
|
|
3763
|
+
this.listeners.onComponentsUpdated(cmd.surfaceId, changes);
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
catch (error) {
|
|
3767
|
+
rollbackMutations(journal);
|
|
3768
|
+
if (previousSchema) {
|
|
3769
|
+
this.surfaces.set(cmd.surfaceId, previousSchema);
|
|
3770
|
+
}
|
|
3771
|
+
else {
|
|
3772
|
+
this.surfaces.delete(cmd.surfaceId);
|
|
3773
|
+
}
|
|
3774
|
+
throw error;
|
|
1647
3775
|
}
|
|
3776
|
+
}
|
|
3777
|
+
applyComponentMutation(schema, cmd, cloneIncoming, journal) {
|
|
1648
3778
|
const changes = [];
|
|
1649
3779
|
// Update rootID if provided
|
|
1650
3780
|
if (cmd.rootID) {
|
|
1651
|
-
schema
|
|
3781
|
+
writeProperty(schema, 'rootID', cmd.rootID, journal);
|
|
1652
3782
|
}
|
|
1653
3783
|
// Process element additions/updates.
|
|
1654
3784
|
// Merge ALL elements first, THEN compute parent/index against the final
|
|
@@ -1656,12 +3786,15 @@ class StreamingEngine {
|
|
|
1656
3786
|
// processed before its parent's updated children array would resolve to
|
|
1657
3787
|
// no parent, and incremental renderers would silently drop it.
|
|
1658
3788
|
if (cmd.elements) {
|
|
3789
|
+
const incomingElements = cloneIncoming
|
|
3790
|
+
? cloneStreamingValue(cmd.elements)
|
|
3791
|
+
: cmd.elements;
|
|
1659
3792
|
const isNewMap = new Map();
|
|
1660
|
-
for (const [id, element] of Object.entries(
|
|
3793
|
+
for (const [id, element] of Object.entries(incomingElements)) {
|
|
1661
3794
|
isNewMap.set(id, !schema.elements[id]);
|
|
1662
|
-
schema.elements
|
|
3795
|
+
writeProperty(schema.elements, id, element, journal);
|
|
1663
3796
|
}
|
|
1664
|
-
for (const [id, element] of Object.entries(
|
|
3797
|
+
for (const [id, element] of Object.entries(incomingElements)) {
|
|
1665
3798
|
const parentId = this.findParentId(schema, id);
|
|
1666
3799
|
const index = parentId ? this.findChildIndex(schema, parentId, id) : undefined;
|
|
1667
3800
|
changes.push({
|
|
@@ -1682,28 +3815,94 @@ class StreamingEngine {
|
|
|
1682
3815
|
elementId: id,
|
|
1683
3816
|
parentId: this.findParentId(schema, id),
|
|
1684
3817
|
});
|
|
1685
|
-
|
|
3818
|
+
deleteProperty(schema.elements, id, journal);
|
|
1686
3819
|
// Also remove from any parent's children lists
|
|
1687
|
-
this.removeFromParentSlots(schema, id);
|
|
3820
|
+
this.removeFromParentSlots(schema, id, journal);
|
|
1688
3821
|
}
|
|
1689
3822
|
}
|
|
1690
3823
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
3824
|
+
return changes;
|
|
3825
|
+
}
|
|
3826
|
+
commandMayIntroduceBinding(cmd) {
|
|
3827
|
+
if (!cmd.elements || Object.keys(cmd.elements).length === 0)
|
|
3828
|
+
return false;
|
|
3829
|
+
return requiresBindingMaterialization({
|
|
3830
|
+
version: '1.0',
|
|
3831
|
+
rootID: cmd.rootID ?? 'root',
|
|
3832
|
+
elements: cmd.elements,
|
|
3833
|
+
variables: {},
|
|
3834
|
+
});
|
|
1694
3835
|
}
|
|
1695
3836
|
handleUpdateDataModel(cmd) {
|
|
1696
3837
|
const schema = this.surfaces.get(cmd.surfaceId);
|
|
1697
3838
|
if (!schema)
|
|
1698
3839
|
return;
|
|
1699
|
-
|
|
1700
|
-
|
|
3840
|
+
if (!requiresBindingMaterialization(schema)) {
|
|
3841
|
+
// Preserve legacy mutation and notification behavior on static cards.
|
|
3842
|
+
// A2UI commands remain strict even when the current surface has no
|
|
3843
|
+
// binding-bearing component.
|
|
3844
|
+
const journal = [];
|
|
3845
|
+
try {
|
|
3846
|
+
this.applyDataModelWrite(schema.variables, cmd, journal);
|
|
3847
|
+
this.emitDataModelUpdated(cmd);
|
|
3848
|
+
}
|
|
3849
|
+
catch (error) {
|
|
3850
|
+
rollbackMutations(journal);
|
|
3851
|
+
throw error;
|
|
3852
|
+
}
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3855
|
+
const candidate = cloneStreamingValue(schema);
|
|
3856
|
+
const candidateCommand = {
|
|
3857
|
+
...cmd,
|
|
3858
|
+
value: cloneStreamingValue(cmd.value),
|
|
3859
|
+
};
|
|
3860
|
+
if (!this.applyDataModelWrite(candidate.variables, candidateCommand))
|
|
3861
|
+
return;
|
|
3862
|
+
this.surfaces.set(cmd.surfaceId, candidate);
|
|
3863
|
+
try {
|
|
3864
|
+
this.emitDataModelUpdated(cmd);
|
|
3865
|
+
}
|
|
3866
|
+
catch (error) {
|
|
3867
|
+
this.surfaces.set(cmd.surfaceId, schema);
|
|
3868
|
+
throw error;
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
applyDataModelWrite(variables, cmd, journal) {
|
|
3872
|
+
if (cmd.pathDialect === 'a2ui') {
|
|
3873
|
+
if (journal) {
|
|
3874
|
+
setByJsonPointerWithMutationRecorder(variables, cmd.path, cmd.value, (target, key) => {
|
|
3875
|
+
recordPropertyMutation(target, key, journal);
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
else {
|
|
3879
|
+
setByJsonPointer(variables, cmd.path, cmd.value);
|
|
3880
|
+
}
|
|
3881
|
+
return true;
|
|
3882
|
+
}
|
|
3883
|
+
return setByLegacyPath(variables, cmd.path, cmd.value, journal);
|
|
3884
|
+
}
|
|
3885
|
+
emitDataModelUpdated(cmd) {
|
|
3886
|
+
if (cmd.pathDialect !== undefined) {
|
|
3887
|
+
this.listeners.onDataModelUpdated(cmd.surfaceId, cmd.path, cmd.value, cmd.pathDialect);
|
|
3888
|
+
return;
|
|
3889
|
+
}
|
|
1701
3890
|
this.listeners.onDataModelUpdated(cmd.surfaceId, cmd.path, cmd.value);
|
|
1702
3891
|
}
|
|
1703
3892
|
handleAppendContent(cmd) {
|
|
1704
3893
|
const schema = this.surfaces.get(cmd.surfaceId);
|
|
1705
3894
|
if (!schema)
|
|
1706
3895
|
return;
|
|
3896
|
+
const isBoundSurface = requiresBindingMaterialization(schema);
|
|
3897
|
+
const isUnknownRuntimeId = (cmd.elementId.startsWith('r_')
|
|
3898
|
+
&& !Object.prototype.hasOwnProperty.call(schema.elements, cmd.elementId));
|
|
3899
|
+
if (isBoundSurface
|
|
3900
|
+
&& (isUnknownRuntimeId
|
|
3901
|
+
|| this.dynamicClosureSourceIds(schema).has(cmd.elementId))) {
|
|
3902
|
+
console.warn(`[StreamingEngine] Rejected appendContent target "${cmd.elementId}" `
|
|
3903
|
+
+ 'because dynamic instances must be updated through their data model');
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
1707
3906
|
// Update internal schema state
|
|
1708
3907
|
const element = schema.elements[cmd.elementId];
|
|
1709
3908
|
if (element) {
|
|
@@ -1766,6 +3965,10 @@ class StreamingEngine {
|
|
|
1766
3965
|
return true;
|
|
1767
3966
|
if (slot.groups?.some(group => group.includes(childId)))
|
|
1768
3967
|
return true;
|
|
3968
|
+
if (slot.repeat?.template === childId)
|
|
3969
|
+
return true;
|
|
3970
|
+
if (getA2UIChildBinding(slot)?.templateId === childId)
|
|
3971
|
+
return true;
|
|
1769
3972
|
if (slot.config?.overlays) {
|
|
1770
3973
|
for (const overlay of slot.config.overlays) {
|
|
1771
3974
|
if (overlay.children?.includes(childId))
|
|
@@ -1778,7 +3981,7 @@ class StreamingEngine {
|
|
|
1778
3981
|
/**
|
|
1779
3982
|
* Remove a child ID from all parent element slot references.
|
|
1780
3983
|
*/
|
|
1781
|
-
removeFromParentSlots(schema, childId) {
|
|
3984
|
+
removeFromParentSlots(schema, childId, journal) {
|
|
1782
3985
|
for (const element of Object.values(schema.elements)) {
|
|
1783
3986
|
if (!element.props.slots)
|
|
1784
3987
|
continue;
|
|
@@ -1786,17 +3989,84 @@ class StreamingEngine {
|
|
|
1786
3989
|
if (slot.children) {
|
|
1787
3990
|
const idx = slot.children.indexOf(childId);
|
|
1788
3991
|
if (idx >= 0)
|
|
1789
|
-
slot.children
|
|
3992
|
+
spliceOne(slot.children, idx, journal);
|
|
1790
3993
|
}
|
|
1791
3994
|
if (slot.groups) {
|
|
1792
3995
|
for (const group of slot.groups) {
|
|
1793
3996
|
const idx = group.indexOf(childId);
|
|
1794
3997
|
if (idx >= 0)
|
|
1795
|
-
group
|
|
3998
|
+
spliceOne(group, idx, journal);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
if (slot.repeat?.template === childId) {
|
|
4002
|
+
deleteProperty(slot, 'repeat', journal);
|
|
4003
|
+
}
|
|
4004
|
+
if (getA2UIChildBinding(slot)?.templateId === childId) {
|
|
4005
|
+
deleteProperty(slot, A2UI_CHILD_BINDING, journal);
|
|
4006
|
+
if (!this.elementStillUsesA2UIBinding(element)) {
|
|
4007
|
+
deleteProperty(element, A2UI_BINDING_DIALECT, journal);
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
elementStillUsesA2UIBinding(element) {
|
|
4014
|
+
if (Object.values(element.props.slots ?? {}).some(slot => getA2UIChildBinding(slot) !== undefined)) {
|
|
4015
|
+
return true;
|
|
4016
|
+
}
|
|
4017
|
+
return containsA2UIPathBinding(element.props)
|
|
4018
|
+
|| containsA2UIPathBinding(element.directives)
|
|
4019
|
+
|| containsA2UIPathBinding(element.events)
|
|
4020
|
+
|| containsA2UIPathBinding(element.lifecycle);
|
|
4021
|
+
}
|
|
4022
|
+
/**
|
|
4023
|
+
* Source definitions inside a dynamic template closure have no unique
|
|
4024
|
+
* runtime address. Token appends must therefore target their data model,
|
|
4025
|
+
* not the shared source definition.
|
|
4026
|
+
*/
|
|
4027
|
+
dynamicClosureSourceIds(schema) {
|
|
4028
|
+
const dynamicSources = new Set();
|
|
4029
|
+
const pending = [];
|
|
4030
|
+
for (const element of Object.values(schema.elements)) {
|
|
4031
|
+
for (const slot of Object.values(element.props.slots ?? {})) {
|
|
4032
|
+
if (typeof slot.repeat?.template === 'string') {
|
|
4033
|
+
pending.push(slot.repeat.template);
|
|
4034
|
+
}
|
|
4035
|
+
const a2ui = getA2UIChildBinding(slot);
|
|
4036
|
+
if (typeof a2ui?.templateId === 'string') {
|
|
4037
|
+
pending.push(a2ui.templateId);
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
while (pending.length > 0) {
|
|
4042
|
+
const sourceId = pending.pop();
|
|
4043
|
+
if (dynamicSources.has(sourceId))
|
|
4044
|
+
continue;
|
|
4045
|
+
dynamicSources.add(sourceId);
|
|
4046
|
+
const source = schema.elements[sourceId];
|
|
4047
|
+
if (!source)
|
|
4048
|
+
continue;
|
|
4049
|
+
for (const slot of Object.values(source.props.slots ?? {})) {
|
|
4050
|
+
pending.push(...(slot.children ?? []));
|
|
4051
|
+
for (const group of slot.groups ?? [])
|
|
4052
|
+
pending.push(...group);
|
|
4053
|
+
if (Array.isArray(slot.config?.overlays)) {
|
|
4054
|
+
for (const overlay of slot.config.overlays) {
|
|
4055
|
+
if (Array.isArray(overlay?.children)) {
|
|
4056
|
+
pending.push(...overlay.children);
|
|
4057
|
+
}
|
|
1796
4058
|
}
|
|
1797
4059
|
}
|
|
4060
|
+
if (typeof slot.repeat?.template === 'string') {
|
|
4061
|
+
pending.push(slot.repeat.template);
|
|
4062
|
+
}
|
|
4063
|
+
const a2ui = getA2UIChildBinding(slot);
|
|
4064
|
+
if (typeof a2ui?.templateId === 'string') {
|
|
4065
|
+
pending.push(a2ui.templateId);
|
|
4066
|
+
}
|
|
1798
4067
|
}
|
|
1799
4068
|
}
|
|
4069
|
+
return dynamicSources;
|
|
1800
4070
|
}
|
|
1801
4071
|
}
|
|
1802
4072
|
// ─── Utility Functions ────────────────────────────────────────────
|
|
@@ -1809,21 +4079,22 @@ class StreamingEngine {
|
|
|
1809
4079
|
*/
|
|
1810
4080
|
const UNSAFE_PATH_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
1811
4081
|
/** Merge only own, non-dangerous keys of `src` into `target`. */
|
|
1812
|
-
function safeMerge(target, src) {
|
|
4082
|
+
function safeMerge(target, src, journal) {
|
|
1813
4083
|
for (const key of Object.keys(src)) {
|
|
1814
4084
|
if (UNSAFE_PATH_KEYS.has(key))
|
|
1815
4085
|
continue;
|
|
1816
|
-
target
|
|
4086
|
+
writeProperty(target, key, src[key], journal);
|
|
1817
4087
|
}
|
|
1818
4088
|
}
|
|
1819
4089
|
/**
|
|
1820
4090
|
* Set a value at a JSON Pointer-like path in an object.
|
|
1821
4091
|
*
|
|
1822
4092
|
* Path format: '/key1/key2/key3' → obj.key1.key2.key3 = value
|
|
1823
|
-
*
|
|
4093
|
+
* Legacy root path '/' merges the value's own safe fields into the object.
|
|
1824
4094
|
*
|
|
1825
4095
|
* Prototype-polluting segments (`__proto__` / `constructor` / `prototype`) are
|
|
1826
|
-
* rejected — the whole write is dropped rather than silently
|
|
4096
|
+
* rejected — the whole write is dropped rather than silently retargeting it.
|
|
4097
|
+
* Other assignments preserve the legacy writer's direct-reference behavior.
|
|
1827
4098
|
*
|
|
1828
4099
|
* @example
|
|
1829
4100
|
* ```ts
|
|
@@ -1833,29 +4104,36 @@ function safeMerge(target, src) {
|
|
|
1833
4104
|
* ```
|
|
1834
4105
|
*/
|
|
1835
4106
|
function setByPath(obj, path, value) {
|
|
1836
|
-
|
|
4107
|
+
setByLegacyPath(obj, path, value);
|
|
4108
|
+
}
|
|
4109
|
+
function setByLegacyPath(obj, path, value, journal) {
|
|
4110
|
+
// Preserve the legacy dialect: `/` (and any other all-empty spelling)
|
|
4111
|
+
// means a root merge, paths may omit the leading slash, and empty segments
|
|
4112
|
+
// are ignored.
|
|
1837
4113
|
const parts = path.replace(/^\//, '').split('/').filter(Boolean);
|
|
1838
|
-
//
|
|
1839
|
-
|
|
4114
|
+
// Preserve the existing prototype-pollution guard without changing the
|
|
4115
|
+
// historical writer's reference, accessor, or sparse-array semantics.
|
|
4116
|
+
if (parts.some(part => UNSAFE_PATH_KEYS.has(part))) {
|
|
1840
4117
|
console.warn(`[StreamingEngine] Rejected unsafe data-model path "${path}"`);
|
|
1841
|
-
return;
|
|
4118
|
+
return false;
|
|
1842
4119
|
}
|
|
1843
4120
|
if (parts.length === 0) {
|
|
1844
4121
|
// Root-level update: merge value into obj (own, safe keys only)
|
|
1845
4122
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1846
|
-
safeMerge(obj, value);
|
|
4123
|
+
safeMerge(obj, value, journal);
|
|
1847
4124
|
}
|
|
1848
|
-
return;
|
|
4125
|
+
return true;
|
|
1849
4126
|
}
|
|
1850
4127
|
let current = obj;
|
|
1851
|
-
for (let
|
|
1852
|
-
const key = parts[
|
|
4128
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
4129
|
+
const key = parts[index];
|
|
1853
4130
|
if (current[key] === undefined || current[key] === null) {
|
|
1854
|
-
current
|
|
4131
|
+
writeProperty(current, key, {}, journal);
|
|
1855
4132
|
}
|
|
1856
4133
|
current = current[key];
|
|
1857
4134
|
}
|
|
1858
|
-
current
|
|
4135
|
+
writeProperty(current, parts[parts.length - 1], value, journal);
|
|
4136
|
+
return true;
|
|
1859
4137
|
}
|
|
1860
4138
|
|
|
1861
4139
|
/**
|
|
@@ -2147,6 +4425,14 @@ const BUILTIN_ICONS = Object.freeze({
|
|
|
2147
4425
|
viewBox: "0 0 32 32",
|
|
2148
4426
|
body: "<path d=\"M17.5 16C17.5 16.2967 17.412 16.5867 17.2472 16.8334C17.0824 17.0801 16.8481 17.2723 16.574 17.3859C16.2999 17.4994 15.9983 17.5291 15.7074 17.4712C15.4164 17.4133 15.1491 17.2705 14.9393 17.0607C14.7296 16.8509 14.5867 16.5836 14.5288 16.2927C14.4709 16.0017 14.5006 15.7001 14.6142 15.426C14.7277 15.1519 14.92 14.9177 15.1666 14.7528C15.4133 14.588 15.7033 14.5 16 14.5C16.3978 14.5 16.7794 14.6581 17.0607 14.9394C17.342 15.2207 17.5 15.6022 17.5 16ZM10.5 14.5C10.2033 14.5 9.91332 14.588 9.66664 14.7528C9.41997 14.9177 9.22771 15.1519 9.11418 15.426C9.00065 15.7001 8.97094 16.0017 9.02882 16.2927C9.0867 16.5836 9.22956 16.8509 9.43934 17.0607C9.64912 17.2705 9.91639 17.4133 10.2074 17.4712C10.4983 17.5291 10.7999 17.4994 11.074 17.3859C11.3481 17.2723 11.5824 17.0801 11.7472 16.8334C11.912 16.5867 12 16.2967 12 16C12 15.6022 11.842 15.2207 11.5607 14.9394C11.2794 14.6581 10.8978 14.5 10.5 14.5ZM21.5 14.5C21.2033 14.5 20.9133 14.588 20.6666 14.7528C20.42 14.9177 20.2277 15.1519 20.1142 15.426C20.0007 15.7001 19.9709 16.0017 20.0288 16.2927C20.0867 16.5836 20.2296 16.8509 20.4393 17.0607C20.6491 17.2705 20.9164 17.4133 21.2074 17.4712C21.4983 17.5291 21.7999 17.4994 22.074 17.3859C22.3481 17.2723 22.5824 17.0801 22.7472 16.8334C22.912 16.5867 23 16.2967 23 16C23 15.6022 22.842 15.2207 22.5607 14.9394C22.2794 14.6581 21.8978 14.5 21.5 14.5ZM29 16C29.0005 18.2445 28.4199 20.4508 27.3147 22.4042C26.2095 24.3577 24.6174 25.9917 22.6934 27.1473C20.7693 28.3029 18.5788 28.9407 16.3352 28.9986C14.0915 29.0564 11.8711 28.5324 9.89 27.4775L5.63375 28.8963C5.28136 29.0138 4.9032 29.0309 4.54166 28.9455C4.18012 28.8602 3.84948 28.6759 3.58681 28.4132C3.32414 28.1506 3.13982 27.8199 3.0545 27.4584C2.96918 27.0968 2.98623 26.7187 3.10375 26.3663L4.5225 22.11C3.59519 20.3666 3.07725 18.4348 3.008 16.4613C2.93875 14.4877 3.32001 12.5244 4.12284 10.7202C4.92567 8.91604 6.12897 7.31847 7.6414 6.04878C9.15383 4.77909 10.9356 3.87063 12.8516 3.39238C14.7675 2.91413 16.7672 2.87865 18.699 3.28862C20.6307 3.6986 22.4436 4.54327 24.0001 5.7585C25.5566 6.97374 26.8158 8.52761 27.6822 10.3022C28.5485 12.0767 28.9992 14.0253 29 16ZM27 16C26.9995 14.3127 26.6109 12.6481 25.8641 11.135C25.1174 9.62186 24.0325 8.30083 22.6935 7.27408C21.3545 6.24733 19.7973 5.54238 18.1422 5.21377C16.4872 4.88517 14.7787 4.94171 13.149 5.37904C11.5194 5.81636 10.0121 6.62274 8.74394 7.73578C7.47577 8.84882 6.48065 10.2387 5.83558 11.7979C5.1905 13.357 4.91277 15.0437 5.02387 16.7274C5.13496 18.4111 5.63191 20.0466 6.47625 21.5075C6.54712 21.6302 6.59112 21.7665 6.60534 21.9074C6.61956 22.0484 6.60368 22.1907 6.55875 22.325L5 27L9.675 25.4413C9.77683 25.4066 9.88367 25.3888 9.99125 25.3888C10.1669 25.3891 10.3393 25.4357 10.4912 25.5238C12.1635 26.4913 14.0611 27.0013 15.9931 27.0026C17.925 27.0038 19.8232 26.4961 21.4967 25.5307C23.1702 24.5653 24.5599 23.1762 25.526 21.5031C26.492 19.83 27.0004 17.932 27 16Z\" fill=\"currentColor\" fill-rule=\"evenodd\"></path>",
|
|
2149
4427
|
}),
|
|
4428
|
+
"check": Object.freeze({
|
|
4429
|
+
viewBox: "0 0 32 32",
|
|
4430
|
+
body: "<path d=\"M27.7071 8.29289C28.0976 8.68342 28.0976 9.31658 27.7071 9.70711L13.7071 23.7071C13.3166 24.0976 12.6834 24.0976 12.2929 23.7071L5.29289 16.7071C4.90237 16.3166 4.90237 15.6834 5.29289 15.2929C5.68342 14.9024 6.31658 14.9024 6.70711 15.2929L13 21.5858L26.2929 8.29289C26.6834 7.90237 27.3166 7.90237 27.7071 8.29289Z\" fill=\"currentColor\" fill-rule=\"evenodd\"></path>",
|
|
4431
|
+
}),
|
|
4432
|
+
"check_bold": Object.freeze({
|
|
4433
|
+
viewBox: "0 0 32 32",
|
|
4434
|
+
body: "<path d=\"M26.66453 8L11.99933 22.6656L5.33333 15.99941\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"5.33333\" stroke-linecap=\"round\"></path>",
|
|
4435
|
+
}),
|
|
2150
4436
|
"check_circle": Object.freeze({
|
|
2151
4437
|
viewBox: "0 0 32 32",
|
|
2152
4438
|
body: "<path d=\"M21.7075 12.2925C21.8005 12.3854 21.8742 12.4957 21.9246 12.6171C21.9749 12.7385 22.0008 12.8686 22.0008 13C22.0008 13.1314 21.9749 13.2615 21.9246 13.3829C21.8742 13.5043 21.8005 13.6146 21.7075 13.7075L14.7075 20.7075C14.6146 20.8005 14.5043 20.8742 14.3829 20.9246C14.2615 20.9749 14.1314 21.0008 14 21.0008C13.8686 21.0008 13.7385 20.9749 13.6171 20.9246C13.4957 20.8742 13.3854 20.8005 13.2925 20.7075L10.2925 17.7075C10.1049 17.5199 9.99945 17.2654 9.99945 17C9.99945 16.7346 10.1049 16.4801 10.2925 16.2925C10.4801 16.1049 10.7346 15.9994 11 15.9994C11.2654 15.9994 11.5199 16.1049 11.7075 16.2925L14 18.5863L20.2925 12.2925C20.3854 12.1995 20.4957 12.1258 20.6171 12.0754C20.7385 12.0251 20.8686 11.9992 21 11.9992C21.1314 11.9992 21.2615 12.0251 21.3829 12.0754C21.5043 12.1258 21.6146 12.1995 21.7075 12.2925ZM29 16C29 18.5712 28.2376 21.0846 26.8091 23.2224C25.3807 25.3603 23.3503 27.0265 20.9749 28.0104C18.5995 28.9944 15.9856 29.2518 13.4638 28.7502C10.9421 28.2486 8.6257 27.0105 6.80762 25.1924C4.98953 23.3743 3.75141 21.0579 3.2498 18.5362C2.74819 16.0144 3.00563 13.4006 3.98957 11.0251C4.97351 8.64968 6.63975 6.61935 8.77759 5.1909C10.9154 3.76244 13.4288 3 16 3C19.4467 3.00364 22.7512 4.37445 25.1884 6.81163C27.6256 9.24882 28.9964 12.5533 29 16ZM27 16C27 13.8244 26.3549 11.6977 25.1462 9.88873C23.9375 8.07979 22.2195 6.66989 20.2095 5.83733C18.1995 5.00476 15.9878 4.78692 13.854 5.21136C11.7202 5.6358 9.76021 6.68345 8.22183 8.22183C6.68345 9.7602 5.63581 11.7202 5.21137 13.854C4.78693 15.9878 5.00477 18.1995 5.83733 20.2095C6.66989 22.2195 8.07979 23.9375 9.88873 25.1462C11.6977 26.3549 13.8244 27 16 27C18.9164 26.9967 21.7123 25.8367 23.7745 23.7745C25.8367 21.7123 26.9967 18.9164 27 16Z\" fill=\"currentColor\" fill-rule=\"evenodd\"></path>",
|
|
@@ -2468,30 +4754,51 @@ function getBuiltinIcon(name) {
|
|
|
2468
4754
|
exports.ActionRegistry = ActionRegistry;
|
|
2469
4755
|
exports.BUILTIN_ICONS = BUILTIN_ICONS;
|
|
2470
4756
|
exports.BUILTIN_ICON_NAMES = BUILTIN_ICON_NAMES;
|
|
4757
|
+
exports.JsonPointerPathError = JsonPointerPathError;
|
|
2471
4758
|
exports.LifecycleManager = LifecycleManager;
|
|
2472
4759
|
exports.StreamingEngine = StreamingEngine;
|
|
2473
4760
|
exports.StreamingParser = StreamingParser;
|
|
2474
4761
|
exports.a2uiComponentToElement = a2uiComponentToElement;
|
|
2475
4762
|
exports.a2uiToCommand = a2uiToCommand;
|
|
4763
|
+
exports.bindingTopologyFingerprint = bindingTopologyFingerprint;
|
|
4764
|
+
exports.cloneJsonData = cloneJsonData;
|
|
2476
4765
|
exports.convertLegacySchema = convertLegacySchema;
|
|
4766
|
+
exports.createA2UIParameterResolver = createA2UIParameterResolver;
|
|
4767
|
+
exports.createExpressionContext = createExpressionContext;
|
|
2477
4768
|
exports.createLifecycleManager = createLifecycleManager;
|
|
4769
|
+
exports.decodeJsonPointerSegment = decodeJsonPointerSegment;
|
|
2478
4770
|
exports.extractPartialSchema = extractPartialSchema;
|
|
4771
|
+
exports.findAffectedRepeatOwners = findAffectedRepeatOwners;
|
|
4772
|
+
exports.findTemplateRepeatOwners = findTemplateRepeatOwners;
|
|
2479
4773
|
exports.getBuiltinIcon = getBuiltinIcon;
|
|
4774
|
+
exports.getByJsonPointer = getByJsonPointer;
|
|
2480
4775
|
exports.getByPath = getByPath$1;
|
|
4776
|
+
exports.hasDynamicChildren = hasDynamicChildren;
|
|
2481
4777
|
exports.hasExpression = hasExpression;
|
|
2482
4778
|
exports.interpolate = interpolate;
|
|
4779
|
+
exports.isA2UIChildList = isA2UIChildList;
|
|
4780
|
+
exports.isA2UIDynamicChildList = isA2UIDynamicChildList;
|
|
2483
4781
|
exports.isA2UIEnvelope = isA2UIEnvelope;
|
|
4782
|
+
exports.isA2UIPathBinding = isA2UIPathBinding;
|
|
4783
|
+
exports.isBoundRenderTreeNode = isBoundRenderTreeNode;
|
|
2484
4784
|
exports.isLegacySchema = isLegacySchema;
|
|
4785
|
+
exports.materializeCard = materializeCard;
|
|
2485
4786
|
exports.normalizeSchema = normalizeSchema;
|
|
4787
|
+
exports.parseJsonPointer = parseJsonPointer;
|
|
2486
4788
|
exports.parseSchema = parseSchema;
|
|
2487
4789
|
exports.registerActionHandler = registerActionHandler;
|
|
2488
4790
|
exports.registry = registry;
|
|
4791
|
+
exports.replaceRootContents = replaceRootContents;
|
|
4792
|
+
exports.requiresBindingMaterialization = requiresBindingMaterialization;
|
|
2489
4793
|
exports.resetIdCounter = resetIdCounter;
|
|
4794
|
+
exports.resolveA2UIDeep = resolveA2UIDeep;
|
|
4795
|
+
exports.resolveA2UIPath = resolveA2UIPath;
|
|
2490
4796
|
exports.resolveActionRef = resolveActionRef;
|
|
2491
4797
|
exports.resolveDeep = resolveDeep;
|
|
2492
4798
|
exports.resolveExpression = resolveExpression;
|
|
2493
4799
|
exports.resolveExpressionValue = resolveExpressionValue;
|
|
2494
4800
|
exports.runActionStep = runActionStep;
|
|
2495
4801
|
exports.runActionSteps = runActionSteps;
|
|
4802
|
+
exports.setByJsonPointer = setByJsonPointer;
|
|
2496
4803
|
exports.setByPath = setByPath;
|
|
2497
4804
|
exports.validateSchema = validateSchema;
|