@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, 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,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
|
const OP_SIGNATURES: Record<string, { sig: string, returns: string, desc: string }> = {
|
|
11
24
|
load: {
|
|
12
25
|
sig: 'load(array $reqmatch, ?array $ctrl = null): mixed',
|
|
@@ -244,8 +257,20 @@ ${info.desc}
|
|
|
244
257
|
|
|
245
258
|
// Show example
|
|
246
259
|
if ('load' === opname || 'remove' === opname) {
|
|
260
|
+
// The id key plus every REQUIRED match key (parent path params
|
|
261
|
+
// like page_id) — the same shape the runtime resolves path
|
|
262
|
+
// params from, so the example always works.
|
|
263
|
+
const matchItems = opRequestShape(ent, opname).items
|
|
264
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
265
|
+
.sort((a: any, b: any) =>
|
|
266
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
267
|
+
const arg = 0 < matchItems.length
|
|
268
|
+
? `[${matchItems.map((it: any) =>
|
|
269
|
+
`"${it.name}" => ${phpLit(it.type,
|
|
270
|
+
it.name === idF ? ent.name + '_id' : it.name)}`).join(', ')}]`
|
|
271
|
+
: ''
|
|
247
272
|
Content(`\`\`\`php
|
|
248
|
-
$result = $client->${ent.Name}()->${opname}(${
|
|
273
|
+
$result = $client->${ent.Name}()->${opname}(${arg});
|
|
249
274
|
\`\`\`
|
|
250
275
|
|
|
251
276
|
`)
|
|
@@ -258,14 +283,17 @@ $results = $client->${ent.Name}()->list();
|
|
|
258
283
|
`)
|
|
259
284
|
}
|
|
260
285
|
else if ('create' === opname) {
|
|
286
|
+
// Members come from the SAME shape the runtime validates
|
|
287
|
+
// (opRequestShape): every required member must appear — including
|
|
288
|
+
// a required id and parent keys like page_id.
|
|
289
|
+
const createItems = opRequestShape(ent, 'create').items
|
|
290
|
+
.filter((it: any) => !it.optional)
|
|
261
291
|
Content(`\`\`\`php
|
|
262
292
|
$result = $client->${ent.Name}()->create([
|
|
263
293
|
`)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
Content(` "${field.name}" => null, // ${canonToType(field.type, target.name)}
|
|
294
|
+
createItems.map((it: any) => {
|
|
295
|
+
Content(` "${it.name}" => null, // ${canonToType(it.type, target.name)}
|
|
267
296
|
`)
|
|
268
|
-
}
|
|
269
297
|
})
|
|
270
298
|
Content(`]);
|
|
271
299
|
\`\`\`
|
|
@@ -273,10 +301,18 @@ $result = $client->${ent.Name}()->create([
|
|
|
273
301
|
`)
|
|
274
302
|
}
|
|
275
303
|
else if ('update' === opname) {
|
|
276
|
-
|
|
304
|
+
// The id key plus every REQUIRED data member — the same shape the
|
|
305
|
+
// runtime validates — then the patch-fields note.
|
|
306
|
+
const updateItems = opRequestShape(ent, 'update').items
|
|
307
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
308
|
+
.sort((a: any, b: any) =>
|
|
309
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
310
|
+
const updateLines = updateItems.map((it: any) =>
|
|
311
|
+
` "${it.name}" => ${phpLit(it.type,
|
|
312
|
+
it.name === idF ? ent.name + '_id' : it.name)},\n`).join('')
|
|
277
313
|
Content(`\`\`\`php
|
|
278
314
|
$result = $client->${ent.Name}()->update([
|
|
279
|
-
${
|
|
315
|
+
${updateLines} // Fields to update
|
|
280
316
|
]);
|
|
281
317
|
\`\`\`
|
|
282
318
|
|
|
@@ -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,18 @@ import {
|
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
// A type-correct, executable PHP literal for a param: numeric/boolean/array
|
|
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 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
|
+
|
|
11
23
|
const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
|
|
12
24
|
const { target, ctx$: { model } } = props
|
|
13
25
|
|
|
@@ -46,9 +58,21 @@ print_r($${eName.toLowerCase()}s);
|
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
if (opnames.includes('load')) {
|
|
61
|
+
// Every REQUIRED load-match key (id first, then parent path params like
|
|
62
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
63
|
+
// the example always works.
|
|
64
|
+
const loadItems = opRequestShape(exampleEntity, 'load').items
|
|
65
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
66
|
+
.sort((a: any, b: any) =>
|
|
67
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
68
|
+
const loadArg = 0 < loadItems.length
|
|
69
|
+
? `[${loadItems.map((it: any) =>
|
|
70
|
+
`"${it.name}" => ${phpLit(it.type,
|
|
71
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')}]`
|
|
72
|
+
: ''
|
|
49
73
|
Content(`
|
|
50
74
|
// Load a specific ${eName.toLowerCase()} (returns the bare record; throws on error)
|
|
51
|
-
$${eName.toLowerCase()} = $client->${eName}()->load(${
|
|
75
|
+
$${eName.toLowerCase()} = $client->${eName}()->load(${loadArg});
|
|
52
76
|
print_r($${eName.toLowerCase()});
|
|
53
77
|
`)
|
|
54
78
|
hasCall = true
|
|
@@ -37,7 +37,16 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
37
37
|
const recBody = idF ? `["${idF}" => "test01"]` : '[]'
|
|
38
38
|
let callArg = ''
|
|
39
39
|
if (isMatchOp) {
|
|
40
|
-
|
|
40
|
+
// Every REQUIRED match key (id first, then parent path params like
|
|
41
|
+
// page_id) — the same shape the runtime resolves path params from.
|
|
42
|
+
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
43
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
44
|
+
.sort((a: any, b: any) =>
|
|
45
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
46
|
+
callArg = 0 < items.length
|
|
47
|
+
? `[${items.map((it: any) =>
|
|
48
|
+
`"${it.name}" => ${it.name === idF ? '"test01"' : phpLit(it.type)}`).join(', ')}]`
|
|
49
|
+
: ''
|
|
41
50
|
} else if ('create' === primaryOp || 'update' === primaryOp) {
|
|
42
51
|
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
43
52
|
.filter((it: any) => it.name !== idF && it.name !== 'id')
|
|
@@ -45,12 +54,14 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
45
54
|
const chosen = required.length ? required : items.slice(0, 3)
|
|
46
55
|
callArg = `[${chosen.map((it: any) => `"${it.name}" => ${phpLit(it.type)}`).join(', ')}]`
|
|
47
56
|
}
|
|
57
|
+
// A list result is an array — name the variable accordingly.
|
|
58
|
+
const eVar = ename + ('list' === primaryOp ? 's' : '')
|
|
48
59
|
Content(`\`\`\`php
|
|
49
60
|
// Seed fixture data so offline calls resolve without a live server.
|
|
50
61
|
$client = ${model.const.Name}SDK::test([
|
|
51
62
|
"entity" => ["${ename}" => ["test01" => ${recBody}]],
|
|
52
63
|
]);
|
|
53
|
-
$${
|
|
64
|
+
$${eVar} = $client->${eName}()->${primaryOp}(${callArg});
|
|
54
65
|
\`\`\`
|
|
55
66
|
`)
|
|
56
67
|
} else {
|
|
@@ -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, canonKey, entityIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, canonKey, entityIdField, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
// type. The create example is EXECUTED by the doc test (the body is
|
|
12
12
|
// JSON-serialised), so an Ellipsis (`...`) placeholder would raise
|
|
13
13
|
// "Object of type ellipsis is not JSON serializable" — use a real value.
|
|
14
|
-
|
|
14
|
+
// Strings render the quoted placeholder.
|
|
15
|
+
function pyLit(type: any, placeholder: string = 'example'): string {
|
|
15
16
|
const k = canonKey(type)
|
|
16
17
|
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
17
18
|
if ('BOOLEAN' === k) return 'True'
|
|
18
19
|
if ('ARRAY' === k) return '[]'
|
|
19
20
|
if ('OBJECT' === k) return '{}'
|
|
20
|
-
return
|
|
21
|
+
return `"${placeholder}"`
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
|
|
@@ -109,10 +110,22 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
if (opnames.includes('load')) {
|
|
113
|
+
// The id key plus every REQUIRED match key (parent path params like
|
|
114
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
115
|
+
// the example always works.
|
|
116
|
+
const loadItems = opRequestShape(entity, 'load').items
|
|
117
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
118
|
+
.sort((a: any, b: any) =>
|
|
119
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
120
|
+
const loadArg = 0 < loadItems.length
|
|
121
|
+
? `{${loadItems.map((it: any) =>
|
|
122
|
+
`"${it.name}": ${pyLit(it.type,
|
|
123
|
+
it.name === idF ? entity.name + '_id' : it.name)}`).join(', ')}}`
|
|
124
|
+
: ''
|
|
112
125
|
Content(`#### Example: Load
|
|
113
126
|
|
|
114
127
|
\`\`\`python
|
|
115
|
-
${entity.name} = client.${entity.Name}().load(${
|
|
128
|
+
${entity.name} = client.${entity.Name}().load(${loadArg})
|
|
116
129
|
\`\`\`
|
|
117
130
|
|
|
118
131
|
`)
|
|
@@ -129,16 +142,21 @@ ${entity.name}s = client.${entity.Name}().list()
|
|
|
129
142
|
}
|
|
130
143
|
|
|
131
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 — with a real, executable
|
|
148
|
+
// literal (the doc test RUNS this block, so a comment placeholder
|
|
149
|
+
// would break it).
|
|
150
|
+
const createItems = opRequestShape(entity, 'create').items
|
|
151
|
+
.filter((it: any) => !it.optional)
|
|
132
152
|
Content(`#### Example: Create
|
|
133
153
|
|
|
134
154
|
\`\`\`python
|
|
135
155
|
${entity.name} = client.${entity.Name}().create({
|
|
136
156
|
`)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Content(` "${field.name}": ${pyLit(field.type)}, # ${canonToType(field.type, target.name)}
|
|
157
|
+
createItems.map((it: any) => {
|
|
158
|
+
Content(` "${it.name}": ${pyLit(it.type, 'example_' + it.name)}, # ${canonToType(it.type, target.name)}
|
|
140
159
|
`)
|
|
141
|
-
}
|
|
142
160
|
})
|
|
143
161
|
Content(`})
|
|
144
162
|
\`\`\`
|
|
@@ -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 authActive = isAuthActive(model)
|
|
@@ -37,7 +45,7 @@ client = ${ctor}
|
|
|
37
45
|
if (exampleEntity) {
|
|
38
46
|
const eName = nom(exampleEntity, 'Name')
|
|
39
47
|
const article = /^[aeiou]/i.test(eName) ? 'an' : 'a'
|
|
40
|
-
const opnames =
|
|
48
|
+
const opnames = entityOps(exampleEntity)
|
|
41
49
|
// Model-driven id key: `idF` is the entity's id-like MATCH field name, or
|
|
42
50
|
// null when it has none (a response-wrapped spec). `dataIdF` is the id on
|
|
43
51
|
// the RETURNED record's data type — an entity can key its match on an id it
|
|
@@ -46,6 +54,19 @@ client = ${ctor}
|
|
|
46
54
|
const idF = entityIdField(exampleEntity)
|
|
47
55
|
const dataIdF = entityDataIdField(exampleEntity)
|
|
48
56
|
|
|
57
|
+
// A type-correct, executable Python literal for a param: numeric/boolean/
|
|
58
|
+
// array/object params render a typed literal; strings render the quoted
|
|
59
|
+
// placeholder (the doc test EXECUTES these blocks, so a comment
|
|
60
|
+
// placeholder would not parse).
|
|
61
|
+
const pyLit = (type: any, placeholder: string = 'example'): string => {
|
|
62
|
+
const k = canonKey(type)
|
|
63
|
+
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
64
|
+
if ('BOOLEAN' === k) return 'True'
|
|
65
|
+
if ('ARRAY' === k) return '[]'
|
|
66
|
+
if ('OBJECT' === k) return '{}'
|
|
67
|
+
return `"${placeholder}"`
|
|
68
|
+
}
|
|
69
|
+
|
|
49
70
|
if (opnames.includes('list')) {
|
|
50
71
|
Content(`### 2. List ${eName.toLowerCase()} records
|
|
51
72
|
|
|
@@ -64,14 +85,62 @@ except Exception as err:
|
|
|
64
85
|
`)
|
|
65
86
|
}
|
|
66
87
|
|
|
67
|
-
if (
|
|
88
|
+
if (nestedEntity) {
|
|
89
|
+
const neName = nom(nestedEntity, 'Name')
|
|
90
|
+
const neArticle = /^[aeiou]/i.test(neName) ? 'an' : 'a'
|
|
91
|
+
const neVar = neName.toLowerCase()
|
|
92
|
+
|
|
93
|
+
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
94
|
+
// the runtime resolves path params from, so the example always works.
|
|
95
|
+
// Parent keys (e.g. page_id) first, the entity's own id last.
|
|
96
|
+
const neIdF = entityIdField(nestedEntity)
|
|
97
|
+
const neRequired = opRequestShape(nestedEntity, 'load').items
|
|
98
|
+
.filter((it: any) => !it.optional)
|
|
99
|
+
.sort((a: any, b: any) =>
|
|
100
|
+
(a.name === neIdF ? 1 : 0) - (b.name === neIdF ? 1 : 0))
|
|
101
|
+
const parentItem = neRequired.find((it: any) => it.name !== neIdF) as any
|
|
102
|
+
const parentParam = parentItem && parentItem.name
|
|
103
|
+
const parentName = parentParam ? parentParam.replace(/_id$/, '') : 'its parent'
|
|
104
|
+
const neMatch = neRequired.map((it: any) =>
|
|
105
|
+
`"${it.name}": ${pyLit(it.type,
|
|
106
|
+
it.name === neIdF ? 'example_id' : 'example_' + it.name)}`)
|
|
107
|
+
|
|
108
|
+
Content(`### 3. Load ${neArticle} ${neName.toLowerCase()}
|
|
109
|
+
|
|
110
|
+
${neName} is nested under ${parentName}, so provide the \`${parentParam}\`.
|
|
111
|
+
\`load()\` returns the bare record (a \`dict\`) and raises on error.
|
|
112
|
+
|
|
113
|
+
\`\`\`python
|
|
114
|
+
try:
|
|
115
|
+
${neVar} = client.${neName}().load({${neMatch.join(', ')}})
|
|
116
|
+
print(${neVar})
|
|
117
|
+
except Exception as err:
|
|
118
|
+
print(f"load failed: {err}")
|
|
119
|
+
\`\`\`
|
|
120
|
+
|
|
121
|
+
`)
|
|
122
|
+
}
|
|
123
|
+
else if (opnames.includes('load')) {
|
|
124
|
+
// Every REQUIRED load-match key (id first, then parent path params like
|
|
125
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
126
|
+
// the example always works.
|
|
127
|
+
const loadRequired = opRequestShape(exampleEntity, 'load').items
|
|
128
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
129
|
+
.sort((a: any, b: any) =>
|
|
130
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
131
|
+
const loadArg = 0 < loadRequired.length
|
|
132
|
+
? `{${loadRequired.map((it: any) =>
|
|
133
|
+
`"${it.name}": ${pyLit(it.type,
|
|
134
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')}}`
|
|
135
|
+
: ''
|
|
136
|
+
|
|
68
137
|
Content(`### 3. Load ${article} ${eName.toLowerCase()}
|
|
69
138
|
|
|
70
139
|
\`load()\` returns the bare record (a \`dict\`) and raises on error.
|
|
71
140
|
|
|
72
141
|
\`\`\`python
|
|
73
142
|
try:
|
|
74
|
-
${eName.toLowerCase()} = client.${eName}().load(${
|
|
143
|
+
${eName.toLowerCase()} = client.${eName}().load(${loadArg})
|
|
75
144
|
print(${eName.toLowerCase()})
|
|
76
145
|
except Exception as err:
|
|
77
146
|
print(f"load failed: {err}")
|
|
@@ -83,25 +152,21 @@ except Exception as err:
|
|
|
83
152
|
// Model-driven example fields: derive the create/update body from the op
|
|
84
153
|
// shape (opRequestShape) so the docs reference REAL writable fields, not a
|
|
85
154
|
// hardcoded "name" the entity may not have. Literals are Python-typed by
|
|
86
|
-
// the field's canonical type.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const k = canonKey(type)
|
|
90
|
-
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
91
|
-
if ('BOOLEAN' === k) return 'True'
|
|
92
|
-
if ('ARRAY' === k) return '[]'
|
|
93
|
-
if ('OBJECT' === k) return '{}'
|
|
94
|
-
return '"example"'
|
|
95
|
-
}
|
|
155
|
+
// the field's canonical type. ids are rendered separately as the match key
|
|
156
|
+
// for update/remove; a REQUIRED create id stays (the call is invalid
|
|
157
|
+
// without it).
|
|
96
158
|
const examplePairs = (opname: string): string[] => {
|
|
97
159
|
const items = opRequestShape(exampleEntity, opname).items
|
|
98
|
-
.filter((it: any) => it.name !==
|
|
160
|
+
.filter((it: any) => (it.name !== idF && it.name !== 'id') ||
|
|
161
|
+
('create' === opname && !it.optional))
|
|
99
162
|
const required = items.filter((it: any) => !it.optional)
|
|
100
|
-
|
|
163
|
+
const optional = items.filter((it: any) => it.optional)
|
|
164
|
+
// create needs ALL required fields; update is a patch, so the required
|
|
165
|
+
// members plus a sample optional field or two suffice.
|
|
101
166
|
const chosen = 'create' === opname
|
|
102
167
|
? (required.length ? required : items.slice(0, 2))
|
|
103
|
-
:
|
|
104
|
-
return chosen.map((it: any) => `"${it.name}": ${pyLit(it.type)}`)
|
|
168
|
+
: required.concat(optional).slice(0, Math.max(2, required.length))
|
|
169
|
+
return chosen.map((it: any) => `"${it.name}": ${pyLit(it.type, 'example_' + it.name)}`)
|
|
105
170
|
}
|
|
106
171
|
|
|
107
172
|
// The id VALUE for an update/remove match: taken off the returned `created`
|
|
@@ -113,7 +178,7 @@ except Exception as err:
|
|
|
113
178
|
}
|
|
114
179
|
const idValueFor = (opname: string): string => (null != dataIdF && opnames.includes('create'))
|
|
115
180
|
? `created["${dataIdF}"]`
|
|
116
|
-
: pyLit(idParamType(opname))
|
|
181
|
+
: pyLit(idParamType(opname), 'example_id')
|
|
117
182
|
|
|
118
183
|
if (opnames.includes('create') || opnames.includes('update') || opnames.includes('remove')) {
|
|
119
184
|
Content(`### 4. Create, update, and remove
|
|
@@ -135,8 +200,17 @@ client.${eName}().update({${updatePairs.join(', ')}})
|
|
|
135
200
|
`)
|
|
136
201
|
}
|
|
137
202
|
if (opnames.includes('remove')) {
|
|
203
|
+
// Every REQUIRED remove-match key: the id (off the created record
|
|
204
|
+
// when possible) plus parent keys like page_id.
|
|
205
|
+
const removePairs = opRequestShape(exampleEntity, 'remove').items
|
|
206
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
207
|
+
.sort((a: any, b: any) =>
|
|
208
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
209
|
+
.map((it: any) => it.name === idF
|
|
210
|
+
? `"${it.name}": ${idValueFor('remove')}`
|
|
211
|
+
: `"${it.name}": ${pyLit(it.type, 'example_' + it.name)}`)
|
|
138
212
|
Content(`# Remove
|
|
139
|
-
client.${eName}().remove(${
|
|
213
|
+
client.${eName}().remove(${removePairs.length ? `{${removePairs.join(', ')}}` : ''})
|
|
140
214
|
`)
|
|
141
215
|
}
|
|
142
216
|
Content(`\`\`\`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, canonKey, 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,
|
|
@@ -8,14 +8,15 @@ import {
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
// A type-correct, JSON-serialisable Python literal — never an Ellipsis (`...`),
|
|
11
|
-
// which is not JSON-serialisable when a create body is executed by the doc
|
|
12
|
-
|
|
11
|
+
// which is not JSON-serialisable when a create body is executed by the doc
|
|
12
|
+
// test. Strings render the quoted placeholder.
|
|
13
|
+
function pyLit(type: any, placeholder: string = 'example'): string {
|
|
13
14
|
const k = canonKey(type)
|
|
14
15
|
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
15
16
|
if ('BOOLEAN' === k) return 'True'
|
|
16
17
|
if ('ARRAY' === k) return '[]'
|
|
17
18
|
if ('OBJECT' === k) return '{}'
|
|
18
|
-
return
|
|
19
|
+
return `"${placeholder}"`
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
|
|
@@ -252,8 +253,20 @@ ${info.desc}
|
|
|
252
253
|
// Show example. Entity ops return the bare result and raise on
|
|
253
254
|
// error; direct() is the only method that returns a result dict.
|
|
254
255
|
if ('load' === opname || 'remove' === opname) {
|
|
256
|
+
// The id key plus every REQUIRED match key (parent path params
|
|
257
|
+
// like page_id) — the same shape the runtime resolves path
|
|
258
|
+
// params from, so the example always works.
|
|
259
|
+
const matchItems = opRequestShape(ent, opname).items
|
|
260
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
261
|
+
.sort((a: any, b: any) =>
|
|
262
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
263
|
+
const arg = 0 < matchItems.length
|
|
264
|
+
? `{${matchItems.map((it: any) =>
|
|
265
|
+
`"${it.name}": ${pyLit(it.type,
|
|
266
|
+
it.name === idF ? ent.name + '_id' : it.name)}`).join(', ')}}`
|
|
267
|
+
: ''
|
|
255
268
|
Content(`\`\`\`python
|
|
256
|
-
result = client.${ent.Name}().${opname}(${
|
|
269
|
+
result = client.${ent.Name}().${opname}(${arg})
|
|
257
270
|
\`\`\`
|
|
258
271
|
|
|
259
272
|
`)
|
|
@@ -268,14 +281,19 @@ for ${ent.name} in results:
|
|
|
268
281
|
`)
|
|
269
282
|
}
|
|
270
283
|
else if ('create' === opname) {
|
|
284
|
+
// Members come from the SAME shape the runtime validates
|
|
285
|
+
// (opRequestShape): every required member must appear — including
|
|
286
|
+
// a required id and parent keys like page_id — with a real,
|
|
287
|
+
// executable literal (the doc test RUNS this block, so a comment
|
|
288
|
+
// placeholder would break it).
|
|
289
|
+
const createItems = opRequestShape(ent, 'create').items
|
|
290
|
+
.filter((it: any) => !it.optional)
|
|
271
291
|
Content(`\`\`\`python
|
|
272
292
|
result = client.${ent.Name}().create({
|
|
273
293
|
`)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
Content(` "${field.name}": ${pyLit(field.type)}, # ${canonToType(field.type, target.name)}
|
|
294
|
+
createItems.map((it: any) => {
|
|
295
|
+
Content(` "${it.name}": ${pyLit(it.type, 'example_' + it.name)}, # ${canonToType(it.type, target.name)}
|
|
277
296
|
`)
|
|
278
|
-
}
|
|
279
297
|
})
|
|
280
298
|
Content(`})
|
|
281
299
|
\`\`\`
|
|
@@ -283,10 +301,18 @@ result = client.${ent.Name}().create({
|
|
|
283
301
|
`)
|
|
284
302
|
}
|
|
285
303
|
else if ('update' === opname) {
|
|
286
|
-
|
|
304
|
+
// The id key plus every REQUIRED data member — the same shape the
|
|
305
|
+
// runtime validates — then the patch-fields note.
|
|
306
|
+
const updateItems = opRequestShape(ent, 'update').items
|
|
307
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
308
|
+
.sort((a: any, b: any) =>
|
|
309
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
310
|
+
const updateLines = updateItems.map((it: any) =>
|
|
311
|
+
` "${it.name}": ${pyLit(it.type,
|
|
312
|
+
it.name === idF ? ent.name + '_id' : it.name)},\n`).join('')
|
|
287
313
|
Content(`\`\`\`python
|
|
288
314
|
result = client.${ent.Name}().update({
|
|
289
|
-
${
|
|
315
|
+
${updateLines} # Fields to update
|
|
290
316
|
})
|
|
291
317
|
\`\`\`
|
|
292
318
|
|
|
@@ -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,20 @@ import {
|
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
// A type-correct, executable Python literal for a param: numeric/boolean/
|
|
12
|
+
// array/object params render a typed literal; strings render the quoted
|
|
13
|
+
// placeholder (the doc test EXECUTES this block, so a comment placeholder
|
|
14
|
+
// would break it).
|
|
15
|
+
function pyLit(type: any, placeholder: string = 'example'): string {
|
|
16
|
+
const k = canonKey(type)
|
|
17
|
+
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
18
|
+
if ('BOOLEAN' === k) return 'True'
|
|
19
|
+
if ('ARRAY' === k) return '[]'
|
|
20
|
+
if ('OBJECT' === k) return '{}'
|
|
21
|
+
return `"${placeholder}"`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
11
25
|
const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
|
|
12
26
|
const { target, ctx$: { model } } = props
|
|
13
27
|
|
|
@@ -47,9 +61,21 @@ for ${eName.toLowerCase()} in ${eName.toLowerCase()}s:
|
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
if (opnames.includes('load')) {
|
|
64
|
+
// Every REQUIRED load-match key (id first, then parent path params like
|
|
65
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
66
|
+
// the example always works.
|
|
67
|
+
const loadItems = opRequestShape(exampleEntity, 'load').items
|
|
68
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
69
|
+
.sort((a: any, b: any) =>
|
|
70
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
71
|
+
const loadArg = 0 < loadItems.length
|
|
72
|
+
? `{${loadItems.map((it: any) =>
|
|
73
|
+
`"${it.name}": ${pyLit(it.type,
|
|
74
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')}}`
|
|
75
|
+
: ''
|
|
50
76
|
Content(`
|
|
51
77
|
# Load a specific ${eName.toLowerCase()} (returns the record, raises on error)
|
|
52
|
-
${eName.toLowerCase()} = client.${eName}().load(${
|
|
78
|
+
${eName.toLowerCase()} = client.${eName}().load(${loadArg})
|
|
53
79
|
print(${eName.toLowerCase()})
|
|
54
80
|
`)
|
|
55
81
|
hasCall = true
|
|
@@ -39,7 +39,16 @@ client = ${model.const.Name}SDK.test()
|
|
|
39
39
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
40
40
|
let arg = ''
|
|
41
41
|
if (isMatchOp) {
|
|
42
|
-
|
|
42
|
+
// Every REQUIRED match key (id first) — the same shape that generates
|
|
43
|
+
// the op's Match type, so the block also satisfies the mypy doc gate.
|
|
44
|
+
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
45
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
46
|
+
.sort((a: any, b: any) =>
|
|
47
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
48
|
+
arg = 0 < items.length
|
|
49
|
+
? `{${items.map((it: any) =>
|
|
50
|
+
`"${it.name}": ${it.name === idF ? '"test01"' : pyLit(it.type)}`).join(', ')}}`
|
|
51
|
+
: ''
|
|
43
52
|
} else if ('create' === primaryOp || 'update' === primaryOp) {
|
|
44
53
|
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
45
54
|
.filter((it: any) => it.name !== idF && it.name !== 'id')
|
|
@@ -47,8 +56,12 @@ client = ${model.const.Name}SDK.test()
|
|
|
47
56
|
const chosen = required.length ? required : items.slice(0, 3)
|
|
48
57
|
arg = `{${chosen.map((it: any) => `"${it.name}": ${pyLit(it.type)}`).join(', ')}}`
|
|
49
58
|
}
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
// A list() result is a list — name the variable accordingly (the root
|
|
60
|
+
// README doc gate concatenates blocks, so reusing the singular name for
|
|
61
|
+
// a different type is a mypy assignment error).
|
|
62
|
+
const eVar = eName.toLowerCase() + ('list' === primaryOp ? 's' : '')
|
|
63
|
+
Content(`${eVar} = client.${eName}().${primaryOp}(${arg})
|
|
64
|
+
print(${eVar})
|
|
52
65
|
`)
|
|
53
66
|
}
|
|
54
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) { }
|