@specverse/engines 4.3.1 → 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.
@@ -16,6 +16,10 @@
16
16
  * its row before asserting. Retrieve + validate fire implicitly and
17
17
  * have no user-facing button.
18
18
  *
19
+ * Models with required `belongsTo` relationships have their parents
20
+ * seeded first (recursively) and the parent's id threaded into the
21
+ * FK field via `$expr:varName.id`.
22
+ *
19
23
  * One TestCase per (controller, cured-operation) pair.
20
24
  */
21
25
  import type { UiContractRule } from '../test-case-types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"action-buttons-present.d.ts","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/action-buttons-present.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAgE,MAAM,uBAAuB,CAAC;AAE1H,eAAO,MAAM,OAAO,2BAA2B,CAAC;AAkEhD,eAAO,MAAM,oBAAoB,EAAE,cA0ClC,CAAC"}
1
+ {"version":3,"file":"action-buttons-present.d.ts","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/action-buttons-present.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAkF,MAAM,uBAAuB,CAAC;AAE5I,eAAO,MAAM,OAAO,2BAA2B,CAAC;AAqHhD,eAAO,MAAM,oBAAoB,EAAE,cAyClC,CAAC"}
@@ -16,6 +16,10 @@
16
16
  * its row before asserting. Retrieve + validate fire implicitly and
17
17
  * have no user-facing button.
18
18
  *
19
+ * Models with required `belongsTo` relationships have their parents
20
+ * seeded first (recursively) and the parent's id threaded into the
21
+ * FK field via `$expr:varName.id`.
22
+ *
19
23
  * One TestCase per (controller, cured-operation) pair.
20
24
  */
21
25
  export const RULE_ID = 'action-buttons-present';
@@ -30,15 +34,8 @@ const CURED_LABELS = new Set([
30
34
  /**
31
35
  * Build a minimal-but-valid body for createEntity against a model.
32
36
  * Covers required non-auto business attributes with type-based
33
- * defaults. Skips relationship/metadata fields and any attribute
34
- * that declares `auto` the backend is responsible for those.
35
- *
36
- * Models with required belongsTo relationships would need a parent
37
- * entity created first; this helper doesn't handle that, so tests
38
- * for such models' update/delete/evolve buttons may fail at the
39
- * createEntity step. That's a rule-design gap, tracked as future
40
- * work — the alternative (skipping the test) would silently lose
41
- * coverage.
37
+ * defaults. Skips metadata + relationship categories — relationship
38
+ * FK values are provided separately by the seed-chain builder.
42
39
  */
43
40
  function synthesizeBody(model) {
44
41
  const out = {};
@@ -78,12 +75,65 @@ function defaultValueForAttribute(attr) {
78
75
  case 'uuid':
79
76
  return '00000000-0000-0000-0000-000000000000';
80
77
  default:
81
- // String / Text / Email / Url / URL / arbitrary — use the
82
- // attribute name as a unique-ish marker so assertEntityInList
83
- // can match it later if the rule binds one.
84
78
  return `test-${attr.name}`;
85
79
  }
86
80
  }
81
+ /**
82
+ * Walk a model's belongsTo relationships and emit createEntity steps
83
+ * for every dependency, seeding leaves first. Each seed binds to a
84
+ * unique variable name; FK fields on downstream models reference
85
+ * those variables via `$expr:varName.id`.
86
+ *
87
+ * Returns the ordered step list + the variable name bound to the
88
+ * final (target) model.
89
+ *
90
+ * Cycles are guarded: if A → B → A, the second visit skips. Cyclic
91
+ * required-FK specs would be unsatisfiable anyway.
92
+ */
93
+ function buildSeedChain(model, modelByName) {
94
+ const steps = [];
95
+ const bindings = new Map(); // modelName -> varName
96
+ const inProgress = new Set();
97
+ function seed(m) {
98
+ const existing = bindings.get(m.name);
99
+ if (existing)
100
+ return existing;
101
+ if (inProgress.has(m.name)) {
102
+ // Cycle — fall back to bare body without the FK (will likely
103
+ // fail at the backend, but the test will surface the issue
104
+ // rather than the rule crashing).
105
+ return `seed_${m.name}`;
106
+ }
107
+ inProgress.add(m.name);
108
+ const body = synthesizeBody(m);
109
+ for (const rel of m.relationships || []) {
110
+ if (!isBelongsTo(rel))
111
+ continue;
112
+ if (!rel.target)
113
+ continue;
114
+ const parent = modelByName.get(rel.target);
115
+ if (!parent)
116
+ continue;
117
+ const parentVar = seed(parent);
118
+ body[`${rel.name}Id`] = `$expr:${parentVar}.id`;
119
+ }
120
+ const myVar = `seed_${m.name}`;
121
+ steps.push({
122
+ action: 'createEntity',
123
+ modelName: m.name,
124
+ data: body,
125
+ bindAs: myVar,
126
+ });
127
+ bindings.set(m.name, myVar);
128
+ inProgress.delete(m.name);
129
+ return myVar;
130
+ }
131
+ const targetVar = seed(model);
132
+ return { steps, targetVar };
133
+ }
134
+ function isBelongsTo(rel) {
135
+ return String(rel.type || '').toLowerCase() === 'belongsto';
136
+ }
87
137
  export const actionButtonsPresent = (spec) => {
88
138
  const cases = [];
89
139
  const modelByName = new Map();
@@ -95,24 +145,24 @@ export const actionButtonsPresent = (spec) => {
95
145
  continue;
96
146
  const modelName = controller.model || controller.name.replace(/Controller$/, '');
97
147
  const model = modelByName.get(modelName);
98
- const body = model ? synthesizeBody(model) : {};
148
+ if (!model)
149
+ continue;
99
150
  for (const opName of Object.keys(cured)) {
100
151
  if (!CURED_LABELS.has(opName))
101
152
  continue;
102
153
  if (opName === 'retrieve' || opName === 'validate')
103
154
  continue;
104
- // Navigate to form — the walker's form view renders Create
105
- // unconditionally and Update / Delete / Evolve once an entity
106
- // is selected.
107
155
  const steps = [
108
156
  { action: 'bootRuntime' },
109
157
  { action: 'navigateToModelForm', modelName },
110
158
  ];
111
159
  if (opName !== 'create') {
112
- // Seed an entity via API and click its row to put the form
113
- // into update mode, where Update / Delete / Evolve appear.
114
- steps.push({ action: 'createEntity', modelName, data: body, bindAs: 'seed' });
115
- steps.push({ action: 'selectEntityInList', idExpr: 'seed.id' });
160
+ // Seed an entity (plus any belongsTo parents) via API, then
161
+ // click its row to put the form into update mode where
162
+ // Update / Delete / Evolve render.
163
+ const { steps: seedSteps, targetVar } = buildSeedChain(model, modelByName);
164
+ steps.push(...seedSteps);
165
+ steps.push({ action: 'selectEntityInList', idExpr: `${targetVar}.id` });
116
166
  }
117
167
  steps.push({ action: 'expectActionButton', modelName, actionLabel: opName });
118
168
  cases.push({
@@ -1 +1 @@
1
- {"version":3,"file":"action-buttons-present.js","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/action-buttons-present.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"action-buttons-present.js","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/action-buttons-present.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../../../src/inference/ui-contracts/translator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,sBAAsB,CAAC;AA2G/D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CA6B5E"}
1
+ {"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../../../src/inference/ui-contracts/translator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,sBAAsB,CAAC;AA+H/D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CA6B5E"}
@@ -7,6 +7,21 @@
7
7
  * helpers. The translator is intentionally dumb: each step maps to a
8
8
  * helper call, nothing more.
9
9
  */
10
+ /**
11
+ * Render a `createEntity` data object. String values prefixed with
12
+ * `$expr:` emit as raw JS identifiers (e.g. `$expr:p.id` → `p.id`),
13
+ * so rules can thread bound variables from earlier steps into FK
14
+ * fields. Everything else round-trips through JSON.stringify.
15
+ */
16
+ function renderDataLit(data) {
17
+ const parts = Object.entries(data).map(([k, v]) => {
18
+ if (typeof v === 'string' && v.startsWith('$expr:')) {
19
+ return `${JSON.stringify(k)}: ${v.slice(6)}`;
20
+ }
21
+ return `${JSON.stringify(k)}: ${JSON.stringify(v)}`;
22
+ });
23
+ return `{ ${parts.join(', ')} }`;
24
+ }
10
25
  /**
11
26
  * Render a single step as a line of test body code.
12
27
  */
@@ -40,7 +55,11 @@ function renderStep(step) {
40
55
  return ` await expectActionButton(page, ${JSON.stringify(step.modelName)}, ${JSON.stringify(step.actionLabel)});`;
41
56
  case 'createEntity': {
42
57
  const varName = step.bindAs || 'created';
43
- const dataLit = JSON.stringify(step.data);
58
+ // String values prefixed with `$expr:` are emitted as raw
59
+ // identifier expressions (not JSON literals). Lets rules
60
+ // reference other bound variables — e.g. pollId = parentPoll.id
61
+ // when seeding Option after Poll.
62
+ const dataLit = renderDataLit(step.data);
44
63
  const optsLit = step.uniqueField
45
64
  ? `, { uniqueField: ${JSON.stringify(step.uniqueField)} }`
46
65
  : '';
@@ -1 +1 @@
1
- {"version":3,"file":"translator.js","sourceRoot":"","sources":["../../../src/inference/ui-contracts/translator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"translator.js","sourceRoot":"","sources":["../../../src/inference/ui-contracts/translator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;;;;GAKG;AACH,SAAS,aAAa,CAAC,IAA6B;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;QAChD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,CAAC;QACD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,SAAS,UAAU,CAAC,IAAc;IAChC,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,aAAa;YAChB,+DAA+D;YAC/D,iEAAiE;YACjE,OAAO,gDAAgD,CAAC;QAC1D,KAAK,mBAAmB;YACtB,OAAO,mCAAmC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAC/E,KAAK,iBAAiB;YACpB,OAAO,iCAAiC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAC7E,KAAK,qBAAqB;YACxB,OAAO,qCAAqC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QACjF,KAAK,uBAAuB;YAC1B,OAAO,uCAAuC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QACnF,KAAK,mBAAmB;YACtB,OAAO,kCAAkC,CAAC;QAC5C,KAAK,kBAAkB;YACrB,OAAO,kCAAkC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;QACrH,KAAK,iBAAiB,CAAC,CAAC,CAAC;YACvB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,OAAO,iCAAiC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,IAAI,IAAI,CAAC;QAC3H,CAAC;QACD,KAAK,sBAAsB;YACzB,OAAO,sCAAsC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QACrH,KAAK,2BAA2B;YAC9B,OAAO,2CAA2C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;QACjI,KAAK,oBAAoB;YACvB,OAAO,oCAAoC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QACrH,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC;YACzC,0DAA0D;YAC1D,yDAAyD;YACzD,gEAAgE;YAChE,kCAAkC;YAClC,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW;gBAC9B,CAAC,CAAC,oBAAoB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI;gBAC1D,CAAC,CAAC,EAAE,CAAC;YACP,OAAO,WAAW,OAAO,+BAA+B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,OAAO,GAAG,OAAO,IAAI,CAAC;QACnH,CAAC;QACD,KAAK,oBAAoB;YACvB,OAAO,oCAAoC,IAAI,CAAC,MAAM,IAAI,CAAC;QAC7D,KAAK,cAAc;YACjB,OAAO,8BAA8B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC;QAC1F,KAAK,cAAc;YACjB,OAAO,8BAA8B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAC/H,KAAK,oBAAoB,CAAC,CAAC,CAAC;YAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;YAC5E,OAAO,oCAAoC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,OAAO,IAAI,CAAC;QAC5F,CAAC;QACD,KAAK,oBAAoB,CAAC,CAAC,CAAC;YAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;YAC5E,OAAO,oCAAoC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,OAAO,IAAI,CAAC;QAC5F,CAAC;QACD,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,IAAI,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,IAAc;IAChC,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,aAAa;YAChB,OAAO,CAAC,aAAa,CAAC,CAAC;QACzB,KAAK,mBAAmB;YACtB,OAAO,CAAC,mBAAmB,CAAC,CAAC;QAC/B,KAAK,iBAAiB;YACpB,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC7B,KAAK,qBAAqB;YACxB,OAAO,CAAC,qBAAqB,CAAC,CAAC;QACjC,KAAK,uBAAuB;YAC1B,OAAO,CAAC,uBAAuB,CAAC,CAAC;QACnC,KAAK,mBAAmB;YACtB,OAAO,CAAC,mBAAmB,CAAC,CAAC;QAC/B,KAAK,kBAAkB;YACrB,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC9B,KAAK,iBAAiB;YACpB,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC7B,KAAK,sBAAsB;YACzB,OAAO,CAAC,sBAAsB,CAAC,CAAC;QAClC,KAAK,2BAA2B;YAC9B,OAAO,CAAC,2BAA2B,CAAC,CAAC;QACvC,KAAK,oBAAoB;YACvB,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAChC,KAAK,cAAc;YACjB,OAAO,CAAC,cAAc,CAAC,CAAC;QAC1B,KAAK,oBAAoB;YACvB,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAChC,KAAK,cAAc;YACjB,OAAO,CAAC,cAAc,CAAC,CAAC;QAC1B,KAAK,cAAc;YACjB,OAAO,CAAC,cAAc,CAAC,CAAC;QAC1B,KAAK,oBAAoB;YACvB,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAChC,KAAK,oBAAoB;YACvB,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAChC;YACE,OAAO,EAAE,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,SAAqB;IAClE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3B,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YAC5B,KAAK,MAAM,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IACD,MAAM,UAAU,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAElD,MAAM,MAAM,GAAG;;WAEN,MAAM;;;;;;WAMN,UAAU;CACpB,CAAC;IAEA,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QAC9B,MAAM,SAAS,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,OAAO;OACJ,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC;EAC5B,SAAS;IACP,CAAC;IACH,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;AAC9B,CAAC"}
@@ -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.1",
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",