@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.
Files changed (48) hide show
  1. package/dist/apidef.js +14 -20
  2. package/dist/apidef.js.map +1 -1
  3. package/dist/builder/flow/flowHeuristic01.js +37 -9
  4. package/dist/builder/flow/flowHeuristic01.js.map +1 -1
  5. package/dist/builder/flow.js +3 -3
  6. package/dist/builder/flow.js.map +1 -1
  7. package/dist/guide/heuristic01.js +197 -117
  8. package/dist/guide/heuristic01.js.map +1 -1
  9. package/dist/guide.js +37 -11
  10. package/dist/guide.js.map +1 -1
  11. package/dist/parse.d.ts +2 -1
  12. package/dist/parse.js +81 -3
  13. package/dist/parse.js.map +1 -1
  14. package/dist/transform/clean.d.ts +3 -0
  15. package/dist/transform/clean.js +16 -0
  16. package/dist/transform/clean.js.map +1 -0
  17. package/dist/transform/entity.js +21 -2
  18. package/dist/transform/entity.js.map +1 -1
  19. package/dist/transform/field.js +24 -1
  20. package/dist/transform/field.js.map +1 -1
  21. package/dist/transform/operation.js +131 -47
  22. package/dist/transform/operation.js.map +1 -1
  23. package/dist/transform.d.ts +1 -8
  24. package/dist/transform.js +121 -95
  25. package/dist/transform.js.map +1 -1
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/dist/types.d.ts +9 -0
  28. package/dist/types.js +3 -2
  29. package/dist/types.js.map +1 -1
  30. package/dist/utility.d.ts +7 -1
  31. package/dist/utility.js +85 -32
  32. package/dist/utility.js.map +1 -1
  33. package/model/apidef.jsonic +1 -0
  34. package/package.json +8 -9
  35. package/src/apidef.ts +23 -33
  36. package/src/builder/flow/flowHeuristic01.ts +44 -9
  37. package/src/builder/flow.ts +4 -3
  38. package/src/guide/heuristic01.ts +281 -124
  39. package/src/guide.ts +49 -14
  40. package/src/parse.ts +106 -4
  41. package/src/transform/clean.ts +28 -0
  42. package/src/transform/entity.ts +26 -3
  43. package/src/transform/field.ts +27 -1
  44. package/src/transform/operation.ts +203 -64
  45. package/src/transform.ts +29 -23
  46. package/src/types.ts +3 -2
  47. package/src/utility.ts +113 -1
  48. package/src/builder/flow/flowHeuristic01.ts~ +0 -45
package/src/parse.ts CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  import { bundleFromString, createConfig } from '@redocly/openapi-core'
4
4
 
5
+ import { each, snakify } from 'jostraca'
6
+
7
+
8
+ import { depluralize } from './utility'
5
9
 
6
10
 
7
11
  // Parse an API definition source into a JSON sructure.
@@ -16,12 +20,51 @@ async function parse(kind: string, source: any, meta?: any) {
16
20
 
17
21
 
18
22
  async function parseOpenAPI(source: any, meta?: any) {
19
- const config = await createConfig(meta?.config || {})
20
- let bundle
23
+ const base = meta?.config || {}
24
+ const config: any = await createConfig(base)
21
25
 
22
- bundle = await bundleFromString({
26
+ // First pass: parse without dereferencing to preserve $refs
27
+ const bundleWithRefs = await bundleFromString({
23
28
  source,
24
29
  config,
30
+ dereference: false,
31
+ })
32
+
33
+ // Walk the tree and add x-ref properties
34
+ const seen = new WeakSet()
35
+ let refCount = 0
36
+
37
+ function addXRefs(obj: any, path: string = '') {
38
+ if (!obj || typeof obj !== 'object' || seen.has(obj)) return
39
+ seen.add(obj)
40
+
41
+ if (Array.isArray(obj)) {
42
+ obj.forEach((item, index) => addXRefs(item, `${path}[${index}]`))
43
+ } else {
44
+ // Check for $ref property
45
+ if (obj.$ref && typeof obj.$ref === 'string') {
46
+ obj['x-ref'] = obj.$ref
47
+ refCount++
48
+ }
49
+
50
+ // Recursively process all properties
51
+ for (const [key, value] of Object.entries(obj)) {
52
+ if (value && typeof value === 'object') {
53
+ addXRefs(value, path ? `${path}.${key}` : key)
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ addXRefs(bundleWithRefs.bundle.parsed)
60
+
61
+ // Serialize back to string with x-refs preserved
62
+ const sourceWithXRefs = JSON.stringify(bundleWithRefs.bundle.parsed)
63
+
64
+ // Second pass: parse with dereferencing
65
+ const bundle = await bundleFromString({
66
+ source: sourceWithXRefs,
67
+ config,
25
68
  dereference: true,
26
69
  })
27
70
 
@@ -31,6 +74,65 @@ async function parseOpenAPI(source: any, meta?: any) {
31
74
  }
32
75
 
33
76
 
77
+
78
+
79
+
80
+
81
+
82
+ // Make consistent changes to support semantic entities.
83
+ function rewrite(def: any) {
84
+ const paths = def.paths
85
+ each(paths, (path) => {
86
+ each(path.parameters, (param: any) => {
87
+
88
+ let new_param = param.name
89
+ let new_path = path.key$
90
+
91
+ // Rename param if nane is "id", and not the final param.
92
+ // Rewrite /foo/{id}/bar as /foo/{foo_id}/bar.
93
+ // Avoids ambiguity with bar id.
94
+ if (param.name.match(/^id$/i)) {
95
+ let m = path.key$.match(/\/([^\/]+)\/{id\}\/[^\/]/)
96
+
97
+ if (m) {
98
+ const parent = depluralize(snakify(m[1]))
99
+ new_param = `${parent}_id`
100
+ new_path = path.key$.replace('{id}', '{' + new_param + '}')
101
+ }
102
+ }
103
+ else {
104
+ new_param = depluralize(snakify(param.name))
105
+ new_path = path.key$.replace('{' + param.name + '}', '{' + new_param + '}')
106
+ }
107
+
108
+ let pathdef = paths[path.key$]
109
+ delete paths[path.key$]
110
+
111
+ paths[new_path] = pathdef
112
+ path.key$ = new_path
113
+
114
+ param.name = new_param
115
+ })
116
+ })
117
+
118
+
119
+ sortkeys(def, 'paths')
120
+ sortkeys(def, 'components')
121
+
122
+ return def
123
+ }
124
+
125
+
126
+ function sortkeys(obj: any, prop: string) {
127
+ const sorted: any = {}
128
+ const sorted_keys = Object.keys(obj[prop]).sort()
129
+ for (let sk of sorted_keys) {
130
+ sorted[sk] = obj[prop][sk]
131
+ }
132
+ obj[prop] = sorted
133
+ }
134
+
34
135
  export {
35
- parse
136
+ parse,
137
+ rewrite,
36
138
  }
@@ -0,0 +1,28 @@
1
+
2
+ import { each, getx } from 'jostraca'
3
+
4
+ import type { TransformResult } from '../transform'
5
+
6
+ import { walk } from '@voxgig/struct'
7
+
8
+
9
+
10
+ const cleanTransform = async function(
11
+ ctx: any,
12
+ ): Promise<TransformResult> {
13
+ const { apimodel } = ctx
14
+
15
+ walk(apimodel, (k: any, v: any) => {
16
+ if ('string' === typeof k && k.includes('$')) {
17
+ return undefined
18
+ }
19
+ return v
20
+ })
21
+
22
+ return { ok: true, msg: 'clean' }
23
+ }
24
+
25
+
26
+ export {
27
+ cleanTransform
28
+ }
@@ -1,11 +1,13 @@
1
1
 
2
2
 
3
- import { each } from 'jostraca'
3
+ import { each, snakify } from 'jostraca'
4
4
 
5
5
  import type { TransformResult, Transform } from '../transform'
6
6
 
7
7
  import { fixName } from '../transform'
8
8
 
9
+ import { depluralize } from '../utility'
10
+
9
11
 
10
12
  const entityTransform: Transform = async function(
11
13
  ctx: any,
@@ -30,12 +32,16 @@ const entityTransform: Transform = async function(
30
32
  id: {
31
33
  name: 'id',
32
34
  field: 'id',
33
- }
35
+ },
36
+ ancestors: []
34
37
  }
35
38
 
36
39
  fixName(entityModel, guideEntity.key$)
37
40
 
38
- each(guideEntity.path, (guidePath: any) => {
41
+ let ancestors: string[] = []
42
+ let ancestorsDone = false
43
+
44
+ each(guideEntity.path, (guidePath: any, pathStr: string) => {
39
45
  const path = guidePath.key$
40
46
  const pathdef = def.paths[path]
41
47
 
@@ -50,8 +56,25 @@ const entityTransform: Transform = async function(
50
56
  .filter((p: string) => p.startsWith('{'))
51
57
  .map((p: string) => p.substring(1, p.length - 1))
52
58
 
59
+ if (!ancestorsDone) {
60
+ // Find all path sections matching /foo/{..param..} and build ancestors array
61
+ const paramRegex = /\/([a-zA-Z0-9_-]+)\/\{[a-zA-Z0-9_-]+\}/g
62
+ let m
63
+ while ((m = paramRegex.exec(pathStr)) !== null) {
64
+ // Skip if this is the last section (the entity itself)
65
+ const remainingPath = pathStr.substring(m.index + m[0].length)
66
+ if (remainingPath.length > 0) {
67
+ const ancestorName = depluralize(snakify(m[1]))
68
+ ancestors.push(ancestorName)
69
+ }
70
+ }
71
+
72
+ ancestorsDone = true
73
+ }
53
74
  })
54
75
 
76
+ entityModel.ancestors = ancestors
77
+
55
78
  msg += guideEntity.name + ' '
56
79
  })
57
80
 
@@ -69,7 +69,8 @@ function fieldbuild(
69
69
  field.name = property.key$
70
70
  fixName(field, field.name)
71
71
 
72
- field.type = property.type
72
+ // field.type = property.type
73
+ resolveFieldType(entityModel, field, property)
73
74
  fixName(field, field.type, 'type')
74
75
 
75
76
  field.short = property.description
@@ -93,6 +94,31 @@ function fieldbuild(
93
94
  }
94
95
 
95
96
 
97
+ // Resovles a heuristic "primary" type which subsumes the more detailed type.
98
+ // The primary type is only: string, number, boolean, null, object, array
99
+ function resolveFieldType(entity: any, field: any, property: any) {
100
+ const ptt = typeof property.type
101
+
102
+ if ('string' === ptt) {
103
+ field.type = property.type
104
+ }
105
+ else if (Array.isArray(property.type)) {
106
+ field.type =
107
+ (property.type.filter((t: string) => 'null' != t).sort()[0]) ||
108
+ property.type[0] ||
109
+ 'string'
110
+ field.typelist = property.type
111
+ }
112
+ else if ('undefined' === ptt && null != property.enum) {
113
+ field.type = 'string'
114
+ field.enum = property.enum
115
+ }
116
+ else {
117
+ throw new Error(
118
+ `APIDEF: Unsupported property type: ${property.type} (${entity.name}.${field.name})`)
119
+ }
120
+ }
121
+
96
122
 
97
123
  export {
98
124
  fieldTransform