@voxgig/sdkgen 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/action.d.ts +2 -1
  3. package/dist/action/action.js +13 -3
  4. package/dist/action/action.js.map +1 -1
  5. package/dist/action/feature.js +3 -5
  6. package/dist/action/feature.js.map +1 -1
  7. package/dist/action/target.js +9 -7
  8. package/dist/action/target.js.map +1 -1
  9. package/dist/cmp/Deploy.js +3 -3
  10. package/dist/cmp/Deploy.js.map +1 -1
  11. package/dist/helpers/opShape.js +38 -6
  12. package/dist/helpers/opShape.js.map +1 -1
  13. package/dist/sdkgen.d.ts +2 -2
  14. package/dist/sdkgen.js +5 -4
  15. package/dist/sdkgen.js.map +1 -1
  16. package/dist/utility.d.ts +2 -1
  17. package/dist/utility.js +20 -1
  18. package/dist/utility.js.map +1 -1
  19. package/model/sdkgen.aontu +1 -1
  20. package/package.json +1 -1
  21. package/project/.sdk/model/feature/feature-index.aontu +5 -0
  22. package/project/.sdk/src/cmp/go/Config_go.ts +3 -2
  23. package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +35 -9
  24. package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +18 -14
  25. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +6 -2
  26. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +6 -2
  27. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +51 -30
  28. package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +66 -13
  29. package/project/.sdk/src/cmp/go/ReadmeTopHowto_go.ts +4 -0
  30. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +51 -16
  31. package/project/.sdk/src/cmp/go/utility_go.ts +70 -0
  32. package/project/.sdk/src/cmp/js/Config_js.ts +3 -6
  33. package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +23 -6
  34. package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +31 -7
  35. package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +33 -10
  36. package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +24 -14
  37. package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +15 -3
  38. package/project/.sdk/src/cmp/lua/Config_lua.ts +3 -2
  39. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +41 -6
  40. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +94 -20
  41. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +51 -8
  42. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +31 -2
  43. package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +13 -2
  44. package/project/.sdk/src/cmp/php/Config_php.ts +3 -2
  45. package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +34 -6
  46. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +95 -19
  47. package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +44 -8
  48. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +26 -2
  49. package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +13 -2
  50. package/project/.sdk/src/cmp/py/Config_py.ts +3 -2
  51. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +26 -8
  52. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +95 -21
  53. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +37 -11
  54. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +28 -2
  55. package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +16 -3
  56. package/project/.sdk/src/cmp/rb/Config_rb.ts +3 -2
  57. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +26 -8
  58. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +96 -21
  59. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +36 -10
  60. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +28 -2
  61. package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +13 -2
  62. package/project/.sdk/src/cmp/ts/Config_ts.ts +3 -6
  63. package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +22 -6
  64. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +56 -18
  65. package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +33 -10
  66. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +21 -14
  67. package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +15 -3
  68. package/project/.sdk/tm/py/test/test_struct_utility.py +1 -1
  69. package/project/.sdk/tm/rb/Makefile +3 -1
  70. package/src/action/action.ts +15 -3
  71. package/src/action/feature.ts +4 -8
  72. package/src/action/target.ts +11 -9
  73. package/src/cmp/Deploy.ts +3 -3
  74. package/src/helpers/opShape.ts +45 -7
  75. package/src/sdkgen.ts +4 -3
  76. package/src/utility.ts +24 -1
  77. package/project/.sdk/model/feature/feature-index.jsonic +0 -5
  78. /package/project/.sdk/model/feature/{log.jsonic → log.aontu} +0 -0
  79. /package/project/.sdk/model/feature/{test.jsonic → test.aontu} +0 -0
  80. /package/project/.sdk/model/target/{go-cli.jsonic → go-cli.aontu} +0 -0
  81. /package/project/.sdk/model/target/{go-mcp.jsonic → go-mcp.aontu} +0 -0
  82. /package/project/.sdk/model/target/{go.jsonic → go.aontu} +0 -0
  83. /package/project/.sdk/model/target/{js.jsonic → js.aontu} +0 -0
  84. /package/project/.sdk/model/target/{lua.jsonic → lua.aontu} +0 -0
  85. /package/project/.sdk/model/target/{php.jsonic → php.aontu} +0 -0
  86. /package/project/.sdk/model/target/{py.jsonic → py.aontu} +0 -0
  87. /package/project/.sdk/model/target/{rb.jsonic → rb.aontu} +0 -0
  88. /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}(${idF ? `["${idF}" => "${ent.name}_id"]` : ''});
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
- each(fields, (field: any) => {
265
- if ('id' !== field.name && field.req) {
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
- const updateIdLine = idF ? ` "${idF}" => "${ent.name}_id",\n` : ''
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
- ${updateIdLine} // Fields to update
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(${idF ? `["${idF}" => "example_id"]` : ''});
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
- callArg = idF ? `["${idF}" => "test01"]` : ''
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
- $${ename} = $client->${eName}()->${primaryOp}(${callArg});
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
- let authPrefix = ''
43
- try { authPrefix = getModelPath(model, `main.${KIT}.config.auth.prefix`) } catch (_e) { }
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
- function pyLit(type: any): string {
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 '"example"'
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(${idF ? `{"${idF}": "${entity.name}_id"}` : ''})
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
- each(fields, (field: any) => {
138
- if ('id' !== field.name && field.req) {
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 && e.ancestors && e.ancestors.length > 0
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 = Object.keys(exampleEntity.op || {})
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 (opnames.includes('load')) {
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(${idF ? `{"${idF}": "example_id"}` : ''})
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
- const idField = (exampleEntity.id && exampleEntity.id.field) || 'id'
88
- const pyLit = (type: any): string => {
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 !== idField && it.name !== 'id')
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
- // create needs ALL required fields; update is a patch, so a couple suffice.
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
- : items.slice(0, 2)
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(${idF ? `{"${idF}": ${idValueFor('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 test.
12
- function pyLit(type: any): string {
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 '"example"'
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}(${idF ? `{"${idF}": "${ent.name}_id"}` : ''})
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
- each(fields, (field: any) => {
275
- if ('id' !== field.name && field.req) {
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
- const updateIdLine = idF ? ` "${idF}": "${ent.name}_id",\n` : ''
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
- ${updateIdLine} # Fields to update
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(${idF ? `{"${idF}": "example_id"}` : ''})
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
- arg = idF ? `{"${idF}": "test01"}` : ''
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
- Content(`${eName.toLowerCase()} = client.${eName}().${primaryOp}(${arg})
51
- print(${eName.toLowerCase()})
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
- let authPrefix = ''
43
- try { authPrefix = getModelPath(model, `main.${KIT}.config.auth.prefix`) } catch (_e) { }
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) { }