@voxgig/apidef 8.11.0 → 8.12.1

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.
Files changed (79) hide show
  1. package/dist/apidef.d.ts +4 -1
  2. package/dist/apidef.js +12 -34
  3. package/dist/apidef.js.map +1 -1
  4. package/dist/builder/entity/entity.js +0 -33
  5. package/dist/builder/entity/entity.js.map +1 -1
  6. package/dist/builder/entity/info.js +0 -4
  7. package/dist/builder/entity/info.js.map +1 -1
  8. package/dist/builder/flow.d.ts +4 -1
  9. package/dist/builder/flow.js +36 -27
  10. package/dist/builder/flow.js.map +1 -1
  11. package/dist/guide/graphql01.js +0 -55
  12. package/dist/guide/graphql01.js.map +1 -1
  13. package/dist/guide/guide.js +3 -126
  14. package/dist/guide/guide.js.map +1 -1
  15. package/dist/guide/heuristic01.js +40 -181
  16. package/dist/guide/heuristic01.js.map +1 -1
  17. package/dist/parse/graphql.js +0 -25
  18. package/dist/parse/graphql.js.map +1 -1
  19. package/dist/parse.js +51 -56
  20. package/dist/parse.js.map +1 -1
  21. package/dist/resolved.d.ts +23 -0
  22. package/dist/resolved.js +92 -0
  23. package/dist/resolved.js.map +1 -0
  24. package/dist/resolver.js +0 -2
  25. package/dist/resolver.js.map +1 -1
  26. package/dist/transform/args.js +2 -25
  27. package/dist/transform/args.js.map +1 -1
  28. package/dist/transform/casecollide.d.ts +3 -0
  29. package/dist/transform/casecollide.js +67 -0
  30. package/dist/transform/casecollide.js.map +1 -0
  31. package/dist/transform/contract.js +43 -38
  32. package/dist/transform/contract.js.map +1 -1
  33. package/dist/transform/entity.js +0 -56
  34. package/dist/transform/entity.js.map +1 -1
  35. package/dist/transform/field.js +1 -368
  36. package/dist/transform/field.js.map +1 -1
  37. package/dist/transform/flowstep.js +0 -49
  38. package/dist/transform/flowstep.js.map +1 -1
  39. package/dist/transform/graphql.js +0 -36
  40. package/dist/transform/graphql.js.map +1 -1
  41. package/dist/transform/operation.js +0 -23
  42. package/dist/transform/operation.js.map +1 -1
  43. package/dist/transform/top.js +1 -128
  44. package/dist/transform/top.js.map +1 -1
  45. package/dist/transform.js.map +1 -1
  46. package/dist/tsconfig.tsbuildinfo +1 -1
  47. package/dist/types.d.ts +2 -0
  48. package/dist/types.js.map +1 -1
  49. package/dist/utility.d.ts +2 -1
  50. package/dist/utility.js +17 -316
  51. package/dist/utility.js.map +1 -1
  52. package/model/apidef.aon +20 -113
  53. package/model/guide.aon +1 -32
  54. package/package.json +5 -5
  55. package/src/apidef.ts +22 -33
  56. package/src/builder/entity/entity.ts +0 -33
  57. package/src/builder/entity/info.ts +0 -4
  58. package/src/builder/flow.ts +39 -24
  59. package/src/desc.ts +0 -22
  60. package/src/guide/graphql01.ts +0 -75
  61. package/src/guide/guide.ts +5 -127
  62. package/src/guide/heuristic01.ts +47 -182
  63. package/src/model.ts +6 -85
  64. package/src/parse/graphql.ts +0 -25
  65. package/src/parse.ts +61 -56
  66. package/src/resolved.ts +136 -0
  67. package/src/resolver.ts +0 -2
  68. package/src/transform/args.ts +2 -25
  69. package/src/transform/casecollide.ts +80 -0
  70. package/src/transform/contract.ts +44 -34
  71. package/src/transform/entity.ts +0 -56
  72. package/src/transform/field.ts +2 -368
  73. package/src/transform/flowstep.ts +0 -49
  74. package/src/transform/graphql.ts +0 -36
  75. package/src/transform/operation.ts +0 -23
  76. package/src/transform/top.ts +1 -131
  77. package/src/transform.ts +0 -1
  78. package/src/types.ts +4 -13
  79. package/src/utility.ts +18 -314
@@ -39,7 +39,6 @@ const METHOD_CONSIDER_ORDER = {
39
39
  'OPTIONS': 700,
40
40
  };
41
41
  async function heuristic01(ctx) {
42
- // TODO: Ordu needs better debug output to track task exec
43
42
  const analysis = new ordu_1.Ordu({ select: { sort: true } }).add([
44
43
  Prepare,
45
44
  {
@@ -68,12 +67,6 @@ async function heuristic01(ctx) {
68
67
  throw result.err;
69
68
  }
70
69
  const guide = result.data.guide;
71
- // Reassign single-segment collection paths (e.g. "/people") onto the entity
72
- // that owns the per-instance path ("/people/{id}"). Heuristic discovery can
73
- // split the two when response schemas wrap the resource in a search/pagination
74
- // component. Running this here — before base-guide.aontu is serialised —
75
- // means the same merged layout flows into both guide-case (transformers off)
76
- // and model-case (transformers on), so they agree on the final guide.
77
70
  (0, entity_1.mergeCollectionPaths)(guide, ctx.log);
78
71
  const metrics = guide.metrics;
79
72
  const entities = Object.values(guide.entity);
@@ -138,7 +131,6 @@ function Prepare(spec) {
138
131
  function MeasurePath(spec) {
139
132
  const guide = spec.data.guide;
140
133
  const metrics = guide.metrics;
141
- // const pathstr = spec.node.key
142
134
  const pathdef = spec.node.val;
143
135
  metrics.count.path++;
144
136
  metrics.count.method += ((pathdef.get ? 1 : 0) +
@@ -154,7 +146,6 @@ function MeasurePath(spec) {
154
146
  function MeasureMethod(spec) {
155
147
  const guide = spec.data.guide;
156
148
  const metrics = guide.metrics;
157
- // const methodstr = spec.node.key
158
149
  const methoddef = spec.node.val;
159
150
  const pathtags = methoddef.tags;
160
151
  if (Array.isArray(pathtags)) {
@@ -206,30 +197,6 @@ function MeasureRef(spec) {
206
197
  }
207
198
  function selectAllMethods(_source, spec) {
208
199
  const ctx = spec.ctx;
209
- // const paths = ctx.def.paths
210
- /*
211
- let caught = capture(ctx.def, {
212
- paths:
213
- ['`$SELECT`', /.* /,
214
- ['`$SELECT`', /^get|post|put|patch|delete$/i,
215
- ['`$APPEND`', 'methods', {
216
- path: '`select$=key.paths`',
217
- method: { '`$UPPER`': '`$KEY`' },
218
- summary: '`.summary`',
219
- tags: '`.tags`',
220
- parameters: '`.parameters`',
221
- responses: '`.responses`',
222
- requestBody: '`.requestBody`'
223
- }]
224
- ]
225
- ]
226
- })
227
-
228
- // TODO: capture should return these empty objects
229
- caught = caught ?? {}
230
- caught.methods = caught.methods ?? []
231
-
232
- */
233
200
  let caught = { methods: [] };
234
201
  for (const [path, pdef] of (0, utility_2.sortedEntries)(ctx.def.paths)) {
235
202
  for (const [m, mdef] of (0, utility_2.sortedEntries)(pdef)) {
@@ -297,7 +264,6 @@ function ResolveEntityComponent(spec) {
297
264
  return xref;
298
265
  })
299
266
  .filter(xref => null != xref.cmp)
300
- // TODO: identify non - ent schemas
301
267
  .filter(xref => !xref.val.includes('Meta'));
302
268
  let cleanxrefs = cmpxrefs
303
269
  .map(xref => {
@@ -430,19 +396,6 @@ function ResolveEntityName(spec) {
430
396
  entname = 'entity' + work.entity.count.unresolved;
431
397
  }
432
398
  }
433
- // A PATH SEGMENT CARRIES NO WORD BOUNDARIES, and the component name does.
434
- //
435
- // `/openbanking/payeeverification` is one lowercase run, so `canonize` has
436
- // nothing to split on and the entity is named `payeeverification` ->
437
- // `Payeeverification`. The boundary was never missing: the response
438
- // component for that same operation is
439
- // `PayeeVerification.PayeeVerificationResult`, which canonizes to
440
- // `payee_verification_result`.
441
- //
442
- // Where the path wins over the component -- which is usually RIGHT, because
443
- // a widely-reused response schema makes a poor entity name -- the component
444
- // was being discarded whole, boundaries and all. Borrow just the boundaries
445
- // back. See resplitFromCmp for why this cannot invent a name.
446
399
  entname = (0, utility_2.resplitFromCmp)(entname, ment.cmp, why_path);
447
400
  // Keep the pre-truncation name so a truncated-name collision can tell a
448
401
  // re-encounter of the SAME origin (merge) from a genuinely different one
@@ -475,7 +428,10 @@ function ResolveEntityName(spec) {
475
428
  work.pathowner = work.pathowner ?? {};
476
429
  work.pathowner[pathStr] = work.pathowner[pathStr] ?? {};
477
430
  work.pathowner[pathStr][methodName] = entname;
478
- (0, utility_2.debugpath)(pathStr, methodName, 'RESOLVE-ENTITY-NAME', (0, utility_2.formatJSONIC)({ entdesc, ment }, { hsepd: 0, $: true, color: true }));
431
+ // Same guard, same reason: the formatting is the cost, not the call.
432
+ if ((0, utility_2.debugpathOn)()) {
433
+ (0, utility_2.debugpath)(pathStr, methodName, 'RESOLVE-ENTITY-NAME', (0, utility_2.formatJSONIC)({ entdesc, ment }, { hsepd: 0, $: true, color: true }));
434
+ }
479
435
  }
480
436
  function RenameParams(spec) {
481
437
  const ctx = spec.ctx;
@@ -490,12 +446,6 @@ function RenameParams(spec) {
490
446
  const entdesc = work.entmap[entname];
491
447
  const pathdesc = spec.data.work.pathmap[pathStr];
492
448
  const methodName = mdesc.method;
493
- // Rewrite path parameters that are identifiers to follow the rules:
494
- // 0. Parameters named [a-z]?id are considered identifiers
495
- // 1. last identifier is always {id} as this is the primary entity
496
- // 2. internal identifiers are formatted as {name_id} where name is the parent entity name
497
- // Example: /api/bar/{id}/zed/{zid}/foo/{fid} ->
498
- // /api/bar/{bar_id}/zed/{zed_id}/foo/{id}
499
449
  const pathDesc = entdesc.path[pathStr];
500
450
  pathDesc.rename = (pathDesc.rename ?? { param: {} });
501
451
  pathDesc.why_rename = (pathDesc.why_rename ?? { why_param: {} });
@@ -506,12 +456,6 @@ function RenameParams(spec) {
506
456
  why: pathDesc.why_rename.why_param = (pathDesc.why_rename.why_param ?? {}),
507
457
  };
508
458
  const parts = pathdesc.parts;
509
- // Implicit snake_case normalization for any path placeholder not already
510
- // renamed by the id-rename logic. apidef's args transform snake-cases param
511
- // names (e.g. spec `platformKey` → param.name `platform_key`); without
512
- // normalizing the placeholder to match, runtime URL substitution by
513
- // param.name fails to fill `{platformKey}`. Defined as a closure so we can
514
- // run it after the id-rename loop OR after the multi-param early-return.
515
459
  const applySnakeCaseRename = () => {
516
460
  for (const part of parts) {
517
461
  const m = part.match(/^\{(.+)\}$/);
@@ -579,7 +523,6 @@ function RenameParams(spec) {
579
523
  && entdesc.name.startsWith(parentName + '_'))
580
524
  // || parentName === cmp.name
581
525
  || parentName === cmpname)) {
582
- // let newParamName = 'id'
583
526
  updateParamRename(ctx, data, pathStr, methodName, paramRenameCapture, oldParam, 'id', 'action-parent:' + entdesc.name);
584
527
  why.push('action');
585
528
  updateAction(methodName, oldParam, parts[partI + 1], entdesc, pathDesc, 'action-not-parent');
@@ -636,8 +579,6 @@ function RenameParams(spec) {
636
579
  && parentName === cmpname) {
637
580
  updateParamRename(ctx, data, pathStr, methodName, paramRenameCapture, oldParam, 'id', 'id-not-last');
638
581
  why.push('id-not-last');
639
- // paramRenames[oldParam] = 'id'
640
- // paramRenamesWhy[oldParam].push('id-not-last')
641
582
  }
642
583
  // Not primary ent.
643
584
  else {
@@ -646,8 +587,6 @@ function RenameParams(spec) {
646
587
  if (newParamName != oldParam) {
647
588
  updateParamRename(ctx, data, pathStr, methodName, paramRenameCapture, oldParam, newParamName, 'not-primary');
648
589
  why.push('not-primary');
649
- // paramRenames[oldParam] = newParamName
650
- // paramRenamesWhy[oldParam].push('not-primary')
651
590
  }
652
591
  }
653
592
  }
@@ -657,7 +596,6 @@ function RenameParams(spec) {
657
596
  delete paramRenameCapture.rename[oldParam];
658
597
  delete paramRenameCapture.why[oldParam];
659
598
  }
660
- // TODO: these need to done via an API
661
599
  (0, utility_2.debugpath)(pathStr, methodName, 'RENAME-PARAM', {
662
600
  pathStr,
663
601
  methodName,
@@ -691,7 +629,6 @@ function FindActions(spec) {
691
629
  const ment = mdesc.MethodEntity;
692
630
  const entname = ment.entname;
693
631
  const entdesc = work.entmap[entname];
694
- // const pathdesc = spec.data.work.pathmap[pathStr]
695
632
  const pathdesc = entdesc.path[pathStr];
696
633
  const methodName = mdesc.method;
697
634
  pathdesc.action = (pathdesc.action ?? {});
@@ -706,12 +643,6 @@ function FindActions(spec) {
706
643
  const lastPart = parts[parts.length - 1];
707
644
  const lastPartCanon = (0, utility_2.canonize)(lastPart);
708
645
  const cmp = ment.cmp;
709
- // A verb that ResolveEntityName assigned to its parent entity
710
- // (verbOnParent) is an action whatever the parent literal canonizes to:
711
- // `/app/installations/{installation_id}/access_tokens` belongs to `app`.
712
- // Recorded directly rather than through updateAction, whose guard against
713
- // an entity "already encoding" the verb would drop `archive` on
714
- // `email_archive` and leave the verb as a plain CRUD point.
715
646
  if (null != ment.verb_on_parent) {
716
647
  pathdesc.action[lastPartCanon] = pathdesc.action[lastPartCanon] ?? {
717
648
  why_action: ['ent', entdesc.name, 'verb-on-parent', lastPart, methodName],
@@ -762,39 +693,13 @@ function ResolveOperation(spec) {
762
693
  why_op.push('no-op:' + methodName);
763
694
  return;
764
695
  }
765
- // REVIEW: using POST and PUT in non-restian ways is too wierd to handle consistently
766
- // correct using guide customizations
767
- // Sometimes POST is used to update, not create. Attempt to identify this.
768
- // And sometimes vice versa for PUT
769
- // const id_param_offset = ment.pm?.expr?.endsWith('/t/') ? 1 : 0
770
- // const has_end_id_param =
771
- // entname == canonize(parts[parts.length - 2 - id_param_offset])
772
- // && parts[parts.length - 1 - id_param_offset]?.toLowerCase().endsWith('id}')
773
696
  if ('load' === standard_opname) {
774
697
  const islist = isListResponse(mdesc, pathStr, why_op);
775
698
  opname = islist ? 'list' : opname;
776
699
  }
777
- /*
778
- else if (
779
- 'create' === standard_opname
780
- && has_end_id_param
781
- ) {
782
- opname = 'update'
783
- why_op.push('id-present')
784
- }
785
-
786
- else if (
787
- 'update' === standard_opname
788
- && !has_end_id_param
789
- ) {
790
- opname = 'create'
791
- why_op.push('no-id-present')
792
- }
793
- */
794
700
  else {
795
701
  why_op.push('not-load');
796
702
  }
797
- // why.push('ent=' + entdesc.name)
798
703
  ment.opname = opname;
799
704
  ment.why_opname = why_op;
800
705
  // Tally access-token exchanges per entity. An entity whose ops are ALL
@@ -829,7 +734,6 @@ function ResolveTransform(spec) {
829
734
  const work = spec.data.work;
830
735
  const entname = mdesc.MethodEntity.entname;
831
736
  const entdesc = work.entmap[entname];
832
- // const pathdesc = spec.data.work.pathmap[pathStr]
833
737
  const pathdesc = entdesc.path[pathStr];
834
738
  const methodName = mdesc.method;
835
739
  const opname = ment.opname;
@@ -843,12 +747,6 @@ function ResolveTransform(spec) {
843
747
  const resprops = getResponseSchema(resokdef)?.properties;
844
748
  (0, utility_2.debugpath)(pathStr, methodName, 'TRANSFORM-RES', (0, struct_1.keysof)(resprops));
845
749
  if (resprops) {
846
- // Only unwrap `body.<entity>` when the entity-named response property is
847
- // itself a structured value (object/array/ref/composed schema) that could
848
- // actually contain the entity. A scalar property that merely shares the
849
- // entity's name (e.g. an entity `advice` whose own fields include a
850
- // string field `advice`) is a FIELD of the entity, not a wrapper around
851
- // it: the response IS the entity, so it must stay `body` (the default).
852
750
  if ((0, utility_1.isEntityWrapperProp)(resprops[entdesc.origname])) {
853
751
  transform.res = '`body.' + entdesc.origname + '`';
854
752
  }
@@ -867,14 +765,6 @@ function ResolveTransform(spec) {
867
765
  }
868
766
  }
869
767
  }
870
- // The SCHEMA is what closedBodyTransform needs (it reads
871
- // additionalProperties); the wrapper-name checks need its PROPERTIES. They
872
- // used to share one value and index the schema itself, so
873
- // `schema['todoitem']` was always undefined and the entity-name request
874
- // envelope was never detected — while the Go port read `.properties` and
875
- // did detect it. That divergence was inert only because `req` was never
876
- // serialised; now that it is, the two implementations would emit different
877
- // request bodies for the same spec.
878
768
  const reqschema = getRequestBodySchema(mdesc.requestBody);
879
769
  const reqprops = reqschema?.properties;
880
770
  (0, utility_2.debugpath)(pathStr, methodName, 'TRANSFORM-REQ', (0, struct_1.keysof)(reqprops));
@@ -933,12 +823,6 @@ function BuildEntity(spec) {
933
823
  orig: entdesc.origcmp,
934
824
  path,
935
825
  };
936
- // An entity built ENTIRELY out of access-token exchanges is not a resource
937
- // — it is the credential plumbing an SDK's auth layer performs. Emitted
938
- // with `active: false` rather than dropped, so it stays visible in
939
- // guide.aon and the classification can be reversed there (ADR-002): flip
940
- // it to `true` and the entity comes back. The exchange itself is recorded
941
- // separately as model facts by transform/top.ts.
942
826
  if (0 < entdesc.authexchange_ops && entdesc.authexchange_ops === entdesc.total_ops) {
943
827
  guideEntity.active = false;
944
828
  guideEntity.why_inactive = 'auth-exchange';
@@ -995,28 +879,6 @@ function endsWithCmp(data, pm) {
995
879
  const last = (0, utility_2.canonize)((0, struct_1.getelem)(pm, -1));
996
880
  return isOrigCmp(data, last);
997
881
  }
998
- // A write on `.../<parent>/{id}/<verb>` is a VERB ON THE PARENT, not an
999
- // entity named after its result shape.
1000
- //
1001
- // GitHub's `PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge` answers with
1002
- // a `pull-request-merge-result` component. Naming the method's entity after
1003
- // that component (the cmp-primary rule) produced a `pull_request_merge_result`
1004
- // entity with a single `update` op, while the GET on the same path (no
1005
- // response schema) stayed an action on `pull`: one route split across two
1006
- // entities by method, and the verb unreachable from the entity it acts on.
1007
- //
1008
- // Five signals, together: the method writes (a GET on such a path is a
1009
- // sub-resource read and keeps the component rule); the response component
1010
- // occurs nowhere else in the spec (a one-off result, not a resource shape);
1011
- // that component is not the literal's own collection shape (a create-only
1012
- // `POST .../{id}/labels` answering with a `label` is a nested collection,
1013
- // not a verb); the item selector itself (`.../pulls/{pull_number}`) is a
1014
- // path of the spec, so the trailing literal cannot be a collection of its
1015
- // own; and nothing extends the path (`.../private-registries/{secret_name}`
1016
- // makes `private-registries` a collection, whatever its POST answers with).
1017
- // The verb then joins the parent entity, where FindActions records it as an
1018
- // action and select stamps `$action` on its points. Returns the parent's
1019
- // entity name, or null when the rule does not apply.
1020
882
  function verbOnParent(data, pm, mdesc) {
1021
883
  const method = mdesc.method;
1022
884
  if ('GET' === method || 'QUERY' === method || 'HEAD' === method || 'OPTIONS' === method) {
@@ -1026,20 +888,10 @@ function verbOnParent(data, pm, mdesc) {
1026
888
  if (1 < (ment.cmpoccur ?? 0)) {
1027
889
  return null;
1028
890
  }
1029
- // A PLURAL literal names a nested collection, whatever it answers with:
1030
- // `asset_keys` under `{environment_id}` creates an asset key, and
1031
- // `approvals` under `{merge_request_iid}` is a collection of approvals.
1032
- // A verb is singular — `merge`, `revoke`, `resend_confirmation` — so the
1033
- // component rule below is never reached for a plural, which is what keeps
1034
- // a create-only collection an entity of its own.
1035
891
  const lit = (0, jostraca_2.snakify)((0, struct_1.getelem)(pm, -1));
1036
892
  if ('' === lit || (0, utility_2.depluralize)(lit) !== lit) {
1037
893
  return null;
1038
894
  }
1039
- // A singular literal still names a collection when its response component
1040
- // is that collection's member shape (`label` answering with `label`, or
1041
- // with a parent-prefixed `thing_label`); a verb answers with something
1042
- // else.
1043
895
  const verb = (0, utility_2.canonize)((0, struct_1.getelem)(pm, -1));
1044
896
  const cmp = String(ment.cmp ?? '');
1045
897
  if ('' === verb || cmp === verb || cmp.endsWith('_' + verb)) {
@@ -1089,7 +941,6 @@ function entityPathMatch_tpe(data, pm, mdesc, why) {
1089
941
  const pathNameIndex = 0;
1090
942
  why.push('path=t/p/');
1091
943
  const origPathName = pm[pathNameIndex];
1092
- // let entname = fixEntName(origPathName)
1093
944
  let entname = (0, utility_2.canonize)(origPathName);
1094
945
  if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
1095
946
  let ecm = entityCmpMatch(data, entname, mdesc, why);
@@ -1120,7 +971,6 @@ function entityPathMatch_te(data, pm, mdesc, why) {
1120
971
  const pathNameIndex = 0;
1121
972
  why.push('path=t/');
1122
973
  const origPathName = pm[pathNameIndex];
1123
- // let entname = fixEntName(origPathName)
1124
974
  let entname = (0, utility_2.canonize)(origPathName);
1125
975
  if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
1126
976
  let ecm = entityCmpMatch(data, entname, mdesc, why);
@@ -1136,7 +986,6 @@ function entityPathMatch_tpp(data, pm, mdesc, why) {
1136
986
  const pathNameIndex = 0;
1137
987
  why.push('path=t/p/p');
1138
988
  const origPathName = pm[pathNameIndex];
1139
- // let entname = fixEntName(origPathName)
1140
989
  let entname = (0, utility_2.canonize)(origPathName);
1141
990
  if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
1142
991
  let ecm = entityCmpMatch(data, entname, mdesc, why);
@@ -1186,7 +1035,6 @@ function inferEntityName(mdesc, parts, why) {
1186
1035
  }
1187
1036
  return null;
1188
1037
  }
1189
- // No entity component was found, but there still might be an entity.
1190
1038
  function probableEntityMethod(data, mdesc, pm, why) {
1191
1039
  const request = mdesc.requestBody;
1192
1040
  const reqSchema = getRequestBodySchema(request);
@@ -1300,14 +1148,6 @@ function isListResponse(mdesc, pathStr, why) {
1300
1148
  const pm = ment.pm;
1301
1149
  let islist = false;
1302
1150
  let schema;
1303
- // 'p/' is anchored (e.g. t/p/): the path ends at a param, so it is an
1304
- // item path and the response shape cannot change that.
1305
- //
1306
- // A bare trailing 'p' (e.g. t/p/p, a compound key like
1307
- // /repos/{owner}/{repo}) also ends at a param, but the same shape covers
1308
- // a sub-collection scoped by a compound key (e.g. /audit-log/{ns}/{repo}).
1309
- // Those are told apart by the response: a collection returns an array at
1310
- // the top level, an item does not.
1311
1151
  const endParamAnchored = !!(pm && pm.expr.endsWith('p/'));
1312
1152
  const endParamBare = !!(pm && !endParamAnchored && pm.expr.endsWith('p'));
1313
1153
  if (endParamAnchored) {
@@ -1324,11 +1164,6 @@ function isListResponse(mdesc, pathStr, why) {
1324
1164
  why.push('array');
1325
1165
  islist = true;
1326
1166
  }
1327
- // The array-prop fallback is deliberately loose, and an item schema
1328
- // often carries an incidental array property (GitHub's full-repository
1329
- // has topics: string[]). That is good enough evidence for an ordinary
1330
- // path, but not for a compound-key path, where it is exactly what
1331
- // misclassifies the item as a list.
1332
1167
  if (!islist && !endParamBare) {
1333
1168
  const properties = resolveSchemaProperties(schema);
1334
1169
  (0, jostraca_1.each)(properties, (prop) => {
@@ -1371,6 +1206,42 @@ function updateAction(methodName, oldParam, actionName, entityDesc, pathdesc, wh
1371
1206
  }
1372
1207
  }
1373
1208
  function updateParamRename(ctx, data, path, method, paramRenameCapture, oldParamName, newParamName, why) {
1209
+ // A name that cannot be an identifier is not an improvement on the one the
1210
+ // specification gave. See docs/design/derived-names.md
1211
+ if (!/^[A-Za-z_]/.test(newParamName)) {
1212
+ ctx.log.debug({
1213
+ point: 'param-rename-skip',
1214
+ path,
1215
+ param: oldParamName,
1216
+ rejected: newParamName,
1217
+ note: 'derived parameter name is not an identifier, keeping the' +
1218
+ " specification's name"
1219
+ });
1220
+ return;
1221
+ }
1222
+ // The clash is with what the path's OTHER parameters end up called: their
1223
+ // rename if they have one, their canonical name if not.
1224
+ const otherParams = (path.match(/\{([^}]+)\}/g) || [])
1225
+ .map((seg) => seg.slice(1, -1))
1226
+ .filter((name) => name !== oldParamName);
1227
+ const takenBy = Object.keys(paramRenameCapture.rename)
1228
+ .find((other) => other !== oldParamName &&
1229
+ paramRenameCapture.rename[other] === newParamName) ??
1230
+ otherParams
1231
+ .find((other) => null == paramRenameCapture.rename[other] &&
1232
+ (0, utility_2.canonize)(other) === newParamName);
1233
+ if (null != takenBy) {
1234
+ ctx.log.debug({
1235
+ point: 'param-rename-collision',
1236
+ path,
1237
+ param: oldParamName,
1238
+ rejected: newParamName,
1239
+ takenBy,
1240
+ note: 'another parameter of this path already renames to ' +
1241
+ newParamName + ", keeping the specification's name"
1242
+ });
1243
+ return;
1244
+ }
1374
1245
  const existingNewName = paramRenameCapture.rename[oldParamName];
1375
1246
  const existingWhy = paramRenameCapture.why[oldParamName];
1376
1247
  (0, utility_2.debugpath)(path, method, 'UPDATE-PARAM-RENAME', path, oldParamName, newParamName, existingNewName);
@@ -1381,9 +1252,6 @@ function updateParamRename(ctx, data, path, method, paramRenameCapture, oldParam
1381
1252
  }
1382
1253
  }
1383
1254
  else if (newParamName == existingNewName) {
1384
- // if (!existingWhy.includes(why)) {
1385
- // existingWhy.push(why)
1386
- // }
1387
1255
  }
1388
1256
  else {
1389
1257
  ctx.warn({
@@ -1398,18 +1266,9 @@ function updateParamRename(ctx, data, path, method, paramRenameCapture, oldParam
1398
1266
  function isParam(partStr) {
1399
1267
  return '{' === partStr[0] && '}' === partStr[partStr.length - 1];
1400
1268
  }
1401
- /*
1402
- function fixEntName(origName: string) {
1403
- if (null == origName) {
1404
- return origName
1405
- }
1406
- return depluralize(snakify(origName))
1407
- }
1408
- */
1409
1269
  function findcmps(data, pathStr, underprops, opts) {
1410
1270
  const cmplist = [];
1411
1271
  const cmpset = new Set();
1412
- // TODO: cache in ctx.work
1413
1272
  (0, jostraca_1.each)(data.def.paths[pathStr])
1414
1273
  .map((md) => {
1415
1274
  underprops.map((up) => {