@voxgig/apidef 1.9.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.
Files changed (73) hide show
  1. package/dist/apidef.d.ts +3 -29
  2. package/dist/apidef.js +65 -186
  3. package/dist/apidef.js.map +1 -1
  4. package/dist/builder/entity/apiEntity.d.ts +3 -0
  5. package/dist/builder/entity/apiEntity.js +51 -0
  6. package/dist/builder/entity/apiEntity.js.map +1 -0
  7. package/dist/builder/entity/def.d.ts +3 -0
  8. package/dist/builder/entity/def.js +19 -0
  9. package/dist/builder/entity/def.js.map +1 -0
  10. package/dist/builder/entity.d.ts +2 -0
  11. package/dist/builder/entity.js +30 -0
  12. package/dist/builder/entity.js.map +1 -0
  13. package/dist/builder/flow/flowHeuristic01.d.ts +2 -0
  14. package/dist/builder/flow/flowHeuristic01.js +153 -0
  15. package/dist/builder/flow/flowHeuristic01.js.map +1 -0
  16. package/dist/builder/flow.d.ts +2 -0
  17. package/dist/builder/flow.js +41 -0
  18. package/dist/builder/flow.js.map +1 -0
  19. package/dist/guide/heuristic01.d.ts +2 -0
  20. package/dist/guide/heuristic01.js +119 -0
  21. package/dist/guide/heuristic01.js.map +1 -0
  22. package/dist/guide.d.ts +2 -0
  23. package/dist/guide.js +60 -0
  24. package/dist/guide.js.map +1 -0
  25. package/dist/parse.d.ts +1 -1
  26. package/dist/parse.js +5 -4
  27. package/dist/parse.js.map +1 -1
  28. package/dist/resolver.d.ts +2 -0
  29. package/dist/resolver.js +62 -0
  30. package/dist/resolver.js.map +1 -0
  31. package/dist/transform/entity.js +25 -4
  32. package/dist/transform/entity.js.map +1 -1
  33. package/dist/transform/field.js +4 -84
  34. package/dist/transform/field.js.map +1 -1
  35. package/dist/transform/operation.d.ts +2 -2
  36. package/dist/transform/operation.js +60 -30
  37. package/dist/transform/operation.js.map +1 -1
  38. package/dist/transform/top.d.ts +2 -2
  39. package/dist/transform/top.js +5 -4
  40. package/dist/transform/top.js.map +1 -1
  41. package/dist/transform.d.ts +1 -1
  42. package/dist/transform.js +20 -10
  43. package/dist/transform.js.map +1 -1
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/dist/types.d.ts +691 -0
  46. package/dist/types.js +39 -0
  47. package/dist/types.js.map +1 -0
  48. package/dist/utility.d.ts +5 -0
  49. package/dist/utility.js +84 -0
  50. package/dist/utility.js.map +1 -0
  51. package/model/apidef.jsonic +28 -24
  52. package/package.json +9 -7
  53. package/src/apidef.ts +94 -271
  54. package/src/builder/entity/apiEntity.ts +88 -0
  55. package/src/builder/entity/def.ts +44 -0
  56. package/src/builder/entity.ts +54 -0
  57. package/src/builder/flow/flowHeuristic01.ts +200 -0
  58. package/src/builder/flow.ts +61 -0
  59. package/src/guide/heuristic01.ts +178 -0
  60. package/src/guide.ts +87 -0
  61. package/src/parse.ts +6 -4
  62. package/src/resolver.ts +91 -0
  63. package/src/transform/entity.ts +36 -10
  64. package/src/transform/field.ts +9 -92
  65. package/src/transform/operation.ts +112 -46
  66. package/src/transform/top.ts +11 -9
  67. package/src/transform.ts +22 -11
  68. package/src/types.ts +89 -0
  69. package/src/utility.ts +161 -0
  70. package/dist/transform/manual.d.ts +0 -3
  71. package/dist/transform/manual.js +0 -12
  72. package/dist/transform/manual.js.map +0 -1
  73. package/src/transform/manual.ts +0 -29
@@ -0,0 +1,88 @@
1
+ /* Copyright (c) 2025 Voxgig, MIT License */
2
+
3
+ import Path from 'node:path'
4
+
5
+ import { each, File, Folder, Content } from 'jostraca'
6
+
7
+
8
+ import type {
9
+ ApiDefOptions,
10
+ } from '../../types'
11
+
12
+ import {
13
+ formatJsonSrc,
14
+ } from '../../utility'
15
+
16
+
17
+
18
+ function resolveApiEntity(
19
+ apimodel: any,
20
+ opts: ApiDefOptions,
21
+ ) {
22
+ const barrel = [
23
+ '# Entity Models\n'
24
+ ]
25
+
26
+ const entityFiles: { name: string, src: string }[] = []
27
+
28
+ each(apimodel.main.api.entity, ((entity: any, entityName: string) => {
29
+ const entityFile = (null == opts.outprefix ? '' : opts.outprefix) + entityName + '.jsonic'
30
+
31
+ const entityJSON =
32
+ JSON.stringify(entity, null, 2)
33
+
34
+ const fieldAliasesSrc = fieldAliases(entity)
35
+
36
+ const entitySrc =
37
+ `# Entity: ${entity.name}\n\n` +
38
+ `main: api: entity: ${entity.name}: {\n\n` +
39
+ ` alias: field: ${fieldAliasesSrc}\n` +
40
+ formatJsonSrc(entityJSON.substring(1, entityJSON.length - 1)) +
41
+ '\n\n}\n'
42
+
43
+ entityFiles.push({ name: entityFile, src: entitySrc })
44
+
45
+ barrel.push(`@"${Path.basename(entityFile)}"`)
46
+ }))
47
+
48
+ const indexFile = (null == opts.outprefix ? '' : opts.outprefix) + 'api-entity-index.jsonic'
49
+
50
+ return function apiEntityBuilder() {
51
+ Folder({ name: 'api' }, () => {
52
+ each(entityFiles, (entityFile) => {
53
+ File({ name: entityFile.name }, () => Content(entityFile.src))
54
+ })
55
+
56
+ File({ name: indexFile }, () => Content(barrel.join('\n')))
57
+ })
58
+ }
59
+
60
+ }
61
+
62
+ function fieldAliases(entity: any) {
63
+ // HEURISTIC: id may be name_id or nameId
64
+ const fieldAliases =
65
+ each(entity.op, (op: any) =>
66
+ each(op.param))
67
+ .flat()
68
+ .reduce((a: any, p: any) =>
69
+
70
+ (entity.field[p.keys] ? null :
71
+ (p.key$.toLowerCase().includes(entity.name) ?
72
+ (a[p.key$] = 'id', a.id = p.key$) :
73
+ null)
74
+
75
+ , a), {})
76
+
77
+ const fieldAliasesSrc =
78
+ JSON.stringify(fieldAliases, null, 2)
79
+ .replace(/\n/g, '\n ')
80
+
81
+ return fieldAliasesSrc
82
+ }
83
+
84
+
85
+
86
+ export {
87
+ resolveApiEntity
88
+ }
@@ -0,0 +1,44 @@
1
+ /* Copyright (c) 2025 Voxgig, MIT License */
2
+
3
+ import Path from 'node:path'
4
+
5
+
6
+ import type {
7
+ ApiDefOptions,
8
+ ApiModel,
9
+ } from '../../types'
10
+
11
+
12
+ import {
13
+ File,
14
+ Folder,
15
+ Content
16
+ } from 'jostraca'
17
+
18
+
19
+ function resolveDef(
20
+ apimodel: ApiModel,
21
+ opts: ApiDefOptions,
22
+ ) {
23
+ const defFile =
24
+ (null == opts.outprefix ? '' : opts.outprefix) + 'api-def.jsonic'
25
+
26
+ const modelDef = { main: { def: apimodel.main.def } }
27
+ let modelDefSrc = JSON.stringify(modelDef, null, 2)
28
+
29
+ modelDefSrc =
30
+ '# API Definition\n\n' +
31
+ modelDefSrc.substring(1, modelDefSrc.length - 1).replace(/\n /g, '\n')
32
+
33
+ return function defBuilder() {
34
+ Folder({ name: 'api' }, () => {
35
+ File({ name: defFile }, () => Content(modelDefSrc))
36
+ })
37
+ }
38
+
39
+ }
40
+
41
+
42
+ export {
43
+ resolveDef
44
+ }
@@ -0,0 +1,54 @@
1
+ /* Copyright (c) 2025 Voxgig, MIT License */
2
+
3
+ import Path from 'node:path'
4
+
5
+ // import { each } from 'jostraca'
6
+
7
+
8
+ // import type {
9
+ // ApiDefOptions,
10
+ // Log,
11
+ // FsUtil,
12
+ // } from '../types'
13
+
14
+
15
+ // import {
16
+ // writeChanged
17
+ // } from '../utility'
18
+
19
+
20
+ import {
21
+ resolveApiEntity
22
+ } from './entity/apiEntity'
23
+
24
+ import {
25
+ resolveDef
26
+ } from './entity/def'
27
+
28
+ // import {
29
+ // resolveSdkEntity
30
+ // } from './entity/sdkEntity'
31
+
32
+
33
+
34
+ async function makeEntityBuilder(ctx: any) {
35
+ const { apimodel, opts } = ctx
36
+
37
+ const apiEntityBuilder = resolveApiEntity(apimodel, opts)
38
+ const defBuilder = resolveDef(apimodel, opts)
39
+ // const sdkEntityBuilder = resolveSdkEntity(apimodel, opts)
40
+
41
+ return function entityBuilder() {
42
+ apiEntityBuilder()
43
+ defBuilder()
44
+ // sdkEntityBuilder()
45
+ }
46
+ }
47
+
48
+
49
+
50
+
51
+
52
+ export {
53
+ makeEntityBuilder
54
+ }
@@ -0,0 +1,200 @@
1
+
2
+ import { size } from '@voxgig/struct'
3
+
4
+ import { each, names } from 'jostraca'
5
+
6
+
7
+ async function flowHeuristic01(ctx: any): Promise<any[]> {
8
+ let entity = ctx.model.main.api.guide.entity
9
+
10
+ const flows: any[] = []
11
+
12
+ each(entity, (entity: any) => {
13
+ flows.push(resolveBasicEntityFlow(ctx, entity))
14
+ })
15
+
16
+ return flows
17
+ }
18
+
19
+
20
+
21
+
22
+ function resolveBasicEntityFlow(ctx: any, entity: any) {
23
+ const { apimodel, model } = ctx
24
+ const apiEntity = apimodel.main.api.entity[entity.name]
25
+
26
+ const flow: any = {
27
+ name: 'Basic' + apiEntity.Name + 'Flow'
28
+ }
29
+
30
+ const refs = [
31
+ `${apiEntity.name}01`,
32
+ `${apiEntity.name}02`,
33
+ `${apiEntity.name}03`,
34
+ ]
35
+
36
+ const idmap = refs.reduce((a: any, ref) => (a[ref] = ref.toUpperCase(), a), {})
37
+
38
+ flow.model = ({
39
+ name: flow.name,
40
+ active: true,
41
+ param: {
42
+ [`${model.NAME}_TEST_${apiEntity.NAME}_ENTID`]: idmap,
43
+ [`${model.NAME}_TEST_LIVE`]: "FALSE",
44
+ [`${model.NAME}_TEST_EXPLAIN`]: "FALSE",
45
+ },
46
+ test: { entity: { [apiEntity.name]: {} } },
47
+ step: []
48
+ } as any)
49
+
50
+ names(flow, flow.name)
51
+
52
+
53
+ const data = flow.model.test.entity[apiEntity.name]
54
+
55
+ refs.map((ref, i) => {
56
+ const id = idmap[ref]
57
+ const ent: any = data[id] = {}
58
+
59
+ let num = (i * size(apiEntity.field) * 10)
60
+ each(apiEntity.field, (field) => {
61
+ ent[field.name] =
62
+ 'number' === field.type ? num :
63
+ 'boolean' === field.type ? 0 === num % 2 :
64
+ 'object' === field.type ? {} :
65
+ 'array' === field.type ? [] :
66
+ 's' + (num.toString(16))
67
+ num++
68
+ })
69
+
70
+ ent.id = id
71
+ })
72
+
73
+
74
+ const steps = flow.model.step
75
+
76
+ let num = 0
77
+ let name
78
+
79
+ const am: any = {}
80
+
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
+
107
+ name = `load_${apiEntity.name}${num}`
108
+ steps.push({
109
+ name,
110
+ kind: 'entity',
111
+ entity: `${apiEntity.name}`,
112
+ action: 'load',
113
+ match: {
114
+ id: `\`dm$=p.${model.NAME}_TEST_${apiEntity.NAME}_ENTID.${apiEntity.name}01\``,
115
+ ...am,
116
+ },
117
+ valid: {
118
+ '`$OPEN`': true,
119
+ id: `\`dm$=s.${name}.match.id\``,
120
+ ...am,
121
+ }
122
+ })
123
+ }
124
+
125
+ if (apiEntity.op.update && apiEntity.op.load) {
126
+ num++
127
+ name = `update_${apiEntity.name}${num}`
128
+ const id = idmap[`${apiEntity.name}01`]
129
+ const loadref = `load_${apiEntity.name}${num - 1}`
130
+ const reqdata = makeUpdateData(name, apiEntity, flow, id)
131
+ const valid = makeUpdateValid(name, apiEntity, flow, id, reqdata)
132
+ steps.push({
133
+ name,
134
+ ref: loadref,
135
+ action: 'update',
136
+ reqdata,
137
+ valid: {
138
+ '`$OPEN`': true,
139
+ id: `\`dm$=s.${loadref}.match.id\``,
140
+ ...am,
141
+ ...valid
142
+ }
143
+ })
144
+
145
+ num++
146
+ name = `load_${apiEntity.name}${num}`
147
+
148
+ steps.push({
149
+ name,
150
+ kind: 'entity',
151
+ entity: `${apiEntity.name}`,
152
+ action: 'load',
153
+ match: {
154
+ id: `\`dm$=p.${model.NAME}_TEST_${apiEntity.NAME}_ENTID.${apiEntity.name}01\``,
155
+ ...am,
156
+ },
157
+ valid: {
158
+ '`$OPEN`': true,
159
+ id: `\`dm$=s.${loadref}.match.id\``,
160
+ ...am,
161
+ ...valid
162
+ }
163
+ })
164
+
165
+ }
166
+
167
+
168
+ return flow
169
+ }
170
+
171
+
172
+ function makeUpdateData(name: string, apiEntity: any, flow: any, id: string) {
173
+ const ud: any = {}
174
+ const data = flow.model.test.entity[apiEntity.name]
175
+
176
+ const dataFields = each(apiEntity.field).filter(f => 'id' !== f.name && !f.name.includes('_id'))
177
+ const stringFields = each(dataFields).filter(f => 'string' === f.type)
178
+
179
+ if (0 < size(stringFields)) {
180
+ const f = stringFields[0]
181
+ ud[f.name] = data[id][f.name] + '-`$WHEN`'
182
+ }
183
+
184
+ return ud
185
+ }
186
+
187
+
188
+ function makeUpdateValid(name: string, apiEntity: any, flow: any, id: string, reqdata: any) {
189
+ const vd: any = {}
190
+
191
+ each(reqdata, (n) => {
192
+ vd[n.key$] = `\`dm$=s.${name}.reqdata.${n.key$}\``
193
+ })
194
+
195
+ return vd
196
+ }
197
+
198
+ export {
199
+ flowHeuristic01
200
+ }
@@ -0,0 +1,61 @@
1
+
2
+ import Path from 'node:path'
3
+
4
+ import { File, Content, Folder, each } from 'jostraca'
5
+
6
+ import {
7
+ formatJsonSrc,
8
+ } from '../utility'
9
+
10
+
11
+ import { flowHeuristic01 } from './flow/flowHeuristic01'
12
+
13
+
14
+ async function makeFlowBuilder(ctx: any) {
15
+ let flows: any[] = []
16
+
17
+ if ('heuristic01' === ctx.opts.strategy) {
18
+ flows = await flowHeuristic01(ctx)
19
+ }
20
+ else {
21
+ throw new Error('Unknown guide strategy: ' + ctx.opts.strategy)
22
+ }
23
+
24
+
25
+ return function flowBuilder() {
26
+
27
+ Folder({ name: 'flow' }, () => {
28
+ const barrel = [
29
+ '# Flows\n'
30
+ ]
31
+
32
+ each(flows, (flow: any) => {
33
+ let flowfile =
34
+ Path.join(ctx.opts.folder, 'flow',
35
+ (null == ctx.opts.outprefix ? '' : ctx.opts.outprefix) +
36
+ flow.Name + '.jsonic')
37
+
38
+ let flowModelSrc = formatJsonSrc(JSON.stringify(flow.model, null, 2))
39
+
40
+ let flowsrc = `# ${flow.Name}
41
+
42
+ main: sdk: flow: ${flow.Name}:
43
+ ` + flowModelSrc
44
+
45
+ barrel.push(`@"${Path.basename(flowfile)}"`)
46
+
47
+ File({ name: Path.basename(flowfile) }, () => Content(flowsrc))
48
+ })
49
+
50
+ File({
51
+ name: (null == ctx.opts.outprefix ? '' : ctx.opts.outprefix) + 'flow-index.jsonic'
52
+ }, () => Content(barrel.join('\n')))
53
+ })
54
+ }
55
+ }
56
+
57
+
58
+
59
+ export {
60
+ makeFlowBuilder
61
+ }
@@ -0,0 +1,178 @@
1
+
2
+
3
+ import { each, snakify, names } from 'jostraca'
4
+
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
+ }
21
+
22
+ async function heuristic01(ctx: any): Promise<Record<string, any>> {
23
+ let guide = ctx.model.main.api.guide
24
+
25
+
26
+ const entityDescs = resolveEntityDescs(ctx)
27
+
28
+ // console.log('entityDescs')
29
+ // console.dir(entityDescs, { depth: null })
30
+
31
+ guide = {
32
+ control: guide.control,
33
+ entity: entityDescs,
34
+ }
35
+
36
+ return guide
37
+ }
38
+
39
+
40
+ const METHOD_IDOP: Record<string, string> = {
41
+ get: 'load',
42
+ post: 'create',
43
+ put: 'update',
44
+ patch: 'update',
45
+ delete: 'remove',
46
+ }
47
+
48
+
49
+ function resolveEntityDescs(ctx: any) {
50
+ const entityDescs: Record<string, any> = {}
51
+ const paths = ctx.def.paths
52
+
53
+ // Analyze paths ending in .../foo/{foo}
54
+ each(paths, (pathdef, pathstr) => {
55
+
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])
60
+
61
+
62
+ each(pathdef, (mdef, method) => {
63
+ const opname = METHOD_IDOP[method]
64
+ if (null == opname) return;
65
+
66
+ const transform: Record<string, any> = {
67
+ // reqform: '`reqdata`',
68
+ // resform: '`body`',
69
+ }
70
+
71
+ const resokdef = mdef.responses[200] || mdef.responses[201]
72
+ const resbody = resokdef?.content?.['application/json']?.schema
73
+ if (resbody) {
74
+ if (resbody[entdesc.origname]) {
75
+ transform.resform = '`body.' + entdesc.origname + '`'
76
+ }
77
+ else if (resbody[entdesc.name]) {
78
+ transform.resform = '`body.' + entdesc.name + '`'
79
+ }
80
+ }
81
+
82
+ const reqdef = mdef.requestBody?.content?.['application/json']?.schema?.properties
83
+ if (reqdef) {
84
+ if (reqdef[entdesc.origname]) {
85
+ transform.reqform = { [entdesc.origname]: '`reqdata`' }
86
+ }
87
+ else if (reqdef[entdesc.origname]) {
88
+ transform.reqform = { [entdesc.origname]: '`reqdata`' }
89
+ }
90
+
91
+ }
92
+
93
+ const op = entdesc.path[pathstr].op
94
+
95
+ op[opname] = {
96
+ // TODO: in actual guide, remove "standard" method ops since redundant
97
+ method,
98
+ }
99
+
100
+ if (0 < Object.entries(transform).length) {
101
+ op[opname].transform = transform
102
+ }
103
+ })
104
+ }
105
+ })
106
+
107
+ // Analyze paths ending in .../foo
108
+ each(paths, (pathdef, pathstr) => {
109
+
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])
114
+
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 + '`'
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
134
+ }
135
+ }
136
+ }
137
+ })
138
+
139
+ return entityDescs
140
+ }
141
+
142
+
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
155
+ entdesc.origname = origentname
156
+
157
+ names(entdesc, entname)
158
+
159
+ entdesc.alias = entdesc.alias || {}
160
+
161
+ if (null != pathParam) {
162
+ const pathParamCanon = snakify(pathParam)
163
+ if ('id' != pathParamCanon) {
164
+ entdesc.alias.id = pathParamCanon
165
+ entdesc.alias[pathParamCanon] = 'id'
166
+ }
167
+ }
168
+
169
+ entdesc.path = (entdesc.path || {})
170
+ entdesc.path[pathStr] = { op: {} }
171
+
172
+ return entdesc
173
+ }
174
+
175
+
176
+ export {
177
+ heuristic01
178
+ }
package/src/guide.ts ADDED
@@ -0,0 +1,87 @@
1
+
2
+ import Path from 'node:path'
3
+
4
+ import { File, Content, each } from 'jostraca'
5
+
6
+
7
+ import { heuristic01 } from './guide/heuristic01'
8
+
9
+
10
+ async function resolveGuide(ctx: any) {
11
+ let guide: Record<string, any> = ctx.model.main.api.guide
12
+
13
+ if ('heuristic01' === ctx.opts.strategy) {
14
+ guide = await heuristic01(ctx)
15
+ }
16
+ else {
17
+ throw new Error('Unknown guide strategy: ' + ctx.opts.strategy)
18
+ }
19
+
20
+ guide = cleanGuide(guide)
21
+
22
+ ctx.model.main.api.guide = guide
23
+
24
+ const guideFile =
25
+ Path.join(ctx.opts.folder,
26
+ (null == ctx.opts.outprefix ? '' : ctx.opts.outprefix) + 'guide.jsonic')
27
+
28
+ const guideBlocks = [
29
+ '# Guide',
30
+ '',
31
+ 'main: api: guide: { ',
32
+ '',
33
+ ]
34
+
35
+
36
+ guideBlocks.push(...each(guide.entity, (entity, entityname) => {
37
+ guideBlocks.push(`\nentity: ${entityname}: {`)
38
+
39
+ each(entity.path, (path, pathname) => {
40
+ guideBlocks.push(` path: '${pathname}': op: {`)
41
+
42
+ each(path.op, (op, opname) => {
43
+ guideBlocks.push(` '${opname}': method: ${op.method}`)
44
+ if (op.transform?.reqform) {
45
+ guideBlocks.push(
46
+ ` '${opname}': transform: reqform: ${JSON.stringify(op.transform.reqform)}`)
47
+ }
48
+ })
49
+
50
+ guideBlocks.push(` }`)
51
+ })
52
+
53
+ guideBlocks.push(`}`)
54
+ }))
55
+
56
+ guideBlocks.push('}')
57
+
58
+ const guideSrc = guideBlocks.join('\n')
59
+
60
+ return () => {
61
+ File({ name: Path.basename(guideFile) }, () => Content(guideSrc))
62
+
63
+ }
64
+ }
65
+
66
+
67
+ function cleanGuide(guide: Record<string, any>): Record<string, any> {
68
+ const clean: Record<string, any> = {
69
+ control: guide.control,
70
+ entity: {}
71
+ }
72
+
73
+ each(guide.entity, (entity: any, name: string) => {
74
+ let ent: any = clean.entity[name] = clean.entity[name] = { name, path: {} }
75
+
76
+ each(entity.path, (path: any, pathname: string) => {
77
+ ent.path[pathname] = path
78
+ })
79
+ })
80
+
81
+ return clean
82
+ }
83
+
84
+
85
+ export {
86
+ resolveGuide
87
+ }