@shirudo/ddd-kit 3.0.0-rc.10 → 3.0.0-rc.11
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/chunks/kit-errors.d.ts +287 -108
- package/dist/chunks/kit-errors.js +250 -111
- package/dist/chunks/kit-errors.js.map +1 -1
- package/dist/chunks/{ports.js → read-stream-pages.js} +531 -92
- package/dist/chunks/read-stream-pages.js.map +1 -0
- package/dist/chunks/snapshot-store.d.ts +542 -115
- package/dist/index.d.ts +598 -434
- package/dist/index.js +1508 -637
- package/dist/index.js.map +1 -1
- package/dist/money.d.ts +1 -1
- package/dist/testing.d.ts +106 -15
- package/dist/testing.js +546 -52
- package/dist/testing.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunks/ports.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as InvalidEventStreamPageError, Y as assertNoHostileOwnProtoKey } from "./kit-errors.js";
|
|
2
2
|
import { err, ok } from "@shirudo/result";
|
|
3
3
|
|
|
4
4
|
//#region src/internal/structural/is-built-in.ts
|
|
@@ -91,9 +91,9 @@ const intrinsicConstructorSources = new Map([
|
|
|
91
91
|
"BigUint64Array"
|
|
92
92
|
].flatMap((name) => {
|
|
93
93
|
const intrinsic = Object.getOwnPropertyDescriptor(globalThis, name)?.value;
|
|
94
|
-
return typeof intrinsic === "function" ? [[name, functionToString.call(intrinsic)]] : [];
|
|
94
|
+
return typeof intrinsic === "function" ? [[name, /* @__PURE__ */ new Set([functionToString.call(intrinsic), `function ${name}() { [native code] }`])]] : [];
|
|
95
95
|
}));
|
|
96
|
-
const intrinsicConstructorSourceSet = new Set(intrinsicConstructorSources.values());
|
|
96
|
+
const intrinsicConstructorSourceSet = new Set([...intrinsicConstructorSources.values()].flatMap((sources) => [...sources]));
|
|
97
97
|
const ERROR_INTRINSIC_NAMES = [
|
|
98
98
|
"Error",
|
|
99
99
|
"EvalError",
|
|
@@ -114,13 +114,13 @@ function isIntrinsicConstructorPrototype(prototype, expectedName) {
|
|
|
114
114
|
} catch {
|
|
115
115
|
return false;
|
|
116
116
|
}
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
117
|
+
const expectedSources = expectedName === void 0 ? void 0 : intrinsicConstructorSources.get(expectedName);
|
|
118
|
+
if (expectedSources !== void 0 && !expectedSources.has(candidateSource) || expectedSources === void 0 && !intrinsicConstructorSourceSet.has(candidateSource)) return false;
|
|
119
119
|
const nameDescriptor = Object.getOwnPropertyDescriptor(candidateConstructor, "name");
|
|
120
120
|
const candidateName = nameDescriptor !== void 0 && "value" in nameDescriptor && typeof nameDescriptor.value === "string" ? nameDescriptor.value : void 0;
|
|
121
121
|
const intrinsicName = expectedName ?? candidateName;
|
|
122
|
-
const
|
|
123
|
-
return candidateName === intrinsicName &&
|
|
122
|
+
const intrinsicSources = intrinsicName === void 0 ? void 0 : intrinsicConstructorSources.get(intrinsicName);
|
|
123
|
+
return candidateName === intrinsicName && intrinsicSources !== void 0 && intrinsicSources.has(candidateSource) && Object.getOwnPropertyDescriptor(candidateConstructor, "prototype")?.value === prototype;
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
126
|
* Accepts an intrinsic prototype, optionally behind transparent
|
|
@@ -675,8 +675,8 @@ const DATE_MUTATORS = [
|
|
|
675
675
|
"setYear"
|
|
676
676
|
];
|
|
677
677
|
const mutationThrowers = /* @__PURE__ */ new Map();
|
|
678
|
-
const mapEntries = Map.prototype.entries;
|
|
679
|
-
const setValues = Set.prototype.values;
|
|
678
|
+
const mapEntries$1 = Map.prototype.entries;
|
|
679
|
+
const setValues$1 = Set.prototype.values;
|
|
680
680
|
function mutationThrower(typeName, method) {
|
|
681
681
|
const key = `${typeName}.${method}`;
|
|
682
682
|
let thrower = mutationThrowers.get(key);
|
|
@@ -880,7 +880,7 @@ function cloneForVo(value, visited) {
|
|
|
880
880
|
if (tag === "[object Map]") {
|
|
881
881
|
const clone = /* @__PURE__ */ new Map();
|
|
882
882
|
visited.set(obj, clone);
|
|
883
|
-
for (const [key, entry] of mapEntries.call(obj)) {
|
|
883
|
+
for (const [key, entry] of mapEntries$1.call(obj)) {
|
|
884
884
|
if (!isPrimitiveValue(key)) throw new TypeError("vo() Map keys must be primitive values to preserve value equality");
|
|
885
885
|
clone.set(key, cloneForVo(entry, visited));
|
|
886
886
|
}
|
|
@@ -889,7 +889,7 @@ function cloneForVo(value, visited) {
|
|
|
889
889
|
if (tag === "[object Set]") {
|
|
890
890
|
const clone = /* @__PURE__ */ new Set();
|
|
891
891
|
visited.set(obj, clone);
|
|
892
|
-
for (const member of setValues.call(obj)) {
|
|
892
|
+
for (const member of setValues$1.call(obj)) {
|
|
893
893
|
if (!isPrimitiveValue(member)) throw new TypeError("vo() Set members must be primitive values to preserve value equality");
|
|
894
894
|
clone.add(member);
|
|
895
895
|
}
|
|
@@ -1247,6 +1247,202 @@ function hasCooperativeBrand(value, brand) {
|
|
|
1247
1247
|
}
|
|
1248
1248
|
}
|
|
1249
1249
|
|
|
1250
|
+
//#endregion
|
|
1251
|
+
//#region src/internal/structural/detach-state.ts
|
|
1252
|
+
/**
|
|
1253
|
+
* Returns a copy of `state` that shares no object with the original.
|
|
1254
|
+
* Throws a `TypeError` that names the path when the graph carries a value
|
|
1255
|
+
* a structured clone would lose or silently degrade. A class instance, a
|
|
1256
|
+
* subclass of a built-in included, loses the methods on its prototype. A
|
|
1257
|
+
* symbol-keyed, non-enumerable, or accessor property and an expando on a
|
|
1258
|
+
* built-in are dropped. A function or a symbol value throws a raw
|
|
1259
|
+
* `DataCloneError`. An Error, a Promise, a WeakMap, or a WeakSet cannot be
|
|
1260
|
+
* detached at all. A SharedArrayBuffer and a view over one keep sharing
|
|
1261
|
+
* their memory. A Proxy fails in the walk or in the clone; a failure in
|
|
1262
|
+
* the clone is rethrown as a `TypeError` with the cause.
|
|
1263
|
+
*
|
|
1264
|
+
* The walk reads the graph through property descriptors and walks a Map
|
|
1265
|
+
* or a Set with the built-in iteration, so no accessor and no own iterator
|
|
1266
|
+
* of the state runs. A Proxy runs its traps during the walk. A trap can
|
|
1267
|
+
* change the graph, and an accessor that a trap adds runs during the
|
|
1268
|
+
* clone. A second walk checks the clone, so a value that a trap swapped in
|
|
1269
|
+
* and the content of a built-in whose prototype was replaced are rejected
|
|
1270
|
+
* too. The walks also count the data properties of records and arrays. A
|
|
1271
|
+
* copy with another count throws, because a built-in whose prototype was
|
|
1272
|
+
* replaced loses its own properties in the copy. `subject` names the state
|
|
1273
|
+
* in the messages.
|
|
1274
|
+
*
|
|
1275
|
+
* Plain objects (from any realm), arrays, Dates, Maps, Sets, bigints, and
|
|
1276
|
+
* typed arrays pass. A RegExp passes: pattern and flags survive the clone,
|
|
1277
|
+
* and `lastIndex` restores as 0. The scan state of a global or sticky
|
|
1278
|
+
* pattern is not domain data. A non-enumerable property on a built-in
|
|
1279
|
+
* passes: it is the built-in's own machinery (`lastIndex`), not data. A
|
|
1280
|
+
* non-enumerable symbol key passes anywhere: it is metadata by convention.
|
|
1281
|
+
*
|
|
1282
|
+
* The concrete entity uses it for a detached read DTO of a plain-data
|
|
1283
|
+
* state. The snapshot model uses it for the captured DTO and the restored
|
|
1284
|
+
* state. A state that carries a class-based child is mapped to plain data
|
|
1285
|
+
* first, in the entity or in the model.
|
|
1286
|
+
*/
|
|
1287
|
+
function detachState(state, subject = "detachState: state") {
|
|
1288
|
+
return detach(state, subject, "clone");
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* {@link detachState} that also rejects what `deepFreeze` cannot make
|
|
1292
|
+
* immutable: binary data (a typed array, a DataView, an ArrayBuffer, or a
|
|
1293
|
+
* SharedArrayBuffer) and a RegExp. The walk rejects such a value at its
|
|
1294
|
+
* tag, before it reads the indices of a typed array. Kit modules only.
|
|
1295
|
+
*/
|
|
1296
|
+
function detachFreezableState(state, subject) {
|
|
1297
|
+
return detach(state, subject, "freeze");
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Throws the `TypeError` of {@link detachFreezableState} and copies
|
|
1301
|
+
* nothing. A caller that has a clone already checks it with this, so the
|
|
1302
|
+
* rule is the same. Kit modules only.
|
|
1303
|
+
*/
|
|
1304
|
+
function assertFreezableState(state, subject) {
|
|
1305
|
+
assertDetachable(state, "", {
|
|
1306
|
+
seen: /* @__PURE__ */ new WeakSet(),
|
|
1307
|
+
subject,
|
|
1308
|
+
rule: "freeze",
|
|
1309
|
+
dataProperties: 0
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
function detach(state, subject, rule) {
|
|
1313
|
+
const original = {
|
|
1314
|
+
seen: /* @__PURE__ */ new WeakSet(),
|
|
1315
|
+
subject,
|
|
1316
|
+
rule,
|
|
1317
|
+
dataProperties: 0
|
|
1318
|
+
};
|
|
1319
|
+
assertDetachable(state, "", original);
|
|
1320
|
+
let copy;
|
|
1321
|
+
try {
|
|
1322
|
+
copy = structuredClone(state);
|
|
1323
|
+
} catch (cause) {
|
|
1324
|
+
throw new TypeError(`${subject} holds a Proxy or a host object that cannot be cloned; map it to plain data`, { cause });
|
|
1325
|
+
}
|
|
1326
|
+
const copied = {
|
|
1327
|
+
seen: /* @__PURE__ */ new WeakSet(),
|
|
1328
|
+
subject,
|
|
1329
|
+
rule,
|
|
1330
|
+
dataProperties: 0
|
|
1331
|
+
};
|
|
1332
|
+
assertDetachable(copy, "", copied);
|
|
1333
|
+
if (copied.dataProperties !== original.dataProperties) throw new TypeError(`${subject} changed in the copy: the state has ${original.dataProperties} data properties and the copy has ${copied.dataProperties}; a built-in whose prototype was replaced loses its own properties in the copy, and a Proxy trap can change the graph; map it to plain data`);
|
|
1334
|
+
return copy;
|
|
1335
|
+
}
|
|
1336
|
+
const INDEX_KEY = /^(0|[1-9]\d*)$/;
|
|
1337
|
+
const BINARY_DATA_TAGS = /* @__PURE__ */ new Set([
|
|
1338
|
+
"[object TypedArray]",
|
|
1339
|
+
"[object DataView]",
|
|
1340
|
+
"[object ArrayBuffer]",
|
|
1341
|
+
"[object SharedArrayBuffer]"
|
|
1342
|
+
]);
|
|
1343
|
+
const REGEXP_TAG = "[object RegExp]";
|
|
1344
|
+
const mapEntries = Map.prototype.entries;
|
|
1345
|
+
const setValues = Set.prototype.values;
|
|
1346
|
+
function assertDetachable(value, path, walk) {
|
|
1347
|
+
if (typeof value === "function") throw new TypeError(`${walk.subject}${path} is a function; map it to plain data`);
|
|
1348
|
+
if (typeof value === "symbol") throw new TypeError(`${walk.subject}${path} is a symbol; map it to plain data`);
|
|
1349
|
+
if (value === null || typeof value !== "object") return;
|
|
1350
|
+
const object = value;
|
|
1351
|
+
if (walk.seen.has(object)) return;
|
|
1352
|
+
walk.seen.add(object);
|
|
1353
|
+
if (Array.isArray(object)) {
|
|
1354
|
+
if (!hasIntrinsicPrototypeChain(object, "Array")) throwClassInstance(object, path, walk.subject);
|
|
1355
|
+
assertOwnPropertiesDetachable(object, path, walk, "array");
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const tag = builtInTagWithoutInvokingAccessors(object);
|
|
1359
|
+
if (tag !== void 0) {
|
|
1360
|
+
if (walk.rule === "freeze") assertFreezable(tag, path, walk.subject);
|
|
1361
|
+
if (!hasIntrinsicPrototypeChain(object)) throwClassInstance(object, path, walk.subject);
|
|
1362
|
+
if (tag === "[object Map]") {
|
|
1363
|
+
let index = 0;
|
|
1364
|
+
for (const [key, entry] of mapEntries.call(object)) {
|
|
1365
|
+
assertDetachable(key, `${path}<map key #${index}>`, walk);
|
|
1366
|
+
assertDetachable(entry, `${path}<map value #${index}>`, walk);
|
|
1367
|
+
index++;
|
|
1368
|
+
}
|
|
1369
|
+
} else if (tag === "[object Set]") {
|
|
1370
|
+
let index = 0;
|
|
1371
|
+
for (const member of setValues.call(object)) {
|
|
1372
|
+
assertDetachable(member, `${path}<set member #${index}>`, walk);
|
|
1373
|
+
index++;
|
|
1374
|
+
}
|
|
1375
|
+
} else if (tag === "[object Promise]" || tag === "[object WeakMap]" || tag === "[object WeakSet]") throw new TypeError(`${walk.subject}${path} is a ${tag.slice(8, -1)} and cannot be detached`);
|
|
1376
|
+
else if (tag === "[object Error]") throw new TypeError(`${walk.subject}${path} is an Error; map it to plain data`);
|
|
1377
|
+
else if (sharesMemory(object, tag)) throw new TypeError(`${walk.subject}${path} is backed by a SharedArrayBuffer and the copy would share its memory; map it to plain data`);
|
|
1378
|
+
assertOwnPropertiesDetachable(object, path, walk, "built-in");
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
const prototype = Object.getPrototypeOf(object);
|
|
1382
|
+
if (!(prototype === null || isIntrinsicConstructorPrototype(prototype, "Object") && Object.getPrototypeOf(prototype) === null)) throwClassInstance(object, path, walk.subject);
|
|
1383
|
+
assertOwnPropertiesDetachable(object, path, walk, "record");
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Audits the own properties the clone would copy or drop. The clone keeps
|
|
1387
|
+
* the enumerable own keys of a record and of an array, expandos included,
|
|
1388
|
+
* and drops every own key of another built-in. `length` on an array and
|
|
1389
|
+
* the non-enumerable keys of a built-in (`lastIndex`) are its own
|
|
1390
|
+
* machinery, not data. Index keys of a typed array or a boxed String are
|
|
1391
|
+
* its content and pass as such.
|
|
1392
|
+
*/
|
|
1393
|
+
function assertOwnPropertiesDetachable(object, path, walk, kind) {
|
|
1394
|
+
for (const key of Reflect.ownKeys(object)) {
|
|
1395
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
1396
|
+
if (descriptor === void 0) continue;
|
|
1397
|
+
if (typeof key === "symbol") {
|
|
1398
|
+
if (!descriptor.enumerable) continue;
|
|
1399
|
+
throw new TypeError(`${walk.subject}${path} has a symbol-keyed property; map it to plain data`);
|
|
1400
|
+
}
|
|
1401
|
+
if (kind === "array" && key === "length") continue;
|
|
1402
|
+
const isIndex = INDEX_KEY.test(key);
|
|
1403
|
+
if (!descriptor.enumerable) {
|
|
1404
|
+
if (kind === "built-in") continue;
|
|
1405
|
+
throw new TypeError(`${walk.subject}${path}.${key} is not enumerable and the clone would drop it; map it to plain data`);
|
|
1406
|
+
}
|
|
1407
|
+
if (kind === "built-in" && isIndex) continue;
|
|
1408
|
+
const memberPath = isIndex ? `${path}[${key}]` : `${path}.${key}`;
|
|
1409
|
+
if (!("value" in descriptor)) throw new TypeError(`${walk.subject}${memberPath} is an accessor property; map it to plain data`);
|
|
1410
|
+
if (kind === "built-in") throw new TypeError(`${walk.subject}${memberPath} is an expando on a ${constructorName(Object.getPrototypeOf(object)) ?? "built-in"} and the clone would drop it; map it to plain data`);
|
|
1411
|
+
walk.dataProperties += 1;
|
|
1412
|
+
assertDetachable(descriptor.value, memberPath, walk);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
const typedArrayBuffer = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), "buffer")?.get;
|
|
1416
|
+
const dataViewBuffer = Object.getOwnPropertyDescriptor(DataView.prototype, "buffer")?.get;
|
|
1417
|
+
function sharesMemory(object, tag) {
|
|
1418
|
+
if (tag === "[object SharedArrayBuffer]") return true;
|
|
1419
|
+
if (tag === "[object TypedArray]") return isSharedArrayBuffer(typedArrayBuffer.call(object));
|
|
1420
|
+
if (tag === "[object DataView]") return isSharedArrayBuffer(dataViewBuffer.call(object));
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
function isSharedArrayBuffer(buffer) {
|
|
1424
|
+
return builtInTagWithoutInvokingAccessors(buffer) === "[object SharedArrayBuffer]";
|
|
1425
|
+
}
|
|
1426
|
+
function assertFreezable(tag, path, subject) {
|
|
1427
|
+
if (BINARY_DATA_TAGS.has(tag)) throw new TypeError(`${subject}${path} is binary data; binary buffers (a typed array, a DataView, an ArrayBuffer, or a SharedArrayBuffer) stay mutable under freezing and JSON does not keep them; encode the data as a string, for example base64`);
|
|
1428
|
+
if (tag === REGEXP_TAG) throw new TypeError(`${subject}${path} is a RegExp; a freeze cannot make it immutable, because compile() changes the pattern of a frozen RegExp before it throws, and a frozen global or sticky pattern throws on a match; keep its source and flags as strings`);
|
|
1429
|
+
}
|
|
1430
|
+
function throwClassInstance(object, path, subject) {
|
|
1431
|
+
const name = constructorName(Object.getPrototypeOf(object)) ?? "anonymous class";
|
|
1432
|
+
throw new TypeError(`${subject}${path} is a class instance (${name}); map it to plain data`);
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* The name of the constructor of `prototype`, read through property
|
|
1436
|
+
* descriptors, so that no accessor runs.
|
|
1437
|
+
*/
|
|
1438
|
+
function constructorName(prototype) {
|
|
1439
|
+
if (prototype === null) return void 0;
|
|
1440
|
+
const owner = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
1441
|
+
if (typeof owner !== "function") return void 0;
|
|
1442
|
+
const name = Object.getOwnPropertyDescriptor(owner, "name")?.value;
|
|
1443
|
+
return typeof name === "string" && name !== "" ? name : void 0;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1250
1446
|
//#endregion
|
|
1251
1447
|
//#region src/domain/event/clock.ts
|
|
1252
1448
|
/** Immutable library default captured by the default domain-event factory. */
|
|
@@ -1313,28 +1509,15 @@ const defaultEventIdFactory = () => crypto.randomUUID();
|
|
|
1313
1509
|
* recordPendingEvents(order, domainEvents);
|
|
1314
1510
|
* ```
|
|
1315
1511
|
*/
|
|
1316
|
-
/**
|
|
1317
|
-
* Fills the origin of a factory into options that name none.
|
|
1318
|
-
*
|
|
1319
|
-
* A call site that states its own source keeps it: an explicit fact about one
|
|
1320
|
-
* event outranks the default of the factory that mints it.
|
|
1321
|
-
*/
|
|
1322
|
-
function withFactorySource(options, source) {
|
|
1323
|
-
const given = options ?? {};
|
|
1324
|
-
if (source === void 0 || given.metadata?.source !== void 0) return given;
|
|
1325
|
-
return {
|
|
1326
|
-
...given,
|
|
1327
|
-
metadata: {
|
|
1328
|
-
...given.metadata,
|
|
1329
|
-
source
|
|
1330
|
-
}
|
|
1331
|
-
};
|
|
1332
|
-
}
|
|
1333
1512
|
function createDomainEventFactory(options = {}) {
|
|
1513
|
+
return buildDomainEventFactory(options, "DomainEventFactory.create");
|
|
1514
|
+
}
|
|
1515
|
+
/** `createOperation` is the name that the errors of `create` give. */
|
|
1516
|
+
function buildDomainEventFactory(options, createOperation) {
|
|
1334
1517
|
const eventIdFactory = options.eventIdFactory ?? defaultEventIdFactory;
|
|
1335
1518
|
const clock = options.clock ?? defaultClockFactory;
|
|
1336
1519
|
const { source } = options;
|
|
1337
|
-
const create = ((type, payload, createOptions) => mintDomainEvent(type, payload,
|
|
1520
|
+
const create = ((type, payload, createOptions) => mintDomainEvent(type, payload, createOptions, eventIdFactory, clock, createOperation, source));
|
|
1338
1521
|
const createStamp = (stampOptions = {}) => {
|
|
1339
1522
|
const explicitOccurredAt = stampOptions.occurredAt === void 0 ? void 0 : copyValidEventDate(stampOptions.occurredAt);
|
|
1340
1523
|
if (stampOptions.eventId !== void 0) assertNonBlankEventField(stampOptions.eventId, "eventId", "EVENT_ID_INVALID");
|
|
@@ -1343,7 +1526,7 @@ function createDomainEventFactory(options = {}) {
|
|
|
1343
1526
|
const stamp = {
|
|
1344
1527
|
eventId,
|
|
1345
1528
|
occurredAt: explicitOccurredAt ?? readEventClock(clock),
|
|
1346
|
-
metadata:
|
|
1529
|
+
metadata: ownedEventMetadata(stampOptions.metadata, "DomainEventFactory.createStamp", source)
|
|
1347
1530
|
};
|
|
1348
1531
|
const owned = deepFreeze(stamp);
|
|
1349
1532
|
FACTORY_OWNED_EVENT_STAMPS.add(owned);
|
|
@@ -1360,7 +1543,7 @@ function createDomainEventFactory(options = {}) {
|
|
|
1360
1543
|
* {@link createDomainEvent}. It cannot be reconfigured; construct an instance
|
|
1361
1544
|
* with {@link createDomainEventFactory} for custom policy.
|
|
1362
1545
|
*/
|
|
1363
|
-
const defaultDomainEventFactory =
|
|
1546
|
+
const defaultDomainEventFactory = buildDomainEventFactory({}, "createDomainEvent");
|
|
1364
1547
|
/**
|
|
1365
1548
|
* Creates a domain event with default values.
|
|
1366
1549
|
* Sets occurredAt to current date and schemaVersion to 1 if not provided.
|
|
@@ -1371,7 +1554,10 @@ const defaultDomainEventFactory = createDomainEventFactory();
|
|
|
1371
1554
|
* event (same contract as `vo()`). The clone follows the plain-data event
|
|
1372
1555
|
* contract via `structuredClone`: functions, Promise, and WeakMap/WeakSet
|
|
1373
1556
|
* values throw a `TypeError`; symbol-keyed properties are not carried
|
|
1374
|
-
* over.
|
|
1557
|
+
* over. The clone is then checked with the rule of the in-memory event
|
|
1558
|
+
* store: an `Error`, a host object such as a `File`, a binary buffer, and
|
|
1559
|
+
* shared memory throw a `TypeError`. A class instance or an accessor becomes
|
|
1560
|
+
* plain data in the clone.
|
|
1375
1561
|
*
|
|
1376
1562
|
* **For aggregate-internal events, prefer `this.createEvent(...)` on
|
|
1377
1563
|
* `StateStoredAggregate` / `EventSourcedAggregate`.** That helper auto-injects
|
|
@@ -1435,12 +1621,20 @@ function isUncommittedDomainEvent(event) {
|
|
|
1435
1621
|
return UNCOMMITTED_EVENTS.has(event) || hasCooperativeBrand(event, UNCOMMITTED_BRAND);
|
|
1436
1622
|
}
|
|
1437
1623
|
function createUncommittedDomainEvent(type, payload, options) {
|
|
1624
|
+
return mintUncommittedDomainEvent(type, payload, options, "createUncommittedDomainEvent");
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* {@link createUncommittedDomainEvent} whose errors name `operation`, the
|
|
1628
|
+
* function that the caller called. Module-internal export; not part of the
|
|
1629
|
+
* package entries.
|
|
1630
|
+
*/
|
|
1631
|
+
function mintUncommittedDomainEvent(type, payload, options, operation) {
|
|
1438
1632
|
assertProducerOwnedEventFields(type, options);
|
|
1439
1633
|
const event = {
|
|
1440
1634
|
type,
|
|
1441
1635
|
aggregateId: options?.aggregateId,
|
|
1442
1636
|
aggregateType: options?.aggregateType,
|
|
1443
|
-
payload: cloneOwnedEventData(payload, "payload"),
|
|
1637
|
+
payload: cloneOwnedEventData(payload, "payload", operation),
|
|
1444
1638
|
schemaVersion: options?.schemaVersion ?? 1
|
|
1445
1639
|
};
|
|
1446
1640
|
stampUncommittedBrand(event);
|
|
@@ -1458,16 +1652,16 @@ function adoptUncommittedDomainEvent(copy) {
|
|
|
1458
1652
|
/**
|
|
1459
1653
|
* Attaches shell-owned recording data to an accepted aggregate decision.
|
|
1460
1654
|
*
|
|
1461
|
-
* The decision supplies the domain type, payload, source
|
|
1462
|
-
* schema version. The stamp supplies only event
|
|
1463
|
-
* trace metadata.
|
|
1655
|
+
* The decision supplies the domain type, payload, source aggregate
|
|
1656
|
+
* identity, and payload schema version. The stamp supplies only event
|
|
1657
|
+
* identity, recording time, and trace metadata.
|
|
1464
1658
|
*/
|
|
1465
1659
|
function recordDomainEvent(event, stamp) {
|
|
1466
1660
|
if (!isUncommittedDomainEvent(event)) throw new TypeError("recordDomainEvent requires an event created by createUncommittedDomainEvent");
|
|
1467
1661
|
if (isFactoryOwnedDomainEventStamp(stamp)) return mintRecordedEvent(event, stamp.eventId, stamp.occurredAt, stamp.metadata);
|
|
1468
1662
|
assertNonBlankEventField(stamp.eventId, "eventId", "EVENT_ID_INVALID");
|
|
1469
1663
|
const occurredAt = deepFreeze(copyValidEventDate(stamp.occurredAt));
|
|
1470
|
-
const metadata =
|
|
1664
|
+
const metadata = ownedEventMetadata(stamp.metadata, "recordDomainEvent");
|
|
1471
1665
|
return mintRecordedEvent(event, stamp.eventId, occurredAt, metadata === void 0 ? void 0 : deepFreeze(metadata));
|
|
1472
1666
|
}
|
|
1473
1667
|
/**
|
|
@@ -1496,7 +1690,8 @@ function mintRecordedEvent(event, eventId, occurredAt, metadata) {
|
|
|
1496
1690
|
}
|
|
1497
1691
|
/**
|
|
1498
1692
|
* Brands, freezes, and registers a kit-derived copy of a recorded event
|
|
1499
|
-
* (e.g. the
|
|
1693
|
+
* (e.g. the copy `apply()` stamps with the aggregate identity) as recorded
|
|
1694
|
+
* itself.
|
|
1500
1695
|
* The copy shares the already-frozen payload/metadata of its source,
|
|
1501
1696
|
* so the mint guarantee carries over. Stamping the cooperative brand
|
|
1502
1697
|
* before freezing keeps the copy recognizable by another loaded kit
|
|
@@ -1515,7 +1710,7 @@ function createDomainEvent(type, payload, options) {
|
|
|
1515
1710
|
function createDomainEventFromFacts(type, payload, options) {
|
|
1516
1711
|
if (options?.eventId === void 0) missingExplicitEventId();
|
|
1517
1712
|
if (options.occurredAt === void 0) missingExplicitOccurredAt();
|
|
1518
|
-
return mintDomainEvent(type, payload, options, missingExplicitEventId, missingExplicitOccurredAt);
|
|
1713
|
+
return mintDomainEvent(type, payload, options, missingExplicitEventId, missingExplicitOccurredAt, "createDomainEventFromFacts");
|
|
1519
1714
|
}
|
|
1520
1715
|
function missingExplicitEventId() {
|
|
1521
1716
|
throw new DomainEventValidationError("EVENT_ID_REQUIRED", "eventId", "createDomainEventFromFacts requires an explicit eventId");
|
|
@@ -1523,7 +1718,7 @@ function missingExplicitEventId() {
|
|
|
1523
1718
|
function missingExplicitOccurredAt() {
|
|
1524
1719
|
throw new DomainEventValidationError("EVENT_OCCURRED_AT_REQUIRED", "occurredAt", "createDomainEventFromFacts requires an explicit occurredAt");
|
|
1525
1720
|
}
|
|
1526
|
-
function mintDomainEvent(type, payload, options, eventIdFactory, clock) {
|
|
1721
|
+
function mintDomainEvent(type, payload, options, eventIdFactory, clock, operation, factorySource) {
|
|
1527
1722
|
assertProducerOwnedEventFields(type, options);
|
|
1528
1723
|
const eventId = options?.eventId ?? eventIdFactory();
|
|
1529
1724
|
assertNonBlankEventField(eventId, "eventId", "EVENT_ID_INVALID");
|
|
@@ -1534,10 +1729,10 @@ function mintDomainEvent(type, payload, options, eventIdFactory, clock) {
|
|
|
1534
1729
|
type,
|
|
1535
1730
|
aggregateId: options?.aggregateId,
|
|
1536
1731
|
aggregateType: options?.aggregateType,
|
|
1537
|
-
payload: cloneOwnedEventData(payload, "payload"),
|
|
1732
|
+
payload: cloneOwnedEventData(payload, "payload", operation),
|
|
1538
1733
|
occurredAt,
|
|
1539
1734
|
schemaVersion,
|
|
1540
|
-
metadata:
|
|
1735
|
+
metadata: ownedEventMetadata(options?.metadata, operation, factorySource)
|
|
1541
1736
|
};
|
|
1542
1737
|
stampRecordedBrand(event);
|
|
1543
1738
|
const minted = deepFreeze(event);
|
|
@@ -1548,8 +1743,8 @@ function assertProducerOwnedEventFields(type, options) {
|
|
|
1548
1743
|
assertNonBlankEventField(type, "type", "EVENT_TYPE_INVALID");
|
|
1549
1744
|
const schemaVersion = options?.schemaVersion ?? 1;
|
|
1550
1745
|
if (!Number.isSafeInteger(schemaVersion) || typeof schemaVersion !== "number" || schemaVersion < 1) throw new DomainEventValidationError("EVENT_SCHEMA_VERSION_INVALID", "schemaVersion", "domain-event schemaVersion must be a safe integer greater than or equal to 1");
|
|
1551
|
-
if (options?.aggregateId !== void 0) assertNonBlankEventField(options.aggregateId, "aggregateId", "
|
|
1552
|
-
if (options?.aggregateType !== void 0) assertNonBlankEventField(options.aggregateType, "aggregateType", "
|
|
1746
|
+
if (options?.aggregateId !== void 0) assertNonBlankEventField(options.aggregateId, "aggregateId", "EVENT_AGGREGATE_IDENTITY_INVALID");
|
|
1747
|
+
if (options?.aggregateType !== void 0) assertNonBlankEventField(options.aggregateType, "aggregateType", "EVENT_AGGREGATE_IDENTITY_INVALID");
|
|
1553
1748
|
}
|
|
1554
1749
|
function assertNonBlankEventField(value, field, code) {
|
|
1555
1750
|
if (typeof value !== "string" || value.trim().length === 0) throw new DomainEventValidationError(code, field, `domain-event ${field} must be a non-blank string`);
|
|
@@ -1562,59 +1757,60 @@ function readEventClock(clock) {
|
|
|
1562
1757
|
return copyValidEventDate(clock());
|
|
1563
1758
|
}
|
|
1564
1759
|
/**
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1571
|
-
* descriptive `TypeError` (they are not data); symbol-keyed properties
|
|
1572
|
-
* are not carried over; a class instance would silently lose its
|
|
1573
|
-
* prototype, which the plain-data contract already rules out.
|
|
1760
|
+
* Copies the caller's metadata and checks the copy. Metadata is a plain
|
|
1761
|
+
* record or absent; a null from a JSON envelope would mint and then fail
|
|
1762
|
+
* every metadata read far from the producer. A factory origin fills
|
|
1763
|
+
* `source` only after the check, and a call site that names its own source
|
|
1764
|
+
* keeps it: an explicit fact about one event outranks the default of the
|
|
1765
|
+
* factory that mints it.
|
|
1574
1766
|
*/
|
|
1575
|
-
function
|
|
1576
|
-
if (
|
|
1577
|
-
|
|
1578
|
-
if (
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
try {
|
|
1582
|
-
return structuredClone(value);
|
|
1583
|
-
} catch (cause) {
|
|
1584
|
-
throw new TypeError(`createDomainEvent: ${field} must be plain, structured-cloneable data (no functions, Promises, or WeakMap/WeakSet values): domain events are plain data`, { cause });
|
|
1767
|
+
function ownedEventMetadata(metadata, operation, factorySource) {
|
|
1768
|
+
if (metadata === void 0) return factorySource === void 0 ? void 0 : { source: factorySource };
|
|
1769
|
+
const owned = cloneOwnedEventData(metadata, "metadata", operation);
|
|
1770
|
+
if (!isPlainRecordCopy(owned)) {
|
|
1771
|
+
const received = typeof owned === "object" ? owned === null ? "null" : Object.prototype.toString.call(owned) : `a ${typeof owned}`;
|
|
1772
|
+
throw new TypeError(`${operation}: metadata must be an object or undefined, and a plain record; received ${received}`);
|
|
1585
1773
|
}
|
|
1774
|
+
const record = owned;
|
|
1775
|
+
if (factorySource === void 0 || record.source !== void 0) return record;
|
|
1776
|
+
return {
|
|
1777
|
+
...record,
|
|
1778
|
+
source: factorySource
|
|
1779
|
+
};
|
|
1586
1780
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1781
|
+
/**
|
|
1782
|
+
* True for a structured-clone copy of a plain record. A runtime such as
|
|
1783
|
+
* Vercel Edge returns the copy with the `Object.prototype` of another realm,
|
|
1784
|
+
* so the check compares the source text of the constructor, not the
|
|
1785
|
+
* prototype object.
|
|
1786
|
+
*/
|
|
1787
|
+
function isPlainRecordCopy(copy) {
|
|
1788
|
+
if (typeof copy !== "object" || copy === null) return false;
|
|
1789
|
+
const prototype = Object.getPrototypeOf(copy);
|
|
1790
|
+
return prototype !== null && isIntrinsicConstructorPrototype(prototype, "Object") && Object.getPrototypeOf(prototype) === null;
|
|
1589
1791
|
}
|
|
1590
1792
|
/**
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
1793
|
+
* Deep-clones caller-supplied event data (payload, metadata) before the
|
|
1794
|
+
* event is frozen, so a constructor never freezes or aliases the caller's
|
|
1795
|
+
* own object graph, and checks the clone with {@link assertFreezableState}.
|
|
1796
|
+
* A primitive needs no copy, but the check rejects a symbol. A class
|
|
1797
|
+
* instance or an accessor becomes plain data in the clone.
|
|
1596
1798
|
*/
|
|
1597
|
-
function
|
|
1598
|
-
if (value ===
|
|
1599
|
-
if (
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
if (value instanceof Map) {
|
|
1603
|
-
for (const [k, v] of value) {
|
|
1604
|
-
assertNoBinaryData(k, field, visited);
|
|
1605
|
-
assertNoBinaryData(v, field, visited);
|
|
1606
|
-
}
|
|
1607
|
-
return;
|
|
1799
|
+
function cloneOwnedEventData(value, field, operation) {
|
|
1800
|
+
if (typeof value === "function") throw new TypeError(`${operation}: ${field} must not be a function: domain events are plain data`);
|
|
1801
|
+
if (value === null || typeof value !== "object") {
|
|
1802
|
+
assertFreezableState(value, `${operation}: ${field}`);
|
|
1803
|
+
return value;
|
|
1608
1804
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
for (const v of value) assertNoBinaryData(v, field, visited);
|
|
1615
|
-
return;
|
|
1805
|
+
let clone;
|
|
1806
|
+
try {
|
|
1807
|
+
clone = structuredClone(value);
|
|
1808
|
+
} catch (cause) {
|
|
1809
|
+
throw new TypeError(`${operation}: ${field} cannot be copied, because structuredClone failed (the cause names the value); domain events are plain data`, { cause });
|
|
1616
1810
|
}
|
|
1617
|
-
|
|
1811
|
+
assertNoHostileOwnProtoKey(clone, `Event ${field} of ${operation}`);
|
|
1812
|
+
assertFreezableState(clone, `${operation}: ${field}`);
|
|
1813
|
+
return clone;
|
|
1618
1814
|
}
|
|
1619
1815
|
/**
|
|
1620
1816
|
* Copies metadata from a source event to a new event.
|
|
@@ -1687,6 +1883,29 @@ function mergeMetadata(...metadataObjects) {
|
|
|
1687
1883
|
function abortReason(signal, fallbackMessage) {
|
|
1688
1884
|
return signal.reason ?? new Error(fallbackMessage);
|
|
1689
1885
|
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Waits for `promise`, but REJECTS with the signal's reason as soon as the
|
|
1888
|
+
* signal fires. The promise keeps running; only the wait ends. The abort
|
|
1889
|
+
* listener goes away when the promise settles.
|
|
1890
|
+
*/
|
|
1891
|
+
function waitRejectingOnAbort(promise, signal, abortMessage) {
|
|
1892
|
+
if (signal === void 0) return promise;
|
|
1893
|
+
return new Promise((resolve, reject) => {
|
|
1894
|
+
if (signal.aborted) {
|
|
1895
|
+
reject(abortReason(signal, abortMessage));
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
const onAbort = () => reject(abortReason(signal, abortMessage));
|
|
1899
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1900
|
+
promise.then((value) => {
|
|
1901
|
+
signal.removeEventListener("abort", onAbort);
|
|
1902
|
+
resolve(value);
|
|
1903
|
+
}, (error) => {
|
|
1904
|
+
signal.removeEventListener("abort", onAbort);
|
|
1905
|
+
reject(error);
|
|
1906
|
+
});
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1690
1909
|
|
|
1691
1910
|
//#endregion
|
|
1692
1911
|
//#region src/internal/validate.ts
|
|
@@ -1699,6 +1918,15 @@ function abortReason(signal, fallbackMessage) {
|
|
|
1699
1918
|
function assertNonNegativeFinite(context, field, value) {
|
|
1700
1919
|
if (!Number.isFinite(value) || value < 0) throw new Error(`${context}: ${field} must be a non-negative finite number, got ${value}`);
|
|
1701
1920
|
}
|
|
1921
|
+
/**
|
|
1922
|
+
* The largest delay that `setTimeout` honors. A larger delay does not wait
|
|
1923
|
+
* longer: the runtime fires the timer after about 1 ms.
|
|
1924
|
+
*/
|
|
1925
|
+
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
1926
|
+
/** Guard for time options that a timer waits for, in milliseconds. */
|
|
1927
|
+
function assertTimerDelay(context, field, value) {
|
|
1928
|
+
if (!Number.isFinite(value) || value < 0 || value > 2147483647) throw new RangeError(`${context}: ${field} must be a finite number of milliseconds from 0 to ${MAX_TIMER_DELAY_MS}, got ${value}`);
|
|
1929
|
+
}
|
|
1702
1930
|
/** Guard for count options that must be a whole number of at least 1. */
|
|
1703
1931
|
function assertPositiveInteger(context, field, value) {
|
|
1704
1932
|
if (!Number.isInteger(value) || value < 1) throw new Error(`${context}: ${field} must be an integer >= 1, got ${value}`);
|
|
@@ -1707,6 +1935,10 @@ function assertPositiveInteger(context, field, value) {
|
|
|
1707
1935
|
function assertPositiveSafeInteger(context, field, value) {
|
|
1708
1936
|
if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${context}: ${field} must be a positive safe integer, got ${value}`);
|
|
1709
1937
|
}
|
|
1938
|
+
/** Guard for stream positions that must fit exact JS integers. */
|
|
1939
|
+
function assertNonNegativeSafeInteger(context, field, value) {
|
|
1940
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${context}: ${field} must be a non-negative safe integer, got ${value}`);
|
|
1941
|
+
}
|
|
1710
1942
|
|
|
1711
1943
|
//#endregion
|
|
1712
1944
|
//#region src/internal/async/execution.ts
|
|
@@ -1741,11 +1973,12 @@ function ownerSignalOf(signal) {
|
|
|
1741
1973
|
* enforce a native timeout no later than `context.deadlineAt`.
|
|
1742
1974
|
*/
|
|
1743
1975
|
function runBoundedExecution(label, options, operation) {
|
|
1744
|
-
if (options.deadlineAt === void 0)
|
|
1976
|
+
if (options.deadlineAt === void 0) assertTimerDelay(label, "timeoutMs", options.timeoutMs);
|
|
1745
1977
|
else assertNonNegativeFinite(label, "deadlineAt", options.deadlineAt);
|
|
1746
1978
|
const startedAt = Date.now();
|
|
1747
1979
|
const deadlineAt = options.deadlineAt ?? startedAt + options.timeoutMs;
|
|
1748
1980
|
const timeoutMs = Math.max(0, deadlineAt - startedAt);
|
|
1981
|
+
if (options.deadlineAt !== void 0) assertTimerDelay(label, "the time until deadlineAt", timeoutMs);
|
|
1749
1982
|
const timeoutError = () => new DOMException(`${label} timed out after ${timeoutMs}ms`, "TimeoutError");
|
|
1750
1983
|
const controller = new AbortController();
|
|
1751
1984
|
const context = Object.freeze({
|
|
@@ -1811,5 +2044,211 @@ function isDispatchTrackingOutbox(outbox) {
|
|
|
1811
2044
|
}
|
|
1812
2045
|
|
|
1813
2046
|
//#endregion
|
|
1814
|
-
|
|
1815
|
-
|
|
2047
|
+
//#region src/persistence/event-store/event-stream-page-guards.ts
|
|
2048
|
+
function assertValidHead(stream, lastVersion, fromVersion, targetVersion) {
|
|
2049
|
+
if (Number.isSafeInteger(lastVersion) && lastVersion >= 1) return;
|
|
2050
|
+
throw new InvalidEventStreamPageError({
|
|
2051
|
+
identity: stream,
|
|
2052
|
+
reason: "invalid_head",
|
|
2053
|
+
fromVersion,
|
|
2054
|
+
targetVersion,
|
|
2055
|
+
lastVersion
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
function assertHeadNotBehindFirstPage(stream, lastVersion, firstPageLastVersion, fromVersion, targetVersion) {
|
|
2059
|
+
if (lastVersion >= firstPageLastVersion) return;
|
|
2060
|
+
throw new InvalidEventStreamPageError({
|
|
2061
|
+
identity: stream,
|
|
2062
|
+
reason: "head_regressed",
|
|
2063
|
+
fromVersion,
|
|
2064
|
+
targetVersion,
|
|
2065
|
+
lastVersion,
|
|
2066
|
+
firstPageLastVersion
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
function assertPageNotEmpty(stream, eventCount, fromVersion, targetVersion) {
|
|
2070
|
+
if (eventCount > 0) return;
|
|
2071
|
+
throw new InvalidEventStreamPageError({
|
|
2072
|
+
identity: stream,
|
|
2073
|
+
reason: "empty_page",
|
|
2074
|
+
fromVersion,
|
|
2075
|
+
targetVersion
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
function assertPageWithinWindow(stream, eventCount, fromVersion, targetVersion) {
|
|
2079
|
+
if (eventCount <= targetVersion - fromVersion) return;
|
|
2080
|
+
throw new InvalidEventStreamPageError({
|
|
2081
|
+
identity: stream,
|
|
2082
|
+
reason: "page_past_target",
|
|
2083
|
+
fromVersion,
|
|
2084
|
+
targetVersion,
|
|
2085
|
+
eventCount
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
function assertPageWithinLimit(stream, eventCount, limit, fromVersion, targetVersion) {
|
|
2089
|
+
if (eventCount <= limit) return;
|
|
2090
|
+
throw new InvalidEventStreamPageError({
|
|
2091
|
+
identity: stream,
|
|
2092
|
+
reason: "page_over_limit",
|
|
2093
|
+
fromVersion,
|
|
2094
|
+
targetVersion,
|
|
2095
|
+
eventCount,
|
|
2096
|
+
limit
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
//#endregion
|
|
2101
|
+
//#region src/persistence/event-store/read-stream-pages.ts
|
|
2102
|
+
/**
|
|
2103
|
+
* Pins the target version of a replay window on an existing stream, or
|
|
2104
|
+
* reports the window as unreachable.
|
|
2105
|
+
*
|
|
2106
|
+
* The target version is `toVersion` when given, else `lastVersion`. The
|
|
2107
|
+
* window is unreachable when `fromVersion` lies beyond the target version,
|
|
2108
|
+
* or the target version lies beyond `lastVersion`. The call never clamps
|
|
2109
|
+
* such a window to the head, so a request for version 10 of a stream that
|
|
2110
|
+
* ends at 7 never loads the latest state by accident.
|
|
2111
|
+
*
|
|
2112
|
+
* This is the decision `readStreamPages` makes on its first page. An
|
|
2113
|
+
* adapter that pages on its own makes it with this call, then hands its
|
|
2114
|
+
* pages to `reconstituteAggregateFromStreamPages`. Values that are not safe
|
|
2115
|
+
* integers, a negative `fromVersion`, and a `toVersion` or `lastVersion`
|
|
2116
|
+
* below 1 reject with `RangeError`: an existing stream holds at least one
|
|
2117
|
+
* event, and no replay can end at version 0.
|
|
2118
|
+
*/
|
|
2119
|
+
function pinTargetVersion(options) {
|
|
2120
|
+
assertNonNegativeSafeInteger("pinTargetVersion", "fromVersion", options.fromVersion);
|
|
2121
|
+
if (options.toVersion !== void 0) assertPositiveSafeInteger("pinTargetVersion", "toVersion", options.toVersion);
|
|
2122
|
+
assertPositiveSafeInteger("pinTargetVersion", "lastVersion", options.lastVersion);
|
|
2123
|
+
return decideTargetVersion(options);
|
|
2124
|
+
}
|
|
2125
|
+
function decideTargetVersion(window) {
|
|
2126
|
+
const targetVersion = window.toVersion ?? window.lastVersion;
|
|
2127
|
+
if (window.fromVersion > targetVersion || targetVersion > window.lastVersion) return { reachable: false };
|
|
2128
|
+
return {
|
|
2129
|
+
reachable: true,
|
|
2130
|
+
targetVersion
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Reads one stream in bounded pages up to a pinned target version.
|
|
2135
|
+
*
|
|
2136
|
+
* The call reads the first page. That page decides existence and reports
|
|
2137
|
+
* the head. The call then makes the window decision of
|
|
2138
|
+
* {@link pinTargetVersion}: the target version is `toVersion` when given,
|
|
2139
|
+
* else the head, and a window outside the stream is unreachable.
|
|
2140
|
+
*
|
|
2141
|
+
* The first iteration of `pages` yields the first page that the call read
|
|
2142
|
+
* and then reads the continuation pages, one page at a time. Every later
|
|
2143
|
+
* iteration reads all pages from the store again, so no two iterations
|
|
2144
|
+
* share an event object. Each of these reads passes the target version as
|
|
2145
|
+
* `toVersion` and starts after the events that the earlier pages
|
|
2146
|
+
* returned. Streams are append-only, so an iteration yields one stable
|
|
2147
|
+
* prefix even when another writer appends during the replay.
|
|
2148
|
+
*
|
|
2149
|
+
* The call checks every page against the `readStream` contract and throws
|
|
2150
|
+
* {@link InvalidEventStreamPageError} for a page that breaks it: a head
|
|
2151
|
+
* that is not a safe integer of at least 1, an empty page while events
|
|
2152
|
+
* remain in the window, a page with more events than its window has left
|
|
2153
|
+
* or than `limit`, and a continuation page that reports the stream absent
|
|
2154
|
+
* or a head below the head of the first page.
|
|
2155
|
+
*
|
|
2156
|
+
* Invalid options reject with `RangeError` before any page is read:
|
|
2157
|
+
* `limit` and `toVersion` must be positive safe integers, `fromVersion` a
|
|
2158
|
+
* non-negative one. The call passes `signal` to every page read, and it
|
|
2159
|
+
* checks the signal before each page read as well. An aborted signal
|
|
2160
|
+
* rejects with its `reason`.
|
|
2161
|
+
*/
|
|
2162
|
+
async function readStreamPages(reader, stream, options) {
|
|
2163
|
+
assertPositiveSafeInteger("readStreamPages", "limit", options.limit);
|
|
2164
|
+
const fromVersion = options.fromVersion ?? 0;
|
|
2165
|
+
assertNonNegativeSafeInteger("readStreamPages", "fromVersion", fromVersion);
|
|
2166
|
+
if (options.toVersion !== void 0) assertPositiveSafeInteger("readStreamPages", "toVersion", options.toVersion);
|
|
2167
|
+
throwIfAborted(options.signal);
|
|
2168
|
+
const identity = {
|
|
2169
|
+
aggregateType: stream.aggregateType,
|
|
2170
|
+
aggregateId: stream.aggregateId
|
|
2171
|
+
};
|
|
2172
|
+
const first = await reader.readStream(identity, {
|
|
2173
|
+
fromVersion,
|
|
2174
|
+
limit: options.limit,
|
|
2175
|
+
...options.toVersion === void 0 ? {} : { toVersion: options.toVersion },
|
|
2176
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
2177
|
+
});
|
|
2178
|
+
if (!first.exists) return {
|
|
2179
|
+
exists: false,
|
|
2180
|
+
reachable: false
|
|
2181
|
+
};
|
|
2182
|
+
assertValidHead(identity, first.lastVersion, fromVersion);
|
|
2183
|
+
const pinned = decideTargetVersion({
|
|
2184
|
+
fromVersion,
|
|
2185
|
+
toVersion: options.toVersion,
|
|
2186
|
+
lastVersion: first.lastVersion
|
|
2187
|
+
});
|
|
2188
|
+
if (!pinned.reachable) return {
|
|
2189
|
+
exists: true,
|
|
2190
|
+
reachable: false,
|
|
2191
|
+
fromVersion,
|
|
2192
|
+
lastVersion: first.lastVersion
|
|
2193
|
+
};
|
|
2194
|
+
if (fromVersion < pinned.targetVersion) assertPageNotEmpty(identity, first.events.length, fromVersion, pinned.targetVersion);
|
|
2195
|
+
assertPageWithinWindow(identity, first.events.length, fromVersion, pinned.targetVersion);
|
|
2196
|
+
assertPageWithinLimit(identity, first.events.length, options.limit, fromVersion, pinned.targetVersion);
|
|
2197
|
+
const window = {
|
|
2198
|
+
stream: identity,
|
|
2199
|
+
fromVersion,
|
|
2200
|
+
firstPageLastVersion: first.lastVersion,
|
|
2201
|
+
targetVersion: pinned.targetVersion,
|
|
2202
|
+
limit: options.limit,
|
|
2203
|
+
signal: options.signal
|
|
2204
|
+
};
|
|
2205
|
+
let unreadFirstPage = first.events;
|
|
2206
|
+
return {
|
|
2207
|
+
exists: true,
|
|
2208
|
+
reachable: true,
|
|
2209
|
+
stream: identity,
|
|
2210
|
+
fromVersion,
|
|
2211
|
+
targetVersion: pinned.targetVersion,
|
|
2212
|
+
pages: { [Symbol.asyncIterator]: () => {
|
|
2213
|
+
const firstPage = unreadFirstPage;
|
|
2214
|
+
unreadFirstPage = void 0;
|
|
2215
|
+
return readPinnedPages(reader, window, firstPage);
|
|
2216
|
+
} }
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
async function* readPinnedPages(reader, window, firstPage) {
|
|
2220
|
+
let cursor = window.fromVersion;
|
|
2221
|
+
if (firstPage !== void 0) {
|
|
2222
|
+
if (firstPage.length > 0) yield firstPage;
|
|
2223
|
+
cursor += firstPage.length;
|
|
2224
|
+
}
|
|
2225
|
+
while (cursor < window.targetVersion) {
|
|
2226
|
+
throwIfAborted(window.signal);
|
|
2227
|
+
const page = await reader.readStream(window.stream, {
|
|
2228
|
+
fromVersion: cursor,
|
|
2229
|
+
toVersion: window.targetVersion,
|
|
2230
|
+
limit: window.limit,
|
|
2231
|
+
...window.signal === void 0 ? {} : { signal: window.signal }
|
|
2232
|
+
});
|
|
2233
|
+
if (!page.exists) throw new InvalidEventStreamPageError({
|
|
2234
|
+
identity: window.stream,
|
|
2235
|
+
reason: "stream_vanished",
|
|
2236
|
+
fromVersion: cursor,
|
|
2237
|
+
targetVersion: window.targetVersion
|
|
2238
|
+
});
|
|
2239
|
+
assertValidHead(window.stream, page.lastVersion, cursor, window.targetVersion);
|
|
2240
|
+
assertHeadNotBehindFirstPage(window.stream, page.lastVersion, window.firstPageLastVersion, cursor, window.targetVersion);
|
|
2241
|
+
assertPageNotEmpty(window.stream, page.events.length, cursor, window.targetVersion);
|
|
2242
|
+
assertPageWithinWindow(window.stream, page.events.length, cursor, window.targetVersion);
|
|
2243
|
+
assertPageWithinLimit(window.stream, page.events.length, window.limit, cursor, window.targetVersion);
|
|
2244
|
+
yield page.events;
|
|
2245
|
+
cursor += page.events.length;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
function throwIfAborted(signal) {
|
|
2249
|
+
if (signal?.aborted) throw abortReason(signal, "readStreamPages aborted");
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
//#endregion
|
|
2253
|
+
export { SnapshotTimeValidationError as A, voWithValidation as B, defaultDomainEventFactory as C, mintUncommittedDomainEvent as D, mergeMetadata as E, ValueObject as F, isBuiltInObject as G, deepOmit as H, deepFreeze as I, isIntrinsicConstructorPrototype as K, vo as L, detachState as M, hasCooperativeBrand as N, recordDomainEvent as O, stampCooperativeBrand as P, voEquals as R, createUncommittedDomainEvent as S, isUncommittedDomainEvent as T, deepEqual as U, deepEqualExcept as V, findPropertyDescriptor as W, adoptUncommittedDomainEvent as _, isDispatchTrackingOutbox as a, createDomainEventFactory as b, runBoundedExecution as c, assertPositiveInteger as d, assertPositiveSafeInteger as f, adoptRecordedDomainEvent as g, waitRejectingOnAbort as h, assertPageWithinWindow as i, detachFreezableState as j, DomainEventValidationError as k, MAX_TIMER_DELAY_MS as l, abortReason as m, readStreamPages as n, DEFAULT_EXECUTION_TIMEOUT_MS as o, assertTimerDelay as p, isWeakMap as q, assertPageNotEmpty as r, ownerSignalOf as s, pinTargetVersion as t, assertNonNegativeSafeInteger as u, copyMetadata as v, isRecordedDomainEvent as w, createDomainEventFromFacts as x, createDomainEvent as y, voEqualsExcept as z };
|
|
2254
|
+
//# sourceMappingURL=read-stream-pages.js.map
|