@voxgig/apidef 2.3.1 → 2.4.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.
Files changed (45) hide show
  1. package/dist/apidef.d.ts +2 -6
  2. package/dist/apidef.js +44 -11
  3. package/dist/apidef.js.map +1 -1
  4. package/dist/builder/flow/flowHeuristic01.js +1 -1
  5. package/dist/builder/flow/flowHeuristic01.js.map +1 -1
  6. package/dist/guide/guide.d.ts +2 -0
  7. package/dist/guide/guide.js +107 -0
  8. package/dist/guide/guide.js.map +1 -0
  9. package/dist/guide/heuristic01.js +149 -218
  10. package/dist/guide/heuristic01.js.map +1 -1
  11. package/dist/resolver.js +2 -2
  12. package/dist/resolver.js.map +1 -1
  13. package/dist/transform/entity.js +1 -2
  14. package/dist/transform/entity.js.map +1 -1
  15. package/dist/transform/field.js +1 -2
  16. package/dist/transform/field.js.map +1 -1
  17. package/dist/transform/operation.js +1 -2
  18. package/dist/transform/operation.js.map +1 -1
  19. package/dist/transform/top.js +0 -1
  20. package/dist/transform/top.js.map +1 -1
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/dist/types.d.ts +297 -2
  23. package/dist/types.js +12 -1
  24. package/dist/types.js.map +1 -1
  25. package/dist/utility.d.ts +3 -1
  26. package/dist/utility.js +155 -0
  27. package/dist/utility.js.map +1 -1
  28. package/model/apidef.jsonic +1 -50
  29. package/model/guide.jsonic +53 -0
  30. package/package.json +9 -6
  31. package/src/apidef.ts +61 -15
  32. package/src/builder/flow/flowHeuristic01.ts +1 -1
  33. package/src/guide/guide.ts +274 -0
  34. package/src/guide/heuristic01.ts +173 -232
  35. package/src/{guide.ts → guide.ts.off} +8 -5
  36. package/src/resolver.ts +2 -2
  37. package/src/transform/entity.ts +1 -6
  38. package/src/transform/field.ts +1 -6
  39. package/src/transform/operation.ts +1 -2
  40. package/src/transform/top.ts +0 -6
  41. package/src/types.ts +31 -0
  42. package/src/utility.ts +184 -37
  43. package/dist/guide.d.ts +0 -2
  44. package/dist/guide.js +0 -95
  45. package/dist/guide.js.map +0 -1
@@ -2,12 +2,14 @@
2
2
 
3
3
  import { each, snakify, names } from 'jostraca'
4
4
 
5
- import { size, walk } from '@voxgig/struct'
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
- // Look for rightmmost /entname/{entid}.
66
- let m = pathStr.match(/\/([a-zA-Z0-1_-]+)(\/\{([a-zA-Z0-1_-]+)\})?$/)
67
- // const m = pathStr.match(/\/([a-zA-Z0-1_-]+)\/\{([a-zA-Z0-1_-]+)\}$/)
68
- if (m) {
69
- // const entdesc = resolveEntity(entityDescs, pathStr, m[1], m[2])
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
- methodStr = methodStr.toLowerCase()
75
- let why_op: string[] = []
110
+ each(caught.methods, (pmdef) => {
111
+ let methodDef = pmdef
112
+ let pathStr = pmdef.path
113
+ let methodStr = pmdef.method
76
114
 
77
- if (!METHOD_IDOP[methodStr]) {
78
- return
79
- }
115
+ // methodStr = methodStr.toLowerCase()
116
+ let why_op: string[] = []
80
117
 
81
- const why_ent: string[] = []
82
- const entdesc =
83
- resolveEntity(entityDescs, pathDef, pathStr, methodDef, methodStr, why_ent)
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
- entdesc.path[pathStr].why_ent = why_ent
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
- let opname = resolveOpName(methodStr, methodDef, pathStr, entdesc, why_op)
137
+ // if (pathStr.includes('courses')) {
138
+ // console.log('ENTRES', pathStr, methodStr)
139
+ // console.dir(ent2, { depth: null })
140
+ // }
102
141
 
103
- if (null == opname) {
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
- const resokdef = methodDef.responses?.[200] || methodDef.responses?.[201]
117
- const resbody = resokdef?.content?.['application/json']?.schema
118
- if (resbody) {
119
- if (resbody[entdesc.origname]) {
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
- const reqdef = methodDef.requestBody?.content?.['application/json']?.schema?.properties
128
- if (reqdef) {
129
- if (reqdef[entdesc.origname]) {
130
- transform.reqform = { [entdesc.origname]: '`reqdata`' }
131
- }
132
- else if (reqdef[entdesc.origname]) {
133
- transform.reqform = { [entdesc.origname]: '`reqdata`' }
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
- const op = entdesc.path[pathStr].op
177
+ }
139
178
 
140
- op[opname] = {
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
- if (0 < Object.entries(transform).length) {
147
- op[opname].transform = transform
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
- // if ('/v2/users/{user_id}/enrollment' === pathStr) {
151
- // console.log('ENT')
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 == content) {
422
- // console.log('NO-CONTENT', pathStr, methodDef)
423
- why.push('no-content')
380
+ if (null == schema) {
381
+ why.push('no-schema')
424
382
  }
425
383
  else {
426
- const schema = content['application/json']?.schema
427
- if (null == schema) {
428
- why.push('no-schema')
384
+ if (schema.type === 'array') {
385
+ why.push('array')
386
+ islist = true
429
387
  }
430
- else {
431
388
 
432
- if (schema.type === 'array') {
433
- why.push('array')
434
- islist = true
435
- }
389
+ if (!islist) {
390
+ const properties = schema.properties || {}
391
+ each(properties, (prop) => {
392
+ if (prop.type === 'array') {
436
393
 
437
- if (!islist) {
438
- const properties = schema.properties || {}
439
- each(properties, (prop) => {
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
- if (!islist) {
480
- why.push('not-list')
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
- each(entity.path, (path, pathname) => {
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
- each(path.op, (op, opname) => {
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 = model.main.api.guide.control[kind][subkind]
18
+ const control = guide.control[kind][subkind]
19
19
 
20
20
  const target = kind + '.' + subkind
21
21
 
@@ -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
 
@@ -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
 
@@ -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,