@voxgig/apidef 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apidef.d.ts +2 -6
- package/dist/apidef.js +44 -11
- package/dist/apidef.js.map +1 -1
- package/dist/builder/flow/flowHeuristic01.js +1 -1
- package/dist/builder/flow/flowHeuristic01.js.map +1 -1
- package/dist/guide/guide.d.ts +2 -0
- package/dist/guide/guide.js +107 -0
- package/dist/guide/guide.js.map +1 -0
- package/dist/guide/heuristic01.js +149 -218
- package/dist/guide/heuristic01.js.map +1 -1
- package/dist/resolver.js +2 -2
- package/dist/resolver.js.map +1 -1
- package/dist/transform/entity.js +1 -2
- package/dist/transform/entity.js.map +1 -1
- package/dist/transform/field.js +1 -2
- package/dist/transform/field.js.map +1 -1
- package/dist/transform/operation.js +1 -2
- package/dist/transform/operation.js.map +1 -1
- package/dist/transform/top.js +0 -1
- package/dist/transform/top.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +297 -2
- package/dist/types.js +12 -1
- package/dist/types.js.map +1 -1
- package/dist/utility.d.ts +3 -1
- package/dist/utility.js +155 -0
- package/dist/utility.js.map +1 -1
- package/model/apidef.jsonic +1 -50
- package/model/guide.jsonic +53 -0
- package/package.json +6 -5
- package/src/apidef.ts +61 -15
- package/src/builder/flow/flowHeuristic01.ts +1 -1
- package/src/guide/guide.ts +274 -0
- package/src/guide/heuristic01.ts +173 -232
- package/src/{guide.ts → guide.ts.off} +8 -5
- package/src/resolver.ts +2 -2
- package/src/transform/entity.ts +1 -6
- package/src/transform/field.ts +1 -6
- package/src/transform/operation.ts +1 -2
- package/src/transform/top.ts +0 -6
- package/src/types.ts +31 -0
- package/src/utility.ts +184 -37
- package/dist/guide.d.ts +0 -2
- package/dist/guide.js +0 -95
- package/dist/guide.js.map +0 -1
package/src/guide/heuristic01.ts
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { each, snakify, names } from 'jostraca'
|
|
4
4
|
|
|
5
|
-
import { size
|
|
5
|
+
import { size } from '@voxgig/struct'
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
depluralize,
|
|
10
|
-
getdlog
|
|
10
|
+
getdlog,
|
|
11
|
+
capture,
|
|
12
|
+
find,
|
|
11
13
|
} from '../utility'
|
|
12
14
|
|
|
13
15
|
|
|
@@ -29,9 +31,13 @@ type EntityPathDesc = {
|
|
|
29
31
|
// Log non-fatal wierdness.
|
|
30
32
|
const dlog = getdlog('apidef', __filename)
|
|
31
33
|
|
|
34
|
+
|
|
32
35
|
async function heuristic01(ctx: any): Promise<Record<string, any>> {
|
|
33
36
|
let guide = ctx.model.main.api.guide
|
|
34
37
|
|
|
38
|
+
const metrics = measure(ctx)
|
|
39
|
+
// console.dir(metrics, { depth: null })
|
|
40
|
+
|
|
35
41
|
const entityDescs = resolveEntityDescs(ctx)
|
|
36
42
|
|
|
37
43
|
guide = {
|
|
@@ -43,6 +49,30 @@ async function heuristic01(ctx: any): Promise<Record<string, any>> {
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
|
|
52
|
+
function measure(ctx: any) {
|
|
53
|
+
const metrics = {
|
|
54
|
+
count: {
|
|
55
|
+
schema: ({} as Record<string, number>)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let xrefs = find(ctx.def, 'x-ref')
|
|
60
|
+
// console.log('XREFS', xrefs)
|
|
61
|
+
|
|
62
|
+
let schemas = xrefs.filter(xref => xref.val.includes('schema'))
|
|
63
|
+
|
|
64
|
+
schemas.map(schema => {
|
|
65
|
+
let m = schema.val.match(/\/components\/schemas\/(.+)$/)
|
|
66
|
+
if (m) {
|
|
67
|
+
const name = m[1]
|
|
68
|
+
metrics.count.schema[name] = 1 + (metrics.count.schema[name] || 0)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
return metrics
|
|
74
|
+
}
|
|
75
|
+
|
|
46
76
|
|
|
47
77
|
|
|
48
78
|
|
|
@@ -59,100 +89,111 @@ function resolveEntityDescs(ctx: any) {
|
|
|
59
89
|
const entityDescs: Record<string, any> = {}
|
|
60
90
|
const paths = ctx.def.paths
|
|
61
91
|
|
|
62
|
-
// Analyze paths ending in .../foo/{foo}
|
|
63
|
-
each(paths, (pathDef: any, pathStr: string) => {
|
|
64
92
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
93
|
+
const caught = capture(ctx.def, {
|
|
94
|
+
paths:
|
|
95
|
+
['`$SELECT`', /\/([a-zA-Z0-1_-]+)(\/\{([a-zA-Z0-1_-]+)\})?$/,
|
|
96
|
+
['`$SELECT`', /get|post|put|patch|delete/i,
|
|
97
|
+
['`$APPEND`', 'methods', {
|
|
98
|
+
path: '`select$=key.paths`',
|
|
99
|
+
method: { '`$LOWER`': '`$KEY`' },
|
|
100
|
+
summary: '`.summary`',
|
|
101
|
+
parameters: '`.parameters`',
|
|
102
|
+
responses: '`.responses`',
|
|
103
|
+
requestBody: '`.requestBody`'
|
|
104
|
+
}]
|
|
105
|
+
]
|
|
106
|
+
]
|
|
107
|
+
})
|
|
70
108
|
|
|
71
|
-
each(pathDef, (methodDef: any, methodStr: string) => {
|
|
72
|
-
// console.log('PPP', pathStr, methodStr, methodDef)
|
|
73
109
|
|
|
74
|
-
|
|
75
|
-
|
|
110
|
+
each(caught.methods, (pmdef) => {
|
|
111
|
+
let methodDef = pmdef
|
|
112
|
+
let pathStr = pmdef.path
|
|
113
|
+
let methodStr = pmdef.method
|
|
76
114
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
115
|
+
// methodStr = methodStr.toLowerCase()
|
|
116
|
+
let why_op: string[] = []
|
|
80
117
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
118
|
+
if (!METHOD_IDOP[methodStr]) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
84
121
|
|
|
122
|
+
const why_ent: string[] = []
|
|
123
|
+
const entdesc =
|
|
124
|
+
resolveEntity(entityDescs, pathStr, methodDef, methodStr, why_ent)
|
|
85
125
|
|
|
86
|
-
if (null == entdesc) {
|
|
87
|
-
console.log(
|
|
88
|
-
'WARNING: unable to resolve entity for method ' + methodStr +
|
|
89
|
-
' path ' + pathStr)
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
126
|
|
|
93
|
-
|
|
127
|
+
if (null == entdesc) {
|
|
128
|
+
console.log(
|
|
129
|
+
'WARNING: unable to resolve entity for method ' + methodStr +
|
|
130
|
+
' path ' + pathStr)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
94
133
|
|
|
134
|
+
entdesc.path[pathStr].why_ent = why_ent
|
|
95
135
|
|
|
96
|
-
// if (pathStr.includes('courses')) {
|
|
97
|
-
// console.log('ENTRES', pathStr, methodStr)
|
|
98
|
-
// console.dir(ent2, { depth: null })
|
|
99
|
-
// }
|
|
100
136
|
|
|
101
|
-
|
|
137
|
+
// if (pathStr.includes('courses')) {
|
|
138
|
+
// console.log('ENTRES', pathStr, methodStr)
|
|
139
|
+
// console.dir(ent2, { depth: null })
|
|
140
|
+
// }
|
|
102
141
|
|
|
103
|
-
|
|
104
|
-
console.log(
|
|
105
|
-
'WARNING: unable to resolve operation for method ' + methodStr +
|
|
106
|
-
' path ' + pathStr)
|
|
107
|
-
return
|
|
108
|
-
}
|
|
142
|
+
let opname = resolveOpName(methodStr, methodDef, pathStr, entdesc, why_op)
|
|
109
143
|
|
|
144
|
+
if (null == opname) {
|
|
145
|
+
console.log(
|
|
146
|
+
'WARNING: unable to resolve operation for method ' + methodStr +
|
|
147
|
+
' path ' + pathStr)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
110
150
|
|
|
111
|
-
const transform: Record<string, any> = {
|
|
112
|
-
// reqform: '`reqdata`',
|
|
113
|
-
// resform: '`body`',
|
|
114
|
-
}
|
|
115
151
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
transform.resform = '`body.' + entdesc.origname + '`'
|
|
121
|
-
}
|
|
122
|
-
else if (resbody[entdesc.name]) {
|
|
123
|
-
transform.resform = '`body.' + entdesc.name + '`'
|
|
124
|
-
}
|
|
125
|
-
}
|
|
152
|
+
const transform: Record<string, any> = {
|
|
153
|
+
// reqform: '`reqdata`',
|
|
154
|
+
// resform: '`body`',
|
|
155
|
+
}
|
|
126
156
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
157
|
+
const resokdef = methodDef.responses?.[200] || methodDef.responses?.[201]
|
|
158
|
+
const resbody = resokdef?.content?.['application/json']?.schema
|
|
159
|
+
if (resbody) {
|
|
160
|
+
if (resbody[entdesc.origname]) {
|
|
161
|
+
transform.resform = '`body.' + entdesc.origname + '`'
|
|
162
|
+
}
|
|
163
|
+
else if (resbody[entdesc.name]) {
|
|
164
|
+
transform.resform = '`body.' + entdesc.name + '`'
|
|
165
|
+
}
|
|
166
|
+
}
|
|
135
167
|
|
|
136
|
-
|
|
168
|
+
const reqdef = methodDef.requestBody?.content?.['application/json']?.schema?.properties
|
|
169
|
+
if (reqdef) {
|
|
170
|
+
if (reqdef[entdesc.origname]) {
|
|
171
|
+
transform.reqform = { [entdesc.origname]: '`reqdata`' }
|
|
172
|
+
}
|
|
173
|
+
else if (reqdef[entdesc.origname]) {
|
|
174
|
+
transform.reqform = { [entdesc.origname]: '`reqdata`' }
|
|
175
|
+
}
|
|
137
176
|
|
|
138
|
-
|
|
177
|
+
}
|
|
139
178
|
|
|
140
|
-
|
|
141
|
-
// TODO: in actual guide, remove "standard" method ops since redundant
|
|
142
|
-
method: methodStr,
|
|
143
|
-
why_op: why_op.join(';')
|
|
144
|
-
}
|
|
179
|
+
const op = entdesc.path[pathStr].op
|
|
145
180
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
181
|
+
op[opname] = {
|
|
182
|
+
// TODO: in actual guide, remove "standard" method ops since redundant
|
|
183
|
+
method: methodStr,
|
|
184
|
+
why_op: why_op.join(';')
|
|
185
|
+
}
|
|
149
186
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// console.dir(entdesc, { depth: null })
|
|
153
|
-
// }
|
|
154
|
-
})
|
|
187
|
+
if (0 < Object.entries(transform).length) {
|
|
188
|
+
op[opname].transform = transform
|
|
155
189
|
}
|
|
190
|
+
|
|
191
|
+
// if ('/v2/users/{user_id}/enrollment' === pathStr) {
|
|
192
|
+
// console.log('ENT')
|
|
193
|
+
// console.dir(entdesc, { depth: null })
|
|
194
|
+
// }
|
|
195
|
+
// })
|
|
196
|
+
// }
|
|
156
197
|
})
|
|
157
198
|
|
|
158
199
|
// console.log('USER')
|
|
@@ -164,7 +205,7 @@ function resolveEntityDescs(ctx: any) {
|
|
|
164
205
|
|
|
165
206
|
function resolveEntity(
|
|
166
207
|
entityDescs: Record<string, EntityDesc>,
|
|
167
|
-
pathDef: Record<string, any>,
|
|
208
|
+
// pathDef: Record<string, any>,
|
|
168
209
|
pathStr: string,
|
|
169
210
|
methodDef: Record<string, any>,
|
|
170
211
|
methodStr: string,
|
|
@@ -180,6 +221,9 @@ function resolveEntity(
|
|
|
180
221
|
const m = pathStr.match(/\/([a-zA-Z0-1_-]+)(\/\{([a-zA-Z0-1_-]+)\})?$/)
|
|
181
222
|
if (m) {
|
|
182
223
|
let pathName = m[1]
|
|
224
|
+
let pathParam = m[3]
|
|
225
|
+
|
|
226
|
+
|
|
183
227
|
origentname = snakify(pathName)
|
|
184
228
|
entname = depluralize(origentname)
|
|
185
229
|
|
|
@@ -201,7 +245,6 @@ function resolveEntity(
|
|
|
201
245
|
alias: {}
|
|
202
246
|
})
|
|
203
247
|
|
|
204
|
-
let pathParam = m[3]
|
|
205
248
|
if (null != pathParam) {
|
|
206
249
|
const pathParamCanon = snakify(pathParam)
|
|
207
250
|
if ('id' != pathParamCanon) {
|
|
@@ -283,94 +326,6 @@ function resolveComponentName(
|
|
|
283
326
|
}
|
|
284
327
|
}
|
|
285
328
|
|
|
286
|
-
/*
|
|
287
|
-
const responses = methodDef.responses
|
|
288
|
-
const schemalist =
|
|
289
|
-
[
|
|
290
|
-
methodDef.requestBody?.content,
|
|
291
|
-
responses?.['201'],
|
|
292
|
-
responses?.['200'],
|
|
293
|
-
]
|
|
294
|
-
.filter(cmp => null != cmp)
|
|
295
|
-
.map(content => content['application/json']?.schema)
|
|
296
|
-
.filter(schema => null != schema)
|
|
297
|
-
// .filter(schema => null != schema['x-ref'])
|
|
298
|
-
.map(schema => {
|
|
299
|
-
|
|
300
|
-
let xrefs = find(schema, 'x-ref')
|
|
301
|
-
|
|
302
|
-
if ('responses' === pathName) {
|
|
303
|
-
console.log('xrefs', xrefs)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
let xrefv = String(xrefs[0])
|
|
307
|
-
|
|
308
|
-
let xrefm = xrefv.match(/\/components\/schemas\/(.+)$/)
|
|
309
|
-
if (xrefm) {
|
|
310
|
-
schema['x-ref-cmp'] = xrefm[1]
|
|
311
|
-
}
|
|
312
|
-
return schema
|
|
313
|
-
})
|
|
314
|
-
.filter(schema => null != schema['x-ref-cmp'])
|
|
315
|
-
|
|
316
|
-
if ('responses' === pathName) {
|
|
317
|
-
console.log('CMP', pathName, schemalist.length)
|
|
318
|
-
// console.dir(methodDef.responses['200'].content['application/json'].schema, { depth: null })
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
let schema = undefined
|
|
322
|
-
let splen = -1
|
|
323
|
-
|
|
324
|
-
if (0 < schemalist.length) {
|
|
325
|
-
why_name.push('schema')
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
for (let sI = 0; sI < schemalist.length; sI++) {
|
|
329
|
-
let nextschema = schemalist[sI]
|
|
330
|
-
let nsplen = nextschema.properties?.length || -1
|
|
331
|
-
|
|
332
|
-
// console.log('QQQ', splen, nsplen, schema?.['x-ref-cmp'], nextschema?.['x-ref-cmp'])
|
|
333
|
-
|
|
334
|
-
if (
|
|
335
|
-
// More properties probably means it is the full entity.
|
|
336
|
-
splen < nsplen ||
|
|
337
|
-
|
|
338
|
-
// Shorter name probably means it is the full entity (no suffix/prefix).
|
|
339
|
-
(schema && splen === nsplen && nextschema['x-ref-cmp'].length < schema['x-ref-cmp'].length)
|
|
340
|
-
|
|
341
|
-
) {
|
|
342
|
-
schema = nextschema
|
|
343
|
-
splen = nsplen
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (schema) {
|
|
348
|
-
let xref = schema['x-ref']
|
|
349
|
-
// console.log('RCN-XREF', methodStr, 'xref-0', xref)
|
|
350
|
-
|
|
351
|
-
if (null == xref) {
|
|
352
|
-
why_name.push('xref')
|
|
353
|
-
const properties = schema.properties || {}
|
|
354
|
-
each(properties, (prop) => {
|
|
355
|
-
if (null == xref) {
|
|
356
|
-
if (prop.type === 'array') {
|
|
357
|
-
xref = prop.items?.['x-ref']
|
|
358
|
-
// console.log('RCN', methodStr, 'xref-1', xref)
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
})
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (null != xref && 'string' === typeof xref) {
|
|
365
|
-
let xrefm = xref.match(/\/components\/schemas\/(.+)$/)
|
|
366
|
-
if (xrefm) {
|
|
367
|
-
why_name.push('cmp')
|
|
368
|
-
compname = xrefm[1]
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
*/
|
|
373
|
-
|
|
374
329
|
return compname
|
|
375
330
|
}
|
|
376
331
|
|
|
@@ -412,74 +367,71 @@ function isListResponse(
|
|
|
412
367
|
entdesc: EntityDesc,
|
|
413
368
|
why: string[]
|
|
414
369
|
): boolean {
|
|
415
|
-
const responses = methodDef.responses
|
|
416
|
-
const resdef = responses?.['201'] || responses?.['200']
|
|
417
|
-
const content = resdef?.content
|
|
418
370
|
|
|
371
|
+
const caught = capture(methodDef, {
|
|
372
|
+
responses: {
|
|
373
|
+
'`$ANY`': { content: { 'application/json': { schema: '`$CAPTURE`' } } },
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
const schema = caught.schema
|
|
419
378
|
let islist = false
|
|
420
379
|
|
|
421
|
-
if (null ==
|
|
422
|
-
|
|
423
|
-
why.push('no-content')
|
|
380
|
+
if (null == schema) {
|
|
381
|
+
why.push('no-schema')
|
|
424
382
|
}
|
|
425
383
|
else {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
384
|
+
if (schema.type === 'array') {
|
|
385
|
+
why.push('array')
|
|
386
|
+
islist = true
|
|
429
387
|
}
|
|
430
|
-
else {
|
|
431
388
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
389
|
+
if (!islist) {
|
|
390
|
+
const properties = schema.properties || {}
|
|
391
|
+
each(properties, (prop) => {
|
|
392
|
+
if (prop.type === 'array') {
|
|
436
393
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (prop.type === 'array') {
|
|
441
|
-
|
|
442
|
-
if (1 === size(properties)) {
|
|
443
|
-
why.push('one-prop:' + prop.key$)
|
|
444
|
-
islist = true
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
if (2 === size(properties) &&
|
|
448
|
-
('data' === prop.key$ ||
|
|
449
|
-
'list' === prop.key$)
|
|
450
|
-
) {
|
|
451
|
-
why.push('two-prop:' + prop.key$)
|
|
452
|
-
islist = true
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (prop.key$ === entdesc.name) {
|
|
456
|
-
why.push('name:' + entdesc.origname)
|
|
457
|
-
islist = true
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (prop.key$ === entdesc.origname) {
|
|
461
|
-
why.push('origname:' + entdesc.origname)
|
|
462
|
-
islist = true
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const listent = listedEntity(prop)
|
|
466
|
-
if (listent === entdesc.name) {
|
|
467
|
-
why.push('listent:' + listent)
|
|
468
|
-
islist = true
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
// if ('/v2/users' === pathStr) {
|
|
473
|
-
// console.log('islistresponse', islist, pathStr, entdesc.name, listedEntity(prop), properties)
|
|
474
|
-
// }
|
|
394
|
+
if (1 === size(properties)) {
|
|
395
|
+
why.push('one-prop:' + prop.key$)
|
|
396
|
+
islist = true
|
|
475
397
|
}
|
|
476
|
-
})
|
|
477
|
-
}
|
|
478
398
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
399
|
+
if (2 === size(properties) &&
|
|
400
|
+
('data' === prop.key$ ||
|
|
401
|
+
'list' === prop.key$)
|
|
402
|
+
) {
|
|
403
|
+
why.push('two-prop:' + prop.key$)
|
|
404
|
+
islist = true
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (prop.key$ === entdesc.name) {
|
|
408
|
+
why.push('name:' + entdesc.origname)
|
|
409
|
+
islist = true
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (prop.key$ === entdesc.origname) {
|
|
413
|
+
why.push('origname:' + entdesc.origname)
|
|
414
|
+
islist = true
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const listent = listedEntity(prop)
|
|
418
|
+
if (listent === entdesc.name) {
|
|
419
|
+
why.push('listent:' + listent)
|
|
420
|
+
islist = true
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
// if ('/v2/users' === pathStr) {
|
|
425
|
+
// console.log('islistresponse', islist, pathStr, entdesc.name, listedEntity(prop), properties)
|
|
426
|
+
// }
|
|
427
|
+
}
|
|
428
|
+
})
|
|
482
429
|
}
|
|
430
|
+
|
|
431
|
+
if (!islist) {
|
|
432
|
+
why.push('not-list')
|
|
433
|
+
}
|
|
434
|
+
// }
|
|
483
435
|
}
|
|
484
436
|
|
|
485
437
|
return islist
|
|
@@ -495,17 +447,6 @@ function listedEntity(prop: any) {
|
|
|
495
447
|
}
|
|
496
448
|
|
|
497
449
|
|
|
498
|
-
function find(obj: any, qkey: string): any[] {
|
|
499
|
-
let vals: any[] = []
|
|
500
|
-
walk(obj, (key: any, val: any, _p: any, t: string[]) => {
|
|
501
|
-
if (qkey === key) {
|
|
502
|
-
vals.push({ key, val, path: t })
|
|
503
|
-
}
|
|
504
|
-
return val
|
|
505
|
-
})
|
|
506
|
-
return vals
|
|
507
|
-
}
|
|
508
|
-
|
|
509
450
|
|
|
510
451
|
export {
|
|
511
452
|
heuristic01
|
|
@@ -3,7 +3,7 @@ import Path from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
import { File, Content, each } from 'jostraca'
|
|
5
5
|
|
|
6
|
-
import { merge } from '@voxgig/struct'
|
|
6
|
+
import { merge, items } from '@voxgig/struct'
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
import { heuristic01 } from './guide/heuristic01'
|
|
@@ -54,8 +54,7 @@ async function resolveGuide(ctx: any) {
|
|
|
54
54
|
const guideBlocks = [
|
|
55
55
|
'# Guide',
|
|
56
56
|
'',
|
|
57
|
-
'main: api: guide: {
|
|
58
|
-
|
|
57
|
+
'main: api: guide: {',
|
|
59
58
|
]
|
|
60
59
|
|
|
61
60
|
|
|
@@ -64,11 +63,13 @@ async function resolveGuide(ctx: any) {
|
|
|
64
63
|
entity: ${entityname}: {` +
|
|
65
64
|
(0 < entity.why_name.length ? ' # name:' + entity.why_name.join(';') : ''))
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
items(entity.path).map((pathn) => {
|
|
67
|
+
const [pathname, path] = pathn
|
|
68
68
|
guideBlocks.push(` path: '${pathname}': op: {` +
|
|
69
69
|
(0 < path.why_ent.length ? ' # ent:' + path.why_ent.join(';') : ''))
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
items(path.op).map((opn) => {
|
|
72
|
+
const [opname, op] = opn
|
|
72
73
|
guideBlocks.push(` '${opname}': method: ${op.method}` +
|
|
73
74
|
(0 < op.why_op.length ? ' # ' + op.why_op : ''))
|
|
74
75
|
if (op.transform?.reqform) {
|
|
@@ -87,6 +88,8 @@ entity: ${entityname}: {` +
|
|
|
87
88
|
|
|
88
89
|
const guideSrc = guideBlocks.join('\n')
|
|
89
90
|
|
|
91
|
+
ctx.note.guide = { base: guideSrc }
|
|
92
|
+
|
|
90
93
|
return () => {
|
|
91
94
|
// Save base guide for reference
|
|
92
95
|
File({ name: '../def/' + Path.basename(guideFile) }, () => Content(guideSrc))
|
package/src/resolver.ts
CHANGED
|
@@ -11,11 +11,11 @@ async function resolveElements(
|
|
|
11
11
|
standard: Record<string, any>
|
|
12
12
|
) {
|
|
13
13
|
|
|
14
|
-
const { log, model } = ctx
|
|
14
|
+
const { log, model, guide } = ctx
|
|
15
15
|
|
|
16
16
|
// TODO: model access should be via a utility that generates
|
|
17
17
|
// useful errors when the target is missing
|
|
18
|
-
const control =
|
|
18
|
+
const control = guide.control[kind][subkind]
|
|
19
19
|
|
|
20
20
|
const target = kind + '.' + subkind
|
|
21
21
|
|
package/src/transform/entity.ts
CHANGED
|
@@ -11,13 +11,8 @@ import { depluralize } from '../utility'
|
|
|
11
11
|
|
|
12
12
|
const entityTransform: Transform = async function(
|
|
13
13
|
ctx: any,
|
|
14
|
-
// guide: Guide,
|
|
15
|
-
// // tspec: TransformSpec,
|
|
16
|
-
// model: any,
|
|
17
|
-
// def: any
|
|
18
14
|
): Promise<TransformResult> {
|
|
19
|
-
const { apimodel, model, def } = ctx
|
|
20
|
-
const guide = model.main.api.guide
|
|
15
|
+
const { apimodel, model, def, guide } = ctx
|
|
21
16
|
|
|
22
17
|
let msg = ''
|
|
23
18
|
|
package/src/transform/field.ts
CHANGED
|
@@ -10,13 +10,8 @@ import { fixName } from '../transform'
|
|
|
10
10
|
|
|
11
11
|
const fieldTransform: Transform = async function(
|
|
12
12
|
ctx: any,
|
|
13
|
-
// guide: Guide,
|
|
14
|
-
// // tspec: TransformSpec,
|
|
15
|
-
// model: any,
|
|
16
|
-
// def: any
|
|
17
13
|
): Promise<TransformResult> {
|
|
18
|
-
const { apimodel, model, def } = ctx
|
|
19
|
-
const guide = model.main.api.guide
|
|
14
|
+
const { apimodel, model, def, guide } = ctx
|
|
20
15
|
|
|
21
16
|
let msg = 'fields: '
|
|
22
17
|
|
|
@@ -14,8 +14,7 @@ import { depluralize } from '../utility'
|
|
|
14
14
|
const operationTransform = async function(
|
|
15
15
|
ctx: any,
|
|
16
16
|
): Promise<TransformResult> {
|
|
17
|
-
const { apimodel, model, def } = ctx
|
|
18
|
-
const guide = model.main.api.guide
|
|
17
|
+
const { apimodel, model, def, guide } = ctx
|
|
19
18
|
|
|
20
19
|
let msg = 'operations: '
|
|
21
20
|
|
package/src/transform/top.ts
CHANGED
|
@@ -8,15 +8,9 @@ import { fixName } from '../transform'
|
|
|
8
8
|
|
|
9
9
|
const topTransform = async function(
|
|
10
10
|
ctx: any,
|
|
11
|
-
// guide: Guide,
|
|
12
|
-
// // tspec: TransformSpec,
|
|
13
|
-
// model: any,
|
|
14
|
-
// def: any
|
|
15
11
|
): Promise<TransformResult> {
|
|
16
12
|
const { apimodel, def } = ctx
|
|
17
13
|
|
|
18
|
-
// const { spec } = ctx
|
|
19
|
-
|
|
20
14
|
apimodel.main.def.info = def.info
|
|
21
15
|
apimodel.main.def.servers = def.servers
|
|
22
16
|
|
package/src/types.ts
CHANGED
|
@@ -21,6 +21,20 @@ type ApiDefOptions = {
|
|
|
21
21
|
strategy?: string
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
const ControlShape = Gubu({
|
|
25
|
+
step: {
|
|
26
|
+
parse: true,
|
|
27
|
+
guide: true,
|
|
28
|
+
transformers: true,
|
|
29
|
+
builders: true,
|
|
30
|
+
generate: true,
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
const OpenControlShape = Gubu(Open(ControlShape), { name: 'Control' })
|
|
34
|
+
|
|
35
|
+
type Control = ReturnType<typeof ControlShape>
|
|
36
|
+
|
|
37
|
+
|
|
24
38
|
|
|
25
39
|
const ModelShape = Gubu({
|
|
26
40
|
name: String,
|
|
@@ -74,7 +88,22 @@ type ApiModel = {
|
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
|
|
91
|
+
type ApiDefResult = {
|
|
92
|
+
ok: boolean
|
|
93
|
+
start: number
|
|
94
|
+
end: number
|
|
95
|
+
steps: string[]
|
|
96
|
+
ctrl: Control
|
|
97
|
+
|
|
98
|
+
guide?: any
|
|
99
|
+
apimodel?: any
|
|
100
|
+
ctx?: any
|
|
101
|
+
jres?: any
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
77
105
|
export {
|
|
106
|
+
OpenControlShape,
|
|
78
107
|
OpenModelShape,
|
|
79
108
|
OpenBuildShape,
|
|
80
109
|
}
|
|
@@ -84,6 +113,8 @@ export type {
|
|
|
84
113
|
Log,
|
|
85
114
|
FsUtil,
|
|
86
115
|
ApiDefOptions,
|
|
116
|
+
ApiDefResult,
|
|
117
|
+
Control,
|
|
87
118
|
Model,
|
|
88
119
|
Build,
|
|
89
120
|
ApiModel,
|