@voxgig/apidef 5.11.0 → 6.0.0

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.
@@ -9,6 +9,16 @@ const entityTransform = async function (ctx) {
9
9
  const { apimodel, guide } = ctx;
10
10
  const kit = apimodel.main[types_1.KIT];
11
11
  let msg = '';
12
+ // Pre-pass: merge collection paths into the entity that owns the
13
+ // per-instance paths. Heuristic01 sometimes assigns "/people" to a
14
+ // separate "*_search" entity (because the response wraps Person in
15
+ // a search/pagination component) while "/people/{id}" and
16
+ // "/people/{id}/anime" land on "person". Result: the person entity has
17
+ // no primary list endpoint, so direct-load tests can't bootstrap an
18
+ // ID. Move "/people" onto person here; this also clears the way for
19
+ // sensible flow generation (one entity, one collection, multiple
20
+ // sub-resources).
21
+ mergeCollectionPaths(guide, ctx.log);
12
22
  (0, jostraca_1.each)(guide.entity, (guideEntity, entname) => {
13
23
  ctx.log.debug({ point: 'guide-entity', note: entname });
14
24
  const paths$ = resolvePathList(guideEntity, ctx.def);
@@ -25,6 +35,76 @@ const entityTransform = async function (ctx) {
25
35
  return { ok: true, msg };
26
36
  };
27
37
  exports.entityTransform = entityTransform;
38
+ // Move "/X" paths onto the entity that owns "/X/{id}" or "/X/{id}/sub".
39
+ // Only acts when the path "/X" sits on a different entity than the
40
+ // per-instance paths — leaves correctly-classified APIs alone.
41
+ function mergeCollectionPaths(guide, log) {
42
+ const entities = guide.entity;
43
+ // First pass: build collectionRoot -> owner-entity-name map.
44
+ // owner is the entity whose name contains "/X/{...}" paths; we prefer
45
+ // the owner whose direct-load path is "/X/{id}" (no further segments)
46
+ // so that nested-resource entities don't claim the root.
47
+ const rootOwners = {};
48
+ for (const [ename, entity] of Object.entries(entities)) {
49
+ for (const pathStr of Object.keys(entity.path ?? {})) {
50
+ // Match /A/{...} or /A/{...}/...
51
+ const m = pathStr.match(/^\/([^\/{}]+)\/\{[^}]+\}(\/.*)?$/);
52
+ if (!m)
53
+ continue;
54
+ const root = m[1];
55
+ const trailing = m[2] ?? '';
56
+ // Depth = number of segments after the {id} placeholder. Lower
57
+ // depth wins (e.g. "/people/{id}" beats "/people/{id}/anime").
58
+ const depth = trailing === '' ? 0 : trailing.split('/').filter(Boolean).length;
59
+ const cur = rootOwners[root];
60
+ if (!cur || depth < cur.depth) {
61
+ rootOwners[root] = { ename, depth };
62
+ }
63
+ }
64
+ }
65
+ // Second pass: for each entity with a "/X" path, if X has an owner
66
+ // elsewhere, move the path there.
67
+ for (const [ename, entity] of Object.entries(entities)) {
68
+ if (entity.path == null)
69
+ continue;
70
+ const pathsToMove = [];
71
+ for (const pathStr of Object.keys(entity.path)) {
72
+ // Match exactly /X (one literal segment, no params).
73
+ const m = pathStr.match(/^\/([^\/{}]+)$/);
74
+ if (!m)
75
+ continue;
76
+ const root = m[1];
77
+ const owner = rootOwners[root];
78
+ if (owner && owner.ename !== ename) {
79
+ pathsToMove.push(pathStr);
80
+ }
81
+ }
82
+ for (const pathStr of pathsToMove) {
83
+ const owner = rootOwners[pathStr.slice(1)];
84
+ const targetEntity = entities[owner.ename];
85
+ if (targetEntity == null)
86
+ continue;
87
+ targetEntity.path = targetEntity.path ?? {};
88
+ // If the target already has this path (unlikely), leave it alone.
89
+ if (targetEntity.path[pathStr] == null) {
90
+ targetEntity.path[pathStr] = entity.path[pathStr];
91
+ }
92
+ delete entity.path[pathStr];
93
+ log?.debug?.({
94
+ point: 'merge-collection-path',
95
+ path: pathStr,
96
+ from: ename,
97
+ to: owner.ename,
98
+ });
99
+ }
100
+ }
101
+ // Drop entities that are now empty after the merge.
102
+ for (const ename of Object.keys(entities)) {
103
+ if (entities[ename].path == null || Object.keys(entities[ename].path).length === 0) {
104
+ delete entities[ename];
105
+ }
106
+ }
107
+ }
28
108
  function resolvePathList(guideEntity, def) {
29
109
  const paths$ = [];
30
110
  (0, jostraca_1.each)(guideEntity.path, (guidePath, orig) => {
@@ -49,9 +129,17 @@ function resolvePathList(guideEntity, def) {
49
129
  return paths$;
50
130
  }
51
131
  function buildRelations(guideEntity, paths$) {
132
+ // An ancestor is a literal collection segment (e.g. "rems") followed by
133
+ // a path-param placeholder that names an instance ID. We only collect
134
+ // the literal parts — placeholder parts like "{año}" must be excluded
135
+ // even when they're themselves followed by another placeholder, otherwise
136
+ // downstream code treats `{año}` as an ancestor name and emits broken
137
+ // idmap entries / match keys.
52
138
  let ancestors = paths$
53
139
  .map(pli => pli.parts
54
- .map((p, i) => (pli.parts[i + 1]?.[0] === '{' && pli.parts[i + 1] !== '{id}') ? p : null)
140
+ .map((p, i) => ('{' !== p[0] &&
141
+ pli.parts[i + 1]?.[0] === '{' &&
142
+ pli.parts[i + 1] !== '{id}') ? p : null)
55
143
  .filter(p => null != p))
56
144
  .filter(n => 0 < n.length)
57
145
  .sort((a, b) => a.length - b.length);
@@ -1 +1 @@
1
- {"version":3,"file":"entity.js","sourceRoot":"","sources":["../../src/transform/entity.ts"],"names":[],"mappings":";;;AA+HE,0CAAe;AACf,wCAAc;AA9HhB,uCAA+B;AAI/B,oCAA8B;AAmB9B,MAAM,eAAe,GAAc,KAAK,WACtC,GAAQ;IAER,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,GAAG,CAAA;IAC/B,MAAM,GAAG,GAAa,QAAQ,CAAC,IAAI,CAAC,WAAG,CAAC,CAAA;IAExC,IAAI,GAAG,GAAG,EAAE,CAAA;IAEZ,IAAA,eAAI,EAAC,KAAK,CAAC,MAAM,EAAE,CAAC,WAAwB,EAAE,OAAe,EAAE,EAAE;QAC/D,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;QAEvD,MAAM,MAAM,GAAG,eAAe,CAAC,WAAW,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;QACpD,MAAM,SAAS,GAAG,cAAc,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;QAErD,MAAM,QAAQ,GAAgB;YAC5B,IAAI,EAAE,OAAO;YACb,EAAE,EAAE,EAAE;YACN,MAAM,EAAE,EAAE;YACV,SAAS;SACV,CAAA;QAED,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAA;QAE9B,GAAG,IAAI,WAAW,CAAC,IAAI,GAAG,GAAG,CAAA;IAC/B,CAAC,CAAC,CAAA;IAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAA;AAC1B,CAAC,CAAA;AA6EC,0CAAe;AAzEjB,SAAS,eAAe,CAAC,WAAwB,EAAE,GAAmC;IACpF,MAAM,MAAM,GAAe,EAAE,CAAA;IAE7B,IAAA,eAAI,EAAC,WAAW,CAAC,IAAI,EAAE,CAAC,SAAoB,EAAE,IAAY,EAAE,EAAE;QAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QAClD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,EAAE,CAAA;QAErC,IAAA,eAAI,EAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAU,EAAE,EAAE;YAChC,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,CAAA;YAChD,IAAI,EAAE,IAAI,CAAC;gBAAE,KAAK,CAAC,EAAE,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,GAAG,CAAA;QACjD,CAAC,CAAC,CAAA;QAEF,MAAM,QAAQ,GAAa;YACzB,IAAI;YACJ,KAAK;YACL,MAAM;YACN,MAAM,EAAE,EAAE,EAAE,mDAAmD;YAC/D,EAAE,EAAE,SAAS,CAAC,EAAE;YAChB,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;SACrB,CAAA;QAED,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACvB,CAAC,CAAC,CAEC;IAAE,WAAmB,CAAC,MAAM,GAAG,MAAM,CAAA;IAExC,OAAO,MAAM,CAAA;AACf,CAAC;AAID,SAAS,cAAc,CAAC,WAAgB,EAAE,MAAkB;IAC1D,IAAI,SAAS,GAAU,MAAM;SAC1B,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK;SAClB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACZ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;SAC3E,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;SACzB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;SACzB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAA;IAEtC,mFAAmF;IACnF,SAAS,GAAG,SAAS;SAClB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACf,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAA;QAC3C,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CAAA;IAEJ,MAAM,SAAS,GAAG;QAChB,SAAS;KACV,CAAA;IAED,WAAW,CAAC,UAAU,GAAG,SAAS,CAAA;IAElC,OAAO,SAAS,CAAA;AAClB,CAAC;AAGD,0CAA0C;AAC1C,SAAS,MAAM,CAAC,CAAW,EAAE,CAAW;IACtC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC;YAAE,OAAO,KAAK,CAAA;IAC/D,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"entity.js","sourceRoot":"","sources":["../../src/transform/entity.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,14 @@ const topTransform = async function (ctx) {
24
24
  url: (def.schemes?.[0] ?? 'https') + '://' + (0, struct_1.join)([def.host, def.basePath], '/', true)
25
25
  });
26
26
  }
27
+ // A usable SDK requires a base URL. OpenAPI 3 puts it in `servers[].url`;
28
+ // Swagger 2 derives it from `host` + `basePath`. If neither yields a
29
+ // non-empty url, the generated SDK has no way to issue requests, so fail
30
+ // the apidef model build rather than emit broken code.
31
+ const firstServerUrl = kit.info.servers?.[0]?.url;
32
+ if (null == firstServerUrl || '' === String(firstServerUrl).trim()) {
33
+ throw new Error('apidef: no server URL found in API definition (servers[0].url is required).');
34
+ }
27
35
  return { ok: true, msg: 'top' };
28
36
  };
29
37
  exports.topTransform = topTransform;
@@ -1 +1 @@
1
- {"version":3,"file":"top.js","sourceRoot":"","sources":["../../src/transform/top.ts"],"names":[],"mappings":";;;AACA,2CAAqC;AAErC,oCAA8B;AAmB9B,6BAA6B;AAC7B,yCAAyC;AACzC,8BAA8B;AAC9B,gCAAgC;AAGhC,uBAAuB;AACvB,kBAAkB;AAClB,oCAAoC;AAEpC,uBAAuB;AACvB,iCAAiC;AACjC,IAAI;AAGJ,MAAM,YAAY,GAAG,KAAK,WACxB,GAAQ;IAER,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,CAAA;IAC7B,MAAM,GAAG,GAAa,QAAQ,CAAC,IAAI,CAAC,WAAG,CAAC,CAAA;IAExC,GAAG,CAAC,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;IAC/C,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAA;IAE1D,cAAc;IACd,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACb,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YACpB,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,KAAK,GAAG,IAAA,aAAI,EAAC,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC;SACvF,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAA;AACjC,CAAC,CAAA;AA4BC,oCAAY;AAzBd,0EAA0E;AAC1E,wEAAwE;AACxE,uEAAuE;AACvE,oEAAoE;AACpE,sEAAsE;AACtE,oEAAoE;AACpE,sDAAsD;AACtD,SAAS,oBAAoB,CAAC,IAAS;IACrC,IAAI,IAAI,IAAI,IAAI;QAAE,OAAO,IAAI,CAAA;IAC7B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;IAC9D,IAAI,QAAQ,KAAK,OAAO,IAAI,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAwB,EAAE,CAAA;QACnC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,GAAG,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAA;QAClC,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO,IAAI,IAAI,SAAS,KAAK,OAAO,IAAI,EAAE,CAAC;QAC1D,OAAO,MAAM,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAQD,gBAAgB;AAChB,iBAAiB;AACjB,IAAI"}
1
+ {"version":3,"file":"top.js","sourceRoot":"","sources":["../../src/transform/top.ts"],"names":[],"mappings":";;;AACA,2CAAqC;AAErC,oCAA8B;AAmB9B,6BAA6B;AAC7B,yCAAyC;AACzC,8BAA8B;AAC9B,gCAAgC;AAGhC,uBAAuB;AACvB,kBAAkB;AAClB,oCAAoC;AAEpC,uBAAuB;AACvB,iCAAiC;AACjC,IAAI;AAGJ,MAAM,YAAY,GAAG,KAAK,WACxB,GAAQ;IAER,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,CAAA;IAC7B,MAAM,GAAG,GAAa,QAAQ,CAAC,IAAI,CAAC,WAAG,CAAC,CAAA;IAExC,GAAG,CAAC,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;IAC/C,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAA;IAE1D,cAAc;IACd,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACb,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YACpB,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,KAAK,GAAG,IAAA,aAAI,EAAC,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC;SACvF,CAAC,CAAA;IACJ,CAAC;IAED,0EAA0E;IAC1E,qEAAqE;IACrE,yEAAyE;IACzE,uDAAuD;IACvD,MAAM,cAAc,GAAQ,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAA;IACtD,IAAI,IAAI,IAAI,cAAc,IAAI,EAAE,KAAK,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CACb,6EAA6E,CAC9E,CAAA;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAA;AACjC,CAAC,CAAA;AA4BC,oCAAY;AAzBd,0EAA0E;AAC1E,wEAAwE;AACxE,uEAAuE;AACvE,oEAAoE;AACpE,sEAAsE;AACtE,oEAAoE;AACpE,sDAAsD;AACtD,SAAS,oBAAoB,CAAC,IAAS;IACrC,IAAI,IAAI,IAAI,IAAI;QAAE,OAAO,IAAI,CAAA;IAC7B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;IAC9D,IAAI,QAAQ,KAAK,OAAO,IAAI,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAwB,EAAE,CAAA;QACnC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,GAAG,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAA;QAClC,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO,IAAI,IAAI,SAAS,KAAK,OAAO,IAAI,EAAE,CAAC;QAC1D,OAAO,MAAM,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAQD,gBAAgB;AAChB,iBAAiB;AACjB,IAAI"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/apidef",
3
- "version": "5.11.0",
3
+ "version": "6.0.0",
4
4
  "main": "dist/apidef.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/apidef.d.ts",
@@ -40,7 +40,7 @@
40
40
  "LICENSE"
41
41
  ],
42
42
  "devDependencies": {
43
- "@types/node": "25.6.0",
43
+ "@types/node": "25.6.2",
44
44
  "typescript": "6.0.3"
45
45
  },
46
46
  "peerDependencies": {
@@ -31,6 +31,17 @@ const entityTransform: Transform = async function(
31
31
 
32
32
  let msg = ''
33
33
 
34
+ // Pre-pass: merge collection paths into the entity that owns the
35
+ // per-instance paths. Heuristic01 sometimes assigns "/people" to a
36
+ // separate "*_search" entity (because the response wraps Person in
37
+ // a search/pagination component) while "/people/{id}" and
38
+ // "/people/{id}/anime" land on "person". Result: the person entity has
39
+ // no primary list endpoint, so direct-load tests can't bootstrap an
40
+ // ID. Move "/people" onto person here; this also clears the way for
41
+ // sensible flow generation (one entity, one collection, multiple
42
+ // sub-resources).
43
+ mergeCollectionPaths(guide, ctx.log)
44
+
34
45
  each(guide.entity, (guideEntity: GuideEntity, entname: string) => {
35
46
  ctx.log.debug({ point: 'guide-entity', note: entname })
36
47
 
@@ -53,6 +64,81 @@ const entityTransform: Transform = async function(
53
64
  }
54
65
 
55
66
 
67
+ // Move "/X" paths onto the entity that owns "/X/{id}" or "/X/{id}/sub".
68
+ // Only acts when the path "/X" sits on a different entity than the
69
+ // per-instance paths — leaves correctly-classified APIs alone.
70
+ function mergeCollectionPaths(guide: any, log?: any) {
71
+ const entities = guide.entity as Record<string, any>
72
+
73
+ // First pass: build collectionRoot -> owner-entity-name map.
74
+ // owner is the entity whose name contains "/X/{...}" paths; we prefer
75
+ // the owner whose direct-load path is "/X/{id}" (no further segments)
76
+ // so that nested-resource entities don't claim the root.
77
+ const rootOwners: Record<string, { ename: string, depth: number }> = {}
78
+
79
+ for (const [ename, entity] of Object.entries(entities)) {
80
+ for (const pathStr of Object.keys(entity.path ?? {})) {
81
+ // Match /A/{...} or /A/{...}/...
82
+ const m = pathStr.match(/^\/([^\/{}]+)\/\{[^}]+\}(\/.*)?$/)
83
+ if (!m) continue
84
+ const root = m[1]
85
+ const trailing = m[2] ?? ''
86
+ // Depth = number of segments after the {id} placeholder. Lower
87
+ // depth wins (e.g. "/people/{id}" beats "/people/{id}/anime").
88
+ const depth = trailing === '' ? 0 : trailing.split('/').filter(Boolean).length
89
+
90
+ const cur = rootOwners[root]
91
+ if (!cur || depth < cur.depth) {
92
+ rootOwners[root] = { ename, depth }
93
+ }
94
+ }
95
+ }
96
+
97
+ // Second pass: for each entity with a "/X" path, if X has an owner
98
+ // elsewhere, move the path there.
99
+ for (const [ename, entity] of Object.entries(entities)) {
100
+ if (entity.path == null) continue
101
+ const pathsToMove: string[] = []
102
+
103
+ for (const pathStr of Object.keys(entity.path)) {
104
+ // Match exactly /X (one literal segment, no params).
105
+ const m = pathStr.match(/^\/([^\/{}]+)$/)
106
+ if (!m) continue
107
+ const root = m[1]
108
+ const owner = rootOwners[root]
109
+ if (owner && owner.ename !== ename) {
110
+ pathsToMove.push(pathStr)
111
+ }
112
+ }
113
+
114
+ for (const pathStr of pathsToMove) {
115
+ const owner = rootOwners[pathStr.slice(1)]
116
+ const targetEntity = entities[owner.ename]
117
+ if (targetEntity == null) continue
118
+ targetEntity.path = targetEntity.path ?? {}
119
+ // If the target already has this path (unlikely), leave it alone.
120
+ if (targetEntity.path[pathStr] == null) {
121
+ targetEntity.path[pathStr] = entity.path[pathStr]
122
+ }
123
+ delete entity.path[pathStr]
124
+ log?.debug?.({
125
+ point: 'merge-collection-path',
126
+ path: pathStr,
127
+ from: ename,
128
+ to: owner.ename,
129
+ })
130
+ }
131
+ }
132
+
133
+ // Drop entities that are now empty after the merge.
134
+ for (const ename of Object.keys(entities)) {
135
+ if (entities[ename].path == null || Object.keys(entities[ename].path).length === 0) {
136
+ delete entities[ename]
137
+ }
138
+ }
139
+ }
140
+
141
+
56
142
 
57
143
  function resolvePathList(guideEntity: GuideEntity, def: { paths: Record<string, any> }) {
58
144
  const paths$: PathDesc[] = []
@@ -86,10 +172,18 @@ function resolvePathList(guideEntity: GuideEntity, def: { paths: Record<string,
86
172
 
87
173
 
88
174
  function buildRelations(guideEntity: any, paths$: PathDesc[]) {
175
+ // An ancestor is a literal collection segment (e.g. "rems") followed by
176
+ // a path-param placeholder that names an instance ID. We only collect
177
+ // the literal parts — placeholder parts like "{año}" must be excluded
178
+ // even when they're themselves followed by another placeholder, otherwise
179
+ // downstream code treats `{año}` as an ancestor name and emits broken
180
+ // idmap entries / match keys.
89
181
  let ancestors: any[] = paths$
90
182
  .map(pli => pli.parts
91
183
  .map((p, i) =>
92
- (pli.parts[i + 1]?.[0] === '{' && pli.parts[i + 1] !== '{id}') ? p : null)
184
+ ('{' !== p[0] &&
185
+ pli.parts[i + 1]?.[0] === '{' &&
186
+ pli.parts[i + 1] !== '{id}') ? p : null)
93
187
  .filter(p => null != p))
94
188
  .filter(n => 0 < n.length)
95
189
  .sort((a, b) => a.length - b.length)
@@ -51,6 +51,17 @@ const topTransform = async function(
51
51
  })
52
52
  }
53
53
 
54
+ // A usable SDK requires a base URL. OpenAPI 3 puts it in `servers[].url`;
55
+ // Swagger 2 derives it from `host` + `basePath`. If neither yields a
56
+ // non-empty url, the generated SDK has no way to issue requests, so fail
57
+ // the apidef model build rather than emit broken code.
58
+ const firstServerUrl: any = kit.info.servers?.[0]?.url
59
+ if (null == firstServerUrl || '' === String(firstServerUrl).trim()) {
60
+ throw new Error(
61
+ 'apidef: no server URL found in API definition (servers[0].url is required).'
62
+ )
63
+ }
64
+
54
65
  return { ok: true, msg: 'top' }
55
66
  }
56
67