@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
@@ -29,6 +29,7 @@ import {
29
29
  Slot,
30
30
  cmp,
31
31
  each,
32
+ isAuthActive,
32
33
  } from '@voxgig/sdkgen'
33
34
 
34
35
 
@@ -37,19 +38,38 @@ import {
37
38
  } from './utility_ts'
38
39
 
39
40
 
40
- type OpGen = (model: any, entity: any, flow: any, step: any, index: { key$: number, val$: any }) => void
41
+ // GenCtx is the per-language generation context passed to every OpGen.
42
+ // Languages with extra needs (Go's `gomodule`, PHP's `accessor`) extend
43
+ // this shape locally. The signature `(ctx, step, index)` is now uniform
44
+ // across all seven language tracks (Phase 1 of the templates refactor).
45
+ type GenCtx = {
46
+ model: Model
47
+ entity: ModelEntity
48
+ flow: ModelEntityFlow
49
+ PROJUPPER: string
50
+ }
51
+
52
+ type OpGen = (ctx: GenCtx, step: ModelEntityFlowStep, index: number) => void
41
53
 
42
54
 
43
55
  const TestEntity = cmp(function TestEntity(props: any) {
44
56
  const ctx$ = props.ctx$
45
- const model = ctx$.model
57
+ const model: Model = ctx$.model
46
58
  const stdrep = ctx$.stdrep
47
59
 
48
60
  const target = props.target
49
- const entity = props.entity
61
+ const entity: ModelEntity = props.entity
50
62
 
51
63
  const PROJENVNAME = nom(model.const, 'NAME').replace(/[^A-Z_]/g, '_')
52
64
  const ENTENVNAME = nom(entity, 'NAME').replace(/[^A-Z_]/g, '_')
65
+ const authActive = isAuthActive(model)
66
+ const apikeyEnvEntry = authActive
67
+ ? `\n '${PROJENVNAME}_APIKEY': 'NONE',`
68
+ : ''
69
+ const apikeyLiveField = authActive
70
+ ? `
71
+ apikey: env.${PROJENVNAME}_APIKEY,`
72
+ : ''
53
73
 
54
74
  // TODO: should be a utility function
55
75
  const ff = projectPath('src/cmp/ts/fragment/')
@@ -63,11 +83,13 @@ const TestEntity = cmp(function TestEntity(props: any) {
63
83
  replace: {
64
84
  SdkName: nom(model.const, 'Name'),
65
85
  EntityName: nom(entity, 'Name'),
86
+ entityname: entity.name,
87
+ PROJECTNAME: PROJENVNAME,
66
88
  ...stdrep,
67
89
  }
68
90
  }, () => {
69
91
 
70
- const basicflow = getModelPath(model, `main.${KIT}.flow.Basic${entity.Name}Flow`)
92
+ const basicflow = getModelPath(model, `main.${KIT}.flow.Basic${nom(entity, 'Name')}Flow`)
71
93
 
72
94
  const dobasic = basicflow && true === basicflow.active
73
95
 
@@ -120,19 +142,26 @@ function basicSetup(extra?: any) {
120
142
  }]
121
143
  })
122
144
 
145
+ // Detect whether the user provided a real ENTID JSON via env var. The
146
+ // basic flow consumes synthetic IDs from the fixture file; without an
147
+ // override those synthetic IDs reach the live API and 4xx. Surface this
148
+ // to the test so it can skip rather than fail.
149
+ const idmapEnvVal = process.env['${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID']
150
+ const idmapOverridden = null != idmapEnvVal && idmapEnvVal.trim().startsWith('{')
151
+
123
152
  const env = envOverride({
124
153
  '${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID': idmap,
125
154
  '${PROJENVNAME}_TEST_LIVE': 'FALSE',
126
- '${PROJENVNAME}_TEST_EXPLAIN': 'FALSE',
127
- '${PROJENVNAME}_APIKEY': 'NONE',
155
+ '${PROJENVNAME}_TEST_EXPLAIN': 'FALSE',${apikeyEnvEntry}
128
156
  })
129
157
 
130
158
  idmap = env['${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID']
131
159
 
132
- if ('TRUE' === env.${PROJENVNAME}_TEST_LIVE) {
160
+ const live = 'TRUE' === env.${PROJENVNAME}_TEST_LIVE
161
+
162
+ if (live) {
133
163
  client = new ${model.Name}SDK(merge([
134
- {
135
- apikey: env.${PROJENVNAME}_APIKEY,
164
+ {${apikeyLiveField}
136
165
  },
137
166
  extra
138
167
  ]))
@@ -146,6 +175,8 @@ function basicSetup(extra?: any) {
146
175
  struct,
147
176
  data: entityData,
148
177
  explain: 'TRUE' === env.${PROJENVNAME}_TEST_EXPLAIN,
178
+ live,
179
+ syntheticOnly: live && !idmapOverridden,
149
180
  now: Date.now(),
150
181
  }
151
182
 
@@ -160,8 +191,30 @@ function basicSetup(extra?: any) {
160
191
  (s: any) => s.op === 'create'
161
192
  )
162
193
 
194
+ // The basic test exercises a flow with one or more ops (load,
195
+ // list, create, update, remove, ...). The control file lets users
196
+ // skip per-op for an entity. Since the flow is sequential and
197
+ // dependent (e.g. update needs prior load), skipping ANY op the
198
+ // flow exercises skips the whole basic test.
199
+ const flowOps = Array.from(new Set(
200
+ (basicflow.step as any[]).map((s: any) => s.op).filter(Boolean)
201
+ ))
202
+ const flowOpsLiteral = '[' + flowOps.map((o: any) => `'${o}'`).join(', ') + ']'
203
+
163
204
  Content(`
205
+ const live = 'TRUE' === process.env.${PROJENVNAME}_TEST_LIVE
206
+ for (const op of ${flowOpsLiteral}) {
207
+ if (maybeSkipControl(t, 'entityOp', '${entity.name}.' + op, live)) return
208
+ }
209
+
164
210
  const setup = basicSetup()
211
+ // The basic flow consumes synthetic IDs and field values from the
212
+ // fixture (entity TestData.json). Those don't exist on the live API.
213
+ // Skip live runs unless the user provided a real ENTID env override.
214
+ if (setup.syntheticOnly) {
215
+ t.skip('live entity test uses synthetic IDs from fixture — set ${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID JSON to run live')
216
+ return
217
+ }
165
218
  const client = setup.client
166
219
  const struct = setup.struct
167
220
 
@@ -179,10 +232,15 @@ function basicSetup(extra?: any) {
179
232
  `)
180
233
  }
181
234
 
182
- each(basicflow.step, (step: any, index: any) => {
183
- const opgen: OpGen = GENERATE_OP[step.op]
184
- opgen(model, entity, basicflow, step, index)
185
- Content('\n')
235
+ const genCtx: GenCtx = {
236
+ model, entity, flow: basicflow, PROJUPPER: PROJENVNAME,
237
+ }
238
+ each(basicflow.step, (step: ModelEntityFlowStep, index: number) => {
239
+ const opgen = GENERATE_OP[step.op]
240
+ if (null != opgen) {
241
+ opgen(genCtx, step, index)
242
+ Content('\n')
243
+ }
186
244
  })
187
245
  })
188
246
  })
@@ -191,25 +249,20 @@ function basicSetup(extra?: any) {
191
249
  })
192
250
 
193
251
 
194
- const generateCreate: OpGen = (
195
- model: Model,
196
- entity: ModelEntity,
197
- flow: ModelEntityFlow,
198
- step: ModelEntityFlowStep,
199
- index: any
200
- ) => {
252
+ const generateCreate: OpGen = (ctx, step, index) => {
253
+ const { entity, flow } = ctx
201
254
  const ref = step.input.ref ?? entity.name + '_ref01'
202
255
  const entvar = step.input.entvar ?? ref + '_ent'
203
256
  const datavar = step.input.datavar ?? (ref + '_data' + (step.input.suffix ?? ''))
204
257
 
205
- const priorSteps = Object.values(flow.step).slice(0, Number(index))
206
- const needsEnt = !priorSteps.some((s: any) =>
258
+ const priorSteps = flow.step.slice(0, Number(index))
259
+ const needsEnt = !priorSteps.some(s =>
207
260
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
208
261
 
209
- const hasDatvar = priorSteps.some((s: any) => {
262
+ const hasDatvar = priorSteps.some(s => {
210
263
  if ('create' === s.op) {
211
- const priorRef = s.input?.ref ?? entity.name + '_ref01'
212
- const priorDatvar = s.input?.datavar ?? (priorRef + '_data' + (s.input?.suffix ?? ''))
264
+ const priorRef = s.input.ref ?? entity.name + '_ref01'
265
+ const priorDatvar = s.input.datavar ?? (priorRef + '_data' + (s.input.suffix ?? ''))
213
266
  return priorDatvar === datavar
214
267
  }
215
268
  return false
@@ -235,27 +288,31 @@ const generateCreate: OpGen = (
235
288
  `)
236
289
  })
237
290
 
291
+ const hasEntIdC = null != entity.id
292
+
238
293
  Content(`
239
294
  ${datavar} = await ${entvar}.create(${datavar})
240
- assert(null != ${datavar}.id)
241
295
  `)
296
+ if (hasEntIdC) {
297
+ Content(` assert(null != ${datavar}.id)
298
+ `)
299
+ }
300
+ else {
301
+ Content(` assert(null != ${datavar})
302
+ `)
303
+ }
242
304
  }
243
305
 
244
306
 
245
- const generateList: OpGen = (
246
- model: Model,
247
- entity: ModelEntity,
248
- flow: ModelEntityFlow,
249
- step: ModelEntityFlowStep,
250
- index: any
251
- ) => {
307
+ const generateList: OpGen = (ctx, step, index) => {
308
+ const { entity, flow } = ctx
252
309
  const ref = step.input.ref ?? entity.name + '_ref01'
253
310
  const entvar = step.input.entvar ?? ref + '_ent'
254
311
  const matchvar = step.input.matchvar ?? (ref + '_match' + (step.input.suffix ?? ''))
255
312
  const listvar = step.input.listvar ?? (ref + '_list' + (step.input.suffix ?? ''))
256
313
 
257
- const priorSteps = Object.values(flow.step).slice(0, Number(index))
258
- const needsEnt = !priorSteps.some((s: any) =>
314
+ const priorSteps = flow.step.slice(0, Number(index))
315
+ const needsEnt = !priorSteps.some(s =>
259
316
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
260
317
 
261
318
  Content(`
@@ -276,12 +333,12 @@ const generateList: OpGen = (
276
333
  Content(`
277
334
  const ${listvar} = await ${entvar}.list(${matchvar})
278
335
  `)
279
- const allSteps = Object.values(flow.step)
336
+ const allSteps = flow.step
280
337
  for (let vI = 0; vI < step.valid.length; vI++) {
281
338
  const validator = step.valid[vI]
282
339
  const validRef = validator.def?.ref
283
- const hasRefData = validRef && allSteps.some((s: any) => 'create' === s.op &&
284
- ((s.input?.ref ?? entity.name + '_ref01') === validRef))
340
+ const hasRefData = validRef && allSteps.some(s => 'create' === s.op &&
341
+ ((s.input.ref ?? entity.name + '_ref01') === validRef))
285
342
 
286
343
  if ('ItemExists' === validator.apply && hasRefData) {
287
344
  Content(`
@@ -297,13 +354,8 @@ const generateList: OpGen = (
297
354
  }
298
355
 
299
356
 
300
- const generateUpdate: OpGen = (
301
- model: Model,
302
- entity: ModelEntity,
303
- flow: ModelEntityFlow,
304
- step: ModelEntityFlowStep,
305
- index: any
306
- ) => {
357
+ const generateUpdate: OpGen = (ctx, step, index) => {
358
+ const { entity, flow } = ctx
307
359
  const ref = step.input.ref ?? entity.name + '_ref01'
308
360
  const entvar = step.input.entvar ?? ref + '_ent'
309
361
  const datavar = step.input.datavar ?? (ref + '_data' + (step.input.suffix ?? ''))
@@ -311,10 +363,12 @@ const generateUpdate: OpGen = (
311
363
  const markdefvar = step.input.markdefvar ?? (ref + '_markdef' + (step.input.suffix ?? ''))
312
364
  const srcdatavar = step.input.srcdatavar ?? (ref + '_data' + (step.input.suffix ?? ''))
313
365
 
314
- const priorSteps = Object.values(flow.step).slice(0, Number(index))
315
- const needsEnt = !priorSteps.some((s: any) =>
366
+ const priorSteps = flow.step.slice(0, Number(index))
367
+ const needsEnt = !priorSteps.some(s =>
316
368
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
317
369
 
370
+ const hasEntIdU = null != entity.id
371
+
318
372
  Content(`
319
373
  // UPDATE
320
374
  `)
@@ -323,8 +377,11 @@ const generateUpdate: OpGen = (
323
377
  `)
324
378
  }
325
379
  Content(` const ${datavar}: any = {}
326
- ${datavar}.id = ${srcdatavar}.id
327
380
  `)
381
+ if (hasEntIdU) {
382
+ Content(` ${datavar}.id = ${srcdatavar}.id
383
+ `)
384
+ }
328
385
 
329
386
  each(step.data, (mi: any) => {
330
387
  if ('id' !== mi.key$) {
@@ -348,8 +405,15 @@ const generateUpdate: OpGen = (
348
405
 
349
406
  Content(`
350
407
  const ${resdatavar} = await ${entvar}.update(${datavar})
351
- assert(${resdatavar}.id === ${datavar}.id)
352
408
  `)
409
+ if (hasEntIdU) {
410
+ Content(` assert(${resdatavar}.id === ${datavar}.id)
411
+ `)
412
+ }
413
+ else {
414
+ Content(` assert(null != ${resdatavar})
415
+ `)
416
+ }
353
417
 
354
418
  for (let sI = 0; sI < step.spec.length; sI++) {
355
419
  const spec = step.spec[sI]
@@ -363,37 +427,57 @@ const generateUpdate: OpGen = (
363
427
  }
364
428
 
365
429
 
366
- const generateLoad: OpGen = (
367
- model: Model,
368
- entity: ModelEntity,
369
- flow: ModelEntityFlow,
370
- step: ModelEntityFlowStep,
371
- index: any
372
- ) => {
430
+ const generateLoad: OpGen = (ctx, step, index) => {
431
+ const { entity, flow } = ctx
373
432
  const ref = step.input.ref ?? entity.name + '_ref01'
374
433
  const entvar = step.input.entvar ?? ref + '_ent'
375
434
  const matchvar = step.input.matchvar ?? (ref + '_match' + (step.input.suffix ?? ''))
376
435
  const datavar = step.input.datavar ?? (ref + '_data' + (step.input.suffix ?? ''))
377
436
  const srcdatavar = step.input.srcdatavar ?? (ref + '_data' + (step.input.suffix ?? ''))
378
437
 
379
- const priorSteps = Object.values(flow.step).slice(0, Number(index))
380
- const hasEntVar = priorSteps.some((s: any) =>
438
+ const priorSteps = flow.step.slice(0, Number(index))
439
+ const hasEntVar = priorSteps.some(s =>
381
440
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
382
441
 
383
442
  // Check if srcdatavar was declared by a prior create step or by the
384
443
  // preamble bootstrap (which runs when the flow has no create step)
385
- const flowHasCreate = Object.values(flow.step).some((s: any) => s.op === 'create')
444
+ const flowHasCreate = flow.step.some(s => s.op === 'create')
386
445
  const preambleRef = entity.name + '_ref01'
387
446
  const hasSrcData = (!flowHasCreate && srcdatavar === preambleRef + '_data') ||
388
- priorSteps.some((s: any) => {
447
+ priorSteps.some(s => {
389
448
  if ('create' === s.op) {
390
- const priorRef = s.input?.ref ?? entity.name + '_ref01'
391
- const priorDatvar = s.input?.datavar ?? (priorRef + '_data' + (s.input?.suffix ?? ''))
449
+ const priorRef = s.input.ref ?? entity.name + '_ref01'
450
+ const priorDatvar = s.input.datavar ?? (priorRef + '_data' + (s.input.suffix ?? ''))
392
451
  return priorDatvar === srcdatavar
393
452
  }
394
453
  return false
395
454
  })
396
455
 
456
+ const hasEntId = null != entity.id
457
+
458
+ // When the entity has no id model field but the load operation requires
459
+ // path parameters (e.g. cotizacion needs {casa}/{fecha}), calling
460
+ // load({}) leaves the URL with literal {param} placeholders and the live
461
+ // API returns 404 HTML, which the SDK then fails to parse as JSON. There
462
+ // is no synthetic identifier to substitute, so skip emitting the load
463
+ // step's call in that case — but still declare the entity-var if no
464
+ // prior step has, so that later flow steps (e.g. remove) referencing
465
+ // ${entvar} compile.
466
+ const loadOp = entity.op?.load
467
+ const loadPoint = loadOp?.points?.[0]
468
+ const loadPathParams = loadPoint?.args?.params || []
469
+ const loadHasRequiredParams = loadPathParams.some((p: any) => p.reqd !== false)
470
+ if (!hasEntId && loadHasRequiredParams) {
471
+ if (!hasEntVar) {
472
+ Content(`
473
+ // LOAD: skipped — no entity id field and load requires path params.
474
+ // Entity-var is declared here so later flow steps still compile.
475
+ const ${entvar} = client.${nom(entity, 'Name')}()
476
+ `)
477
+ }
478
+ return
479
+ }
480
+
397
481
  Content(`
398
482
  // LOAD
399
483
  `)
@@ -401,34 +485,39 @@ const generateLoad: OpGen = (
401
485
  Content(` const ${entvar} = client.${nom(entity, 'Name')}()
402
486
  `)
403
487
  }
404
- if (!hasSrcData) {
488
+ if (!hasSrcData && hasEntId) {
405
489
  Content(` const ${srcdatavar} = Object.values(setup.data.existing.${entity.name})[0] as any
406
490
  `)
407
491
  }
408
- Content(` const ${matchvar}: any = {}
492
+ if (hasEntId) {
493
+ Content(` const ${matchvar}: any = {}
409
494
  ${matchvar}.id = ${srcdatavar}.id
410
495
  const ${datavar} = await ${entvar}.load(${matchvar})
411
496
  assert(${datavar}.id === ${srcdatavar}.id)
412
497
  `)
498
+ }
499
+ else {
500
+ Content(` const ${matchvar}: any = {}
501
+ const ${datavar} = await ${entvar}.load(${matchvar})
502
+ assert(null != ${datavar})
503
+ `)
504
+ }
413
505
  }
414
506
 
415
507
 
416
- const generateRemove: OpGen = (
417
- model: Model,
418
- entity: ModelEntity,
419
- flow: ModelEntityFlow,
420
- step: ModelEntityFlowStep,
421
- index: any
422
- ) => {
508
+ const generateRemove: OpGen = (ctx, step, index) => {
509
+ const { entity, flow } = ctx
423
510
  const ref = step.input.ref ?? entity.name + '_ref01'
424
511
  const entvar = step.input.entvar ?? ref + '_ent'
425
512
  const matchvar = step.input.matchvar ?? (ref + '_match' + (step.input.suffix ?? ''))
426
513
  const srcdatavar = step.input.srcdatavar ?? (ref + '_data')
427
514
 
428
- const priorSteps = Object.values(flow.step).slice(0, Number(index))
429
- const needsEnt = !priorSteps.some((s: any) =>
515
+ const priorSteps = flow.step.slice(0, Number(index))
516
+ const needsEnt = !priorSteps.some(s =>
430
517
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
431
518
 
519
+ const hasEntIdR = null != entity.id
520
+
432
521
  Content(`
433
522
  // REMOVE
434
523
  `)
@@ -437,8 +526,12 @@ const generateRemove: OpGen = (
437
526
  `)
438
527
  }
439
528
  Content(` const ${matchvar}: any = {}
440
- ${matchvar}.id = ${srcdatavar}.id
441
- await ${entvar}.remove(${matchvar})
529
+ `)
530
+ if (hasEntIdR) {
531
+ Content(` ${matchvar}.id = ${srcdatavar}.id
532
+ `)
533
+ }
534
+ Content(` await ${entvar}.remove(${matchvar})
442
535
  `)
443
536
  }
444
537
 
@@ -31,11 +31,7 @@ class Config {
31
31
  options = {
32
32
  base: '$$main.kit.info.servers.0.url$$',
33
33
 
34
- auth: {
35
- prefix: '$$main.kit.config.auth.prefix$$',
36
- },
37
-
38
- headers: 'HEADERS',
34
+ 'AUTHBLOCK'headers: 'HEADERS',
39
35
 
40
36
  entity: {
41
37
  // #EntityConfigs
@@ -2,7 +2,7 @@
2
2
  const envlocal = __dirname + '/../../../.env.local'
3
3
  require('dotenv').config({ quiet: true, path: [envlocal] })
4
4
 
5
- import { test, describe } from 'node:test'
5
+ import { test, describe, afterEach } from 'node:test'
6
6
  import assert from 'node:assert'
7
7
 
8
8
 
@@ -10,11 +10,18 @@ import { ProjectNameSDK } from '../../..'
10
10
 
11
11
  import {
12
12
  envOverride,
13
+ liveDelay,
14
+ maybeSkipControl,
15
+ skipIfMissingIds,
13
16
  } from '../../utility'
14
17
 
15
18
 
16
19
  describe('EntityNameDirect', async () => {
17
20
 
21
+ // Per-test live pacing. Delay is read from sdk-test-control.json's
22
+ // `test.live.delayMs`; only sleeps when PROJECTNAME_TEST_LIVE=TRUE.
23
+ afterEach(liveDelay('PROJECTNAME_TEST_LIVE'))
24
+
18
25
  test('direct-exists', async () => {
19
26
  const sdk = new ProjectNameSDK({
20
27
  system: { fetch: async () => ({}) }
@@ -5,7 +5,7 @@ require('dotenv').config({ quiet: true, path: [envlocal] })
5
5
  import Path from 'node:path'
6
6
  import * as Fs from 'node:fs'
7
7
 
8
- import { test, describe } from 'node:test'
8
+ import { test, describe, afterEach } from 'node:test'
9
9
  import assert from 'node:assert'
10
10
 
11
11
 
@@ -13,16 +13,22 @@ import { ProjectNameSDK, BaseFeature, stdutil } from '../../..'
13
13
 
14
14
  import {
15
15
  envOverride,
16
+ liveDelay,
16
17
  makeCtrl,
17
18
  makeMatch,
18
19
  makeReqdata,
19
20
  makeStepData,
20
21
  makeValid,
22
+ maybeSkipControl,
21
23
  } from '../../utility'
22
24
 
23
25
 
24
26
  describe('EntityNameEntity', async () => {
25
27
 
28
+ // Per-test live pacing. Delay is read from sdk-test-control.json's
29
+ // `test.live.delayMs`; only sleeps when PROJECTNAME_TEST_LIVE=TRUE.
30
+ afterEach(liveDelay('PROJECTNAME_TEST_LIVE'))
31
+
26
32
  test('instance', async () => {
27
33
  const testsdk = ProjectNameSDK.test()
28
34
  const ent = testsdk.EntityName()
@@ -30,7 +36,7 @@ describe('EntityNameEntity', async () => {
30
36
  })
31
37
 
32
38
 
33
- test('basic', async () => {
39
+ test('basic', async (t) => {
34
40
  // <[SLOT:basic]>
35
41
  })
36
42
  })
@@ -162,7 +162,27 @@ class ProjectNameSDK {
162
162
  }
163
163
 
164
164
  const status = fetched.status
165
- const json = 'function' === typeof fetched.json ? await fetched.json() : fetched.json
165
+
166
+ // No body responses (204 No Content, 304 Not Modified) and explicit
167
+ // zero content-length must skip JSON parsing — fetched.json() would
168
+ // throw `Unexpected end of JSON input` on an empty body.
169
+ const headers = fetched.headers
170
+ const contentLength = headers && 'function' === typeof headers.get
171
+ ? headers.get('content-length')
172
+ : (headers || {})['content-length']
173
+ const noBody = 204 === status || 304 === status || '0' === String(contentLength)
174
+
175
+ let json: any = undefined
176
+ if (!noBody) {
177
+ try {
178
+ json = 'function' === typeof fetched.json ? await fetched.json() : fetched.json
179
+ }
180
+ catch (parseErr) {
181
+ // Body wasn't valid JSON — surface the raw response rather than
182
+ // throwing. data stays undefined; callers can inspect status/headers.
183
+ json = undefined
184
+ }
185
+ }
166
186
 
167
187
  return {
168
188
  ok: status >= 200 && status < 300,
@@ -69,8 +69,28 @@ func (f *TestFeature) Init(ctx *core.Context, options map[string]any) {
69
69
  entmap = map[string]any{}
70
70
  }
71
71
 
72
+ // For single-entity ops (load, remove) with an empty explicit match,
73
+ // fall back to the id the entity client already knows from a prior
74
+ // create/load (in ctx.Match / ctx.Data). Mirrors the TS mock where
75
+ // param() resolves the id from that accumulated state.
76
+ resolveMatch := func(explicit map[string]any) map[string]any {
77
+ if len(explicit) > 0 {
78
+ return explicit
79
+ }
80
+ for _, src := range []any{ctx.Match, ctx.Data} {
81
+ if src == nil {
82
+ continue
83
+ }
84
+ v := vs.GetProp(src, "id")
85
+ if v != nil && v != "__UNDEFINED__" {
86
+ return map[string]any{"id": v}
87
+ }
88
+ }
89
+ return map[string]any{}
90
+ }
91
+
72
92
  if op.Name == "load" {
73
- args := self.buildArgs(ctx, op, ctx.Reqmatch)
93
+ args := self.buildArgs(ctx, op, resolveMatch(ctx.Reqmatch))
74
94
  found := vs.Select(entmap, args)
75
95
  ent := vs.GetElem(found, 0)
76
96
  if ent == nil {
@@ -91,9 +111,37 @@ func (f *TestFeature) Init(ctx *core.Context, options map[string]any) {
91
111
  out := vs.Clone(found)
92
112
  return respond(200, out, nil), nil
93
113
  } else if op.Name == "update" {
94
- args := self.buildArgs(ctx, op, ctx.Reqdata)
114
+ // Match the existing entity by id only (or its alias). Reqdata
115
+ // also contains the new field values, which would otherwise
116
+ // cause Select to filter out the entity we want to update.
117
+ // Falls back to first entity when no match found, mirroring
118
+ // the TS mock.
119
+ updateMatch := map[string]any{}
120
+ if ctx.Reqdata != nil {
121
+ if v, has := ctx.Reqdata["id"]; has {
122
+ updateMatch["id"] = v
123
+ }
124
+ if op.Alias != nil {
125
+ if aliasIdRaw := vs.GetProp(op.Alias, "id"); aliasIdRaw != nil {
126
+ if aliasId, ok := aliasIdRaw.(string); ok {
127
+ if v, has := ctx.Reqdata[aliasId]; has {
128
+ updateMatch[aliasId] = v
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ args := self.buildArgs(ctx, op, updateMatch)
95
135
  found := vs.Select(entmap, args)
96
136
  ent := vs.GetElem(found, 0)
137
+ if ent == nil && entmap != nil {
138
+ for _, e := range entmap {
139
+ if _, ok := e.(map[string]any); ok {
140
+ ent = e
141
+ break
142
+ }
143
+ }
144
+ }
97
145
  if ent == nil {
98
146
  return respond(404, nil, map[string]any{"statusText": "Not found"}), nil
99
147
  }
@@ -109,7 +157,7 @@ func (f *TestFeature) Init(ctx *core.Context, options map[string]any) {
109
157
  out := vs.Clone(ent)
110
158
  return respond(200, out, nil), nil
111
159
  } else if op.Name == "remove" {
112
- args := self.buildArgs(ctx, op, ctx.Reqmatch)
160
+ args := self.buildArgs(ctx, op, resolveMatch(ctx.Reqmatch))
113
161
  found := vs.Select(entmap, args)
114
162
  ent := vs.GetElem(found, 0)
115
163
  if ent == nil {