@voxgig/apidef 2.0.2 → 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/utility.js CHANGED
@@ -1,8 +1,25 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getdlog = getdlog;
3
7
  exports.loadFile = loadFile;
4
8
  exports.formatJsonSrc = formatJsonSrc;
5
9
  exports.depluralize = depluralize;
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ function getdlog(tagin, filepath) {
12
+ const tag = tagin || '-';
13
+ const file = node_path_1.default.basename(filepath || '-');
14
+ const g = global;
15
+ g.__dlog__ = (g.__dlog__ || []);
16
+ const dlog = (...args) => g.__dlog__.push([tag, file, Date.now(), ...args]);
17
+ dlog.tag = tag;
18
+ dlog.file = file;
19
+ dlog.log = (filepath, f) => (f = null == filepath ? null : node_path_1.default.basename(filepath),
20
+ g.__dlog__.filter((n) => n[0] === tag && (null == f || n[2] === f)));
21
+ return dlog;
22
+ }
6
23
  function loadFile(path, what, fs, log) {
7
24
  try {
8
25
  const source = fs.readFileSync(path, 'utf8');
@@ -26,32 +43,43 @@ function depluralize(word) {
26
43
  }
27
44
  // Common irregular plurals
28
45
  const irregulars = {
46
+ 'analyses': 'analysis',
47
+ 'appendices': 'appendix',
48
+ 'axes': 'axis',
29
49
  'children': 'child',
30
- 'men': 'man',
31
- 'women': 'woman',
32
- 'teeth': 'tooth',
50
+ 'courses': 'course',
51
+ 'crises': 'crisis',
52
+ 'criteria': 'criterion',
53
+ 'data': 'datum',
54
+ 'diagnoses': 'diagnosis',
33
55
  'feet': 'foot',
56
+ 'furnace': 'furnaces',
34
57
  'geese': 'goose',
35
- 'mice': 'mouse',
36
- 'people': 'person',
37
- 'data': 'datum',
38
- 'criteria': 'criterion',
39
- 'phenomena': 'phenomenon',
58
+ 'horses': 'horse',
59
+ 'house': 'houses',
40
60
  'indices': 'index',
61
+ 'license': 'licenses',
41
62
  'matrices': 'matrix',
42
- 'vertices': 'vertex',
43
- 'analyses': 'analysis',
44
- 'axes': 'axis',
45
- 'crises': 'crisis',
46
- 'diagnoses': 'diagnosis',
63
+ 'men': 'man',
64
+ 'mice': 'mouse',
65
+ 'notice': 'notices',
47
66
  'oases': 'oasis',
67
+ 'people': 'person',
68
+ 'phenomena': 'phenomenon',
69
+ 'practice': 'practices',
70
+ 'promise': 'promises',
71
+ 'teeth': 'tooth',
48
72
  'theses': 'thesis',
49
- 'appendices': 'appendix'
73
+ 'vertices': 'vertex',
74
+ 'women': 'woman',
50
75
  };
51
76
  if (irregulars[word]) {
52
77
  return irregulars[word];
53
78
  }
54
79
  // Rules for regular plurals (applied in order)
80
+ if (word.endsWith('ies') && word.length > 3) {
81
+ return word.slice(0, -3) + 'y';
82
+ }
55
83
  // -ies -> -y (cities -> city)
56
84
  if (word.endsWith('ies') && word.length > 3) {
57
85
  return word.slice(0, -3) + 'y';
@@ -1 +1 @@
1
- {"version":3,"file":"utility.js","sourceRoot":"","sources":["../src/utility.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utility.js","sourceRoot":"","sources":["../src/utility.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ main: api: guide: control: {
7
7
  entity,
8
8
  operation,
9
9
  field,
10
+ clean,
10
11
  ` | string,
11
12
 
12
13
  element: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/apidef",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "main": "dist/apidef.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/apidef.d.ts",
@@ -41,23 +41,22 @@
41
41
  "devDependencies": {
42
42
  "@hapi/code": "^9.0.3",
43
43
  "@types/js-yaml": "^4.0.9",
44
- "@types/node": "24.0.12",
44
+ "@types/node": "24.1.0",
45
45
  "aontu": "^0.28.0",
46
- "esbuild": "^0.25.6",
47
46
  "json-schema-to-ts": "^3.1.1",
48
- "memfs": "^4.17.2",
47
+ "memfs": "^4.24.0",
49
48
  "patch-package": "^8.0.0",
50
49
  "typescript": "^5.8.3"
51
50
  },
52
51
  "dependencies": {
53
- "@redocly/openapi-core": "^1.34.3",
54
- "@voxgig/struct": "^0.0.4",
55
- "@voxgig/util": "^0.0.10",
52
+ "@redocly/openapi-core": "^1.34.5",
53
+ "@voxgig/struct": "^0.0.6",
54
+ "@voxgig/util": "^0.2.0",
56
55
  "chokidar": "^4.0.3",
57
56
  "gubu": "^9.0.0",
58
- "jostraca": "^0.20.0",
57
+ "jostraca": "^0.23.0",
59
58
  "pino": "^9.7.0",
60
- "pino-pretty": "^13.0.0",
59
+ "pino-pretty": "^13.1.1",
61
60
  "sonic-boom": "^4.2.0"
62
61
  }
63
62
  }
package/src/apidef.ts CHANGED
@@ -28,7 +28,8 @@ import {
28
28
 
29
29
 
30
30
  import {
31
- parse
31
+ parse,
32
+ rewrite,
32
33
  } from './parse'
33
34
 
34
35
 
@@ -44,20 +45,25 @@ import {
44
45
 
45
46
  import {
46
47
  loadFile,
48
+ getdlog,
47
49
  } from './utility'
48
50
 
49
-
50
51
  import { topTransform } from './transform/top'
51
52
  import { entityTransform } from './transform/entity'
52
53
  import { operationTransform } from './transform/operation'
53
54
  import { fieldTransform } from './transform/field'
55
+ import { cleanTransform } from './transform/clean'
54
56
 
55
57
  import { makeEntityBuilder } from './builder/entity'
56
58
  import { makeFlowBuilder } from './builder/flow'
57
59
 
60
+ // Log non-fatal wierdness.
61
+ const dlog = getdlog('apidef', __filename)
62
+
58
63
 
59
64
  function ApiDef(opts: ApiDefOptions) {
60
65
 
66
+
61
67
  // TODO: gubu opts!
62
68
  const fs = opts.fs || Fs
63
69
  const pino = prettyPino('apidef', opts)
@@ -68,6 +74,7 @@ function ApiDef(opts: ApiDefOptions) {
68
74
 
69
75
  async function generate(spec: any) {
70
76
  const start = Date.now()
77
+ // dlog('start')
71
78
 
72
79
  const model: Model = OpenModelShape(spec.model)
73
80
  const build: Build = OpenBuildShape(spec.build)
@@ -110,9 +117,15 @@ function ApiDef(opts: ApiDefOptions) {
110
117
 
111
118
  const defsrc = loadFile(defpath, 'def', fs, log)
112
119
 
113
- const def = await parse('OpenAPI', defsrc, { file: defpath })
120
+ let def = await parse('OpenAPI', defsrc, { file: defpath })
121
+
122
+ def = rewrite(def)
123
+
124
+ fs.writeFileSync(defpath + '.full.json', JSON.stringify(def, null, 2))
125
+
114
126
  ctx.def = def
115
127
 
128
+
116
129
  const guideBuilder = await resolveGuide(ctx)
117
130
 
118
131
 
@@ -122,6 +135,7 @@ function ApiDef(opts: ApiDefOptions) {
122
135
  entity: entityTransform,
123
136
  operation: operationTransform,
124
137
  field: fieldTransform,
138
+ clean: cleanTransform,
125
139
  })
126
140
 
127
141
  const builders = await resolveElements(ctx, 'builder', 'standard', {
@@ -155,6 +169,13 @@ function ApiDef(opts: ApiDefOptions) {
155
169
  existing: { txt: { merge: true } }
156
170
  }, root)
157
171
 
172
+ const dlogs = dlog.log()
173
+ if (0 < dlogs.length) {
174
+ for (let dlogentry of dlogs) {
175
+ log.debug({ point: 'generate-warning', dlogentry, note: String(dlogentry) })
176
+ }
177
+ }
178
+
158
179
  log.info({ point: 'generate-end', note: 'success', break: true })
159
180
 
160
181
  return {
@@ -2,8 +2,13 @@
2
2
 
3
3
  import { each, snakify, names } from 'jostraca'
4
4
 
5
+ import { size } from '@voxgig/struct'
5
6
 
6
- import { depluralize } from '../utility'
7
+
8
+ import {
9
+ depluralize,
10
+ getdlog
11
+ } from '../utility'
7
12
 
8
13
 
9
14
  type EntityDesc = {
@@ -19,10 +24,12 @@ type EntityPathDesc = {
19
24
  op: Record<string, any>
20
25
  }
21
26
 
27
+ // Log non-fatal wierdness.
28
+ const dlog = getdlog('apidef', __filename)
29
+
22
30
  async function heuristic01(ctx: any): Promise<Record<string, any>> {
23
31
  let guide = ctx.model.main.api.guide
24
32
 
25
-
26
33
  const entityDescs = resolveEntityDescs(ctx)
27
34
 
28
35
  // console.log('entityDescs')
@@ -33,10 +40,16 @@ async function heuristic01(ctx: any): Promise<Record<string, any>> {
33
40
  entity: entityDescs,
34
41
  }
35
42
 
43
+ // console.log('GUIDE')
44
+ // console.dir(guide, { depth: null })
45
+
36
46
  return guide
37
47
  }
38
48
 
39
49
 
50
+
51
+
52
+
40
53
  const METHOD_IDOP: Record<string, string> = {
41
54
  get: 'load',
42
55
  post: 'create',
@@ -51,24 +64,52 @@ function resolveEntityDescs(ctx: any) {
51
64
  const paths = ctx.def.paths
52
65
 
53
66
  // Analyze paths ending in .../foo/{foo}
54
- each(paths, (pathdef, pathstr) => {
67
+ each(paths, (pathDef: any, pathStr: string) => {
55
68
 
56
69
  // Look for rightmmost /entname/{entid}.
57
- const m = pathstr.match(/\/([a-zA-Z0-1_-]+)\/\{([a-zA-Z0-1_-]+)\}$/)
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_-]+)\}$/)
58
72
  if (m) {
59
- const entdesc = resolveEntity(entityDescs, pathstr, m[1], m[2])
73
+ // const entdesc = resolveEntity(entityDescs, pathStr, m[1], m[2])
60
74
 
75
+ each(pathDef, (methodDef: any, methodStr: string) => {
76
+ methodStr = methodStr.toLowerCase()
77
+
78
+
79
+ if (!METHOD_IDOP[methodStr]) {
80
+ return
81
+ }
82
+
83
+ const entdesc = resolveEntity(entityDescs, pathDef, pathStr, methodDef, methodStr)
84
+
85
+ if (null == entdesc) {
86
+ console.log(
87
+ 'WARNING: unable to resolve entity for method ' + methodStr +
88
+ ' path ' + pathStr)
89
+ return
90
+ }
91
+
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 = mdef.responses[200] || mdef.responses[201]
112
+ const resokdef = methodDef.responses[200] || methodDef.responses[201]
72
113
  const resbody = resokdef?.content?.['application/json']?.schema
73
114
  if (resbody) {
74
115
  if (resbody[entdesc.origname]) {
@@ -79,7 +120,7 @@ function resolveEntityDescs(ctx: any) {
79
120
  }
80
121
  }
81
122
 
82
- const reqdef = mdef.requestBody?.content?.['application/json']?.schema?.properties
123
+ const reqdef = methodDef.requestBody?.content?.['application/json']?.schema?.properties
83
124
  if (reqdef) {
84
125
  if (reqdef[entdesc.origname]) {
85
126
  transform.reqform = { [entdesc.origname]: '`reqdata`' }
@@ -90,89 +131,252 @@ function resolveEntityDescs(ctx: any) {
90
131
 
91
132
  }
92
133
 
93
- const op = entdesc.path[pathstr].op
134
+ const op = entdesc.path[pathStr].op
94
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
 
107
- // Analyze paths ending in .../foo
108
- each(paths, (pathdef, pathstr) => {
153
+ // console.log('USER')
154
+ // console.dir(entityDescs.user, { depth: null })
109
155
 
110
- // Look for rightmmost /entname.
111
- const m = pathstr.match(/\/([a-zA-Z0-1_-]+)$/)
112
- if (m) {
113
- const entdesc = resolveEntity(entityDescs, pathstr, m[1])
156
+ return entityDescs
157
+ }
114
158
 
115
- if (pathdef.get) {
116
- const op: Record<string, any> = { list: { method: 'get' } }
117
- entdesc.path[pathstr] = { op }
118
159
 
119
- const transform: Record<string, any> = {}
120
- const mdef = pathdef.get
121
- const resokdef = mdef.responses[200] || mdef.responses[201]
122
- const resbody = resokdef?.content?.['application/json']?.schema
123
- if (resbody) {
124
- if (resbody[entdesc.origname]) {
125
- transform.resform = '`body.' + entdesc.origname + '`'
126
- }
127
- else if (resbody[entdesc.name]) {
128
- transform.resform = '`body.' + entdesc.name + '`'
129
- }
130
- }
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)
181
+ }
131
182
 
132
- if (0 < Object.entries(transform).length) {
133
- op.transform = transform
134
- }
183
+ entname = depluralize(origentname)
184
+
185
+ entdesc = (entityDescs[entname] = entityDescs[entname] || {
186
+ name: entname,
187
+ id: Math.random(),
188
+ alias: {}
189
+ })
190
+
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'
135
197
  }
136
198
  }
137
- })
199
+ }
138
200
 
139
- return entityDescs
140
- }
201
+ // Can't figure out the entity
202
+ else {
203
+ console.log('NO ENTTIY', pathStr)
204
+ return
205
+ }
141
206
 
142
207
 
143
- function resolveEntity(
144
- entityDescs: Record<string, EntityDesc>,
145
- pathStr: string,
146
- pathName: string,
147
- pathParam?: string
148
- )
149
- : EntityDesc {
150
- let origentname = snakify(pathName)
151
- let entname = depluralize(origentname)
152
-
153
- let entdesc = (entityDescs[entname] = entityDescs[entname] || { name: entname })
154
- entdesc.plural = origentname
208
+ // entdesc.plural = origentname
155
209
  entdesc.origname = origentname
156
210
 
157
211
  names(entdesc, entname)
158
212
 
159
213
  entdesc.alias = entdesc.alias || {}
160
214
 
161
- if (null != pathParam) {
162
- const pathParamCanon = snakify(pathParam)
163
- if ('id' != pathParamCanon) {
164
- entdesc.alias.id = pathParamCanon
165
- entdesc.alias[pathParamCanon] = 'id'
215
+ entdesc.path = (entdesc.path || {})
216
+ entdesc.path[pathStr] = entdesc.path[pathStr] || {}
217
+ entdesc.path[pathStr].op = entdesc.path[pathStr].op || {}
218
+
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
166
277
  }
167
278
  }
168
279
 
169
- entdesc.path = (entdesc.path || {})
170
- entdesc.path[pathStr] = { op: {} }
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
+ }
171
295
 
172
- return entdesc
296
+ if (null != xref && 'string' === typeof xref) {
297
+ let xrefm = xref.match(/\/components\/schemas\/(.+)$/)
298
+ if (xrefm) {
299
+ compname = xrefm[1]
300
+ }
301
+ }
302
+ }
303
+
304
+ return compname
173
305
  }
174
306
 
175
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)
319
+ }
320
+
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
+ }
364
+ }
365
+
366
+ return islist
367
+ }
368
+
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
+
176
380
  export {
177
381
  heuristic01
178
382
  }