@cratis/chronicle 6.3.2 → 6.5.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 +1 -1
- package/EventStore.ts +7 -2
- package/README.md +13 -2
- package/artifacts/validateArtifactSchemas.spec.ts +75 -0
- package/artifacts/validateArtifactSchemas.ts +39 -0
- package/auditing/AmbientContext.spec.ts +57 -0
- package/auditing/CausationManager.ts +13 -15
- package/compliance/pii.ts +7 -6
- package/compliance/subject.ts +6 -5
- package/confidentiality/encrypted.ts +7 -6
- package/correlation/CorrelationIdManager.ts +5 -0
- package/dist/ChronicleClient.js +1 -1
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/EventStore.d.ts +3 -1
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +6 -2
- package/dist/EventStore.js.map +1 -1
- package/dist/artifacts/validateArtifactSchemas.d.ts +4 -0
- package/dist/artifacts/validateArtifactSchemas.d.ts.map +1 -0
- package/dist/artifacts/validateArtifactSchemas.js +38 -0
- package/dist/artifacts/validateArtifactSchemas.js.map +1 -0
- package/dist/auditing/CausationManager.d.ts +3 -1
- package/dist/auditing/CausationManager.d.ts.map +1 -1
- package/dist/auditing/CausationManager.js +9 -15
- package/dist/auditing/CausationManager.js.map +1 -1
- package/dist/compliance/pii.d.ts +2 -1
- package/dist/compliance/pii.d.ts.map +1 -1
- package/dist/compliance/pii.js +6 -5
- package/dist/compliance/pii.js.map +1 -1
- package/dist/compliance/subject.d.ts +2 -1
- package/dist/compliance/subject.d.ts.map +1 -1
- package/dist/compliance/subject.js +5 -4
- package/dist/compliance/subject.js.map +1 -1
- package/dist/confidentiality/encrypted.d.ts +2 -1
- package/dist/confidentiality/encrypted.d.ts.map +1 -1
- package/dist/confidentiality/encrypted.js +6 -5
- package/dist/confidentiality/encrypted.js.map +1 -1
- package/dist/correlation/CorrelationIdManager.d.ts +2 -0
- package/dist/correlation/CorrelationIdManager.d.ts.map +1 -1
- package/dist/correlation/CorrelationIdManager.js +4 -0
- package/dist/correlation/CorrelationIdManager.js.map +1 -1
- package/dist/eventSequences/AppendOptions.d.ts +3 -4
- package/dist/eventSequences/AppendOptions.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +19 -14
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/eventSequences/EventSequenceNumber.d.ts +3 -1
- package/dist/eventSequences/EventSequenceNumber.d.ts.map +1 -1
- package/dist/eventSequences/EventSequenceNumber.js +3 -1
- package/dist/eventSequences/EventSequenceNumber.js.map +1 -1
- package/dist/events/eventTypeDecorator.d.ts +8 -7
- package/dist/events/eventTypeDecorator.d.ts.map +1 -1
- package/dist/events/eventTypeDecorator.js +22 -5
- package/dist/events/eventTypeDecorator.js.map +1 -1
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +2 -0
- package/dist/events/index.js.map +1 -1
- package/dist/events/isRegisteredEvent.d.ts +4 -0
- package/dist/events/isRegisteredEvent.d.ts.map +1 -0
- package/dist/events/isRegisteredEvent.js +9 -0
- package/dist/events/isRegisteredEvent.js.map +1 -0
- package/dist/identity/IdentityProvider.d.ts +2 -0
- package/dist/identity/IdentityProvider.d.ts.map +1 -1
- package/dist/identity/IdentityProvider.js +4 -0
- package/dist/identity/IdentityProvider.js.map +1 -1
- package/dist/projections/modelBound/addFrom.d.ts +2 -1
- package/dist/projections/modelBound/addFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/addFrom.js +4 -3
- package/dist/projections/modelBound/addFrom.js.map +1 -1
- package/dist/projections/modelBound/childrenFrom.d.ts +2 -1
- package/dist/projections/modelBound/childrenFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/childrenFrom.js +4 -3
- package/dist/projections/modelBound/childrenFrom.js.map +1 -1
- package/dist/projections/modelBound/clearWith.d.ts +2 -1
- package/dist/projections/modelBound/clearWith.d.ts.map +1 -1
- package/dist/projections/modelBound/clearWith.js +4 -3
- package/dist/projections/modelBound/clearWith.js.map +1 -1
- package/dist/projections/modelBound/count.d.ts +2 -1
- package/dist/projections/modelBound/count.d.ts.map +1 -1
- package/dist/projections/modelBound/count.js +4 -3
- package/dist/projections/modelBound/count.js.map +1 -1
- package/dist/projections/modelBound/decrement.d.ts +2 -1
- package/dist/projections/modelBound/decrement.d.ts.map +1 -1
- package/dist/projections/modelBound/decrement.js +4 -3
- package/dist/projections/modelBound/decrement.js.map +1 -1
- package/dist/projections/modelBound/fromAll.d.ts +2 -1
- package/dist/projections/modelBound/fromAll.d.ts.map +1 -1
- package/dist/projections/modelBound/fromAll.js +4 -3
- package/dist/projections/modelBound/fromAll.js.map +1 -1
- package/dist/projections/modelBound/fromEvery.d.ts +2 -1
- package/dist/projections/modelBound/fromEvery.d.ts.map +1 -1
- package/dist/projections/modelBound/fromEvery.js +4 -3
- package/dist/projections/modelBound/fromEvery.js.map +1 -1
- package/dist/projections/modelBound/increment.d.ts +2 -1
- package/dist/projections/modelBound/increment.d.ts.map +1 -1
- package/dist/projections/modelBound/increment.js +4 -3
- package/dist/projections/modelBound/increment.js.map +1 -1
- package/dist/projections/modelBound/join.d.ts +2 -1
- package/dist/projections/modelBound/join.d.ts.map +1 -1
- package/dist/projections/modelBound/join.js +4 -3
- package/dist/projections/modelBound/join.js.map +1 -1
- package/dist/projections/modelBound/nested.d.ts +1 -6
- package/dist/projections/modelBound/nested.d.ts.map +1 -1
- package/dist/projections/modelBound/nested.js +6 -2
- package/dist/projections/modelBound/nested.js.map +1 -1
- package/dist/projections/modelBound/noAutoMap.d.ts +4 -11
- package/dist/projections/modelBound/noAutoMap.d.ts.map +1 -1
- package/dist/projections/modelBound/noAutoMap.js +6 -2
- package/dist/projections/modelBound/noAutoMap.js.map +1 -1
- package/dist/projections/modelBound/removedWith.d.ts +2 -1
- package/dist/projections/modelBound/removedWith.d.ts.map +1 -1
- package/dist/projections/modelBound/removedWith.js +4 -3
- package/dist/projections/modelBound/removedWith.js.map +1 -1
- package/dist/projections/modelBound/removedWithJoin.d.ts +2 -1
- package/dist/projections/modelBound/removedWithJoin.d.ts.map +1 -1
- package/dist/projections/modelBound/removedWithJoin.js +4 -3
- package/dist/projections/modelBound/removedWithJoin.js.map +1 -1
- package/dist/projections/modelBound/setFrom.d.ts +2 -1
- package/dist/projections/modelBound/setFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/setFrom.js +4 -3
- package/dist/projections/modelBound/setFrom.js.map +1 -1
- package/dist/projections/modelBound/setFromContext.d.ts +3 -2
- package/dist/projections/modelBound/setFromContext.d.ts.map +1 -1
- package/dist/projections/modelBound/setFromContext.js +4 -3
- package/dist/projections/modelBound/setFromContext.js.map +1 -1
- package/dist/projections/modelBound/setValue.d.ts +2 -1
- package/dist/projections/modelBound/setValue.d.ts.map +1 -1
- package/dist/projections/modelBound/setValue.js +4 -3
- package/dist/projections/modelBound/setValue.js.map +1 -1
- package/dist/projections/modelBound/subtractFrom.d.ts +2 -1
- package/dist/projections/modelBound/subtractFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/subtractFrom.js +4 -3
- package/dist/projections/modelBound/subtractFrom.js.map +1 -1
- package/dist/readModels/IReadModels.d.ts +10 -1
- package/dist/readModels/IReadModels.d.ts.map +1 -1
- package/dist/readModels/MaterializedReadModels.js +2 -2
- package/dist/readModels/MaterializedReadModels.js.map +1 -1
- package/dist/readModels/ReadModels.d.ts +6 -1
- package/dist/readModels/ReadModels.d.ts.map +1 -1
- package/dist/readModels/ReadModels.js +28 -2
- package/dist/readModels/ReadModels.js.map +1 -1
- package/dist/readModels/readModel.d.ts +2 -1
- package/dist/readModels/readModel.d.ts.map +1 -1
- package/dist/readModels/readModel.js +22 -5
- package/dist/readModels/readModel.js.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.d.ts +6 -1
- package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.js +41 -31
- package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
- package/dist/schemas/createDeferredSchema.d.ts +7 -0
- package/dist/schemas/createDeferredSchema.d.ts.map +1 -0
- package/dist/schemas/createDeferredSchema.js +19 -0
- package/dist/schemas/createDeferredSchema.js.map +1 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/jsonSchemaProperty.d.ts +2 -1
- package/dist/schemas/jsonSchemaProperty.d.ts.map +1 -1
- package/dist/schemas/jsonSchemaProperty.js +3 -2
- package/dist/schemas/jsonSchemaProperty.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/TypeIntrospector.d.ts.map +1 -1
- package/dist/types/TypeIntrospector.js +9 -3
- package/dist/types/TypeIntrospector.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/propertyDecoratorMetadata.d.ts +17 -0
- package/dist/types/propertyDecoratorMetadata.d.ts.map +1 -0
- package/dist/types/propertyDecoratorMetadata.js +76 -0
- package/dist/types/propertyDecoratorMetadata.js.map +1 -0
- package/dist/types/standardDecoratorMetadata.d.ts +9 -0
- package/dist/types/standardDecoratorMetadata.d.ts.map +1 -0
- package/dist/types/standardDecoratorMetadata.js +20 -0
- package/dist/types/standardDecoratorMetadata.js.map +1 -0
- package/eventSequences/AppendOptions.ts +3 -4
- package/eventSequences/EventSequence.spec.ts +59 -0
- package/eventSequences/EventSequence.ts +21 -15
- package/eventSequences/EventSequenceNumber.ts +4 -1
- package/eventSequences/for_EventSequence/when_appending/with_metadata.spec.ts +2 -1
- package/events/classDecorators.standard.spec.ts +77 -0
- package/events/eventTypeDecorator.ts +31 -13
- package/events/index.ts +3 -0
- package/events/isRegisteredEvent.spec.ts +26 -0
- package/events/isRegisteredEvent.ts +12 -0
- package/identity/IdentityProvider.ts +5 -0
- package/package.json +3 -1
- package/projections/modelBound/addFrom.ts +5 -4
- package/projections/modelBound/childrenFrom.ts +5 -4
- package/projections/modelBound/clearWith.ts +5 -4
- package/projections/modelBound/count.ts +5 -4
- package/projections/modelBound/decorators.standard.spec.ts +71 -0
- package/projections/modelBound/decrement.ts +5 -4
- package/projections/modelBound/fromAll.ts +5 -4
- package/projections/modelBound/fromEvery.ts +5 -4
- package/projections/modelBound/increment.ts +5 -4
- package/projections/modelBound/join.ts +5 -4
- package/projections/modelBound/nested.ts +9 -2
- package/projections/modelBound/noAutoMap.ts +11 -2
- package/projections/modelBound/removedWith.ts +5 -4
- package/projections/modelBound/removedWithJoin.ts +5 -4
- package/projections/modelBound/setFrom.ts +5 -4
- package/projections/modelBound/setFromContext.ts +7 -6
- package/projections/modelBound/setValue.ts +5 -4
- package/projections/modelBound/subtractFrom.ts +5 -4
- package/readModels/IReadModels.ts +11 -1
- package/readModels/MaterializedReadModels.spec.ts +17 -6
- package/readModels/MaterializedReadModels.ts +2 -2
- package/readModels/ReadModels.spec.ts +96 -3
- package/readModels/ReadModels.ts +32 -2
- package/readModels/readModel.ts +24 -6
- package/readModels/standardFallbacks.standard.spec.ts +39 -0
- package/schemas/JsonSchemaGenerator.confidentiality.spec.ts +2 -0
- package/schemas/JsonSchemaGenerator.spec.ts +28 -1
- package/schemas/JsonSchemaGenerator.ts +45 -30
- package/schemas/createDeferredSchema.ts +21 -0
- package/schemas/decorators.expected.fixture.ts +24 -0
- package/schemas/decorators.legacy.spec.ts +62 -0
- package/schemas/decorators.standard.spec.ts +235 -0
- package/schemas/index.ts +1 -0
- package/schemas/jsonSchemaProperty.ts +4 -3
- package/schemas/legacyDesignMetadata.spec.ts +39 -0
- package/types/TypeIntrospector.ts +8 -3
- package/types/index.ts +2 -0
- package/types/propertyDecoratorMetadata.ts +80 -0
- package/types/standardDecoratorMetadata.spec.ts +31 -0
- package/types/standardDecoratorMetadata.ts +26 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,62 @@
|
|
|
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, expect, it } from 'vitest';
|
|
6
|
+
import { eventType, getEventTypeMetadata } from '../events/eventTypeDecorator.js';
|
|
7
|
+
import { getReadModelMetadata, readModel } from '../readModels/readModel.js';
|
|
8
|
+
import { expectedProperties } from './decorators.expected.fixture.js';
|
|
9
|
+
import { getProjectionMetadata, projection } from '../projections/declarative/projection.js';
|
|
10
|
+
import { getReducerMetadata, reducer } from '../reducers/reducer.js';
|
|
11
|
+
import { getReactorMetadata, reactor } from '../reactors/reactor.js';
|
|
12
|
+
|
|
13
|
+
class Code extends ConceptAs<string> {
|
|
14
|
+
static readonly valueType = String;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class Quantity extends ConceptAs<number> {
|
|
18
|
+
static readonly valueType = Number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class Address {
|
|
22
|
+
@field(String) city!: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@eventType('legacy-fixture')
|
|
26
|
+
class LegacyEvent {
|
|
27
|
+
@field(String) name!: string;
|
|
28
|
+
@field(Number) age!: number;
|
|
29
|
+
@field(Boolean) active!: boolean;
|
|
30
|
+
@field(Guid) id!: Guid;
|
|
31
|
+
@field(Date) occurred!: Date;
|
|
32
|
+
@field(Array, { genericArguments: [Code] }) codes!: Code[];
|
|
33
|
+
@field(Address) address!: Address;
|
|
34
|
+
@field(Code) code!: Code;
|
|
35
|
+
@field(Quantity) quantity!: Quantity;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@readModel('legacy-model')
|
|
39
|
+
class LegacyModel {
|
|
40
|
+
@field(Number) count!: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@projection('legacy-projection', LegacyModel)
|
|
44
|
+
class LegacyProjection {}
|
|
45
|
+
|
|
46
|
+
@reducer('legacy-reducer', undefined, LegacyModel)
|
|
47
|
+
class LegacyReducer {}
|
|
48
|
+
|
|
49
|
+
@reactor('legacy-reactor')
|
|
50
|
+
class LegacyReactor {}
|
|
51
|
+
|
|
52
|
+
describe('legacy decorator syntax', () => {
|
|
53
|
+
it('registers the same schemas as standard decorators', () => {
|
|
54
|
+
expect(getEventTypeMetadata(LegacyEvent)?.eventType.id.value).toBe('legacy-fixture');
|
|
55
|
+
expect(getReadModelMetadata(LegacyModel)?.schema.properties?.count.type).toBe('number');
|
|
56
|
+
expect(getEventTypeMetadata(LegacyEvent)?.schema.properties).toEqual(expectedProperties);
|
|
57
|
+
expect(getEventTypeMetadata(LegacyEvent)?.schema.required).toEqual(Object.keys(expectedProperties));
|
|
58
|
+
expect(getProjectionMetadata(LegacyProjection)?.id.value).toBe('legacy-projection');
|
|
59
|
+
expect(getReducerMetadata(LegacyReducer)?.id.value).toBe('legacy-reducer');
|
|
60
|
+
expect(getReactorMetadata(LegacyReactor)?.id.value).toBe('legacy-reactor');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
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, typeKey } from '@cratis/fundamentals';
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import { eventType, getEventTypeMetadata } from '../events/eventTypeDecorator.js';
|
|
7
|
+
import { getReadModelMetadata, readModel } from '../readModels/readModel.js';
|
|
8
|
+
import { getSetFromMetadata, setFrom } from '../projections/modelBound/setFrom.js';
|
|
9
|
+
import { getProjectionMetadata, projection } from '../projections/declarative/projection.js';
|
|
10
|
+
import { getReducerMetadata, reducer } from '../reducers/reducer.js';
|
|
11
|
+
import { getReactorMetadata, reactor } from '../reactors/reactor.js';
|
|
12
|
+
import { DecoratorType, TypeDiscoverer } from '../types/index.js';
|
|
13
|
+
import { pii } from '../compliance/pii.js';
|
|
14
|
+
import { subject, getSubjectPropertyName } from '../compliance/subject.js';
|
|
15
|
+
import { encrypted } from '../confidentiality/encrypted.js';
|
|
16
|
+
import { JsonSchemaGenerator } from './JsonSchemaGenerator.js';
|
|
17
|
+
import { expectedProperties } from './decorators.expected.fixture.js';
|
|
18
|
+
|
|
19
|
+
class Code extends ConceptAs<string> {
|
|
20
|
+
static readonly valueType = String;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class Quantity extends ConceptAs<number> {
|
|
24
|
+
static readonly valueType = Number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class Address {
|
|
28
|
+
@field(String) city!: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function replaceClass<T extends new (...args: never[]) => object>(value: T, _context: ClassDecoratorContext<T>): T {
|
|
32
|
+
return class extends (value as new () => object) {} as T;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@replaceClass
|
|
36
|
+
@eventType('replaced-event')
|
|
37
|
+
class ReplacedEvent {
|
|
38
|
+
@field(String) value!: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@replaceClass
|
|
42
|
+
@readModel('replaced-model')
|
|
43
|
+
class ReplacedModel {
|
|
44
|
+
@field(Number) amount!: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@eventType('standard-fixture')
|
|
48
|
+
class StandardEvent {
|
|
49
|
+
@field(String) name!: string;
|
|
50
|
+
@field(Number) age!: number;
|
|
51
|
+
@field(Boolean) active!: boolean;
|
|
52
|
+
@field(Guid) id!: Guid;
|
|
53
|
+
@field(Date) occurred!: Date;
|
|
54
|
+
@field(Array, { genericArguments: [Code] }) codes!: Code[];
|
|
55
|
+
@field(Address) address!: Address;
|
|
56
|
+
@field(Code) code!: Code;
|
|
57
|
+
@field(Quantity) quantity!: Quantity;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@readModel('standard-model')
|
|
61
|
+
class StandardModel {
|
|
62
|
+
@field(Number)
|
|
63
|
+
@setFrom(StandardEvent)
|
|
64
|
+
count!: number;
|
|
65
|
+
|
|
66
|
+
@field(String)
|
|
67
|
+
@subject()
|
|
68
|
+
ownerId!: string;
|
|
69
|
+
|
|
70
|
+
@field(String)
|
|
71
|
+
@pii('personal')
|
|
72
|
+
contact!: string;
|
|
73
|
+
|
|
74
|
+
@field(String)
|
|
75
|
+
@encrypted()
|
|
76
|
+
secret!: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class ParentMapping {
|
|
80
|
+
@setFrom(StandardEvent) value!: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class ChildMapping extends ParentMapping {
|
|
84
|
+
@setFrom(StandardEvent, 'name') value = '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@eventType('empty-schema')
|
|
88
|
+
class MissingMembers {}
|
|
89
|
+
|
|
90
|
+
@readModel('empty-model')
|
|
91
|
+
class EmptyModel {}
|
|
92
|
+
|
|
93
|
+
@eventType('missing-field')
|
|
94
|
+
class MissingField {
|
|
95
|
+
constructor(readonly value: string) {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class UnspecifiedQuantity extends ConceptAs<number> {}
|
|
99
|
+
|
|
100
|
+
class ConceptGuid extends ConceptAs<Guid> {
|
|
101
|
+
static readonly valueType = Guid;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class ConceptDate extends ConceptAs<Date> {
|
|
105
|
+
static readonly valueType = Date;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@eventType('untyped-concept')
|
|
109
|
+
class UntypedConceptEvent {
|
|
110
|
+
@field(UnspecifiedQuantity) quantity!: UnspecifiedQuantity;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class ForeignConcept {
|
|
114
|
+
static readonly [typeKey] = 'ConceptAs';
|
|
115
|
+
static readonly valueType = Number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
class ForeignGuid {
|
|
119
|
+
static readonly [typeKey] = 'Guid';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@eventType('foreign-types')
|
|
123
|
+
class ForeignTypes {
|
|
124
|
+
@field(ForeignConcept) amount!: ForeignConcept;
|
|
125
|
+
@field(ForeignGuid) id!: ForeignGuid;
|
|
126
|
+
@field(Object) data!: object;
|
|
127
|
+
@field(ConceptGuid) conceptId!: ConceptGuid;
|
|
128
|
+
@field(ConceptDate) conceptDate!: ConceptDate;
|
|
129
|
+
@field(Array, { genericArguments: [String] }) names!: string[];
|
|
130
|
+
@field(Array, { genericArguments: [Number] }) numbers!: number[];
|
|
131
|
+
@field(Array, { genericArguments: [Boolean] }) flags!: boolean[];
|
|
132
|
+
@field(Array, { genericArguments: [Guid] }) guids!: Guid[];
|
|
133
|
+
@field(Array, { genericArguments: [Address] }) addresses!: Address[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class UndecoratedModel {
|
|
137
|
+
@field(Array) items!: string[];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@eventType('untyped-array')
|
|
141
|
+
class UntypedArray {
|
|
142
|
+
@field(Array) items!: string[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@projection('standard-projection', StandardModel)
|
|
146
|
+
class StandardProjection {}
|
|
147
|
+
|
|
148
|
+
@reducer('standard-reducer', undefined, StandardModel)
|
|
149
|
+
class StandardReducer {}
|
|
150
|
+
|
|
151
|
+
@reactor('standard-reactor')
|
|
152
|
+
class StandardReactor {}
|
|
153
|
+
|
|
154
|
+
const standardSchema = getEventTypeMetadata(StandardEvent)!.schema;
|
|
155
|
+
|
|
156
|
+
// No instance is constructed: @field's standard metadata must be available after class evaluation.
|
|
157
|
+
describe('standard decorator syntax', () => {
|
|
158
|
+
it('registers the event and read model with their declared member types', () => {
|
|
159
|
+
expect(getEventTypeMetadata(StandardEvent)?.eventType.id.value).toBe('standard-fixture');
|
|
160
|
+
expect(getReadModelMetadata(StandardModel)?.schema.properties?.count.type).toBe('number');
|
|
161
|
+
expect(getSetFromMetadata(StandardModel.prototype, 'count')).toEqual([{ eventType: StandardEvent, eventPropertyName: undefined }]);
|
|
162
|
+
expect(getSubjectPropertyName(StandardModel)).toBe('ownerId');
|
|
163
|
+
expect(getReadModelMetadata(StandardModel)?.schema.properties?.contact.compliance).toEqual([{ metadataType: 'PII', details: 'personal' }]);
|
|
164
|
+
expect(getReadModelMetadata(StandardModel)?.schema.properties?.secret.security).toEqual([{ metadataType: 'EncryptedSubject', details: '' }]);
|
|
165
|
+
expect(standardSchema.properties).toEqual(expectedProperties);
|
|
166
|
+
expect(standardSchema.required).toEqual(Object.keys(expectedProperties));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('resolves schemas on an outer decorator replacement class', () => {
|
|
170
|
+
expect(getEventTypeMetadata(ReplacedEvent)!.schema.properties?.value.type).toBe('string');
|
|
171
|
+
expect(getReadModelMetadata(ReplacedModel)!.schema.properties?.amount.type).toBe('number');
|
|
172
|
+
expect(TypeDiscoverer.default.getTypeByDecoratorTypeAndName(DecoratorType.EventType, 'replaced-event')).toBe(ReplacedEvent);
|
|
173
|
+
expect(TypeDiscoverer.default.getTypeByDecoratorTypeAndName(DecoratorType.ReadModel, 'replaced-model')).toBe(ReplacedModel);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('keeps inherited property metadata copy-on-write', () => {
|
|
177
|
+
expect(getSetFromMetadata(ParentMapping.prototype, 'value')).toHaveLength(1);
|
|
178
|
+
expect(getSetFromMetadata(ChildMapping.prototype, 'value')).toHaveLength(2);
|
|
179
|
+
expect(getSetFromMetadata(ParentMapping.prototype, 'value')).toHaveLength(1);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('registers projections, reducers, and reactors', () => {
|
|
183
|
+
expect(getProjectionMetadata(StandardProjection)?.id.value).toBe('standard-projection');
|
|
184
|
+
expect(getReducerMetadata(StandardReducer)?.id.value).toBe('standard-reducer');
|
|
185
|
+
expect(getReactorMetadata(StandardReactor)?.id.value).toBe('standard-reactor');
|
|
186
|
+
expect(TypeDiscoverer.default.getTypeByDecoratorTypeAndName(DecoratorType.Projection, 'standard-projection')).toBe(StandardProjection);
|
|
187
|
+
expect(TypeDiscoverer.default.getTypeByDecoratorTypeAndName(DecoratorType.Reducer, 'standard-reducer')).toBe(StandardReducer);
|
|
188
|
+
expect(TypeDiscoverer.default.getTypeByDecoratorTypeAndName(DecoratorType.Reactor, 'standard-reactor')).toBe(StandardReactor);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('rejects protected event source identifiers in standard mode', () => {
|
|
192
|
+
expect(() => {
|
|
193
|
+
class BadPII {
|
|
194
|
+
@pii() eventSourceId!: string;
|
|
195
|
+
}
|
|
196
|
+
void BadPII;
|
|
197
|
+
}).toThrow(/cannot be applied to 'eventSourceId'/);
|
|
198
|
+
expect(() => {
|
|
199
|
+
class BadEncrypted {
|
|
200
|
+
@encrypted() eventSourceId!: string;
|
|
201
|
+
}
|
|
202
|
+
void BadEncrypted;
|
|
203
|
+
}).toThrow(/cannot be applied to 'eventSourceId'/);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('rejects missing member metadata instead of registering an empty schema', () => {
|
|
207
|
+
expect(getEventTypeMetadata(MissingMembers)!.schema.properties).toEqual({});
|
|
208
|
+
expect(getReadModelMetadata(EmptyModel)!.schema.properties).toEqual({});
|
|
209
|
+
expect(() => getEventTypeMetadata(MissingField)!.schema).toThrow(/Cannot determine the type of MissingField.value/);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('rejects a concept without a declared primitive in standard mode', () => {
|
|
213
|
+
expect(() => getEventTypeMetadata(UntypedConceptEvent)!.schema).toThrow(/Cannot determine the value type of concept UnspecifiedQuantity/);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('recognizes Fundamentals types from another package copy by their type key', () => {
|
|
217
|
+
expect(getEventTypeMetadata(ForeignTypes)!.schema.properties).toMatchObject({
|
|
218
|
+
amount: { type: 'number' },
|
|
219
|
+
id: { type: 'string', format: 'guid' },
|
|
220
|
+
data: { type: 'object' },
|
|
221
|
+
conceptId: { type: 'string', format: 'guid' },
|
|
222
|
+
conceptDate: { type: 'string', format: 'date-time' },
|
|
223
|
+
names: { type: 'array', items: { type: 'string' } },
|
|
224
|
+
numbers: { type: 'array', items: { type: 'number' } },
|
|
225
|
+
flags: { type: 'array', items: { type: 'boolean' } },
|
|
226
|
+
guids: { type: 'array', items: { type: 'string', format: 'guid' } },
|
|
227
|
+
addresses: { type: 'array', items: { type: 'object', properties: { city: { type: 'string' } } } }
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('rejects untyped arrays in standard mode', () => {
|
|
232
|
+
expect(() => getEventTypeMetadata(UntypedArray)!.schema).toThrow(/Cannot determine the element type of UntypedArray.items/);
|
|
233
|
+
expect(() => JsonSchemaGenerator.generate(UndecoratedModel)).toThrow(/Cannot determine the element type of UndecoratedModel.items/);
|
|
234
|
+
});
|
|
235
|
+
});
|
package/schemas/index.ts
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
4
|
export { JsonSchemaGenerator } from './JsonSchemaGenerator.js';
|
|
5
|
+
export type { JsonSchemaGenerationOptions } from './JsonSchemaGenerator.js';
|
|
5
6
|
export { jsonSchemaProperty, getTrackedJsonSchemaProperties } from './jsonSchemaProperty.js';
|
|
6
7
|
export type { ComplianceSchemaMetadata, JsonSchema } from './JsonSchema.js';
|
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { TypeIntrospector } from '../types/index.js';
|
|
6
|
+
import { ChroniclePropertyDecorator, decorateProperty } from '../types/propertyDecoratorMetadata.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Decorates a class property so its runtime type metadata can be used for JSON schema generation.
|
|
9
10
|
* @returns A property decorator.
|
|
10
11
|
*/
|
|
11
|
-
export function jsonSchemaProperty():
|
|
12
|
-
return (target: object, propertyKey: string | symbol) => {
|
|
12
|
+
export function jsonSchemaProperty(): ChroniclePropertyDecorator {
|
|
13
|
+
return decorateProperty((target: object, propertyKey: string | symbol) => {
|
|
13
14
|
TypeIntrospector.trackProperty(target.constructor as Function, propertyKey.toString());
|
|
14
|
-
};
|
|
15
|
+
});
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -0,0 +1,39 @@
|
|
|
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 { runInNewContext } from 'node:vm';
|
|
6
|
+
import { ConceptAs, field, Guid } from '@cratis/fundamentals';
|
|
7
|
+
import ts from 'typescript';
|
|
8
|
+
import { describe, expect, it } from 'vitest';
|
|
9
|
+
import { JsonSchemaGenerator } from './JsonSchemaGenerator.js';
|
|
10
|
+
|
|
11
|
+
const source = `
|
|
12
|
+
function mark() { return () => {}; }
|
|
13
|
+
class LegacyId extends ConceptAs {
|
|
14
|
+
@mark() value!: Guid;
|
|
15
|
+
}
|
|
16
|
+
class LegacyHolder {
|
|
17
|
+
@field(LegacyId) id!: LegacyId;
|
|
18
|
+
}
|
|
19
|
+
exports.types = { LegacyId, LegacyHolder };
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const compiled = ts.transpileModule(source, {
|
|
23
|
+
compilerOptions: {
|
|
24
|
+
target: ts.ScriptTarget.ES2022,
|
|
25
|
+
module: ts.ModuleKind.CommonJS,
|
|
26
|
+
experimentalDecorators: true,
|
|
27
|
+
emitDecoratorMetadata: true
|
|
28
|
+
}
|
|
29
|
+
}).outputText;
|
|
30
|
+
|
|
31
|
+
describe('legacy emitDecoratorMetadata compatibility', () => {
|
|
32
|
+
it('resolves a Guid-backed concept through emitted design:type', () => {
|
|
33
|
+
const sandbox = { exports: {} as { types?: { LegacyId: Function; LegacyHolder: Function } }, ConceptAs, field, Guid, Reflect };
|
|
34
|
+
runInNewContext(compiled, sandbox);
|
|
35
|
+
const { LegacyId, LegacyHolder } = sandbox.exports.types!;
|
|
36
|
+
expect(Reflect.getMetadata('design:type', LegacyId.prototype, 'value')).toBe(Guid);
|
|
37
|
+
expect(JsonSchemaGenerator.generate(LegacyHolder).properties?.id).toEqual({ type: 'string', format: 'guid' });
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { Constructor, Fields } from '@cratis/fundamentals';
|
|
6
|
+
import { getStandardMetadata } from './standardDecoratorMetadata.js';
|
|
6
7
|
|
|
7
8
|
/** Metadata key for tracked schema properties on a target type. */
|
|
8
9
|
const TRACKED_PROPERTIES_METADATA_KEY = 'chronicle:typeIntrospection:properties';
|
|
@@ -31,7 +32,10 @@ export class TypeIntrospector {
|
|
|
31
32
|
* @returns The tracked property names.
|
|
32
33
|
*/
|
|
33
34
|
static getTrackedProperties(target: Function): string[] {
|
|
34
|
-
|
|
35
|
+
const legacy = Reflect.getMetadata(TRACKED_PROPERTIES_METADATA_KEY, target) as string[] | undefined ?? [];
|
|
36
|
+
const metadata = getStandardMetadata(target);
|
|
37
|
+
const standard = metadata ? Reflect.getMetadata(TRACKED_PROPERTIES_METADATA_KEY, metadata) as string[] | undefined ?? [] : [];
|
|
38
|
+
return [...new Set([...legacy, ...standard])];
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
/**
|
|
@@ -63,10 +67,11 @@ export class TypeIntrospector {
|
|
|
63
67
|
for (const property of propertyNames) {
|
|
64
68
|
let runtimeType = fieldTypes.get(property);
|
|
65
69
|
if (!runtimeType || runtimeType === Object) {
|
|
66
|
-
|
|
70
|
+
const reflected = Reflect.getMetadata('design:type', target.prototype, property) as Function | undefined;
|
|
71
|
+
if (reflected && reflected !== Object) runtimeType = reflected;
|
|
67
72
|
}
|
|
68
73
|
if (!runtimeType || runtimeType === Object) {
|
|
69
|
-
runtimeType = this.getRuntimeTypeFromValue(defaultValues[property]);
|
|
74
|
+
runtimeType = this.getRuntimeTypeFromValue(defaultValues[property]) ?? runtimeType;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
members.set(property, runtimeType);
|
package/types/index.ts
CHANGED
|
@@ -4,3 +4,5 @@
|
|
|
4
4
|
export { DecoratorType } from './DecoratorType.js';
|
|
5
5
|
export { TypeDiscoverer } from './TypeDiscoverer.js';
|
|
6
6
|
export { TypeIntrospector } from './TypeIntrospector.js';
|
|
7
|
+
export type { ChronicleClassDecorator } from './standardDecoratorMetadata.js';
|
|
8
|
+
export type { ChroniclePropertyDecorator, ChronicleClassOrPropertyDecorator } from './propertyDecoratorMetadata.js';
|
|
@@ -0,0 +1,80 @@
|
|
|
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 { ChronicleClassDecorator, getStandardMetadata } from './standardDecoratorMetadata.js';
|
|
6
|
+
|
|
7
|
+
/** A property decorator accepted by both the legacy and standard TypeScript transforms. */
|
|
8
|
+
export type ChroniclePropertyDecorator = PropertyDecorator & ((value: undefined, context: ClassFieldDecoratorContext) => void);
|
|
9
|
+
|
|
10
|
+
/** A decorator applicable to either a class or a public instance field. */
|
|
11
|
+
export type ChronicleClassOrPropertyDecorator = ChronicleClassDecorator & ChroniclePropertyDecorator;
|
|
12
|
+
|
|
13
|
+
/** Dispatches class and field decorations while retaining the legacy invocation shape. */
|
|
14
|
+
export function decorateClassOrProperty(legacy: (target: object, property?: string | symbol) => void): ChronicleClassOrPropertyDecorator {
|
|
15
|
+
const propertyDecorator = decorateProperty(legacy);
|
|
16
|
+
return (target: object | undefined, propertyOrContext?: string | symbol | ClassDecoratorContext | ClassFieldDecoratorContext) => {
|
|
17
|
+
if (typeof propertyOrContext === 'object' && propertyOrContext !== null) {
|
|
18
|
+
if (propertyOrContext.kind === 'class') {
|
|
19
|
+
if (typeof target !== 'function') throw new TypeError('Chronicle class decorators require a class constructor.');
|
|
20
|
+
legacy(target);
|
|
21
|
+
} else {
|
|
22
|
+
propertyDecorator(target as undefined, propertyOrContext as ClassFieldDecoratorContext);
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!target) throw new TypeError('Invalid legacy decorator invocation.');
|
|
27
|
+
legacy(target, propertyOrContext);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Stores standard property annotations on the class metadata object, without constructing an instance. */
|
|
32
|
+
export function decorateProperty(legacy: PropertyDecorator): ChroniclePropertyDecorator {
|
|
33
|
+
return (target: object | undefined, keyOrContext: string | symbol | ClassFieldDecoratorContext) => {
|
|
34
|
+
if (typeof keyOrContext === 'object' && keyOrContext !== null) {
|
|
35
|
+
const context = keyOrContext;
|
|
36
|
+
if (target !== undefined || context.kind !== 'field' || context.static || context.private || typeof context.name !== 'string' || !context.metadata) {
|
|
37
|
+
throw new TypeError('Chronicle property decorators require a public instance field with standard decorator metadata.');
|
|
38
|
+
}
|
|
39
|
+
// Legacy decorators use target.constructor to track fields and store type-level annotations.
|
|
40
|
+
// The context.metadata object is owned by this class; never mutate its parent's metadata.
|
|
41
|
+
Object.defineProperty(context.metadata, 'constructor', { value: context.metadata, configurable: true });
|
|
42
|
+
legacy(context.metadata, context.name);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!target || (typeof keyOrContext !== 'string' && typeof keyOrContext !== 'symbol')) {
|
|
46
|
+
throw new TypeError('Invalid legacy property decorator invocation.');
|
|
47
|
+
}
|
|
48
|
+
legacy(target, keyOrContext);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Reads an annotation declared by either decorator transform on a class field. */
|
|
53
|
+
export function getPropertyMetadata<T>(key: string, target: object, property: string): T | undefined {
|
|
54
|
+
const ownLegacy = Reflect.getOwnMetadata(key, target, property) as T | undefined;
|
|
55
|
+
if (ownLegacy !== undefined) return ownLegacy;
|
|
56
|
+
const type = (target as { constructor?: Function }).constructor;
|
|
57
|
+
const metadata = type && getStandardMetadata(type);
|
|
58
|
+
const ownStandard = metadata && Reflect.getOwnMetadata(key, metadata, property) as T | undefined;
|
|
59
|
+
if (ownStandard !== undefined) return ownStandard;
|
|
60
|
+
return Reflect.getMetadata(key, target, property) as T | undefined
|
|
61
|
+
?? (metadata ? Reflect.getMetadata(key, metadata, property) as T | undefined : undefined);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Checks whether either decorator transform declared an annotation on a class field. */
|
|
65
|
+
export function hasPropertyMetadata(key: string, target: object, property: string): boolean {
|
|
66
|
+
const type = (target as { constructor?: Function }).constructor;
|
|
67
|
+
const metadata = type && getStandardMetadata(type);
|
|
68
|
+
return Reflect.hasMetadata(key, target, property) || !!metadata && Reflect.hasMetadata(key, metadata, property);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Reads a type-level annotation emitted by a standard field decorator (or its legacy equivalent). */
|
|
72
|
+
export function getTypeOrFieldMetadata<T>(key: string, type: Function): T | undefined {
|
|
73
|
+
const ownLegacy = Reflect.getOwnMetadata(key, type) as T | undefined;
|
|
74
|
+
if (ownLegacy !== undefined) return ownLegacy;
|
|
75
|
+
const metadata = getStandardMetadata(type);
|
|
76
|
+
const ownStandard = metadata && Reflect.getOwnMetadata(key, metadata) as T | undefined;
|
|
77
|
+
if (ownStandard !== undefined) return ownStandard;
|
|
78
|
+
return Reflect.getMetadata(key, type) as T | undefined
|
|
79
|
+
?? (metadata ? Reflect.getMetadata(key, metadata) as T | undefined : undefined);
|
|
80
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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 { readFileSync } from 'node:fs';
|
|
5
|
+
import { runInNewContext } from 'node:vm';
|
|
6
|
+
import ts from 'typescript';
|
|
7
|
+
import { describe, expect, it } from 'vitest';
|
|
8
|
+
|
|
9
|
+
const compilerOptions = { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS, experimentalDecorators: false };
|
|
10
|
+
const fixture = ts.transpileModule(`
|
|
11
|
+
function mark(_value: Function, context: ClassDecoratorContext) {
|
|
12
|
+
if (!context.metadata) throw new Error('Missing standard decorator metadata');
|
|
13
|
+
context.metadata.marked = true;
|
|
14
|
+
}
|
|
15
|
+
@mark class Example {}
|
|
16
|
+
exports.attached = Example[Symbol.metadata]?.marked;
|
|
17
|
+
`, { compilerOptions }).outputText;
|
|
18
|
+
|
|
19
|
+
const shim = ts.transpileModule(
|
|
20
|
+
readFileSync(new URL('./standardDecoratorMetadata.ts', import.meta.url), 'utf8'),
|
|
21
|
+
{ compilerOptions }
|
|
22
|
+
).outputText;
|
|
23
|
+
|
|
24
|
+
describe('standard decorator metadata shim with TypeScript output', () => {
|
|
25
|
+
it('attaches metadata when the runtime does not provide Symbol.metadata', () => {
|
|
26
|
+
expect(() => runInNewContext(fixture, { exports: {} })).toThrow('Missing standard decorator metadata');
|
|
27
|
+
const sandbox = { exports: {} as { attached?: boolean } };
|
|
28
|
+
runInNewContext(`${shim}\n${fixture}`, sandbox);
|
|
29
|
+
expect(sandbox.exports.attached).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
// Node versions without native decorator metadata need this before evaluating a decorated class.
|
|
5
|
+
const symbolWithMetadata = Symbol as unknown as { metadata: symbol };
|
|
6
|
+
symbolWithMetadata.metadata ??= Symbol.for('Symbol.metadata');
|
|
7
|
+
|
|
8
|
+
/** A class decorator accepted by both the legacy and standard TypeScript transforms. */
|
|
9
|
+
export type ChronicleClassDecorator = ClassDecorator & ((value: Function, context: ClassDecoratorContext) => void);
|
|
10
|
+
|
|
11
|
+
/** Gets the metadata object attached when a standard-decorated class finishes evaluation. */
|
|
12
|
+
export function getStandardMetadata(type: Function): object | undefined {
|
|
13
|
+
return Reflect.get(type, symbolWithMetadata.metadata) as object | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Whether a class completed its own standard decorator evaluation. */
|
|
17
|
+
export function hasOwnStandardMetadata(type: Function): boolean {
|
|
18
|
+
return Object.prototype.hasOwnProperty.call(type, symbolWithMetadata.metadata);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Prevents a class decorator from inspecting fields before their metadata is attached. */
|
|
22
|
+
export function requireCompletedStandardMetadata(type: Function): void {
|
|
23
|
+
if (!hasOwnStandardMetadata(type)) {
|
|
24
|
+
throw new TypeError(`Standard decorator metadata for ${type.name} is not complete; inspect its schema after class evaluation.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
package/vitest.config.ts
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
// Copyright (c) Cratis. All rights reserved.
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
|
+
import { transform } from 'esbuild';
|
|
4
5
|
import { defineConfig } from 'vitest/config';
|
|
5
6
|
|
|
6
7
|
export default defineConfig({
|
|
8
|
+
plugins: [{
|
|
9
|
+
name: 'standard-decorators-esbuild',
|
|
10
|
+
enforce: 'pre',
|
|
11
|
+
async transform(code, id) {
|
|
12
|
+
const file = id.split('?', 1)[0];
|
|
13
|
+
if (!file.endsWith('.ts') || file.includes('/node_modules/')) return;
|
|
14
|
+
return transform(code, {
|
|
15
|
+
loader: 'ts',
|
|
16
|
+
sourcefile: file,
|
|
17
|
+
target: 'es2022',
|
|
18
|
+
format: 'esm',
|
|
19
|
+
sourcemap: true,
|
|
20
|
+
tsconfigRaw: { compilerOptions: { useDefineForClassFields: true, experimentalDecorators: file.endsWith('.legacy.spec.ts') } }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}],
|
|
7
24
|
test: {
|
|
8
25
|
include: ['**/*.spec.ts', '**/*.spec.js'],
|
|
9
26
|
exclude: ['node_modules', 'dist'],
|