@voxgig/sdkgen 0.44.0 → 1.0.1

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 (160) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/ReadmeEntity.js +9 -153
  3. package/dist/cmp/ReadmeEntity.js.map +1 -1
  4. package/dist/cmp/ReadmeIntro.js +9 -14
  5. package/dist/cmp/ReadmeIntro.js.map +1 -1
  6. package/dist/cmp/ReadmeModel.js +6 -4
  7. package/dist/cmp/ReadmeModel.js.map +1 -1
  8. package/dist/cmp/ReadmeOptions.js +9 -61
  9. package/dist/cmp/ReadmeOptions.js.map +1 -1
  10. package/dist/cmp/ReadmeRef.js +10 -1328
  11. package/dist/cmp/ReadmeRef.js.map +1 -1
  12. package/dist/sdkgen.d.ts +2 -2
  13. package/dist/sdkgen.js +2 -1
  14. package/dist/sdkgen.js.map +1 -1
  15. package/dist/utility.d.ts +2 -1
  16. package/dist/utility.js +9 -0
  17. package/dist/utility.js.map +1 -1
  18. package/package.json +3 -3
  19. package/project/.sdk/src/cmp/go/Config_go.ts +9 -4
  20. package/project/.sdk/src/cmp/go/Entity_go.ts +2 -2
  21. package/project/.sdk/src/cmp/go/Main_go.ts +8 -4
  22. package/project/.sdk/src/cmp/go/Package_go.ts +2 -2
  23. package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +138 -0
  24. package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +2 -2
  25. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +8 -5
  26. package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +2 -2
  27. package/project/.sdk/src/cmp/go/ReadmeIntro_go.ts +18 -0
  28. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +8 -5
  29. package/project/.sdk/src/cmp/go/ReadmeOptions_go.ts +58 -0
  30. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +13 -9
  31. package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +354 -0
  32. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +8 -6
  33. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +2 -2
  34. package/project/.sdk/src/cmp/go/TestDirect_go.ts +222 -41
  35. package/project/.sdk/src/cmp/go/TestEntity_go.ts +142 -60
  36. package/project/.sdk/src/cmp/go/Test_go.ts +2 -2
  37. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +21 -4
  38. package/project/.sdk/src/cmp/js/Config_js.ts +18 -0
  39. package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +138 -0
  40. package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +11 -6
  41. package/project/.sdk/src/cmp/js/ReadmeIntro_js.ts +18 -0
  42. package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +6 -3
  43. package/project/.sdk/src/cmp/js/ReadmeOptions_js.ts +58 -0
  44. package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +6 -4
  45. package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +384 -0
  46. package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +6 -4
  47. package/project/.sdk/src/cmp/js/TestDirect_js.ts +23 -12
  48. package/project/.sdk/src/cmp/js/TestEntity_js.ts +107 -74
  49. package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +1 -5
  50. package/project/.sdk/src/cmp/lua/Config_lua.ts +9 -4
  51. package/project/.sdk/src/cmp/lua/Package_lua.ts +9 -2
  52. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +138 -0
  53. package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +6 -3
  54. package/project/.sdk/src/cmp/lua/ReadmeIntro_lua.ts +18 -0
  55. package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +6 -3
  56. package/project/.sdk/src/cmp/lua/ReadmeOptions_lua.ts +58 -0
  57. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +6 -4
  58. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +360 -0
  59. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +6 -4
  60. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +172 -29
  61. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +120 -52
  62. package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +20 -4
  63. package/project/.sdk/src/cmp/php/Config_php.ts +10 -8
  64. package/project/.sdk/src/cmp/php/Package_php.ts +7 -1
  65. package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +138 -0
  66. package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +6 -3
  67. package/project/.sdk/src/cmp/php/ReadmeIntro_php.ts +18 -0
  68. package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +6 -3
  69. package/project/.sdk/src/cmp/php/ReadmeOptions_php.ts +58 -0
  70. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +6 -4
  71. package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +358 -0
  72. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +6 -4
  73. package/project/.sdk/src/cmp/php/TestDirect_php.ts +171 -28
  74. package/project/.sdk/src/cmp/php/TestEntity_php.ts +126 -55
  75. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +17 -3
  76. package/project/.sdk/src/cmp/py/Config_py.ts +9 -4
  77. package/project/.sdk/src/cmp/py/Package_py.ts +8 -1
  78. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +138 -0
  79. package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +6 -3
  80. package/project/.sdk/src/cmp/py/ReadmeIntro_py.ts +18 -0
  81. package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +6 -3
  82. package/project/.sdk/src/cmp/py/ReadmeOptions_py.ts +58 -0
  83. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +9 -6
  84. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +356 -0
  85. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +9 -6
  86. package/project/.sdk/src/cmp/py/TestDirect_py.ts +164 -27
  87. package/project/.sdk/src/cmp/py/TestEntity_py.ts +125 -51
  88. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +19 -4
  89. package/project/.sdk/src/cmp/rb/Config_rb.ts +9 -4
  90. package/project/.sdk/src/cmp/rb/Package_rb.ts +9 -2
  91. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +138 -0
  92. package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +6 -3
  93. package/project/.sdk/src/cmp/rb/ReadmeIntro_rb.ts +18 -0
  94. package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +6 -3
  95. package/project/.sdk/src/cmp/rb/ReadmeOptions_rb.ts +58 -0
  96. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +6 -4
  97. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +361 -0
  98. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +6 -4
  99. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +172 -29
  100. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +120 -52
  101. package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +19 -3
  102. package/project/.sdk/src/cmp/ts/Config_ts.ts +18 -0
  103. package/project/.sdk/src/cmp/ts/Package_ts.ts +1 -1
  104. package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +138 -0
  105. package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +11 -6
  106. package/project/.sdk/src/cmp/ts/ReadmeIntro_ts.ts +18 -0
  107. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +9 -5
  108. package/project/.sdk/src/cmp/ts/ReadmeOptions_ts.ts +58 -0
  109. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +6 -4
  110. package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +384 -0
  111. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +6 -4
  112. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +213 -42
  113. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +168 -75
  114. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +1 -5
  115. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +8 -1
  116. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +8 -2
  117. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +21 -1
  118. package/project/.sdk/tm/go/feature/test_feature.go +51 -3
  119. package/project/.sdk/tm/go/test/runner_test.go +106 -6
  120. package/project/.sdk/tm/go/test/sdk-test-control.json +19 -0
  121. package/project/.sdk/tm/go/utility/fetcher.go +10 -0
  122. package/project/.sdk/tm/go/utility/make_url.go +12 -0
  123. package/project/.sdk/tm/go/utility/prepare_auth.go +15 -1
  124. package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +7 -1
  125. package/project/.sdk/tm/lua/feature/test_feature.lua +41 -3
  126. package/project/.sdk/tm/lua/test/runner.lua +74 -0
  127. package/project/.sdk/tm/lua/test/sdk-test-control.json +19 -0
  128. package/project/.sdk/tm/lua/utility/fetcher.lua +13 -0
  129. package/project/.sdk/tm/lua/utility/make_url.lua +16 -0
  130. package/project/.sdk/tm/lua/utility/prepare_auth.lua +9 -1
  131. package/project/.sdk/tm/php/feature/TestFeature.php +185 -43
  132. package/project/.sdk/tm/php/test/Runner.php +62 -0
  133. package/project/.sdk/tm/php/test/sdk-test-control.json +19 -0
  134. package/project/.sdk/tm/php/utility/Fetcher.php +132 -9
  135. package/project/.sdk/tm/php/utility/MakeUrl.php +16 -0
  136. package/project/.sdk/tm/php/utility/PrepareAuth.php +11 -1
  137. package/project/.sdk/tm/py/feature/test_feature.py +35 -3
  138. package/project/.sdk/tm/py/test/runner.py +60 -0
  139. package/project/.sdk/tm/py/test/sdk-test-control.json +19 -0
  140. package/project/.sdk/tm/py/utility/fetcher.py +13 -0
  141. package/project/.sdk/tm/py/utility/make_url.py +13 -0
  142. package/project/.sdk/tm/py/utility/prepare_auth.py +10 -1
  143. package/project/.sdk/tm/rb/feature/test_feature.rb +36 -3
  144. package/project/.sdk/tm/rb/test/runner.rb +46 -0
  145. package/project/.sdk/tm/rb/test/sdk-test-control.json +19 -0
  146. package/project/.sdk/tm/rb/utility/fetcher.rb +49 -28
  147. package/project/.sdk/tm/rb/utility/make_url.rb +16 -0
  148. package/project/.sdk/tm/rb/utility/prepare_auth.rb +8 -1
  149. package/project/.sdk/tm/ts/src/utility/MakeUrlUtility.ts +7 -8
  150. package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +7 -1
  151. package/project/.sdk/tm/ts/test/sdk-test-control.json +19 -0
  152. package/project/.sdk/tm/ts/test/utility.ts +120 -2
  153. package/src/cmp/ReadmeEntity.ts +11 -178
  154. package/src/cmp/ReadmeIntro.ts +11 -25
  155. package/src/cmp/ReadmeModel.ts +7 -5
  156. package/src/cmp/ReadmeOptions.ts +12 -74
  157. package/src/cmp/ReadmeRef.ts +11 -1372
  158. package/src/sdkgen.ts +2 -1
  159. package/src/utility.ts +12 -0
  160. /package/project/.sdk/tm/go/utility/{make_target.go → make_point.go} +0 -0
@@ -7,7 +7,12 @@ import {
7
7
 
8
8
  import {
9
9
  KIT,
10
+ Model,
11
+ ModelEntity,
12
+ ModelEntityFlow,
13
+ ModelEntityFlowStep,
10
14
  getModelPath,
15
+ nom,
11
16
  } from '@voxgig/apidef'
12
17
 
13
18
 
@@ -18,42 +23,54 @@ import {
18
23
  each,
19
24
  buildIdNames,
20
25
  getMatchEntries,
26
+ isAuthActive,
21
27
  } from '@voxgig/sdkgen'
22
28
 
23
29
 
24
- type OpGen = (ctx: GenCtx, step: any, index: any) => void
25
-
30
+ // PHP's GenCtx mirrors the shared shape (see TestEntity_ts.ts) plus an
31
+ // `accessor` slot used to mangle the entity factory name when it collides
32
+ // with PHP's case-insensitive `test()` static constructor.
26
33
  type GenCtx = {
27
- model: any
28
- entity: any
29
- flow: any
34
+ model: Model
35
+ entity: ModelEntity
36
+ flow: ModelEntityFlow
30
37
  PROJUPPER: string
31
38
  accessor: string
32
39
  }
33
40
 
41
+ type OpGen = (ctx: GenCtx, step: ModelEntityFlowStep, index: number) => void
42
+
34
43
 
35
44
  const TestEntity = cmp(function TestEntity(props: any) {
36
45
  const ctx$ = props.ctx$
37
- const model = ctx$.model
46
+ const model: Model = ctx$.model
38
47
 
39
48
  const target = props.target
40
- const entity = props.entity
49
+ const entity: ModelEntity = props.entity
41
50
 
42
- const basicflow = getModelPath(model, `main.${KIT}.flow.Basic${entity.Name}Flow`)
43
- const dobasic = basicflow && true === basicflow.active
44
-
45
- if (!dobasic) {
51
+ const basicflow: ModelEntityFlow | undefined =
52
+ getModelPath(model, `main.${KIT}.flow.Basic${nom(entity, 'Name')}Flow`)
53
+ if (null == basicflow || true !== basicflow.active) {
46
54
  return
47
55
  }
48
56
 
49
57
  // PHP method names are case-insensitive — an entity literally named 'test'
50
58
  // collides with the static `test()` test-mode constructor on the SDK class.
51
59
  // Mirror the mangling done in MainEntity_php.ts.
52
- const accessor = 'test' === entity.Name.toLowerCase()
53
- ? entity.Name + '_'
54
- : entity.Name
60
+ const entName = nom(entity, 'Name')
61
+ const accessor = 'test' === entName.toLowerCase()
62
+ ? entName + '_'
63
+ : entName
64
+
65
+ const PROJUPPER = nom(model.const, 'Name').toUpperCase().replace(/[^A-Z_]/g, '_')
55
66
 
56
- const PROJUPPER = model.const.Name.toUpperCase().replace(/[^A-Z_]/g, '_')
67
+ const authActive = isAuthActive(model)
68
+ const apikeyEnvEntry = authActive
69
+ ? `\n "${PROJUPPER}_APIKEY" => "NONE",`
70
+ : ''
71
+ const apikeyLiveField = authActive
72
+ ? `\n "apikey" => $env["${PROJUPPER}_APIKEY"],`
73
+ : ''
57
74
 
58
75
  const idnames = buildIdNames(entity, basicflow)
59
76
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
@@ -92,6 +109,21 @@ class ${entity.Name}EntityTest extends TestCase
92
109
  public function test_basic_flow(): void
93
110
  {
94
111
  $setup = ${entity.name}_basic_setup(null);
112
+ // Per-op sdk-test-control.json skip.
113
+ $_live = !empty($setup["live"]);
114
+ foreach ([${(Array.from(new Set((allSteps as any[]).map((s: any) => s.op).filter(Boolean)))).map(o => `"${o}"`).join(', ')}] as $_op) {
115
+ [$_shouldSkip, $_reason] = Runner::is_control_skipped("entityOp", "${entity.name}." . $_op, $_live ? "live" : "unit");
116
+ if ($_shouldSkip) {
117
+ $this->markTestSkipped($_reason ?? "skipped via sdk-test-control.json");
118
+ return;
119
+ }
120
+ }
121
+ // The basic flow consumes synthetic IDs from the fixture. In live mode
122
+ // without an *_ENTID env override, those IDs hit the live API and 4xx.
123
+ if (!empty($setup["synthetic_only"])) {
124
+ $this->markTestSkipped("live entity test uses synthetic IDs from fixture — set ${PROJUPPER}_TEST_${entity.name.toUpperCase().replace(/[^A-Z_]/g, '_')}_ENTID JSON to run live");
125
+ return;
126
+ }
95
127
  $client = $setup["client"];
96
128
 
97
129
  `)
@@ -149,11 +181,16 @@ class ${entity.Name}EntityTest extends TestCase
149
181
 
150
182
  `)
151
183
 
152
- Content(` $env = Runner::env_override([
184
+ Content(` // Detect ENTID env override before envOverride consumes it. When live
185
+ // mode is on without a real override, the basic test runs against synthetic
186
+ // IDs from the fixture and 4xx's. Surface this so the test can skip.
187
+ $entid_env_raw = getenv("${PROJUPPER}_TEST_${entity.name.toUpperCase().replace(/[^A-Z_]/g, '_')}_ENTID");
188
+ $idmap_overridden = $entid_env_raw !== false && str_starts_with(trim($entid_env_raw), "{");
189
+
190
+ $env = Runner::env_override([
153
191
  "${PROJUPPER}_TEST_${entity.name.toUpperCase().replace(/[^A-Z_]/g, '_')}_ENTID" => $idmap,
154
192
  "${PROJUPPER}_TEST_LIVE" => "FALSE",
155
- "${PROJUPPER}_TEST_EXPLAIN" => "FALSE",
156
- "${PROJUPPER}_APIKEY" => "NONE",
193
+ "${PROJUPPER}_TEST_EXPLAIN" => "FALSE",${apikeyEnvEntry}
157
194
  ]);
158
195
 
159
196
  $idmap_resolved = Helpers::to_map(
@@ -174,20 +211,22 @@ class ${entity.Name}EntityTest extends TestCase
174
211
  Content(`
175
212
  if ($env["${PROJUPPER}_TEST_LIVE"] === "TRUE") {
176
213
  $merged_opts = Vs::merge([
177
- [
178
- "apikey" => $env["${PROJUPPER}_APIKEY"],
214
+ [${apikeyLiveField}
179
215
  ],
180
216
  $extra ?? [],
181
217
  ]);
182
218
  $client = new ${model.const.Name}SDK(Helpers::to_map($merged_opts));
183
219
  }
184
220
 
221
+ $live = $env["${PROJUPPER}_TEST_LIVE"] === "TRUE";
185
222
  return [
186
223
  "client" => $client,
187
224
  "data" => $entity_data,
188
225
  "idmap" => $idmap_resolved,
189
226
  "env" => $env,
190
227
  "explain" => $env["${PROJUPPER}_TEST_EXPLAIN"] === "TRUE",
228
+ "live" => $live,
229
+ "synthetic_only" => $live && !$idmap_overridden,
191
230
  "now" => (int)(microtime(true) * 1000),
192
231
  ];
193
232
  }
@@ -198,9 +237,9 @@ class ${entity.Name}EntityTest extends TestCase
198
237
 
199
238
  const generateCreate: OpGen = (ctx, step, index) => {
200
239
  const { entity, flow, accessor } = ctx
201
- const ref = step.input?.ref ?? entity.name + '_ref01'
202
- const entvar = step.input?.entvar ?? ref + '_ent'
203
- const datavar = step.input?.datavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
240
+ const ref = step.input.ref ?? entity.name + '_ref01'
241
+ const entvar = step.input.entvar ?? ref + '_ent'
242
+ const datavar = step.input.datavar ?? (ref + '_data' + (step.input.suffix ?? ''))
204
243
 
205
244
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
206
245
  const needsEnt = !priorSteps.some((s: any) =>
@@ -208,8 +247,8 @@ const generateCreate: OpGen = (ctx, step, index) => {
208
247
 
209
248
  const hasDatvar = priorSteps.some((s: any) => {
210
249
  if ('create' === s.op) {
211
- const priorRef = s.input?.ref ?? entity.name + '_ref01'
212
- const priorDatvar = s.input?.datavar ?? (priorRef + '_data' + (s.input?.suffix ?? ''))
250
+ const priorRef = s.input.ref ?? entity.name + '_ref01'
251
+ const priorDatvar = s.input.datavar ?? (priorRef + '_data' + (s.input.suffix ?? ''))
213
252
  return priorDatvar === datavar
214
253
  }
215
254
  return false
@@ -243,17 +282,20 @@ const generateCreate: OpGen = (ctx, step, index) => {
243
282
  $this->assertNull($err);
244
283
  $${datavar} = Helpers::to_map($${datavar}_result);
245
284
  $this->assertNotNull($${datavar});
246
- $this->assertNotNull($${datavar}["id"]);
247
285
  `)
286
+ if (null != ctx.entity.id) {
287
+ Content(` $this->assertNotNull($${datavar}["id"]);
288
+ `)
289
+ }
248
290
  }
249
291
 
250
292
 
251
293
  const generateList: OpGen = (ctx, step, index) => {
252
294
  const { entity, flow, accessor } = ctx
253
- const ref = step.input?.ref ?? entity.name + '_ref01'
254
- const entvar = step.input?.entvar ?? ref + '_ent'
255
- const matchvar = step.input?.matchvar ?? (ref + '_match' + (step.input?.suffix ?? ''))
256
- const listvar = step.input?.listvar ?? (ref + '_list' + (step.input?.suffix ?? ''))
295
+ const ref = step.input.ref ?? entity.name + '_ref01'
296
+ const entvar = step.input.entvar ?? ref + '_ent'
297
+ const matchvar = step.input.matchvar ?? (ref + '_match' + (step.input.suffix ?? ''))
298
+ const listvar = step.input.listvar ?? (ref + '_list' + (step.input.suffix ?? ''))
257
299
 
258
300
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
259
301
  const needsEnt = !priorSteps.some((s: any) =>
@@ -293,7 +335,7 @@ const generateList: OpGen = (ctx, step, index) => {
293
335
  for (const validator of step.valid) {
294
336
  const validRef = validator.def?.ref
295
337
  const hasRefData = validRef && allSteps.some((s: any) => 'create' === s.op &&
296
- ((s.input?.ref ?? entity.name + '_ref01') === validRef))
338
+ ((s.input.ref ?? entity.name + '_ref01') === validRef))
297
339
 
298
340
  if ('ItemExists' === validator.apply && hasRefData) {
299
341
  const refDataVar = validRef + '_data'
@@ -319,17 +361,19 @@ const generateList: OpGen = (ctx, step, index) => {
319
361
 
320
362
  const generateUpdate: OpGen = (ctx, step, index) => {
321
363
  const { entity, flow, accessor } = ctx
322
- const ref = step.input?.ref ?? entity.name + '_ref01'
323
- const entvar = step.input?.entvar ?? ref + '_ent'
324
- const datavar = step.input?.datavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
325
- const resdatavar = step.input?.resdatavar ?? (ref + '_resdata' + (step.input?.suffix ?? ''))
326
- const markdefvar = step.input?.markdefvar ?? (ref + '_markdef' + (step.input?.suffix ?? ''))
327
- const srcdatavar = step.input?.srcdatavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
364
+ const ref = step.input.ref ?? entity.name + '_ref01'
365
+ const entvar = step.input.entvar ?? ref + '_ent'
366
+ const datavar = step.input.datavar ?? (ref + '_data' + (step.input.suffix ?? ''))
367
+ const resdatavar = step.input.resdatavar ?? (ref + '_resdata' + (step.input.suffix ?? ''))
368
+ const markdefvar = step.input.markdefvar ?? (ref + '_markdef' + (step.input.suffix ?? ''))
369
+ const srcdatavar = step.input.srcdatavar ?? (ref + '_data' + (step.input.suffix ?? ''))
328
370
 
329
371
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
330
372
  const needsEnt = !priorSteps.some((s: any) =>
331
373
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
332
374
 
375
+ const hasEntIdU = null != entity.id
376
+
333
377
  Content(` // UPDATE
334
378
  `)
335
379
  if (needsEnt) {
@@ -337,8 +381,11 @@ const generateUpdate: OpGen = (ctx, step, index) => {
337
381
  `)
338
382
  }
339
383
  Content(` $${datavar}_up = [
340
- "id" => $${srcdatavar}["id"],
341
384
  `)
385
+ if (hasEntIdU) {
386
+ Content(` "id" => $${srcdatavar}["id"],
387
+ `)
388
+ }
342
389
 
343
390
  if (step.data) {
344
391
  const dataEntries = Object.entries(step.data).filter(([k]: any) => k !== 'id' && !k.endsWith('$'))
@@ -353,7 +400,7 @@ const generateUpdate: OpGen = (ctx, step, index) => {
353
400
 
354
401
  if (step.spec) {
355
402
  for (const spec of step.spec) {
356
- if ('TextFieldMark' === spec.apply && null != step.input?.textfield) {
403
+ if ('TextFieldMark' === spec.apply && null != step.input.textfield) {
357
404
  const fieldname = step.input.textfield
358
405
  const fieldvalue = spec.def?.mark ?? `Mark01-${ref}`
359
406
  Content(`
@@ -370,12 +417,15 @@ const generateUpdate: OpGen = (ctx, step, index) => {
370
417
  $this->assertNull($err);
371
418
  $${resdatavar} = Helpers::to_map($${resdatavar}_result);
372
419
  $this->assertNotNull($${resdatavar});
373
- $this->assertEquals($${resdatavar}["id"], $${datavar}_up["id"]);
374
420
  `)
421
+ if (hasEntIdU) {
422
+ Content(` $this->assertEquals($${resdatavar}["id"], $${datavar}_up["id"]);
423
+ `)
424
+ }
375
425
 
376
426
  if (step.spec) {
377
427
  for (const spec of step.spec) {
378
- if ('TextFieldMark' === spec.apply && null != step.input?.textfield) {
428
+ if ('TextFieldMark' === spec.apply && null != step.input.textfield) {
379
429
  Content(` $this->assertEquals($${resdatavar}[$${markdefvar}_name], $${markdefvar}_value);
380
430
  `)
381
431
  }
@@ -386,11 +436,11 @@ const generateUpdate: OpGen = (ctx, step, index) => {
386
436
 
387
437
  const generateLoad: OpGen = (ctx, step, index) => {
388
438
  const { entity, flow, accessor } = ctx
389
- const ref = step.input?.ref ?? entity.name + '_ref01'
390
- const entvar = step.input?.entvar ?? ref + '_ent'
391
- const matchvar = step.input?.matchvar ?? (ref + '_match' + (step.input?.suffix ?? ''))
392
- const datavar = step.input?.datavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
393
- const srcdatavar = step.input?.srcdatavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
439
+ const ref = step.input.ref ?? entity.name + '_ref01'
440
+ const entvar = step.input.entvar ?? ref + '_ent'
441
+ const matchvar = step.input.matchvar ?? (ref + '_match' + (step.input.suffix ?? ''))
442
+ const datavar = step.input.datavar ?? (ref + '_data' + (step.input.suffix ?? ''))
443
+ const srcdatavar = step.input.srcdatavar ?? (ref + '_data' + (step.input.suffix ?? ''))
394
444
 
395
445
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
396
446
  const hasEntVar = priorSteps.some((s: any) =>
@@ -401,20 +451,22 @@ const generateLoad: OpGen = (ctx, step, index) => {
401
451
  const hasSrcData = (!flowHasCreate && srcdatavar === (preambleRef + '_data')) ||
402
452
  priorSteps.some((s: any) => {
403
453
  if ('create' === s.op) {
404
- const priorRef = s.input?.ref ?? entity.name + '_ref01'
405
- const priorDatvar = s.input?.datavar ?? (priorRef + '_data' + (s.input?.suffix ?? ''))
454
+ const priorRef = s.input.ref ?? entity.name + '_ref01'
455
+ const priorDatvar = s.input.datavar ?? (priorRef + '_data' + (s.input.suffix ?? ''))
406
456
  return priorDatvar === srcdatavar
407
457
  }
408
458
  return false
409
459
  })
410
460
 
461
+ const hasEntId = null != entity.id
462
+
411
463
  Content(` // LOAD
412
464
  `)
413
465
  if (!hasEntVar) {
414
466
  Content(` $${entvar} = $client->${accessor}(null);
415
467
  `)
416
468
  }
417
- if (!hasSrcData) {
469
+ if (!hasSrcData && hasEntId) {
418
470
  Content(` $${srcdatavar}_raw = Vs::items(Helpers::to_map(
419
471
  Vs::getpath($setup["data"], "existing.${entity.name}")));
420
472
  $${srcdatavar} = null;
@@ -423,7 +475,8 @@ const generateLoad: OpGen = (ctx, step, index) => {
423
475
  }
424
476
  `)
425
477
  }
426
- Content(` $${matchvar} = [
478
+ if (hasEntId) {
479
+ Content(` $${matchvar} = [
427
480
  "id" => $${srcdatavar}["id"],
428
481
  ];
429
482
  [$${datavar}_loaded, $err] = $${entvar}->load($${matchvar}, null);
@@ -432,32 +485,50 @@ const generateLoad: OpGen = (ctx, step, index) => {
432
485
  $this->assertNotNull($${datavar}_load_result);
433
486
  $this->assertEquals($${datavar}_load_result["id"], $${srcdatavar}["id"]);
434
487
  `)
488
+ }
489
+ else {
490
+ Content(` $${matchvar} = [];
491
+ [$${datavar}_loaded, $err] = $${entvar}->load($${matchvar}, null);
492
+ $this->assertNull($err);
493
+ $this->assertNotNull($${datavar}_loaded);
494
+ `)
495
+ }
435
496
  }
436
497
 
437
498
 
438
499
  const generateRemove: OpGen = (ctx, step, index) => {
439
500
  const { entity, flow, accessor } = ctx
440
- const ref = step.input?.ref ?? entity.name + '_ref01'
441
- const entvar = step.input?.entvar ?? ref + '_ent'
442
- const matchvar = step.input?.matchvar ?? (ref + '_match' + (step.input?.suffix ?? ''))
443
- const srcdatavar = step.input?.srcdatavar ?? (ref + '_data')
501
+ const ref = step.input.ref ?? entity.name + '_ref01'
502
+ const entvar = step.input.entvar ?? ref + '_ent'
503
+ const matchvar = step.input.matchvar ?? (ref + '_match' + (step.input.suffix ?? ''))
504
+ const srcdatavar = step.input.srcdatavar ?? (ref + '_data')
444
505
 
445
506
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
446
507
  const needsEnt = !priorSteps.some((s: any) =>
447
508
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
448
509
 
510
+ const hasEntIdR = null != entity.id
511
+
449
512
  Content(` // REMOVE
450
513
  `)
451
514
  if (needsEnt) {
452
515
  Content(` $${entvar} = $client->${accessor}(null);
453
516
  `)
454
517
  }
455
- Content(` $${matchvar} = [
518
+ if (hasEntIdR) {
519
+ Content(` $${matchvar} = [
456
520
  "id" => $${srcdatavar}["id"],
457
521
  ];
458
522
  [$_, $err] = $${entvar}->remove($${matchvar}, null);
459
523
  $this->assertNull($err);
460
524
  `)
525
+ }
526
+ else {
527
+ Content(` $${matchvar} = [];
528
+ [$_, $err] = $${entvar}->remove($${matchvar}, null);
529
+ $this->assertNull($err);
530
+ `)
531
+ }
461
532
  }
462
533
 
463
534
 
@@ -188,10 +188,24 @@ class ProjectNameSDK
188
188
 
189
189
  if (is_array($fetched)) {
190
190
  $status = ProjectNameHelpers::to_int(Struct::getprop($fetched, "status"));
191
+ $headers = Struct::getprop($fetched, "headers") ?? [];
192
+
193
+ // No-body responses (204, 304) and explicit zero content-length
194
+ // must skip JSON parsing — calling json() on an empty body errors.
195
+ $content_length = is_array($headers) ? ($headers["content-length"] ?? null) : null;
196
+ $no_body = $status === 204 || $status === 304 || (string)$content_length === "0";
197
+
191
198
  $json_data = null;
192
- $jf = Struct::getprop($fetched, "json");
193
- if (is_callable($jf)) {
194
- $json_data = $jf();
199
+ if (!$no_body) {
200
+ $jf = Struct::getprop($fetched, "json");
201
+ if (is_callable($jf)) {
202
+ try {
203
+ $json_data = $jf();
204
+ } catch (\Throwable $e) {
205
+ // Non-JSON body — leave data null but keep status/ok.
206
+ $json_data = null;
207
+ }
208
+ }
195
209
  }
196
210
 
197
211
  return [[
@@ -9,6 +9,7 @@ import {
9
9
  Line,
10
10
  cmp,
11
11
  each,
12
+ isAuthActive,
12
13
  } from '@voxgig/sdkgen'
13
14
 
14
15
 
@@ -37,12 +38,19 @@ const Config = cmp(async function Config(props: any) {
37
38
 
38
39
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
39
40
 
41
+ const authActive = isAuthActive(model)
40
42
  let authPrefix = ''
41
43
  try { authPrefix = getModelPath(model, `main.${KIT}.config.auth.prefix`) } catch (_e) { }
42
44
 
43
45
  let baseUrl = ''
44
46
  try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
45
47
 
48
+ const authBlock = authActive
49
+ ? ` "auth": {
50
+ "prefix": "${authPrefix}",
51
+ },\n`
52
+ : ''
53
+
46
54
  File({ name: 'config.' + target.ext }, () => {
47
55
 
48
56
  Content(`# ${model.const.Name} SDK configuration
@@ -65,10 +73,7 @@ def make_config():
65
73
  Content(` },
66
74
  "options": {
67
75
  "base": "${baseUrl}",
68
- "auth": {
69
- "prefix": "${authPrefix}",
70
- },
71
- "headers": ${formatPyDict(headers, 3)},
76
+ ${authBlock} "headers": ${formatPyDict(headers, 3)},
72
77
  "entity": {
73
78
  `)
74
79
 
@@ -18,13 +18,20 @@ const Package = cmp(async function Package(props: any) {
18
18
 
19
19
  const model: Model = ctx$.model
20
20
 
21
+ // PyPI distribution name is namespaced to model.origin (e.g. "voxgig-sdk").
22
+ // PyPI names can't contain "/", so the parts are hyphen-joined. The import
23
+ // package (the `${model.name}_sdk/` dir) is unchanged.
24
+ const ns = model.origin || 'voxgig-sdk'
25
+ const pkgBase = ns.endsWith('-sdk') ? model.name : `${model.name}-sdk`
26
+ const distName = `${ns}-${pkgBase}`
27
+
21
28
  File({ name: 'pyproject.toml' }, () => {
22
29
  Content(`[build-system]
23
30
  requires = ["setuptools>=61.0"]
24
31
  build-backend = "setuptools.build_meta"
25
32
 
26
33
  [project]
27
- name = "${model.name}-sdk"
34
+ name = "${distName}"
28
35
  version = "0.0.1"
29
36
  description = "${model.const.Name} SDK for Python"
30
37
  license = "MIT"
@@ -0,0 +1,138 @@
1
+
2
+ import { cmp, each, Content } from '@voxgig/sdkgen'
3
+
4
+ import {
5
+ KIT,
6
+ getModelPath,
7
+ } from '@voxgig/apidef'
8
+
9
+
10
+ // Operation method spelling differs between Go and other languages — Go
11
+ // uses PascalCase methods with explicit ctrl arg, others use lowercase
12
+ // methods with optional ctrl. The op descriptions are language-agnostic.
13
+ const OP_DESC: Record<string, { method: string, desc: string }> = {
14
+ load: { method: 'load(match)', desc: 'Load a single entity by match criteria.' },
15
+ list: { method: 'list(match)', desc: 'List entities matching the criteria.' },
16
+ create: { method: 'create(data)', desc: 'Create a new entity with the given data.' },
17
+ update: { method: 'update(data)', desc: 'Update an existing entity.' },
18
+ remove: { method: 'remove(match)', desc: 'Remove the matching entity.' },
19
+ }
20
+
21
+
22
+ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
23
+ const { target } = props
24
+ const { model } = props.ctx$
25
+
26
+ const entity = getModelPath(model, `main.${KIT}.entity`)
27
+
28
+ const publishedEntities = each(entity)
29
+ .filter((entity: any) => entity.active !== false)
30
+
31
+ if (0 === publishedEntities.length) {
32
+ return
33
+ }
34
+
35
+ Content(`
36
+
37
+ ## Entities
38
+
39
+ `)
40
+
41
+ publishedEntities.map((entity: any) => {
42
+ const opnames = Object.keys(entity.op || {})
43
+ const fields = entity.fields || []
44
+
45
+ Content(`
46
+ ### ${entity.Name}
47
+
48
+ `)
49
+
50
+ if (entity.short) {
51
+ Content(`${entity.short}
52
+
53
+ `)
54
+ }
55
+
56
+ Content(`Create an instance: \`const ${entity.name} = client.${entity.Name}()\`
57
+
58
+ `)
59
+
60
+ if (opnames.length > 0) {
61
+ Content(`#### Operations
62
+
63
+ | Method | Description |
64
+ | --- | --- |
65
+ `)
66
+ opnames.map((opname: string) => {
67
+ const info = OP_DESC[opname]
68
+ if (info) {
69
+ Content(`| \`${info.method}\` | ${info.desc} |
70
+ `)
71
+ }
72
+ })
73
+
74
+ Content(`
75
+ `)
76
+ }
77
+
78
+ if (fields.length > 0) {
79
+ Content(`#### Fields
80
+
81
+ | Field | Type | Description |
82
+ | --- | --- | --- |
83
+ `)
84
+
85
+ each(fields, (field: any) => {
86
+ const desc = field.short || ''
87
+ Content(`| \`${field.name}\` | \`${field.type || 'any'}\` | ${desc} |
88
+ `)
89
+ })
90
+
91
+ Content(`
92
+ `)
93
+ }
94
+
95
+ if (opnames.includes('load')) {
96
+ Content(`#### Example: Load
97
+
98
+ \`\`\`ts
99
+ const ${entity.name} = await client.${entity.Name}().load({ id: '${entity.name}_id' })
100
+ \`\`\`
101
+
102
+ `)
103
+ }
104
+
105
+ if (opnames.includes('list')) {
106
+ Content(`#### Example: List
107
+
108
+ \`\`\`ts
109
+ const ${entity.name}s = await client.${entity.Name}().list()
110
+ \`\`\`
111
+
112
+ `)
113
+ }
114
+
115
+ if (opnames.includes('create')) {
116
+ Content(`#### Example: Create
117
+
118
+ \`\`\`ts
119
+ const ${entity.name} = await client.${entity.Name}().create({
120
+ `)
121
+ each(fields, (field: any) => {
122
+ if ('id' !== field.name && field.req) {
123
+ Content(` ${field.name}: /* ${field.type || 'value'} */,
124
+ `)
125
+ }
126
+ })
127
+ Content(`})
128
+ \`\`\`
129
+
130
+ `)
131
+ }
132
+ })
133
+ })
134
+
135
+
136
+ export {
137
+ ReadmeEntity
138
+ }
@@ -1,10 +1,14 @@
1
1
 
2
- import { cmp, Content } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive } from '@voxgig/sdkgen'
3
3
 
4
4
 
5
5
  const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
6
6
  const { target, ctx$: { model } } = props
7
7
 
8
+ const apikeyEnvLine = isAuthActive(model)
9
+ ? `\n${model.NAME}_APIKEY=<your-key>`
10
+ : ''
11
+
8
12
  Content(`### Make a direct HTTP request
9
13
 
10
14
  For endpoints not covered by entity methods:
@@ -78,8 +82,7 @@ client = ${model.const.Name}SDK({
78
82
  Create a \`.env.local\` file at the project root:
79
83
 
80
84
  \`\`\`
81
- ${model.NAME}_TEST_LIVE=TRUE
82
- ${model.NAME}_APIKEY=<your-key>
85
+ ${model.NAME}_TEST_LIVE=TRUE${apikeyEnvLine}
83
86
  \`\`\`
84
87
 
85
88
  Then run:
@@ -0,0 +1,18 @@
1
+
2
+ import { cmp, Content } from '@voxgig/sdkgen'
3
+
4
+
5
+ const ReadmeIntro = cmp(function ReadmeIntro(props: any) {
6
+ const { target, ctx$: { model } } = props
7
+
8
+ Content(`# ${model.Name} ${target.title} SDK
9
+
10
+ The ${target.title} SDK for the ${model.Name} API. Provides an entity-oriented interface following Pythonic conventions.
11
+
12
+ `)
13
+ })
14
+
15
+
16
+ export {
17
+ ReadmeIntro
18
+ }
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, each, Content } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, isAuthActive } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -13,6 +13,10 @@ const ReadmeModel = cmp(function ReadmeModel(props: any) {
13
13
  const entity = getModelPath(model, `main.${KIT}.entity`)
14
14
  const entityList = each(entity).filter((e: any) => e.active !== false)
15
15
 
16
+ const apikeyOptionRow = isAuthActive(model)
17
+ ? '| `apikey` | `str` | API key for authentication. |\n'
18
+ : ''
19
+
16
20
  Content(`### ${model.const.Name}SDK
17
21
 
18
22
  \`\`\`python
@@ -25,8 +29,7 @@ Creates a new SDK client.
25
29
 
26
30
  | Option | Type | Description |
27
31
  | --- | --- | --- |
28
- | \`apikey\` | \`str\` | API key for authentication. |
29
- | \`base\` | \`str\` | Base URL of the API server. |
32
+ ${apikeyOptionRow}| \`base\` | \`str\` | Base URL of the API server. |
30
33
  | \`prefix\` | \`str\` | URL path prefix prepended to all requests. |
31
34
  | \`suffix\` | \`str\` | URL path suffix appended to all requests. |
32
35
  | \`feature\` | \`dict\` | Feature activation flags. |