@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.
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 +6 -5
  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
@@ -1,53 +1,4 @@
1
1
 
2
- main: api: guide: control: {
3
-
4
- transform: openapi: {
5
- order: *`
6
- top,
7
- entity,
8
- operation,
9
- field,
10
- clean,
11
- ` | string,
12
-
13
- element: {
14
- top: {}
15
- entity: {}
16
- operation: {}
17
- field: {}
18
- }
19
- }
20
-
21
- builder: standard: {
22
- order: *`
23
- entity,
24
- flow,
25
- ` | string,
26
-
27
- element: {
28
- entity: {}
29
- flow: {}
30
- }
31
- }
32
-
33
- }
34
-
35
-
36
-
37
- main: api: guide: entity: &: {
38
- name: .$KEY
39
- path: &: {
40
- op: &: {
41
- method: *'get' | string
42
- transform: {
43
- # TODO: make these work
44
- # inward: *null | string | object
45
- # outward: *null | string | object
46
- }
47
- }
48
- }
49
- }
50
-
51
-
2
+ main: api: {}
52
3
 
53
4
 
@@ -0,0 +1,53 @@
1
+
2
+ guide: control: {
3
+
4
+ transform: openapi: {
5
+ order: *`
6
+ top,
7
+ entity,
8
+ operation,
9
+ field,
10
+ clean,
11
+ ` | string,
12
+
13
+ element: {
14
+ top: {}
15
+ entity: {}
16
+ operation: {}
17
+ field: {}
18
+ }
19
+ }
20
+
21
+ builder: standard: {
22
+ order: *`
23
+ entity,
24
+ flow,
25
+ ` | string,
26
+
27
+ element: {
28
+ entity: {}
29
+ flow: {}
30
+ }
31
+ }
32
+
33
+ }
34
+
35
+
36
+
37
+ guide: entity: &: {
38
+ name: .$KEY
39
+ path: &: {
40
+ op: &: {
41
+ method: *'get' | string
42
+ transform: {
43
+ # TODO: make these work
44
+ # inward: *null | string | object
45
+ # outward: *null | string | object
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+
52
+
53
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/apidef",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "main": "dist/apidef.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/apidef.d.ts",
@@ -40,20 +40,21 @@
40
40
  "devDependencies": {
41
41
  "@hapi/code": "^9.0.3",
42
42
  "@types/js-yaml": "^4.0.9",
43
- "@types/node": "24.2.0",
43
+ "@types/node": "24.3.0",
44
44
  "aontu": "^0.28.0",
45
45
  "json-schema-to-ts": "^3.1.1",
46
- "memfs": "^4.36.0",
46
+ "memfs": "^4.36.3",
47
47
  "typescript": "^5.9.2"
48
48
  },
49
49
  "dependencies": {
50
50
  "@redocly/openapi-core": "1.34.5",
51
- "@voxgig/struct": "^0.0.6",
51
+ "@voxgig/struct": "^0.0.9",
52
52
  "@voxgig/util": "^0.2.0",
53
53
  "chokidar": "^4.0.3",
54
+ "diff": "^8.0.2",
54
55
  "gubu": "^9.0.0",
55
56
  "jostraca": "^0.24.1",
56
- "pino": "^9.7.0",
57
+ "pino": "^9.9.0",
57
58
  "pino-pretty": "^13.1.1",
58
59
  "sonic-boom": "^4.2.0"
59
60
  }
package/src/apidef.ts CHANGED
@@ -11,6 +11,8 @@ import { prettyPino } from '@voxgig/util'
11
11
 
12
12
  import type {
13
13
  ApiDefOptions,
14
+ ApiDefResult,
15
+ Control,
14
16
  Model,
15
17
  Build,
16
18
  ApiModel,
@@ -19,12 +21,13 @@ import type {
19
21
  import {
20
22
  OpenModelShape,
21
23
  OpenBuildShape,
24
+ OpenControlShape,
22
25
  } from './types'
23
26
 
24
27
 
25
28
  import {
26
- resolveGuide,
27
- } from './guide'
29
+ buildGuide,
30
+ } from './guide/guide'
28
31
 
29
32
 
30
33
  import {
@@ -72,12 +75,19 @@ function ApiDef(opts: ApiDefOptions) {
72
75
  opts.strategy = opts.strategy || 'heuristic01'
73
76
 
74
77
 
75
- async function generate(spec: any) {
78
+ async function generate(spec: any): Promise<ApiDefResult> {
76
79
  const start = Date.now()
80
+ const steps: string[] = []
77
81
  // dlog('start')
78
82
 
79
- const model: Model = OpenModelShape(spec.model)
80
- const build: Build = OpenBuildShape(spec.build)
83
+ const ctrl: Control = OpenControlShape(spec.ctrl || {})
84
+ const model: Model = OpenModelShape(spec.model || {})
85
+ const build: Build = OpenBuildShape(spec.build || {})
86
+
87
+ // Step: parse (API spec).
88
+ if (!ctrl.step.parse) {
89
+ return { ok: false, steps, start, end: Date.now(), ctrl }
90
+ }
81
91
 
82
92
  names(model, model.name)
83
93
 
@@ -112,7 +122,9 @@ function ApiDef(opts: ApiDefOptions) {
112
122
  defpath: Path.dirname(defpath),
113
123
  model,
114
124
  apimodel,
115
- def: undefined
125
+ guide: {},
126
+ def: undefined,
127
+ note: {}
116
128
  }
117
129
 
118
130
  const defsrc = loadFile(defpath, 'def', fs, log)
@@ -125,9 +137,24 @@ function ApiDef(opts: ApiDefOptions) {
125
137
 
126
138
  ctx.def = def
127
139
 
140
+ steps.push('parse')
141
+
142
+
143
+ // Step: guide (derive).
144
+ if (!ctrl.step.guide) {
145
+ return { ok: false, steps, start, end: Date.now(), ctrl }
146
+ }
147
+
148
+ const guideModel = await buildGuide(ctx)
149
+ ctx.guide = guideModel.guide
150
+
151
+ steps.push('guide')
128
152
 
129
- const guideBuilder = await resolveGuide(ctx)
130
153
 
154
+ // Step: transformers (transform spec and guide into core structures).
155
+ if (!ctrl.step.transformers) {
156
+ return { ok: false, steps, start, end: Date.now(), ctrl, guide: ctx.guide }
157
+ }
131
158
 
132
159
  // const transformSpec = await resolveTransforms(ctx)
133
160
  const transforms = await resolveElements(ctx, 'transform', 'openapi', {
@@ -138,11 +165,25 @@ function ApiDef(opts: ApiDefOptions) {
138
165
  clean: cleanTransform,
139
166
  })
140
167
 
168
+ steps.push('transformers')
169
+
170
+ // Step: builders (build generated sub models).
171
+ if (!ctrl.step.builders) {
172
+ return { ok: false, steps, start, end: Date.now(), ctrl, guide: ctx.guide }
173
+ }
174
+
141
175
  const builders = await resolveElements(ctx, 'builder', 'standard', {
142
176
  entity: makeEntityBuilder,
143
177
  flow: makeFlowBuilder,
144
178
  })
145
179
 
180
+ steps.push('builders')
181
+
182
+
183
+ // Step: generate (generate model files).
184
+ if (!ctrl.step.generate) {
185
+ return { ok: false, steps, start, end: Date.now(), ctrl, guide: ctx.guide }
186
+ }
146
187
 
147
188
  const jostraca = Jostraca({
148
189
  now: spec.now,
@@ -153,10 +194,6 @@ function ApiDef(opts: ApiDefOptions) {
153
194
  const jmodel = {}
154
195
 
155
196
  const root = () => Project({ folder: '.' }, async () => {
156
- guideBuilder()
157
- // entityBuilder()
158
- // flowBuilder()
159
-
160
197
  for (let builder of builders) {
161
198
  builder()
162
199
  }
@@ -176,12 +213,21 @@ function ApiDef(opts: ApiDefOptions) {
176
213
  }
177
214
  }
178
215
 
216
+ steps.push('generate')
217
+
179
218
  log.info({ point: 'generate-end', note: 'success', break: true })
180
219
 
181
220
  return {
182
221
  ok: true,
183
- name: 'apidef',
222
+ start,
223
+ end: Date.now(),
224
+ steps,
225
+ ctrl,
226
+
227
+ guide: ctx.guide,
184
228
  apimodel,
229
+ ctx,
230
+ jres,
185
231
  }
186
232
  }
187
233
 
@@ -194,8 +240,6 @@ function ApiDef(opts: ApiDefOptions) {
194
240
  ApiDef.makeBuild = async function(opts: ApiDefOptions) {
195
241
  let apidef: any = undefined
196
242
 
197
- // const outprefix = null == opts.outprefix ? '' : opts.outprefix
198
-
199
243
  const config = {
200
244
  def: opts.def || 'no-def',
201
245
  kind: 'openapi3',
@@ -217,7 +261,9 @@ ApiDef.makeBuild = async function(opts: ApiDefOptions) {
217
261
  })
218
262
  }
219
263
 
220
- return await apidef.generate({ model, build, config })
264
+ const ctrl = build.spec.buildargs?.apidef?.ctrl || {}
265
+
266
+ return await apidef.generate({ model, build, config, ctrl })
221
267
  }
222
268
 
223
269
  build.step = 'pre'
@@ -5,7 +5,7 @@ import { each, names } from 'jostraca'
5
5
 
6
6
 
7
7
  async function flowHeuristic01(ctx: any): Promise<any[]> {
8
- let entity = ctx.model.main.api.guide.entity
8
+ let entity = ctx.guide.entity
9
9
 
10
10
  const flows: any[] = []
11
11
 
@@ -0,0 +1,274 @@
1
+
2
+ import Path from 'node:path'
3
+
4
+ import { Jostraca, Project, names, File, Content, each } from 'jostraca'
5
+
6
+ import { Aontu, Val, Nil, Context } from 'aontu'
7
+
8
+
9
+ import { items } from '@voxgig/struct'
10
+
11
+
12
+ import { heuristic01 } from './heuristic01'
13
+
14
+ import {
15
+ getdlog,
16
+ } from '../utility'
17
+
18
+
19
+ // Log non-fatal wierdness.
20
+ const dlog = getdlog('apidef', __filename)
21
+
22
+
23
+ async function buildGuide(ctx: any): Promise<any> {
24
+ const errs: any[] = []
25
+
26
+ // console.log(ctx)
27
+ const folder = Path.resolve(ctx.opts.folder)
28
+ // console.log('GUIDE folder', folder)
29
+
30
+ try {
31
+ const basejres = await buildBaseGuide(ctx)
32
+ }
33
+ catch (err: any) {
34
+ errs.push(err)
35
+ }
36
+
37
+ handleErrors(ctx, errs)
38
+
39
+ let src = ''
40
+ let guidePath = Path.join(folder, 'guide',
41
+ (null == ctx.opts.outprefix ? '' : ctx.opts.outprefix) + 'guide.jsonic')
42
+
43
+ try {
44
+ src = ctx.fs.readFileSync(guidePath, 'utf8')
45
+ }
46
+ catch (err: any) {
47
+ errs.push(err)
48
+ }
49
+
50
+ handleErrors(ctx, errs)
51
+
52
+
53
+ const opts = {
54
+ path: guidePath
55
+ }
56
+
57
+ const guideRoot = Aontu(src, opts)
58
+ errs.push(...guideRoot.err)
59
+
60
+ handleErrors(ctx, errs)
61
+
62
+
63
+ let genctx = new Context({ root: guideRoot })
64
+ const guideModel = guideRoot.gen(genctx)
65
+
66
+ errs.push(...genctx.err)
67
+
68
+ handleErrors(ctx, errs)
69
+
70
+ return guideModel
71
+ }
72
+
73
+
74
+ function handleErrors(ctx: any, errs: any[]) {
75
+ if (0 < errs.length) {
76
+ let topmsg: string[] = []
77
+ for (let err of errs) {
78
+ topmsg.push((err?.message?.split('\n')[0]) || '')
79
+ ctx.log.error({ err })
80
+ }
81
+ throw new Error('SUMMARY: ' + topmsg.join('; '))
82
+ }
83
+ }
84
+
85
+
86
+ async function buildBaseGuide(ctx: any) {
87
+ let baseguide: Record<string, any> = {}
88
+
89
+ if ('heuristic01' === ctx.opts.strategy) {
90
+ baseguide = await heuristic01(ctx)
91
+ }
92
+ else {
93
+ throw new Error('Unknown guide strategy: ' + ctx.opts.strategy)
94
+ }
95
+
96
+ const guideBlocks = [
97
+ '# Guide',
98
+ '',
99
+ 'guide: {',
100
+ ]
101
+
102
+
103
+ items(baseguide.entity).map(([entityname, entity]: any[]) => {
104
+ guideBlocks.push(`
105
+ entity: ${entityname}: {` +
106
+ (0 < entity.why_name.length ? ' # name:' + entity.why_name.join(';') : ''))
107
+
108
+ items(entity.path).map(([pathname, path]: any[]) => {
109
+ guideBlocks.push(` path: '${pathname}': op: {` +
110
+ (0 < path.why_ent.length ? ' # ent:' + path.why_ent.join(';') : ''))
111
+
112
+ items(path.op).map(([opname, op]: any[]) => {
113
+ guideBlocks.push(` '${opname}': method: ${op.method}` +
114
+ (0 < op.why_op.length ? ' # ' + op.why_op : ''))
115
+ if (op.transform?.reqform) {
116
+ guideBlocks.push(
117
+ ` '${opname}': transform: reqform: ${JSON.stringify(op.transform.reqform)}`)
118
+ }
119
+ })
120
+
121
+ guideBlocks.push(` }`)
122
+ })
123
+
124
+ guideBlocks.push(` }`)
125
+ })
126
+
127
+ guideBlocks.push('', '}')
128
+
129
+ const guideSrc = guideBlocks.join('\n')
130
+
131
+ ctx.note.guide = { base: guideSrc }
132
+
133
+ const baseGuideFileName =
134
+ (null == ctx.opts.outprefix ? '' : ctx.opts.outprefix) + 'base-guide.jsonic'
135
+
136
+ const jostraca = Jostraca({
137
+ folder: ctx.opts.folder + '/guide',
138
+ now: ctx.spec.now,
139
+ fs: () => ctx.fs,
140
+ log: ctx.log,
141
+ })
142
+
143
+ const root = () => Project({ folder: '.' }, async () => {
144
+ File({ name: baseGuideFileName }, () => Content(guideSrc))
145
+ })
146
+
147
+ const jres = await jostraca.generate({
148
+ existing: { txt: { merge: true } }
149
+ }, root)
150
+
151
+ return jres
152
+ }
153
+
154
+
155
+ /*
156
+ async function resolveGuide(ctx: any) {
157
+ let baseguide: Record<string, any> = {}
158
+ let override: Record<string, any> = ctx.model.main.api.guide
159
+
160
+ if ('heuristic01' === ctx.opts.strategy) {
161
+ baseguide = await heuristic01(ctx)
162
+ }
163
+ else {
164
+ throw new Error('Unknown guide strategy: ' + ctx.opts.strategy)
165
+ }
166
+
167
+ // Override generated base guide with custom hints
168
+ let guide = merge([{}, baseguide, override])
169
+
170
+ // TODO: this is a hack!!!
171
+ // Instead, update @voxgig/model, so that builders can request a reload of the entire
172
+ // model. This allows builders to modify the model for later buidlers
173
+ // during a single generation pass.
174
+
175
+
176
+ guide = cleanGuide(guide)
177
+
178
+
179
+ // TODO: FIX: sdk.jsonic should have final version of guide
180
+ if (ctx.model.main?.api) {
181
+ ctx.model.main.api.guide = guide
182
+ }
183
+ else {
184
+ dlog('missing', 'ctx.model.main.api')
185
+ }
186
+
187
+ const guideFile =
188
+ Path.join(ctx.opts.folder,
189
+ (null == ctx.opts.outprefix ? '' : ctx.opts.outprefix) + 'base-guide.jsonic')
190
+
191
+ const guideBlocks = [
192
+ '# Guide',
193
+ '',
194
+ 'main: api: guide: {',
195
+ ]
196
+
197
+
198
+ guideBlocks.push(...each(baseguide.entity, (entity, entityname) => {
199
+ guideBlocks.push(`
200
+ entity: ${entityname}: {` +
201
+ (0 < entity.why_name.length ? ' # name:' + entity.why_name.join(';') : ''))
202
+
203
+ items(entity.path).map((pathn) => {
204
+ const [pathname, path] = pathn
205
+ guideBlocks.push(` path: '${pathname}': op: {` +
206
+ (0 < path.why_ent.length ? ' # ent:' + path.why_ent.join(';') : ''))
207
+
208
+ items(path.op).map((opn) => {
209
+ const [opname, op] = opn
210
+ guideBlocks.push(` '${opname}': method: ${op.method}` +
211
+ (0 < op.why_op.length ? ' # ' + op.why_op : ''))
212
+ if (op.transform?.reqform) {
213
+ guideBlocks.push(
214
+ ` '${opname}': transform: reqform: ${JSON.stringify(op.transform.reqform)}`)
215
+ }
216
+ })
217
+
218
+ guideBlocks.push(` }`)
219
+ })
220
+
221
+ guideBlocks.push(`}`)
222
+ }))
223
+
224
+ guideBlocks.push('}')
225
+
226
+ const guideSrc = guideBlocks.join('\n')
227
+
228
+ ctx.note.guide = { base: guideSrc }
229
+
230
+ return () => {
231
+ // Save base guide for reference
232
+ File({ name: '../def/' + Path.basename(guideFile) }, () => Content(guideSrc))
233
+ }
234
+ }
235
+
236
+
237
+ function cleanGuide(guide: Record<string, any>): Record<string, any> {
238
+ const clean: Record<string, any> = {
239
+ control: guide.control,
240
+ entity: {}
241
+ }
242
+
243
+ const exclude_entity = guide.exclude?.entity?.split(',') || []
244
+ const include_entity = guide.include?.entity?.split(',') || []
245
+
246
+ each(guide.entity, (entity: any, name: string) => {
247
+ if (exclude_entity.includes(name)) {
248
+ return
249
+ }
250
+ if (exclude_entity.includes('*')) {
251
+ if (!include_entity.includes(name)) {
252
+ return
253
+ }
254
+ }
255
+
256
+ let ent: any = clean.entity[name] = clean.entity[name] = {
257
+ name,
258
+ why_name: entity.why_name || [],
259
+ path: {}
260
+ }
261
+
262
+ each(entity.path, (path: any, pathname: string) => {
263
+ ent.path[pathname] = path
264
+ })
265
+ })
266
+
267
+ return clean
268
+ }
269
+ */
270
+
271
+
272
+ export {
273
+ buildGuide
274
+ }