@voxgig/apidef 2.0.0 → 2.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.
@@ -24,7 +24,7 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
24
24
  const apiEntity = apimodel.main.api.entity[entity.name]
25
25
 
26
26
  const flow: any = {
27
- name: 'Basic' + apiEntity.Name
27
+ name: 'Basic' + apiEntity.Name + 'Flow'
28
28
  }
29
29
 
30
30
  const refs = [
@@ -36,18 +36,21 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
36
36
  const idmap = refs.reduce((a: any, ref) => (a[ref] = ref.toUpperCase(), a), {})
37
37
 
38
38
  flow.model = ({
39
- name: flow.Name,
39
+ name: flow.name,
40
+ active: true,
40
41
  param: {
41
- [`${model.NAME}_TEST_${apiEntity.NAME}_ENTID`]: idmap
42
+ [`${model.NAME}_TEST_${apiEntity.NAME}_ENTID`]: idmap,
43
+ [`${model.NAME}_TEST_LIVE`]: "FALSE",
44
+ [`${model.NAME}_TEST_EXPLAIN`]: "FALSE",
42
45
  },
43
- test: { entity: { [apiEntity.Name]: {} } },
46
+ test: { entity: { [apiEntity.name]: {} } },
44
47
  step: []
45
48
  } as any)
46
49
 
47
50
  names(flow, flow.name)
48
51
 
49
52
 
50
- const data = flow.model.test.entity[apiEntity.Name]
53
+ const data = flow.model.test.entity[apiEntity.name]
51
54
 
52
55
  refs.map((ref, i) => {
53
56
  const id = idmap[ref]
@@ -73,7 +76,34 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
73
76
  let num = 0
74
77
  let name
75
78
 
79
+ const am: any = {}
80
+
76
81
  if (apiEntity.op.load) {
82
+
83
+ // Get additional required match properties
84
+ each(apiEntity.op.load.param, (param: any) => {
85
+ if (param.required) {
86
+ let ancestorName = param.name
87
+ let ancestorEntity = apimodel.main.api.entity[ancestorName]
88
+
89
+ if (null == ancestorEntity) {
90
+ ancestorName = ancestorName.replace('_id', '')
91
+ ancestorEntity = apimodel.main.api.entity[ancestorName]
92
+ }
93
+
94
+ if (ancestorEntity && ancestorName !== apiEntity.name) {
95
+ flow.model.param[`${model.NAME}_TEST_${ancestorEntity.NAME}_ENTID`] = {
96
+ [ancestorEntity.name + '01']: ancestorEntity.NAME + '01'
97
+ }
98
+ am[param.name] =
99
+ `\`dm$=p.${model.NAME}_TEST_${ancestorEntity.NAME}_ENTID.${ancestorEntity.name}01\``
100
+
101
+ data[`${apiEntity.NAME}01`][param.name] = ancestorEntity.NAME + '01'
102
+ }
103
+ }
104
+ })
105
+
106
+
77
107
  name = `load_${apiEntity.name}${num}`
78
108
  steps.push({
79
109
  name,
@@ -81,11 +111,13 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
81
111
  entity: `${apiEntity.name}`,
82
112
  action: 'load',
83
113
  match: {
84
- id: `\`dm$=p.${model.NAME}_TEST_${apiEntity.NAME}_ENTID.${apiEntity.name}01\``
114
+ id: `\`dm$=p.${model.NAME}_TEST_${apiEntity.NAME}_ENTID.${apiEntity.name}01\``,
115
+ ...am,
85
116
  },
86
117
  valid: {
87
118
  '`$OPEN`': true,
88
- id: `\`dm$=s.${name}.match.id\``
119
+ id: `\`dm$=s.${name}.match.id\``,
120
+ ...am,
89
121
  }
90
122
  })
91
123
  }
@@ -105,6 +137,7 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
105
137
  valid: {
106
138
  '`$OPEN`': true,
107
139
  id: `\`dm$=s.${loadref}.match.id\``,
140
+ ...am,
108
141
  ...valid
109
142
  }
110
143
  })
@@ -118,11 +151,13 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
118
151
  entity: `${apiEntity.name}`,
119
152
  action: 'load',
120
153
  match: {
121
- id: `\`dm$=p.${model.NAME}_TEST_${apiEntity.NAME}_ENTID.${apiEntity.name}01\``
154
+ id: `\`dm$=p.${model.NAME}_TEST_${apiEntity.NAME}_ENTID.${apiEntity.name}01\``,
155
+ ...am,
122
156
  },
123
157
  valid: {
124
158
  '`$OPEN`': true,
125
159
  id: `\`dm$=s.${loadref}.match.id\``,
160
+ ...am,
126
161
  ...valid
127
162
  }
128
163
  })
@@ -136,7 +171,7 @@ function resolveBasicEntityFlow(ctx: any, entity: any) {
136
171
 
137
172
  function makeUpdateData(name: string, apiEntity: any, flow: any, id: string) {
138
173
  const ud: any = {}
139
- const data = flow.model.test.entity[apiEntity.Name]
174
+ const data = flow.model.test.entity[apiEntity.name]
140
175
 
141
176
  const dataFields = each(apiEntity.field).filter(f => 'id' !== f.name && !f.name.includes('_id'))
142
177
  const stringFields = each(dataFields).filter(f => 'string' === f.type)
@@ -21,6 +21,7 @@ async function makeFlowBuilder(ctx: any) {
21
21
  throw new Error('Unknown guide strategy: ' + ctx.opts.strategy)
22
22
  }
23
23
 
24
+
24
25
  return function flowBuilder() {
25
26
 
26
27
  Folder({ name: 'flow' }, () => {
@@ -32,13 +33,13 @@ async function makeFlowBuilder(ctx: any) {
32
33
  let flowfile =
33
34
  Path.join(ctx.opts.folder, 'flow',
34
35
  (null == ctx.opts.outprefix ? '' : ctx.opts.outprefix) +
35
- flow.Name + 'Flow.jsonic')
36
+ flow.Name + '.jsonic')
36
37
 
37
38
  let flowModelSrc = formatJsonSrc(JSON.stringify(flow.model, null, 2))
38
39
 
39
- let flowsrc = `# ${flow.Name}Flow
40
+ let flowsrc = `# ${flow.Name}
40
41
 
41
- main: sdk: flow: ${flow.Name}Flow:
42
+ main: sdk: flow: ${flow.Name}:
42
43
  ` + flowModelSrc
43
44
 
44
45
  barrel.push(`@"${Path.basename(flowfile)}"`)
@@ -3,6 +3,21 @@
3
3
  import { each, snakify, names } from 'jostraca'
4
4
 
5
5
 
6
+ import { depluralize } from '../utility'
7
+
8
+
9
+ type EntityDesc = {
10
+ name: string
11
+ origname: string
12
+ plural: string
13
+ path: Record<string, EntityPathDesc>
14
+ alias: Record<string, string>
15
+ }
16
+
17
+
18
+ type EntityPathDesc = {
19
+ op: Record<string, any>
20
+ }
6
21
 
7
22
  async function heuristic01(ctx: any): Promise<Record<string, any>> {
8
23
  let guide = ctx.model.main.api.guide
@@ -35,29 +50,14 @@ function resolveEntityDescs(ctx: any) {
35
50
  const entityDescs: Record<string, any> = {}
36
51
  const paths = ctx.def.paths
37
52
 
38
- each(paths, (pathdef, pathname) => {
39
- // look for rightmmost /entname/{entid}
40
- const m = pathname.match(/\/([a-zA-Z0-1_-]+)\/\{([a-zA-Z0-1_-]+)\}$/)
41
- if (m) {
42
- let origentname = snakify(m[1])
43
- let entname = depluralize(origentname)
44
-
45
- let entdesc = (entityDescs[entname] = entityDescs[entname] || { name: entname })
46
- entdesc.plural = origentname
47
-
48
- names(entdesc, entname)
53
+ // Analyze paths ending in .../foo/{foo}
54
+ each(paths, (pathdef, pathstr) => {
49
55
 
50
- entdesc.alias = entdesc.alias || {}
51
-
52
- if ('id' != m[2]) {
53
- entdesc.alias.id = m[2]
54
- entdesc.alias[m[2]] = 'id'
55
- }
56
-
57
- entdesc.path = (entdesc.path || {})
56
+ // Look for rightmmost /entname/{entid}.
57
+ const m = pathstr.match(/\/([a-zA-Z0-1_-]+)\/\{([a-zA-Z0-1_-]+)\}$/)
58
+ if (m) {
59
+ const entdesc = resolveEntity(entityDescs, pathstr, m[1], m[2])
58
60
 
59
- const op: Record<string, any> = {}
60
- entdesc.path[pathname] = { op }
61
61
 
62
62
  each(pathdef, (mdef, method) => {
63
63
  const opname = METHOD_IDOP[method]
@@ -71,27 +71,27 @@ function resolveEntityDescs(ctx: any) {
71
71
  const resokdef = mdef.responses[200] || mdef.responses[201]
72
72
  const resbody = resokdef?.content?.['application/json']?.schema
73
73
  if (resbody) {
74
- if (resbody[origentname]) {
75
- // TODO: use quotes when @voxgig/struct updated to support them
76
- // transform.resform = '`body."'+origentname+'"`'
77
- transform.resform = '`body.' + origentname + '`'
74
+ if (resbody[entdesc.origname]) {
75
+ transform.resform = '`body.' + entdesc.origname + '`'
78
76
  }
79
- else if (resbody[entname]) {
80
- transform.resform = '`body.' + entname + '`'
77
+ else if (resbody[entdesc.name]) {
78
+ transform.resform = '`body.' + entdesc.name + '`'
81
79
  }
82
80
  }
83
81
 
84
82
  const reqdef = mdef.requestBody?.content?.['application/json']?.schema?.properties
85
83
  if (reqdef) {
86
- if (reqdef[origentname]) {
87
- transform.reqform = { [origentname]: '`reqdata`' }
84
+ if (reqdef[entdesc.origname]) {
85
+ transform.reqform = { [entdesc.origname]: '`reqdata`' }
88
86
  }
89
- else if (reqdef[entname]) {
90
- transform.reqform = { [entname]: '`reqdata`' }
87
+ else if (reqdef[entdesc.origname]) {
88
+ transform.reqform = { [entdesc.origname]: '`reqdata`' }
91
89
  }
92
90
 
93
91
  }
94
92
 
93
+ const op = entdesc.path[pathstr].op
94
+
95
95
  op[opname] = {
96
96
  // TODO: in actual guide, remove "standard" method ops since redundant
97
97
  method,
@@ -104,41 +104,33 @@ function resolveEntityDescs(ctx: any) {
104
104
  }
105
105
  })
106
106
 
107
+ // Analyze paths ending in .../foo
108
+ each(paths, (pathdef, pathstr) => {
107
109
 
108
- each(paths, (pathdef, pathname) => {
109
- // look for rightmmost /entname/{entid}
110
- const m = pathname.match(/\/([a-zA-Z0-1_-]+)$/)
110
+ // Look for rightmmost /entname.
111
+ const m = pathstr.match(/\/([a-zA-Z0-1_-]+)$/)
111
112
  if (m) {
112
- let origentname = snakify(m[1])
113
- let entname = depluralize(origentname)
114
-
115
- let entdesc = entityDescs[entname]
116
-
117
- if (entdesc) {
118
- entdesc.path = (entdesc.path || {})
119
-
120
- if (pathdef.get) {
121
- const op: Record<string, any> = { list: { method: 'get' } }
122
- entdesc.path[pathname] = { op }
123
-
124
- const transform: Record<string, any> = {}
125
- const mdef = pathdef.get
126
- const resokdef = mdef.responses[200] || mdef.responses[201]
127
- const resbody = resokdef?.content?.['application/json']?.schema
128
- if (resbody) {
129
- if (resbody[origentname]) {
130
- // TODO: use quotes when @voxgig/struct updated to support them
131
- // transform.resform = '`body."'+origentname+'"`'
132
- transform.resform = '`body.' + origentname + '`'
133
- }
134
- else if (resbody[entname]) {
135
- transform.resform = '`body.' + entname + '`'
136
- }
137
- }
113
+ const entdesc = resolveEntity(entityDescs, pathstr, m[1])
138
114
 
139
- if (0 < Object.entries(transform).length) {
140
- op.transform = transform
115
+ if (pathdef.get) {
116
+ const op: Record<string, any> = { list: { method: 'get' } }
117
+ entdesc.path[pathstr] = { op }
118
+
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 + '`'
141
126
  }
127
+ else if (resbody[entdesc.name]) {
128
+ transform.resform = '`body.' + entdesc.name + '`'
129
+ }
130
+ }
131
+
132
+ if (0 < Object.entries(transform).length) {
133
+ op.transform = transform
142
134
  }
143
135
  }
144
136
  }
@@ -148,75 +140,36 @@ function resolveEntityDescs(ctx: any) {
148
140
  }
149
141
 
150
142
 
151
- function depluralize(word: string): string {
152
- if (!word || word.length === 0) {
153
- return word
154
- }
155
-
156
- // Common irregular plurals
157
- const irregulars: Record<string, string> = {
158
- 'children': 'child',
159
- 'men': 'man',
160
- 'women': 'woman',
161
- 'teeth': 'tooth',
162
- 'feet': 'foot',
163
- 'geese': 'goose',
164
- 'mice': 'mouse',
165
- 'people': 'person',
166
- 'data': 'datum',
167
- 'criteria': 'criterion',
168
- 'phenomena': 'phenomenon',
169
- 'indices': 'index',
170
- 'matrices': 'matrix',
171
- 'vertices': 'vertex',
172
- 'analyses': 'analysis',
173
- 'axes': 'axis',
174
- 'crises': 'crisis',
175
- 'diagnoses': 'diagnosis',
176
- 'oases': 'oasis',
177
- 'theses': 'thesis',
178
- 'appendices': 'appendix'
179
- }
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)
180
152
 
181
- if (irregulars[word]) {
182
- return irregulars[word]
183
- }
153
+ let entdesc = (entityDescs[entname] = entityDescs[entname] || { name: entname })
154
+ entdesc.plural = origentname
155
+ entdesc.origname = origentname
184
156
 
185
- // Rules for regular plurals (applied in order)
157
+ names(entdesc, entname)
186
158
 
187
- // -ies -> -y (cities -> city)
188
- if (word.endsWith('ies') && word.length > 3) {
189
- return word.slice(0, -3) + 'y'
190
- }
159
+ entdesc.alias = entdesc.alias || {}
191
160
 
192
- // -ves -> -f or -fe (wolves -> wolf, knives -> knife)
193
- if (word.endsWith('ves')) {
194
- const stem = word.slice(0, -3)
195
- // Check if it should be -fe (like knife, wife, life)
196
- if (['kni', 'wi', 'li'].includes(stem)) {
197
- return stem + 'fe'
161
+ if (null != pathParam) {
162
+ const pathParamCanon = snakify(pathParam)
163
+ if ('id' != pathParamCanon) {
164
+ entdesc.alias.id = pathParamCanon
165
+ entdesc.alias[pathParamCanon] = 'id'
198
166
  }
199
- return stem + 'f'
200
- }
201
-
202
- // -oes -> -o (potatoes -> potato)
203
- if (word.endsWith('oes')) {
204
- return word.slice(0, -2)
205
- }
206
-
207
- // -ses, -xes, -zes, -shes, -ches -> remove -es (boxes -> box)
208
- if (word.endsWith('ses') || word.endsWith('xes') || word.endsWith('zes') ||
209
- word.endsWith('shes') || word.endsWith('ches')) {
210
- return word.slice(0, -2)
211
167
  }
212
168
 
213
- // -s -> remove -s (cats -> cat)
214
- if (word.endsWith('s') && !word.endsWith('ss')) {
215
- return word.slice(0, -1)
216
- }
169
+ entdesc.path = (entdesc.path || {})
170
+ entdesc.path[pathStr] = { op: {} }
217
171
 
218
- // If none of the rules apply, return as is
219
- return word
172
+ return entdesc
220
173
  }
221
174
 
222
175
 
package/src/guide.ts CHANGED
@@ -8,8 +8,6 @@ import { heuristic01 } from './guide/heuristic01'
8
8
 
9
9
 
10
10
  async function resolveGuide(ctx: any) {
11
- // console.log('GUIDE CTX', ctx)
12
-
13
11
  let guide: Record<string, any> = ctx.model.main.api.guide
14
12
 
15
13
  if ('heuristic01' === ctx.opts.strategy) {
@@ -36,10 +34,10 @@ async function resolveGuide(ctx: any) {
36
34
 
37
35
 
38
36
  guideBlocks.push(...each(guide.entity, (entity, entityname) => {
39
- guideBlocks.push(`\nentity: ${entityname}: path: {`)
37
+ guideBlocks.push(`\nentity: ${entityname}: {`)
40
38
 
41
39
  each(entity.path, (path, pathname) => {
42
- guideBlocks.push(` '${pathname}': op: {`)
40
+ guideBlocks.push(` path: '${pathname}': op: {`)
43
41
 
44
42
  each(path.op, (op, opname) => {
45
43
  guideBlocks.push(` '${opname}': method: ${op.method}`)
@@ -59,8 +57,6 @@ async function resolveGuide(ctx: any) {
59
57
 
60
58
  const guideSrc = guideBlocks.join('\n')
61
59
 
62
- // console.log(guideSrc)
63
-
64
60
  return () => {
65
61
  File({ name: Path.basename(guideFile) }, () => Content(guideSrc))
66
62
 
@@ -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