@cratis/chronicle 6.18.0 → 6.19.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/IReadModelProcessor.d.ts +7 -0
- package/dist/testing/IReadModelProcessor.d.ts.map +1 -0
- package/dist/testing/IReadModelProcessor.js +4 -0
- package/dist/testing/IReadModelProcessor.js.map +1 -0
- package/dist/testing/ReadModelScenario.d.ts +6 -8
- package/dist/testing/ReadModelScenario.d.ts.map +1 -1
- package/dist/testing/ReadModelScenario.js +50 -50
- package/dist/testing/ReadModelScenario.js.map +1 -1
- package/dist/testing/ReadModelState.d.ts +6 -0
- package/dist/testing/ReadModelState.d.ts.map +1 -0
- package/dist/testing/ReadModelState.js +4 -0
- package/dist/testing/ReadModelState.js.map +1 -0
- package/dist/testing/ReducerReadModelProcessor.d.ts +13 -0
- package/dist/testing/ReducerReadModelProcessor.d.ts.map +1 -0
- package/dist/testing/ReducerReadModelProcessor.js +38 -0
- package/dist/testing/ReducerReadModelProcessor.js.map +1 -0
- package/dist/testing/ScenarioEvent.d.ts +8 -0
- package/dist/testing/ScenarioEvent.d.ts.map +1 -0
- package/dist/testing/ScenarioEvent.js +4 -0
- package/dist/testing/ScenarioEvent.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/projections/ProjectionCapabilities.d.ts +3 -0
- package/dist/testing/projections/ProjectionCapabilities.d.ts.map +1 -1
- package/dist/testing/projections/ProjectionCapabilities.js +117 -43
- package/dist/testing/projections/ProjectionCapabilities.js.map +1 -1
- package/dist/testing/projections/ProjectionExpressionEvaluator.d.ts +9 -0
- package/dist/testing/projections/ProjectionExpressionEvaluator.d.ts.map +1 -0
- package/dist/testing/projections/ProjectionExpressionEvaluator.js +68 -0
- package/dist/testing/projections/ProjectionExpressionEvaluator.js.map +1 -0
- package/dist/testing/projections/ProjectionReadModelProcessor.d.ts +30 -0
- package/dist/testing/projections/ProjectionReadModelProcessor.d.ts.map +1 -0
- package/dist/testing/projections/ProjectionReadModelProcessor.js +142 -0
- package/dist/testing/projections/ProjectionReadModelProcessor.js.map +1 -0
- package/dist/testing/projections/ProjectionValueConverter.d.ts +13 -0
- package/dist/testing/projections/ProjectionValueConverter.d.ts.map +1 -0
- package/dist/testing/projections/ProjectionValueConverter.js +122 -0
- package/dist/testing/projections/ProjectionValueConverter.js.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts +0 -2
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts.map +1 -1
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js +0 -4
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/testing/IReadModelProcessor.ts +10 -0
- package/testing/ReadModelScenario.spec.ts +156 -10
- package/testing/ReadModelScenario.ts +57 -57
- package/testing/ReadModelState.ts +5 -0
- package/testing/ReducerReadModelProcessor.ts +39 -0
- package/testing/ScenarioEvent.ts +7 -0
- package/testing/index.ts +1 -0
- package/testing/projections/ProjectionCapabilities.ts +100 -39
- package/testing/projections/ProjectionExpressionEvaluator.ts +57 -0
- package/testing/projections/ProjectionReadModelProcessor.fixtures.spec.ts +135 -0
- package/testing/projections/ProjectionReadModelProcessor.ts +147 -0
- package/testing/projections/ProjectionValueConverter.spec.ts +81 -0
- package/testing/projections/ProjectionValueConverter.ts +113 -0
- package/testing/projections/for_ProjectionCapabilities/given/Changed.ts +1 -3
- package/testing/projections/for_ProjectionCapabilities/when_rejecting_unsupported_operations.spec.ts +81 -1
- package/testing/projections/for_ProjectionCapabilities/when_validating_numeric_schemas.spec.ts +40 -12
- package/testing/projections/for_ProjectionCapabilities/when_validating_schema_boundaries.spec.ts +76 -29
- package/testing/projections/for_ProjectionCapabilities/when_validating_supported_operations.spec.ts +8 -35
package/testing/projections/for_ProjectionCapabilities/when_validating_numeric_schemas.spec.ts
CHANGED
|
@@ -12,22 +12,31 @@ chai.should();
|
|
|
12
12
|
|
|
13
13
|
describe('when validating the numeric capability matrix', () => {
|
|
14
14
|
for (const format of ['int32', 'uint32']) {
|
|
15
|
-
it(`should accept ${format}
|
|
15
|
+
it(`should accept a finite ${format} literal without admitting arithmetic`, () => {
|
|
16
16
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from
|
|
17
|
-
.add(model => model.total).with(event => event.quantity)
|
|
18
17
|
.set(model => model.quantity).toValue(12)));
|
|
19
|
-
const modelSchema = JSON.parse(compiled.readModels[0].Schema) as { properties: {
|
|
20
|
-
modelSchema.properties.total = { type: 'integer', format };
|
|
18
|
+
const modelSchema = JSON.parse(compiled.readModels[0].Schema) as { properties: { quantity: { type: string; format?: string } } };
|
|
21
19
|
modelSchema.properties.quantity = { type: 'integer', format };
|
|
22
20
|
compiled.readModels[0].Schema = JSON.stringify(modelSchema);
|
|
23
|
-
const eventSchema = compiled.eventSchemas.get(definition)!.get('capability-changed:1:0')!.schema;
|
|
24
|
-
eventSchema.properties!.quantity = { type: 'integer', format };
|
|
25
21
|
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
26
22
|
});
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
it('should
|
|
30
|
-
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from
|
|
25
|
+
it('should admit mappings into unformatted Number fields generated by TypeScript', () => {
|
|
26
|
+
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from
|
|
27
|
+
.set(model => model.total).to(event => event.name)
|
|
28
|
+
.set(model => model.quantity).to(event => event.quantity)));
|
|
29
|
+
const schema = JSON.parse(compiled.readModels[0].Schema) as { properties: { total: { type: string; format?: string }; quantity: { type: string; format?: string } } };
|
|
30
|
+
('format' in schema.properties.total).should.be.false;
|
|
31
|
+
('format' in schema.properties.quantity).should.be.false;
|
|
32
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
33
|
+
// The oracle's compatible-mappings fixture also tests formatted int32/uint32 sources into unformatted Number targets.
|
|
34
|
+
compiled.eventSchemas.get(definition)!.get('capability-changed:1:0')!.schema.properties!.quantity = { type: 'integer', format: 'int32' };
|
|
35
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should accept GUID identifiers and a finite double literal', () => {
|
|
39
|
+
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from.set(model => model.total).toValue(2.5)));
|
|
31
40
|
const schema = JSON.parse(compiled.readModels[0].Schema) as { properties: { id: { type: string; format?: string }; total: { format?: string } } };
|
|
32
41
|
schema.properties.id.format = 'guid';
|
|
33
42
|
schema.properties.total.format = 'double';
|
|
@@ -35,10 +44,10 @@ describe('when validating the numeric capability matrix', () => {
|
|
|
35
44
|
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
36
45
|
});
|
|
37
46
|
|
|
38
|
-
it('should accept numeric identifiers with
|
|
47
|
+
it('should accept numeric identifiers with the fixture-backed double schema', () => {
|
|
39
48
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed));
|
|
40
|
-
const schema = JSON.parse(compiled.readModels[0].Schema) as { properties: { id: { type: string } } };
|
|
41
|
-
schema.properties.id
|
|
49
|
+
const schema = JSON.parse(compiled.readModels[0].Schema) as { properties: { id: { type: string; format?: string } } };
|
|
50
|
+
schema.properties.id = { type: 'number', format: 'double' };
|
|
42
51
|
compiled.readModels[0].Schema = JSON.stringify(schema);
|
|
43
52
|
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
44
53
|
});
|
|
@@ -54,6 +63,25 @@ describe('when validating the numeric capability matrix', () => {
|
|
|
54
63
|
});
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
it('should reject a double event operand into an integer accumulator before replay', () => {
|
|
67
|
+
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from.add(model => model.total).with(event => event.quantity)));
|
|
68
|
+
const schema = JSON.parse(compiled.readModels[0].Schema) as { properties: { total: { type: string; format?: string } } };
|
|
69
|
+
schema.properties.total = { type: 'integer', format: 'int32' };
|
|
70
|
+
compiled.readModels[0].Schema = JSON.stringify(schema);
|
|
71
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation)
|
|
72
|
+
.with.property('message').that.includes('arithmetic requires a kernel-backed test');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should reject an int32 $value literal with a fractional spelling even if its value is integral', () => {
|
|
76
|
+
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from.set(model => model.total).toValue(1)));
|
|
77
|
+
const schema = JSON.parse(compiled.readModels[0].Schema) as { properties: { total: { type: string; format?: string } } };
|
|
78
|
+
schema.properties.total = { type: 'integer', format: 'int32' };
|
|
79
|
+
compiled.readModels[0].Schema = JSON.stringify(schema);
|
|
80
|
+
definition.From[0].Value.Properties.total = '$value(1.0)';
|
|
81
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation)
|
|
82
|
+
.with.property('message').that.includes('$value numeric literal is outside');
|
|
83
|
+
});
|
|
84
|
+
|
|
57
85
|
it('should reject an overflowing initial integer value', () => {
|
|
58
86
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed)
|
|
59
87
|
.withInitialValues(() => ({ id: '', name: '', quantity: 0, total: 2147483648, state: '', labels: [], details: {} })));
|
|
@@ -68,6 +96,6 @@ describe('when validating the numeric capability matrix', () => {
|
|
|
68
96
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from.add(model => model.total).with(event => event.quantity)));
|
|
69
97
|
compiled.eventSchemas.get(definition)!.get('capability-changed:1:0')!.schema.properties!.quantity.format = 'int64';
|
|
70
98
|
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation)
|
|
71
|
-
.with.property('message').that.includes('arithmetic
|
|
99
|
+
.with.property('message').that.includes('arithmetic requires a kernel-backed test');
|
|
72
100
|
});
|
|
73
101
|
});
|
package/testing/projections/for_ProjectionCapabilities/when_validating_schema_boundaries.spec.ts
CHANGED
|
@@ -29,22 +29,16 @@ function setModelSchema(compiled: ReturnType<typeof compileDeclarative>['compile
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
describe('when validating schema-bound projection operations', () => {
|
|
32
|
-
it('should accept generated
|
|
32
|
+
it('should accept generated scalar date and date-concept formats for pass-through', () => {
|
|
33
33
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from
|
|
34
34
|
.set(model => model.name).to(event => event.name)
|
|
35
|
-
.set(model => model.state).to(event => event.name)
|
|
36
|
-
.set(model => model.details).to(event => event.details))
|
|
37
|
-
.withInitialValues(() => ({ id: '', name: '', quantity: 0, total: 0, state: new Date('2026-01-01') as unknown as string,
|
|
38
|
-
labels: [], details: { created: new Date('2026-01-01') } })));
|
|
35
|
+
.set(model => model.state).to(event => event.name)));
|
|
39
36
|
const dates = JsonSchemaGenerator.generate(Dated).properties!;
|
|
40
37
|
setModelSchema(compiled, properties => {
|
|
41
38
|
properties.name = dates.created;
|
|
42
39
|
properties.state = dates.timestamp;
|
|
43
|
-
properties.details = dates.details;
|
|
44
40
|
});
|
|
45
|
-
|
|
46
|
-
event.properties!.name = dates.created;
|
|
47
|
-
event.properties!.details = dates.details;
|
|
41
|
+
compiled.eventSchemas.get(definition)!.get('capability-changed:1:0')!.schema.properties!.name = dates.created;
|
|
48
42
|
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
49
43
|
});
|
|
50
44
|
|
|
@@ -56,6 +50,70 @@ describe('when validating schema-bound projection operations', () => {
|
|
|
56
50
|
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
57
51
|
});
|
|
58
52
|
|
|
53
|
+
const mismatches: Array<{ name: string; source: JsonSchema; target: JsonSchema }> = [
|
|
54
|
+
{ name: 'date-time to string', source: { type: 'string', format: 'date-time' }, target: { type: 'string' } },
|
|
55
|
+
{ name: 'boolean to string', source: { type: 'boolean' }, target: { type: 'string' } },
|
|
56
|
+
{ name: 'number to integer', source: { type: 'number', format: 'double' }, target: { type: 'integer', format: 'int32' } },
|
|
57
|
+
{ name: 'object to scalar', source: { type: 'object', properties: { other: { type: 'string' } } }, target: { type: 'string' } },
|
|
58
|
+
{ name: 'concept-shaped object to object', source: { type: 'object', properties: { value: { type: 'integer', format: 'int32' } } },
|
|
59
|
+
target: { type: 'object', properties: { value: { type: 'integer', format: 'int32' } } } }
|
|
60
|
+
];
|
|
61
|
+
for (const mapping of mismatches) {
|
|
62
|
+
for (const autoMap of [false, true]) {
|
|
63
|
+
it(`should reject ${mapping.name} for ${autoMap ? 'AutoMap' : 'setFrom'} with the declaration and path`, () => {
|
|
64
|
+
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => {
|
|
65
|
+
if (!autoMap) from.set(model => model.name).to(event => event.name);
|
|
66
|
+
}));
|
|
67
|
+
setModelSchema(compiled, properties => { properties.name = mapping.target; });
|
|
68
|
+
compiled.eventSchemas.get(definition)!.get('capability-changed:1:0')!.schema.properties!.name = mapping.source;
|
|
69
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation)
|
|
70
|
+
.with.property('message').that.includes(`From[capability-changed:1].${autoMap ? 'AutoMap' : 'Properties'}.name`)
|
|
71
|
+
.and.includes(autoMap ? '.from (AutoMap)' : '.from().set')
|
|
72
|
+
.and.includes('kernel-backed test');
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const [property, target, value, reason] of [
|
|
78
|
+
['state', { type: 'boolean' }, 'yes', 'initial boolean value must be a JSON boolean'],
|
|
79
|
+
['total', { type: 'integer', format: 'int32' }, '5', 'initial numeric value must be a JSON number'],
|
|
80
|
+
['state', { type: 'string', format: 'guid' }, 'not-a-guid', 'initial GUID value must be canonical lowercase text']
|
|
81
|
+
] as const) {
|
|
82
|
+
it(`should reject incompatible initial value for ${property}`, () => {
|
|
83
|
+
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed));
|
|
84
|
+
setModelSchema(compiled, properties => { properties[property] = target; });
|
|
85
|
+
definition.InitialModelState = JSON.stringify({ [property]: value });
|
|
86
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation)
|
|
87
|
+
.with.property('message').that.includes(`InitialModelState.${property} (.withInitialValues)`)
|
|
88
|
+
.and.includes(reason);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
it('should reject unproven initial date-time, object and array values', () => {
|
|
93
|
+
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed));
|
|
94
|
+
for (const [property, value] of [['state', '2026-01-01T00:00:00.000Z'], ['details', {}], ['labels', []]] as const) {
|
|
95
|
+
if (property === 'state') setModelSchema(compiled, properties => { properties.state = { type: 'string', format: 'date-time' }; });
|
|
96
|
+
definition.InitialModelState = JSON.stringify({ [property]: value });
|
|
97
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation)
|
|
98
|
+
.with.property('message').that.includes(`InitialModelState.${property}`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
for (const [name, schema] of [
|
|
103
|
+
['unmapped', { type: 'string', security: [{ metadataType: 'Secret', details: '' }] }],
|
|
104
|
+
['nested', { type: 'object', properties: { secret: { type: 'string', compliance: [{ metadataType: 'PII', details: '' }] } } }],
|
|
105
|
+
['array item', { type: 'array', items: { type: 'string', compliance: [{ metadataType: 'PII', details: '' }] } }],
|
|
106
|
+
['additional property', { type: 'object', additionalProperties: { type: 'string', security: [{ metadataType: 'Encrypted', details: '' }] } }]
|
|
107
|
+
] as const) {
|
|
108
|
+
it(`should reject protected ${name} schema members even when unmapped`, () => {
|
|
109
|
+
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed));
|
|
110
|
+
setModelSchema(compiled, properties => { properties.unmapped = schema as JsonSchema; });
|
|
111
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation)
|
|
112
|
+
.with.property('message').that.includes('ReadModel.Schema.unmapped')
|
|
113
|
+
.and.includes('protected fields require a kernel-backed test');
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
59
117
|
it('should accept $null for a date destination', () => {
|
|
60
118
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from.set(model => model.state).toValue(null)));
|
|
61
119
|
setModelSchema(compiled, properties => { properties.state = { type: 'string', format: 'date-time' }; });
|
|
@@ -72,37 +130,26 @@ describe('when validating schema-bound projection operations', () => {
|
|
|
72
130
|
});
|
|
73
131
|
}
|
|
74
132
|
|
|
75
|
-
it('should
|
|
133
|
+
it('should reject missing or differently cased id schemas until their key behavior is fixture-backed', () => {
|
|
76
134
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed));
|
|
77
135
|
setModelSchema(compiled, properties => { delete properties.id; });
|
|
78
|
-
(() => ProjectionCapabilities.validate(compiled, definition)).should.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
it('should use Id when id is absent, but prefer id when both exist', () => {
|
|
82
|
-
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed));
|
|
83
|
-
setModelSchema(compiled, properties => { properties.Id = { type: 'string' }; delete properties.id; });
|
|
84
|
-
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
85
|
-
setModelSchema(compiled, properties => { properties.id = { type: 'boolean' }; });
|
|
136
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation, 'ReadModel.Schema.id');
|
|
137
|
+
setModelSchema(compiled, properties => { properties.Id = { type: 'string' }; });
|
|
86
138
|
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation, 'ReadModel.Schema.id');
|
|
87
139
|
});
|
|
88
140
|
|
|
89
|
-
it('should
|
|
141
|
+
it('should reject schemas whose properties collide with the lowercase id', () => {
|
|
90
142
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed));
|
|
91
|
-
setModelSchema(compiled, properties => {
|
|
92
|
-
(() => ProjectionCapabilities.validate(compiled, definition)).should.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
it('should skip AutoMap validation for arithmetic-only events', () => {
|
|
96
|
-
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from.add(model => model.total).with(event => event.quantity)));
|
|
97
|
-
setModelSchema(compiled, properties => { properties.name.format = 'float'; });
|
|
98
|
-
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
143
|
+
setModelSchema(compiled, properties => { properties.Id = { type: 'string' }; });
|
|
144
|
+
(() => ProjectionCapabilities.validate(compiled, definition)).should.throw(UnsupportedProjectionOperation,
|
|
145
|
+
'case-insensitively colliding read-model properties require a kernel-backed test');
|
|
99
146
|
});
|
|
100
147
|
|
|
101
148
|
for (const [expression, destination, reason] of [
|
|
102
149
|
['$value(maybe)', 'state', '$value boolean literal'],
|
|
103
150
|
['$value(not-a-guid)', 'state', '$value GUID literal'],
|
|
104
|
-
['$value(text)', 'details', '
|
|
105
|
-
['$value(text)', 'labels', '
|
|
151
|
+
['$value(text)', 'details', 'object/array target mappings require a kernel-backed test'],
|
|
152
|
+
['$value(text)', 'labels', 'object/array target mappings require a kernel-backed test']
|
|
106
153
|
]) {
|
|
107
154
|
it(`should reject invalid ${expression} for ${destination} (${reason})`, () => {
|
|
108
155
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed));
|
package/testing/projections/for_ProjectionCapabilities/when_validating_supported_operations.spec.ts
CHANGED
|
@@ -3,17 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { chai, describe, it } from 'vitest';
|
|
6
|
-
import { addFrom } from '../../../projections/modelBound/addFrom.js';
|
|
7
6
|
import { clearWith } from '../../../projections/modelBound/clearWith.js';
|
|
8
|
-
import { count } from '../../../projections/modelBound/count.js';
|
|
9
|
-
import { decrement } from '../../../projections/modelBound/decrement.js';
|
|
10
|
-
import { increment } from '../../../projections/modelBound/increment.js';
|
|
11
7
|
import { noAutoMap } from '../../../projections/modelBound/noAutoMap.js';
|
|
12
8
|
import { removedWith } from '../../../projections/modelBound/removedWith.js';
|
|
13
9
|
import { setFrom } from '../../../projections/modelBound/setFrom.js';
|
|
14
10
|
import { setFromContext } from '../../../projections/modelBound/setFromContext.js';
|
|
15
11
|
import { setValue } from '../../../projections/modelBound/setValue.js';
|
|
16
|
-
import { subtractFrom } from '../../../projections/modelBound/subtractFrom.js';
|
|
17
12
|
import { ProjectionCapabilities } from '../ProjectionCapabilities.js';
|
|
18
13
|
import { Changed } from './given/Changed.js';
|
|
19
14
|
import { Removed } from './given/Removed.js';
|
|
@@ -25,16 +20,11 @@ describe('when validating the phase-one projection subset', () => {
|
|
|
25
20
|
const modelBound = [
|
|
26
21
|
{ name: 'root From and schema-inferred AutoMap', configure: () => {} },
|
|
27
22
|
{ name: 'setFrom and event source identity', configure: (model: Function) => setFrom(Changed, 'name')(model.prototype, 'name') },
|
|
28
|
-
{ name: 'setFromContext maps
|
|
23
|
+
{ name: 'setFromContext maps eventSourceId without needing it in the event schema', configure: (model: Function) => setFromContext(Changed, 'eventSourceId')(model.prototype, 'state') },
|
|
29
24
|
{ name: 'setFromContext maps the event source identity', configure: (model: Function) => setFromContext(Changed, 'eventSourceId')(model.prototype, 'state') },
|
|
30
25
|
{ name: 'setFromContext maps a nested identity member', configure: (model: Function) => setFromContext(Changed, 'causedBy.subject')(model.prototype, 'state') },
|
|
31
26
|
{ name: 'constant text', configure: (model: Function) => setValue(Changed, 'ready')(model.prototype, 'state') },
|
|
32
27
|
{ name: 'scalar clearing', configure: (model: Function) => clearWith(Removed)(model.prototype, 'state') },
|
|
33
|
-
{ name: 'addition', configure: (model: Function) => addFrom(Changed, 'quantity')(model.prototype, 'total') },
|
|
34
|
-
{ name: 'subtraction', configure: (model: Function) => subtractFrom(Changed, 'quantity')(model.prototype, 'total') },
|
|
35
|
-
{ name: 'count', configure: (model: Function) => count(Changed)(model.prototype, 'total') },
|
|
36
|
-
{ name: 'increment', configure: (model: Function) => increment(Changed)(model.prototype, 'total') },
|
|
37
|
-
{ name: 'decrement', configure: (model: Function) => decrement(Changed)(model.prototype, 'total') },
|
|
38
28
|
{ name: 'root removal', configure: (model: Function) => removedWith(Removed)(model) },
|
|
39
29
|
{ name: 'AutoMap exclusions', configure: (model: Function) => noAutoMap(model.prototype, 'quantity') }
|
|
40
30
|
];
|
|
@@ -45,15 +35,11 @@ describe('when validating the phase-one projection subset', () => {
|
|
|
45
35
|
});
|
|
46
36
|
}
|
|
47
37
|
|
|
48
|
-
it('should accept declarative
|
|
38
|
+
it('should accept declarative explicit mappings, constants, and a separate removal event', () => {
|
|
49
39
|
const { compiled, definition } = compileDeclarative(builder => builder
|
|
50
40
|
.from(Changed, from => from.set(model => model.name).to(event => event.name)
|
|
51
|
-
.set(model => model.
|
|
52
|
-
|
|
53
|
-
.add(model => model.total).with(event => event.quantity)
|
|
54
|
-
.subtract(model => model.total).with(event => event.quantity)
|
|
55
|
-
.increment(model => model.total).decrement(model => model.total).count(model => model.total))
|
|
56
|
-
.from(Removed).removedWith(Removed).withInitialValues(() => ({ id: '', name: '', quantity: 0, total: 0, state: 'new', labels: [], details: {} })));
|
|
41
|
+
.set(model => model.state).toValue('ready'))
|
|
42
|
+
.removedWith(Removed).withInitialValues(() => ({ id: '', name: '', quantity: 0, total: 0, state: 'new' })));
|
|
57
43
|
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
58
44
|
});
|
|
59
45
|
|
|
@@ -67,17 +53,10 @@ describe('when validating the phase-one projection subset', () => {
|
|
|
67
53
|
['eventStreamId', '$eventContext(EventStreamId)'],
|
|
68
54
|
['subject', '$eventContext(Subject)'],
|
|
69
55
|
['hash', '$eventContext(Hash)'],
|
|
70
|
-
['causedBy', '$eventContext(CausedBy)'],
|
|
71
|
-
['observationState', '$eventContext(ObservationState)'],
|
|
72
|
-
['eventType', '$eventContext(EventType)'],
|
|
73
|
-
['occurred', '$eventContext(Occurred)'],
|
|
74
56
|
['correlationId', '$eventContext(CorrelationId)'],
|
|
75
|
-
['causation', '$eventContext(Causation)'],
|
|
76
|
-
['tags', '$eventContext(Tags)'],
|
|
77
57
|
['causedBy.subject', '$eventContext(CausedBy.Subject)'],
|
|
78
58
|
['causedBy.name', '$eventContext(CausedBy.Name)'],
|
|
79
|
-
['causedBy.userName', '$eventContext(CausedBy.UserName)']
|
|
80
|
-
['causedBy.onBehalfOf', '$eventContext(CausedBy.OnBehalfOf)']
|
|
59
|
+
['causedBy.userName', '$eventContext(CausedBy.UserName)']
|
|
81
60
|
]) {
|
|
82
61
|
it(`should accept the client's emitted context expression ${expression}`, () => {
|
|
83
62
|
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from =>
|
|
@@ -87,17 +66,11 @@ describe('when validating the phase-one projection subset', () => {
|
|
|
87
66
|
});
|
|
88
67
|
}
|
|
89
68
|
|
|
90
|
-
it('should accept declarative null clearing
|
|
69
|
+
it('should accept declarative null clearing with numeric initial state', () => {
|
|
91
70
|
const { compiled, definition } = compileDeclarative(builder => builder
|
|
92
|
-
.from(Changed, from => from.set(model => model.state).toValue(null)
|
|
93
|
-
.withInitialValues(() => ({ id: '', name: '', quantity: 0, total:
|
|
71
|
+
.from(Changed, from => from.set(model => model.state).toValue(null))
|
|
72
|
+
.withInitialValues(() => ({ id: '', name: '', quantity: 0, total: 0, state: '' })));
|
|
94
73
|
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
95
74
|
});
|
|
96
75
|
|
|
97
|
-
it('should accept an ordinary object or array value without mistaking it for a child projection', () => {
|
|
98
|
-
const { compiled, definition } = compileDeclarative(builder => builder.from(Changed, from => from
|
|
99
|
-
.set(model => model.labels).to(event => event.labels)
|
|
100
|
-
.set(model => model.details).to(event => event.details)));
|
|
101
|
-
(() => ProjectionCapabilities.validate(compiled, definition)).should.not.throw();
|
|
102
|
-
});
|
|
103
76
|
});
|