@cratis/chronicle 6.38.0 → 6.40.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/ChronicleClient.ts +2 -3
- package/ChronicleOptions.ts +5 -1
- package/EventStore.ts +1 -2
- package/WellKnownTelemetryNames.ts +21 -0
- package/dist/ChronicleClient.d.ts.map +1 -1
- package/dist/ChronicleClient.js +2 -3
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/ChronicleOptions.d.ts +1 -1
- package/dist/ChronicleOptions.d.ts.map +1 -1
- package/dist/ChronicleOptions.js +5 -1
- package/dist/ChronicleOptions.js.map +1 -1
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +1 -2
- package/dist/EventStore.js.map +1 -1
- package/dist/WellKnownTelemetryNames.d.ts +21 -0
- package/dist/WellKnownTelemetryNames.d.ts.map +1 -1
- package/dist/WellKnownTelemetryNames.js +21 -0
- package/dist/WellKnownTelemetryNames.js.map +1 -1
- package/dist/eventSequences/EventSequence.js +9 -9
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/telemetry/ChronicleTelemetryOptions.d.ts +3 -1
- package/dist/telemetry/ChronicleTelemetryOptions.d.ts.map +1 -1
- package/dist/telemetry/observeOperation.d.ts +3 -1
- package/dist/telemetry/observeOperation.d.ts.map +1 -1
- package/dist/telemetry/observeOperation.js +3 -1
- package/dist/telemetry/observeOperation.js.map +1 -1
- package/dist/testing/EventFieldSchema.d.ts +6 -0
- package/dist/testing/EventFieldSchema.d.ts.map +1 -0
- package/dist/testing/EventFieldSchema.js +78 -0
- package/dist/testing/EventFieldSchema.js.map +1 -0
- package/dist/testing/EventScenario.d.ts.map +1 -1
- package/dist/testing/EventScenario.js +17 -7
- package/dist/testing/EventScenario.js.map +1 -1
- package/dist/testing/InProcessConstraints.d.ts +3 -1
- package/dist/testing/InProcessConstraints.d.ts.map +1 -1
- package/dist/testing/InProcessConstraints.js +20 -9
- package/dist/testing/InProcessConstraints.js.map +1 -1
- package/dist/testing/InProcessEventSequence.d.ts.map +1 -1
- package/dist/testing/InProcessEventSequence.js +9 -10
- package/dist/testing/InProcessEventSequence.js.map +1 -1
- package/dist/testing/ReadModelScenario.js +1 -1
- package/dist/testing/ReadModelScenario.js.map +1 -1
- package/dist/testing/ScenarioEvent.d.ts +1 -0
- package/dist/testing/ScenarioEvent.d.ts.map +1 -1
- package/dist/testing/projections/ProjectionReadModelProcessor.d.ts.map +1 -1
- package/dist/testing/projections/ProjectionReadModelProcessor.js +7 -0
- package/dist/testing/projections/ProjectionReadModelProcessor.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/eventSequences/EventSequence.ts +9 -9
- package/events/migrations/for_EventTypeMigrators/against_a_kernel/when_replaying_an_older_generation.integration.spec.ts +30 -9
- package/for_ChronicleOptions/when_configuring_telemetry/with_span_names.spec.ts +32 -0
- package/package.json +2 -2
- package/telemetry/ChronicleTelemetryOptions.ts +4 -1
- package/telemetry/for_ChronicleTelemetry/given/a_client.fixture.ts +47 -0
- package/telemetry/for_ChronicleTelemetry/given/a_span_name_registry.fixture.ts +33 -0
- package/telemetry/for_ChronicleTelemetry/when_exposing_names/with_legacy_compatibility.spec.ts +44 -0
- package/telemetry/for_ChronicleTelemetry/when_naming_spans/with_per_client_modes.spec.ts +138 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_product_attributes/with_both_naming_modes.spec.ts +52 -0
- package/telemetry/observeOperation.ts +9 -1
- package/testing/EventFieldSchema.ts +71 -0
- package/testing/EventScenario.constraint-cycles.spec.ts +1 -1
- package/testing/EventScenario.constraints.spec.ts +4 -2
- package/testing/EventScenario.fixtures.spec.ts +5 -5
- package/testing/EventScenario.ts +18 -7
- package/testing/InProcessConstraints.ts +19 -9
- package/testing/InProcessEventSequence.ts +9 -10
- package/testing/ReadModelScenario.ts +1 -1
- package/testing/ScenarioEvent.ts +1 -1
- package/testing/for_EventScenario/when_appending_field_types/field_scenario_behaviors.fixture.ts +219 -0
- package/testing/for_EventScenario/when_appending_field_types/with_invalid_formatted_content.legacy.spec.ts +123 -0
- package/testing/for_EventScenario/when_appending_field_types/with_legacy_decorators.legacy.spec.ts +124 -0
- package/testing/for_EventScenario/when_appending_field_types/with_standard_decorators.standard.spec.ts +123 -0
- package/testing/for_EventScenario/when_appending_field_types/with_unproven_schemas.legacy.spec.ts +82 -0
- package/testing/projections/ProjectionReadModelProcessor.ts +10 -0
package/testing/for_EventScenario/when_appending_field_types/with_legacy_decorators.legacy.spec.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
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 { ConceptAs, field, Guid } from '@cratis/fundamentals';
|
|
6
|
+
import { describe } from 'vitest';
|
|
7
|
+
import { eventType, getEventTypeMetadata } from '../../../events/eventTypeDecorator.js';
|
|
8
|
+
import { unique } from '../../../events/constraints/unique.js';
|
|
9
|
+
import { constraint } from '../../../events/constraints/constraint.js';
|
|
10
|
+
import type { IConstraintBuilder } from '../../../events/constraints/IConstraintBuilder.js';
|
|
11
|
+
import { fromEvent } from '../../../projections/modelBound/fromEvent.js';
|
|
12
|
+
import { setFrom } from '../../../projections/modelBound/setFrom.js';
|
|
13
|
+
import { reactor } from '../../../reactors/reactor.js';
|
|
14
|
+
import { reducer } from '../../../reducers/reducer.js';
|
|
15
|
+
import { fieldScenarioBehaviors } from './field_scenario_behaviors.fixture.js';
|
|
16
|
+
|
|
17
|
+
class Identifier extends ConceptAs<Guid> { static readonly valueType = Guid; }
|
|
18
|
+
class Amount extends ConceptAs<number> { static readonly valueType = Number; }
|
|
19
|
+
class Occurred extends ConceptAs<Date> { static readonly valueType = Date; }
|
|
20
|
+
|
|
21
|
+
const identifier = () => Guid.parse('abcdef01-abcd-abcd-abcd-abcdef012345');
|
|
22
|
+
const occurred = () => new Date('2025-01-02T03:04:05.123Z');
|
|
23
|
+
|
|
24
|
+
class Payload {
|
|
25
|
+
@field(Number) amount = 1.5;
|
|
26
|
+
@field(Guid) identifier = identifier();
|
|
27
|
+
@field(Date) occurred = occurred();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@eventType('FieldTypesRecorded')
|
|
31
|
+
class FieldsRecorded {
|
|
32
|
+
@field(Number) amount = 1.5;
|
|
33
|
+
@field(Guid) identifier = identifier();
|
|
34
|
+
@field(Date) occurred = occurred();
|
|
35
|
+
@field(Amount) amountConcept = new Amount(1e-7);
|
|
36
|
+
@field(Identifier) identifierConcept = new Identifier(identifier());
|
|
37
|
+
@field(Occurred) occurredConcept = new Occurred(occurred());
|
|
38
|
+
@field(Payload) payload = new Payload();
|
|
39
|
+
@field(Object) arbitrary = { nested: { number: 9007199254740992 }, values: [true, 1.5] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@reactor('FieldTypesReactor')
|
|
43
|
+
class Echo {
|
|
44
|
+
fieldsRecorded(event: FieldsRecorded) { return Object.assign(new FieldsRecorded(), event); }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class StoredFields {}
|
|
48
|
+
class StoreFields {
|
|
49
|
+
fieldsRecorded(event: FieldsRecorded) { return event; }
|
|
50
|
+
}
|
|
51
|
+
reducer('FieldTypesReducer', undefined, StoredFields)(StoreFields);
|
|
52
|
+
|
|
53
|
+
@fromEvent(FieldsRecorded)
|
|
54
|
+
class ScalarProjection {
|
|
55
|
+
@field(String) id = '';
|
|
56
|
+
@field(Number) amount = 0;
|
|
57
|
+
@field(Guid) identifier = Guid.empty;
|
|
58
|
+
@field(Date) occurred = new Date(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@fromEvent(FieldsRecorded)
|
|
62
|
+
class ObjectProjection {
|
|
63
|
+
@field(String) id = '';
|
|
64
|
+
@field(Object) payload = {};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@fromEvent(FieldsRecorded)
|
|
68
|
+
class HashProjection {
|
|
69
|
+
@field(String) id = '';
|
|
70
|
+
@field(String) @setFrom(FieldsRecorded, '$eventContext(Hash)') hash = '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@eventType('OracleFieldGuid')
|
|
74
|
+
class GuidClaimed {
|
|
75
|
+
@field(Guid) key = identifier();
|
|
76
|
+
}
|
|
77
|
+
@eventType('OracleFieldNumber')
|
|
78
|
+
class NumberClaimed {
|
|
79
|
+
@field(Number) key = 1;
|
|
80
|
+
}
|
|
81
|
+
@eventType('OracleFieldDouble')
|
|
82
|
+
class DoubleClaimed {
|
|
83
|
+
@field(Number) key = 1;
|
|
84
|
+
}
|
|
85
|
+
getEventTypeMetadata(DoubleClaimed)!.schema.properties!.key.format = 'double';
|
|
86
|
+
@constraint('OracleFieldKey')
|
|
87
|
+
class DoubleKey {
|
|
88
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(DoubleClaimed, event => event.key)); }
|
|
89
|
+
}
|
|
90
|
+
@constraint('OracleFieldKey')
|
|
91
|
+
class GuidKey {
|
|
92
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(GuidClaimed, event => event.key)); }
|
|
93
|
+
}
|
|
94
|
+
@constraint('OracleFieldKey')
|
|
95
|
+
class NumberKey {
|
|
96
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(NumberClaimed, event => event.key)); }
|
|
97
|
+
}
|
|
98
|
+
@eventType('ConceptGuidClaimed')
|
|
99
|
+
class ConceptGuidClaimed {
|
|
100
|
+
@field(Identifier) @unique('ConceptGuidKey') key = new Identifier(identifier());
|
|
101
|
+
}
|
|
102
|
+
@eventType('ConceptNumberClaimed')
|
|
103
|
+
class ConceptNumberClaimed {
|
|
104
|
+
@field(Amount) @unique('ConceptNumberKey') key = new Amount(42);
|
|
105
|
+
}
|
|
106
|
+
@eventType('DateClaimed')
|
|
107
|
+
class DateClaimed {
|
|
108
|
+
@field(Date) @unique('DateKey') key = occurred();
|
|
109
|
+
}
|
|
110
|
+
@eventType('ConceptDateClaimed')
|
|
111
|
+
class ConceptDateClaimed {
|
|
112
|
+
@field(Occurred) @unique('ConceptDateKey') key = new Occurred(occurred());
|
|
113
|
+
}
|
|
114
|
+
@eventType('ObjectClaimed')
|
|
115
|
+
class ObjectClaimed {
|
|
116
|
+
@field(Object) @unique('ObjectKey') key = { amount: 1.5 };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
describe('when appending field types with legacy decorators', () => {
|
|
120
|
+
fieldScenarioBehaviors({ recorded: FieldsRecorded, reactor: Echo, model: StoredFields, reducer: StoreFields,
|
|
121
|
+
projection: ScalarProjection, objectProjection: ObjectProjection, hashProjection: HashProjection,
|
|
122
|
+
guid: GuidClaimed, number: NumberClaimed, guidConstraint: GuidKey, numberConstraint: NumberKey, guidConcept: ConceptGuidClaimed, numberConcept: ConceptNumberClaimed,
|
|
123
|
+
double: DoubleClaimed, doubleConstraint: DoubleKey, rejected: [DateClaimed, ConceptDateClaimed, ObjectClaimed] });
|
|
124
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
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 { ConceptAs, field, Guid } from '@cratis/fundamentals';
|
|
5
|
+
import { describe } from 'vitest';
|
|
6
|
+
import { eventType, getEventTypeMetadata } from '../../../events/eventTypeDecorator.js';
|
|
7
|
+
import { unique } from '../../../events/constraints/unique.js';
|
|
8
|
+
import { constraint } from '../../../events/constraints/constraint.js';
|
|
9
|
+
import type { IConstraintBuilder } from '../../../events/constraints/IConstraintBuilder.js';
|
|
10
|
+
import { fromEvent } from '../../../projections/modelBound/fromEvent.js';
|
|
11
|
+
import { setFrom } from '../../../projections/modelBound/setFrom.js';
|
|
12
|
+
import { reactor } from '../../../reactors/reactor.js';
|
|
13
|
+
import { reducer } from '../../../reducers/reducer.js';
|
|
14
|
+
import { fieldScenarioBehaviors } from './field_scenario_behaviors.fixture.js';
|
|
15
|
+
|
|
16
|
+
class Identifier extends ConceptAs<Guid> { static readonly valueType = Guid; }
|
|
17
|
+
class Amount extends ConceptAs<number> { static readonly valueType = Number; }
|
|
18
|
+
class Occurred extends ConceptAs<Date> { static readonly valueType = Date; }
|
|
19
|
+
|
|
20
|
+
const identifier = () => Guid.parse('abcdef01-abcd-abcd-abcd-abcdef012345');
|
|
21
|
+
const occurred = () => new Date('2025-01-02T03:04:05.123Z');
|
|
22
|
+
|
|
23
|
+
class Payload {
|
|
24
|
+
@field(Number) amount = 1.5;
|
|
25
|
+
@field(Guid) identifier = identifier();
|
|
26
|
+
@field(Date) occurred = occurred();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@eventType('FieldTypesRecorded')
|
|
30
|
+
class FieldsRecorded {
|
|
31
|
+
@field(Number) amount = 1.5;
|
|
32
|
+
@field(Guid) identifier = identifier();
|
|
33
|
+
@field(Date) occurred = occurred();
|
|
34
|
+
@field(Amount) amountConcept = new Amount(1e-7);
|
|
35
|
+
@field(Identifier) identifierConcept = new Identifier(identifier());
|
|
36
|
+
@field(Occurred) occurredConcept = new Occurred(occurred());
|
|
37
|
+
@field(Payload) payload = new Payload();
|
|
38
|
+
@field(Object) arbitrary = { nested: { number: 9007199254740992 }, values: [true, 1.5] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@reactor('FieldTypesReactor')
|
|
42
|
+
class Echo {
|
|
43
|
+
fieldsRecorded(event: FieldsRecorded) { return Object.assign(new FieldsRecorded(), event); }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class StoredFields {}
|
|
47
|
+
class StoreFields {
|
|
48
|
+
fieldsRecorded(event: FieldsRecorded) { return event; }
|
|
49
|
+
}
|
|
50
|
+
reducer('FieldTypesReducer', undefined, StoredFields)(StoreFields);
|
|
51
|
+
|
|
52
|
+
@fromEvent(FieldsRecorded)
|
|
53
|
+
class ScalarProjection {
|
|
54
|
+
@field(String) id = '';
|
|
55
|
+
@field(Number) amount = 0;
|
|
56
|
+
@field(Guid) identifier = Guid.empty;
|
|
57
|
+
@field(Date) occurred = new Date(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@fromEvent(FieldsRecorded)
|
|
61
|
+
class ObjectProjection {
|
|
62
|
+
@field(String) id = '';
|
|
63
|
+
@field(Object) payload = {};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@fromEvent(FieldsRecorded)
|
|
67
|
+
class HashProjection {
|
|
68
|
+
@field(String) id = '';
|
|
69
|
+
@field(String) @setFrom(FieldsRecorded, '$eventContext(Hash)') hash = '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@eventType('OracleFieldGuid')
|
|
73
|
+
class GuidClaimed {
|
|
74
|
+
@field(Guid) key = identifier();
|
|
75
|
+
}
|
|
76
|
+
@eventType('OracleFieldNumber')
|
|
77
|
+
class NumberClaimed {
|
|
78
|
+
@field(Number) key = 1;
|
|
79
|
+
}
|
|
80
|
+
@eventType('OracleFieldDouble')
|
|
81
|
+
class DoubleClaimed {
|
|
82
|
+
@field(Number) key = 1;
|
|
83
|
+
}
|
|
84
|
+
getEventTypeMetadata(DoubleClaimed)!.schema.properties!.key.format = 'double';
|
|
85
|
+
@constraint('OracleFieldKey')
|
|
86
|
+
class DoubleKey {
|
|
87
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(DoubleClaimed, event => event.key)); }
|
|
88
|
+
}
|
|
89
|
+
@constraint('OracleFieldKey')
|
|
90
|
+
class GuidKey {
|
|
91
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(GuidClaimed, event => event.key)); }
|
|
92
|
+
}
|
|
93
|
+
@constraint('OracleFieldKey')
|
|
94
|
+
class NumberKey {
|
|
95
|
+
define(builder: IConstraintBuilder) { builder.unique(key => key.on(NumberClaimed, event => event.key)); }
|
|
96
|
+
}
|
|
97
|
+
@eventType('ConceptGuidClaimed')
|
|
98
|
+
class ConceptGuidClaimed {
|
|
99
|
+
@field(Identifier) @unique('ConceptGuidKey') key = new Identifier(identifier());
|
|
100
|
+
}
|
|
101
|
+
@eventType('ConceptNumberClaimed')
|
|
102
|
+
class ConceptNumberClaimed {
|
|
103
|
+
@field(Amount) @unique('ConceptNumberKey') key = new Amount(42);
|
|
104
|
+
}
|
|
105
|
+
@eventType('DateClaimed')
|
|
106
|
+
class DateClaimed {
|
|
107
|
+
@field(Date) @unique('DateKey') key = occurred();
|
|
108
|
+
}
|
|
109
|
+
@eventType('ConceptDateClaimed')
|
|
110
|
+
class ConceptDateClaimed {
|
|
111
|
+
@field(Occurred) @unique('ConceptDateKey') key = new Occurred(occurred());
|
|
112
|
+
}
|
|
113
|
+
@eventType('ObjectClaimed')
|
|
114
|
+
class ObjectClaimed {
|
|
115
|
+
@field(Object) @unique('ObjectKey') key = { amount: 1.5 };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
describe('when appending field types with standard decorators', () => {
|
|
119
|
+
fieldScenarioBehaviors({ recorded: FieldsRecorded, reactor: Echo, model: StoredFields, reducer: StoreFields,
|
|
120
|
+
projection: ScalarProjection, objectProjection: ObjectProjection, hashProjection: HashProjection,
|
|
121
|
+
guid: GuidClaimed, number: NumberClaimed, guidConstraint: GuidKey, numberConstraint: NumberKey, guidConcept: ConceptGuidClaimed, numberConcept: ConceptNumberClaimed,
|
|
122
|
+
double: DoubleClaimed, doubleConstraint: DoubleKey, rejected: [DateClaimed, ConceptDateClaimed, ObjectClaimed] });
|
|
123
|
+
});
|
package/testing/for_EventScenario/when_appending_field_types/with_unproven_schemas.legacy.spec.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
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 { field, Guid } from '@cratis/fundamentals';
|
|
6
|
+
import { chai, describe, it } from 'vitest';
|
|
7
|
+
import { eventType, getEventTypeMetadata } from '../../../events/eventTypeDecorator.js';
|
|
8
|
+
import { unique } from '../../../events/constraints/unique.js';
|
|
9
|
+
import { constraint } from '../../../events/constraints/constraint.js';
|
|
10
|
+
import type { IConstraintBuilder } from '../../../events/constraints/IConstraintBuilder.js';
|
|
11
|
+
import type { JsonSchema } from '../../../schemas/JsonSchema.js';
|
|
12
|
+
import { EventScenario, UnsupportedEventSequenceOperation } from '../../index.js';
|
|
13
|
+
|
|
14
|
+
chai.should();
|
|
15
|
+
|
|
16
|
+
for (const schema of [
|
|
17
|
+
{ type: 'string', format: 'date' }, { type: 'string', format: 'date-time' },
|
|
18
|
+
{ type: 'object' }, { type: 'integer', format: 'int64' }, { type: 'number', format: 'decimal' }
|
|
19
|
+
] satisfies JsonSchema[]) {
|
|
20
|
+
describe(`when registering a ${schema.type}/${schema.format ?? 'unformatted'} constrained schema`, () => {
|
|
21
|
+
it('should reject the constraint without rejecting an unconstrained field', async () => {
|
|
22
|
+
class Recorded { @field(Object) @unique(`Unsupported${schema.type}${schema.format ?? ''}`) key: unknown = {}; }
|
|
23
|
+
eventType(`Unsupported${schema.type}${schema.format ?? ''}`)(Recorded);
|
|
24
|
+
getEventTypeMetadata(Recorded)!.schema.properties!.key = schema;
|
|
25
|
+
(() => new EventScenario({ artifacts: { eventTypes: [Recorded] } })).should.throw(UnsupportedEventSequenceOperation, 'artifacts.constraints');
|
|
26
|
+
const scenario = new EventScenario({ artifacts: { eventTypes: [Recorded] }, constraints: 'disabled' });
|
|
27
|
+
const event = new Recorded();
|
|
28
|
+
event.key = schema.type === 'string' ? '2025-01-02' : schema.type === 'object' ? { value: 42 } : 1.5;
|
|
29
|
+
if (schema.format === 'date-time') event.key = '2025-01-02T03:04:05.000Z';
|
|
30
|
+
if (schema.type === 'integer') event.key = 42;
|
|
31
|
+
(await scenario.append('A', event)).isSuccess.should.equal(true);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const mode of ['composite', 'scoped', 'ignoreCasing']) {
|
|
37
|
+
describe(`when registering a ${mode} Guid key without fixture evidence`, () => {
|
|
38
|
+
it('should reject instead of inheriting plain-string constraint support', () => {
|
|
39
|
+
class Recorded { @field(Guid) key = Guid.empty; @field(String) label = ''; }
|
|
40
|
+
eventType(`UnprovenGuid${mode}`)(Recorded);
|
|
41
|
+
class Definition {
|
|
42
|
+
define(builder: IConstraintBuilder) {
|
|
43
|
+
if (mode === 'scoped') builder.perEventSourceType();
|
|
44
|
+
builder.unique(key => {
|
|
45
|
+
if (mode === 'composite') key.on(Recorded, event => event.key, event => event.label);
|
|
46
|
+
else key.on(Recorded, event => event.key);
|
|
47
|
+
if (mode === 'ignoreCasing') key.ignoreCasing();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
constraint(`UnprovenGuid${mode}`)(Definition);
|
|
52
|
+
(() => new EventScenario({ artifacts: { eventTypes: [Recorded], constraints: [Definition] } }))
|
|
53
|
+
.should.throw(UnsupportedEventSequenceOperation, 'artifacts.constraints');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const protection of ['compliance', 'security'] as const) {
|
|
59
|
+
describe(`when registering nested ${protection} metadata`, () => {
|
|
60
|
+
it('should reject before serializing an object payload', () => {
|
|
61
|
+
class Recorded { @field(Object) payload = {}; }
|
|
62
|
+
eventType(`Nested${protection}`)(Recorded);
|
|
63
|
+
getEventTypeMetadata(Recorded)!.schema.properties!.payload = {
|
|
64
|
+
type: 'object', properties: { nested: { type: 'string', [protection]: [{ metadataType: 'protected', details: '' }] } }
|
|
65
|
+
};
|
|
66
|
+
(() => new EventScenario({ artifacts: { eventTypes: [Recorded] }, constraints: 'disabled' }))
|
|
67
|
+
.should.throw(UnsupportedEventSequenceOperation, 'artifacts.eventTypes.schema');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe('when registering a uuid constraint schema alias', () => {
|
|
73
|
+
it('should reject unproven constraint semantics but accept a valid unconstrained payload', async () => {
|
|
74
|
+
class Recorded { @field(Guid) @unique('UuidAlias') key: Guid | string = Guid.empty; }
|
|
75
|
+
eventType('UuidAlias')(Recorded);
|
|
76
|
+
getEventTypeMetadata(Recorded)!.schema.properties!.key.format = 'uuid';
|
|
77
|
+
(() => new EventScenario({ artifacts: { eventTypes: [Recorded] } }))
|
|
78
|
+
.should.throw(UnsupportedEventSequenceOperation, 'artifacts.constraints');
|
|
79
|
+
const scenario = new EventScenario({ artifacts: { eventTypes: [Recorded] }, constraints: 'disabled' });
|
|
80
|
+
(await scenario.append('A', new Recorded())).isSuccess.should.equal(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -123,6 +123,16 @@ export class ProjectionReadModelProcessor<TReadModel extends object> implements
|
|
|
123
123
|
continue;
|
|
124
124
|
}
|
|
125
125
|
const schema = this._eventSchemas.get(typeId)!;
|
|
126
|
+
// EventScenario preserves client JSON, not kernel-normalized GUID/date/number/object JSON.
|
|
127
|
+
// Its deterministic hash is not kernel evidence for those payloads. Ordinary mappings
|
|
128
|
+
// still use the independently validated projection converters below.
|
|
129
|
+
if (event.scenarioLocalHash && from && Object.values(from.Value.Properties ?? {}).some(expression =>
|
|
130
|
+
/^\$eventContext\(Hash(?:\.Value)?\)$/.test(expression)) &&
|
|
131
|
+
Object.values(schema.properties ?? {}).some(property =>
|
|
132
|
+
!['string', 'boolean'].includes(property.type ?? '') || property.format !== undefined)) {
|
|
133
|
+
throw new UnsupportedProjectionOperation(String(this._definition.ReadModel), binding.path, binding.declaration,
|
|
134
|
+
'observed Hash mappings for numeric, Guid, date or object event fields are not fixture-backed; use a kernel-backed test');
|
|
135
|
+
}
|
|
126
136
|
const content = ProjectionValueConverter.eventContent(event.content, schema);
|
|
127
137
|
let state = engine[key];
|
|
128
138
|
if (!state) {
|