@api-client/core 0.18.2 → 0.18.4

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 (31) hide show
  1. package/build/src/modeling/ApiModel.d.ts +5 -5
  2. package/build/src/modeling/ApiModel.d.ts.map +1 -1
  3. package/build/src/modeling/ApiModel.js +8 -8
  4. package/build/src/modeling/ApiModel.js.map +1 -1
  5. package/build/src/modeling/helpers/database.d.ts +24 -0
  6. package/build/src/modeling/helpers/database.d.ts.map +1 -0
  7. package/build/src/modeling/helpers/database.js +42 -0
  8. package/build/src/modeling/helpers/database.js.map +1 -0
  9. package/build/src/modeling/importers/CsvImporter.d.ts +36 -0
  10. package/build/src/modeling/importers/CsvImporter.d.ts.map +1 -0
  11. package/build/src/modeling/importers/CsvImporter.js +81 -0
  12. package/build/src/modeling/importers/CsvImporter.js.map +1 -0
  13. package/build/src/modeling/importers/ImporterException.d.ts +10 -0
  14. package/build/src/modeling/importers/ImporterException.d.ts.map +1 -0
  15. package/build/src/modeling/importers/ImporterException.js +10 -0
  16. package/build/src/modeling/importers/ImporterException.js.map +1 -0
  17. package/build/src/modeling/importers/JsonSchemaImporter.d.ts +99 -0
  18. package/build/src/modeling/importers/JsonSchemaImporter.d.ts.map +1 -0
  19. package/build/src/modeling/importers/JsonSchemaImporter.js +525 -0
  20. package/build/src/modeling/importers/JsonSchemaImporter.js.map +1 -0
  21. package/build/tsconfig.tsbuildinfo +1 -1
  22. package/data/models/example-generator-api.json +13 -13
  23. package/package.json +5 -2
  24. package/src/modeling/ApiModel.ts +11 -10
  25. package/src/modeling/helpers/database.ts +48 -0
  26. package/src/modeling/importers/CsvImporter.ts +88 -0
  27. package/src/modeling/importers/ImporterException.ts +10 -0
  28. package/src/modeling/importers/JsonSchemaImporter.ts +642 -0
  29. package/tests/unit/modeling/api_model.spec.ts +9 -9
  30. package/tests/unit/modeling/importers/csv_importer.spec.ts +189 -0
  31. package/tests/unit/modeling/importers/json_schema_importer.spec.ts +451 -0
@@ -42065,6 +42065,9 @@
42065
42065
  "@id": "#191"
42066
42066
  },
42067
42067
  {
42068
+ "@id": "#206"
42069
+ },
42070
+ {
42068
42071
  "@id": "#194"
42069
42072
  },
42070
42073
  {
@@ -42077,9 +42080,6 @@
42077
42080
  "@id": "#203"
42078
42081
  },
42079
42082
  {
42080
- "@id": "#206"
42081
- },
42082
- {
42083
42083
  "@id": "#209"
42084
42084
  }
42085
42085
  ],
@@ -43457,7 +43457,7 @@
43457
43457
  "doc:ExternalDomainElement",
43458
43458
  "doc:DomainElement"
43459
43459
  ],
43460
- "doc:raw": "addressType: 'REGISTERED-OFFICE-ADDRESS'\nstreetName: 'UITBREIDINGSTRAAT'\nhouseNumber: '84'\nhouseNumberAddition: '/1'\npostalCode: '2600'\ncity: 'BERCHEM (ANTWERPEN)'\ncountry: 'Belgium'\ncountryCode: 'BE'\nfullFormatedAddress: \"UITBREIDINGSTRAAT 84 /1, 2600 BERCHEM (ANTWERPEN), BELIUM\"\n",
43460
+ "doc:raw": "code: '5'\ndescription: 'Limited company'\n",
43461
43461
  "core:mediaType": "application/yaml",
43462
43462
  "sourcemaps:sources": [
43463
43463
  {
@@ -43478,7 +43478,7 @@
43478
43478
  "doc:ExternalDomainElement",
43479
43479
  "doc:DomainElement"
43480
43480
  ],
43481
- "doc:raw": "code: '5'\ndescription: 'Limited company'\n",
43481
+ "doc:raw": "class: '3'\ndescription: '150 - 300'\nnumberOfFte: 5500\nnumberOfEmployees: 5232\n",
43482
43482
  "core:mediaType": "application/yaml",
43483
43483
  "sourcemaps:sources": [
43484
43484
  {
@@ -43499,7 +43499,7 @@
43499
43499
  "doc:ExternalDomainElement",
43500
43500
  "doc:DomainElement"
43501
43501
  ],
43502
- "doc:raw": "class: '3'\ndescription: '150 - 300'\nnumberOfFte: 5500\nnumberOfEmployees: 5232\n",
43502
+ "doc:raw": "code: 'J'\ndescription: 'Information and communication'\n",
43503
43503
  "core:mediaType": "application/yaml",
43504
43504
  "sourcemaps:sources": [
43505
43505
  {
@@ -43520,7 +43520,7 @@
43520
43520
  "doc:ExternalDomainElement",
43521
43521
  "doc:DomainElement"
43522
43522
  ],
43523
- "doc:raw": "code: 'J'\ndescription: 'Information and communication'\n",
43523
+ "doc:raw": "code: '7487'\ndescription: 'Financial and insurance activities'\ntype: \"PRIMARY\"\nclassificationCode: 'BE_NACEBEL2008'\nactivityGroupCode: 'ABCDE'\n",
43524
43524
  "core:mediaType": "application/yaml",
43525
43525
  "sourcemaps:sources": [
43526
43526
  {
@@ -43541,7 +43541,7 @@
43541
43541
  "doc:ExternalDomainElement",
43542
43542
  "doc:DomainElement"
43543
43543
  ],
43544
- "doc:raw": "code: '7487'\ndescription: 'Financial and insurance activities'\ntype: \"PRIMARY\"\nclassificationCode: 'BE_NACEBEL2008'\nactivityGroupCode: 'ABCDE'\n",
43544
+ "doc:raw": "addressType: 'REGISTERED-OFFICE-ADDRESS'\nstreetName: 'UITBREIDINGSTRAAT'\nhouseNumber: '84'\nhouseNumberAddition: '/1'\npostalCode: '2600'\ncity: 'BERCHEM (ANTWERPEN)'\ncountry: 'Belgium'\ncountryCode: 'BE'\nfullFormatedAddress: \"UITBREIDINGSTRAAT 84 /1, 2600 BERCHEM (ANTWERPEN), BELIUM\"\n",
43545
43545
  "core:mediaType": "application/yaml",
43546
43546
  "sourcemaps:sources": [
43547
43547
  {
@@ -44761,27 +44761,27 @@
44761
44761
  {
44762
44762
  "@id": "#196/source-map/lexical/element_0",
44763
44763
  "sourcemaps:element": "amf://id#196",
44764
- "sourcemaps:value": "[(1,0)-(10,0)]"
44764
+ "sourcemaps:value": "[(1,0)-(3,0)]"
44765
44765
  },
44766
44766
  {
44767
44767
  "@id": "#199/source-map/lexical/element_0",
44768
44768
  "sourcemaps:element": "amf://id#199",
44769
- "sourcemaps:value": "[(1,0)-(3,0)]"
44769
+ "sourcemaps:value": "[(1,0)-(5,0)]"
44770
44770
  },
44771
44771
  {
44772
44772
  "@id": "#202/source-map/lexical/element_0",
44773
44773
  "sourcemaps:element": "amf://id#202",
44774
- "sourcemaps:value": "[(1,0)-(5,0)]"
44774
+ "sourcemaps:value": "[(1,0)-(3,0)]"
44775
44775
  },
44776
44776
  {
44777
44777
  "@id": "#205/source-map/lexical/element_0",
44778
44778
  "sourcemaps:element": "amf://id#205",
44779
- "sourcemaps:value": "[(1,0)-(3,0)]"
44779
+ "sourcemaps:value": "[(1,0)-(6,0)]"
44780
44780
  },
44781
44781
  {
44782
44782
  "@id": "#208/source-map/lexical/element_0",
44783
44783
  "sourcemaps:element": "amf://id#208",
44784
- "sourcemaps:value": "[(1,0)-(6,0)]"
44784
+ "sourcemaps:value": "[(1,0)-(10,0)]"
44785
44785
  },
44786
44786
  {
44787
44787
  "@id": "#223/source-map/lexical/element_0",
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.18.2",
4
+ "version": "0.18.4",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {
7
7
  "./browser.js": {
@@ -87,12 +87,14 @@
87
87
  "@api-client/json": "^0.2.0",
88
88
  "@esm-bundle/chai": "^4.3.4-fix.0",
89
89
  "@metrichor/jmespath": "^0.3.1",
90
+ "@pawel-up/csv": "^0.2.0",
90
91
  "@pawel-up/data-mock": "^0.4.0",
91
92
  "@pawel-up/jexl": "^4.0.1",
92
93
  "@xmldom/xmldom": "^0.9.7",
93
94
  "amf-json-ld-lib": "^0.0.15",
94
95
  "chalk": "^5.4.1",
95
96
  "console-table-printer": "^2.11.2",
97
+ "isomorphic-dompurify": "^2.26.0",
96
98
  "nanoid": "^5.1.5",
97
99
  "ws": "^8.12.0",
98
100
  "xpath": "^0.0.34"
@@ -111,6 +113,7 @@
111
113
  "@types/cors": "^2.8.12",
112
114
  "@types/express-ntlm": "^2.3.3",
113
115
  "@types/jsdom": "^21.1.7",
116
+ "@types/json-schema": "^7.0.15",
114
117
  "@types/mocha": "^10.0.10",
115
118
  "@types/node": "^24.0.1",
116
119
  "@types/sinon": "^17.0.1",
@@ -131,11 +134,11 @@
131
134
  "get-port": "^7.0.0",
132
135
  "globals": "^16.0.0",
133
136
  "husky": "^9.0.11",
134
- "jsdom": "^26.0.0",
135
137
  "lint-staged": "^16.0.0",
136
138
  "nock": "^14.0.1",
137
139
  "oauth2-mock-server": "^8.0.0",
138
140
  "prettier": "^3.5.1",
141
+ "schema-org-json-schemas": "^2.1.4",
139
142
  "sinon": "^21.0.0",
140
143
  "ts-lit-plugin": "^2.0.2",
141
144
  "ts-node-maintained": "^10.9.5",
@@ -3,6 +3,7 @@ import { ApiModelKind, DataDomainKind } from '../models/kinds.js'
3
3
  import { type IThing, Thing } from '../models/Thing.js'
4
4
  import type {
5
5
  AccessRule,
6
+ AssociationTarget,
6
7
  AuthenticationConfiguration,
7
8
  AuthorizationConfiguration,
8
9
  ExposedEntity,
@@ -64,11 +65,11 @@ export interface ApiModelSchema extends DependentModelSchema {
64
65
 
65
66
  /**
66
67
  * The designated Data Entity that represents a "User".
67
- * This entity must be marked with the "User" semantic in the Data Modeler.
68
+ * This entity should be marked with the "User" semantic in the Data Modeler.
68
69
  *
69
70
  * This property is required to publish the API.
70
71
  */
71
- userKey?: string
72
+ user?: AssociationTarget
72
73
 
73
74
  /**
74
75
  * Configuration for how users prove their identity.
@@ -142,7 +143,7 @@ export class ApiModel extends DependentModel {
142
143
  *
143
144
  * This property is required to publish the API.
144
145
  */
145
- userKey?: string
146
+ user?: AssociationTarget
146
147
 
147
148
  /**
148
149
  * Configuration for how users prove their identity.
@@ -232,8 +233,8 @@ export class ApiModel extends DependentModel {
232
233
  info,
233
234
  exposes,
234
235
  }
235
- if (input.userKey) {
236
- result.userKey = input.userKey
236
+ if (input.user) {
237
+ result.user = { ...input.user }
237
238
  }
238
239
  if (input.dependencyList) {
239
240
  result.dependencyList = structuredClone(input.dependencyList)
@@ -279,7 +280,7 @@ export class ApiModel extends DependentModel {
279
280
  this.kind = init.kind
280
281
  this.key = init.key
281
282
  this.info = new Thing(init.info)
282
- this.userKey = init.userKey
283
+ this.user = init.user
283
284
  if (init.authentication) {
284
285
  this.authentication = structuredClone(init.authentication)
285
286
  }
@@ -322,8 +323,8 @@ export class ApiModel extends DependentModel {
322
323
  info: this.info.toJSON(),
323
324
  exposes: structuredClone(this.exposes),
324
325
  }
325
- if (this.userKey) {
326
- result.userKey = this.userKey
326
+ if (this.user) {
327
+ result.user = { ...this.user }
327
328
  }
328
329
  if (this.dependencyList.length > 0) {
329
330
  result.dependencyList = structuredClone(this.dependencyList)
@@ -414,14 +415,14 @@ export class ApiModel extends DependentModel {
414
415
 
415
416
  /**
416
417
  * Clears the API model for a new entity change.
417
- * This method resets the dependencies, exposes, userKey,
418
+ * This method resets the dependencies, exposes, user,
418
419
  * authentication, authorization, and session properties.
419
420
  */
420
421
  cleanForEntityChange(): void {
421
422
  this.dependencies.clear()
422
423
  this.dependencyList = []
423
424
  this.exposes = []
424
- this.userKey = undefined
425
+ this.user = undefined
425
426
  if (this.session) {
426
427
  this.session.properties = []
427
428
  }
@@ -0,0 +1,48 @@
1
+ import DOMPurify from 'isomorphic-dompurify'
2
+ import { snakeCase } from '@pawel-up/jexl/string.js'
3
+
4
+ export function sanitizeInput(input: string): string {
5
+ return DOMPurify.isSupported ? DOMPurify.sanitize(input) : input
6
+ }
7
+
8
+ /**
9
+ * Converts a string into a sanitized, database column friendly name.
10
+ * - Sanitizes the input using DOMPurify.
11
+ * - Converts to lowercase.
12
+ * - Replaces spaces and multiple underscores with a single underscore.
13
+ * - Removes any characters that are not alphanumeric or underscore.
14
+ *
15
+ * @param inputName The string to format.
16
+ * @returns A database column friendly name.
17
+ */
18
+ export function toDatabaseColumnName(inputName: string, defaultName = 'untitled_column'): string {
19
+ // 1. Sanitize
20
+ let name = sanitizeInput(inputName)
21
+
22
+ // 2. Convert to lowercase
23
+ name = snakeCase(name)
24
+
25
+ // 3. Replace spaces and multiple hyphens/underscores with a single underscore
26
+ name = name.replace(/[\s-]+/g, '_')
27
+
28
+ // 4. Remove any characters that are not alphanumeric or underscore
29
+ name = name.replace(/[^a-z0-9_]/g, '')
30
+
31
+ // 5. Ensure it doesn't start or end with an underscore (optional, but good practice)
32
+ name = name.replace(/^_+|_+$/g, '')
33
+
34
+ return name || defaultName
35
+ }
36
+ /**
37
+ * Converts a string into a sanitized, database table friendly name.
38
+ * - Sanitizes the input using DOMPurify.
39
+ * - Converts to lowercase.
40
+ * - Replaces spaces and multiple underscores with a single underscore.
41
+ * - Removes any characters that are not alphanumeric or underscore.
42
+ *
43
+ * @param inputName The string to format.
44
+ * @returns A database table friendly name.
45
+ */
46
+ export function toDatabaseTableName(inputName: string, defaultName = 'untitled_table'): string {
47
+ return toDatabaseColumnName(inputName, defaultName)
48
+ }
@@ -0,0 +1,88 @@
1
+ import { type CSVOptions, CSVParser, type ParseResult } from '@pawel-up/csv'
2
+ import type { DataDomain } from '../DataDomain.js'
3
+ import { sanitizeInput, toDatabaseColumnName, toDatabaseTableName } from '../helpers/database.js'
4
+ import type { DomainPropertySchema } from '../DomainProperty.js'
5
+
6
+ export type { CSVOptions, ParseResult }
7
+
8
+ /**
9
+ * Imports CSV data into a DataDomain.
10
+ *
11
+ * This class parses CSV files and translates the column definitions into DomainProperty instances
12
+ * within a specified DomainModel and DomainEntity.
13
+ */
14
+ export class CsvImporter {
15
+ private parser: CSVParser
16
+ private domain: DataDomain
17
+
18
+ /**
19
+ * Creates an instance of CsvImporter.
20
+ * @param domain The DataDomain to which the imported data will be added.
21
+ * @param options Optional CSV parsing options.
22
+ */
23
+ constructor(domain: DataDomain, options?: CSVOptions) {
24
+ this.parser = new CSVParser(options)
25
+ this.domain = domain
26
+ }
27
+
28
+ /**
29
+ * Parses a CSV file or string to extract its structure and data.
30
+ * This is a wrapper around the underlying CSV parser.
31
+ * @param csv The CSV content to parse, as a File object or a string.
32
+ * @returns A promise that resolves with the parsed result.
33
+ */
34
+ public parse(csv: File | string): Promise<ParseResult> {
35
+ return this.parser.parse(csv)
36
+ }
37
+
38
+ /**
39
+ * Imports the parsed CSV data structure into the domain as a new model and entity.
40
+ * It creates a `DomainEntity` where each column from the CSV is represented as a `DomainProperty`.
41
+ * The first row of data is used to provide an example value for each property.
42
+ * @param data The result from the `parse` method, containing the CSV structure and values.
43
+ * @param modelName The name to be used for the created `DomainModel` and `DomainEntity`.
44
+ * @returns A promise that resolves when the import is complete.
45
+ */
46
+ public async import(data: ParseResult, modelName: string): Promise<void> {
47
+ const name = toDatabaseTableName(modelName, 'imported_cvs_data')
48
+ const model = this.domain.addModel({ info: { name } })
49
+ const entity = model.addEntity({ info: { name } })
50
+ const sanitizedInputName = sanitizeInput(modelName)
51
+ if (name !== sanitizedInputName) {
52
+ model.info.displayName = sanitizedInputName
53
+ entity.info.displayName = sanitizedInputName
54
+ }
55
+ for (const row of data.format) {
56
+ const { index, name, type, format } = row
57
+ const columnName = toDatabaseColumnName(name, `column_${index}`)
58
+ const schema: Partial<DomainPropertySchema> = {
59
+ info: { name: columnName },
60
+ type,
61
+ }
62
+ if (format) {
63
+ schema.bindings = [
64
+ {
65
+ type: 'web',
66
+ schema: {
67
+ format: format === 'integer' ? 'int64' : 'double',
68
+ },
69
+ },
70
+ ]
71
+ }
72
+ const exampleRow = data.values[0]
73
+ if (Array.isArray(exampleRow) && exampleRow[index]) {
74
+ const value = exampleRow[index]
75
+ if (value !== undefined && value !== null) {
76
+ schema.schema = {
77
+ examples: [sanitizeInput(String(value))],
78
+ }
79
+ }
80
+ }
81
+ const prop = entity.addProperty(schema)
82
+ const sn = sanitizeInput(name)
83
+ if (sn !== columnName) {
84
+ prop.info.displayName = sn
85
+ }
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,10 @@
1
+ import { Exception } from '../../exceptions/exception.js'
2
+
3
+ /**
4
+ * An exception thrown during the JSON Schema import process when an
5
+ * unrecoverable error occurs.
6
+ */
7
+ export class ImporterException extends Exception {
8
+ static override code = 'E_IMPORTER_FAILURE'
9
+ static override status = 400 // Bad Request, as it's likely due to invalid input schemas
10
+ }