@cratis/arc 20.66.0 → 20.66.1

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 (58) hide show
  1. package/dist/cjs/queries/ObservableQueryFor.js +3 -7
  2. package/dist/cjs/queries/ObservableQueryFor.js.map +1 -1
  3. package/dist/cjs/queries/QueryResult.js +5 -17
  4. package/dist/cjs/queries/QueryResult.js.map +1 -1
  5. package/dist/cjs/queries/deserializeQueryModel.js +62 -0
  6. package/dist/cjs/queries/deserializeQueryModel.js.map +1 -0
  7. package/dist/cjs/queries/index.js +4 -0
  8. package/dist/cjs/queries/index.js.map +1 -1
  9. package/dist/esm/queries/ObservableQueryFor.d.ts.map +1 -1
  10. package/dist/esm/queries/ObservableQueryFor.js +3 -7
  11. package/dist/esm/queries/ObservableQueryFor.js.map +1 -1
  12. package/dist/esm/queries/QueryResult.d.ts.map +1 -1
  13. package/dist/esm/queries/QueryResult.js +5 -17
  14. package/dist/esm/queries/QueryResult.js.map +1 -1
  15. package/dist/esm/queries/deserializeQueryModel.d.ts +5 -0
  16. package/dist/esm/queries/deserializeQueryModel.d.ts.map +1 -0
  17. package/dist/esm/queries/deserializeQueryModel.js +58 -0
  18. package/dist/esm/queries/deserializeQueryModel.js.map +1 -0
  19. package/dist/esm/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_type_is_a_primitive_collection.d.ts +2 -0
  20. package/dist/esm/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_type_is_a_primitive_collection.d.ts.map +1 -0
  21. package/dist/esm/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_type_is_a_primitive_collection.js +49 -0
  22. package/dist/esm/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_type_is_a_primitive_collection.js.map +1 -0
  23. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_models.d.ts +2 -0
  24. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_models.d.ts.map +1 -0
  25. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_models.js +16 -0
  26. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_models.js.map +1 -0
  27. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_numbers.d.ts +2 -0
  28. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_numbers.d.ts.map +1 -0
  29. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_numbers.js +7 -0
  30. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_numbers.js.map +1 -0
  31. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_strings.d.ts +2 -0
  32. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_strings.d.ts.map +1 -0
  33. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_strings.js +7 -0
  34. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_strings.js.map +1 -0
  35. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_non_array_as_a_collection.d.ts +2 -0
  36. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_non_array_as_a_collection.d.ts.map +1 -0
  37. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_non_array_as_a_collection.js +6 -0
  38. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_non_array_as_a_collection.js.map +1 -0
  39. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_single_string.d.ts +2 -0
  40. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_single_string.d.ts.map +1 -0
  41. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_single_string.js +7 -0
  42. package/dist/esm/queries/for_deserializeQueryModel/when_deserializing_a_single_string.js.map +1 -0
  43. package/dist/esm/queries/index.d.ts +1 -0
  44. package/dist/esm/queries/index.d.ts.map +1 -1
  45. package/dist/esm/queries/index.js +1 -0
  46. package/dist/esm/queries/index.js.map +1 -1
  47. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +1 -1
  49. package/queries/ObservableQueryFor.ts +3 -7
  50. package/queries/QueryResult.ts +8 -26
  51. package/queries/deserializeQueryModel.ts +66 -0
  52. package/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_type_is_a_primitive_collection.ts +71 -0
  53. package/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_models.ts +17 -0
  54. package/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_numbers.ts +12 -0
  55. package/queries/for_deserializeQueryModel/when_deserializing_a_collection_of_strings.ts +12 -0
  56. package/queries/for_deserializeQueryModel/when_deserializing_a_non_array_as_a_collection.ts +11 -0
  57. package/queries/for_deserializeQueryModel/when_deserializing_a_single_string.ts +12 -0
  58. package/queries/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/arc",
3
- "version": "20.66.0",
3
+ "version": "20.66.1",
4
4
  "description": "",
5
5
  "author": "Cratis",
6
6
  "license": "MIT",
@@ -9,7 +9,7 @@ import { IObservableQueryConnection } from './IObservableQueryConnection';
9
9
  import { NullObservableQueryConnection } from './NullObservableQueryConnection';
10
10
  import { createObservableQueryConnection } from './ObservableQueryConnectionFactory';
11
11
  import { Constructor } from '@cratis/fundamentals';
12
- import { JsonSerializer } from '@cratis/fundamentals';
12
+ import { deserializeQueryModel, deserializeQueryModels } from './deserializeQueryModel';
13
13
  import { QueryResult } from './QueryResult';
14
14
  import { Sorting } from './Sorting';
15
15
  import { Paging } from './Paging';
@@ -219,13 +219,9 @@ export abstract class ObservableQueryFor<TDataType, TParameters = object> implem
219
219
 
220
220
  private deserializeResult(result: any): void {
221
221
  if (this.enumerable) {
222
- if (Array.isArray(result.data)) {
223
- result.data = JsonSerializer.deserializeArrayFromInstance(this.modelType, result.data);
224
- } else {
225
- result.data = [];
226
- }
222
+ result.data = deserializeQueryModels(this.modelType, result.data);
227
223
  } else if (result.data) {
228
- result.data = JsonSerializer.deserializeFromInstance(this.modelType, result.data);
224
+ result.data = deserializeQueryModel(this.modelType, result.data);
229
225
  }
230
226
  }
231
227
  }
@@ -1,11 +1,12 @@
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 { Constructor, JsonSerializer } from '@cratis/fundamentals';
4
+ import { Constructor } from '@cratis/fundamentals';
5
5
  import { ValidationResult } from '../validation/ValidationResult';
6
6
  import { IQueryResult } from './IQueryResult';
7
7
  import { PagingInfo } from './PagingInfo';
8
8
  import { ChangeSet } from './ChangeSet';
9
+ import { deserializeQueryModel, deserializeQueryModels } from './deserializeQueryModel';
9
10
 
10
11
  type ServerQueryResult = {
11
12
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -162,37 +163,18 @@ export class QueryResult<TDataType = object> implements IQueryResult<TDataType>
162
163
  this.paging.totalPages = result.paging.totalPages;
163
164
 
164
165
  if (result.data) {
165
- let data: object = result.data;
166
- const isPrimitive = instanceType === String || instanceType === Number || instanceType === Boolean;
167
- if (enumerable) {
168
- if (Array.isArray(result.data)) {
169
- data = isPrimitive
170
- ? Array.from(result.data)
171
- : JsonSerializer.deserializeArrayFromInstance(instanceType, data);
172
- } else {
173
- data = [];
174
- }
175
- } else {
176
- data = isPrimitive ? data : JsonSerializer.deserializeFromInstance(instanceType, data);
177
- }
178
-
179
- this.data = data as TDataType;
166
+ this.data = (enumerable
167
+ ? deserializeQueryModels(instanceType, result.data)
168
+ : deserializeQueryModel(instanceType, result.data)) as TDataType;
180
169
  } else {
181
170
  this.data = (enumerable ? [] : null) as TDataType;
182
171
  }
183
172
 
184
173
  if (enumerable && result.changeSet) {
185
- const isPrimitive = instanceType === String || instanceType === Number || instanceType === Boolean;
186
174
  this.changeSet = {
187
- added: isPrimitive
188
- ? Array.from(result.changeSet.added ?? [])
189
- : JsonSerializer.deserializeArrayFromInstance(instanceType, result.changeSet.added ?? []),
190
- replaced: isPrimitive
191
- ? Array.from(result.changeSet.replaced ?? [])
192
- : JsonSerializer.deserializeArrayFromInstance(instanceType, result.changeSet.replaced ?? []),
193
- removed: isPrimitive
194
- ? Array.from(result.changeSet.removed ?? [])
195
- : JsonSerializer.deserializeArrayFromInstance(instanceType, result.changeSet.removed ?? []),
175
+ added: deserializeQueryModels(instanceType, result.changeSet.added ?? []),
176
+ replaced: deserializeQueryModels(instanceType, result.changeSet.replaced ?? []),
177
+ removed: deserializeQueryModels(instanceType, result.changeSet.removed ?? []),
196
178
  } as ChangeSet<unknown>;
197
179
  }
198
180
  }
@@ -0,0 +1,66 @@
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 { Constructor, JsonSerializer } from '@cratis/fundamentals';
5
+
6
+ /*
7
+ * ⚠️ deserializeQueryModel / deserializeQueryModels are for use WITHIN this package only.
8
+ *
9
+ * JsonSerializer's converter registry is module state, and a consumer package can easily resolve a
10
+ * different physical copy of @cratis/fundamentals than this one. Calling these from another package
11
+ * therefore looks up Guid/Date/concept converters in the wrong registry and silently degrades those
12
+ * values to plain deserialization. Other packages should use isPrimitiveModelType - which compares
13
+ * against the JavaScript globals and is safe across copies - and deserialize through their own
14
+ * JsonSerializer.
15
+ */
16
+
17
+ /**
18
+ * Determines whether a model type is a JavaScript primitive wrapper rather than a real model.
19
+ *
20
+ * A query whose backend returns a primitive collection - `IEnumerable<string>`, `IEnumerable<int>` -
21
+ * generates a proxy with `String`, `Number` or `Boolean` as its model type.
22
+ * @param {Constructor} modelType The model type the query describes itself with.
23
+ * @returns {boolean} True when the type is a primitive wrapper.
24
+ */
25
+ export function isPrimitiveModelType(modelType: Constructor | null | undefined): boolean {
26
+ return modelType === String || modelType === Number || modelType === Boolean;
27
+ }
28
+
29
+ /**
30
+ * Deserializes a single query payload into its model type, passing primitives through untouched.
31
+ *
32
+ * {@link JsonSerializer.deserializeFromInstance} is destructive for primitive wrapper types: it
33
+ * constructs `new String()` and copies declared fields onto it, which for a primitive means the
34
+ * value is discarded and an empty wrapper object is produced. Primitives therefore need to bypass
35
+ * deserialization entirely - they already arrive in their final shape.
36
+ * @param {Constructor} modelType The model type to deserialize into.
37
+ * @param {unknown} data The payload to deserialize.
38
+ * @returns {TDataType} The deserialized instance, or the payload unchanged for primitive types.
39
+ */
40
+ export function deserializeQueryModel<TDataType>(modelType: Constructor | null | undefined, data: unknown): TDataType {
41
+ if (!modelType || modelType === Object || isPrimitiveModelType(modelType)) {
42
+ return data as TDataType;
43
+ }
44
+
45
+ return JsonSerializer.deserializeFromInstance(modelType, data) as TDataType;
46
+ }
47
+
48
+ /**
49
+ * Deserializes a query payload collection into its model type, passing primitives through untouched.
50
+ *
51
+ * See {@link deserializeQueryModel} for why primitive model types must bypass deserialization.
52
+ * @param {Constructor} modelType The instance type of the items to deserialize into.
53
+ * @param {unknown} data The payload to deserialize. A non-array payload yields an empty collection.
54
+ * @returns {TDataType[]} The deserialized items, or the items unchanged for primitive types.
55
+ */
56
+ export function deserializeQueryModels<TDataType>(modelType: Constructor | null | undefined, data: unknown): TDataType[] {
57
+ if (!Array.isArray(data)) {
58
+ return [];
59
+ }
60
+
61
+ if (!modelType || modelType === Object || isPrimitiveModelType(modelType)) {
62
+ return Array.from(data) as TDataType[];
63
+ }
64
+
65
+ return JsonSerializer.deserializeArrayFromInstance(modelType, data) as TDataType[];
66
+ }
@@ -0,0 +1,71 @@
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 * as sinon from 'sinon';
5
+ import { an_observable_query_for } from '../given/an_observable_query_for';
6
+ import { given } from '../../../given';
7
+ import { Globals } from '../../../Globals';
8
+ import { EventSourceFactory } from '../../../EventSourceFactory';
9
+ import { QueryResult } from '../../QueryResult';
10
+ import { QueryTransportMethod } from '../../QueryTransportMethod';
11
+ import { ObservableQuerySubscription } from '../../ObservableQuerySubscription';
12
+
13
+ interface FakeEventSource {
14
+ onmessage: ((event: MessageEvent) => void) | null;
15
+ onerror: (() => void) | null;
16
+ close: sinon.SinonStub;
17
+ }
18
+
19
+ describe('and the model type is a primitive collection', given(an_observable_query_for, context => {
20
+ let subscription: ObservableQuerySubscription<string[]>;
21
+ let fakeEventSource: FakeEventSource;
22
+ let received: QueryResult<string[]>[];
23
+ let originalQueryDirectMode: boolean;
24
+ let originalTransportMethod: QueryTransportMethod;
25
+ let originalFactory: EventSourceFactory | undefined;
26
+
27
+ beforeEach(() => {
28
+ originalQueryDirectMode = Globals.queryDirectMode;
29
+ originalTransportMethod = Globals.queryTransportMethod;
30
+ originalFactory = Globals.eventSourceFactory;
31
+
32
+ Globals.queryDirectMode = true;
33
+ Globals.queryTransportMethod = QueryTransportMethod.ServerSentEvents;
34
+
35
+ fakeEventSource = { onmessage: null, onerror: null, close: sinon.stub() };
36
+ Globals.eventSourceFactory = (() => fakeEventSource) as unknown as EventSourceFactory;
37
+
38
+ context.enumerableQuery.setOrigin('https://example.com');
39
+
40
+ received = [];
41
+ subscription = context.enumerableQuery.subscribe(
42
+ result => received.push(result),
43
+ { category: 'test-category' });
44
+
45
+ fakeEventSource.onmessage!({
46
+ data: JSON.stringify({
47
+ data: ['first', 'second'],
48
+ isSuccess: true,
49
+ isAuthorized: true,
50
+ isValid: true,
51
+ hasExceptions: false,
52
+ validationResults: [],
53
+ exceptionMessages: [],
54
+ exceptionStackTrace: '',
55
+ paging: { page: 0, size: 0, totalItems: 0, totalPages: 0 }
56
+ })
57
+ } as MessageEvent);
58
+ });
59
+
60
+ afterEach(() => {
61
+ subscription?.unsubscribe();
62
+ Globals.queryDirectMode = originalQueryDirectMode;
63
+ Globals.queryTransportMethod = originalTransportMethod;
64
+ Globals.eventSourceFactory = originalFactory;
65
+ sinon.restore();
66
+ });
67
+
68
+ it('should deliver the result to the subscriber', () => received.length.should.equal(1));
69
+ it('should keep every item a primitive string', () => received[0].data.every(_ => typeof _ === 'string').should.be.true);
70
+ it('should keep the values intact', () => received[0].data.should.deep.equal(['first', 'second']));
71
+ }));
@@ -0,0 +1,17 @@
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 { Constructor, field } from '@cratis/fundamentals';
5
+ import { deserializeQueryModels } from '../deserializeQueryModel';
6
+
7
+ class Item {
8
+ @field(String)
9
+ name!: string;
10
+ }
11
+
12
+ describe('when deserializing a collection of models', () => {
13
+ const result = deserializeQueryModels<Item>(Item as Constructor, [{ name: 'first' }, { name: 'second' }]);
14
+
15
+ it('should produce instances of the model type', () => result.every(_ => _ instanceof Item).should.be.true);
16
+ it('should carry the values over', () => result.map(_ => _.name).should.deep.equal(['first', 'second']));
17
+ });
@@ -0,0 +1,12 @@
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 { Constructor } from '@cratis/fundamentals';
5
+ import { deserializeQueryModels } from '../deserializeQueryModel';
6
+
7
+ describe('when deserializing a collection of numbers', () => {
8
+ const result = deserializeQueryModels<number>(Number as Constructor, [42, 43]);
9
+
10
+ it('should keep every item a primitive number', () => result.every(_ => typeof _ === 'number').should.be.true);
11
+ it('should keep the values intact', () => result.should.deep.equal([42, 43]));
12
+ });
@@ -0,0 +1,12 @@
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 { Constructor } from '@cratis/fundamentals';
5
+ import { deserializeQueryModels } from '../deserializeQueryModel';
6
+
7
+ describe('when deserializing a collection of strings', () => {
8
+ const result = deserializeQueryModels<string>(String as Constructor, ['first', 'second']);
9
+
10
+ it('should keep every item a primitive string', () => result.every(_ => typeof _ === 'string').should.be.true);
11
+ it('should keep the values intact', () => result.should.deep.equal(['first', 'second']));
12
+ });
@@ -0,0 +1,11 @@
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 { Constructor } from '@cratis/fundamentals';
5
+ import { deserializeQueryModels } from '../deserializeQueryModel';
6
+
7
+ describe('when deserializing a non array as a collection', () => {
8
+ const result = deserializeQueryModels<string>(String as Constructor, null);
9
+
10
+ it('should produce an empty collection', () => result.should.be.empty);
11
+ });
@@ -0,0 +1,12 @@
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 { Constructor } from '@cratis/fundamentals';
5
+ import { deserializeQueryModel } from '../deserializeQueryModel';
6
+
7
+ describe('when deserializing a single string', () => {
8
+ const result = deserializeQueryModel<string>(String as Constructor, 'the value');
9
+
10
+ it('should keep it a primitive string', () => (typeof result).should.equal('string'));
11
+ it('should keep the value intact', () => result.should.equal('the value'));
12
+ });
package/queries/index.ts CHANGED
@@ -36,6 +36,7 @@ export * from './WebSocketMessage';
36
36
  export * from './QueryTransportMethod';
37
37
  export * from './QueryInstanceCache';
38
38
  export * from './reconcileQueryData';
39
+ export * from './deserializeQueryModel';
39
40
  export * from './IQueryProvider';
40
41
  export * from './QueryProvider';
41
42
  export * from './QueryValidator';