@api-client/core 0.18.31 → 0.18.32

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 (38) hide show
  1. package/build/src/browser.d.ts +1 -0
  2. package/build/src/browser.d.ts.map +1 -1
  3. package/build/src/browser.js +1 -0
  4. package/build/src/browser.js.map +1 -1
  5. package/build/src/index.d.ts +1 -0
  6. package/build/src/index.d.ts.map +1 -1
  7. package/build/src/index.js +1 -0
  8. package/build/src/index.js.map +1 -1
  9. package/build/src/modeling/ApiModel.d.ts +3 -2
  10. package/build/src/modeling/ApiModel.d.ts.map +1 -1
  11. package/build/src/modeling/ApiModel.js +12 -16
  12. package/build/src/modeling/ApiModel.js.map +1 -1
  13. package/build/src/modeling/ExposedEntity.d.ts +114 -0
  14. package/build/src/modeling/ExposedEntity.d.ts.map +1 -0
  15. package/build/src/modeling/ExposedEntity.js +300 -0
  16. package/build/src/modeling/ExposedEntity.js.map +1 -0
  17. package/build/src/modeling/helpers/endpointHelpers.d.ts +11 -0
  18. package/build/src/modeling/helpers/endpointHelpers.d.ts.map +1 -1
  19. package/build/src/modeling/helpers/endpointHelpers.js +21 -0
  20. package/build/src/modeling/helpers/endpointHelpers.js.map +1 -1
  21. package/build/src/modeling/types.d.ts +9 -12
  22. package/build/src/modeling/types.d.ts.map +1 -1
  23. package/build/src/modeling/types.js.map +1 -1
  24. package/build/src/models/kinds.d.ts +1 -0
  25. package/build/src/models/kinds.d.ts.map +1 -1
  26. package/build/src/models/kinds.js +1 -0
  27. package/build/src/models/kinds.js.map +1 -1
  28. package/build/tsconfig.tsbuildinfo +1 -1
  29. package/data/models/example-generator-api.json +6 -6
  30. package/package.json +1 -1
  31. package/src/modeling/ApiModel.ts +17 -21
  32. package/src/modeling/ExposedEntity.ts +344 -0
  33. package/src/modeling/helpers/endpointHelpers.ts +22 -0
  34. package/src/modeling/types.ts +9 -13
  35. package/src/models/kinds.ts +1 -0
  36. package/tests/unit/modeling/api_model.spec.ts +49 -10
  37. package/tests/unit/modeling/api_model_expose_entity.spec.ts +0 -1
  38. package/tests/unit/modeling/exposed_entity.spec.ts +100 -0
@@ -5,9 +5,11 @@ import {
5
5
  DataDomain,
6
6
  type RolesBasedAccessControl,
7
7
  type ApiModelSchema,
8
- type ExposedEntity,
8
+ type ExposedEntitySchema,
9
9
  type ApiContact,
10
10
  type ApiLicense,
11
+ ExposedEntityKind,
12
+ ExposedEntity,
11
13
  } from '../../../src/index.js'
12
14
 
13
15
  test.group('ApiModel.createSchema()', () => {
@@ -34,7 +36,16 @@ test.group('ApiModel.createSchema()', () => {
34
36
  const input: Partial<ApiModelSchema> = {
35
37
  key: 'test-api',
36
38
  info: { name: 'Test API', description: 'A test API' },
37
- exposes: [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }],
39
+ exposes: [
40
+ {
41
+ key: 'entity1',
42
+ actions: [],
43
+ hasCollection: true,
44
+ kind: ExposedEntityKind,
45
+ relativeResourcePath: '/',
46
+ entity: { key: 'entity1' },
47
+ },
48
+ ],
38
49
  user: { key: 'user-entity' },
39
50
  dependencyList: [{ key: 'domain1', version: '1.0.0' }],
40
51
  authentication: { strategy: 'UsernamePassword' },
@@ -51,7 +62,8 @@ test.group('ApiModel.createSchema()', () => {
51
62
  assert.equal(schema.kind, ApiModelKind)
52
63
  assert.equal(schema.key, 'test-api')
53
64
  assert.deepInclude(schema.info, { name: 'Test API', description: 'A test API' })
54
- assert.deepEqual(schema.exposes, [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }])
65
+ assert.lengthOf(schema.exposes, 1, 'should have one exposed entity')
66
+ assert.equal(schema.exposes[0].key, 'entity1', 'exposed entity should have correct key')
55
67
  assert.deepEqual(schema.user, { key: 'user-entity' })
56
68
  assert.deepEqual(schema.dependencyList, [{ key: 'domain1', version: '1.0.0' }])
57
69
  assert.deepEqual(schema.authentication, { strategy: 'UsernamePassword' })
@@ -100,7 +112,16 @@ test.group('ApiModel.constructor()', () => {
100
112
  kind: ApiModelKind,
101
113
  key: 'test-api',
102
114
  info: { name: 'Test API', description: 'A test API' },
103
- exposes: [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }],
115
+ exposes: [
116
+ {
117
+ key: 'entity1',
118
+ actions: [],
119
+ relativeResourcePath: '/',
120
+ entity: { key: 'entity1' },
121
+ hasCollection: true,
122
+ kind: ExposedEntityKind,
123
+ },
124
+ ],
104
125
  user: { key: 'user-entity' },
105
126
  dependencyList: [{ key: 'domain1', version: '1.0.0' }],
106
127
  authentication: { strategy: 'UsernamePassword' },
@@ -116,7 +137,8 @@ test.group('ApiModel.constructor()', () => {
116
137
 
117
138
  assert.equal(model.key, 'test-api')
118
139
  assert.equal(model.info.name, 'Test API')
119
- assert.deepEqual(model.exposes, [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }])
140
+ assert.lengthOf(model.exposes, 1, 'should have one exposed entity')
141
+ assert.equal(model.exposes[0].key, 'entity1', 'exposed entity should have correct key')
120
142
  assert.deepEqual(model.user, { key: 'user-entity' })
121
143
  assert.deepEqual(model.dependencyList, [{ key: 'domain1', version: '1.0.0' }])
122
144
  assert.deepEqual(model.authentication, { strategy: 'UsernamePassword' })
@@ -179,7 +201,16 @@ test.group('ApiModel.toJSON()', () => {
179
201
  kind: ApiModelKind,
180
202
  key: 'test-api',
181
203
  info: { name: 'Test API', description: 'A test API' },
182
- exposes: [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }],
204
+ exposes: [
205
+ {
206
+ key: 'entity1',
207
+ actions: [],
208
+ relativeResourcePath: '/',
209
+ entity: { key: 'entity1' },
210
+ hasCollection: true,
211
+ kind: ExposedEntityKind,
212
+ },
213
+ ],
183
214
  user: { key: 'user-entity' },
184
215
  dependencyList: [{ key: 'domain1', version: '1.0.0' }],
185
216
  authentication: { strategy: 'UsernamePassword' },
@@ -196,7 +227,8 @@ test.group('ApiModel.toJSON()', () => {
196
227
 
197
228
  assert.equal(json.key, 'test-api')
198
229
  assert.deepInclude(json.info, { name: 'Test API', description: 'A test API' })
199
- assert.deepEqual(json.exposes, [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }])
230
+ assert.lengthOf(json.exposes, 1, 'should have one exposed entity')
231
+ assert.equal(json.exposes[0].key, 'entity1', 'exposed entity should have correct key')
200
232
  assert.deepEqual(json.user, { key: 'user-entity' })
201
233
  assert.deepEqual(json.dependencyList, [{ key: 'domain1', version: '1.0.0' }])
202
234
  assert.deepEqual(json.authentication, { strategy: 'UsernamePassword' })
@@ -214,11 +246,18 @@ test.group('ApiModel.getExposedEntity()', () => {
214
246
  test('returns an existing exposed entity', ({ assert }) => {
215
247
  const model = new ApiModel()
216
248
  const entityKey = 'get-entity'
217
- const exposed: ExposedEntity = { key: entityKey, actions: [], path: '', entity: { key: entityKey } }
218
- model.exposes.push(exposed)
249
+ const exposed: ExposedEntitySchema = {
250
+ key: entityKey,
251
+ actions: [],
252
+ hasCollection: true,
253
+ kind: ExposedEntityKind,
254
+ relativeResourcePath: '/',
255
+ entity: { key: entityKey },
256
+ }
257
+ model.exposes.push(new ExposedEntity(model, exposed))
219
258
 
220
259
  const retrievedEntity = model.getExposedEntity({ key: entityKey })
221
- assert.deepEqual(retrievedEntity, exposed)
260
+ assert.deepEqual(retrievedEntity?.toJSON(), exposed)
222
261
  }).tags(['@modeling', '@api'])
223
262
 
224
263
  test('returns undefined if entity is not exposed', ({ assert }) => {
@@ -82,7 +82,6 @@ test.group('ApiModel.exposeEntity()', () => {
82
82
  assert.isDefined(nestedB)
83
83
  assert.deepEqual(nestedB?.parent?.key, exposedA.key)
84
84
  assert.strictEqual(nestedB?.relativeCollectionPath, '/entitybs')
85
- assert.strictEqual(nestedB?.absoluteCollectionPath, '/as/{id}/entitybs')
86
85
  })
87
86
 
88
87
  test('does not infinitely expose circular associations', ({ assert }) => {
@@ -0,0 +1,100 @@
1
+ import { test } from '@japa/runner'
2
+ import { ApiModel, ExposedEntity } from '../../../src/index.js'
3
+
4
+ test.group('ExposedEntity', () => {
5
+ test('setRelativeCollectionPath normalizes and preserves resource param', ({ assert }) => {
6
+ const model = new ApiModel()
7
+ const ex = new ExposedEntity(model, {
8
+ hasCollection: true,
9
+ relativeCollectionPath: '/items',
10
+ relativeResourcePath: '/items/{customId}',
11
+ })
12
+
13
+ ex.setRelativeCollectionPath('products')
14
+
15
+ assert.equal(ex.relativeCollectionPath, '/products')
16
+ assert.equal(ex.relativeResourcePath, '/products/{customId}')
17
+ }).tags(['@modeling', '@exposed-entity'])
18
+
19
+ test('setRelativeResourcePath with collection allows only parameter name change', ({ assert }) => {
20
+ const model = new ApiModel()
21
+ const ex = new ExposedEntity(model, {
22
+ hasCollection: true,
23
+ relativeCollectionPath: '/products',
24
+ relativeResourcePath: '/products/{id}',
25
+ })
26
+
27
+ // valid: same collection segment, different param name
28
+ ex.setRelativeResourcePath('/products/{productId}')
29
+ assert.equal(ex.relativeResourcePath, '/products/{productId}')
30
+
31
+ // invalid: different first segment
32
+ assert.throws(() => ex.setRelativeResourcePath('/wrong/{id}'))
33
+
34
+ // invalid: second segment not a parameter
35
+ assert.throws(() => ex.setRelativeResourcePath('/products/notParam'))
36
+ }).tags(['@modeling', '@exposed-entity'])
37
+
38
+ test('setRelativeResourcePath without collection must have exactly two segments', ({ assert }) => {
39
+ const model = new ApiModel()
40
+ const ex = new ExposedEntity(model, {
41
+ hasCollection: false,
42
+ relativeResourcePath: '/profile/{id}',
43
+ })
44
+
45
+ ex.setRelativeResourcePath('settings/secret')
46
+ assert.equal(ex.relativeResourcePath, '/settings/secret')
47
+
48
+ assert.throws(() => ex.setRelativeResourcePath('onlyone'))
49
+ }).tags(['@modeling', '@exposed-entity'])
50
+
51
+ test('computes absolute resource and collection paths along parent chain', ({ assert }) => {
52
+ const model = new ApiModel()
53
+
54
+ // Build exposure schemas
55
+ const rootSchema = {
56
+ key: 'root',
57
+ entity: { key: 'user' },
58
+ hasCollection: true,
59
+ relativeCollectionPath: '/users',
60
+ relativeResourcePath: '/users/{userId}',
61
+ isRoot: true,
62
+ actions: [],
63
+ }
64
+ const childSchema = {
65
+ key: 'child',
66
+ entity: { key: 'post' },
67
+ hasCollection: true,
68
+ relativeCollectionPath: '/posts',
69
+ relativeResourcePath: '/posts/{postId}',
70
+ parent: { key: 'root', association: { key: 'toPosts' } },
71
+ actions: [],
72
+ }
73
+ const grandSchema = {
74
+ key: 'grand',
75
+ entity: { key: 'details' },
76
+ hasCollection: false,
77
+ relativeResourcePath: '/details',
78
+ parent: { key: 'child', association: { key: 'toDetails' } },
79
+ actions: [],
80
+ }
81
+ // Instantiate instances bound to the model
82
+ const rootEx = new ExposedEntity(model, rootSchema)
83
+ const childEx = new ExposedEntity(model, childSchema)
84
+ const grandEx = new ExposedEntity(model, grandSchema)
85
+ // attach to model as instances
86
+ model.exposes = [rootEx, childEx, grandEx]
87
+
88
+ // root
89
+ assert.equal(rootEx.getAbsoluteCollectionPath(), '/users')
90
+ assert.equal(rootEx.getAbsoluteResourcePath(), '/users/{userId}')
91
+
92
+ // child
93
+ assert.equal(childEx.getAbsoluteCollectionPath(), '/users/{userId}/posts')
94
+ assert.equal(childEx.getAbsoluteResourcePath(), '/users/{userId}/posts/{postId}')
95
+
96
+ // grand (no collection)
97
+ assert.isUndefined(grandEx.getAbsoluteCollectionPath())
98
+ assert.equal(grandEx.getAbsoluteResourcePath(), '/users/{userId}/posts/{postId}/details')
99
+ }).tags(['@modeling', '@exposed-entity'])
100
+ })