@api-client/core 0.18.23 → 0.18.25

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 (41) hide show
  1. package/build/src/modeling/DomainEntity.d.ts +6 -1
  2. package/build/src/modeling/DomainEntity.d.ts.map +1 -1
  3. package/build/src/modeling/DomainEntity.js +24 -5
  4. package/build/src/modeling/DomainEntity.js.map +1 -1
  5. package/build/src/modeling/Semantics.d.ts +68 -0
  6. package/build/src/modeling/Semantics.d.ts.map +1 -1
  7. package/build/src/modeling/Semantics.js +217 -0
  8. package/build/src/modeling/Semantics.js.map +1 -1
  9. package/build/src/modeling/definitions/Email.d.ts +0 -4
  10. package/build/src/modeling/definitions/Email.d.ts.map +1 -1
  11. package/build/src/modeling/definitions/Email.js +1 -2
  12. package/build/src/modeling/definitions/Email.js.map +1 -1
  13. package/build/src/modeling/definitions/Password.d.ts.map +1 -1
  14. package/build/src/modeling/definitions/Password.js +1 -3
  15. package/build/src/modeling/definitions/Password.js.map +1 -1
  16. package/build/src/modeling/helpers/Intelisense.d.ts.map +1 -1
  17. package/build/src/modeling/helpers/Intelisense.js +24 -58
  18. package/build/src/modeling/helpers/Intelisense.js.map +1 -1
  19. package/build/src/modeling/helpers/database.js +2 -2
  20. package/build/src/modeling/helpers/database.js.map +1 -1
  21. package/build/src/modeling/templates/verticals/business-services/ecommerce-domain.js +3 -3
  22. package/build/src/modeling/templates/verticals/business-services/ecommerce-domain.js.map +1 -1
  23. package/build/src/modeling/templates/verticals/technology-media/blog-domain.js +5 -5
  24. package/build/src/modeling/templates/verticals/technology-media/blog-domain.js.map +1 -1
  25. package/build/tsconfig.tsbuildinfo +1 -1
  26. package/data/models/example-generator-api.json +20 -20
  27. package/package.json +1 -1
  28. package/src/modeling/DomainEntity.ts +27 -5
  29. package/src/modeling/Semantics.ts +254 -0
  30. package/src/modeling/definitions/Email.ts +1 -6
  31. package/src/modeling/definitions/Password.ts +1 -3
  32. package/src/modeling/helpers/Intelisense.ts +26 -58
  33. package/src/modeling/helpers/database.ts +2 -2
  34. package/src/modeling/templates/verticals/business-services/ecommerce-domain.ts +3 -3
  35. package/src/modeling/templates/verticals/technology-media/blog-domain.ts +5 -5
  36. package/tests/unit/modeling/definitions/email.spec.ts +0 -1
  37. package/tests/unit/modeling/definitions/password.spec.ts +0 -2
  38. package/tests/unit/modeling/domain_entity_parents.spec.ts +243 -0
  39. package/tests/unit/modeling/semantic-configs.spec.ts +0 -1
  40. package/tests/unit/modeling/semantic_runtime.spec.ts +113 -0
  41. package/tests/unit/modeling/semantics.spec.ts +68 -0
@@ -147,9 +147,7 @@ export const DEFAULT_PASSWORD_CONFIG: PasswordConfig = {
147
147
  requireNumbers: true,
148
148
  requireUppercase: true,
149
149
  requireLowercase: true,
150
- encryptionAlgorithm: PasswordEncryptionAlgorithm.Bcrypt,
151
- saltRounds: 12,
152
- maxAge: undefined,
150
+ minLength: 8,
153
151
  preventReuse: false,
154
152
  preventReuseCount: 5,
155
153
  allowCommonPasswords: false,
@@ -4,12 +4,12 @@ import type { DomainProperty } from '../DomainProperty.js'
4
4
  import type { DomainAssociation } from '../DomainAssociation.js'
5
5
  import type { IThing } from '../../models/Thing.js'
6
6
  import { SemanticType } from '../Semantics.js'
7
- import { createEmailSemantic } from '../definitions/Email.js'
8
- import { createPasswordSemantic } from '../definitions/Password.js'
7
+ import { createEmailSemantic, DEFAULT_EMAIL_CONFIG } from '../definitions/Email.js'
8
+ import { createPasswordSemantic, DEFAULT_PASSWORD_CONFIG } from '../definitions/Password.js'
9
9
  import { createPhoneSemantic } from '../definitions/Phone.js'
10
10
  import { createPublicUniqueNameSemantic, DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG } from '../definitions/PublicUniqueName.js'
11
11
  import { createDescriptionSemantic } from '../definitions/Description.js'
12
- import { createCurrencySemantic } from '../definitions/Currency.js'
12
+ import { CURRENCY_PRESETS } from '../definitions/Currency.js'
13
13
  import { createStatusSemantic } from '../definitions/Status.js'
14
14
  import { SKU_PRESETS } from '../definitions/SKU.js'
15
15
 
@@ -131,13 +131,8 @@ export function addEmailField(entity: DomainEntity, info: Partial<IThing> = {}):
131
131
  type: 'string',
132
132
  required: true,
133
133
  index: true,
134
- semantics: [
135
- createEmailSemantic({
136
- requireVerification: true,
137
- verificationMethod: 'email',
138
- }),
139
- ],
140
134
  })
135
+ prop.addSemantic(createEmailSemantic(DEFAULT_EMAIL_CONFIG))
141
136
  return prop
142
137
  }
143
138
 
@@ -158,16 +153,8 @@ export function addPasswordField(entity: DomainEntity, info: Partial<IThing> = {
158
153
  info: { name: 'password', displayName: 'Password', ...info },
159
154
  type: 'string',
160
155
  required: true,
161
- semantics: [
162
- createPasswordSemantic({
163
- requireSpecialChars: true,
164
- requireNumbers: true,
165
- requireUppercase: true,
166
- requireLowercase: true,
167
- minLength: 8,
168
- }),
169
- ],
170
156
  })
157
+ prop.addSemantic(createPasswordSemantic(DEFAULT_PASSWORD_CONFIG))
171
158
  return prop
172
159
  }
173
160
 
@@ -235,8 +222,8 @@ export function addDescriptionField(entity: DomainEntity, info: Partial<IThing>
235
222
  const prop = entity.addProperty({
236
223
  info: { name: 'description', displayName: 'Description', ...info },
237
224
  type: 'string',
238
- semantics: [createDescriptionSemantic()],
239
225
  })
226
+ prop.addSemantic(createDescriptionSemantic())
240
227
  return prop
241
228
  }
242
229
 
@@ -603,15 +590,15 @@ export function addPhoneField(entity: DomainEntity, info: Partial<IThing> = {}):
603
590
  info: { name: 'phone', displayName: 'Phone Number', ...info },
604
591
  type: 'string',
605
592
  index: true,
606
- semantics: [
607
- createPhoneSemantic({
608
- format: 'international',
609
- requireCountryCode: true,
610
- requireVerification: true,
611
- verificationMethod: 'sms',
612
- }),
613
- ],
614
593
  })
594
+ prop.addSemantic(
595
+ createPhoneSemantic({
596
+ format: 'international',
597
+ requireCountryCode: true,
598
+ requireVerification: true,
599
+ verificationMethod: 'sms',
600
+ })
601
+ )
615
602
  return prop
616
603
  }
617
604
 
@@ -640,8 +627,8 @@ export function addAvatarUrlField(entity: DomainEntity, info: Partial<IThing> =
640
627
  const prop = entity.addProperty({
641
628
  info: { name: 'avatar_url', displayName: 'Avatar URL', ...info },
642
629
  type: 'string',
643
- semantics: [{ id: SemanticType.ImageURL }],
644
630
  })
631
+ prop.addSemantic({ id: SemanticType.ImageURL })
645
632
  return prop
646
633
  }
647
634
 
@@ -676,8 +663,8 @@ export function addPublicUniqueNameField(entity: DomainEntity, info: Partial<ITh
676
663
  index: true,
677
664
  required: true,
678
665
  unique: true,
679
- semantics: [createPublicUniqueNameSemantic(DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG)],
680
666
  })
667
+ prop.addSemantic(createPublicUniqueNameSemantic(DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG))
681
668
  return prop
682
669
  }
683
670
 
@@ -707,8 +694,8 @@ export function addSkuField(entity: DomainEntity, info: Partial<IThing> = {}): D
707
694
  type: 'string',
708
695
  required: true,
709
696
  unique: true,
710
- semantics: [{ ...SKU_PRESETS.PRODUCT_STANDARD }],
711
697
  })
698
+ prop.addSemantic({ ...SKU_PRESETS.PRODUCT_STANDARD })
712
699
  return prop
713
700
  }
714
701
 
@@ -748,16 +735,8 @@ export function addPriceField(entity: DomainEntity, info: Partial<IThing> = {}):
748
735
  },
749
736
  },
750
737
  ],
751
- semantics: [
752
- createCurrencySemantic({
753
- storageFormat: 'integer_cents',
754
- defaultCurrency: 'USD',
755
- decimalPlaces: 2,
756
- allowNegative: false,
757
- validateCurrencyCode: true,
758
- }),
759
- ],
760
738
  })
739
+ prop.addSemantic({ ...CURRENCY_PRESETS.USD_CENTS })
761
740
  return prop
762
741
  }
763
742
 
@@ -843,7 +822,9 @@ export function addImagesField(entity: DomainEntity, info: Partial<IThing> = {})
843
822
  info: { name: 'images', displayName: 'Images', ...info },
844
823
  type: 'string',
845
824
  multiple: true,
846
- semantics: [{ id: SemanticType.ImageURL }],
825
+ })
826
+ prop.addSemantic({
827
+ id: SemanticType.ImageURL,
847
828
  })
848
829
  return prop
849
830
  }
@@ -880,7 +861,6 @@ export function addCustomStatusField(
880
861
  info: { name: 'status', displayName: 'Status', ...info },
881
862
  type: 'string',
882
863
  required: true,
883
- semantics: [createStatusSemantic()],
884
864
  schema: {
885
865
  enum: enumValues,
886
866
  defaultValue: {
@@ -889,6 +869,7 @@ export function addCustomStatusField(
889
869
  },
890
870
  },
891
871
  })
872
+ prop.addSemantic(createStatusSemantic())
892
873
  return prop
893
874
  }
894
875
 
@@ -1008,15 +989,8 @@ export function addUnitPriceField(entity: DomainEntity, info: Partial<IThing> =
1008
989
  info: { name: 'unitPrice', displayName: 'Unit Price', ...info },
1009
990
  type: 'number',
1010
991
  required: true,
1011
- semantics: [
1012
- createCurrencySemantic({
1013
- storageFormat: 'integer_cents',
1014
- defaultCurrency: 'USD',
1015
- decimalPlaces: 2,
1016
- allowNegative: false,
1017
- }),
1018
- ],
1019
992
  })
993
+ prop.addSemantic({ ...CURRENCY_PRESETS.USD_CENTS })
1020
994
  return prop
1021
995
  }
1022
996
 
@@ -1029,7 +1003,7 @@ export function addCurrencyAmountField(
1029
1003
  displayName: string,
1030
1004
  info: Partial<IThing> = {}
1031
1005
  ): DomainProperty {
1032
- return entity.addProperty({
1006
+ const prop = entity.addProperty({
1033
1007
  info: {
1034
1008
  name: fieldName,
1035
1009
  displayName,
@@ -1037,15 +1011,9 @@ export function addCurrencyAmountField(
1037
1011
  },
1038
1012
  type: 'number',
1039
1013
  required: true,
1040
- semantics: [
1041
- createCurrencySemantic({
1042
- storageFormat: 'integer_cents',
1043
- defaultCurrency: 'USD',
1044
- decimalPlaces: 2,
1045
- allowNegative: false,
1046
- }),
1047
- ],
1048
1014
  })
1015
+ prop.addSemantic({ ...CURRENCY_PRESETS.USD_CENTS })
1016
+ return prop
1049
1017
  }
1050
1018
 
1051
1019
  /**
@@ -1,4 +1,4 @@
1
- import { snakeCase } from '@pawel-up/jexl/string.js'
1
+ import { SNAKE_CASE } from '@pawel-up/jexl/string.js'
2
2
  import DOMPurify from '../../lib/dom_purify.js'
3
3
 
4
4
  export function sanitizeInput(input: string): string {
@@ -20,7 +20,7 @@ export function toDatabaseColumnName(inputName: string, defaultName = 'untitled_
20
20
  let name = sanitizeInput(inputName)
21
21
 
22
22
  // 2. Convert to lowercase
23
- name = snakeCase(name)
23
+ name = SNAKE_CASE(name)
24
24
 
25
25
  // 3. Replace spaces and multiple hyphens/underscores with a single underscore
26
26
  name = name.replace(/[\s-]+/g, '_')
@@ -549,7 +549,7 @@ export default function createEcommerceDomain(options: CreateTemplateOptions = {
549
549
  allowNegative: false,
550
550
  }),
551
551
  createCalculatedSemantic({
552
- formula: '{{unitPrice}} * {{quantity}}',
552
+ formula: `{{${cartUnitPrice.key}}} * {{${cartItemQuantity.key}}}`,
553
553
  dependencies: [cartUnitPrice.key, cartItemQuantity.key],
554
554
  recalculateOnUpdate: true,
555
555
  }),
@@ -637,7 +637,7 @@ export default function createEcommerceDomain(options: CreateTemplateOptions = {
637
637
  allowNegative: false,
638
638
  }),
639
639
  createCalculatedSemantic({
640
- formula: '{{subtotal}} + {{taxAmount}} + {{shippingAmount}}',
640
+ formula: `{{${subtotal.key}}} + {{${taxAmount.key}}} + {{${shippingAmount.key}}}`,
641
641
  dependencies: [subtotal.key, taxAmount.key, shippingAmount.key],
642
642
  recalculateOnUpdate: true,
643
643
  }),
@@ -711,7 +711,7 @@ export default function createEcommerceDomain(options: CreateTemplateOptions = {
711
711
  allowNegative: false,
712
712
  }),
713
713
  createCalculatedSemantic({
714
- formula: '{{quantity}} * {{unitPrice}}',
714
+ formula: `{{${quantity.key}}} * {{${unitPrice.key}}}`,
715
715
  dependencies: [quantity.key, unitPrice.key],
716
716
  recalculateOnUpdate: true,
717
717
  }),
@@ -251,7 +251,7 @@ export default function createBlogDomain(options: CreateTemplateOptions = {}): D
251
251
  semantics: [{ id: SemanticType.Summary }],
252
252
  })
253
253
 
254
- postEntity.addProperty({
254
+ const postContent = postEntity.addProperty({
255
255
  info: { name: 'content', displayName: 'Content', description: 'Full content of the post in HTML or Markdown' },
256
256
  type: 'string',
257
257
  required: true,
@@ -310,8 +310,8 @@ export default function createBlogDomain(options: CreateTemplateOptions = {}): D
310
310
  readOnly: true,
311
311
  semantics: [
312
312
  createCalculatedSemantic({
313
- formula: 'CEILING(LENGTH(REPLACE({{content}}, " ", "")) / 250)',
314
- dependencies: [],
313
+ formula: `CEIL(LENGTH(REPLACE({{${postContent.key}}}, " ", "")) / 250)`,
314
+ dependencies: [postContent.key],
315
315
  recalculateOnUpdate: true,
316
316
  }),
317
317
  ],
@@ -323,8 +323,8 @@ export default function createBlogDomain(options: CreateTemplateOptions = {}): D
323
323
  readOnly: true,
324
324
  semantics: [
325
325
  createCalculatedSemantic({
326
- formula: 'LENGTH({{content}}) - LENGTH(REPLACE({{content}}, " ", "")) + 1',
327
- dependencies: [],
326
+ formula: `LENGTH({{${postContent.key}}}) - LENGTH(REPLACE({{${postContent.key}}}, " ", "")) + 1`,
327
+ dependencies: [postContent.key],
328
328
  recalculateOnUpdate: true,
329
329
  }),
330
330
  ],
@@ -18,7 +18,6 @@ test.group('Email Semantic Configuration', () => {
18
18
  const config: EmailConfig = {
19
19
  allowedDomains: ['example.com'],
20
20
  requireVerification: true,
21
- verificationMethod: 'email',
22
21
  }
23
22
  const semantic = createEmailSemantic(config)
24
23
  assert.equal(semantic.id, SemanticType.Email)
@@ -77,8 +77,6 @@ test.group('Password Semantic Configuration', () => {
77
77
  assert.equal(DEFAULT_PASSWORD_CONFIG.requireNumbers, true)
78
78
  assert.equal(DEFAULT_PASSWORD_CONFIG.requireUppercase, true)
79
79
  assert.equal(DEFAULT_PASSWORD_CONFIG.requireLowercase, true)
80
- assert.equal(DEFAULT_PASSWORD_CONFIG.encryptionAlgorithm, PasswordEncryptionAlgorithm.Bcrypt)
81
- assert.equal(DEFAULT_PASSWORD_CONFIG.saltRounds, 12)
82
80
  assert.equal(DEFAULT_PASSWORD_CONFIG.maxAge, undefined)
83
81
  assert.equal(DEFAULT_PASSWORD_CONFIG.preventReuse, false)
84
82
  assert.equal(DEFAULT_PASSWORD_CONFIG.preventReuseCount, 5)
@@ -141,6 +141,101 @@ test.group('DomainEntity.addParent()', () => {
141
141
  assert.isTrue(dataDomain.graph.hasEdge(entity2.key, entity1.key))
142
142
  assert.deepEqual(dataDomain.graph.edge(entity2.key, entity1.key), { type: 'parent' })
143
143
  })
144
+
145
+ test('adds a foreign domain parent to the entity', ({ assert }) => {
146
+ const dataDomain = new DataDomain()
147
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
148
+ const model = dataDomain.addModel()
149
+ const entity = model.addEntity({ key: 'child-entity' })
150
+ const foreignModel = foreignDomain.addModel()
151
+ const foreignEntity = foreignModel.addEntity({ key: 'parent-entity' })
152
+ const foreignKey = `${foreignDomain.key}:${foreignEntity.key}`
153
+
154
+ // Register foreign domain and add foreign parent
155
+ foreignDomain.info.version = '1.0.0'
156
+ dataDomain.registerForeignDomain(foreignDomain)
157
+ entity.addParent('parent-entity', 'external-domain')
158
+
159
+ // Verify it was added correctly
160
+ assert.isTrue(dataDomain.graph.hasEdge(entity.key, foreignKey))
161
+ assert.deepEqual(dataDomain.graph.edge(entity.key, foreignKey), {
162
+ type: 'parent',
163
+ domain: 'external-domain',
164
+ foreign: true,
165
+ })
166
+ const parents = [...entity.listParents()]
167
+ assert.lengthOf(parents, 1)
168
+ assert.deepEqual(parents[0], foreignEntity)
169
+ })
170
+
171
+ test('throws an error if foreign domain parent does not exist', ({ assert }) => {
172
+ const dataDomain = new DataDomain()
173
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
174
+ const model = dataDomain.addModel()
175
+ const entity = model.addEntity({ key: 'child-entity' })
176
+
177
+ // Register foreign domain without the parent entity
178
+ foreignDomain.info.version = '1.0.0'
179
+ dataDomain.registerForeignDomain(foreignDomain)
180
+
181
+ assert.throws(() => {
182
+ entity.addParent('non-existent-parent', 'external-domain')
183
+ }, 'Entity with key "non-existent-parent" not found in domain "external-domain"')
184
+ })
185
+
186
+ test('throws an error if foreign domain is not registered', ({ assert }) => {
187
+ const dataDomain = new DataDomain()
188
+ const model = dataDomain.addModel()
189
+ const entity = model.addEntity({ key: 'child-entity' })
190
+
191
+ assert.throws(() => {
192
+ entity.addParent('parent-entity', 'non-existent-domain')
193
+ }, 'Foreign domain non-existent-domain not found')
194
+ })
195
+
196
+ test('can add both local and foreign parents', ({ assert }) => {
197
+ const dataDomain = new DataDomain()
198
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
199
+ const model = dataDomain.addModel()
200
+ const localParent = model.addEntity({ key: 'local-parent' })
201
+ const entity = model.addEntity({ key: 'child-entity' })
202
+ const foreignModel = foreignDomain.addModel()
203
+ const foreignEntity = foreignModel.addEntity({ key: 'foreign-parent' })
204
+ const foreignKey = `${foreignDomain.key}:${foreignEntity.key}`
205
+
206
+ // Register foreign domain and add both parents
207
+ foreignDomain.info.version = '1.0.0'
208
+ dataDomain.registerForeignDomain(foreignDomain)
209
+ entity.addParent(localParent.key)
210
+ entity.addParent('foreign-parent', 'external-domain')
211
+
212
+ // Verify both parents were added
213
+ assert.isTrue(dataDomain.graph.hasEdge(entity.key, localParent.key))
214
+ assert.isTrue(dataDomain.graph.hasEdge(entity.key, foreignKey))
215
+
216
+ const parents = [...entity.listParents()]
217
+ assert.lengthOf(parents, 2)
218
+ assert.deepInclude(parents, localParent)
219
+ assert.deepInclude(parents, foreignEntity)
220
+ })
221
+
222
+ test('throws an error if trying to add the same foreign parent twice', ({ assert }) => {
223
+ const dataDomain = new DataDomain()
224
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
225
+ const model = dataDomain.addModel()
226
+ const entity = model.addEntity({ key: 'child-entity' })
227
+ const foreignModel = foreignDomain.addModel()
228
+ foreignModel.addEntity({ key: 'parent-entity' })
229
+
230
+ // Register foreign domain and add foreign parent
231
+ foreignDomain.info.version = '1.0.0'
232
+ dataDomain.registerForeignDomain(foreignDomain)
233
+ entity.addParent('parent-entity', 'external-domain')
234
+
235
+ assert.throws(() => {
236
+ entity.addParent('parent-entity', 'external-domain')
237
+ }, 'Parent parent-entity already exists')
238
+ })
144
239
  })
145
240
 
146
241
  test.group('DomainEntity.removeParent()', () => {
@@ -164,6 +259,121 @@ test.group('DomainEntity.removeParent()', () => {
164
259
  }, `Trying to remove a parent non-existent-entity from ${entity.key}, but it doesn't exist`)
165
260
  })
166
261
 
262
+ test('throws an error if foreign domain parent does not exist', ({ assert }) => {
263
+ const dataDomain = new DataDomain()
264
+ const model = dataDomain.addModel()
265
+ const entity = model.addEntity({ key: 'child-entity' })
266
+ assert.throws(() => {
267
+ entity.removeParent('non-existent-entity', 'external-domain')
268
+ }, `Foreign domain external-domain not found`)
269
+ })
270
+
271
+ test('throws an error if edge exists but is not a parent relationship', ({ assert }) => {
272
+ const dataDomain = new DataDomain()
273
+ const model = dataDomain.addModel()
274
+ const entity1 = model.addEntity({ key: 'entity1' })
275
+ const entity2 = model.addEntity({ key: 'entity2' })
276
+ // Create a non-parent edge
277
+ dataDomain.graph.setEdge(entity2.key, entity1.key, { type: 'association' })
278
+ assert.throws(() => {
279
+ entity2.removeParent(entity1.key)
280
+ }, `Edge between ${entity2.key} and entity1 exists but is not a parent relationship`)
281
+ })
282
+
283
+ test('throws an error if foreign domain edge exists but is not a parent relationship', ({ assert }) => {
284
+ const dataDomain = new DataDomain()
285
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
286
+ const model = dataDomain.addModel()
287
+ const entity = model.addEntity({ key: 'child-entity' })
288
+ const foreignModel = foreignDomain.addModel()
289
+ const foreignEntity = foreignModel.addEntity({ key: 'parent-entity' })
290
+
291
+ // Register foreign domain and create a non-parent edge
292
+ foreignDomain.info.version = '1.0.0'
293
+ dataDomain.registerForeignDomain(foreignDomain)
294
+ const foreignKey = `${foreignDomain.key}:${foreignEntity.key}`
295
+ dataDomain.graph.setEdge(entity.key, foreignKey, { type: 'association' })
296
+
297
+ assert.throws(() => {
298
+ entity.removeParent('parent-entity', 'external-domain')
299
+ }, `Edge between child-entity and parent-entity in domain "external-domain" exists but is not a parent relationship`)
300
+ })
301
+
302
+ test('removes a foreign domain parent from the entity', ({ assert }) => {
303
+ const dataDomain = new DataDomain()
304
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
305
+ const model = dataDomain.addModel()
306
+ const entity = model.addEntity({ key: 'child-entity' })
307
+ const foreignModel = foreignDomain.addModel()
308
+ const foreignEntity = foreignModel.addEntity({ key: 'parent-entity' })
309
+ const foreignKey = `${foreignDomain.key}:${foreignEntity.key}`
310
+
311
+ // Register foreign domain and add foreign parent
312
+ foreignDomain.info.version = '1.0.0'
313
+ dataDomain.registerForeignDomain(foreignDomain)
314
+ entity.addParent('parent-entity', 'external-domain')
315
+
316
+ // Verify it was added
317
+ assert.isTrue(dataDomain.graph.hasEdge(entity.key, foreignKey))
318
+ assert.deepEqual(dataDomain.graph.edge(entity.key, foreignKey), {
319
+ type: 'parent',
320
+ domain: 'external-domain',
321
+ foreign: true,
322
+ })
323
+
324
+ // Remove the foreign parent
325
+ entity.removeParent('parent-entity', 'external-domain')
326
+ assert.isFalse(dataDomain.graph.hasEdge(entity.key, foreignKey))
327
+ })
328
+
329
+ test('removes local parent while keeping foreign parent', ({ assert }) => {
330
+ const dataDomain = new DataDomain()
331
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
332
+ const model = dataDomain.addModel()
333
+ const localParent = model.addEntity({ key: 'local-parent' })
334
+ const entity = model.addEntity({ key: 'child-entity' })
335
+ const foreignModel = foreignDomain.addModel()
336
+ const foreignEntity = foreignModel.addEntity({ key: 'foreign-parent' })
337
+ const foreignKey = `${foreignDomain.key}:${foreignEntity.key}`
338
+
339
+ // Register foreign domain and add both parents
340
+ foreignDomain.info.version = '1.0.0'
341
+ dataDomain.registerForeignDomain(foreignDomain)
342
+ entity.addParent(localParent.key)
343
+ entity.addParent('foreign-parent', 'external-domain')
344
+
345
+ // Remove only the local parent
346
+ entity.removeParent(localParent.key)
347
+
348
+ // Verify local parent is removed but foreign remains
349
+ assert.isFalse(dataDomain.graph.hasEdge(entity.key, localParent.key))
350
+ assert.isTrue(dataDomain.graph.hasEdge(entity.key, foreignKey))
351
+ })
352
+
353
+ test('removes foreign parent while keeping local parent', ({ assert }) => {
354
+ const dataDomain = new DataDomain()
355
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
356
+ const model = dataDomain.addModel()
357
+ const localParent = model.addEntity({ key: 'local-parent' })
358
+ const entity = model.addEntity({ key: 'child-entity' })
359
+ const foreignModel = foreignDomain.addModel()
360
+ const foreignEntity = foreignModel.addEntity({ key: 'foreign-parent' })
361
+ const foreignKey = `${foreignDomain.key}:${foreignEntity.key}`
362
+
363
+ // Register foreign domain and add both parents
364
+ foreignDomain.info.version = '1.0.0'
365
+ dataDomain.registerForeignDomain(foreignDomain)
366
+ entity.addParent(localParent.key)
367
+ entity.addParent('foreign-parent', 'external-domain')
368
+
369
+ // Remove only the foreign parent
370
+ entity.removeParent('foreign-parent', 'external-domain')
371
+
372
+ // Verify foreign parent is removed but local remains
373
+ assert.isTrue(dataDomain.graph.hasEdge(entity.key, localParent.key))
374
+ assert.isFalse(dataDomain.graph.hasEdge(entity.key, foreignKey))
375
+ })
376
+
167
377
  test('notifies change', async ({ assert }) => {
168
378
  const dataDomain = new DataDomain()
169
379
  const model = dataDomain.addModel()
@@ -174,6 +384,22 @@ test.group('DomainEntity.removeParent()', () => {
174
384
  await assert.dispatches(dataDomain, 'change', { timeout: 20 })
175
385
  })
176
386
 
387
+ test('notifies change when removing foreign domain parent', async ({ assert }) => {
388
+ const dataDomain = new DataDomain()
389
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
390
+ const model = dataDomain.addModel()
391
+ const entity = model.addEntity({ key: 'child-entity' })
392
+ const foreignModel = foreignDomain.addModel()
393
+ foreignModel.addEntity({ key: 'parent-entity' })
394
+
395
+ // Register foreign domain and add foreign parent
396
+ foreignDomain.info.version = '1.0.0'
397
+ dataDomain.registerForeignDomain(foreignDomain)
398
+ entity.addParent('parent-entity', 'external-domain')
399
+ entity.removeParent('parent-entity', 'external-domain')
400
+ await assert.dispatches(dataDomain, 'change', { timeout: 20 })
401
+ })
402
+
177
403
  test('removes the graph edge', ({ assert }) => {
178
404
  const dataDomain = new DataDomain()
179
405
  const model = dataDomain.addModel()
@@ -183,6 +409,23 @@ test.group('DomainEntity.removeParent()', () => {
183
409
  entity2.removeParent(entity1.key)
184
410
  assert.isFalse(dataDomain.graph.hasEdge(entity2.key, entity1.key))
185
411
  })
412
+
413
+ test('removes the foreign domain graph edge', ({ assert }) => {
414
+ const dataDomain = new DataDomain()
415
+ const foreignDomain = new DataDomain({ key: 'external-domain' })
416
+ const model = dataDomain.addModel()
417
+ const entity = model.addEntity({ key: 'child-entity' })
418
+ const foreignModel = foreignDomain.addModel()
419
+ foreignModel.addEntity({ key: 'parent-entity' })
420
+ const foreignKey = `${foreignDomain.key}:parent-entity`
421
+
422
+ // Register foreign domain and add foreign parent
423
+ foreignDomain.info.version = '1.0.0'
424
+ dataDomain.registerForeignDomain(foreignDomain)
425
+ entity.addParent('parent-entity', 'external-domain')
426
+ entity.removeParent('parent-entity', 'external-domain')
427
+ assert.isFalse(dataDomain.graph.hasEdge(entity.key, foreignKey))
428
+ })
186
429
  })
187
430
 
188
431
  test.group('DomainEntity.hasCircularParent()', () => {
@@ -266,7 +266,6 @@ test.group('Semantic Configurations', () => {
266
266
  const config: EmailConfig = {
267
267
  allowedDomains: ['example.com'],
268
268
  requireVerification: true,
269
- verificationMethod: 'email',
270
269
  }
271
270
  const semantic = createEmailSemantic(config)
272
271
  assert.equal(semantic.id, SemanticType.Email)