@voxgig/apidef 2.4.0 → 3.0.2

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 (96) hide show
  1. package/dist/apidef.d.ts +5 -1
  2. package/dist/apidef.js +197 -112
  3. package/dist/apidef.js.map +1 -1
  4. package/dist/builder/entity/entity.d.ts +3 -0
  5. package/dist/builder/entity/{apiEntity.js → entity.js} +12 -9
  6. package/dist/builder/entity/entity.js.map +1 -0
  7. package/dist/builder/entity/info.d.ts +3 -0
  8. package/dist/builder/entity/info.js +22 -0
  9. package/dist/builder/entity/info.js.map +1 -0
  10. package/dist/builder/entity.js +7 -21
  11. package/dist/builder/entity.js.map +1 -1
  12. package/dist/builder/flow/flowHeuristic01.js +21 -11
  13. package/dist/builder/flow/flowHeuristic01.js.map +1 -1
  14. package/dist/builder/flow.d.ts +2 -1
  15. package/dist/builder/flow.js +29 -4
  16. package/dist/builder/flow.js.map +1 -1
  17. package/dist/def.d.ts +62 -0
  18. package/dist/def.js +4 -0
  19. package/dist/def.js.map +1 -0
  20. package/dist/desc.d.ts +89 -0
  21. package/dist/desc.js +4 -0
  22. package/dist/desc.js.map +1 -0
  23. package/dist/guide/guide.d.ts +2 -1
  24. package/dist/guide/guide.js +161 -30
  25. package/dist/guide/guide.js.map +1 -1
  26. package/dist/guide/heuristic01.d.ts +2 -1
  27. package/dist/guide/heuristic01.js +1120 -234
  28. package/dist/guide/heuristic01.js.map +1 -1
  29. package/dist/model.d.ts +55 -0
  30. package/dist/model.js +4 -0
  31. package/dist/model.js.map +1 -0
  32. package/dist/parse.d.ts +1 -2
  33. package/dist/parse.js +8 -47
  34. package/dist/parse.js.map +1 -1
  35. package/dist/transform/args.d.ts +3 -0
  36. package/dist/transform/args.js +58 -0
  37. package/dist/transform/args.js.map +1 -0
  38. package/dist/transform/clean.js +27 -3
  39. package/dist/transform/clean.js.map +1 -1
  40. package/dist/transform/entity.d.ts +11 -3
  41. package/dist/transform/entity.js +57 -41
  42. package/dist/transform/entity.js.map +1 -1
  43. package/dist/transform/field.d.ts +3 -3
  44. package/dist/transform/field.js +90 -65
  45. package/dist/transform/field.js.map +1 -1
  46. package/dist/transform/operation.d.ts +1 -1
  47. package/dist/transform/operation.js +94 -296
  48. package/dist/transform/operation.js.map +1 -1
  49. package/dist/transform/select.d.ts +3 -0
  50. package/dist/transform/select.js +44 -0
  51. package/dist/transform/select.js.map +1 -0
  52. package/dist/transform/top.d.ts +9 -0
  53. package/dist/transform/top.js +11 -2
  54. package/dist/transform/top.js.map +1 -1
  55. package/dist/transform.js +4 -0
  56. package/dist/transform.js.map +1 -1
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/dist/types.d.ts +112 -19
  59. package/dist/types.js +4 -2
  60. package/dist/types.js.map +1 -1
  61. package/dist/utility.d.ts +30 -2
  62. package/dist/utility.js +381 -6
  63. package/dist/utility.js.map +1 -1
  64. package/model/apidef.jsonic +75 -1
  65. package/model/guide.jsonic +14 -44
  66. package/package.json +19 -14
  67. package/src/apidef.ts +264 -121
  68. package/src/builder/entity/{apiEntity.ts → entity.ts} +18 -11
  69. package/src/builder/entity/info.ts +53 -0
  70. package/src/builder/entity.ts +9 -35
  71. package/src/builder/flow/flowHeuristic01.ts +46 -12
  72. package/src/builder/flow.ts +39 -5
  73. package/src/def.ts +91 -0
  74. package/src/desc.ts +143 -0
  75. package/src/guide/guide.ts +207 -134
  76. package/src/guide/heuristic01.ts +1651 -272
  77. package/src/model.ts +98 -0
  78. package/src/parse.ts +5 -61
  79. package/src/schematron.ts.off +317 -0
  80. package/src/transform/args.ts +102 -0
  81. package/src/transform/clean.ts +43 -8
  82. package/src/transform/entity.ts +100 -51
  83. package/src/transform/field.ts +150 -71
  84. package/src/transform/operation.ts +118 -414
  85. package/src/transform/select.ts +90 -0
  86. package/src/transform/top.ts +76 -3
  87. package/src/transform.ts +4 -0
  88. package/src/types.ts +185 -5
  89. package/src/utility.ts +481 -9
  90. package/dist/builder/entity/apiEntity.d.ts +0 -3
  91. package/dist/builder/entity/apiEntity.js.map +0 -1
  92. package/dist/builder/entity/def.d.ts +0 -3
  93. package/dist/builder/entity/def.js +0 -19
  94. package/dist/builder/entity/def.js.map +0 -1
  95. package/src/builder/entity/def.ts +0 -44
  96. package/src/guide.ts.off +0 -136
package/src/model.ts ADDED
@@ -0,0 +1,98 @@
1
+ /* Copyright (c) 2024-2025 Voxgig, MIT License */
2
+
3
+ // Consolidated model types for the API model derived from OpenAPI specifications
4
+
5
+ import type { MethodName } from './types'
6
+
7
+
8
+ // Operation names available on entities
9
+ type OpName = 'load' | 'list' | 'create' | 'update' | 'delete' | 'patch' | 'head' | 'options'
10
+
11
+
12
+ // Entity relationships information
13
+ type ModelEntityRelations = {
14
+ ancestors: string[][]
15
+ }
16
+
17
+
18
+ // Map of operations available on an entity
19
+ type ModelOpMap = Partial<Record<OpName, ModelOp | undefined>>
20
+
21
+
22
+ // Field-specific operation configuration
23
+ type ModelFieldOp = {
24
+ type: any // @voxgig/struct validation schema
25
+ req: boolean
26
+ }
27
+
28
+
29
+ // Entity field definition
30
+ type ModelField = {
31
+ name: string
32
+ type: any // @voxgig/struct validation schema
33
+ req: boolean
34
+ op: Partial<Record<OpName, ModelFieldOp>>
35
+ }
36
+
37
+
38
+ // Operation argument/parameter definition
39
+ type ModelArg = {
40
+ name: string
41
+ type: any // @voxgig/struct validation schema
42
+ kind: 'param' | 'query' | 'header' | 'cookie'
43
+ req: boolean
44
+ }
45
+
46
+
47
+ // Alternative implementation of an operation
48
+ type ModelAlt = {
49
+ orig: string
50
+ method: MethodName
51
+ parts: string[]
52
+ args: Partial<{
53
+ param: ModelArg[]
54
+ query: ModelArg[]
55
+ header: ModelArg[]
56
+ cookie: ModelArg[]
57
+ }>
58
+ select: {
59
+ param?: Record<string, boolean | string>
60
+ query?: Record<string, boolean | string>
61
+ header?: Record<string, boolean | string>
62
+ cookie?: Record<string, boolean | string>
63
+ $action?: string
64
+ }
65
+ }
66
+
67
+
68
+ // Operation definition
69
+ type ModelOp = {
70
+ name: OpName
71
+ alts: ModelAlt[]
72
+ }
73
+
74
+
75
+ // Entity definition - core model entity with operations and fields
76
+ type ModelEntity = {
77
+ name: string,
78
+ op: ModelOpMap,
79
+ fields: ModelField[],
80
+ id: {
81
+ name: string,
82
+ field: string,
83
+ },
84
+ relations: ModelEntityRelations
85
+ }
86
+
87
+
88
+ export type {
89
+ OpName,
90
+ ModelEntityRelations,
91
+ ModelOpMap,
92
+ ModelFieldOp,
93
+ ModelField,
94
+ ModelArg,
95
+ ModelAlt,
96
+ ModelOp,
97
+ ModelEntity,
98
+ }
package/src/parse.ts CHANGED
@@ -1,11 +1,8 @@
1
- /* Copyright (c) 2024 Voxgig, MIT License */
1
+ /* Copyright (c) 2024-2025 Voxgig, MIT License */
2
2
 
3
3
  import { bundleFromString, createConfig } from '@redocly/openapi-core'
4
4
 
5
- import { each, snakify } from 'jostraca'
6
-
7
-
8
- import { depluralize } from './utility'
5
+ import decircular from 'decircular'
9
6
 
10
7
 
11
8
  // Parse an API definition source into a JSON sructure.
@@ -59,16 +56,17 @@ async function parseOpenAPI(source: any, meta?: any) {
59
56
  addXRefs(bundleWithRefs.bundle.parsed)
60
57
 
61
58
  // Serialize back to string with x-refs preserved
62
- const sourceWithXRefs = JSON.stringify(bundleWithRefs.bundle.parsed)
59
+ const sourceWithXRefs = JSON.stringify(decircular(bundleWithRefs.bundle.parsed))
63
60
 
64
61
  // Second pass: parse with dereferencing
65
62
  const bundle = await bundleFromString({
66
63
  source: sourceWithXRefs,
64
+ // source,
67
65
  config,
68
66
  dereference: true,
69
67
  })
70
68
 
71
- const def = bundle.bundle.parsed
69
+ const def = decircular(bundle.bundle.parsed)
72
70
 
73
71
  return def
74
72
  }
@@ -79,60 +77,6 @@ async function parseOpenAPI(source: any, meta?: any) {
79
77
 
80
78
 
81
79
 
82
- // Make consistent changes to support semantic entities.
83
- function rewrite(def: any) {
84
- const paths = def.paths
85
- each(paths, (path) => {
86
- each(path.parameters, (param: any) => {
87
-
88
- let new_param = param.name
89
- let new_path = path.key$
90
-
91
- // Rename param if nane is "id", and not the final param.
92
- // Rewrite /foo/{id}/bar as /foo/{foo_id}/bar.
93
- // Avoids ambiguity with bar id.
94
- if (param.name.match(/^id$/i)) {
95
- let m = path.key$.match(/\/([^\/]+)\/{id\}\/[^\/]/)
96
-
97
- if (m) {
98
- const parent = depluralize(snakify(m[1]))
99
- new_param = `${parent}_id`
100
- new_path = path.key$.replace('{id}', '{' + new_param + '}')
101
- }
102
- }
103
- else {
104
- new_param = depluralize(snakify(param.name))
105
- new_path = path.key$.replace('{' + param.name + '}', '{' + new_param + '}')
106
- }
107
-
108
- let pathdef = paths[path.key$]
109
- delete paths[path.key$]
110
-
111
- paths[new_path] = pathdef
112
- path.key$ = new_path
113
-
114
- param.name = new_param
115
- })
116
- })
117
-
118
-
119
- sortkeys(def, 'paths')
120
- sortkeys(def, 'components')
121
-
122
- return def
123
- }
124
-
125
-
126
- function sortkeys(obj: any, prop: string) {
127
- const sorted: any = {}
128
- const sorted_keys = Object.keys(obj[prop]).sort()
129
- for (let sk of sorted_keys) {
130
- sorted[sk] = obj[prop][sk]
131
- }
132
- obj[prop] = sorted
133
- }
134
-
135
80
  export {
136
81
  parse,
137
- rewrite,
138
82
  }
@@ -0,0 +1,317 @@
1
+ type JSONSchema = Record<string, any>;
2
+
3
+ type ConvertOptions = {
4
+ /** Where to resolve $ref from. Use your OpenAPI doc’s components.schemas, or JSON Schema $defs. */
5
+ refRoots?: Array<Record<string, JSONSchema>>;
6
+ /** Optional: pass the entire root doc; only used if your $ref are absolute like #/components/schemas/X */
7
+ rootDoc?: Record<string, any>;
8
+ };
9
+
10
+ const TYPE_TOKEN: Record<string, string> = {
11
+ string: "$STRING",
12
+ number: "$NUMBER",
13
+ integer: "$INTEGER",
14
+ boolean: "$BOOLEAN",
15
+ null: "$NULL",
16
+ };
17
+
18
+ export function convert(
19
+ schema: JSONSchema | undefined,
20
+ opts: ConvertOptions = {}
21
+ ): any {
22
+ const seen = new Set<object>();
23
+ const resolvingRefs = new Set<string>();
24
+
25
+
26
+ const resolveRef = ($ref: string): JSONSchema | undefined => {
27
+ if (!$ref.startsWith("#/")) return undefined; // non-local refs not supported per spec; return undefined to fallback
28
+ const parts = $ref.slice(2).split("/").map(unescapeRefToken);
29
+ let node: any = opts.rootDoc ?? {};
30
+ for (const p of parts) node = node?.[p];
31
+ if (node) return node;
32
+
33
+ // Try common roots passed in refRoots (e.g., components.schemas, $defs)
34
+ for (const root of opts.refRoots ?? []) {
35
+ let probe: any = root;
36
+ for (const p of parts) probe = probe?.[p];
37
+ if (probe) return probe;
38
+ }
39
+ return undefined;
40
+ };
41
+
42
+ const expandRef = (sch: JSONSchema): JSONSchema => {
43
+ if (typeof sch?.$ref === "string") {
44
+ const target = resolveRef(sch.$ref);
45
+ if (target) return target;
46
+ // Not supported → per rule 10, “just expand the reference”; if we cannot, fall back to ANY.
47
+ return {};
48
+ }
49
+ return sch;
50
+ };
51
+
52
+ function convert(s: JSONSchema | undefined): any {
53
+ if (!s || Object.keys(s).length === 0) {
54
+ // Rule 13 – empty schema accepts anything
55
+ return "$ANY";
56
+ }
57
+
58
+ // Prevent cycles from blowing the stack on malformed docs
59
+ if (seen.has(s)) return "$ANY";
60
+ seen.add(s);
61
+
62
+ // // // Expand $ref immediately (Rule 10)
63
+ // // if (s.$ref) {
64
+ // // const tgt = expandRef(s);
65
+ // // // Merge local decorations on top of the ref target (common in OAS)
66
+ // // const merged = { ...tgt, ...without(s, ["$ref"]) };
67
+ // // const out = convert(merged);
68
+ // // seen.delete(s);
69
+ // // return out;
70
+ // // }
71
+
72
+
73
+ // // Expand $ref immediately (Rule 10)
74
+ // if (s.$ref) {
75
+ // const target = expandRef(s);
76
+ // const localDecor = without(s, ["$ref"]); // local fields that decorate the ref
77
+ // // Use the same semantics as allOf merging to deep-merge properties/openness/etc.
78
+ // const merged = mergeAllOf([target, localDecor]);
79
+ // const out = convert(merged);
80
+ // seen.delete(s);
81
+ // return out;
82
+ // }
83
+
84
+ if (s.$ref) {
85
+ const ref = String(s.$ref);
86
+ if (resolvingRefs.has(ref)) {
87
+ // cycle detected -> cannot expand, fall back to ANY
88
+ seen.delete(s);
89
+ return "$ANY";
90
+ }
91
+ resolvingRefs.add(ref);
92
+
93
+ const target = expandRef(s); // returns referenced schema or {}
94
+ const localDecor = without(s, ["$ref"]);
95
+ // merge like allOf so local decorations augment target
96
+ const merged = mergeAllOf([target, localDecor]);
97
+
98
+ const out = convert(merged);
99
+
100
+ resolvingRefs.delete(ref);
101
+ seen.delete(s);
102
+ return out;
103
+ }
104
+
105
+
106
+ // Composition
107
+ if (Array.isArray(s.allOf) && s.allOf.length > 0) {
108
+ const merged = mergeAllOf(s.allOf.map((x) => expandRef(x)));
109
+ // Carry over top-level decorations (e.g., nullable, annotations)
110
+ const mergedWithTop = { ...merged, ...without(s, ["allOf"]) };
111
+ const out = convert(mergedWithTop);
112
+ seen.delete(s);
113
+ return out;
114
+ }
115
+
116
+ if (Array.isArray(s.oneOf) && s.oneOf.length > 0) {
117
+ const alts = s.oneOf.map((x) => convert(expandRef(x)));
118
+ const out = ["$ONE", ...alts];
119
+ seen.delete(s);
120
+ return out;
121
+ }
122
+
123
+ if (Array.isArray(s.anyOf) && s.anyOf.length > 0) {
124
+ const alts = s.anyOf.map((x) => convert(expandRef(x)));
125
+ const out = ["$ANY", ...alts]; // Rule 3 – anyOf uses "$ANY" in the same directive position
126
+ seen.delete(s);
127
+ return out;
128
+ }
129
+
130
+ // Enum / const → $EXACT
131
+ if (Array.isArray(s.enum) && s.enum.length > 0) {
132
+ const out = ["$EXACT", ...s.enum];
133
+ seen.delete(s);
134
+ return out;
135
+ }
136
+ if (Object.prototype.hasOwnProperty.call(s, "const")) {
137
+ const out = ["$EXACT", s.const];
138
+ seen.delete(s);
139
+ return out;
140
+ }
141
+
142
+ // Handle OpenAPI nullable at this level by wrapping the base type later
143
+ const nullable = s.nullable === true;
144
+
145
+ // Type handling (could be string or array)
146
+ const t = s.type;
147
+ if (Array.isArray(t) && t.length > 0) {
148
+ // Union of primitives (and possibly null)
149
+ const tokens = t.map((tt) => TYPE_TOKEN[tt] ?? "$ANY");
150
+ const out = ["$ONE", ...tokens];
151
+ seen.delete(s);
152
+ return out;
153
+ }
154
+
155
+ // Objects
156
+ if (t === "object" || s.properties || s.additionalProperties !== undefined) {
157
+ const obj: Record<string, any> = {};
158
+
159
+ // $OPEN
160
+ if (s.additionalProperties === true) {
161
+ obj["$OPEN"] = true;
162
+ } else if (s.additionalProperties && typeof s.additionalProperties === "object") {
163
+ // We cannot express typed extras (Rule 6). Keep it open but untyped.
164
+ obj["$OPEN"] = true;
165
+ }
166
+ // $NOTE annotations (Rule 11)
167
+ const note: Record<string, any> = {};
168
+ if (s.readOnly === true) note.readOnly = true;
169
+ if (s.writeOnly === true) note.writeOnly = true;
170
+ if (s.deprecated === true) note.deprecated = true;
171
+ if (Object.keys(note).length > 0) obj["$NOTE"] = note;
172
+
173
+ const props = s.properties ?? {};
174
+ for (const [key, sub] of Object.entries<JSONSchema>(props)) {
175
+ const converted = convert(expandRef(sub));
176
+ const isNullable =
177
+ sub?.nullable === true ||
178
+ (Array.isArray(sub?.type) && sub.type.includes("null")) ||
179
+ includesNullViaOneAnyOf(sub);
180
+
181
+ obj[key] = isNullable ? wrapNullable(converted) : converted;
182
+ }
183
+
184
+ seen.delete(s);
185
+ return obj;
186
+ }
187
+
188
+ // Arrays
189
+ if (t === "array" || s.items) {
190
+ const items = s.items;
191
+ if (Array.isArray(items)) {
192
+ // Tuple validation → positional sub-schemas
193
+ const out = items.map((it) => convert(expandRef(it)));
194
+ seen.delete(s);
195
+ return out;
196
+ } else if (items && typeof items === "object") {
197
+ // Homogeneous array → ["$CHILD", sub]
198
+ const out = ["$CHILD", convert(expandRef(items))];
199
+ seen.delete(s);
200
+ return out;
201
+ } else {
202
+ // No items → accept any child element
203
+ seen.delete(s);
204
+ return [];
205
+ }
206
+ }
207
+
208
+ // Primitives
209
+ if (typeof t === "string" && TYPE_TOKEN[t]) {
210
+ const token = TYPE_TOKEN[t];
211
+ const base = token;
212
+
213
+ const out = nullable ? wrapNullable(base) : base;
214
+ seen.delete(s);
215
+ return out;
216
+ }
217
+
218
+ // No explicit type, but we might still infer:
219
+ if (s.properties || s.additionalProperties !== undefined) {
220
+ // already handled in object branch, but keep a guard
221
+ const out = convert({ ...s, type: "object" });
222
+ seen.delete(s);
223
+ return out;
224
+ }
225
+ if (s.items) {
226
+ const out = convert({ ...s, type: "array" });
227
+ seen.delete(s);
228
+ return out;
229
+ }
230
+
231
+ // Fallback
232
+ seen.delete(s);
233
+ return "$ANY";
234
+ }
235
+
236
+ const result = convert(schema);
237
+ return result;
238
+ }
239
+
240
+ /* -------------------------- helpers -------------------------- */
241
+
242
+ function unescapeRefToken(s: string) {
243
+ // JSON Pointer ~0 -> ~, ~1 -> /
244
+ return s.replace(/~1/g, "/").replace(/~0/g, "~");
245
+ }
246
+
247
+ function without<T extends Record<string, any>>(obj: T, keys: string[]): T {
248
+ const copy: any = {};
249
+ for (const k of Object.keys(obj)) if (!keys.includes(k)) copy[k] = obj[k];
250
+ return copy;
251
+ }
252
+
253
+ function includesNullViaOneAnyOf(s: JSONSchema): boolean {
254
+ const hasNullIn = (arr?: any[]) =>
255
+ Array.isArray(arr) &&
256
+ arr.some((x) => x?.type === "null" || (Array.isArray(x?.type) && x.type.includes("null")));
257
+ return hasNullIn(s.oneOf) || hasNullIn(s.anyOf);
258
+ }
259
+
260
+ function wrapNullable(inner: any) {
261
+ // Rule 1/4 – nullable → ["$ONE", inner, "$NULL"]
262
+ return Array.isArray(inner) && inner[0] === "$ONE"
263
+ ? inner.includes("$NULL")
264
+ ? inner
265
+ : ["$ONE", ...inner.slice(1), "$NULL"]
266
+ : ["$ONE", inner, "$NULL"];
267
+ }
268
+
269
+ function mergeAllOf(schemas: JSONSchema[]): JSONSchema {
270
+ const out: JSONSchema = {};
271
+ let anyObject = false;
272
+
273
+ for (const s of schemas) {
274
+ const schema = s || {};
275
+
276
+ if (schema.type === "object" || schema.properties || schema.additionalProperties !== undefined) {
277
+ anyObject = true;
278
+ out.type = "object";
279
+
280
+ // --- properties: merge without losing earlier branches ---
281
+ const srcProps = schema.properties ?? {};
282
+ const dstProps = (out.properties = out.properties ?? {});
283
+ for (const [k, v] of Object.entries(srcProps)) {
284
+ if (k in dstProps) {
285
+ // Combine the two property schemas using allOf semantics.
286
+ // This preserves earlier constraints like {type:'number'} and
287
+ // later decorations like {nullable:true}.
288
+ dstProps[k] = { allOf: [dstProps[k], v] };
289
+ } else {
290
+ dstProps[k] = v;
291
+ }
292
+ }
293
+
294
+ // --- additionalProperties: openness is "true if any true/object" ---
295
+ const ap = schema.additionalProperties;
296
+ if (ap === true || (ap && typeof ap === "object")) {
297
+ out.additionalProperties = true;
298
+ } else if (out.additionalProperties === undefined) {
299
+ out.additionalProperties = ap;
300
+ }
301
+
302
+ // --- required: union (not used in voxgig/struct but harmless to keep) ---
303
+ if (Array.isArray(schema.required)) {
304
+ const req = new Set([...(out.required ?? []), ...schema.required]);
305
+ out.required = Array.from(req);
306
+ }
307
+ } else {
308
+ // For non-object shapes in allOf, a shallow merge is the best we can do.
309
+ Object.assign(out, schema);
310
+ }
311
+ }
312
+
313
+ // If nothing was objecty, return a shallow merge across all schemas.
314
+ return anyObject
315
+ ? out
316
+ : (schemas.reduce((a, b) => ({ ...a, ...(b || {}) }), {}) as JSONSchema);
317
+ }
@@ -0,0 +1,102 @@
1
+
2
+
3
+ import { each, snakify } from 'jostraca'
4
+
5
+ import type { TransformResult, Transform } from '../transform'
6
+
7
+ import { formatJSONIC, depluralize, validator } from '../utility'
8
+
9
+
10
+ import { KIT } from '../types'
11
+
12
+ import type { KitModel } from '../types'
13
+
14
+ import type {
15
+ PathDef,
16
+ ParameterDef,
17
+ MethodDef,
18
+ } from '../def'
19
+
20
+ import type {
21
+ OpName,
22
+ ModelOp,
23
+ ModelEntity,
24
+ ModelAlt,
25
+ ModelArg,
26
+ } from '../model'
27
+
28
+
29
+
30
+ const argsTransform = async function(
31
+ ctx: any,
32
+ ): Promise<TransformResult> {
33
+ const { apimodel, def } = ctx
34
+ const kit: KitModel = apimodel.main[KIT]
35
+
36
+ let msg = 'args '
37
+
38
+
39
+ each(kit.entity, (ment: ModelEntity, entname: string) => {
40
+ each(ment.op, (mop: ModelOp, opname: OpName) => {
41
+ each(mop.alts, (malt: ModelAlt) => {
42
+ const argdefs: ParameterDef[] = []
43
+
44
+ const pathdef: PathDef = def.paths[malt.orig]
45
+ argdefs.push(...(pathdef.parameters ?? []))
46
+
47
+ const opdef: MethodDef = (pathdef as any)[malt.method.toLowerCase()]
48
+ argdefs.push(...(opdef.parameters ?? []))
49
+
50
+ resolveArgs(ment, mop, malt, argdefs)
51
+ })
52
+ })
53
+
54
+ msg += ment.name + ' '
55
+ })
56
+
57
+ return { ok: true, msg }
58
+ }
59
+
60
+
61
+ const ARG_KIND: Record<string, ModelArg["kind"]> = {
62
+ 'query': 'query',
63
+ 'header': 'header',
64
+ 'path': 'param',
65
+ 'cookie': 'cookie',
66
+ }
67
+
68
+
69
+ function resolveArgs(ment: ModelEntity, mop: ModelOp, malt: ModelAlt, argdefs: ParameterDef[]) {
70
+ each(argdefs, (argdef: ParameterDef) => {
71
+ const marg: ModelArg = {
72
+ name: depluralize(snakify(argdef.name)),
73
+ type: validator(argdef.schema?.type),
74
+ kind: ARG_KIND[argdef.in] ?? 'query',
75
+ req: !!argdef.required
76
+ }
77
+
78
+ if (argdef.nullable) {
79
+ marg.type = ['`$ONE`', '`$NULL`', marg.type]
80
+ }
81
+
82
+ // insert sorted by name
83
+ let kindargs = (malt.args[marg.kind] = malt.args[marg.kind] ?? [])
84
+
85
+ let kalen = kindargs.length
86
+ for (let ka, i = 0; i <= kalen; i++) {
87
+ ka = kindargs[i]
88
+ if (ka && ka.name > marg.name) {
89
+ kindargs = [...kindargs.slice(0, i), marg, ...kindargs.slice(i + 1)]
90
+ }
91
+ else {
92
+ kindargs.push(marg)
93
+ }
94
+ }
95
+ })
96
+ }
97
+
98
+
99
+
100
+ export {
101
+ argsTransform,
102
+ }
@@ -1,10 +1,9 @@
1
1
 
2
- import { each, getx } from 'jostraca'
3
-
4
2
  import type { TransformResult } from '../transform'
5
3
 
6
- import { walk } from '@voxgig/struct'
4
+ import { walk, isempty, isnode, ismap, islist } from '@voxgig/struct'
7
5
 
6
+ import { formatJSONIC } from '../utility'
8
7
 
9
8
 
10
9
  const cleanTransform = async function(
@@ -12,12 +11,48 @@ const cleanTransform = async function(
12
11
  ): Promise<TransformResult> {
13
12
  const { apimodel } = ctx
14
13
 
15
- walk(apimodel, (k: any, v: any) => {
16
- if ('string' === typeof k && k.includes('$')) {
17
- return undefined
14
+ let cur: any[] = []
15
+
16
+ // Remove empty nodes and undefined values
17
+ walk(
18
+ apimodel,
19
+ (k: any, v: any, _p: any, ancestors: any) => {
20
+ if (undefined === k) {
21
+ cur[ancestors.length] = ismap(v) ? {} : islist(v) ? [] : v
22
+ return v
23
+ }
24
+
25
+ let vi = v
26
+
27
+ if (isnode(v)) {
28
+ if (isempty(v)) {
29
+ vi = undefined
30
+ }
31
+ else {
32
+ vi = cur[ancestors.length] = ismap(v) ? {} : []
33
+ }
34
+
35
+ }
36
+
37
+ if (undefined !== vi && !k.endsWith('$')) {
38
+ cur[ancestors.length - 1][k] = vi
39
+ }
40
+
41
+ return v
42
+ },
43
+
44
+ (k: any, _v: any, _p: any, ancestors: any) => {
45
+ const pi = cur[ancestors.length - 1]
46
+ if (undefined !== pi) {
47
+ const vi = pi[k]
48
+ if (isnode(vi) && isempty(vi)) {
49
+ delete pi[k]
50
+ }
51
+ }
18
52
  }
19
- return v
20
- })
53
+ )
54
+
55
+ ctx.apimodel = cur[0]
21
56
 
22
57
  return { ok: true, msg: 'clean' }
23
58
  }