@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.
- package/dist/apidef.d.ts +4 -1
- package/dist/apidef.js +12 -34
- package/dist/apidef.js.map +1 -1
- package/dist/builder/entity/entity.js +0 -33
- package/dist/builder/entity/entity.js.map +1 -1
- package/dist/builder/entity/info.js +0 -4
- package/dist/builder/entity/info.js.map +1 -1
- package/dist/builder/flow.d.ts +4 -1
- package/dist/builder/flow.js +36 -27
- package/dist/builder/flow.js.map +1 -1
- package/dist/guide/graphql01.js +0 -55
- package/dist/guide/graphql01.js.map +1 -1
- package/dist/guide/guide.js +3 -126
- package/dist/guide/guide.js.map +1 -1
- package/dist/guide/heuristic01.js +40 -181
- package/dist/guide/heuristic01.js.map +1 -1
- package/dist/parse/graphql.js +0 -25
- package/dist/parse/graphql.js.map +1 -1
- package/dist/parse.js +51 -56
- package/dist/parse.js.map +1 -1
- package/dist/resolved.d.ts +23 -0
- package/dist/resolved.js +92 -0
- package/dist/resolved.js.map +1 -0
- package/dist/resolver.js +0 -2
- package/dist/resolver.js.map +1 -1
- package/dist/transform/args.js +2 -25
- package/dist/transform/args.js.map +1 -1
- package/dist/transform/casecollide.d.ts +3 -0
- package/dist/transform/casecollide.js +67 -0
- package/dist/transform/casecollide.js.map +1 -0
- package/dist/transform/contract.js +43 -38
- package/dist/transform/contract.js.map +1 -1
- package/dist/transform/entity.js +0 -56
- package/dist/transform/entity.js.map +1 -1
- package/dist/transform/field.js +1 -368
- package/dist/transform/field.js.map +1 -1
- package/dist/transform/flowstep.js +0 -49
- package/dist/transform/flowstep.js.map +1 -1
- package/dist/transform/graphql.js +0 -36
- package/dist/transform/graphql.js.map +1 -1
- package/dist/transform/operation.js +0 -23
- package/dist/transform/operation.js.map +1 -1
- package/dist/transform/top.js +1 -128
- package/dist/transform/top.js.map +1 -1
- package/dist/transform.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.js.map +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +17 -316
- package/dist/utility.js.map +1 -1
- package/model/apidef.aon +20 -113
- package/model/guide.aon +1 -32
- package/package.json +5 -5
- package/src/apidef.ts +22 -33
- package/src/builder/entity/entity.ts +0 -33
- package/src/builder/entity/info.ts +0 -4
- package/src/builder/flow.ts +39 -24
- package/src/desc.ts +0 -22
- package/src/guide/graphql01.ts +0 -75
- package/src/guide/guide.ts +5 -127
- package/src/guide/heuristic01.ts +47 -182
- package/src/model.ts +6 -85
- package/src/parse/graphql.ts +0 -25
- package/src/parse.ts +61 -56
- package/src/resolved.ts +136 -0
- package/src/resolver.ts +0 -2
- package/src/transform/args.ts +2 -25
- package/src/transform/casecollide.ts +80 -0
- package/src/transform/contract.ts +44 -34
- package/src/transform/entity.ts +0 -56
- package/src/transform/field.ts +2 -368
- package/src/transform/flowstep.ts +0 -49
- package/src/transform/graphql.ts +0 -36
- package/src/transform/operation.ts +0 -23
- package/src/transform/top.ts +1 -131
- package/src/transform.ts +0 -1
- package/src/types.ts +4 -13
- package/src/utility.ts +18 -314
package/dist/utility.js
CHANGED
|
@@ -33,6 +33,7 @@ exports.inferFieldType = inferFieldType;
|
|
|
33
33
|
exports.normalizeFieldName = normalizeFieldName;
|
|
34
34
|
exports.prefixLeadingDigit = prefixLeadingDigit;
|
|
35
35
|
exports.debugpath = debugpath;
|
|
36
|
+
exports.debugpathOn = debugpathOn;
|
|
36
37
|
exports.findPathsWithPrefix = findPathsWithPrefix;
|
|
37
38
|
exports.writeFileSyncWarn = writeFileSyncWarn;
|
|
38
39
|
exports.warnOnError = warnOnError;
|
|
@@ -115,35 +116,6 @@ function formatJsonSrc(jsonsrc) {
|
|
|
115
116
|
.replace(RE_JSON_TRAILING_BRACE, '}\n')
|
|
116
117
|
.replace(RE_JSON_COMMENT, '\n\n$1# $2 $3');
|
|
117
118
|
}
|
|
118
|
-
// Common irregular plurals, in the form plural → singular. Used at the
|
|
119
|
-
// head of depluralize() to short-circuit cases the suffix rules below
|
|
120
|
-
// would otherwise mishandle.
|
|
121
|
-
//
|
|
122
|
-
// Three over-strip classes are worked around here because the surface
|
|
123
|
-
// form gives no clean discriminator:
|
|
124
|
-
//
|
|
125
|
-
// * `-Vse+s` plurals (houses, phases, noses, …) would hit the
|
|
126
|
-
// generic `-ses → ∅` rule and become hous/phas/nos. Every such
|
|
127
|
-
// -se+s plural needs an explicit entry.
|
|
128
|
-
//
|
|
129
|
-
// * `-che+s` plurals (caches, niches, headaches, …) would hit the
|
|
130
|
-
// generic `-ches → ∅` rule and become cach/nich/headach. Same
|
|
131
|
-
// pattern: no letter-doubling tell exists (cache vs church both
|
|
132
|
-
// have a single 'ch'), so each -che singular is enumerated.
|
|
133
|
-
//
|
|
134
|
-
// * `-oe+s` plurals (shoes, canoes, oboes) would hit the generic
|
|
135
|
-
// `-oes → -o` rule (for potatoes/heroes) and become sho/cano/obo.
|
|
136
|
-
// Only collision-safe entries are listed: a key must not also be a
|
|
137
|
-
// suffix of a real `-o`+es plural (e.g. `toes` is excluded because
|
|
138
|
-
// it would turn tomatoes → tomatoe).
|
|
139
|
-
//
|
|
140
|
-
// Keys are lowercase; depluralize() does a case-insensitive lookup
|
|
141
|
-
// and reapplies the caller's casing on the way out.
|
|
142
|
-
//
|
|
143
|
-
// Null-prototype: these tables are indexed by spec-derived names, so a
|
|
144
|
-
// schema or path segment called `constructor` / `__proto__` / `toString`
|
|
145
|
-
// would otherwise resolve to the inherited Object member and be returned
|
|
146
|
-
// as a "match" — crashing matchCase() on a function. See NULL_PROTO_NOTE.
|
|
147
119
|
const IRREGULARS = Object.assign(Object.create(null), {
|
|
148
120
|
'analytics': 'analytics',
|
|
149
121
|
'analyses': 'analysis',
|
|
@@ -209,26 +181,11 @@ const IRREGULARS = Object.assign(Object.create(null), {
|
|
|
209
181
|
'women': 'woman',
|
|
210
182
|
'yes': 'yes',
|
|
211
183
|
});
|
|
212
|
-
// NULL_PROTO_NOTE: every plain-object lookup table in this module whose keys
|
|
213
|
-
// come from an API spec is built with a null prototype
|
|
214
|
-
// (`Object.assign(Object.create(null), {...})`). Without it, `TABLE[name]`
|
|
215
|
-
// inherits from Object.prototype, so `TABLE['constructor']` yields the Object
|
|
216
|
-
// constructor (truthy, a function) and `TABLE['__proto__']` yields
|
|
217
|
-
// Object.prototype (truthy, an object). Both then flow into code expecting a
|
|
218
|
-
// string. A spec with `components.schemas.Constructor` is enough to reach
|
|
219
|
-
// this: canonize -> depluralize -> matchCase -> `.toLowerCase is not a
|
|
220
|
-
// function`, failing the whole build at the guide stage. Keep new tables
|
|
221
|
-
// null-prototype, or use a Map (CANONIZE_CACHE already does).
|
|
222
184
|
// Sorted longest-first so the most specific IRREGULARS suffix wins.
|
|
223
185
|
// Without this, 'women' would be shadowed by 'men' (3 < 5) under
|
|
224
186
|
// insertion-order iteration. Both happen to round-trip correctly
|
|
225
187
|
// today, but the sort makes any future entry safe by construction.
|
|
226
188
|
const IRREGULAR_KEYS = Object.keys(IRREGULARS).sort((a, b) => b.length - a.length);
|
|
227
|
-
// Reapply the case pattern of `source` to `target`. Used so the
|
|
228
|
-
// case-insensitive lookups in depluralize() preserve the caller's
|
|
229
|
-
// casing on the way out (HOUSES → HOUSE, Houses → House, houses →
|
|
230
|
-
// house). Falls through to `target` unchanged for mixed-case sources
|
|
231
|
-
// that don't fit one of the three canonical patterns.
|
|
232
189
|
function matchCase(source, target) {
|
|
233
190
|
if (source === source.toLowerCase())
|
|
234
191
|
return target.toLowerCase();
|
|
@@ -239,20 +196,6 @@ function matchCase(source, target) {
|
|
|
239
196
|
}
|
|
240
197
|
return target;
|
|
241
198
|
}
|
|
242
|
-
// Per-model plural overrides, populated from the model's
|
|
243
|
-
// `main.custom.plurals` section at apidef pipeline entry and cleared
|
|
244
|
-
// between runs. Checked by depluralize() before the built-in
|
|
245
|
-
// IRREGULARS table and rule chain — so a model can override any
|
|
246
|
-
// default depluralization, including correct-by-default cases, when
|
|
247
|
-
// its domain demands a different singular (e.g. fitness API with
|
|
248
|
-
// {axes: axe}, photography app with {lenses: lens}).
|
|
249
|
-
//
|
|
250
|
-
// Module-level rather than a parameter so the many existing
|
|
251
|
-
// depluralize/canonize call sites across transforms and guide
|
|
252
|
-
// inherit the override without signature churn. apidef is
|
|
253
|
-
// single-model-per-process; if that ever changes, switch this to a
|
|
254
|
-
// per-context map.
|
|
255
|
-
// Null-prototype: see NULL_PROTO_NOTE above.
|
|
256
199
|
let CUSTOM_PLURALS = Object.create(null);
|
|
257
200
|
let CUSTOM_PLURAL_KEYS = [];
|
|
258
201
|
function setCustomPlurals(plurals) {
|
|
@@ -313,7 +256,6 @@ function depluralize(word) {
|
|
|
313
256
|
// Rules for regular plurals (applied in order). The -ies and -ves
|
|
314
257
|
// rules add a letter, so they need to match the case of the dropped
|
|
315
258
|
// suffix; all other rules just slice and inherit the caller's case.
|
|
316
|
-
// -ies -> -y (cities -> city), but only if result is > 2 chars
|
|
317
259
|
if (lower.endsWith('ies') && word.length > 3) {
|
|
318
260
|
const dropped = word.slice(-3);
|
|
319
261
|
const y = dropped === dropped.toUpperCase() ? 'Y' : 'y';
|
|
@@ -342,12 +284,6 @@ function depluralize(word) {
|
|
|
342
284
|
if (lower.endsWith('nses')) {
|
|
343
285
|
return word.slice(0, -1);
|
|
344
286
|
}
|
|
345
|
-
// -zes plurals come from -ze singulars (prize, size, freeze, maze,
|
|
346
|
-
// breeze, …) far more often than from a bare -z taking -es. The only
|
|
347
|
-
// -zes plurals that strip the full -es have a doubled-z stem
|
|
348
|
-
// (buzz/buzzes, fez/fezzes). Discriminate on -zzes so prizes → prize
|
|
349
|
-
// instead of priz. Mirrors the -ses/-Vse+s problem the IRREGULARS
|
|
350
|
-
// table works around for the -se case.
|
|
351
287
|
if (lower.endsWith('zzes')) {
|
|
352
288
|
return word.slice(0, -2);
|
|
353
289
|
}
|
|
@@ -359,7 +295,6 @@ function depluralize(word) {
|
|
|
359
295
|
lower.endsWith('shes') || lower.endsWith('ches')) {
|
|
360
296
|
return word.slice(0, -2);
|
|
361
297
|
}
|
|
362
|
-
// -s -> remove -s (cats -> cat), but only if result is > 2 chars
|
|
363
298
|
if (lower.endsWith('s') &&
|
|
364
299
|
!lower.endsWith('ss') &&
|
|
365
300
|
!lower.endsWith('us') &&
|
|
@@ -371,9 +306,13 @@ function depluralize(word) {
|
|
|
371
306
|
}
|
|
372
307
|
function find(obj, qkey) {
|
|
373
308
|
const vals = [];
|
|
309
|
+
const seen = new WeakSet();
|
|
374
310
|
const collect = (o) => {
|
|
375
311
|
if (!o || 'object' !== typeof o)
|
|
376
312
|
return;
|
|
313
|
+
if (seen.has(o))
|
|
314
|
+
return;
|
|
315
|
+
seen.add(o);
|
|
377
316
|
if (Array.isArray(o)) {
|
|
378
317
|
for (let i = 0; i < o.length; i++)
|
|
379
318
|
collect(o[i]);
|
|
@@ -456,7 +395,6 @@ function $APPEND(inj, val, ref, store) {
|
|
|
456
395
|
const dval = dparent?.[key];
|
|
457
396
|
const vstore = { ...store };
|
|
458
397
|
vstore.$TOP = { [key]: dval };
|
|
459
|
-
// const ptval = transform({ [key]: dval }, { [key]: xform }, {
|
|
460
398
|
const ptval = (0, struct_1.inject)({ [key]: xform }, vstore, {
|
|
461
399
|
meta: { ...inj.meta },
|
|
462
400
|
errs: inj.errs,
|
|
@@ -500,7 +438,6 @@ function $SELECT(inj, _val, _ref, store) {
|
|
|
500
438
|
'$KEY': { '`$LIKE`': selector.toString() }
|
|
501
439
|
};
|
|
502
440
|
}
|
|
503
|
-
// TODO: select should be safe for scalars
|
|
504
441
|
const children = (0, struct_1.select)(dparents, selector);
|
|
505
442
|
if (0 < children.length) {
|
|
506
443
|
for (let child of children) {
|
|
@@ -509,8 +446,6 @@ function $SELECT(inj, _val, _ref, store) {
|
|
|
509
446
|
(0, struct_1.inject)((0, struct_1.clone)({ [child.$KEY]: descendor }), vstore, {
|
|
510
447
|
meta: (0, struct_1.merge)([
|
|
511
448
|
inj.meta,
|
|
512
|
-
// TODO: need this hack as struct does not provide a way to get grandparent keys
|
|
513
|
-
// also, these capture actions are not preserving the path!
|
|
514
449
|
{ select: { key: { [(0, struct_1.slice)(inj.path, 1, -1).join('+')]: child.$KEY } } }
|
|
515
450
|
]),
|
|
516
451
|
errs: inj.errs,
|
|
@@ -524,8 +459,6 @@ function $RECASE(inj, val, ref, store) {
|
|
|
524
459
|
&& null != inj.prior
|
|
525
460
|
&& null != inj.prior.prior) {
|
|
526
461
|
const dval = inj.parent[inj.key];
|
|
527
|
-
// TODO: handle paths more generally! use inj.prior?
|
|
528
|
-
// TODO: mkae this into a utility method on inj?
|
|
529
462
|
const dkey = inj.prior.key;
|
|
530
463
|
const gkey = inj.prior.prior.key;
|
|
531
464
|
const vstore = { ...store };
|
|
@@ -542,13 +475,6 @@ function $RECASE(inj, val, ref, store) {
|
|
|
542
475
|
inj.setval(tval, 2);
|
|
543
476
|
}
|
|
544
477
|
}
|
|
545
|
-
// A special-purpose regex-style matcher for url paths.
|
|
546
|
-
// t - text part
|
|
547
|
-
// p - param part
|
|
548
|
-
// / - part separator
|
|
549
|
-
// / at start - must match from start
|
|
550
|
-
// / at end - must match to end
|
|
551
|
-
// See utility.test.ts for examples
|
|
552
478
|
function pathMatch(path, expr) {
|
|
553
479
|
if (null == path) {
|
|
554
480
|
return null;
|
|
@@ -658,9 +584,6 @@ function formatJSONIC(val, opts) {
|
|
|
658
584
|
'`' + JSON.stringify(v)
|
|
659
585
|
.substring(1)
|
|
660
586
|
.replace(/\\n/g, '\n')
|
|
661
|
-
// Inside a JSONIC backtick literal a double quote is a literal
|
|
662
|
-
// character, so unescape JSON's \" back to " (was previously
|
|
663
|
-
// replaced with ':' which silently corrupted quoted text).
|
|
664
587
|
.replace(/\\"/g, '"')
|
|
665
588
|
.replace(/`/g, '\\`')
|
|
666
589
|
.replace(/"$/, '`'));
|
|
@@ -690,13 +613,7 @@ function formatJSONIC(val, opts) {
|
|
|
690
613
|
};
|
|
691
614
|
return renderJSONIC(val, hsepd, showd, useColor, maxlines, exclude, c, renderPrimitive, renderComment, false);
|
|
692
615
|
}
|
|
693
|
-
function renderJSONIC(val, hsepd, showd, useColor, maxlines, exclude, c, renderPrimitive, renderComment,
|
|
694
|
-
// True once the value has already been through decircular. `seen` never
|
|
695
|
-
// forgets a node, so a merely REPEATED reference (a shared node in a DAG,
|
|
696
|
-
// not a cycle) also lands in the fallback below. If decircular leaves such
|
|
697
|
-
// a repeat in place, retrying forever overflows the stack — which is what
|
|
698
|
-
// formatting a deep validation error used to do. Retry at most once.
|
|
699
|
-
decircularized) {
|
|
616
|
+
function renderJSONIC(val, hsepd, showd, useColor, maxlines, exclude, c, renderPrimitive, renderComment, decircularized) {
|
|
700
617
|
const space = ' ';
|
|
701
618
|
const seen = new WeakSet();
|
|
702
619
|
let stack = new Array(32);
|
|
@@ -817,16 +734,6 @@ decircularized) {
|
|
|
817
734
|
}
|
|
818
735
|
return lines.join('\n') + '\n';
|
|
819
736
|
}
|
|
820
|
-
// Canonical type-sentinel vocabulary. VALID_CANON maps an OpenAPI type NAME
|
|
821
|
-
// to its `$SENTINEL` form; CANON_ONE is the union sentinel produced by
|
|
822
|
-
// `validator` for a multi-type (`['`$ONE`', [member, ...]]`). Both are part
|
|
823
|
-
// of the public API so downstream consumers (e.g. @voxgig/sdkgen's
|
|
824
|
-
// sentinel -> language-type table) can verify they cover the full set
|
|
825
|
-
// instead of hand-syncing against this file.
|
|
826
|
-
//
|
|
827
|
-
// Null-prototype (see NULL_PROTO_NOTE): `type` values come from the spec, so
|
|
828
|
-
// a schema declaring `type: constructor` would otherwise return the Object
|
|
829
|
-
// constructor here rather than falling through to the 'Any' default.
|
|
830
737
|
const VALID_CANON = Object.assign(Object.create(null), {
|
|
831
738
|
'string': '`$STRING`',
|
|
832
739
|
'number': '`$NUMBER`',
|
|
@@ -858,61 +765,24 @@ function transliterate(s) {
|
|
|
858
765
|
return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
859
766
|
}
|
|
860
767
|
const CANONIZE_CACHE = new Map();
|
|
768
|
+
// A trailing plural after an acronym is part of the word, and snakify would
|
|
769
|
+
// split it letter by letter. See docs/design/derived-names.md
|
|
770
|
+
const ACRONYM_PLURAL_RE = /([A-Z]{2,})s(?![a-zA-Z])/g;
|
|
861
771
|
function canonize(s) {
|
|
862
772
|
if (null == s || '' === s)
|
|
863
773
|
return '';
|
|
864
774
|
const cached = CANONIZE_CACHE.get(s);
|
|
865
775
|
if (undefined !== cached)
|
|
866
776
|
return cached;
|
|
867
|
-
const
|
|
777
|
+
const deacronymed = transliterate(s)
|
|
778
|
+
.replace(FILE_EXT_RE, '')
|
|
779
|
+
.replace(ACRONYM_PLURAL_RE, (_m, run) => run[0] + run.slice(1).toLowerCase() + 's');
|
|
780
|
+
const out = depluralize((0, jostraca_1.snakify)(deacronymed))
|
|
868
781
|
.replace(/[^a-zA-Z_0-9]/g, '');
|
|
869
782
|
CANONIZE_CACHE.set(s, out);
|
|
870
783
|
return out;
|
|
871
784
|
}
|
|
872
|
-
// Canonicalise a FIELD name — which is a WIRE identifier, not a type name.
|
|
873
|
-
//
|
|
874
|
-
// `canonize` is right for entity/type names: it snakifies and depluralizes so
|
|
875
|
-
// `Users` and `user-items` converge on `user` / `user_item`. Applied to a
|
|
876
|
-
// field it is actively WRONG, because the name has to match the JSON the
|
|
877
|
-
// server actually sends:
|
|
878
|
-
//
|
|
879
|
-
// modelType -> canonize -> model_type (server sends modelType)
|
|
880
|
-
// items -> canonize -> item (server sends items)
|
|
881
|
-
//
|
|
882
|
-
// Nothing maps back: the model's `alias.field` map is emitted empty and no
|
|
883
|
-
// generator consumes it, so the wire name is simply lost. Across the fleet's
|
|
884
|
-
// specs that renamed 23% of all fields (146 repos) and depluralized another
|
|
885
|
-
// 13% — every one of those SDKs reading a key the server never sends.
|
|
886
|
-
//
|
|
887
|
-
// So: keep the transliteration and identifier sanitisation that stop a name
|
|
888
|
-
// being unusable in a target language, and drop the snakify/depluralize that
|
|
889
|
-
// change what the name MEANS. Case and plurality are preserved verbatim.
|
|
890
785
|
const CANONIZE_FIELD_CACHE = new Map();
|
|
891
|
-
// Re-split a boundary-less entity name using the component name's boundaries.
|
|
892
|
-
//
|
|
893
|
-
// ONLY when the name has no boundaries of its own. A name that already
|
|
894
|
-
// contains `_` was split by something that knew where the words were, and is
|
|
895
|
-
// left exactly as it is.
|
|
896
|
-
//
|
|
897
|
-
// ONLY on an EXACT concatenation match, which is what stops this inventing
|
|
898
|
-
// anything. The component's segments are accumulated left to right and the
|
|
899
|
-
// result is used only if it equals the path-derived name with its separators
|
|
900
|
-
// removed:
|
|
901
|
-
//
|
|
902
|
-
// payeeverification + payee_verification_result
|
|
903
|
-
// payee -> "payee" no
|
|
904
|
-
// payee_verification -> "payeeverification" YES -> payee_verification
|
|
905
|
-
//
|
|
906
|
-
// virtual + payment_account
|
|
907
|
-
// payment -> "payment" no
|
|
908
|
-
// payment_account -> "paymentaccount" no -> unchanged
|
|
909
|
-
//
|
|
910
|
-
// So a component that has nothing to do with the path token cannot rename it,
|
|
911
|
-
// and a component that merely EXTENDS the token (`payment_account_entry` for
|
|
912
|
-
// `payment`) cannot either -- only the prefix that reconstructs the token
|
|
913
|
-
// exactly is taken. A different entity name is never reachable from here; the
|
|
914
|
-
// same letters in the same order, with separators restored, is the whole of
|
|
915
|
-
// what this can produce.
|
|
916
786
|
function resplitFromCmp(entname, cmp, why) {
|
|
917
787
|
if (null == entname || '' === entname || entname.includes('_')) {
|
|
918
788
|
return entname;
|
|
@@ -945,12 +815,6 @@ function canonizeField(s) {
|
|
|
945
815
|
CANONIZE_FIELD_CACHE.set(s, out);
|
|
946
816
|
return out;
|
|
947
817
|
}
|
|
948
|
-
// Namespace-qualified schema names (ASP.NET / Java style:
|
|
949
|
-
// "NoFrixion.MoneyMoov.Models.PaymentRequests.MerchantPayment",
|
|
950
|
-
// "com.example.api.Payment") describe the type by their LAST dotted
|
|
951
|
-
// segment; the namespace prefix is packaging noise. Reduce to the last
|
|
952
|
-
// meaningful segment — skipping version-ish ("v2", "10") or too-short
|
|
953
|
-
// tails — so entity names derive from the type, not the namespace.
|
|
954
818
|
function stripSchemaNamespace(name) {
|
|
955
819
|
if (null == name || !name.includes('.'))
|
|
956
820
|
return name;
|
|
@@ -971,24 +835,6 @@ function canonizeCmpName(orig) {
|
|
|
971
835
|
return canonize(stripSchemaNamespace(orig));
|
|
972
836
|
}
|
|
973
837
|
const FIRST_LETTER_RE = /[a-zA-Z]/;
|
|
974
|
-
// No target language permits an identifier that starts with a digit, so a
|
|
975
|
-
// name derived from one — a `3dsSession` schema, a `/2fa` path segment, a
|
|
976
|
-
// `_3DSecure` GraphQL type — is prefixed with an `n`.
|
|
977
|
-
//
|
|
978
|
-
// The prefix takes the case of the name it guards: lower for `3ds_session`,
|
|
979
|
-
// upper for `3DSecure`. That keeps the result inside whatever casing
|
|
980
|
-
// convention the caller was already working in, so a later PascalCase or
|
|
981
|
-
// camelCase conversion has nothing to undo. A name with no letter in it at
|
|
982
|
-
// all (`404`) takes the lower-case prefix.
|
|
983
|
-
//
|
|
984
|
-
// This is the ONE place the rule lives. Entity names reach it through
|
|
985
|
-
// `ensureMinEntityName` and the GraphQL guide's `entityName`; project slugs
|
|
986
|
-
// through `sanitizeSlug`.
|
|
987
|
-
//
|
|
988
|
-
// FIELD names deliberately do NOT come here. A field name is a WIRE
|
|
989
|
-
// identifier and renaming it makes the SDK read a key the server never sends
|
|
990
|
-
// — the mistake `canonizeField` exists to document. Targets escape those at
|
|
991
|
-
// the point of emission instead.
|
|
992
838
|
function prefixLeadingDigit(s) {
|
|
993
839
|
if (null == s || '' === s)
|
|
994
840
|
return s;
|
|
@@ -1096,13 +942,6 @@ function ensureMinEntityName(name, existing) {
|
|
|
1096
942
|
padded = padded + padding;
|
|
1097
943
|
}
|
|
1098
944
|
if (padded !== name && null != existing[padded]) {
|
|
1099
|
-
// The name was modified (truncated/sanitized) and collides with an
|
|
1100
|
-
// existing entity. Only a collision between DIFFERENT origins needs a
|
|
1101
|
-
// numeric suffix — the same original name re-encountered (e.g. the same
|
|
1102
|
-
// long schema referenced by several methods on one path) must reuse the
|
|
1103
|
-
// existing entity so its ops merge instead of minting phantom
|
|
1104
|
-
// "<entity>2/3/4" entities. Entities record their pre-truncation name
|
|
1105
|
-
// as `longname`; entries without one keep the old always-suffix rule.
|
|
1106
945
|
if (existing[padded].longname === name) {
|
|
1107
946
|
return padded;
|
|
1108
947
|
}
|
|
@@ -1119,23 +958,7 @@ function ensureMinEntityName(name, existing) {
|
|
|
1119
958
|
}
|
|
1120
959
|
// Unconditional suffixes: framework noise, always stripped.
|
|
1121
960
|
const CMP_SUFFIXES = ['_rest_controller', '_controller', '_response', '_request'];
|
|
1122
|
-
// Guarded suffixes: pagination wrappers ('_page_response', '_page') and
|
|
1123
|
-
// op-reply wrappers ('_create_response', '_update_response') fold wrapper
|
|
1124
|
-
// schemas (BeneficiaryPageResponse, MerchantTokenPage,
|
|
1125
|
-
// BeneficiariesCreateResponse, ...) into their base entity — but ONLY when
|
|
1126
|
-
// the remainder is itself a known component schema (the wrapper
|
|
1127
|
-
// convention). Without that guard a real noun gets mangled: an API whose
|
|
1128
|
-
// resource IS a page (LandingPage entity at /landing-pages) must keep
|
|
1129
|
-
// 'landing_page', not become 'landing'. Order matters: longer first, since
|
|
1130
|
-
// '_page_response'/'_create_response' also end with '_response'. Bare
|
|
1131
|
-
// '_create'/'_update' are never stripped: too likely part of a real noun.
|
|
1132
961
|
const CMP_GUARDED_SUFFIXES = ['_create_response', '_update_response', '_page_response', '_page'];
|
|
1133
|
-
// Op-reply wrappers of the form <namespace><Verb>Result (incident.io:
|
|
1134
|
-
// AlertsResolveResultV2, SeveritiesListResultV1). Same idea as
|
|
1135
|
-
// '_create_response' above, but the verb varies and sits before the wrapper
|
|
1136
|
-
// word rather than being part of a fixed pair, so the shape is matched rather
|
|
1137
|
-
// than enumerated. Guarded exactly like the others: nothing is stripped
|
|
1138
|
-
// unless the remainder is a real schema.
|
|
1139
962
|
const CMP_RESULT_VERBS = [
|
|
1140
963
|
'list', 'create', 'show', 'update', 'delete', 'remove',
|
|
1141
964
|
'get', 'edit', 'resolve', 'rotate', 'search',
|
|
@@ -1149,26 +972,6 @@ const CMP_PREFIXES = ['get_', 'post_', 'put_', 'delete_', 'patch_'];
|
|
|
1149
972
|
function guideActive(node) {
|
|
1150
973
|
return false !== node?.active;
|
|
1151
974
|
}
|
|
1152
|
-
// An API's ACCESS-TOKEN EXCHANGE is not a resource, and must not become an
|
|
1153
|
-
// entity. The shape apidef looks for is the one every such endpoint has:
|
|
1154
|
-
//
|
|
1155
|
-
// 1. The spec as a whole is SECURED (a top-level `security` requirement).
|
|
1156
|
-
// Without that, a per-operation `security: []` clears nothing and
|
|
1157
|
-
// carries no signal at all.
|
|
1158
|
-
// 2. The operation clears that requirement with its own `security: []` —
|
|
1159
|
-
// it is the one call a client can make before it holds a credential,
|
|
1160
|
-
// because it is what issues them.
|
|
1161
|
-
// 3. It is a POST. A credential exchange writes; a GET that happens to
|
|
1162
|
-
// return a field called `token` is far likelier to be a resource.
|
|
1163
|
-
// 4. Its success response carries a TOKEN-shaped field.
|
|
1164
|
-
//
|
|
1165
|
-
// All four together, or it is a resource. There is deliberately no vendor
|
|
1166
|
-
// extension and no overlay to say otherwise (ADR-002): a spec apidef does
|
|
1167
|
-
// not control cannot be annotated anyway, and a heuristic that can be
|
|
1168
|
-
// corrected in guide.aon needs no second correction surface.
|
|
1169
|
-
//
|
|
1170
|
-
// Returns the field names the exchange uses, which is what sdkgen's
|
|
1171
|
-
// `secrets` feature needs to drive it, or null when this is a resource.
|
|
1172
975
|
const AUTH_TOKEN_FIELDS = [
|
|
1173
976
|
'access_token', 'accessToken', 'access-token',
|
|
1174
977
|
'id_token', 'idToken',
|
|
@@ -1188,8 +991,6 @@ function authExchangeOp(op, specSecured) {
|
|
|
1188
991
|
if (true !== specSecured) {
|
|
1189
992
|
return null;
|
|
1190
993
|
}
|
|
1191
|
-
// An empty ARRAY, specifically. `security` absent means "inherit the
|
|
1192
|
-
// global requirement"; `security: []` means "no credential needed here".
|
|
1193
994
|
if (!Array.isArray(op?.security) || 0 !== op.security.length) {
|
|
1194
995
|
return null;
|
|
1195
996
|
}
|
|
@@ -1230,9 +1031,6 @@ function schemaProps(schema) {
|
|
|
1230
1031
|
}
|
|
1231
1032
|
return Object.keys(props);
|
|
1232
1033
|
}
|
|
1233
|
-
// First name in `names` that the schema declares, compared case-insensitively
|
|
1234
|
-
// so `Access_Token` matches `access_token`. Ordered by the CANDIDATE list, not
|
|
1235
|
-
// by declaration order, so `access_token` wins over a sibling `token`.
|
|
1236
1034
|
function firstFieldMatch(props, names) {
|
|
1237
1035
|
const lower = new Map();
|
|
1238
1036
|
for (const p of props) {
|
|
@@ -1344,6 +1142,10 @@ function warnOnError(where, warn, fn, result) {
|
|
|
1344
1142
|
return result;
|
|
1345
1143
|
}
|
|
1346
1144
|
}
|
|
1145
|
+
function debugpathOn() {
|
|
1146
|
+
const apipath = process.env.APIDEF_DEBUG_PATH;
|
|
1147
|
+
return null != apipath && '' !== apipath;
|
|
1148
|
+
}
|
|
1347
1149
|
function debugpath(pathStr, methodName, ...args) {
|
|
1348
1150
|
const apipath = process.env.APIDEF_DEBUG_PATH;
|
|
1349
1151
|
if (null == apipath || '' === apipath)
|
|
@@ -1353,7 +1155,6 @@ function debugpath(pathStr, methodName, ...args) {
|
|
|
1353
1155
|
// Check if path matches
|
|
1354
1156
|
if (pathStr !== targetPath)
|
|
1355
1157
|
return;
|
|
1356
|
-
// If a method is specified in apipath and we have a method name, check if it matches
|
|
1357
1158
|
if (targetMethod && methodName) {
|
|
1358
1159
|
if (methodName.toLowerCase() !== targetMethod.toLowerCase())
|
|
1359
1160
|
return;
|
|
@@ -1384,7 +1185,6 @@ function findPathsWithPrefix(ctx, pathStr, opts) {
|
|
|
1384
1185
|
});
|
|
1385
1186
|
return matchingPaths.length;
|
|
1386
1187
|
}
|
|
1387
|
-
// TODO: move to jostraca?
|
|
1388
1188
|
function allcapify(s) {
|
|
1389
1189
|
return 'string' === typeof s ? (0, jostraca_1.snakify)(s).toUpperCase() : '';
|
|
1390
1190
|
}
|
|
@@ -1505,47 +1305,11 @@ function isEntityWrapperProp(propSchema) {
|
|
|
1505
1305
|
const t = propSchema.type;
|
|
1506
1306
|
return 'object' === t || 'array' === t;
|
|
1507
1307
|
}
|
|
1508
|
-
// A response body that is nothing but a single wrapper property is an
|
|
1509
|
-
// ENVELOPE around the result: `{item: {...}}`, `{data: {...}}`,
|
|
1510
|
-
// `{items: [...]}`, `{results: [...]}`. Return that property's name so the
|
|
1511
|
-
// caller can unwrap to it, or null when the body is the result itself.
|
|
1512
|
-
//
|
|
1513
|
-
// Two conditions keep this from firing on a response that IS the entity:
|
|
1514
|
-
//
|
|
1515
|
-
// 1. EXACTLY ONE property. A body with siblings is a structure in its own
|
|
1516
|
-
// right, not a wrapper — `{ok, id}` from a delete, or any paged
|
|
1517
|
-
// `{results, next}`, must be handed over whole.
|
|
1518
|
-
// 2. The property's SHAPE matches the operation's cardinality. A `list`
|
|
1519
|
-
// unwraps only to an array, every other op only to a non-array. So a
|
|
1520
|
-
// single-entity op facing `{items: [...]}` is left alone rather than
|
|
1521
|
-
// silently yielding a list, and vice versa.
|
|
1522
|
-
//
|
|
1523
|
-
// A one-field entity whose sole field is itself structured can still be
|
|
1524
|
-
// unwrapped wrongly; that is the residual cost of the spec not saying which
|
|
1525
|
-
// it means. Naming the wrapper after the entity remains the unambiguous
|
|
1526
|
-
// signal, and is still checked first.
|
|
1527
1308
|
function envelopeProp(resprops, opname) {
|
|
1528
1309
|
const keys = (0, struct_1.keysof)(resprops);
|
|
1529
1310
|
if (0 === keys.length) {
|
|
1530
1311
|
return null;
|
|
1531
1312
|
}
|
|
1532
|
-
// Exactly one STRUCTURED property, with any siblings being scalars.
|
|
1533
|
-
//
|
|
1534
|
-
// The original rule demanded exactly one property full stop, which missed
|
|
1535
|
-
// the single most common envelope shape in the wild:
|
|
1536
|
-
//
|
|
1537
|
-
// { "success": true, "data": [ ... ] }
|
|
1538
|
-
// { "status": "ok", "result": { ... } }
|
|
1539
|
-
//
|
|
1540
|
-
// A boolean/string status flag beside the payload is metadata, not a
|
|
1541
|
-
// sibling of equal standing, so the body is still an envelope. UniVec's
|
|
1542
|
-
// /v1/models returns exactly this and every generated SDK — TypeScript, Go,
|
|
1543
|
-
// Python alike — returned an empty list against an API plainly serving
|
|
1544
|
-
// data.
|
|
1545
|
-
//
|
|
1546
|
-
// Scalar-only siblings keep the guard meaningful: `{ok, id}` from a delete
|
|
1547
|
-
// has no structured member and is still handed over whole, and a body with
|
|
1548
|
-
// TWO structured members is a composite we must not guess at.
|
|
1549
1313
|
const structured = keys.filter((k) => isEntityWrapperProp(resprops[k]));
|
|
1550
1314
|
if (1 !== structured.length) {
|
|
1551
1315
|
return null;
|
|
@@ -1558,15 +1322,6 @@ function envelopeProp(resprops, opname) {
|
|
|
1558
1322
|
}
|
|
1559
1323
|
return key;
|
|
1560
1324
|
}
|
|
1561
|
-
// An UNTAGGED union: `oneOf`/`anyOf` with two or more real branches and no
|
|
1562
|
-
// `discriminator`. Nothing in the schema says which branch a given value is,
|
|
1563
|
-
// so no generator can choose a variant, and the field can only be modelled as
|
|
1564
|
-
// an open type.
|
|
1565
|
-
//
|
|
1566
|
-
// Two shapes are deliberately NOT unions to resolve:
|
|
1567
|
-
// - a discriminated union — the discriminator names the deciding property;
|
|
1568
|
-
// - the nullable idiom `anyOf: [X, {type: 'null'}]`, which is one type that
|
|
1569
|
-
// may be absent, not a choice between variants.
|
|
1570
1325
|
function untaggedUnionBranches(schema) {
|
|
1571
1326
|
if (null == schema || 'object' !== typeof schema) {
|
|
1572
1327
|
return 0;
|
|
@@ -1581,17 +1336,6 @@ function untaggedUnionBranches(schema) {
|
|
|
1581
1336
|
const real = branches.filter((b) => null != b && 'null' !== b.type);
|
|
1582
1337
|
return real.length < 2 ? 0 : real.length;
|
|
1583
1338
|
}
|
|
1584
|
-
// Deepest/widest untagged union reachable from a field schema, or null when
|
|
1585
|
-
// the field is resolvable.
|
|
1586
|
-
//
|
|
1587
|
-
// The search is RECURSIVE because the union is rarely at the top: in the
|
|
1588
|
-
// Typebot Builder spec the `groups` field is an array whose item schema
|
|
1589
|
-
// carries 18 untagged unions, the widest 19 branches, 12 levels down. Only a
|
|
1590
|
-
// field that bottoms out in such a union has to degrade to an open type, so
|
|
1591
|
-
// only a recursive scan can report which fields those are.
|
|
1592
|
-
//
|
|
1593
|
-
// `seen` guards the self-referential schemas these specs use freely; `depth`
|
|
1594
|
-
// is bounded so a pathological document cannot spin.
|
|
1595
1339
|
function scanUntaggedUnion(schema, depth = 0, seen = new Set()) {
|
|
1596
1340
|
if (null == schema || 'object' !== typeof schema ||
|
|
1597
1341
|
seen.has(schema) || depth > MAX_UNION_SCAN_DEPTH) {
|
|
@@ -1626,15 +1370,6 @@ function scanUntaggedUnion(schema, depth = 0, seen = new Set()) {
|
|
|
1626
1370
|
return 0 === count ? null : { count, branches, depth: at };
|
|
1627
1371
|
}
|
|
1628
1372
|
const MAX_UNION_SCAN_DEPTH = 64;
|
|
1629
|
-
// Is this schema a collection? null when the schema does not say.
|
|
1630
|
-
//
|
|
1631
|
-
// `isEntityWrapperProp` accepts a composed schema (allOf/oneOf/anyOf) as
|
|
1632
|
-
// structured, but a composed schema carries no outer `type` or `items` — so
|
|
1633
|
-
// reading those alone silently called it a non-list. A `list` then kept its
|
|
1634
|
-
// envelope, and worse, a single-entity op unwrapped to an array-valued
|
|
1635
|
-
// property. Composed branches are inspected instead, and unanimity required:
|
|
1636
|
-
// a union that is an array in one branch and an object in another does not
|
|
1637
|
-
// say what the caller will get, and an envelope is not worth guessing at.
|
|
1638
1373
|
function propIsList(schema) {
|
|
1639
1374
|
if (null == schema || 'object' !== typeof schema) {
|
|
1640
1375
|
return null;
|
|
@@ -1657,26 +1392,6 @@ function propIsList(schema) {
|
|
|
1657
1392
|
}
|
|
1658
1393
|
return 'array' === schema.type || null != schema.items;
|
|
1659
1394
|
}
|
|
1660
|
-
// The request BODY a closed schema permits, as a transform mapping.
|
|
1661
|
-
//
|
|
1662
|
-
// `additionalProperties: false` is the spec saying the server rejects any
|
|
1663
|
-
// property it did not declare. When a body says that, sending the caller's
|
|
1664
|
-
// whole request payload is wrong: an op's payload also carries its PATH
|
|
1665
|
-
// params (`id` for `PUT /item/{id}`), and a closed shape 400s the entire
|
|
1666
|
-
// request over that one extra key. Restricting the body to the declared
|
|
1667
|
-
// properties is then not a heuristic — it is what the spec asked for.
|
|
1668
|
-
//
|
|
1669
|
-
// Returns null for an open or property-less schema, where `reqdata` (send
|
|
1670
|
-
// everything) remains the right default: an open body accepts extras, and
|
|
1671
|
-
// with no declared properties there is nothing to restrict to.
|
|
1672
|
-
//
|
|
1673
|
-
// The KEY is the property's wire name — that is what goes on the wire and
|
|
1674
|
-
// what the server matches against. The SOURCE is read by the field's
|
|
1675
|
-
// CANONICAL name, because that is the only name the caller ever sees:
|
|
1676
|
-
// findFieldDefs runs every property through `canonize(normalizeFieldName())`,
|
|
1677
|
-
// so a spec property `UserName` reaches the generated request type as
|
|
1678
|
-
// `user_name`. Reading `reqdata.UserName` would find nothing and send
|
|
1679
|
-
// undefined.
|
|
1680
1395
|
function closedBodyTransform(schema) {
|
|
1681
1396
|
if (null == schema || 'object' !== typeof schema) {
|
|
1682
1397
|
return null;
|
|
@@ -1688,26 +1403,12 @@ function closedBodyTransform(schema) {
|
|
|
1688
1403
|
if (0 === names.length) {
|
|
1689
1404
|
return null;
|
|
1690
1405
|
}
|
|
1691
|
-
// Null prototype: a spec is free to declare a property called `__proto__`,
|
|
1692
|
-
// and on an ordinary object that assignment sets the prototype instead of
|
|
1693
|
-
// creating an own property — the mapping would vanish, and with it the
|
|
1694
|
-
// whole body restriction if it were the only one.
|
|
1695
1406
|
const out = Object.create(null);
|
|
1696
1407
|
for (const name of names) {
|
|
1697
1408
|
out[name] = '`reqdata.' + canonize(normalizeFieldName(name)) + '`';
|
|
1698
1409
|
}
|
|
1699
1410
|
return out;
|
|
1700
1411
|
}
|
|
1701
|
-
// The first sentence of `text` (up to a `.`/`!`/`?` followed by whitespace
|
|
1702
|
-
// or end), whitespace-collapsed and length-capped with an ellipsis.
|
|
1703
|
-
//
|
|
1704
|
-
// Shared by the API summary and by a field's `short`, because both answer
|
|
1705
|
-
// the same question — give me one line of prose for a place that has room
|
|
1706
|
-
// for one line. A field's `short` is interpolated straight into a markdown
|
|
1707
|
-
// table cell by every generated Readme, where a raw newline ends the row and
|
|
1708
|
-
// takes the rest of the table with it. Specs supply plenty: bullet lists,
|
|
1709
|
-
// fenced examples and multi-paragraph notes all appear as a property's
|
|
1710
|
-
// `description`, up to 1725 characters of it in the validation corpus.
|
|
1711
1412
|
function firstSentence(text) {
|
|
1712
1413
|
const collapsed = text.replace(/\s+/g, ' ').trim();
|
|
1713
1414
|
const m = collapsed.match(/^(.+?[.!?])(\s|$)/);
|