@distilled.cloud/core 1.0.0-rc.1 → 1.0.0-rc.10

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 (148) hide show
  1. package/LICENSE +201 -0
  2. package/lib/api.d.ts.map +1 -1
  3. package/lib/api.js +16 -4
  4. package/lib/api.js.map +1 -1
  5. package/lib/category.d.ts +4 -4
  6. package/lib/category.js +4 -4
  7. package/lib/codegen/boolean-string-enums.d.ts +36 -0
  8. package/lib/codegen/boolean-string-enums.d.ts.map +1 -0
  9. package/lib/codegen/boolean-string-enums.js +94 -0
  10. package/lib/codegen/boolean-string-enums.js.map +1 -0
  11. package/lib/codegen/boolean-string-enums.test.d.ts +2 -0
  12. package/lib/codegen/boolean-string-enums.test.d.ts.map +1 -0
  13. package/lib/codegen/boolean-string-enums.test.js +147 -0
  14. package/lib/codegen/boolean-string-enums.test.js.map +1 -0
  15. package/lib/codegen/cli.d.ts +56 -6
  16. package/lib/codegen/cli.d.ts.map +1 -1
  17. package/lib/codegen/cli.js +59 -75
  18. package/lib/codegen/cli.js.map +1 -1
  19. package/lib/codegen/emit.d.ts +3 -7
  20. package/lib/codegen/emit.d.ts.map +1 -1
  21. package/lib/codegen/emit.js +6 -10
  22. package/lib/codegen/emit.js.map +1 -1
  23. package/lib/codegen/generator.d.ts.map +1 -1
  24. package/lib/codegen/generator.js +142 -20
  25. package/lib/codegen/generator.js.map +1 -1
  26. package/lib/codegen/graphql-client.d.ts +62 -0
  27. package/lib/codegen/graphql-client.d.ts.map +1 -0
  28. package/lib/codegen/graphql-client.js +294 -0
  29. package/lib/codegen/graphql-client.js.map +1 -0
  30. package/lib/codegen/graphql-client.test.d.ts +2 -0
  31. package/lib/codegen/graphql-client.test.d.ts.map +1 -0
  32. package/lib/codegen/graphql-client.test.js +311 -0
  33. package/lib/codegen/graphql-client.test.js.map +1 -0
  34. package/lib/codegen/graphql.d.ts +207 -0
  35. package/lib/codegen/graphql.d.ts.map +1 -0
  36. package/lib/codegen/graphql.js +799 -0
  37. package/lib/codegen/graphql.js.map +1 -0
  38. package/lib/codegen/openapi-cli.d.ts +17 -3
  39. package/lib/codegen/openapi-cli.d.ts.map +1 -1
  40. package/lib/codegen/openapi-cli.js +50 -48
  41. package/lib/codegen/openapi-cli.js.map +1 -1
  42. package/lib/codegen/openapi.d.ts +69 -6
  43. package/lib/codegen/openapi.d.ts.map +1 -1
  44. package/lib/codegen/openapi.js +173 -16
  45. package/lib/codegen/openapi.js.map +1 -1
  46. package/lib/codegen/patches.d.ts +65 -0
  47. package/lib/codegen/patches.d.ts.map +1 -0
  48. package/lib/codegen/patches.js +236 -0
  49. package/lib/codegen/patches.js.map +1 -0
  50. package/lib/codegen/patches.test.d.ts +2 -0
  51. package/lib/codegen/patches.test.d.ts.map +1 -0
  52. package/lib/codegen/patches.test.js +105 -0
  53. package/lib/codegen/patches.test.js.map +1 -0
  54. package/lib/codegen/proto.d.ts +121 -0
  55. package/lib/codegen/proto.d.ts.map +1 -0
  56. package/lib/codegen/proto.js +962 -0
  57. package/lib/codegen/proto.js.map +1 -0
  58. package/lib/codegen/rewrite-operation-ids.d.ts +131 -0
  59. package/lib/codegen/rewrite-operation-ids.d.ts.map +1 -0
  60. package/lib/codegen/rewrite-operation-ids.js +1079 -0
  61. package/lib/codegen/rewrite-operation-ids.js.map +1 -0
  62. package/lib/codegen/rewrite-operation-ids.test.d.ts +2 -0
  63. package/lib/codegen/rewrite-operation-ids.test.d.ts.map +1 -0
  64. package/lib/codegen/rewrite-operation-ids.test.js +533 -0
  65. package/lib/codegen/rewrite-operation-ids.test.js.map +1 -0
  66. package/lib/codegen/spec-path.d.ts +16 -0
  67. package/lib/codegen/spec-path.d.ts.map +1 -0
  68. package/lib/codegen/spec-path.js +101 -0
  69. package/lib/codegen/spec-path.js.map +1 -0
  70. package/lib/errors.d.ts.map +1 -1
  71. package/lib/errors.js +17 -13
  72. package/lib/errors.js.map +1 -1
  73. package/lib/graphql.d.ts +284 -0
  74. package/lib/graphql.d.ts.map +1 -0
  75. package/lib/graphql.fixture.d.ts +249 -0
  76. package/lib/graphql.fixture.d.ts.map +1 -0
  77. package/lib/graphql.fixture.js +240 -0
  78. package/lib/graphql.fixture.js.map +1 -0
  79. package/lib/graphql.js +718 -0
  80. package/lib/graphql.js.map +1 -0
  81. package/lib/graphql.test.d.ts +2 -0
  82. package/lib/graphql.test.d.ts.map +1 -0
  83. package/lib/graphql.test.js +780 -0
  84. package/lib/graphql.test.js.map +1 -0
  85. package/lib/graphql.types.d.ts +2 -0
  86. package/lib/graphql.types.d.ts.map +1 -0
  87. package/lib/graphql.types.js +45 -0
  88. package/lib/graphql.types.js.map +1 -0
  89. package/lib/json-patch.d.ts +18 -11
  90. package/lib/json-patch.d.ts.map +1 -1
  91. package/lib/json-patch.js +63 -25
  92. package/lib/json-patch.js.map +1 -1
  93. package/lib/pagination.d.ts +43 -3
  94. package/lib/pagination.d.ts.map +1 -1
  95. package/lib/pagination.js +91 -5
  96. package/lib/pagination.js.map +1 -1
  97. package/lib/protocol-http.d.ts.map +1 -1
  98. package/lib/protocol-http.js +62 -26
  99. package/lib/protocol-http.js.map +1 -1
  100. package/lib/protocol-http.test.d.ts +2 -0
  101. package/lib/protocol-http.test.d.ts.map +1 -0
  102. package/lib/protocol-http.test.js +88 -0
  103. package/lib/protocol-http.test.js.map +1 -0
  104. package/lib/protocol-rest.d.ts +12 -2
  105. package/lib/protocol-rest.d.ts.map +1 -1
  106. package/lib/protocol-rest.js +17 -3
  107. package/lib/protocol-rest.js.map +1 -1
  108. package/lib/retry.js +1 -1
  109. package/lib/schema.d.ts +1 -1
  110. package/lib/schema.d.ts.map +1 -1
  111. package/lib/schema.js +1 -1
  112. package/lib/schema.js.map +1 -1
  113. package/lib/trait.d.ts +27 -3
  114. package/lib/trait.d.ts.map +1 -1
  115. package/lib/trait.js +17 -1
  116. package/lib/trait.js.map +1 -1
  117. package/package.json +13 -10
  118. package/src/api.ts +18 -4
  119. package/src/category.ts +4 -4
  120. package/src/codegen/boolean-string-enums.test.ts +168 -0
  121. package/src/codegen/boolean-string-enums.ts +106 -0
  122. package/src/codegen/cli.ts +127 -110
  123. package/src/codegen/emit.ts +6 -10
  124. package/src/codegen/generator.ts +151 -21
  125. package/src/codegen/graphql-client.test.ts +386 -0
  126. package/src/codegen/graphql-client.ts +419 -0
  127. package/src/codegen/graphql.ts +1217 -0
  128. package/src/codegen/openapi-cli.ts +75 -59
  129. package/src/codegen/openapi.ts +255 -16
  130. package/src/codegen/patches.test.ts +130 -0
  131. package/src/codegen/patches.ts +291 -0
  132. package/src/codegen/proto.ts +1128 -0
  133. package/src/codegen/rewrite-operation-ids.test.ts +563 -0
  134. package/src/codegen/rewrite-operation-ids.ts +1206 -0
  135. package/src/codegen/spec-path.ts +115 -0
  136. package/src/errors.ts +20 -25
  137. package/src/graphql.fixture.ts +371 -0
  138. package/src/graphql.test.ts +974 -0
  139. package/src/graphql.ts +1321 -0
  140. package/src/graphql.types.ts +185 -0
  141. package/src/json-patch.ts +82 -25
  142. package/src/pagination.ts +134 -7
  143. package/src/protocol-http.test.ts +107 -0
  144. package/src/protocol-http.ts +67 -31
  145. package/src/protocol-rest.ts +28 -4
  146. package/src/retry.ts +1 -1
  147. package/src/schema.ts +2 -2
  148. package/src/trait.ts +39 -3
@@ -0,0 +1,1079 @@
1
+ /** Verb spellings normalised on the way out. */
2
+ const VERB_ALIAS = {
3
+ show: "get",
4
+ index: "list",
5
+ retrieve: "get",
6
+ };
7
+ /**
8
+ * Rails `index` is a verb only when it is the whole id or a whole `_`
9
+ * segment (`index`, `Apps_index`). Joined into a camel id it is the noun
10
+ * (`InfoIndex`, `IndexCreate`, `IndexDocument`).
11
+ */
12
+ const isIndexVerb = (raw) => raw.toLowerCase() === "index";
13
+ /**
14
+ * Unambiguous verbs. An id that STARTS with one is already verb-first and
15
+ * is never reordered (`WatchPodList`, `InsertCalendarList`,
16
+ * `BulkDeleteMessages`); one found in the middle of a resource name
17
+ * (`Apps_getOrCreate`) marks the id as a compound we leave alone.
18
+ */
19
+ const STRONG_VERBS = new Set([
20
+ "get",
21
+ "list",
22
+ "create",
23
+ "delete",
24
+ "update",
25
+ "patch",
26
+ "put",
27
+ "post",
28
+ "show",
29
+ "index",
30
+ "retrieve",
31
+ "watch",
32
+ "read",
33
+ "replace",
34
+ "connect",
35
+ "disconnect",
36
+ "insert",
37
+ "mutate",
38
+ "fetch",
39
+ "remove",
40
+ "add",
41
+ "upload",
42
+ "download",
43
+ "send",
44
+ "resend",
45
+ "validate",
46
+ "verify",
47
+ "enable",
48
+ "disable",
49
+ "start",
50
+ "stop",
51
+ "restart",
52
+ "cancel",
53
+ "search",
54
+ "purge",
55
+ "revoke",
56
+ "approve",
57
+ "reject",
58
+ "accept",
59
+ "invoke",
60
+ "trigger",
61
+ "deploy",
62
+ "redeploy",
63
+ "describe",
64
+ "generate",
65
+ "regenerate",
66
+ "rotate",
67
+ "restore",
68
+ "suspend",
69
+ "resume",
70
+ "activate",
71
+ "deactivate",
72
+ "archive",
73
+ "unarchive",
74
+ "publish",
75
+ "unpublish",
76
+ "subscribe",
77
+ "unsubscribe",
78
+ "register",
79
+ "unregister",
80
+ "authenticate",
81
+ "authorize",
82
+ "deauthorize",
83
+ "attach",
84
+ "detach",
85
+ "assign",
86
+ "unassign",
87
+ "import",
88
+ "export",
89
+ "copy",
90
+ "move",
91
+ "rename",
92
+ "retry",
93
+ "reset",
94
+ "refresh",
95
+ "encrypt",
96
+ "decrypt",
97
+ "sign",
98
+ "edit",
99
+ "bulk",
100
+ "execute",
101
+ "submit",
102
+ "apply",
103
+ "undelete",
104
+ "redeliver",
105
+ "invalidate",
106
+ "upsert",
107
+ "cordon",
108
+ "uncordon",
109
+ "exec",
110
+ "reclaim",
111
+ "modify",
112
+ "install",
113
+ "uninstall",
114
+ "migrate",
115
+ "terminate",
116
+ "reboot",
117
+ "rebuild",
118
+ "resize",
119
+ "clone",
120
+ "duplicate",
121
+ "acknowledge",
122
+ "unpause",
123
+ "unblock",
124
+ "unlink",
125
+ "unpin",
126
+ "unmute",
127
+ "unhide",
128
+ "unstar",
129
+ "unfollow",
130
+ "unshare",
131
+ "unban",
132
+ "unlock",
133
+ "unflag",
134
+ "unclaim",
135
+ ]);
136
+ /**
137
+ * Words that act as the verb when they TRAIL a resource (`MachinesStart`,
138
+ * `SecretkeysSet`, `JobsRun`) but are ordinary nouns when they lead
139
+ * (`ReleaseGet`, `RequestGet`, `SetGet`, `CheckRunsList`). Includes every
140
+ * strong verb.
141
+ */
142
+ const TRAILING_VERBS = new Set([
143
+ ...STRONG_VERBS,
144
+ "notify",
145
+ "set",
146
+ "check",
147
+ "run",
148
+ "wait",
149
+ "signal",
150
+ "fork",
151
+ "extend",
152
+ "test",
153
+ "sync",
154
+ "review",
155
+ "invite",
156
+ "join",
157
+ "leave",
158
+ "capture",
159
+ "refund",
160
+ "settle",
161
+ "void",
162
+ "preview",
163
+ "complete",
164
+ "confirm",
165
+ "abort",
166
+ "kill",
167
+ "scale",
168
+ "promote",
169
+ "demote",
170
+ "renew",
171
+ "recover",
172
+ "reveal",
173
+ "presign",
174
+ "provision",
175
+ "trim",
176
+ "vacuum",
177
+ "compact",
178
+ "flush",
179
+ "merge",
180
+ "dispatch",
181
+ "poll",
182
+ "redeem",
183
+ "claim",
184
+ "lint",
185
+ "toggle",
186
+ "pin",
187
+ "mute",
188
+ "hide",
189
+ "ban",
190
+ "block",
191
+ "lock",
192
+ "share",
193
+ "follow",
194
+ "star",
195
+ "pause",
196
+ "finalize",
197
+ "transfer",
198
+ "lookup",
199
+ "clear",
200
+ "convert",
201
+ "dismiss",
202
+ "reply",
203
+ "squash",
204
+ "commit",
205
+ "swap",
206
+ "recreate",
207
+ "seal",
208
+ "ping",
209
+ "login",
210
+ "logout",
211
+ ]);
212
+ /**
213
+ * Leading tokens that are verbs often enough that an id starting with one
214
+ * is left as-is unless a STRONG verb trails it (`QueryCreate` →
215
+ * `createQuery`, but `QueryRun` stays).
216
+ */
217
+ const WEAK_LEADING_VERBS = new Set([
218
+ ...TRAILING_VERBS,
219
+ "request",
220
+ "release",
221
+ "report",
222
+ "query",
223
+ "stream",
224
+ "open",
225
+ "close",
226
+ "head",
227
+ "options",
228
+ "compute",
229
+ "batch",
230
+ "count",
231
+ "link",
232
+ "mark",
233
+ "grant",
234
+ "aggregate",
235
+ "skip",
236
+ "filter",
237
+ "change",
238
+ "vote",
239
+ "debug",
240
+ "expire",
241
+ "process",
242
+ "redirect",
243
+ "estimate",
244
+ "calculate",
245
+ "evaluate",
246
+ "resolve",
247
+ "load",
248
+ "unload",
249
+ "backup",
250
+ "snapshot",
251
+ "rollback",
252
+ "flag",
253
+ "certify",
254
+ "checkout",
255
+ "checkin",
256
+ "suggest",
257
+ "predict",
258
+ "analyze",
259
+ "annotate",
260
+ "translate",
261
+ "recognize",
262
+ "detect",
263
+ "classify",
264
+ "simulate",
265
+ "take",
266
+ "end",
267
+ "reassign",
268
+ "consume",
269
+ "prepare",
270
+ "action",
271
+ "act",
272
+ "ask",
273
+ "answer",
274
+ "handle",
275
+ "trace",
276
+ "track",
277
+ "log",
278
+ ]);
279
+ /** Compound tokens the naive split would leave as `Secretkeys`. */
280
+ const TOKEN_ALIAS = {
281
+ secretkeys: "SecretKeys",
282
+ secretkey: "SecretKey",
283
+ };
284
+ const IRREGULAR_SINGULAR = {
285
+ processes: "process",
286
+ statuses: "status",
287
+ addresses: "address",
288
+ aliases: "alias",
289
+ indexes: "index",
290
+ indices: "index",
291
+ matrices: "matrix",
292
+ vertices: "vertex",
293
+ analyses: "analysis",
294
+ bases: "base",
295
+ cases: "case",
296
+ databases: "database",
297
+ releases: "release",
298
+ responses: "response",
299
+ licenses: "license",
300
+ courses: "course",
301
+ purchases: "purchase",
302
+ phases: "phase",
303
+ leases: "lease",
304
+ clauses: "clause",
305
+ causes: "cause",
306
+ pauses: "pause",
307
+ houses: "house",
308
+ children: "child",
309
+ people: "person",
310
+ media: "media",
311
+ data: "data",
312
+ metadata: "metadata",
313
+ schemas: "schema",
314
+ criteria: "criterion",
315
+ feet: "foot",
316
+ teeth: "tooth",
317
+ mice: "mouse",
318
+ geese: "goose",
319
+ };
320
+ /**
321
+ * Nouns whose plural and singular coincide, or product names that end in
322
+ * `s`. Never trimmed.
323
+ */
324
+ const UNCOUNTABLE = new Set([
325
+ "postgres",
326
+ "redis",
327
+ "kubernetes",
328
+ "ios",
329
+ "macos",
330
+ "windows",
331
+ "dns",
332
+ "tls",
333
+ "https",
334
+ "sms",
335
+ "cors",
336
+ "oidc",
337
+ "sso",
338
+ "saas",
339
+ "aws",
340
+ "gcs",
341
+ "ecs",
342
+ "eks",
343
+ "rds",
344
+ "sqs",
345
+ "sns",
346
+ "kms",
347
+ "ses",
348
+ "efs",
349
+ "ebs",
350
+ "status",
351
+ "series",
352
+ "timeseries",
353
+ "analytics",
354
+ "metrics",
355
+ "settings",
356
+ "credentials",
357
+ "permissions",
358
+ "news",
359
+ "canvas",
360
+ "lens",
361
+ "bonus",
362
+ "campus",
363
+ "census",
364
+ "focus",
365
+ "virus",
366
+ "corpus",
367
+ "chorus",
368
+ "genus",
369
+ "radius",
370
+ "consensus",
371
+ "apparatus",
372
+ "bus",
373
+ "gas",
374
+ "plus",
375
+ "minus",
376
+ "nexus",
377
+ "prometheus",
378
+ "chaos",
379
+ "cosmos",
380
+ "ethos",
381
+ "pathos",
382
+ "atlas",
383
+ "bias",
384
+ "gps",
385
+ "cds",
386
+ "css",
387
+ "sass",
388
+ "less",
389
+ "js",
390
+ "ts",
391
+ "os",
392
+ "fs",
393
+ "vs",
394
+ "as",
395
+ "is",
396
+ "has",
397
+ "was",
398
+ "this",
399
+ "always",
400
+ "sis",
401
+ "axis",
402
+ "basis",
403
+ "crisis",
404
+ "thesis",
405
+ "diagnosis",
406
+ "synthesis",
407
+ "emphasis",
408
+ "hypothesis",
409
+ "oasis",
410
+ "iris",
411
+ "tennis",
412
+ "chassis",
413
+ "debris",
414
+ "physics",
415
+ "ethics",
416
+ "economics",
417
+ "logistics",
418
+ "mathematics",
419
+ "politics",
420
+ "statistics",
421
+ "dynamics",
422
+ "graphics",
423
+ "robotics",
424
+ "genetics",
425
+ "linguistics",
426
+ "means",
427
+ "species",
428
+ "sheep",
429
+ "fish",
430
+ "deer",
431
+ "aircraft",
432
+ "software",
433
+ "hardware",
434
+ "firmware",
435
+ "middleware",
436
+ "access",
437
+ "address",
438
+ "progress",
439
+ "success",
440
+ "process",
441
+ "business",
442
+ "ingress",
443
+ "egress",
444
+ "express",
445
+ "compress",
446
+ "stress",
447
+ "witness",
448
+ "fitness",
449
+ "wellness",
450
+ "readiness",
451
+ "liveness",
452
+ "awareness",
453
+ "class",
454
+ "pass",
455
+ "mass",
456
+ "glass",
457
+ "grass",
458
+ "bypass",
459
+ "compass",
460
+ "kms",
461
+ "eos",
462
+ "nas",
463
+ "ras",
464
+ "sas",
465
+ "das",
466
+ "pas",
467
+ "s3",
468
+ "k8s",
469
+ "kubeadm",
470
+ ]);
471
+ /** Certificate variants that sit in front of the resource noun. */
472
+ const QUALIFIERS = new Set(["acme", "custom"]);
473
+ export const operationNameKey = (ctx) => `${ctx.method.toUpperCase()} ${ctx.path}`;
474
+ /** Resolve {@link OperationIdRewrite} against method+path, then operationId. */
475
+ export const resolveOperationName = (rewrite, operationId, ctx) => {
476
+ if (typeof rewrite === "function")
477
+ return rewrite(operationId, ctx);
478
+ return rewrite[operationNameKey(ctx)] ?? rewrite[operationId];
479
+ };
480
+ /** `foo` → `Foo`; all-caps tokens (`CSI`, `API`) keep their casing. */
481
+ const pascalToken = (raw) => {
482
+ const alias = TOKEN_ALIAS[raw.toLowerCase()];
483
+ if (alias !== undefined)
484
+ return alias;
485
+ return raw.charAt(0).toUpperCase() + raw.slice(1);
486
+ };
487
+ /**
488
+ * Conservative singular. Only strips a trailing `s` when the word is a
489
+ * regular plural we are confident about; anything ambiguous is returned
490
+ * unchanged, because `getAlia` is worse than `getAliases`.
491
+ */
492
+ export const singularize = (raw) => {
493
+ const lower = raw.toLowerCase();
494
+ const keepCase = (s) => raw[0] === raw[0]?.toUpperCase()
495
+ ? s.charAt(0).toUpperCase() + s.slice(1)
496
+ : s;
497
+ const alias = TOKEN_ALIAS[lower];
498
+ if (alias !== undefined) {
499
+ return alias.endsWith("s") ? alias.slice(0, -1) : alias;
500
+ }
501
+ const irregular = IRREGULAR_SINGULAR[lower];
502
+ if (irregular !== undefined)
503
+ return keepCase(irregular);
504
+ if (UNCOUNTABLE.has(lower))
505
+ return raw;
506
+ // All-caps acronyms (`IPs`, `CIDRs`, `DNS`) — only trim a lowercase `s`
507
+ // after an acronym.
508
+ if (/^[A-Z0-9]+s$/.test(raw))
509
+ return raw.slice(0, -1);
510
+ if (raw !== lower && raw.toUpperCase() === raw)
511
+ return raw;
512
+ if (lower.length <= 3)
513
+ return raw;
514
+ if (!lower.endsWith("s"))
515
+ return raw;
516
+ // Vowel + `ys` is a regular plural (`keys`, `days`); consonant + `ys` is not.
517
+ if (/(?:ss|us|is|os|as|[^aeiou]ys)$/.test(lower))
518
+ return raw;
519
+ if (lower.endsWith("ies") && lower.length > 4)
520
+ return `${raw.slice(0, -3)}y`;
521
+ if (/(?:ches|shes|xes|zes|sses)$/.test(lower))
522
+ return raw.slice(0, -2);
523
+ if (lower.endsWith("oes") && lower.length > 4)
524
+ return raw.slice(0, -2);
525
+ // `-ses` after a vowel is usually a regular `-se` noun (`Response`,
526
+ // `Release`, `Database`), not an `-s` + `es` plural.
527
+ if (/[aeiou]ses$/.test(lower))
528
+ return raw.slice(0, -1);
529
+ if (lower.endsWith("ses"))
530
+ return raw.slice(0, -2);
531
+ return raw.slice(0, -1);
532
+ };
533
+ const lowerFirst = (s) => s.length === 0 ? s : s.charAt(0).toLowerCase() + s.slice(1);
534
+ /**
535
+ * Split on `_`, `-`, `/` and camel boundaries. Acronym runs stay together
536
+ * (`CSIDriver` → `CSI`, `Driver`; `APIGroup` → `API`, `Group`).
537
+ */
538
+ const splitIdent = (s) => {
539
+ const parts = [];
540
+ for (const chunk of s.split(/[_/\-.\s]+/).filter(Boolean)) {
541
+ const split = chunk
542
+ .replace(/([a-z0-9])([A-Z])/g, "$1\0$2")
543
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1\0$2");
544
+ for (const p of split.split("\0")) {
545
+ if (p)
546
+ parts.push(p);
547
+ }
548
+ }
549
+ return parts;
550
+ };
551
+ const isStrongVerb = (raw) => STRONG_VERBS.has(raw.toLowerCase());
552
+ const isTrailingVerb = (raw) => TRAILING_VERBS.has(raw.toLowerCase());
553
+ const isWeakLeadingVerb = (raw) => WEAK_LEADING_VERBS.has(raw.toLowerCase());
554
+ const alias = (raw) => VERB_ALIAS[raw.toLowerCase()] ?? raw.toLowerCase();
555
+ /** `version`-like tokens (`V1`, `v1beta1`, `V2`) are never nouns to singularise. */
556
+ const isVersionToken = (raw) => /^[vV]\d/.test(raw);
557
+ /** Adverbs that sit between resource and verb: `HoldoutsPartialUpdate`. */
558
+ const MODIFIERS = new Set(["partial", "bulk", "batch", "all", "many"]);
559
+ /**
560
+ * `verb + Resource + Object`. Only the resource (last noun before the verb)
561
+ * is singularised, and only for non-list verbs (`getApp`, `listApps`,
562
+ * `listMachineEvents`). Parent tokens keep their spelling.
563
+ */
564
+ const assemble = (verb, nouns, tail = []) => {
565
+ // `App_Certificates_acme_create`: the qualifier trails the resource in
566
+ // the id but reads better in front of it (`createAppAcmeCertificate`).
567
+ let ordered = [...nouns];
568
+ if (ordered.length >= 2 && QUALIFIERS.has(ordered.at(-1).toLowerCase())) {
569
+ const q = ordered.pop();
570
+ ordered.splice(ordered.length - 1, 0, q);
571
+ }
572
+ const shaped = ordered.map((p, i) => {
573
+ const isLast = i === ordered.length - 1;
574
+ if (!isLast || isVersionToken(p))
575
+ return pascalToken(p);
576
+ if (verb === "list" && tail.length === 0)
577
+ return pascalToken(p);
578
+ return pascalToken(singularize(p));
579
+ });
580
+ return verb + shaped.join("") + tail.map(pascalToken).join("");
581
+ };
582
+ /**
583
+ * Distilled SDK names are verbNoun (`listApps`, `getApp`, `createMachine`).
584
+ *
585
+ * - Already verb-first (`listSprites`, `GetObject`, `showContact`,
586
+ * `WatchPodList`) stays, with `show`/`index`/`retrieve` aliased.
587
+ * - Trailing action (`ConfigsList`, `PlansGet`, `ContainerCreate`,
588
+ * `VirtualMachinesStart`, `ReleaseGet`) moves the verb first and
589
+ * singularises the resource for non-list verbs.
590
+ * - go-swagger / autorest `Apps_list`, `App_Certificates_show`,
591
+ * `Machines_list_events`, `ServiceMembers_getMetrics`: the verb heads the
592
+ * first `_` segment after the resource.
593
+ * - Anything else (`AppGetOrCreate`, `VirtualMachines_createOrUpdate`,
594
+ * `DnsRecordsBatch`, `accountById`) is returned unchanged apart from
595
+ * lowerFirst. Use {@link OperationIdRewrite} for those.
596
+ */
597
+ export const toVerbNoun = (operationId) => {
598
+ const trimmed = operationId.trim();
599
+ if (trimmed === "")
600
+ return trimmed;
601
+ const segments = trimmed.split(/[_/]+/).filter(Boolean);
602
+ if (segments.length >= 2) {
603
+ // snake_case verb-first (`get_apps`, `list_all_users`): join.
604
+ const lead = splitIdent(segments[0]);
605
+ if (lead.length === 1 && isStrongVerb(lead[0])) {
606
+ const rest = segments.slice(1).flatMap(splitIdent);
607
+ return alias(lead[0]) + rest.map(pascalToken).join("");
608
+ }
609
+ // Segments before the verb are the resource by construction, so a
610
+ // verb-looking token inside them (`OpenIdConnectProvider_get`) is a
611
+ // noun. Only the object after the verb can make this a compound.
612
+ const verbSeg = segments.findIndex((seg, i) => {
613
+ if (i === 0)
614
+ return false;
615
+ const tokens = splitIdent(seg);
616
+ const head = tokens[0] ?? "";
617
+ // `Apps_index` lists; `Apps_indexInfo` is about the index entity.
618
+ if (isIndexVerb(head))
619
+ return tokens.length === 1;
620
+ return isTrailingVerb(head);
621
+ });
622
+ if (verbSeg > 0) {
623
+ const head = segments.slice(0, verbSeg).flatMap(splitIdent);
624
+ const [verb, ...objectHere] = splitIdent(segments[verbSeg]);
625
+ const object = [
626
+ ...objectHere,
627
+ ...segments.slice(verbSeg + 1).flatMap(splitIdent),
628
+ ];
629
+ const compound = object.some((t) => isStrongVerb(t) || /^(or|and)$/i.test(t));
630
+ if (!compound)
631
+ return assemble(alias(verb), head, object);
632
+ }
633
+ return lowerFirst(trimmed);
634
+ }
635
+ const parts = splitIdent(trimmed);
636
+ // `Index1`, `Index2`: a converter's duplicate-route suffix on a bare id.
637
+ if (parts.length <= 1 || parts.slice(1).every((p) => /^\d+$/.test(p))) {
638
+ return parts.length >= 1 && isTrailingVerb(parts[0])
639
+ ? alias(parts[0]) + parts.slice(1).join("")
640
+ : lowerFirst(trimmed);
641
+ }
642
+ const first = parts[0];
643
+ const last = parts.at(-1);
644
+ // A camel id never uses Rails `index` as its verb: `InfoIndex`,
645
+ // `IndexCreate`, `IndexDocument` all name the index entity.
646
+ const strong = (raw) => !isIndexVerb(raw) && isStrongVerb(raw);
647
+ const trailing = (raw) => !isIndexVerb(raw) && isTrailingVerb(raw);
648
+ const weakLeading = (raw) => !isIndexVerb(raw) && isWeakLeadingVerb(raw);
649
+ if (strong(first)) {
650
+ return alias(first) + parts.slice(1).map(pascalToken).join("");
651
+ }
652
+ if (parts.some((p) => /^(or|and)$/i.test(p)))
653
+ return lowerFirst(trimmed);
654
+ // Swagger tag-prefixed ids whose object repeats the tag (`issueListIssues`,
655
+ // `repoGetRepo`): the tag is redundant, drop it. Other tag-prefixed shapes
656
+ // (`userGetCurrent`, `notifyGetList`) are left for per-package
657
+ // `operationNames`; a middle verb is too often a noun (`environmentPatchCommit`).
658
+ if (parts.length >= 3 &&
659
+ !weakLeading(first) &&
660
+ !isVersionToken(first) &&
661
+ strong(parts[1]) &&
662
+ singularize(parts[2]).toLowerCase() === singularize(first).toLowerCase() &&
663
+ !parts.slice(2).some(strong)) {
664
+ return alias(parts[1]) + parts.slice(2).map(pascalToken).join("");
665
+ }
666
+ const reorder = weakLeading(first) ? strong(last) : trailing(last);
667
+ if (!reorder) {
668
+ return weakLeading(first)
669
+ ? alias(first) + parts.slice(1).map(pascalToken).join("")
670
+ : lowerFirst(trimmed);
671
+ }
672
+ const nouns = parts.slice(0, -1);
673
+ if (nouns.slice(1).some(strong))
674
+ return lowerFirst(trimmed);
675
+ const modifier = nouns.at(-1);
676
+ if (nouns.length > 1 &&
677
+ modifier !== undefined &&
678
+ MODIFIERS.has(modifier.toLowerCase())) {
679
+ return assemble(alias(last), nouns.slice(0, -1), [modifier]);
680
+ }
681
+ return assemble(alias(last), nouns);
682
+ };
683
+ const COMPANION_SUFFIXES = [
684
+ "Request",
685
+ "Response",
686
+ "Input",
687
+ "Output",
688
+ "Error",
689
+ "Result",
690
+ ];
691
+ const remapTargets = (node, mapping) => {
692
+ if (Array.isArray(node)) {
693
+ return node.map((item) => remapTargets(item, mapping));
694
+ }
695
+ if (node === null || typeof node !== "object")
696
+ return node;
697
+ const out = {};
698
+ for (const [key, value] of Object.entries(node)) {
699
+ if (key === "target" && typeof value === "string") {
700
+ out[key] = mapping.get(value) ?? value;
701
+ }
702
+ else {
703
+ out[key] = remapTargets(value, mapping);
704
+ }
705
+ }
706
+ return out;
707
+ };
708
+ /** Version prefixes and API roots that carry no meaning in a name. */
709
+ const NOISE_SEGMENTS = new Set(["api", "rest", "v", "public"]);
710
+ const isNoiseSegment = (seg) => {
711
+ const lower = seg.toLowerCase();
712
+ return NOISE_SEGMENTS.has(lower) || /^v\d+(\.\d+)*$/.test(lower);
713
+ };
714
+ const HTTP_METHOD_ID = /^(get|post|put|patch|delete|head|options)(?=$|[-_./]|[A-Z0-9])/i;
715
+ /**
716
+ * Whether an OpenAPI `operationId` carries no information beyond the
717
+ * method and path: absent, or a mechanical restatement of the route such
718
+ * as `get-api-card`, `post_v1_users_id`, `getApiV1AnnotationLayer`. A
719
+ * hand-chosen id that merely starts with the method (`delete-artifact` for
720
+ * `/repos/{owner}/{repo}/actions/artifacts/{id}`) is NOT mechanical — it
721
+ * names a subset of the route on purpose — so the id must cover every
722
+ * literal path segment, and every id token must come from the route.
723
+ */
724
+ /**
725
+ * A stricter subclass of mechanical ids: nothing in the id was chosen by
726
+ * a person. Either there is no id, or the id is the route spelled out —
727
+ * `postV1AppsByAppIdPromote`, `get_v1_users_id`, `get-api-card`,
728
+ * `getCustomerById`. Such names are derived from the route alone.
729
+ *
730
+ * A `By…` clause counts as verbatim only when it names a whole parameter
731
+ * (`ByAppId` for `{appId}`) or is a bare `ById`. `get_webhook_by_token`
732
+ * for `/webhooks/{webhook_id}/{webhook_token}` is a chosen
733
+ * disambiguation, so its nouns are kept; likewise `deleteScheduledJob`.
734
+ */
735
+ export const isVerbatimRouteId = (operationId, ctx) => {
736
+ if (!operationId)
737
+ return true;
738
+ if (!isMechanicalOperationId(operationId, ctx))
739
+ return false;
740
+ const id = stripTag(operationId);
741
+ const m = HTTP_METHOD_ID.exec(id);
742
+ const norm = (t) => singularize(t.toLowerCase()).toLowerCase();
743
+ const idTokens = splitIdent(id.slice(m ? m[0].length : 0)).map(norm);
744
+ const literal = new Set();
745
+ const params = [];
746
+ for (const seg of ctx.path.split(/[?#]/)[0].split("/")) {
747
+ for (const mm of seg.matchAll(/\{([^}]+)\}/g)) {
748
+ params.push(splitIdent(mm[1]).map(norm));
749
+ }
750
+ for (const t of splitIdent(seg.replace(/\{[^}]*\}/g, " ")).map(norm)) {
751
+ literal.add(t);
752
+ }
753
+ }
754
+ const paramTokens = new Set(params.flat());
755
+ const clauses = [[]];
756
+ for (const t of idTokens) {
757
+ if (t === "by")
758
+ clauses.push([]);
759
+ else
760
+ clauses.at(-1).push(t);
761
+ }
762
+ const [head, ...byClauses] = clauses;
763
+ if (!head.every((t) => literal.has(t) || paramTokens.has(t)))
764
+ return false;
765
+ // `ByAppIdPromote`: a whole parameter, then route literals.
766
+ return byClauses.every((c) => {
767
+ const lengths = [
768
+ ...params
769
+ .filter((p) => p.every((t, i) => t === c[i]))
770
+ .map((p) => p.length),
771
+ ...(c[0] === "id" ? [1] : []),
772
+ ];
773
+ return lengths.some((n) => n <= c.length && c.slice(n).every((t) => literal.has(t)));
774
+ });
775
+ };
776
+ /** `activity/get-feeds` → `get-feeds`: a tag prefix is not part of the name. */
777
+ const stripTag = (operationId) => operationId.includes("/")
778
+ ? operationId.slice(operationId.lastIndexOf("/") + 1)
779
+ : operationId;
780
+ export const isMechanicalOperationId = (operationId, ctx) => {
781
+ if (!operationId)
782
+ return true;
783
+ const id = stripTag(operationId);
784
+ const m = HTTP_METHOD_ID.exec(id);
785
+ if (!m || m[1].toLowerCase() !== ctx.method.toLowerCase())
786
+ return false;
787
+ const norm = (t) => singularize(t.toLowerCase()).toLowerCase();
788
+ const joiners = new Set(["by", "for", "of", "in", "with", "and", "to"]);
789
+ const idTokens = splitIdent(id.slice(m[0].length))
790
+ .map(norm)
791
+ .filter((t) => !joiners.has(t));
792
+ const segments = ctx.path.split(/[?#]/)[0].split("/").filter(Boolean);
793
+ // Route tokens in order; literals are required, params and `/api`,
794
+ // `/v2` roots are optional (`getApiV1Foo` and `getFoo` both count).
795
+ const route = [];
796
+ for (const seg of segments) {
797
+ const param = /^\{(.*)\}$/.exec(seg);
798
+ const required = param === null && !isNoiseSegment(seg);
799
+ for (const t of splitIdent(param ? param[1] : seg).map(norm)) {
800
+ route.push({ token: t, required });
801
+ }
802
+ }
803
+ if (idTokens.length === 0)
804
+ return !route.some((r) => r.required);
805
+ // The id must read the route left to right, skipping only optional
806
+ // tokens. `delete-package-for-org` on /orgs/{org}/packages/… reads
807
+ // `package, org` — out of order — and stays a hand-written name.
808
+ let i = 0;
809
+ for (const r of route) {
810
+ if (i < idTokens.length && idTokens[i] === r.token) {
811
+ i++;
812
+ }
813
+ else if (r.required) {
814
+ return false;
815
+ }
816
+ }
817
+ return i === idTokens.length;
818
+ };
819
+ export const pathToVerbNoun = (ctx, hints = {}) => {
820
+ const method = ctx.method.toLowerCase();
821
+ const segments = ctx.path.split(/[?#]/)[0].split("/").filter(Boolean);
822
+ const literal = [];
823
+ // `{id}`, `:id`, and composite segments such as `{slug}-{id}`.
824
+ const isParam = (seg) => /\{[^}]*\}/.test(seg) || seg.startsWith(":");
825
+ let endsWithParam = false;
826
+ for (let i = 0; i < segments.length; i++) {
827
+ const seg = segments[i];
828
+ if (isParam(seg)) {
829
+ endsWithParam = i === segments.length - 1;
830
+ continue;
831
+ }
832
+ endsWithParam = false;
833
+ if (isNoiseSegment(seg))
834
+ continue;
835
+ literal.push(seg);
836
+ }
837
+ if (literal.length === 0)
838
+ return lowerFirst(method + "Root");
839
+ if (hints.nouns !== undefined) {
840
+ // `activity/get-feeds`: a tag prefix before the method is not a noun.
841
+ const id = hints.nouns.includes("/")
842
+ ? hints.nouns.slice(hints.nouns.lastIndexOf("/") + 1)
843
+ : hints.nouns;
844
+ const m = HTTP_METHOD_ID.exec(id);
845
+ const rest = m ? id.slice(m[0].length) : id;
846
+ // Route literals, both as written and singularised, so a parameter
847
+ // that echoes a literal (`{team}` under `/teams/`) is not mistaken
848
+ // for a parameter token.
849
+ const literal = new Set(segments
850
+ .filter((seg) => !isParam(seg))
851
+ .flatMap((seg) => splitIdent(seg))
852
+ .flatMap((t) => [t.toLowerCase(), singularize(t).toLowerCase()]));
853
+ const routeNoise = new Set(segments
854
+ .filter((seg) => !isParam(seg) && isNoiseSegment(seg))
855
+ .flatMap((seg) => splitIdent(seg).map((t) => t.toLowerCase())));
856
+ /** Whole parameter names as token lists (`{appId}` → ["app","id"]). */
857
+ const params = segments
858
+ .flatMap((seg) => [...seg.matchAll(/\{([^}]+)\}/g)])
859
+ .map((mm) => splitIdent(mm[1]).map((t) => t.toLowerCase()));
860
+ const raw = splitIdent(rest);
861
+ /** Longest whole parameter name starting at raw[at], else 0. */
862
+ const matchParam = (at) => {
863
+ let best = 0;
864
+ for (const prm of params) {
865
+ if (prm.length > best &&
866
+ prm.every((t, k) => raw[at + k]?.toLowerCase() === t)) {
867
+ best = prm.length;
868
+ }
869
+ }
870
+ if (best === 0 && raw[at]?.toLowerCase() === "id")
871
+ best = 1;
872
+ return best;
873
+ };
874
+ const tokens = [];
875
+ /** Index in `tokens` of a noun that a parameter immediately follows. */
876
+ const followedByParam = new Set();
877
+ for (let i = 0; i < raw.length; i++) {
878
+ const t = raw[i];
879
+ const lower = t.toLowerCase();
880
+ // `getApiV1Foo` → drop the api/version root the id copied from the
881
+ // route; a token the route does not have (`…DataAPI`) is kept.
882
+ if (isNoiseSegment(t) && routeNoise.has(lower))
883
+ continue;
884
+ let n = 0;
885
+ let skip = 0;
886
+ if (/^(by|for|of|in|with|and|to)$/.test(lower)) {
887
+ n = matchParam(i + 1);
888
+ skip = n + 1;
889
+ // Non-verbatim ids only drop a TRAILING `ById`; a `By` in the
890
+ // middle (`getBalanceByAsset`) is part of the chosen name.
891
+ if (n > 0 && !hints.verbatim && i + skip !== raw.length)
892
+ n = 0;
893
+ }
894
+ else if (hints.verbatim && i > 0) {
895
+ // `get_v1_users_id`, `GetAccountsAccount`: a whole parameter name
896
+ // with no `By`. When the parameter echoes the resource it follows
897
+ // (`/accounts/{account}`, `/teams/{team}`) the previous token has
898
+ // to be that resource; otherwise (`deleteProjectBranchCustomDomain`
899
+ // on `/custom-domains/{domain}`) it is the literal.
900
+ n = matchParam(i);
901
+ skip = n;
902
+ const echoes = raw
903
+ .slice(i, i + n)
904
+ .some((x) => literal.has(x.toLowerCase()));
905
+ const prev = tokens.at(-1)?.toLowerCase();
906
+ const prevIsResource = prev !== undefined &&
907
+ raw
908
+ .slice(i, i + n)
909
+ .some((x) => singularize(prev) === singularize(x.toLowerCase()) ||
910
+ prev === x.toLowerCase());
911
+ if (echoes && !prevIsResource)
912
+ n = 0;
913
+ }
914
+ if (n > 0) {
915
+ if (tokens.length)
916
+ followedByParam.add(tokens.length - 1);
917
+ i += skip - 1;
918
+ continue;
919
+ }
920
+ tokens.push(t);
921
+ }
922
+ if (hints.verbatim) {
923
+ for (const i of followedByParam)
924
+ tokens[i] = singularize(tokens[i]);
925
+ // `PostAccounts` → createAccount: a create makes one member.
926
+ if (method === "post" && !endsWithParam && tokens.length > 0) {
927
+ tokens[tokens.length - 1] = singularize(tokens.at(-1));
928
+ }
929
+ }
930
+ if (tokens.length > 0) {
931
+ // A POST that addresses one member (`POST /accounts/{account}`,
932
+ // Stripe-style) updates it; a POST on a collection creates.
933
+ const verb = method === "get"
934
+ ? !endsWithParam && hints.returnsCollection === true
935
+ ? "list"
936
+ : "get"
937
+ : method === "post"
938
+ ? endsWithParam
939
+ ? "update"
940
+ : "create"
941
+ : method === "patch"
942
+ ? "update"
943
+ : method;
944
+ return verb + tokens.map(pascalToken).join("");
945
+ }
946
+ }
947
+ const tokens = literal.map((seg) => splitIdent(seg));
948
+ const lastTokens = tokens.at(-1);
949
+ // `/papers/index`, `/opensearch/index` address an index resource, not a
950
+ // Rails `index` action.
951
+ const lastIsVerb = lastTokens.length > 0 &&
952
+ !isIndexVerb(lastTokens[0]) &&
953
+ (isTrailingVerb(lastTokens[0]) || isStrongVerb(lastTokens[0])) &&
954
+ !endsWithParam &&
955
+ method === "post";
956
+ // POST /things/{id}/publish → publishThing; POST /login → login.
957
+ if (lastIsVerb) {
958
+ const [verb, ...rest] = lastTokens;
959
+ const parents = tokens.slice(0, -1);
960
+ const nouns = parents.map((t, i) => i === parents.length - 1
961
+ ? t.map((x, j) => (j === t.length - 1 ? singularize(x) : x))
962
+ : t);
963
+ return (alias(verb) +
964
+ nouns.flat().map(pascalToken).join("") +
965
+ rest.map(pascalToken).join(""));
966
+ }
967
+ // GET is `list` only when the body is known to be a collection; an
968
+ // unknown or non-JSON body (`/zen`, `/octocat`) is a plain `get`.
969
+ const verb = method === "get"
970
+ ? !endsWithParam && hints.returnsCollection === true
971
+ ? "list"
972
+ : "get"
973
+ : method === "post"
974
+ ? endsWithParam
975
+ ? "update"
976
+ : "create"
977
+ : method === "patch"
978
+ ? "update"
979
+ : method === "put"
980
+ ? "put"
981
+ : method === "delete"
982
+ ? "delete"
983
+ : method;
984
+ // A GET/DELETE/PATCH/PUT on a collection route (no trailing parameter)
985
+ // addresses the collection itself — keep its plural. With a trailing
986
+ // parameter the route addresses one member, and `create` always makes
987
+ // one — singular.
988
+ const plural = !endsWithParam && verb !== "create";
989
+ const shaped = tokens.map((t, i) => {
990
+ const isResource = i === tokens.length - 1;
991
+ return t.map((x, j) => {
992
+ const last = j === t.length - 1;
993
+ if (!last)
994
+ return pascalToken(x);
995
+ // Parents are singular (`Org` in listOrgRepos).
996
+ if (!isResource || !plural)
997
+ return pascalToken(singularize(x));
998
+ return pascalToken(x);
999
+ });
1000
+ });
1001
+ return verb + shaped.flat().join("");
1002
+ };
1003
+ /**
1004
+ * Rename operations (and every shape whose name starts with the operation
1005
+ * name — `FooRequest`, `FooResponse`, `FooRequestBody`, `FooResponseItemsList`)
1006
+ * in a Smithy model to verbNoun PascalCase. Mutates `model.shapes`.
1007
+ * Colliding names keep the original id and are reported.
1008
+ */
1009
+ export const verbNounSmithyModel = (model) => {
1010
+ const shapes = model.shapes ?? {};
1011
+ const mapping = new Map();
1012
+ const collisions = [];
1013
+ const taken = new Set(Object.keys(shapes));
1014
+ const ops = Object.entries(shapes).filter(([, def]) => def?.type === "operation");
1015
+ const opLocals = new Set();
1016
+ for (const [id] of ops) {
1017
+ const hash = id.indexOf("#");
1018
+ opLocals.add(hash >= 0 ? id.slice(hash + 1) : id);
1019
+ }
1020
+ for (const [id] of ops) {
1021
+ const hash = id.indexOf("#");
1022
+ const ns = hash >= 0 ? id.slice(0, hash) : "";
1023
+ const local = hash >= 0 ? id.slice(hash + 1) : id;
1024
+ const camel = toVerbNoun(local);
1025
+ const nextLocal = camel.charAt(0).toUpperCase() + camel.slice(1);
1026
+ if (nextLocal === local)
1027
+ continue;
1028
+ const nextId = ns ? `${ns}#${nextLocal}` : nextLocal;
1029
+ if (taken.has(nextId) && !mapping.has(nextId)) {
1030
+ collisions.push(`${local} → ${nextLocal}`);
1031
+ continue;
1032
+ }
1033
+ if ([...mapping.values()].includes(nextId)) {
1034
+ collisions.push(`${local} → ${nextLocal}`);
1035
+ continue;
1036
+ }
1037
+ mapping.set(id, nextId);
1038
+ taken.add(nextId);
1039
+ // Companions: `<Op>Request`, `<Op>Response…`, and anything derived
1040
+ // from them by the converters' `${opName}Request${Member}` naming.
1041
+ // Skip prefixes that are themselves another operation's name
1042
+ // (`Get` vs `GetObject`) — only exact suffix matches count there.
1043
+ for (const candidate of Object.keys(shapes)) {
1044
+ if (candidate === id || mapping.has(candidate))
1045
+ continue;
1046
+ const cHash = candidate.indexOf("#");
1047
+ const cNs = cHash >= 0 ? candidate.slice(0, cHash) : "";
1048
+ const cLocal = cHash >= 0 ? candidate.slice(cHash + 1) : candidate;
1049
+ if (cNs !== ns || !cLocal.startsWith(local))
1050
+ continue;
1051
+ const tail = cLocal.slice(local.length);
1052
+ if (tail === "")
1053
+ continue;
1054
+ const suffixed = COMPANION_SUFFIXES.some((s) => tail.startsWith(s));
1055
+ if (!suffixed)
1056
+ continue;
1057
+ // `ListAppsRequest` vs op `List` + `AppsRequest`: require the tail to
1058
+ // start with a companion suffix immediately after the op name.
1059
+ const to = ns ? `${ns}#${nextLocal}${tail}` : `${nextLocal}${tail}`;
1060
+ if (taken.has(to))
1061
+ continue;
1062
+ mapping.set(candidate, to);
1063
+ taken.add(to);
1064
+ }
1065
+ }
1066
+ if (mapping.size === 0)
1067
+ return { renamed: 0, collisions };
1068
+ const nextShapes = {};
1069
+ for (const [id, def] of Object.entries(shapes)) {
1070
+ const newId = mapping.get(id) ?? id;
1071
+ nextShapes[newId] = remapTargets(def, mapping);
1072
+ }
1073
+ model.shapes = nextShapes;
1074
+ return {
1075
+ renamed: ops.filter(([opId]) => mapping.has(opId)).length,
1076
+ collisions,
1077
+ };
1078
+ };
1079
+ //# sourceMappingURL=rewrite-operation-ids.js.map