@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, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField, entityOps } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -14,8 +14,16 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
|
14
14
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
15
15
|
|
|
16
16
|
const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
|
|
17
|
+
|
|
18
|
+
// Find a nested entity if available: one with a parent chain
|
|
19
|
+
// (relations.ancestors), an active load op, and a required non-id load
|
|
20
|
+
// param to demonstrate (the parent key, e.g. page_id).
|
|
17
21
|
const nestedEntity = Object.values(entity).find((e: any) =>
|
|
18
|
-
e.active !== false &&
|
|
22
|
+
e.active !== false &&
|
|
23
|
+
e.relations && e.relations.ancestors && 0 < e.relations.ancestors.length &&
|
|
24
|
+
entityOps(e).includes('load') &&
|
|
25
|
+
opRequestShape(e, 'load').items.some((it: any) =>
|
|
26
|
+
!it.optional && it.name !== entityIdField(e))
|
|
19
27
|
) as any
|
|
20
28
|
|
|
21
29
|
const ctor = isAuthActive(model)
|
|
@@ -35,7 +43,7 @@ local client = ${ctor}
|
|
|
35
43
|
if (exampleEntity) {
|
|
36
44
|
const eName = nom(exampleEntity, 'Name')
|
|
37
45
|
const article = /^[aeiou]/i.test(eName) ? "an" : "a"
|
|
38
|
-
const opnames =
|
|
46
|
+
const opnames = entityOps(exampleEntity)
|
|
39
47
|
// Model-driven id key: `idF` is the MATCH key (null when none). `dataIdF`
|
|
40
48
|
// is the id on the RETURNED record's data type — an entity can key its match
|
|
41
49
|
// on an id it does not carry as data, so a record id read (`item["id"]`,
|
|
@@ -43,6 +51,20 @@ local client = ${ctor}
|
|
|
43
51
|
const idF = entityIdField(exampleEntity)
|
|
44
52
|
const dataIdF = entityDataIdField(exampleEntity)
|
|
45
53
|
|
|
54
|
+
// A type-correct, executable Lua literal for a param: numeric/boolean/
|
|
55
|
+
// table params render a typed literal; strings render the quoted
|
|
56
|
+
// placeholder (the doc test EXECUTES these blocks, so a comment
|
|
57
|
+
// placeholder would not parse). Non-identifier keys use bracket syntax.
|
|
58
|
+
const luaLit = (type: any, placeholder: string = 'example'): string => {
|
|
59
|
+
const k = canonKey(type)
|
|
60
|
+
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
61
|
+
if ('BOOLEAN' === k) return 'true'
|
|
62
|
+
if ('ARRAY' === k || 'OBJECT' === k) return '{}'
|
|
63
|
+
return `"${placeholder}"`
|
|
64
|
+
}
|
|
65
|
+
const luaKey = (name: string): string =>
|
|
66
|
+
/^[A-Za-z_]\w*$/.test(name) ? name : `["${name}"]`
|
|
67
|
+
|
|
46
68
|
// MODEL-DRIVEN display field: the list example must reference a field
|
|
47
69
|
// the entity actually has, not a hardcoded "name". Pick the entity's
|
|
48
70
|
// first non-id string field (falling back to the first non-id field of
|
|
@@ -80,11 +102,56 @@ end
|
|
|
80
102
|
`)
|
|
81
103
|
}
|
|
82
104
|
|
|
83
|
-
if (
|
|
105
|
+
if (nestedEntity) {
|
|
106
|
+
const neName = nom(nestedEntity, 'Name')
|
|
107
|
+
const neArticle = /^[aeiou]/i.test(neName) ? "an" : "a"
|
|
108
|
+
const neVar = neName.toLowerCase()
|
|
109
|
+
|
|
110
|
+
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
111
|
+
// the runtime resolves path params from, so the example always works.
|
|
112
|
+
// Parent keys (e.g. page_id) first, the entity's own id last.
|
|
113
|
+
const neIdF = entityIdField(nestedEntity)
|
|
114
|
+
const neRequired = opRequestShape(nestedEntity, 'load').items
|
|
115
|
+
.filter((it: any) => !it.optional)
|
|
116
|
+
.sort((a: any, b: any) =>
|
|
117
|
+
(a.name === neIdF ? 1 : 0) - (b.name === neIdF ? 1 : 0))
|
|
118
|
+
const parentItem = neRequired.find((it: any) => it.name !== neIdF) as any
|
|
119
|
+
const parentParam = parentItem && parentItem.name
|
|
120
|
+
const parentName = parentParam ? parentParam.replace(/_id$/, '') : 'its parent'
|
|
121
|
+
const neMatch = neRequired.map((it: any) =>
|
|
122
|
+
`${luaKey(it.name)} = ${luaLit(it.type,
|
|
123
|
+
it.name === neIdF ? 'example_id' : 'example_' + it.name)}`)
|
|
124
|
+
|
|
125
|
+
Content(`### 3. Load ${neArticle} ${neName.toLowerCase()}
|
|
126
|
+
|
|
127
|
+
${neName} is nested under ${parentName}, so provide the \`${parentParam}\`.
|
|
128
|
+
|
|
129
|
+
\`\`\`lua
|
|
130
|
+
local ${neVar}, err = client:${neName}():load({ ${neMatch.join(', ')} })
|
|
131
|
+
if err then error(err) end
|
|
132
|
+
print(${neVar})
|
|
133
|
+
\`\`\`
|
|
134
|
+
|
|
135
|
+
`)
|
|
136
|
+
}
|
|
137
|
+
else if (opnames.includes('load')) {
|
|
138
|
+
// Every REQUIRED load-match key (id first, then parent path params like
|
|
139
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
140
|
+
// the example always works.
|
|
141
|
+
const loadRequired = opRequestShape(exampleEntity, 'load').items
|
|
142
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
143
|
+
.sort((a: any, b: any) =>
|
|
144
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
145
|
+
const loadArg = 0 < loadRequired.length
|
|
146
|
+
? `{ ${loadRequired.map((it: any) =>
|
|
147
|
+
`${luaKey(it.name)} = ${luaLit(it.type,
|
|
148
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')} }`
|
|
149
|
+
: ''
|
|
150
|
+
|
|
84
151
|
Content(`### 3. Load ${article} ${eName.toLowerCase()}
|
|
85
152
|
|
|
86
153
|
\`\`\`lua
|
|
87
|
-
local ${eName.toLowerCase()}, err = client:${eName}():load(${
|
|
154
|
+
local ${eName.toLowerCase()}, err = client:${eName}():load(${loadArg})
|
|
88
155
|
if err then error(err) end
|
|
89
156
|
print(${eName.toLowerCase()})
|
|
90
157
|
\`\`\`
|
|
@@ -95,24 +162,22 @@ print(${eName.toLowerCase()})
|
|
|
95
162
|
// Model-driven example fields: derive the create/update body from the op
|
|
96
163
|
// shape (opRequestShape) so the docs reference REAL writable fields, not a
|
|
97
164
|
// hardcoded "name" the entity may not have. Literals are Lua-typed by the
|
|
98
|
-
// field's canonical type; non-identifier keys use bracket syntax.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
102
|
-
if ('BOOLEAN' === k) return 'true'
|
|
103
|
-
if ('ARRAY' === k || 'OBJECT' === k) return '{}'
|
|
104
|
-
return '"example"'
|
|
105
|
-
}
|
|
106
|
-
const luaKey = (name: string): string =>
|
|
107
|
-
/^[A-Za-z_]\w*$/.test(name) ? name : `["${name}"]`
|
|
165
|
+
// field's canonical type; non-identifier keys use bracket syntax. ids are
|
|
166
|
+
// rendered separately as the match key for update/remove; a REQUIRED
|
|
167
|
+
// create id stays (the call is invalid without it).
|
|
108
168
|
const examplePairs = (opname: string): string[] => {
|
|
109
169
|
const items = opRequestShape(exampleEntity, opname).items
|
|
110
|
-
.filter((it: any) => !idNames.has(it.name)
|
|
170
|
+
.filter((it: any) => !idNames.has(it.name) ||
|
|
171
|
+
('create' === opname && !it.optional))
|
|
111
172
|
const required = items.filter((it: any) => !it.optional)
|
|
173
|
+
const optional = items.filter((it: any) => it.optional)
|
|
174
|
+
// create needs ALL required fields; update is a patch, so the required
|
|
175
|
+
// members plus a sample optional field or two suffice.
|
|
112
176
|
const chosen = 'create' === opname
|
|
113
177
|
? (required.length ? required : items.slice(0, 2))
|
|
114
|
-
:
|
|
115
|
-
return chosen.map((it: any) =>
|
|
178
|
+
: required.concat(optional).slice(0, Math.max(2, required.length))
|
|
179
|
+
return chosen.map((it: any) =>
|
|
180
|
+
`${luaKey(it.name)} = ${luaLit(it.type, 'example_' + it.name)}`)
|
|
116
181
|
}
|
|
117
182
|
|
|
118
183
|
// The id VALUE for an update/remove match: off the returned `created` record
|
|
@@ -123,7 +188,7 @@ print(${eName.toLowerCase()})
|
|
|
123
188
|
}
|
|
124
189
|
const idValueFor = (opname: string): string => (null != dataIdF && opnames.includes('create'))
|
|
125
190
|
? `created["${dataIdF}"]`
|
|
126
|
-
: luaLit(idParamType(opname))
|
|
191
|
+
: luaLit(idParamType(opname), 'example_id')
|
|
127
192
|
|
|
128
193
|
if (opnames.includes('create') || opnames.includes('update') || opnames.includes('remove')) {
|
|
129
194
|
Content(`### 4. Create, update, and remove
|
|
@@ -145,8 +210,17 @@ client:${eName}():update({ ${updatePairs.join(', ')} })
|
|
|
145
210
|
`)
|
|
146
211
|
}
|
|
147
212
|
if (opnames.includes('remove')) {
|
|
213
|
+
// Every REQUIRED remove-match key: the id (off the created record
|
|
214
|
+
// when possible) plus parent keys like page_id.
|
|
215
|
+
const removePairs = opRequestShape(exampleEntity, 'remove').items
|
|
216
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
217
|
+
.sort((a: any, b: any) =>
|
|
218
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
219
|
+
.map((it: any) => it.name === idF
|
|
220
|
+
? `${luaKey(it.name)} = ${idValueFor('remove')}`
|
|
221
|
+
: `${luaKey(it.name)} = ${luaLit(it.type, 'example_' + it.name)}`)
|
|
148
222
|
Content(`-- Remove
|
|
149
|
-
client:${eName}():remove(${
|
|
223
|
+
client:${eName}():remove(${removePairs.length ? `{ ${removePairs.join(', ')} }` : ''})
|
|
150
224
|
`)
|
|
151
225
|
}
|
|
152
226
|
Content(`\`\`\`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, File, isAuthActive, entityIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, canonKey, File, isAuthActive, 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
|
const OP_SIGNATURES: Record<string, { sig: string, returns: string, desc: string }> = {
|
|
11
29
|
load: {
|
|
12
30
|
sig: 'load(reqmatch, ctrl) -> any, err',
|
|
@@ -242,8 +260,20 @@ ${info.desc}
|
|
|
242
260
|
|
|
243
261
|
// Show example
|
|
244
262
|
if ('load' === opname || 'remove' === opname) {
|
|
263
|
+
// The id key plus every REQUIRED match key (parent path params
|
|
264
|
+
// like page_id) — the same shape the runtime resolves path
|
|
265
|
+
// params from, so the example always works.
|
|
266
|
+
const matchItems = opRequestShape(ent, opname).items
|
|
267
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
268
|
+
.sort((a: any, b: any) =>
|
|
269
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
270
|
+
const arg = 0 < matchItems.length
|
|
271
|
+
? `{ ${matchItems.map((it: any) =>
|
|
272
|
+
`${luaKey(it.name)} = ${luaLit(it.type,
|
|
273
|
+
it.name === idF ? ent.name + '_id' : it.name)}`).join(', ')} }`
|
|
274
|
+
: ''
|
|
245
275
|
Content(`\`\`\`lua
|
|
246
|
-
local result, err = client:${ent.Name}():${opname}(${
|
|
276
|
+
local result, err = client:${ent.Name}():${opname}(${arg})
|
|
247
277
|
\`\`\`
|
|
248
278
|
|
|
249
279
|
`)
|
|
@@ -256,14 +286,19 @@ local results, err = client:${ent.Name}():list()
|
|
|
256
286
|
`)
|
|
257
287
|
}
|
|
258
288
|
else if ('create' === opname) {
|
|
289
|
+
// Members come from the SAME shape the runtime validates
|
|
290
|
+
// (opRequestShape): every required member must appear — including
|
|
291
|
+
// a required id and parent keys like page_id (the --[[ type ]]
|
|
292
|
+
// placeholders mark the block as an illustration for the doc
|
|
293
|
+
// gate).
|
|
294
|
+
const createItems = opRequestShape(ent, 'create').items
|
|
295
|
+
.filter((it: any) => !it.optional)
|
|
259
296
|
Content(`\`\`\`lua
|
|
260
297
|
local result, err = client:${ent.Name}():create({
|
|
261
298
|
`)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
Content(` ${field.name} = --[[ ${canonToType(field.type, target.name)} ]],
|
|
299
|
+
createItems.map((it: any) => {
|
|
300
|
+
Content(` ${luaKey(it.name)} = --[[ ${canonToType(it.type, target.name)} ]],
|
|
265
301
|
`)
|
|
266
|
-
}
|
|
267
302
|
})
|
|
268
303
|
Content(`})
|
|
269
304
|
\`\`\`
|
|
@@ -271,10 +306,18 @@ local result, err = client:${ent.Name}():create({
|
|
|
271
306
|
`)
|
|
272
307
|
}
|
|
273
308
|
else if ('update' === opname) {
|
|
274
|
-
|
|
309
|
+
// The id key plus every REQUIRED data member — the same shape the
|
|
310
|
+
// runtime validates — then the patch-fields note.
|
|
311
|
+
const updateItems = opRequestShape(ent, 'update').items
|
|
312
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
313
|
+
.sort((a: any, b: any) =>
|
|
314
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
315
|
+
const updateLines = updateItems.map((it: any) =>
|
|
316
|
+
` ${luaKey(it.name)} = ${luaLit(it.type,
|
|
317
|
+
it.name === idF ? ent.name + '_id' : it.name)},\n`).join('')
|
|
275
318
|
Content(`\`\`\`lua
|
|
276
319
|
local result, err = client:${ent.Name}():update({
|
|
277
|
-
${
|
|
320
|
+
${updateLines} -- Fields to update
|
|
278
321
|
})
|
|
279
322
|
\`\`\`
|
|
280
323
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName, entityIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -8,6 +8,23 @@ import {
|
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
// A type-correct, executable Lua literal for a param: numeric/boolean/table
|
|
12
|
+
// params render a typed literal; strings render the quoted placeholder (the
|
|
13
|
+
// doc test EXECUTES this block, so a comment placeholder would break it).
|
|
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
|
+
|
|
11
28
|
const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
|
|
12
29
|
const { target, ctx$: { model } } = props
|
|
13
30
|
|
|
@@ -45,9 +62,21 @@ print(${eName.toLowerCase()}s)
|
|
|
45
62
|
}
|
|
46
63
|
|
|
47
64
|
if (opnames.includes('load')) {
|
|
65
|
+
// Every REQUIRED load-match key (id first, then parent path params like
|
|
66
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
67
|
+
// the example always works.
|
|
68
|
+
const loadItems = opRequestShape(exampleEntity, 'load').items
|
|
69
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
70
|
+
.sort((a: any, b: any) =>
|
|
71
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
72
|
+
const loadArg = 0 < loadItems.length
|
|
73
|
+
? `{ ${loadItems.map((it: any) =>
|
|
74
|
+
`${luaKey(it.name)} = ${luaLit(it.type,
|
|
75
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')} }`
|
|
76
|
+
: ''
|
|
48
77
|
Content(`
|
|
49
78
|
-- Load a specific ${eName.toLowerCase()}
|
|
50
|
-
local ${eName.toLowerCase()}, err = client:${eName}():load(${
|
|
79
|
+
local ${eName.toLowerCase()}, err = client:${eName}():load(${loadArg})
|
|
51
80
|
print(${eName.toLowerCase()})
|
|
52
81
|
`)
|
|
53
82
|
hasCall = true
|
|
@@ -42,7 +42,16 @@ local client = sdk.test()
|
|
|
42
42
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
43
43
|
let arg = ''
|
|
44
44
|
if (isMatchOp) {
|
|
45
|
-
|
|
45
|
+
// Every REQUIRED match key (id first, then parent path params like
|
|
46
|
+
// page_id) — the same shape the runtime resolves path params from.
|
|
47
|
+
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
48
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
49
|
+
.sort((a: any, b: any) =>
|
|
50
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
51
|
+
arg = 0 < items.length
|
|
52
|
+
? `{ ${items.map((it: any) =>
|
|
53
|
+
`${luaKey(it.name)} = ${it.name === idF ? '"test01"' : luaLit(it.type)}`).join(', ')} }`
|
|
54
|
+
: ''
|
|
46
55
|
} else if ('create' === primaryOp || 'update' === primaryOp) {
|
|
47
56
|
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
48
57
|
.filter((it: any) => it.name !== idF && it.name !== 'id')
|
|
@@ -50,7 +59,9 @@ local client = sdk.test()
|
|
|
50
59
|
const chosen = required.length ? required : items.slice(0, 3)
|
|
51
60
|
arg = `{ ${chosen.map((it: any) => `${luaKey(it.name)} = ${luaLit(it.type)}`).join(', ')} }`
|
|
52
61
|
}
|
|
53
|
-
|
|
62
|
+
// A list result is an array — name the variable accordingly.
|
|
63
|
+
const rVar = 'list' === primaryOp ? 'results' : 'result'
|
|
64
|
+
Content(`local ${rVar}, err = client:${eName}():${primaryOp}(${arg})
|
|
54
65
|
`)
|
|
55
66
|
}
|
|
56
67
|
|
|
@@ -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,19 @@ import {
|
|
|
7
7
|
} from '@voxgig/apidef'
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
// A type-correct, executable PHP literal for a param: numeric/boolean/array
|
|
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 phpLit(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
|
+
|
|
10
23
|
// Operation method spelling differs between Go and other languages — Go
|
|
11
24
|
// uses PascalCase methods with explicit ctrl arg, others use lowercase
|
|
12
25
|
// methods with optional ctrl. The op descriptions are language-agnostic.
|
|
@@ -95,11 +108,23 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
if (opnames.includes('load')) {
|
|
111
|
+
// The id key plus every REQUIRED match key (parent path params like
|
|
112
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
113
|
+
// the example always works.
|
|
114
|
+
const loadItems = opRequestShape(entity, 'load').items
|
|
115
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
116
|
+
.sort((a: any, b: any) =>
|
|
117
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
118
|
+
const loadArg = 0 < loadItems.length
|
|
119
|
+
? `[${loadItems.map((it: any) =>
|
|
120
|
+
`"${it.name}" => ${phpLit(it.type,
|
|
121
|
+
it.name === idF ? entity.name + '_id' : it.name)}`).join(', ')}]`
|
|
122
|
+
: ''
|
|
98
123
|
Content(`#### Example: Load
|
|
99
124
|
|
|
100
125
|
\`\`\`php
|
|
101
126
|
// load() returns the bare ${entity.Name} record (throws on error).
|
|
102
|
-
$${entity.name} = $client->${entity.Name}()->load(${
|
|
127
|
+
$${entity.name} = $client->${entity.Name}()->load(${loadArg});
|
|
103
128
|
\`\`\`
|
|
104
129
|
|
|
105
130
|
`)
|
|
@@ -117,16 +142,19 @@ $${entity.name}s = $client->${entity.Name}()->list();
|
|
|
117
142
|
}
|
|
118
143
|
|
|
119
144
|
if (opnames.includes('create')) {
|
|
145
|
+
// Members come from the SAME shape the runtime validates
|
|
146
|
+
// (opRequestShape): every required member must appear — including a
|
|
147
|
+
// required id and parent keys like page_id.
|
|
148
|
+
const createItems = opRequestShape(entity, 'create').items
|
|
149
|
+
.filter((it: any) => !it.optional)
|
|
120
150
|
Content(`#### Example: Create
|
|
121
151
|
|
|
122
152
|
\`\`\`php
|
|
123
153
|
$${entity.name} = $client->${entity.Name}()->create([
|
|
124
154
|
`)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
Content(` "${field.name}" => null, // ${canonToType(field.type, target.name)}
|
|
155
|
+
createItems.map((it: any) => {
|
|
156
|
+
Content(` "${it.name}" => null, // ${canonToType(it.type, target.name)}
|
|
128
157
|
`)
|
|
129
|
-
}
|
|
130
158
|
})
|
|
131
159
|
Content(`]);
|
|
132
160
|
\`\`\`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField, entityOps } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -14,8 +14,16 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
|
14
14
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
15
15
|
|
|
16
16
|
const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
|
|
17
|
+
|
|
18
|
+
// Find a nested entity if available: one with a parent chain
|
|
19
|
+
// (relations.ancestors), an active load op, and a required non-id load
|
|
20
|
+
// param to demonstrate (the parent key, e.g. page_id).
|
|
17
21
|
const nestedEntity = Object.values(entity).find((e: any) =>
|
|
18
|
-
e.active !== false &&
|
|
22
|
+
e.active !== false &&
|
|
23
|
+
e.relations && e.relations.ancestors && 0 < e.relations.ancestors.length &&
|
|
24
|
+
entityOps(e).includes('load') &&
|
|
25
|
+
opRequestShape(e, 'load').items.some((it: any) =>
|
|
26
|
+
!it.optional && it.name !== entityIdField(e))
|
|
19
27
|
) as any
|
|
20
28
|
|
|
21
29
|
const ctor = isAuthActive(model)
|
|
@@ -36,7 +44,7 @@ $client = ${ctor};
|
|
|
36
44
|
if (exampleEntity) {
|
|
37
45
|
const eName = nom(exampleEntity, 'Name')
|
|
38
46
|
const article = /^[aeiou]/i.test(eName) ? "an" : "a"
|
|
39
|
-
const opnames =
|
|
47
|
+
const opnames = entityOps(exampleEntity)
|
|
40
48
|
// Model-driven id key: `idF` is the MATCH key (null when none). `dataIdF` is
|
|
41
49
|
// the id on the RETURNED record's data type — an entity can key its match on
|
|
42
50
|
// an id it does not carry as data, so `["id"]` off a returned record must be
|
|
@@ -44,6 +52,18 @@ $client = ${ctor};
|
|
|
44
52
|
const idF = entityIdField(exampleEntity)
|
|
45
53
|
const dataIdF = entityDataIdField(exampleEntity)
|
|
46
54
|
|
|
55
|
+
// A type-correct, executable PHP literal for a param: numeric/boolean/
|
|
56
|
+
// array params render a typed literal; strings render the quoted
|
|
57
|
+
// placeholder (the doc test EXECUTES these blocks, so a comment
|
|
58
|
+
// placeholder would not parse).
|
|
59
|
+
const phpLit = (type: any, placeholder: string = 'example'): string => {
|
|
60
|
+
const k = canonKey(type)
|
|
61
|
+
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
62
|
+
if ('BOOLEAN' === k) return 'true'
|
|
63
|
+
if ('ARRAY' === k || 'OBJECT' === k) return '[]'
|
|
64
|
+
return `"${placeholder}"`
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
// Model-driven display field: the entity's first non-id string field
|
|
48
68
|
// (falling back to any non-id field), so the list example prints a real
|
|
49
69
|
// column instead of a hardcoded "name" the entity may not have.
|
|
@@ -74,13 +94,62 @@ try {
|
|
|
74
94
|
`)
|
|
75
95
|
}
|
|
76
96
|
|
|
77
|
-
if (
|
|
97
|
+
if (nestedEntity) {
|
|
98
|
+
const neName = nom(nestedEntity, 'Name')
|
|
99
|
+
const neArticle = /^[aeiou]/i.test(neName) ? "an" : "a"
|
|
100
|
+
const neVar = neName.toLowerCase()
|
|
101
|
+
|
|
102
|
+
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
103
|
+
// the runtime resolves path params from, so the example always works.
|
|
104
|
+
// Parent keys (e.g. page_id) first, the entity's own id last.
|
|
105
|
+
const neIdF = entityIdField(nestedEntity)
|
|
106
|
+
const neRequired = opRequestShape(nestedEntity, 'load').items
|
|
107
|
+
.filter((it: any) => !it.optional)
|
|
108
|
+
.sort((a: any, b: any) =>
|
|
109
|
+
(a.name === neIdF ? 1 : 0) - (b.name === neIdF ? 1 : 0))
|
|
110
|
+
const parentItem = neRequired.find((it: any) => it.name !== neIdF) as any
|
|
111
|
+
const parentParam = parentItem && parentItem.name
|
|
112
|
+
const parentName = parentParam ? parentParam.replace(/_id$/, '') : 'its parent'
|
|
113
|
+
const neMatch = neRequired.map((it: any) =>
|
|
114
|
+
`"${it.name}" => ${phpLit(it.type,
|
|
115
|
+
it.name === neIdF ? 'example_id' : 'example_' + it.name)}`)
|
|
116
|
+
|
|
117
|
+
Content(`### 3. Load ${neArticle} ${neName.toLowerCase()}
|
|
118
|
+
|
|
119
|
+
${neName} is nested under ${parentName}, so provide the \`${parentParam}\`.
|
|
120
|
+
|
|
121
|
+
\`\`\`php
|
|
122
|
+
try {
|
|
123
|
+
// load() returns the bare ${neName} record (throws on error).
|
|
124
|
+
$${neVar} = $client->${neName}()->load([${neMatch.join(', ')}]);
|
|
125
|
+
print_r($${neVar});
|
|
126
|
+
} catch (\\Throwable $err) {
|
|
127
|
+
echo "Error: " . $err->getMessage();
|
|
128
|
+
}
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
`)
|
|
132
|
+
}
|
|
133
|
+
else if (opnames.includes('load')) {
|
|
134
|
+
// Every REQUIRED load-match key (id first, then parent path params like
|
|
135
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
136
|
+
// the example always works.
|
|
137
|
+
const loadRequired = opRequestShape(exampleEntity, 'load').items
|
|
138
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
139
|
+
.sort((a: any, b: any) =>
|
|
140
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
141
|
+
const loadArg = 0 < loadRequired.length
|
|
142
|
+
? `[${loadRequired.map((it: any) =>
|
|
143
|
+
`"${it.name}" => ${phpLit(it.type,
|
|
144
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')}]`
|
|
145
|
+
: ''
|
|
146
|
+
|
|
78
147
|
Content(`### 3. Load ${article} ${eName.toLowerCase()}
|
|
79
148
|
|
|
80
149
|
\`\`\`php
|
|
81
150
|
try {
|
|
82
151
|
// load() returns the bare ${eName} record (throws on error).
|
|
83
|
-
$${eName.toLowerCase()} = $client->${eName}()->load(${
|
|
152
|
+
$${eName.toLowerCase()} = $client->${eName}()->load(${loadArg});
|
|
84
153
|
print_r($${eName.toLowerCase()});
|
|
85
154
|
} catch (\\Throwable $err) {
|
|
86
155
|
echo "Error: " . $err->getMessage();
|
|
@@ -93,23 +162,21 @@ try {
|
|
|
93
162
|
// Model-driven example fields: derive the create/update body from the op
|
|
94
163
|
// shape (opRequestShape) so the docs reference REAL writable fields, not a
|
|
95
164
|
// hardcoded "name" the entity may not have. Literals are PHP-typed by the
|
|
96
|
-
// field's canonical type.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const k = canonKey(type)
|
|
100
|
-
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
101
|
-
if ('BOOLEAN' === k) return 'true'
|
|
102
|
-
if ('ARRAY' === k || 'OBJECT' === k) return '[]'
|
|
103
|
-
return '"example"'
|
|
104
|
-
}
|
|
165
|
+
// field's canonical type. ids are rendered separately as the match key for
|
|
166
|
+
// update/remove; a REQUIRED create id stays (the call is invalid without
|
|
167
|
+
// it).
|
|
105
168
|
const examplePairs = (opname: string): string[] => {
|
|
106
169
|
const items = opRequestShape(exampleEntity, opname).items
|
|
107
|
-
.filter((it: any) => it.name !==
|
|
170
|
+
.filter((it: any) => (it.name !== idF && it.name !== 'id') ||
|
|
171
|
+
('create' === opname && !it.optional))
|
|
108
172
|
const required = items.filter((it: any) => !it.optional)
|
|
173
|
+
const optional = items.filter((it: any) => it.optional)
|
|
174
|
+
// create needs ALL required fields; update is a patch, so the required
|
|
175
|
+
// members plus a sample optional field or two suffice.
|
|
109
176
|
const chosen = 'create' === opname
|
|
110
177
|
? (required.length ? required : items.slice(0, 2))
|
|
111
|
-
:
|
|
112
|
-
return chosen.map((it: any) => `"${it.name}" => ${phpLit(it.type)}`)
|
|
178
|
+
: required.concat(optional).slice(0, Math.max(2, required.length))
|
|
179
|
+
return chosen.map((it: any) => `"${it.name}" => ${phpLit(it.type, 'example_' + it.name)}`)
|
|
113
180
|
}
|
|
114
181
|
|
|
115
182
|
// The id VALUE for an update/remove match: off the returned `$created`
|
|
@@ -121,7 +188,7 @@ try {
|
|
|
121
188
|
}
|
|
122
189
|
const idValueFor = (opname: string): string => (null != dataIdF && opnames.includes('create'))
|
|
123
190
|
? `$created["${dataIdF}"]`
|
|
124
|
-
: phpLit(idParamType(opname))
|
|
191
|
+
: phpLit(idParamType(opname), 'example_id')
|
|
125
192
|
|
|
126
193
|
if (opnames.includes('create') || opnames.includes('update') || opnames.includes('remove')) {
|
|
127
194
|
Content(`### 4. Create, update, and remove
|
|
@@ -143,8 +210,17 @@ $client->${eName}()->update([${updatePairs.join(', ')}]);
|
|
|
143
210
|
`)
|
|
144
211
|
}
|
|
145
212
|
if (opnames.includes('remove')) {
|
|
213
|
+
// Every REQUIRED remove-match key: the id (off the created record
|
|
214
|
+
// when possible) plus parent keys like page_id.
|
|
215
|
+
const removePairs = opRequestShape(exampleEntity, 'remove').items
|
|
216
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
217
|
+
.sort((a: any, b: any) =>
|
|
218
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
219
|
+
.map((it: any) => it.name === idF
|
|
220
|
+
? `"${it.name}" => ${idValueFor('remove')}`
|
|
221
|
+
: `"${it.name}" => ${phpLit(it.type, 'example_' + it.name)}`)
|
|
146
222
|
Content(`// Remove
|
|
147
|
-
$client->${eName}()->remove(${
|
|
223
|
+
$client->${eName}()->remove(${removePairs.length ? `[${removePairs.join(', ')}]` : ''});
|
|
148
224
|
`)
|
|
149
225
|
}
|
|
150
226
|
Content(`\`\`\`
|