doxum 0.1.17 → 0.1.19
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/README.md +54 -3
- package/dist/advanced-BYZCjNWL.js +680 -0
- package/dist/advanced-BYZCjNWL.js.map +1 -0
- package/dist/advanced-C2fpjbK3.d.ts +171 -0
- package/dist/advanced-CMMNk49G.cjs +763 -0
- package/dist/advanced-CMMNk49G.cjs.map +1 -0
- package/dist/advanced-DlOYk1vi.d.cts +171 -0
- package/dist/advanced.cjs +2 -149
- package/dist/advanced.d.cts +2 -39
- package/dist/advanced.d.ts +2 -39
- package/dist/advanced.js +1 -148
- package/dist/index.cjs +788 -569
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +755 -536
- package/dist/index.js.map +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/skills/doxum-runtime/references/projections.en.md +37 -1
- package/skills/doxum-runtime/references/projections.zh-CN.md +31 -1
- package/dist/advanced.cjs.map +0 -1
- package/dist/advanced.js.map +0 -1
- package/dist/definition-BdLGD8Rq.d.ts +0 -101
- package/dist/definition-Bf8Z7xtX.d.cts +0 -101
- package/dist/definition-DUo1lfQ8.js +0 -71
- package/dist/definition-DUo1lfQ8.js.map +0 -1
- package/dist/definition-De6rhTlZ.cjs +0 -106
- package/dist/definition-De6rhTlZ.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@ const require_issue = require("./issue-DhrNdQNg.cjs");
|
|
|
3
3
|
const require_scope = require("./scope-Ezs-Y3TY.cjs");
|
|
4
4
|
const require_notification = require("./notification-CDSUfiip.cjs");
|
|
5
5
|
const require_driver = require("./driver-BCyfqxsl.cjs");
|
|
6
|
-
const
|
|
6
|
+
const require_advanced = require("./advanced-CMMNk49G.cjs");
|
|
7
7
|
//#region core/src/schema.ts
|
|
8
8
|
const node = (value) => Object.freeze(value);
|
|
9
9
|
const field = (validator) => node({
|
|
@@ -1104,314 +1104,6 @@ const asReadable = (runtime) => {
|
|
|
1104
1104
|
//#region core/src/projection/select.ts
|
|
1105
1105
|
const read = (runtime, selector) => require_notification.readWith(runtime, selector);
|
|
1106
1106
|
//#endregion
|
|
1107
|
-
//#region core/src/projection/contract.ts
|
|
1108
|
-
var ProjectionError = class extends Error {
|
|
1109
|
-
constructor(phase, identity, revisions, cause) {
|
|
1110
|
-
super(`Projection ${phase} failed: ${identity}`, { cause });
|
|
1111
|
-
this.phase = phase;
|
|
1112
|
-
this.identity = identity;
|
|
1113
|
-
this.revisions = revisions;
|
|
1114
|
-
this.name = "ProjectionError";
|
|
1115
|
-
}
|
|
1116
|
-
};
|
|
1117
|
-
var ProjectionDisposedError = class extends Error {
|
|
1118
|
-
constructor() {
|
|
1119
|
-
super("Projection has been disposed.");
|
|
1120
|
-
this.name = "ProjectionDisposedError";
|
|
1121
|
-
}
|
|
1122
|
-
};
|
|
1123
|
-
//#endregion
|
|
1124
|
-
//#region core/src/projection/scheduler.ts
|
|
1125
|
-
const projectionHandles = /* @__PURE__ */ new WeakSet();
|
|
1126
|
-
const createScheduler = (onError) => {
|
|
1127
|
-
let disposed = false;
|
|
1128
|
-
let depth = 0;
|
|
1129
|
-
let batchSequence = 0;
|
|
1130
|
-
let activeBatch;
|
|
1131
|
-
let phase = "idle";
|
|
1132
|
-
let sequence = 0;
|
|
1133
|
-
const records = /* @__PURE__ */ new Map();
|
|
1134
|
-
const nodes = /* @__PURE__ */ new Set();
|
|
1135
|
-
const pending = /* @__PURE__ */ new Set();
|
|
1136
|
-
const heap = [];
|
|
1137
|
-
const queued = /* @__PURE__ */ new Set();
|
|
1138
|
-
const completed = [];
|
|
1139
|
-
const emissions = /* @__PURE__ */ new Set();
|
|
1140
|
-
const errors = [];
|
|
1141
|
-
let reportingFailures = [];
|
|
1142
|
-
const guards = /* @__PURE__ */ new Set();
|
|
1143
|
-
const cleanups = /* @__PURE__ */ new Set();
|
|
1144
|
-
const assertActive = () => {
|
|
1145
|
-
if (disposed) throw new ProjectionDisposedError();
|
|
1146
|
-
};
|
|
1147
|
-
const assertIdle = () => {
|
|
1148
|
-
assertActive();
|
|
1149
|
-
if (phase !== "idle") throw new Error("Projection cannot be re-entered during processing or notification.");
|
|
1150
|
-
};
|
|
1151
|
-
const lock = (value) => guards.forEach((guard) => guard(value));
|
|
1152
|
-
const enqueue = (node) => {
|
|
1153
|
-
if (node.disposed || queued.has(node)) return;
|
|
1154
|
-
queued.add(node);
|
|
1155
|
-
require_issue.profile.projection("scheduledNodes");
|
|
1156
|
-
let i = heap.length;
|
|
1157
|
-
heap.push(node);
|
|
1158
|
-
while (i > 0) {
|
|
1159
|
-
const p = i - 1 >> 1;
|
|
1160
|
-
if (heap[p].order < node.order) break;
|
|
1161
|
-
heap[i] = heap[p];
|
|
1162
|
-
i = p;
|
|
1163
|
-
}
|
|
1164
|
-
heap[i] = node;
|
|
1165
|
-
};
|
|
1166
|
-
const pop = () => {
|
|
1167
|
-
const first = heap[0];
|
|
1168
|
-
const last = heap.pop();
|
|
1169
|
-
if (heap.length) {
|
|
1170
|
-
let i = 0;
|
|
1171
|
-
while (i * 2 + 1 < heap.length) {
|
|
1172
|
-
let c = i * 2 + 1;
|
|
1173
|
-
if (c + 1 < heap.length && heap[c + 1].order < heap[c].order) c++;
|
|
1174
|
-
if (last.order < heap[c].order) break;
|
|
1175
|
-
heap[i] = heap[c];
|
|
1176
|
-
i = c;
|
|
1177
|
-
}
|
|
1178
|
-
heap[i] = last;
|
|
1179
|
-
}
|
|
1180
|
-
return first;
|
|
1181
|
-
};
|
|
1182
|
-
const errorFor = (node, cause, kind = "processor") => new ProjectionError(kind, node.name, node.sources.map((source) => source.revision()), cause);
|
|
1183
|
-
const capture = (source) => {
|
|
1184
|
-
if (disposed) return;
|
|
1185
|
-
if (phase !== "idle") {
|
|
1186
|
-
source.fault = new ProjectionError("source", "reentrant source", [source.revision()], /* @__PURE__ */ new Error("Source changed while projection was running."));
|
|
1187
|
-
errors.push(source.fault);
|
|
1188
|
-
throw source.fault;
|
|
1189
|
-
}
|
|
1190
|
-
pending.add(source);
|
|
1191
|
-
require_issue.profile.projection("sourceEvents");
|
|
1192
|
-
if (source.fault) errors.push(source.fault);
|
|
1193
|
-
};
|
|
1194
|
-
const settle = () => {
|
|
1195
|
-
if (disposed || depth || phase !== "idle") return;
|
|
1196
|
-
phase = "compute";
|
|
1197
|
-
lock(true);
|
|
1198
|
-
try {
|
|
1199
|
-
pending.forEach((source) => source.consumers.forEach(enqueue));
|
|
1200
|
-
while (heap.length) {
|
|
1201
|
-
const node = pop();
|
|
1202
|
-
if (node.disposed) continue;
|
|
1203
|
-
const wasFaulted = node.fault !== void 0;
|
|
1204
|
-
require_issue.profile.projection("processedNodes");
|
|
1205
|
-
let changed = false;
|
|
1206
|
-
const blocked = node.sources.find((source) => source.fault || source.disposed);
|
|
1207
|
-
if (blocked) node.fault = errorFor(node, blocked.fault ?? new ProjectionDisposedError(), "blocked");
|
|
1208
|
-
else {
|
|
1209
|
-
const build = wasFaulted || node.sources.some((source) => source.reset());
|
|
1210
|
-
try {
|
|
1211
|
-
changed = node.evaluate(build);
|
|
1212
|
-
const failure = node.sources.find((source) => source.fault)?.fault;
|
|
1213
|
-
if (failure) throw failure;
|
|
1214
|
-
node.fault = void 0;
|
|
1215
|
-
} catch (cause) {
|
|
1216
|
-
const error = errorFor(node, cause);
|
|
1217
|
-
errors.push(error);
|
|
1218
|
-
node.fault = error;
|
|
1219
|
-
if (!build) try {
|
|
1220
|
-
changed = node.evaluate(true);
|
|
1221
|
-
const failure = node.sources.find((source) => source.fault)?.fault;
|
|
1222
|
-
if (failure) throw failure;
|
|
1223
|
-
node.fault = void 0;
|
|
1224
|
-
} catch (cause) {
|
|
1225
|
-
node.fault = errorFor(node, cause);
|
|
1226
|
-
errors.push(node.fault);
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
completed.push(node);
|
|
1231
|
-
if (changed || wasFaulted || node.fault) {
|
|
1232
|
-
require_issue.profile.projection("publishedNodes");
|
|
1233
|
-
emissions.add(node);
|
|
1234
|
-
node.consumers.forEach(enqueue);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
completed.forEach((node) => node.publish());
|
|
1238
|
-
} finally {
|
|
1239
|
-
lock(false);
|
|
1240
|
-
phase = "idle";
|
|
1241
|
-
}
|
|
1242
|
-
};
|
|
1243
|
-
const flush = () => {
|
|
1244
|
-
if (disposed || depth) return [];
|
|
1245
|
-
if (pending.size || completed.length) require_issue.profile.projection("flushes");
|
|
1246
|
-
phase = "notify";
|
|
1247
|
-
lock(true);
|
|
1248
|
-
const reportFailures = [];
|
|
1249
|
-
try {
|
|
1250
|
-
emissions.forEach((node) => node.emit((listener) => {
|
|
1251
|
-
try {
|
|
1252
|
-
listener();
|
|
1253
|
-
} catch (cause) {
|
|
1254
|
-
errors.push(errorFor(node, cause, "listener"));
|
|
1255
|
-
}
|
|
1256
|
-
}));
|
|
1257
|
-
for (const error of errors.slice()) try {
|
|
1258
|
-
onError(error);
|
|
1259
|
-
} catch (cause) {
|
|
1260
|
-
reportFailures.push(cause);
|
|
1261
|
-
}
|
|
1262
|
-
return Object.freeze([...errors.map((error) => Object.freeze({
|
|
1263
|
-
phase: error.phase === "listener" ? "listener" : "processor",
|
|
1264
|
-
error
|
|
1265
|
-
})), ...reportFailures.map((error) => Object.freeze({
|
|
1266
|
-
phase: "listener",
|
|
1267
|
-
error
|
|
1268
|
-
}))]);
|
|
1269
|
-
} finally {
|
|
1270
|
-
pending.forEach((source) => source.clear());
|
|
1271
|
-
completed.forEach((node) => node.clear());
|
|
1272
|
-
pending.clear();
|
|
1273
|
-
queued.clear();
|
|
1274
|
-
completed.length = 0;
|
|
1275
|
-
emissions.clear();
|
|
1276
|
-
errors.length = 0;
|
|
1277
|
-
reportingFailures = reportFailures;
|
|
1278
|
-
lock(false);
|
|
1279
|
-
phase = "idle";
|
|
1280
|
-
}
|
|
1281
|
-
};
|
|
1282
|
-
const run = () => {
|
|
1283
|
-
settle();
|
|
1284
|
-
const result = flush();
|
|
1285
|
-
const failures = reportingFailures;
|
|
1286
|
-
reportingFailures = [];
|
|
1287
|
-
if (failures.length) throw new AggregateError(failures, "Projection error reporter failed.");
|
|
1288
|
-
return result;
|
|
1289
|
-
};
|
|
1290
|
-
const batch = (optionsOrCallback, maybeCallback) => {
|
|
1291
|
-
assertIdle();
|
|
1292
|
-
const options = typeof optionsOrCallback === "function" ? void 0 : optionsOrCallback;
|
|
1293
|
-
const callback = typeof optionsOrCallback === "function" ? optionsOrCallback : maybeCallback;
|
|
1294
|
-
if (!callback) throw new TypeError("Projection batch requires a callback.");
|
|
1295
|
-
if (depth === 0) activeBatch = Object.freeze({
|
|
1296
|
-
id: ++batchSequence,
|
|
1297
|
-
...options?.cause === void 0 ? {} : { cause: options.cause }
|
|
1298
|
-
});
|
|
1299
|
-
depth++;
|
|
1300
|
-
let failed = false;
|
|
1301
|
-
try {
|
|
1302
|
-
const value = callback();
|
|
1303
|
-
assertSynchronous(value);
|
|
1304
|
-
return value;
|
|
1305
|
-
} catch (error) {
|
|
1306
|
-
failed = true;
|
|
1307
|
-
throw error;
|
|
1308
|
-
} finally {
|
|
1309
|
-
depth--;
|
|
1310
|
-
if (!depth) try {
|
|
1311
|
-
if (failed) try {
|
|
1312
|
-
run();
|
|
1313
|
-
} catch {}
|
|
1314
|
-
else run();
|
|
1315
|
-
} finally {
|
|
1316
|
-
activeBatch = void 0;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
};
|
|
1320
|
-
return {
|
|
1321
|
-
assertActive,
|
|
1322
|
-
assertIdle,
|
|
1323
|
-
capture,
|
|
1324
|
-
settle,
|
|
1325
|
-
flush,
|
|
1326
|
-
run,
|
|
1327
|
-
records,
|
|
1328
|
-
guards,
|
|
1329
|
-
cleanups,
|
|
1330
|
-
get active() {
|
|
1331
|
-
return !disposed;
|
|
1332
|
-
},
|
|
1333
|
-
register(handle, record) {
|
|
1334
|
-
assertIdle();
|
|
1335
|
-
records.set(handle, record);
|
|
1336
|
-
projectionHandles.add(handle);
|
|
1337
|
-
},
|
|
1338
|
-
unregisterSource(handle) {
|
|
1339
|
-
const record = records.get(handle);
|
|
1340
|
-
if (record) {
|
|
1341
|
-
record.disposed = true;
|
|
1342
|
-
pending.delete(record);
|
|
1343
|
-
record.clear();
|
|
1344
|
-
records.delete(handle);
|
|
1345
|
-
}
|
|
1346
|
-
},
|
|
1347
|
-
source(handle) {
|
|
1348
|
-
assertActive();
|
|
1349
|
-
const source = records.get(handle);
|
|
1350
|
-
if (!source || source.disposed) throw new Error("Unknown, disposed, or foreign projection source.");
|
|
1351
|
-
return source;
|
|
1352
|
-
},
|
|
1353
|
-
addNode(node) {
|
|
1354
|
-
nodes.add(node);
|
|
1355
|
-
node.sources.forEach((source) => source.consumers.add(node));
|
|
1356
|
-
},
|
|
1357
|
-
order: () => sequence++,
|
|
1358
|
-
initialize(run) {
|
|
1359
|
-
assertIdle();
|
|
1360
|
-
phase = "compute";
|
|
1361
|
-
lock(true);
|
|
1362
|
-
try {
|
|
1363
|
-
return run();
|
|
1364
|
-
} finally {
|
|
1365
|
-
lock(false);
|
|
1366
|
-
phase = "idle";
|
|
1367
|
-
}
|
|
1368
|
-
},
|
|
1369
|
-
batch,
|
|
1370
|
-
batchContext: () => activeBatch,
|
|
1371
|
-
dispose() {
|
|
1372
|
-
if (disposed) return;
|
|
1373
|
-
assertIdle();
|
|
1374
|
-
disposed = true;
|
|
1375
|
-
const failures = [];
|
|
1376
|
-
cleanups.forEach((cleanup) => {
|
|
1377
|
-
try {
|
|
1378
|
-
cleanup();
|
|
1379
|
-
} catch (error) {
|
|
1380
|
-
failures.push(error);
|
|
1381
|
-
}
|
|
1382
|
-
});
|
|
1383
|
-
cleanups.clear();
|
|
1384
|
-
nodes.forEach((node) => {
|
|
1385
|
-
node.disposed = true;
|
|
1386
|
-
node.release();
|
|
1387
|
-
node.consumers.clear();
|
|
1388
|
-
});
|
|
1389
|
-
records.forEach((source) => {
|
|
1390
|
-
source.disposed = true;
|
|
1391
|
-
source.consumers.clear();
|
|
1392
|
-
source.clear();
|
|
1393
|
-
});
|
|
1394
|
-
nodes.clear();
|
|
1395
|
-
records.clear();
|
|
1396
|
-
pending.clear();
|
|
1397
|
-
heap.length = 0;
|
|
1398
|
-
completed.length = 0;
|
|
1399
|
-
queued.clear();
|
|
1400
|
-
emissions.clear();
|
|
1401
|
-
guards.clear();
|
|
1402
|
-
errors.length = 0;
|
|
1403
|
-
reportingFailures = [];
|
|
1404
|
-
if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
|
|
1405
|
-
}
|
|
1406
|
-
};
|
|
1407
|
-
};
|
|
1408
|
-
const assertSynchronous = (value) => {
|
|
1409
|
-
if (value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") throw new TypeError("Projection callbacks must be synchronous.");
|
|
1410
|
-
};
|
|
1411
|
-
const assertScope = (active) => {
|
|
1412
|
-
if (!active()) throw new Error("Projection reader or writer is no longer active.");
|
|
1413
|
-
};
|
|
1414
|
-
//#endregion
|
|
1415
1107
|
//#region core/src/projection/source.ts
|
|
1416
1108
|
const createSources = (scheduler) => {
|
|
1417
1109
|
const documents = /* @__PURE__ */ new Map();
|
|
@@ -1464,7 +1156,7 @@ const createSources = (scheduler) => {
|
|
|
1464
1156
|
nextBaselineIds = Object.freeze([]);
|
|
1465
1157
|
},
|
|
1466
1158
|
context: (active) => {
|
|
1467
|
-
assertScope(active);
|
|
1159
|
+
require_advanced.assertScope(active);
|
|
1468
1160
|
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1469
1161
|
const context = {
|
|
1470
1162
|
state,
|
|
@@ -1474,16 +1166,16 @@ const createSources = (scheduler) => {
|
|
|
1474
1166
|
const snapshots = collection ? /* @__PURE__ */ new Map() : void 0;
|
|
1475
1167
|
const read = collection ? Object.freeze({
|
|
1476
1168
|
get: (key) => {
|
|
1477
|
-
assertScope(active);
|
|
1169
|
+
require_advanced.assertScope(active);
|
|
1478
1170
|
if (!snapshots.has(key)) snapshots.set(key, require_scope.snapshot(rawRead.get(key)));
|
|
1479
1171
|
return snapshots.get(key);
|
|
1480
1172
|
},
|
|
1481
1173
|
has: (key) => {
|
|
1482
|
-
assertScope(active);
|
|
1174
|
+
require_advanced.assertScope(active);
|
|
1483
1175
|
return rawRead.has(key);
|
|
1484
1176
|
},
|
|
1485
1177
|
ids: () => {
|
|
1486
|
-
assertScope(active);
|
|
1178
|
+
require_advanced.assertScope(active);
|
|
1487
1179
|
return rawRead.ids();
|
|
1488
1180
|
}
|
|
1489
1181
|
}) : require_scope.createAccess(context);
|
|
@@ -1623,7 +1315,7 @@ const createSources = (scheduler) => {
|
|
|
1623
1315
|
flush: scheduler.flush,
|
|
1624
1316
|
dispose: () => {
|
|
1625
1317
|
bindings.forEach(({ record }) => {
|
|
1626
|
-
record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1318
|
+
record.fault = new require_advanced.ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1627
1319
|
scheduler.capture(record);
|
|
1628
1320
|
});
|
|
1629
1321
|
scheduler.run();
|
|
@@ -1653,7 +1345,7 @@ const createSources = (scheduler) => {
|
|
|
1653
1345
|
reset = false;
|
|
1654
1346
|
},
|
|
1655
1347
|
context: (active) => {
|
|
1656
|
-
assertScope(active);
|
|
1348
|
+
require_advanced.assertScope(active);
|
|
1657
1349
|
return Object.freeze({
|
|
1658
1350
|
kind: "value",
|
|
1659
1351
|
value,
|
|
@@ -1685,6 +1377,149 @@ const createSources = (scheduler) => {
|
|
|
1685
1377
|
}
|
|
1686
1378
|
};
|
|
1687
1379
|
};
|
|
1380
|
+
const collectionInput = (initial) => {
|
|
1381
|
+
const values = new Map(initial);
|
|
1382
|
+
const touched = /* @__PURE__ */ new Map();
|
|
1383
|
+
let beforeIds;
|
|
1384
|
+
let revision = 0;
|
|
1385
|
+
let cause;
|
|
1386
|
+
const handle = Object.freeze({ kind: "collection" });
|
|
1387
|
+
const change = () => {
|
|
1388
|
+
const added = [];
|
|
1389
|
+
const updated = [];
|
|
1390
|
+
const removed = [];
|
|
1391
|
+
for (const [key, before] of touched) {
|
|
1392
|
+
const afterPresent = values.has(key);
|
|
1393
|
+
if (!before.present && afterPresent) added.push({
|
|
1394
|
+
key,
|
|
1395
|
+
after: values.get(key)
|
|
1396
|
+
});
|
|
1397
|
+
else if (before.present && !afterPresent) removed.push({
|
|
1398
|
+
key,
|
|
1399
|
+
before: before.value
|
|
1400
|
+
});
|
|
1401
|
+
else if (before.present && afterPresent && !Object.is(before.value, values.get(key))) updated.push({
|
|
1402
|
+
key,
|
|
1403
|
+
before: before.value,
|
|
1404
|
+
after: values.get(key)
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
let order;
|
|
1408
|
+
if (beforeIds) {
|
|
1409
|
+
const afterIds = [...values.keys()];
|
|
1410
|
+
const beforeKeys = new Set(beforeIds);
|
|
1411
|
+
const afterKeys = new Set(afterIds);
|
|
1412
|
+
const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
|
|
1413
|
+
const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
|
|
1414
|
+
if (beforeCommon.some((key, index) => key !== afterCommon[index])) order = Object.freeze({
|
|
1415
|
+
before: beforeIds,
|
|
1416
|
+
after: Object.freeze(afterIds)
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
if (!added.length && !updated.length && !removed.length && !order) return void 0;
|
|
1420
|
+
return Object.freeze({
|
|
1421
|
+
kind: "incremental",
|
|
1422
|
+
added: Object.freeze(added),
|
|
1423
|
+
updated: Object.freeze(updated),
|
|
1424
|
+
removed: Object.freeze(removed),
|
|
1425
|
+
...order ? { order } : {}
|
|
1426
|
+
});
|
|
1427
|
+
};
|
|
1428
|
+
const record = {
|
|
1429
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1430
|
+
disposed: false,
|
|
1431
|
+
fault: void 0,
|
|
1432
|
+
revision: () => revision,
|
|
1433
|
+
reset: () => false,
|
|
1434
|
+
shouldSettle: () => change() !== void 0,
|
|
1435
|
+
clear: () => {
|
|
1436
|
+
touched.clear();
|
|
1437
|
+
beforeIds = void 0;
|
|
1438
|
+
cause = void 0;
|
|
1439
|
+
},
|
|
1440
|
+
context: (active) => {
|
|
1441
|
+
require_advanced.assertScope(active);
|
|
1442
|
+
const read = Object.freeze({
|
|
1443
|
+
get: (key) => {
|
|
1444
|
+
require_advanced.assertScope(active);
|
|
1445
|
+
return values.get(key);
|
|
1446
|
+
},
|
|
1447
|
+
has: (key) => {
|
|
1448
|
+
require_advanced.assertScope(active);
|
|
1449
|
+
return values.has(key);
|
|
1450
|
+
},
|
|
1451
|
+
ids: () => {
|
|
1452
|
+
require_advanced.assertScope(active);
|
|
1453
|
+
return Object.freeze([...values.keys()]);
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
return Object.freeze({
|
|
1457
|
+
kind: "collection",
|
|
1458
|
+
read,
|
|
1459
|
+
change: change(),
|
|
1460
|
+
revision,
|
|
1461
|
+
cause
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
scheduler.register(handle, record);
|
|
1466
|
+
return {
|
|
1467
|
+
source: handle,
|
|
1468
|
+
update(run) {
|
|
1469
|
+
scheduler.assertIdle();
|
|
1470
|
+
const staged = /* @__PURE__ */ new Map();
|
|
1471
|
+
let active = true;
|
|
1472
|
+
const draft = Object.freeze({
|
|
1473
|
+
get: (key) => {
|
|
1474
|
+
require_advanced.assertScope(() => active);
|
|
1475
|
+
const entry = staged.get(key);
|
|
1476
|
+
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
1477
|
+
},
|
|
1478
|
+
has: (key) => {
|
|
1479
|
+
require_advanced.assertScope(() => active);
|
|
1480
|
+
const entry = staged.get(key);
|
|
1481
|
+
return entry ? entry.present : values.has(key);
|
|
1482
|
+
},
|
|
1483
|
+
set: (key, value) => {
|
|
1484
|
+
require_advanced.assertScope(() => active);
|
|
1485
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
1486
|
+
staged.set(key, {
|
|
1487
|
+
present: true,
|
|
1488
|
+
value
|
|
1489
|
+
});
|
|
1490
|
+
},
|
|
1491
|
+
remove: (key) => {
|
|
1492
|
+
require_advanced.assertScope(() => active);
|
|
1493
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
1494
|
+
staged.set(key, { present: false });
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
try {
|
|
1498
|
+
require_advanced.assertSynchronous(run(draft));
|
|
1499
|
+
} finally {
|
|
1500
|
+
active = false;
|
|
1501
|
+
}
|
|
1502
|
+
let changed = false;
|
|
1503
|
+
for (const [key, entry] of staged) {
|
|
1504
|
+
const present = values.has(key);
|
|
1505
|
+
if (entry.present ? present && Object.is(values.get(key), entry.value) : !present) continue;
|
|
1506
|
+
if (!touched.has(key)) touched.set(key, {
|
|
1507
|
+
present,
|
|
1508
|
+
value: values.get(key)
|
|
1509
|
+
});
|
|
1510
|
+
if (!beforeIds && present !== entry.present) beforeIds = Object.freeze([...values.keys()]);
|
|
1511
|
+
if (entry.present) values.set(key, entry.value);
|
|
1512
|
+
else values.delete(key);
|
|
1513
|
+
changed = true;
|
|
1514
|
+
}
|
|
1515
|
+
if (!changed) return;
|
|
1516
|
+
revision++;
|
|
1517
|
+
cause = scheduler.batchContext()?.cause ?? cause;
|
|
1518
|
+
scheduler.capture(record);
|
|
1519
|
+
scheduler.run();
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
};
|
|
1688
1523
|
const externalValueSource = (port, equal) => {
|
|
1689
1524
|
scheduler.assertIdle();
|
|
1690
1525
|
const old = externalSources.get(port);
|
|
@@ -1692,7 +1527,7 @@ const createSources = (scheduler) => {
|
|
|
1692
1527
|
const input = valueSource(port.current(), equal);
|
|
1693
1528
|
const fail = (cause) => {
|
|
1694
1529
|
const record = scheduler.source(input.source);
|
|
1695
|
-
record.fault = new ProjectionError("source", "external source", [record.revision()], cause);
|
|
1530
|
+
record.fault = new require_advanced.ProjectionError("source", "external source", [record.revision()], cause);
|
|
1696
1531
|
scheduler.capture(record);
|
|
1697
1532
|
};
|
|
1698
1533
|
let unsubscribe;
|
|
@@ -1748,18 +1583,18 @@ const createSources = (scheduler) => {
|
|
|
1748
1583
|
transitionKeys.clear();
|
|
1749
1584
|
},
|
|
1750
1585
|
context: (active) => {
|
|
1751
|
-
assertScope(active);
|
|
1586
|
+
require_advanced.assertScope(active);
|
|
1752
1587
|
const currentRead = Object.freeze({
|
|
1753
1588
|
get: (key) => {
|
|
1754
|
-
assertScope(active);
|
|
1589
|
+
require_advanced.assertScope(active);
|
|
1755
1590
|
return read.get(key);
|
|
1756
1591
|
},
|
|
1757
1592
|
has: (key) => {
|
|
1758
|
-
assertScope(active);
|
|
1593
|
+
require_advanced.assertScope(active);
|
|
1759
1594
|
return read.has(key);
|
|
1760
1595
|
},
|
|
1761
1596
|
ids: () => {
|
|
1762
|
-
assertScope(active);
|
|
1597
|
+
require_advanced.assertScope(active);
|
|
1763
1598
|
return read.ids();
|
|
1764
1599
|
}
|
|
1765
1600
|
});
|
|
@@ -1774,7 +1609,7 @@ const createSources = (scheduler) => {
|
|
|
1774
1609
|
};
|
|
1775
1610
|
scheduler.register(handle, record);
|
|
1776
1611
|
const fail = (error) => {
|
|
1777
|
-
record.fault = new ProjectionError("source", "external collection source", [revision], error);
|
|
1612
|
+
record.fault = new require_advanced.ProjectionError("source", "external collection source", [revision], error);
|
|
1778
1613
|
scheduler.capture(record);
|
|
1779
1614
|
};
|
|
1780
1615
|
const deriveChange = () => {
|
|
@@ -1877,9 +1712,10 @@ const createSources = (scheduler) => {
|
|
|
1877
1712
|
set
|
|
1878
1713
|
});
|
|
1879
1714
|
},
|
|
1715
|
+
collectionInput,
|
|
1880
1716
|
fromReadable: (readable, options) => {
|
|
1881
1717
|
scheduler.assertIdle();
|
|
1882
|
-
if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
|
|
1718
|
+
if (require_advanced.projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
|
|
1883
1719
|
const equal = options?.isEqual ?? Object.is;
|
|
1884
1720
|
let bindings = readables.get(readable);
|
|
1885
1721
|
const old = bindings?.get(equal);
|
|
@@ -1905,7 +1741,7 @@ const createSources = (scheduler) => {
|
|
|
1905
1741
|
} catch (cause) {
|
|
1906
1742
|
for (const member of members.values()) {
|
|
1907
1743
|
const record = scheduler.source(member.source);
|
|
1908
|
-
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
1744
|
+
record.fault = new require_advanced.ProjectionError("source", "external readable", [record.revision()], cause);
|
|
1909
1745
|
scheduler.capture(record);
|
|
1910
1746
|
}
|
|
1911
1747
|
if (settle) scheduler.run();
|
|
@@ -1915,7 +1751,7 @@ const createSources = (scheduler) => {
|
|
|
1915
1751
|
member.accept(value);
|
|
1916
1752
|
} catch (cause) {
|
|
1917
1753
|
const record = scheduler.source(member.source);
|
|
1918
|
-
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
1754
|
+
record.fault = new require_advanced.ProjectionError("source", "external readable", [record.revision()], cause);
|
|
1919
1755
|
scheduler.capture(record);
|
|
1920
1756
|
}
|
|
1921
1757
|
if (settle) scheduler.run();
|
|
@@ -1933,7 +1769,7 @@ const createSources = (scheduler) => {
|
|
|
1933
1769
|
dispose: () => {
|
|
1934
1770
|
for (const member of members.values()) {
|
|
1935
1771
|
const record = scheduler.source(member.source);
|
|
1936
|
-
record.fault = new ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1772
|
+
record.fault = new require_advanced.ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1937
1773
|
scheduler.capture(record);
|
|
1938
1774
|
}
|
|
1939
1775
|
scheduler.run();
|
|
@@ -1970,6 +1806,7 @@ const createNode = (scheduler, spec, behavior) => {
|
|
|
1970
1806
|
const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
|
|
1971
1807
|
const order = scheduler.order();
|
|
1972
1808
|
const node = {
|
|
1809
|
+
owner: void 0,
|
|
1973
1810
|
...behavior,
|
|
1974
1811
|
publish: () => {
|
|
1975
1812
|
if (node.fault) behavior.clear();
|
|
@@ -1992,15 +1829,16 @@ const createNode = (scheduler, spec, behavior) => {
|
|
|
1992
1829
|
}
|
|
1993
1830
|
}
|
|
1994
1831
|
};
|
|
1832
|
+
node.owner = node;
|
|
1995
1833
|
scheduler.initialize(() => {
|
|
1996
1834
|
const invalid = node.sources.find((source) => source.fault || source.disposed);
|
|
1997
|
-
if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
|
|
1835
|
+
if (invalid) throw invalid.fault ?? new require_advanced.ProjectionDisposedError();
|
|
1998
1836
|
node.evaluate(true);
|
|
1999
1837
|
node.publish();
|
|
2000
1838
|
node.clear();
|
|
2001
1839
|
});
|
|
2002
1840
|
const check = () => {
|
|
2003
|
-
if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
|
|
1841
|
+
if (node.disposed || !scheduler.active) throw new require_advanced.ProjectionDisposedError();
|
|
2004
1842
|
if (node.fault) throw node.fault;
|
|
2005
1843
|
};
|
|
2006
1844
|
return {
|
|
@@ -2032,17 +1870,17 @@ const createValue = (scheduler, spec, options) => {
|
|
|
2032
1870
|
require_issue.profile.materialized.rebuilt();
|
|
2033
1871
|
instance = void 0;
|
|
2034
1872
|
const built = spec.build(sources);
|
|
2035
|
-
assertSynchronous(built);
|
|
1873
|
+
require_advanced.assertSynchronous(built);
|
|
2036
1874
|
instance = built;
|
|
2037
1875
|
candidate = built.value;
|
|
2038
1876
|
} else {
|
|
2039
1877
|
require_issue.profile.materialized.updated();
|
|
2040
1878
|
const result = instance.update(sources);
|
|
2041
|
-
assertSynchronous(result);
|
|
1879
|
+
require_advanced.assertSynchronous(result);
|
|
2042
1880
|
if (result.kind === "rebuild") {
|
|
2043
1881
|
instance = void 0;
|
|
2044
1882
|
const built = spec.build(sources);
|
|
2045
|
-
assertSynchronous(built);
|
|
1883
|
+
require_advanced.assertSynchronous(built);
|
|
2046
1884
|
instance = built;
|
|
2047
1885
|
candidate = built.value;
|
|
2048
1886
|
build = true;
|
|
@@ -2054,7 +1892,7 @@ const createValue = (scheduler, spec, options) => {
|
|
|
2054
1892
|
return changed;
|
|
2055
1893
|
},
|
|
2056
1894
|
context: (active) => {
|
|
2057
|
-
assertScope(active);
|
|
1895
|
+
require_advanced.assertScope(active);
|
|
2058
1896
|
return Object.freeze({
|
|
2059
1897
|
kind: "value",
|
|
2060
1898
|
value: next,
|
|
@@ -2108,7 +1946,7 @@ const createValue = (scheduler, spec, options) => {
|
|
|
2108
1946
|
return handle;
|
|
2109
1947
|
};
|
|
2110
1948
|
//#endregion
|
|
2111
|
-
//#region core/src/projection/collection.ts
|
|
1949
|
+
//#region core/src/projection/collection-state.ts
|
|
2112
1950
|
const height = (node) => node?.height ?? 0;
|
|
2113
1951
|
const makeTreeNode = (key, value, left, right) => Object.freeze({
|
|
2114
1952
|
key,
|
|
@@ -2174,214 +2012,197 @@ const treeFromSorted = (entries, start = 0, end = entries.length) => {
|
|
|
2174
2012
|
const [key, value] = entries[middle];
|
|
2175
2013
|
return makeTreeNode(key, value, treeFromSorted(entries, start, middle), treeFromSorted(entries, middle + 1, end));
|
|
2176
2014
|
};
|
|
2177
|
-
|
|
2015
|
+
/**
|
|
2016
|
+
* The one keyed publication kernel used by ordinary collection nodes and
|
|
2017
|
+
* processor-group output leaves. It owns staged intent, exact transitions and
|
|
2018
|
+
* the persistent published snapshot; callers only provide the processor
|
|
2019
|
+
* callback and the equality policy.
|
|
2020
|
+
*/
|
|
2021
|
+
const createCollectionState = () => {
|
|
2178
2022
|
const values = /* @__PURE__ */ new Map();
|
|
2179
2023
|
let ids = Object.freeze([]);
|
|
2180
2024
|
let staged = /* @__PURE__ */ new Map();
|
|
2181
2025
|
let nextIds = ids;
|
|
2182
2026
|
let change;
|
|
2183
|
-
let instance;
|
|
2184
2027
|
let initialized = false;
|
|
2185
2028
|
let reset = false;
|
|
2186
2029
|
let revision = 0;
|
|
2187
2030
|
let publishedRoot;
|
|
2188
|
-
let
|
|
2189
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
2190
|
-
const changedKeys = /* @__PURE__ */ new Set();
|
|
2031
|
+
let changedKeys = /* @__PURE__ */ new Set();
|
|
2191
2032
|
let orderChanged = false;
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
},
|
|
2197
|
-
has: (key) => {
|
|
2198
|
-
owner.check();
|
|
2199
|
-
return treeHas(root, key);
|
|
2200
|
-
},
|
|
2201
|
-
ids: () => {
|
|
2202
|
-
owner.check();
|
|
2203
|
-
return publishedIds;
|
|
2204
|
-
}
|
|
2205
|
-
});
|
|
2206
|
-
const hasNext = (key) => staged.has(key) ? staged.get(key).present : values.has(key);
|
|
2033
|
+
let explicitOrder;
|
|
2034
|
+
let cleared = false;
|
|
2035
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2036
|
+
const hasNext = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
|
|
2207
2037
|
const getNext = (key) => {
|
|
2208
2038
|
const entry = staged.get(key);
|
|
2209
|
-
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
2039
|
+
return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
|
|
2210
2040
|
};
|
|
2211
|
-
const
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2041
|
+
const deriveIds = () => {
|
|
2042
|
+
const result = cleared ? [] : ids.filter(hasNext);
|
|
2043
|
+
for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
|
|
2044
|
+
return result;
|
|
2045
|
+
};
|
|
2046
|
+
const begin = (active, nextReset) => {
|
|
2047
|
+
staged = /* @__PURE__ */ new Map();
|
|
2048
|
+
nextIds = ids;
|
|
2049
|
+
change = void 0;
|
|
2050
|
+
changedKeys = /* @__PURE__ */ new Set();
|
|
2051
|
+
explicitOrder = void 0;
|
|
2052
|
+
orderChanged = false;
|
|
2053
|
+
cleared = nextReset;
|
|
2054
|
+
const read = (next) => ({
|
|
2055
|
+
get: (key) => {
|
|
2056
|
+
require_advanced.assertScope(active);
|
|
2057
|
+
return next ? getNext(key) : values.get(key);
|
|
2058
|
+
},
|
|
2059
|
+
has: (key) => {
|
|
2060
|
+
require_advanced.assertScope(active);
|
|
2061
|
+
return next ? hasNext(key) : values.has(key);
|
|
2062
|
+
},
|
|
2063
|
+
ids: () => {
|
|
2064
|
+
require_advanced.assertScope(active);
|
|
2065
|
+
return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : ids;
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
return {
|
|
2069
|
+
previous: read(false),
|
|
2070
|
+
next: read(true),
|
|
2071
|
+
output: {
|
|
2072
|
+
set: (key, value) => {
|
|
2073
|
+
require_advanced.assertScope(active);
|
|
2074
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
2075
|
+
staged.set(key, {
|
|
2076
|
+
present: true,
|
|
2077
|
+
value
|
|
2078
|
+
});
|
|
2234
2079
|
},
|
|
2235
|
-
|
|
2236
|
-
assertScope(active);
|
|
2237
|
-
|
|
2080
|
+
remove: (key) => {
|
|
2081
|
+
require_advanced.assertScope(active);
|
|
2082
|
+
staged.set(key, { present: false });
|
|
2238
2083
|
},
|
|
2239
|
-
|
|
2240
|
-
assertScope(active);
|
|
2241
|
-
|
|
2242
|
-
}
|
|
2243
|
-
});
|
|
2244
|
-
const input = {
|
|
2245
|
-
sources,
|
|
2246
|
-
previous: read(false),
|
|
2247
|
-
next: read(true),
|
|
2248
|
-
output: {
|
|
2249
|
-
set: (key, value) => {
|
|
2250
|
-
assertScope(active);
|
|
2251
|
-
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
2252
|
-
staged.set(key, {
|
|
2253
|
-
present: true,
|
|
2254
|
-
value
|
|
2255
|
-
});
|
|
2256
|
-
},
|
|
2257
|
-
remove: (key) => {
|
|
2258
|
-
assertScope(active);
|
|
2259
|
-
staged.set(key, { present: false });
|
|
2260
|
-
},
|
|
2261
|
-
order: (order) => {
|
|
2262
|
-
assertScope(active);
|
|
2263
|
-
explicitOrder = order.slice();
|
|
2264
|
-
}
|
|
2084
|
+
order: (order) => {
|
|
2085
|
+
require_advanced.assertScope(active);
|
|
2086
|
+
explicitOrder = Object.freeze([...order]);
|
|
2265
2087
|
}
|
|
2266
|
-
};
|
|
2267
|
-
if (build || !instance) {
|
|
2268
|
-
require_issue.profile.materialized.rebuilt();
|
|
2269
|
-
instance = void 0;
|
|
2270
|
-
cleared = true;
|
|
2271
|
-
const built = spec.build(input);
|
|
2272
|
-
assertSynchronous(built);
|
|
2273
|
-
instance = built;
|
|
2274
|
-
} else {
|
|
2275
|
-
require_issue.profile.materialized.updated();
|
|
2276
|
-
const result = instance.update(input);
|
|
2277
|
-
assertSynchronous(result);
|
|
2278
|
-
if (result?.kind === "rebuild") {
|
|
2279
|
-
staged.clear();
|
|
2280
|
-
cleared = true;
|
|
2281
|
-
explicitOrder = void 0;
|
|
2282
|
-
instance = void 0;
|
|
2283
|
-
const built = spec.build(input);
|
|
2284
|
-
assertSynchronous(built);
|
|
2285
|
-
instance = built;
|
|
2286
|
-
build = true;
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
if (cleared) {
|
|
2290
|
-
for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
|
|
2291
2088
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
for (const
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
if (
|
|
2305
|
-
|
|
2306
|
-
if (
|
|
2307
|
-
|
|
2089
|
+
};
|
|
2090
|
+
};
|
|
2091
|
+
const seal = (nextReset, isEqual) => {
|
|
2092
|
+
if (cleared) {
|
|
2093
|
+
for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
|
|
2094
|
+
}
|
|
2095
|
+
require_issue.profile.projection("touchedKeys", staged.size);
|
|
2096
|
+
const added = /* @__PURE__ */ new Set();
|
|
2097
|
+
const removed = /* @__PURE__ */ new Set();
|
|
2098
|
+
const updated = /* @__PURE__ */ new Set();
|
|
2099
|
+
for (const [key, entry] of staged) {
|
|
2100
|
+
const existed = values.has(key);
|
|
2101
|
+
if (entry.present) {
|
|
2102
|
+
if (!existed) added.add(key);
|
|
2103
|
+
else if (!isEqual(values.get(key), entry.value)) updated.add(key);
|
|
2104
|
+
else if (!nextReset) staged.delete(key);
|
|
2105
|
+
} else if (existed) removed.add(key);
|
|
2106
|
+
else staged.delete(key);
|
|
2107
|
+
}
|
|
2108
|
+
if (explicitOrder || added.size || removed.size) {
|
|
2109
|
+
const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
|
|
2110
|
+
if (new Set(order).size !== order.length || order.length !== values.size + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
|
|
2111
|
+
nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
|
|
2112
|
+
} else nextIds = ids;
|
|
2113
|
+
orderChanged = nextIds !== ids;
|
|
2114
|
+
for (const key of added) changedKeys.add(key);
|
|
2115
|
+
for (const key of removed) changedKeys.add(key);
|
|
2116
|
+
for (const key of updated) changedKeys.add(key);
|
|
2117
|
+
require_issue.profile.projection("changedKeys", changedKeys.size);
|
|
2118
|
+
const addedEntries = [];
|
|
2119
|
+
const updatedEntries = [];
|
|
2120
|
+
const removedEntries = [];
|
|
2121
|
+
for (const key of changedKeys) {
|
|
2122
|
+
const beforePresent = values.has(key);
|
|
2123
|
+
const afterPresent = hasNext(key);
|
|
2124
|
+
if (beforePresent === afterPresent) {
|
|
2125
|
+
if (!beforePresent || isEqual(values.get(key), getNext(key))) continue;
|
|
2308
2126
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
else if (!afterPresent) removedEntries.push({
|
|
2331
|
-
key,
|
|
2332
|
-
before: values.get(key)
|
|
2333
|
-
});
|
|
2334
|
-
else updatedEntries.push({
|
|
2335
|
-
key,
|
|
2336
|
-
before: values.get(key),
|
|
2337
|
-
after: getNext(key)
|
|
2338
|
-
});
|
|
2127
|
+
if (!beforePresent) addedEntries.push({
|
|
2128
|
+
key,
|
|
2129
|
+
after: getNext(key)
|
|
2130
|
+
});
|
|
2131
|
+
else if (!afterPresent) removedEntries.push({
|
|
2132
|
+
key,
|
|
2133
|
+
before: values.get(key)
|
|
2134
|
+
});
|
|
2135
|
+
else updatedEntries.push({
|
|
2136
|
+
key,
|
|
2137
|
+
before: values.get(key),
|
|
2138
|
+
after: getNext(key)
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
if (nextReset) change = Object.freeze({ kind: "reset" });
|
|
2142
|
+
else if (addedEntries.length || updatedEntries.length || removedEntries.length || orderChanged) {
|
|
2143
|
+
let commonOrderChanged = false;
|
|
2144
|
+
if (orderChanged) {
|
|
2145
|
+
const before = ids.filter((key) => !removed.has(key));
|
|
2146
|
+
const after = nextIds.filter((key) => !added.has(key));
|
|
2147
|
+
commonOrderChanged = before.some((key, index) => after[index] !== key);
|
|
2339
2148
|
}
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2149
|
+
change = Object.freeze({
|
|
2150
|
+
kind: "incremental",
|
|
2151
|
+
added: Object.freeze(addedEntries),
|
|
2152
|
+
updated: Object.freeze(updatedEntries),
|
|
2153
|
+
removed: Object.freeze(removedEntries),
|
|
2154
|
+
...commonOrderChanged ? { order: Object.freeze({
|
|
2155
|
+
before: Object.freeze([...ids]),
|
|
2156
|
+
after: Object.freeze([...nextIds])
|
|
2157
|
+
}) } : {}
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
reset = nextReset;
|
|
2161
|
+
return change !== void 0;
|
|
2162
|
+
};
|
|
2163
|
+
const context = (active, cause) => Object.freeze({
|
|
2164
|
+
kind: "collection",
|
|
2165
|
+
read: {
|
|
2166
|
+
get: (key) => {
|
|
2167
|
+
require_advanced.assertScope(active);
|
|
2168
|
+
return getNext(key);
|
|
2169
|
+
},
|
|
2170
|
+
has: (key) => {
|
|
2171
|
+
require_advanced.assertScope(active);
|
|
2172
|
+
return hasNext(key);
|
|
2173
|
+
},
|
|
2174
|
+
ids: () => {
|
|
2175
|
+
require_advanced.assertScope(active);
|
|
2176
|
+
return nextIds;
|
|
2358
2177
|
}
|
|
2359
|
-
reset = build;
|
|
2360
|
-
return change !== void 0;
|
|
2361
|
-
},
|
|
2362
|
-
context: (active) => {
|
|
2363
|
-
const input = {
|
|
2364
|
-
kind: "collection",
|
|
2365
|
-
read: {
|
|
2366
|
-
get: (key) => {
|
|
2367
|
-
assertScope(active);
|
|
2368
|
-
return getNext(key);
|
|
2369
|
-
},
|
|
2370
|
-
has: (key) => {
|
|
2371
|
-
assertScope(active);
|
|
2372
|
-
return hasNext(key);
|
|
2373
|
-
},
|
|
2374
|
-
ids: () => {
|
|
2375
|
-
assertScope(active);
|
|
2376
|
-
return nextIds;
|
|
2377
|
-
}
|
|
2378
|
-
},
|
|
2379
|
-
change,
|
|
2380
|
-
revision: revision + (change ? 1 : 0),
|
|
2381
|
-
cause
|
|
2382
|
-
};
|
|
2383
|
-
return Object.freeze(input);
|
|
2384
2178
|
},
|
|
2179
|
+
change,
|
|
2180
|
+
revision: revision + (change ? 1 : 0),
|
|
2181
|
+
cause
|
|
2182
|
+
});
|
|
2183
|
+
const current = (check) => {
|
|
2184
|
+
const root = publishedRoot;
|
|
2185
|
+
const publishedIds = ids;
|
|
2186
|
+
return Object.freeze({
|
|
2187
|
+
get: (key) => {
|
|
2188
|
+
check();
|
|
2189
|
+
return treeGet(root, key);
|
|
2190
|
+
},
|
|
2191
|
+
has: (key) => {
|
|
2192
|
+
check();
|
|
2193
|
+
return treeHas(root, key);
|
|
2194
|
+
},
|
|
2195
|
+
ids: () => {
|
|
2196
|
+
check();
|
|
2197
|
+
return publishedIds;
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
};
|
|
2201
|
+
return {
|
|
2202
|
+
begin,
|
|
2203
|
+
seal,
|
|
2204
|
+
context,
|
|
2205
|
+
current,
|
|
2385
2206
|
revision: () => revision,
|
|
2386
2207
|
reset: () => reset,
|
|
2387
2208
|
publish: () => {
|
|
@@ -2393,14 +2214,11 @@ const createCollection = (scheduler, spec) => {
|
|
|
2393
2214
|
} else for (const [key, entry] of staged) publishedRoot = entry.present ? treeSet(publishedRoot, key, entry.value) : treeRemove(publishedRoot, key);
|
|
2394
2215
|
for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
|
|
2395
2216
|
else values.delete(key);
|
|
2396
|
-
if (change)
|
|
2397
|
-
if (initialized) revision++;
|
|
2398
|
-
}
|
|
2217
|
+
if (change && initialized) revision++;
|
|
2399
2218
|
ids = nextIds;
|
|
2400
2219
|
initialized = true;
|
|
2401
2220
|
},
|
|
2402
2221
|
emit: (call) => {
|
|
2403
|
-
require_issue.profile.materialized.notification();
|
|
2404
2222
|
const resetEvent = Object.freeze({ kind: "reset" });
|
|
2405
2223
|
Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
|
|
2406
2224
|
},
|
|
@@ -2410,8 +2228,9 @@ const createCollection = (scheduler, spec) => {
|
|
|
2410
2228
|
change = void 0;
|
|
2411
2229
|
reset = false;
|
|
2412
2230
|
changedKeys.clear();
|
|
2413
|
-
|
|
2231
|
+
explicitOrder = void 0;
|
|
2414
2232
|
orderChanged = false;
|
|
2233
|
+
cleared = false;
|
|
2415
2234
|
},
|
|
2416
2235
|
release: () => {
|
|
2417
2236
|
values.clear();
|
|
@@ -2419,6 +2238,66 @@ const createCollection = (scheduler, spec) => {
|
|
|
2419
2238
|
publishedRoot = void 0;
|
|
2420
2239
|
listeners.clear();
|
|
2421
2240
|
ids = nextIds = Object.freeze([]);
|
|
2241
|
+
change = void 0;
|
|
2242
|
+
initialized = false;
|
|
2243
|
+
revision = 0;
|
|
2244
|
+
},
|
|
2245
|
+
subscribe: (listener) => {
|
|
2246
|
+
listeners.add(listener);
|
|
2247
|
+
},
|
|
2248
|
+
unsubscribe: (listener) => {
|
|
2249
|
+
listeners.delete(listener);
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
};
|
|
2253
|
+
//#endregion
|
|
2254
|
+
//#region core/src/projection/collection.ts
|
|
2255
|
+
const createCollection = (scheduler, spec) => {
|
|
2256
|
+
const state = createCollectionState();
|
|
2257
|
+
let instance;
|
|
2258
|
+
let cause;
|
|
2259
|
+
const owner = createNode(scheduler, spec, {
|
|
2260
|
+
evaluate: (sources, build, active) => {
|
|
2261
|
+
cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
|
|
2262
|
+
const input = state.begin(active, build);
|
|
2263
|
+
if (build || !instance) {
|
|
2264
|
+
require_issue.profile.materialized.rebuilt();
|
|
2265
|
+
instance = void 0;
|
|
2266
|
+
const built = spec.build({
|
|
2267
|
+
sources,
|
|
2268
|
+
...input
|
|
2269
|
+
});
|
|
2270
|
+
require_advanced.assertSynchronous(built);
|
|
2271
|
+
instance = built;
|
|
2272
|
+
} else {
|
|
2273
|
+
require_issue.profile.materialized.updated();
|
|
2274
|
+
const result = instance.update({
|
|
2275
|
+
sources,
|
|
2276
|
+
...input
|
|
2277
|
+
});
|
|
2278
|
+
require_advanced.assertSynchronous(result);
|
|
2279
|
+
if (result?.kind === "rebuild") {
|
|
2280
|
+
const rebuildInput = state.begin(active, true);
|
|
2281
|
+
instance = void 0;
|
|
2282
|
+
const built = spec.build({
|
|
2283
|
+
sources,
|
|
2284
|
+
...rebuildInput
|
|
2285
|
+
});
|
|
2286
|
+
require_advanced.assertSynchronous(built);
|
|
2287
|
+
instance = built;
|
|
2288
|
+
build = true;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
return state.seal(build, spec.isEqual ?? Object.is);
|
|
2292
|
+
},
|
|
2293
|
+
context: (active) => state.context(active, cause),
|
|
2294
|
+
revision: state.revision,
|
|
2295
|
+
reset: state.reset,
|
|
2296
|
+
publish: state.publish,
|
|
2297
|
+
emit: state.emit,
|
|
2298
|
+
clear: state.clear,
|
|
2299
|
+
release: () => {
|
|
2300
|
+
state.release();
|
|
2422
2301
|
instance = void 0;
|
|
2423
2302
|
}
|
|
2424
2303
|
});
|
|
@@ -2426,34 +2305,186 @@ const createCollection = (scheduler, spec) => {
|
|
|
2426
2305
|
kind: "collection",
|
|
2427
2306
|
current: () => {
|
|
2428
2307
|
owner.check();
|
|
2429
|
-
return
|
|
2308
|
+
return state.current(owner.check);
|
|
2430
2309
|
},
|
|
2431
2310
|
revision: () => {
|
|
2432
2311
|
owner.check();
|
|
2433
|
-
return revision;
|
|
2312
|
+
return state.revision();
|
|
2434
2313
|
},
|
|
2435
2314
|
subscribe: (listener) => {
|
|
2436
2315
|
owner.check();
|
|
2437
|
-
|
|
2438
|
-
return () =>
|
|
2439
|
-
listeners.delete(listener);
|
|
2440
|
-
};
|
|
2316
|
+
state.subscribe(listener);
|
|
2317
|
+
return () => state.unsubscribe(listener);
|
|
2441
2318
|
}
|
|
2442
2319
|
};
|
|
2443
2320
|
owner.install(handle);
|
|
2444
2321
|
return Object.freeze(handle);
|
|
2445
2322
|
};
|
|
2446
2323
|
//#endregion
|
|
2324
|
+
//#region core/src/projection/group.ts
|
|
2325
|
+
/**
|
|
2326
|
+
* Materializes one processor callback with several keyed output leaves. The
|
|
2327
|
+
* scheduler sees one node and the output leaves remain ordinary source records
|
|
2328
|
+
* for dependency, revision and listener matching.
|
|
2329
|
+
*/
|
|
2330
|
+
const createCollectionGroup = (scheduler, spec) => {
|
|
2331
|
+
scheduler.assertIdle();
|
|
2332
|
+
const sources = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
|
|
2333
|
+
const states = spec.outputs.map(() => createCollectionState());
|
|
2334
|
+
let instance;
|
|
2335
|
+
let cause;
|
|
2336
|
+
let changedOutputs = Object.freeze([]);
|
|
2337
|
+
let node;
|
|
2338
|
+
const order = scheduler.order();
|
|
2339
|
+
const outputs = states.map((state, index) => {
|
|
2340
|
+
const output = {
|
|
2341
|
+
owner: void 0,
|
|
2342
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
2343
|
+
context: (active) => state.context(active, cause),
|
|
2344
|
+
revision: state.revision,
|
|
2345
|
+
reset: state.reset,
|
|
2346
|
+
fault: void 0,
|
|
2347
|
+
disposed: false,
|
|
2348
|
+
clear: state.clear,
|
|
2349
|
+
emit: state.emit
|
|
2350
|
+
};
|
|
2351
|
+
Object.defineProperty(output, "fault", {
|
|
2352
|
+
configurable: false,
|
|
2353
|
+
enumerable: true,
|
|
2354
|
+
get: () => node.fault,
|
|
2355
|
+
set: (value) => {
|
|
2356
|
+
node.fault = value;
|
|
2357
|
+
}
|
|
2358
|
+
});
|
|
2359
|
+
return output;
|
|
2360
|
+
});
|
|
2361
|
+
const check = () => {
|
|
2362
|
+
if (node.disposed || !scheduler.active) throw new require_advanced.ProjectionDisposedError();
|
|
2363
|
+
if (node.fault) throw node.fault;
|
|
2364
|
+
};
|
|
2365
|
+
const evaluate = (inputSources, build, active) => {
|
|
2366
|
+
cause = Object.values(inputSources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
|
|
2367
|
+
const makeInput = (reset) => {
|
|
2368
|
+
const evaluations = states.map((state) => state.begin(active, reset));
|
|
2369
|
+
return {
|
|
2370
|
+
sources: inputSources,
|
|
2371
|
+
previous: evaluations.map((evaluation) => evaluation.previous),
|
|
2372
|
+
next: evaluations.map((evaluation) => evaluation.next),
|
|
2373
|
+
outputs: evaluations.map((evaluation) => evaluation.output)
|
|
2374
|
+
};
|
|
2375
|
+
};
|
|
2376
|
+
let input = makeInput(build);
|
|
2377
|
+
if (build || !instance) {
|
|
2378
|
+
require_issue.profile.materialized.rebuilt();
|
|
2379
|
+
instance = void 0;
|
|
2380
|
+
const built = spec.build(input);
|
|
2381
|
+
require_advanced.assertSynchronous(built);
|
|
2382
|
+
instance = built;
|
|
2383
|
+
} else {
|
|
2384
|
+
require_issue.profile.materialized.updated();
|
|
2385
|
+
const result = instance.update(input);
|
|
2386
|
+
require_advanced.assertSynchronous(result);
|
|
2387
|
+
if (result?.kind === "rebuild") {
|
|
2388
|
+
input = makeInput(true);
|
|
2389
|
+
instance = void 0;
|
|
2390
|
+
const built = spec.build(input);
|
|
2391
|
+
require_advanced.assertSynchronous(built);
|
|
2392
|
+
instance = built;
|
|
2393
|
+
build = true;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
changedOutputs = Object.freeze(states.flatMap((state, index) => state.seal(build, spec.outputs[index].isEqual ?? Object.is) ? [outputs[index]] : []));
|
|
2397
|
+
return changedOutputs.length > 0;
|
|
2398
|
+
};
|
|
2399
|
+
node = {
|
|
2400
|
+
owner: void 0,
|
|
2401
|
+
order,
|
|
2402
|
+
name: `processor-group (${order})`,
|
|
2403
|
+
sources: sources.map(([, source]) => source),
|
|
2404
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
2405
|
+
outputs,
|
|
2406
|
+
disposed: false,
|
|
2407
|
+
fault: void 0,
|
|
2408
|
+
context: (active) => {
|
|
2409
|
+
require_advanced.assertScope(active);
|
|
2410
|
+
return Object.freeze({ kind: "group" });
|
|
2411
|
+
},
|
|
2412
|
+
revision: () => Math.max(0, ...states.map((state) => state.revision())),
|
|
2413
|
+
reset: () => states.some((state) => state.reset()),
|
|
2414
|
+
evaluate: (build) => {
|
|
2415
|
+
let active = true;
|
|
2416
|
+
try {
|
|
2417
|
+
const scopeActive = () => active;
|
|
2418
|
+
return evaluate(Object.fromEntries(sources.map(([key, source]) => [key, source.context(scopeActive)])), build, scopeActive);
|
|
2419
|
+
} finally {
|
|
2420
|
+
active = false;
|
|
2421
|
+
}
|
|
2422
|
+
},
|
|
2423
|
+
changedOutputs: () => changedOutputs,
|
|
2424
|
+
publish: () => {
|
|
2425
|
+
if (node.fault) states.forEach((state) => state.clear());
|
|
2426
|
+
else states.forEach((state) => state.publish());
|
|
2427
|
+
},
|
|
2428
|
+
emit: () => void 0,
|
|
2429
|
+
clear: () => {
|
|
2430
|
+
states.forEach((state) => state.clear());
|
|
2431
|
+
changedOutputs = Object.freeze([]);
|
|
2432
|
+
cause = void 0;
|
|
2433
|
+
},
|
|
2434
|
+
release: () => {
|
|
2435
|
+
states.forEach((state) => state.release());
|
|
2436
|
+
outputs.forEach((output) => {
|
|
2437
|
+
output.disposed = true;
|
|
2438
|
+
output.consumers.clear();
|
|
2439
|
+
});
|
|
2440
|
+
instance = void 0;
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
node.owner = node;
|
|
2444
|
+
outputs.forEach((output) => {
|
|
2445
|
+
output.owner = node;
|
|
2446
|
+
});
|
|
2447
|
+
scheduler.initialize(() => {
|
|
2448
|
+
const invalid = node.sources.find((source) => source.fault || source.disposed);
|
|
2449
|
+
if (invalid) throw invalid.fault ?? new require_advanced.ProjectionDisposedError();
|
|
2450
|
+
node.evaluate(true);
|
|
2451
|
+
node.publish();
|
|
2452
|
+
node.clear();
|
|
2453
|
+
});
|
|
2454
|
+
const handles = outputs.map((output, index) => {
|
|
2455
|
+
const handle = {
|
|
2456
|
+
kind: "collection",
|
|
2457
|
+
current: () => {
|
|
2458
|
+
check();
|
|
2459
|
+
return states[index].current(check);
|
|
2460
|
+
},
|
|
2461
|
+
revision: () => {
|
|
2462
|
+
check();
|
|
2463
|
+
return states[index].revision();
|
|
2464
|
+
},
|
|
2465
|
+
subscribe: (listener) => {
|
|
2466
|
+
check();
|
|
2467
|
+
states[index].subscribe(listener);
|
|
2468
|
+
return () => states[index].unsubscribe(listener);
|
|
2469
|
+
}
|
|
2470
|
+
};
|
|
2471
|
+
scheduler.register(handle, output);
|
|
2472
|
+
return Object.freeze(handle);
|
|
2473
|
+
});
|
|
2474
|
+
scheduler.addNode(node);
|
|
2475
|
+
return Object.freeze(handles);
|
|
2476
|
+
};
|
|
2477
|
+
//#endregion
|
|
2447
2478
|
//#region core/src/projection/graph.ts
|
|
2448
2479
|
const createProjectionGraph = (options) => {
|
|
2449
|
-
const scheduler = createScheduler(options.onError);
|
|
2480
|
+
const scheduler = require_advanced.createScheduler(options.onError);
|
|
2450
2481
|
const sources = createSources(scheduler);
|
|
2451
2482
|
const value = (spec, options) => createValue(scheduler, spec, options);
|
|
2452
2483
|
const map = (source, mapper, options) => {
|
|
2453
2484
|
sources.assertCollection(source);
|
|
2454
2485
|
const calculate = (id, entry) => {
|
|
2455
2486
|
const value = mapper(id, entry);
|
|
2456
|
-
assertSynchronous(value);
|
|
2487
|
+
require_advanced.assertSynchronous(value);
|
|
2457
2488
|
return value;
|
|
2458
2489
|
};
|
|
2459
2490
|
return createCollection(scheduler, {
|
|
@@ -2497,11 +2528,16 @@ const createProjectionGraph = (options) => {
|
|
|
2497
2528
|
fromSource: (source, options) => sources.fromSource(source, options),
|
|
2498
2529
|
fromCollectionSource: (source) => sources.fromCollectionSource(source),
|
|
2499
2530
|
input: sources.input,
|
|
2531
|
+
collectionInput: sources.collectionInput,
|
|
2500
2532
|
fromReadable: sources.fromReadable,
|
|
2501
2533
|
value,
|
|
2502
2534
|
collection: (spec) => createCollection(scheduler, spec),
|
|
2535
|
+
group: (spec) => createCollectionGroup(scheduler, spec),
|
|
2503
2536
|
map,
|
|
2504
2537
|
batch: scheduler.batch,
|
|
2538
|
+
assertIdle: scheduler.assertIdle,
|
|
2539
|
+
releaseNode: scheduler.releaseNode,
|
|
2540
|
+
releaseInput: scheduler.unregisterSource,
|
|
2505
2541
|
dispose: () => {
|
|
2506
2542
|
try {
|
|
2507
2543
|
scheduler.dispose();
|
|
@@ -2671,16 +2707,52 @@ const sourceOf = (instance) => instance;
|
|
|
2671
2707
|
const createProjectionRuntime = (options) => {
|
|
2672
2708
|
const runtime = createProjectionGraph({ onError: options?.onError ?? (() => void 0) });
|
|
2673
2709
|
const materialized = /* @__PURE__ */ new WeakMap();
|
|
2674
|
-
const
|
|
2710
|
+
const groups = /* @__PURE__ */ new WeakMap();
|
|
2711
|
+
let disposed = false;
|
|
2712
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
2713
|
+
let materialize;
|
|
2714
|
+
const materializeGroup = (definition, requester) => {
|
|
2715
|
+
const existing = groups.get(definition);
|
|
2716
|
+
if (existing) return existing;
|
|
2717
|
+
if (definition.projections.map((projection) => require_advanced.ownerOf(projection)).filter((owner) => owner !== void 0).some((owner) => owner !== requester)) throw new TypeError("Projection group belongs to another scope.");
|
|
2718
|
+
const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, requester))]));
|
|
2719
|
+
const nodes = runtime.group({
|
|
2720
|
+
sources,
|
|
2721
|
+
outputs: definition.outputs,
|
|
2722
|
+
build: definition.build
|
|
2723
|
+
});
|
|
2724
|
+
const group = Object.freeze({
|
|
2725
|
+
definition,
|
|
2726
|
+
nodes
|
|
2727
|
+
});
|
|
2728
|
+
groups.set(definition, group);
|
|
2729
|
+
for (let index = 0; index < definition.projections.length; index++) materialized.set(definition.projections[index], {
|
|
2730
|
+
kind: "projection",
|
|
2731
|
+
node: nodes[index]
|
|
2732
|
+
});
|
|
2733
|
+
if (requester) requester.materialized.push(definition);
|
|
2734
|
+
return group;
|
|
2735
|
+
};
|
|
2736
|
+
materialize = (projection, requester) => {
|
|
2737
|
+
const owner = require_advanced.ownerOf(projection);
|
|
2738
|
+
if (owner) {
|
|
2739
|
+
if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
|
|
2740
|
+
if (!requester.active) throw new require_advanced.ProjectionDisposedError();
|
|
2741
|
+
}
|
|
2675
2742
|
const old = materialized.get(projection);
|
|
2676
2743
|
if (old) return old.node;
|
|
2677
|
-
const definition =
|
|
2744
|
+
const definition = require_advanced.definitionOf(projection);
|
|
2745
|
+
if (definition.kind === "incremental-group-output") return materializeGroup(definition.group, requester).nodes[definition.output];
|
|
2678
2746
|
let instance;
|
|
2679
|
-
let
|
|
2747
|
+
let write;
|
|
2680
2748
|
switch (definition.kind) {
|
|
2681
2749
|
case "input": {
|
|
2682
2750
|
const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
|
|
2683
|
-
|
|
2751
|
+
write = {
|
|
2752
|
+
kind: "value-input",
|
|
2753
|
+
source: input.source,
|
|
2754
|
+
set: input.set
|
|
2755
|
+
};
|
|
2684
2756
|
instance = runtime.value({
|
|
2685
2757
|
sources: { input: input.source },
|
|
2686
2758
|
build: (values) => ({
|
|
@@ -2693,6 +2765,16 @@ const createProjectionRuntime = (options) => {
|
|
|
2693
2765
|
});
|
|
2694
2766
|
break;
|
|
2695
2767
|
}
|
|
2768
|
+
case "collection-input": {
|
|
2769
|
+
const source = runtime.collectionInput(definition.initial);
|
|
2770
|
+
write = {
|
|
2771
|
+
kind: "collection-input",
|
|
2772
|
+
source: source.source,
|
|
2773
|
+
update: source.update
|
|
2774
|
+
};
|
|
2775
|
+
instance = runtime.map(source.source, (_key, value) => value);
|
|
2776
|
+
break;
|
|
2777
|
+
}
|
|
2696
2778
|
case "readable": {
|
|
2697
2779
|
const source = runtime.fromReadable(definition.readable, { isEqual: definition.isEqual });
|
|
2698
2780
|
instance = runtime.value({
|
|
@@ -2764,7 +2846,7 @@ const createProjectionRuntime = (options) => {
|
|
|
2764
2846
|
break;
|
|
2765
2847
|
}
|
|
2766
2848
|
case "derive": {
|
|
2767
|
-
const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency))]));
|
|
2849
|
+
const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency, owner))]));
|
|
2768
2850
|
instance = runtime.value({
|
|
2769
2851
|
sources,
|
|
2770
2852
|
build: (values) => ({
|
|
@@ -2778,7 +2860,7 @@ const createProjectionRuntime = (options) => {
|
|
|
2778
2860
|
break;
|
|
2779
2861
|
}
|
|
2780
2862
|
case "incremental-value": {
|
|
2781
|
-
const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
|
|
2863
|
+
const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
|
|
2782
2864
|
instance = runtime.value({
|
|
2783
2865
|
sources,
|
|
2784
2866
|
build: definition.build
|
|
@@ -2786,7 +2868,7 @@ const createProjectionRuntime = (options) => {
|
|
|
2786
2868
|
break;
|
|
2787
2869
|
}
|
|
2788
2870
|
case "incremental-collection": {
|
|
2789
|
-
const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
|
|
2871
|
+
const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
|
|
2790
2872
|
instance = runtime.collection({
|
|
2791
2873
|
sources,
|
|
2792
2874
|
build: definition.build,
|
|
@@ -2796,14 +2878,19 @@ const createProjectionRuntime = (options) => {
|
|
|
2796
2878
|
break;
|
|
2797
2879
|
}
|
|
2798
2880
|
}
|
|
2799
|
-
|
|
2881
|
+
const record = write ? {
|
|
2800
2882
|
node: instance,
|
|
2801
|
-
|
|
2802
|
-
}
|
|
2883
|
+
...write
|
|
2884
|
+
} : {
|
|
2885
|
+
kind: "projection",
|
|
2886
|
+
node: instance
|
|
2887
|
+
};
|
|
2888
|
+
materialized.set(projection, record);
|
|
2889
|
+
if (owner) owner.materialized.push(projection);
|
|
2803
2890
|
return instance;
|
|
2804
2891
|
};
|
|
2805
|
-
const get = (projection) => {
|
|
2806
|
-
const instance = materialize(projection);
|
|
2892
|
+
const get = (projection, requester) => {
|
|
2893
|
+
const instance = materialize(projection, requester);
|
|
2807
2894
|
const current = instance.current();
|
|
2808
2895
|
if (instance.kind === "collection") {
|
|
2809
2896
|
const record = materialized.get(projection) ?? materialized.get(instance);
|
|
@@ -2831,11 +2918,11 @@ const createProjectionRuntime = (options) => {
|
|
|
2831
2918
|
}
|
|
2832
2919
|
return current;
|
|
2833
2920
|
};
|
|
2834
|
-
|
|
2921
|
+
const makeReadable = (projection, selector, equality = Object.is, requester) => {
|
|
2835
2922
|
if (!selector) return Object.freeze({
|
|
2836
|
-
current: () => get(projection),
|
|
2837
|
-
revision: () => materialize(projection).revision(),
|
|
2838
|
-
subscribe: (listener) => materialize(projection).subscribe(listener)
|
|
2923
|
+
current: () => get(projection, requester),
|
|
2924
|
+
revision: () => materialize(projection, requester).revision(),
|
|
2925
|
+
subscribe: (listener) => materialize(projection, requester).subscribe(listener)
|
|
2839
2926
|
});
|
|
2840
2927
|
let initialized = false;
|
|
2841
2928
|
let value;
|
|
@@ -2850,7 +2937,7 @@ const createProjectionRuntime = (options) => {
|
|
|
2850
2937
|
let unsubscribeSource;
|
|
2851
2938
|
const listeners = /* @__PURE__ */ new Set();
|
|
2852
2939
|
const updateCollectionSnapshot = () => {
|
|
2853
|
-
const current = get(projection);
|
|
2940
|
+
const current = get(projection, requester);
|
|
2854
2941
|
previousCollection = isMapLike(current) ? current : void 0;
|
|
2855
2942
|
};
|
|
2856
2943
|
const evaluate = () => {
|
|
@@ -2864,7 +2951,7 @@ const createProjectionRuntime = (options) => {
|
|
|
2864
2951
|
let source;
|
|
2865
2952
|
let next;
|
|
2866
2953
|
try {
|
|
2867
|
-
source = get(projection);
|
|
2954
|
+
source = get(projection, requester);
|
|
2868
2955
|
next = selector(source);
|
|
2869
2956
|
} finally {
|
|
2870
2957
|
activeTracker = previousTracker;
|
|
@@ -2881,30 +2968,30 @@ const createProjectionRuntime = (options) => {
|
|
|
2881
2968
|
}
|
|
2882
2969
|
selection = nextSelection;
|
|
2883
2970
|
initialized = true;
|
|
2884
|
-
sourceRevision = materialize(projection).revision();
|
|
2971
|
+
sourceRevision = materialize(projection, requester).revision();
|
|
2885
2972
|
updateCollectionSnapshot();
|
|
2886
2973
|
return changed;
|
|
2887
2974
|
};
|
|
2888
2975
|
const ensureCurrent = () => {
|
|
2889
|
-
const revision = materialize(projection).revision();
|
|
2976
|
+
const revision = materialize(projection, requester).revision();
|
|
2890
2977
|
if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
|
|
2891
2978
|
return value;
|
|
2892
2979
|
};
|
|
2893
2980
|
const onProjectionChange = (incoming) => {
|
|
2894
2981
|
let change = incoming;
|
|
2895
2982
|
if (!change && previousCollection) {
|
|
2896
|
-
const current = get(projection);
|
|
2983
|
+
const current = get(projection, requester);
|
|
2897
2984
|
if (isMapLike(current)) {
|
|
2898
2985
|
change = mapChange(previousCollection, current);
|
|
2899
2986
|
previousCollection = current;
|
|
2900
2987
|
if (!change) {
|
|
2901
|
-
sourceRevision = materialize(projection).revision();
|
|
2988
|
+
sourceRevision = materialize(projection, requester).revision();
|
|
2902
2989
|
return;
|
|
2903
2990
|
}
|
|
2904
2991
|
} else previousCollection = void 0;
|
|
2905
2992
|
}
|
|
2906
2993
|
if (!selectionAffects(selection, change)) {
|
|
2907
|
-
sourceRevision = materialize(projection).revision();
|
|
2994
|
+
sourceRevision = materialize(projection, requester).revision();
|
|
2908
2995
|
updateCollectionSnapshot();
|
|
2909
2996
|
return;
|
|
2910
2997
|
}
|
|
@@ -2912,12 +2999,12 @@ const createProjectionRuntime = (options) => {
|
|
|
2912
2999
|
const changed = evaluate();
|
|
2913
3000
|
if (unsubscribeSource && !sameSelection(previousSelection, selection)) {
|
|
2914
3001
|
unsubscribeSource();
|
|
2915
|
-
unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
|
|
3002
|
+
unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
|
|
2916
3003
|
}
|
|
2917
3004
|
if (changed) Array.from(listeners).forEach((listener) => listener());
|
|
2918
3005
|
};
|
|
2919
3006
|
const installSource = () => {
|
|
2920
|
-
if (!unsubscribeSource) unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
|
|
3007
|
+
if (!unsubscribeSource) unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
|
|
2921
3008
|
};
|
|
2922
3009
|
return Object.freeze({
|
|
2923
3010
|
current: ensureCurrent,
|
|
@@ -2938,18 +3025,150 @@ const createProjectionRuntime = (options) => {
|
|
|
2938
3025
|
};
|
|
2939
3026
|
}
|
|
2940
3027
|
});
|
|
3028
|
+
};
|
|
3029
|
+
function readable(projection, selector, equality) {
|
|
3030
|
+
return makeReadable(projection, selector, equality);
|
|
2941
3031
|
}
|
|
3032
|
+
const setInput = (target, value, requester) => {
|
|
3033
|
+
materialize(target, requester);
|
|
3034
|
+
const record = materialized.get(target);
|
|
3035
|
+
if (record?.kind !== "value-input") throw new TypeError("Projection is not a value input.");
|
|
3036
|
+
record.set(value);
|
|
3037
|
+
};
|
|
3038
|
+
const updateInput = (target, run, requester) => {
|
|
3039
|
+
materialize(target, requester);
|
|
3040
|
+
const record = materialized.get(target);
|
|
3041
|
+
if (record?.kind !== "collection-input") throw new TypeError("Projection is not a collection input.");
|
|
3042
|
+
record.update(run);
|
|
3043
|
+
};
|
|
3044
|
+
const createScope = () => {
|
|
3045
|
+
runtime.assertIdle();
|
|
3046
|
+
const state = {
|
|
3047
|
+
active: true,
|
|
3048
|
+
materialized: [],
|
|
3049
|
+
subscriptions: /* @__PURE__ */ new Set()
|
|
3050
|
+
};
|
|
3051
|
+
scopes.add(state);
|
|
3052
|
+
const assertActive = () => {
|
|
3053
|
+
if (!state.active) throw new require_advanced.ProjectionDisposedError();
|
|
3054
|
+
};
|
|
3055
|
+
const own = (projection) => {
|
|
3056
|
+
assertActive();
|
|
3057
|
+
const definition = require_advanced.definitionOf(projection);
|
|
3058
|
+
const dependencies = definition.kind === "derive" ? definition.dependencies : definition.kind === "incremental-value" || definition.kind === "incremental-collection" ? Object.values(definition.dependencies) : definition.kind === "incremental-group-output" ? Object.values(definition.group.dependencies) : [];
|
|
3059
|
+
for (const dependency of dependencies) {
|
|
3060
|
+
const dependencyOwner = require_advanced.ownerOf(dependency);
|
|
3061
|
+
if (dependencyOwner && dependencyOwner !== state) throw new TypeError("A scope cannot depend on another scope.");
|
|
3062
|
+
}
|
|
3063
|
+
require_advanced.ownDefinition(projection, state);
|
|
3064
|
+
return projection;
|
|
3065
|
+
};
|
|
3066
|
+
const scopedInput = Object.assign((initial, equality) => own(require_advanced.input(initial, equality)), { collection: (initial) => own(require_advanced.input.collection(initial)) });
|
|
3067
|
+
const scopedDerive = (dependencies, compute, equality) => own(require_advanced.derive(dependencies, compute, equality));
|
|
3068
|
+
const ownOutputTree = (tree) => {
|
|
3069
|
+
if (require_advanced.isProjection(tree)) return own(tree);
|
|
3070
|
+
if (!tree || typeof tree !== "object") throw new TypeError("Incremental group output tree must be an object.");
|
|
3071
|
+
for (const value of Object.values(tree)) ownOutputTree(value);
|
|
3072
|
+
return tree;
|
|
3073
|
+
};
|
|
3074
|
+
const scopedIncremental = Object.assign((dependencies, processor) => own(require_advanced.incremental(dependencies, processor)), {
|
|
3075
|
+
collection: (dependencies, processor) => own(require_advanced.incremental.collection(dependencies, processor)),
|
|
3076
|
+
group: (dependencies, defineOutputs, processor) => ownOutputTree(require_advanced.incremental.group(dependencies, defineOutputs, processor))
|
|
3077
|
+
});
|
|
3078
|
+
function scopeReadable(projection, selector, equality) {
|
|
3079
|
+
assertActive();
|
|
3080
|
+
materialize(projection, state);
|
|
3081
|
+
const source = makeReadable(projection, selector, equality, state);
|
|
3082
|
+
return Object.freeze({
|
|
3083
|
+
current: () => {
|
|
3084
|
+
assertActive();
|
|
3085
|
+
return source.current();
|
|
3086
|
+
},
|
|
3087
|
+
revision: () => {
|
|
3088
|
+
assertActive();
|
|
3089
|
+
return source.revision();
|
|
3090
|
+
},
|
|
3091
|
+
subscribe: (listener) => {
|
|
3092
|
+
assertActive();
|
|
3093
|
+
const stop = source.subscribe(listener);
|
|
3094
|
+
let subscribed = true;
|
|
3095
|
+
const unsubscribe = () => {
|
|
3096
|
+
if (!subscribed) return;
|
|
3097
|
+
subscribed = false;
|
|
3098
|
+
state.subscriptions.delete(unsubscribe);
|
|
3099
|
+
stop();
|
|
3100
|
+
};
|
|
3101
|
+
state.subscriptions.add(unsubscribe);
|
|
3102
|
+
return unsubscribe;
|
|
3103
|
+
}
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
3106
|
+
function scopeBatch(optionsOrCallback, maybeCallback) {
|
|
3107
|
+
assertActive();
|
|
3108
|
+
return typeof optionsOrCallback === "function" ? runtime.batch(optionsOrCallback) : runtime.batch(optionsOrCallback, maybeCallback);
|
|
3109
|
+
}
|
|
3110
|
+
return Object.freeze({
|
|
3111
|
+
input: scopedInput,
|
|
3112
|
+
derive: scopedDerive,
|
|
3113
|
+
incremental: scopedIncremental,
|
|
3114
|
+
get: (projection) => {
|
|
3115
|
+
assertActive();
|
|
3116
|
+
return get(projection, state);
|
|
3117
|
+
},
|
|
3118
|
+
readable: scopeReadable,
|
|
3119
|
+
set: (target, value) => {
|
|
3120
|
+
assertActive();
|
|
3121
|
+
setInput(target, value, state);
|
|
3122
|
+
},
|
|
3123
|
+
update: (target, run) => {
|
|
3124
|
+
assertActive();
|
|
3125
|
+
updateInput(target, run, state);
|
|
3126
|
+
},
|
|
3127
|
+
batch: scopeBatch,
|
|
3128
|
+
dispose: () => {
|
|
3129
|
+
if (!state.active) return;
|
|
3130
|
+
runtime.assertIdle();
|
|
3131
|
+
state.active = false;
|
|
3132
|
+
state.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
3133
|
+
for (const definition of state.materialized.reverse()) {
|
|
3134
|
+
const group = groups.get(definition);
|
|
3135
|
+
if (group) {
|
|
3136
|
+
runtime.releaseNode(group.nodes[0]);
|
|
3137
|
+
groups.delete(definition);
|
|
3138
|
+
group.definition.projections.forEach((projection) => materialized.delete(projection));
|
|
3139
|
+
continue;
|
|
3140
|
+
}
|
|
3141
|
+
const record = materialized.get(definition);
|
|
3142
|
+
if (!record) continue;
|
|
3143
|
+
runtime.releaseNode(record.node);
|
|
3144
|
+
if (record.kind !== "projection") runtime.releaseInput(record.source);
|
|
3145
|
+
materialized.delete(definition);
|
|
3146
|
+
}
|
|
3147
|
+
state.materialized.length = 0;
|
|
3148
|
+
scopes.delete(state);
|
|
3149
|
+
}
|
|
3150
|
+
});
|
|
3151
|
+
};
|
|
2942
3152
|
const store = {
|
|
2943
3153
|
get,
|
|
2944
3154
|
readable,
|
|
2945
|
-
set:
|
|
2946
|
-
|
|
2947
|
-
const target = materialized.get(input);
|
|
2948
|
-
if (!target?.set) throw new TypeError("Projection is not an input.");
|
|
2949
|
-
target.set(value);
|
|
2950
|
-
}),
|
|
3155
|
+
set: setInput,
|
|
3156
|
+
update: updateInput,
|
|
2951
3157
|
batch: runtime.batch,
|
|
2952
|
-
|
|
3158
|
+
scope: createScope,
|
|
3159
|
+
dispose: () => {
|
|
3160
|
+
if (disposed) return;
|
|
3161
|
+
runtime.assertIdle();
|
|
3162
|
+
disposed = true;
|
|
3163
|
+
for (const scope of scopes) {
|
|
3164
|
+
scope.active = false;
|
|
3165
|
+
scope.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
3166
|
+
scope.subscriptions.clear();
|
|
3167
|
+
scope.materialized.length = 0;
|
|
3168
|
+
}
|
|
3169
|
+
scopes.clear();
|
|
3170
|
+
runtime.dispose();
|
|
3171
|
+
}
|
|
2953
3172
|
};
|
|
2954
3173
|
return Object.freeze(store);
|
|
2955
3174
|
};
|
|
@@ -2957,19 +3176,19 @@ const createProjectionRuntime = (options) => {
|
|
|
2957
3176
|
exports.DocumentDisposedError = require_notification.DocumentDisposedError;
|
|
2958
3177
|
exports.DocumentReentrancyError = require_notification.DocumentReentrancyError;
|
|
2959
3178
|
exports.ParseError = require_scope.ParseError;
|
|
2960
|
-
exports.ProjectionDisposedError = ProjectionDisposedError;
|
|
2961
|
-
exports.ProjectionError = ProjectionError;
|
|
3179
|
+
exports.ProjectionDisposedError = require_advanced.ProjectionDisposedError;
|
|
3180
|
+
exports.ProjectionError = require_advanced.ProjectionError;
|
|
2962
3181
|
exports.TransactionRejected = require_notification.TransactionRejected;
|
|
2963
3182
|
exports.asReadable = asReadable;
|
|
2964
3183
|
exports.createDocument = createDocument;
|
|
2965
3184
|
exports.createProjectionRuntime = createProjectionRuntime;
|
|
2966
|
-
exports.derive =
|
|
3185
|
+
exports.derive = require_advanced.derive;
|
|
2967
3186
|
exports.field = field;
|
|
2968
|
-
exports.input =
|
|
3187
|
+
exports.input = require_advanced.input;
|
|
2969
3188
|
exports.list = list;
|
|
2970
3189
|
exports.map = map;
|
|
2971
3190
|
exports.object = object;
|
|
2972
|
-
exports.observe =
|
|
3191
|
+
exports.observe = require_advanced.observe;
|
|
2973
3192
|
exports.optional = optional;
|
|
2974
3193
|
exports.parse = require_scope.parse;
|
|
2975
3194
|
exports.read = read;
|