@voxgig/apidef 2.0.0 → 2.1.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.js +14 -20
- package/dist/apidef.js.map +1 -1
- package/dist/builder/flow/flowHeuristic01.js +37 -9
- package/dist/builder/flow/flowHeuristic01.js.map +1 -1
- package/dist/builder/flow.js +3 -3
- package/dist/builder/flow.js.map +1 -1
- package/dist/guide/heuristic01.js +197 -117
- package/dist/guide/heuristic01.js.map +1 -1
- package/dist/guide.js +37 -11
- package/dist/guide.js.map +1 -1
- package/dist/parse.d.ts +2 -1
- package/dist/parse.js +81 -3
- package/dist/parse.js.map +1 -1
- package/dist/transform/clean.d.ts +3 -0
- package/dist/transform/clean.js +16 -0
- package/dist/transform/clean.js.map +1 -0
- package/dist/transform/entity.js +21 -2
- package/dist/transform/entity.js.map +1 -1
- package/dist/transform/field.js +24 -1
- package/dist/transform/field.js.map +1 -1
- package/dist/transform/operation.js +131 -47
- package/dist/transform/operation.js.map +1 -1
- package/dist/transform.d.ts +1 -8
- package/dist/transform.js +121 -95
- package/dist/transform.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.js +3 -2
- package/dist/types.js.map +1 -1
- package/dist/utility.d.ts +7 -1
- package/dist/utility.js +85 -32
- package/dist/utility.js.map +1 -1
- package/model/apidef.jsonic +1 -0
- package/package.json +8 -9
- package/src/apidef.ts +23 -33
- package/src/builder/flow/flowHeuristic01.ts +44 -9
- package/src/builder/flow.ts +4 -3
- package/src/guide/heuristic01.ts +281 -124
- package/src/guide.ts +49 -14
- package/src/parse.ts +106 -4
- package/src/transform/clean.ts +28 -0
- package/src/transform/entity.ts +26 -3
- package/src/transform/field.ts +27 -1
- package/src/transform/operation.ts +203 -64
- package/src/transform.ts +29 -23
- package/src/types.ts +3 -2
- package/src/utility.ts +113 -1
- package/src/builder/flow/flowHeuristic01.ts~ +0 -45
package/src/guide/heuristic01.ts
CHANGED
|
@@ -2,12 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
import { each, snakify, names } from 'jostraca'
|
|
4
4
|
|
|
5
|
+
import { size } from '@voxgig/struct'
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
import {
|
|
9
|
+
depluralize,
|
|
10
|
+
getdlog
|
|
11
|
+
} from '../utility'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
type EntityDesc = {
|
|
15
|
+
name: string
|
|
16
|
+
origname: string
|
|
17
|
+
plural: string
|
|
18
|
+
path: Record<string, EntityPathDesc>
|
|
19
|
+
alias: Record<string, string>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
type EntityPathDesc = {
|
|
24
|
+
op: Record<string, any>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Log non-fatal wierdness.
|
|
28
|
+
const dlog = getdlog('apidef', __filename)
|
|
29
|
+
|
|
7
30
|
async function heuristic01(ctx: any): Promise<Record<string, any>> {
|
|
8
31
|
let guide = ctx.model.main.api.guide
|
|
9
32
|
|
|
10
|
-
|
|
11
33
|
const entityDescs = resolveEntityDescs(ctx)
|
|
12
34
|
|
|
13
35
|
// console.log('entityDescs')
|
|
@@ -18,10 +40,16 @@ async function heuristic01(ctx: any): Promise<Record<string, any>> {
|
|
|
18
40
|
entity: entityDescs,
|
|
19
41
|
}
|
|
20
42
|
|
|
43
|
+
// console.log('GUIDE')
|
|
44
|
+
// console.dir(guide, { depth: null })
|
|
45
|
+
|
|
21
46
|
return guide
|
|
22
47
|
}
|
|
23
48
|
|
|
24
49
|
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
25
53
|
const METHOD_IDOP: Record<string, string> = {
|
|
26
54
|
get: 'load',
|
|
27
55
|
post: 'create',
|
|
@@ -35,191 +63,320 @@ function resolveEntityDescs(ctx: any) {
|
|
|
35
63
|
const entityDescs: Record<string, any> = {}
|
|
36
64
|
const paths = ctx.def.paths
|
|
37
65
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
// Analyze paths ending in .../foo/{foo}
|
|
67
|
+
each(paths, (pathDef: any, pathStr: string) => {
|
|
68
|
+
|
|
69
|
+
// Look for rightmmost /entname/{entid}.
|
|
70
|
+
let m = pathStr.match(/\/([a-zA-Z0-1_-]+)(\/\{([a-zA-Z0-1_-]+)\})?$/)
|
|
71
|
+
// const m = pathStr.match(/\/([a-zA-Z0-1_-]+)\/\{([a-zA-Z0-1_-]+)\}$/)
|
|
41
72
|
if (m) {
|
|
42
|
-
|
|
43
|
-
let entname = depluralize(origentname)
|
|
73
|
+
// const entdesc = resolveEntity(entityDescs, pathStr, m[1], m[2])
|
|
44
74
|
|
|
45
|
-
|
|
46
|
-
|
|
75
|
+
each(pathDef, (methodDef: any, methodStr: string) => {
|
|
76
|
+
methodStr = methodStr.toLowerCase()
|
|
47
77
|
|
|
48
|
-
names(entdesc, entname)
|
|
49
78
|
|
|
50
|
-
|
|
79
|
+
if (!METHOD_IDOP[methodStr]) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
51
82
|
|
|
52
|
-
|
|
53
|
-
entdesc.alias.id = m[2]
|
|
54
|
-
entdesc.alias[m[2]] = 'id'
|
|
55
|
-
}
|
|
83
|
+
const entdesc = resolveEntity(entityDescs, pathDef, pathStr, methodDef, methodStr)
|
|
56
84
|
|
|
57
|
-
|
|
85
|
+
if (null == entdesc) {
|
|
86
|
+
console.log(
|
|
87
|
+
'WARNING: unable to resolve entity for method ' + methodStr +
|
|
88
|
+
' path ' + pathStr)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
58
91
|
|
|
59
|
-
|
|
60
|
-
|
|
92
|
+
// if (pathStr.includes('courses')) {
|
|
93
|
+
// console.log('ENTRES', pathStr, methodStr)
|
|
94
|
+
// console.dir(ent2, { depth: null })
|
|
95
|
+
// }
|
|
96
|
+
|
|
97
|
+
let opname = resolveOpName(methodStr, methodDef, pathStr, entdesc)
|
|
98
|
+
|
|
99
|
+
if (null == opname) {
|
|
100
|
+
console.log(
|
|
101
|
+
'WARNING: unable to resolve operation for method ' + methodStr +
|
|
102
|
+
' path ' + pathStr)
|
|
103
|
+
return
|
|
104
|
+
}
|
|
61
105
|
|
|
62
|
-
each(pathdef, (mdef, method) => {
|
|
63
|
-
const opname = METHOD_IDOP[method]
|
|
64
|
-
if (null == opname) return;
|
|
65
106
|
|
|
66
107
|
const transform: Record<string, any> = {
|
|
67
108
|
// reqform: '`reqdata`',
|
|
68
109
|
// resform: '`body`',
|
|
69
110
|
}
|
|
70
111
|
|
|
71
|
-
const resokdef =
|
|
112
|
+
const resokdef = methodDef.responses[200] || methodDef.responses[201]
|
|
72
113
|
const resbody = resokdef?.content?.['application/json']?.schema
|
|
73
114
|
if (resbody) {
|
|
74
|
-
if (resbody[
|
|
75
|
-
|
|
76
|
-
// transform.resform = '`body."'+origentname+'"`'
|
|
77
|
-
transform.resform = '`body.' + origentname + '`'
|
|
115
|
+
if (resbody[entdesc.origname]) {
|
|
116
|
+
transform.resform = '`body.' + entdesc.origname + '`'
|
|
78
117
|
}
|
|
79
|
-
else if (resbody[
|
|
80
|
-
transform.resform = '`body.' +
|
|
118
|
+
else if (resbody[entdesc.name]) {
|
|
119
|
+
transform.resform = '`body.' + entdesc.name + '`'
|
|
81
120
|
}
|
|
82
121
|
}
|
|
83
122
|
|
|
84
|
-
const reqdef =
|
|
123
|
+
const reqdef = methodDef.requestBody?.content?.['application/json']?.schema?.properties
|
|
85
124
|
if (reqdef) {
|
|
86
|
-
if (reqdef[
|
|
87
|
-
transform.reqform = { [
|
|
125
|
+
if (reqdef[entdesc.origname]) {
|
|
126
|
+
transform.reqform = { [entdesc.origname]: '`reqdata`' }
|
|
88
127
|
}
|
|
89
|
-
else if (reqdef[
|
|
90
|
-
transform.reqform = { [
|
|
128
|
+
else if (reqdef[entdesc.origname]) {
|
|
129
|
+
transform.reqform = { [entdesc.origname]: '`reqdata`' }
|
|
91
130
|
}
|
|
92
131
|
|
|
93
132
|
}
|
|
94
133
|
|
|
134
|
+
const op = entdesc.path[pathStr].op
|
|
135
|
+
|
|
95
136
|
op[opname] = {
|
|
96
137
|
// TODO: in actual guide, remove "standard" method ops since redundant
|
|
97
|
-
method,
|
|
138
|
+
method: methodStr,
|
|
98
139
|
}
|
|
99
140
|
|
|
100
141
|
if (0 < Object.entries(transform).length) {
|
|
101
142
|
op[opname].transform = transform
|
|
102
143
|
}
|
|
144
|
+
|
|
145
|
+
if ('/v2/users/{user_id}/enrollment' === pathStr) {
|
|
146
|
+
console.log('ENT')
|
|
147
|
+
console.dir(entdesc, { depth: null })
|
|
148
|
+
}
|
|
103
149
|
})
|
|
104
150
|
}
|
|
105
151
|
})
|
|
106
152
|
|
|
153
|
+
// console.log('USER')
|
|
154
|
+
// console.dir(entityDescs.user, { depth: null })
|
|
107
155
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const m = pathname.match(/\/([a-zA-Z0-1_-]+)$/)
|
|
111
|
-
if (m) {
|
|
112
|
-
let origentname = snakify(m[1])
|
|
113
|
-
let entname = depluralize(origentname)
|
|
114
|
-
|
|
115
|
-
let entdesc = entityDescs[entname]
|
|
116
|
-
|
|
117
|
-
if (entdesc) {
|
|
118
|
-
entdesc.path = (entdesc.path || {})
|
|
119
|
-
|
|
120
|
-
if (pathdef.get) {
|
|
121
|
-
const op: Record<string, any> = { list: { method: 'get' } }
|
|
122
|
-
entdesc.path[pathname] = { op }
|
|
123
|
-
|
|
124
|
-
const transform: Record<string, any> = {}
|
|
125
|
-
const mdef = pathdef.get
|
|
126
|
-
const resokdef = mdef.responses[200] || mdef.responses[201]
|
|
127
|
-
const resbody = resokdef?.content?.['application/json']?.schema
|
|
128
|
-
if (resbody) {
|
|
129
|
-
if (resbody[origentname]) {
|
|
130
|
-
// TODO: use quotes when @voxgig/struct updated to support them
|
|
131
|
-
// transform.resform = '`body."'+origentname+'"`'
|
|
132
|
-
transform.resform = '`body.' + origentname + '`'
|
|
133
|
-
}
|
|
134
|
-
else if (resbody[entname]) {
|
|
135
|
-
transform.resform = '`body.' + entname + '`'
|
|
136
|
-
}
|
|
137
|
-
}
|
|
156
|
+
return entityDescs
|
|
157
|
+
}
|
|
138
158
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
159
|
+
|
|
160
|
+
function resolveEntity(
|
|
161
|
+
entityDescs: Record<string, EntityDesc>,
|
|
162
|
+
pathDef: Record<string, any>,
|
|
163
|
+
pathStr: string,
|
|
164
|
+
methodDef: Record<string, any>,
|
|
165
|
+
methodStr: string,
|
|
166
|
+
): EntityDesc | undefined {
|
|
167
|
+
|
|
168
|
+
let entdesc: EntityDesc
|
|
169
|
+
let entname: string = ''
|
|
170
|
+
let origentname: string = ''
|
|
171
|
+
|
|
172
|
+
const m = pathStr.match(/\/([a-zA-Z0-1_-]+)(\/\{([a-zA-Z0-1_-]+)\})?$/)
|
|
173
|
+
if (m) {
|
|
174
|
+
let pathName = m[1]
|
|
175
|
+
origentname = snakify(pathName)
|
|
176
|
+
|
|
177
|
+
// Check schema
|
|
178
|
+
const compname = resolveComponentName(methodDef, methodStr)
|
|
179
|
+
if (compname) {
|
|
180
|
+
origentname = snakify(compname)
|
|
144
181
|
}
|
|
145
|
-
})
|
|
146
182
|
|
|
147
|
-
|
|
148
|
-
}
|
|
183
|
+
entname = depluralize(origentname)
|
|
149
184
|
|
|
185
|
+
entdesc = (entityDescs[entname] = entityDescs[entname] || {
|
|
186
|
+
name: entname,
|
|
187
|
+
id: Math.random(),
|
|
188
|
+
alias: {}
|
|
189
|
+
})
|
|
150
190
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
191
|
+
let pathParam = m[3]
|
|
192
|
+
if (null != pathParam) {
|
|
193
|
+
const pathParamCanon = snakify(pathParam)
|
|
194
|
+
if ('id' != pathParamCanon) {
|
|
195
|
+
entdesc.alias.id = pathParamCanon
|
|
196
|
+
entdesc.alias[pathParamCanon] = 'id'
|
|
197
|
+
}
|
|
198
|
+
}
|
|
154
199
|
}
|
|
155
200
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
'
|
|
159
|
-
|
|
160
|
-
'women': 'woman',
|
|
161
|
-
'teeth': 'tooth',
|
|
162
|
-
'feet': 'foot',
|
|
163
|
-
'geese': 'goose',
|
|
164
|
-
'mice': 'mouse',
|
|
165
|
-
'people': 'person',
|
|
166
|
-
'data': 'datum',
|
|
167
|
-
'criteria': 'criterion',
|
|
168
|
-
'phenomena': 'phenomenon',
|
|
169
|
-
'indices': 'index',
|
|
170
|
-
'matrices': 'matrix',
|
|
171
|
-
'vertices': 'vertex',
|
|
172
|
-
'analyses': 'analysis',
|
|
173
|
-
'axes': 'axis',
|
|
174
|
-
'crises': 'crisis',
|
|
175
|
-
'diagnoses': 'diagnosis',
|
|
176
|
-
'oases': 'oasis',
|
|
177
|
-
'theses': 'thesis',
|
|
178
|
-
'appendices': 'appendix'
|
|
201
|
+
// Can't figure out the entity
|
|
202
|
+
else {
|
|
203
|
+
console.log('NO ENTTIY', pathStr)
|
|
204
|
+
return
|
|
179
205
|
}
|
|
180
206
|
|
|
181
|
-
if (irregulars[word]) {
|
|
182
|
-
return irregulars[word]
|
|
183
|
-
}
|
|
184
207
|
|
|
185
|
-
//
|
|
208
|
+
// entdesc.plural = origentname
|
|
209
|
+
entdesc.origname = origentname
|
|
186
210
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
211
|
+
names(entdesc, entname)
|
|
212
|
+
|
|
213
|
+
entdesc.alias = entdesc.alias || {}
|
|
214
|
+
|
|
215
|
+
entdesc.path = (entdesc.path || {})
|
|
216
|
+
entdesc.path[pathStr] = entdesc.path[pathStr] || {}
|
|
217
|
+
entdesc.path[pathStr].op = entdesc.path[pathStr].op || {}
|
|
191
218
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
219
|
+
return entdesc
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
const REQKIND: any = {
|
|
224
|
+
get: 'res',
|
|
225
|
+
post: 'req',
|
|
226
|
+
put: 'req',
|
|
227
|
+
patch: 'req',
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
function resolveComponentName(
|
|
232
|
+
methodDef: Record<string, any>,
|
|
233
|
+
methodStr: string,
|
|
234
|
+
): string | undefined {
|
|
235
|
+
const kind = REQKIND[methodStr]
|
|
236
|
+
let compname: string | undefined = undefined
|
|
237
|
+
|
|
238
|
+
const responses = methodDef.responses
|
|
239
|
+
const schemalist =
|
|
240
|
+
[
|
|
241
|
+
methodDef.requestBody?.content,
|
|
242
|
+
responses?.['201'],
|
|
243
|
+
responses?.['200'],
|
|
244
|
+
]
|
|
245
|
+
.filter(cmp => null != cmp)
|
|
246
|
+
.map(content => content['application/json']?.schema)
|
|
247
|
+
.filter(schema => null != schema)
|
|
248
|
+
.filter(schema => null != schema['x-ref'])
|
|
249
|
+
.map(schema => {
|
|
250
|
+
let xrefm = schema['x-ref'].match(/\/components\/schemas\/(.+)$/)
|
|
251
|
+
if (xrefm) {
|
|
252
|
+
schema['x-ref-cmp'] = xrefm[1]
|
|
253
|
+
}
|
|
254
|
+
return schema
|
|
255
|
+
})
|
|
256
|
+
.filter(schema => null != schema['x-ref-cmp'])
|
|
257
|
+
|
|
258
|
+
let schema = undefined
|
|
259
|
+
let splen = -1
|
|
260
|
+
|
|
261
|
+
for (let sI = 0; sI < schemalist.length; sI++) {
|
|
262
|
+
let nextschema = schemalist[sI]
|
|
263
|
+
let nsplen = nextschema.properties?.length || -1
|
|
264
|
+
|
|
265
|
+
// console.log('QQQ', splen, nsplen, schema?.['x-ref-cmp'], nextschema?.['x-ref-cmp'])
|
|
266
|
+
|
|
267
|
+
if (
|
|
268
|
+
// More properties probably means it is the full entity.
|
|
269
|
+
splen < nsplen ||
|
|
270
|
+
|
|
271
|
+
// Shorter name probably means it is the full entity (no suffix/prefix).
|
|
272
|
+
(schema && splen === nsplen && nextschema['x-ref-cmp'].length < schema['x-ref-cmp'].length)
|
|
273
|
+
|
|
274
|
+
) {
|
|
275
|
+
schema = nextschema
|
|
276
|
+
splen = nsplen
|
|
198
277
|
}
|
|
199
|
-
return stem + 'f'
|
|
200
278
|
}
|
|
201
279
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
280
|
+
if (schema) {
|
|
281
|
+
let xref = schema['x-ref']
|
|
282
|
+
// console.log('RCN-XREF', methodStr, 'xref-0', xref)
|
|
283
|
+
|
|
284
|
+
if (null == xref) {
|
|
285
|
+
const properties = schema.properties || {}
|
|
286
|
+
each(properties, (prop) => {
|
|
287
|
+
if (null == xref) {
|
|
288
|
+
if (prop.type === 'array') {
|
|
289
|
+
xref = prop.items?.['x-ref']
|
|
290
|
+
// console.log('RCN', methodStr, 'xref-1', xref)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (null != xref && 'string' === typeof xref) {
|
|
297
|
+
let xrefm = xref.match(/\/components\/schemas\/(.+)$/)
|
|
298
|
+
if (xrefm) {
|
|
299
|
+
compname = xrefm[1]
|
|
300
|
+
}
|
|
301
|
+
}
|
|
205
302
|
}
|
|
206
303
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
304
|
+
return compname
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
function resolveOpName(methodStr: string, methodDef: any, pathStr: string, entdesc: EntityDesc)
|
|
309
|
+
: string | undefined {
|
|
310
|
+
|
|
311
|
+
let opname = METHOD_IDOP[methodStr]
|
|
312
|
+
if (null == opname) return;
|
|
313
|
+
|
|
314
|
+
if ('load' === opname) {
|
|
315
|
+
const islist = isListResponse(methodDef, pathStr, entdesc)
|
|
316
|
+
opname = islist ? 'list' : opname
|
|
317
|
+
|
|
318
|
+
console.log('ISLIST', entdesc.name, methodStr, opname, pathStr)
|
|
211
319
|
}
|
|
212
320
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
321
|
+
return opname
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
function isListResponse(
|
|
326
|
+
methodDef: Record<string, any>,
|
|
327
|
+
pathStr: string,
|
|
328
|
+
entdesc: EntityDesc
|
|
329
|
+
): boolean {
|
|
330
|
+
const responses = methodDef.responses
|
|
331
|
+
const resdef = responses?.['201'] || responses?.['200']
|
|
332
|
+
const content = resdef?.content
|
|
333
|
+
|
|
334
|
+
let islist = false
|
|
335
|
+
|
|
336
|
+
if (null != content) {
|
|
337
|
+
const schema = content['application/json']?.schema
|
|
338
|
+
if (schema) {
|
|
339
|
+
if (schema.type === 'array') {
|
|
340
|
+
islist = true
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!islist) {
|
|
344
|
+
const properties = schema.properties || {}
|
|
345
|
+
each(properties, (prop) => {
|
|
346
|
+
if (prop.type === 'array') {
|
|
347
|
+
|
|
348
|
+
if (
|
|
349
|
+
1 === size(properties) ||
|
|
350
|
+
prop.key$ === entdesc.name ||
|
|
351
|
+
prop.key$ === entdesc.origname ||
|
|
352
|
+
listedEntity(prop) === entdesc.name
|
|
353
|
+
) {
|
|
354
|
+
islist = true
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if ('/v2/users' === pathStr) {
|
|
358
|
+
console.log('islistresponse', islist, pathStr, entdesc.name, listedEntity(prop), properties)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
}
|
|
216
364
|
}
|
|
217
365
|
|
|
218
|
-
|
|
219
|
-
return word
|
|
366
|
+
return islist
|
|
220
367
|
}
|
|
221
368
|
|
|
222
369
|
|
|
370
|
+
function listedEntity(prop: any) {
|
|
371
|
+
const xref = prop?.items?.['x-ref']
|
|
372
|
+
const m = 'string' === typeof xref && xref.match(/^#\/components\/schemas\/(.+)$/)
|
|
373
|
+
if (m) {
|
|
374
|
+
return depluralize(snakify(m[1]))
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
223
380
|
export {
|
|
224
381
|
heuristic01
|
|
225
382
|
}
|
package/src/guide.ts
CHANGED
|
@@ -3,43 +3,68 @@ import Path from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
import { File, Content, each } from 'jostraca'
|
|
5
5
|
|
|
6
|
+
import { merge } from '@voxgig/struct'
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
import { heuristic01 } from './guide/heuristic01'
|
|
8
10
|
|
|
11
|
+
import {
|
|
12
|
+
getdlog,
|
|
13
|
+
} from './utility'
|
|
9
14
|
|
|
10
|
-
async function resolveGuide(ctx: any) {
|
|
11
|
-
// console.log('GUIDE CTX', ctx)
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
// Log non-fatal wierdness.
|
|
17
|
+
const dlog = getdlog('apidef', __filename)
|
|
18
|
+
|
|
19
|
+
async function resolveGuide(ctx: any) {
|
|
20
|
+
let baseguide: Record<string, any> = {}
|
|
21
|
+
let override: Record<string, any> = ctx.model.main.api.guide
|
|
14
22
|
|
|
15
23
|
if ('heuristic01' === ctx.opts.strategy) {
|
|
16
|
-
|
|
24
|
+
baseguide = await heuristic01(ctx)
|
|
17
25
|
}
|
|
18
26
|
else {
|
|
19
27
|
throw new Error('Unknown guide strategy: ' + ctx.opts.strategy)
|
|
20
28
|
}
|
|
21
29
|
|
|
30
|
+
// Override generated base guide with custom hints
|
|
31
|
+
let guide = merge([{}, baseguide, override])
|
|
32
|
+
|
|
33
|
+
// TODO: this is a hack!!!
|
|
34
|
+
// Instead, update @voxgig/model, so that builders can request a reload of the entire
|
|
35
|
+
// model. This allows builders to modify the model for later buidlers
|
|
36
|
+
// during a single generation pass.
|
|
37
|
+
|
|
38
|
+
|
|
22
39
|
guide = cleanGuide(guide)
|
|
23
40
|
|
|
24
|
-
|
|
41
|
+
|
|
42
|
+
// TODO: FIX: sdk.jsonic should have final version of guide
|
|
43
|
+
if (ctx.model.main?.api) {
|
|
44
|
+
ctx.model.main.api.guide = guide
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
dlog('missing', 'ctx.model.main.api')
|
|
48
|
+
}
|
|
25
49
|
|
|
26
50
|
const guideFile =
|
|
27
51
|
Path.join(ctx.opts.folder,
|
|
28
|
-
(null == ctx.opts.outprefix ? '' : ctx.opts.outprefix) + 'guide.jsonic')
|
|
52
|
+
(null == ctx.opts.outprefix ? '' : ctx.opts.outprefix) + 'base-guide.jsonic')
|
|
29
53
|
|
|
30
54
|
const guideBlocks = [
|
|
31
55
|
'# Guide',
|
|
32
56
|
'',
|
|
33
57
|
'main: api: guide: { ',
|
|
34
|
-
|
|
58
|
+
|
|
35
59
|
]
|
|
36
60
|
|
|
37
61
|
|
|
38
|
-
guideBlocks.push(...each(
|
|
39
|
-
guideBlocks.push(
|
|
62
|
+
guideBlocks.push(...each(baseguide.entity, (entity, entityname) => {
|
|
63
|
+
guideBlocks.push(`
|
|
64
|
+
entity: ${entityname}: {`)
|
|
40
65
|
|
|
41
66
|
each(entity.path, (path, pathname) => {
|
|
42
|
-
guideBlocks.push(` '${pathname}': op: {`)
|
|
67
|
+
guideBlocks.push(` path: '${pathname}': op: {`)
|
|
43
68
|
|
|
44
69
|
each(path.op, (op, opname) => {
|
|
45
70
|
guideBlocks.push(` '${opname}': method: ${op.method}`)
|
|
@@ -59,11 +84,9 @@ async function resolveGuide(ctx: any) {
|
|
|
59
84
|
|
|
60
85
|
const guideSrc = guideBlocks.join('\n')
|
|
61
86
|
|
|
62
|
-
// console.log(guideSrc)
|
|
63
|
-
|
|
64
87
|
return () => {
|
|
65
|
-
|
|
66
|
-
|
|
88
|
+
// Save base guide for reference
|
|
89
|
+
File({ name: '../def/' + Path.basename(guideFile) }, () => Content(guideSrc))
|
|
67
90
|
}
|
|
68
91
|
}
|
|
69
92
|
|
|
@@ -74,7 +97,19 @@ function cleanGuide(guide: Record<string, any>): Record<string, any> {
|
|
|
74
97
|
entity: {}
|
|
75
98
|
}
|
|
76
99
|
|
|
100
|
+
const exclude_entity = guide.exclude?.entity?.split(',') || []
|
|
101
|
+
const include_entity = guide.include?.entity?.split(',') || []
|
|
102
|
+
|
|
77
103
|
each(guide.entity, (entity: any, name: string) => {
|
|
104
|
+
if (exclude_entity.includes(name)) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
if (exclude_entity.includes('*')) {
|
|
108
|
+
if (!include_entity.includes(name)) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
78
113
|
let ent: any = clean.entity[name] = clean.entity[name] = { name, path: {} }
|
|
79
114
|
|
|
80
115
|
each(entity.path, (path: any, pathname: string) => {
|