@cratis/chronicle 6.25.0 → 6.27.0
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/testing/EventScenario.d.ts.map +1 -1
- package/dist/testing/EventScenario.js +10 -1
- package/dist/testing/EventScenario.js.map +1 -1
- package/dist/testing/InProcessConstraints.d.ts +1 -0
- package/dist/testing/InProcessConstraints.d.ts.map +1 -1
- package/dist/testing/InProcessConstraints.js +100 -31
- package/dist/testing/InProcessConstraints.js.map +1 -1
- package/package.json +1 -1
- package/testing/EventScenario.constraint-composite.spec.ts +343 -0
- package/testing/EventScenario.constraint-cycles.spec.ts +236 -0
- package/testing/EventScenario.constraints.spec.ts +5 -5
- package/testing/EventScenario.fixtures.spec.ts +3 -1
- package/testing/EventScenario.ts +12 -1
- package/testing/InProcessConstraints.ts +102 -30
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventScenario.d.ts","sourceRoot":"","sources":["../../testing/EventScenario.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACxF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAQhE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAUzE,sFAAsF;AACtF,qBAAa,aAAa;IACtB,QAAQ,CAAC,aAAa,EAAE,cAAc,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,yBAAyB,CAAC;IAC1C,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IAExC,YAAY,OAAO,GAAE,oBAAyB,
|
|
1
|
+
{"version":3,"file":"EventScenario.d.ts","sourceRoot":"","sources":["../../testing/EventScenario.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACxF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAQhE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAUzE,sFAAsF;AACtF,qBAAa,aAAa;IACtB,QAAQ,CAAC,aAAa,EAAE,cAAc,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,yBAAyB,CAAC;IAC1C,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IAExC,YAAY,OAAO,GAAE,oBAAyB,EA8F7C;IAED,qDAAqD;IACrD,IAAI,QAAQ,IAAI,SAAS,CAKxB;IACK,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAE1F;IACD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC/F,UAAU,CAAC,MAAM,EAAE,qBAAqB,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAO9F,IAAI,OAAO,IAAI,SAAS,YAAY,EAAE,CAAmE;IACzG,IAAI,cAAc,IAAI,SAAS,aAAa,EAAE,CAA0E;IAExH,+DAA+D;IAC/D,IAAI,IAAI,IAAI;QAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;QAAC,QAAQ,CAAC,cAAc,EAAE,SAAS,aAAa,EAAE,CAAA;KAAE,CAE3G;CACJ"}
|
|
@@ -83,9 +83,18 @@ export class EventScenario {
|
|
|
83
83
|
}
|
|
84
84
|
for (const entry of capture.uniqueConstraint?.eventDefinitions ?? []) {
|
|
85
85
|
const type = eventTypes.find(type => getEventTypeFor(type).id.value === entry.eventTypeId);
|
|
86
|
-
|
|
86
|
+
const schemaTypes = entry.properties.map(property => getEventTypeMetadata(type)?.schema.properties?.[property]?.type ?? '');
|
|
87
|
+
if (entry.properties.length === 1 && !['string', 'boolean'].includes(schemaTypes[0])) {
|
|
87
88
|
throw new UnsupportedEventSequenceOperation('artifacts.constraints', name, 'The constrained property must be a schema-backed string or boolean.');
|
|
88
89
|
}
|
|
90
|
+
// constraints-ignore-casing.json captures folded string keys only.
|
|
91
|
+
if (capture.uniqueConstraint.ignoreCasing && schemaTypes.some(schemaType => schemaType !== 'string')) {
|
|
92
|
+
throw new UnsupportedEventSequenceOperation('artifacts.constraints', name, 'Case-insensitive keys must be schema-backed strings.');
|
|
93
|
+
}
|
|
94
|
+
// constraints-composite.json captures string components only.
|
|
95
|
+
if (entry.properties.length > 1 && schemaTypes.some(schemaType => schemaType !== 'string')) {
|
|
96
|
+
throw new UnsupportedEventSequenceOperation('artifacts.constraints', name, 'Every property of a composite key must be a schema-backed string.');
|
|
97
|
+
}
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventScenario.js","sourceRoot":"","sources":["../../testing/EventScenario.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"EventScenario.js","sourceRoot":"","sources":["../../testing/EventScenario.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ export declare class InProcessConstraints {
|
|
|
8
8
|
private readonly _definitions;
|
|
9
9
|
private readonly _constrainedProperties;
|
|
10
10
|
private readonly _removalTypes;
|
|
11
|
+
private readonly _propertyRemovalTypes;
|
|
11
12
|
constructor(_definitions: ReadonlyMap<string, ConstraintCapture>);
|
|
12
13
|
isRemovalOnlyType(eventTypeId: string): boolean;
|
|
13
14
|
hasRemovalType(eventTypeId: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InProcessConstraints.d.ts","sourceRoot":"","sources":["../../testing/InProcessConstraints.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,IAAI,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AACvG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAEpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAGpF,MAAM,MAAM,uBAAuB,GAAG,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"InProcessConstraints.d.ts","sourceRoot":"","sources":["../../testing/InProcessConstraints.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,IAAI,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AACvG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAEpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAGpF,MAAM,MAAM,uBAAuB,GAAG,4BAA4B,CAAC;AAwCnE,6FAA6F;AAC7F,qBAAa,oBAAoB;IAKjB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAJzC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAkC;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAE3D,YAA6B,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAyEhF;IAED,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE9C;IAED,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAgD;IAE5F,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpE;IAED,yGAAyG;IACzG,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,EAAE,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,uBAAuB,EAAE,CA+FzG;IAED,cAAc,CAAC,SAAS,EAAE,mBAAmB,GAAG,mBAAmB,CAElE;IAED,OAAO,CAAC,WAAW;CAGtB"}
|
|
@@ -16,11 +16,32 @@ function kernelKeyString(value, name) {
|
|
|
16
16
|
function keyHash(value) {
|
|
17
17
|
return createHash('sha256').update(value).digest('hex');
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The kernel key: each declared property's string in declared order, joined with a literal '-'
|
|
21
|
+
* (UniqueConstraintDefinitionExtensions.GetPropertiesAndValues/GetValue at Chronicle 8fe5d30). Not a tuple:
|
|
22
|
+
* ['a-b', 'c'] and ['a', 'b-c'] claim the same key, as constraints-composite.json captures.
|
|
23
|
+
*/
|
|
24
|
+
function claimOf(name, unique, eventTypeId, content) {
|
|
25
|
+
const properties = unique.eventDefinitions.find(entry => entry.eventTypeId === eventTypeId)?.properties;
|
|
26
|
+
if (properties === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
const parts = properties.map(property => ({ property, raw: kernelKeyString(content[property], name) }));
|
|
29
|
+
const joined = parts.map(part => part.raw).join('-');
|
|
30
|
+
if (!unique.ignoreCasing)
|
|
31
|
+
return { key: keyHash(joined), parts };
|
|
32
|
+
// The kernel applies .NET ToLowerInvariant to the joined value. constraints-ignore-casing.json proves
|
|
33
|
+
// only the ASCII key domain, where that is exactly A-Z to a-z; host Unicode case tables are not used.
|
|
34
|
+
if (!/^[\x20-\x7e]*$/.test(joined)) {
|
|
35
|
+
throw new UnsupportedEventSequenceOperation('artifacts.constraints', name, 'Case-insensitive keys outside the ASCII key domain are not fixture-backed.');
|
|
36
|
+
}
|
|
37
|
+
return { key: keyHash(joined.replace(/[A-Z]/g, letter => String.fromCharCode(letter.charCodeAt(0) + 32))), parts };
|
|
38
|
+
}
|
|
19
39
|
/** Narrow, fixture-backed unscoped constraint validation over serialized event snapshots. */
|
|
20
40
|
export class InProcessConstraints {
|
|
21
41
|
_definitions;
|
|
22
42
|
_constrainedProperties = new Map();
|
|
23
43
|
_removalTypes = new Set();
|
|
44
|
+
_propertyRemovalTypes = new Set();
|
|
24
45
|
constructor(_definitions) {
|
|
25
46
|
this._definitions = _definitions;
|
|
26
47
|
const coveredTypes = new Map();
|
|
@@ -34,10 +55,12 @@ export class InProcessConstraints {
|
|
|
34
55
|
}
|
|
35
56
|
if (capture.uniqueConstraint) {
|
|
36
57
|
const unique = capture.uniqueConstraint;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
58
|
+
// constraints-composite.json installs one, two and three flat properties per event type.
|
|
59
|
+
// The kernel keys properties by path with ToDictionary, so duplicate paths are not a key shape.
|
|
60
|
+
if (unique.eventDefinitions.length === 0 || unique.eventDefinitions.some(entry => entry.properties.length < 1 || entry.properties.length > 3 ||
|
|
61
|
+
new Set(entry.properties).size !== entry.properties.length ||
|
|
62
|
+
entry.properties.some(property => !/^[a-z][a-zA-Z0-9]*$/.test(property)))) {
|
|
63
|
+
throw this.unsupported(name, 'Only one to three distinct flat properties per event type are fixture-backed; nested, indexed or repeated key paths are not.');
|
|
41
64
|
}
|
|
42
65
|
for (const id of [unique.removedWithEventTypeId, ...(unique.removedWithEventTypeIds ?? [])]) {
|
|
43
66
|
if (id === undefined)
|
|
@@ -48,18 +71,33 @@ export class InProcessConstraints {
|
|
|
48
71
|
}
|
|
49
72
|
removalOwners.set(id, name);
|
|
50
73
|
this._removalTypes.add(id);
|
|
74
|
+
this._propertyRemovalTypes.add(id);
|
|
51
75
|
}
|
|
52
76
|
for (const entry of unique.eventDefinitions) {
|
|
53
77
|
const properties = this._constrainedProperties.get(entry.eventTypeId) ?? new Set();
|
|
54
|
-
properties.add(
|
|
78
|
+
entry.properties.forEach(property => properties.add(property));
|
|
55
79
|
this._constrainedProperties.set(entry.eventTypeId, properties);
|
|
56
80
|
}
|
|
57
81
|
}
|
|
58
82
|
else if (capture.uniqueEventType) {
|
|
59
83
|
const unique = capture.uniqueEventType;
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
const ids = unique.eventTypeIds ?? [unique.eventTypeId];
|
|
85
|
+
const removals = unique.removedWithEventTypeIds ?? [];
|
|
86
|
+
const shared = ids.filter(id => removals.includes(id)).length;
|
|
87
|
+
// Exactly the shapes installed by the pinned fixtures: one covered type without removal
|
|
88
|
+
// (constraints.json), two covered types with two separate removers
|
|
89
|
+
// (constraints-event-type-siblings.json), and three covered types with three removers of
|
|
90
|
+
// which one is also covered (constraints-event-type-cycles.json).
|
|
91
|
+
if (!(ids.length === 1 && removals.length === 0) &&
|
|
92
|
+
!(ids.length === 2 && removals.length === 2 && shared === 0) &&
|
|
93
|
+
!(ids.length === 3 && removals.length === 3 && shared === 1)) {
|
|
94
|
+
throw this.unsupported(name, 'This unique event type set and removal combination is not fixture-backed.');
|
|
95
|
+
}
|
|
96
|
+
// Each cycle fixture installs its definition alone; interaction with other definitions is unproven.
|
|
97
|
+
if (removals.length && _definitions.size !== 1) {
|
|
98
|
+
throw this.unsupported(name, 'Unique event cycles alongside other definitions are not fixture-backed.');
|
|
62
99
|
}
|
|
100
|
+
removals.forEach(id => this._removalTypes.add(id));
|
|
63
101
|
}
|
|
64
102
|
else {
|
|
65
103
|
throw this.unsupported(name, 'Unknown constraint definition.');
|
|
@@ -84,7 +122,7 @@ export class InProcessConstraints {
|
|
|
84
122
|
}
|
|
85
123
|
}
|
|
86
124
|
isRemovalOnlyType(eventTypeId) {
|
|
87
|
-
return this.
|
|
125
|
+
return this._propertyRemovalTypes.has(eventTypeId) && !this._constrainedProperties.has(eventTypeId);
|
|
88
126
|
}
|
|
89
127
|
hasRemovalType(eventTypeId) { return this._removalTypes.has(eventTypeId); }
|
|
90
128
|
isConstrainedProperty(eventTypeId, property) {
|
|
@@ -109,36 +147,39 @@ export class InProcessConstraints {
|
|
|
109
147
|
claims.delete(source);
|
|
110
148
|
continue;
|
|
111
149
|
}
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
150
|
+
const properties = unique.eventDefinitions.find(entry => entry.eventTypeId === type)?.properties;
|
|
151
|
+
if (properties === undefined)
|
|
114
152
|
continue;
|
|
115
|
-
|
|
116
|
-
if (typeof value !== 'string' && typeof value !== 'boolean')
|
|
153
|
+
if (properties.some(property => !['string', 'boolean'].includes(typeof prior.content[property]))) {
|
|
117
154
|
throw this.unsupported(name, 'History has an unproven key.');
|
|
118
|
-
|
|
155
|
+
}
|
|
156
|
+
claims.set(source, { key: claimOf(name, unique, type, prior.content).key,
|
|
119
157
|
sequence: prior.context.sequenceNumber.toString() });
|
|
120
158
|
}
|
|
121
159
|
owners.set(name, claims);
|
|
122
160
|
}
|
|
123
161
|
const stagedKeys = new Map();
|
|
124
|
-
const
|
|
162
|
+
const stagedCycles = new Map();
|
|
125
163
|
for (const event of incoming) {
|
|
126
164
|
const type = event.eventType.id.value;
|
|
127
165
|
const source = event.context.eventSourceId;
|
|
166
|
+
const violationCount = violations.length;
|
|
128
167
|
for (const [name, definition] of this._definitions) {
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
131
|
-
const
|
|
132
|
-
const key = keyHash(raw);
|
|
168
|
+
const claim = definition.uniqueConstraint && claimOf(name, definition.uniqueConstraint, type, event.content);
|
|
169
|
+
if (claim) {
|
|
170
|
+
const key = claim.key;
|
|
133
171
|
const existing = [...owners.get(name).entries()].find(([, claim]) => claim.key === key);
|
|
134
172
|
const batchClaims = stagedKeys.get(name) ?? new Map();
|
|
135
173
|
const batchOwner = batchClaims.get(key);
|
|
136
174
|
if (existing && existing[0] !== source || batchOwner !== undefined && batchOwner !== source) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
175
|
+
// One violation per declared property, in declared order, each with its own original value.
|
|
176
|
+
const sequence = existing?.[1].sequence ?? unavailable;
|
|
177
|
+
for (const { property, raw } of claim.parts) {
|
|
178
|
+
violations.push({ EventTypeId: type, SequenceNumber: BigInt(sequence), ConstraintType: 1,
|
|
179
|
+
ConstraintName: name,
|
|
180
|
+
Message: `Event '${type}' on member '${property}' violated a unique constraint on sequence number ${sequence}`,
|
|
181
|
+
Details: { PropertyName: property, PropertyValue: raw } });
|
|
182
|
+
}
|
|
142
183
|
}
|
|
143
184
|
else {
|
|
144
185
|
batchClaims.set(key, source);
|
|
@@ -147,17 +188,45 @@ export class InProcessConstraints {
|
|
|
147
188
|
}
|
|
148
189
|
const uniqueType = definition.uniqueEventType;
|
|
149
190
|
if (uniqueType && (uniqueType.eventTypeIds ?? [uniqueType.eventTypeId]).includes(type)) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
191
|
+
const covered = uniqueType.eventTypeIds ?? [uniqueType.eventTypeId];
|
|
192
|
+
const removals = uniqueType.removedWithEventTypeIds ?? [];
|
|
193
|
+
// Durable answer: the earliest covered event after the latest removal for this source.
|
|
194
|
+
let first;
|
|
195
|
+
for (const prior of history) {
|
|
196
|
+
if (prior.context.eventSourceId !== source)
|
|
197
|
+
continue;
|
|
198
|
+
const priorType = prior.eventType.id.value;
|
|
199
|
+
if (removals.includes(priorType))
|
|
200
|
+
first = undefined;
|
|
201
|
+
else if (!first && covered.includes(priorType))
|
|
202
|
+
first = prior;
|
|
203
|
+
}
|
|
204
|
+
const cycles = stagedCycles.get(name) ?? new Map();
|
|
205
|
+
const state = cycles.get(source);
|
|
206
|
+
// A cycle state recorded earlier in this append overrides durable history.
|
|
207
|
+
if (state === 'open' || state === undefined && first) {
|
|
208
|
+
// Durable storage supplies the sequence even when a staged claim causes rejection.
|
|
209
|
+
const sequence = first?.context.sequenceNumber.toString() ?? unavailable;
|
|
210
|
+
violations.push({ EventTypeId: type, SequenceNumber: BigInt(sequence), ConstraintType: 2,
|
|
155
211
|
ConstraintName: name,
|
|
156
|
-
Message: `Event '${type}' violated a unique event type constraint on sequence number ${
|
|
212
|
+
Message: `Event '${type}' violated a unique event type constraint on sequence number ${sequence}`,
|
|
157
213
|
Details: {} });
|
|
158
214
|
}
|
|
159
|
-
else
|
|
160
|
-
|
|
215
|
+
else {
|
|
216
|
+
cycles.set(source, 'open');
|
|
217
|
+
stagedCycles.set(name, cycles);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Kernel batch observers release a cycle only after the entire event validates.
|
|
222
|
+
// A covered-and-removal event first attempts its claim, then releases it.
|
|
223
|
+
if (violations.length === violationCount) {
|
|
224
|
+
for (const [name, definition] of this._definitions) {
|
|
225
|
+
if (definition.uniqueEventType?.removedWithEventTypeIds?.includes(type)) {
|
|
226
|
+
const cycles = stagedCycles.get(name) ?? new Map();
|
|
227
|
+
cycles.set(source, 'released');
|
|
228
|
+
stagedCycles.set(name, cycles);
|
|
229
|
+
}
|
|
161
230
|
}
|
|
162
231
|
}
|
|
163
232
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InProcessConstraints.js","sourceRoot":"","sources":["../../testing/InProcessConstraints.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InProcessConstraints.js","sourceRoot":"","sources":["../../testing/InProcessConstraints.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import 'reflect-metadata';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { chai, describe, it, vi } from 'vitest';
|
|
7
|
+
import { field } from '@cratis/fundamentals';
|
|
8
|
+
import { eventType, getEventTypeMetadata } from '../events/eventTypeDecorator.js';
|
|
9
|
+
import { constraint } from '../events/constraints/constraint.js';
|
|
10
|
+
import { compileConstraints } from '../events/constraints/Constraints.js';
|
|
11
|
+
import type { IConstraint } from '../events/constraints/IConstraint.js';
|
|
12
|
+
import type { IConstraintBuilder } from '../events/constraints/IConstraintBuilder.js';
|
|
13
|
+
import { EventScenario, UnsupportedEventSequenceOperation } from './index.js';
|
|
14
|
+
import { InProcessConstraints } from './InProcessConstraints.js';
|
|
15
|
+
import { ReactorScenario } from './ReactorScenario.js';
|
|
16
|
+
import { reactor } from '../reactors/reactor.js';
|
|
17
|
+
|
|
18
|
+
chai.should();
|
|
19
|
+
class OracleDomainText { @field(String) key: string; constructor(key: string) { this.key = key; } }
|
|
20
|
+
eventType('OracleDomainText')(OracleDomainText);
|
|
21
|
+
class OracleDomainRemoved { @field(String) label: string; constructor(label: string) { this.label = label; } }
|
|
22
|
+
eventType('OracleDomainRemoved')(OracleDomainRemoved);
|
|
23
|
+
class OracleCompositeName {
|
|
24
|
+
@field(String) first: string; @field(String) last: string;
|
|
25
|
+
constructor(first: string, last: string) { this.first = first; this.last = last; }
|
|
26
|
+
}
|
|
27
|
+
eventType('OracleCompositeName')(OracleCompositeName);
|
|
28
|
+
class OracleCompositeAlias {
|
|
29
|
+
@field(String) given: string; @field(String) family: string;
|
|
30
|
+
constructor(given: string, family: string) { this.given = given; this.family = family; }
|
|
31
|
+
}
|
|
32
|
+
eventType('OracleCompositeAlias')(OracleCompositeAlias);
|
|
33
|
+
class OracleCompositeTriple {
|
|
34
|
+
@field(String) first: string; @field(String) middle: string; @field(String) last: string;
|
|
35
|
+
constructor(first: string, middle: string, last: string) { this.first = first; this.middle = middle; this.last = last; }
|
|
36
|
+
}
|
|
37
|
+
eventType('OracleCompositeTriple')(OracleCompositeTriple);
|
|
38
|
+
|
|
39
|
+
// Declared order differs from field order on the alias type: family, then given.
|
|
40
|
+
class FullName implements IConstraint {
|
|
41
|
+
define(builder: IConstraintBuilder) {
|
|
42
|
+
builder.unique(key => key.on(OracleCompositeName, event => event.first, event => event.last)
|
|
43
|
+
.on(OracleCompositeAlias, event => event.family, event => event.given)
|
|
44
|
+
.on(OracleDomainText, event => event.key)
|
|
45
|
+
.removedWith(OracleDomainRemoved).withMessage('Taken: {PropertyName}={PropertyValue}'));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
constraint('OracleFullName')(FullName);
|
|
49
|
+
class Triple implements IConstraint {
|
|
50
|
+
define(builder: IConstraintBuilder) {
|
|
51
|
+
builder.unique(key => key.on(OracleCompositeTriple, event => event.first, event => event.middle, event => event.last));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
constraint('OracleTriple')(Triple);
|
|
55
|
+
class Handle implements IConstraint {
|
|
56
|
+
define(builder: IConstraintBuilder) {
|
|
57
|
+
builder.unique(key => key.on(OracleDomainText, event => event.key).ignoreCasing()
|
|
58
|
+
.removedWith(OracleDomainRemoved).withMessage('Handle taken: {PropertyValue}'));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
constraint('OracleHandle')(Handle);
|
|
62
|
+
class CasedName implements IConstraint {
|
|
63
|
+
define(builder: IConstraintBuilder) {
|
|
64
|
+
builder.unique(key => key.on(OracleCompositeName, event => event.first, event => event.last).ignoreCasing());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
constraint('OracleCasedName')(CasedName);
|
|
68
|
+
|
|
69
|
+
const aliases = { string: OracleDomainText, removed: OracleDomainRemoved, name: OracleCompositeName,
|
|
70
|
+
alias: OracleCompositeAlias, triple: OracleCompositeTriple };
|
|
71
|
+
type Alias = keyof typeof aliases;
|
|
72
|
+
type Input = { source: string; type: Alias; value?: string; values?: Record<string, string> };
|
|
73
|
+
type History = { sequence: string; source: string; sourceType: string; streamType: string; streamId: string;
|
|
74
|
+
type: string; content: Record<string, string>; hash: string };
|
|
75
|
+
type Violation = { EventTypeId: string; SequenceNumber: string; ConstraintType: number; ConstraintName: string;
|
|
76
|
+
Message: string; Details: Record<string, string> };
|
|
77
|
+
type Outcome = { success: boolean; sequences: string[]; violations: Array<{ id: string; message: string; details: Record<string, string> }>;
|
|
78
|
+
wireViolations: Violation[]; errors: string[]; history: History[]; next: string };
|
|
79
|
+
type Fixture = { eventSchemas: Record<string, { eventTypeId: string; properties: Record<string, string> }>;
|
|
80
|
+
constraintDefinitions: Array<{ kind: string; name: string; events: Array<{ type: string; properties: string[] }>;
|
|
81
|
+
ignoreCasing: boolean; removedWithEventTypeIds: string[]; message: string }>;
|
|
82
|
+
isolatedConstraintOperations: Array<{ mode: 'single' | 'batch'; events: Input[] }>;
|
|
83
|
+
expected: { outcomes: Outcome[] } };
|
|
84
|
+
const aliasById = Object.fromEntries(Object.entries(aliases).map(([alias, type]) => [getEventTypeMetadata(type)!.eventType.id.value, alias]));
|
|
85
|
+
const makeEvent = ({ type, value, values }: Input): object => {
|
|
86
|
+
switch (type) {
|
|
87
|
+
case 'string': return new OracleDomainText(value!);
|
|
88
|
+
case 'removed': return new OracleDomainRemoved(value!);
|
|
89
|
+
case 'name': return new OracleCompositeName(values!.first, values!.last);
|
|
90
|
+
case 'alias': return new OracleCompositeAlias(values!.given, values!.family);
|
|
91
|
+
case 'triple': return new OracleCompositeTriple(values!.first, values!.middle, values!.last);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const historyOf = (scenario: EventScenario): History[] => scenario.appendedEvents.map(entry => ({
|
|
95
|
+
sequence: entry.context.sequenceNumber.toString(), source: entry.context.eventSourceId,
|
|
96
|
+
sourceType: entry.context.eventSourceType, streamType: entry.context.eventStreamType, streamId: entry.context.eventStreamId,
|
|
97
|
+
type: entry.eventType.id.value, content: entry.content as Record<string, string>, hash: entry.context.hash
|
|
98
|
+
}));
|
|
99
|
+
const unsupported = async (action: () => unknown, operation: string, reason: string) => {
|
|
100
|
+
await Promise.resolve().then(action).then(() => { throw new Error('Expected rejection'); }, error => {
|
|
101
|
+
(error instanceof UnsupportedEventSequenceOperation).should.be.true;
|
|
102
|
+
(error as Error).message.should.include(`UnsupportedEventSequenceOperation: ${operation}:`);
|
|
103
|
+
(error as Error).message.should.include(reason);
|
|
104
|
+
(error as Error).message.should.include('Use a kernel-backed test.');
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
const compositeTypes = Object.values(aliases);
|
|
108
|
+
const shapeReason = 'Only one to three distinct flat properties per event type are fixture-backed; nested, indexed or repeated key paths are not.';
|
|
109
|
+
|
|
110
|
+
async function matchesFixture(name: string, selected: readonly Function[], constraints: readonly Function[]) {
|
|
111
|
+
const fixture = JSON.parse(readFileSync(new URL(`./fixtures/${name}.json`, import.meta.url), 'utf8')) as Fixture;
|
|
112
|
+
const ids = new Set(selected);
|
|
113
|
+
Object.fromEntries(Object.entries(aliases).filter(([, type]) => ids.has(type)).map(([alias, type]) => {
|
|
114
|
+
const metadata = getEventTypeMetadata(type)!;
|
|
115
|
+
return [alias, { eventTypeId: metadata.eventType.id.value,
|
|
116
|
+
properties: Object.fromEntries(Object.entries(metadata.schema.properties ?? {}).map(([key, property]) => [key, property.type])) }];
|
|
117
|
+
})).should.deep.equal(fixture.eventSchemas);
|
|
118
|
+
const compiled = compileConstraints({ eventTypes: [...selected], constraints: [...constraints] });
|
|
119
|
+
[...compiled].map(([key, capture]) => ({ kind: 'uniqueProperty', name: key,
|
|
120
|
+
events: capture.uniqueConstraint!.eventDefinitions.map(entry => ({ type: aliasById[entry.eventTypeId], properties: entry.properties })),
|
|
121
|
+
ignoreCasing: capture.uniqueConstraint!.ignoreCasing,
|
|
122
|
+
removedWithEventTypeIds: (capture.uniqueConstraint!.removedWithEventTypeIds ?? []).map(id => aliasById[id]),
|
|
123
|
+
message: capture.uniqueConstraint!.message ?? ''
|
|
124
|
+
})).should.deep.equal(fixture.constraintDefinitions);
|
|
125
|
+
fixture.isolatedConstraintOperations.length.should.be.greaterThan(0);
|
|
126
|
+
fixture.expected.outcomes.length.should.equal(fixture.isolatedConstraintOperations.length);
|
|
127
|
+
fixture.expected.outcomes.some(outcome => !outcome.success).should.be.true;
|
|
128
|
+
const scenario = new EventScenario({ artifacts: { eventTypes: [...selected], constraints: [...constraints] } });
|
|
129
|
+
const notifications = scenario.eventSequence.appendOperations[Symbol.asyncIterator]();
|
|
130
|
+
const validate = vi.spyOn(InProcessConstraints.prototype, 'validate');
|
|
131
|
+
let count = 0;
|
|
132
|
+
try {
|
|
133
|
+
for (const [index, operation] of fixture.isolatedConstraintOperations.entries()) {
|
|
134
|
+
const expected = fixture.expected.outcomes[index];
|
|
135
|
+
const before = historyOf(scenario);
|
|
136
|
+
const calls = validate.mock.calls.length;
|
|
137
|
+
const nextNotification = notifications.next();
|
|
138
|
+
const results = operation.mode === 'single'
|
|
139
|
+
? [await scenario.append(operation.events[0].source, makeEvent(operation.events[0]))]
|
|
140
|
+
: await scenario.appendMany(operation.events.map(entry => ({ eventSourceId: entry.source, event: makeEvent(entry) })));
|
|
141
|
+
const notification = (await nextNotification).value!;
|
|
142
|
+
validate.mock.calls.length.should.equal(calls + 1);
|
|
143
|
+
const raw = validate.mock.results.at(-1)?.value as ReturnType<InProcessConstraints['validate']>;
|
|
144
|
+
raw.map(({ SequenceNumber, ...violation }) => ({ ...violation, SequenceNumber: SequenceNumber.toString() }))
|
|
145
|
+
.should.deep.equal(expected.wireViolations);
|
|
146
|
+
notification.length.should.equal(operation.events.length);
|
|
147
|
+
notification.every(item => item.result.isSuccess === expected.success).should.be.true;
|
|
148
|
+
count += operation.events.length;
|
|
149
|
+
scenario.results.length.should.equal(count);
|
|
150
|
+
results.length.should.equal(operation.events.length);
|
|
151
|
+
for (const [position, result] of results.entries()) {
|
|
152
|
+
result.isSuccess.should.equal(expected.success);
|
|
153
|
+
result.sequenceNumber.value.toString().should.equal(expected.success ? expected.sequences[position] : '0');
|
|
154
|
+
result.constraintViolations.map(item => ({ id: item.constraintId, message: item.message, details: item.details }))
|
|
155
|
+
.should.deep.equal(expected.violations);
|
|
156
|
+
result.errors.map(item => item.message).should.deep.equal(expected.errors);
|
|
157
|
+
}
|
|
158
|
+
if (!expected.success) historyOf(scenario).should.deep.equal(before);
|
|
159
|
+
historyOf(scenario).should.deep.equal(expected.history);
|
|
160
|
+
(await scenario.eventSequence.getNextSequenceNumber()).value.toString().should.equal(expected.next);
|
|
161
|
+
}
|
|
162
|
+
} finally {
|
|
163
|
+
validate.mockRestore();
|
|
164
|
+
await notifications.return?.();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
describe('fixture-backed composite unique-property keys', () => {
|
|
169
|
+
it('matches the pinned kernel constraints-composite: declared order, dash joining, per-property violations, hashes and rollback',
|
|
170
|
+
() => matchesFixture('constraints-composite', compositeTypes, [FullName, Triple]));
|
|
171
|
+
|
|
172
|
+
it('rejects a repeated key path at the key-shape check, while the distinct shape is accepted', async () => {
|
|
173
|
+
class Repeated implements IConstraint {
|
|
174
|
+
define(builder: IConstraintBuilder) {
|
|
175
|
+
builder.unique(key => key.on(OracleCompositeName, event => event.first, event => event.first));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
constraint('OracleRepeatedPath')(Repeated);
|
|
179
|
+
class Distinct implements IConstraint {
|
|
180
|
+
define(builder: IConstraintBuilder) {
|
|
181
|
+
builder.unique(key => key.on(OracleCompositeName, event => event.first, event => event.last));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
constraint('OracleRepeatedPath')(Distinct);
|
|
185
|
+
new EventScenario({ artifacts: { eventTypes: [OracleCompositeName], constraints: [Distinct] } }).should.be.instanceOf(EventScenario);
|
|
186
|
+
await unsupported(() => new EventScenario({ artifacts: { eventTypes: [OracleCompositeName], constraints: [Repeated] } }),
|
|
187
|
+
'artifacts.constraints (OracleRepeatedPath)', shapeReason);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('rejects four key properties at the key-shape check, while three are accepted', async () => {
|
|
191
|
+
class Quad {
|
|
192
|
+
@field(String) a = 'a'; @field(String) b = 'b'; @field(String) c = 'c'; @field(String) d = 'd';
|
|
193
|
+
}
|
|
194
|
+
eventType('OracleCompositeQuad')(Quad);
|
|
195
|
+
class Four implements IConstraint {
|
|
196
|
+
define(builder: IConstraintBuilder) {
|
|
197
|
+
builder.unique(key => key.on(Quad, event => event.a, event => event.b, event => event.c, event => event.d));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
constraint('OracleQuad')(Four);
|
|
201
|
+
class Three implements IConstraint {
|
|
202
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(Quad, event => event.a, event => event.b, event => event.c)); }
|
|
203
|
+
}
|
|
204
|
+
constraint('OracleQuad')(Three);
|
|
205
|
+
new EventScenario({ artifacts: { eventTypes: [Quad], constraints: [Three] } }).should.be.instanceOf(EventScenario);
|
|
206
|
+
await unsupported(() => new EventScenario({ artifacts: { eventTypes: [Quad], constraints: [Four] } }),
|
|
207
|
+
'artifacts.constraints (OracleQuad)', shapeReason);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('rejects nested and indexed key paths at the key-shape check, while the flat path is accepted', async () => {
|
|
211
|
+
// The event schema must stay flat to pass the schema check; the accessors reach the paths 'a.b' and 'items.0'.
|
|
212
|
+
class Nested { @field(String) a = 'a'; }
|
|
213
|
+
eventType('OracleCompositeNested')(Nested);
|
|
214
|
+
type Shaped = { a: { b: string }; items: string[] };
|
|
215
|
+
class Deep implements IConstraint {
|
|
216
|
+
define(builder: IConstraintBuilder) {
|
|
217
|
+
builder.unique(key => key.on(Nested, event => event.a, event => (event as unknown as Shaped).a.b));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
constraint('OracleNestedPath')(Deep);
|
|
221
|
+
class Indexed implements IConstraint {
|
|
222
|
+
define(builder: IConstraintBuilder) {
|
|
223
|
+
builder.unique(key => key.on(Nested, event => event.a, event => (event as unknown as Shaped).items[0]));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
constraint('OracleNestedPath')(Indexed);
|
|
227
|
+
class Flat implements IConstraint {
|
|
228
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(Nested, event => event.a)); }
|
|
229
|
+
}
|
|
230
|
+
constraint('OracleNestedPath')(Flat);
|
|
231
|
+
new EventScenario({ artifacts: { eventTypes: [Nested], constraints: [Flat] } }).should.be.instanceOf(EventScenario);
|
|
232
|
+
await unsupported(() => new EventScenario({ artifacts: { eventTypes: [Nested], constraints: [Deep] } }),
|
|
233
|
+
'artifacts.constraints (OracleNestedPath)', shapeReason);
|
|
234
|
+
await unsupported(() => new EventScenario({ artifacts: { eventTypes: [Nested], constraints: [Indexed] } }),
|
|
235
|
+
'artifacts.constraints (OracleNestedPath)', shapeReason);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('rejects a composite with a boolean component, while the boolean alone is accepted', async () => {
|
|
239
|
+
class Mixed { @field(String) label = 'x'; @field(Boolean) active = true; }
|
|
240
|
+
eventType('OracleCompositeMixed')(Mixed);
|
|
241
|
+
class Composite implements IConstraint {
|
|
242
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(Mixed, event => event.label, event => event.active)); }
|
|
243
|
+
}
|
|
244
|
+
constraint('OracleMixed')(Composite);
|
|
245
|
+
class Single implements IConstraint {
|
|
246
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(Mixed, event => event.active)); }
|
|
247
|
+
}
|
|
248
|
+
constraint('OracleMixed')(Single);
|
|
249
|
+
new EventScenario({ artifacts: { eventTypes: [Mixed], constraints: [Single] } }).should.be.instanceOf(EventScenario);
|
|
250
|
+
await unsupported(() => new EventScenario({ artifacts: { eventTypes: [Mixed], constraints: [Composite] } }),
|
|
251
|
+
'artifacts.constraints (OracleMixed)', 'Every property of a composite key must be a schema-backed string.');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('rejects an unproven component value in a batch and leaves history, results and claims unchanged', async () => {
|
|
255
|
+
const scenario = new EventScenario({ artifacts: { eventTypes: compositeTypes, constraints: [FullName, Triple] } });
|
|
256
|
+
(await scenario.append('A', new OracleCompositeName('Ada', 'Lovelace'))).isSuccess.should.be.true;
|
|
257
|
+
const before = historyOf(scenario);
|
|
258
|
+
const results = [...scenario.results];
|
|
259
|
+
await unsupported(() => scenario.appendMany([{ eventSourceId: 'B', event: new OracleCompositeName('Grace', 'Hopper') },
|
|
260
|
+
{ eventSourceId: 'C', event: new OracleCompositeName('Ada', 'Love!') }]), 'artifacts.constraints (OracleFullName)',
|
|
261
|
+
'Only fixture-backed scalar string and boolean keys are supported.');
|
|
262
|
+
historyOf(scenario).should.deep.equal(before);
|
|
263
|
+
scenario.results.should.deep.equal(results);
|
|
264
|
+
(await scenario.append('D', new OracleCompositeName('Grace', 'Hopper'))).isSuccess.should.be.true;
|
|
265
|
+
(await scenario.append('E', new OracleCompositeName('Ada', 'Lovelace'))).constraintViolations
|
|
266
|
+
.map(item => item.message).should.deep.equal(['Taken: first=Ada', 'Taken: last=Lovelace']);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('rolls back a setup call that ends in a delimiter collision', async () => {
|
|
270
|
+
const scenario = new EventScenario({ artifacts: { eventTypes: compositeTypes, constraints: [FullName, Triple] } });
|
|
271
|
+
await scenario.given.forEventSource('A').events(new OracleCompositeName('a-b', 'c'));
|
|
272
|
+
await scenario.given.forEventSource('B').events(new OracleCompositeName('x', 'y'), new OracleCompositeName('a', 'b-c'))
|
|
273
|
+
.then(() => { throw new Error('Invalid setup succeeded'); }, error => {
|
|
274
|
+
(error as Error).message.should.include('EventScenario given setup failed');
|
|
275
|
+
});
|
|
276
|
+
historyOf(scenario).map(entry => entry.source).should.deep.equal(['A']);
|
|
277
|
+
(await scenario.append('C', new OracleCompositeName('x', 'y'))).isSuccess.should.be.true;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('delivers only committed composite claims to a reactor', async () => {
|
|
281
|
+
const calls: string[] = [];
|
|
282
|
+
@reactor('composite-reactor')
|
|
283
|
+
class CompositeReactor {
|
|
284
|
+
oracleCompositeName(event: OracleCompositeName) { calls.push(`${event.first}/${event.last}`); }
|
|
285
|
+
oracleCompositeAlias(event: OracleCompositeAlias) { calls.push(`${event.family}/${event.given}`); }
|
|
286
|
+
}
|
|
287
|
+
const subject = new ReactorScenario(CompositeReactor, { artifacts: { eventTypes: compositeTypes, constraints: [FullName, Triple] } });
|
|
288
|
+
await subject.when.forEventSource('A').events(new OracleCompositeName('Ada', 'Lovelace'));
|
|
289
|
+
await subject.when.forEventSource('B').events(new OracleCompositeAlias('Lovelace', 'Ada'))
|
|
290
|
+
.then(() => { throw new Error('Colliding alias delivered'); }, error => {
|
|
291
|
+
(error as Error).message.should.include('ReactorScenario action append failed');
|
|
292
|
+
});
|
|
293
|
+
await subject.when.forEventSource('B').events(new OracleCompositeAlias('Ada', 'Lovelace'));
|
|
294
|
+
calls.should.deep.equal(['Ada/Lovelace', 'Lovelace/Ada']);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('fixture-backed ASCII case-insensitive unique-property keys', () => {
|
|
299
|
+
const casingTypes = [OracleDomainText, OracleDomainRemoved, OracleCompositeName];
|
|
300
|
+
const asciiReason = 'Case-insensitive keys outside the ASCII key domain are not fixture-backed.';
|
|
301
|
+
|
|
302
|
+
it('matches the pinned kernel constraints-ignore-casing: ASCII folding, original-cased details, ownership, hashes and rollback',
|
|
303
|
+
() => matchesFixture('constraints-ignore-casing', casingTypes, [Handle, CasedName]));
|
|
304
|
+
|
|
305
|
+
it('rejects a non-ASCII key under ignoreCasing before mutation, while the case-sensitive definition accepts it', async () => {
|
|
306
|
+
const sensitive = new EventScenario({ artifacts: { eventTypes: compositeTypes, constraints: [FullName, Triple] } });
|
|
307
|
+
(await sensitive.append('A', new OracleCompositeName('Zo\u00e9', 'X'))).isSuccess.should.be.true;
|
|
308
|
+
const scenario = new EventScenario({ artifacts: { eventTypes: casingTypes, constraints: [Handle, CasedName] } });
|
|
309
|
+
(await scenario.append('A', new OracleDomainText('Alice'))).isSuccess.should.be.true;
|
|
310
|
+
const before = historyOf(scenario);
|
|
311
|
+
const results = [...scenario.results];
|
|
312
|
+
const notifications = scenario.eventSequence.appendOperations[Symbol.asyncIterator]();
|
|
313
|
+
try {
|
|
314
|
+
const notification = notifications.next();
|
|
315
|
+
await unsupported(() => scenario.append('B', new OracleDomainText('Zo\u00e9')), 'artifacts.constraints (OracleHandle)', asciiReason);
|
|
316
|
+
await unsupported(() => scenario.appendMany([{ eventSourceId: 'C', event: new OracleDomainText('Bob') },
|
|
317
|
+
{ eventSourceId: 'D', event: new OracleCompositeName('zo\u00e9', 'x') }]), 'artifacts.constraints (OracleCasedName)', asciiReason);
|
|
318
|
+
await unsupported(() => scenario.append('E', new OracleCompositeName('Zo\u00e9', 'X')), 'artifacts.constraints (OracleCasedName)', asciiReason);
|
|
319
|
+
historyOf(scenario).should.deep.equal(before);
|
|
320
|
+
scenario.results.should.deep.equal(results);
|
|
321
|
+
(await scenario.append('C', new OracleDomainText('BOB'))).isSuccess.should.be.true;
|
|
322
|
+
(await notification).value!.map(item => item.event.content).should.deep.equal([{ key: 'BOB' }]);
|
|
323
|
+
} finally { await notifications.return?.(); }
|
|
324
|
+
(await scenario.append('F', new OracleDomainText('bob'))).constraintViolations.map(item => item.message)
|
|
325
|
+
.should.deep.equal(['Handle taken: bob']);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('rejects ignoreCasing on a boolean key, while the case-sensitive boolean key is accepted', async () => {
|
|
329
|
+
class Flagged { @field(Boolean) active = true; }
|
|
330
|
+
eventType('OracleCasedFlag')(Flagged);
|
|
331
|
+
class Folded implements IConstraint {
|
|
332
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(Flagged, event => event.active).ignoreCasing()); }
|
|
333
|
+
}
|
|
334
|
+
constraint('OracleCasedFlag')(Folded);
|
|
335
|
+
class Exact implements IConstraint {
|
|
336
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(Flagged, event => event.active)); }
|
|
337
|
+
}
|
|
338
|
+
constraint('OracleCasedFlag')(Exact);
|
|
339
|
+
new EventScenario({ artifacts: { eventTypes: [Flagged], constraints: [Exact] } }).should.be.instanceOf(EventScenario);
|
|
340
|
+
await unsupported(() => new EventScenario({ artifacts: { eventTypes: [Flagged], constraints: [Folded] } }),
|
|
341
|
+
'artifacts.constraints (OracleCasedFlag)', 'Case-insensitive keys must be schema-backed strings.');
|
|
342
|
+
});
|
|
343
|
+
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import 'reflect-metadata';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { chai, describe, it, vi } from 'vitest';
|
|
7
|
+
import { field } from '@cratis/fundamentals';
|
|
8
|
+
import { eventType, getEventTypeMetadata } from '../events/eventTypeDecorator.js';
|
|
9
|
+
import { constraint } from '../events/constraints/constraint.js';
|
|
10
|
+
import { compileConstraints } from '../events/constraints/Constraints.js';
|
|
11
|
+
import type { IConstraint } from '../events/constraints/IConstraint.js';
|
|
12
|
+
import type { IConstraintBuilder } from '../events/constraints/IConstraintBuilder.js';
|
|
13
|
+
import { removeConstraint } from '../events/constraints/removeConstraint.js';
|
|
14
|
+
import { EventScenario, UnsupportedEventSequenceOperation } from './index.js';
|
|
15
|
+
import { InProcessConstraints } from './InProcessConstraints.js';
|
|
16
|
+
import { ReactorScenario } from './ReactorScenario.js';
|
|
17
|
+
import { reactor } from '../reactors/reactor.js';
|
|
18
|
+
|
|
19
|
+
chai.should();
|
|
20
|
+
class OracleCycleFirst { @field(String) label: string; constructor(label: string) { this.label = label; } }
|
|
21
|
+
eventType('OracleCycleFirst')(OracleCycleFirst);
|
|
22
|
+
class OracleCycleSibling { @field(String) label: string; constructor(label: string) { this.label = label; } }
|
|
23
|
+
eventType('OracleCycleSibling')(OracleCycleSibling);
|
|
24
|
+
class OracleCycleRemoved { @field(String) label: string; constructor(label: string) { this.label = label; } }
|
|
25
|
+
eventType('OracleCycleRemoved')(OracleCycleRemoved);
|
|
26
|
+
removeConstraint('OracleCycle')(OracleCycleRemoved);
|
|
27
|
+
class OracleCycleExpired { @field(String) label: string; constructor(label: string) { this.label = label; } }
|
|
28
|
+
eventType('OracleCycleExpired')(OracleCycleExpired);
|
|
29
|
+
removeConstraint('OracleCycle')(OracleCycleExpired);
|
|
30
|
+
class OracleCycleRenewed { @field(String) label: string; constructor(label: string) { this.label = label; } }
|
|
31
|
+
eventType('OracleCycleRenewed')(OracleCycleRenewed);
|
|
32
|
+
removeConstraint('OracleCycle')(OracleCycleRenewed);
|
|
33
|
+
// Same-named uniqueFor declarations merge into one definition covering the whole set.
|
|
34
|
+
class CycleFirst implements IConstraint {
|
|
35
|
+
define(builder: IConstraintBuilder) { builder.uniqueFor(OracleCycleFirst, 'Cycle occupied', 'OracleCycle'); }
|
|
36
|
+
}
|
|
37
|
+
constraint('CycleFirst')(CycleFirst);
|
|
38
|
+
class CycleSibling implements IConstraint {
|
|
39
|
+
define(builder: IConstraintBuilder) { builder.uniqueFor(OracleCycleSibling, undefined, 'OracleCycle'); }
|
|
40
|
+
}
|
|
41
|
+
constraint('CycleSibling')(CycleSibling);
|
|
42
|
+
class CycleRenewed implements IConstraint {
|
|
43
|
+
define(builder: IConstraintBuilder) { builder.uniqueFor(OracleCycleRenewed, undefined, 'OracleCycle'); }
|
|
44
|
+
}
|
|
45
|
+
constraint('CycleRenewed')(CycleRenewed);
|
|
46
|
+
|
|
47
|
+
const aliases = { first: OracleCycleFirst, sibling: OracleCycleSibling, cycleRemoved: OracleCycleRemoved,
|
|
48
|
+
cycleExpired: OracleCycleExpired, renewed: OracleCycleRenewed };
|
|
49
|
+
type Alias = keyof typeof aliases;
|
|
50
|
+
type Input = { source: string; type: Alias; value: string };
|
|
51
|
+
type History = { sequence: string; source: string; sourceType: string; streamType: string; streamId: string;
|
|
52
|
+
type: string; content: Record<string, string>; hash: string };
|
|
53
|
+
type Violation = { EventTypeId: string; SequenceNumber: string; ConstraintType: number; ConstraintName: string;
|
|
54
|
+
Message: string; Details: Record<string, string> };
|
|
55
|
+
type Outcome = { success: boolean; sequences: string[]; violations: Array<{ id: string; message: string; details: Record<string, string> }>;
|
|
56
|
+
wireViolations: Violation[]; errors: string[]; history: History[]; next: string };
|
|
57
|
+
type Fixture = { eventSchemas: Record<string, { eventTypeId: string; properties: Record<string, string> }>;
|
|
58
|
+
constraintDefinitions: Array<{ kind: string; name: string; eventTypes: string[]; removedWithEventTypeIds: string[]; message: string }>;
|
|
59
|
+
isolatedConstraintOperations: Array<{ mode: 'single' | 'batch'; events: Input[] }>;
|
|
60
|
+
expected: { outcomes: Outcome[] } };
|
|
61
|
+
const aliasById = Object.fromEntries(Object.entries(aliases).map(([alias, type]) => [getEventTypeMetadata(type)!.eventType.id.value, alias]));
|
|
62
|
+
const makeEvent = ({ type, value }: Input): object => new aliases[type](value);
|
|
63
|
+
const historyOf = (scenario: EventScenario): History[] => scenario.appendedEvents.map(entry => ({
|
|
64
|
+
sequence: entry.context.sequenceNumber.toString(), source: entry.context.eventSourceId,
|
|
65
|
+
sourceType: entry.context.eventSourceType, streamType: entry.context.eventStreamType, streamId: entry.context.eventStreamId,
|
|
66
|
+
type: entry.eventType.id.value, content: entry.content as Record<string, string>, hash: entry.context.hash
|
|
67
|
+
}));
|
|
68
|
+
const unsupported = async (action: () => unknown, operation: string, reason: string) => {
|
|
69
|
+
await Promise.resolve().then(action).then(() => { throw new Error('Expected rejection'); }, error => {
|
|
70
|
+
(error instanceof UnsupportedEventSequenceOperation).should.be.true;
|
|
71
|
+
(error as Error).message.should.include(`UnsupportedEventSequenceOperation: ${operation}:`);
|
|
72
|
+
(error as Error).message.should.include(reason);
|
|
73
|
+
(error as Error).message.should.include('Use a kernel-backed test.');
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
const siblingTypes = [OracleCycleFirst, OracleCycleSibling, OracleCycleRemoved, OracleCycleExpired];
|
|
77
|
+
const cycleTypes = [...siblingTypes, OracleCycleRenewed];
|
|
78
|
+
const shapeReason = 'This unique event type set and removal combination is not fixture-backed.';
|
|
79
|
+
|
|
80
|
+
describe('fixture-backed unique event type cycles', () => {
|
|
81
|
+
for (const [name, selected, constraints] of [
|
|
82
|
+
['constraints-event-type-siblings', siblingTypes, [CycleFirst, CycleSibling]],
|
|
83
|
+
['constraints-event-type-cycles', cycleTypes, [CycleFirst, CycleSibling, CycleRenewed]]
|
|
84
|
+
] as const) {
|
|
85
|
+
it(`matches the pinned kernel ${name}: raw and mapped violations, cycles, hashes and rollback`, async () => {
|
|
86
|
+
const fixture = JSON.parse(readFileSync(new URL(`./fixtures/${name}.json`, import.meta.url), 'utf8')) as Fixture;
|
|
87
|
+
Object.fromEntries(selected.map(type => {
|
|
88
|
+
const metadata = getEventTypeMetadata(type)!;
|
|
89
|
+
return [aliasById[metadata.eventType.id.value], { eventTypeId: metadata.eventType.id.value,
|
|
90
|
+
properties: Object.fromEntries(Object.entries(metadata.schema.properties ?? {}).map(([key, property]) => [key, property.type])) }];
|
|
91
|
+
})).should.deep.equal(fixture.eventSchemas);
|
|
92
|
+
const compiled = compileConstraints({ eventTypes: [...selected], constraints: [...constraints] });
|
|
93
|
+
[...compiled].map(([key, capture]) => ({ kind: 'uniqueEventType', name: key,
|
|
94
|
+
eventTypes: capture.uniqueEventType!.eventTypeIds!.map(id => aliasById[id]),
|
|
95
|
+
removedWithEventTypeIds: capture.uniqueEventType!.removedWithEventTypeIds!.map(id => aliasById[id]),
|
|
96
|
+
message: capture.uniqueEventType!.message ?? ''
|
|
97
|
+
})).should.deep.equal(fixture.constraintDefinitions);
|
|
98
|
+
fixture.isolatedConstraintOperations.length.should.be.greaterThan(0);
|
|
99
|
+
fixture.expected.outcomes.length.should.equal(fixture.isolatedConstraintOperations.length);
|
|
100
|
+
fixture.expected.outcomes.some(outcome => !outcome.success).should.be.true;
|
|
101
|
+
const scenario = new EventScenario({ artifacts: { eventTypes: [...selected], constraints: [...constraints] } });
|
|
102
|
+
const notifications = scenario.eventSequence.appendOperations[Symbol.asyncIterator]();
|
|
103
|
+
const validate = vi.spyOn(InProcessConstraints.prototype, 'validate');
|
|
104
|
+
let count = 0;
|
|
105
|
+
try {
|
|
106
|
+
for (const [index, operation] of fixture.isolatedConstraintOperations.entries()) {
|
|
107
|
+
const expected = fixture.expected.outcomes[index];
|
|
108
|
+
const before = historyOf(scenario);
|
|
109
|
+
const calls = validate.mock.calls.length;
|
|
110
|
+
const nextNotification = notifications.next();
|
|
111
|
+
const results = operation.mode === 'single'
|
|
112
|
+
? [await scenario.append(operation.events[0].source, makeEvent(operation.events[0]))]
|
|
113
|
+
: await scenario.appendMany(operation.events.map(entry => ({ eventSourceId: entry.source, event: makeEvent(entry) })));
|
|
114
|
+
const notification = (await nextNotification).value!;
|
|
115
|
+
validate.mock.calls.length.should.equal(calls + 1);
|
|
116
|
+
const raw = validate.mock.results.at(-1)?.value as ReturnType<InProcessConstraints['validate']>;
|
|
117
|
+
raw.map(({ SequenceNumber, ...violation }) => ({ ...violation, SequenceNumber: SequenceNumber.toString() }))
|
|
118
|
+
.should.deep.equal(expected.wireViolations);
|
|
119
|
+
notification.length.should.equal(operation.events.length);
|
|
120
|
+
notification.every(item => item.result.isSuccess === expected.success).should.be.true;
|
|
121
|
+
count += operation.events.length;
|
|
122
|
+
scenario.results.length.should.equal(count);
|
|
123
|
+
results.length.should.equal(operation.events.length);
|
|
124
|
+
for (const [position, result] of results.entries()) {
|
|
125
|
+
result.isSuccess.should.equal(expected.success);
|
|
126
|
+
result.sequenceNumber.value.toString().should.equal(expected.success ? expected.sequences[position] : '0');
|
|
127
|
+
result.constraintViolations.map(item => ({ id: item.constraintId, message: item.message, details: item.details }))
|
|
128
|
+
.should.deep.equal(expected.violations);
|
|
129
|
+
result.errors.map(item => item.message).should.deep.equal(expected.errors);
|
|
130
|
+
}
|
|
131
|
+
if (!expected.success) historyOf(scenario).should.deep.equal(before);
|
|
132
|
+
historyOf(scenario).should.deep.equal(expected.history);
|
|
133
|
+
(await scenario.eventSequence.getNextSequenceNumber()).value.toString().should.equal(expected.next);
|
|
134
|
+
}
|
|
135
|
+
} finally {
|
|
136
|
+
validate.mockRestore();
|
|
137
|
+
await notifications.return?.();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
it('rolls back a release and reclaim in a failing setup call without results or notifications', async () => {
|
|
143
|
+
const scenario = new EventScenario({ artifacts: { eventTypes: siblingTypes, constraints: [CycleFirst, CycleSibling] } });
|
|
144
|
+
const notifications = scenario.eventSequence.appendOperations[Symbol.asyncIterator]();
|
|
145
|
+
try {
|
|
146
|
+
const first = notifications.next();
|
|
147
|
+
await scenario.given.forEventSource('A').events(new OracleCycleFirst('claim'));
|
|
148
|
+
(await first).value!.length.should.equal(1);
|
|
149
|
+
const before = historyOf(scenario);
|
|
150
|
+
await scenario.given.forEventSource('A').events(new OracleCycleRemoved('release'), new OracleCycleFirst('reopen'),
|
|
151
|
+
new OracleCycleSibling('blocked'))
|
|
152
|
+
.then(() => { throw new Error('Invalid setup succeeded'); }, error => {
|
|
153
|
+
(error as Error).message.should.include('EventScenario given setup failed');
|
|
154
|
+
});
|
|
155
|
+
historyOf(scenario).should.deep.equal(before);
|
|
156
|
+
scenario.results.length.should.equal(0);
|
|
157
|
+
(await scenario.eventSequence.getNextSequenceNumber()).value.should.equal(1n);
|
|
158
|
+
const next = notifications.next();
|
|
159
|
+
const blocked = await scenario.append('A', new OracleCycleSibling('still held'));
|
|
160
|
+
blocked.constraintViolations.map(item => item.constraintId).should.deep.equal(['OracleCycle']);
|
|
161
|
+
(await next).value!.map(item => item.event.eventType.id.value).should.deep.equal(['OracleCycleSibling']);
|
|
162
|
+
} finally { await notifications.return?.(); }
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('delivers only committed claim and release events to a reactor', async () => {
|
|
166
|
+
const calls: string[] = [];
|
|
167
|
+
@reactor('cycle-reactor')
|
|
168
|
+
class CycleReactor {
|
|
169
|
+
oracleCycleFirst(event: OracleCycleFirst) { calls.push(event.label); }
|
|
170
|
+
oracleCycleSibling(event: OracleCycleSibling) { calls.push(event.label); }
|
|
171
|
+
oracleCycleExpired(event: OracleCycleExpired) { calls.push(event.label); }
|
|
172
|
+
}
|
|
173
|
+
const subject = new ReactorScenario(CycleReactor,
|
|
174
|
+
{ artifacts: { eventTypes: siblingTypes, constraints: [CycleFirst, CycleSibling] } });
|
|
175
|
+
await subject.when.forEventSource('A').events(new OracleCycleFirst('claim'));
|
|
176
|
+
await subject.when.forEventSource('A').events(new OracleCycleSibling('blocked'))
|
|
177
|
+
.then(() => { throw new Error('Occupied cycle delivered'); }, error => {
|
|
178
|
+
(error as Error).message.should.include('ReactorScenario action append failed');
|
|
179
|
+
});
|
|
180
|
+
calls.should.deep.equal(['claim']);
|
|
181
|
+
subject.results.length.should.equal(1);
|
|
182
|
+
await subject.when.forEventSource('A').events(new OracleCycleExpired('release'), new OracleCycleSibling('reclaim'));
|
|
183
|
+
calls.should.deep.equal(['claim', 'release', 'reclaim']);
|
|
184
|
+
subject.results.length.should.equal(2);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('rejects unsupported input in a batch and leaves history and results unchanged', async () => {
|
|
188
|
+
const scenario = new EventScenario({ artifacts: { eventTypes: siblingTypes, constraints: [CycleFirst, CycleSibling] } });
|
|
189
|
+
(await scenario.append('A', new OracleCycleFirst('claim'))).isSuccess.should.be.true;
|
|
190
|
+
const before = historyOf(scenario);
|
|
191
|
+
const results = scenario.results;
|
|
192
|
+
await unsupported(() => scenario.appendMany([{ eventSourceId: 'A', event: new OracleCycleExpired('release') },
|
|
193
|
+
{ eventSourceId: 'B!', event: new OracleCycleFirst('claim') }]), 'appendMany.source (B!)',
|
|
194
|
+
'Only simple source identifiers are fixture-backed.');
|
|
195
|
+
historyOf(scenario).should.deep.equal(before);
|
|
196
|
+
scenario.results.should.deep.equal(results);
|
|
197
|
+
const blocked = await scenario.append('A', new OracleCycleSibling('still held'));
|
|
198
|
+
blocked.isSuccess.should.be.false;
|
|
199
|
+
blocked.constraintViolations.map(item => item.message).should.deep.equal(['Cycle occupied']);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('rejects covered and removal type shapes that no fixture installs, at the shape check', async () => {
|
|
203
|
+
await unsupported(() => new EventScenario({ artifacts: {
|
|
204
|
+
eventTypes: [OracleCycleFirst, OracleCycleSibling, OracleCycleRemoved], constraints: [CycleFirst, CycleSibling]
|
|
205
|
+
} }), 'artifacts.constraints (OracleCycle)', shapeReason);
|
|
206
|
+
await unsupported(() => new EventScenario({ artifacts: {
|
|
207
|
+
eventTypes: [OracleCycleFirst, OracleCycleRemoved, OracleCycleRenewed], constraints: [CycleFirst, CycleRenewed]
|
|
208
|
+
} }), 'artifacts.constraints (OracleCycle)', shapeReason);
|
|
209
|
+
await unsupported(() => new EventScenario({ artifacts: {
|
|
210
|
+
eventTypes: [OracleCycleFirst, OracleCycleSibling, OracleCycleRemoved, OracleCycleRenewed],
|
|
211
|
+
constraints: [CycleFirst, CycleSibling, CycleRenewed]
|
|
212
|
+
} }), 'artifacts.constraints (OracleCycle)', shapeReason);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('rejects a fixture-shaped cycle installed alongside another definition', async () => {
|
|
216
|
+
class OtherOnce { @field(String) label = 'other'; }
|
|
217
|
+
eventType('OracleCycleOtherOnce')(OtherOnce);
|
|
218
|
+
class OtherOnceConstraint implements IConstraint {
|
|
219
|
+
define(builder: IConstraintBuilder) { builder.uniqueFor(OtherOnce, undefined, 'OracleCycleOther'); }
|
|
220
|
+
}
|
|
221
|
+
constraint('OracleCycleOther')(OtherOnceConstraint);
|
|
222
|
+
await unsupported(() => new EventScenario({ artifacts: {
|
|
223
|
+
eventTypes: [...siblingTypes, OtherOnce], constraints: [CycleFirst, CycleSibling, OtherOnceConstraint]
|
|
224
|
+
} }), 'artifacts.constraints (OracleCycle)', 'Unique event cycles alongside other definitions are not fixture-backed.');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('rejects a fieldless event-type remover at the schema check', async () => {
|
|
228
|
+
class FieldlessRemoval {}
|
|
229
|
+
eventType('OracleCycleFieldless')(FieldlessRemoval);
|
|
230
|
+
removeConstraint('OracleCycle')(FieldlessRemoval);
|
|
231
|
+
await unsupported(() => new EventScenario({ artifacts: {
|
|
232
|
+
eventTypes: [OracleCycleFirst, OracleCycleSibling, OracleCycleRemoved, FieldlessRemoval],
|
|
233
|
+
constraints: [CycleFirst, CycleSibling]
|
|
234
|
+
} }), 'artifacts.eventTypes.schema (FieldlessRemoval)', 'Only flat, unclassified string/boolean fields are fixture-backed.');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -172,7 +172,7 @@ describe('fixture-backed unscoped constraints', () => {
|
|
|
172
172
|
const production = compileConstraints({ eventTypes: [GlobalOnce, SelectedOnce], constraints: [] });
|
|
173
173
|
production.get('ScenarioSharedOnce')!.uniqueEventType!.eventTypeIds.should.deep.equal(['GlobalOnce', 'SelectedOnce']);
|
|
174
174
|
unsupported(() => new EventScenario({ artifacts: { eventTypes: [SelectedOnce] } }), 'artifacts.constraints')
|
|
175
|
-
.message.should.include('
|
|
175
|
+
.message.should.include('This unique event type set and removal combination is not fixture-backed.');
|
|
176
176
|
});
|
|
177
177
|
|
|
178
178
|
it('rejects removal of a selected unique-event-type constraint at construction', () => {
|
|
@@ -183,8 +183,8 @@ describe('fixture-backed unscoped constraints', () => {
|
|
|
183
183
|
eventType('OnceNameRemoval')(Removal);
|
|
184
184
|
removeConstraint('OnceName')(Removal);
|
|
185
185
|
const message = unsupported(() => new EventScenario({ artifacts: { eventTypes: [Once, Removal] } }), 'artifacts.constraints').message;
|
|
186
|
-
message.should.include('
|
|
187
|
-
message.should.include('
|
|
186
|
+
message.should.include('OnceName');
|
|
187
|
+
message.should.include('This unique event type set and removal combination is not fixture-backed.');
|
|
188
188
|
});
|
|
189
189
|
|
|
190
190
|
it('rejects a global removal decorator targeting a selected constraint', () => {
|
|
@@ -367,14 +367,14 @@ describe('fixture-backed unscoped constraints', () => {
|
|
|
367
367
|
scoped(builder => builder.perEventStreamType().unique(key => key.on(OracleKeyClaimed, event => event.key)));
|
|
368
368
|
scoped(builder => builder.perEventSourceType().unique(key => key.on(OracleKeyClaimed, event => event.key)));
|
|
369
369
|
scoped(builder => builder.unique(key => key.on(OracleKeyClaimed, event => event.key, event => event.key)));
|
|
370
|
-
scoped(builder => builder.unique(key => key.on(OracleKeyClaimed, event => event.key).ignoreCasing()));
|
|
370
|
+
scoped(builder => builder.perEventStreamId().unique(key => key.on(OracleKeyClaimed, event => event.key).ignoreCasing()));
|
|
371
371
|
scoped(builder => builder.unique(key => key.on(OracleKeyClaimed, event => event.key))
|
|
372
372
|
.uniqueFor(OracleOnceRecorded));
|
|
373
373
|
class AnotherOnce { @field(String) label = 'second'; }
|
|
374
374
|
eventType('AnotherOnce')(AnotherOnce);
|
|
375
375
|
unique('OracleOnce')(AnotherOnce);
|
|
376
376
|
unsupported(() => new EventScenario({ artifacts: { eventTypes: [...types, AnotherOnce], constraints: [OracleFluentOnceConstraint] } }), 'artifacts.constraints')
|
|
377
|
-
.message.should.include('
|
|
377
|
+
.message.should.include('This unique event type set and removal combination is not fixture-backed.');
|
|
378
378
|
const subject = scenario();
|
|
379
379
|
await subject.append('A', new OracleKeyClaimed('Alpha'));
|
|
380
380
|
await subject.append('B', new OracleKeyClaimed('beta'));
|
|
@@ -57,7 +57,9 @@ interface Fixture {
|
|
|
57
57
|
const directory = new URL('./fixtures/', import.meta.url);
|
|
58
58
|
const fixtures = readdirSync(directory).filter(name => name.endsWith('.json') &&
|
|
59
59
|
!['batches.json', 'batch-omitted-routes.json', 'batch-rollback.json', 'builders.json', 'constraints.json', 'constraints-isolation.json', 'constraints-key-domain.json',
|
|
60
|
-
'constraints-property-lifecycle.json', 'constraints-property-covered-removal.json'
|
|
60
|
+
'constraints-property-lifecycle.json', 'constraints-property-covered-removal.json', 'constraints-event-type-cycles.json',
|
|
61
|
+
'constraints-event-type-siblings.json', 'constraints-composite.json',
|
|
62
|
+
'constraints-ignore-casing.json'].includes(name)).map(name => ({
|
|
61
63
|
name, fixture: JSON.parse(readFileSync(new URL(name, directory), 'utf8')) as Fixture
|
|
62
64
|
}));
|
|
63
65
|
const artifacts = { eventTypes: [OracleEventRecorded, AlternateRecorded, BoundaryRecorded], constraints: [] };
|
package/testing/EventScenario.ts
CHANGED
|
@@ -98,10 +98,21 @@ export class EventScenario {
|
|
|
98
98
|
}
|
|
99
99
|
for (const entry of capture.uniqueConstraint?.eventDefinitions ?? []) {
|
|
100
100
|
const type = eventTypes.find(type => getEventTypeFor(type).id.value === entry.eventTypeId)!;
|
|
101
|
-
|
|
101
|
+
const schemaTypes = entry.properties.map(property => getEventTypeMetadata(type)?.schema.properties?.[property]?.type ?? '');
|
|
102
|
+
if (entry.properties.length === 1 && !['string', 'boolean'].includes(schemaTypes[0])) {
|
|
102
103
|
throw new UnsupportedEventSequenceOperation('artifacts.constraints', name,
|
|
103
104
|
'The constrained property must be a schema-backed string or boolean.');
|
|
104
105
|
}
|
|
106
|
+
// constraints-ignore-casing.json captures folded string keys only.
|
|
107
|
+
if (capture.uniqueConstraint!.ignoreCasing && schemaTypes.some(schemaType => schemaType !== 'string')) {
|
|
108
|
+
throw new UnsupportedEventSequenceOperation('artifacts.constraints', name,
|
|
109
|
+
'Case-insensitive keys must be schema-backed strings.');
|
|
110
|
+
}
|
|
111
|
+
// constraints-composite.json captures string components only.
|
|
112
|
+
if (entry.properties.length > 1 && schemaTypes.some(schemaType => schemaType !== 'string')) {
|
|
113
|
+
throw new UnsupportedEventSequenceOperation('artifacts.constraints', name,
|
|
114
|
+
'Every property of a composite key must be a schema-backed string.');
|
|
115
|
+
}
|
|
105
116
|
}
|
|
106
117
|
}
|
|
107
118
|
} catch (error) {
|
|
@@ -26,10 +26,34 @@ function keyHash(value: string): string {
|
|
|
26
26
|
return createHash('sha256').update(value).digest('hex');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
type UniqueCapture = NonNullable<ConstraintCapture['uniqueConstraint']>;
|
|
30
|
+
type Claim = { key: string; parts: Array<{ property: string; raw: string }> };
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The kernel key: each declared property's string in declared order, joined with a literal '-'
|
|
34
|
+
* (UniqueConstraintDefinitionExtensions.GetPropertiesAndValues/GetValue at Chronicle 8fe5d30). Not a tuple:
|
|
35
|
+
* ['a-b', 'c'] and ['a', 'b-c'] claim the same key, as constraints-composite.json captures.
|
|
36
|
+
*/
|
|
37
|
+
function claimOf(name: string, unique: UniqueCapture, eventTypeId: string, content: Record<string, unknown>): Claim | undefined {
|
|
38
|
+
const properties = unique.eventDefinitions.find(entry => entry.eventTypeId === eventTypeId)?.properties;
|
|
39
|
+
if (properties === undefined) return undefined;
|
|
40
|
+
const parts = properties.map(property => ({ property, raw: kernelKeyString(content[property], name) }));
|
|
41
|
+
const joined = parts.map(part => part.raw).join('-');
|
|
42
|
+
if (!unique.ignoreCasing) return { key: keyHash(joined), parts };
|
|
43
|
+
// The kernel applies .NET ToLowerInvariant to the joined value. constraints-ignore-casing.json proves
|
|
44
|
+
// only the ASCII key domain, where that is exactly A-Z to a-z; host Unicode case tables are not used.
|
|
45
|
+
if (!/^[\x20-\x7e]*$/.test(joined)) {
|
|
46
|
+
throw new UnsupportedEventSequenceOperation('artifacts.constraints', name,
|
|
47
|
+
'Case-insensitive keys outside the ASCII key domain are not fixture-backed.');
|
|
48
|
+
}
|
|
49
|
+
return { key: keyHash(joined.replace(/[A-Z]/g, letter => String.fromCharCode(letter.charCodeAt(0) + 32))), parts };
|
|
50
|
+
}
|
|
51
|
+
|
|
29
52
|
/** Narrow, fixture-backed unscoped constraint validation over serialized event snapshots. */
|
|
30
53
|
export class InProcessConstraints {
|
|
31
54
|
private readonly _constrainedProperties = new Map<string, Set<string>>();
|
|
32
55
|
private readonly _removalTypes = new Set<string>();
|
|
56
|
+
private readonly _propertyRemovalTypes = new Set<string>();
|
|
33
57
|
|
|
34
58
|
constructor(private readonly _definitions: ReadonlyMap<string, ConstraintCapture>) {
|
|
35
59
|
const coveredTypes = new Map<string, string>();
|
|
@@ -43,10 +67,13 @@ export class InProcessConstraints {
|
|
|
43
67
|
}
|
|
44
68
|
if (capture.uniqueConstraint) {
|
|
45
69
|
const unique = capture.uniqueConstraint;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
// constraints-composite.json installs one, two and three flat properties per event type.
|
|
71
|
+
// The kernel keys properties by path with ToDictionary, so duplicate paths are not a key shape.
|
|
72
|
+
if (unique.eventDefinitions.length === 0 || unique.eventDefinitions.some(entry =>
|
|
73
|
+
entry.properties.length < 1 || entry.properties.length > 3 ||
|
|
74
|
+
new Set(entry.properties).size !== entry.properties.length ||
|
|
75
|
+
entry.properties.some(property => !/^[a-z][a-zA-Z0-9]*$/.test(property)))) {
|
|
76
|
+
throw this.unsupported(name, 'Only one to three distinct flat properties per event type are fixture-backed; nested, indexed or repeated key paths are not.');
|
|
50
77
|
}
|
|
51
78
|
for (const id of [unique.removedWithEventTypeId, ...(unique.removedWithEventTypeIds ?? [])]) {
|
|
52
79
|
if (id === undefined) continue;
|
|
@@ -56,17 +83,32 @@ export class InProcessConstraints {
|
|
|
56
83
|
}
|
|
57
84
|
removalOwners.set(id, name);
|
|
58
85
|
this._removalTypes.add(id);
|
|
86
|
+
this._propertyRemovalTypes.add(id);
|
|
59
87
|
}
|
|
60
88
|
for (const entry of unique.eventDefinitions) {
|
|
61
89
|
const properties = this._constrainedProperties.get(entry.eventTypeId) ?? new Set<string>();
|
|
62
|
-
properties.add(
|
|
90
|
+
entry.properties.forEach(property => properties.add(property));
|
|
63
91
|
this._constrainedProperties.set(entry.eventTypeId, properties);
|
|
64
92
|
}
|
|
65
93
|
} else if (capture.uniqueEventType) {
|
|
66
94
|
const unique = capture.uniqueEventType;
|
|
67
|
-
|
|
68
|
-
|
|
95
|
+
const ids = unique.eventTypeIds ?? [unique.eventTypeId];
|
|
96
|
+
const removals = unique.removedWithEventTypeIds ?? [];
|
|
97
|
+
const shared = ids.filter(id => removals.includes(id)).length;
|
|
98
|
+
// Exactly the shapes installed by the pinned fixtures: one covered type without removal
|
|
99
|
+
// (constraints.json), two covered types with two separate removers
|
|
100
|
+
// (constraints-event-type-siblings.json), and three covered types with three removers of
|
|
101
|
+
// which one is also covered (constraints-event-type-cycles.json).
|
|
102
|
+
if (!(ids.length === 1 && removals.length === 0) &&
|
|
103
|
+
!(ids.length === 2 && removals.length === 2 && shared === 0) &&
|
|
104
|
+
!(ids.length === 3 && removals.length === 3 && shared === 1)) {
|
|
105
|
+
throw this.unsupported(name, 'This unique event type set and removal combination is not fixture-backed.');
|
|
69
106
|
}
|
|
107
|
+
// Each cycle fixture installs its definition alone; interaction with other definitions is unproven.
|
|
108
|
+
if (removals.length && _definitions.size !== 1) {
|
|
109
|
+
throw this.unsupported(name, 'Unique event cycles alongside other definitions are not fixture-backed.');
|
|
110
|
+
}
|
|
111
|
+
removals.forEach(id => this._removalTypes.add(id));
|
|
70
112
|
} else {
|
|
71
113
|
throw this.unsupported(name, 'Unknown constraint definition.');
|
|
72
114
|
}
|
|
@@ -89,7 +131,7 @@ export class InProcessConstraints {
|
|
|
89
131
|
}
|
|
90
132
|
|
|
91
133
|
isRemovalOnlyType(eventTypeId: string): boolean {
|
|
92
|
-
return this.
|
|
134
|
+
return this._propertyRemovalTypes.has(eventTypeId) && !this._constrainedProperties.has(eventTypeId);
|
|
93
135
|
}
|
|
94
136
|
|
|
95
137
|
hasRemovalType(eventTypeId: string): boolean { return this._removalTypes.has(eventTypeId); }
|
|
@@ -116,34 +158,38 @@ export class InProcessConstraints {
|
|
|
116
158
|
claims.delete(source);
|
|
117
159
|
continue;
|
|
118
160
|
}
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
161
|
+
const properties = unique.eventDefinitions.find(entry => entry.eventTypeId === type)?.properties;
|
|
162
|
+
if (properties === undefined) continue;
|
|
163
|
+
if (properties.some(property => !['string', 'boolean'].includes(typeof prior.content[property]))) {
|
|
164
|
+
throw this.unsupported(name, 'History has an unproven key.');
|
|
165
|
+
}
|
|
166
|
+
claims.set(source, { key: claimOf(name, unique, type, prior.content)!.key,
|
|
124
167
|
sequence: prior.context.sequenceNumber.toString() });
|
|
125
168
|
}
|
|
126
169
|
owners.set(name, claims);
|
|
127
170
|
}
|
|
128
171
|
const stagedKeys = new Map<string, Map<string, string>>();
|
|
129
|
-
const
|
|
172
|
+
const stagedCycles = new Map<string, Map<string, 'open' | 'released'>>();
|
|
130
173
|
for (const event of incoming) {
|
|
131
174
|
const type = event.eventType.id.value;
|
|
132
175
|
const source = event.context.eventSourceId;
|
|
176
|
+
const violationCount = violations.length;
|
|
133
177
|
for (const [name, definition] of this._definitions) {
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
136
|
-
const
|
|
137
|
-
const key = keyHash(raw);
|
|
178
|
+
const claim = definition.uniqueConstraint && claimOf(name, definition.uniqueConstraint, type, event.content);
|
|
179
|
+
if (claim) {
|
|
180
|
+
const key = claim.key;
|
|
138
181
|
const existing = [...owners.get(name)!.entries()].find(([, claim]) => claim.key === key);
|
|
139
182
|
const batchClaims = stagedKeys.get(name) ?? new Map<string, string>();
|
|
140
183
|
const batchOwner = batchClaims.get(key);
|
|
141
184
|
if (existing && existing[0] !== source || batchOwner !== undefined && batchOwner !== source) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
185
|
+
// One violation per declared property, in declared order, each with its own original value.
|
|
186
|
+
const sequence = existing?.[1].sequence ?? unavailable;
|
|
187
|
+
for (const { property, raw } of claim.parts) {
|
|
188
|
+
violations.push({ EventTypeId: type, SequenceNumber: BigInt(sequence), ConstraintType: 1,
|
|
189
|
+
ConstraintName: name,
|
|
190
|
+
Message: `Event '${type}' on member '${property}' violated a unique constraint on sequence number ${sequence}`,
|
|
191
|
+
Details: { PropertyName: property, PropertyValue: raw } });
|
|
192
|
+
}
|
|
147
193
|
} else {
|
|
148
194
|
batchClaims.set(key, source);
|
|
149
195
|
stagedKeys.set(name, batchClaims);
|
|
@@ -151,15 +197,41 @@ export class InProcessConstraints {
|
|
|
151
197
|
}
|
|
152
198
|
const uniqueType = definition.uniqueEventType;
|
|
153
199
|
if (uniqueType && (uniqueType.eventTypeIds ?? [uniqueType.eventTypeId]).includes(type)) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
200
|
+
const covered = uniqueType.eventTypeIds ?? [uniqueType.eventTypeId];
|
|
201
|
+
const removals = uniqueType.removedWithEventTypeIds ?? [];
|
|
202
|
+
// Durable answer: the earliest covered event after the latest removal for this source.
|
|
203
|
+
let first: AppendedEvent | undefined;
|
|
204
|
+
for (const prior of history) {
|
|
205
|
+
if (prior.context.eventSourceId !== source) continue;
|
|
206
|
+
const priorType = prior.eventType.id.value;
|
|
207
|
+
if (removals.includes(priorType)) first = undefined;
|
|
208
|
+
else if (!first && covered.includes(priorType)) first = prior;
|
|
209
|
+
}
|
|
210
|
+
const cycles = stagedCycles.get(name) ?? new Map<string, 'open' | 'released'>();
|
|
211
|
+
const state = cycles.get(source);
|
|
212
|
+
// A cycle state recorded earlier in this append overrides durable history.
|
|
213
|
+
if (state === 'open' || state === undefined && first) {
|
|
214
|
+
// Durable storage supplies the sequence even when a staged claim causes rejection.
|
|
215
|
+
const sequence = first?.context.sequenceNumber.toString() ?? unavailable;
|
|
216
|
+
violations.push({ EventTypeId: type, SequenceNumber: BigInt(sequence), ConstraintType: 2,
|
|
159
217
|
ConstraintName: name,
|
|
160
|
-
Message: `Event '${type}' violated a unique event type constraint on sequence number ${
|
|
218
|
+
Message: `Event '${type}' violated a unique event type constraint on sequence number ${sequence}`,
|
|
161
219
|
Details: {} });
|
|
162
|
-
} else
|
|
220
|
+
} else {
|
|
221
|
+
cycles.set(source, 'open');
|
|
222
|
+
stagedCycles.set(name, cycles);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Kernel batch observers release a cycle only after the entire event validates.
|
|
227
|
+
// A covered-and-removal event first attempts its claim, then releases it.
|
|
228
|
+
if (violations.length === violationCount) {
|
|
229
|
+
for (const [name, definition] of this._definitions) {
|
|
230
|
+
if (definition.uniqueEventType?.removedWithEventTypeIds?.includes(type)) {
|
|
231
|
+
const cycles = stagedCycles.get(name) ?? new Map<string, 'open' | 'released'>();
|
|
232
|
+
cycles.set(source, 'released');
|
|
233
|
+
stagedCycles.set(name, cycles);
|
|
234
|
+
}
|
|
163
235
|
}
|
|
164
236
|
}
|
|
165
237
|
}
|