@spine-event-engine/server 2.0.0-snapshot.14 → 2.0.0-snapshot.16
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 +53 -0
- package/REFERENCE.md +146 -23
- package/dist/context/bounded-context.d.ts +131 -20
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +647 -48
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/entity/entity-metadata.d.ts +12 -3
- package/dist/entity/entity-metadata.d.ts.map +1 -1
- package/dist/entity/entity-metadata.js +37 -1
- package/dist/entity/entity-metadata.js.map +1 -1
- package/dist/entity/entity-transaction.d.ts +46 -42
- package/dist/entity/entity-transaction.d.ts.map +1 -1
- package/dist/entity/entity-transaction.js +130 -35
- package/dist/entity/entity-transaction.js.map +1 -1
- package/dist/entity/entity.d.ts +138 -100
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +217 -227
- package/dist/entity/entity.js.map +1 -1
- package/dist/handler/event-registration-readiness.d.ts +13 -56
- package/dist/handler/event-registration-readiness.d.ts.map +1 -1
- package/dist/handler/event-registration-readiness.js +23 -43
- package/dist/handler/event-registration-readiness.js.map +1 -1
- package/dist/handler/generated-handler-registry.d.ts +8 -0
- package/dist/handler/generated-handler-registry.d.ts.map +1 -1
- package/dist/handler/generated-handler-registry.js +165 -3
- package/dist/handler/generated-handler-registry.js.map +1 -1
- package/dist/handler/handler-decorators.d.ts +70 -33
- package/dist/handler/handler-decorators.d.ts.map +1 -1
- package/dist/handler/handler-decorators.js +88 -31
- package/dist/handler/handler-decorators.js.map +1 -1
- package/dist/handler/handler-metadata.d.ts +53 -51
- package/dist/handler/handler-metadata.d.ts.map +1 -1
- package/dist/handler/handler-metadata.js +214 -42
- package/dist/handler/handler-metadata.js.map +1 -1
- package/dist/handler/registration-readiness-metadata.d.ts +4 -0
- package/dist/handler/registration-readiness-metadata.d.ts.map +1 -1
- package/dist/handler/registration-readiness-metadata.js +82 -1
- package/dist/handler/registration-readiness-metadata.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/repository/repository.d.ts +217 -70
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +2728 -354
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/standalone-handler-runtime.d.ts +16 -0
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -1
- package/dist/runtime/standalone-handler-runtime.js +93 -18
- package/dist/runtime/standalone-handler-runtime.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
|
@@ -116,7 +116,7 @@ export class BoundedContext {
|
|
|
116
116
|
constructBoundedContext = (snapshot, commandBus, eventBus, systemEventBus, publisher, stand, systemStand, runtime, systemSpec, storageFactory, repositories, deliveryStrategy, token) => new BoundedContext(snapshot, commandBus, eventBus, systemEventBus, publisher, stand, systemStand, runtime, systemSpec, storageFactory, repositories, deliveryStrategy, token);
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
|
-
* Creates a framework
|
|
119
|
+
* Creates a Bounded Context from its prepared framework services.
|
|
120
120
|
*
|
|
121
121
|
* @param snapshot Contains the immutable context metadata.
|
|
122
122
|
* @param commandBus Dispatches commands accepted by this context.
|
|
@@ -125,7 +125,7 @@ export class BoundedContext {
|
|
|
125
125
|
* @param publisher Publishes signals produced by context handlers.
|
|
126
126
|
* @param stand Stores read-side state for this context.
|
|
127
127
|
* @param systemStand Stores read-side state for the paired System Context.
|
|
128
|
-
* @param subscriptionRuntime Coordinates
|
|
128
|
+
* @param subscriptionRuntime Coordinates subscriptions for the paired contexts.
|
|
129
129
|
* @param systemSpec Contains paired System Context metadata.
|
|
130
130
|
* @param storageFactory Creates context storage.
|
|
131
131
|
* @param repositories Lists repositories to register.
|
|
@@ -303,7 +303,7 @@ export class BoundedContext {
|
|
|
303
303
|
return ContextParts.cloneContextSnapshot(this.#snapshot);
|
|
304
304
|
}
|
|
305
305
|
/**
|
|
306
|
-
* Returns
|
|
306
|
+
* Returns this context's command endpoint.
|
|
307
307
|
*
|
|
308
308
|
* @returns Returns the context command endpoint.
|
|
309
309
|
*/
|
|
@@ -311,7 +311,7 @@ export class BoundedContext {
|
|
|
311
311
|
return this.#commandEndpoint;
|
|
312
312
|
}
|
|
313
313
|
/**
|
|
314
|
-
* Returns
|
|
314
|
+
* Returns this context's event endpoint.
|
|
315
315
|
*
|
|
316
316
|
* @returns Returns the context event endpoint.
|
|
317
317
|
*/
|
|
@@ -319,7 +319,7 @@ export class BoundedContext {
|
|
|
319
319
|
return this.#eventEndpoint;
|
|
320
320
|
}
|
|
321
321
|
/**
|
|
322
|
-
* Returns the context
|
|
322
|
+
* Returns the Stand that stores this context's read-side state.
|
|
323
323
|
*
|
|
324
324
|
* @returns Returns the read-side state store.
|
|
325
325
|
*/
|
|
@@ -391,14 +391,14 @@ export class BoundedContext {
|
|
|
391
391
|
});
|
|
392
392
|
}
|
|
393
393
|
/**
|
|
394
|
-
* Closes context
|
|
394
|
+
* Closes this context's buses, Stand, and repository storage and runtime bindings.
|
|
395
395
|
*
|
|
396
396
|
* Close is idempotent and returns the same close outcome on repeated calls.
|
|
397
|
-
* The context attempts every
|
|
397
|
+
* The context attempts every close hook; when any hook fails, the
|
|
398
398
|
* returned promise rejects with an `AggregateError` after the remaining hooks
|
|
399
399
|
* have also been attempted.
|
|
400
400
|
*
|
|
401
|
-
* @returns A promise that settles after all
|
|
401
|
+
* @returns A promise that settles after all resources close.
|
|
402
402
|
*/
|
|
403
403
|
close() {
|
|
404
404
|
closingContexts.add(this);
|
|
@@ -570,7 +570,7 @@ export class BoundedContextBuilder {
|
|
|
570
570
|
constructBoundedContextBuilder = (snapshot, token) => new BoundedContextBuilder(snapshot, token);
|
|
571
571
|
}
|
|
572
572
|
/**
|
|
573
|
-
* Creates a
|
|
573
|
+
* Creates a builder from a prepared context specification.
|
|
574
574
|
*
|
|
575
575
|
* @param specSnapshot Contains the initial context specification.
|
|
576
576
|
* @param token Proves framework-controlled construction.
|
|
@@ -616,6 +616,7 @@ export class BoundedContextBuilder {
|
|
|
616
616
|
/**
|
|
617
617
|
* Adds an explicitly assembled repository or an Entity class.
|
|
618
618
|
*
|
|
619
|
+
* @typeParam EntityType Concrete Entity class represented by this entry.
|
|
619
620
|
* @param entry The repository or Entity class to register.
|
|
620
621
|
* @param options Optional settings used only when an Entity class is supplied.
|
|
621
622
|
* @returns This builder for further configuration.
|
|
@@ -638,6 +639,7 @@ export class BoundedContextBuilder {
|
|
|
638
639
|
/**
|
|
639
640
|
* Removes a repository from the context registration list.
|
|
640
641
|
*
|
|
642
|
+
* @typeParam EntityType Concrete Entity class represented by the repository.
|
|
641
643
|
* @param repository Identifies the repository to remove.
|
|
642
644
|
* @returns Returns this builder for further configuration.
|
|
643
645
|
*/
|
|
@@ -748,7 +750,7 @@ export class BoundedContextBuilder {
|
|
|
748
750
|
return this;
|
|
749
751
|
}
|
|
750
752
|
/**
|
|
751
|
-
* Sets
|
|
753
|
+
* Sets how Entity Inbox targets are assigned to shards.
|
|
752
754
|
*
|
|
753
755
|
* @param strategy Selects the durable shard for Aggregate and Process Manager targets.
|
|
754
756
|
* @returns Returns this builder for further configuration.
|
|
@@ -972,6 +974,12 @@ export class BoundedContextBuilder {
|
|
|
972
974
|
#standaloneInstances() {
|
|
973
975
|
return Object.freeze([...this.#assignees, ...this.#commanders, ...this.#eventReceivers]);
|
|
974
976
|
}
|
|
977
|
+
/**
|
|
978
|
+
* Creates the domain EventStore using this context's storage mode.
|
|
979
|
+
*
|
|
980
|
+
* @param storageFactory Storage provider for event records.
|
|
981
|
+
* @returns Domain EventStore.
|
|
982
|
+
*/
|
|
975
983
|
createEventStore(storageFactory) {
|
|
976
984
|
return new EventStore(ContextParts.createStorageMode(this.#specSnapshot), storageFactory);
|
|
977
985
|
}
|
|
@@ -988,7 +996,7 @@ export class ContextSpec {
|
|
|
988
996
|
constructContextSpec = (snapshot, token) => new ContextSpec(snapshot, token);
|
|
989
997
|
}
|
|
990
998
|
/**
|
|
991
|
-
* Creates a
|
|
999
|
+
* Creates a context specification from prepared values.
|
|
992
1000
|
*
|
|
993
1001
|
* @param snapshot Contains immutable specification values.
|
|
994
1002
|
* @param token Proves framework-controlled construction.
|
|
@@ -1040,10 +1048,19 @@ export class ContextSpec {
|
|
|
1040
1048
|
}
|
|
1041
1049
|
}
|
|
1042
1050
|
const registeredRepositories = new WeakMap();
|
|
1051
|
+
/**
|
|
1052
|
+
* Reports failure while redispatching a stored event during read-side replay.
|
|
1053
|
+
*/
|
|
1043
1054
|
class CatchUpReplayError extends Error {
|
|
1044
1055
|
code = "READ_SIDE_CATCH_UP_REPLAY_FAILED";
|
|
1045
1056
|
eventId;
|
|
1046
1057
|
detail;
|
|
1058
|
+
/**
|
|
1059
|
+
* Creates a replay error tied to the stored event that failed.
|
|
1060
|
+
*
|
|
1061
|
+
* @param eventId Identifies the stored event.
|
|
1062
|
+
* @param detail Contains the bounded cause name and message.
|
|
1063
|
+
*/
|
|
1047
1064
|
constructor(eventId, detail) {
|
|
1048
1065
|
super(`Read-side catch-up failed for stored event "${eventId}".`);
|
|
1049
1066
|
this.name = "ReadCatchUpReplayError";
|
|
@@ -1056,6 +1073,11 @@ class CatchUpReplayError extends Error {
|
|
|
1056
1073
|
* Assembles private bounded-context lifecycle and replay details.
|
|
1057
1074
|
*/
|
|
1058
1075
|
const ContextParts = Object.freeze({
|
|
1076
|
+
/**
|
|
1077
|
+
* Rejects duplicate command receptors in generated standalone groups.
|
|
1078
|
+
*
|
|
1079
|
+
* @param receivers Generated groups to validate.
|
|
1080
|
+
*/
|
|
1059
1081
|
assertUniqueCommandReceptors(receivers) {
|
|
1060
1082
|
const receptorByType = new Map();
|
|
1061
1083
|
for (const receiver of receivers) {
|
|
@@ -1070,6 +1092,14 @@ const ContextParts = Object.freeze({
|
|
|
1070
1092
|
}
|
|
1071
1093
|
}
|
|
1072
1094
|
},
|
|
1095
|
+
/**
|
|
1096
|
+
* Matches generated standalone groups to their registered instances.
|
|
1097
|
+
*
|
|
1098
|
+
* @param generated Generated handler groups.
|
|
1099
|
+
* @param instances Registered standalone instances.
|
|
1100
|
+
* @param publisher Publisher used by bindings.
|
|
1101
|
+
* @returns Frozen bindings in generated order.
|
|
1102
|
+
*/
|
|
1073
1103
|
matchStandaloneHandlers(generated, instances, publisher) {
|
|
1074
1104
|
const byConstructor = new Map();
|
|
1075
1105
|
for (const instance of instances) {
|
|
@@ -1094,6 +1124,12 @@ const ContextParts = Object.freeze({
|
|
|
1094
1124
|
}
|
|
1095
1125
|
return Object.freeze(bindings);
|
|
1096
1126
|
},
|
|
1127
|
+
/**
|
|
1128
|
+
* Calls cleanup and records any thrown error.
|
|
1129
|
+
*
|
|
1130
|
+
* @param onCleanup Cleanup action.
|
|
1131
|
+
* @param errors Collection receiving failures.
|
|
1132
|
+
*/
|
|
1097
1133
|
attemptCleanup(onCleanup, errors) {
|
|
1098
1134
|
try {
|
|
1099
1135
|
onCleanup();
|
|
@@ -1102,6 +1138,12 @@ const ContextParts = Object.freeze({
|
|
|
1102
1138
|
errors.push(error);
|
|
1103
1139
|
}
|
|
1104
1140
|
},
|
|
1141
|
+
/**
|
|
1142
|
+
* Closes buses and stores after context assembly fails.
|
|
1143
|
+
*
|
|
1144
|
+
* @param resources Partially created resources.
|
|
1145
|
+
* @param errors Collection receiving failures.
|
|
1146
|
+
*/
|
|
1105
1147
|
cleanupBuildBuses(resources, errors) {
|
|
1106
1148
|
ContextParts.attemptCleanup(() => {
|
|
1107
1149
|
if (resources.commandBus !== undefined)
|
|
@@ -1120,20 +1162,61 @@ const ContextParts = Object.freeze({
|
|
|
1120
1162
|
resources.eventStore?.close();
|
|
1121
1163
|
}, errors);
|
|
1122
1164
|
},
|
|
1165
|
+
/**
|
|
1166
|
+
* Returns constituent failures from nested aggregate errors.
|
|
1167
|
+
*
|
|
1168
|
+
* @param errors Errors to expand.
|
|
1169
|
+
* @returns Flattened error list.
|
|
1170
|
+
*/
|
|
1123
1171
|
flattenErrors(errors) {
|
|
1124
1172
|
return errors.flatMap((error) => error instanceof AggregateError ? ContextParts.flattenErrors(error.errors) : [error]);
|
|
1125
1173
|
},
|
|
1174
|
+
/**
|
|
1175
|
+
* Rejects construction without the framework token.
|
|
1176
|
+
*
|
|
1177
|
+
* @param token Supplied token.
|
|
1178
|
+
* @param message Error message for an invalid token.
|
|
1179
|
+
*/
|
|
1126
1180
|
requireFrameworkConstructionToken(token, message) {
|
|
1127
1181
|
if (token !== frameworkConstructionToken) {
|
|
1128
1182
|
throw new TypeError(message);
|
|
1129
1183
|
}
|
|
1130
1184
|
},
|
|
1185
|
+
/**
|
|
1186
|
+
* Creates a context specification from its snapshot.
|
|
1187
|
+
*
|
|
1188
|
+
* @param specSnapshot Snapshot used for construction.
|
|
1189
|
+
* @returns Constructed context specification.
|
|
1190
|
+
*/
|
|
1131
1191
|
createContextSpec(specSnapshot) {
|
|
1132
1192
|
return constructContextSpec(specSnapshot, frameworkConstructionToken);
|
|
1133
1193
|
},
|
|
1194
|
+
/**
|
|
1195
|
+
* Creates a builder for a context snapshot.
|
|
1196
|
+
*
|
|
1197
|
+
* @param specSnapshot Context configuration snapshot.
|
|
1198
|
+
* @returns Constructed context builder.
|
|
1199
|
+
*/
|
|
1134
1200
|
createBoundedContextBuilder(specSnapshot) {
|
|
1135
1201
|
return constructBoundedContextBuilder(specSnapshot, frameworkConstructionToken);
|
|
1136
1202
|
},
|
|
1203
|
+
/**
|
|
1204
|
+
* Creates a bounded context from assembled runtime components.
|
|
1205
|
+
*
|
|
1206
|
+
* @param specSnapshot Context configuration.
|
|
1207
|
+
* @param commandBus Command dispatch bus.
|
|
1208
|
+
* @param eventBus Domain event bus.
|
|
1209
|
+
* @param systemEventBus System event bus.
|
|
1210
|
+
* @param publisher Signal publisher.
|
|
1211
|
+
* @param stand Domain read-side Stand.
|
|
1212
|
+
* @param systemStand System read-side Stand.
|
|
1213
|
+
* @param runtime Subscription runtime.
|
|
1214
|
+
* @param systemSpec Paired System specification.
|
|
1215
|
+
* @param storageFactory Storage provider.
|
|
1216
|
+
* @param repositories Registered repository views.
|
|
1217
|
+
* @param deliveryStrategy Delivery sharding strategy.
|
|
1218
|
+
* @returns Constructed bounded context.
|
|
1219
|
+
*/
|
|
1137
1220
|
createBoundedContext(specSnapshot, commandBus, eventBus, systemEventBus, publisher, stand, systemStand, runtime, systemSpec, storageFactory, repositories, deliveryStrategy) {
|
|
1138
1221
|
return constructBoundedContext({
|
|
1139
1222
|
name: specSnapshot.name,
|
|
@@ -1141,18 +1224,36 @@ const ContextParts = Object.freeze({
|
|
|
1141
1224
|
spec: specSnapshot,
|
|
1142
1225
|
}, commandBus, eventBus, systemEventBus, publisher, stand, systemStand, runtime, systemSpec, storageFactory, repositories, deliveryStrategy, frameworkConstructionToken);
|
|
1143
1226
|
},
|
|
1227
|
+
/**
|
|
1228
|
+
* Creates the storage mode represented by a context snapshot.
|
|
1229
|
+
*
|
|
1230
|
+
* @param specSnapshot Context configuration snapshot.
|
|
1231
|
+
* @returns Storage mode for the context.
|
|
1232
|
+
*/
|
|
1144
1233
|
createStorageMode(specSnapshot) {
|
|
1145
1234
|
return Object.freeze({
|
|
1146
1235
|
name: specSnapshot.name.value,
|
|
1147
1236
|
multitenant: specSnapshot.multitenant,
|
|
1148
1237
|
});
|
|
1149
1238
|
},
|
|
1239
|
+
/**
|
|
1240
|
+
* Creates a non-tenant-scoped subscription storage context.
|
|
1241
|
+
*
|
|
1242
|
+
* @param specSnapshot Context configuration snapshot.
|
|
1243
|
+
* @returns Subscription storage context.
|
|
1244
|
+
*/
|
|
1150
1245
|
createSubscriptionStorageContext(specSnapshot) {
|
|
1151
1246
|
return Object.freeze({
|
|
1152
1247
|
name: `${specSnapshot.name.value}:subscriptions`,
|
|
1153
1248
|
multitenant: false,
|
|
1154
1249
|
});
|
|
1155
1250
|
},
|
|
1251
|
+
/**
|
|
1252
|
+
* Captures the shard count and validates later shard results.
|
|
1253
|
+
*
|
|
1254
|
+
* @param strategy Configured delivery strategy.
|
|
1255
|
+
* @returns Immutable delivery strategy snapshot.
|
|
1256
|
+
*/
|
|
1156
1257
|
snapshotDeliveryStrategy(strategy) {
|
|
1157
1258
|
if (!Number.isSafeInteger(strategy.shardCount) || strategy.shardCount <= 0) {
|
|
1158
1259
|
throw new Error("Delivery strategy shard count must be a positive safe integer.");
|
|
@@ -1160,6 +1261,13 @@ const ContextParts = Object.freeze({
|
|
|
1160
1261
|
const shardCount = strategy.shardCount;
|
|
1161
1262
|
return Object.freeze({
|
|
1162
1263
|
shardCount,
|
|
1264
|
+
/**
|
|
1265
|
+
* Returns a target shard after verifying the captured shard total.
|
|
1266
|
+
*
|
|
1267
|
+
* @param targetId Inbox target identifier.
|
|
1268
|
+
* @param targetType Inbox target type.
|
|
1269
|
+
* @returns Validated shard index.
|
|
1270
|
+
*/
|
|
1163
1271
|
shardFor(targetId, targetType) {
|
|
1164
1272
|
const shard = strategy.shardFor(InboxTargets.clone(targetId), targetType);
|
|
1165
1273
|
if (shard.ofTotal !== shardCount) {
|
|
@@ -1169,6 +1277,13 @@ const ContextParts = Object.freeze({
|
|
|
1169
1277
|
},
|
|
1170
1278
|
});
|
|
1171
1279
|
},
|
|
1280
|
+
/**
|
|
1281
|
+
* Creates the storage context for read-side catch-up.
|
|
1282
|
+
*
|
|
1283
|
+
* @param specSnapshot Context configuration.
|
|
1284
|
+
* @param options Catch-up tenant options.
|
|
1285
|
+
* @returns Storage context for the selected scope.
|
|
1286
|
+
*/
|
|
1172
1287
|
catchUpStorageContext(specSnapshot, options) {
|
|
1173
1288
|
if (!specSnapshot.multitenant) {
|
|
1174
1289
|
if (options.tenantId !== undefined) {
|
|
@@ -1186,6 +1301,12 @@ const ContextParts = Object.freeze({
|
|
|
1186
1301
|
tenantId: TenantBoundary.from(tenantId).tenantId,
|
|
1187
1302
|
});
|
|
1188
1303
|
},
|
|
1304
|
+
/**
|
|
1305
|
+
* Creates a bounded-context name after checking reserved names.
|
|
1306
|
+
*
|
|
1307
|
+
* @param value Candidate context name.
|
|
1308
|
+
* @returns Immutable validated name.
|
|
1309
|
+
*/
|
|
1189
1310
|
createBoundedContextName(value) {
|
|
1190
1311
|
if (typeof value !== "string" ||
|
|
1191
1312
|
value.trim().length === 0 ||
|
|
@@ -1194,6 +1315,13 @@ const ContextParts = Object.freeze({
|
|
|
1194
1315
|
}
|
|
1195
1316
|
return Object.freeze({ value });
|
|
1196
1317
|
},
|
|
1318
|
+
/**
|
|
1319
|
+
* Creates a context snapshot with event storage enabled.
|
|
1320
|
+
*
|
|
1321
|
+
* @param name Context name.
|
|
1322
|
+
* @param multitenant Whether tenant isolation is enabled.
|
|
1323
|
+
* @returns Immutable specification snapshot.
|
|
1324
|
+
*/
|
|
1197
1325
|
createSpecSnapshot(name, multitenant) {
|
|
1198
1326
|
return Object.freeze({
|
|
1199
1327
|
name: ContextParts.createBoundedContextName(name),
|
|
@@ -1201,9 +1329,21 @@ const ContextParts = Object.freeze({
|
|
|
1201
1329
|
storesEvents: true,
|
|
1202
1330
|
});
|
|
1203
1331
|
},
|
|
1332
|
+
/**
|
|
1333
|
+
* Copies a bounded-context name into an immutable value.
|
|
1334
|
+
*
|
|
1335
|
+
* @param name Source context name.
|
|
1336
|
+
* @returns Immutable name copy.
|
|
1337
|
+
*/
|
|
1204
1338
|
cloneName(name) {
|
|
1205
1339
|
return ContextParts.createBoundedContextName(name.value);
|
|
1206
1340
|
},
|
|
1341
|
+
/**
|
|
1342
|
+
* Copies a context specification snapshot and its name.
|
|
1343
|
+
*
|
|
1344
|
+
* @param spec Source specification.
|
|
1345
|
+
* @returns Immutable specification copy.
|
|
1346
|
+
*/
|
|
1207
1347
|
cloneSpecSnapshot(spec) {
|
|
1208
1348
|
return Object.freeze({
|
|
1209
1349
|
name: ContextParts.cloneName(spec.name),
|
|
@@ -1211,6 +1351,12 @@ const ContextParts = Object.freeze({
|
|
|
1211
1351
|
storesEvents: spec.storesEvents,
|
|
1212
1352
|
});
|
|
1213
1353
|
},
|
|
1354
|
+
/**
|
|
1355
|
+
* Copies a bounded-context snapshot and its specification.
|
|
1356
|
+
*
|
|
1357
|
+
* @param snapshot Source context snapshot.
|
|
1358
|
+
* @returns Immutable context snapshot copy.
|
|
1359
|
+
*/
|
|
1214
1360
|
cloneContextSnapshot(snapshot) {
|
|
1215
1361
|
return Object.freeze({
|
|
1216
1362
|
name: ContextParts.cloneName(snapshot.name),
|
|
@@ -1218,6 +1364,13 @@ const ContextParts = Object.freeze({
|
|
|
1218
1364
|
spec: ContextParts.cloneSpecSnapshot(snapshot.spec),
|
|
1219
1365
|
});
|
|
1220
1366
|
},
|
|
1367
|
+
/**
|
|
1368
|
+
* Creates the System specification paired with a domain context.
|
|
1369
|
+
*
|
|
1370
|
+
* @param domainSpec Domain specification.
|
|
1371
|
+
* @param storesEvents Whether System events are stored.
|
|
1372
|
+
* @returns System context snapshot.
|
|
1373
|
+
*/
|
|
1221
1374
|
createSystemSpec(domainSpec, storesEvents) {
|
|
1222
1375
|
return Object.freeze({
|
|
1223
1376
|
name: ContextParts.createBoundedContextName(`${domainSpec.name.value}_System`),
|
|
@@ -1225,24 +1378,49 @@ const ContextParts = Object.freeze({
|
|
|
1225
1378
|
storesEvents,
|
|
1226
1379
|
});
|
|
1227
1380
|
},
|
|
1381
|
+
/**
|
|
1382
|
+
* Creates a pairing snapshot for domain and System specifications.
|
|
1383
|
+
*
|
|
1384
|
+
* @param snapshot Domain snapshot.
|
|
1385
|
+
* @param systemSpec Paired System specification.
|
|
1386
|
+
* @returns Immutable context pairing.
|
|
1387
|
+
*/
|
|
1228
1388
|
createSystemPairing(snapshot, systemSpec) {
|
|
1229
1389
|
return Object.freeze({
|
|
1230
1390
|
domain: ContextParts.cloneContextSnapshot(snapshot),
|
|
1231
1391
|
system: ContextParts.cloneSpecSnapshot(systemSpec),
|
|
1232
1392
|
});
|
|
1233
1393
|
},
|
|
1394
|
+
/**
|
|
1395
|
+
* Copies both specifications in a System pairing.
|
|
1396
|
+
*
|
|
1397
|
+
* @param pairing Pairing to copy.
|
|
1398
|
+
* @returns Immutable pairing copy.
|
|
1399
|
+
*/
|
|
1234
1400
|
cloneSystemPairing(pairing) {
|
|
1235
1401
|
return Object.freeze({
|
|
1236
1402
|
domain: ContextParts.cloneContextSnapshot(pairing.domain),
|
|
1237
1403
|
system: ContextParts.cloneSpecSnapshot(pairing.system),
|
|
1238
1404
|
});
|
|
1239
1405
|
},
|
|
1406
|
+
/**
|
|
1407
|
+
* Lists non-internal event type URLs exposed by an event bus.
|
|
1408
|
+
*
|
|
1409
|
+
* @param eventBus Bus whose schemas are inspected.
|
|
1410
|
+
* @returns Frozen exposed type URLs.
|
|
1411
|
+
*/
|
|
1240
1412
|
exposedEventTypeUrls(eventBus) {
|
|
1241
1413
|
return Object.freeze(eventBusAccess
|
|
1242
1414
|
.eventSchemas(eventBus)
|
|
1243
1415
|
.filter((schema) => !ContextParts.isInternalEventSchema(schema))
|
|
1244
1416
|
.map((schema) => TypeUrls.derive(schema)));
|
|
1245
1417
|
},
|
|
1418
|
+
/**
|
|
1419
|
+
* Collects external event schemas by type URL.
|
|
1420
|
+
*
|
|
1421
|
+
* @param dispatchers Dispatchers to inspect.
|
|
1422
|
+
* @returns Unique frozen schemas.
|
|
1423
|
+
*/
|
|
1246
1424
|
externalEventSchemas(dispatchers) {
|
|
1247
1425
|
const schemas = new Map();
|
|
1248
1426
|
for (const dispatcher of dispatchers) {
|
|
@@ -1252,6 +1430,14 @@ const ContextParts = Object.freeze({
|
|
|
1252
1430
|
}
|
|
1253
1431
|
return Object.freeze([...schemas.values()]);
|
|
1254
1432
|
},
|
|
1433
|
+
/**
|
|
1434
|
+
* Opens the integration broker and associates context readiness.
|
|
1435
|
+
*
|
|
1436
|
+
* @param context Context receiving integration.
|
|
1437
|
+
* @param eventBus Event bus supplied to broker.
|
|
1438
|
+
* @param systemSpec Paired System specification.
|
|
1439
|
+
* @param externalEventSchemas Schemas accepted from outside.
|
|
1440
|
+
*/
|
|
1255
1441
|
attachIntegration(context, eventBus, systemSpec, externalEventSchemas) {
|
|
1256
1442
|
const broker = new IntegrationBroker({
|
|
1257
1443
|
contextName: create(BoundedContextNameSchema, { value: context.name.value }),
|
|
@@ -1274,6 +1460,13 @@ const ContextParts = Object.freeze({
|
|
|
1274
1460
|
void ready.catch(() => undefined);
|
|
1275
1461
|
contextIntegrations.set(context, { broker, ready });
|
|
1276
1462
|
},
|
|
1463
|
+
/**
|
|
1464
|
+
* Posts an event after integration readiness and tenant validation.
|
|
1465
|
+
*
|
|
1466
|
+
* @param context Target context.
|
|
1467
|
+
* @param event Event to post.
|
|
1468
|
+
* @returns Promise fulfilled after posting.
|
|
1469
|
+
*/
|
|
1277
1470
|
postContextEvent(context, event) {
|
|
1278
1471
|
const buses = contextEventBuses.get(context);
|
|
1279
1472
|
if (buses === undefined)
|
|
@@ -1285,15 +1478,34 @@ const ContextParts = Object.freeze({
|
|
|
1285
1478
|
return buses[0].post(event);
|
|
1286
1479
|
});
|
|
1287
1480
|
},
|
|
1481
|
+
/**
|
|
1482
|
+
* Closes the context integration broker.
|
|
1483
|
+
*
|
|
1484
|
+
* @param context Context whose broker is closed.
|
|
1485
|
+
* @returns Promise fulfilled after closing.
|
|
1486
|
+
*/
|
|
1288
1487
|
closeIntegration(context) {
|
|
1289
1488
|
return contextIntegrations.get(context)?.broker.close() ?? Promise.resolve();
|
|
1290
1489
|
},
|
|
1490
|
+
/**
|
|
1491
|
+
* Publishes an imported event through the integration broker.
|
|
1492
|
+
*
|
|
1493
|
+
* @param context Context integration.
|
|
1494
|
+
* @param event Imported event.
|
|
1495
|
+
* @returns Promise fulfilled after publishing.
|
|
1496
|
+
*/
|
|
1291
1497
|
publishImported(context, event) {
|
|
1292
1498
|
const integration = contextIntegrations.get(context);
|
|
1293
1499
|
if (integration === undefined)
|
|
1294
1500
|
return Promise.reject(new Error("Context integration is unavailable."));
|
|
1295
1501
|
return integration.ready.then(() => integration.broker.publishImported(event));
|
|
1296
1502
|
},
|
|
1503
|
+
/**
|
|
1504
|
+
* Checks imported event tenant against the context tenant mode.
|
|
1505
|
+
*
|
|
1506
|
+
* @param context Target context.
|
|
1507
|
+
* @param event Imported event to validate.
|
|
1508
|
+
*/
|
|
1297
1509
|
validateImportedTenant(context, event) {
|
|
1298
1510
|
const tenantId = ContextParts.readReplayTenant(event);
|
|
1299
1511
|
if (!context.isMultitenant) {
|
|
@@ -1307,17 +1519,41 @@ const ContextParts = Object.freeze({
|
|
|
1307
1519
|
}
|
|
1308
1520
|
TenantBoundary.from(tenantId);
|
|
1309
1521
|
},
|
|
1522
|
+
/**
|
|
1523
|
+
* Waits for the integration broker to become ready.
|
|
1524
|
+
*
|
|
1525
|
+
* @param context Context whose readiness is observed.
|
|
1526
|
+
* @returns Readiness promise.
|
|
1527
|
+
*/
|
|
1310
1528
|
integrationReady(context) {
|
|
1311
1529
|
return contextIntegrations.get(context)?.ready ?? Promise.resolve();
|
|
1312
1530
|
},
|
|
1531
|
+
/**
|
|
1532
|
+
* Checks whether a schema carries an internal or SPI marker.
|
|
1533
|
+
*
|
|
1534
|
+
* @param schema Event schema to inspect.
|
|
1535
|
+
* @returns True when an internal marker is set.
|
|
1536
|
+
*/
|
|
1313
1537
|
isInternalEventSchema(schema) {
|
|
1314
1538
|
return ((hasOption(schema, internal_type) && getOption(schema, internal_type)) ||
|
|
1315
1539
|
(hasOption(schema, SPI_type) && getOption(schema, SPI_type)) ||
|
|
1316
1540
|
(hasOption(schema.file, internal_all) && getOption(schema.file, internal_all)));
|
|
1317
1541
|
},
|
|
1542
|
+
/**
|
|
1543
|
+
* Converts the multitenant flag to a tenant mode.
|
|
1544
|
+
*
|
|
1545
|
+
* @param multitenant Context isolation flag.
|
|
1546
|
+
* @returns Corresponding tenant mode.
|
|
1547
|
+
*/
|
|
1318
1548
|
toTenantMode(multitenant) {
|
|
1319
1549
|
return multitenant ? "multitenant" : "single-tenant";
|
|
1320
1550
|
},
|
|
1551
|
+
/**
|
|
1552
|
+
* Reads the System pairing recorded for a built context.
|
|
1553
|
+
*
|
|
1554
|
+
* @param context Context to inspect.
|
|
1555
|
+
* @returns Pairing snapshot; throws if unpaired.
|
|
1556
|
+
*/
|
|
1321
1557
|
requireSystemPairing(context) {
|
|
1322
1558
|
const pairing = contextSystemPairings.get(context);
|
|
1323
1559
|
if (pairing === undefined) {
|
|
@@ -1325,6 +1561,12 @@ const ContextParts = Object.freeze({
|
|
|
1325
1561
|
}
|
|
1326
1562
|
return pairing;
|
|
1327
1563
|
},
|
|
1564
|
+
/**
|
|
1565
|
+
* Reads the tenant index recorded for a built context.
|
|
1566
|
+
*
|
|
1567
|
+
* @param context Context to inspect.
|
|
1568
|
+
* @returns Tenant index; throws if unavailable.
|
|
1569
|
+
*/
|
|
1328
1570
|
requireTenantIndex(context) {
|
|
1329
1571
|
const tenantIndex = contextTenantIndexes.get(context);
|
|
1330
1572
|
if (tenantIndex === undefined) {
|
|
@@ -1332,16 +1574,33 @@ const ContextParts = Object.freeze({
|
|
|
1332
1574
|
}
|
|
1333
1575
|
return tenantIndex;
|
|
1334
1576
|
},
|
|
1577
|
+
/**
|
|
1578
|
+
* Rejects values that are not Repository instances.
|
|
1579
|
+
*
|
|
1580
|
+
* @param repository Candidate value.
|
|
1581
|
+
* @param operation Operation name included in the error.
|
|
1582
|
+
*/
|
|
1335
1583
|
requireRepositoryInstance(repository, operation) {
|
|
1336
1584
|
if (!repositoryAccess.hasInstance(repository)) {
|
|
1337
1585
|
throw new TypeError(`${operation} requires a Repository instance.`);
|
|
1338
1586
|
}
|
|
1339
1587
|
},
|
|
1588
|
+
/**
|
|
1589
|
+
* Rejects values that are not Entity classes.
|
|
1590
|
+
*
|
|
1591
|
+
* @param entityType Candidate Entity constructor.
|
|
1592
|
+
* @param operation Operation name included in the error.
|
|
1593
|
+
*/
|
|
1340
1594
|
requireEntityClass(entityType, operation) {
|
|
1341
1595
|
if (typeof entityType !== "function") {
|
|
1342
1596
|
throw new TypeError(`${operation} requires a Repository instance. Use an entity class only with buildAsync().`);
|
|
1343
1597
|
}
|
|
1344
1598
|
},
|
|
1599
|
+
/**
|
|
1600
|
+
* Rejects synchronous assembly when classes need generated metadata.
|
|
1601
|
+
*
|
|
1602
|
+
* @param entityTypes Entity classes to assemble.
|
|
1603
|
+
*/
|
|
1345
1604
|
rejectSyncEntityAssembly(entityTypes) {
|
|
1346
1605
|
if (entityTypes.size === 0) {
|
|
1347
1606
|
return;
|
|
@@ -1349,6 +1608,12 @@ const ContextParts = Object.freeze({
|
|
|
1349
1608
|
throw new Error("BoundedContextBuilder.build() cannot assemble entity classes from generated metadata. " +
|
|
1350
1609
|
"Use buildAsync().");
|
|
1351
1610
|
},
|
|
1611
|
+
/**
|
|
1612
|
+
* Validates that a root exists when generated metadata must be loaded.
|
|
1613
|
+
*
|
|
1614
|
+
* @param root Configured registry location.
|
|
1615
|
+
* @returns Configured root; throws when absent.
|
|
1616
|
+
*/
|
|
1352
1617
|
requireGeneratedRegistryRoot(root) {
|
|
1353
1618
|
if (root !== undefined) {
|
|
1354
1619
|
return root;
|
|
@@ -1356,6 +1621,12 @@ const ContextParts = Object.freeze({
|
|
|
1356
1621
|
throw new Error("BoundedContextBuilder.buildAsync() requires withGeneratedRegistryRoot(root) " +
|
|
1357
1622
|
"when assembling entity classes from generated metadata.");
|
|
1358
1623
|
},
|
|
1624
|
+
/**
|
|
1625
|
+
* Resolves the generated registry module and verifies it stays beneath the configured root.
|
|
1626
|
+
*
|
|
1627
|
+
* @param root Configured filesystem path or file URL.
|
|
1628
|
+
* @returns Registry module file URL.
|
|
1629
|
+
*/
|
|
1359
1630
|
async trustedGeneratedRegistryModule(root) {
|
|
1360
1631
|
const trustedRoot = await ContextParts.canonicalGeneratedRegistryRoot(root);
|
|
1361
1632
|
const registryPath = resolve(trustedRoot, generatedRegistryFile);
|
|
@@ -1366,6 +1637,12 @@ const ContextParts = Object.freeze({
|
|
|
1366
1637
|
}
|
|
1367
1638
|
return pathToFileURL(canonicalRegistryPath);
|
|
1368
1639
|
},
|
|
1640
|
+
/**
|
|
1641
|
+
* Resolves a generated registry root to its canonical filesystem path.
|
|
1642
|
+
*
|
|
1643
|
+
* @param root Configured path or file URL.
|
|
1644
|
+
* @returns Canonical directory path.
|
|
1645
|
+
*/
|
|
1369
1646
|
async canonicalGeneratedRegistryRoot(root) {
|
|
1370
1647
|
const rootPath = ContextParts.generatedRegistryRootPath(root);
|
|
1371
1648
|
try {
|
|
@@ -1375,6 +1652,12 @@ const ContextParts = Object.freeze({
|
|
|
1375
1652
|
throw new Error(`Generated registry root "${rootPath}" must be an existing readable directory.`, { cause: error });
|
|
1376
1653
|
}
|
|
1377
1654
|
},
|
|
1655
|
+
/**
|
|
1656
|
+
* Converts a registry root path or file URL to an absolute filesystem path.
|
|
1657
|
+
*
|
|
1658
|
+
* @param root Configured path or file URL.
|
|
1659
|
+
* @returns Absolute filesystem path.
|
|
1660
|
+
*/
|
|
1378
1661
|
generatedRegistryRootPath(root) {
|
|
1379
1662
|
if (root instanceof URL) {
|
|
1380
1663
|
return ContextParts.fileUrlPath(root, "Generated registry root");
|
|
@@ -1384,6 +1667,13 @@ const ContextParts = Object.freeze({
|
|
|
1384
1667
|
}
|
|
1385
1668
|
return resolve(root);
|
|
1386
1669
|
},
|
|
1670
|
+
/**
|
|
1671
|
+
* Validates a file URL and converts it to an absolute filesystem path.
|
|
1672
|
+
*
|
|
1673
|
+
* @param url File URL to convert.
|
|
1674
|
+
* @param label Label used in validation errors.
|
|
1675
|
+
* @returns Filesystem path.
|
|
1676
|
+
*/
|
|
1387
1677
|
fileUrlPath(url, label) {
|
|
1388
1678
|
if (url.protocol !== "file:") {
|
|
1389
1679
|
throw new Error(`${label} "${url.href}" must use the file: URL scheme.`);
|
|
@@ -1393,6 +1683,12 @@ const ContextParts = Object.freeze({
|
|
|
1393
1683
|
}
|
|
1394
1684
|
return resolve(fileURLToPath(url));
|
|
1395
1685
|
},
|
|
1686
|
+
/**
|
|
1687
|
+
* Parses a generated registry root string as a URL.
|
|
1688
|
+
*
|
|
1689
|
+
* @param root Registry root string.
|
|
1690
|
+
* @returns Parsed URL.
|
|
1691
|
+
*/
|
|
1396
1692
|
parseRootUrl(root) {
|
|
1397
1693
|
try {
|
|
1398
1694
|
return new URL(root);
|
|
@@ -1401,9 +1697,21 @@ const ContextParts = Object.freeze({
|
|
|
1401
1697
|
throw new Error(`Generated registry root "${root}" is not a valid URL.`, { cause: error });
|
|
1402
1698
|
}
|
|
1403
1699
|
},
|
|
1700
|
+
/**
|
|
1701
|
+
* Checks strings for a URL scheme, excluding Windows drive paths.
|
|
1702
|
+
*
|
|
1703
|
+
* @param value String to inspect.
|
|
1704
|
+
* @returns True when the string has a URL scheme.
|
|
1705
|
+
*/
|
|
1404
1706
|
isUrlLike(value) {
|
|
1405
1707
|
return moduleSchemeRe.test(value) && !/^[A-Za-z]:[\\/]/.test(value);
|
|
1406
1708
|
},
|
|
1709
|
+
/**
|
|
1710
|
+
* Verifies registry-module readability and resolves its canonical path.
|
|
1711
|
+
*
|
|
1712
|
+
* @param registryPath Registry module filesystem path.
|
|
1713
|
+
* @returns Canonical readable path.
|
|
1714
|
+
*/
|
|
1407
1715
|
async canonicalReadableRegistryPath(registryPath) {
|
|
1408
1716
|
try {
|
|
1409
1717
|
await access(registryPath, fsConstants.R_OK);
|
|
@@ -1413,6 +1721,13 @@ const ContextParts = Object.freeze({
|
|
|
1413
1721
|
throw new Error(`Generated handler registry module "${registryPath}" must exist and be readable.`, { cause: error });
|
|
1414
1722
|
}
|
|
1415
1723
|
},
|
|
1724
|
+
/**
|
|
1725
|
+
* Checks whether a canonical path escapes a canonical root.
|
|
1726
|
+
*
|
|
1727
|
+
* @param canonicalRoot Root directory.
|
|
1728
|
+
* @param canonicalPath Candidate path.
|
|
1729
|
+
* @returns True when the candidate escapes the root.
|
|
1730
|
+
*/
|
|
1416
1731
|
resolvesOutsideRoot(canonicalRoot, canonicalPath) {
|
|
1417
1732
|
const relativePath = relative(canonicalRoot, canonicalPath);
|
|
1418
1733
|
return (relativePath.startsWith("..") ||
|
|
@@ -1420,13 +1735,30 @@ const ContextParts = Object.freeze({
|
|
|
1420
1735
|
relativePath.split(sep).includes("..") ||
|
|
1421
1736
|
isAbsolute(relativePath));
|
|
1422
1737
|
},
|
|
1738
|
+
/**
|
|
1739
|
+
* Creates a cache-bust query after a prior registry load failure.
|
|
1740
|
+
*
|
|
1741
|
+
* @param registryKey Canonical registry identifier.
|
|
1742
|
+
* @returns Retry query, or an empty object on the first attempt.
|
|
1743
|
+
*/
|
|
1423
1744
|
generatedRegistryCacheBust(registryKey) {
|
|
1424
1745
|
const attempt = generatedRegistryLoadAttempts.get(registryKey) ?? 0;
|
|
1425
1746
|
return attempt === 0 ? {} : { cacheBust: `retry-${attempt.toString()}` };
|
|
1426
1747
|
},
|
|
1748
|
+
/**
|
|
1749
|
+
* Records another failed load for a registry.
|
|
1750
|
+
*
|
|
1751
|
+
* @param registryKey Canonical registry identifier.
|
|
1752
|
+
*/
|
|
1427
1753
|
recordGeneratedRegistryFailure(registryKey) {
|
|
1428
1754
|
generatedRegistryLoadAttempts.set(registryKey, (generatedRegistryLoadAttempts.get(registryKey) ?? 0) + 1);
|
|
1429
1755
|
},
|
|
1756
|
+
/**
|
|
1757
|
+
* Loads generated handler registries into a metadata registry.
|
|
1758
|
+
*
|
|
1759
|
+
* @param registries Generated registries to ingest.
|
|
1760
|
+
* @returns Populated handler metadata registry.
|
|
1761
|
+
*/
|
|
1430
1762
|
ingestGeneratedRegistries(registries) {
|
|
1431
1763
|
const registry = new HandlerMetadataRegistry();
|
|
1432
1764
|
const ingestor = new HandlerRegistryIngestor();
|
|
@@ -1435,6 +1767,15 @@ const ContextParts = Object.freeze({
|
|
|
1435
1767
|
}
|
|
1436
1768
|
return registry;
|
|
1437
1769
|
},
|
|
1770
|
+
/**
|
|
1771
|
+
* Creates a repository from generated Entity handler metadata.
|
|
1772
|
+
*
|
|
1773
|
+
* @param entityType Entity class.
|
|
1774
|
+
* @param registries Generated registries to search.
|
|
1775
|
+
* @param metadata Ingested handler metadata.
|
|
1776
|
+
* @param options Repository options overriding defaults.
|
|
1777
|
+
* @returns Configured repository view.
|
|
1778
|
+
*/
|
|
1438
1779
|
createGeneratedRepository(entityType, registries, metadata, options) {
|
|
1439
1780
|
const generated = ContextParts.findGeneratedEntity(entityType, registries);
|
|
1440
1781
|
const handlers = ContextParts.findGeneratedHandlers(entityType, generated, metadata);
|
|
@@ -1446,6 +1787,13 @@ const ContextParts = Object.freeze({
|
|
|
1446
1787
|
}, options);
|
|
1447
1788
|
return new Repository(repositoryOptions);
|
|
1448
1789
|
},
|
|
1790
|
+
/**
|
|
1791
|
+
* Finds the generated Entity handler group for an Entity class.
|
|
1792
|
+
*
|
|
1793
|
+
* @param entityType Entity class to find.
|
|
1794
|
+
* @param registries Generated registries to search.
|
|
1795
|
+
* @returns Matching handler group; throws when absent.
|
|
1796
|
+
*/
|
|
1449
1797
|
findGeneratedEntity(entityType, registries) {
|
|
1450
1798
|
for (const registry of registries) {
|
|
1451
1799
|
const generated = registry.receivers.find((receiver) => receiver.receiverKind === "entity" && receiver.receiverType === entityType);
|
|
@@ -1455,6 +1803,14 @@ const ContextParts = Object.freeze({
|
|
|
1455
1803
|
}
|
|
1456
1804
|
throw new Error(`Generated handler registry is missing metadata for ${entityType.name}.`);
|
|
1457
1805
|
},
|
|
1806
|
+
/**
|
|
1807
|
+
* Finds handler metadata for an Entity class and state schema.
|
|
1808
|
+
*
|
|
1809
|
+
* @param entityType Entity class.
|
|
1810
|
+
* @param generated Matching generated Entity group.
|
|
1811
|
+
* @param metadata Handler metadata registry.
|
|
1812
|
+
* @returns Matching handler metadata; throws when absent.
|
|
1813
|
+
*/
|
|
1458
1814
|
findGeneratedHandlers(entityType, generated, metadata) {
|
|
1459
1815
|
const matches = metadata.findByState(generated.stateSchema.typeName);
|
|
1460
1816
|
const handlers = matches.find((candidate) => candidate.entityType === entityType);
|
|
@@ -1463,6 +1819,12 @@ const ContextParts = Object.freeze({
|
|
|
1463
1819
|
}
|
|
1464
1820
|
return handlers;
|
|
1465
1821
|
},
|
|
1822
|
+
/**
|
|
1823
|
+
* Collects event schemas declared as returned or thrown handler outcomes.
|
|
1824
|
+
*
|
|
1825
|
+
* @param generated Generated Entity handler group.
|
|
1826
|
+
* @returns Frozen unique event schemas.
|
|
1827
|
+
*/
|
|
1466
1828
|
aggregateAssignedEvents(generated) {
|
|
1467
1829
|
return ContextParts.uniqueSchemas(generated.handlers.flatMap((handler) => [
|
|
1468
1830
|
...(handler.kind === "command-assignment" || handler.kind === "event-reaction"
|
|
@@ -1473,6 +1835,12 @@ const ContextParts = Object.freeze({
|
|
|
1473
1835
|
: []),
|
|
1474
1836
|
]));
|
|
1475
1837
|
},
|
|
1838
|
+
/**
|
|
1839
|
+
* Collects event schemas produced by standalone handler outcomes.
|
|
1840
|
+
*
|
|
1841
|
+
* @param receivers Generated standalone groups.
|
|
1842
|
+
* @returns Frozen unique event schemas.
|
|
1843
|
+
*/
|
|
1476
1844
|
standaloneProducedEventSchemas(receivers) {
|
|
1477
1845
|
return ContextParts.uniqueSchemas(receivers.flatMap((receiver) => receiver.handlers.flatMap((handler) => [
|
|
1478
1846
|
...(handler.kind === "command-assignment" || handler.kind === "event-reaction"
|
|
@@ -1483,6 +1851,13 @@ const ContextParts = Object.freeze({
|
|
|
1483
1851
|
: []),
|
|
1484
1852
|
])));
|
|
1485
1853
|
},
|
|
1854
|
+
/**
|
|
1855
|
+
* Returns unique schemas by Protobuf type name, preserving the last schema.
|
|
1856
|
+
*
|
|
1857
|
+
* @typeParam Schema Descriptor schema type retained in the result.
|
|
1858
|
+
* @param schemas Schemas to deduplicate.
|
|
1859
|
+
* @returns Frozen deduplicated schemas.
|
|
1860
|
+
*/
|
|
1486
1861
|
uniqueSchemas(schemas) {
|
|
1487
1862
|
const byTypeName = new Map();
|
|
1488
1863
|
for (const schema of schemas) {
|
|
@@ -1490,6 +1865,11 @@ const ContextParts = Object.freeze({
|
|
|
1490
1865
|
}
|
|
1491
1866
|
return Object.freeze([...byTypeName.values()]);
|
|
1492
1867
|
},
|
|
1868
|
+
/**
|
|
1869
|
+
* Validates repository uniqueness, registration state, and projection cycles.
|
|
1870
|
+
*
|
|
1871
|
+
* @param repositories Repository views to validate.
|
|
1872
|
+
*/
|
|
1493
1873
|
preflightRepositories(repositories) {
|
|
1494
1874
|
const entityTypes = new Set();
|
|
1495
1875
|
const stateTypeNames = new Set();
|
|
@@ -1512,6 +1892,11 @@ const ContextParts = Object.freeze({
|
|
|
1512
1892
|
}
|
|
1513
1893
|
ContextParts.rejectStateCycles(repositories);
|
|
1514
1894
|
},
|
|
1895
|
+
/**
|
|
1896
|
+
* Rejects cycles among repository state-subscription dependencies.
|
|
1897
|
+
*
|
|
1898
|
+
* @param repositories Repository views whose dependencies are checked.
|
|
1899
|
+
*/
|
|
1515
1900
|
rejectStateCycles(repositories) {
|
|
1516
1901
|
const dependencies = new Map();
|
|
1517
1902
|
for (const repository of repositories) {
|
|
@@ -1542,30 +1927,66 @@ const ContextParts = Object.freeze({
|
|
|
1542
1927
|
for (const stateType of dependencies.keys())
|
|
1543
1928
|
visit(stateType);
|
|
1544
1929
|
},
|
|
1930
|
+
/**
|
|
1931
|
+
* Collects command dispatchers configured by repositories.
|
|
1932
|
+
*
|
|
1933
|
+
* @param repositories Repository views to inspect.
|
|
1934
|
+
* @returns Present command dispatchers.
|
|
1935
|
+
*/
|
|
1545
1936
|
repositoryCommandDispatchers(repositories) {
|
|
1546
1937
|
return repositories.flatMap((repository) => {
|
|
1547
1938
|
const dispatcher = repositoryAccess.commandDispatcher(repository);
|
|
1548
1939
|
return dispatcher === undefined ? [] : [dispatcher];
|
|
1549
1940
|
});
|
|
1550
1941
|
},
|
|
1942
|
+
/**
|
|
1943
|
+
* Collects event dispatchers configured by repositories.
|
|
1944
|
+
*
|
|
1945
|
+
* @param repositories Repository views to inspect.
|
|
1946
|
+
* @returns Present event dispatchers.
|
|
1947
|
+
*/
|
|
1551
1948
|
repositoryEventDispatchers(repositories) {
|
|
1552
1949
|
return repositories.flatMap((repository) => {
|
|
1553
1950
|
const dispatcher = repositoryAccess.eventDispatcher(repository);
|
|
1554
1951
|
return dispatcher === undefined ? [] : [dispatcher];
|
|
1555
1952
|
});
|
|
1556
1953
|
},
|
|
1954
|
+
/**
|
|
1955
|
+
* Collects System event dispatchers configured by repositories.
|
|
1956
|
+
*
|
|
1957
|
+
* @param repositories Repository views to inspect.
|
|
1958
|
+
* @returns Present System event dispatchers.
|
|
1959
|
+
*/
|
|
1557
1960
|
repositorySystemEventDispatchers(repositories) {
|
|
1558
1961
|
return repositories.flatMap((repository) => {
|
|
1559
1962
|
const dispatcher = repositoryAccess.systemEventDispatcher(repository);
|
|
1560
1963
|
return dispatcher === undefined ? [] : [dispatcher];
|
|
1561
1964
|
});
|
|
1562
1965
|
},
|
|
1966
|
+
/**
|
|
1967
|
+
* Returns event dispatchers excluding System routes.
|
|
1968
|
+
*
|
|
1969
|
+
* @param dispatchers Event dispatchers to filter.
|
|
1970
|
+
* @returns Frozen domain event dispatchers.
|
|
1971
|
+
*/
|
|
1563
1972
|
domainEventDispatchers(dispatchers) {
|
|
1564
1973
|
return Object.freeze(dispatchers.filter((dispatcher) => !ContextParts.isSystemEventDispatcher(dispatcher)));
|
|
1565
1974
|
},
|
|
1975
|
+
/**
|
|
1976
|
+
* Returns System event dispatchers.
|
|
1977
|
+
*
|
|
1978
|
+
* @param dispatchers Event dispatchers to filter.
|
|
1979
|
+
* @returns Frozen System event dispatchers.
|
|
1980
|
+
*/
|
|
1566
1981
|
systemEventDispatchers(dispatchers) {
|
|
1567
1982
|
return Object.freeze(dispatchers.filter((dispatcher) => ContextParts.isSystemEventDispatcher(dispatcher)));
|
|
1568
1983
|
},
|
|
1984
|
+
/**
|
|
1985
|
+
* Checks a dispatcher's event family and rejects mixed families.
|
|
1986
|
+
*
|
|
1987
|
+
* @param dispatcher Dispatcher to inspect.
|
|
1988
|
+
* @returns True when its schemas are System event schemas.
|
|
1989
|
+
*/
|
|
1569
1990
|
isSystemEventDispatcher(dispatcher) {
|
|
1570
1991
|
const schemas = [...dispatcher.messageSchemas()];
|
|
1571
1992
|
const systemSchemas = schemas.filter((schema) => schema.typeName.startsWith("spine.system."));
|
|
@@ -1574,6 +1995,12 @@ const ContextParts = Object.freeze({
|
|
|
1574
1995
|
}
|
|
1575
1996
|
return systemSchemas.length > 0;
|
|
1576
1997
|
},
|
|
1998
|
+
/**
|
|
1999
|
+
* Collects repository-produced event schemas by type URL.
|
|
2000
|
+
*
|
|
2001
|
+
* @param repositories Repository views to inspect.
|
|
2002
|
+
* @returns Frozen unique event schemas.
|
|
2003
|
+
*/
|
|
1577
2004
|
repositoryProducedEventSchemas(repositories) {
|
|
1578
2005
|
const schemas = new Map();
|
|
1579
2006
|
for (const repository of repositories) {
|
|
@@ -1583,10 +2010,21 @@ const ContextParts = Object.freeze({
|
|
|
1583
2010
|
}
|
|
1584
2011
|
return Object.freeze([...schemas.values()]);
|
|
1585
2012
|
},
|
|
2013
|
+
/**
|
|
2014
|
+
* Clears metadata for a failed context build and closes its tenant index.
|
|
2015
|
+
*
|
|
2016
|
+
* @param context Context whose build failed.
|
|
2017
|
+
* @param tenantIndex Tenant index to close.
|
|
2018
|
+
*/
|
|
1586
2019
|
cleanupFailedContext(context, tenantIndex) {
|
|
1587
2020
|
ContextParts.clearContextMetadata(context);
|
|
1588
2021
|
tenantIndex.close();
|
|
1589
2022
|
},
|
|
2023
|
+
/**
|
|
2024
|
+
* Removes runtime metadata associated with a context.
|
|
2025
|
+
*
|
|
2026
|
+
* @param context Context whose WeakMap metadata is removed.
|
|
2027
|
+
*/
|
|
1590
2028
|
clearContextMetadata(context) {
|
|
1591
2029
|
const buses = contextEventBuses.get(context);
|
|
1592
2030
|
if (buses !== undefined) {
|
|
@@ -1608,48 +2046,72 @@ const ContextParts = Object.freeze({
|
|
|
1608
2046
|
eventSubscribers.delete(context);
|
|
1609
2047
|
systemEventPosters.delete(context);
|
|
1610
2048
|
},
|
|
2049
|
+
/**
|
|
2050
|
+
* Creates delivery operations from the context inboxes and readiness state.
|
|
2051
|
+
*
|
|
2052
|
+
* @param context Built context snapshot.
|
|
2053
|
+
* @param storageFactory Context storage provider.
|
|
2054
|
+
* @param tenantIndex Tenant index for startup scopes.
|
|
2055
|
+
* @param entityInbox Registered Entity inbox.
|
|
2056
|
+
* @param projections Projection inbox.
|
|
2057
|
+
* @param readiness Delivery readiness coordinator.
|
|
2058
|
+
* @returns Delivery descriptor.
|
|
2059
|
+
*/
|
|
1611
2060
|
createDeliveryDescriptor(context, storageFactory, tenantIndex, entityInbox, projections, readiness) {
|
|
1612
2061
|
return Object.freeze({
|
|
1613
2062
|
storageFactory,
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
2063
|
+
startupScopes: () => ContextParts.deliveryStartupScopes(tenantIndex),
|
|
2064
|
+
storageContext: (scope) => ContextParts.deliveryStorageContext(context, scope),
|
|
2065
|
+
endpoints: () => Object.freeze([...entityInbox.endpoints(), ...projections.endpoints()]),
|
|
2066
|
+
replay: (message, tenantId) => message.label === "UPDATE_SUBSCRIBER"
|
|
2067
|
+
? projections.replay(message, tenantId)
|
|
2068
|
+
: entityInbox.replay(message, tenantId),
|
|
2069
|
+
onReady: (onReady) => readiness.onReady(onReady),
|
|
2070
|
+
transition: (scopes, onReady, options) => readiness.transition(scopes, onReady, options),
|
|
2071
|
+
});
|
|
2072
|
+
},
|
|
2073
|
+
/**
|
|
2074
|
+
* Lists delivery startup scopes, one per tenant in multitenant contexts.
|
|
2075
|
+
*
|
|
2076
|
+
* @param tenantIndex Tenant index supplying configured scopes.
|
|
2077
|
+
* @returns Frozen startup scope list.
|
|
2078
|
+
*/
|
|
2079
|
+
async deliveryStartupScopes(tenantIndex) {
|
|
2080
|
+
if (tenantIndex.tenantMode === "single-tenant") {
|
|
2081
|
+
return Object.freeze([Object.freeze({})]);
|
|
2082
|
+
}
|
|
2083
|
+
return Object.freeze((await tenantIndex.all()).map((tenantId) => Object.freeze({ tenantId })));
|
|
2084
|
+
},
|
|
2085
|
+
/**
|
|
2086
|
+
* Creates tenant-validated storage context for a delivery scope.
|
|
2087
|
+
*
|
|
2088
|
+
* @param context Built context snapshot.
|
|
2089
|
+
* @param scope Selected startup scope.
|
|
2090
|
+
* @returns Storage context for the selected tenant.
|
|
2091
|
+
*/
|
|
2092
|
+
deliveryStorageContext(context, scope) {
|
|
2093
|
+
if (context.tenantMode === "single-tenant") {
|
|
2094
|
+
if (scope.tenantId !== undefined) {
|
|
2095
|
+
throw new Error(`Single-tenant context "${context.name.value}" does not accept tenantId.`);
|
|
2096
|
+
}
|
|
2097
|
+
return Object.freeze({ name: context.name.value, multitenant: false });
|
|
2098
|
+
}
|
|
2099
|
+
const tenantId = scope.tenantId;
|
|
2100
|
+
if (tenantId === undefined) {
|
|
2101
|
+
throw new Error(`Multitenant context "${context.name.value}" requires tenantId.`);
|
|
2102
|
+
}
|
|
2103
|
+
return Object.freeze({
|
|
2104
|
+
name: context.name.value,
|
|
2105
|
+
multitenant: true,
|
|
2106
|
+
tenantId: TenantBoundary.from(tenantId).tenantId,
|
|
1651
2107
|
});
|
|
1652
2108
|
},
|
|
2109
|
+
/**
|
|
2110
|
+
* Closes prepared repositories and collects close failures.
|
|
2111
|
+
*
|
|
2112
|
+
* @param preparedRepositories Prepared repository resources to close.
|
|
2113
|
+
* @returns Collected close errors.
|
|
2114
|
+
*/
|
|
1653
2115
|
closePreparedRepositories(preparedRepositories) {
|
|
1654
2116
|
const errors = [];
|
|
1655
2117
|
for (const preparedRepository of preparedRepositories) {
|
|
@@ -1662,6 +2124,13 @@ const ContextParts = Object.freeze({
|
|
|
1662
2124
|
}
|
|
1663
2125
|
return errors;
|
|
1664
2126
|
},
|
|
2127
|
+
/**
|
|
2128
|
+
* Calls one asynchronous context close action and records failures.
|
|
2129
|
+
*
|
|
2130
|
+
* @param close Close operation.
|
|
2131
|
+
* @param errors Failure collection.
|
|
2132
|
+
* @returns Promise fulfilled after the close attempt is recorded.
|
|
2133
|
+
*/
|
|
1665
2134
|
async closeContextPart(close, errors) {
|
|
1666
2135
|
try {
|
|
1667
2136
|
await close();
|
|
@@ -1670,6 +2139,15 @@ const ContextParts = Object.freeze({
|
|
|
1670
2139
|
ContextParts.collectCloseError(error, errors);
|
|
1671
2140
|
}
|
|
1672
2141
|
},
|
|
2142
|
+
/**
|
|
2143
|
+
* Waits until accepted command, event, System event, and signal work drains.
|
|
2144
|
+
*
|
|
2145
|
+
* @param commandBus Command bus.
|
|
2146
|
+
* @param eventBus Domain event bus.
|
|
2147
|
+
* @param systemEventBus System event bus.
|
|
2148
|
+
* @param publisher Signal publisher.
|
|
2149
|
+
* @returns Promise fulfilled when accepted work stabilizes.
|
|
2150
|
+
*/
|
|
1673
2151
|
async drainContextWork(commandBus, eventBus, systemEventBus, publisher) {
|
|
1674
2152
|
let observedCommandWork = -1;
|
|
1675
2153
|
let observedEventWork = -1;
|
|
@@ -1686,6 +2164,12 @@ const ContextParts = Object.freeze({
|
|
|
1686
2164
|
eventBusAccess.acceptedWorkCount(eventBus) !== observedEventWork ||
|
|
1687
2165
|
eventBusAccess.acceptedWorkCount(systemEventBus) !== observedSystemEventWork);
|
|
1688
2166
|
},
|
|
2167
|
+
/**
|
|
2168
|
+
* Adds an error or aggregate causes to the close error collection.
|
|
2169
|
+
*
|
|
2170
|
+
* @param error Close failure.
|
|
2171
|
+
* @param errors Failure collection.
|
|
2172
|
+
*/
|
|
1689
2173
|
collectCloseError(error, errors) {
|
|
1690
2174
|
if (error instanceof AggregateError) {
|
|
1691
2175
|
const causes = error.errors;
|
|
@@ -1696,6 +2180,13 @@ const ContextParts = Object.freeze({
|
|
|
1696
2180
|
}
|
|
1697
2181
|
errors.push(error);
|
|
1698
2182
|
},
|
|
2183
|
+
/**
|
|
2184
|
+
* Binds a repository to context runtime services and supplies commit and close operations.
|
|
2185
|
+
*
|
|
2186
|
+
* @param repository Repository to prepare.
|
|
2187
|
+
* @param registration Context services and schemas.
|
|
2188
|
+
* @returns Prepared repository.
|
|
2189
|
+
*/
|
|
1699
2190
|
prepareRepositoryForContext(repository, registration) {
|
|
1700
2191
|
ContextParts.requireRepositoryInstance(repository, "BoundedContext repository registration");
|
|
1701
2192
|
const snapshot = ContextParts.repositorySnapshot(repository);
|
|
@@ -1726,6 +2217,12 @@ const ContextParts = Object.freeze({
|
|
|
1726
2217
|
},
|
|
1727
2218
|
};
|
|
1728
2219
|
},
|
|
2220
|
+
/**
|
|
2221
|
+
* Captures repository metadata needed for context registration.
|
|
2222
|
+
*
|
|
2223
|
+
* @param repository Repository to inspect.
|
|
2224
|
+
* @returns Frozen registration snapshot.
|
|
2225
|
+
*/
|
|
1729
2226
|
repositorySnapshot(repository) {
|
|
1730
2227
|
const snapshot = repositoryAccess.snapshot(repository);
|
|
1731
2228
|
return Object.freeze({
|
|
@@ -1738,6 +2235,11 @@ const ContextParts = Object.freeze({
|
|
|
1738
2235
|
snapshot,
|
|
1739
2236
|
});
|
|
1740
2237
|
},
|
|
2238
|
+
/**
|
|
2239
|
+
* Rejects a repository already registered with a bounded context.
|
|
2240
|
+
*
|
|
2241
|
+
* @param repository Repository to check.
|
|
2242
|
+
*/
|
|
1741
2243
|
rejectRegisteredRepository(repository) {
|
|
1742
2244
|
const snapshot = ContextParts.repositorySnapshot(repository);
|
|
1743
2245
|
const registration = registeredRepositories.get(repository);
|
|
@@ -1746,9 +2248,21 @@ const ContextParts = Object.freeze({
|
|
|
1746
2248
|
`"${registration.name.value}".`);
|
|
1747
2249
|
}
|
|
1748
2250
|
},
|
|
2251
|
+
/**
|
|
2252
|
+
* Creates storage record columns from Entity metadata.
|
|
2253
|
+
*
|
|
2254
|
+
* @param snapshot Registration snapshot supplying fields.
|
|
2255
|
+
* @returns Record columns.
|
|
2256
|
+
*/
|
|
1749
2257
|
repositoryColumns(snapshot) {
|
|
1750
2258
|
return snapshot.metadata.columns.map((field) => new RecordColumn(field.name, ColumnTypes.fromField(field.descriptor), (record) => ContextParts.readRecordField(record, field.localName)));
|
|
1751
2259
|
},
|
|
2260
|
+
/**
|
|
2261
|
+
* Creates an immutable repository view from a registration snapshot.
|
|
2262
|
+
*
|
|
2263
|
+
* @param snapshot Registration snapshot.
|
|
2264
|
+
* @returns Immutable repository view.
|
|
2265
|
+
*/
|
|
1752
2266
|
createRepositoryView(snapshot) {
|
|
1753
2267
|
return Object.freeze({
|
|
1754
2268
|
entityType: snapshot.entityType,
|
|
@@ -1760,6 +2274,12 @@ const ContextParts = Object.freeze({
|
|
|
1760
2274
|
snapshot: snapshot.snapshot,
|
|
1761
2275
|
});
|
|
1762
2276
|
},
|
|
2277
|
+
/**
|
|
2278
|
+
* Builds dispatch descriptors for Projection repositories with event handlers.
|
|
2279
|
+
*
|
|
2280
|
+
* @param repositories Repository views to inspect.
|
|
2281
|
+
* @returns Frozen Projection dispatch descriptors.
|
|
2282
|
+
*/
|
|
1763
2283
|
projectionDispatchers(repositories) {
|
|
1764
2284
|
const projections = [];
|
|
1765
2285
|
for (const repository of repositories) {
|
|
@@ -1778,6 +2298,12 @@ const ContextParts = Object.freeze({
|
|
|
1778
2298
|
}
|
|
1779
2299
|
return Object.freeze(projections);
|
|
1780
2300
|
},
|
|
2301
|
+
/**
|
|
2302
|
+
* Collects unique Projection state schemas for clearing.
|
|
2303
|
+
*
|
|
2304
|
+
* @param projections Projection dispatch descriptors.
|
|
2305
|
+
* @returns Frozen unique clear targets.
|
|
2306
|
+
*/
|
|
1781
2307
|
projectionStateClearTargets(projections) {
|
|
1782
2308
|
const unique = new Map();
|
|
1783
2309
|
for (const projection of projections) {
|
|
@@ -1790,6 +2316,13 @@ const ContextParts = Object.freeze({
|
|
|
1790
2316
|
}
|
|
1791
2317
|
return Object.freeze([...unique.values()]);
|
|
1792
2318
|
},
|
|
2319
|
+
/**
|
|
2320
|
+
* Reads stored events in deterministic timestamp, producer, version, and ID order.
|
|
2321
|
+
*
|
|
2322
|
+
* @param context Event-store storage context.
|
|
2323
|
+
* @param storageFactory Event storage provider.
|
|
2324
|
+
* @returns Stored events in deterministic order.
|
|
2325
|
+
*/
|
|
1793
2326
|
async readStoredEvents(context, storageFactory) {
|
|
1794
2327
|
const eventStore = new EventStore(context, storageFactory);
|
|
1795
2328
|
try {
|
|
@@ -1806,12 +2339,24 @@ const ContextParts = Object.freeze({
|
|
|
1806
2339
|
eventStore.close();
|
|
1807
2340
|
}
|
|
1808
2341
|
},
|
|
2342
|
+
/**
|
|
2343
|
+
* Builds Stand options for the catch-up tenant scope.
|
|
2344
|
+
*
|
|
2345
|
+
* @param context Catch-up storage context.
|
|
2346
|
+
* @returns Options with a cloned tenant ID when multitenant.
|
|
2347
|
+
*/
|
|
1809
2348
|
catchUpStandOptions(context) {
|
|
1810
2349
|
if (!context.multitenant) {
|
|
1811
2350
|
return {};
|
|
1812
2351
|
}
|
|
1813
2352
|
return Object.freeze({ tenantId: clone(TenantIdSchema, context.tenantId) });
|
|
1814
2353
|
},
|
|
2354
|
+
/**
|
|
2355
|
+
* Verifies a stored event envelope belongs to the catch-up tenant.
|
|
2356
|
+
*
|
|
2357
|
+
* @param context Catch-up storage context.
|
|
2358
|
+
* @param event Stored event to validate.
|
|
2359
|
+
*/
|
|
1815
2360
|
validateReplayTenant(context, event) {
|
|
1816
2361
|
if (!context.multitenant) {
|
|
1817
2362
|
return;
|
|
@@ -1825,6 +2370,12 @@ const ContextParts = Object.freeze({
|
|
|
1825
2370
|
throw new Error("Read-side catch-up stored event envelope tenant does not match.");
|
|
1826
2371
|
}
|
|
1827
2372
|
},
|
|
2373
|
+
/**
|
|
2374
|
+
* Reads the tenant ID from an event import or past-message origin.
|
|
2375
|
+
*
|
|
2376
|
+
* @param event Event whose origin is inspected.
|
|
2377
|
+
* @returns Cloned tenant ID when present.
|
|
2378
|
+
*/
|
|
1828
2379
|
readReplayTenant(event) {
|
|
1829
2380
|
switch (event.context?.origin.case) {
|
|
1830
2381
|
case "importContext":
|
|
@@ -1835,9 +2386,23 @@ const ContextParts = Object.freeze({
|
|
|
1835
2386
|
return undefined;
|
|
1836
2387
|
}
|
|
1837
2388
|
},
|
|
2389
|
+
/**
|
|
2390
|
+
* Copies a tenant ID when present.
|
|
2391
|
+
*
|
|
2392
|
+
* @param tenantId Optional tenant ID.
|
|
2393
|
+
* @returns Cloned tenant ID or undefined.
|
|
2394
|
+
*/
|
|
1838
2395
|
tenantIdValue(tenantId) {
|
|
1839
2396
|
return tenantId === undefined ? undefined : clone(TenantIdSchema, tenantId);
|
|
1840
2397
|
},
|
|
2398
|
+
/**
|
|
2399
|
+
* Dispatches a stored event to matching Projection handlers and direct updates.
|
|
2400
|
+
*
|
|
2401
|
+
* @param projections Projection dispatch descriptors.
|
|
2402
|
+
* @param event Stored event.
|
|
2403
|
+
* @param rebuild Whether direct dispatch is a rebuild.
|
|
2404
|
+
* @returns One when a Projection matched, otherwise zero.
|
|
2405
|
+
*/
|
|
1841
2406
|
async dispatchStoredProjectionEvent(projections, event, rebuild) {
|
|
1842
2407
|
const typeUrl = event.message?.typeUrl;
|
|
1843
2408
|
if (typeUrl === undefined || typeUrl === "") {
|
|
@@ -1852,12 +2417,32 @@ const ContextParts = Object.freeze({
|
|
|
1852
2417
|
}
|
|
1853
2418
|
return matching.length > 0 ? 1 : 0;
|
|
1854
2419
|
},
|
|
2420
|
+
/**
|
|
2421
|
+
* Creates a replay error with the event ID and bounded cause details.
|
|
2422
|
+
*
|
|
2423
|
+
* @param event Event that failed.
|
|
2424
|
+
* @param cause Thrown replay cause.
|
|
2425
|
+
* @returns Replay error.
|
|
2426
|
+
*/
|
|
1855
2427
|
catchUpReplayError(event, cause) {
|
|
1856
2428
|
return new CatchUpReplayError(event.id?.value ?? "(missing)", ContextParts.catchUpReplayDetail(cause));
|
|
1857
2429
|
},
|
|
2430
|
+
/**
|
|
2431
|
+
* Returns an error string truncated to a maximum length.
|
|
2432
|
+
*
|
|
2433
|
+
* @param value String to bound.
|
|
2434
|
+
* @param limit Maximum character count.
|
|
2435
|
+
* @returns Bounded string.
|
|
2436
|
+
*/
|
|
1858
2437
|
boundedErrorString(value, limit) {
|
|
1859
2438
|
return value.length <= limit ? value : `${value.slice(0, limit - 3)}...`;
|
|
1860
2439
|
},
|
|
2440
|
+
/**
|
|
2441
|
+
* Converts a thrown value to bounded error name and message details.
|
|
2442
|
+
*
|
|
2443
|
+
* @param error Thrown value.
|
|
2444
|
+
* @returns Immutable bounded details.
|
|
2445
|
+
*/
|
|
1861
2446
|
catchUpReplayDetail(error) {
|
|
1862
2447
|
if (error instanceof Error) {
|
|
1863
2448
|
return Object.freeze({
|
|
@@ -1870,6 +2455,13 @@ const ContextParts = Object.freeze({
|
|
|
1870
2455
|
message: ContextParts.boundedErrorString(String(error), errorDetailLimit),
|
|
1871
2456
|
});
|
|
1872
2457
|
},
|
|
2458
|
+
/**
|
|
2459
|
+
* Reads and requires the Entity ID field from a stored record.
|
|
2460
|
+
*
|
|
2461
|
+
* @param record Protobuf record.
|
|
2462
|
+
* @param snapshot Repository metadata identifying the ID field.
|
|
2463
|
+
* @returns ID field value; throws when missing.
|
|
2464
|
+
*/
|
|
1873
2465
|
readRecordId(record, snapshot) {
|
|
1874
2466
|
const value = ContextParts.readRecordField(record, snapshot.idField.localName);
|
|
1875
2467
|
if (value === undefined || value === null) {
|
|
@@ -1877,6 +2469,13 @@ const ContextParts = Object.freeze({
|
|
|
1877
2469
|
}
|
|
1878
2470
|
return value;
|
|
1879
2471
|
},
|
|
2472
|
+
/**
|
|
2473
|
+
* Reads a field from a Protobuf record by descriptor local name.
|
|
2474
|
+
*
|
|
2475
|
+
* @param record Record message.
|
|
2476
|
+
* @param localName Descriptor local field name.
|
|
2477
|
+
* @returns Field value.
|
|
2478
|
+
*/
|
|
1880
2479
|
readRecordField(record, localName) {
|
|
1881
2480
|
return record[localName];
|
|
1882
2481
|
},
|