@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.
- package/build/src/modeling/DomainEntity.d.ts +6 -1
- package/build/src/modeling/DomainEntity.d.ts.map +1 -1
- package/build/src/modeling/DomainEntity.js +24 -5
- package/build/src/modeling/DomainEntity.js.map +1 -1
- package/build/src/modeling/Semantics.d.ts +68 -0
- package/build/src/modeling/Semantics.d.ts.map +1 -1
- package/build/src/modeling/Semantics.js +217 -0
- package/build/src/modeling/Semantics.js.map +1 -1
- package/build/src/modeling/definitions/Email.d.ts +0 -4
- package/build/src/modeling/definitions/Email.d.ts.map +1 -1
- package/build/src/modeling/definitions/Email.js +1 -2
- package/build/src/modeling/definitions/Email.js.map +1 -1
- package/build/src/modeling/definitions/Password.d.ts.map +1 -1
- package/build/src/modeling/definitions/Password.js +1 -3
- package/build/src/modeling/definitions/Password.js.map +1 -1
- package/build/src/modeling/helpers/Intelisense.d.ts.map +1 -1
- package/build/src/modeling/helpers/Intelisense.js +24 -58
- package/build/src/modeling/helpers/Intelisense.js.map +1 -1
- package/build/src/modeling/helpers/database.js +2 -2
- package/build/src/modeling/helpers/database.js.map +1 -1
- package/build/src/modeling/templates/verticals/business-services/ecommerce-domain.js +3 -3
- package/build/src/modeling/templates/verticals/business-services/ecommerce-domain.js.map +1 -1
- package/build/src/modeling/templates/verticals/technology-media/blog-domain.js +5 -5
- package/build/src/modeling/templates/verticals/technology-media/blog-domain.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +20 -20
- package/package.json +1 -1
- package/src/modeling/DomainEntity.ts +27 -5
- package/src/modeling/Semantics.ts +254 -0
- package/src/modeling/definitions/Email.ts +1 -6
- package/src/modeling/definitions/Password.ts +1 -3
- package/src/modeling/helpers/Intelisense.ts +26 -58
- package/src/modeling/helpers/database.ts +2 -2
- package/src/modeling/templates/verticals/business-services/ecommerce-domain.ts +3 -3
- package/src/modeling/templates/verticals/technology-media/blog-domain.ts +5 -5
- package/tests/unit/modeling/definitions/email.spec.ts +0 -1
- package/tests/unit/modeling/definitions/password.spec.ts +0 -2
- package/tests/unit/modeling/domain_entity_parents.spec.ts +243 -0
- package/tests/unit/modeling/semantic-configs.spec.ts +0 -1
- package/tests/unit/modeling/semantic_runtime.spec.ts +113 -0
- 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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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)
|