@cratis/fundamentals 7.19.3 → 7.19.5

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.
Files changed (42) hide show
  1. package/DateOnly.ts +3 -3
  2. package/Guid.ts +1 -1
  3. package/IEquatable.ts +1 -1
  4. package/JsonSerializer.ts +13 -6
  5. package/StandardClassDecoratorContext.ts +1 -0
  6. package/StandardFieldDecoratorContext.ts +1 -0
  7. package/dist/JsonSerializer.d.ts.map +1 -1
  8. package/dist/JsonSerializer.js +15 -6
  9. package/dist/JsonSerializer.js.map +1 -1
  10. package/dist/StandardClassDecoratorContext.d.ts.map +1 -1
  11. package/dist/StandardFieldDecoratorContext.d.ts.map +1 -1
  12. package/dist/cjs/DateOnly.js.map +1 -1
  13. package/dist/cjs/Guid.js.map +1 -1
  14. package/dist/cjs/JsonSerializer.d.ts.map +1 -1
  15. package/dist/cjs/JsonSerializer.js +15 -6
  16. package/dist/cjs/JsonSerializer.js.map +1 -1
  17. package/dist/cjs/StandardClassDecoratorContext.d.ts.map +1 -1
  18. package/dist/cjs/StandardFieldDecoratorContext.d.ts.map +1 -1
  19. package/dist/cjs/fieldDecorator.d.ts.map +1 -1
  20. package/dist/cjs/fieldDecorator.js.map +1 -1
  21. package/dist/esm/DateOnly.js.map +1 -1
  22. package/dist/esm/Guid.js.map +1 -1
  23. package/dist/esm/JsonSerializer.d.ts.map +1 -1
  24. package/dist/esm/JsonSerializer.js +15 -6
  25. package/dist/esm/JsonSerializer.js.map +1 -1
  26. package/dist/esm/StandardClassDecoratorContext.d.ts.map +1 -1
  27. package/dist/esm/StandardFieldDecoratorContext.d.ts.map +1 -1
  28. package/dist/esm/fieldDecorator.d.ts.map +1 -1
  29. package/dist/esm/fieldDecorator.js.map +1 -1
  30. package/dist/fieldDecorator.d.ts.map +1 -1
  31. package/dist/fieldDecorator.js.map +1 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/fieldDecorator.ts +4 -0
  34. package/for_JsonSerializer/Types.ts +0 -1
  35. package/for_JsonSerializer/given/a_model_with_field_metadata.ts +63 -0
  36. package/for_JsonSerializer/given/given.ts +6 -0
  37. package/for_JsonSerializer/when_deserializing_fields_with_legacy_decorators.ts +41 -0
  38. package/for_JsonSerializer/when_deserializing_generic_array_without_a_value.ts +10 -0
  39. package/for_JsonSerializer/when_round_tripping_fields_with_legacy_decorators.ts +31 -0
  40. package/for_fieldDecorator/fixtures/standard_decorated_types.ts +53 -2
  41. package/for_fieldDecorator/when_executing_standard_typescript_decorator_emit.ts +46 -1
  42. package/package.json +2 -2
@@ -0,0 +1,10 @@
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 { JsonSerializer } from '../JsonSerializer';
5
+ import { Document } from './given/a_model_with_field_metadata';
6
+
7
+ describe('when deserializing a generic array without a value', () => {
8
+ it('should use an empty array for an absent collection', () => JsonSerializer.deserialize(Document, '{}').names.should.deep.equal([]));
9
+ it('should preserve an explicit null collection', () => (JsonSerializer.deserialize(Document, '{"names":null}').names === null).should.be.true);
10
+ });
@@ -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 { given } from './given/given';
5
+ import { JsonSerializer } from '../JsonSerializer';
6
+ import { Document, a_model_with_field_metadata } from './given/a_model_with_field_metadata';
7
+
8
+ describe('when round tripping fields with legacy decorators', given(a_model_with_field_metadata, context => {
9
+ let written: {
10
+ identifier: string;
11
+ label: string;
12
+ quantity: number;
13
+ enabled: boolean;
14
+ identifiers: string[];
15
+ children: { identifier: string }[];
16
+ };
17
+ let read: Document;
18
+
19
+ beforeEach(() => {
20
+ const document = JsonSerializer.deserialize(Document, JSON.stringify(context.payload));
21
+ const json = JsonSerializer.serialize(document);
22
+ written = JSON.parse(json);
23
+ read = JsonSerializer.deserialize(Document, json);
24
+ });
25
+
26
+ it('should write a concept wrapping a Guid as a string', () => written.identifier.should.equal(context.identifier));
27
+ it('should write falsy concept payloads without object wrappers', () => [written.label, written.quantity, written.enabled].should.deep.equal(['', 0, false]));
28
+ it('should write generic arrays of concepts as underlying values', () => written.identifiers.should.deep.equal([context.identifier]));
29
+ it('should write generic arrays of nested models as objects', () => written.children.should.deep.equal([{ identifier: context.identifier }]));
30
+ it('should read back the nested array model and typed concept', () => read.children[0].identifier.value.toString().should.equal(context.identifier));
31
+ }));
@@ -3,7 +3,20 @@
3
3
 
4
4
  declare const fundamentals: typeof import('../../index');
5
5
 
6
- const { derivedType, field, JsonSerializer, ValueMap } = fundamentals;
6
+ const { ConceptAs, derivedType, field, Guid, JsonSerializer, ValueMap } = fundamentals;
7
+
8
+ class Identifier extends ConceptAs<InstanceType<typeof Guid>> {
9
+ static readonly valueType = Guid;
10
+ }
11
+
12
+ class Label extends ConceptAs<string> {}
13
+ class Quantity extends ConceptAs<number> {}
14
+ class Enabled extends ConceptAs<boolean> {}
15
+
16
+ class Child {
17
+ @field(Identifier)
18
+ identifier!: Identifier;
19
+ }
7
20
 
8
21
  function replaceClass<Target extends import('../../Constructor').Constructor>(target: Target): Target {
9
22
  return class extends target { } as Target;
@@ -39,6 +52,33 @@ class Drawing {
39
52
  @field(String)
40
53
  title!: string;
41
54
 
55
+ @field(Array, { genericArguments: [String] })
56
+ names!: string[];
57
+
58
+ @field(Identifier)
59
+ identifier!: Identifier;
60
+
61
+ @field(Label)
62
+ label!: Label;
63
+
64
+ @field(Quantity)
65
+ quantity!: Quantity;
66
+
67
+ @field(Enabled)
68
+ enabled!: Enabled;
69
+
70
+ @field(Child)
71
+ child!: Child;
72
+
73
+ @field(Array, { genericArguments: [Identifier] })
74
+ identifiers!: Identifier[];
75
+
76
+ @field(Array, { genericArguments: [Child] })
77
+ children!: Child[];
78
+
79
+ @field(Label, true)
80
+ legacyLabels!: Label[];
81
+
42
82
  @field(Date)
43
83
  createdAt!: Date;
44
84
 
@@ -54,8 +94,18 @@ class Drawing {
54
94
  }
55
95
 
56
96
  const instanceCountBeforeDeserialize = Drawing.instanceCount;
97
+ const identifier = 'f0fa7c5e-9f7b-4688-8851-e0b6eeebe28b';
57
98
  const drawing = JsonSerializer.deserialize(Drawing, JSON.stringify({
99
+ child: { identifier },
100
+ children: [{ identifier }],
58
101
  createdAt: '2026-08-16T09:30:00.000Z',
102
+ enabled: false,
103
+ identifier,
104
+ identifiers: [identifier],
105
+ label: '',
106
+ legacyLabels: ['older'],
107
+ names: ['one', 'two'],
108
+ quantity: 0,
59
109
  scores: {
60
110
  first: 42
61
111
  },
@@ -72,5 +122,6 @@ const drawing = JsonSerializer.deserialize(Drawing, JSON.stringify({
72
122
  title: 'Standard decorators'
73
123
  }));
74
124
  const instanceCountAfterDeserialize = Drawing.instanceCount;
125
+ const serializedDrawing = JsonSerializer.serialize(drawing);
75
126
 
76
- void [Circle, Rectangle, ReplacedShape, instanceCountBeforeDeserialize, drawing, instanceCountAfterDeserialize];
127
+ void [Child, Circle, Rectangle, ReplacedShape, instanceCountBeforeDeserialize, drawing, instanceCountAfterDeserialize, serializedDrawing];
@@ -19,19 +19,34 @@ describe('when executing standard TypeScript decorator emit', () => {
19
19
  let symbolMetadataWasAbsent: boolean;
20
20
  let symbolMetadataWasPolyfilled: boolean;
21
21
  let executionResult: {
22
+ Child: fundamentals.Constructor;
22
23
  Circle: fundamentals.Constructor;
23
24
  Drawing: fundamentals.Constructor;
25
+ Enabled: fundamentals.Constructor;
26
+ Identifier: fundamentals.Constructor;
27
+ Label: fundamentals.Constructor;
28
+ Quantity: fundamentals.Constructor;
24
29
  Rectangle: fundamentals.Constructor;
25
30
  ReplacedShape: fundamentals.Constructor;
26
31
  Shape: fundamentals.Constructor;
27
32
  drawing: {
33
+ child: { identifier: { value: fundamentals.Guid } };
34
+ children: object[];
28
35
  createdAt: Date;
36
+ enabled: { value: boolean };
37
+ identifier: { value: fundamentals.Guid };
38
+ identifiers: { value: fundamentals.Guid }[];
39
+ label: { value: string };
40
+ legacyLabels: { value: string }[];
41
+ names: string[];
42
+ quantity: { value: number };
29
43
  scores: fundamentals.ValueMap<string, number>;
30
44
  shapes: object[];
31
45
  title: string;
32
46
  };
33
47
  instanceCountAfterDeserialize: number;
34
48
  instanceCountBeforeDeserialize: number;
49
+ serializedDrawing: string;
35
50
  };
36
51
  let registeredDerivedTypes: fundamentals.Constructor[];
37
52
  let legacySemanticDiagnosticCount: number;
@@ -86,7 +101,7 @@ describe('when executing standard TypeScript decorator emit', () => {
86
101
  reportDiagnostics: true
87
102
  }).outputText;
88
103
 
89
- const script = new Script(`${emittedJavaScript}\n({ Circle, Drawing, Rectangle, ReplacedShape, Shape, drawing, instanceCountAfterDeserialize, instanceCountBeforeDeserialize })`);
104
+ const script = new Script(`${emittedJavaScript}\n({ Child, Circle, Drawing, Enabled, Identifier, Label, Quantity, Rectangle, ReplacedShape, Shape, drawing, instanceCountAfterDeserialize, instanceCountBeforeDeserialize, serializedDrawing })`);
90
105
  executionResult = script.runInNewContext({ Array, Boolean, Date, fundamentals, JSON, Number, Object, String, Symbol }) as typeof executionResult;
91
106
  registeredDerivedTypes = fundamentals.DerivedType.getDerivedTypesFor(executionResult.Shape);
92
107
  });
@@ -104,6 +119,36 @@ describe('when executing standard TypeScript decorator emit', () => {
104
119
  it('should expose field metadata before constructing an instance', () => executionResult.instanceCountBeforeDeserialize.should.equal(0));
105
120
  it('should construct the target only during first-call deserialization', () => executionResult.instanceCountAfterDeserialize.should.equal(1));
106
121
  it('should deserialize primitive fields', () => executionResult.drawing.title.should.equal('Standard decorators'));
122
+ it('should read array elements declared through generic arguments', () => executionResult.drawing.names.should.deep.equal(['one', 'two']));
123
+ it('should read a concept underlying Guid as a Guid', () => executionResult.drawing.identifier.value.should.be.instanceOf(fundamentals.Guid));
124
+ it('should wrap an empty string in its concept', () => {
125
+ (executionResult.drawing.label instanceof executionResult.Label).should.be.true;
126
+ executionResult.drawing.label.value.should.equal('');
127
+ });
128
+ it('should wrap zero in its concept', () => {
129
+ (executionResult.drawing.quantity instanceof executionResult.Quantity).should.be.true;
130
+ executionResult.drawing.quantity.value.should.equal(0);
131
+ });
132
+ it('should wrap false in its concept', () => {
133
+ (executionResult.drawing.enabled instanceof executionResult.Enabled).should.be.true;
134
+ executionResult.drawing.enabled.value.should.equal(false);
135
+ });
136
+ it('should read a nested model and its concept', () => {
137
+ (executionResult.drawing.child instanceof executionResult.Child).should.be.true;
138
+ executionResult.drawing.child.identifier.value.should.be.instanceOf(fundamentals.Guid);
139
+ });
140
+ it('should read an array of concepts', () => {
141
+ (executionResult.drawing.identifiers[0] instanceof executionResult.Identifier).should.be.true;
142
+ executionResult.drawing.identifiers[0].value.should.be.instanceOf(fundamentals.Guid);
143
+ });
144
+ it('should read an array of nested models', () => (executionResult.drawing.children[0] instanceof executionResult.Child).should.be.true);
145
+ it('should keep reading legacy enumerable fields', () => executionResult.drawing.legacyLabels[0].value.should.equal('older'));
146
+ it('should serialize generic arrays of concepts as values', () => JSON.parse(executionResult.serializedDrawing).identifiers.should.deep.equal(['f0fa7c5e-9f7b-4688-8851-e0b6eeebe28b']));
147
+ it('should serialize generic arrays of nested models', () => JSON.parse(executionResult.serializedDrawing).children.should.deep.equal([{ identifier: 'f0fa7c5e-9f7b-4688-8851-e0b6eeebe28b' }]));
148
+ it('should serialize falsy concept values', () => {
149
+ const written = JSON.parse(executionResult.serializedDrawing);
150
+ [written.label, written.quantity, written.enabled].should.deep.equal(['', 0, false]);
151
+ });
107
152
  it('should deserialize date fields', () => executionResult.drawing.createdAt.should.be.instanceOf(Date));
108
153
  it('should deserialize polymorphic array entries', () => (executionResult.drawing.shapes[0] instanceof executionResult.Circle).should.be.true);
109
154
  it('should deserialize every registered derivative', () => (executionResult.drawing.shapes[1] instanceof executionResult.Rectangle).should.be.true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/fundamentals",
3
- "version": "7.19.3",
3
+ "version": "7.19.5",
4
4
  "description": "The shared building blocks beneath the Cratis stack — GUIDs, concepts, derived-type serialization, property-path proxies, and decorators for TypeScript",
5
5
  "keywords": [
6
6
  "cratis",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@typescript/native": "npm:typescript@^7.0.2",
59
- "rollup": "4.63.3",
59
+ "rollup": "4.63.4",
60
60
  "rollup-plugin-typescript2": "0.37.0",
61
61
  "typescript": "npm:@typescript/typescript6@^6.0.2",
62
62
  "typescript-programmatic-api": "npm:@typescript/typescript6@^6.0.2"