@ripplo/testing 0.7.23 → 0.7.25
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.js +1686 -1688
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1054,1890 +1054,1888 @@ function collectValueSpaces(stateName, builders) {
|
|
|
1054
1054
|
}));
|
|
1055
1055
|
}
|
|
1056
1056
|
|
|
1057
|
-
// src/
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
var
|
|
1075
|
-
var
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
var
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
var
|
|
1086
|
-
var
|
|
1087
|
-
var
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
var
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
var
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
var
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
var
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
is: isPredicate
|
|
1150
|
-
})
|
|
1151
|
-
};
|
|
1152
|
-
function isPredicate(value2) {
|
|
1153
|
-
return condLeaf({
|
|
1154
|
-
assertion: { kind: "is", value: value2 },
|
|
1155
|
-
kind: "singleton",
|
|
1156
|
-
singleton: name,
|
|
1157
|
-
wait: void 0
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// src/builtins.ts
|
|
1163
|
-
function browserSingleton(name) {
|
|
1164
|
-
return {
|
|
1165
|
-
name,
|
|
1166
|
-
is: (strings, ...values) => leaf({
|
|
1167
|
-
kind: "browser",
|
|
1168
|
-
name,
|
|
1169
|
-
value: stringValueFromTemplate(strings, values),
|
|
1170
|
-
wait: void 0
|
|
1171
|
-
})
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
var url = browserSingleton("url");
|
|
1175
|
-
var title = browserSingleton("title");
|
|
1176
|
-
var viewport = browserSingleton("viewport");
|
|
1177
|
-
|
|
1178
|
-
// src/actions.ts
|
|
1179
|
-
function goto(strings, ...values) {
|
|
1180
|
-
return stepBuilder({ kind: "goto", url: stringValueFromTemplate(strings, values) }, [], []);
|
|
1181
|
-
}
|
|
1182
|
-
function click(locator) {
|
|
1183
|
-
return stepBuilder({ kind: "click", locator }, [], []);
|
|
1184
|
-
}
|
|
1185
|
-
function dblclick(locator) {
|
|
1186
|
-
return stepBuilder({ kind: "dblclick", locator }, [], []);
|
|
1187
|
-
}
|
|
1188
|
-
function fill(locator, binding) {
|
|
1189
|
-
return stepBuilder({ kind: "fill", locator, value: binding }, [], []);
|
|
1190
|
-
}
|
|
1191
|
-
function clear(locator) {
|
|
1192
|
-
return stepBuilder({ kind: "clear", locator }, [], []);
|
|
1193
|
-
}
|
|
1194
|
-
function select(locator, binding) {
|
|
1195
|
-
return stepBuilder({ kind: "select", locator, value: binding }, [], []);
|
|
1196
|
-
}
|
|
1197
|
-
function check(locator) {
|
|
1198
|
-
return stepBuilder({ kind: "check", locator }, [], []);
|
|
1199
|
-
}
|
|
1200
|
-
function uncheck(locator) {
|
|
1201
|
-
return stepBuilder({ kind: "uncheck", locator }, [], []);
|
|
1202
|
-
}
|
|
1203
|
-
function hover(locator) {
|
|
1204
|
-
return stepBuilder({ kind: "hover", locator }, [], []);
|
|
1205
|
-
}
|
|
1206
|
-
function upload(locator, files) {
|
|
1207
|
-
return stepBuilder({ files: [...files], kind: "upload", locator }, [], []);
|
|
1208
|
-
}
|
|
1209
|
-
function press(pressKey, locator) {
|
|
1210
|
-
return stepBuilder({ key: pressKey, kind: "press", locator }, [], []);
|
|
1211
|
-
}
|
|
1212
|
-
function stepBuilder(action, expected, captures) {
|
|
1213
|
-
return {
|
|
1214
|
-
captures,
|
|
1215
|
-
step: { action, expect: [...expected] },
|
|
1216
|
-
expect: (...predicates) => stepBuilder(
|
|
1217
|
-
action,
|
|
1218
|
-
[...expected, ...predicates.map((p) => toPredicate(p))],
|
|
1219
|
-
[...captures, ...predicates.flatMap((p) => isCapturePredicate(p) ? [captureOf(p)] : [])]
|
|
1220
|
-
)
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// src/coherence.ts
|
|
1225
|
-
function assertEntityCoherence(entities) {
|
|
1226
|
-
const counts = entities.reduce(
|
|
1227
|
-
(acc, d) => new Map([...acc, [d.entity, (acc.get(d.entity) ?? 0) + 1]]),
|
|
1228
|
-
/* @__PURE__ */ new Map()
|
|
1229
|
-
);
|
|
1230
|
-
const over = entities.find((d) => d.kind === "only" && (counts.get(d.entity) ?? 0) > 1);
|
|
1231
|
-
if (over != null) {
|
|
1232
|
-
throw new Error(
|
|
1233
|
-
`entity "${over.entity}" is declared only() but seeded ${String(counts.get(over.entity) ?? 0)} times \u2014 only() means a single exclusive instance; use of() for multiple instances`
|
|
1234
|
-
);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1057
|
+
// ../spec/src/leaves.ts
|
|
1058
|
+
import { z } from "zod";
|
|
1059
|
+
var budgetSchema = z.enum(["fast", "slow", "async"]);
|
|
1060
|
+
var valueRefSchema = z.object({ ref: z.string().min(1) });
|
|
1061
|
+
var primitiveSchema = z.union([z.string(), z.number(), z.boolean()]);
|
|
1062
|
+
var templateSchema = z.object({
|
|
1063
|
+
template: z.array(z.union([z.string(), valueRefSchema])).min(1)
|
|
1064
|
+
});
|
|
1065
|
+
var setValueSchema = z.union([valueRefSchema, primitiveSchema, templateSchema, z.null()]);
|
|
1066
|
+
var changedSchema = z.object({ kind: z.literal("changed") });
|
|
1067
|
+
var updateValueSchema = z.union([setValueSchema, changedSchema]);
|
|
1068
|
+
var stringValueSchema = z.union([z.string(), valueRefSchema, templateSchema]);
|
|
1069
|
+
var roleLocatorSchema = z.object({
|
|
1070
|
+
by: z.literal("role"),
|
|
1071
|
+
name: z.union([stringValueSchema, z.undefined()]).optional().transform((value2) => value2),
|
|
1072
|
+
role: z.string().min(1)
|
|
1073
|
+
});
|
|
1074
|
+
var testIdLocatorSchema = z.object({ by: z.literal("testId"), value: stringValueSchema });
|
|
1075
|
+
var insideLocatorSchema = z.object({
|
|
1076
|
+
by: z.literal("inside"),
|
|
1077
|
+
scope: z.lazy(() => locatorSchema),
|
|
1078
|
+
target: z.lazy(() => locatorSchema)
|
|
1079
|
+
});
|
|
1080
|
+
var locatorSchema = z.discriminatedUnion("by", [
|
|
1081
|
+
roleLocatorSchema,
|
|
1082
|
+
testIdLocatorSchema,
|
|
1083
|
+
insideLocatorSchema
|
|
1084
|
+
]);
|
|
1085
|
+
var primitiveTypeSchema = z.enum(["string", "number", "boolean"]);
|
|
1086
|
+
var consistencyClassSchema = z.enum(["strict", "eventual"]);
|
|
1087
|
+
var stringConstraintsSchema = z.object({
|
|
1088
|
+
kind: z.literal("string"),
|
|
1089
|
+
maxLength: z.number().int().positive().optional(),
|
|
1090
|
+
minLength: z.number().int().nonnegative().optional(),
|
|
1091
|
+
pattern: z.string().optional()
|
|
1092
|
+
});
|
|
1093
|
+
var numberConstraintsSchema = z.object({
|
|
1094
|
+
kind: z.literal("number"),
|
|
1095
|
+
max: z.number().int().optional(),
|
|
1096
|
+
min: z.number().int().optional()
|
|
1097
|
+
});
|
|
1098
|
+
var datetimeConstraintsSchema = z.object({
|
|
1099
|
+
kind: z.literal("datetime"),
|
|
1100
|
+
maxOffsetDays: z.number().int(),
|
|
1101
|
+
minOffsetDays: z.number().int()
|
|
1102
|
+
});
|
|
1103
|
+
var constraintsSchema = z.discriminatedUnion("kind", [
|
|
1104
|
+
stringConstraintsSchema,
|
|
1105
|
+
numberConstraintsSchema,
|
|
1106
|
+
datetimeConstraintsSchema
|
|
1107
|
+
]);
|
|
1108
|
+
var generatorSchema = z.enum([
|
|
1109
|
+
"company.name",
|
|
1110
|
+
"date.iso",
|
|
1111
|
+
"internet.email",
|
|
1112
|
+
"internet.url",
|
|
1113
|
+
"person.fullName",
|
|
1114
|
+
"lorem.slug",
|
|
1115
|
+
"lorem.word"
|
|
1116
|
+
]);
|
|
1117
|
+
var valueSpaceSchema = z.object({
|
|
1118
|
+
constraints: constraintsSchema.optional(),
|
|
1119
|
+
generator: generatorSchema,
|
|
1120
|
+
name: z.string().min(1),
|
|
1121
|
+
type: primitiveTypeSchema,
|
|
1122
|
+
values: z.array(primitiveSchema).min(1).optional()
|
|
1123
|
+
});
|
|
1124
|
+
var propSpecSchema = z.object({
|
|
1125
|
+
consistency: consistencyClassSchema.default("strict"),
|
|
1126
|
+
optional: z.boolean(),
|
|
1127
|
+
type: primitiveTypeSchema,
|
|
1128
|
+
valueSpace: z.string().min(1).optional()
|
|
1129
|
+
});
|
|
1130
|
+
var sourceSchema = z.enum(["backend", "client"]);
|
|
1131
|
+
var entitySchemaSchema = z.object({
|
|
1132
|
+
description: z.string().optional(),
|
|
1133
|
+
identity: z.array(z.string().min(1)).min(1),
|
|
1134
|
+
identityKind: z.enum(["surrogate", "natural"]),
|
|
1135
|
+
name: z.string().min(1),
|
|
1136
|
+
props: z.record(z.string().min(1), propSpecSchema),
|
|
1137
|
+
source: sourceSchema.default("backend")
|
|
1138
|
+
});
|
|
1139
|
+
var singletonSchemaSchema = z.object({
|
|
1140
|
+
consistency: consistencyClassSchema.default("strict"),
|
|
1141
|
+
default: primitiveSchema,
|
|
1142
|
+
description: z.string().optional(),
|
|
1143
|
+
name: z.string().min(1),
|
|
1144
|
+
source: sourceSchema.default("backend"),
|
|
1145
|
+
type: primitiveTypeSchema,
|
|
1146
|
+
valueSpace: z.string().min(1).optional()
|
|
1147
|
+
});
|
|
1148
|
+
var browserSingletonSchema = z.enum(["url", "title", "viewport"]);
|
|
1237
1149
|
|
|
1238
|
-
// src/
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
)
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
if (predicate.kind === "not") {
|
|
1343
|
-
assertPredicateScoped(predicate.predicate, given);
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
if (predicate.kind !== "when") {
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
|
-
predicate.branches.forEach((row2) => {
|
|
1350
|
-
const names = row2.condition == null ? [] : conditionSingletons(row2.condition);
|
|
1351
|
-
names.forEach((name) => {
|
|
1352
|
-
if (!given.has(name)) {
|
|
1353
|
-
throw new Error(
|
|
1354
|
-
`when() conditions on singleton "${name}", which is not in the workflow's given \u2014 add ${name}.of(...) to given`
|
|
1355
|
-
);
|
|
1356
|
-
}
|
|
1357
|
-
});
|
|
1358
|
-
row2.consequence.forEach((consequence) => {
|
|
1359
|
-
assertPredicateScoped(consequence, given);
|
|
1360
|
-
});
|
|
1361
|
-
});
|
|
1362
|
-
}
|
|
1363
|
-
function conditionSingletons(predicate) {
|
|
1364
|
-
if (predicate.kind === "singleton") {
|
|
1365
|
-
return [predicate.singleton];
|
|
1366
|
-
}
|
|
1367
|
-
if (predicate.kind === "not") {
|
|
1368
|
-
return conditionSingletons(predicate.predicate);
|
|
1369
|
-
}
|
|
1370
|
-
if (predicate.kind === "and") {
|
|
1371
|
-
return predicate.predicates.flatMap((p) => conditionSingletons(p));
|
|
1372
|
-
}
|
|
1373
|
-
return [];
|
|
1374
|
-
}
|
|
1375
|
-
function assignAliases(entities) {
|
|
1376
|
-
return entities.reduce(
|
|
1377
|
-
(acc, d) => {
|
|
1378
|
-
const ordinal = acc.counts[d.entity] ?? 0;
|
|
1379
|
-
return {
|
|
1380
|
-
aliases: new Map([...acc.aliases, [d, `${d.entity}_${String(ordinal)}`]]),
|
|
1381
|
-
counts: { ...acc.counts, [d.entity]: ordinal + 1 }
|
|
1382
|
-
};
|
|
1383
|
-
},
|
|
1384
|
-
{ aliases: /* @__PURE__ */ new Map(), counts: {} }
|
|
1385
|
-
).aliases;
|
|
1386
|
-
}
|
|
1387
|
-
function assignParams(bindings) {
|
|
1388
|
-
const unique = [...new Set(bindings.filter((b) => b.__bind.kind === "param"))];
|
|
1389
|
-
const assigned = unique.reduce(
|
|
1390
|
-
(acc, b) => {
|
|
1391
|
-
const token = tokenOf(b);
|
|
1392
|
-
const ordinal = acc.counts[token.base] ?? 0;
|
|
1393
|
-
const name = ordinal === 0 ? token.base : `${token.base}_${String(ordinal - 1)}`;
|
|
1394
|
-
if (name in acc.params) {
|
|
1395
|
-
throw new Error(
|
|
1396
|
-
`param name "${name}" collides with an existing param \u2014 rename the field so deduplicated param names stay unique`
|
|
1397
|
-
);
|
|
1398
|
-
}
|
|
1399
|
-
return {
|
|
1400
|
-
counts: { ...acc.counts, [token.base]: ordinal + 1 },
|
|
1401
|
-
names: new Map([...acc.names, [b, name]]),
|
|
1402
|
-
params: { ...acc.params, [name]: { valueSpace: token.valueSpace } }
|
|
1403
|
-
};
|
|
1404
|
-
},
|
|
1405
|
-
{ counts: {}, names: /* @__PURE__ */ new Map(), params: {} }
|
|
1406
|
-
);
|
|
1407
|
-
return { names: assigned.names, params: assigned.params };
|
|
1408
|
-
}
|
|
1409
|
-
function tokenOf(binding) {
|
|
1410
|
-
if (binding.__bind.kind !== "param") {
|
|
1411
|
-
throw new Error("internal: expected a param binding");
|
|
1412
|
-
}
|
|
1413
|
-
return binding.__bind.token;
|
|
1414
|
-
}
|
|
1415
|
-
function allBindings(body, entities, absences, singletonStates) {
|
|
1416
|
-
return [
|
|
1417
|
-
...entities.flatMap((d) => Object.values(d.props).flatMap((v2) => valueBindings(v2))),
|
|
1418
|
-
...absences.flatMap((a) => Object.values(a.where).flatMap((v2) => valueBindings(v2))),
|
|
1419
|
-
...singletonStates.flatMap((s) => valueBindings(s.value)),
|
|
1420
|
-
...body.steps.flatMap((builder) => stepBindings(builder.step))
|
|
1421
|
-
];
|
|
1422
|
-
}
|
|
1423
|
-
function stepBindings(step) {
|
|
1424
|
-
return [...actionBindings(step.action), ...step.expect.flatMap((p) => predicateBindings(p))];
|
|
1425
|
-
}
|
|
1426
|
-
function actionBindings(action) {
|
|
1427
|
-
if (action.kind === "goto") {
|
|
1428
|
-
return valueBindings(action.url);
|
|
1429
|
-
}
|
|
1430
|
-
if (action.kind === "fill" || action.kind === "select") {
|
|
1431
|
-
return [...locatorBindings(action.locator), ...valueBindings(action.value)];
|
|
1432
|
-
}
|
|
1433
|
-
if (action.kind === "press") {
|
|
1434
|
-
return action.locator == null ? [] : locatorBindings(action.locator);
|
|
1150
|
+
// ../spec/src/predicate.ts
|
|
1151
|
+
import { z as z2 } from "zod";
|
|
1152
|
+
var stateAssertionSchema = z2.discriminatedUnion("kind", [
|
|
1153
|
+
z2.object({
|
|
1154
|
+
as: z2.string().min(1),
|
|
1155
|
+
kind: z2.literal("created"),
|
|
1156
|
+
props: z2.record(z2.string().min(1), setValueSchema)
|
|
1157
|
+
}),
|
|
1158
|
+
z2.object({
|
|
1159
|
+
as: z2.string().min(1),
|
|
1160
|
+
kind: z2.literal("updated"),
|
|
1161
|
+
props: z2.record(z2.string().min(1), updateValueSchema)
|
|
1162
|
+
}),
|
|
1163
|
+
z2.object({ kind: z2.literal("deleted") })
|
|
1164
|
+
]);
|
|
1165
|
+
var singletonAssertionSchema = z2.object({ kind: z2.literal("is"), value: setValueSchema });
|
|
1166
|
+
var whereValueSchema = z2.lazy(
|
|
1167
|
+
() => z2.union([setValueSchema, withinSchema])
|
|
1168
|
+
);
|
|
1169
|
+
var selectionSchema = z2.object({
|
|
1170
|
+
entity: z2.string().min(1),
|
|
1171
|
+
where: z2.record(z2.string().min(1), whereValueSchema)
|
|
1172
|
+
});
|
|
1173
|
+
var withinSchema = z2.object({
|
|
1174
|
+
field: z2.string().min(1),
|
|
1175
|
+
kind: z2.literal("within"),
|
|
1176
|
+
selection: selectionSchema
|
|
1177
|
+
});
|
|
1178
|
+
var wait = z2.union([budgetSchema, z2.undefined()]).optional().transform((value2) => value2);
|
|
1179
|
+
var singletonPredicateSchema = z2.object({
|
|
1180
|
+
assertion: singletonAssertionSchema,
|
|
1181
|
+
kind: z2.literal("singleton"),
|
|
1182
|
+
singleton: z2.string().min(1),
|
|
1183
|
+
wait
|
|
1184
|
+
});
|
|
1185
|
+
var countPredicateSchema = z2.object({
|
|
1186
|
+
entity: z2.string().min(1),
|
|
1187
|
+
kind: z2.literal("count"),
|
|
1188
|
+
value: z2.number().int().nonnegative()
|
|
1189
|
+
});
|
|
1190
|
+
var conditionSchema = z2.lazy(
|
|
1191
|
+
() => z2.discriminatedUnion("kind", [
|
|
1192
|
+
singletonPredicateSchema,
|
|
1193
|
+
countPredicateSchema,
|
|
1194
|
+
z2.object({ kind: z2.literal("not"), predicate: conditionSchema }),
|
|
1195
|
+
z2.object({ kind: z2.literal("and"), predicates: z2.array(conditionSchema) })
|
|
1196
|
+
])
|
|
1197
|
+
);
|
|
1198
|
+
var whenBranchSchema = z2.lazy(
|
|
1199
|
+
() => z2.object({
|
|
1200
|
+
condition: z2.union([conditionSchema, z2.undefined()]).optional().transform((value2) => value2),
|
|
1201
|
+
consequence: z2.array(predicateSchema),
|
|
1202
|
+
name: z2.string().min(1)
|
|
1203
|
+
})
|
|
1204
|
+
);
|
|
1205
|
+
var predicateSchema = z2.lazy(
|
|
1206
|
+
() => z2.discriminatedUnion("kind", [
|
|
1207
|
+
z2.object({ kind: z2.literal("visible"), locator: locatorSchema, wait }),
|
|
1208
|
+
z2.object({ kind: z2.literal("disabled"), locator: locatorSchema, wait }),
|
|
1209
|
+
z2.object({ kind: z2.literal("enabled"), locator: locatorSchema, wait }),
|
|
1210
|
+
z2.object({ kind: z2.literal("focused"), locator: locatorSchema, wait }),
|
|
1211
|
+
z2.object({ kind: z2.literal("checked"), locator: locatorSchema, wait }),
|
|
1212
|
+
z2.object({ kind: z2.literal("value"), locator: locatorSchema, value: stringValueSchema, wait }),
|
|
1213
|
+
z2.object({ kind: z2.literal("text"), locator: locatorSchema, value: stringValueSchema, wait }),
|
|
1214
|
+
singletonPredicateSchema,
|
|
1215
|
+
z2.object({
|
|
1216
|
+
kind: z2.literal("browser"),
|
|
1217
|
+
name: browserSingletonSchema,
|
|
1218
|
+
value: stringValueSchema,
|
|
1219
|
+
wait
|
|
1220
|
+
}),
|
|
1221
|
+
z2.object({
|
|
1222
|
+
assertion: stateAssertionSchema,
|
|
1223
|
+
entity: z2.string().min(1),
|
|
1224
|
+
key: z2.record(z2.string().min(1), whereValueSchema),
|
|
1225
|
+
kind: z2.literal("state"),
|
|
1226
|
+
wait
|
|
1227
|
+
}),
|
|
1228
|
+
z2.object({ kind: z2.literal("not"), predicate: predicateSchema }),
|
|
1229
|
+
z2.object({ kind: z2.literal("and"), predicates: z2.array(predicateSchema) }),
|
|
1230
|
+
countPredicateSchema,
|
|
1231
|
+
z2.object({ branches: z2.array(whenBranchSchema), kind: z2.literal("when") })
|
|
1232
|
+
])
|
|
1233
|
+
);
|
|
1234
|
+
|
|
1235
|
+
// ../spec/src/codec.ts
|
|
1236
|
+
import { z as z3 } from "zod";
|
|
1237
|
+
var envelopeSchema = z3.object({
|
|
1238
|
+
__codec: z3.string().min(1),
|
|
1239
|
+
data: z3.unknown(),
|
|
1240
|
+
version: z3.number().int().positive()
|
|
1241
|
+
});
|
|
1242
|
+
var CodecVersionError = class extends Error {
|
|
1243
|
+
codec;
|
|
1244
|
+
currentVersion;
|
|
1245
|
+
gotVersion;
|
|
1246
|
+
constructor(params) {
|
|
1247
|
+
super(
|
|
1248
|
+
`Unsupported ${params.codec} version ${String(params.gotVersion)} (current ${String(params.currentVersion)}). Upgrade Ripplo or rebuild with a compatible CLI.`
|
|
1249
|
+
);
|
|
1250
|
+
this.name = "CodecVersionError";
|
|
1251
|
+
this.codec = params.codec;
|
|
1252
|
+
this.currentVersion = params.currentVersion;
|
|
1253
|
+
this.gotVersion = params.gotVersion;
|
|
1435
1254
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1255
|
+
};
|
|
1256
|
+
var CodecMismatchError = class extends Error {
|
|
1257
|
+
constructor(params) {
|
|
1258
|
+
super(`Codec mismatch: expected "${params.expected}", got "${params.got}"`);
|
|
1259
|
+
this.name = "CodecMismatchError";
|
|
1438
1260
|
}
|
|
1439
|
-
|
|
1261
|
+
};
|
|
1262
|
+
function defineCodec({
|
|
1263
|
+
name,
|
|
1264
|
+
schema
|
|
1265
|
+
}) {
|
|
1266
|
+
return {
|
|
1267
|
+
currentVersion: 1,
|
|
1268
|
+
name,
|
|
1269
|
+
decode: (raw) => decode({ name, raw, schema }),
|
|
1270
|
+
encode: (value2) => ({
|
|
1271
|
+
__codec: name,
|
|
1272
|
+
data: value2,
|
|
1273
|
+
version: 1
|
|
1274
|
+
})
|
|
1275
|
+
};
|
|
1440
1276
|
}
|
|
1441
|
-
function
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1277
|
+
function decode({
|
|
1278
|
+
name,
|
|
1279
|
+
raw,
|
|
1280
|
+
schema
|
|
1281
|
+
}) {
|
|
1282
|
+
const envelope = envelopeSchema.parse(raw);
|
|
1283
|
+
if (envelope.__codec !== name) {
|
|
1284
|
+
throw new CodecMismatchError({ expected: name, got: envelope.__codec });
|
|
1447
1285
|
}
|
|
1448
|
-
|
|
1449
|
-
}
|
|
1450
|
-
function predicateBindings(predicate) {
|
|
1451
|
-
switch (predicate.kind) {
|
|
1452
|
-
case "visible":
|
|
1453
|
-
case "disabled":
|
|
1454
|
-
case "enabled":
|
|
1455
|
-
case "focused":
|
|
1456
|
-
case "checked": {
|
|
1457
|
-
return locatorBindings(predicate.locator);
|
|
1458
|
-
}
|
|
1459
|
-
case "value":
|
|
1460
|
-
case "text": {
|
|
1461
|
-
return [...locatorBindings(predicate.locator), ...valueBindings(predicate.value)];
|
|
1462
|
-
}
|
|
1463
|
-
case "singleton": {
|
|
1464
|
-
return valueBindings(predicate.assertion.value);
|
|
1465
|
-
}
|
|
1466
|
-
case "browser": {
|
|
1467
|
-
return valueBindings(predicate.value);
|
|
1468
|
-
}
|
|
1469
|
-
case "state": {
|
|
1470
|
-
return [
|
|
1471
|
-
...predicate.assertion.kind === "deleted" ? [] : Object.values(predicate.assertion.props).flatMap(
|
|
1472
|
-
(v2) => isChanged(v2) ? [] : valueBindings(v2)
|
|
1473
|
-
),
|
|
1474
|
-
...Object.values(predicate.key).flatMap((v2) => whereBindings(v2))
|
|
1475
|
-
];
|
|
1476
|
-
}
|
|
1477
|
-
case "not": {
|
|
1478
|
-
return predicateBindings(predicate.predicate);
|
|
1479
|
-
}
|
|
1480
|
-
case "when": {
|
|
1481
|
-
return predicate.branches.flatMap((row2) => [
|
|
1482
|
-
...row2.condition == null ? [] : predicateBindings(row2.condition),
|
|
1483
|
-
...row2.consequence.flatMap((consequence) => predicateBindings(consequence))
|
|
1484
|
-
]);
|
|
1485
|
-
}
|
|
1486
|
-
case "and": {
|
|
1487
|
-
return predicate.predicates.flatMap((p) => predicateBindings(p));
|
|
1488
|
-
}
|
|
1489
|
-
case "count": {
|
|
1490
|
-
return [];
|
|
1491
|
-
}
|
|
1286
|
+
if (envelope.version !== 1) {
|
|
1287
|
+
throw new CodecVersionError({ codec: name, currentVersion: 1, gotVersion: envelope.version });
|
|
1492
1288
|
}
|
|
1289
|
+
return schema.parse(envelope.data);
|
|
1493
1290
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1291
|
+
|
|
1292
|
+
// ../spec/src/client-channel.ts
|
|
1293
|
+
var CLIENT_MOUNT_KEY = "__ripplo__";
|
|
1294
|
+
var CLIENT_SEED_KEY = "__ripplo_seed__";
|
|
1295
|
+
|
|
1296
|
+
// ../spec/src/lockfile.ts
|
|
1297
|
+
import { z as z4 } from "zod";
|
|
1298
|
+
var actionSchema = z4.discriminatedUnion("kind", [
|
|
1299
|
+
z4.object({ kind: z4.literal("goto"), url: stringValueSchema }),
|
|
1300
|
+
z4.object({ kind: z4.literal("fill"), locator: locatorSchema, value: setValueSchema }),
|
|
1301
|
+
z4.object({ kind: z4.literal("clear"), locator: locatorSchema }),
|
|
1302
|
+
z4.object({ kind: z4.literal("click"), locator: locatorSchema }),
|
|
1303
|
+
z4.object({ kind: z4.literal("dblclick"), locator: locatorSchema }),
|
|
1304
|
+
z4.object({ kind: z4.literal("select"), locator: locatorSchema, value: setValueSchema }),
|
|
1305
|
+
z4.object({ kind: z4.literal("check"), locator: locatorSchema }),
|
|
1306
|
+
z4.object({ kind: z4.literal("uncheck"), locator: locatorSchema }),
|
|
1307
|
+
z4.object({ kind: z4.literal("hover"), locator: locatorSchema }),
|
|
1308
|
+
z4.object({
|
|
1309
|
+
files: z4.array(z4.string().min(1)).min(1),
|
|
1310
|
+
kind: z4.literal("upload"),
|
|
1311
|
+
locator: locatorSchema
|
|
1312
|
+
}),
|
|
1313
|
+
z4.object({ key: z4.string().min(1), kind: z4.literal("press"), locator: locatorSchema.optional() })
|
|
1314
|
+
]);
|
|
1315
|
+
var stepSchema = z4.object({
|
|
1316
|
+
action: actionSchema,
|
|
1317
|
+
expect: z4.array(predicateSchema).default([])
|
|
1318
|
+
});
|
|
1319
|
+
var paramSchema = z4.object({
|
|
1320
|
+
valueSpace: z4.string().min(1)
|
|
1321
|
+
});
|
|
1322
|
+
var setupSchema = z4.object({
|
|
1323
|
+
as: z4.string().min(1),
|
|
1324
|
+
entity: z4.string().min(1),
|
|
1325
|
+
set: z4.record(z4.string().min(1), setValueSchema)
|
|
1326
|
+
});
|
|
1327
|
+
var absenceSchema = z4.object({
|
|
1328
|
+
entity: z4.string().min(1),
|
|
1329
|
+
where: z4.record(z4.string().min(1), setValueSchema)
|
|
1330
|
+
});
|
|
1331
|
+
var resolvedTestSchema = z4.object({
|
|
1332
|
+
absent: z4.array(absenceSchema).default([]),
|
|
1333
|
+
exclusive: z4.array(z4.string().min(1)).default([]),
|
|
1334
|
+
intent: z4.string().min(1),
|
|
1335
|
+
name: z4.string().min(1),
|
|
1336
|
+
params: z4.record(z4.string().min(1), paramSchema),
|
|
1337
|
+
singletons: z4.record(z4.string().min(1), setValueSchema).default({}),
|
|
1338
|
+
slug: z4.string().min(1),
|
|
1339
|
+
steps: z4.array(stepSchema).default([]),
|
|
1340
|
+
workflow: z4.string().min(1),
|
|
1341
|
+
world: z4.array(setupSchema).default([])
|
|
1342
|
+
});
|
|
1343
|
+
var workflowSchema = z4.object({
|
|
1344
|
+
absent: z4.array(absenceSchema).default([]),
|
|
1345
|
+
exclusive: z4.array(z4.string().min(1)).default([]),
|
|
1346
|
+
intent: z4.string().min(1),
|
|
1347
|
+
maybe: z4.array(setupSchema).default([]),
|
|
1348
|
+
name: z4.string().min(1),
|
|
1349
|
+
params: z4.record(z4.string().min(1), paramSchema),
|
|
1350
|
+
singletons: z4.record(z4.string().min(1), setValueSchema).default({}),
|
|
1351
|
+
sourcePath: z4.string().min(1).optional(),
|
|
1352
|
+
steps: z4.array(stepSchema).default([]),
|
|
1353
|
+
stub: z4.boolean().default(false),
|
|
1354
|
+
tests: z4.array(resolvedTestSchema).default([]),
|
|
1355
|
+
world: z4.array(setupSchema).default([])
|
|
1356
|
+
});
|
|
1357
|
+
var fixtureEntrySchema = z4.object({
|
|
1358
|
+
sha256: z4.string().regex(/^[0-9a-f]{64}$/u),
|
|
1359
|
+
size: z4.number().int().nonnegative()
|
|
1360
|
+
});
|
|
1361
|
+
var lockfileSchema = z4.object({
|
|
1362
|
+
entities: z4.array(entitySchemaSchema),
|
|
1363
|
+
fixtures: z4.record(z4.string().min(1), fixtureEntrySchema).default({}),
|
|
1364
|
+
singletons: z4.array(singletonSchemaSchema).default([]),
|
|
1365
|
+
valueSpaces: z4.array(valueSpaceSchema),
|
|
1366
|
+
workflows: z4.array(workflowSchema)
|
|
1367
|
+
});
|
|
1368
|
+
var lockfileCodec = defineCodec({ name: "ripplo-lockfile", schema: lockfileSchema });
|
|
1369
|
+
|
|
1370
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
1371
|
+
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
1372
|
+
var configure = import__.default.configure;
|
|
1373
|
+
|
|
1374
|
+
// ../spec/src/sync-payload.ts
|
|
1375
|
+
import { z as z5 } from "zod";
|
|
1376
|
+
var stepDescriptorSchema = z5.object({
|
|
1377
|
+
index: z5.number().int().nonnegative(),
|
|
1378
|
+
kind: z5.string(),
|
|
1379
|
+
target: z5.string(),
|
|
1380
|
+
value: z5.string()
|
|
1381
|
+
});
|
|
1382
|
+
function slugify(name) {
|
|
1383
|
+
return name.toLowerCase().replaceAll(/[^a-z0-9]/g, "-").split("-").filter((part) => part.length > 0).join("-");
|
|
1499
1384
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1385
|
+
|
|
1386
|
+
// ../spec/src/session.ts
|
|
1387
|
+
import { z as z6 } from "zod";
|
|
1388
|
+
var sameSiteSchema = z6.enum(["Strict", "Lax", "None"]);
|
|
1389
|
+
var cookieSchema = z6.object({
|
|
1390
|
+
domain: z6.string().min(1),
|
|
1391
|
+
expires: z6.number(),
|
|
1392
|
+
httpOnly: z6.boolean(),
|
|
1393
|
+
name: z6.string().min(1),
|
|
1394
|
+
path: z6.string().min(1),
|
|
1395
|
+
sameSite: sameSiteSchema,
|
|
1396
|
+
secure: z6.boolean(),
|
|
1397
|
+
value: z6.string()
|
|
1398
|
+
});
|
|
1399
|
+
var originSchema = z6.object({
|
|
1400
|
+
localStorage: z6.array(z6.object({ name: z6.string().min(1), value: z6.string() })),
|
|
1401
|
+
origin: z6.string().min(1)
|
|
1402
|
+
});
|
|
1403
|
+
var sessionSchema = z6.object({
|
|
1404
|
+
cookies: z6.array(cookieSchema),
|
|
1405
|
+
headers: z6.record(z6.string().min(1), z6.string()).optional(),
|
|
1406
|
+
origins: z6.array(originSchema)
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
// ../spec/src/engine.ts
|
|
1410
|
+
import { z as z7 } from "zod";
|
|
1411
|
+
var cellSchema = z7.union([primitiveSchema, z7.null()]);
|
|
1412
|
+
var rowSchema = z7.record(z7.string().min(1), cellSchema);
|
|
1413
|
+
var setupSpecSchema = z7.object({
|
|
1414
|
+
as: z7.string().min(1),
|
|
1415
|
+
entity: z7.string().min(1),
|
|
1416
|
+
fields: z7.record(z7.string().min(1), setValueSchema)
|
|
1417
|
+
});
|
|
1418
|
+
var setupRequestSchema = z7.object({
|
|
1419
|
+
entities: z7.array(setupSpecSchema),
|
|
1420
|
+
runId: z7.string().min(1),
|
|
1421
|
+
singletons: z7.record(z7.string().min(1), cellSchema).default({})
|
|
1422
|
+
});
|
|
1423
|
+
var setupRowSchema = z7.object({
|
|
1424
|
+
as: z7.string().min(1),
|
|
1425
|
+
row: rowSchema,
|
|
1426
|
+
session: sessionSchema.optional()
|
|
1427
|
+
});
|
|
1428
|
+
var setupResponseSchema = z7.object({
|
|
1429
|
+
rows: z7.array(setupRowSchema)
|
|
1430
|
+
});
|
|
1431
|
+
var stateRequestSchema = z7.object({
|
|
1432
|
+
entities: z7.array(z7.string().min(1)),
|
|
1433
|
+
runId: z7.string().min(1),
|
|
1434
|
+
singletons: z7.array(z7.string().min(1)).default([])
|
|
1435
|
+
});
|
|
1436
|
+
var stateResponseSchema = z7.object({
|
|
1437
|
+
entities: z7.record(z7.string().min(1), z7.array(rowSchema)),
|
|
1438
|
+
singletons: z7.record(z7.string().min(1), cellSchema).default({})
|
|
1439
|
+
});
|
|
1440
|
+
var teardownRequestSchema = z7.object({
|
|
1441
|
+
runId: z7.string().min(1)
|
|
1442
|
+
});
|
|
1443
|
+
var teardownResponseSchema = z7.object({
|
|
1444
|
+
ok: z7.literal(true)
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
// src/expand-refs.ts
|
|
1448
|
+
function usedRefHeads({
|
|
1449
|
+
singletons,
|
|
1450
|
+
steps,
|
|
1451
|
+
workflow: workflow2,
|
|
1452
|
+
world
|
|
1453
|
+
}) {
|
|
1454
|
+
const values = [
|
|
1455
|
+
...world.flatMap((setup) => Object.values(setup.set)),
|
|
1456
|
+
...workflow2.absent.flatMap((absence) => Object.values(absence.where)),
|
|
1457
|
+
...Object.values(singletons),
|
|
1458
|
+
...steps.flatMap((step) => stepRefValues(step))
|
|
1459
|
+
];
|
|
1460
|
+
return new Set(values.flatMap((value2) => refStrings(value2)).map((ref) => headOf(ref)));
|
|
1504
1461
|
}
|
|
1505
|
-
function
|
|
1506
|
-
if (
|
|
1507
|
-
return
|
|
1508
|
-
}
|
|
1509
|
-
if (isTemplate(value2)) {
|
|
1510
|
-
return resolveTemplate(value2, ctx);
|
|
1462
|
+
function collectRefObjects(node) {
|
|
1463
|
+
if (Array.isArray(node)) {
|
|
1464
|
+
return node.flatMap((item) => collectRefObjects(item));
|
|
1511
1465
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
function resolveBinding(binding, ctx) {
|
|
1515
|
-
const bind = binding.__bind;
|
|
1516
|
-
if (bind.kind === "param") {
|
|
1517
|
-
const name = ctx.params.get(binding);
|
|
1518
|
-
if (name == null) {
|
|
1519
|
-
throw new Error("internal: param binding was not collected");
|
|
1520
|
-
}
|
|
1521
|
-
return { ref: name };
|
|
1466
|
+
if (node == null || typeof node !== "object") {
|
|
1467
|
+
return [];
|
|
1522
1468
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
throw new Error(
|
|
1526
|
-
`references a "${bind.descriptor.entity}" entity that is not included in the test's given`
|
|
1527
|
-
);
|
|
1469
|
+
if ("ref" in node && typeof node.ref === "string" && Object.keys(node).length === 1) {
|
|
1470
|
+
return [node.ref];
|
|
1528
1471
|
}
|
|
1529
|
-
return
|
|
1530
|
-
}
|
|
1531
|
-
function resolveTemplate(template, ctx) {
|
|
1532
|
-
return {
|
|
1533
|
-
template: template.template.map(
|
|
1534
|
-
(segment) => isBinding(segment) ? resolveBinding(segment, ctx) : segment
|
|
1535
|
-
)
|
|
1536
|
-
};
|
|
1472
|
+
return Object.values(node).flatMap((value2) => collectRefObjects(value2));
|
|
1537
1473
|
}
|
|
1538
|
-
function
|
|
1539
|
-
return
|
|
1474
|
+
function isRefValue(value2) {
|
|
1475
|
+
return value2 != null && typeof value2 === "object" && "ref" in value2 ? value2.ref : void 0;
|
|
1540
1476
|
}
|
|
1541
|
-
function
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
throw new Error(`internal: no alias for ${descriptor.entity}`);
|
|
1545
|
-
}
|
|
1546
|
-
return alias;
|
|
1477
|
+
function headOf(ref) {
|
|
1478
|
+
const dot = ref.indexOf(".");
|
|
1479
|
+
return dot === -1 ? ref : ref.slice(0, dot);
|
|
1547
1480
|
}
|
|
1548
|
-
function
|
|
1549
|
-
return {
|
|
1550
|
-
action: resolveAction(step.action, ctx),
|
|
1551
|
-
expect: step.expect.map((predicate) => resolvePredicate(predicate, ctx))
|
|
1552
|
-
};
|
|
1481
|
+
function stepRefValues(step) {
|
|
1482
|
+
return collectRefObjects(step).map((ref) => ({ ref }));
|
|
1553
1483
|
}
|
|
1554
|
-
function
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
if (action.kind === "fill" || action.kind === "select") {
|
|
1559
|
-
return {
|
|
1560
|
-
...action,
|
|
1561
|
-
locator: resolveLocator(action.locator, ctx),
|
|
1562
|
-
value: resolveValue(action.value, ctx)
|
|
1563
|
-
};
|
|
1564
|
-
}
|
|
1565
|
-
if (action.kind === "press") {
|
|
1566
|
-
return {
|
|
1567
|
-
...action,
|
|
1568
|
-
locator: action.locator == null ? void 0 : resolveLocator(action.locator, ctx)
|
|
1569
|
-
};
|
|
1484
|
+
function refStrings(value2) {
|
|
1485
|
+
const ref = isRefValue(value2);
|
|
1486
|
+
if (ref != null) {
|
|
1487
|
+
return [ref];
|
|
1570
1488
|
}
|
|
1571
|
-
if (
|
|
1572
|
-
return
|
|
1489
|
+
if (value2 != null && typeof value2 === "object" && "template" in value2) {
|
|
1490
|
+
return value2.template.flatMap((segment) => typeof segment === "string" ? [] : [segment.ref]);
|
|
1573
1491
|
}
|
|
1574
|
-
return
|
|
1492
|
+
return [];
|
|
1575
1493
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1494
|
+
|
|
1495
|
+
// src/expand.ts
|
|
1496
|
+
var MAX_CANDIDATES = 4096;
|
|
1497
|
+
function singletonValuesOf(singletons) {
|
|
1498
|
+
return new Map(
|
|
1499
|
+
singletons.flatMap((singleton2) => {
|
|
1500
|
+
const space = singleton2.valueSpaces.find((s) => s.name === singleton2.schema.valueSpace);
|
|
1501
|
+
return space?.values == null ? [] : [[singleton2.schema.name, space.values]];
|
|
1502
|
+
})
|
|
1503
|
+
);
|
|
1584
1504
|
}
|
|
1585
|
-
function
|
|
1586
|
-
if (
|
|
1587
|
-
return {
|
|
1588
|
-
by: "inside",
|
|
1589
|
-
scope: resolveLocator(locator.scope, ctx),
|
|
1590
|
-
target: resolveLocator(locator.target, ctx)
|
|
1591
|
-
};
|
|
1505
|
+
function expandWorkflow(workflow2, options) {
|
|
1506
|
+
if (workflow2.stub) {
|
|
1507
|
+
return { ...workflow2, tests: [] };
|
|
1592
1508
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1509
|
+
assertUniqueBranchNames(workflow2);
|
|
1510
|
+
const targets = collectTargets(workflow2.steps);
|
|
1511
|
+
const whenIds = new Map(targets.map((target, index) => [target.when, index]));
|
|
1512
|
+
const sims = proposeCandidates(workflow2, options).map((candidate) => simulate(workflow2, candidate)).filter((sim) => sim != null);
|
|
1513
|
+
if (targets.length === 0) {
|
|
1514
|
+
return { ...workflow2, tests: [requireMainTest(workflow2, sims)] };
|
|
1595
1515
|
}
|
|
1596
|
-
|
|
1516
|
+
const { tests } = targets.reduce(
|
|
1517
|
+
(acc, target) => coverTarget({ acc, sims, target, whenIds, workflow: workflow2 }),
|
|
1518
|
+
{ covered: /* @__PURE__ */ new Set(), tests: [] }
|
|
1519
|
+
);
|
|
1520
|
+
return { ...workflow2, tests };
|
|
1597
1521
|
}
|
|
1598
|
-
function
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
return {
|
|
1617
|
-
...predicate,
|
|
1618
|
-
assertion: { ...predicate.assertion, value: resolveValue(predicate.assertion.value, ctx) }
|
|
1619
|
-
};
|
|
1620
|
-
}
|
|
1621
|
-
case "browser": {
|
|
1622
|
-
return { ...predicate, value: resolveString(predicate.value, ctx) };
|
|
1623
|
-
}
|
|
1624
|
-
case "state": {
|
|
1625
|
-
return {
|
|
1626
|
-
...predicate,
|
|
1627
|
-
assertion: resolveAssertion(predicate.assertion, ctx),
|
|
1628
|
-
key: whereMap(predicate.key, ctx)
|
|
1629
|
-
};
|
|
1630
|
-
}
|
|
1631
|
-
case "not": {
|
|
1632
|
-
return { ...predicate, predicate: resolvePredicate(predicate.predicate, ctx) };
|
|
1633
|
-
}
|
|
1634
|
-
case "when": {
|
|
1635
|
-
return {
|
|
1636
|
-
...predicate,
|
|
1637
|
-
branches: predicate.branches.map((row2) => ({
|
|
1638
|
-
condition: row2.condition == null ? void 0 : resolveCondition(row2.condition, ctx),
|
|
1639
|
-
consequence: row2.consequence.map((consequence) => resolvePredicate(consequence, ctx)),
|
|
1640
|
-
name: row2.name
|
|
1641
|
-
}))
|
|
1642
|
-
};
|
|
1643
|
-
}
|
|
1644
|
-
case "and": {
|
|
1645
|
-
return {
|
|
1646
|
-
...predicate,
|
|
1647
|
-
predicates: predicate.predicates.map((p) => resolvePredicate(p, ctx))
|
|
1648
|
-
};
|
|
1522
|
+
function resolveValidTest(workflow2, sim, name) {
|
|
1523
|
+
const test = resolveTest(workflow2, sim, name);
|
|
1524
|
+
return danglingRefHeads(test).length === 0 ? test : null;
|
|
1525
|
+
}
|
|
1526
|
+
function danglingRefHeads(test) {
|
|
1527
|
+
const known = /* @__PURE__ */ new Set([
|
|
1528
|
+
...test.world.map((setup) => setup.as),
|
|
1529
|
+
...Object.keys(test.params),
|
|
1530
|
+
...test.steps.flatMap((step) => createdAliasesIn(step))
|
|
1531
|
+
]);
|
|
1532
|
+
return [...new Set(collectRefObjects(test).map((ref) => headOf(ref)))].filter(
|
|
1533
|
+
(head) => !known.has(head)
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
function createdAliasesIn(step) {
|
|
1537
|
+
return step.expect.flatMap((predicate) => {
|
|
1538
|
+
if (predicate.kind !== "state" || predicate.assertion.kind === "deleted") {
|
|
1539
|
+
return [];
|
|
1649
1540
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1541
|
+
return [predicate.assertion.as];
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
function coverTarget({ acc, sims, target, whenIds, workflow: workflow2 }) {
|
|
1545
|
+
const key2 = choiceKey(whenIds, target.when, target.index);
|
|
1546
|
+
if (acc.covered.has(key2)) {
|
|
1547
|
+
return { covered: acc.covered, tests: acc.tests };
|
|
1548
|
+
}
|
|
1549
|
+
const picked = sims.filter((s) => s.choices.get(target.when) === target.index).reduce((found, s) => {
|
|
1550
|
+
if (found != null) {
|
|
1551
|
+
return found;
|
|
1652
1552
|
}
|
|
1553
|
+
const test = resolveValidTest(workflow2, s, target.name);
|
|
1554
|
+
return test == null ? null : { sim: s, test };
|
|
1555
|
+
}, null);
|
|
1556
|
+
if (picked == null) {
|
|
1557
|
+
throw new Error(
|
|
1558
|
+
`workflow "${workflow2.name}": branch "${target.name}" is unreachable \u2014 no combination of optional entities and singleton values reaches it`
|
|
1559
|
+
);
|
|
1653
1560
|
}
|
|
1561
|
+
const reached = [...picked.sim.choices].map(([when2, index]) => choiceKey(whenIds, when2, index));
|
|
1562
|
+
return {
|
|
1563
|
+
covered: /* @__PURE__ */ new Set([...acc.covered, ...reached]),
|
|
1564
|
+
tests: [...acc.tests, picked.test]
|
|
1565
|
+
};
|
|
1654
1566
|
}
|
|
1655
|
-
function
|
|
1656
|
-
|
|
1657
|
-
|
|
1567
|
+
function choiceKey(whenIds, when2, index) {
|
|
1568
|
+
const id2 = whenIds.get(when2);
|
|
1569
|
+
if (id2 == null) {
|
|
1570
|
+
throw new Error("internal: when node missing from target index");
|
|
1658
1571
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1572
|
+
return `${String(id2)}:${String(index)}`;
|
|
1573
|
+
}
|
|
1574
|
+
function requireMainTest(workflow2, sims) {
|
|
1575
|
+
const test = sims.reduce(
|
|
1576
|
+
(found, sim) => found ?? resolveValidTest(workflow2, sim, "main"),
|
|
1577
|
+
null
|
|
1578
|
+
);
|
|
1579
|
+
if (test == null) {
|
|
1580
|
+
throw new Error(
|
|
1581
|
+
`workflow "${workflow2.name}": no valid seed found \u2014 steps may reference optional entities no candidate provides, or a condition mentions values the solver cannot pin`
|
|
1582
|
+
);
|
|
1662
1583
|
}
|
|
1663
|
-
|
|
1664
|
-
return assertion.kind === "created" ? { as, kind: "created", props: setMap(assertion.props, ctx) } : { as, kind: "updated", props: updateMap(assertion.props, ctx) };
|
|
1584
|
+
return test;
|
|
1665
1585
|
}
|
|
1666
|
-
function
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1586
|
+
function assertUniqueBranchNames(workflow2) {
|
|
1587
|
+
const names = collectTargets(workflow2.steps).map((target) => target.name);
|
|
1588
|
+
const dup = names.find((name, index) => names.indexOf(name) !== index);
|
|
1589
|
+
if (dup != null) {
|
|
1590
|
+
throw new Error(
|
|
1591
|
+
`workflow "${workflow2.name}": branch name "${dup}" is used twice \u2014 branch names must be unique within a workflow`
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
function collectTargets(steps) {
|
|
1596
|
+
return steps.flatMap((step) => step.expect.flatMap((predicate) => targetsIn(predicate)));
|
|
1597
|
+
}
|
|
1598
|
+
function targetsIn(predicate) {
|
|
1599
|
+
if (predicate.kind === "not") {
|
|
1600
|
+
return targetsIn(predicate.predicate);
|
|
1601
|
+
}
|
|
1602
|
+
if (predicate.kind === "and") {
|
|
1603
|
+
return predicate.predicates.flatMap((p) => targetsIn(p));
|
|
1604
|
+
}
|
|
1605
|
+
if (predicate.kind !== "when") {
|
|
1606
|
+
return [];
|
|
1607
|
+
}
|
|
1608
|
+
return predicate.branches.flatMap((row2, index) => [
|
|
1609
|
+
{ index, name: row2.name, when: predicate },
|
|
1610
|
+
...row2.consequence.flatMap((consequence) => targetsIn(consequence))
|
|
1611
|
+
]);
|
|
1612
|
+
}
|
|
1613
|
+
function proposeCandidates(workflow2, options) {
|
|
1614
|
+
const subsets = maybeSubsets(workflow2.maybe);
|
|
1615
|
+
const pinSets = pinAssignments(workflow2, options);
|
|
1616
|
+
if (subsets.length * pinSets.length > MAX_CANDIDATES) {
|
|
1617
|
+
throw new Error(
|
|
1618
|
+
`workflow "${workflow2.name}": too many optional entities and singleton values to solve \u2014 split the workflow or convert maybe(...) entities to of(...)`
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
return subsets.flatMap((maybes) => pinSets.map((pins) => ({ maybes, pins })));
|
|
1622
|
+
}
|
|
1623
|
+
function maybeSubsets(maybe) {
|
|
1624
|
+
const masks = Array.from({ length: 1 << maybe.length }, (_, mask) => mask);
|
|
1625
|
+
return masks.map((mask) => ({ mask, size: maybe.filter((_, i) => (mask & 1 << i) !== 0).length })).toSorted((a, b) => a.size === b.size ? a.mask - b.mask : a.size - b.size).map(({ mask }) => maybe.filter((_, i) => (mask & 1 << i) !== 0));
|
|
1626
|
+
}
|
|
1627
|
+
function pinAssignments(workflow2, options) {
|
|
1628
|
+
const domains = pinDomains(workflow2, options);
|
|
1629
|
+
return Object.entries(domains).reduce(
|
|
1630
|
+
(acc, [param, domain]) => acc.flatMap((pins) => domain.map((value2) => ({ ...pins, [param]: value2 }))),
|
|
1631
|
+
[{}]
|
|
1672
1632
|
);
|
|
1673
1633
|
}
|
|
1674
|
-
function
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1634
|
+
function pinDomains(workflow2, options) {
|
|
1635
|
+
const literals = conditionSingletonLiterals(workflow2.steps);
|
|
1636
|
+
return Object.entries(workflow2.singletons).reduce((acc, [name, value2]) => {
|
|
1637
|
+
const param = paramRefOf(value2);
|
|
1638
|
+
const compared = literals.get(name);
|
|
1639
|
+
if (param == null || compared == null) {
|
|
1640
|
+
return acc;
|
|
1641
|
+
}
|
|
1642
|
+
const enumValues = options.singletonValues.get(name);
|
|
1643
|
+
return { ...acc, [param]: withComplements(compared, enumValues) };
|
|
1644
|
+
}, {});
|
|
1645
|
+
}
|
|
1646
|
+
function conditionSingletonLiterals(steps) {
|
|
1647
|
+
const pairs = steps.flatMap(
|
|
1648
|
+
(step) => step.expect.flatMap((predicate) => conditionLiteralsIn(predicate))
|
|
1649
|
+
);
|
|
1650
|
+
return pairs.reduce(
|
|
1651
|
+
(acc, [name, literal]) => new Map([...acc, [name, [.../* @__PURE__ */ new Set([...acc.get(name) ?? [], literal])]]]),
|
|
1652
|
+
/* @__PURE__ */ new Map()
|
|
1680
1653
|
);
|
|
1681
1654
|
}
|
|
1682
|
-
function
|
|
1683
|
-
|
|
1655
|
+
function conditionLiteralsIn(predicate) {
|
|
1656
|
+
if (predicate.kind === "not") {
|
|
1657
|
+
return conditionLiteralsIn(predicate.predicate);
|
|
1658
|
+
}
|
|
1659
|
+
if (predicate.kind === "and") {
|
|
1660
|
+
return predicate.predicates.flatMap((p) => conditionLiteralsIn(p));
|
|
1661
|
+
}
|
|
1662
|
+
if (predicate.kind !== "when") {
|
|
1663
|
+
return [];
|
|
1664
|
+
}
|
|
1665
|
+
return predicate.branches.flatMap((row2) => [
|
|
1666
|
+
...row2.condition == null ? [] : singletonLiteralsInCondition(row2.condition),
|
|
1667
|
+
...row2.consequence.flatMap((consequence) => conditionLiteralsIn(consequence))
|
|
1668
|
+
]);
|
|
1684
1669
|
}
|
|
1685
|
-
function
|
|
1670
|
+
function singletonLiteralsInCondition(condition) {
|
|
1686
1671
|
switch (condition.kind) {
|
|
1687
1672
|
case "singleton": {
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
assertion: {
|
|
1691
|
-
...condition.assertion,
|
|
1692
|
-
value: resolveValue(condition.assertion.value, ctx)
|
|
1693
|
-
}
|
|
1694
|
-
};
|
|
1673
|
+
const value2 = condition.assertion.value;
|
|
1674
|
+
return value2 == null || typeof value2 !== "object" ? [[condition.singleton, value2]] : [];
|
|
1695
1675
|
}
|
|
1696
1676
|
case "count": {
|
|
1697
|
-
return
|
|
1677
|
+
return [];
|
|
1698
1678
|
}
|
|
1699
1679
|
case "not": {
|
|
1700
|
-
return
|
|
1680
|
+
return singletonLiteralsInCondition(condition.predicate);
|
|
1701
1681
|
}
|
|
1702
1682
|
case "and": {
|
|
1703
|
-
return
|
|
1704
|
-
...condition,
|
|
1705
|
-
predicates: condition.predicates.map((p) => resolveCondition(p, ctx))
|
|
1706
|
-
};
|
|
1683
|
+
return condition.predicates.flatMap((p) => singletonLiteralsInCondition(p));
|
|
1707
1684
|
}
|
|
1708
1685
|
}
|
|
1709
1686
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
function workflow(intent, fn) {
|
|
1713
|
-
const sourcePath = captureSourcePath();
|
|
1714
|
-
if (fn == null) {
|
|
1715
|
-
return { spec: stubSpec(intent, sourcePath) };
|
|
1716
|
-
}
|
|
1717
|
-
const final = finalize(fn());
|
|
1718
|
-
return {
|
|
1719
|
-
spec: {
|
|
1720
|
-
absent: final.absent,
|
|
1721
|
-
exclusive: final.exclusive,
|
|
1722
|
-
intent,
|
|
1723
|
-
maybe: final.maybe,
|
|
1724
|
-
name: slugify(intent),
|
|
1725
|
-
params: final.params,
|
|
1726
|
-
singletons: final.singletons,
|
|
1727
|
-
sourcePath,
|
|
1728
|
-
steps: final.steps,
|
|
1729
|
-
stub: false,
|
|
1730
|
-
tests: [],
|
|
1731
|
-
world: final.world
|
|
1732
|
-
}
|
|
1733
|
-
};
|
|
1734
|
-
}
|
|
1735
|
-
function stubSpec(intent, sourcePath) {
|
|
1736
|
-
return {
|
|
1737
|
-
absent: [],
|
|
1738
|
-
exclusive: [],
|
|
1739
|
-
intent,
|
|
1740
|
-
maybe: [],
|
|
1741
|
-
name: slugify(intent),
|
|
1742
|
-
params: {},
|
|
1743
|
-
singletons: {},
|
|
1744
|
-
sourcePath,
|
|
1745
|
-
steps: [],
|
|
1746
|
-
stub: true,
|
|
1747
|
-
tests: [],
|
|
1748
|
-
world: []
|
|
1749
|
-
};
|
|
1750
|
-
}
|
|
1751
|
-
var WORKFLOWS_ANCHOR_PATTERN = /[/\\]\.ripplo[/\\]workflows[/\\]([^):]+?)(?::\d+:\d+\)?)?$/;
|
|
1752
|
-
function captureSourcePath() {
|
|
1753
|
-
const stack = new Error("capture").stack;
|
|
1754
|
-
if (stack == null) {
|
|
1687
|
+
function paramRefOf(value2) {
|
|
1688
|
+
if (value2 == null || typeof value2 !== "object" || !("ref" in value2)) {
|
|
1755
1689
|
return void 0;
|
|
1756
1690
|
}
|
|
1757
|
-
|
|
1758
|
-
const captured = match?.[1];
|
|
1759
|
-
return captured == null ? void 0 : captured.replaceAll("\\", "/");
|
|
1691
|
+
return value2.ref.includes(".") ? void 0 : value2.ref;
|
|
1760
1692
|
}
|
|
1761
|
-
function
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
throw new Error(`workflow intent "${intent}" slugifies to an empty string`);
|
|
1693
|
+
function withComplements(values, enumValues) {
|
|
1694
|
+
if (enumValues != null) {
|
|
1695
|
+
return [.../* @__PURE__ */ new Set([...values, ...enumValues])];
|
|
1765
1696
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
base: `${field2.entity}_${field2.field}`,
|
|
1773
|
-
valueSpace: field2.valueSpaceName
|
|
1774
|
-
};
|
|
1775
|
-
return paramBinding(token);
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
// src/engine.ts
|
|
1779
|
-
import { err, ok, okAsync, Result, ResultAsync } from "neverthrow";
|
|
1780
|
-
function createEngine(ripplo, impls, teardown) {
|
|
1781
|
-
const entities = new Map(Object.entries(impls.entities));
|
|
1782
|
-
const singletons = new Map(
|
|
1783
|
-
Object.entries(impls.singletons).map(([name, impl]) => [
|
|
1784
|
-
name,
|
|
1785
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- per-name impls typed at the call site; erase the contravariant scalar seed to the loose dispatch shape
|
|
1786
|
-
impl
|
|
1787
|
-
])
|
|
1788
|
-
);
|
|
1789
|
-
assertDeclared(ripplo, [...entities.keys()], [...singletons.keys()]);
|
|
1790
|
-
const entityImpl = (name) => lookup(entities.get(name), name);
|
|
1791
|
-
const singletonImpl = (name) => lookup(singletons.get(name), name);
|
|
1792
|
-
return {
|
|
1793
|
-
read: (request, runId) => ResultAsync.combine([
|
|
1794
|
-
ResultAsync.combine(
|
|
1795
|
-
request.entities.map((name) => readEntity(entityImpl(name), name, runId))
|
|
1796
|
-
),
|
|
1797
|
-
ResultAsync.combine(
|
|
1798
|
-
request.singletons.map((name) => readSingleton(singletonImpl(name), name, runId))
|
|
1799
|
-
)
|
|
1800
|
-
]).map(([entityPairs, singletonPairs]) => ({
|
|
1801
|
-
entities: Object.fromEntries(entityPairs),
|
|
1802
|
-
singletons: Object.fromEntries(singletonPairs)
|
|
1803
|
-
})),
|
|
1804
|
-
seed: (request, runId) => ResultAsync.fromPromise(teardown(runId), implFailure).andThen(() => seedSingletons(request.singletons, singletonImpl, runId)).andThen(() => seedSetups(request.entities, entityImpl, runId)),
|
|
1805
|
-
teardown: (runId) => ResultAsync.fromPromise(teardown(runId), implFailure)
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
function assertDeclared(ripplo, entityNames, singletonNames) {
|
|
1809
|
-
const backendEntities = new Set(
|
|
1810
|
-
ripplo.entities.filter((e) => e.schema.source === "backend").map((e) => e.name)
|
|
1811
|
-
);
|
|
1812
|
-
const backendSingletons = new Set(
|
|
1813
|
-
ripplo.singletons.filter((s) => s.schema.source === "backend").map((s) => s.name)
|
|
1814
|
-
);
|
|
1815
|
-
entityNames.forEach((name) => {
|
|
1816
|
-
if (!backendEntities.has(name)) {
|
|
1817
|
-
throw new Error(`engine impl "${name}" has no matching backend entity`);
|
|
1697
|
+
const complements = values.flatMap((value2) => {
|
|
1698
|
+
if (typeof value2 === "boolean") {
|
|
1699
|
+
return [!value2];
|
|
1700
|
+
}
|
|
1701
|
+
if (typeof value2 === "number") {
|
|
1702
|
+
return [value2 + 1];
|
|
1818
1703
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
if (!backendSingletons.has(name)) {
|
|
1822
|
-
throw new Error(`engine impl "${name}" has no matching backend singleton`);
|
|
1704
|
+
if (typeof value2 === "string") {
|
|
1705
|
+
return [syntheticDistinct(value2, values)];
|
|
1823
1706
|
}
|
|
1707
|
+
return [];
|
|
1824
1708
|
});
|
|
1709
|
+
return [.../* @__PURE__ */ new Set([...values, ...complements])];
|
|
1825
1710
|
}
|
|
1826
|
-
function
|
|
1827
|
-
|
|
1711
|
+
function syntheticDistinct(seed, taken) {
|
|
1712
|
+
const candidate = `${seed}-alt`;
|
|
1713
|
+
return taken.includes(candidate) ? syntheticDistinct(candidate, taken) : candidate;
|
|
1828
1714
|
}
|
|
1829
|
-
function
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
[...
|
|
1834
|
-
|
|
1715
|
+
function simulate(workflow2, candidate) {
|
|
1716
|
+
const initial = {
|
|
1717
|
+
choices: /* @__PURE__ */ new Map(),
|
|
1718
|
+
state: {
|
|
1719
|
+
rows: [...workflow2.world, ...candidate.maybes],
|
|
1720
|
+
singles: seedSingles(workflow2.singletons, candidate.pins)
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
const folded = workflow2.steps.reduce(
|
|
1724
|
+
(acc, step) => acc == null ? null : stepSim(acc, step),
|
|
1725
|
+
initial
|
|
1835
1726
|
);
|
|
1727
|
+
return folded == null ? null : { candidate, choices: folded.choices };
|
|
1836
1728
|
}
|
|
1837
|
-
function
|
|
1838
|
-
return
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
name,
|
|
1844
|
-
value2
|
|
1845
|
-
])
|
|
1729
|
+
function seedSingles(singletons, pins) {
|
|
1730
|
+
return Object.fromEntries(
|
|
1731
|
+
Object.entries(singletons).map(([name, value2]) => {
|
|
1732
|
+
const param = paramRefOf(value2);
|
|
1733
|
+
return [name, param != null && param in pins ? pins[param] ?? null : value2];
|
|
1734
|
+
})
|
|
1846
1735
|
);
|
|
1847
1736
|
}
|
|
1848
|
-
function
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1737
|
+
function stepSim(acc, step) {
|
|
1738
|
+
const rows = step.expect.filter((p) => p.kind === "state").reduce((current, effect) => applyEffect(current, effect), acc.state.rows);
|
|
1739
|
+
const preWhens = { rows, singles: acc.state.singles };
|
|
1740
|
+
const whens = step.expect.filter((p) => p.kind === "when");
|
|
1741
|
+
const resolved = whens.reduce((current, when2) => current == null ? null : foldWhen(current, when2, preWhens), {
|
|
1742
|
+
choices: acc.choices,
|
|
1743
|
+
sets: {}
|
|
1744
|
+
});
|
|
1745
|
+
if (resolved == null) {
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
const immediate = singletonSets(step.expect);
|
|
1749
|
+
return {
|
|
1750
|
+
choices: resolved.choices,
|
|
1751
|
+
state: { rows, singles: { ...preWhens.singles, ...immediate, ...resolved.sets } }
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
function foldWhen(acc, when2, state) {
|
|
1755
|
+
const picked = pickBranch(when2, state);
|
|
1756
|
+
if (picked === "unknown") {
|
|
1757
|
+
return null;
|
|
1758
|
+
}
|
|
1759
|
+
if (picked == null) {
|
|
1760
|
+
return acc;
|
|
1761
|
+
}
|
|
1762
|
+
const row2 = when2.branches[picked];
|
|
1763
|
+
if (row2 == null) {
|
|
1764
|
+
return acc;
|
|
1765
|
+
}
|
|
1766
|
+
const choices = new Map([...acc.choices, [when2, picked]]);
|
|
1767
|
+
return row2.consequence.reduce(
|
|
1768
|
+
(folded, consequence) => {
|
|
1769
|
+
if (folded == null) {
|
|
1770
|
+
return null;
|
|
1771
|
+
}
|
|
1772
|
+
if (consequence.kind === "when") {
|
|
1773
|
+
return foldWhen(folded, consequence, state);
|
|
1774
|
+
}
|
|
1775
|
+
return { choices: folded.choices, sets: { ...folded.sets, ...singletonSets([consequence]) } };
|
|
1776
|
+
},
|
|
1777
|
+
{ choices, sets: acc.sets }
|
|
1855
1778
|
);
|
|
1856
1779
|
}
|
|
1857
|
-
function
|
|
1858
|
-
|
|
1859
|
-
|
|
1780
|
+
function pickBranch(when2, state) {
|
|
1781
|
+
return when2.branches.reduce((picked, row2, index) => {
|
|
1782
|
+
if (picked !== void 0) {
|
|
1783
|
+
return picked;
|
|
1784
|
+
}
|
|
1785
|
+
if (row2.condition == null) {
|
|
1786
|
+
return index;
|
|
1787
|
+
}
|
|
1788
|
+
const holds = evalCondition(row2.condition, state);
|
|
1789
|
+
if (holds === "unknown") {
|
|
1790
|
+
return "unknown";
|
|
1791
|
+
}
|
|
1792
|
+
return holds ? index : void 0;
|
|
1793
|
+
}, void 0);
|
|
1860
1794
|
}
|
|
1861
|
-
function
|
|
1862
|
-
|
|
1795
|
+
function evalCondition(condition, state) {
|
|
1796
|
+
switch (condition.kind) {
|
|
1797
|
+
case "count": {
|
|
1798
|
+
return state.rows.filter((row2) => row2.entity === condition.entity).length === condition.value;
|
|
1799
|
+
}
|
|
1800
|
+
case "singleton": {
|
|
1801
|
+
return compareValues(state.singles[condition.singleton], condition.assertion.value);
|
|
1802
|
+
}
|
|
1803
|
+
case "not": {
|
|
1804
|
+
return negate(evalCondition(condition.predicate, state));
|
|
1805
|
+
}
|
|
1806
|
+
case "and": {
|
|
1807
|
+
return conjoin(condition.predicates.map((p) => evalCondition(p, state)));
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1863
1810
|
}
|
|
1864
|
-
function
|
|
1865
|
-
if (
|
|
1866
|
-
return
|
|
1811
|
+
function compareValues(seeded, wanted) {
|
|
1812
|
+
if (seeded === void 0) {
|
|
1813
|
+
return "unknown";
|
|
1867
1814
|
}
|
|
1868
|
-
const
|
|
1869
|
-
const
|
|
1870
|
-
if (
|
|
1871
|
-
return
|
|
1815
|
+
const seededRef = isRefValue(seeded);
|
|
1816
|
+
const wantedRef = isRefValue(wanted);
|
|
1817
|
+
if (seededRef != null && wantedRef != null) {
|
|
1818
|
+
return seededRef === wantedRef ? true : "unknown";
|
|
1872
1819
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
);
|
|
1820
|
+
if (seededRef != null || wantedRef != null || isTemplateValue(seeded) || isTemplateValue(wanted)) {
|
|
1821
|
+
return "unknown";
|
|
1822
|
+
}
|
|
1823
|
+
return sameSetValue(seeded, wanted);
|
|
1877
1824
|
}
|
|
1878
|
-
function
|
|
1879
|
-
return
|
|
1880
|
-
if (value2 === null || typeof value2 !== "object" || !("ref" in value2)) {
|
|
1881
|
-
return [];
|
|
1882
|
-
}
|
|
1883
|
-
const lastDot = value2.ref.lastIndexOf(".");
|
|
1884
|
-
return lastDot === -1 ? [] : [value2.ref.slice(0, lastDot)];
|
|
1885
|
-
});
|
|
1825
|
+
function isTemplateValue(value2) {
|
|
1826
|
+
return value2 != null && typeof value2 === "object" && "template" in value2;
|
|
1886
1827
|
}
|
|
1887
|
-
function
|
|
1888
|
-
return
|
|
1889
|
-
wave.map((spec) => seedSetup(entityImpl(spec.entity), acc, spec, runId))
|
|
1890
|
-
).map((folds) => mergeFolds(acc, folds));
|
|
1828
|
+
function negate(value2) {
|
|
1829
|
+
return value2 === "unknown" ? "unknown" : !value2;
|
|
1891
1830
|
}
|
|
1892
|
-
function
|
|
1893
|
-
|
|
1831
|
+
function conjoin(values) {
|
|
1832
|
+
if (values.includes(false)) {
|
|
1833
|
+
return false;
|
|
1834
|
+
}
|
|
1835
|
+
return values.every((v2) => v2 === true) ? true : "unknown";
|
|
1894
1836
|
}
|
|
1895
|
-
function
|
|
1896
|
-
return
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1837
|
+
function singletonSets(predicates) {
|
|
1838
|
+
return Object.fromEntries(
|
|
1839
|
+
predicates.flatMap((predicate) => {
|
|
1840
|
+
if (predicate.kind === "singleton") {
|
|
1841
|
+
return [[predicate.singleton, predicate.assertion.value]];
|
|
1842
|
+
}
|
|
1843
|
+
return [];
|
|
1844
|
+
})
|
|
1845
|
+
);
|
|
1901
1846
|
}
|
|
1902
|
-
function
|
|
1903
|
-
if (
|
|
1904
|
-
return
|
|
1847
|
+
function applyEffect(rows, effect) {
|
|
1848
|
+
if (effect.assertion.kind === "created") {
|
|
1849
|
+
return [
|
|
1850
|
+
...rows,
|
|
1851
|
+
{ as: effect.assertion.as, entity: effect.entity, set: effect.assertion.props }
|
|
1852
|
+
];
|
|
1905
1853
|
}
|
|
1906
|
-
if ("
|
|
1907
|
-
return
|
|
1854
|
+
if (effect.assertion.kind === "deleted") {
|
|
1855
|
+
return rows.filter((row2) => row2.entity !== effect.entity || !matchesKey(row2, effect.key, rows));
|
|
1908
1856
|
}
|
|
1909
|
-
|
|
1857
|
+
return rows;
|
|
1910
1858
|
}
|
|
1911
|
-
function
|
|
1912
|
-
return
|
|
1859
|
+
function matchesKey(row2, key2, rows) {
|
|
1860
|
+
return Object.entries(key2).every(([field2, want]) => matchesField({ field: field2, row: row2, rows, want }));
|
|
1913
1861
|
}
|
|
1914
|
-
function
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1862
|
+
function matchesField({ field: field2, row: row2, rows, want }) {
|
|
1863
|
+
if (isWithinValue(want)) {
|
|
1864
|
+
const candidates = rows.filter(
|
|
1865
|
+
(candidate) => candidate.entity === want.selection.entity && matchesKey(candidate, want.selection.where, rows)
|
|
1866
|
+
).map((candidate) => fieldValue(candidate, want.field));
|
|
1867
|
+
const own = fieldValue(row2, field2);
|
|
1868
|
+
return candidates.some((candidate) => valuesEqual(own, candidate));
|
|
1869
|
+
}
|
|
1870
|
+
return valuesEqual(fieldValue(row2, field2), want);
|
|
1871
|
+
}
|
|
1872
|
+
function isWithinValue(value2) {
|
|
1873
|
+
return value2 != null && typeof value2 === "object" && "kind" in value2;
|
|
1874
|
+
}
|
|
1875
|
+
function fieldValue(row2, field2) {
|
|
1876
|
+
return row2.set[field2] ?? { ref: `${row2.as}.${field2}` };
|
|
1877
|
+
}
|
|
1878
|
+
function valuesEqual(a, b) {
|
|
1879
|
+
const aRef = isRefValue(a);
|
|
1880
|
+
const bRef = isRefValue(b);
|
|
1881
|
+
if (aRef != null || bRef != null) {
|
|
1882
|
+
return aRef === bRef;
|
|
1883
|
+
}
|
|
1884
|
+
if (isTemplateValue(a) || isTemplateValue(b)) {
|
|
1885
|
+
return false;
|
|
1886
|
+
}
|
|
1887
|
+
return sameSetValue(a, b);
|
|
1888
|
+
}
|
|
1889
|
+
function resolveTest(workflow2, sim, name) {
|
|
1890
|
+
const steps = workflow2.steps.map((step) => ({
|
|
1891
|
+
action: step.action,
|
|
1892
|
+
expect: step.expect.flatMap((predicate) => resolvePredicate(predicate, sim.choices))
|
|
1893
|
+
}));
|
|
1894
|
+
const world = [...workflow2.world, ...sim.candidate.maybes];
|
|
1895
|
+
const singletons = seedSingles(workflow2.singletons, sim.candidate.pins);
|
|
1896
|
+
const used = usedRefHeads({ singletons, steps, workflow: workflow2, world });
|
|
1919
1897
|
return {
|
|
1920
|
-
|
|
1921
|
-
|
|
1898
|
+
absent: workflow2.absent,
|
|
1899
|
+
exclusive: workflow2.exclusive,
|
|
1900
|
+
intent: workflow2.intent,
|
|
1901
|
+
name,
|
|
1902
|
+
params: Object.fromEntries(
|
|
1903
|
+
Object.entries(workflow2.params).filter(([param]) => used.has(param))
|
|
1904
|
+
),
|
|
1905
|
+
singletons,
|
|
1906
|
+
slug: slugify(name),
|
|
1907
|
+
steps,
|
|
1908
|
+
workflow: workflow2.name,
|
|
1909
|
+
world
|
|
1922
1910
|
};
|
|
1923
1911
|
}
|
|
1924
|
-
function
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
}
|
|
1912
|
+
function resolvePredicate(predicate, choices) {
|
|
1913
|
+
if (predicate.kind === "not") {
|
|
1914
|
+
const inner = resolvePredicate(predicate.predicate, choices);
|
|
1915
|
+
return inner.map((p) => ({ kind: "not", predicate: p }));
|
|
1916
|
+
}
|
|
1917
|
+
if (predicate.kind === "and") {
|
|
1918
|
+
return [
|
|
1919
|
+
{
|
|
1920
|
+
kind: "and",
|
|
1921
|
+
predicates: predicate.predicates.flatMap((p) => resolvePredicate(p, choices))
|
|
1922
|
+
}
|
|
1923
|
+
];
|
|
1924
|
+
}
|
|
1925
|
+
if (predicate.kind !== "when") {
|
|
1926
|
+
return [predicate];
|
|
1927
|
+
}
|
|
1928
|
+
const picked = choices.get(predicate);
|
|
1929
|
+
const row2 = picked == null ? void 0 : predicate.branches[picked];
|
|
1930
|
+
return row2 == null ? [] : row2.consequence.flatMap((c) => resolvePredicate(c, choices));
|
|
1929
1931
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1932
|
+
|
|
1933
|
+
// src/build.ts
|
|
1934
|
+
function buildLockfile(input) {
|
|
1935
|
+
assertUniqueNames(input.entities);
|
|
1936
|
+
const lockfile = lockfileSchema.parse({
|
|
1937
|
+
entities: input.entities.map((handle) => handle.schema),
|
|
1938
|
+
singletons: input.singletons.map((handle) => handle.schema),
|
|
1939
|
+
valueSpaces: dedupeByName([
|
|
1940
|
+
...input.entities.flatMap((handle) => handle.valueSpaces),
|
|
1941
|
+
...input.singletons.flatMap((handle) => handle.valueSpaces)
|
|
1942
|
+
]),
|
|
1943
|
+
workflows: input.workflows.map(
|
|
1944
|
+
(ripploWorkflow) => expandWorkflow(ripploWorkflow.spec, { singletonValues: singletonValuesOf(input.singletons) })
|
|
1945
|
+
)
|
|
1935
1946
|
});
|
|
1947
|
+
assertNoContradictions(lockfile);
|
|
1948
|
+
return lockfile;
|
|
1936
1949
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
import { z } from "zod";
|
|
1943
|
-
var budgetSchema = z.enum(["fast", "slow", "async"]);
|
|
1944
|
-
var valueRefSchema = z.object({ ref: z.string().min(1) });
|
|
1945
|
-
var primitiveSchema = z.union([z.string(), z.number(), z.boolean()]);
|
|
1946
|
-
var templateSchema = z.object({
|
|
1947
|
-
template: z.array(z.union([z.string(), valueRefSchema])).min(1)
|
|
1948
|
-
});
|
|
1949
|
-
var setValueSchema = z.union([valueRefSchema, primitiveSchema, templateSchema, z.null()]);
|
|
1950
|
-
var changedSchema = z.object({ kind: z.literal("changed") });
|
|
1951
|
-
var updateValueSchema = z.union([setValueSchema, changedSchema]);
|
|
1952
|
-
var stringValueSchema = z.union([z.string(), valueRefSchema, templateSchema]);
|
|
1953
|
-
var roleLocatorSchema = z.object({
|
|
1954
|
-
by: z.literal("role"),
|
|
1955
|
-
name: z.union([stringValueSchema, z.undefined()]).optional().transform((value2) => value2),
|
|
1956
|
-
role: z.string().min(1)
|
|
1957
|
-
});
|
|
1958
|
-
var testIdLocatorSchema = z.object({ by: z.literal("testId"), value: stringValueSchema });
|
|
1959
|
-
var insideLocatorSchema = z.object({
|
|
1960
|
-
by: z.literal("inside"),
|
|
1961
|
-
scope: z.lazy(() => locatorSchema),
|
|
1962
|
-
target: z.lazy(() => locatorSchema)
|
|
1963
|
-
});
|
|
1964
|
-
var locatorSchema = z.discriminatedUnion("by", [
|
|
1965
|
-
roleLocatorSchema,
|
|
1966
|
-
testIdLocatorSchema,
|
|
1967
|
-
insideLocatorSchema
|
|
1968
|
-
]);
|
|
1969
|
-
var primitiveTypeSchema = z.enum(["string", "number", "boolean"]);
|
|
1970
|
-
var consistencyClassSchema = z.enum(["strict", "eventual"]);
|
|
1971
|
-
var stringConstraintsSchema = z.object({
|
|
1972
|
-
kind: z.literal("string"),
|
|
1973
|
-
maxLength: z.number().int().positive().optional(),
|
|
1974
|
-
minLength: z.number().int().nonnegative().optional(),
|
|
1975
|
-
pattern: z.string().optional()
|
|
1976
|
-
});
|
|
1977
|
-
var numberConstraintsSchema = z.object({
|
|
1978
|
-
kind: z.literal("number"),
|
|
1979
|
-
max: z.number().int().optional(),
|
|
1980
|
-
min: z.number().int().optional()
|
|
1981
|
-
});
|
|
1982
|
-
var datetimeConstraintsSchema = z.object({
|
|
1983
|
-
kind: z.literal("datetime"),
|
|
1984
|
-
maxOffsetDays: z.number().int(),
|
|
1985
|
-
minOffsetDays: z.number().int()
|
|
1986
|
-
});
|
|
1987
|
-
var constraintsSchema = z.discriminatedUnion("kind", [
|
|
1988
|
-
stringConstraintsSchema,
|
|
1989
|
-
numberConstraintsSchema,
|
|
1990
|
-
datetimeConstraintsSchema
|
|
1991
|
-
]);
|
|
1992
|
-
var generatorSchema = z.enum([
|
|
1993
|
-
"company.name",
|
|
1994
|
-
"date.iso",
|
|
1995
|
-
"internet.email",
|
|
1996
|
-
"internet.url",
|
|
1997
|
-
"person.fullName",
|
|
1998
|
-
"lorem.slug",
|
|
1999
|
-
"lorem.word"
|
|
2000
|
-
]);
|
|
2001
|
-
var valueSpaceSchema = z.object({
|
|
2002
|
-
constraints: constraintsSchema.optional(),
|
|
2003
|
-
generator: generatorSchema,
|
|
2004
|
-
name: z.string().min(1),
|
|
2005
|
-
type: primitiveTypeSchema,
|
|
2006
|
-
values: z.array(primitiveSchema).min(1).optional()
|
|
2007
|
-
});
|
|
2008
|
-
var propSpecSchema = z.object({
|
|
2009
|
-
consistency: consistencyClassSchema.default("strict"),
|
|
2010
|
-
optional: z.boolean(),
|
|
2011
|
-
type: primitiveTypeSchema,
|
|
2012
|
-
valueSpace: z.string().min(1).optional()
|
|
2013
|
-
});
|
|
2014
|
-
var sourceSchema = z.enum(["backend", "client"]);
|
|
2015
|
-
var entitySchemaSchema = z.object({
|
|
2016
|
-
description: z.string().optional(),
|
|
2017
|
-
identity: z.array(z.string().min(1)).min(1),
|
|
2018
|
-
identityKind: z.enum(["surrogate", "natural"]),
|
|
2019
|
-
name: z.string().min(1),
|
|
2020
|
-
props: z.record(z.string().min(1), propSpecSchema),
|
|
2021
|
-
source: sourceSchema.default("backend")
|
|
2022
|
-
});
|
|
2023
|
-
var singletonSchemaSchema = z.object({
|
|
2024
|
-
consistency: consistencyClassSchema.default("strict"),
|
|
2025
|
-
default: primitiveSchema,
|
|
2026
|
-
description: z.string().optional(),
|
|
2027
|
-
name: z.string().min(1),
|
|
2028
|
-
source: sourceSchema.default("backend"),
|
|
2029
|
-
type: primitiveTypeSchema,
|
|
2030
|
-
valueSpace: z.string().min(1).optional()
|
|
2031
|
-
});
|
|
2032
|
-
var browserSingletonSchema = z.enum(["url", "title", "viewport"]);
|
|
2033
|
-
|
|
2034
|
-
// ../spec/src/predicate.ts
|
|
2035
|
-
import { z as z2 } from "zod";
|
|
2036
|
-
var stateAssertionSchema = z2.discriminatedUnion("kind", [
|
|
2037
|
-
z2.object({
|
|
2038
|
-
as: z2.string().min(1),
|
|
2039
|
-
kind: z2.literal("created"),
|
|
2040
|
-
props: z2.record(z2.string().min(1), setValueSchema)
|
|
2041
|
-
}),
|
|
2042
|
-
z2.object({
|
|
2043
|
-
as: z2.string().min(1),
|
|
2044
|
-
kind: z2.literal("updated"),
|
|
2045
|
-
props: z2.record(z2.string().min(1), updateValueSchema)
|
|
2046
|
-
}),
|
|
2047
|
-
z2.object({ kind: z2.literal("deleted") })
|
|
2048
|
-
]);
|
|
2049
|
-
var singletonAssertionSchema = z2.object({ kind: z2.literal("is"), value: setValueSchema });
|
|
2050
|
-
var whereValueSchema = z2.lazy(
|
|
2051
|
-
() => z2.union([setValueSchema, withinSchema])
|
|
2052
|
-
);
|
|
2053
|
-
var selectionSchema = z2.object({
|
|
2054
|
-
entity: z2.string().min(1),
|
|
2055
|
-
where: z2.record(z2.string().min(1), whereValueSchema)
|
|
2056
|
-
});
|
|
2057
|
-
var withinSchema = z2.object({
|
|
2058
|
-
field: z2.string().min(1),
|
|
2059
|
-
kind: z2.literal("within"),
|
|
2060
|
-
selection: selectionSchema
|
|
2061
|
-
});
|
|
2062
|
-
var wait = z2.union([budgetSchema, z2.undefined()]).optional().transform((value2) => value2);
|
|
2063
|
-
var singletonPredicateSchema = z2.object({
|
|
2064
|
-
assertion: singletonAssertionSchema,
|
|
2065
|
-
kind: z2.literal("singleton"),
|
|
2066
|
-
singleton: z2.string().min(1),
|
|
2067
|
-
wait
|
|
2068
|
-
});
|
|
2069
|
-
var countPredicateSchema = z2.object({
|
|
2070
|
-
entity: z2.string().min(1),
|
|
2071
|
-
kind: z2.literal("count"),
|
|
2072
|
-
value: z2.number().int().nonnegative()
|
|
2073
|
-
});
|
|
2074
|
-
var conditionSchema = z2.lazy(
|
|
2075
|
-
() => z2.discriminatedUnion("kind", [
|
|
2076
|
-
singletonPredicateSchema,
|
|
2077
|
-
countPredicateSchema,
|
|
2078
|
-
z2.object({ kind: z2.literal("not"), predicate: conditionSchema }),
|
|
2079
|
-
z2.object({ kind: z2.literal("and"), predicates: z2.array(conditionSchema) })
|
|
2080
|
-
])
|
|
2081
|
-
);
|
|
2082
|
-
var whenBranchSchema = z2.lazy(
|
|
2083
|
-
() => z2.object({
|
|
2084
|
-
condition: z2.union([conditionSchema, z2.undefined()]).optional().transform((value2) => value2),
|
|
2085
|
-
consequence: z2.array(predicateSchema),
|
|
2086
|
-
name: z2.string().min(1)
|
|
2087
|
-
})
|
|
2088
|
-
);
|
|
2089
|
-
var predicateSchema = z2.lazy(
|
|
2090
|
-
() => z2.discriminatedUnion("kind", [
|
|
2091
|
-
z2.object({ kind: z2.literal("visible"), locator: locatorSchema, wait }),
|
|
2092
|
-
z2.object({ kind: z2.literal("disabled"), locator: locatorSchema, wait }),
|
|
2093
|
-
z2.object({ kind: z2.literal("enabled"), locator: locatorSchema, wait }),
|
|
2094
|
-
z2.object({ kind: z2.literal("focused"), locator: locatorSchema, wait }),
|
|
2095
|
-
z2.object({ kind: z2.literal("checked"), locator: locatorSchema, wait }),
|
|
2096
|
-
z2.object({ kind: z2.literal("value"), locator: locatorSchema, value: stringValueSchema, wait }),
|
|
2097
|
-
z2.object({ kind: z2.literal("text"), locator: locatorSchema, value: stringValueSchema, wait }),
|
|
2098
|
-
singletonPredicateSchema,
|
|
2099
|
-
z2.object({
|
|
2100
|
-
kind: z2.literal("browser"),
|
|
2101
|
-
name: browserSingletonSchema,
|
|
2102
|
-
value: stringValueSchema,
|
|
2103
|
-
wait
|
|
2104
|
-
}),
|
|
2105
|
-
z2.object({
|
|
2106
|
-
assertion: stateAssertionSchema,
|
|
2107
|
-
entity: z2.string().min(1),
|
|
2108
|
-
key: z2.record(z2.string().min(1), whereValueSchema),
|
|
2109
|
-
kind: z2.literal("state"),
|
|
2110
|
-
wait
|
|
2111
|
-
}),
|
|
2112
|
-
z2.object({ kind: z2.literal("not"), predicate: predicateSchema }),
|
|
2113
|
-
z2.object({ kind: z2.literal("and"), predicates: z2.array(predicateSchema) }),
|
|
2114
|
-
countPredicateSchema,
|
|
2115
|
-
z2.object({ branches: z2.array(whenBranchSchema), kind: z2.literal("when") })
|
|
2116
|
-
])
|
|
2117
|
-
);
|
|
2118
|
-
|
|
2119
|
-
// ../spec/src/codec.ts
|
|
2120
|
-
import { z as z3 } from "zod";
|
|
2121
|
-
var envelopeSchema = z3.object({
|
|
2122
|
-
__codec: z3.string().min(1),
|
|
2123
|
-
data: z3.unknown(),
|
|
2124
|
-
version: z3.number().int().positive()
|
|
2125
|
-
});
|
|
2126
|
-
var CodecVersionError = class extends Error {
|
|
2127
|
-
codec;
|
|
2128
|
-
currentVersion;
|
|
2129
|
-
gotVersion;
|
|
2130
|
-
constructor(params) {
|
|
2131
|
-
super(
|
|
2132
|
-
`Unsupported ${params.codec} version ${String(params.gotVersion)} (current ${String(params.currentVersion)}). Upgrade Ripplo or rebuild with a compatible CLI.`
|
|
2133
|
-
);
|
|
2134
|
-
this.name = "CodecVersionError";
|
|
2135
|
-
this.codec = params.codec;
|
|
2136
|
-
this.currentVersion = params.currentVersion;
|
|
2137
|
-
this.gotVersion = params.gotVersion;
|
|
2138
|
-
}
|
|
2139
|
-
};
|
|
2140
|
-
var CodecMismatchError = class extends Error {
|
|
2141
|
-
constructor(params) {
|
|
2142
|
-
super(`Codec mismatch: expected "${params.expected}", got "${params.got}"`);
|
|
2143
|
-
this.name = "CodecMismatchError";
|
|
1950
|
+
function assertUniqueNames(entities) {
|
|
1951
|
+
const names = entities.map((handle) => handle.schema.name);
|
|
1952
|
+
const duplicate = names.find((name, index) => names.indexOf(name) !== index);
|
|
1953
|
+
if (duplicate != null) {
|
|
1954
|
+
throw new Error(`duplicate entity name "${duplicate}" \u2014 each entity name must be unique`);
|
|
2144
1955
|
}
|
|
2145
|
-
}
|
|
2146
|
-
function
|
|
2147
|
-
name,
|
|
2148
|
-
|
|
2149
|
-
}
|
|
1956
|
+
}
|
|
1957
|
+
function dedupeByName(spaces) {
|
|
1958
|
+
const byName = new Map(spaces.map((space) => [space.name, space]));
|
|
1959
|
+
return [...byName.values()];
|
|
1960
|
+
}
|
|
1961
|
+
function assertNoContradictions(lockfile) {
|
|
1962
|
+
lockfile.workflows.forEach((workflow2) => {
|
|
1963
|
+
assertNoContradiction(workflow2);
|
|
1964
|
+
assertNoDanglingRefs(workflow2);
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
function assertNoContradiction(workflow2) {
|
|
1968
|
+
const setups = [...workflow2.world, ...workflow2.maybe];
|
|
1969
|
+
workflow2.absent.forEach((absence) => {
|
|
1970
|
+
const clash = setups.find(
|
|
1971
|
+
(setup) => setup.entity === absence.entity && whereMatches(absence.where, setup.set)
|
|
1972
|
+
);
|
|
1973
|
+
if (clash != null) {
|
|
1974
|
+
throw new Error(
|
|
1975
|
+
`test "${workflow2.name}": creates a "${absence.entity}" ("${clash.as}") that a none(${absence.entity}, \u2026) in its world forbids`
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
function whereMatches(where, set) {
|
|
1981
|
+
return Object.entries(where).every(([field2, want]) => sameValue(want, set[field2]));
|
|
1982
|
+
}
|
|
1983
|
+
function sameValue(a, b) {
|
|
1984
|
+
return b !== void 0 && sameSetValue(a, b);
|
|
1985
|
+
}
|
|
1986
|
+
function assertNoDanglingRefs(workflow2) {
|
|
1987
|
+
const aliases = new Set([...workflow2.world, ...workflow2.maybe].map((setup) => setup.as));
|
|
1988
|
+
const paramKeys = new Set(Object.keys(workflow2.params));
|
|
1989
|
+
const fieldSets = [
|
|
1990
|
+
...workflow2.world.map((setup) => setup.set),
|
|
1991
|
+
...workflow2.maybe.map((setup) => setup.set),
|
|
1992
|
+
...workflow2.absent.map((absence) => absence.where)
|
|
1993
|
+
];
|
|
1994
|
+
fieldSets.forEach((set) => {
|
|
1995
|
+
assertSetRefs(workflow2.name, set, aliases, paramKeys);
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
function assertSetRefs(workflowName, set, aliases, paramKeys) {
|
|
1999
|
+
Object.values(set).forEach((value2) => {
|
|
2000
|
+
if (!isRef(value2) || paramKeys.has(value2.ref)) {
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
const lastDot = value2.ref.lastIndexOf(".");
|
|
2004
|
+
if (lastDot === -1) {
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
const aliasPath = value2.ref.slice(0, lastDot);
|
|
2008
|
+
if (!aliases.has(aliasPath)) {
|
|
2009
|
+
throw new Error(
|
|
2010
|
+
`test "${workflowName}": ref "${value2.ref}" points at unknown alias "${aliasPath}"`
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
function isRef(value2) {
|
|
2016
|
+
return value2 != null && typeof value2 === "object" && "ref" in value2;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
// src/ripplo.ts
|
|
2020
|
+
function createRipplo(input) {
|
|
2150
2021
|
return {
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2022
|
+
entities: input.entities,
|
|
2023
|
+
lockfile: buildLockfile({
|
|
2024
|
+
entities: input.entities,
|
|
2025
|
+
singletons: input.singletons,
|
|
2026
|
+
workflows: input.workflows
|
|
2027
|
+
}),
|
|
2028
|
+
singletons: input.singletons,
|
|
2029
|
+
workflows: input.workflows
|
|
2159
2030
|
};
|
|
2160
2031
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
if (
|
|
2171
|
-
|
|
2032
|
+
|
|
2033
|
+
// src/locators.ts
|
|
2034
|
+
function role(roleName, name, ...bindings) {
|
|
2035
|
+
return { by: "role", name: name == null ? void 0 : nameValue(name, bindings), role: roleName };
|
|
2036
|
+
}
|
|
2037
|
+
function inside(scope, target) {
|
|
2038
|
+
return { by: "inside", scope, target };
|
|
2039
|
+
}
|
|
2040
|
+
function testId(value2, ...bindings) {
|
|
2041
|
+
if (typeof value2 === "string") {
|
|
2042
|
+
return { by: "testId", value: value2 };
|
|
2172
2043
|
}
|
|
2173
|
-
return
|
|
2044
|
+
return { by: "testId", value: stringValueFromTemplate(value2, bindings) };
|
|
2174
2045
|
}
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
var
|
|
2178
|
-
var
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
var
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
var
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
var
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
var
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
var
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
var
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
intent: z4.string().min(1),
|
|
2231
|
-
maybe: z4.array(setupSchema).default([]),
|
|
2232
|
-
name: z4.string().min(1),
|
|
2233
|
-
params: z4.record(z4.string().min(1), paramSchema),
|
|
2234
|
-
singletons: z4.record(z4.string().min(1), setValueSchema).default({}),
|
|
2235
|
-
sourcePath: z4.string().min(1).optional(),
|
|
2236
|
-
steps: z4.array(stepSchema).default([]),
|
|
2237
|
-
stub: z4.boolean().default(false),
|
|
2238
|
-
tests: z4.array(resolvedTestSchema).default([]),
|
|
2239
|
-
world: z4.array(setupSchema).default([])
|
|
2240
|
-
});
|
|
2241
|
-
var fixtureEntrySchema = z4.object({
|
|
2242
|
-
sha256: z4.string().regex(/^[0-9a-f]{64}$/u),
|
|
2243
|
-
size: z4.number().int().nonnegative()
|
|
2244
|
-
});
|
|
2245
|
-
var lockfileSchema = z4.object({
|
|
2246
|
-
entities: z4.array(entitySchemaSchema),
|
|
2247
|
-
fixtures: z4.record(z4.string().min(1), fixtureEntrySchema).default({}),
|
|
2248
|
-
singletons: z4.array(singletonSchemaSchema).default([]),
|
|
2249
|
-
valueSpaces: z4.array(valueSpaceSchema),
|
|
2250
|
-
workflows: z4.array(workflowSchema)
|
|
2251
|
-
});
|
|
2252
|
-
var lockfileCodec = defineCodec({ name: "ripplo-lockfile", schema: lockfileSchema });
|
|
2253
|
-
|
|
2254
|
-
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
2255
|
-
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
2256
|
-
var configure = import__.default.configure;
|
|
2257
|
-
|
|
2258
|
-
// ../spec/src/sync-payload.ts
|
|
2259
|
-
import { z as z5 } from "zod";
|
|
2260
|
-
var stepDescriptorSchema = z5.object({
|
|
2261
|
-
index: z5.number().int().nonnegative(),
|
|
2262
|
-
kind: z5.string(),
|
|
2263
|
-
target: z5.string(),
|
|
2264
|
-
value: z5.string()
|
|
2265
|
-
});
|
|
2266
|
-
function slugify2(name) {
|
|
2267
|
-
return name.toLowerCase().replaceAll(/[^a-z0-9]/g, "-").split("-").filter((part) => part.length > 0).join("-");
|
|
2046
|
+
var alertdialog = named("alertdialog");
|
|
2047
|
+
var button = named("button");
|
|
2048
|
+
var cell = named("cell");
|
|
2049
|
+
var checkbox = named("checkbox");
|
|
2050
|
+
var columnheader = named("columnheader");
|
|
2051
|
+
var combobox = named("combobox");
|
|
2052
|
+
var dialog = named("dialog");
|
|
2053
|
+
var heading = named("heading");
|
|
2054
|
+
var img = named("img");
|
|
2055
|
+
var link = named("link");
|
|
2056
|
+
var listitem = named("listitem");
|
|
2057
|
+
var menuitem = named("menuitem");
|
|
2058
|
+
var option = named("option");
|
|
2059
|
+
var radio = named("radio");
|
|
2060
|
+
var row = named("row");
|
|
2061
|
+
var searchbox = named("searchbox");
|
|
2062
|
+
var slider = named("slider");
|
|
2063
|
+
var spinbutton = named("spinbutton");
|
|
2064
|
+
var switchControl = named("switch");
|
|
2065
|
+
var tab = named("tab");
|
|
2066
|
+
var textbox = named("textbox");
|
|
2067
|
+
var treeitem = named("treeitem");
|
|
2068
|
+
var alert = container("alert");
|
|
2069
|
+
var banner = container("banner");
|
|
2070
|
+
var complementary = container("complementary");
|
|
2071
|
+
var contentinfo = container("contentinfo");
|
|
2072
|
+
var form = container("form");
|
|
2073
|
+
var grid = container("grid");
|
|
2074
|
+
var group = container("group");
|
|
2075
|
+
var list = container("list");
|
|
2076
|
+
var main = container("main");
|
|
2077
|
+
var menu = container("menu");
|
|
2078
|
+
var navigation = container("navigation");
|
|
2079
|
+
var progressbar = container("progressbar");
|
|
2080
|
+
var radiogroup = container("radiogroup");
|
|
2081
|
+
var region = container("region");
|
|
2082
|
+
var status = container("status");
|
|
2083
|
+
var table = container("table");
|
|
2084
|
+
var tablist = container("tablist");
|
|
2085
|
+
var tabpanel = container("tabpanel");
|
|
2086
|
+
var toolbar = container("toolbar");
|
|
2087
|
+
function named(roleName) {
|
|
2088
|
+
return (name, ...bindings) => role(roleName, name, ...bindings);
|
|
2089
|
+
}
|
|
2090
|
+
function container(roleName) {
|
|
2091
|
+
return (name, ...bindings) => role(roleName, name, ...bindings);
|
|
2092
|
+
}
|
|
2093
|
+
function nameValue(name, bindings) {
|
|
2094
|
+
if (typeof name === "string") {
|
|
2095
|
+
return name;
|
|
2096
|
+
}
|
|
2097
|
+
if (isBinding(name)) {
|
|
2098
|
+
return name;
|
|
2099
|
+
}
|
|
2100
|
+
return stringValueFromTemplate(name, bindings);
|
|
2268
2101
|
}
|
|
2269
2102
|
|
|
2270
|
-
//
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
});
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
runId: z7.string().min(1),
|
|
2305
|
-
singletons: z7.record(z7.string().min(1), cellSchema).default({})
|
|
2306
|
-
});
|
|
2307
|
-
var setupRowSchema = z7.object({
|
|
2308
|
-
as: z7.string().min(1),
|
|
2309
|
-
row: rowSchema,
|
|
2310
|
-
session: sessionSchema.optional()
|
|
2311
|
-
});
|
|
2312
|
-
var setupResponseSchema = z7.object({
|
|
2313
|
-
rows: z7.array(setupRowSchema)
|
|
2314
|
-
});
|
|
2315
|
-
var stateRequestSchema = z7.object({
|
|
2316
|
-
entities: z7.array(z7.string().min(1)),
|
|
2317
|
-
runId: z7.string().min(1),
|
|
2318
|
-
singletons: z7.array(z7.string().min(1)).default([])
|
|
2319
|
-
});
|
|
2320
|
-
var stateResponseSchema = z7.object({
|
|
2321
|
-
entities: z7.record(z7.string().min(1), z7.array(rowSchema)),
|
|
2322
|
-
singletons: z7.record(z7.string().min(1), cellSchema).default({})
|
|
2323
|
-
});
|
|
2324
|
-
var teardownRequestSchema = z7.object({
|
|
2325
|
-
runId: z7.string().min(1)
|
|
2326
|
-
});
|
|
2327
|
-
var teardownResponseSchema = z7.object({
|
|
2328
|
-
ok: z7.literal(true)
|
|
2329
|
-
});
|
|
2103
|
+
// src/singleton.ts
|
|
2104
|
+
function singleton(name, config) {
|
|
2105
|
+
const valueSpaceName = `singleton.${name}`;
|
|
2106
|
+
const { constraints, generator, primitive } = config.value;
|
|
2107
|
+
const schema = {
|
|
2108
|
+
consistency: config.consistency ?? "strict",
|
|
2109
|
+
default: config.default,
|
|
2110
|
+
description: config.description,
|
|
2111
|
+
name,
|
|
2112
|
+
source: config.source,
|
|
2113
|
+
type: primitive,
|
|
2114
|
+
valueSpace: valueSpaceName
|
|
2115
|
+
};
|
|
2116
|
+
return {
|
|
2117
|
+
is: isPredicate,
|
|
2118
|
+
name,
|
|
2119
|
+
schema,
|
|
2120
|
+
source: config.source,
|
|
2121
|
+
value: { __t: void 0, entity: name, field: "value", primitive, valueSpaceName },
|
|
2122
|
+
valueSpaces: [{ constraints, generator, name: valueSpaceName, type: primitive }],
|
|
2123
|
+
of: (value2) => ({
|
|
2124
|
+
__entity: { kind: "singletonState", singleton: name, value: toSetValue(value2) },
|
|
2125
|
+
is: isPredicate
|
|
2126
|
+
})
|
|
2127
|
+
};
|
|
2128
|
+
function isPredicate(value2) {
|
|
2129
|
+
return condLeaf({
|
|
2130
|
+
assertion: { kind: "is", value: value2 },
|
|
2131
|
+
kind: "singleton",
|
|
2132
|
+
singleton: name,
|
|
2133
|
+
wait: void 0
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2330
2137
|
|
|
2331
|
-
// src/
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2138
|
+
// src/builtins.ts
|
|
2139
|
+
function browserSingleton(name) {
|
|
2140
|
+
return {
|
|
2141
|
+
name,
|
|
2142
|
+
is: (strings, ...values) => leaf({
|
|
2143
|
+
kind: "browser",
|
|
2336
2144
|
name,
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2145
|
+
value: stringValueFromTemplate(strings, values),
|
|
2146
|
+
wait: void 0
|
|
2147
|
+
})
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
var url = browserSingleton("url");
|
|
2151
|
+
var title = browserSingleton("title");
|
|
2152
|
+
var viewport = browserSingleton("viewport");
|
|
2153
|
+
|
|
2154
|
+
// src/actions.ts
|
|
2155
|
+
function goto(strings, ...values) {
|
|
2156
|
+
return stepBuilder({ kind: "goto", url: stringValueFromTemplate(strings, values) }, [], []);
|
|
2157
|
+
}
|
|
2158
|
+
function click(locator) {
|
|
2159
|
+
return stepBuilder({ kind: "click", locator }, [], []);
|
|
2160
|
+
}
|
|
2161
|
+
function dblclick(locator) {
|
|
2162
|
+
return stepBuilder({ kind: "dblclick", locator }, [], []);
|
|
2163
|
+
}
|
|
2164
|
+
function fill(locator, binding) {
|
|
2165
|
+
return stepBuilder({ kind: "fill", locator, value: binding }, [], []);
|
|
2166
|
+
}
|
|
2167
|
+
function clear(locator) {
|
|
2168
|
+
return stepBuilder({ kind: "clear", locator }, [], []);
|
|
2169
|
+
}
|
|
2170
|
+
function select(locator, binding) {
|
|
2171
|
+
return stepBuilder({ kind: "select", locator, value: binding }, [], []);
|
|
2172
|
+
}
|
|
2173
|
+
function check(locator) {
|
|
2174
|
+
return stepBuilder({ kind: "check", locator }, [], []);
|
|
2175
|
+
}
|
|
2176
|
+
function uncheck(locator) {
|
|
2177
|
+
return stepBuilder({ kind: "uncheck", locator }, [], []);
|
|
2178
|
+
}
|
|
2179
|
+
function hover(locator) {
|
|
2180
|
+
return stepBuilder({ kind: "hover", locator }, [], []);
|
|
2181
|
+
}
|
|
2182
|
+
function upload(locator, files) {
|
|
2183
|
+
return stepBuilder({ files: [...files], kind: "upload", locator }, [], []);
|
|
2184
|
+
}
|
|
2185
|
+
function press(pressKey, locator) {
|
|
2186
|
+
return stepBuilder({ key: pressKey, kind: "press", locator }, [], []);
|
|
2187
|
+
}
|
|
2188
|
+
function stepBuilder(action, expected, captures) {
|
|
2344
2189
|
return {
|
|
2345
|
-
|
|
2346
|
-
|
|
2190
|
+
captures,
|
|
2191
|
+
step: { action, expect: [...expected] },
|
|
2192
|
+
expect: (...predicates) => stepBuilder(
|
|
2193
|
+
action,
|
|
2194
|
+
[...expected, ...predicates.map((p) => toPredicate(p))],
|
|
2195
|
+
[...captures, ...predicates.flatMap((p) => isCapturePredicate(p) ? [captureOf(p)] : [])]
|
|
2196
|
+
)
|
|
2347
2197
|
};
|
|
2348
2198
|
}
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2199
|
+
|
|
2200
|
+
// src/coherence.ts
|
|
2201
|
+
function assertEntityCoherence(entities) {
|
|
2202
|
+
const counts = entities.reduce(
|
|
2203
|
+
(acc, d) => new Map([...acc, [d.entity, (acc.get(d.entity) ?? 0) + 1]]),
|
|
2204
|
+
/* @__PURE__ */ new Map()
|
|
2205
|
+
);
|
|
2206
|
+
const over = entities.find((d) => d.kind === "only" && (counts.get(d.entity) ?? 0) > 1);
|
|
2207
|
+
if (over != null) {
|
|
2208
|
+
throw new Error(
|
|
2209
|
+
`entity "${over.entity}" is declared only() but seeded ${String(counts.get(over.entity) ?? 0)} times \u2014 only() means a single exclusive instance; use of() for multiple instances`
|
|
2210
|
+
);
|
|
2352
2211
|
}
|
|
2353
|
-
Reflect.set(globalThis, CLIENT_MOUNT_KEY, createClientEngine(ripplo, impls));
|
|
2354
2212
|
}
|
|
2355
2213
|
|
|
2356
|
-
// src/
|
|
2357
|
-
function
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
const
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2214
|
+
// src/finalize.ts
|
|
2215
|
+
function finalize(body) {
|
|
2216
|
+
const descriptors = [...new Set(body.given.map((item) => item.__entity))];
|
|
2217
|
+
const absences = descriptors.filter((d) => d.kind === "none");
|
|
2218
|
+
const singletonStates = descriptors.filter(
|
|
2219
|
+
(d) => d.kind === "singletonState"
|
|
2220
|
+
);
|
|
2221
|
+
const entities = topoSort(
|
|
2222
|
+
descriptors.filter(
|
|
2223
|
+
(d) => d.kind === "of" || d.kind === "only" || d.kind === "maybe"
|
|
2224
|
+
)
|
|
2225
|
+
);
|
|
2226
|
+
assertNoMaybeRefs({ absences, entities, singletonStates });
|
|
2227
|
+
assertEntityCoherence(entities);
|
|
2228
|
+
const captures = body.steps.flatMap((builder) => builder.captures);
|
|
2229
|
+
assertScopedConditions(body.steps, new Set(singletonStates.map((s) => s.singleton)));
|
|
2230
|
+
const aliases = assignAliases([...entities, ...captures.map((c) => c.descriptor)]);
|
|
2231
|
+
const { names, params } = assignParams(allBindings(body, entities, absences, singletonStates));
|
|
2232
|
+
const ctx = {
|
|
2233
|
+
aliases,
|
|
2234
|
+
captures: new Map(
|
|
2235
|
+
captures.map((c) => [c.assertion, c.descriptor])
|
|
2236
|
+
),
|
|
2237
|
+
params: names
|
|
2238
|
+
};
|
|
2239
|
+
return {
|
|
2240
|
+
absent: absences.map((a) => ({ entity: a.entity, where: setMap(a.where, ctx) })),
|
|
2241
|
+
exclusive: [...new Set(entities.filter((d) => d.kind === "only").map((d) => d.entity))],
|
|
2242
|
+
maybe: setupsOf(entities, "maybe", ctx),
|
|
2243
|
+
params,
|
|
2244
|
+
singletons: resolveSingletons(singletonStates, ctx),
|
|
2245
|
+
steps: body.steps.map((builder) => resolveStep(builder.step, ctx)),
|
|
2246
|
+
world: entities.filter((d) => d.kind === "of" || d.kind === "only").map((d) => ({ as: aliasFor(ctx, d), entity: d.entity, set: setMap(d.props, ctx) }))
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
function resolveSingletons(states, ctx) {
|
|
2250
|
+
return states.reduce((acc, state) => {
|
|
2251
|
+
const value2 = resolveValue(state.value, ctx);
|
|
2252
|
+
const existing = acc[state.singleton];
|
|
2253
|
+
if (existing !== void 0 && !sameSetValue(existing, value2)) {
|
|
2254
|
+
throw new Error(
|
|
2255
|
+
`singleton "${state.singleton}" is given two conflicting values \u2014 a test may set each singleton to one value`
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
return { ...acc, [state.singleton]: value2 };
|
|
2259
|
+
}, {});
|
|
2260
|
+
}
|
|
2261
|
+
function assertNoMaybeRefs({ absences, entities, singletonStates }) {
|
|
2262
|
+
const offenders = [
|
|
2263
|
+
...entities.filter((d) => d.kind === "of" || d.kind === "only").flatMap((d) => maybeBindings(Object.values(d.props), `${d.kind}(${d.entity})`)),
|
|
2264
|
+
...absences.flatMap((a) => maybeBindings(Object.values(a.where), `none(${a.entity})`)),
|
|
2265
|
+
...singletonStates.flatMap((s) => maybeBindings([s.value], `singleton "${s.singleton}"`))
|
|
2368
2266
|
];
|
|
2369
|
-
|
|
2267
|
+
const first = offenders[0];
|
|
2268
|
+
if (first == null || first.binding.__bind.kind !== "field") {
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
const { descriptor, field: field2 } = first.binding.__bind;
|
|
2272
|
+
throw new Error(
|
|
2273
|
+
`${first.site} references ${descriptor.entity}.${field2} from a maybe(${descriptor.entity}) \u2014 maybe entities are not materialized at setup; reference them only in steps`
|
|
2274
|
+
);
|
|
2370
2275
|
}
|
|
2371
|
-
function
|
|
2372
|
-
|
|
2373
|
-
|
|
2276
|
+
function maybeBindings(values, site) {
|
|
2277
|
+
return values.flatMap((value2) => valueBindings(value2)).filter((b) => b.__bind.kind === "field" && b.__bind.descriptor.kind === "maybe").map((binding) => ({ binding, site }));
|
|
2278
|
+
}
|
|
2279
|
+
function valueBindings(value2) {
|
|
2280
|
+
if (isBinding(value2)) {
|
|
2281
|
+
return [value2];
|
|
2374
2282
|
}
|
|
2375
|
-
if (
|
|
2376
|
-
return [];
|
|
2283
|
+
if (isTemplate(value2)) {
|
|
2284
|
+
return value2.template.flatMap((segment) => isBinding(segment) ? [segment] : []);
|
|
2377
2285
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2286
|
+
return [];
|
|
2287
|
+
}
|
|
2288
|
+
function isTemplate(value2) {
|
|
2289
|
+
return typeof value2 === "object" && value2 !== null && "template" in value2;
|
|
2290
|
+
}
|
|
2291
|
+
function topoSort(entities) {
|
|
2292
|
+
return emitReady([], entities);
|
|
2293
|
+
}
|
|
2294
|
+
function emitReady(done, remaining) {
|
|
2295
|
+
if (remaining.length === 0) {
|
|
2296
|
+
return [...done];
|
|
2380
2297
|
}
|
|
2381
|
-
|
|
2298
|
+
const ready = remaining.filter(
|
|
2299
|
+
(d) => depsOf(d, remaining).every((dep) => done.includes(dep) || dep === d)
|
|
2300
|
+
);
|
|
2301
|
+
if (ready.length === 0) {
|
|
2302
|
+
throw new Error("cyclic dependency between given entities");
|
|
2303
|
+
}
|
|
2304
|
+
return emitReady(
|
|
2305
|
+
[...done, ...ready],
|
|
2306
|
+
remaining.filter((d) => !ready.includes(d))
|
|
2307
|
+
);
|
|
2382
2308
|
}
|
|
2383
|
-
function
|
|
2384
|
-
return
|
|
2309
|
+
function depsOf(descriptor, among) {
|
|
2310
|
+
return Object.values(descriptor.props).flatMap((v2) => valueBindings(v2)).flatMap((b) => b.__bind.kind === "field" ? [b.__bind.descriptor] : []).filter((target) => target !== descriptor && among.includes(target));
|
|
2385
2311
|
}
|
|
2386
|
-
function
|
|
2387
|
-
|
|
2388
|
-
|
|
2312
|
+
function assertScopedConditions(steps, givenSingletons) {
|
|
2313
|
+
steps.flatMap((builder) => builder.step.expect).forEach((predicate) => {
|
|
2314
|
+
assertPredicateScoped(predicate, givenSingletons);
|
|
2315
|
+
});
|
|
2389
2316
|
}
|
|
2390
|
-
function
|
|
2391
|
-
|
|
2317
|
+
function assertPredicateScoped(predicate, given) {
|
|
2318
|
+
if (predicate.kind === "not") {
|
|
2319
|
+
assertPredicateScoped(predicate.predicate, given);
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
if (predicate.kind !== "when") {
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
predicate.branches.forEach((row2) => {
|
|
2326
|
+
const names = row2.condition == null ? [] : conditionSingletons(row2.condition);
|
|
2327
|
+
names.forEach((name) => {
|
|
2328
|
+
if (!given.has(name)) {
|
|
2329
|
+
throw new Error(
|
|
2330
|
+
`when() conditions on singleton "${name}", which is not in the workflow's given \u2014 add ${name}.of(...) to given`
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
});
|
|
2334
|
+
row2.consequence.forEach((consequence) => {
|
|
2335
|
+
assertPredicateScoped(consequence, given);
|
|
2336
|
+
});
|
|
2337
|
+
});
|
|
2392
2338
|
}
|
|
2393
|
-
function
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
return [ref];
|
|
2339
|
+
function conditionSingletons(predicate) {
|
|
2340
|
+
if (predicate.kind === "singleton") {
|
|
2341
|
+
return [predicate.singleton];
|
|
2397
2342
|
}
|
|
2398
|
-
if (
|
|
2399
|
-
return
|
|
2343
|
+
if (predicate.kind === "not") {
|
|
2344
|
+
return conditionSingletons(predicate.predicate);
|
|
2345
|
+
}
|
|
2346
|
+
if (predicate.kind === "and") {
|
|
2347
|
+
return predicate.predicates.flatMap((p) => conditionSingletons(p));
|
|
2400
2348
|
}
|
|
2401
2349
|
return [];
|
|
2402
2350
|
}
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
}
|
|
2351
|
+
function assignAliases(entities) {
|
|
2352
|
+
return entities.reduce(
|
|
2353
|
+
(acc, d) => {
|
|
2354
|
+
const ordinal = acc.counts[d.entity] ?? 0;
|
|
2355
|
+
return {
|
|
2356
|
+
aliases: new Map([...acc.aliases, [d, `${d.entity}_${String(ordinal)}`]]),
|
|
2357
|
+
counts: { ...acc.counts, [d.entity]: ordinal + 1 }
|
|
2358
|
+
};
|
|
2359
|
+
},
|
|
2360
|
+
{ aliases: /* @__PURE__ */ new Map(), counts: {} }
|
|
2361
|
+
).aliases;
|
|
2362
|
+
}
|
|
2363
|
+
function assignParams(bindings) {
|
|
2364
|
+
const unique = [...new Set(bindings.filter((b) => b.__bind.kind === "param"))];
|
|
2365
|
+
const assigned = unique.reduce(
|
|
2366
|
+
(acc, b) => {
|
|
2367
|
+
const token = tokenOf(b);
|
|
2368
|
+
const ordinal = acc.counts[token.base] ?? 0;
|
|
2369
|
+
const name = ordinal === 0 ? token.base : `${token.base}_${String(ordinal - 1)}`;
|
|
2370
|
+
if (name in acc.params) {
|
|
2371
|
+
throw new Error(
|
|
2372
|
+
`param name "${name}" collides with an existing param \u2014 rename the field so deduplicated param names stay unique`
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2375
|
+
return {
|
|
2376
|
+
counts: { ...acc.counts, [token.base]: ordinal + 1 },
|
|
2377
|
+
names: new Map([...acc.names, [b, name]]),
|
|
2378
|
+
params: { ...acc.params, [name]: { valueSpace: token.valueSpace } }
|
|
2379
|
+
};
|
|
2380
|
+
},
|
|
2381
|
+
{ counts: {}, names: /* @__PURE__ */ new Map(), params: {} }
|
|
2412
2382
|
);
|
|
2383
|
+
return { names: assigned.names, params: assigned.params };
|
|
2413
2384
|
}
|
|
2414
|
-
function
|
|
2415
|
-
if (
|
|
2416
|
-
|
|
2417
|
-
}
|
|
2418
|
-
assertUniqueBranchNames(workflow2);
|
|
2419
|
-
const targets = collectTargets(workflow2.steps);
|
|
2420
|
-
const whenIds = new Map(targets.map((target, index) => [target.when, index]));
|
|
2421
|
-
const sims = proposeCandidates(workflow2, options).map((candidate) => simulate(workflow2, candidate)).filter((sim) => sim != null);
|
|
2422
|
-
if (targets.length === 0) {
|
|
2423
|
-
return { ...workflow2, tests: [requireMainTest(workflow2, sims)] };
|
|
2385
|
+
function tokenOf(binding) {
|
|
2386
|
+
if (binding.__bind.kind !== "param") {
|
|
2387
|
+
throw new Error("internal: expected a param binding");
|
|
2424
2388
|
}
|
|
2425
|
-
|
|
2426
|
-
(acc, target) => coverTarget({ acc, sims, target, whenIds, workflow: workflow2 }),
|
|
2427
|
-
{ covered: /* @__PURE__ */ new Set(), tests: [] }
|
|
2428
|
-
);
|
|
2429
|
-
return { ...workflow2, tests };
|
|
2389
|
+
return binding.__bind.token;
|
|
2430
2390
|
}
|
|
2431
|
-
function
|
|
2432
|
-
|
|
2433
|
-
|
|
2391
|
+
function allBindings(body, entities, absences, singletonStates) {
|
|
2392
|
+
return [
|
|
2393
|
+
...entities.flatMap((d) => Object.values(d.props).flatMap((v2) => valueBindings(v2))),
|
|
2394
|
+
...absences.flatMap((a) => Object.values(a.where).flatMap((v2) => valueBindings(v2))),
|
|
2395
|
+
...singletonStates.flatMap((s) => valueBindings(s.value)),
|
|
2396
|
+
...body.steps.flatMap((builder) => stepBindings(builder.step))
|
|
2397
|
+
];
|
|
2434
2398
|
}
|
|
2435
|
-
function
|
|
2436
|
-
|
|
2437
|
-
...test.world.map((setup) => setup.as),
|
|
2438
|
-
...Object.keys(test.params),
|
|
2439
|
-
...test.steps.flatMap((step) => createdAliasesIn(step))
|
|
2440
|
-
]);
|
|
2441
|
-
return [...new Set(collectRefObjects(test).map((ref) => headOf(ref)))].filter(
|
|
2442
|
-
(head) => !known.has(head)
|
|
2443
|
-
);
|
|
2399
|
+
function stepBindings(step) {
|
|
2400
|
+
return [...actionBindings(step.action), ...step.expect.flatMap((p) => predicateBindings(p))];
|
|
2444
2401
|
}
|
|
2445
|
-
function
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
return [
|
|
2451
|
-
}
|
|
2402
|
+
function actionBindings(action) {
|
|
2403
|
+
if (action.kind === "goto") {
|
|
2404
|
+
return valueBindings(action.url);
|
|
2405
|
+
}
|
|
2406
|
+
if (action.kind === "fill" || action.kind === "select") {
|
|
2407
|
+
return [...locatorBindings(action.locator), ...valueBindings(action.value)];
|
|
2408
|
+
}
|
|
2409
|
+
if (action.kind === "press") {
|
|
2410
|
+
return action.locator == null ? [] : locatorBindings(action.locator);
|
|
2411
|
+
}
|
|
2412
|
+
if (action.kind === "upload") {
|
|
2413
|
+
return locatorBindings(action.locator);
|
|
2414
|
+
}
|
|
2415
|
+
return locatorBindings(action.locator);
|
|
2452
2416
|
}
|
|
2453
|
-
function
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
return { covered: acc.covered, tests: acc.tests };
|
|
2417
|
+
function locatorBindings(locator) {
|
|
2418
|
+
if (locator.by === "inside") {
|
|
2419
|
+
return [...locatorBindings(locator.scope), ...locatorBindings(locator.target)];
|
|
2457
2420
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
return found;
|
|
2461
|
-
}
|
|
2462
|
-
const test = resolveValidTest(workflow2, s, target.name);
|
|
2463
|
-
return test == null ? null : { sim: s, test };
|
|
2464
|
-
}, null);
|
|
2465
|
-
if (picked == null) {
|
|
2466
|
-
throw new Error(
|
|
2467
|
-
`workflow "${workflow2.name}": branch "${target.name}" is unreachable \u2014 no combination of optional entities and singleton values reaches it`
|
|
2468
|
-
);
|
|
2421
|
+
if (locator.by === "role") {
|
|
2422
|
+
return locator.name == null ? [] : valueBindings(locator.name);
|
|
2469
2423
|
}
|
|
2470
|
-
|
|
2471
|
-
return {
|
|
2472
|
-
covered: /* @__PURE__ */ new Set([...acc.covered, ...reached]),
|
|
2473
|
-
tests: [...acc.tests, picked.test]
|
|
2474
|
-
};
|
|
2424
|
+
return valueBindings(locator.value);
|
|
2475
2425
|
}
|
|
2476
|
-
function
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2426
|
+
function predicateBindings(predicate) {
|
|
2427
|
+
switch (predicate.kind) {
|
|
2428
|
+
case "visible":
|
|
2429
|
+
case "disabled":
|
|
2430
|
+
case "enabled":
|
|
2431
|
+
case "focused":
|
|
2432
|
+
case "checked": {
|
|
2433
|
+
return locatorBindings(predicate.locator);
|
|
2434
|
+
}
|
|
2435
|
+
case "value":
|
|
2436
|
+
case "text": {
|
|
2437
|
+
return [...locatorBindings(predicate.locator), ...valueBindings(predicate.value)];
|
|
2438
|
+
}
|
|
2439
|
+
case "singleton": {
|
|
2440
|
+
return valueBindings(predicate.assertion.value);
|
|
2441
|
+
}
|
|
2442
|
+
case "browser": {
|
|
2443
|
+
return valueBindings(predicate.value);
|
|
2444
|
+
}
|
|
2445
|
+
case "state": {
|
|
2446
|
+
return [
|
|
2447
|
+
...predicate.assertion.kind === "deleted" ? [] : Object.values(predicate.assertion.props).flatMap(
|
|
2448
|
+
(v2) => isChanged(v2) ? [] : valueBindings(v2)
|
|
2449
|
+
),
|
|
2450
|
+
...Object.values(predicate.key).flatMap((v2) => whereBindings(v2))
|
|
2451
|
+
];
|
|
2452
|
+
}
|
|
2453
|
+
case "not": {
|
|
2454
|
+
return predicateBindings(predicate.predicate);
|
|
2455
|
+
}
|
|
2456
|
+
case "when": {
|
|
2457
|
+
return predicate.branches.flatMap((row2) => [
|
|
2458
|
+
...row2.condition == null ? [] : predicateBindings(row2.condition),
|
|
2459
|
+
...row2.consequence.flatMap((consequence) => predicateBindings(consequence))
|
|
2460
|
+
]);
|
|
2461
|
+
}
|
|
2462
|
+
case "and": {
|
|
2463
|
+
return predicate.predicates.flatMap((p) => predicateBindings(p));
|
|
2464
|
+
}
|
|
2465
|
+
case "count": {
|
|
2466
|
+
return [];
|
|
2467
|
+
}
|
|
2480
2468
|
}
|
|
2481
|
-
return `${String(id2)}:${String(index)}`;
|
|
2482
2469
|
}
|
|
2483
|
-
function
|
|
2484
|
-
|
|
2485
|
-
(found, sim) => found ?? resolveValidTest(workflow2, sim, "main"),
|
|
2486
|
-
null
|
|
2487
|
-
);
|
|
2488
|
-
if (test == null) {
|
|
2489
|
-
throw new Error(
|
|
2490
|
-
`workflow "${workflow2.name}": no valid seed found \u2014 steps may reference optional entities no candidate provides, or a condition mentions values the solver cannot pin`
|
|
2491
|
-
);
|
|
2492
|
-
}
|
|
2493
|
-
return test;
|
|
2470
|
+
function whereBindings(value2) {
|
|
2471
|
+
return isWithin2(value2) ? Object.values(value2.selection.where).flatMap((v2) => whereBindings(v2)) : valueBindings(value2);
|
|
2494
2472
|
}
|
|
2495
|
-
function
|
|
2496
|
-
|
|
2497
|
-
const dup = names.find((name, index) => names.indexOf(name) !== index);
|
|
2498
|
-
if (dup != null) {
|
|
2499
|
-
throw new Error(
|
|
2500
|
-
`workflow "${workflow2.name}": branch name "${dup}" is used twice \u2014 branch names must be unique within a workflow`
|
|
2501
|
-
);
|
|
2502
|
-
}
|
|
2473
|
+
function isWithin2(value2) {
|
|
2474
|
+
return typeof value2 === "object" && value2 !== null && "kind" in value2;
|
|
2503
2475
|
}
|
|
2504
|
-
function
|
|
2505
|
-
return
|
|
2476
|
+
function setMap(map, ctx) {
|
|
2477
|
+
return Object.fromEntries(
|
|
2478
|
+
Object.entries(map).map(([key2, value2]) => [key2, resolveValue(value2, ctx)])
|
|
2479
|
+
);
|
|
2506
2480
|
}
|
|
2507
|
-
function
|
|
2508
|
-
if (
|
|
2509
|
-
return
|
|
2510
|
-
}
|
|
2511
|
-
if (predicate.kind === "and") {
|
|
2512
|
-
return predicate.predicates.flatMap((p) => targetsIn(p));
|
|
2481
|
+
function resolveValue(value2, ctx) {
|
|
2482
|
+
if (isBinding(value2)) {
|
|
2483
|
+
return resolveBinding(value2, ctx);
|
|
2513
2484
|
}
|
|
2514
|
-
if (
|
|
2515
|
-
return
|
|
2485
|
+
if (isTemplate(value2)) {
|
|
2486
|
+
return resolveTemplate(value2, ctx);
|
|
2516
2487
|
}
|
|
2517
|
-
return
|
|
2518
|
-
{ index, name: row2.name, when: predicate },
|
|
2519
|
-
...row2.consequence.flatMap((consequence) => targetsIn(consequence))
|
|
2520
|
-
]);
|
|
2488
|
+
return value2;
|
|
2521
2489
|
}
|
|
2522
|
-
function
|
|
2523
|
-
const
|
|
2524
|
-
|
|
2525
|
-
|
|
2490
|
+
function resolveBinding(binding, ctx) {
|
|
2491
|
+
const bind = binding.__bind;
|
|
2492
|
+
if (bind.kind === "param") {
|
|
2493
|
+
const name = ctx.params.get(binding);
|
|
2494
|
+
if (name == null) {
|
|
2495
|
+
throw new Error("internal: param binding was not collected");
|
|
2496
|
+
}
|
|
2497
|
+
return { ref: name };
|
|
2498
|
+
}
|
|
2499
|
+
const alias = ctx.aliases.get(bind.descriptor);
|
|
2500
|
+
if (alias == null) {
|
|
2526
2501
|
throw new Error(
|
|
2527
|
-
`
|
|
2502
|
+
`references a "${bind.descriptor.entity}" entity that is not included in the test's given`
|
|
2528
2503
|
);
|
|
2529
2504
|
}
|
|
2530
|
-
return
|
|
2505
|
+
return { ref: `${alias}.${bind.field}` };
|
|
2531
2506
|
}
|
|
2532
|
-
function
|
|
2533
|
-
|
|
2534
|
-
|
|
2507
|
+
function resolveTemplate(template, ctx) {
|
|
2508
|
+
return {
|
|
2509
|
+
template: template.template.map(
|
|
2510
|
+
(segment) => isBinding(segment) ? resolveBinding(segment, ctx) : segment
|
|
2511
|
+
)
|
|
2512
|
+
};
|
|
2535
2513
|
}
|
|
2536
|
-
function
|
|
2537
|
-
|
|
2538
|
-
return Object.entries(domains).reduce(
|
|
2539
|
-
(acc, [param, domain]) => acc.flatMap((pins) => domain.map((value2) => ({ ...pins, [param]: value2 }))),
|
|
2540
|
-
[{}]
|
|
2541
|
-
);
|
|
2514
|
+
function setupsOf(entities, kind, ctx) {
|
|
2515
|
+
return entities.filter((d) => d.kind === kind).map((d) => ({ as: aliasFor(ctx, d), entity: d.entity, set: setMap(d.props, ctx) }));
|
|
2542
2516
|
}
|
|
2543
|
-
function
|
|
2544
|
-
const
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
return acc;
|
|
2550
|
-
}
|
|
2551
|
-
const enumValues = options.singletonValues.get(name);
|
|
2552
|
-
return { ...acc, [param]: withComplements(compared, enumValues) };
|
|
2553
|
-
}, {});
|
|
2517
|
+
function aliasFor(ctx, descriptor) {
|
|
2518
|
+
const alias = ctx.aliases.get(descriptor);
|
|
2519
|
+
if (alias == null) {
|
|
2520
|
+
throw new Error(`internal: no alias for ${descriptor.entity}`);
|
|
2521
|
+
}
|
|
2522
|
+
return alias;
|
|
2554
2523
|
}
|
|
2555
|
-
function
|
|
2556
|
-
|
|
2557
|
-
(step
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
(acc, [name, literal]) => new Map([...acc, [name, [.../* @__PURE__ */ new Set([...acc.get(name) ?? [], literal])]]]),
|
|
2561
|
-
/* @__PURE__ */ new Map()
|
|
2562
|
-
);
|
|
2524
|
+
function resolveStep(step, ctx) {
|
|
2525
|
+
return {
|
|
2526
|
+
action: resolveAction(step.action, ctx),
|
|
2527
|
+
expect: step.expect.map((predicate) => resolvePredicate2(predicate, ctx))
|
|
2528
|
+
};
|
|
2563
2529
|
}
|
|
2564
|
-
function
|
|
2565
|
-
if (
|
|
2566
|
-
return
|
|
2530
|
+
function resolveAction(action, ctx) {
|
|
2531
|
+
if (action.kind === "goto") {
|
|
2532
|
+
return { ...action, url: resolveString(action.url, ctx) };
|
|
2567
2533
|
}
|
|
2568
|
-
if (
|
|
2569
|
-
return
|
|
2534
|
+
if (action.kind === "fill" || action.kind === "select") {
|
|
2535
|
+
return {
|
|
2536
|
+
...action,
|
|
2537
|
+
locator: resolveLocator(action.locator, ctx),
|
|
2538
|
+
value: resolveValue(action.value, ctx)
|
|
2539
|
+
};
|
|
2570
2540
|
}
|
|
2571
|
-
if (
|
|
2572
|
-
return
|
|
2541
|
+
if (action.kind === "press") {
|
|
2542
|
+
return {
|
|
2543
|
+
...action,
|
|
2544
|
+
locator: action.locator == null ? void 0 : resolveLocator(action.locator, ctx)
|
|
2545
|
+
};
|
|
2573
2546
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
...row2.consequence.flatMap((consequence) => conditionLiteralsIn(consequence))
|
|
2577
|
-
]);
|
|
2578
|
-
}
|
|
2579
|
-
function singletonLiteralsInCondition(condition) {
|
|
2580
|
-
switch (condition.kind) {
|
|
2581
|
-
case "singleton": {
|
|
2582
|
-
const value2 = condition.assertion.value;
|
|
2583
|
-
return value2 == null || typeof value2 !== "object" ? [[condition.singleton, value2]] : [];
|
|
2584
|
-
}
|
|
2585
|
-
case "count": {
|
|
2586
|
-
return [];
|
|
2587
|
-
}
|
|
2588
|
-
case "not": {
|
|
2589
|
-
return singletonLiteralsInCondition(condition.predicate);
|
|
2590
|
-
}
|
|
2591
|
-
case "and": {
|
|
2592
|
-
return condition.predicates.flatMap((p) => singletonLiteralsInCondition(p));
|
|
2593
|
-
}
|
|
2547
|
+
if (action.kind === "upload") {
|
|
2548
|
+
return { ...action, locator: resolveLocator(action.locator, ctx) };
|
|
2594
2549
|
}
|
|
2550
|
+
return { ...action, locator: resolveLocator(action.locator, ctx) };
|
|
2595
2551
|
}
|
|
2596
|
-
function
|
|
2597
|
-
if (
|
|
2598
|
-
return
|
|
2552
|
+
function resolveString(value2, ctx) {
|
|
2553
|
+
if (isBinding(value2)) {
|
|
2554
|
+
return resolveBinding(value2, ctx);
|
|
2599
2555
|
}
|
|
2600
|
-
|
|
2556
|
+
if (isTemplate(value2)) {
|
|
2557
|
+
return resolveTemplate(value2, ctx);
|
|
2558
|
+
}
|
|
2559
|
+
return value2;
|
|
2601
2560
|
}
|
|
2602
|
-
function
|
|
2603
|
-
if (
|
|
2604
|
-
return
|
|
2561
|
+
function resolveLocator(locator, ctx) {
|
|
2562
|
+
if (locator.by === "inside") {
|
|
2563
|
+
return {
|
|
2564
|
+
by: "inside",
|
|
2565
|
+
scope: resolveLocator(locator.scope, ctx),
|
|
2566
|
+
target: resolveLocator(locator.target, ctx)
|
|
2567
|
+
};
|
|
2605
2568
|
}
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2569
|
+
if (locator.by === "role") {
|
|
2570
|
+
return locator.name == null ? locator : { ...locator, name: resolveString(locator.name, ctx) };
|
|
2571
|
+
}
|
|
2572
|
+
return { ...locator, value: resolveString(locator.value, ctx) };
|
|
2573
|
+
}
|
|
2574
|
+
function resolvePredicate2(predicate, ctx) {
|
|
2575
|
+
switch (predicate.kind) {
|
|
2576
|
+
case "visible":
|
|
2577
|
+
case "disabled":
|
|
2578
|
+
case "enabled":
|
|
2579
|
+
case "focused":
|
|
2580
|
+
case "checked": {
|
|
2581
|
+
return { ...predicate, locator: resolveLocator(predicate.locator, ctx) };
|
|
2609
2582
|
}
|
|
2610
|
-
|
|
2611
|
-
|
|
2583
|
+
case "value":
|
|
2584
|
+
case "text": {
|
|
2585
|
+
return {
|
|
2586
|
+
...predicate,
|
|
2587
|
+
locator: resolveLocator(predicate.locator, ctx),
|
|
2588
|
+
value: resolveString(predicate.value, ctx)
|
|
2589
|
+
};
|
|
2612
2590
|
}
|
|
2613
|
-
|
|
2614
|
-
return
|
|
2591
|
+
case "singleton": {
|
|
2592
|
+
return {
|
|
2593
|
+
...predicate,
|
|
2594
|
+
assertion: { ...predicate.assertion, value: resolveValue(predicate.assertion.value, ctx) }
|
|
2595
|
+
};
|
|
2615
2596
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2597
|
+
case "browser": {
|
|
2598
|
+
return { ...predicate, value: resolveString(predicate.value, ctx) };
|
|
2599
|
+
}
|
|
2600
|
+
case "state": {
|
|
2601
|
+
return {
|
|
2602
|
+
...predicate,
|
|
2603
|
+
assertion: resolveAssertion(predicate.assertion, ctx),
|
|
2604
|
+
key: whereMap(predicate.key, ctx)
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
case "not": {
|
|
2608
|
+
return { ...predicate, predicate: resolvePredicate2(predicate.predicate, ctx) };
|
|
2609
|
+
}
|
|
2610
|
+
case "when": {
|
|
2611
|
+
return {
|
|
2612
|
+
...predicate,
|
|
2613
|
+
branches: predicate.branches.map((row2) => ({
|
|
2614
|
+
condition: row2.condition == null ? void 0 : resolveCondition(row2.condition, ctx),
|
|
2615
|
+
consequence: row2.consequence.map((consequence) => resolvePredicate2(consequence, ctx)),
|
|
2616
|
+
name: row2.name
|
|
2617
|
+
}))
|
|
2618
|
+
};
|
|
2619
|
+
}
|
|
2620
|
+
case "and": {
|
|
2621
|
+
return {
|
|
2622
|
+
...predicate,
|
|
2623
|
+
predicates: predicate.predicates.map((p) => resolvePredicate2(p, ctx))
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
case "count": {
|
|
2627
|
+
return predicate;
|
|
2630
2628
|
}
|
|
2631
|
-
};
|
|
2632
|
-
const folded = workflow2.steps.reduce(
|
|
2633
|
-
(acc, step) => acc == null ? null : stepSim(acc, step),
|
|
2634
|
-
initial
|
|
2635
|
-
);
|
|
2636
|
-
return folded == null ? null : { candidate, choices: folded.choices };
|
|
2637
|
-
}
|
|
2638
|
-
function seedSingles(singletons, pins) {
|
|
2639
|
-
return Object.fromEntries(
|
|
2640
|
-
Object.entries(singletons).map(([name, value2]) => {
|
|
2641
|
-
const param = paramRefOf(value2);
|
|
2642
|
-
return [name, param != null && param in pins ? pins[param] ?? null : value2];
|
|
2643
|
-
})
|
|
2644
|
-
);
|
|
2645
|
-
}
|
|
2646
|
-
function stepSim(acc, step) {
|
|
2647
|
-
const rows = step.expect.filter((p) => p.kind === "state").reduce((current, effect) => applyEffect(current, effect), acc.state.rows);
|
|
2648
|
-
const preWhens = { rows, singles: acc.state.singles };
|
|
2649
|
-
const whens = step.expect.filter((p) => p.kind === "when");
|
|
2650
|
-
const resolved = whens.reduce((current, when2) => current == null ? null : foldWhen(current, when2, preWhens), {
|
|
2651
|
-
choices: acc.choices,
|
|
2652
|
-
sets: {}
|
|
2653
|
-
});
|
|
2654
|
-
if (resolved == null) {
|
|
2655
|
-
return null;
|
|
2656
2629
|
}
|
|
2657
|
-
const immediate = singletonSets(step.expect);
|
|
2658
|
-
return {
|
|
2659
|
-
choices: resolved.choices,
|
|
2660
|
-
state: { rows, singles: { ...preWhens.singles, ...immediate, ...resolved.sets } }
|
|
2661
|
-
};
|
|
2662
2630
|
}
|
|
2663
|
-
function
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
return null;
|
|
2667
|
-
}
|
|
2668
|
-
if (picked == null) {
|
|
2669
|
-
return acc;
|
|
2631
|
+
function resolveAssertion(assertion, ctx) {
|
|
2632
|
+
if (assertion.kind === "deleted") {
|
|
2633
|
+
return assertion;
|
|
2670
2634
|
}
|
|
2671
|
-
const
|
|
2672
|
-
if (
|
|
2673
|
-
|
|
2635
|
+
const descriptor = ctx.captures.get(assertion);
|
|
2636
|
+
if (descriptor == null) {
|
|
2637
|
+
throw new Error("internal: capture assertion was not registered");
|
|
2674
2638
|
}
|
|
2675
|
-
const
|
|
2676
|
-
return
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
return { choices: folded.choices, sets: { ...folded.sets, ...singletonSets([consequence]) } };
|
|
2685
|
-
},
|
|
2686
|
-
{ choices, sets: acc.sets }
|
|
2639
|
+
const as = aliasFor(ctx, descriptor);
|
|
2640
|
+
return assertion.kind === "created" ? { as, kind: "created", props: setMap(assertion.props, ctx) } : { as, kind: "updated", props: updateMap(assertion.props, ctx) };
|
|
2641
|
+
}
|
|
2642
|
+
function updateMap(map, ctx) {
|
|
2643
|
+
return Object.fromEntries(
|
|
2644
|
+
Object.entries(map).map(([key2, value2]) => [
|
|
2645
|
+
key2,
|
|
2646
|
+
isChanged(value2) ? value2 : resolveValue(value2, ctx)
|
|
2647
|
+
])
|
|
2687
2648
|
);
|
|
2688
2649
|
}
|
|
2689
|
-
function
|
|
2690
|
-
return
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
}
|
|
2697
|
-
const holds = evalCondition(row2.condition, state);
|
|
2698
|
-
if (holds === "unknown") {
|
|
2699
|
-
return "unknown";
|
|
2700
|
-
}
|
|
2701
|
-
return holds ? index : void 0;
|
|
2702
|
-
}, void 0);
|
|
2650
|
+
function whereMap(map, ctx) {
|
|
2651
|
+
return Object.fromEntries(
|
|
2652
|
+
Object.entries(map).map(([key2, value2]) => [
|
|
2653
|
+
key2,
|
|
2654
|
+
resolveWhere(value2, ctx)
|
|
2655
|
+
])
|
|
2656
|
+
);
|
|
2703
2657
|
}
|
|
2704
|
-
function
|
|
2658
|
+
function resolveWhere(value2, ctx) {
|
|
2659
|
+
return isWithin2(value2) ? { ...value2, selection: { ...value2.selection, where: whereMap(value2.selection.where, ctx) } } : resolveValue(value2, ctx);
|
|
2660
|
+
}
|
|
2661
|
+
function resolveCondition(condition, ctx) {
|
|
2705
2662
|
switch (condition.kind) {
|
|
2706
|
-
case "count": {
|
|
2707
|
-
return state.rows.filter((row2) => row2.entity === condition.entity).length === condition.value;
|
|
2708
|
-
}
|
|
2709
2663
|
case "singleton": {
|
|
2710
|
-
return
|
|
2664
|
+
return {
|
|
2665
|
+
...condition,
|
|
2666
|
+
assertion: {
|
|
2667
|
+
...condition.assertion,
|
|
2668
|
+
value: resolveValue(condition.assertion.value, ctx)
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
case "count": {
|
|
2673
|
+
return condition;
|
|
2711
2674
|
}
|
|
2712
2675
|
case "not": {
|
|
2713
|
-
return
|
|
2676
|
+
return { ...condition, predicate: resolveCondition(condition.predicate, ctx) };
|
|
2714
2677
|
}
|
|
2715
2678
|
case "and": {
|
|
2716
|
-
return
|
|
2679
|
+
return {
|
|
2680
|
+
...condition,
|
|
2681
|
+
predicates: condition.predicates.map((p) => resolveCondition(p, ctx))
|
|
2682
|
+
};
|
|
2717
2683
|
}
|
|
2718
2684
|
}
|
|
2719
2685
|
}
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
if (seededRef != null && wantedRef != null) {
|
|
2727
|
-
return seededRef === wantedRef ? true : "unknown";
|
|
2728
|
-
}
|
|
2729
|
-
if (seededRef != null || wantedRef != null || isTemplateValue(seeded) || isTemplateValue(wanted)) {
|
|
2730
|
-
return "unknown";
|
|
2686
|
+
|
|
2687
|
+
// src/workflow.ts
|
|
2688
|
+
function workflow(intent, fn) {
|
|
2689
|
+
const sourcePath = captureSourcePath();
|
|
2690
|
+
if (fn == null) {
|
|
2691
|
+
return { spec: stubSpec(intent, sourcePath) };
|
|
2731
2692
|
}
|
|
2732
|
-
|
|
2693
|
+
const final = finalize(fn());
|
|
2694
|
+
return {
|
|
2695
|
+
spec: {
|
|
2696
|
+
absent: final.absent,
|
|
2697
|
+
exclusive: final.exclusive,
|
|
2698
|
+
intent,
|
|
2699
|
+
maybe: final.maybe,
|
|
2700
|
+
name: slugify2(intent),
|
|
2701
|
+
params: final.params,
|
|
2702
|
+
singletons: final.singletons,
|
|
2703
|
+
sourcePath,
|
|
2704
|
+
steps: final.steps,
|
|
2705
|
+
stub: false,
|
|
2706
|
+
tests: [],
|
|
2707
|
+
world: final.world
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2733
2710
|
}
|
|
2734
|
-
function
|
|
2735
|
-
return
|
|
2711
|
+
function stubSpec(intent, sourcePath) {
|
|
2712
|
+
return {
|
|
2713
|
+
absent: [],
|
|
2714
|
+
exclusive: [],
|
|
2715
|
+
intent,
|
|
2716
|
+
maybe: [],
|
|
2717
|
+
name: slugify2(intent),
|
|
2718
|
+
params: {},
|
|
2719
|
+
singletons: {},
|
|
2720
|
+
sourcePath,
|
|
2721
|
+
steps: [],
|
|
2722
|
+
stub: true,
|
|
2723
|
+
tests: [],
|
|
2724
|
+
world: []
|
|
2725
|
+
};
|
|
2736
2726
|
}
|
|
2737
|
-
|
|
2738
|
-
|
|
2727
|
+
var WORKFLOWS_ANCHOR_PATTERN = /[/\\]\.ripplo[/\\]workflows[/\\]([^):]+?)(?::\d+:\d+\)?)?$/;
|
|
2728
|
+
function captureSourcePath() {
|
|
2729
|
+
const stack = new Error("capture").stack;
|
|
2730
|
+
if (stack == null) {
|
|
2731
|
+
return void 0;
|
|
2732
|
+
}
|
|
2733
|
+
const match = stack.split("\n").map((line) => WORKFLOWS_ANCHOR_PATTERN.exec(line)).find((m) => m != null);
|
|
2734
|
+
const captured = match?.[1];
|
|
2735
|
+
return captured == null ? void 0 : captured.replaceAll("\\", "/");
|
|
2739
2736
|
}
|
|
2740
|
-
function
|
|
2741
|
-
|
|
2742
|
-
|
|
2737
|
+
function slugify2(intent) {
|
|
2738
|
+
const slug = intent.toLowerCase().replaceAll(/[^a-z0-9]+/g, " ").trim().split(" ").join("-");
|
|
2739
|
+
if (slug.length === 0) {
|
|
2740
|
+
throw new Error(`workflow intent "${intent}" slugifies to an empty string`);
|
|
2743
2741
|
}
|
|
2744
|
-
return
|
|
2742
|
+
return slug;
|
|
2745
2743
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2744
|
+
|
|
2745
|
+
// src/params.ts
|
|
2746
|
+
function arbitrary(field2) {
|
|
2747
|
+
const token = {
|
|
2748
|
+
base: `${field2.entity}_${field2.field}`,
|
|
2749
|
+
valueSpace: field2.valueSpaceName
|
|
2750
|
+
};
|
|
2751
|
+
return paramBinding(token);
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/engine.ts
|
|
2755
|
+
import { err, ok, okAsync, Result, ResultAsync } from "neverthrow";
|
|
2756
|
+
function createEngine(ripplo, impls, teardown) {
|
|
2757
|
+
const entities = new Map(Object.entries(impls.entities));
|
|
2758
|
+
const singletons = new Map(
|
|
2759
|
+
Object.entries(impls.singletons).map(([name, impl]) => [
|
|
2760
|
+
name,
|
|
2761
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- per-name impls typed at the call site; erase the contravariant scalar seed to the loose dispatch shape
|
|
2762
|
+
impl
|
|
2763
|
+
])
|
|
2754
2764
|
);
|
|
2765
|
+
assertDeclared(ripplo, [...entities.keys()], [...singletons.keys()]);
|
|
2766
|
+
const entityImpl = (name) => lookup(entities.get(name), name);
|
|
2767
|
+
const singletonImpl = (name) => lookup(singletons.get(name), name);
|
|
2768
|
+
return {
|
|
2769
|
+
read: (request, runId) => ResultAsync.combine([
|
|
2770
|
+
ResultAsync.combine(
|
|
2771
|
+
request.entities.map((name) => readEntity(entityImpl(name), name, runId))
|
|
2772
|
+
),
|
|
2773
|
+
ResultAsync.combine(
|
|
2774
|
+
request.singletons.map((name) => readSingleton(singletonImpl(name), name, runId))
|
|
2775
|
+
)
|
|
2776
|
+
]).map(([entityPairs, singletonPairs]) => ({
|
|
2777
|
+
entities: Object.fromEntries(entityPairs),
|
|
2778
|
+
singletons: Object.fromEntries(singletonPairs)
|
|
2779
|
+
})),
|
|
2780
|
+
seed: (request, runId) => ResultAsync.fromPromise(teardown(runId), implFailure).andThen(() => seedSingletons(request.singletons, singletonImpl, runId)).andThen(() => seedSetups(request.entities, entityImpl, runId)),
|
|
2781
|
+
teardown: (runId) => ResultAsync.fromPromise(teardown(runId), implFailure)
|
|
2782
|
+
};
|
|
2755
2783
|
}
|
|
2756
|
-
function
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2784
|
+
function assertDeclared(ripplo, entityNames, singletonNames) {
|
|
2785
|
+
const backendEntities = new Set(
|
|
2786
|
+
ripplo.entities.filter((e) => e.schema.source === "backend").map((e) => e.name)
|
|
2787
|
+
);
|
|
2788
|
+
const backendSingletons = new Set(
|
|
2789
|
+
ripplo.singletons.filter((s) => s.schema.source === "backend").map((s) => s.name)
|
|
2790
|
+
);
|
|
2791
|
+
entityNames.forEach((name) => {
|
|
2792
|
+
if (!backendEntities.has(name)) {
|
|
2793
|
+
throw new Error(`engine impl "${name}" has no matching backend entity`);
|
|
2794
|
+
}
|
|
2795
|
+
});
|
|
2796
|
+
singletonNames.forEach((name) => {
|
|
2797
|
+
if (!backendSingletons.has(name)) {
|
|
2798
|
+
throw new Error(`engine impl "${name}" has no matching backend singleton`);
|
|
2799
|
+
}
|
|
2800
|
+
});
|
|
2767
2801
|
}
|
|
2768
|
-
function
|
|
2769
|
-
return
|
|
2802
|
+
function lookup(impl, name) {
|
|
2803
|
+
return impl == null ? err({ message: `no engine impl for "${name}"` }) : ok(impl);
|
|
2770
2804
|
}
|
|
2771
|
-
function
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
}
|
|
2779
|
-
return valuesEqual(fieldValue(row2, field2), want);
|
|
2805
|
+
function readEntity(impl, name, runId) {
|
|
2806
|
+
return impl.asyncAndThen(
|
|
2807
|
+
(i) => ResultAsync.fromPromise(i.read({ runId }), implFailure).map((rows) => [
|
|
2808
|
+
name,
|
|
2809
|
+
[...rows]
|
|
2810
|
+
])
|
|
2811
|
+
);
|
|
2780
2812
|
}
|
|
2781
|
-
function
|
|
2782
|
-
return
|
|
2813
|
+
function implFailure(error) {
|
|
2814
|
+
return { message: error instanceof Error ? error.message : String(error) };
|
|
2783
2815
|
}
|
|
2784
|
-
function
|
|
2785
|
-
return
|
|
2816
|
+
function readSingleton(impl, name, runId) {
|
|
2817
|
+
return impl.asyncAndThen(
|
|
2818
|
+
(i) => ResultAsync.fromPromise(i.read({ runId }), implFailure).map((value2) => [
|
|
2819
|
+
name,
|
|
2820
|
+
value2
|
|
2821
|
+
])
|
|
2822
|
+
);
|
|
2786
2823
|
}
|
|
2787
|
-
function
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
}
|
|
2796
|
-
return sameSetValue(a, b);
|
|
2824
|
+
function seedSingletons(values, singletonImpl, runId) {
|
|
2825
|
+
return ResultAsync.combine(
|
|
2826
|
+
Object.entries(values).map(
|
|
2827
|
+
([name, value2]) => singletonImpl(name).asyncAndThen(
|
|
2828
|
+
(impl) => ResultAsync.fromPromise(impl.seed({ runId, value: value2 }), implFailure)
|
|
2829
|
+
)
|
|
2830
|
+
)
|
|
2831
|
+
);
|
|
2797
2832
|
}
|
|
2798
|
-
function
|
|
2799
|
-
const
|
|
2800
|
-
|
|
2801
|
-
expect: step.expect.flatMap((predicate) => resolvePredicate2(predicate, sim.choices))
|
|
2802
|
-
}));
|
|
2803
|
-
const world = [...workflow2.world, ...sim.candidate.maybes];
|
|
2804
|
-
const singletons = seedSingles(workflow2.singletons, sim.candidate.pins);
|
|
2805
|
-
const used = usedRefHeads({ singletons, steps, workflow: workflow2, world });
|
|
2806
|
-
return {
|
|
2807
|
-
absent: workflow2.absent,
|
|
2808
|
-
exclusive: workflow2.exclusive,
|
|
2809
|
-
intent: workflow2.intent,
|
|
2810
|
-
name,
|
|
2811
|
-
params: Object.fromEntries(
|
|
2812
|
-
Object.entries(workflow2.params).filter(([param]) => used.has(param))
|
|
2813
|
-
),
|
|
2814
|
-
singletons,
|
|
2815
|
-
slug: slugify2(name),
|
|
2816
|
-
steps,
|
|
2817
|
-
workflow: workflow2.name,
|
|
2818
|
-
world
|
|
2819
|
-
};
|
|
2833
|
+
function seedSetups(specs, entityImpl, runId) {
|
|
2834
|
+
const waves = dependencyWaves(specs);
|
|
2835
|
+
return waves.reduce((accR, wave) => accR.andThen((acc) => seedWave(wave, entityImpl, acc, runId)), okAsync({ env: /* @__PURE__ */ new Map(), rows: [] })).map((fold) => orderRows(specs, fold.rows));
|
|
2820
2836
|
}
|
|
2821
|
-
function
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
return [
|
|
2828
|
-
{
|
|
2829
|
-
kind: "and",
|
|
2830
|
-
predicates: predicate.predicates.flatMap((p) => resolvePredicate2(p, choices))
|
|
2831
|
-
}
|
|
2832
|
-
];
|
|
2837
|
+
function dependencyWaves(specs) {
|
|
2838
|
+
return buildWaves([], specs);
|
|
2839
|
+
}
|
|
2840
|
+
function buildWaves(done, remaining) {
|
|
2841
|
+
if (remaining.length === 0) {
|
|
2842
|
+
return done;
|
|
2833
2843
|
}
|
|
2834
|
-
|
|
2835
|
-
|
|
2844
|
+
const seeded = new Set(done.flat().map((spec) => spec.as));
|
|
2845
|
+
const ready = remaining.filter((spec) => refAliases(spec).every((alias) => seeded.has(alias)));
|
|
2846
|
+
if (ready.length === 0) {
|
|
2847
|
+
return [...done, remaining];
|
|
2836
2848
|
}
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2849
|
+
return buildWaves(
|
|
2850
|
+
[...done, ready],
|
|
2851
|
+
remaining.filter((spec) => !ready.includes(spec))
|
|
2852
|
+
);
|
|
2840
2853
|
}
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
valueSpaces: dedupeByName([
|
|
2849
|
-
...input.entities.flatMap((handle) => handle.valueSpaces),
|
|
2850
|
-
...input.singletons.flatMap((handle) => handle.valueSpaces)
|
|
2851
|
-
]),
|
|
2852
|
-
workflows: input.workflows.map(
|
|
2853
|
-
(ripploWorkflow) => expandWorkflow(ripploWorkflow.spec, { singletonValues: singletonValuesOf(input.singletons) })
|
|
2854
|
-
)
|
|
2854
|
+
function refAliases(spec) {
|
|
2855
|
+
return Object.values(spec.fields).flatMap((value2) => {
|
|
2856
|
+
if (value2 === null || typeof value2 !== "object" || !("ref" in value2)) {
|
|
2857
|
+
return [];
|
|
2858
|
+
}
|
|
2859
|
+
const lastDot = value2.ref.lastIndexOf(".");
|
|
2860
|
+
return lastDot === -1 ? [] : [value2.ref.slice(0, lastDot)];
|
|
2855
2861
|
});
|
|
2856
|
-
assertNoContradictions(lockfile);
|
|
2857
|
-
return lockfile;
|
|
2858
2862
|
}
|
|
2859
|
-
function
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
throw new Error(`duplicate entity name "${duplicate}" \u2014 each entity name must be unique`);
|
|
2864
|
-
}
|
|
2863
|
+
function seedWave(wave, entityImpl, acc, runId) {
|
|
2864
|
+
return ResultAsync.combine(
|
|
2865
|
+
wave.map((spec) => seedSetup(entityImpl(spec.entity), acc, spec, runId))
|
|
2866
|
+
).map((folds) => mergeFolds(acc, folds));
|
|
2865
2867
|
}
|
|
2866
|
-
function
|
|
2867
|
-
|
|
2868
|
-
return [...byName.values()];
|
|
2868
|
+
function seedSetup(impl, acc, spec, runId) {
|
|
2869
|
+
return resolveFields(spec.fields, acc.env).asyncAndThen((fields) => runSeed(impl, fields, runId)).map(({ row: row2, session }) => foldRow(acc, spec, row2, session));
|
|
2869
2870
|
}
|
|
2870
|
-
function
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2871
|
+
function resolveFields(fields, env) {
|
|
2872
|
+
return Result.combine(
|
|
2873
|
+
Object.entries(fields).map(
|
|
2874
|
+
([field2, value2]) => resolveValue2(value2, env).map((cell2) => [field2, cell2])
|
|
2875
|
+
)
|
|
2876
|
+
).map((entries) => Object.fromEntries(entries));
|
|
2875
2877
|
}
|
|
2876
|
-
function
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
);
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
`test "${workflow2.name}": creates a "${absence.entity}" ("${clash.as}") that a none(${absence.entity}, \u2026) in its world forbids`
|
|
2885
|
-
);
|
|
2886
|
-
}
|
|
2887
|
-
});
|
|
2878
|
+
function resolveValue2(value2, env) {
|
|
2879
|
+
if (value2 === null || typeof value2 !== "object") {
|
|
2880
|
+
return ok(value2);
|
|
2881
|
+
}
|
|
2882
|
+
if ("ref" in value2) {
|
|
2883
|
+
return env.has(value2.ref) ? ok(env.get(value2.ref) ?? null) : err({ message: `setup ref "${value2.ref}" was not produced by an earlier entity` });
|
|
2884
|
+
}
|
|
2885
|
+
throw new Error("internal: a setup value resolved to an unresolved template");
|
|
2888
2886
|
}
|
|
2889
|
-
function
|
|
2890
|
-
return
|
|
2887
|
+
function runSeed(impl, fields, runId) {
|
|
2888
|
+
return impl.asyncAndThen((i) => ResultAsync.fromPromise(i.seed({ fields, runId }), implFailure));
|
|
2891
2889
|
}
|
|
2892
|
-
function
|
|
2893
|
-
|
|
2890
|
+
function foldRow(acc, spec, row2, session) {
|
|
2891
|
+
const next = Object.entries(row2).map(([field2, value2]) => [
|
|
2892
|
+
`${spec.as}.${field2}`,
|
|
2893
|
+
value2
|
|
2894
|
+
]);
|
|
2895
|
+
return {
|
|
2896
|
+
env: new Map([...acc.env, ...next]),
|
|
2897
|
+
rows: [...acc.rows, { as: spec.as, row: row2, session }]
|
|
2898
|
+
};
|
|
2894
2899
|
}
|
|
2895
|
-
function
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
...workflow2.maybe.map((setup) => setup.set),
|
|
2901
|
-
...workflow2.absent.map((absence) => absence.where)
|
|
2902
|
-
];
|
|
2903
|
-
fieldSets.forEach((set) => {
|
|
2904
|
-
assertSetRefs(workflow2.name, set, aliases, paramKeys);
|
|
2905
|
-
});
|
|
2900
|
+
function mergeFolds(base, folds) {
|
|
2901
|
+
return {
|
|
2902
|
+
env: new Map([...base.env, ...folds.flatMap((fold) => [...fold.env])]),
|
|
2903
|
+
rows: [...base.rows, ...folds.flatMap((fold) => fold.rows.slice(base.rows.length))]
|
|
2904
|
+
};
|
|
2906
2905
|
}
|
|
2907
|
-
function
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
const lastDot = value2.ref.lastIndexOf(".");
|
|
2913
|
-
if (lastDot === -1) {
|
|
2914
|
-
return;
|
|
2915
|
-
}
|
|
2916
|
-
const aliasPath = value2.ref.slice(0, lastDot);
|
|
2917
|
-
if (!aliases.has(aliasPath)) {
|
|
2918
|
-
throw new Error(
|
|
2919
|
-
`test "${workflowName}": ref "${value2.ref}" points at unknown alias "${aliasPath}"`
|
|
2920
|
-
);
|
|
2921
|
-
}
|
|
2906
|
+
function orderRows(specs, rows) {
|
|
2907
|
+
const byAs = new Map(rows.map((row2) => [row2.as, row2]));
|
|
2908
|
+
return specs.flatMap((spec) => {
|
|
2909
|
+
const row2 = byAs.get(spec.as);
|
|
2910
|
+
return row2 == null ? [] : [row2];
|
|
2922
2911
|
});
|
|
2923
2912
|
}
|
|
2924
|
-
function isRef(value2) {
|
|
2925
|
-
return value2 != null && typeof value2 === "object" && "ref" in value2;
|
|
2926
|
-
}
|
|
2927
2913
|
|
|
2928
|
-
// src/
|
|
2929
|
-
|
|
2914
|
+
// src/client-engine.ts
|
|
2915
|
+
import { z as z8 } from "zod";
|
|
2916
|
+
var seedSchema = z8.record(z8.string(), z8.union([z8.string(), z8.number(), z8.boolean()])).catch({});
|
|
2917
|
+
function createClientEngine(_ripplo, impls) {
|
|
2918
|
+
const singletons = new Map(
|
|
2919
|
+
Object.entries(impls.singletons).map(([name, impl]) => [
|
|
2920
|
+
name,
|
|
2921
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- per-name impls typed at the call site; erase the contravariant scalar seed to the loose dispatch shape
|
|
2922
|
+
impl
|
|
2923
|
+
])
|
|
2924
|
+
);
|
|
2925
|
+
const entities = new Map(Object.entries(impls.entities));
|
|
2926
|
+
const seed = seedSchema.parse(Reflect.get(globalThis, CLIENT_SEED_KEY));
|
|
2927
|
+
Object.entries(seed).forEach(([name, value2]) => singletons.get(name)?.seed(value2));
|
|
2930
2928
|
return {
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
entities: input.entities,
|
|
2934
|
-
singletons: input.singletons,
|
|
2935
|
-
workflows: input.workflows
|
|
2936
|
-
}),
|
|
2937
|
-
singletons: input.singletons,
|
|
2938
|
-
workflows: input.workflows
|
|
2929
|
+
readEntity: (name) => entities.get(name)?.read() ?? [],
|
|
2930
|
+
readSingleton: (name) => singletons.get(name)?.read() ?? null
|
|
2939
2931
|
};
|
|
2940
2932
|
}
|
|
2933
|
+
function mountClientEngine(ripplo, impls, { enabled: enabled2 }) {
|
|
2934
|
+
if (!enabled2) {
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
Reflect.set(globalThis, CLIENT_MOUNT_KEY, createClientEngine(ripplo, impls));
|
|
2938
|
+
}
|
|
2941
2939
|
export {
|
|
2942
2940
|
CLIENT_MOUNT_KEY,
|
|
2943
2941
|
CLIENT_SEED_KEY,
|