@voxgig/sdkgen 1.3.1 → 1.3.4
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/bin/voxgig-sdkgen +1 -1
- package/dist/action/action.d.ts +2 -1
- package/dist/action/action.js +13 -3
- package/dist/action/action.js.map +1 -1
- package/dist/action/feature.js +3 -5
- package/dist/action/feature.js.map +1 -1
- package/dist/action/target.js +9 -7
- package/dist/action/target.js.map +1 -1
- package/dist/cmp/Deploy.js +3 -3
- package/dist/cmp/Deploy.js.map +1 -1
- package/dist/cmp/ReadmeTop.js +24 -2
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/opShape.js +38 -6
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +5 -4
- package/dist/sdkgen.js.map +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +20 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aontu +1 -1
- package/package.json +1 -1
- package/project/.sdk/model/feature/feature-index.aontu +5 -0
- package/project/.sdk/src/cmp/go/Config_go.ts +3 -2
- package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +35 -9
- package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +18 -14
- package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +6 -2
- package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +6 -2
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +51 -30
- package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +66 -13
- package/project/.sdk/src/cmp/go/ReadmeTopHowto_go.ts +4 -0
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +51 -16
- package/project/.sdk/src/cmp/go/utility_go.ts +70 -0
- package/project/.sdk/src/cmp/js/Config_js.ts +3 -6
- package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +23 -6
- package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +31 -7
- package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +33 -10
- package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +24 -14
- package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +15 -3
- package/project/.sdk/src/cmp/lua/Config_lua.ts +3 -2
- package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +41 -6
- package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +94 -20
- package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +51 -8
- package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +31 -2
- package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +13 -2
- package/project/.sdk/src/cmp/php/Config_php.ts +3 -2
- package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +34 -6
- package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +95 -19
- package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +44 -8
- package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +26 -2
- package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +13 -2
- package/project/.sdk/src/cmp/py/Config_py.ts +3 -2
- package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +26 -8
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +95 -21
- package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +37 -11
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +28 -2
- package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +16 -3
- package/project/.sdk/src/cmp/rb/Config_rb.ts +3 -2
- package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +26 -8
- package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +96 -21
- package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +36 -10
- package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +28 -2
- package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +13 -2
- package/project/.sdk/src/cmp/ts/Config_ts.ts +3 -6
- package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +22 -6
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +56 -18
- package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +33 -10
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +21 -14
- package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +15 -3
- package/project/.sdk/tm/go/utility/prepare_auth.go +6 -1
- package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +3 -1
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +6 -1
- package/project/.sdk/tm/php/utility/PrepareAuth.php +3 -1
- package/project/.sdk/tm/py/test/test_struct_utility.py +1 -1
- package/project/.sdk/tm/py/utility/prepare_auth.py +4 -1
- package/project/.sdk/tm/rb/Makefile +3 -1
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +3 -1
- package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +5 -1
- package/src/action/action.ts +15 -3
- package/src/action/feature.ts +4 -8
- package/src/action/target.ts +11 -9
- package/src/cmp/Deploy.ts +3 -3
- package/src/cmp/ReadmeTop.ts +25 -2
- package/src/helpers/opShape.ts +45 -7
- package/src/sdkgen.ts +4 -3
- package/src/utility.ts +24 -1
- package/project/.sdk/model/feature/feature-index.jsonic +0 -5
- /package/project/.sdk/model/feature/{log.jsonic → log.aontu} +0 -0
- /package/project/.sdk/model/feature/{test.jsonic → test.aontu} +0 -0
- /package/project/.sdk/model/target/{go-cli.jsonic → go-cli.aontu} +0 -0
- /package/project/.sdk/model/target/{go-mcp.jsonic → go-mcp.aontu} +0 -0
- /package/project/.sdk/model/target/{go.jsonic → go.aontu} +0 -0
- /package/project/.sdk/model/target/{js.jsonic → js.aontu} +0 -0
- /package/project/.sdk/model/target/{lua.jsonic → lua.aontu} +0 -0
- /package/project/.sdk/model/target/{php.jsonic → php.aontu} +0 -0
- /package/project/.sdk/model/target/{py.jsonic → py.aontu} +0 -0
- /package/project/.sdk/model/target/{rb.jsonic → rb.aontu} +0 -0
- /package/project/.sdk/model/target/{ts.jsonic → ts.aontu} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName, entityIdField,
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, entityIdField, entityOps, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
nom,
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
|
+
import { exampleValue, goVarName } from './utility_go'
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
|
|
12
14
|
const { target, ctx$: { model } } = props
|
|
@@ -31,42 +33,61 @@ client := ${ctor}
|
|
|
31
33
|
|
|
32
34
|
if (exampleEntity) {
|
|
33
35
|
const eName = nom(exampleEntity, 'Name')
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
+
// camelCase Go identifier (never snake_case, never a Go keyword).
|
|
37
|
+
const eVar = goVarName(exampleEntity.name)
|
|
38
|
+
// ACTIVE ops only — an inactive op generates no method, so an example
|
|
39
|
+
// calling it would not compile.
|
|
40
|
+
const opnames = entityOps(exampleEntity)
|
|
36
41
|
|
|
37
42
|
let hasCall = false
|
|
38
43
|
|
|
39
44
|
if (opnames.includes('list')) {
|
|
40
45
|
Content(`// List all ${eName.toLowerCase()}s
|
|
41
46
|
${eVar}s, err := client.${eName}(nil).List(nil, nil)
|
|
47
|
+
if err != nil {
|
|
48
|
+
panic(err)
|
|
49
|
+
}
|
|
42
50
|
fmt.Println(${eVar}s)
|
|
43
51
|
`)
|
|
44
52
|
hasCall = true
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
// Find a nested entity for a more interesting example
|
|
55
|
+
// Find a nested entity for a more interesting example: one with a parent
|
|
56
|
+
// chain (relations.ancestors), an active load op, and a required non-id
|
|
57
|
+
// load param to demonstrate (the parent key, e.g. page_id).
|
|
48
58
|
const nestedEntity = Object.values(entity).find((e: any) =>
|
|
49
|
-
e.active !== false &&
|
|
59
|
+
e.active !== false &&
|
|
60
|
+
e.relations && e.relations.ancestors && 0 < e.relations.ancestors.length &&
|
|
61
|
+
entityOps(e).includes('load') &&
|
|
62
|
+
opRequestShape(e, 'load').items.some((it: any) =>
|
|
63
|
+
!it.optional && it.name !== entityIdField(e))
|
|
50
64
|
) as any
|
|
51
65
|
|
|
52
|
-
if (nestedEntity
|
|
66
|
+
if (nestedEntity) {
|
|
53
67
|
const neName = nom(nestedEntity, 'Name')
|
|
54
|
-
const neVar =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
68
|
+
const neVar = goVarName(nestedEntity.name)
|
|
69
|
+
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
70
|
+
// that generates the op's request match, so the example always carries
|
|
71
|
+
// the keys the route needs. Parent keys (e.g. page_id) first, the
|
|
72
|
+
// entity's own id last, each value a type-correct Go literal.
|
|
59
73
|
const neIdF = entityIdField(nestedEntity)
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
const neLoadOp = nestedEntity.op && nestedEntity.op.load
|
|
75
|
+
const neMatchPairs = opRequestShape(nestedEntity, 'load').items
|
|
76
|
+
.filter((it: any) => !it.optional)
|
|
77
|
+
.sort((a: any, b: any) =>
|
|
78
|
+
(a.name === neIdF ? 1 : 0) - (b.name === neIdF ? 1 : 0))
|
|
79
|
+
.map((it: any) =>
|
|
80
|
+
`"${it.name}": ${exampleValue(nestedEntity, neLoadOp, it.name,
|
|
81
|
+
it.name === neIdF ? 'example_id' : 'example_' + it.name)}`)
|
|
64
82
|
|
|
65
83
|
Content(`
|
|
66
84
|
// Load a specific ${neName.toLowerCase()}
|
|
67
85
|
${neVar}, err := client.${neName}(nil).Load(
|
|
68
86
|
map[string]any{${neMatchPairs.join(', ')}}, nil,
|
|
69
87
|
)
|
|
88
|
+
if err != nil {
|
|
89
|
+
panic(err)
|
|
90
|
+
}
|
|
70
91
|
fmt.Println(${neVar})
|
|
71
92
|
`)
|
|
72
93
|
hasCall = true
|
|
@@ -74,8 +95,22 @@ fmt.Println(${neVar})
|
|
|
74
95
|
|
|
75
96
|
// Fallback: APIs with only `load` (no list, no nested) — still show one call.
|
|
76
97
|
if (!hasCall && opnames.includes('load')) {
|
|
98
|
+
// Every REQUIRED load-match key (id first) — nil when there are none.
|
|
99
|
+
const idF = entityIdField(exampleEntity)
|
|
100
|
+
const loadItems = opRequestShape(exampleEntity, 'load').items
|
|
101
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
102
|
+
.sort((a: any, b: any) =>
|
|
103
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
104
|
+
const loadArg = 0 < loadItems.length
|
|
105
|
+
? `map[string]any{${loadItems.map((it: any) =>
|
|
106
|
+
`"${it.name}": ${exampleValue(exampleEntity, exampleEntity.op && exampleEntity.op.load, it.name,
|
|
107
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')}}`
|
|
108
|
+
: 'nil'
|
|
77
109
|
Content(`// Load ${eName.toLowerCase()} data
|
|
78
|
-
${eVar}, err := client.${eName}(nil).Load(
|
|
110
|
+
${eVar}, err := client.${eName}(nil).Load(${loadArg}, nil)
|
|
111
|
+
if err != nil {
|
|
112
|
+
panic(err)
|
|
113
|
+
}
|
|
79
114
|
fmt.Println(${eVar})
|
|
80
115
|
`)
|
|
81
116
|
hasCall = true
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
import * as Path from 'node:path'
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
camelify,
|
|
7
|
+
canonKey,
|
|
8
|
+
each,
|
|
9
|
+
safeVarName,
|
|
10
|
+
} from '@voxgig/sdkgen'
|
|
11
|
+
|
|
5
12
|
import {
|
|
6
13
|
clone,
|
|
7
14
|
walk,
|
|
@@ -13,6 +20,67 @@ function projectPath(suffix?: string): string {
|
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
|
|
23
|
+
// --- Model-driven example literals -----------------------------------------
|
|
24
|
+
// Doc snippets must use example values whose TYPE matches the model's
|
|
25
|
+
// declared param/field type — a number id shown as a quoted string documents
|
|
26
|
+
// a call the API would reject. These helpers derive the example literal from
|
|
27
|
+
// the SAME model source the request shapes are built from (opRequestShape),
|
|
28
|
+
// so the docs and the model can never disagree. Go twin of the ts
|
|
29
|
+
// exampleValue (cmp/ts/utility_ts.ts), rendering Go literals.
|
|
30
|
+
|
|
31
|
+
// The declared canon-type sentinel of a named parameter of an op — looked up
|
|
32
|
+
// in the op's `points[].args.params[]` exactly as the typed-model generator
|
|
33
|
+
// does. Falls back to the entity field of the same name (used when the op
|
|
34
|
+
// has no params and the request shape mirrors the entity fields). Returns
|
|
35
|
+
// undefined when neither is present.
|
|
36
|
+
function paramCanonType(entity: any, op: any, paramName: string): unknown {
|
|
37
|
+
const points = op && op.points ? each(op.points) : []
|
|
38
|
+
for (const pt of points as any[]) {
|
|
39
|
+
const params = pt && pt.args && pt.args.params ? each(pt.args.params) : []
|
|
40
|
+
const found = (params as any[]).find((p: any) => p && p.name === paramName)
|
|
41
|
+
if (found) {
|
|
42
|
+
return found.type
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const field = (entity && entity.fields ? each(entity.fields) : [])
|
|
46
|
+
.find((f: any) => f && f.name === paramName) as any
|
|
47
|
+
return field && field.type
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// A type-correct Go example literal for a named match/data parameter of an
|
|
52
|
+
// op, derived entirely from the model. INTEGER/NUMBER render as the bare
|
|
53
|
+
// number `1`, BOOLEAN as `true`, ARRAY as the empty `[]any{}` and OBJECT as
|
|
54
|
+
// the empty `map[string]any{}`, everything else (STRING, unknown, missing)
|
|
55
|
+
// as the quoted `placeholder`.
|
|
56
|
+
function exampleValue(entity: any, op: any, paramName: string, placeholder: string): string {
|
|
57
|
+
const key = canonKey(paramCanonType(entity, op, paramName))
|
|
58
|
+
if ('INTEGER' === key || 'NUMBER' === key) {
|
|
59
|
+
return '1'
|
|
60
|
+
}
|
|
61
|
+
if ('BOOLEAN' === key) {
|
|
62
|
+
return 'true'
|
|
63
|
+
}
|
|
64
|
+
if ('ARRAY' === key) {
|
|
65
|
+
return '[]any{}'
|
|
66
|
+
}
|
|
67
|
+
if ('OBJECT' === key) {
|
|
68
|
+
return 'map[string]any{}'
|
|
69
|
+
}
|
|
70
|
+
return `"${placeholder}"`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// A camelCase Go identifier for a snake_case model name
|
|
75
|
+
// (`status_embed_config` -> `statusEmbedConfig`) — Go variables are
|
|
76
|
+
// camelCase, never snake_case — with the reserved-word guard applied (a
|
|
77
|
+
// `type`/`range` entity must not bind a Go keyword).
|
|
78
|
+
function goVarName(name: string): string {
|
|
79
|
+
const pascal = camelify(name)
|
|
80
|
+
return safeVarName(pascal.charAt(0).toLowerCase() + pascal.slice(1), 'go')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
16
84
|
function formatGoMap(obj: any, indent: number = 0): string {
|
|
17
85
|
if (obj == null) {
|
|
18
86
|
return 'nil'
|
|
@@ -82,7 +150,9 @@ function clean(o: any) {
|
|
|
82
150
|
|
|
83
151
|
export {
|
|
84
152
|
clean,
|
|
153
|
+
exampleValue,
|
|
85
154
|
formatGoMap,
|
|
86
155
|
formatGoValue,
|
|
156
|
+
goVarName,
|
|
87
157
|
projectPath,
|
|
88
158
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
each,
|
|
12
12
|
indent,
|
|
13
13
|
isAuthActive,
|
|
14
|
+
resolveAuthPrefix,
|
|
14
15
|
} from '@voxgig/sdkgen'
|
|
15
16
|
|
|
16
17
|
|
|
@@ -42,12 +43,8 @@ const Config = cmp(async function Config(props: any) {
|
|
|
42
43
|
const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
|
|
43
44
|
|
|
44
45
|
const authActive = isAuthActive(model)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const v = getModelPath(model, `main.${KIT}.config.auth.prefix`,
|
|
48
|
-
{ only_active: false, required: false })
|
|
49
|
-
if (null != v) authPrefix = v
|
|
50
|
-
} catch (_e) { /* ignore */ }
|
|
46
|
+
// config.auth.prefix override -> spec-derived info.security.prefix -> 'Bearer'
|
|
47
|
+
const authPrefix = resolveAuthPrefix(model)
|
|
51
48
|
const authBlock = authActive
|
|
52
49
|
? `auth: {
|
|
53
50
|
prefix: '${authPrefix}',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, entityIdField, safeVarName } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, entityIdField, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -99,10 +99,22 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
if (opnames.includes('load')) {
|
|
102
|
+
// The id key plus every REQUIRED match key (parent path params like
|
|
103
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
104
|
+
// the example always works.
|
|
105
|
+
const loadItems = opRequestShape(entity, 'load').items
|
|
106
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
107
|
+
.sort((a: any, b: any) =>
|
|
108
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
109
|
+
const loadArg = 0 < loadItems.length
|
|
110
|
+
? `{ ${loadItems.map((it: any) =>
|
|
111
|
+
`${it.name}: ${exampleValue(entity, entity.op && entity.op.load, it.name,
|
|
112
|
+
it.name === idF ? entity.name + '_id' : it.name)}`).join(', ')} }`
|
|
113
|
+
: ''
|
|
102
114
|
Content(`#### Example: Load
|
|
103
115
|
|
|
104
116
|
\`\`\`ts
|
|
105
|
-
const ${eVar} = await client.${entity.Name}().load(${
|
|
117
|
+
const ${eVar} = await client.${entity.Name}().load(${loadArg})
|
|
106
118
|
\`\`\`
|
|
107
119
|
|
|
108
120
|
`)
|
|
@@ -119,16 +131,21 @@ const ${eVar}s = await client.${entity.Name}().list()
|
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
if (opnames.includes('create')) {
|
|
134
|
+
// Members come from the SAME shape the runtime validates
|
|
135
|
+
// (opRequestShape): every required member must appear — including a
|
|
136
|
+
// required id and parent keys like page_id (the /* type */
|
|
137
|
+
// placeholders mark the block as an illustration); an all-optional
|
|
138
|
+
// create renders an empty — still valid — literal.
|
|
139
|
+
const createItems = opRequestShape(entity, 'create').items
|
|
140
|
+
.filter((it: any) => !it.optional)
|
|
122
141
|
Content(`#### Example: Create
|
|
123
142
|
|
|
124
143
|
\`\`\`ts
|
|
125
144
|
const ${eVar} = await client.${entity.Name}().create({
|
|
126
145
|
`)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Content(` ${field.name}: /* ${canonToType(field.type, target.name)} */,
|
|
146
|
+
createItems.map((it: any) => {
|
|
147
|
+
Content(` ${it.name}: /* ${canonToType(it.type, target.name)} */,
|
|
130
148
|
`)
|
|
131
|
-
}
|
|
132
149
|
})
|
|
133
150
|
Content(`})
|
|
134
151
|
\`\`\`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, isAuthActive, packageName, envName, opRequestShape, entityIdField, safeVarName } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, isAuthActive, packageName, envName, opRequestShape, entityIdField, entityOps, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -36,7 +36,9 @@ const client = ${ctor}
|
|
|
36
36
|
if (exampleEntity) {
|
|
37
37
|
const eName = nom(exampleEntity, 'Name')
|
|
38
38
|
const article = /^[aeiou]/i.test(eName) ? 'an' : 'a'
|
|
39
|
-
|
|
39
|
+
// ACTIVE ops only — an inactive op generates no method, so an example
|
|
40
|
+
// calling it would be wrong.
|
|
41
|
+
const opnames = entityOps(exampleEntity)
|
|
40
42
|
|
|
41
43
|
// Model-driven example fields, in parity with the ts target: derive the
|
|
42
44
|
// create/update body from the op shape (opRequestShape) so the docs show
|
|
@@ -47,23 +49,45 @@ const client = ${ctor}
|
|
|
47
49
|
// Variable-safe lowercase name (a `Delete` entity must not bind `delete`).
|
|
48
50
|
const eVar = safeVarName(exampleEntity.name, 'js')
|
|
49
51
|
const exampleFields = (opname: string): string[] => {
|
|
52
|
+
// ids are rendered separately as the match key for update/remove; a
|
|
53
|
+
// REQUIRED id stays for create (dropping it makes the payload
|
|
54
|
+
// incomplete).
|
|
50
55
|
const items = opRequestShape(exampleEntity, opname).items
|
|
51
|
-
.filter((it: any) => it.name !== idF && it.name !== 'id')
|
|
56
|
+
.filter((it: any) => (it.name !== idF && it.name !== 'id') ||
|
|
57
|
+
('create' === opname && !it.optional))
|
|
52
58
|
const required = items.filter((it: any) => !it.optional)
|
|
53
|
-
|
|
59
|
+
const optional = items.filter((it: any) => it.optional)
|
|
60
|
+
// create needs ALL required fields for parity with the typed ts target;
|
|
61
|
+
// update is a patch, so the required members plus a sample optional
|
|
62
|
+
// field or two suffice.
|
|
54
63
|
const chosen = 'create' === opname
|
|
55
64
|
? (required.length ? required : items.slice(0, 2))
|
|
56
|
-
:
|
|
65
|
+
: required.concat(optional).slice(0, Math.max(2, required.length))
|
|
57
66
|
return chosen.map((it: any) =>
|
|
58
67
|
` ${it.name}: ${exampleValue(exampleEntity, exampleEntity.op[opname], it.name, 'example_' + it.name)},`)
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
// The full REQUIRED match for load/remove (id first, then parent path
|
|
71
|
+
// params like page_id) — the same shape the runtime resolves path params
|
|
72
|
+
// from, so the example always carries the keys the route needs.
|
|
73
|
+
const matchArg = (opname: string): string => {
|
|
74
|
+
const matchItems = opRequestShape(exampleEntity, opname).items
|
|
75
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
76
|
+
.sort((a: any, b: any) =>
|
|
77
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
78
|
+
return 0 < matchItems.length
|
|
79
|
+
? `{ ${matchItems.map((it: any) =>
|
|
80
|
+
`${it.name}: ${exampleValue(exampleEntity, exampleEntity.op && exampleEntity.op[opname], it.name,
|
|
81
|
+
it.name === idF ? exampleEntity.name + '_id' : 'example_' + it.name)}`).join(', ')} }`
|
|
82
|
+
: ''
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
if (opnames.includes('load')) {
|
|
62
86
|
Content(`
|
|
63
87
|
### Load ${article} ${eName}
|
|
64
88
|
|
|
65
89
|
\`\`\`js
|
|
66
|
-
const ${eVar} = await client.${eName}().load(${
|
|
90
|
+
const ${eVar} = await client.${eName}().load(${matchArg('load')})
|
|
67
91
|
console.log(${eVar})
|
|
68
92
|
\`\`\`
|
|
69
93
|
`)
|
|
@@ -115,7 +139,7 @@ console.log(updated)
|
|
|
115
139
|
### Remove a ${eName}
|
|
116
140
|
|
|
117
141
|
\`\`\`js
|
|
118
|
-
await client.${eName}().remove(${
|
|
142
|
+
await client.${eName}().remove(${matchArg('remove')})
|
|
119
143
|
\`\`\`
|
|
120
144
|
`)
|
|
121
145
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, File, isAuthActive, entityIdField, safeVarName } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, File, isAuthActive, entityIdField, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -272,8 +272,20 @@ ${info.desc}
|
|
|
272
272
|
|
|
273
273
|
// Show example
|
|
274
274
|
if ('load' === opname || 'remove' === opname) {
|
|
275
|
+
// The id key plus every REQUIRED match key (parent path params
|
|
276
|
+
// like page_id) — the same shape the runtime resolves path
|
|
277
|
+
// params from, so the example always works.
|
|
278
|
+
const matchItems = opRequestShape(ent, opname).items
|
|
279
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
280
|
+
.sort((a: any, b: any) =>
|
|
281
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
282
|
+
const arg = 0 < matchItems.length
|
|
283
|
+
? `{ ${matchItems.map((it: any) =>
|
|
284
|
+
`${it.name}: ${exampleValue(ent, ent.op && ent.op[opname], it.name,
|
|
285
|
+
it.name === idF ? ent.name + '_id' : it.name)}`).join(', ')} }`
|
|
286
|
+
: ''
|
|
275
287
|
Content(`\`\`\`ts
|
|
276
|
-
const result = await client.${ent.Name}().${opname}(${
|
|
288
|
+
const result = await client.${ent.Name}().${opname}(${arg})
|
|
277
289
|
\`\`\`
|
|
278
290
|
|
|
279
291
|
`)
|
|
@@ -286,14 +298,19 @@ const results = await client.${ent.Name}().${opname}()
|
|
|
286
298
|
`)
|
|
287
299
|
}
|
|
288
300
|
else if ('create' === opname) {
|
|
301
|
+
// Members come from the SAME shape the runtime validates
|
|
302
|
+
// (opRequestShape): every required member must appear — including
|
|
303
|
+
// a required id and parent keys like page_id (the /* type */
|
|
304
|
+
// placeholders mark the block as an illustration); an
|
|
305
|
+
// all-optional create renders an empty — still valid — literal.
|
|
306
|
+
const createItems = opRequestShape(ent, 'create').items
|
|
307
|
+
.filter((it: any) => !it.optional)
|
|
289
308
|
Content(`\`\`\`ts
|
|
290
309
|
const result = await client.${ent.Name}().create({
|
|
291
310
|
`)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
Content(` ${field.name}: /* ${canonToType(field.type, target.name)} */,
|
|
311
|
+
createItems.map((it: any) => {
|
|
312
|
+
Content(` ${it.name}: /* ${canonToType(it.type, target.name)} */,
|
|
295
313
|
`)
|
|
296
|
-
}
|
|
297
314
|
})
|
|
298
315
|
Content(`})
|
|
299
316
|
\`\`\`
|
|
@@ -301,12 +318,18 @@ const result = await client.${ent.Name}().create({
|
|
|
301
318
|
`)
|
|
302
319
|
}
|
|
303
320
|
else if ('update' === opname) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
321
|
+
// The id key plus every REQUIRED data member — the same shape the
|
|
322
|
+
// runtime validates — then the patch-fields note.
|
|
323
|
+
const updateItems = opRequestShape(ent, 'update').items
|
|
324
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
325
|
+
.sort((a: any, b: any) =>
|
|
326
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
327
|
+
const updateLines = updateItems.map((it: any) =>
|
|
328
|
+
` ${it.name}: ${exampleValue(ent, ent.op && ent.op.update, it.name,
|
|
329
|
+
it.name === idF ? ent.name + '_id' : it.name)},\n`).join('')
|
|
307
330
|
Content(`\`\`\`ts
|
|
308
331
|
const result = await client.${ent.Name}().update({
|
|
309
|
-
${
|
|
332
|
+
${updateLines} // Fields to update
|
|
310
333
|
})
|
|
311
334
|
\`\`\`
|
|
312
335
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, packageName, envName, entityIdField, safeVarName } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, Content, isAuthActive, packageName, envName, entityIdField, entityOps, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -30,7 +30,9 @@ const client = ${ctor}
|
|
|
30
30
|
if (exampleEntity) {
|
|
31
31
|
const eName = nom(exampleEntity, 'Name')
|
|
32
32
|
const eVar = safeVarName(eName.toLowerCase(), 'js')
|
|
33
|
-
|
|
33
|
+
// ACTIVE ops only — an inactive op generates no method, so an example
|
|
34
|
+
// calling it would be wrong.
|
|
35
|
+
const opnames = entityOps(exampleEntity)
|
|
34
36
|
|
|
35
37
|
if (opnames.includes('list')) {
|
|
36
38
|
Content(`// List all ${eName.toLowerCase()}s (returns an array)
|
|
@@ -41,26 +43,34 @@ for (const ${eVar} of ${eVar}s) {
|
|
|
41
43
|
`)
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
// Find a nested entity for a more interesting example
|
|
46
|
+
// Find a nested entity for a more interesting example: one with a parent
|
|
47
|
+
// chain (relations.ancestors), an active load op of its OWN, and a
|
|
48
|
+
// required non-id load param to demonstrate (the parent key, e.g.
|
|
49
|
+
// page_id).
|
|
45
50
|
const nestedEntity = Object.values(entity).find((e: any) =>
|
|
46
|
-
e.active !== false &&
|
|
51
|
+
e.active !== false &&
|
|
52
|
+
e.relations && e.relations.ancestors && 0 < e.relations.ancestors.length &&
|
|
53
|
+
entityOps(e).includes('load') &&
|
|
54
|
+
opRequestShape(e, 'load').items.some((it: any) =>
|
|
55
|
+
!it.optional && it.name !== entityIdField(e))
|
|
47
56
|
) as any
|
|
48
57
|
|
|
49
|
-
if (nestedEntity
|
|
58
|
+
if (nestedEntity) {
|
|
50
59
|
const neName = nom(nestedEntity, 'Name')
|
|
51
60
|
const neVar = safeVarName(neName.toLowerCase(), 'js')
|
|
52
|
-
const parentFields = (nestedEntity.fields || [])
|
|
53
|
-
.filter((f: any) => f.name !== 'id' && f.name.endsWith('_id'))
|
|
54
|
-
const parentParam = parentFields.length > 0 ? parentFields[0].name : 'parent_id'
|
|
55
61
|
const loadOp = nestedEntity.op && nestedEntity.op.load
|
|
56
62
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
63
|
+
// Every REQUIRED load-match key (parent keys like page_id first, the
|
|
64
|
+
// entity's own id last) — the same shape the runtime resolves path
|
|
65
|
+
// params from, so the example always works.
|
|
59
66
|
const neIdF = entityIdField(nestedEntity)
|
|
60
|
-
const neMatchLines =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
const neMatchLines = opRequestShape(nestedEntity, 'load').items
|
|
68
|
+
.filter((it: any) => !it.optional || it.name === neIdF)
|
|
69
|
+
.sort((a: any, b: any) =>
|
|
70
|
+
(a.name === neIdF ? 1 : 0) - (b.name === neIdF ? 1 : 0))
|
|
71
|
+
.map((it: any) =>
|
|
72
|
+
` ${it.name}: ${exampleValue(nestedEntity, loadOp, it.name,
|
|
73
|
+
it.name === neIdF ? 'example_id' : 'example_' + it.name)},`)
|
|
64
74
|
|
|
65
75
|
Content(`
|
|
66
76
|
// Load a specific ${neName.toLowerCase()} (returns the entity)
|
|
@@ -22,15 +22,27 @@ const client = ${model.const.Name}SDK.test()
|
|
|
22
22
|
|
|
23
23
|
if (exampleEntity) {
|
|
24
24
|
const eName = nom(exampleEntity, 'Name')
|
|
25
|
-
const eVar = safeVarName(eName.toLowerCase(), 'js')
|
|
26
25
|
// Drive the test-mode example off the entity's PRIMARY op (never a
|
|
27
26
|
// hardcoded `load` a create-only entity lacks).
|
|
28
27
|
const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
|
|
28
|
+
// A list() result is an array — name the variable accordingly.
|
|
29
|
+
const eVar = safeVarName(eName.toLowerCase(), 'js') +
|
|
30
|
+
('list' === primaryOp ? 's' : '')
|
|
29
31
|
const primaryOpDef = exampleEntity.op && exampleEntity.op[primaryOp]
|
|
30
32
|
const idF = entityIdField(exampleEntity)
|
|
31
33
|
let arg = ''
|
|
32
34
|
if ('load' === primaryOp || 'remove' === primaryOp) {
|
|
33
|
-
|
|
35
|
+
// Every REQUIRED match key (id first, then parent path params like
|
|
36
|
+
// page_id) — the same shape the runtime resolves path params from.
|
|
37
|
+
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
38
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
39
|
+
.sort((a: any, b: any) =>
|
|
40
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
41
|
+
arg = 0 < items.length
|
|
42
|
+
? `{ ${items.map((it: any) =>
|
|
43
|
+
`${it.name}: ${exampleValue(exampleEntity, primaryOpDef, it.name,
|
|
44
|
+
it.name === idF ? 'test01' : 'example_' + it.name)}`).join(', ')} }`
|
|
45
|
+
: ''
|
|
34
46
|
} else if ('create' === primaryOp || 'update' === primaryOp) {
|
|
35
47
|
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
36
48
|
.filter((it: any) => it.name !== idF && it.name !== 'id')
|
|
@@ -40,7 +52,7 @@ const client = ${model.const.Name}SDK.test()
|
|
|
40
52
|
`${it.name}: ${exampleValue(exampleEntity, primaryOpDef, it.name, 'example_' + it.name)}`).join(', ')} }`
|
|
41
53
|
}
|
|
42
54
|
Content(`const ${eVar} = await client.${eName}().${primaryOp}(${arg})
|
|
43
|
-
// ${eVar} is a bare entity populated with mock data
|
|
55
|
+
// ${eVar} is ${'list' === primaryOp ? 'an array of bare entities' : 'a bare entity'} populated with mock data
|
|
44
56
|
console.log(${eVar})
|
|
45
57
|
`)
|
|
46
58
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
cmp,
|
|
11
11
|
each,
|
|
12
12
|
isAuthActive,
|
|
13
|
+
resolveAuthPrefix,
|
|
13
14
|
} from '@voxgig/sdkgen'
|
|
14
15
|
|
|
15
16
|
|
|
@@ -39,8 +40,8 @@ const Config = cmp(async function Config(props: any) {
|
|
|
39
40
|
const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
|
|
40
41
|
|
|
41
42
|
const authActive = isAuthActive(model)
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
// config.auth.prefix override -> spec-derived info.security.prefix -> 'Bearer'
|
|
44
|
+
const authPrefix = resolveAuthPrefix(model)
|
|
44
45
|
|
|
45
46
|
let baseUrl = ''
|
|
46
47
|
try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, entityIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, canonKey, entityIdField, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -7,6 +7,24 @@ import {
|
|
|
7
7
|
} from '@voxgig/apidef'
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
// A type-correct, executable Lua literal for a param: numeric/boolean/table
|
|
11
|
+
// params render a typed literal; strings render the quoted placeholder (the
|
|
12
|
+
// doc test EXECUTES runnable blocks, so a comment placeholder would not
|
|
13
|
+
// parse).
|
|
14
|
+
function luaLit(type: any, placeholder: string = 'example'): string {
|
|
15
|
+
const k = canonKey(type)
|
|
16
|
+
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
17
|
+
if ('BOOLEAN' === k) return 'true'
|
|
18
|
+
if ('ARRAY' === k || 'OBJECT' === k) return '{}'
|
|
19
|
+
return `"${placeholder}"`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Non-identifier table keys use bracket syntax.
|
|
23
|
+
function luaKey(name: string): string {
|
|
24
|
+
return /^[A-Za-z_]\w*$/.test(name) ? name : `["${name}"]`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
10
28
|
// Operation method spelling differs between Go and other languages — Go
|
|
11
29
|
// uses PascalCase methods with explicit ctrl arg, others use lowercase
|
|
12
30
|
// methods with optional ctrl. The op descriptions are language-agnostic.
|
|
@@ -95,10 +113,22 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
95
113
|
}
|
|
96
114
|
|
|
97
115
|
if (opnames.includes('load')) {
|
|
116
|
+
// The id key plus every REQUIRED match key (parent path params like
|
|
117
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
118
|
+
// the example always works.
|
|
119
|
+
const loadItems = opRequestShape(entity, 'load').items
|
|
120
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
121
|
+
.sort((a: any, b: any) =>
|
|
122
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
123
|
+
const loadArg = 0 < loadItems.length
|
|
124
|
+
? `{ ${loadItems.map((it: any) =>
|
|
125
|
+
`${luaKey(it.name)} = ${luaLit(it.type,
|
|
126
|
+
it.name === idF ? entity.name + '_id' : it.name)}`).join(', ')} }`
|
|
127
|
+
: ''
|
|
98
128
|
Content(`#### Example: Load
|
|
99
129
|
|
|
100
130
|
\`\`\`lua
|
|
101
|
-
local ${entity.name}, err = client:${entity.Name}():load(${
|
|
131
|
+
local ${entity.name}, err = client:${entity.Name}():load(${loadArg})
|
|
102
132
|
\`\`\`
|
|
103
133
|
|
|
104
134
|
`)
|
|
@@ -115,16 +145,21 @@ local ${entity.name}s, err = client:${entity.Name}():list()
|
|
|
115
145
|
}
|
|
116
146
|
|
|
117
147
|
if (opnames.includes('create')) {
|
|
148
|
+
// Members come from the SAME shape the runtime validates
|
|
149
|
+
// (opRequestShape): every required member must appear — including a
|
|
150
|
+
// required id and parent keys like page_id — with a real, executable
|
|
151
|
+
// literal (the doc test RUNS this block, so a nil member would vanish
|
|
152
|
+
// from the table and a comment placeholder would not parse).
|
|
153
|
+
const createItems = opRequestShape(entity, 'create').items
|
|
154
|
+
.filter((it: any) => !it.optional)
|
|
118
155
|
Content(`#### Example: Create
|
|
119
156
|
|
|
120
157
|
\`\`\`lua
|
|
121
158
|
local ${entity.name}, err = client:${entity.Name}():create({
|
|
122
159
|
`)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Content(` ${field.name} = nil, -- ${canonToType(field.type, target.name)}
|
|
160
|
+
createItems.map((it: any) => {
|
|
161
|
+
Content(` ${luaKey(it.name)} = ${luaLit(it.type, 'example_' + it.name)}, -- ${canonToType(it.type, target.name)}
|
|
126
162
|
`)
|
|
127
|
-
}
|
|
128
163
|
})
|
|
129
164
|
Content(`})
|
|
130
165
|
\`\`\`
|