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