@voxgig/apidef 2.4.1 → 3.0.2

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 (96) hide show
  1. package/dist/apidef.d.ts +5 -1
  2. package/dist/apidef.js +197 -112
  3. package/dist/apidef.js.map +1 -1
  4. package/dist/builder/entity/entity.d.ts +3 -0
  5. package/dist/builder/entity/{apiEntity.js → entity.js} +12 -9
  6. package/dist/builder/entity/entity.js.map +1 -0
  7. package/dist/builder/entity/info.d.ts +3 -0
  8. package/dist/builder/entity/info.js +22 -0
  9. package/dist/builder/entity/info.js.map +1 -0
  10. package/dist/builder/entity.js +7 -21
  11. package/dist/builder/entity.js.map +1 -1
  12. package/dist/builder/flow/flowHeuristic01.js +21 -11
  13. package/dist/builder/flow/flowHeuristic01.js.map +1 -1
  14. package/dist/builder/flow.d.ts +2 -1
  15. package/dist/builder/flow.js +29 -4
  16. package/dist/builder/flow.js.map +1 -1
  17. package/dist/def.d.ts +62 -0
  18. package/dist/def.js +4 -0
  19. package/dist/def.js.map +1 -0
  20. package/dist/desc.d.ts +89 -0
  21. package/dist/desc.js +4 -0
  22. package/dist/desc.js.map +1 -0
  23. package/dist/guide/guide.d.ts +2 -1
  24. package/dist/guide/guide.js +161 -30
  25. package/dist/guide/guide.js.map +1 -1
  26. package/dist/guide/heuristic01.d.ts +2 -1
  27. package/dist/guide/heuristic01.js +1120 -234
  28. package/dist/guide/heuristic01.js.map +1 -1
  29. package/dist/model.d.ts +55 -0
  30. package/dist/model.js +4 -0
  31. package/dist/model.js.map +1 -0
  32. package/dist/parse.d.ts +1 -2
  33. package/dist/parse.js +8 -47
  34. package/dist/parse.js.map +1 -1
  35. package/dist/transform/args.d.ts +3 -0
  36. package/dist/transform/args.js +58 -0
  37. package/dist/transform/args.js.map +1 -0
  38. package/dist/transform/clean.js +27 -3
  39. package/dist/transform/clean.js.map +1 -1
  40. package/dist/transform/entity.d.ts +11 -3
  41. package/dist/transform/entity.js +57 -41
  42. package/dist/transform/entity.js.map +1 -1
  43. package/dist/transform/field.d.ts +3 -3
  44. package/dist/transform/field.js +90 -65
  45. package/dist/transform/field.js.map +1 -1
  46. package/dist/transform/operation.d.ts +1 -1
  47. package/dist/transform/operation.js +94 -296
  48. package/dist/transform/operation.js.map +1 -1
  49. package/dist/transform/select.d.ts +3 -0
  50. package/dist/transform/select.js +44 -0
  51. package/dist/transform/select.js.map +1 -0
  52. package/dist/transform/top.d.ts +9 -0
  53. package/dist/transform/top.js +11 -2
  54. package/dist/transform/top.js.map +1 -1
  55. package/dist/transform.js +4 -0
  56. package/dist/transform.js.map +1 -1
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/dist/types.d.ts +112 -19
  59. package/dist/types.js +4 -2
  60. package/dist/types.js.map +1 -1
  61. package/dist/utility.d.ts +30 -2
  62. package/dist/utility.js +381 -6
  63. package/dist/utility.js.map +1 -1
  64. package/model/apidef.jsonic +75 -1
  65. package/model/guide.jsonic +14 -44
  66. package/package.json +17 -14
  67. package/src/apidef.ts +264 -121
  68. package/src/builder/entity/{apiEntity.ts → entity.ts} +18 -11
  69. package/src/builder/entity/info.ts +53 -0
  70. package/src/builder/entity.ts +9 -35
  71. package/src/builder/flow/flowHeuristic01.ts +46 -12
  72. package/src/builder/flow.ts +39 -5
  73. package/src/def.ts +91 -0
  74. package/src/desc.ts +143 -0
  75. package/src/guide/guide.ts +207 -134
  76. package/src/guide/heuristic01.ts +1651 -272
  77. package/src/model.ts +98 -0
  78. package/src/parse.ts +5 -61
  79. package/src/schematron.ts.off +317 -0
  80. package/src/transform/args.ts +102 -0
  81. package/src/transform/clean.ts +43 -8
  82. package/src/transform/entity.ts +100 -51
  83. package/src/transform/field.ts +150 -71
  84. package/src/transform/operation.ts +118 -414
  85. package/src/transform/select.ts +90 -0
  86. package/src/transform/top.ts +76 -3
  87. package/src/transform.ts +4 -0
  88. package/src/types.ts +185 -5
  89. package/src/utility.ts +481 -9
  90. package/dist/builder/entity/apiEntity.d.ts +0 -3
  91. package/dist/builder/entity/apiEntity.js.map +0 -1
  92. package/dist/builder/entity/def.d.ts +0 -3
  93. package/dist/builder/entity/def.js +0 -19
  94. package/dist/builder/entity/def.js.map +0 -1
  95. package/src/builder/entity/def.ts +0 -44
  96. package/src/guide.ts.off +0 -136
@@ -1,74 +1,57 @@
1
1
 
2
2
 
3
- import { each, snakify } from 'jostraca'
3
+ import { each } from 'jostraca'
4
4
 
5
5
  import type { TransformResult, Transform } from '../transform'
6
6
 
7
- import { fixName } from '../transform'
7
+ import { formatJSONIC } from '../utility'
8
8
 
9
- import { depluralize } from '../utility'
9
+ import { KIT } from '../types'
10
10
 
11
+ import type { KitModel } from '../types'
11
12
 
12
- const entityTransform: Transform = async function(
13
+ import type {
14
+ GuideEntity,
15
+ } from './top'
16
+
17
+ import type {
18
+ GuidePath,
19
+ PathDesc,
20
+ } from '../desc'
21
+
22
+ import type {
23
+ ModelEntity,
24
+ } from '../model'
25
+
26
+
27
+
28
+
29
+ const entityTransform = async function(
13
30
  ctx: any,
14
31
  ): Promise<TransformResult> {
15
- const { apimodel, model, def, guide } = ctx
32
+ const { apimodel, guide } = ctx
33
+ const kit: KitModel = apimodel.main[KIT]
16
34
 
17
35
  let msg = ''
18
36
 
19
- each(guide.entity, (guideEntity: any) => {
20
- const entityName = guideEntity.key$
21
- ctx.log.debug({ point: 'guide-entity', note: entityName })
37
+ each(guide.entity, (guideEntity: GuideEntity, entname: string) => {
38
+ ctx.log.debug({ point: 'guide-entity', note: entname })
39
+
40
+ const paths$ = resolvePathList(guideEntity, ctx.def)
41
+ const relations = buildRelations(guideEntity, paths$)
22
42
 
23
- const entityModel: any = apimodel.main.api.entity[entityName] = {
43
+ const modelent: ModelEntity = {
44
+ name: entname,
24
45
  op: {},
25
- field: {},
26
- cmd: {},
46
+ fields: [],
27
47
  id: {
28
48
  name: 'id',
29
49
  field: 'id',
30
50
  },
31
- ancestors: []
51
+ relations,
32
52
  }
33
53
 
34
- fixName(entityModel, guideEntity.key$)
35
-
36
- let ancestors: string[] = []
37
- let ancestorsDone = false
38
-
39
- each(guideEntity.path, (guidePath: any, pathStr: string) => {
40
- const path = guidePath.key$
41
- const pathdef = def.paths[path]
42
-
43
- if (null == pathdef) {
44
- throw new Error('path not found in OpenAPI: ' + path +
45
- ' (entity: ' + guideEntity.name + ')')
46
- }
47
-
48
- // TODO: is this needed?
49
- guidePath.parts$ = path.split('/')
50
- guidePath.params$ = guidePath.parts$
51
- .filter((p: string) => p.startsWith('{'))
52
- .map((p: string) => p.substring(1, p.length - 1))
53
-
54
- if (!ancestorsDone) {
55
- // Find all path sections matching /foo/{..param..} and build ancestors array
56
- const paramRegex = /\/([a-zA-Z0-9_-]+)\/\{[a-zA-Z0-9_-]+\}/g
57
- let m
58
- while ((m = paramRegex.exec(pathStr)) !== null) {
59
- // Skip if this is the last section (the entity itself)
60
- const remainingPath = pathStr.substring(m.index + m[0].length)
61
- if (remainingPath.length > 0) {
62
- const ancestorName = depluralize(snakify(m[1]))
63
- ancestors.push(ancestorName)
64
- }
65
- }
66
-
67
- ancestorsDone = true
68
- }
69
- })
70
-
71
- entityModel.ancestors = ancestors
54
+ kit.entity[entname] = modelent
72
55
 
73
56
  msg += guideEntity.name + ' '
74
57
  })
@@ -77,6 +60,72 @@ const entityTransform: Transform = async function(
77
60
  }
78
61
 
79
62
 
63
+
64
+ function resolvePathList(guideEntity: GuideEntity, def: { paths: Record<string, any> }) {
65
+ const paths$: PathDesc[] = []
66
+
67
+ each(guideEntity.path, (guidePath: GuidePath, orig: string) => {
68
+ const parts = orig.split('/').filter(p => '' != p)
69
+ const rename = guidePath.rename ?? {}
70
+
71
+ each(rename.param, (param: any) => {
72
+ const pI = parts.indexOf('{' + param.key$ + '}')
73
+ parts[pI] = '{' + param.val$ + '}'
74
+ })
75
+
76
+ const pathdesc: PathDesc = {
77
+ orig,
78
+ parts,
79
+ rename,
80
+ method: '', // operation collectOps will copy and assign per op
81
+ op: guidePath.op,
82
+ def: def.paths[orig],
83
+ }
84
+
85
+ paths$.push(pathdesc)
86
+ })
87
+
88
+ guideEntity.paths$ = paths$
89
+
90
+ return paths$
91
+ }
92
+
93
+
94
+
95
+ function buildRelations(guideEntity: any, paths$: PathDesc[]) {
96
+ let ancestors: any[] = paths$
97
+ .map(pli => pli.parts
98
+ .map((p, i) =>
99
+ (pli.parts[i + 1]?.[0] === '{' && pli.parts[i + 1] !== '{id}') ? p : null)
100
+ .filter(p => null != p))
101
+ .filter(n => 0 < n.length)
102
+ .sort((a, b) => a.length - b.length)
103
+
104
+ // remove suffixes
105
+ ancestors = ancestors
106
+ .reduce((a, n, j) =>
107
+ ((0 < (ancestors.slice(j + 1).filter(p => suffix(p, n))).length
108
+ ? null : a.push(n)), a), [])
109
+
110
+ const relations = {
111
+ ancestors
112
+ }
113
+
114
+ guideEntity.relations$ = relations
115
+
116
+ return relations
117
+ }
118
+
119
+
120
+ // True if array c is a suffix of array p,
121
+ function suffix(p: string[], c: string[]): boolean {
122
+ return c.reduce((b, _, i) => (b && c[c.length - 1 - i] === p[p.length - 1 - i]), true)
123
+ }
124
+
125
+
126
+
80
127
  export {
81
- entityTransform
128
+ resolvePathList,
129
+ buildRelations,
130
+ entityTransform,
82
131
  }
@@ -2,120 +2,199 @@
2
2
 
3
3
  import { each, getx } from 'jostraca'
4
4
 
5
+ import { getelem } from '@voxgig/struct'
6
+
5
7
  import type { TransformResult, Transform } from '../transform'
6
8
 
7
9
  import { fixName } from '../transform'
8
10
 
11
+ import { formatJSONIC, validator, canonize } from '../utility'
12
+
13
+ import { KIT } from '../types'
14
+
15
+ import type { KitModel } from '../types'
16
+
17
+ import type {
18
+ PathDef,
19
+ ParameterDef,
20
+ MethodDef,
21
+ SchemaDef,
22
+ } from '../def'
23
+
24
+ import type {
25
+ GuideEntity,
26
+ } from './top'
9
27
 
28
+ import type {
29
+ GuideOp,
30
+ PathDesc,
31
+ } from '../desc'
10
32
 
11
- const fieldTransform: Transform = async function(
33
+ import type {
34
+ OpName,
35
+ ModelOpMap,
36
+ ModelOp,
37
+ ModelEntity,
38
+ ModelAlt,
39
+ ModelArg,
40
+ ModelField,
41
+ } from '../model'
42
+
43
+
44
+
45
+ const fieldTransform = async function(
12
46
  ctx: any,
13
47
  ): Promise<TransformResult> {
14
- const { apimodel, model, def, guide } = ctx
48
+ const { apimodel, def } = ctx
49
+ const kit: KitModel = apimodel.main[KIT]
50
+
51
+ let msg = 'field '
52
+
53
+ const opFieldPrecedence: OpName[] = ['load', 'create', 'update', 'patch', 'list']
15
54
 
16
- let msg = 'fields: '
55
+ each(kit.entity, (ment: ModelEntity, entname: string) => {
56
+ const fielddefs: SchemaDef[] = []
17
57
 
18
- each(guide.entity, (guideEntity: any) => {
19
- const entityName = guideEntity.key$
20
- const entityModel = apimodel.main.api.entity[entityName]
58
+ const fields = ment.fields
59
+ const seen: any = {}
21
60
 
22
- let fieldCount = 0
23
- each(guideEntity.path, (guidePath: any) => {
24
- const path = guidePath.key$
25
- const pathdef = def.paths[path]
61
+ for (let opname of opFieldPrecedence) {
62
+ const mop = ment.op[opname]
63
+ if (mop) {
64
+ const malts = mop.alts
26
65
 
27
- each(guidePath.op, (op: any) => {
28
- const opname = op.key$
66
+ for (let malt of malts) {
67
+ const opfields = resolveOpFields(ment, mop, malt, def)
29
68
 
30
- if ('load' === opname) {
31
- fieldCount = fieldbuild(entityModel, pathdef, op, guidePath, guideEntity, model)
69
+ for (let opfield of opfields) {
70
+ if (!seen[opfield.name]) {
71
+ fields.push(opfield)
72
+ seen[opfield.name] = opfield
73
+ }
74
+ else {
75
+ mergeField(ment, mop, malt, def, seen[opfield.name], opfield)
76
+ }
77
+ }
32
78
  }
79
+ }
80
+ }
33
81
 
34
- })
82
+ fields.sort((a: ModelField, b: ModelField) => {
83
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0
35
84
  })
36
85
 
37
- msg += guideEntity.name + '=' + fieldCount + ' '
86
+ msg += ment.name + ' '
38
87
  })
39
88
 
40
89
  return { ok: true, msg }
41
90
  }
42
91
 
43
92
 
44
- function fieldbuild(
45
- entityModel: any, pathdef: any, op: any, path: any, entity: any, model: any
46
- ) {
47
- let fieldCount = 0
48
- let fieldSets = getx(pathdef.get, 'responses 200 content "application/json" schema')
49
93
 
50
- if (fieldSets) {
51
- if (Array.isArray(fieldSets.allOf)) {
52
- fieldSets = fieldSets.allOf
53
- }
54
- else if (fieldSets.properties) {
55
- fieldSets = [fieldSets]
94
+ function resolveOpFields(
95
+ ment: ModelEntity,
96
+ mop: ModelOp,
97
+ malt: ModelAlt,
98
+ def: any
99
+ ): ModelField[] {
100
+ const mfields: ModelField[] = []
101
+ const fielddefs = findFieldDefs(ment, mop, malt, def)
102
+
103
+ for (let fielddef of fielddefs) {
104
+ const fieldname = (fielddef as any).key$ as string
105
+ const mfield: ModelField = {
106
+ name: canonize(fieldname),
107
+ type: validator(fielddef.type),
108
+ req: !!fielddef.required,
109
+ op: {},
56
110
  }
111
+ mfields.push(mfield)
57
112
  }
58
113
 
59
- each(fieldSets, (fieldSet: any) => {
60
- each(fieldSet.properties, (property: any) => {
61
- const field =
62
- (entityModel.field[property.key$] = entityModel.field[property.key$] || {})
114
+ return mfields
115
+ }
63
116
 
64
- field.name = property.key$
65
- fixName(field, field.name)
66
117
 
67
- // field.type = property.type
68
- resolveFieldType(entityModel, field, property)
69
- fixName(field, field.type, 'type')
118
+ function findFieldDefs(
119
+ ment: ModelEntity,
120
+ mop: ModelOp,
121
+ malt: ModelAlt,
122
+ def: any
123
+ ): SchemaDef[] {
124
+ const fielddefs: SchemaDef[] = []
125
+ const pathdef = def.paths[malt.orig]
70
126
 
71
- field.short = property.description
127
+ const method = malt.method.toLowerCase()
128
+ const opdef: any = pathdef[method]
72
129
 
73
- fieldCount++
74
- })
75
- })
130
+ if (opdef) {
131
+ const responses = opdef.responses
132
+ const requestBody = opdef.requestBody
76
133
 
77
- // Guess id field name using GET path param
78
- if ('load' === op.key$) {
79
- const getdef = pathdef.get
80
- const getparams = getdef.parameters || []
81
- if (1 === getparams.length) {
82
- if (entityModel.op.load.path.match(RegExp('\\{' + getdef.parameters[0].name + '\\}$'))) {
83
- entityModel.id.field = getdef.parameters[0].name
134
+ let fieldSets
135
+
136
+ if (responses) {
137
+ fieldSets = getx(responses, '200 content "application/json" schema') ??
138
+ getx(responses, '200 schema')
139
+ if ('get' === method && 'list' == mop.name) {
140
+ fieldSets = getx(responses, '201 content "application/json" schema items') ??
141
+ getx(responses, '201 schema items')
142
+ }
143
+ else if ('put' === method && null == fieldSets) {
144
+ fieldSets = getx(responses, '201 content "application/json" schema') ??
145
+ getx(responses, '201 schema')
146
+ }
147
+ }
148
+
149
+ if (requestBody) {
150
+ fieldSets = [
151
+ fieldSets,
152
+ getx(requestBody, 'content "application/json" schema') ??
153
+ getx(requestBody, 'schema')
154
+ ]
155
+ }
156
+
157
+
158
+ if (fieldSets) {
159
+ if (Array.isArray(fieldSets.allOf)) {
160
+ fieldSets = fieldSets.allOf
161
+ }
162
+ else if (fieldSets.properties) {
163
+ fieldSets = [fieldSets]
84
164
  }
85
165
  }
166
+
167
+ each(fieldSets, (fieldSet: any) => {
168
+ each(fieldSet?.properties, (property: any) => {
169
+ fielddefs.push(property)
170
+ })
171
+ })
86
172
  }
87
173
 
88
- return fieldCount
174
+ return fielddefs
89
175
  }
90
176
 
91
177
 
92
- // Resovles a heuristic "primary" type which subsumes the more detailed type.
93
- // The primary type is only: string, number, boolean, null, object, array
94
- function resolveFieldType(entity: any, field: any, property: any) {
95
- const ptt = typeof property.type
178
+ function mergeField(
179
+ ment: ModelEntity,
180
+ mop: ModelOp,
181
+ malt: ModelAlt,
182
+ def: any,
183
+ exisingField: ModelField,
184
+ newField: ModelField
185
+ ) {
96
186
 
97
- if ('string' === ptt) {
98
- field.type = property.type
99
- }
100
- else if (Array.isArray(property.type)) {
101
- field.type =
102
- (property.type.filter((t: string) => 'null' != t).sort()[0]) ||
103
- property.type[0] ||
104
- 'string'
105
- field.typelist = property.type
106
- }
107
- else if ('undefined' === ptt && null != property.enum) {
108
- field.type = 'string'
109
- field.enum = property.enum
110
- }
111
- else {
112
- throw new Error(
113
- `APIDEF: Unsupported property type: ${property.type} (${entity.name}.${field.name})`)
187
+ if (newField.req !== exisingField.req) {
188
+ exisingField.op[mop.name] = {
189
+ req: newField.req,
190
+ type: newField.type,
191
+ }
114
192
  }
193
+
194
+ return exisingField
115
195
  }
116
196
 
117
197
 
118
198
  export {
119
- fieldTransform
199
+ fieldTransform,
120
200
  }
121
-