@api-client/core 0.18.32 → 0.18.33

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.
@@ -1,3 +1,5 @@
1
+ import { __esDecorate, __runInitializers } from "tslib";
2
+ import { observed } from '../decorators/observed.js';
1
3
  import { ExposedEntityKind } from '../models/kinds.js';
2
4
  import { nanoid } from '../nanoid.js';
3
5
  import { ensureLeadingSlash, joinPaths } from './helpers/endpointHelpers.js';
@@ -6,295 +8,357 @@ import { ensureLeadingSlash, joinPaths } from './helpers/endpointHelpers.js';
6
8
  *
7
9
  * @fires change - Emitted when the exposed entity has changed.
8
10
  */
9
- export class ExposedEntity extends EventTarget {
10
- /**
11
- * The exposed entity kind recognizable by the ecosystem.
12
- */
13
- kind;
14
- /**
15
- * The unique key of the exposed entity.
16
- * This is a stable identifier that does not change across versions.
17
- */
18
- key;
19
- /**
20
- * A pointer to a Data Entity from the Data Domain.
21
- */
22
- entity;
23
- /**
24
- * Indicates whether this exposure has a collection endpoint.
25
- * A collection endpoint is optional for nested exposures where the association is 1:1
26
- * and the schema is embedded directly under the parent resource.
27
- */
28
- hasCollection;
29
- /**
30
- * Relative path to the collection endpoint for this exposure.
31
- * Starts with '/'. Not set for 1:1 nested exposures where collection does not exist.
32
- */
33
- relativeCollectionPath;
34
- /**
35
- * Relative path to the resource endpoint for this exposure.
36
- * Starts with '/'. For 1:1 nested exposures the resource path typically does not include an id segment.
37
- */
38
- relativeResourcePath;
39
- /**
40
- * Whether this exposure is a root exposure (top-level collection).
41
- * If this is set then the `parent` reference must be populated.
42
- */
43
- isRoot;
44
- /**
45
- * Parent reference when this exposure was created via following an association.
46
- */
47
- parent;
48
- /**
49
- * Expose-time config used to create this exposure (persisted for auditing/UI).
50
- * This is only populated for the root exposure. All children exposures inherit this config.
51
- */
52
- exposeOptions;
53
- /**
54
- * The list of enabled API actions for this exposure (List/Read/Create/etc.)
55
- */
56
- actions;
57
- /**
58
- * Optional array of access rules that define the access control policies for this exposure.
59
- */
60
- accessRule;
61
- /**
62
- * Optional configuration for rate limiting for this exposure.
63
- */
64
- rateLimiting;
65
- /**
66
- * When true, generation for this exposure hit configured limits
67
- */
68
- truncated;
69
- /**
70
- * When the notifying flag is set to true,
71
- * the domain is pending a notification.
72
- * No other notifications will be sent until
73
- * the current notification is sent.
74
- */
75
- #notifying = false;
76
- /**
77
- * A reference to the parent API Model instance.
78
- */
79
- api;
80
- static createSchema(input = {}) {
81
- const { key = nanoid(), entity = { key: '' }, relativeCollectionPath, relativeResourcePath = '/', hasCollection = true, isRoot, parent, exposeOptions, actions = [], accessRule, rateLimiting, truncated, } = input;
82
- const result = {
83
- kind: ExposedEntityKind,
84
- key,
85
- entity: { ...entity },
86
- hasCollection,
87
- relativeResourcePath,
88
- actions: actions.map((a) => ({ ...a })),
89
- };
90
- if (relativeCollectionPath !== undefined) {
91
- result.relativeCollectionPath = relativeCollectionPath;
11
+ let ExposedEntity = (() => {
12
+ let _classSuper = EventTarget;
13
+ let _entity_decorators;
14
+ let _entity_initializers = [];
15
+ let _entity_extraInitializers = [];
16
+ let _collectionPath_decorators;
17
+ let _collectionPath_initializers = [];
18
+ let _collectionPath_extraInitializers = [];
19
+ let _resourcePath_decorators;
20
+ let _resourcePath_initializers = [];
21
+ let _resourcePath_extraInitializers = [];
22
+ let _actions_decorators;
23
+ let _actions_initializers = [];
24
+ let _actions_extraInitializers = [];
25
+ let _accessRule_decorators;
26
+ let _accessRule_initializers = [];
27
+ let _accessRule_extraInitializers = [];
28
+ let _rateLimiting_decorators;
29
+ let _rateLimiting_initializers = [];
30
+ let _rateLimiting_extraInitializers = [];
31
+ return class ExposedEntity extends _classSuper {
32
+ static {
33
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
34
+ _entity_decorators = [observed()];
35
+ _collectionPath_decorators = [observed()];
36
+ _resourcePath_decorators = [observed()];
37
+ _actions_decorators = [observed({ deep: true })];
38
+ _accessRule_decorators = [observed({ deep: true })];
39
+ _rateLimiting_decorators = [observed({ deep: true })];
40
+ __esDecorate(this, null, _entity_decorators, { kind: "accessor", name: "entity", static: false, private: false, access: { has: obj => "entity" in obj, get: obj => obj.entity, set: (obj, value) => { obj.entity = value; } }, metadata: _metadata }, _entity_initializers, _entity_extraInitializers);
41
+ __esDecorate(this, null, _collectionPath_decorators, { kind: "accessor", name: "collectionPath", static: false, private: false, access: { has: obj => "collectionPath" in obj, get: obj => obj.collectionPath, set: (obj, value) => { obj.collectionPath = value; } }, metadata: _metadata }, _collectionPath_initializers, _collectionPath_extraInitializers);
42
+ __esDecorate(this, null, _resourcePath_decorators, { kind: "accessor", name: "resourcePath", static: false, private: false, access: { has: obj => "resourcePath" in obj, get: obj => obj.resourcePath, set: (obj, value) => { obj.resourcePath = value; } }, metadata: _metadata }, _resourcePath_initializers, _resourcePath_extraInitializers);
43
+ __esDecorate(this, null, _actions_decorators, { kind: "accessor", name: "actions", static: false, private: false, access: { has: obj => "actions" in obj, get: obj => obj.actions, set: (obj, value) => { obj.actions = value; } }, metadata: _metadata }, _actions_initializers, _actions_extraInitializers);
44
+ __esDecorate(this, null, _accessRule_decorators, { kind: "accessor", name: "accessRule", static: false, private: false, access: { has: obj => "accessRule" in obj, get: obj => obj.accessRule, set: (obj, value) => { obj.accessRule = value; } }, metadata: _metadata }, _accessRule_initializers, _accessRule_extraInitializers);
45
+ __esDecorate(this, null, _rateLimiting_decorators, { kind: "accessor", name: "rateLimiting", static: false, private: false, access: { has: obj => "rateLimiting" in obj, get: obj => obj.rateLimiting, set: (obj, value) => { obj.rateLimiting = value; } }, metadata: _metadata }, _rateLimiting_initializers, _rateLimiting_extraInitializers);
46
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
92
47
  }
93
- if (isRoot !== undefined) {
94
- result.isRoot = isRoot;
95
- }
96
- if (parent !== undefined) {
97
- result.parent = { ...parent };
98
- }
99
- if (exposeOptions !== undefined) {
100
- result.exposeOptions = { ...exposeOptions };
101
- }
102
- if (accessRule !== undefined) {
103
- result.accessRule = accessRule.map((ar) => ({ ...ar }));
104
- }
105
- if (rateLimiting !== undefined) {
106
- result.rateLimiting = { ...rateLimiting };
107
- }
108
- if (truncated !== undefined) {
109
- result.truncated = truncated;
110
- }
111
- return result;
112
- }
113
- constructor(model, state) {
114
- super();
115
- this.api = model;
116
- const init = ExposedEntity.createSchema(state);
117
- this.kind = init.kind;
118
- this.key = init.key;
119
- this.entity = init.entity;
120
- this.hasCollection = init.hasCollection;
121
- this.relativeCollectionPath = init.relativeCollectionPath;
122
- this.relativeResourcePath = init.relativeResourcePath;
123
- this.isRoot = init.isRoot;
124
- this.parent = init.parent;
125
- this.exposeOptions = init.exposeOptions;
126
- this.actions = init.actions;
127
- this.accessRule = init.accessRule;
128
- this.rateLimiting = init.rateLimiting;
129
- this.truncated = init.truncated;
130
- }
131
- notifyChange() {
132
- if (this.#notifying) {
133
- return;
134
- }
135
- this.#notifying = true;
136
- queueMicrotask(() => {
137
- this.#notifying = false;
138
- const event = new Event('change');
139
- this.dispatchEvent(event);
140
- });
141
- }
142
- toJSON() {
143
- const result = {
144
- kind: this.kind,
145
- key: this.key,
146
- entity: { ...this.entity },
147
- relativeResourcePath: this.relativeResourcePath,
148
- actions: this.actions.map((a) => ({ ...a })),
149
- hasCollection: this.hasCollection,
150
- };
151
- if (this.relativeCollectionPath !== undefined) {
152
- result.relativeCollectionPath = this.relativeCollectionPath;
153
- }
154
- if (this.isRoot !== undefined) {
155
- result.isRoot = this.isRoot;
156
- }
157
- if (this.parent !== undefined) {
158
- result.parent = { ...this.parent };
159
- }
160
- if (this.exposeOptions !== undefined) {
161
- result.exposeOptions = { ...this.exposeOptions };
162
- }
163
- if (this.accessRule !== undefined) {
164
- result.accessRule = this.accessRule.map((ar) => ({ ...ar }));
165
- }
166
- if (this.rateLimiting !== undefined) {
167
- result.rateLimiting = { ...this.rateLimiting };
168
- }
169
- if (this.truncated !== undefined) {
170
- result.truncated = this.truncated;
171
- }
172
- return result;
173
- }
174
- /**
175
- * Sets a new relative collection path for this exposed entity.
176
- *
177
- * It:
178
- * - updates the relativeCollectionPath property
179
- * - updates the absoluteCollectionPath property accordingly
180
- * - updates the relativeResourcePath and absoluteResourcePath accordingly.
181
- * @param path The new path to set.
182
- */
183
- setRelativeCollectionPath(path) {
184
- if (!this.hasCollection) {
185
- throw new Error(`Cannot set collection path on an exposure that does not have a collection`);
48
+ /**
49
+ * The exposed entity kind recognizable by the ecosystem.
50
+ */
51
+ kind;
52
+ /**
53
+ * The unique key of the exposed entity.
54
+ * This is a stable identifier that does not change across versions.
55
+ */
56
+ key;
57
+ #entity_accessor_storage = __runInitializers(this, _entity_initializers, void 0);
58
+ /**
59
+ * A pointer to a Data Entity from the Data Domain.
60
+ */
61
+ get entity() { return this.#entity_accessor_storage; }
62
+ set entity(value) { this.#entity_accessor_storage = value; }
63
+ /**
64
+ * Indicates whether this exposure has a collection endpoint.
65
+ * A collection endpoint is optional for nested exposures where the association is 1:1
66
+ * and the schema is embedded directly under the parent resource.
67
+ *
68
+ * Note that this property is not observed for changes as it is immutable after creation.
69
+ */
70
+ hasCollection = __runInitializers(this, _entity_extraInitializers);
71
+ #collectionPath_accessor_storage = __runInitializers(this, _collectionPath_initializers, void 0);
72
+ /**
73
+ * Path to the collection endpoint for this exposure.
74
+ * Starts with '/'. Not set for 1:1 nested exposures where collection does not exist.
75
+ */
76
+ get collectionPath() { return this.#collectionPath_accessor_storage; }
77
+ set collectionPath(value) { this.#collectionPath_accessor_storage = value; }
78
+ #resourcePath_accessor_storage = (__runInitializers(this, _collectionPath_extraInitializers), __runInitializers(this, _resourcePath_initializers, void 0));
79
+ /**
80
+ * Path to the resource endpoint for this exposure.
81
+ * Starts with '/'. For 1:1 nested exposures the resource path typically does not include an id segment.
82
+ */
83
+ get resourcePath() { return this.#resourcePath_accessor_storage; }
84
+ set resourcePath(value) { this.#resourcePath_accessor_storage = value; }
85
+ /**
86
+ * Whether this exposure is a root exposure (top-level collection).
87
+ * If this is set then the `parent` reference must be populated.
88
+ *
89
+ * Note that this property is not observed for changes as it is immutable after creation.
90
+ */
91
+ isRoot = __runInitializers(this, _resourcePath_extraInitializers);
92
+ /**
93
+ * Parent reference when this exposure was created via following an association.
94
+ *
95
+ * Note that this property is not observed for changes as it is immutable after creation.
96
+ */
97
+ parent;
98
+ /**
99
+ * Expose-time config used to create this exposure (persisted for auditing/UI).
100
+ * This is only populated for the root exposure. All children exposures inherit this config.
101
+ *
102
+ * Note that this property is not observed for changes as it is immutable after creation.
103
+ */
104
+ exposeOptions;
105
+ #actions_accessor_storage = __runInitializers(this, _actions_initializers, void 0);
106
+ /**
107
+ * The list of enabled API actions for this exposure (List/Read/Create/etc.)
108
+ */
109
+ get actions() { return this.#actions_accessor_storage; }
110
+ set actions(value) { this.#actions_accessor_storage = value; }
111
+ #accessRule_accessor_storage = (__runInitializers(this, _actions_extraInitializers), __runInitializers(this, _accessRule_initializers, void 0));
112
+ /**
113
+ * Optional array of access rules that define the access control policies for this exposure.
114
+ */
115
+ get accessRule() { return this.#accessRule_accessor_storage; }
116
+ set accessRule(value) { this.#accessRule_accessor_storage = value; }
117
+ #rateLimiting_accessor_storage = (__runInitializers(this, _accessRule_extraInitializers), __runInitializers(this, _rateLimiting_initializers, void 0));
118
+ /**
119
+ * Optional configuration for rate limiting for this exposure.
120
+ */
121
+ get rateLimiting() { return this.#rateLimiting_accessor_storage; }
122
+ set rateLimiting(value) { this.#rateLimiting_accessor_storage = value; }
123
+ /**
124
+ * When true, generation for this exposure hit configured limits
125
+ *
126
+ * Note that this property is not observed for changes as it is immutable after creation.
127
+ */
128
+ truncated = __runInitializers(this, _rateLimiting_extraInitializers);
129
+ /**
130
+ * When the notifying flag is set to true,
131
+ * the domain is pending a notification.
132
+ * No other notifications will be sent until
133
+ * the current notification is sent.
134
+ */
135
+ #notifying = false;
136
+ /**
137
+ * When the initializing flag is set to true,
138
+ * the domain is not notified of changes.
139
+ */
140
+ #initializing = true;
141
+ /**
142
+ * A reference to the parent API Model instance.
143
+ */
144
+ api;
145
+ static createSchema(input = {}) {
146
+ const { key = nanoid(), entity = { key: '' }, collectionPath, resourcePath = '/', hasCollection = true, isRoot, parent, exposeOptions, actions = [], accessRule, rateLimiting, truncated, } = input;
147
+ const result = {
148
+ kind: ExposedEntityKind,
149
+ key,
150
+ entity: { ...entity },
151
+ hasCollection,
152
+ resourcePath,
153
+ actions: actions.map((a) => ({ ...a })),
154
+ };
155
+ if (collectionPath !== undefined) {
156
+ result.collectionPath = collectionPath;
157
+ }
158
+ if (isRoot !== undefined) {
159
+ result.isRoot = isRoot;
160
+ }
161
+ if (parent !== undefined) {
162
+ result.parent = { ...parent };
163
+ }
164
+ if (exposeOptions !== undefined) {
165
+ result.exposeOptions = { ...exposeOptions };
166
+ }
167
+ if (accessRule !== undefined) {
168
+ result.accessRule = accessRule.map((ar) => ({ ...ar }));
169
+ }
170
+ if (rateLimiting !== undefined) {
171
+ result.rateLimiting = { ...rateLimiting };
172
+ }
173
+ if (truncated !== undefined) {
174
+ result.truncated = truncated;
175
+ }
176
+ return result;
186
177
  }
187
- const cleaned = ensureLeadingSlash(path);
188
- // Ensure exactly one non-empty segment
189
- const segments = cleaned.split('/').filter(Boolean);
190
- if (segments.length !== 1) {
191
- throw new Error(`Collection path must contain exactly one segment. Received: "${path}"`);
178
+ constructor(model, state) {
179
+ super();
180
+ this.api = model;
181
+ const init = ExposedEntity.createSchema(state);
182
+ this.kind = init.kind;
183
+ this.key = init.key;
184
+ this.entity = init.entity;
185
+ this.hasCollection = init.hasCollection;
186
+ this.collectionPath = init.collectionPath;
187
+ this.resourcePath = init.resourcePath;
188
+ this.isRoot = init.isRoot;
189
+ this.parent = init.parent;
190
+ this.exposeOptions = init.exposeOptions;
191
+ this.actions = init.actions;
192
+ this.accessRule = init.accessRule;
193
+ this.rateLimiting = init.rateLimiting;
194
+ this.truncated = init.truncated;
195
+ this.#initializing = false;
192
196
  }
193
- const normalizedCollection = `/${segments[0]}`;
194
- // Preserve current parameter name if present, otherwise default to {id}
195
- let param = '{id}';
196
- if (this.relativeResourcePath) {
197
- const curSegments = this.relativeResourcePath.split('/').filter(Boolean);
198
- const maybeParam = curSegments[1];
199
- if (maybeParam && /^\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(maybeParam)) {
200
- param = maybeParam;
197
+ notifyChange() {
198
+ if (this.#notifying || this.#initializing) {
199
+ return;
201
200
  }
201
+ this.#notifying = true;
202
+ queueMicrotask(() => {
203
+ this.#notifying = false;
204
+ const event = new Event('change');
205
+ this.dispatchEvent(event);
206
+ });
202
207
  }
203
- const nextResource = `${normalizedCollection}/${param}`;
204
- const changed = this.relativeCollectionPath !== normalizedCollection || this.relativeResourcePath !== nextResource;
205
- this.relativeCollectionPath = normalizedCollection;
206
- this.relativeResourcePath = nextResource;
207
- if (changed)
208
- this.notifyChange();
209
- }
210
- /**
211
- * Sets a new relative resource path for this exposed entity.
212
- *
213
- * Rules:
214
- * - Must start with '/'.
215
- * - If this exposure has a collection, the path must be exactly the collection path plus a single
216
- * parameter segment (e.g. `/products/{productId}`) and only the parameter name may vary.
217
- * - If this exposure does NOT have a collection, the path can be any two segments (e.g. `/profile/{id}` or `/a/b`).
218
- */
219
- setRelativeResourcePath(path) {
220
- const cleaned = ensureLeadingSlash(path);
221
- const segments = cleaned.split('/').filter(Boolean);
222
- if (this.hasCollection) {
223
- if (!this.relativeCollectionPath) {
224
- throw new Error('Cannot set resource path: missing collection path for this exposure');
208
+ toJSON() {
209
+ const result = {
210
+ kind: this.kind,
211
+ key: this.key,
212
+ entity: { ...this.entity },
213
+ resourcePath: this.resourcePath,
214
+ actions: this.actions.map((a) => ({ ...a })),
215
+ hasCollection: this.hasCollection,
216
+ };
217
+ if (this.collectionPath !== undefined) {
218
+ result.collectionPath = this.collectionPath;
225
219
  }
226
- const colSegments = this.relativeCollectionPath.split('/').filter(Boolean);
227
- if (colSegments.length !== 1) {
228
- throw new Error(`Invalid stored collection path "${this.relativeCollectionPath}"`);
220
+ if (this.isRoot !== undefined) {
221
+ result.isRoot = this.isRoot;
229
222
  }
230
- if (segments.length !== 2) {
231
- throw new Error(`Resource path must be exactly two segments (collection + parameter). Received: "${cleaned}"`);
223
+ if (this.parent !== undefined) {
224
+ result.parent = { ...this.parent };
232
225
  }
233
- const [s1, s2] = segments;
234
- if (s1 !== colSegments[0]) {
235
- throw new Error(`Resource path must start with the collection segment "${colSegments[0]}". Received: "${s1}"`);
226
+ if (this.exposeOptions !== undefined) {
227
+ result.exposeOptions = { ...this.exposeOptions };
236
228
  }
237
- // s2 must be a parameter segment {name}
238
- if (!/^\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(s2)) {
239
- throw new Error(`The second segment must be a parameter in braces, e.g. {id}. Received: "${s2}"`);
229
+ if (this.accessRule !== undefined) {
230
+ result.accessRule = this.accessRule.map((ar) => ({ ...ar }));
240
231
  }
241
- if (this.relativeResourcePath !== cleaned) {
242
- this.relativeResourcePath = `/${s1}/${s2}`;
243
- this.notifyChange();
232
+ if (this.rateLimiting !== undefined) {
233
+ result.rateLimiting = { ...this.rateLimiting };
244
234
  }
245
- return;
235
+ if (this.truncated !== undefined) {
236
+ result.truncated = this.truncated;
237
+ }
238
+ return result;
246
239
  }
247
- // No collection: allow any two segments
248
- if (segments.length !== 2) {
249
- throw new Error(`Resource path must contain exactly two segments when no collection is present. Received: "${cleaned}"`);
240
+ /**
241
+ * Sets a new collection path for this exposed entity.
242
+ *
243
+ * It:
244
+ * - updates the collectionPath property
245
+ * - updates the absoluteCollectionPath property accordingly
246
+ * - updates the resourcePath accordingly.
247
+ * @param path The new path to set.
248
+ */
249
+ setCollectionPath(path) {
250
+ if (!this.hasCollection) {
251
+ throw new Error(`Cannot set collection path on an exposure that does not have a collection`);
252
+ }
253
+ const cleaned = ensureLeadingSlash(path);
254
+ // Ensure exactly one non-empty segment
255
+ const segments = cleaned.split('/').filter(Boolean);
256
+ if (segments.length !== 1) {
257
+ throw new Error(`Collection path must contain exactly one segment. Received: "${path}"`);
258
+ }
259
+ const normalizedCollection = `/${segments[0]}`;
260
+ // Preserve current parameter name if present, otherwise default to {id}
261
+ let param = '{id}';
262
+ if (this.resourcePath) {
263
+ const curSegments = this.resourcePath.split('/').filter(Boolean);
264
+ const maybeParam = curSegments[1];
265
+ if (maybeParam && /^\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(maybeParam)) {
266
+ param = maybeParam;
267
+ }
268
+ }
269
+ const nextResource = `${normalizedCollection}/${param}`;
270
+ this.collectionPath = normalizedCollection;
271
+ this.resourcePath = nextResource;
272
+ // rely on ApiModel.exposes deep observation to notify on property sets
250
273
  }
251
- if (this.relativeResourcePath !== cleaned) {
252
- this.relativeResourcePath = `/${segments[0]}/${segments[1]}`;
253
- this.notifyChange();
274
+ /**
275
+ * Sets a new resource path for this exposed entity.
276
+ *
277
+ * Rules:
278
+ * - Must start with '/'.
279
+ * - If this exposure has a collection, the path must be exactly the collection path plus a single
280
+ * parameter segment (e.g. `/products/{productId}`) and only the parameter name may vary.
281
+ * - If this exposure does NOT have a collection, the path can be any two segments (e.g. `/profile/{id}` or `/a/b`).
282
+ */
283
+ setResourcePath(path) {
284
+ const cleaned = ensureLeadingSlash(path);
285
+ const segments = cleaned.split('/').filter(Boolean);
286
+ if (this.hasCollection) {
287
+ if (!this.collectionPath) {
288
+ throw new Error('Cannot set resource path: missing collection path for this exposure');
289
+ }
290
+ const colSegments = this.collectionPath.split('/').filter(Boolean);
291
+ if (colSegments.length !== 1) {
292
+ throw new Error(`Invalid stored collection path "${this.collectionPath}"`);
293
+ }
294
+ if (segments.length !== 2) {
295
+ throw new Error(`Resource path must be exactly two segments (collection + parameter). Received: "${cleaned}"`);
296
+ }
297
+ const [s1, s2] = segments;
298
+ if (s1 !== colSegments[0]) {
299
+ throw new Error(`Resource path must start with the collection segment "${colSegments[0]}". Received: "${s1}"`);
300
+ }
301
+ // s2 must be a parameter segment {name}
302
+ if (!/^\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(s2)) {
303
+ throw new Error(`The second segment must be a parameter in braces, e.g. {id}. Received: "${s2}"`);
304
+ }
305
+ if (this.resourcePath !== cleaned) {
306
+ this.resourcePath = `/${s1}/${s2}`;
307
+ }
308
+ return;
309
+ }
310
+ // No collection: allow any two segments
311
+ if (segments.length !== 2) {
312
+ throw new Error(`Resource path must contain exactly two segments when no collection is present. Received: "${cleaned}"`);
313
+ }
314
+ if (this.resourcePath !== cleaned) {
315
+ this.resourcePath = `/${segments[0]}/${segments[1]}`;
316
+ }
254
317
  }
255
- }
256
- /**
257
- * Computes the absolute path for this exposure's resource endpoint by
258
- * walking up the exposure tree using `parent.key` until reaching a root exposure.
259
- * The absolute path is composed by concatenating each ancestor's resource path
260
- * with this exposure's relative resource path.
261
- */
262
- getAbsoluteResourcePath() {
263
- let absolute = ensureLeadingSlash(this.relativeResourcePath);
264
- // Traverse parents, always joining with the parent's resource path
265
- let parentKey = this.parent?.key;
266
- while (parentKey) {
267
- const parent = this.api.exposes.find((e) => e.key === parentKey);
268
- if (!parent)
269
- break;
270
- const parentResource = ensureLeadingSlash(parent.relativeResourcePath);
271
- absolute = joinPaths(parentResource, absolute);
272
- parentKey = parent.parent?.key;
318
+ /**
319
+ * Computes the absolute path for this exposure's resource endpoint by
320
+ * walking up the exposure tree using `parent.key` until reaching a root exposure.
321
+ * The absolute path is composed by concatenating each ancestor's resource path
322
+ * with this exposure's resource path.
323
+ */
324
+ getAbsoluteResourcePath() {
325
+ let absolute = ensureLeadingSlash(this.resourcePath);
326
+ // Traverse parents, always joining with the parent's resource path
327
+ let parentKey = this.parent?.key;
328
+ while (parentKey) {
329
+ const parent = this.api.exposes.find((e) => e.key === parentKey);
330
+ if (!parent)
331
+ break;
332
+ const parentResource = ensureLeadingSlash(parent.resourcePath);
333
+ absolute = joinPaths(parentResource, absolute);
334
+ parentKey = parent.parent?.key;
335
+ }
336
+ return absolute;
273
337
  }
274
- return absolute;
275
- }
276
- /**
277
- * Computes the absolute path for this exposure's collection endpoint (if any)
278
- * by walking up the exposure tree using `parent.key` until reaching a root exposure.
279
- * The absolute path is composed by concatenating each ancestor's resource path
280
- * with this exposure's relative collection path.
281
- * Returns undefined if this exposure has no collection.
282
- */
283
- getAbsoluteCollectionPath() {
284
- if (!this.hasCollection || !this.relativeCollectionPath)
285
- return undefined;
286
- let absolute = ensureLeadingSlash(this.relativeCollectionPath);
287
- // Traverse parents, always joining with the parent's resource path
288
- let parentKey = this.parent?.key;
289
- while (parentKey) {
290
- const parent = this.api.exposes.find((e) => e.key === parentKey);
291
- if (!parent)
292
- break;
293
- const parentResource = ensureLeadingSlash(parent.relativeResourcePath);
294
- absolute = joinPaths(parentResource, absolute);
295
- parentKey = parent.parent?.key;
338
+ /**
339
+ * Computes the absolute path for this exposure's collection endpoint (if any)
340
+ * by walking up the exposure tree using `parent.key` until reaching a root exposure.
341
+ * The absolute path is composed by concatenating each ancestor's resource path
342
+ * with this exposure's collection path.
343
+ * Returns undefined if this exposure has no collection.
344
+ */
345
+ getAbsoluteCollectionPath() {
346
+ if (!this.hasCollection || !this.collectionPath)
347
+ return undefined;
348
+ let absolute = ensureLeadingSlash(this.collectionPath);
349
+ // Traverse parents, always joining with the parent's resource path
350
+ let parentKey = this.parent?.key;
351
+ while (parentKey) {
352
+ const parent = this.api.exposes.find((e) => e.key === parentKey);
353
+ if (!parent)
354
+ break;
355
+ const parentResource = ensureLeadingSlash(parent.resourcePath);
356
+ absolute = joinPaths(parentResource, absolute);
357
+ parentKey = parent.parent?.key;
358
+ }
359
+ return absolute;
296
360
  }
297
- return absolute;
298
- }
299
- }
361
+ };
362
+ })();
363
+ export { ExposedEntity };
300
364
  //# sourceMappingURL=ExposedEntity.js.map