@specverse/engines 4.3.2 → 4.3.3

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.
@@ -30,6 +30,19 @@ function generateApiClient(pattern) {
30
30
 
31
31
  import type { ApiResponse, RuntimeInfo, ModelSchema, View, Service, Behavior, Entity, Operation } from '../types/api';
32
32
 
33
+ /**
34
+ * English pluralizer, matches \`deriveBasePath\` in
35
+ * @specverse/types/spec-rules \u2014 the source of truth for how the
36
+ * backend pluralizes REST paths. Keep in sync: Category \u2192
37
+ * categories (not \`categorys\`), Box \u2192 boxes, Child \u2192 childs (yes,
38
+ * naive \u2014 irregulars not supported).
39
+ */
40
+ function pluralize(s: string): string {
41
+ if (/[^aeiou]y$/i.test(s)) return s.slice(0, -1) + 'ies';
42
+ if (/(s|x|z|ch|sh)$/i.test(s)) return s + 'es';
43
+ return s + 's';
44
+ }
45
+
33
46
  // Get API URL from query parameter or default to proxy
34
47
  const urlParams = new URLSearchParams(window.location.search);
35
48
  const apiUrl = urlParams.get('api');
@@ -225,10 +238,8 @@ export async function getModelSchema(modelName: string): Promise<ModelSchema> {
225
238
  */
226
239
  export async function listEntities(controllerName: string): Promise<Entity[]> {
227
240
  try {
228
- // Extract model name from controller name (e.g., "AuthorController" -> "authors")
229
- const modelName = controllerName
230
- .replace(/Controller$/, '')
231
- .toLowerCase() + 's';
241
+ // Derive REST path \u2014 matches \`deriveBasePath\` in the backend.
242
+ const modelName = pluralize(controllerName.replace(/Controller$/, '').toLowerCase());
232
243
 
233
244
  const entities = await apiRequest<Entity[]>('GET', \`/\${modelName}\`);
234
245
  return entities || [];
@@ -247,10 +258,8 @@ export async function executeOperation(
247
258
  operationName: string,
248
259
  params: Record<string, any>
249
260
  ): Promise<ApiResponse> {
250
- // Extract model name from controller name (e.g., "AuthorController" -> "authors")
251
- const modelName = controllerName
252
- .replace(/Controller$/, '')
253
- .toLowerCase() + 's';
261
+ // Derive REST path \u2014 matches \`deriveBasePath\` in the backend.
262
+ const modelName = pluralize(controllerName.replace(/Controller$/, '').toLowerCase());
254
263
 
255
264
  let method: string;
256
265
  let path: string;
@@ -344,7 +353,7 @@ export async function transitionState(
344
353
  body.lifecycleName = lifecycleName;
345
354
  }
346
355
 
347
- const resource = modelName.toLowerCase() + 's';
356
+ const resource = pluralize(modelName.toLowerCase());
348
357
  return apiRequest<ApiResponse>(
349
358
  'PATCH',
350
359
  \`/\${resource}/\${entityId}/evolve\`,
@@ -360,7 +369,7 @@ export async function getAvailableTransitions(
360
369
  entityId: string,
361
370
  lifecycleName?: string
362
371
  ): Promise<string[]> {
363
- const resource = modelName.toLowerCase() + 's';
372
+ const resource = pluralize(modelName.toLowerCase());
364
373
  const url = lifecycleName
365
374
  ? \`/\${resource}/\${entityId}/transitions?lifecycleName=\${lifecycleName}\`
366
375
  : \`/\${resource}/\${entityId}/transitions\`;
@@ -453,7 +462,7 @@ export async function transitionState(
453
462
  body.lifecycleName = lifecycleName;
454
463
  }
455
464
 
456
- const resource = modelName.toLowerCase() + 's';
465
+ const resource = pluralize(modelName.toLowerCase());
457
466
  return apiRequest<ApiResponse>(
458
467
  'PATCH',
459
468
  \`/\${resource}/\${entityId}/evolve\`,
@@ -63,6 +63,19 @@ function generateApiClient(pattern: 'rest' | 'controller'): string {
63
63
 
64
64
  import type { ApiResponse, RuntimeInfo, ModelSchema, View, Service, Behavior, Entity, Operation } from '../types/api';
65
65
 
66
+ /**
67
+ * English pluralizer, matches \`deriveBasePath\` in
68
+ * @specverse/types/spec-rules — the source of truth for how the
69
+ * backend pluralizes REST paths. Keep in sync: Category →
70
+ * categories (not \`categorys\`), Box → boxes, Child → childs (yes,
71
+ * naive — irregulars not supported).
72
+ */
73
+ function pluralize(s: string): string {
74
+ if (/[^aeiou]y$/i.test(s)) return s.slice(0, -1) + 'ies';
75
+ if (/(s|x|z|ch|sh)$/i.test(s)) return s + 'es';
76
+ return s + 's';
77
+ }
78
+
66
79
  // Get API URL from query parameter or default to proxy
67
80
  const urlParams = new URLSearchParams(window.location.search);
68
81
  const apiUrl = urlParams.get('api');
@@ -261,10 +274,8 @@ export async function getModelSchema(modelName: string): Promise<ModelSchema> {
261
274
  */
262
275
  export async function listEntities(controllerName: string): Promise<Entity[]> {
263
276
  try {
264
- // Extract model name from controller name (e.g., "AuthorController" -> "authors")
265
- const modelName = controllerName
266
- .replace(/Controller$/, '')
267
- .toLowerCase() + 's';
277
+ // Derive REST path matches \`deriveBasePath\` in the backend.
278
+ const modelName = pluralize(controllerName.replace(/Controller$/, '').toLowerCase());
268
279
 
269
280
  const entities = await apiRequest<Entity[]>('GET', \`/\${modelName}\`);
270
281
  return entities || [];
@@ -283,10 +294,8 @@ export async function executeOperation(
283
294
  operationName: string,
284
295
  params: Record<string, any>
285
296
  ): Promise<ApiResponse> {
286
- // Extract model name from controller name (e.g., "AuthorController" -> "authors")
287
- const modelName = controllerName
288
- .replace(/Controller$/, '')
289
- .toLowerCase() + 's';
297
+ // Derive REST path matches \`deriveBasePath\` in the backend.
298
+ const modelName = pluralize(controllerName.replace(/Controller$/, '').toLowerCase());
290
299
 
291
300
  let method: string;
292
301
  let path: string;
@@ -380,7 +389,7 @@ export async function transitionState(
380
389
  body.lifecycleName = lifecycleName;
381
390
  }
382
391
 
383
- const resource = modelName.toLowerCase() + 's';
392
+ const resource = pluralize(modelName.toLowerCase());
384
393
  return apiRequest<ApiResponse>(
385
394
  'PATCH',
386
395
  \`/\${resource}/\${entityId}/evolve\`,
@@ -396,7 +405,7 @@ export async function getAvailableTransitions(
396
405
  entityId: string,
397
406
  lifecycleName?: string
398
407
  ): Promise<string[]> {
399
- const resource = modelName.toLowerCase() + 's';
408
+ const resource = pluralize(modelName.toLowerCase());
400
409
  const url = lifecycleName
401
410
  ? \`/\${resource}/\${entityId}/transitions?lifecycleName=\${lifecycleName}\`
402
411
  : \`/\${resource}/\${entityId}/transitions\`;
@@ -490,7 +499,7 @@ export async function transitionState(
490
499
  body.lifecycleName = lifecycleName;
491
500
  }
492
501
 
493
- const resource = modelName.toLowerCase() + 's';
502
+ const resource = pluralize(modelName.toLowerCase());
494
503
  return apiRequest<ApiResponse>(
495
504
  'PATCH',
496
505
  \`/\${resource}/\${entityId}/evolve\`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specverse/engines",
3
- "version": "4.3.2",
3
+ "version": "4.3.3",
4
4
  "description": "SpecVerse toolchain — parser, inference, realize, generators, AI, registry",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",