@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.
Files changed (98) 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/cmp/ReadmeTop.js +24 -2
  12. package/dist/cmp/ReadmeTop.js.map +1 -1
  13. package/dist/helpers/opShape.js +38 -6
  14. package/dist/helpers/opShape.js.map +1 -1
  15. package/dist/sdkgen.d.ts +2 -2
  16. package/dist/sdkgen.js +5 -4
  17. package/dist/sdkgen.js.map +1 -1
  18. package/dist/utility.d.ts +2 -1
  19. package/dist/utility.js +20 -1
  20. package/dist/utility.js.map +1 -1
  21. package/model/sdkgen.aontu +1 -1
  22. package/package.json +1 -1
  23. package/project/.sdk/model/feature/feature-index.aontu +5 -0
  24. package/project/.sdk/src/cmp/go/Config_go.ts +3 -2
  25. package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +35 -9
  26. package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +18 -14
  27. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +6 -2
  28. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +6 -2
  29. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +51 -30
  30. package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +66 -13
  31. package/project/.sdk/src/cmp/go/ReadmeTopHowto_go.ts +4 -0
  32. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +51 -16
  33. package/project/.sdk/src/cmp/go/utility_go.ts +70 -0
  34. package/project/.sdk/src/cmp/js/Config_js.ts +3 -6
  35. package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +23 -6
  36. package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +31 -7
  37. package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +33 -10
  38. package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +24 -14
  39. package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +15 -3
  40. package/project/.sdk/src/cmp/lua/Config_lua.ts +3 -2
  41. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +41 -6
  42. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +94 -20
  43. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +51 -8
  44. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +31 -2
  45. package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +13 -2
  46. package/project/.sdk/src/cmp/php/Config_php.ts +3 -2
  47. package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +34 -6
  48. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +95 -19
  49. package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +44 -8
  50. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +26 -2
  51. package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +13 -2
  52. package/project/.sdk/src/cmp/py/Config_py.ts +3 -2
  53. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +26 -8
  54. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +95 -21
  55. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +37 -11
  56. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +28 -2
  57. package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +16 -3
  58. package/project/.sdk/src/cmp/rb/Config_rb.ts +3 -2
  59. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +26 -8
  60. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +96 -21
  61. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +36 -10
  62. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +28 -2
  63. package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +13 -2
  64. package/project/.sdk/src/cmp/ts/Config_ts.ts +3 -6
  65. package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +22 -6
  66. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +56 -18
  67. package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +33 -10
  68. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +21 -14
  69. package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +15 -3
  70. package/project/.sdk/tm/go/utility/prepare_auth.go +6 -1
  71. package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +3 -1
  72. package/project/.sdk/tm/lua/utility/prepare_auth.lua +6 -1
  73. package/project/.sdk/tm/php/utility/PrepareAuth.php +3 -1
  74. package/project/.sdk/tm/py/test/test_struct_utility.py +1 -1
  75. package/project/.sdk/tm/py/utility/prepare_auth.py +4 -1
  76. package/project/.sdk/tm/rb/Makefile +3 -1
  77. package/project/.sdk/tm/rb/utility/prepare_auth.rb +3 -1
  78. package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +5 -1
  79. package/src/action/action.ts +15 -3
  80. package/src/action/feature.ts +4 -8
  81. package/src/action/target.ts +11 -9
  82. package/src/cmp/Deploy.ts +3 -3
  83. package/src/cmp/ReadmeTop.ts +25 -2
  84. package/src/helpers/opShape.ts +45 -7
  85. package/src/sdkgen.ts +4 -3
  86. package/src/utility.ts +24 -1
  87. package/project/.sdk/model/feature/feature-index.jsonic +0 -5
  88. /package/project/.sdk/model/feature/{log.jsonic → log.aontu} +0 -0
  89. /package/project/.sdk/model/feature/{test.jsonic → test.aontu} +0 -0
  90. /package/project/.sdk/model/target/{go-cli.jsonic → go-cli.aontu} +0 -0
  91. /package/project/.sdk/model/target/{go-mcp.jsonic → go-mcp.aontu} +0 -0
  92. /package/project/.sdk/model/target/{go.jsonic → go.aontu} +0 -0
  93. /package/project/.sdk/model/target/{js.jsonic → js.aontu} +0 -0
  94. /package/project/.sdk/model/target/{lua.jsonic → lua.aontu} +0 -0
  95. /package/project/.sdk/model/target/{php.jsonic → php.aontu} +0 -0
  96. /package/project/.sdk/model/target/{py.jsonic → py.aontu} +0 -0
  97. /package/project/.sdk/model/target/{rb.jsonic → rb.aontu} +0 -0
  98. /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 && 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 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 = Object.keys(exampleEntity.op || {})
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 (opnames.includes('load')) {
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(${idF ? `{ ${idF} = "example_id" }` : ''})
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
- const luaLit = (type: any): string => {
100
- const k = canonKey(type)
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
- : items.slice(0, 2)
115
- return chosen.map((it: any) => `${luaKey(it.name)} = ${luaLit(it.type)}`)
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(${idF ? `{ ${idF} = ${idValueFor('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}(${idF ? `{ ${idF} = "${ent.name}_id" }` : ''})
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
- each(fields, (field: any) => {
263
- if ('id' !== field.name && field.req) {
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
- const updateIdLine = idF ? ` ${idF} = "${ent.name}_id",\n` : ''
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
- ${updateIdLine} -- Fields to update
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(${idF ? `{ ${idF} = "example_id" }` : ''})
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
- arg = idF ? `{ ${idF} = "test01" }` : ''
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
- Content(`local result, err = client:${eName}():${primaryOp}(${arg})
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
- 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, 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(${idF ? `["${idF}" => "${entity.name}_id"]` : ''});
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
- each(fields, (field: any) => {
126
- if ('id' !== field.name && field.req) {
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 && 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 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 = Object.keys(exampleEntity.op || {})
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 (opnames.includes('load')) {
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(${idF ? `["${idF}" => "example_id"]` : ''});
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
- const idField = (exampleEntity.id && exampleEntity.id.field) || 'id'
98
- const phpLit = (type: any): string => {
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 !== idField && it.name !== 'id')
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
- : items.slice(0, 2)
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(${idF ? `["${idF}" => ${idValueFor('remove')}]` : ''});
223
+ $client->${eName}()->remove(${removePairs.length ? `[${removePairs.join(', ')}]` : ''});
148
224
  `)
149
225
  }
150
226
  Content(`\`\`\`