@coast/core-utils 0.0.24 → 0.0.28

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 (50) hide show
  1. package/dist/AssertionUtils.d.ts +0 -9
  2. package/dist/AssertionUtils.js +0 -16
  3. package/dist/AssertionUtils.js.map +1 -1
  4. package/dist/CompactSet.d.ts +5 -0
  5. package/dist/CompactSet.js +21 -0
  6. package/dist/CompactSet.js.map +1 -0
  7. package/dist/EntityRehydrator.d.ts +12 -0
  8. package/dist/EntityRehydrator.js +30 -0
  9. package/dist/EntityRehydrator.js.map +1 -0
  10. package/dist/EntityUtils.d.ts +11 -0
  11. package/dist/EntityUtils.js +24 -0
  12. package/dist/EntityUtils.js.map +1 -0
  13. package/dist/ISODateStringUtils.d.ts +5 -0
  14. package/dist/ISODateStringUtils.js +16 -0
  15. package/dist/ISODateStringUtils.js.map +1 -0
  16. package/dist/JsonPath.d.ts +8 -0
  17. package/dist/JsonPath.js +38 -0
  18. package/dist/JsonPath.js.map +1 -0
  19. package/dist/ObjectUtils.d.ts +4 -0
  20. package/dist/ObjectUtils.js +9 -0
  21. package/dist/ObjectUtils.js.map +1 -1
  22. package/dist/test/AssertionUtilsTest.js +0 -41
  23. package/dist/test/AssertionUtilsTest.js.map +1 -1
  24. package/dist/test/CompactSetTest.d.ts +1 -0
  25. package/dist/test/CompactSetTest.js +95 -0
  26. package/dist/test/CompactSetTest.js.map +1 -0
  27. package/dist/test/EntityRehydratorTest.d.ts +1 -0
  28. package/dist/test/EntityRehydratorTest.js +48 -0
  29. package/dist/test/EntityRehydratorTest.js.map +1 -0
  30. package/dist/test/EntityUtilsTest.d.ts +1 -0
  31. package/dist/test/EntityUtilsTest.js +87 -0
  32. package/dist/test/EntityUtilsTest.js.map +1 -0
  33. package/dist/test/JsonPathTest.d.ts +1 -0
  34. package/dist/test/JsonPathTest.js +419 -0
  35. package/dist/test/JsonPathTest.js.map +1 -0
  36. package/jest.config.js +1 -0
  37. package/package.json +5 -7
  38. package/src/AssertionUtils.ts +0 -33
  39. package/src/CompactSet.ts +31 -0
  40. package/src/EntityRehydrator.ts +35 -0
  41. package/src/EntityUtils.ts +37 -0
  42. package/src/ISODateStringUtils.ts +24 -0
  43. package/src/JsonPath.ts +58 -0
  44. package/src/ObjectUtils.ts +29 -0
  45. package/src/test/AssertionUtilsTest.ts +0 -48
  46. package/src/test/CompactSetTest.ts +106 -0
  47. package/src/test/EntityRehydratorTest.ts +74 -0
  48. package/src/test/EntityUtilsTest.ts +97 -0
  49. package/src/test/JsonPathTest.ts +516 -0
  50. package/tsconfig.tsbuildinfo +1 -1
@@ -1,10 +1,7 @@
1
- import { EntityType } from '@coast/core-types/common/EntityType';
2
1
  import { GeneralErrorCode } from '@coast/core-types/error/GeneralErrorCode';
3
2
  import { AssertionFailedException } from '@coast/core-types/exception/AssertionFailedException';
4
3
  import { CoastException } from '@coast/core-types/exception/CoastException';
5
- import { InvalidEntityReferenceException } from '@coast/core-types/exception/InvalidEntityReferenceException';
6
4
  import { NotDefinedException } from '@coast/core-types/exception/NotDefinedException';
7
- import { NotFoundException } from '@coast/core-types/exception/NotFoundException';
8
5
  import { DeepLogObject } from '@coast/service-common/logger/DeepLogObject';
9
6
  import _ from 'lodash';
10
7
  import nodeAssert from 'node:assert';
@@ -19,36 +16,6 @@ export class AssertionUtils {
19
16
  }
20
17
  }
21
18
 
22
- public static assertValidEntityReference<T extends object>(
23
- entity: T | null | undefined,
24
- entityType: EntityType,
25
- options: { id?: string; message?: string } = {}
26
- ): asserts entity is T {
27
- AssertionUtils.assertIsDefined(
28
- entity,
29
- new InvalidEntityReferenceException({
30
- entityType,
31
- id: options?.id,
32
- message: options?.message,
33
- })
34
- );
35
- }
36
-
37
- public static assertEntityFound<T extends object>(
38
- entity: T | null | undefined,
39
- entityType: EntityType,
40
- options: { id?: string; message?: string } = {}
41
- ): asserts entity is T {
42
- AssertionUtils.assertIsDefined(
43
- entity,
44
- new NotFoundException({
45
- entityType,
46
- id: options?.id,
47
- message: options?.message,
48
- })
49
- );
50
- }
51
-
52
19
  public static assert(value: unknown, error?: CoastException | string): asserts value {
53
20
  try {
54
21
  nodeAssert(value); // eslint-disable-line @typescript-eslint/strict-boolean-expressions
@@ -0,0 +1,31 @@
1
+ /**
2
+ * CompactSet is a typical Set but ignores null and undefined
3
+ */
4
+ export class CompactSet<T = unknown> extends Set<T> {
5
+ /**
6
+ * Null and undefined values will be ingnored
7
+ * (new CompactSet()).add(null).add(undefined).size // 0
8
+ */
9
+ public add(value: T | null | undefined): this {
10
+ if (value === undefined || value === null) {
11
+ return this;
12
+ }
13
+ super.add(value);
14
+
15
+ return this;
16
+ }
17
+
18
+ /**
19
+ * Convenience method to add all items in an array to this set
20
+ * Handles null, undefined and empty arrays gracefully
21
+ */
22
+ public addAll(values: Array<T | null | undefined> | null | undefined): this {
23
+ values?.forEach((value: T | null | undefined) => this.add(value));
24
+
25
+ return this;
26
+ }
27
+
28
+ public asArray(): T[] {
29
+ return Array.from(this);
30
+ }
31
+ }
@@ -0,0 +1,35 @@
1
+ import { EntityIdMap } from '@coast/core-types/common/EntityIdMap';
2
+ import { EntityType } from '@coast/core-types/common/EntityType';
3
+ import { EntityTypeLabels } from '@coast/core-types/common/EntityTypeLabels';
4
+ import { CoastLogger } from '@coast/service-common/logger/CoastLogger';
5
+ import _ from 'lodash';
6
+
7
+ export class EntityRehydrator<ET extends EntityType> {
8
+ public static readonly MISSING_REQUIRED_PROPERTY = 'MISSING_REQUIRED_PROPERTY';
9
+
10
+ public constructor(
11
+ private readonly entityType: ET,
12
+ private readonly logger?: CoastLogger
13
+ ) {}
14
+
15
+ /**
16
+ * A read never throws. The row exists either way, and failing it hides every other row in the
17
+ * same fetch while giving the caller nothing it can act on, so a property the stored row left
18
+ * out is narrowed and reported rather than rejected.
19
+ */
20
+ public require<T extends { id: EntityIdMap[ET] }, K extends keyof T>(entity: T, propertyName: K): NonNullable<T[K]> {
21
+ const value = entity[propertyName];
22
+
23
+ if (_.isNil(value)) {
24
+ this.logger?.warn({
25
+ msg: `${EntityTypeLabels[this.entityType]} is missing required property ${String(propertyName)}`,
26
+ event: EntityRehydrator.MISSING_REQUIRED_PROPERTY,
27
+ entityType: this.entityType,
28
+ propertyName: String(propertyName),
29
+ id: _.isNil(entity.id) ? undefined : String(entity.id),
30
+ });
31
+ }
32
+
33
+ return value as NonNullable<T[K]>;
34
+ }
35
+ }
@@ -0,0 +1,37 @@
1
+ import { EntityType } from '@coast/core-types/common/EntityType';
2
+ import { InvalidEntityReferenceException } from '@coast/core-types/exception/InvalidEntityReferenceException';
3
+ import { NotFoundException } from '@coast/core-types/exception/NotFoundException';
4
+
5
+ import { AssertionUtils } from './AssertionUtils';
6
+
7
+ export class EntityUtils {
8
+ public static assertValidEntityReference<T extends object>(
9
+ entity: T | null | undefined,
10
+ entityType: EntityType,
11
+ options: { id?: string; message?: string } = {}
12
+ ): asserts entity is T {
13
+ AssertionUtils.assertIsDefined(
14
+ entity,
15
+ new InvalidEntityReferenceException({
16
+ entityType,
17
+ id: options?.id,
18
+ message: options?.message,
19
+ })
20
+ );
21
+ }
22
+
23
+ public static assertEntityFound<T extends object>(
24
+ entity: T | null | undefined,
25
+ entityType: EntityType,
26
+ options: { id?: string; message?: string } = {}
27
+ ): asserts entity is T {
28
+ AssertionUtils.assertIsDefined(
29
+ entity,
30
+ new NotFoundException({
31
+ entityType,
32
+ id: options?.id,
33
+ message: options?.message,
34
+ })
35
+ );
36
+ }
37
+ }
@@ -0,0 +1,24 @@
1
+ import { ISODateString } from '@coast/core-types/common/ISODateString';
2
+
3
+ /**
4
+ * Conversions between a DTO `Date` and the `ISODateString` stored in a JSON column.
5
+ *
6
+ * A JSON column is unvalidated, so a field typed `Date` may hold any string a past write left
7
+ * there. Neither direction throws and neither normalizes: an unparseable value is carried through
8
+ * unchanged in both directions, so a read-modify-write writes the original bytes back. The cast is
9
+ * the cost — an unparseable stored value surfaces as a string typed `Date`, exactly as every
10
+ * strangler-read date does today — and it is confined to this class.
11
+ */
12
+ export class ISODateStringUtils {
13
+ public static fromDate(value: Date): ISODateString {
14
+ const parsed = new Date(value);
15
+
16
+ return Number.isNaN(parsed.getTime()) ? (value as unknown as ISODateString) : ISODateString(parsed.toISOString());
17
+ }
18
+
19
+ public static toDate(value: ISODateString): Date {
20
+ const parsed = new Date(value);
21
+
22
+ return Number.isNaN(parsed.getTime()) ? (value as unknown as Date) : parsed;
23
+ }
24
+ }
@@ -0,0 +1,58 @@
1
+ export class JsonPath {
2
+ public constructor(public readonly value: string) {}
3
+
4
+ /**
5
+ * Converts a dot-notation path string into a properly formatted JsonPath.
6
+ * Handles paths that may need bracket notation for special characters or numbers.
7
+ * Produces RFC 9535 compliant JSONPath syntax.
8
+ */
9
+ public static fromString(path: string): JsonPath {
10
+ if (path.startsWith('$')) {
11
+ return new JsonPath(path);
12
+ }
13
+
14
+ const segments = path.split('.');
15
+
16
+ const formattedPath = segments
17
+ .map((segment, index) => {
18
+ const isFirstSegment = index === 0;
19
+ const requiresBrackets = this.needsBracketNotation(segment);
20
+
21
+ if (isFirstSegment) {
22
+ return requiresBrackets ? `$['${segment}']` : `$.${segment}`;
23
+ }
24
+
25
+ return requiresBrackets ? `['${segment}']` : `.${segment}`;
26
+ })
27
+ .join('');
28
+
29
+ return new JsonPath(formattedPath);
30
+ }
31
+
32
+ /**
33
+ * Determines if a path segment requires bracket notation.
34
+ * Returns true if the segment:
35
+ * 1. Starts with a number
36
+ * 2. Contains special characters (anything other than alphanumeric and underscore)
37
+ *
38
+ * Note: UUIDs are automatically handled by condition #2 since they contain hyphens.
39
+ */
40
+ private static needsBracketNotation(segment: string): boolean {
41
+ return /^\d/.test(segment) || /\W/.test(segment);
42
+ }
43
+
44
+ /**
45
+ * Appends a path to the current path.
46
+ * If a string is provided, it will be converted to a JsonPath first.
47
+ */
48
+ public appendPath(path: JsonPath | string): JsonPath {
49
+ const pathToAppend = path instanceof JsonPath ? path : JsonPath.fromString(path);
50
+ const appendValue = pathToAppend.value.replace('$', '');
51
+
52
+ return new JsonPath(`${this.value}${appendValue}`);
53
+ }
54
+
55
+ public toString(): string {
56
+ return this.value;
57
+ }
58
+ }
@@ -1,3 +1,5 @@
1
+ import { Patch } from '@coast/core-types/utils/Patch';
2
+ import { WithNullable } from '@coast/core-types/utils/WithNullable';
1
3
  import _ from 'lodash';
2
4
 
3
5
  import { DeepPartial } from './DeepPartial';
@@ -27,6 +29,33 @@ export class ObjectUtils {
27
29
  return { ...target, ...cleanSource };
28
30
  }
29
31
 
32
+ /**
33
+ * Applies a `Patch` to `original`, returning the exhaustive next value. Per the patch contract:
34
+ * `undefined` is SKIPPED (keep the original value), `null` CLEARS the property, and any other
35
+ * value SETS it.
36
+ */
37
+ public static applyPatch<T extends object, TImmutableProperties extends keyof T>(
38
+ original: T,
39
+ patch: Patch<T, TImmutableProperties>
40
+ ): T {
41
+ const appliedEntries = Object.fromEntries(
42
+ Object.entries(patch)
43
+ .filter(([_key, value]) => value !== undefined)
44
+ .map(([key, value]) => [key, value === null ? undefined : value])
45
+ );
46
+
47
+ return { ...original, ...appliedEntries };
48
+ }
49
+
50
+ /**
51
+ * Shallow-replaces every `undefined` value with `null`.
52
+ */
53
+ public static nullifyUndefined<T extends object>(obj: T): WithNullable<T> {
54
+ return Object.fromEntries(
55
+ Object.entries(obj).map(([key, value]) => [key, value === undefined ? null : value])
56
+ ) as WithNullable<T>;
57
+ }
58
+
30
59
  public static getFirstKey<T extends object>(obj: T): keyof T {
31
60
  return Object.keys(obj)[0] as keyof T;
32
61
  }
@@ -1,8 +1,5 @@
1
- import { EntityType } from '@coast/core-types/common/EntityType';
2
- import { EntityTypeLabels } from '@coast/core-types/common/EntityTypeLabels';
3
1
  import { AssertionFailedException } from '@coast/core-types/exception/AssertionFailedException';
4
2
  import { CoastException } from '@coast/core-types/exception/CoastException';
5
- import { InvalidEntityReferenceException } from '@coast/core-types/exception/InvalidEntityReferenceException';
6
3
  import { NotDefinedException } from '@coast/core-types/exception/NotDefinedException';
7
4
  import { assertType } from '@coast/service-common/types/assertType';
8
5
  import { Equals } from '@coast/service-common/types/Equals';
@@ -95,49 +92,4 @@ describe('AssertionUtils', () => {
95
92
  assertType<Equals<typeof x, string>>();
96
93
  });
97
94
  });
98
-
99
- describe('assertValidEntityReference', () => {
100
- it('should throw if value is not defined', () => {
101
- const x = undefined as object | undefined;
102
- try {
103
- AssertionUtils.assertValidEntityReference(x, EntityType.WORKFLOW_ENTITY);
104
- } catch (error) {
105
- expect(error).toBeInstanceOf(InvalidEntityReferenceException);
106
-
107
- safeExpect((error as InvalidEntityReferenceException).message).toEqual(
108
- `Invalid entity reference: ${EntityTypeLabels[EntityType.WORKFLOW_ENTITY]} not found`
109
- );
110
-
111
- safeExpect((error as InvalidEntityReferenceException).additionalData).toMatchObject({
112
- entityType: EntityType.WORKFLOW_ENTITY,
113
- });
114
- }
115
- });
116
-
117
- it('should use custom error message', () => {
118
- const x = undefined as object | undefined;
119
- try {
120
- AssertionUtils.assertValidEntityReference(x, EntityType.WORKFLOW_ENTITY, {
121
- message: 'x should exist...',
122
- id: 'abc-123',
123
- });
124
- } catch (error) {
125
- expect(error).toBeInstanceOf(InvalidEntityReferenceException);
126
- safeExpect((error as InvalidEntityReferenceException).message).toEqual('x should exist...');
127
-
128
- safeExpect((error as InvalidEntityReferenceException).additionalData).toMatchObject({
129
- entityType: EntityType.WORKFLOW_ENTITY,
130
- id: 'abc-123',
131
- });
132
- }
133
- });
134
-
135
- it('should not throw and should type narrow if value is defined', () => {
136
- const x = { id: 'abc' } as object | undefined;
137
- assertType<Equals<typeof x, object | undefined>>();
138
-
139
- AssertionUtils.assertValidEntityReference(x, EntityType.COMPONENT);
140
- assertType<Equals<typeof x, object>>();
141
- });
142
- });
143
95
  });
@@ -0,0 +1,106 @@
1
+ import { safeExpect } from '@paradoxical-io/common-test';
2
+
3
+ import { CompactSet } from '../CompactSet';
4
+
5
+ describe('CompactSet', () => {
6
+ it('adds values', () => {
7
+ const set = new CompactSet();
8
+ set.add(1).add(null).add(undefined);
9
+ safeExpect(set.values().next().value).toEqual(1);
10
+ safeExpect(set.keys().next().value).toEqual(1);
11
+ safeExpect(set.size).toEqual(1);
12
+ });
13
+
14
+ it('does not add null', () => {
15
+ const set = new CompactSet();
16
+ set.add(null);
17
+ safeExpect(set.values().next().done).toEqual(true);
18
+ safeExpect(set.keys().next().done).toEqual(true);
19
+ safeExpect(set.size).toEqual(0);
20
+ });
21
+
22
+ it('does not add undefined', () => {
23
+ const set = new CompactSet();
24
+ set.add(undefined);
25
+ safeExpect(set.values().next().done).toEqual(true);
26
+ safeExpect(set.keys().next().done).toEqual(true);
27
+ safeExpect(set.size).toEqual(0);
28
+ });
29
+
30
+ it('adds a value via the constructor iterable', () => {
31
+ const seedSet = new Set([1]);
32
+ const set = new CompactSet(seedSet);
33
+ safeExpect(set.values().next().value).toEqual(1);
34
+ safeExpect(set.keys().next().value).toEqual(1);
35
+ safeExpect(set.size).toEqual(1);
36
+ });
37
+
38
+ it('adds a value via the constructor array', () => {
39
+ const set = new CompactSet([1]);
40
+ safeExpect(set.values().next().value).toEqual(1);
41
+ safeExpect(set.keys().next().value).toEqual(1);
42
+ safeExpect(set.size).toEqual(1);
43
+ });
44
+
45
+ it('does not add a null value via the constructor iterable', () => {
46
+ const seedSet = new Set([1, null, 2]);
47
+ const set = new CompactSet(seedSet);
48
+ const iterator = set.values();
49
+ safeExpect(iterator.next().value).toEqual(1);
50
+ safeExpect(iterator.next().value).toEqual(2);
51
+ safeExpect(set.size).toEqual(2);
52
+ });
53
+
54
+ it('does not add an undefined value via the constructor iterable', () => {
55
+ const seedSet = new Set([1, undefined, 2]);
56
+ const set = new CompactSet(seedSet);
57
+ const iterator = set.values();
58
+ safeExpect(iterator.next().value).toEqual(1);
59
+ safeExpect(iterator.next().value).toEqual(2);
60
+ safeExpect(set.size).toEqual(2);
61
+ });
62
+
63
+ it('does not add a null value via the constructor array', () => {
64
+ const set = new CompactSet([1, null, 2]);
65
+ const iterator = set.values();
66
+ safeExpect(iterator.next().value).toEqual(1);
67
+ safeExpect(iterator.next().value).toEqual(2);
68
+ safeExpect(set.size).toEqual(2);
69
+ });
70
+
71
+ it('does not add an undefined value via the constructor array', () => {
72
+ const set = new CompactSet([1, undefined, 2]);
73
+ const iterator = set.values();
74
+ safeExpect(iterator.next().value).toEqual(1);
75
+ safeExpect(iterator.next().value).toEqual(2);
76
+ safeExpect(set.size).toEqual(2);
77
+ });
78
+
79
+ it('addAll adds many values', () => {
80
+ const set = new CompactSet();
81
+ set.addAll([1, 2, null, undefined, 3]);
82
+ const iterator = set.values();
83
+ safeExpect(iterator.next().value).toEqual(1);
84
+ safeExpect(iterator.next().value).toEqual(2);
85
+ safeExpect(iterator.next().value).toEqual(3);
86
+ safeExpect(set.size).toEqual(3);
87
+ });
88
+
89
+ it('addAll handles undefined', () => {
90
+ const set = new CompactSet();
91
+ set.addAll(undefined);
92
+ safeExpect(set.size).toEqual(0);
93
+ });
94
+
95
+ it('addAll handles null arrays', () => {
96
+ const set = new CompactSet();
97
+ set.addAll(null);
98
+ safeExpect(set.size).toEqual(0);
99
+ });
100
+
101
+ it('addAll handles empty arrays', () => {
102
+ const set = new CompactSet();
103
+ set.addAll([]);
104
+ safeExpect(set.size).toEqual(0);
105
+ });
106
+ });
@@ -0,0 +1,74 @@
1
+ import { EntityType } from '@coast/core-types/common/EntityType';
2
+ import { ViewTemplateId } from '@coast/core-types/view-template/ViewTemplateId';
3
+ import { WorkflowTemplateId } from '@coast/core-types/workflow-template/WorkflowTemplateId';
4
+ import { CoastLogger } from '@coast/service-common/logger/CoastLogger';
5
+ import { safeExpect } from '@paradoxical-io/common-test';
6
+
7
+ import { EntityRehydrator } from '../EntityRehydrator';
8
+
9
+ describe('EntityRehydrator', () => {
10
+ function mockLogger(): { logger: CoastLogger; warn: jest.Mock } {
11
+ const warn = jest.fn();
12
+
13
+ return { logger: { warn } as unknown as CoastLogger, warn };
14
+ }
15
+
16
+ describe('require', () => {
17
+ it('returns a stored property that is present', () => {
18
+ const { logger, warn } = mockLogger();
19
+ const rehydrator = new EntityRehydrator(EntityType.WORKFLOW_TEMPLATE, logger);
20
+
21
+ safeExpect(
22
+ rehydrator.require({ id: WorkflowTemplateId('wft-1'), parentTemplateId: 'wft-owner' }, 'parentTemplateId')
23
+ ).toEqual('wft-owner');
24
+
25
+ expect(warn).not.toHaveBeenCalled();
26
+ });
27
+
28
+ it('returns the missing property rather than throwing, and warns', () => {
29
+ const { logger, warn } = mockLogger();
30
+ const rehydrator = new EntityRehydrator(EntityType.WORKFLOW_TEMPLATE, logger);
31
+
32
+ safeExpect(
33
+ rehydrator.require({ id: WorkflowTemplateId('wft-1'), parentTemplateId: null }, 'parentTemplateId')
34
+ ).toBeNull();
35
+
36
+ expect(warn).toHaveBeenCalledTimes(1);
37
+
38
+ expect(warn).toHaveBeenCalledWith(
39
+ expect.objectContaining({
40
+ msg: 'Workflow Template is missing required property parentTemplateId',
41
+ event: EntityRehydrator.MISSING_REQUIRED_PROPERTY,
42
+ entityType: EntityType.WORKFLOW_TEMPLATE,
43
+ id: 'wft-1',
44
+ propertyName: 'parentTemplateId',
45
+ })
46
+ );
47
+ });
48
+
49
+ it('labels the warning with the entity type it was built for', () => {
50
+ const { logger, warn } = mockLogger();
51
+
52
+ new EntityRehydrator(EntityType.VIEW_TEMPLATE, logger).require(
53
+ { id: ViewTemplateId('vt-1'), name: undefined },
54
+ 'name'
55
+ );
56
+
57
+ expect(warn).toHaveBeenCalledWith(
58
+ expect.objectContaining({
59
+ msg: 'View Template is missing required property name',
60
+ entityType: EntityType.VIEW_TEMPLATE,
61
+ id: 'vt-1',
62
+ })
63
+ );
64
+ });
65
+
66
+ it('completes without a logger', () => {
67
+ const rehydrator = new EntityRehydrator(EntityType.WORKFLOW_TEMPLATE);
68
+
69
+ safeExpect(
70
+ rehydrator.require({ id: WorkflowTemplateId('wft-1'), parentTemplateId: undefined }, 'parentTemplateId')
71
+ ).toBeUndefined();
72
+ });
73
+ });
74
+ });
@@ -0,0 +1,97 @@
1
+ import { EntityType } from '@coast/core-types/common/EntityType';
2
+ import { EntityTypeLabels } from '@coast/core-types/common/EntityTypeLabels';
3
+ import { InvalidEntityReferenceException } from '@coast/core-types/exception/InvalidEntityReferenceException';
4
+ import { NotFoundException } from '@coast/core-types/exception/NotFoundException';
5
+ import { assertType } from '@coast/service-common/types/assertType';
6
+ import { Equals } from '@coast/service-common/types/Equals';
7
+ import { safeExpect } from '@paradoxical-io/common-test';
8
+
9
+ import { EntityUtils } from '../EntityUtils';
10
+
11
+ describe('EntityUtils', () => {
12
+ describe('assertValidEntityReference', () => {
13
+ it('should throw if value is not defined', () => {
14
+ const x = undefined as object | undefined;
15
+ try {
16
+ EntityUtils.assertValidEntityReference(x, EntityType.WORKFLOW_ENTITY);
17
+ } catch (error) {
18
+ expect(error).toBeInstanceOf(InvalidEntityReferenceException);
19
+
20
+ safeExpect((error as InvalidEntityReferenceException).message).toEqual(
21
+ `Invalid entity reference: ${EntityTypeLabels[EntityType.WORKFLOW_ENTITY]} not found`
22
+ );
23
+
24
+ safeExpect((error as InvalidEntityReferenceException).additionalData).toMatchObject({
25
+ entityType: EntityType.WORKFLOW_ENTITY,
26
+ });
27
+ }
28
+ });
29
+
30
+ it('should use custom error message', () => {
31
+ const x = undefined as object | undefined;
32
+ try {
33
+ EntityUtils.assertValidEntityReference(x, EntityType.WORKFLOW_ENTITY, {
34
+ message: 'x should exist...',
35
+ id: 'abc-123',
36
+ });
37
+ } catch (error) {
38
+ expect(error).toBeInstanceOf(InvalidEntityReferenceException);
39
+ safeExpect((error as InvalidEntityReferenceException).message).toEqual('x should exist...');
40
+
41
+ safeExpect((error as InvalidEntityReferenceException).additionalData).toMatchObject({
42
+ entityType: EntityType.WORKFLOW_ENTITY,
43
+ id: 'abc-123',
44
+ });
45
+ }
46
+ });
47
+
48
+ it('should not throw and should type narrow if value is defined', () => {
49
+ const x = { id: 'abc' } as object | undefined;
50
+ assertType<Equals<typeof x, object | undefined>>();
51
+
52
+ EntityUtils.assertValidEntityReference(x, EntityType.COMPONENT);
53
+ assertType<Equals<typeof x, object>>();
54
+ });
55
+ });
56
+
57
+ describe('assertEntityFound', () => {
58
+ it('should throw if value is not defined', () => {
59
+ const x = undefined as object | undefined;
60
+ try {
61
+ EntityUtils.assertEntityFound(x, EntityType.WORKFLOW_ENTITY);
62
+ } catch (error) {
63
+ expect(error).toBeInstanceOf(NotFoundException);
64
+
65
+ safeExpect((error as NotFoundException).additionalData).toMatchObject({
66
+ entityType: EntityType.WORKFLOW_ENTITY,
67
+ });
68
+ }
69
+ });
70
+
71
+ it('should use custom error message', () => {
72
+ const x = undefined as object | undefined;
73
+ try {
74
+ EntityUtils.assertEntityFound(x, EntityType.WORKFLOW_ENTITY, {
75
+ message: 'x should exist...',
76
+ id: 'abc-123',
77
+ });
78
+ } catch (error) {
79
+ expect(error).toBeInstanceOf(NotFoundException);
80
+ safeExpect((error as NotFoundException).message).toEqual('x should exist...');
81
+
82
+ safeExpect((error as NotFoundException).additionalData).toMatchObject({
83
+ entityType: EntityType.WORKFLOW_ENTITY,
84
+ id: 'abc-123',
85
+ });
86
+ }
87
+ });
88
+
89
+ it('should not throw and should type narrow if value is defined', () => {
90
+ const x = { id: 'abc' } as object | undefined;
91
+ assertType<Equals<typeof x, object | undefined>>();
92
+
93
+ EntityUtils.assertEntityFound(x, EntityType.COMPONENT);
94
+ assertType<Equals<typeof x, object>>();
95
+ });
96
+ });
97
+ });