@api-client/core 0.18.18 → 0.18.20

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 (43) hide show
  1. package/build/src/modeling/DataDomain.d.ts +35 -1
  2. package/build/src/modeling/DataDomain.d.ts.map +1 -1
  3. package/build/src/modeling/DataDomain.js +120 -0
  4. package/build/src/modeling/DataDomain.js.map +1 -1
  5. package/build/src/modeling/DomainProperty.d.ts +10 -0
  6. package/build/src/modeling/DomainProperty.d.ts.map +1 -1
  7. package/build/src/modeling/DomainProperty.js +26 -2
  8. package/build/src/modeling/DomainProperty.js.map +1 -1
  9. package/build/src/modeling/definitions/SKU.d.ts.map +1 -1
  10. package/build/src/modeling/definitions/SKU.js +2 -0
  11. package/build/src/modeling/definitions/SKU.js.map +1 -1
  12. package/build/src/modeling/helpers/Intelisense.d.ts +472 -0
  13. package/build/src/modeling/helpers/Intelisense.d.ts.map +1 -0
  14. package/build/src/modeling/helpers/Intelisense.js +1201 -0
  15. package/build/src/modeling/helpers/Intelisense.js.map +1 -0
  16. package/build/src/modeling/templates/blog-domain.d.ts +40 -0
  17. package/build/src/modeling/templates/blog-domain.d.ts.map +1 -0
  18. package/build/src/modeling/templates/blog-domain.js +621 -0
  19. package/build/src/modeling/templates/blog-domain.js.map +1 -0
  20. package/build/src/modeling/templates/ecommerce-domain.d.ts +39 -0
  21. package/build/src/modeling/templates/ecommerce-domain.d.ts.map +1 -0
  22. package/build/src/modeling/templates/ecommerce-domain.js +662 -0
  23. package/build/src/modeling/templates/ecommerce-domain.js.map +1 -0
  24. package/build/src/modeling/templates/types.d.ts +9 -0
  25. package/build/src/modeling/templates/types.d.ts.map +1 -0
  26. package/build/src/modeling/templates/types.js +2 -0
  27. package/build/src/modeling/templates/types.js.map +1 -0
  28. package/build/src/modeling/types.d.ts +49 -0
  29. package/build/src/modeling/types.d.ts.map +1 -1
  30. package/build/src/modeling/types.js.map +1 -1
  31. package/build/tsconfig.tsbuildinfo +1 -1
  32. package/data/models/example-generator-api.json +6 -6
  33. package/package.json +1 -1
  34. package/src/modeling/DataDomain.ts +144 -0
  35. package/src/modeling/DomainProperty.ts +23 -0
  36. package/src/modeling/definitions/SKU.ts +2 -0
  37. package/src/modeling/helpers/Intelisense.ts +1346 -0
  38. package/src/modeling/templates/blog-domain.ts +788 -0
  39. package/src/modeling/templates/ecommerce-domain.ts +834 -0
  40. package/src/modeling/templates/types.ts +9 -0
  41. package/src/modeling/types.ts +63 -0
  42. package/tests/unit/modeling/DataDomain.search.spec.ts +188 -0
  43. package/tests/unit/modeling/helpers/intellisense.spec.ts +971 -0
@@ -0,0 +1,1346 @@
1
+ import type { DomainEntity } from '../DomainEntity.js'
2
+ import type { DomainElement } from '../DomainElement.js'
3
+ import type { DomainProperty } from '../DomainProperty.js'
4
+ import type { DomainAssociation } from '../DomainAssociation.js'
5
+ import type { IThing } from '../../models/Thing.js'
6
+ import { SemanticType } from '../Semantics.js'
7
+ import { createEmailSemantic } from '../definitions/Email.js'
8
+ import { createPasswordSemantic } from '../definitions/Password.js'
9
+ import { createPhoneSemantic } from '../definitions/Phone.js'
10
+ import { createPublicUniqueNameSemantic, DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG } from '../definitions/PublicUniqueName.js'
11
+ import { createDescriptionSemantic } from '../definitions/Description.js'
12
+ import { createCurrencySemantic } from '../definitions/Currency.js'
13
+ import { createStatusSemantic } from '../definitions/Status.js'
14
+ import { SKU_PRESETS } from '../definitions/SKU.js'
15
+
16
+ /**
17
+ * Adds a field to the given entity that is automatically generated by the system.
18
+ *
19
+ * @param entity The entity to which the auto field will be added.
20
+ * @param autoField The name of the auto field to add.
21
+ * @returns The added DomainElement representing the auto field.
22
+ * @throws Error if the auto field is not supported.
23
+ */
24
+ export function addAutoField(entity: DomainEntity, autoField: string): DomainElement {
25
+ switch (autoField) {
26
+ case 'id':
27
+ return addIdField(entity)
28
+ case 'email':
29
+ return addEmailField(entity)
30
+ case 'name':
31
+ return addNameField(entity)
32
+ case 'description':
33
+ return addDescriptionField(entity)
34
+ case 'status':
35
+ return addStatusField(entity)
36
+ case 'version':
37
+ return addVersionField(entity)
38
+ case 'owner':
39
+ return addOwnerField(entity)
40
+ case 'created':
41
+ return addCreatedAtField(entity)
42
+ // case 'created-by':
43
+ // return addCreatedByField(entity)
44
+ case 'updated':
45
+ return addUpdatedAtField(entity)
46
+ case 'updated-by':
47
+ return addUpdatedByField(entity)
48
+ case 'is-deleted':
49
+ return addIsDeletedField(entity)
50
+ case 'deleted':
51
+ return addDeletedAtField(entity)
52
+ case 'first-name':
53
+ return addFirstNameField(entity)
54
+ case 'last-name':
55
+ return addLastNameField(entity)
56
+ case 'phone':
57
+ return addPhoneField(entity)
58
+ case 'avatar-url':
59
+ return addAvatarUrlField(entity)
60
+ case 'public-unique-name':
61
+ return addPublicUniqueNameField(entity)
62
+ case 'price':
63
+ return addPriceField(entity)
64
+ case 'sku':
65
+ return addSkuField(entity)
66
+ case 'quantity':
67
+ return addQuantityField(entity)
68
+ case 'weight':
69
+ return addWeightField(entity)
70
+ case 'images':
71
+ return addImagesField(entity)
72
+ case 'session-id':
73
+ return addSessionIdField(entity)
74
+ case 'expires-at':
75
+ return addExpiresAtField(entity)
76
+ case 'unit-price':
77
+ return addUnitPriceField(entity)
78
+ default:
79
+ throw new Error(`Unsupported auto field: ${autoField}`)
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Adds the primary field (ID) to the given entity.
85
+ * If the ID field already exists, it returns the existing field.
86
+ * Otherwise, it creates a new ID field with a name based on the entity's name.
87
+ *
88
+ * Set properties:
89
+ * - type: 'string'
90
+ * - info.name: 'id'
91
+ * - primary: true
92
+ * - readOnly: true
93
+ * - schema.defaultValue: { type: 'function', value: 'uuid-v4' }
94
+ *
95
+ * @param entity The entity to which the ID field will be added.
96
+ * @returns The added primary field property.
97
+ */
98
+ export function addIdField(entity: DomainEntity, info: Partial<IThing> = {}): DomainElement {
99
+ const existing = findIdField(entity, true)
100
+ if (existing) {
101
+ // If the ID field already exists, return it.
102
+ return existing
103
+ }
104
+ const prop = entity.addProperty({
105
+ info: { name: `id`, ...info },
106
+ primary: true,
107
+ readOnly: true,
108
+ // I don't think we need to index the ID field..
109
+ // index: true,
110
+ type: 'string',
111
+ // @TODO: We should experiment with the random ID generation using the nano library.
112
+ schema: { defaultValue: { type: 'function', value: 'uuid-v4' } },
113
+ })
114
+ return prop
115
+ }
116
+
117
+ /**
118
+ * Adds an email field to the given entity.
119
+ * If the email field already exists, it returns the existing field.
120
+ * Otherwise, it creates a new email field with a semantic annotation.
121
+ * @param entity The entity to which the email field will be added.
122
+ * @returns The added email field property.
123
+ */
124
+ export function addEmailField(entity: DomainEntity, info: Partial<IThing> = {}): DomainElement {
125
+ const existing = findEmailField(entity, true)
126
+ if (existing) {
127
+ return existing
128
+ }
129
+ const prop = entity.addProperty({
130
+ info: { name: 'email', displayName: 'Email Address', ...info },
131
+ type: 'string',
132
+ required: true,
133
+ index: true,
134
+ semantics: [
135
+ createEmailSemantic({
136
+ requireVerification: true,
137
+ verificationMethod: 'email',
138
+ }),
139
+ ],
140
+ })
141
+ return prop
142
+ }
143
+
144
+ /**
145
+ * Adds a password field to the given entity.
146
+ * If the password field already exists, it returns the existing field.
147
+ * Otherwise, it creates a new password field with a semantic annotation.
148
+ * @param entity The entity to which the password field will be added.
149
+ * @param info Additional information for the password field.
150
+ * @returns The added password field property.
151
+ */
152
+ export function addPasswordField(entity: DomainEntity, info: Partial<IThing> = {}): DomainElement {
153
+ const existing = findPasswordField(entity, true)
154
+ if (existing) {
155
+ return existing
156
+ }
157
+ const prop = entity.addProperty({
158
+ info: { name: 'password', displayName: 'Password', ...info },
159
+ type: 'string',
160
+ required: true,
161
+ semantics: [
162
+ createPasswordSemantic({
163
+ requireSpecialChars: true,
164
+ requireNumbers: true,
165
+ requireUppercase: true,
166
+ requireLowercase: true,
167
+ minLength: 8,
168
+ }),
169
+ ],
170
+ })
171
+ return prop
172
+ }
173
+
174
+ /**
175
+ * Adds a version field to the given entity.
176
+ * @param entity The entity to which the version field will be added.
177
+ * The version field is used to track the version of the entity.
178
+ * @returns The added version field property.
179
+ */
180
+ export function addVersionField(entity: DomainEntity, info: Partial<IThing> = {}): DomainElement {
181
+ const existing = findVersionField(entity, true)
182
+ if (existing) {
183
+ // If the version field already exists, return it.
184
+ return existing
185
+ }
186
+ const prop = entity.addProperty({
187
+ info: { name: 'version', displayName: 'Version', ...info },
188
+ readOnly: true,
189
+ index: true,
190
+ type: 'number',
191
+ schema: { defaultValue: { type: 'function', value: 'incremental' }, minimum: 0 },
192
+ })
193
+ prop.addSemantic({
194
+ id: SemanticType.Version,
195
+ })
196
+ return prop
197
+ }
198
+
199
+ export function addNameField(entity: DomainEntity, info: Partial<IThing> = {}): DomainElement {
200
+ const existing = findNameField(entity, true)
201
+ if (existing) {
202
+ return existing
203
+ }
204
+ const prop = entity.addProperty({
205
+ info: { name: 'name', displayName: 'Name', ...info },
206
+ type: 'string',
207
+ })
208
+ prop.addSemantic({
209
+ id: SemanticType.Title,
210
+ })
211
+ return prop
212
+ }
213
+
214
+ /**
215
+ * Adds a description field to the specified entity.
216
+ * If a description field already exists, it returns the existing field.
217
+ * Otherwise, it creates a new description field with the specified information.
218
+ *
219
+ * Set properties:
220
+ * - type: 'string'
221
+ * - info.name: 'description'
222
+ * - info.displayName: 'Description'
223
+ *
224
+ * Set semantics:
225
+ * - `SemanticType.Description`
226
+ *
227
+ * @param entity The entity to which the description field will be added.
228
+ * @returns The added description field property.
229
+ */
230
+ export function addDescriptionField(entity: DomainEntity, info: Partial<IThing> = {}): DomainElement {
231
+ const existing = findDescriptionField(entity, true)
232
+ if (existing) {
233
+ return existing
234
+ }
235
+ const prop = entity.addProperty({
236
+ info: { name: 'description', displayName: 'Description', ...info },
237
+ type: 'string',
238
+ semantics: [createDescriptionSemantic()],
239
+ })
240
+ return prop
241
+ }
242
+
243
+ /**
244
+ * Adds a status field to the specified entity.
245
+ * If a status field already exists, it returns the existing field.
246
+ * Otherwise, it creates a new status field with the specified information.
247
+ *
248
+ * Set properties:
249
+ * - type: 'string'
250
+ * - info.name: 'status'
251
+ * - info.displayName: 'Status'
252
+ * - schema.enum: ['Active', 'Draft', 'Archived']
253
+ * - schema.defaultValue: { type: 'literal', value: 'Draft' }
254
+ *
255
+ * Set semantics:
256
+ * - `SemanticType.Status`
257
+ *
258
+ * @param entity The entity to which the status field will be added.
259
+ * @param info Additional information about the field.
260
+ * @returns The added status field property.
261
+ */
262
+ export function addStatusField(entity: DomainEntity, info: Partial<IThing> = {}): DomainElement {
263
+ const existing = findStatusField(entity, true)
264
+ if (existing) {
265
+ return existing
266
+ }
267
+ const prop = entity.addProperty({
268
+ info: { name: 'status', displayName: 'Status', ...info },
269
+ type: 'string',
270
+ schema: {
271
+ enum: ['Active', 'Draft', 'Archived'],
272
+ defaultValue: { type: 'literal', value: 'Draft' },
273
+ },
274
+ })
275
+ prop.addSemantic({
276
+ id: SemanticType.Status,
277
+ })
278
+ return prop
279
+ }
280
+
281
+ /**
282
+ * Adds an owner field to the specified entity.
283
+ * If an owner field already exists, it returns the existing field.
284
+ * Otherwise, it creates a new owner field with the specified information.
285
+ *
286
+ * Set properties:
287
+ * - type: 'string'
288
+ * - info.name: 'owner'
289
+ * - info.displayName: 'Owner'
290
+ * - required: true
291
+ *
292
+ * Set semantics:
293
+ * - `SemanticType.ResourceOwnerIdentifier`
294
+ *
295
+ * @param entity The entity to which the owner field will be added.
296
+ * @param info Additional information about the field.
297
+ * @returns The added owner field property.
298
+ */
299
+ export function addOwnerField(entity: DomainEntity, info: Partial<IThing> = {}): DomainAssociation {
300
+ const existing = findOwnerField(entity, true)
301
+ if (existing) {
302
+ return existing
303
+ }
304
+ const prop = entity.addAssociation(
305
+ {},
306
+ {
307
+ info: { name: 'owner', displayName: 'Owner', ...info },
308
+ required: true,
309
+ }
310
+ )
311
+ prop.addSemantic({
312
+ id: SemanticType.ResourceOwnerIdentifier,
313
+ })
314
+ return prop
315
+ }
316
+
317
+ // This is owner with different name. Use that one instead.
318
+ // function addCreatedByField(entity: DomainEntity): DomainElement {
319
+ // const existing = findCreatedByField(entity, true)
320
+ // if (existing) {
321
+ // return existing
322
+ // }
323
+ // const prop = entity.addProperty({
324
+ // info: { name: 'created_by', displayName: 'Created By' },
325
+ // type: 'string',
326
+ // readOnly: true,
327
+ // index: true,
328
+ // })
329
+ // prop.addSemantic({
330
+ // id: SemanticType.CreatedTimestamp,
331
+ // })
332
+ // return prop
333
+ // }
334
+
335
+ /**
336
+ * Adds an updated by field to the specified entity as an association to the User entity.
337
+ * If an updated by field already exists, it returns the existing field.
338
+ * Otherwise, it creates a new updated by field with the specified information.
339
+ *
340
+ * Set properties:
341
+ * - info.name: 'updated_by'
342
+ * - info.displayName: 'Updated By'
343
+ * - required: true
344
+ *
345
+ * Set semantics:
346
+ * - `SemanticType.ResourceOwnerIdentifier`
347
+ *
348
+ * @param entity The entity to which the updated by field will be added.
349
+ * @param info Additional information about the field.
350
+ * @returns The added updated by field property.
351
+ */
352
+ export function addUpdatedByField(entity: DomainEntity, info: Partial<IThing> = {}): DomainAssociation {
353
+ const existing = findUpdatedByField(entity, true)
354
+ if (existing) {
355
+ return existing
356
+ }
357
+ const prop = entity.addAssociation(
358
+ {},
359
+ {
360
+ info: { name: 'updated_by', displayName: 'Updated By', ...info },
361
+ required: true,
362
+ }
363
+ )
364
+ prop.addSemantic({
365
+ id: SemanticType.ResourceOwnerIdentifier,
366
+ })
367
+ return prop
368
+ }
369
+
370
+ /**
371
+ * Adds a created at field to the specified entity.
372
+ * If a created at field already exists, it returns the existing field.
373
+ * Otherwise, it creates a new created at field with the specified information.
374
+ *
375
+ * Set properties:
376
+ * - type: 'datetime'
377
+ * - info.name: 'created_at'
378
+ * - info.displayName: 'Created At'
379
+ * - readOnly: true
380
+ * - index: true
381
+ * - schema.defaultValue: { type: 'function', value: 'now' }
382
+ *
383
+ * Set semantics:
384
+ * - `CreatedTimestamp`
385
+ *
386
+ * @param entity The entity to which the created at field will be added.
387
+ * @param info Additional information about the field.
388
+ * @returns The created created at field.
389
+ */
390
+ export function addCreatedAtField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
391
+ const existing = findCreatedAtField(entity, true)
392
+ if (existing) {
393
+ // If the created_at field already exists, return it.
394
+ return existing
395
+ }
396
+ const prop = entity.addProperty({
397
+ info: { name: 'created_at', displayName: 'Created At', ...info },
398
+ readOnly: true,
399
+ index: true,
400
+ type: 'datetime',
401
+ schema: { defaultValue: { type: 'function', value: 'now' } },
402
+ })
403
+ prop.addSemantic({
404
+ id: SemanticType.CreatedTimestamp,
405
+ })
406
+ return prop
407
+ }
408
+
409
+ /**
410
+ * Adds an updated at field to the specified entity.
411
+ * If an updated at field already exists, it returns the existing field.
412
+ * Otherwise, it creates a new updated at field with the specified information.
413
+ *
414
+ * Set properties:
415
+ * - type: 'datetime'
416
+ * - info.name: 'updated_at'
417
+ * - info.displayName: 'Updated At'
418
+ * - readOnly: true
419
+ * - index: true
420
+ * - schema.defaultValue: { type: 'function', value: 'now' }
421
+ *
422
+ * Set semantics:
423
+ * - `UpdatedTimestamp`
424
+ *
425
+ * @param entity The entity to which the updated at field will be added.
426
+ * @param info Additional information about the field.
427
+ * @returns The created updated at field.
428
+ */
429
+ export function addUpdatedAtField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
430
+ const existing = findUpdatedAtField(entity, true)
431
+ if (existing) {
432
+ // If the updated_at field already exists, return it.
433
+ return existing
434
+ }
435
+ const prop = entity.addProperty({
436
+ info: { name: 'updated_at', displayName: 'Updated At', ...info },
437
+ readOnly: true,
438
+ index: true,
439
+ type: 'datetime',
440
+ schema: { defaultValue: { type: 'function', value: 'now' } },
441
+ })
442
+ prop.addSemantic({
443
+ id: SemanticType.UpdatedTimestamp,
444
+ })
445
+ return prop
446
+ }
447
+
448
+ /**
449
+ * Adds a deleted at field to the specified entity.
450
+ * If a deleted at field already exists, it returns the existing field.
451
+ * Otherwise, it creates a new deleted at field with the specified information.
452
+ *
453
+ * Set properties:
454
+ * - required: true
455
+ * - type: 'datetime'
456
+ * - info.name: 'deleted_at'
457
+ * - info.displayName: 'Deleted At'
458
+ * - readOnly: true
459
+ * - index: true
460
+ * - schema.defaultValue: { type: 'function', value: 'now' }
461
+ *
462
+ * Set semantics:
463
+ * - `SemanticType.DeletedTimestamp`
464
+ *
465
+ * @param entity The entity to which the deleted_at field will be added.
466
+ * @param info Additional information about the field.
467
+ * @returns The added deleted at field property.
468
+ */
469
+ export function addDeletedAtField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
470
+ const existing = findDeletedAtField(entity, true)
471
+ if (existing) {
472
+ // If the deleted_at field already exists, return it.
473
+ return existing
474
+ }
475
+ const prop = entity.addProperty({
476
+ info: { name: 'deleted_at', displayName: 'Deleted At', ...info },
477
+ readOnly: true,
478
+ index: true,
479
+ type: 'datetime',
480
+ schema: { defaultValue: { type: 'function', value: 'now' } },
481
+ })
482
+ prop.addSemantic({
483
+ id: SemanticType.DeletedTimestamp,
484
+ })
485
+ return prop
486
+ }
487
+
488
+ /**
489
+ * Adds an is_deleted field to the specified entity.
490
+ * If an is_deleted field already exists, it returns the existing field.
491
+ * Otherwise, it creates a new is_deleted field with the specified information.
492
+ *
493
+ * Set properties:
494
+ * - type: 'boolean'
495
+ * - info.name: 'is_deleted'
496
+ * - info.displayName: 'Is Deleted'
497
+ * - readOnly: true
498
+ * - index: true
499
+ * - schema.defaultValue: { type: 'literal', value: 'false' }
500
+ *
501
+ * Set semantics:
502
+ * - `SemanticType.DeletedFlag`
503
+ *
504
+ * @param entity The entity to which the is_deleted field will be added.
505
+ * @param info Additional information about the field.
506
+ * @returns The added is_deleted field property.
507
+ */
508
+ export function addIsDeletedField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
509
+ const existing = findIsDeletedField(entity, true)
510
+ if (existing) {
511
+ // If the is_deleted field already exists, return it.
512
+ return existing
513
+ }
514
+ const prop = entity.addProperty({
515
+ info: { name: 'is_deleted', displayName: 'Is Deleted', ...info },
516
+ readOnly: true,
517
+ index: true,
518
+ type: 'boolean',
519
+ schema: { defaultValue: { type: 'literal', value: 'false' } },
520
+ })
521
+ prop.addSemantic({
522
+ id: SemanticType.DeletedFlag,
523
+ })
524
+ return prop
525
+ }
526
+
527
+ /**
528
+ * Adds a first name field to the specified entity.
529
+ * If a first name field already exists, it returns the existing field.
530
+ * Otherwise, it creates a new first name field with the specified information.
531
+ *
532
+ * Set properties:
533
+ * - type: 'string'
534
+ * - info.name: 'first_name'
535
+ * - info.displayName: 'First Name'
536
+ *
537
+ * @param entity The entity to which the first name field will be added.
538
+ * @param info Additional information about the field.
539
+ * @returns The created first name field.
540
+ */
541
+ export function addFirstNameField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
542
+ const existing = findFirstNameField(entity)
543
+ if (existing) {
544
+ return existing
545
+ }
546
+ const prop = entity.addProperty({
547
+ info: { name: 'first_name', displayName: 'First Name', ...info },
548
+ type: 'string',
549
+ })
550
+ return prop
551
+ }
552
+
553
+ /**
554
+ * Adds a first name field to the specified entity.
555
+ * If a first name field already exists, it returns the existing field.
556
+ * Otherwise, it creates a new first name field with the specified information.
557
+ *
558
+ * Set properties:
559
+ * - type: 'string'
560
+ * - info.name: 'first_name'
561
+ * - info.displayName: 'First Name'
562
+ *
563
+ * @param entity The entity to which the first name field will be added.
564
+ * @param info Additional information about the field.
565
+ * @returns The created first name field.
566
+ */
567
+ export function addLastNameField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
568
+ const existing = findLastNameField(entity)
569
+ if (existing) {
570
+ return existing
571
+ }
572
+ const prop = entity.addProperty({
573
+ info: { name: 'last_name', displayName: 'Last Name', ...info },
574
+ type: 'string',
575
+ })
576
+ return prop
577
+ }
578
+
579
+ /**
580
+ * Adds a phone field to the specified entity.
581
+ * If a phone field already exists, it returns the existing field.
582
+ * Otherwise, it creates a new phone field with the specified information.
583
+ *
584
+ * Set properties:
585
+ * - type: 'string'
586
+ * - info.name: 'phone'
587
+ * - info.displayName: 'Phone Number'
588
+ * - index: true (to search by phone number)
589
+ *
590
+ * Set semantics:
591
+ * - id: SemanticType.Phone
592
+ *
593
+ * @param entity The entity to which the phone field will be added.
594
+ * @param info Additional information about the field.
595
+ * @returns The created phone field.
596
+ */
597
+ export function addPhoneField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
598
+ const existing = findPhoneField(entity, true)
599
+ if (existing) {
600
+ return existing
601
+ }
602
+ const prop = entity.addProperty({
603
+ info: { name: 'phone', displayName: 'Phone Number', ...info },
604
+ type: 'string',
605
+ index: true,
606
+ semantics: [
607
+ createPhoneSemantic({
608
+ format: 'international',
609
+ requireCountryCode: true,
610
+ requireVerification: true,
611
+ verificationMethod: 'sms',
612
+ }),
613
+ ],
614
+ })
615
+ return prop
616
+ }
617
+
618
+ /**
619
+ * Adds an avatar URL field to the specified entity.
620
+ * If an avatar URL field already exists, it returns the existing field.
621
+ * Otherwise, it creates a new avatar URL field with the specified information.
622
+ *
623
+ * Set properties:
624
+ * - type: 'string'
625
+ * - info.name: 'avatar_url'
626
+ * - info.displayName: 'Avatar URL'
627
+ *
628
+ * Set semantics:
629
+ * - id: SemanticType.ImageURL
630
+ *
631
+ * @param entity The entity to which the avatar URL field will be added.
632
+ * @param info Additional information about the field.
633
+ * @returns The created avatar URL field.
634
+ */
635
+ export function addAvatarUrlField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
636
+ const existing = findAvatarUrlField(entity, true)
637
+ if (existing) {
638
+ return existing
639
+ }
640
+ const prop = entity.addProperty({
641
+ info: { name: 'avatar_url', displayName: 'Avatar URL', ...info },
642
+ type: 'string',
643
+ semantics: [{ id: SemanticType.ImageURL }],
644
+ })
645
+ return prop
646
+ }
647
+
648
+ /**
649
+ * Adds a public unique name (slug) field to the specified entity.
650
+ * If a public unique name field already exists, it returns the existing field.
651
+ * Otherwise, it creates a new public unique name field with the specified information.
652
+ *
653
+ * Set properties:
654
+ * - type: 'string'
655
+ * - info.name: 'slug'
656
+ * - info.displayName: 'Public Unique Name'
657
+ * - index: true
658
+ * - required: true
659
+ * - unique: true
660
+ *
661
+ * Set semantics:
662
+ * - id: SemanticType.PublicUniqueName
663
+ *
664
+ * @param entity The entity to which the public unique name field will be added.
665
+ * @param info Additional information about the field.
666
+ * @returns The created public unique name field.
667
+ */
668
+ export function addPublicUniqueNameField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
669
+ const existing = findPublicUniqueNameField(entity, true)
670
+ if (existing) {
671
+ return existing
672
+ }
673
+ const prop = entity.addProperty({
674
+ info: { name: 'slug', displayName: 'Public Unique Name', ...info },
675
+ type: 'string',
676
+ index: true,
677
+ required: true,
678
+ unique: true,
679
+ semantics: [createPublicUniqueNameSemantic(DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG)],
680
+ })
681
+ return prop
682
+ }
683
+
684
+ /**
685
+ * Adds a SKU field to the specified entity.
686
+ * If a SKU field already exists, it returns the existing field.
687
+ * Otherwise, it creates a new SKU field with semantic annotation.
688
+ *
689
+ * Set properties:
690
+ * - type: 'string'
691
+ * - info.name: 'sku'
692
+ * - info.displayName: 'SKU'
693
+ * - required: true
694
+ * - unique: true
695
+ *
696
+ * @param entity The entity to which the SKU field will be added.
697
+ * @param info Additional information about the field.
698
+ * @returns The created SKU field.
699
+ */
700
+ export function addSkuField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
701
+ const existing = findSkuField(entity, true)
702
+ if (existing) {
703
+ return existing
704
+ }
705
+ const prop = entity.addProperty({
706
+ info: { name: 'sku', displayName: 'SKU', ...info },
707
+ type: 'string',
708
+ required: true,
709
+ unique: true,
710
+ semantics: [{ ...SKU_PRESETS.PRODUCT_STANDARD }],
711
+ })
712
+ return prop
713
+ }
714
+
715
+ /**
716
+ * Adds a price field to the specified entity with currency semantic.
717
+ * If a price field already exists, it returns the existing field.
718
+ * Otherwise, it creates a new price field with proper currency handling.
719
+ *
720
+ * Set properties:
721
+ * - type: 'number'
722
+ * - info.name: 'price'
723
+ * - info.displayName: 'Price'
724
+ * - required: true
725
+ * - schema.maximum: 0
726
+ *
727
+ * @param entity The entity to which the price field will be added.
728
+ * @param info Additional information about the field.
729
+ * @returns The created price field.
730
+ */
731
+ export function addPriceField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
732
+ const existing = findPriceField(entity, true)
733
+ if (existing) {
734
+ return existing
735
+ }
736
+ const prop = entity.addProperty({
737
+ info: { name: 'price', displayName: 'Price', ...info },
738
+ type: 'number',
739
+ required: true,
740
+ schema: {
741
+ maximum: 0,
742
+ },
743
+ bindings: [
744
+ {
745
+ type: 'web',
746
+ schema: {
747
+ dataType: 'float',
748
+ },
749
+ },
750
+ ],
751
+ semantics: [
752
+ createCurrencySemantic({
753
+ storageFormat: 'integer_cents',
754
+ defaultCurrency: 'USD',
755
+ decimalPlaces: 2,
756
+ allowNegative: false,
757
+ validateCurrencyCode: true,
758
+ }),
759
+ ],
760
+ })
761
+ return prop
762
+ }
763
+
764
+ /**
765
+ * Adds a quantity field to the specified entity.
766
+ * If a quantity field already exists, it returns the existing field.
767
+ * Otherwise, it creates a new quantity field with validation.
768
+ *
769
+ * Set properties:
770
+ * - type: 'number'
771
+ * - info.name: 'quantity'
772
+ * - info.displayName: 'Quantity'
773
+ * - required: true
774
+ * - schema.minimum: 0
775
+ *
776
+ * @param entity The entity to which the quantity field will be added.
777
+ * @param info Additional information about the field.
778
+ * @returns The created quantity field.
779
+ */
780
+ export function addQuantityField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
781
+ const existing = findQuantityField(entity, true)
782
+ if (existing) {
783
+ return existing
784
+ }
785
+ const prop = entity.addProperty({
786
+ info: { name: 'quantity', displayName: 'Quantity', ...info },
787
+ type: 'number',
788
+ required: true,
789
+ schema: {
790
+ minimum: 0,
791
+ },
792
+ })
793
+ return prop
794
+ }
795
+
796
+ /**
797
+ * Adds a weight field to the specified entity.
798
+ * If a weight field already exists, it returns the existing field.
799
+ * Otherwise, it creates a new weight field.
800
+ *
801
+ * Set properties:
802
+ * - type: 'number'
803
+ * - info.name: 'weight'
804
+ * - info.displayName: 'Weight'
805
+ *
806
+ * @param entity The entity to which the weight field will be added.
807
+ * @param info Additional information about the field.
808
+ * @returns The created weight field.
809
+ */
810
+ export function addWeightField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
811
+ const existing = findWeightField(entity, true)
812
+ if (existing) {
813
+ return existing
814
+ }
815
+ const prop = entity.addProperty({
816
+ info: { name: 'weight', displayName: 'Weight', ...info },
817
+ type: 'number',
818
+ })
819
+ return prop
820
+ }
821
+
822
+ /**
823
+ * Adds an images field to the specified entity for multiple image URLs.
824
+ * If an images field already exists, it returns the existing field.
825
+ * Otherwise, it creates a new images field with ImageURL semantic.
826
+ *
827
+ * Set properties:
828
+ * - type: 'string'
829
+ * - info.name: 'images'
830
+ * - info.displayName: 'Images'
831
+ * - multiple: true
832
+ *
833
+ * @param entity The entity to which the images field will be added.
834
+ * @param info Additional information about the field.
835
+ * @returns The created images field.
836
+ */
837
+ export function addImagesField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
838
+ const existing = findImagesField(entity, true)
839
+ if (existing) {
840
+ return existing
841
+ }
842
+ const prop = entity.addProperty({
843
+ info: { name: 'images', displayName: 'Images', ...info },
844
+ type: 'string',
845
+ multiple: true,
846
+ semantics: [{ id: SemanticType.ImageURL }],
847
+ })
848
+ return prop
849
+ }
850
+
851
+ /**
852
+ * Adds a status field with custom enum values to the specified entity.
853
+ * If a status field already exists, it returns the existing field.
854
+ * Otherwise, it creates a new status field with provided enum values.
855
+ *
856
+ * Set properties:
857
+ * - type: 'string'
858
+ * - info.name: 'status'
859
+ * - info.displayName: 'Status'
860
+ * - required: true
861
+ * - schema.enum: provided enum values
862
+ * - schema.defaultValue: first enum value
863
+ *
864
+ * @param entity The entity to which the status field will be added.
865
+ * @param enumValues Array of status values.
866
+ * @param info Additional information about the field.
867
+ * @returns The created status field.
868
+ */
869
+ export function addCustomStatusField(
870
+ entity: DomainEntity,
871
+ enumValues: string[],
872
+ info: Partial<IThing> = {}
873
+ ): DomainProperty {
874
+ const existing = findStatusField(entity, true)
875
+ if (existing) {
876
+ return existing
877
+ }
878
+ const prop = entity.addProperty({
879
+ info: { name: 'status', displayName: 'Status', ...info },
880
+ type: 'string',
881
+ required: true,
882
+ semantics: [createStatusSemantic()],
883
+ schema: {
884
+ enum: enumValues,
885
+ defaultValue: {
886
+ type: 'literal',
887
+ value: enumValues[0],
888
+ },
889
+ },
890
+ })
891
+ return prop
892
+ }
893
+
894
+ /**
895
+ * Adds a boolean field to the specified entity.
896
+ * If the field already exists, it returns the existing field.
897
+ * Otherwise, it creates a new boolean field with default value.
898
+ *
899
+ * Set properties:
900
+ * - type: 'boolean'
901
+ * - required: true
902
+ * - schema.defaultValue: provided default or 'false'
903
+ *
904
+ * @param entity The entity to which the boolean field will be added.
905
+ * @param fieldName Name of the boolean field.
906
+ * @param defaultValue Default boolean value.
907
+ * @param info Additional information about the field.
908
+ * @returns The created boolean field.
909
+ */
910
+ export function addBooleanField(
911
+ entity: DomainEntity,
912
+ fieldName: string,
913
+ defaultValue = 'false',
914
+ info: Partial<IThing> = {}
915
+ ): DomainProperty {
916
+ const existing = findBooleanField(entity, fieldName, true)
917
+ if (existing) {
918
+ return existing
919
+ }
920
+ const prop = entity.addProperty({
921
+ info: { name: fieldName, ...info },
922
+ type: 'boolean',
923
+ required: true,
924
+ schema: {
925
+ defaultValue: {
926
+ type: 'literal',
927
+ value: defaultValue,
928
+ },
929
+ },
930
+ })
931
+ return prop
932
+ }
933
+
934
+ /**
935
+ * Adds a session ID field to the specified entity.
936
+ * If a session ID field already exists, it returns the existing field.
937
+ * Otherwise, it creates a new session ID field.
938
+ *
939
+ * Set properties:
940
+ * - type: 'string'
941
+ * - info.name: 'sessionId'
942
+ * - info.displayName: 'Session ID'
943
+ *
944
+ * @param entity The entity to which the session ID field will be added.
945
+ * @param info Additional information about the field.
946
+ * @returns The created session ID field.
947
+ */
948
+ export function addSessionIdField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
949
+ const existing = findSessionIdField(entity, true)
950
+ if (existing) {
951
+ return existing
952
+ }
953
+ const prop = entity.addProperty({
954
+ info: { name: 'sessionId', displayName: 'Session ID', ...info },
955
+ type: 'string',
956
+ })
957
+ return prop
958
+ }
959
+
960
+ /**
961
+ * Adds an expiration datetime field to the specified entity.
962
+ * If an expiration field already exists, it returns the existing field.
963
+ * Otherwise, it creates a new expiration field.
964
+ *
965
+ * Set properties:
966
+ * - type: 'datetime'
967
+ * - info.name: 'expiresAt'
968
+ * - info.displayName: 'Expires At'
969
+ *
970
+ * @param entity The entity to which the expiration field will be added.
971
+ * @param info Additional information about the field.
972
+ * @returns The created expiration field.
973
+ */
974
+ export function addExpiresAtField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
975
+ const existing = findExpiresAtField(entity, true)
976
+ if (existing) {
977
+ return existing
978
+ }
979
+ const prop = entity.addProperty({
980
+ info: { name: 'expiresAt', displayName: 'Expires At', ...info },
981
+ type: 'datetime',
982
+ })
983
+ return prop
984
+ }
985
+
986
+ /**
987
+ * Adds a unit price field to the specified entity with currency semantic.
988
+ * If a unit price field already exists, it returns the existing field.
989
+ * Otherwise, it creates a new unit price field with proper currency handling.
990
+ *
991
+ * Set properties:
992
+ * - type: 'number'
993
+ * - info.name: 'unitPrice'
994
+ * - info.displayName: 'Unit Price'
995
+ * - required: true
996
+ *
997
+ * @param entity The entity to which the unit price field will be added.
998
+ * @param info Additional information about the field.
999
+ * @returns The created unit price field.
1000
+ */
1001
+ export function addUnitPriceField(entity: DomainEntity, info: Partial<IThing> = {}): DomainProperty {
1002
+ const existing = findUnitPriceField(entity, true)
1003
+ if (existing) {
1004
+ return existing
1005
+ }
1006
+ const prop = entity.addProperty({
1007
+ info: { name: 'unitPrice', displayName: 'Unit Price', ...info },
1008
+ type: 'number',
1009
+ required: true,
1010
+ semantics: [
1011
+ createCurrencySemantic({
1012
+ storageFormat: 'integer_cents',
1013
+ defaultCurrency: 'USD',
1014
+ decimalPlaces: 2,
1015
+ allowNegative: false,
1016
+ }),
1017
+ ],
1018
+ })
1019
+ return prop
1020
+ }
1021
+
1022
+ /**
1023
+ * Adds a currency amount field (subtotal, tax, shipping, etc.).
1024
+ */
1025
+ export function addCurrencyAmountField(
1026
+ entity: DomainEntity,
1027
+ fieldName: string,
1028
+ displayName: string,
1029
+ info: Partial<IThing> = {}
1030
+ ): DomainProperty {
1031
+ return entity.addProperty({
1032
+ info: {
1033
+ name: fieldName,
1034
+ displayName,
1035
+ ...info,
1036
+ },
1037
+ type: 'number',
1038
+ required: true,
1039
+ semantics: [
1040
+ createCurrencySemantic({
1041
+ storageFormat: 'integer_cents',
1042
+ defaultCurrency: 'USD',
1043
+ decimalPlaces: 2,
1044
+ allowNegative: false,
1045
+ }),
1046
+ ],
1047
+ })
1048
+ }
1049
+
1050
+ /**
1051
+ * Finds a field in the given entity that matches the specified predicate.
1052
+ * @param entity The entity to search within.
1053
+ * @param predicate A function that defines the search criteria.
1054
+ * @param direct Whether to search only the direct properties of the entity.
1055
+ * @returns The found field property or undefined.
1056
+ */
1057
+ function findPropertyField(
1058
+ entity: DomainEntity,
1059
+ predicate: (property: DomainProperty) => boolean,
1060
+ direct = false
1061
+ ): DomainProperty | undefined {
1062
+ for (const property of entity.properties) {
1063
+ if (predicate(property)) {
1064
+ return property
1065
+ }
1066
+ }
1067
+ if (direct) {
1068
+ return undefined
1069
+ }
1070
+ for (const parent of entity.listParents()) {
1071
+ // we can't call a recursive function here because it would scan vertically through the hierarchy
1072
+ // and not horizontally through all parents. We need to check each **direct** parent separately,
1073
+ // and then move up.
1074
+ for (const property of parent.properties) {
1075
+ if (predicate(property)) {
1076
+ return property
1077
+ }
1078
+ }
1079
+ }
1080
+ return undefined
1081
+ }
1082
+
1083
+ /**
1084
+ * Finds a field in the given entity that matches the specified predicate.
1085
+ * @param entity The entity to search within.
1086
+ * @param predicate A function that defines the search criteria.
1087
+ * @param direct Whether to search only the direct properties of the entity.
1088
+ * @returns The found field association or undefined.
1089
+ */
1090
+ function findAssociationField(
1091
+ entity: DomainEntity,
1092
+ predicate: (property: DomainAssociation) => boolean,
1093
+ direct = false
1094
+ ): DomainAssociation | undefined {
1095
+ for (const assoc of entity.associations) {
1096
+ if (predicate(assoc)) {
1097
+ return assoc
1098
+ }
1099
+ }
1100
+ if (direct) {
1101
+ return undefined
1102
+ }
1103
+ for (const parent of entity.listParents()) {
1104
+ // we can't call a recursive function here because it would scan vertically through the hierarchy
1105
+ // and not horizontally through all parents. We need to check each **direct** parent separately,
1106
+ // and then move up.
1107
+ for (const assoc of parent.associations) {
1108
+ if (predicate(assoc)) {
1109
+ return assoc
1110
+ }
1111
+ }
1112
+ }
1113
+ return undefined
1114
+ }
1115
+
1116
+ /**
1117
+ * Searches for the ID field in the given entity.
1118
+ * @param entity The entity to search for the ID field.
1119
+ * @param direct Whether to search only the direct properties of the entity.
1120
+ * @returns The found ID field property or undefined.
1121
+ */
1122
+ function findIdField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1123
+ if (!direct) {
1124
+ // Find the primary key property in the entity, which can be inherited or defined on the entity itself.
1125
+ return entity.primaryKey()
1126
+ }
1127
+ for (const property of entity.properties) {
1128
+ if (property.primary) {
1129
+ return property
1130
+ }
1131
+ }
1132
+ }
1133
+
1134
+ /**
1135
+ * Searches for the email field in the given entity.
1136
+ * @param entity The entity to search for the email field.
1137
+ * @param direct Whether to search only the direct properties of the entity.
1138
+ * @returns The found email field property or undefined.
1139
+ */
1140
+ function findEmailField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1141
+ return findPropertyField(entity, (property) => property.hasSemantic(SemanticType.Email), direct)
1142
+ }
1143
+
1144
+ /**
1145
+ * Searches for the password field in the given entity.
1146
+ * @param entity The entity to search for the password field.
1147
+ * @param direct Whether to search only the direct properties of the entity.
1148
+ * @returns The found password field property or undefined.
1149
+ */
1150
+ function findPasswordField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1151
+ return findPropertyField(entity, (property) => property.hasSemantic(SemanticType.Password), direct)
1152
+ }
1153
+
1154
+ function findVersionField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1155
+ return findPropertyField(
1156
+ entity,
1157
+ (property) => {
1158
+ return property.hasSemantic(SemanticType.Version)
1159
+ },
1160
+ direct
1161
+ )
1162
+ }
1163
+
1164
+ function findNameField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1165
+ return findPropertyField(entity, (property) => property.hasSemantic(SemanticType.Title), direct)
1166
+ }
1167
+
1168
+ function findDescriptionField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1169
+ return findPropertyField(entity, (property) => property.hasSemantic(SemanticType.Description), direct)
1170
+ }
1171
+
1172
+ function findStatusField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1173
+ return findPropertyField(entity, (property) => property.hasSemantic(SemanticType.Status), direct)
1174
+ }
1175
+
1176
+ function findOwnerField(entity: DomainEntity, direct = false): DomainAssociation | undefined {
1177
+ return findAssociationField(
1178
+ entity,
1179
+ (association) =>
1180
+ association.hasSemantic(SemanticType.ResourceOwnerIdentifier) &&
1181
+ ['owner', 'author'].includes(association.info.name || ''),
1182
+ direct
1183
+ )
1184
+ }
1185
+
1186
+ // function findCreatedByField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1187
+ // return findField(entity, (property) => property.hasSemantic(SemanticType.CreatedTimestamp), direct)
1188
+ // }
1189
+
1190
+ function findUpdatedByField(entity: DomainEntity, direct = false): DomainAssociation | undefined {
1191
+ return findAssociationField(
1192
+ entity,
1193
+ (association) =>
1194
+ association.hasSemantic(SemanticType.ResourceOwnerIdentifier) &&
1195
+ ['updated_by', 'updatedBy'].includes(association.info.name || ''),
1196
+ direct
1197
+ )
1198
+ }
1199
+
1200
+ function findCreatedAtField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1201
+ return findPropertyField(
1202
+ entity,
1203
+ (property) => {
1204
+ return property.hasSemantic(SemanticType.CreatedTimestamp)
1205
+ },
1206
+ direct
1207
+ )
1208
+ }
1209
+
1210
+ function findUpdatedAtField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1211
+ return findPropertyField(
1212
+ entity,
1213
+ (property) => {
1214
+ return property.hasSemantic(SemanticType.UpdatedTimestamp)
1215
+ },
1216
+ direct
1217
+ )
1218
+ }
1219
+
1220
+ function findDeletedAtField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1221
+ return findPropertyField(
1222
+ entity,
1223
+ (property) => {
1224
+ return property.hasSemantic(SemanticType.DeletedTimestamp)
1225
+ },
1226
+ direct
1227
+ )
1228
+ }
1229
+
1230
+ function findIsDeletedField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1231
+ return findPropertyField(entity, (property) => property.hasSemantic(SemanticType.DeletedFlag), direct)
1232
+ }
1233
+
1234
+ export function addRecommendedFields(entity: DomainEntity): DomainElement[] {
1235
+ const fields: DomainElement[] = []
1236
+ if (!findIdField(entity)) {
1237
+ fields.push(addIdField(entity))
1238
+ }
1239
+ // The version field is not always recommended, so we don't add it by default.
1240
+ // if (!findVersionField(entity)) {
1241
+ // fields.push(addVersionField(entity))
1242
+ // }
1243
+ if (!findCreatedAtField(entity)) {
1244
+ fields.push(addCreatedAtField(entity))
1245
+ }
1246
+ // if (!findCreatedByField(entity)) {
1247
+ // fields.push(addCreatedByField(entity))
1248
+ // }
1249
+ if (!findUpdatedAtField(entity)) {
1250
+ fields.push(addUpdatedAtField(entity))
1251
+ }
1252
+ if (!findUpdatedByField(entity)) {
1253
+ fields.push(addUpdatedByField(entity))
1254
+ }
1255
+ if (!findDeletedAtField(entity)) {
1256
+ fields.push(addDeletedAtField(entity))
1257
+ }
1258
+ if (!findIsDeletedField(entity)) {
1259
+ fields.push(addIsDeletedField(entity))
1260
+ }
1261
+ return fields
1262
+ }
1263
+
1264
+ function findFirstNameField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1265
+ return findPropertyField(entity, (property) => ['first_name', 'firstName'].includes(property.info.name || ''), direct)
1266
+ }
1267
+
1268
+ function findLastNameField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1269
+ return findPropertyField(entity, (property) => ['last_name', 'lastName'].includes(property.info.name || ''), direct)
1270
+ }
1271
+
1272
+ // Avatar URL has the `SemanticType.ImageURL` semantic, but it can be used for any image URL.
1273
+ // It is not a specific semantic, but it is a common field in many entities.
1274
+ function findAvatarUrlField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1275
+ return findPropertyField(entity, (property) => ['avatar_url', 'avatarUrl'].includes(property.info.name || ''), direct)
1276
+ }
1277
+
1278
+ function findPhoneField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1279
+ return findPropertyField(entity, (property) => property.hasSemantic(SemanticType.Phone), direct)
1280
+ }
1281
+
1282
+ function findPublicUniqueNameField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1283
+ return findPropertyField(entity, (property) => property.hasSemantic(SemanticType.PublicUniqueName), direct)
1284
+ }
1285
+
1286
+ function findSkuField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1287
+ return findPropertyField(entity, (property) => property.hasSemantic(SemanticType.SKU), direct)
1288
+ }
1289
+
1290
+ function findPriceField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1291
+ return findPropertyField(
1292
+ entity,
1293
+ (property) =>
1294
+ ['price', 'unitPrice', 'totalPrice'].includes(property.info.name || '') &&
1295
+ property.hasSemantic(SemanticType.Currency),
1296
+ direct
1297
+ )
1298
+ }
1299
+
1300
+ function findQuantityField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1301
+ return findPropertyField(
1302
+ entity,
1303
+ (property) => ['quantity', 'stock', 'amount'].includes(property.info.name || '') && property.type === 'number',
1304
+ direct
1305
+ )
1306
+ }
1307
+
1308
+ function findWeightField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1309
+ return findPropertyField(entity, (property) => property.info.name === 'weight' && property.type === 'number', direct)
1310
+ }
1311
+
1312
+ function findImagesField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1313
+ return findPropertyField(
1314
+ entity,
1315
+ (property) => property.info.name === 'images' && property.hasSemantic(SemanticType.ImageURL),
1316
+ direct
1317
+ )
1318
+ }
1319
+
1320
+ function findBooleanField(entity: DomainEntity, fieldName: string, direct = false): DomainProperty | undefined {
1321
+ return findPropertyField(
1322
+ entity,
1323
+ (property) => property.info.name === fieldName && property.type === 'boolean',
1324
+ direct
1325
+ )
1326
+ }
1327
+
1328
+ function findSessionIdField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1329
+ return findPropertyField(entity, (property) => property.info.name === 'sessionId', direct)
1330
+ }
1331
+
1332
+ function findUnitPriceField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1333
+ return findPropertyField(
1334
+ entity,
1335
+ (property) => property.info.name === 'unitPrice' && property.hasSemantic(SemanticType.Currency),
1336
+ direct
1337
+ )
1338
+ }
1339
+
1340
+ function findExpiresAtField(entity: DomainEntity, direct = false): DomainProperty | undefined {
1341
+ return findPropertyField(
1342
+ entity,
1343
+ (property) => property.info.name === 'expiresAt' && property.type === 'datetime',
1344
+ direct
1345
+ )
1346
+ }