@api-client/core 0.11.0 → 0.11.2

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.
@@ -42470,13 +42470,13 @@
42470
42470
  "@id": "#219"
42471
42471
  },
42472
42472
  {
42473
- "@id": "#219"
42473
+ "@id": "#213"
42474
42474
  },
42475
42475
  {
42476
42476
  "@id": "#210"
42477
42477
  },
42478
42478
  {
42479
- "@id": "#213"
42479
+ "@id": "#219"
42480
42480
  },
42481
42481
  {
42482
42482
  "@id": "#216"
@@ -43913,7 +43913,7 @@
43913
43913
  "doc:ExternalDomainElement",
43914
43914
  "doc:DomainElement"
43915
43915
  ],
43916
- "doc:raw": "-\n type: 'GENERAL'\n value: 'info@company.be'\n-\n type: 'IT_DEPT'\n value: 'it-service@company.be'\n",
43916
+ "doc:raw": "type: 'GENERAL'\ncountryDialCode : '+32'\nareaCode : '22'\nsubscriberNumber: '12.87.00'\nformatted: '+32-(0)22 000000'\n",
43917
43917
  "core:mediaType": "application/yaml",
43918
43918
  "sourcemaps:sources": [
43919
43919
  {
@@ -43955,7 +43955,7 @@
43955
43955
  "doc:ExternalDomainElement",
43956
43956
  "doc:DomainElement"
43957
43957
  ],
43958
- "doc:raw": "type: 'GENERAL'\ncountryDialCode : '+32'\nareaCode : '22'\nsubscriberNumber: '12.87.00'\nformatted: '+32-(0)22 000000'\n",
43958
+ "doc:raw": "-\n type: 'GENERAL'\n value: 'info@company.be'\n-\n type: 'IT_DEPT'\n value: 'it-service@company.be'\n",
43959
43959
  "core:mediaType": "application/yaml",
43960
43960
  "sourcemaps:sources": [
43961
43961
  {
@@ -44781,7 +44781,7 @@
44781
44781
  {
44782
44782
  "@id": "#215/source-map/lexical/element_0",
44783
44783
  "sourcemaps:element": "amf://id#215",
44784
- "sourcemaps:value": "[(1,0)-(7,0)]"
44784
+ "sourcemaps:value": "[(1,0)-(6,0)]"
44785
44785
  },
44786
44786
  {
44787
44787
  "@id": "#218/source-map/lexical/element_0",
@@ -44791,7 +44791,7 @@
44791
44791
  {
44792
44792
  "@id": "#221/source-map/lexical/element_0",
44793
44793
  "sourcemaps:element": "amf://id#221",
44794
- "sourcemaps:value": "[(1,0)-(6,0)]"
44794
+ "sourcemaps:value": "[(1,0)-(7,0)]"
44795
44795
  },
44796
44796
  {
44797
44797
  "@id": "#338/source-map/synthesized-field/element_1",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@api-client/core",
3
3
  "description": "The API Client's core client library. Works in NodeJS and in a ES enabled browser.",
4
- "version": "0.11.0",
4
+ "version": "0.11.2",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {
7
7
  "./browser.js": {
@@ -58,7 +58,7 @@
58
58
  "@pawel-up/data-mock": "^0.3.2",
59
59
  "@pawel-up/jexl": "^3.0.0",
60
60
  "@xmldom/xmldom": "^0.9.7",
61
- "amf-json-ld-lib": "^0.0.14",
61
+ "amf-json-ld-lib": "^0.0.15",
62
62
  "console-table-printer": "^2.11.2",
63
63
  "dompurify": "^3.1.5",
64
64
  "idb-keyval": "^6.2.1",
@@ -94,7 +94,7 @@
94
94
  "@web/test-runner": "^0.20.0",
95
95
  "@web/test-runner-commands": "^0.9.0",
96
96
  "@web/test-runner-playwright": "^0.11.0",
97
- "amf-client-js": "^5.5.2-0",
97
+ "amf-client-js": "^5.7.0",
98
98
  "c8": "^10.1.3",
99
99
  "cors": "^2.8.5",
100
100
  "eslint": "^9.20.1",
@@ -121,6 +121,7 @@
121
121
  "wireit": "^0.14.4"
122
122
  },
123
123
  "scripts": {
124
+ "build:test": "wireit",
124
125
  "build:ts": "wireit",
125
126
  "build": "npm run build:ts && npm run lint && npm run copy:assets",
126
127
  "lint": "wireit",
@@ -192,6 +193,19 @@
192
193
  ".tsbuildinfo"
193
194
  ]
194
195
  },
196
+ "build:test": {
197
+ "command": "tsc --project tsconfig.browser.json",
198
+ "clean": "if-file-deleted",
199
+ "files": [
200
+ "src/**/*.ts",
201
+ "test/**/*.ts",
202
+ "tsconfig.browser.json"
203
+ ],
204
+ "output": [
205
+ ".tmp/testing/**",
206
+ ".tsbuildinfo"
207
+ ]
208
+ },
195
209
  "lint": {
196
210
  "command": "eslint --color --cache --cache-location .eslintcache .",
197
211
  "files": [
@@ -13,9 +13,7 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
13
  License for the specific language governing permissions and limitations under
14
14
  the License.
15
15
  */
16
-
17
- // @ts-expect-error This library has no proper types defined.
18
- import { AmfModelExpander, JsonLdOptions, JsonLd } from 'amf-json-ld-lib'
16
+ import { AmfModelExpander } from 'amf-json-ld-lib'
19
17
  import {
20
18
  IAmfDocument,
21
19
  IAmfDomainElement,
@@ -78,7 +76,7 @@ export declare class AmfMixinInterface {
78
76
  /**
79
77
  * Expands flattened AMF model
80
78
  */
81
- _expand(amf: any): any
79
+ _expand(amf: unknown): IAmfDocument
82
80
 
83
81
  /**
84
82
  * Returns compact model key for given value.
@@ -94,7 +92,7 @@ export declare class AmfMixinInterface {
94
92
  * @param amf AMF json/ld model
95
93
  * @returns The API spec
96
94
  */
97
- _ensureAmfModel(amf: any): IAmfDocument | undefined
95
+ _ensureAmfModel(amf: unknown): IAmfDocument | undefined
98
96
 
99
97
  /**
100
98
  * Ensures that the value is an array.
@@ -525,31 +523,20 @@ export declare class AmfMixinInterface {
525
523
  export const AmfMixin = <T extends Constructor<any>>(superClass: T): Constructor<AmfMixinInterface> & T => {
526
524
  class AmfMixin extends superClass {
527
525
  _amf?: IAmfDocument
528
- _flattenedAmf?: any
529
526
  __cachedKeys?: any
527
+ __lastSetValue?: unknown
530
528
 
531
529
  get amf(): IAmfDocument | undefined {
532
530
  return this._amf
533
531
  }
534
532
 
535
533
  set amf(value: IAmfDocument | undefined) {
536
- const old = this._amf
537
- if (old === value) {
534
+ if (this._amf === value || this.__lastSetValue === value) {
538
535
  return
539
536
  }
540
- let expanded
541
- if (!value || AmfModelExpander.isInExpandedForm(value)) {
542
- this._flattenedAmf = undefined
543
- expanded = value
544
- } else {
545
- const oldFlattened = this._flattenedAmf
546
- if (oldFlattened === value) {
547
- return
548
- }
549
- this._flattenedAmf = value
550
- expanded = this._expand(value)
551
- }
552
- // Cached keys cannot be static as this element can be using in the sane
537
+ this.__lastSetValue = value
538
+ const expanded = value ? this._expand(value) : undefined
539
+ // Cached keys cannot be static as this element can be used in the sane
553
540
  // document with different AMF models
554
541
  this.__cachedKeys = {}
555
542
  this._amf = expanded
@@ -569,16 +556,8 @@ export const AmfMixin = <T extends Constructor<any>>(superClass: T): Constructor
569
556
  /**
570
557
  * Expands flattened AMF model
571
558
  */
572
- _expand(amf: any): any {
573
- AmfModelExpander.preprocessLegacyRootNodeId(amf)
574
- const linkEmbeddingFilter = (key: string): boolean => !key.endsWith('fixPoint')
575
- const rootNode = amf['@context'] ? '' : 'amf://id'
576
- const options = JsonLdOptions.apply()
577
- .withEmbeddedLinks(linkEmbeddingFilter)
578
- .withCompactedIris()
579
- .withExpandedStructure()
580
- .withRootNode(rootNode)
581
- return JsonLd.process(amf, options)
559
+ _expand(amf: any): IAmfDocument {
560
+ return AmfModelExpander.expand(amf) as IAmfDocument
582
561
  }
583
562
 
584
563
  /**
@@ -8,6 +8,7 @@ import { DataNamespace } from './DataNamespace.js'
8
8
  import { type FieldValidationMessage, ValidationError } from '../exceptions/validation_error.js'
9
9
  import { DataAssociationKind } from '../models/kinds.js'
10
10
  import type { AssociationBinding, AssociationBindings, AssociationWebBindings } from './Bindings.js'
11
+ import { DataAttributeAttributes, type DataAttributeAttribute } from './DataFormat.js'
11
12
 
12
13
  /**
13
14
  * Describes association target of an entity
@@ -484,6 +485,18 @@ export class DataAssociation {
484
485
  return this.schema
485
486
  }
486
487
 
488
+ /**
489
+ * Checks whether the passed value is one of the supported data proprty attributes.
490
+ * @param value The value to test
491
+ * @returns True when the passed value is one of the supported data proprty attributes.
492
+ */
493
+ static isValidAttribute(value: unknown): value is DataAttributeAttribute {
494
+ if (typeof value !== 'string') {
495
+ return false
496
+ }
497
+ return DataAttributeAttributes.includes(value as DataAttributeAttribute)
498
+ }
499
+
487
500
  /**
488
501
  * Creates if not existing and returns web bindings definition.
489
502
  * @returns The web binding definition
@@ -17,6 +17,11 @@ import { ValidationError, type FieldValidationMessage } from '../exceptions/vali
17
17
  import { RemoveEntityException } from '../exceptions/remove_entity_exception.js'
18
18
  import { ForeignAssociationException } from '../exceptions/foreign_association_exception.js'
19
19
 
20
+ interface OrderedItem {
21
+ type: 'property' | 'association'
22
+ key: string
23
+ }
24
+
20
25
  /**
21
26
  * Data entity is the smallest description of a data in the system
22
27
  * It contains properties and associations. At least one entity describe a data model.
@@ -54,6 +59,14 @@ export interface IDataEntity {
54
59
  */
55
60
  associations?: string[]
56
61
 
62
+ /**
63
+ * The ordered list of fields (properties and associations) in the schema.
64
+ * These only keep references to the properties and associations.
65
+ * The order of the fields is important as it defines the order of the
66
+ * properties in the schema.
67
+ */
68
+ fields?: OrderedItem[]
69
+
57
70
  /**
58
71
  * The list of keys of entities that are parents to this entity.
59
72
  *
@@ -62,6 +75,9 @@ export interface IDataEntity {
62
75
  * with the same name to shadow parent property. When the property is
63
76
  * not shadowed this may cause unexpected results as the processing could result
64
77
  * with inconsistent definition of a schema because the last read property wins.
78
+ *
79
+ * @todo(jarrodek): This should also hold a reference to a namespace to support
80
+ * foreign entities as parents.
65
81
  */
66
82
  parents?: string[]
67
83
 
@@ -112,6 +128,14 @@ export class DataEntity {
112
128
  */
113
129
  associations: DataAssociation[] = []
114
130
 
131
+ /**
132
+ * The ordered list of fields (properties and associations) in the schema.
133
+ * These only keep references to the properties and associations.
134
+ * The order of the fields is important as it defines the order of the
135
+ * properties in the schema.
136
+ */
137
+ fields: OrderedItem[] = []
138
+
115
139
  /**
116
140
  * The list of keys of entities that are parents to this entity.
117
141
  *
@@ -167,6 +191,7 @@ export class DataEntity {
167
191
  parents,
168
192
  properties,
169
193
  associations,
194
+ fields,
170
195
  deprecated,
171
196
  } = init
172
197
  this.kind = kind
@@ -209,6 +234,11 @@ export class DataEntity {
209
234
  }
210
235
  })
211
236
  }
237
+ if (Array.isArray(fields)) {
238
+ this.fields = [...fields]
239
+ } else {
240
+ this.fields = this.createOrderedFields()
241
+ }
212
242
  if (typeof deprecated === 'boolean') {
213
243
  this.deprecated = deprecated
214
244
  } else {
@@ -216,6 +246,23 @@ export class DataEntity {
216
246
  }
217
247
  }
218
248
 
249
+ private createOrderedFields(): OrderedItem[] {
250
+ const result: OrderedItem[] = []
251
+ this.properties.forEach((i) => {
252
+ result.push({
253
+ type: 'property',
254
+ key: i.key,
255
+ })
256
+ })
257
+ this.associations.forEach((i) => {
258
+ result.push({
259
+ type: 'association',
260
+ key: i.key,
261
+ })
262
+ })
263
+ return result
264
+ }
265
+
219
266
  static validate(input: unknown): void {
220
267
  const typed = input as IDataEntity
221
268
  const messages: FieldValidationMessage[] = []
@@ -260,6 +307,15 @@ export class DataEntity {
260
307
  if (Array.isArray(this.associations) && this.associations.length) {
261
308
  result.associations = this.associations.map((i) => i.key)
262
309
  }
310
+ if (Array.isArray(this.fields) && this.fields.length) {
311
+ result.fields = [...this.fields]
312
+ }
313
+ if (Array.isArray(this.taxonomy) && this.taxonomy.length) {
314
+ result.taxonomy = [...this.taxonomy]
315
+ }
316
+ if (Array.isArray(this.tags) && this.tags.length) {
317
+ result.tags = [...this.tags]
318
+ }
263
319
  if (typeof this.deprecated === 'boolean') {
264
320
  result.deprecated = this.deprecated
265
321
  }
@@ -286,6 +342,7 @@ export class DataEntity {
286
342
  }
287
343
  this.root.definitions.properties.push(property)
288
344
  this.properties.push(property)
345
+ this.fields.push({ type: 'property', key: property.key })
289
346
  return property
290
347
  }
291
348
 
@@ -298,6 +355,7 @@ export class DataEntity {
298
355
  const property = DataProperty.fromName(this.root, name)
299
356
  this.root.definitions.properties.push(property)
300
357
  this.properties.push(property)
358
+ this.fields.push({ type: 'property', key: property.key })
301
359
  return property
302
360
  }
303
361
 
@@ -316,24 +374,70 @@ export class DataEntity {
316
374
  if (defIndex >= 0) {
317
375
  this.root.definitions.properties.splice(defIndex, 1)
318
376
  }
377
+ this.removeField(key)
319
378
  propertyToRemove.remove()
320
379
  }
321
380
 
381
+ private removeField(key: string): void {
382
+ this.fields = this.fields.filter((item) => item.key !== key)
383
+ }
384
+
322
385
  /**
323
386
  * Lists all properties for this Entity.
324
387
  * @returns The list of properties that belong to this entity.
325
388
  */
326
389
  listProperties(): DataProperty[] {
327
- const result: DataProperty[] = []
328
- for (const prop of this.properties) {
329
- this.root.definitions.properties.find((p) => p.key === prop.key)
330
- if (prop) {
331
- result.push(prop)
390
+ return [...this.properties]
391
+ }
392
+
393
+ /**
394
+ * Lists all associations for this Entity.
395
+ * @returns The list of associations that belong to this entity.
396
+ */
397
+ listAssociations(): DataAssociation[] {
398
+ return [...this.associations]
399
+ }
400
+
401
+ /**
402
+ * Generates an ordered list of fields (properties and associations) for this entity.
403
+ *
404
+ * @returns The list of fields (properties and associations) that belong to this entity.
405
+ */
406
+ listFields(): (DataAssociation | DataProperty)[] {
407
+ const result: (DataAssociation | DataProperty)[] = []
408
+ this.fields.forEach((i) => {
409
+ if (i.type === 'property') {
410
+ const property = this.properties.find((p) => p.key === i.key)
411
+ if (property) {
412
+ result.push(property)
413
+ }
414
+ } else if (i.type === 'association') {
415
+ const association = this.associations.find((a) => a.key === i.key)
416
+ if (association) {
417
+ result.push(association)
418
+ }
332
419
  }
333
- }
420
+ })
334
421
  return result
335
422
  }
336
423
 
424
+ /**
425
+ * Moves the field to a new index.
426
+ * @param key The key of the field to move.
427
+ * @param toIndex The new index to which move the field to.
428
+ */
429
+ moveField(key: string, toIndex: number): void {
430
+ const fromIndex = this.fields.findIndex((i) => i.key === key)
431
+ if (fromIndex < 0) {
432
+ throw new ValidationError([{ field: 'fields', message: `Field ${key} not found.`, rule: 'not-found' }])
433
+ }
434
+ if (toIndex < 0 || toIndex >= this.fields.length) {
435
+ throw new ValidationError([{ field: 'fields', message: `Invalid index ${toIndex}.`, rule: 'invalid' }])
436
+ }
437
+ const [item] = this.fields.splice(fromIndex, 1)
438
+ this.fields.splice(toIndex, 0, item)
439
+ }
440
+
337
441
  /**
338
442
  * Creates an association for a given name, adds it to definitions, and returns it.
339
443
  * @param name The name of the association
@@ -343,6 +447,7 @@ export class DataEntity {
343
447
  const result = DataAssociation.fromName(this.root, name)
344
448
  this.root.definitions.associations.push(result)
345
449
  this.associations.push(result)
450
+ this.fields.push({ type: 'association', key: result.key })
346
451
  return result
347
452
  }
348
453
 
@@ -367,6 +472,7 @@ export class DataEntity {
367
472
  }
368
473
  this.root.definitions.associations.push(result)
369
474
  this.associations.push(result)
475
+ this.fields.push({ type: 'association', key: result.key })
370
476
  return result
371
477
  }
372
478
 
@@ -395,6 +501,7 @@ export class DataEntity {
395
501
  }
396
502
  this.root.definitions.associations.push(result)
397
503
  this.associations.push(result)
504
+ this.fields.push({ type: 'association', key: result.key })
398
505
  return result
399
506
  }
400
507
 
@@ -414,6 +521,7 @@ export class DataEntity {
414
521
  if (defIndex >= 0) {
415
522
  this.root.definitions.associations.splice(defIndex, 1)
416
523
  }
524
+ this.removeField(key)
417
525
  associationToRemove.remove()
418
526
  }
419
527
 
@@ -511,6 +619,12 @@ export class DataEntity {
511
619
  return this.root.definitions.models.find((m) => m.entities.some((e) => e === this))
512
620
  }
513
621
 
622
+ /**
623
+ * Adds a parent reference to this entity.
624
+ * This will not add the parent to the namespace. It only adds a reference to the parent.
625
+ * @param key The key of the parent entity to add.
626
+ * @returns `this` for chaining.
627
+ */
514
628
  addParent(key: string): this {
515
629
  // Prevent adding self as parent
516
630
  if (key === this.key) {
@@ -555,6 +669,20 @@ export class DataEntity {
555
669
  { message }
556
670
  )
557
671
  }
672
+ const has = this.parents.some((i) => i === key)
673
+ if (has) {
674
+ const message = `Parent ${key} already exists.`
675
+ throw new ValidationError(
676
+ [
677
+ {
678
+ field: 'parents',
679
+ message,
680
+ rule: 'exists',
681
+ },
682
+ ],
683
+ { message }
684
+ )
685
+ }
558
686
  this.parents.push(key)
559
687
  return this
560
688
  }
@@ -622,19 +750,27 @@ export class DataEntity {
622
750
  }
623
751
 
624
752
  /**
625
- * Returns a list of entities that are associated with this entity through an association
626
- * that the target property points to this entity.
753
+ * Returns a list of entities that are associated with this entity as a target.
627
754
  *
628
- * In other words, if entity `A` has association target with `B`, then asking `B` for related entities will
629
- * result with `[A]`.
755
+ * This method identifies entities that have an association where this entity is a target.
756
+ * In other words, it finds entities that "point to" this entity through an association.
757
+ *
758
+ * For example, consider the following relationships:
630
759
  *
631
760
  * ```plain
632
- * A -> B -> C
633
- * D -> C
761
+ * A -> B (A has an association that targets B)
762
+ * C -> B (C has an association that targets B)
763
+ * B -> D (B has an association that targets D)
634
764
  *
635
- * C => [B, D]
636
- * B => [A, C]
765
+ * Calling `getRelatedEntities()` on B would return [A, C]
766
+ * Calling `getRelatedEntities()` on D would return [B]
767
+ * Calling `getRelatedEntities()` on A would return []
637
768
  * ```
769
+ *
770
+ * Note, this method only returns entities that are directly associated with this entity as a target.
771
+ * It does not traverse the association graph to find indirectly related entities.
772
+ *
773
+ * @returns An array of `DataEntity` instances that have an association targeting this entity.
638
774
  */
639
775
  getRelatedEntities(): DataEntity[] {
640
776
  const { key, root } = this
@@ -650,9 +786,9 @@ export class DataEntity {
650
786
  return result
651
787
  }
652
788
 
653
- static getRelatedEntities(namespace: DataNamespace, entity: string): DataEntity[] {
789
+ static getRelatedEntities(namespace: DataNamespace, entityKey: string): DataEntity[] {
654
790
  const result: DataEntity[] = []
655
- const inverse = namespace.definitions.associations.filter((i) => i.targets.some((j) => j.key === entity))
791
+ const inverse = namespace.definitions.associations.filter((i) => i.targets.some((j) => j.key === entityKey))
656
792
  inverse.forEach((assoc) => {
657
793
  const entity = namespace.definitions.entities.find((e) => e.associations.includes(assoc))
658
794
  if (entity && !result.includes(entity)) {
@@ -45,6 +45,9 @@ export enum DataPropertyList {
45
45
  file = 'binary',
46
46
  }
47
47
 
48
+ /**
49
+ * The data property "attributes".
50
+ */
48
51
  export type DataPropertyAttribute =
49
52
  | 'required'
50
53
  | 'multiple'
@@ -64,6 +67,13 @@ export const DataPropertyAttributes: DataPropertyAttribute[] = [
64
67
  'deprecated',
65
68
  ]
66
69
 
70
+ /**
71
+ * The data attribute "attributes".
72
+ */
73
+ export type DataAttributeAttribute = 'required' | 'multiple'
74
+
75
+ export const DataAttributeAttributes: DataAttributeAttribute[] = ['required', 'multiple']
76
+
67
77
  /**
68
78
  * Note, OAS supports the `integer` data type and not format.
69
79
  * We need to account for that.
@@ -24,8 +24,11 @@ interface IDataDefinitions {
24
24
 
25
25
  interface DataDefinitions {
26
26
  models: DataModel[]
27
+ // @todo: This should be a map of entities with a key of the entity key.
27
28
  entities: DataEntity[]
29
+ // @todo: This should be a map of properties with a key of the property key.
28
30
  properties: DataProperty[]
31
+ // @todo: This should be a map of associations with a key of the association key.
29
32
  associations: DataAssociation[]
30
33
  namespaces: DataNamespace[]
31
34
  /**
@@ -672,6 +675,16 @@ export class DataNamespace extends DataNamespaceParent {
672
675
  return definitions.properties.find((i) => i.key === key)
673
676
  }
674
677
 
678
+ /**
679
+ * Finds an association by its key.
680
+ * @param key The key of the property to find
681
+ * @returns The property or undefined if not found.
682
+ */
683
+ findAssociation(key: string): DataAssociation | undefined {
684
+ const { definitions } = this.root || this
685
+ return definitions.associations.find((i) => i.key === key)
686
+ }
687
+
675
688
  /**
676
689
  * Searches for entities for association targets.
677
690
  * This is a helper function to discover entities in the current and foreign namespaces.