@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
package/dist/utility.js CHANGED
@@ -7,6 +7,7 @@ exports.SdkGenError = void 0;
7
7
  exports.resolvePath = resolvePath;
8
8
  exports.requirePath = requirePath;
9
9
  exports.isAuthActive = isAuthActive;
10
+ exports.resolveAuthPrefix = resolveAuthPrefix;
10
11
  const node_path_1 = __importDefault(require("node:path"));
11
12
  const apidef_1 = require("@voxgig/apidef");
12
13
  function resolvePath(ctx$, path) {
@@ -16,7 +17,7 @@ function resolvePath(ctx$, path) {
16
17
  // True unless the model declares auth off. Templates use this to gate
17
18
  // apikey-related code, docs, and examples for public APIs that need no
18
19
  // authentication. Two opt-outs, in priority order:
19
- // 1. main.kit.info.auth: false (user-facing, set in api-info.jsonic)
20
+ // 1. main.kit.info.auth: false (user-facing, set in api-info.aontu)
20
21
  // 2. main.kit.config.auth.active: false
21
22
  function isAuthActive(model) {
22
23
  const info = (0, apidef_1.getModelPath)(model, `main.${apidef_1.KIT}.info`, { only_active: false, required: false });
@@ -25,6 +26,24 @@ function isAuthActive(model) {
25
26
  const auth = (0, apidef_1.getModelPath)(model, `main.${apidef_1.KIT}.config.auth`, { only_active: false, required: false });
26
27
  return null == auth || false !== auth.active;
27
28
  }
29
+ // The credential prefix for the Authorization header value, resolved in
30
+ // priority order:
31
+ // 1. main.kit.config.auth.prefix (per-SDK user override)
32
+ // 2. main.kit.info.security.prefix (spec-derived, set by apidef from the
33
+ // API's securityScheme — e.g. 'OAuth' for Statuspage)
34
+ // 3. 'Bearer' (conventional fallback)
35
+ // '' is a valid resolved value: it means a raw credential with no prefix
36
+ // (e.g. an apiKey scheme in a custom header). Config generators for every
37
+ // language target must use this instead of hardcoding 'Bearer'.
38
+ function resolveAuthPrefix(model) {
39
+ const auth = (0, apidef_1.getModelPath)(model, `main.${apidef_1.KIT}.config.auth`, { only_active: false, required: false });
40
+ if (null != auth && null != auth.prefix)
41
+ return String(auth.prefix);
42
+ const security = (0, apidef_1.getModelPath)(model, `main.${apidef_1.KIT}.info.security`, { only_active: false, required: false });
43
+ if (null != security && null != security.prefix)
44
+ return String(security.prefix);
45
+ return 'Bearer';
46
+ }
28
47
  function requirePath(ctx$, path, flags) {
29
48
  const fullpath = resolvePath(ctx$, path);
30
49
  const ignore = null == flags?.ignore ? false : flags.ignore;
@@ -1 +1 @@
1
- {"version":3,"file":"utility.js","sourceRoot":"","sources":["../src/utility.ts"],"names":[],"mappings":";;;;;;AA8DE,kCAAW;AACX,kCAAW;AACX,oCAAY;AA/Dd,0DAA4B;AAI5B,2CAAkD;AAGlD,SAAS,WAAW,CAAC,IAAS,EAAE,IAAY;IAC1C,MAAM,QAAQ,GAAG,mBAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;IAC7D,OAAO,QAAQ,CAAA;AACjB,CAAC;AAGD,sEAAsE;AACtE,uEAAuE;AACvE,mDAAmD;AACnD,8EAA8E;AAC9E,0CAA0C;AAC1C,SAAS,YAAY,CAAC,KAAU;IAC9B,MAAM,IAAI,GAAG,IAAA,qBAAY,EAAC,KAAK,EAAE,QAAQ,YAAG,OAAO,EACjD,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1C,IAAI,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IAE7C,MAAM,IAAI,GAAG,IAAA,qBAAY,EAAC,KAAK,EAAE,QAAQ,YAAG,cAAc,EACxD,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1C,OAAO,IAAI,IAAI,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,MAAM,CAAA;AAC9C,CAAC;AAGD,SAAS,WAAW,CAAC,IAAS,EAAE,IAAY,EAAE,KAA4B;IACxE,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,IAAI,IAAI,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAA;IAE3D,kEAAkE;IAClE,sEAAsE;IACtE,qEAAqE;IACrE,wEAAwE;IACxE,qCAAqC;IACrC,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YACH,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAC3B,CAAC;QACD,OAAO,GAAQ,EAAE,CAAC;YAChB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAC7D,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAA;AAC1B,CAAC;AAGD,MAAM,WAAY,SAAQ,KAAK;IAC7B,YAAY,GAAG,IAAW;QACxB,KAAK,CAAC,GAAG,IAAI,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,aAAa,CAAA;IAC3B,CAAC;CACF;AAOC,kCAAW"}
1
+ {"version":3,"file":"utility.js","sourceRoot":"","sources":["../src/utility.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ main: def: {
10
10
  # SDK but not listed here are appended afterwards in spec-defined
11
11
  # order. Override per-SDK by setting
12
12
  # `main.kit.config.docs_order: ['py','ts',...]` in
13
- # `<sdk>/.sdk/model/config.jsonic`.
13
+ # `<sdk>/.sdk/model/config.aontu`.
14
14
  main: kit: config: docs_order: *['ts','py','php','go','rb','lua'] | [&: string]
15
15
 
16
16
  main: kit: target: &: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/sdkgen",
3
- "version": "1.3.1",
3
+ "version": "1.3.4",
4
4
  "main": "dist/sdkgen.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/sdkgen.d.ts",
@@ -0,0 +1,5 @@
1
+ # Features
2
+
3
+
4
+ @"test.aontu"
5
+ @"log.aontu"
@@ -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,11 +1,13 @@
1
1
 
2
- import { cmp, each, Content, canonToType, entityIdField, safeVarName } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, canonToType, entityIdField, entityOps, opRequestShape } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
6
6
  getModelPath,
7
7
  } from '@voxgig/apidef'
8
8
 
9
+ import { exampleValue, goVarName } from './utility_go'
10
+
9
11
 
10
12
  // Operation method spelling differs between Go and other languages — Go
11
13
  // uses PascalCase methods with explicit ctrl arg, others use lowercase
@@ -39,12 +41,15 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
39
41
  `)
40
42
 
41
43
  publishedEntities.map((entity: any) => {
42
- const opnames = Object.keys(entity.op || {})
44
+ // ACTIVE ops only — an inactive op generates no method, so an example
45
+ // calling it would not compile.
46
+ const opnames = entityOps(entity)
43
47
  const fields = entity.fields || []
44
48
  // Model-driven id key: null when this entity has no id-like field.
45
49
  const idF = entityIdField(entity)
46
- // Variable-safe lowercase name (a `Type`/`Range` entity must not bind a Go keyword).
47
- const eVar = safeVarName(entity.name, 'go')
50
+ // camelCase Go identifier (a `status_embed_config` entity must not bind a
51
+ // snake_case Go variable, and a `type`/`range` entity not a Go keyword).
52
+ const eVar = goVarName(entity.name)
48
53
 
49
54
  Content(`
50
55
  ### ${entity.Name}
@@ -97,10 +102,22 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
97
102
  }
98
103
 
99
104
  if (opnames.includes('load')) {
105
+ // The id key plus every REQUIRED match key (parent path params like
106
+ // page_id) — the same shape that generates the op's request match, so
107
+ // the example always carries the keys the route needs.
108
+ const loadItems = opRequestShape(entity, 'load').items
109
+ .filter((it: any) => !it.optional || it.name === idF)
110
+ .sort((a: any, b: any) =>
111
+ (a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
112
+ const loadArg = 0 < loadItems.length
113
+ ? `map[string]any{${loadItems.map((it: any) =>
114
+ `"${it.name}": ${exampleValue(entity, entity.op && entity.op.load, it.name,
115
+ it.name === idF ? entity.name + '_id' : it.name)}`).join(', ')}}`
116
+ : 'nil'
100
117
  Content(`#### Example: Load
101
118
 
102
119
  \`\`\`go
103
- ${eVar}, err := client.${entity.Name}(nil).Load(${idF ? `map[string]any{"${idF}": "${entity.name}_id"}` : 'nil'}, nil)
120
+ ${eVar}, err := client.${entity.Name}(nil).Load(${loadArg}, nil)
104
121
  if err != nil {
105
122
  panic(err)
106
123
  }
@@ -125,18 +142,27 @@ fmt.Println(${eVar}s) // the array of records
125
142
  }
126
143
 
127
144
  if (opnames.includes('create')) {
145
+ // Members come from the SAME shape that generates the op's request
146
+ // data: every required member must appear — including a required id
147
+ // (the /* type */ placeholders also mark the block as an illustration
148
+ // for the doc gates); an all-optional create renders an empty — still
149
+ // valid — literal, and the compiled example is self-consuming.
150
+ const createItems = opRequestShape(entity, 'create').items
151
+ .filter((it: any) => !it.optional)
128
152
  Content(`#### Example: Create
129
153
 
130
154
  \`\`\`go
131
155
  result, err := client.${entity.Name}(nil).Create(map[string]any{
132
156
  `)
133
- each(fields, (field: any) => {
134
- if ('id' !== field.name && field.req) {
135
- Content(` "${field.name}": /* ${canonToType(field.type, target.name)} */,
157
+ createItems.map((it: any) => {
158
+ Content(` "${it.name}": /* ${canonToType(it.type, target.name)} */,
136
159
  `)
137
- }
138
160
  })
139
161
  Content(`}, nil)
162
+ if err != nil {
163
+ panic(err)
164
+ }
165
+ fmt.Println(result)
140
166
  \`\`\`
141
167
 
142
168
  `)
@@ -206,10 +206,14 @@ func TestReadmeGoSnippets(t *testing.T) {
206
206
 
207
207
  // Build. The Go compiler is the oracle for unused imports/vars in the
208
208
  // wrapped fragments: blank them out and rebuild. Any OTHER error is a
209
- // genuine snippet bug and fails the test.
209
+ // genuine snippet bug and fails the test. The loop is progress-based,
210
+ // not attempt-capped: \`-gcflags=-e\` lifts the compiler's 10-error cap so
211
+ // every error surfaces in one round, applyFixes repairs them all, and
212
+ // the loop continues while a round makes progress. It fails hard when no
213
+ // fix applies, and fails as non-converged when a round changes nothing
214
+ // in the build output (the fixes stopped helping).
210
215
  var lastOut string
211
- built := false
212
- for attempt := 0; attempt < 15; attempt++ {
216
+ for {
213
217
  for name, content := range fragFiles {
214
218
  if err := os.WriteFile(filepath.Join(fragDir, name+".go"), []byte(content), 0o644); err != nil {
215
219
  t.Fatal(err)
@@ -217,17 +221,16 @@ func TestReadmeGoSnippets(t *testing.T) {
217
221
  }
218
222
  out, buildErr := runGoBuild(moduleRoot, binDir, progDirs, fragPkg)
219
223
  if buildErr == nil {
220
- built = true
221
224
  break
222
225
  }
226
+ if out == lastOut {
227
+ t.Fatalf("README go snippets did not converge to a clean build:\\n%s", out)
228
+ }
223
229
  lastOut = out
224
230
  if !applyFixes(out, fragFiles) {
225
231
  t.Fatalf("README go snippet failed to compile:\\n%s", out)
226
232
  }
227
233
  }
228
- if !built {
229
- t.Fatalf("README go snippets did not converge to a clean build:\\n%s", lastOut)
230
- }
231
234
 
232
235
  // Everything compiled. Now RUN the test-mode variants of the complete
233
236
  // programs and fail on a genuine runtime panic.
@@ -357,15 +360,16 @@ func isIdentByte(c byte) bool {
357
360
  (c >= '0' && c <= '9')
358
361
  }
359
362
 
360
- // runGoBuild type-checks the fragment package with a plain \`go build\`
361
- // (a non-main package produces no output but is still fully type-checked
362
- // unlike \`go build -o dir/\`, which skips non-main packages), and builds
363
- // each complete program with \`-o binDir/\`. Any compile error from either
364
- // is returned.
363
+ // runGoBuild type-checks the fragment package with \`go build -gcflags=-e\`
364
+ // (a non-main package produces no output but is still fully type-checked
365
+ // unlike \`go build -o dir/\`, which skips non-main packages and \`-e\` lifts
366
+ // the compiler's 10-errors-per-package cap so EVERY unused-var/import error
367
+ // surfaces in a single round), and builds each complete program with
368
+ // \`-o binDir/\`. Any compile error from either is returned.
365
369
  func runGoBuild(dir, binDir string, progDirs []string, fragPkg string) (string, error) {
366
370
  var out strings.Builder
367
371
  if fragPkg != "" {
368
- cmd := exec.Command("go", "build", fragPkg)
372
+ cmd := exec.Command("go", "build", "-gcflags=-e", fragPkg)
369
373
  cmd.Dir = dir
370
374
  o, err := cmd.CombinedOutput()
371
375
  out.Write(o)
@@ -374,7 +378,7 @@ func runGoBuild(dir, binDir string, progDirs []string, fragPkg string) (string,
374
378
  }
375
379
  }
376
380
  if len(progDirs) > 0 {
377
- args := append([]string{"build", "-o", binDir + string(os.PathSeparator)}, progDirs...)
381
+ args := append([]string{"build", "-gcflags=-e", "-o", binDir + string(os.PathSeparator)}, progDirs...)
378
382
  cmd := exec.Command("go", args...)
379
383
  cmd.Dir = dir
380
384
  o, err := cmd.CombinedOutput()
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, entityPrimaryOp, opRequestShape, safeVarName } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, entityPrimaryOp, 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 { goVarName } from './utility_go'
11
+
10
12
 
11
13
  // A type-correct Go literal for a field's canonical type.
12
14
  function goLit(type: any): string {
@@ -32,7 +34,9 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
32
34
  const entity = getModelPath(model, `main.${KIT}.entity`)
33
35
  const exampleEntity = Object.values(entity || {}).find((e: any) => e && e.active !== false) as any
34
36
  const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
35
- const eLower = safeVarName(eName.toLowerCase(), 'go')
37
+ // camelCase Go identifier (never snake_case or flattened lowercase,
38
+ // never a Go keyword).
39
+ const eLower = exampleEntity ? goVarName(exampleEntity.name) : 'entity'
36
40
  // Model-driven id key: null when the entity has no id-like field.
37
41
  const idF = exampleEntity ? entityIdField(exampleEntity) : null
38
42
  // Drive the test-mode example off the entity's PRIMARY op — never a hardcoded
@@ -1,11 +1,13 @@
1
1
 
2
- import { cmp, each, Content, isAuthActive, entityIdField, entityPrimaryOp, safeVarName } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, isAuthActive, entityIdField, entityPrimaryOp } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
6
6
  getModelPath,
7
7
  } from '@voxgig/apidef'
8
8
 
9
+ import { goVarName } from './utility_go'
10
+
9
11
 
10
12
  const ReadmeModel = cmp(function ReadmeModel(props: any) {
11
13
  const { target, ctx$: { model } } = props
@@ -50,7 +52,9 @@ const ReadmeModel = cmp(function ReadmeModel(props: any) {
50
52
 
51
53
  // First published entity name, for the Result shape illustration.
52
54
  const firstEntityName = (entityList[0] as any)?.Name || 'Entity'
53
- const firstEntityVar = safeVarName(firstEntityName.toLowerCase(), 'go')
55
+ // camelCase Go identifier (never snake_case or flattened lowercase,
56
+ // never a Go keyword).
57
+ const firstEntityVar = goVarName((entityList[0] as any)?.name || 'entity')
54
58
  // Model-driven id key: null when the example entity has no id-like field, so
55
59
  // the Result-shape illustration passes a nil match.
56
60
  const firstIdF = entityIdField(entityList[0] || {})
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, safeVarName } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, opRequestShape, entityIdField, entityOps } 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
  // Emits the go/README.md Quickstart as ONE complete, compilable program.
12
14
  // Every entity operation returns `(value, error)` where `value` is the
@@ -35,40 +37,54 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
35
37
 
36
38
  if (exampleEntity) {
37
39
  const eName = nom(exampleEntity, 'Name')
38
- // Variable-safe lowercase name — a `Type`/`Range` entity must not bind a Go
39
- // keyword (`type, err := ...` fails `go build`).
40
- const eLower = safeVarName(eName.toLowerCase(), 'go')
41
- const opnames = Object.keys(exampleEntity.op || {})
42
-
43
- // Model-driven example fields (from the same op shape the request types are
44
- // built from) so create/update reference REAL writable fields, not a
45
- // hardcoded "name", and ids use a type-correct literal.
46
- const idField = (exampleEntity.id && exampleEntity.id.field) || 'id'
40
+ // camelCase variable name — a `status_embed_config` entity must not bind
41
+ // a snake_case Go variable, and a `Type`/`Range` entity must not bind a
42
+ // Go keyword (`type, err := ...` fails `go build`).
43
+ const eLower = goVarName(exampleEntity.name)
44
+ // ACTIVE ops only — an inactive op generates no method, so an example
45
+ // calling it would not compile.
46
+ const opnames = entityOps(exampleEntity)
47
+
47
48
  // Model-driven id key: null when the entity has no id-like field (a
48
49
  // response-wrapped spec). When null, load/remove pass a nil match and
49
50
  // update omits the id member.
50
51
  const idF = entityIdField(exampleEntity)
51
- const goLit = (type: any): string => {
52
- const k = canonKey(type)
53
- if ('INTEGER' === k || 'NUMBER' === k) return '1'
54
- if ('BOOLEAN' === k) return 'true'
55
- if ('ARRAY' === k) return '[]any{}'
56
- if ('OBJECT' === k) return 'map[string]any{}'
57
- return '"example"'
58
- }
59
- const examplePairs = (opname: string): string[] => {
52
+
53
+ // Model-driven example members for an op body, from the SAME op shape the
54
+ // request types are built from (opRequestShape), so create/update
55
+ // reference REAL writable fields, not a hardcoded "name", and every value
56
+ // is a type-correct Go literal (exampleValue). ids are rendered
57
+ // separately as the match key for update/remove; a REQUIRED id stays for
58
+ // create (dropping it makes the payload incomplete).
59
+ const exampleFields = (opname: string): string[] => {
60
60
  const items = opRequestShape(exampleEntity, opname).items
61
- .filter((it: any) => it.name !== idField && it.name !== 'id')
61
+ .filter((it: any) => (it.name !== idF && it.name !== 'id') ||
62
+ ('create' === opname && !it.optional))
62
63
  const required = items.filter((it: any) => !it.optional)
64
+ const optional = items.filter((it: any) => it.optional)
65
+ // Required members must all appear or the payload is incomplete; pad
66
+ // update (a patch) with a sample optional field or two.
63
67
  const chosen = 'create' === opname
64
68
  ? (required.length ? required : items.slice(0, 2))
65
- : items.slice(0, 2)
66
- return chosen.map((it: any) => `"${it.name}": ${goLit(it.type)}`)
69
+ : required.concat(optional).slice(0, Math.max(2, required.length))
70
+ return chosen.map((it: any) =>
71
+ `"${it.name}": ${exampleValue(exampleEntity, exampleEntity.op[opname], it.name, 'example_' + it.name)}`)
72
+ }
73
+
74
+ // The full REQUIRED match for load/remove (id first, then parent path
75
+ // params like page_id) — the same shape that generates the op's request
76
+ // match, so the example always carries the keys the route needs.
77
+ const matchArg = (opname: string): string => {
78
+ const matchItems = opRequestShape(exampleEntity, opname).items
79
+ .filter((it: any) => !it.optional || it.name === idF)
80
+ .sort((a: any, b: any) =>
81
+ (a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
82
+ return 0 < matchItems.length
83
+ ? `map[string]any{${matchItems.map((it: any) =>
84
+ `"${it.name}": ${exampleValue(exampleEntity, exampleEntity.op[opname], it.name,
85
+ it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')}}`
86
+ : 'nil'
67
87
  }
68
- const idOp = opnames.includes('load') ? 'load' : (opnames.includes('update') ? 'update' : 'remove')
69
- const idShape = opRequestShape(exampleEntity, idOp)
70
- .items.find((it: any) => it.name === idField || it.name === 'id')
71
- const idLit = idShape ? goLit(idShape.type) : '"example_id"'
72
88
 
73
89
  if (opnames.includes('list')) {
74
90
  body.push(` // List ${eLower} records — the value is the array of records itself.`)
@@ -85,7 +101,7 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
85
101
 
86
102
  if (opnames.includes('load')) {
87
103
  body.push(` // Load a single ${eLower} — the value is the loaded record.`)
88
- body.push(` ${eLower}, err := client.${eName}(nil).Load(${idF ? `map[string]any{"${idF}": ${idLit}}` : 'nil'}, nil)`)
104
+ body.push(` ${eLower}, err := client.${eName}(nil).Load(${matchArg('load')}, nil)`)
89
105
  body.push(` if err != nil {`)
90
106
  body.push(` panic(err)`)
91
107
  body.push(` }`)
@@ -96,7 +112,7 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
96
112
 
97
113
  if (opnames.includes('create')) {
98
114
  body.push(` // Create a ${eLower}.`)
99
- body.push(` created, err := client.${eName}(nil).Create(map[string]any{${examplePairs('create').join(', ')}}, nil)`)
115
+ body.push(` created, err := client.${eName}(nil).Create(map[string]any{${exampleFields('create').join(', ')}}, nil)`)
100
116
  body.push(` if err != nil {`)
101
117
  body.push(` panic(err)`)
102
118
  body.push(` }`)
@@ -106,8 +122,13 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
106
122
  }
107
123
 
108
124
  if (opnames.includes('update')) {
125
+ // The id member (when the entity has an id-like key) plus example
126
+ // patch fields — the same shape that generates the op's request data.
127
+ const updateMembers = (idF
128
+ ? [`"${idF}": ${exampleValue(exampleEntity, exampleEntity.op.update, idF, 'example_id')}`]
129
+ : []).concat(exampleFields('update'))
109
130
  body.push(` // Update a ${eLower}.`)
110
- body.push(` updated, err := client.${eName}(nil).Update(map[string]any{${(idF ? [`"${idF}": ${idLit}`] : []).concat(examplePairs('update')).join(', ')}}, nil)`)
131
+ body.push(` updated, err := client.${eName}(nil).Update(map[string]any{${updateMembers.join(', ')}}, nil)`)
111
132
  body.push(` if err != nil {`)
112
133
  body.push(` panic(err)`)
113
134
  body.push(` }`)
@@ -118,7 +139,7 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
118
139
 
119
140
  if (opnames.includes('remove')) {
120
141
  body.push(` // Remove a ${eLower}.`)
121
- body.push(` removed, err := client.${eName}(nil).Remove(${idF ? `map[string]any{"${idF}": ${idLit}}` : 'nil'}, nil)`)
142
+ body.push(` removed, err := client.${eName}(nil).Remove(${matchArg('remove')}, nil)`)
122
143
  body.push(` if err != nil {`)
123
144
  body.push(` panic(err)`)
124
145
  body.push(` }`)
@@ -1,11 +1,13 @@
1
1
 
2
- import { cmp, each, Content, canonToType, File, isAuthActive, entityIdField } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, canonToType, File, isAuthActive, entityIdField, entityOps, opRequestShape } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
6
6
  getModelPath,
7
7
  } from '@voxgig/apidef'
8
8
 
9
+ import { exampleValue, goVarName } from './utility_go'
10
+
9
11
 
10
12
  const OP_SIGNATURES: Record<string, { sig: string, returns: string, desc: string }> = {
11
13
  load: {
@@ -158,11 +160,16 @@ same parameters as \`Direct()\`.
158
160
 
159
161
  // Entity reference sections
160
162
  publishedEntities.map((ent: any) => {
161
- const opnames = Object.keys(ent.op || {})
163
+ // ACTIVE ops only — an inactive op generates no method, so an example
164
+ // calling it would not compile.
165
+ const opnames = entityOps(ent)
162
166
  const fields = ent.fields || []
163
167
  // Model-driven id key: null when this entity has no id-like field, in
164
168
  // which case load/remove pass a nil match and update omits the id.
165
169
  const idF = entityIdField(ent)
170
+ // camelCase Go identifier (a `status_embed_config` entity must not bind
171
+ // a snake_case Go variable).
172
+ const eVar = goVarName(ent.name)
166
173
 
167
174
  Content(`
168
175
  ---
@@ -178,7 +185,8 @@ same parameters as \`Direct()\`.
178
185
  }
179
186
 
180
187
  Content(`\`\`\`go
181
- ${ent.name} := client.${ent.Name}(nil)
188
+ ${eVar} := client.${ent.Name}(nil)
189
+ fmt.Println(${eVar}.GetName()) // "${ent.name}"
182
190
  \`\`\`
183
191
 
184
192
  `)
@@ -205,9 +213,10 @@ ${ent.name} := client.${ent.Name}(nil)
205
213
  const hasFieldOps = fields.some((f: any) => f.op && Object.keys(f.op).length > 0)
206
214
  if (hasFieldOps) {
207
215
  // Only emit columns for operations this entity actually exposes —
208
- // never advertise a create/update/remove column the entity lacks.
216
+ // never advertise a create/update/remove column the entity lacks
217
+ // (opnames already carries active ops only).
209
218
  const opcols = ['load', 'list', 'create', 'update', 'remove']
210
- .filter((op: string) => opnames.includes(op) && ent.op[op]?.active !== false)
219
+ .filter((op: string) => opnames.includes(op))
211
220
  Content(`### Field Usage by Operation
212
221
 
213
222
  | Field | ${opcols.join(' | ')} |
@@ -247,11 +256,29 @@ ${info.desc}
247
256
 
248
257
  `)
249
258
 
250
- // Show example
259
+ // Show example. Every compiled example is self-consuming — check
260
+ // `err` and print the result — so the doc gate builds it without
261
+ // unused-variable repairs, and readers see the idiomatic pattern.
251
262
  if ('load' === opname || 'remove' === opname) {
252
263
  const goOpName = opname.charAt(0).toUpperCase() + opname.slice(1)
264
+ // The id key plus every REQUIRED match key (parent path params
265
+ // like page_id) — the same shape that generates the op's request
266
+ // match, so the example always carries the keys the route needs.
267
+ const matchItems = opRequestShape(ent, opname).items
268
+ .filter((it: any) => !it.optional || it.name === idF)
269
+ .sort((a: any, b: any) =>
270
+ (a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
271
+ const arg = 0 < matchItems.length
272
+ ? `map[string]any{${matchItems.map((it: any) =>
273
+ `"${it.name}": ${exampleValue(ent, ent.op && ent.op[opname], it.name,
274
+ it.name === idF ? ent.name + '_id' : it.name)}`).join(', ')}}`
275
+ : 'nil'
253
276
  Content(`\`\`\`go
254
- result, err := client.${ent.Name}(nil).${goOpName}(${idF ? `map[string]any{"${idF}": "${ent.name}_id"}` : 'nil'}, nil)
277
+ result, err := client.${ent.Name}(nil).${goOpName}(${arg}, nil)
278
+ if err != nil {
279
+ panic(err)
280
+ }
281
+ fmt.Println(result)
255
282
  \`\`\`
256
283
 
257
284
  `)
@@ -259,31 +286,57 @@ result, err := client.${ent.Name}(nil).${goOpName}(${idF ? `map[string]any{"${id
259
286
  else if ('list' === opname) {
260
287
  Content(`\`\`\`go
261
288
  results, err := client.${ent.Name}(nil).List(nil, nil)
289
+ if err != nil {
290
+ panic(err)
291
+ }
292
+ fmt.Println(results)
262
293
  \`\`\`
263
294
 
264
295
  `)
265
296
  }
266
297
  else if ('create' === opname) {
298
+ // Members come from the SAME shape that generates the op's
299
+ // request data: every required member must appear — including a
300
+ // required id (the /* type */ placeholders also mark the block
301
+ // as an illustration for the doc gates); an all-optional create
302
+ // renders an empty — still valid — literal.
303
+ const createItems = opRequestShape(ent, 'create').items
304
+ .filter((it: any) => !it.optional)
267
305
  Content(`\`\`\`go
268
306
  result, err := client.${ent.Name}(nil).Create(map[string]any{
269
307
  `)
270
- each(fields, (field: any) => {
271
- if ('id' !== field.name && field.req) {
272
- Content(` "${field.name}": /* ${canonToType(field.type, target.name)} */,
308
+ createItems.map((it: any) => {
309
+ Content(` "${it.name}": /* ${canonToType(it.type, target.name)} */,
273
310
  `)
274
- }
275
311
  })
276
312
  Content(`}, nil)
313
+ if err != nil {
314
+ panic(err)
315
+ }
316
+ fmt.Println(result)
277
317
  \`\`\`
278
318
 
279
319
  `)
280
320
  }
281
321
  else if ('update' === opname) {
282
- const updateIdLine = idF ? ` "${idF}": "${ent.name}_id",\n` : ''
322
+ // The id key plus every REQUIRED data member — the same shape
323
+ // that generates the op's request data — then the patch-fields
324
+ // note.
325
+ const updateItems = opRequestShape(ent, 'update').items
326
+ .filter((it: any) => !it.optional || it.name === idF)
327
+ .sort((a: any, b: any) =>
328
+ (a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
329
+ const updateLines = updateItems.map((it: any) =>
330
+ ` "${it.name}": ${exampleValue(ent, ent.op && ent.op.update, it.name,
331
+ it.name === idF ? ent.name + '_id' : it.name)},\n`).join('')
283
332
  Content(`\`\`\`go
284
333
  result, err := client.${ent.Name}(nil).Update(map[string]any{
285
- ${updateIdLine} // Fields to update
334
+ ${updateLines} // Fields to update
286
335
  }, nil)
336
+ if err != nil {
337
+ panic(err)
338
+ }
339
+ fmt.Println(result)
287
340
  \`\`\`
288
341
 
289
342
  `)
@@ -12,6 +12,10 @@ result, err := client.Direct(map[string]any{
12
12
  "method": "GET",
13
13
  "params": map[string]any{"id": "example"},
14
14
  })
15
+ if err != nil {
16
+ panic(err)
17
+ }
18
+ fmt.Println(result)
15
19
  \`\`\`
16
20
 
17
21
  `)