@api-client/core 0.19.12 → 0.19.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.19.12",
4
+ "version": "0.19.13",
5
5
  "license": "UNLICENSED",
6
6
  "exports": {
7
7
  "./browser.js": {
@@ -96,7 +96,7 @@
96
96
  "chalk": "^5.4.1",
97
97
  "console-table-printer": "^2.11.2",
98
98
  "dompurify": "^3.2.6",
99
- "jsdom": "^28.0.0",
99
+ "jsdom": "^29.0.0",
100
100
  "nanoid": "^5.1.5",
101
101
  "tslog": "^4.9.3",
102
102
  "ws": "^8.12.0",
@@ -318,10 +318,10 @@ export class ExposedEntity extends EventTarget {
318
318
  return
319
319
  }
320
320
 
321
- // No collection: allow any two segments
322
- if (segments.length !== 2) {
321
+ // No collection: allow exactly one segment
322
+ if (segments.length !== 1) {
323
323
  throw new Error(
324
- `Resource path must contain exactly two segments when no collection is present. Received: "${cleaned}"`
324
+ `Resource path must contain exactly one segment when no collection is present. Received: "${cleaned}"`
325
325
  )
326
326
  }
327
327
  // Check for collision if this is a root entity (singleton case)
@@ -333,7 +333,7 @@ export class ExposedEntity extends EventTarget {
333
333
  }
334
334
 
335
335
  if (this.resourcePath !== cleaned) {
336
- this.resourcePath = `/${segments[0]}/${segments[1]}`
336
+ this.resourcePath = `/${segments[0]}`
337
337
  }
338
338
  }
339
339
 
@@ -522,11 +522,11 @@ export function validateExposedEntity(entity: ExposedEntity, apiModel: ApiModel)
522
522
  })
523
523
  } else {
524
524
  const parts = entity.resourcePath.split('/').filter(Boolean)
525
- if (parts.length !== 2 || !entity.resourcePath.startsWith('/')) {
525
+ if (parts.length !== 1 || !entity.resourcePath.startsWith('/')) {
526
526
  issues.push({
527
527
  code: createCode('EXPOSURE', 'INVALID_RESOURCE_PATH_FORMAT'),
528
- message: 'The URL route must only contain two exact parts or levels.',
529
- suggestion: 'Simplify the endpoint URL to prevent deep-nesting.',
528
+ message: 'The URL route must only contain one exact part or level.',
529
+ suggestion: 'Simplify the endpoint URL to a single segment.',
530
530
  severity: 'error',
531
531
  context: { ...context, property: 'resourcePath' },
532
532
  })
@@ -37,17 +37,17 @@ test.group('ExposedEntity', () => {
37
37
  assert.throws(() => ex.setResourcePath('/products/notParam'))
38
38
  }).tags(['@modeling', '@exposed-entity'])
39
39
 
40
- test('setResourcePath without collection must have exactly two segments', ({ assert }) => {
40
+ test('setResourcePath without collection must have exactly one segment', ({ assert }) => {
41
41
  const model = new ApiModel()
42
42
  const ex = new ExposedEntity(model, {
43
43
  hasCollection: false,
44
- resourcePath: '/profile/{id}',
44
+ resourcePath: '/profile',
45
45
  })
46
46
 
47
- ex.setResourcePath('settings/secret')
48
- assert.equal(ex.resourcePath, '/settings/secret')
47
+ ex.setResourcePath('settings')
48
+ assert.equal(ex.resourcePath, '/settings')
49
49
 
50
- assert.throws(() => ex.setResourcePath('onlyone'))
50
+ assert.throws(() => ex.setResourcePath('settings/secret'))
51
51
  }).tags(['@modeling', '@exposed-entity'])
52
52
 
53
53
  test('computes absolute resource and collection paths along parent chain', ({ assert }) => {
@@ -43,12 +43,12 @@ test.group('ExposedEntity Path Setter Validation', () => {
43
43
 
44
44
  // Set exp1 resource path to something specific
45
45
  exp1.hasCollection = false
46
- exp1.setResourcePath('/shared/path')
46
+ exp1.setResourcePath('/shared')
47
47
 
48
48
  // Try to set exp2 resource path to same
49
49
  assert.throws(
50
- () => exp2.setResourcePath('/shared/path'),
51
- 'Resource path "/shared/path" is already in use by another root entity.'
50
+ () => exp2.setResourcePath('/shared'),
51
+ 'Resource path "/shared" is already in use by another root entity.'
52
52
  )
53
53
  })
54
54
 
@@ -67,8 +67,8 @@ test.group('ExposedEntity Path Setter Validation', () => {
67
67
  assert.equal(exp1.collectionPath, '/new-path')
68
68
 
69
69
  exp1.hasCollection = false // allow arbitrary resource path
70
- exp1.setResourcePath('/custom/resource')
71
- assert.equal(exp1.resourcePath, '/custom/resource')
70
+ exp1.setResourcePath('/custom')
71
+ assert.equal(exp1.resourcePath, '/custom')
72
72
  })
73
73
 
74
74
  test('does not validate collision for non-root entities', ({ assert }) => {
@@ -235,6 +235,19 @@ test.group('ApiModel Validation', () => {
235
235
  assert.isTrue(issues.some((i) => i.code === 'EXPOSURE_INVALID_RESOURCE_PATH_FORMAT'))
236
236
  })
237
237
 
238
+ test('validateExposedEntity - path integrity (no collection)', ({ assert }) => {
239
+ const domain = new DataDomain({ info: { version: '1.0.0' } })
240
+ const model = new ApiModel({}, domain)
241
+ const exposure = new ExposedEntity(model, {
242
+ entity: { key: 'fake' },
243
+ hasCollection: false,
244
+ })
245
+ exposure.resourcePath = '/invalid/two_segments'
246
+ exposure.actions = [new ReadAction(exposure)]
247
+ const issues = validateExposedEntity(exposure, model)
248
+ assert.isTrue(issues.some((i) => i.code === 'EXPOSURE_INVALID_RESOURCE_PATH_FORMAT'))
249
+ })
250
+
238
251
  test('validateAction - List needs pagination', ({ assert }) => {
239
252
  const model = new ApiModel()
240
253
  const exposure = new ExposedEntity(model, { entity: { key: '1' } })