@voxgig/sdkgen 1.3.17 → 1.3.18

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 (220) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/Entity.js +5 -1
  3. package/dist/cmp/Entity.js.map +1 -1
  4. package/dist/cmp/Feature.js +2 -1
  5. package/dist/cmp/Feature.js.map +1 -1
  6. package/dist/helpers/canonType.js +8 -3
  7. package/dist/helpers/canonType.js.map +1 -1
  8. package/dist/helpers/collectDeps.d.ts +1 -1
  9. package/dist/helpers/collectDeps.js +36 -13
  10. package/dist/helpers/collectDeps.js.map +1 -1
  11. package/dist/helpers/naming.d.ts +3 -1
  12. package/dist/helpers/naming.js +53 -0
  13. package/dist/helpers/naming.js.map +1 -1
  14. package/dist/helpers/opShape.d.ts +3 -1
  15. package/dist/helpers/opShape.js +87 -8
  16. package/dist/helpers/opShape.js.map +1 -1
  17. package/dist/sdkgen.d.ts +3 -3
  18. package/dist/sdkgen.js +6 -2
  19. package/dist/sdkgen.js.map +1 -1
  20. package/dist/tsconfig.tsbuildinfo +1 -1
  21. package/package.json +15 -10
  22. package/project/.sdk/model/target/cpp.aontu +4 -4
  23. package/project/.sdk/model/target/go-cli.aontu +17 -17
  24. package/project/.sdk/model/target/go-mcp.aontu +11 -11
  25. package/project/.sdk/model/target/go.aontu +1 -1
  26. package/project/.sdk/model/target/java.aontu +4 -4
  27. package/project/.sdk/model/target/kotlin.aontu +4 -4
  28. package/project/.sdk/model/target/lean.aontu +51 -0
  29. package/project/.sdk/model/target/scala.aontu +4 -4
  30. package/project/.sdk/src/cmp/c/EntityTypes_c.ts +3 -4
  31. package/project/.sdk/src/cmp/c/Entity_c.ts +3 -3
  32. package/project/.sdk/src/cmp/c/ReadmeExamplesTest_c.ts +24 -7
  33. package/project/.sdk/src/cmp/c/TestDirect_c.ts +3 -3
  34. package/project/.sdk/src/cmp/c/TestEntity_c.ts +1 -1
  35. package/project/.sdk/src/cmp/clojure/Entity_clojure.ts +1 -1
  36. package/project/.sdk/src/cmp/clojure/ReadmeExamplesTest_clojure.ts +19 -8
  37. package/project/.sdk/src/cmp/cpp/EntityTypes_cpp.ts +2 -2
  38. package/project/.sdk/src/cmp/cpp/Entity_cpp.ts +3 -3
  39. package/project/.sdk/src/cmp/cpp/MainEntity_cpp.ts +2 -2
  40. package/project/.sdk/src/cmp/cpp/ReadmeExamplesTest_cpp.ts +136 -0
  41. package/project/.sdk/src/cmp/cpp/TestDirect_cpp.ts +3 -3
  42. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +4 -0
  43. package/project/.sdk/src/cmp/csharp/EntityTypes_csharp.ts +2 -3
  44. package/project/.sdk/src/cmp/csharp/Entity_csharp.ts +3 -3
  45. package/project/.sdk/src/cmp/csharp/MainEntity_csharp.ts +2 -2
  46. package/project/.sdk/src/cmp/csharp/Package_csharp.ts +1 -1
  47. package/project/.sdk/src/cmp/csharp/ReadmeExamplesTest_csharp.ts +13 -1
  48. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +3 -3
  49. package/project/.sdk/src/cmp/dart/EntityTypes_dart.ts +2 -3
  50. package/project/.sdk/src/cmp/dart/Entity_dart.ts +3 -3
  51. package/project/.sdk/src/cmp/dart/MainEntity_dart.ts +2 -2
  52. package/project/.sdk/src/cmp/dart/Main_dart.ts +3 -3
  53. package/project/.sdk/src/cmp/dart/Package_dart.ts +1 -1
  54. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +4 -4
  55. package/project/.sdk/src/cmp/elixir/EntityTypes_elixir.ts +2 -3
  56. package/project/.sdk/src/cmp/elixir/Entity_elixir.ts +1 -1
  57. package/project/.sdk/src/cmp/elixir/ReadmeExamplesTest_elixir.ts +21 -3
  58. package/project/.sdk/src/cmp/go/Config_go.ts +4 -1
  59. package/project/.sdk/src/cmp/go/EntityTypes_go.ts +110 -18
  60. package/project/.sdk/src/cmp/go/Entity_go.ts +3 -3
  61. package/project/.sdk/src/cmp/go/Main_go.ts +7 -6
  62. package/project/.sdk/src/cmp/go/Package_go.ts +1 -1
  63. package/project/.sdk/src/cmp/go/TestDirect_go.ts +3 -3
  64. package/project/.sdk/src/cmp/go/TestEntity_go.ts +1 -1
  65. package/project/.sdk/src/cmp/go/utility_go.ts +25 -0
  66. package/project/.sdk/src/cmp/go-cli/Main_go-cli.ts +9 -1
  67. package/project/.sdk/src/cmp/go-mcp/Main_go-mcp.ts +9 -1
  68. package/project/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +28 -18
  69. package/project/.sdk/src/cmp/java/EntityTypes_java.ts +2 -3
  70. package/project/.sdk/src/cmp/java/Entity_java.ts +3 -3
  71. package/project/.sdk/src/cmp/java/MainEntity_java.ts +2 -2
  72. package/project/.sdk/src/cmp/java/Package_java.ts +1 -1
  73. package/project/.sdk/src/cmp/java/ReadmeExamplesTest_java.ts +23 -1
  74. package/project/.sdk/src/cmp/java/TestDirect_java.ts +3 -3
  75. package/project/.sdk/src/cmp/js/EntityTypes_js.ts +2 -3
  76. package/project/.sdk/src/cmp/js/Entity_js.ts +3 -3
  77. package/project/.sdk/src/cmp/js/MainEntity_js.ts +2 -2
  78. package/project/.sdk/src/cmp/js/Main_js.ts +2 -2
  79. package/project/.sdk/src/cmp/js/TestDirect_js.ts +4 -4
  80. package/project/.sdk/src/cmp/kotlin/EntityTypes_kotlin.ts +2 -3
  81. package/project/.sdk/src/cmp/kotlin/Entity_kotlin.ts +3 -3
  82. package/project/.sdk/src/cmp/kotlin/MainEntity_kotlin.ts +2 -2
  83. package/project/.sdk/src/cmp/kotlin/Package_kotlin.ts +1 -1
  84. package/project/.sdk/src/cmp/kotlin/ReadmeExamplesTest_kotlin.ts +27 -2
  85. package/project/.sdk/src/cmp/kotlin/TestDirect_kotlin.ts +3 -3
  86. package/project/.sdk/src/cmp/lean/Config_lean.ts +88 -0
  87. package/project/.sdk/src/cmp/lean/Entity_lean.ts +9 -0
  88. package/project/.sdk/src/cmp/lean/Gitignore_lean.ts +18 -0
  89. package/project/.sdk/src/cmp/lean/Main_lean.ts +120 -0
  90. package/project/.sdk/src/cmp/lean/Package_lean.ts +68 -0
  91. package/project/.sdk/src/cmp/lean/ReadmeEntity_lean.ts +29 -0
  92. package/project/.sdk/src/cmp/lean/ReadmeExplanation_lean.ts +19 -0
  93. package/project/.sdk/src/cmp/lean/ReadmeHowto_lean.ts +25 -0
  94. package/project/.sdk/src/cmp/lean/ReadmeInstall_lean.ts +38 -0
  95. package/project/.sdk/src/cmp/lean/ReadmeIntro_lean.ts +25 -0
  96. package/project/.sdk/src/cmp/lean/ReadmeModel_lean.ts +24 -0
  97. package/project/.sdk/src/cmp/lean/ReadmeOptions_lean.ts +22 -0
  98. package/project/.sdk/src/cmp/lean/ReadmeQuick_lean.ts +50 -0
  99. package/project/.sdk/src/cmp/lean/ReadmeRef_lean.ts +16 -0
  100. package/project/.sdk/src/cmp/lean/ReadmeTopHowto_lean.ts +16 -0
  101. package/project/.sdk/src/cmp/lean/ReadmeTopQuick_lean.ts +23 -0
  102. package/project/.sdk/src/cmp/lean/ReadmeTopTest_lean.ts +18 -0
  103. package/project/.sdk/src/cmp/lean/Test_lean.ts +207 -0
  104. package/project/.sdk/src/cmp/lean/utility_lean.ts +78 -0
  105. package/project/.sdk/src/cmp/lua/EntityTypes_lua.ts +2 -3
  106. package/project/.sdk/src/cmp/lua/Entity_lua.ts +3 -3
  107. package/project/.sdk/src/cmp/lua/Package_lua.ts +1 -1
  108. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +3 -3
  109. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +1 -1
  110. package/project/.sdk/src/cmp/ocaml/ReadmeExamplesTest_ocaml.ts +29 -14
  111. package/project/.sdk/src/cmp/perl/Entity_perl.ts +3 -3
  112. package/project/.sdk/src/cmp/perl/MainEntity_perl.ts +2 -2
  113. package/project/.sdk/src/cmp/perl/Package_perl.ts +1 -1
  114. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +3 -3
  115. package/project/.sdk/src/cmp/php/EntityTypes_php.ts +2 -3
  116. package/project/.sdk/src/cmp/php/Entity_php.ts +3 -3
  117. package/project/.sdk/src/cmp/php/MainEntity_php.ts +2 -2
  118. package/project/.sdk/src/cmp/php/Package_php.ts +1 -1
  119. package/project/.sdk/src/cmp/php/TestDirect_php.ts +3 -3
  120. package/project/.sdk/src/cmp/php/TestEntity_php.ts +1 -1
  121. package/project/.sdk/src/cmp/py/EntityTypes_py.ts +2 -3
  122. package/project/.sdk/src/cmp/py/Entity_py.ts +3 -3
  123. package/project/.sdk/src/cmp/py/MainEntity_py.ts +2 -2
  124. package/project/.sdk/src/cmp/py/Main_py.ts +2 -2
  125. package/project/.sdk/src/cmp/py/Package_py.ts +1 -1
  126. package/project/.sdk/src/cmp/py/TestDirect_py.ts +3 -3
  127. package/project/.sdk/src/cmp/py/TestEntity_py.ts +1 -1
  128. package/project/.sdk/src/cmp/rb/EntityTypes_rb.ts +15 -5
  129. package/project/.sdk/src/cmp/rb/Entity_rb.ts +3 -3
  130. package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +2 -2
  131. package/project/.sdk/src/cmp/rb/Package_rb.ts +2 -2
  132. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +3 -3
  133. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +1 -1
  134. package/project/.sdk/src/cmp/rust/EntityBase_rust.ts +2 -2
  135. package/project/.sdk/src/cmp/rust/EntityTypes_rust.ts +3 -4
  136. package/project/.sdk/src/cmp/rust/Entity_rust.ts +3 -3
  137. package/project/.sdk/src/cmp/rust/MainEntity_rust.ts +2 -2
  138. package/project/.sdk/src/cmp/rust/Package_rust.ts +1 -1
  139. package/project/.sdk/src/cmp/rust/ReadmeExamplesTest_rust.ts +22 -10
  140. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +3 -3
  141. package/project/.sdk/src/cmp/scala/EntityTypes_scala.ts +2 -3
  142. package/project/.sdk/src/cmp/scala/Entity_scala.ts +3 -3
  143. package/project/.sdk/src/cmp/scala/MainEntity_scala.ts +2 -2
  144. package/project/.sdk/src/cmp/scala/ReadmeExamplesTest_scala.ts +22 -3
  145. package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +3 -3
  146. package/project/.sdk/src/cmp/scala/Test_scala.ts +1 -1
  147. package/project/.sdk/src/cmp/swift/EntityTypes_swift.ts +3 -4
  148. package/project/.sdk/src/cmp/swift/Entity_swift.ts +3 -3
  149. package/project/.sdk/src/cmp/swift/MainEntity_swift.ts +2 -2
  150. package/project/.sdk/src/cmp/swift/Package_swift.ts +1 -1
  151. package/project/.sdk/src/cmp/swift/ReadmeExamplesTest_swift.ts +18 -1
  152. package/project/.sdk/src/cmp/swift/TestDirect_swift.ts +1 -1
  153. package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +2 -3
  154. package/project/.sdk/src/cmp/ts/Entity_ts.ts +3 -3
  155. package/project/.sdk/src/cmp/ts/MainEntity_ts.ts +2 -2
  156. package/project/.sdk/src/cmp/ts/Main_ts.ts +2 -2
  157. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +4 -4
  158. package/project/.sdk/src/cmp/zig/Entity_zig.ts +3 -3
  159. package/project/.sdk/src/cmp/zig/MainEntity_zig.ts +2 -2
  160. package/project/.sdk/src/cmp/zig/Package_zig.ts +10 -2
  161. package/project/.sdk/tm/cpp/test/primary_utility_test.cpp +8 -16
  162. package/project/.sdk/tm/csharp/test/PrimaryUtilityTest.cs +10 -28
  163. package/project/.sdk/tm/dart/test/primary_test.dart +7 -0
  164. package/project/.sdk/tm/go/feature/test_feature.go +33 -1
  165. package/project/.sdk/tm/go/test/primary_utility_test.go +41 -42
  166. package/project/.sdk/tm/go/test/runner_test.go +30 -1
  167. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +7 -24
  168. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +18 -1
  169. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +8 -0
  170. package/project/.sdk/tm/kotlin/test/PrimaryUtilityTest.kt +7 -20
  171. package/project/.sdk/tm/lean/LICENSE +21 -0
  172. package/project/.sdk/tm/lean/VERSION +1 -0
  173. package/project/.sdk/tm/lean/src/SdkFeature.lean +171 -0
  174. package/project/.sdk/tm/lean/src/SdkFeatures.lean +602 -0
  175. package/project/.sdk/tm/lean/src/SdkJson.lean +134 -0
  176. package/project/.sdk/tm/lean/src/SdkRuntime.lean +356 -0
  177. package/project/.sdk/tm/lean/src/SdkUtility.lean +617 -0
  178. package/project/.sdk/tm/lean/src/VoxgigStruct.lean +2880 -0
  179. package/project/.sdk/tm/lean/src/Vregex.lean +412 -0
  180. package/project/.sdk/tm/lean/src/feature/README.md +5 -0
  181. package/project/.sdk/tm/lean/src/feature/audit/.gitkeep +0 -0
  182. package/project/.sdk/tm/lean/src/feature/base/.gitkeep +0 -0
  183. package/project/.sdk/tm/lean/src/feature/cache/.gitkeep +0 -0
  184. package/project/.sdk/tm/lean/src/feature/clienttrack/.gitkeep +0 -0
  185. package/project/.sdk/tm/lean/src/feature/debug/.gitkeep +0 -0
  186. package/project/.sdk/tm/lean/src/feature/idempotency/.gitkeep +0 -0
  187. package/project/.sdk/tm/lean/src/feature/log/.gitkeep +0 -0
  188. package/project/.sdk/tm/lean/src/feature/metrics/.gitkeep +0 -0
  189. package/project/.sdk/tm/lean/src/feature/netsim/.gitkeep +0 -0
  190. package/project/.sdk/tm/lean/src/feature/paging/.gitkeep +0 -0
  191. package/project/.sdk/tm/lean/src/feature/proxy/.gitkeep +0 -0
  192. package/project/.sdk/tm/lean/src/feature/ratelimit/.gitkeep +0 -0
  193. package/project/.sdk/tm/lean/src/feature/rbac/.gitkeep +0 -0
  194. package/project/.sdk/tm/lean/src/feature/retry/.gitkeep +0 -0
  195. package/project/.sdk/tm/lean/src/feature/streaming/.gitkeep +0 -0
  196. package/project/.sdk/tm/lean/src/feature/telemetry/.gitkeep +0 -0
  197. package/project/.sdk/tm/lean/src/feature/test/.gitkeep +0 -0
  198. package/project/.sdk/tm/lean/src/feature/timeout/.gitkeep +0 -0
  199. package/project/.sdk/tm/lean/test/StructCorpus.lean +697 -0
  200. package/project/.sdk/tm/lean/test/TFeature.lean +268 -0
  201. package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +349 -0
  202. package/project/.sdk/tm/lua/test/primary_utility_test.lua +10 -8
  203. package/project/.sdk/tm/perl/t/primary_utility.t +8 -17
  204. package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +9 -7
  205. package/project/.sdk/tm/py/feature/test_feature.py +28 -1
  206. package/project/.sdk/tm/py/test/test_primary_utility.py +13 -18
  207. package/project/.sdk/tm/rb/feature/test_feature.rb +24 -1
  208. package/project/.sdk/tm/rb/test/primary_utility_test.rb +7 -8
  209. package/project/.sdk/tm/rust/tests/common/mod.rs +37 -1
  210. package/project/.sdk/tm/rust/tests/primary_utility_test.rs +12 -26
  211. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/PrimaryUtilityTest.swift +7 -8
  212. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +18 -1
  213. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +65 -21
  214. package/src/cmp/Entity.ts +5 -1
  215. package/src/cmp/Feature.ts +2 -1
  216. package/src/helpers/canonType.ts +8 -3
  217. package/src/helpers/collectDeps.ts +39 -12
  218. package/src/helpers/naming.ts +59 -0
  219. package/src/helpers/opShape.ts +99 -12
  220. package/src/sdkgen.ts +7 -3
@@ -0,0 +1,207 @@
1
+ import {
2
+ Content,
3
+ File,
4
+ Folder,
5
+ cmp,
6
+ each,
7
+ } from '@voxgig/sdkgen'
8
+
9
+ import {
10
+ KIT,
11
+ Model,
12
+ getModelPath,
13
+ } from '@voxgig/apidef'
14
+
15
+
16
+ // True when an op has at least one endpoint point that needs no path params
17
+ // (a top-level operation the live smoke test can call without an id).
18
+ function hasParamFreePoint(op: any): boolean {
19
+ if (null == op) return false
20
+ const points = op.points || []
21
+ for (const pt of points) {
22
+ const params = ((pt.args || {}).params) || []
23
+ if (params.length === 0) return true
24
+ }
25
+ return false
26
+ }
27
+
28
+
29
+ // Synthesize a create payload from the entity's required non-id fields.
30
+ function synthData(fields: any): any {
31
+ const o: any = {}
32
+ each(fields, (f: any) => {
33
+ if (f.req && f.name !== 'id') {
34
+ const t = String(f.type || '').toLowerCase()
35
+ o[f.name] =
36
+ (t.includes('number') || t.includes('integer') || t.includes('decimal')) ? 42
37
+ : t.includes('bool') ? true
38
+ : 'leantest'
39
+ }
40
+ })
41
+ return o
42
+ }
43
+
44
+
45
+ // The generated `test/Runner.lean` exe runs TWO lanes:
46
+ //
47
+ // - OFFLINE (always): a test-mode client seeded from the entity's generated
48
+ // `<Entity>TestData.json` answers operations from an in-memory store, so
49
+ // entity behaviour (list/load/create/load-back/remove) is verified with no
50
+ // server — the same guarantee the `test` feature gives the other targets.
51
+ // - LIVE (only when SDK_TEST_BASE is set): the same flow against a real
52
+ // server, exercising the curl transport end to end.
53
+ const Test = cmp(async function Test(props: any) {
54
+ const ctx$ = props.ctx$
55
+ const target = props.target
56
+ const model: Model = ctx$.model
57
+
58
+ const entity = getModelPath(model, `main.${KIT}.entity`)
59
+
60
+ // Per-entity OFFLINE (test-mode) blocks: seeded store, no server.
61
+ let offline = ''
62
+ each(entity, (e: any) => {
63
+ const ns = e.name.charAt(0).toUpperCase() + e.name.slice(1)
64
+ const Name = e.Name || (e.name.charAt(0).toUpperCase() + e.name.slice(1))
65
+ const ops = e.op || {}
66
+ if (!ops.list && !ops.load) return
67
+ offline += ` -- ${e.name}: offline (test-mode) entity behaviour
68
+ (do
69
+ let seedPath := "../.sdk/test/entity/${e.name}/${Name}TestData.json"
70
+ if !(← System.FilePath.pathExists seedPath) then
71
+ IO.println s!"skip - ${e.name}: no seed at {seedPath}"
72
+ else do
73
+ let seed ← SdkJson.jsonRead (← IO.FS.readFile seedPath)
74
+ let tclient ← Sdk.testSdk0 seed
75
+ let existing ← SdkRuntime.gp (← SdkRuntime.gp seed "existing") "${e.name}"
76
+ let ids ← keysof existing
77
+ -- list returns every seeded entity
78
+ let items ← ${ns}.list tclient (← emptyMap) (← emptyMap)
79
+ let n ← (match items with | .list lid => do pure (← listItems lid).size | _ => pure 0)
80
+ if n == ids.size then pass s!"${e.name}.list offline -> {n} seeded"
81
+ else fail s!"${e.name}.list offline: got {n}, want {ids.size}"
82
+ -- load the first seeded entity by id
83
+ if ids.size > 0 then do
84
+ let wid := ids[0]!
85
+ let m ← newMap #[("id", .str wid)]
86
+ let got ← ${ns}.load tclient m (← emptyMap)
87
+ let gid ← SdkRuntime.gpS got "id"
88
+ if gid == wid then pass s!"${e.name}.load offline (id={wid})"
89
+ else fail s!"${e.name}.load offline: got {gid}, want {wid}"
90
+ -- create -> load back -> remove, all in the store
91
+ let newmap ← SdkRuntime.gp (← SdkRuntime.gp seed "new") "${e.name}"
92
+ let nks ← keysof newmap
93
+ if nks.size > 0 then do
94
+ let payload ← SdkRuntime.gp newmap nks[0]!
95
+ let created ← ${ns}.create tclient payload (← emptyMap)
96
+ let cid ← SdkRuntime.gpS created "id"
97
+ if cid == "" then fail "${e.name}.create offline returned no id" else do
98
+ let m2 ← newMap #[("id", .str cid)]
99
+ let back ← ${ns}.load tclient m2 (← emptyMap)
100
+ if (← SdkRuntime.gpS back "id") == cid then
101
+ pass s!"${e.name}.create/load offline (id={cid})"
102
+ else fail s!"${e.name}.create/load offline mismatch"
103
+ let _ ← ${ns}.remove tclient m2 (← emptyMap)
104
+ let gone ← ${ns}.load tclient m2 (← emptyMap)
105
+ match gone with
106
+ | .map _ => fail s!"${e.name}.remove offline: still present"
107
+ | _ => pass s!"${e.name}.remove offline (id={cid})")
108
+ `
109
+ })
110
+
111
+ // Per-entity LIVE test blocks.
112
+ let blocks = ''
113
+ each(entity, (e: any) => {
114
+ const ns = e.name.charAt(0).toUpperCase() + e.name.slice(1)
115
+ const listOp = (e.op || {}).list
116
+ const createOp = (e.op || {}).create
117
+ const removeOp = (e.op || {}).remove
118
+
119
+ if (hasParamFreePoint(listOp)) {
120
+ blocks += ` -- ${e.name}: list smoke
121
+ (do
122
+ let items ← ${ns}.list client (← emptyMap) (← emptyMap)
123
+ match items with
124
+ | .list lid => pass s!"${e.name}.list -> {(← listItems lid).size} items"
125
+ | _ => fail "${e.name}.list did not return a list")
126
+ `
127
+ }
128
+
129
+ // Round-trip when create is param-free (creatable without an id); load and
130
+ // remove then use the id returned by create.
131
+ const loadOp = (e.op || {}).load
132
+ if (hasParamFreePoint(createOp) && loadOp && removeOp) {
133
+ const data = JSON.stringify(synthData(e.fields))
134
+ blocks += ` -- ${e.name}: create / load / remove round-trip
135
+ (do
136
+ let d ← SdkJson.jsonRead ${JSON.stringify(data)}
137
+ let created ← ${ns}.create client d (← emptyMap)
138
+ let nid ← SdkRuntime.gpS created "id"
139
+ if nid == "" then fail "${e.name}.create returned no id" else do
140
+ let m ← newMap #[("id", .str nid)]
141
+ let back ← ${ns}.load client m (← emptyMap)
142
+ let bid ← SdkRuntime.gpS back "id"
143
+ if bid == nid then pass s!"${e.name}.create/load round-trip (id={nid})"
144
+ else fail s!"${e.name}.load mismatch: {bid} != {nid}"
145
+ let _ ← ${ns}.remove client m (← emptyMap)
146
+ pass s!"${e.name}.remove (id={nid})")
147
+ `
148
+ }
149
+ })
150
+
151
+ // The live lane nests two levels deeper than the offline lane.
152
+ const liveBlocks = blocks.split('\n').map((l) => l ? ' ' + l : l).join('\n')
153
+
154
+ Folder({ name: 'test' }, () => {
155
+ File({ name: 'Runner.' + target.ext }, () => {
156
+ Content(`-- ${model.const.Name} SDK test runner (generated by @voxgig/sdkgen).
157
+ --
158
+ -- OFFLINE lane (always): a test-mode client seeded from the generated entity
159
+ -- test data answers operations from an in-memory store — no server needed.
160
+ -- LIVE lane (only when SDK_TEST_BASE is set): the same flow over real HTTP.
161
+
162
+ import VoxgigStruct
163
+ import SdkJson
164
+ import SdkRuntime
165
+ import SdkClient
166
+
167
+ open VoxgigStruct
168
+
169
+ initialize npass : IO.Ref Nat ← IO.mkRef 0
170
+ initialize nfail : IO.Ref Nat ← IO.mkRef 0
171
+
172
+ def pass (msg : String) : SIO Unit := do
173
+ npass.modify (· + 1)
174
+ IO.println s!"ok - {msg}"
175
+
176
+ def fail (msg : String) : SIO Unit := do
177
+ nfail.modify (· + 1)
178
+ IO.println s!"FAIL - {msg}"
179
+
180
+ def defaultBase : String := "http://localhost:8901"
181
+
182
+ def main : IO UInt32 := do
183
+ let liveBase ← IO.getEnv "SDK_TEST_BASE"
184
+ let ctx ← mkCtx
185
+ let offline : SIO Unit := do
186
+ ${offline} offline.run ctx
187
+ match liveBase with
188
+ | none => IO.println "skip - live lane (set SDK_TEST_BASE to enable)"
189
+ | some base => do
190
+ let live : SIO Unit := do
191
+ let opts ← newMap #[("base", .str base)]
192
+ let client ← Sdk.newSdk opts
193
+ ${liveBlocks} live.run ctx
194
+ let p ← npass.get
195
+ let f ← nfail.get
196
+ IO.println ""
197
+ IO.println s!"PASS {p} FAIL {f}"
198
+ if f > 0 then return 1 else return 0
199
+ `)
200
+ })
201
+ })
202
+ })
203
+
204
+
205
+ export {
206
+ Test
207
+ }
@@ -0,0 +1,78 @@
1
+ import * as Path from 'node:path'
2
+
3
+ import {
4
+ clone,
5
+ walk,
6
+ } from '@voxgig/struct'
7
+
8
+
9
+ function projectPath(suffix?: string): string {
10
+ return Path.normalize(Path.join(__dirname, '../../..', suffix ?? ''))
11
+ }
12
+
13
+
14
+ // A Lean 4 string literal. Lean strings are UTF-8, so non-ASCII passes through;
15
+ // only the structural characters need escaping. (Backticks are ordinary in Lean
16
+ // strings, so the config's `body`-style transform exprs embed verbatim.)
17
+ function leanString(s: string): string {
18
+ let out = '"'
19
+ for (const ch of String(s)) {
20
+ if (ch === '"') out += '\\"'
21
+ else if (ch === '\\') out += '\\\\'
22
+ else if (ch === '\n') out += '\\n'
23
+ else if (ch === '\t') out += '\\t'
24
+ else if (ch === '\r') out += '\\r'
25
+ else out += ch
26
+ }
27
+ return out + '"'
28
+ }
29
+
30
+
31
+ // Lean reserved words that cannot be bare identifiers.
32
+ const LEAN_RESERVED = new Set<string>([
33
+ 'do', 'let', 'fun', 'match', 'with', 'if', 'then', 'else', 'by', 'end',
34
+ 'namespace', 'section', 'open', 'import', 'def', 'partial', 'mutual',
35
+ 'structure', 'inductive', 'class', 'instance', 'where', 'deriving', 'return',
36
+ 'try', 'catch', 'for', 'in', 'while', 'have', 'show', 'from', 'set_option',
37
+ ])
38
+
39
+
40
+ // A collision-free lower-camel Lean identifier for a model name.
41
+ function leanVarName(name: string): string {
42
+ let s = String(name).replace(/[^a-zA-Z0-9_]/g, '_')
43
+ if (s.length === 0) {
44
+ s = 'x'
45
+ }
46
+ s = s.charAt(0).toLowerCase() + s.slice(1)
47
+ if (!/^[a-z_]/.test(s)) {
48
+ s = 'e_' + s
49
+ }
50
+ return LEAN_RESERVED.has(s) ? s + '_' : s
51
+ }
52
+
53
+
54
+ // The lowercase-hyphenated package name (used for the lake package + repo).
55
+ function pkgName(model: any): string {
56
+ const org = (model.origin || 'voxgig-sdk').replace(/-sdk$/, '')
57
+ return `${org}-${model.name}-sdk`.toLowerCase().replace(/[^a-z0-9-]/g, '-')
58
+ }
59
+
60
+
61
+ // Remove `$`-suffixed model annotation keys (so the embedded config is clean).
62
+ function clean(o: any) {
63
+ return walk(clone(o), (k: any, v: any, p: any) => {
64
+ if (null != k && k.endsWith('$')) {
65
+ delete p[k]
66
+ }
67
+ return v
68
+ })
69
+ }
70
+
71
+
72
+ export {
73
+ clean,
74
+ leanString,
75
+ leanVarName,
76
+ pkgName,
77
+ projectPath,
78
+ }
@@ -21,7 +21,7 @@ import {
21
21
  File, Content,
22
22
  } from '@voxgig/sdkgen'
23
23
 
24
- import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
24
+ import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
25
25
 
26
26
  import {
27
27
  KIT,
@@ -53,11 +53,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
53
53
  // filter, so inactive entities still get class files referencing these
54
54
  // typed names. Filter on `name` (always present), NOT `active` — parity
55
55
  // with the go emitter's fix.
56
- const entityList = each(entity).filter((e: any) => e && null != e.name)
56
+ const entityList = deriveEntityNames(entity)
57
57
  // Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
58
58
  // entity not yet named (e.g. a fieldless placeholder) would otherwise read
59
59
  // `Name = undefined` below. Parity with the go emitter's fix.
60
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
61
60
 
62
61
  // Surface duplicate generated type names (two entities with the same
63
62
  // PascalCase Name) — they would redeclare a type in statically-typed
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, each, camelify, names,
6
6
  File, Content, Folder, Fragment, Line, FeatureHook, Slot,
7
- entityClassName,
7
+ entityClassName, entityCollection,
8
8
  } from '@voxgig/sdkgen'
9
9
 
10
10
  import {
@@ -24,7 +24,7 @@ const Entity = cmp(function Entity(props: any) {
24
24
  // data-type name. The class is a module-local table (snake-cased file name),
25
25
  // so this is used only for the local identifier — kept uniform with the other
26
26
  // languages.
27
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
27
+ const entityColl = entityCollection(model)
28
28
  const cls = entityClassName(entity, entityColl)
29
29
 
30
30
  const entrep = {
@@ -40,7 +40,7 @@ const Entity = cmp(function Entity(props: any) {
40
40
 
41
41
  File({ name: entity.name + '_entity.' + target.ext }, () => {
42
42
 
43
- const opnames = Object.keys(entity.op)
43
+ const opnames = Object.keys(entity.op || {})
44
44
 
45
45
  const opfrags =
46
46
  (['load', 'list', 'create', 'update', 'remove']
@@ -57,7 +57,7 @@ dependencies = {
57
57
  `)
58
58
 
59
59
  const seen = new Set<string>(['lua', 'dkjson'])
60
- for (const d of collectDeps(model, target.name, target.deps)) {
60
+ for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
61
61
  if (seen.has(d.name)) continue
62
62
  seen.add(d.name)
63
63
  const v = d.source === 'target' ? (d.version || '0.0') : d.version
@@ -70,7 +70,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
70
70
  ? `\n apikey = env["${PROJECTNAME}_APIKEY"],`
71
71
  : ''
72
72
 
73
- const opnames = Object.keys(entity.op)
73
+ const opnames = Object.keys(entity.op || {})
74
74
  const hasLoad = opnames.includes('load')
75
75
  const hasList = opnames.includes('list')
76
76
 
@@ -78,8 +78,8 @@ const TestDirect = cmp(function TestDirect(props: any) {
78
78
  return
79
79
  }
80
80
 
81
- const loadOp = entity.op.load
82
- const listOp = entity.op.list
81
+ const loadOp = entity.op?.load
82
+ const listOp = entity.op?.list
83
83
 
84
84
  const loadPoint = loadOp?.points?.[0]
85
85
  const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
@@ -50,7 +50,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
50
50
  // it only applies to entities that declare a list op. Others (e.g. Batch =
51
51
  // create/load) have no list endpoint — make_point errors and the stream
52
52
  // yields nothing — so skip the stream test for them.
53
- const hasList = !!(entity.op && (entity.op as any).list)
53
+ const hasList = !!(entity.op && (entity.op as any)?.list)
54
54
 
55
55
  const basicflow: ModelEntityFlow | undefined =
56
56
  getModelPath(model, `main.${KIT}.flow.Basic${nom(entity, 'Name')}Flow`)
@@ -45,8 +45,11 @@ let read_file (path : string) : string option =
45
45
 
46
46
  let split_lines (s : string) : string list = String.split_on_char '\\n' s
47
47
 
48
- (* Extract the body of every \`\`\`ocaml ... \`\`\` fenced block from markdown. *)
49
- let extract_ocaml_blocks (md : string) : string list =
48
+ (* Extract the body of every \`\`\`ocaml ... \`\`\` fenced block from markdown.
49
+ * Returns (blocks, unclosed) where unclosed is true when a fence was opened
50
+ * but never closed before EOF — a malformed doc that must fail rather than
51
+ * silently drop the unterminated block. *)
52
+ let extract_ocaml_blocks (md : string) : string list * bool =
50
53
  let blocks = ref [] in
51
54
  let cur = ref [] in
52
55
  let in_block = ref false in
@@ -61,23 +64,31 @@ let extract_ocaml_blocks (md : string) : string list =
61
64
  end
62
65
  else cur := line :: !cur)
63
66
  (split_lines md);
64
- List.rev !blocks
67
+ (List.rev !blocks, !in_block)
65
68
 
66
- (* Balanced (), [], {} outside of string literals. Naive quote toggle: the
67
- * generated snippets never embed an escaped quote inside a string. *)
69
+ (* Balanced (), [], {} outside of string literals, with each closer matching
70
+ * the most-recent opener (so "([)]" is rejected, not just counted). Naive
71
+ * quote toggle: the generated snippets never embed an escaped quote inside a
72
+ * string. *)
68
73
  let is_balanced (block : string) : bool =
69
74
  let in_str = ref false in
70
- let depth = ref 0 in
75
+ let stack = ref [] in
71
76
  let ok = ref true in
72
77
  String.iter (fun c ->
73
- if c = '"' then in_str := not !in_str
74
- else if not !in_str then
75
- match c with
76
- | '(' | '[' | '{' -> incr depth
77
- | ')' | ']' | '}' -> decr depth; if !depth < 0 then ok := false
78
- | _ -> ())
78
+ if !ok then
79
+ if c = '"' then in_str := not !in_str
80
+ else if not !in_str then
81
+ match c with
82
+ | '(' -> stack := ')' :: !stack
83
+ | '[' -> stack := ']' :: !stack
84
+ | '{' -> stack := '}' :: !stack
85
+ | ')' | ']' | '}' ->
86
+ (match !stack with
87
+ | top :: rest when top = c -> stack := rest
88
+ | _ -> ok := false)
89
+ | _ -> ())
79
90
  block;
80
- !ok && !depth = 0 && not !in_str
91
+ !ok && !stack = [] && not !in_str
81
92
 
82
93
  let docs = [
83
94
  ("root README", "../README.md");
@@ -92,13 +103,17 @@ let () =
92
103
  match read_file path with
93
104
  | None -> () (* doc not generated in this run — tolerate *)
94
105
  | Some src ->
106
+ let (blocks, unclosed) = extract_ocaml_blocks src in
107
+ (* A fence opened but never closed is a malformed doc — fail
108
+ * rather than silently skipping the unterminated block. *)
109
+ check (label ^ " ocaml fence closed") (not unclosed);
95
110
  List.iteri (fun i block ->
96
111
  check (label ^ " block " ^ string_of_int (i + 1) ^ " non-empty")
97
112
  (trim block <> "");
98
113
  check (label ^ " block " ^ string_of_int (i + 1) ^ " balanced delimiters")
99
114
  (is_balanced block);
100
115
  incr total)
101
- (extract_ocaml_blocks src))
116
+ blocks)
102
117
  docs;
103
118
  check "at least one ocaml code block across the docs" (!total > 0))
104
119
  `)
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, camelify, names,
6
6
  File, Folder, Fragment,
7
- entityClassName,
7
+ entityClassName, entityCollection,
8
8
  } from '@voxgig/sdkgen'
9
9
 
10
10
  import {
@@ -22,7 +22,7 @@ const Entity = cmp(function Entity(props: any) {
22
22
  // Collision-free entity CLASS name (see entityClassName): normally
23
23
  // `<Name>Entity`, disambiguated when it would clash with another entity's
24
24
  // data-type name. The snake-cased source-file name is unaffected.
25
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
25
+ const entityColl = entityCollection(model)
26
26
  const cls = entityClassName(entity, entityColl)
27
27
 
28
28
  const entrep = {
@@ -38,7 +38,7 @@ const Entity = cmp(function Entity(props: any) {
38
38
 
39
39
  File({ name: entity.name + '_entity.pm' }, () => {
40
40
 
41
- const opnames = Object.keys(entity.op)
41
+ const opnames = Object.keys(entity.op || {})
42
42
 
43
43
  const opfrags =
44
44
  (['load', 'list', 'create', 'update', 'remove']
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- import { cmp, Content, entityClassName } from '@voxgig/sdkgen'
3
+ import { cmp, Content, entityClassName, entityCollection } from '@voxgig/sdkgen'
4
4
 
5
5
  import {
6
6
  KIT,
@@ -14,7 +14,7 @@ const MainEntity = cmp(async function MainEntity(props: any) {
14
14
 
15
15
  // Return the collision-free class (entityClassName); the accessor METHOD
16
16
  // name (entity.Name) is unchanged so callers still write $client->{Name}.
17
- const cls = entityClassName(entity, getModelPath(model, `main.${KIT}.entity`))
17
+ const cls = entityClassName(entity, entityCollection(model))
18
18
 
19
19
  Content(`
20
20
  # Canonical facade: $client->${entity.Name}->list / ->load({ 'id' => ... })
@@ -27,7 +27,7 @@ const Package = cmp(async function Package(props: any) {
27
27
  // (e.g. "Voxgig::SDK::Solar" -> dist voxgig-sdk-solar).
28
28
  const Name = model.const.Name
29
29
 
30
- const deps = collectDeps(model, target.name, target.deps)
30
+ const deps = collectDeps(model, target.name, target.deps, ctx$.log)
31
31
  const prereq = deps
32
32
  .map((d: any) => ` '${d.name}' => '${d.version || '0'}',`)
33
33
  .join('\n')
@@ -80,7 +80,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
80
80
  ? `\n 'apikey' => $env->{'${PROJECTNAME}_APIKEY'},`
81
81
  : ''
82
82
 
83
- const opnames = Object.keys(entity.op)
83
+ const opnames = Object.keys(entity.op || {})
84
84
  const hasLoad = opnames.includes('load')
85
85
  const hasList = opnames.includes('list')
86
86
 
@@ -88,8 +88,8 @@ const TestDirect = cmp(function TestDirect(props: any) {
88
88
  return
89
89
  }
90
90
 
91
- const loadOp = entity.op.load
92
- const listOp = entity.op.list
91
+ const loadOp = entity.op?.load
92
+ const listOp = entity.op?.list
93
93
 
94
94
  const loadPoint = loadOp?.points?.[0]
95
95
  const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
@@ -29,7 +29,7 @@ import {
29
29
  File, Content, Folder,
30
30
  } from '@voxgig/sdkgen'
31
31
 
32
- import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
32
+ import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
33
33
 
34
34
  import {
35
35
  KIT,
@@ -79,11 +79,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
79
79
  // filter, so inactive entities still get class files referencing these
80
80
  // typed names. Filter on `name` (always present), NOT `active` — parity
81
81
  // with the go emitter's fix.
82
- const entityList = each(entity).filter((e: any) => e && null != e.name)
82
+ const entityList = deriveEntityNames(entity)
83
83
  // Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
84
84
  // entity not yet named (e.g. a fieldless placeholder) would otherwise read
85
85
  // `Name = undefined` below. Parity with the go emitter's fix.
86
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
87
86
 
88
87
  // Surface duplicate generated type names (two entities with the same
89
88
  // PascalCase Name) — they would redeclare a type in statically-typed
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, each, camelify, names,
6
6
  File, Content, Folder, Fragment, Line, FeatureHook, Slot,
7
- entityClassName,
7
+ entityClassName, entityCollection,
8
8
  } from '@voxgig/sdkgen'
9
9
 
10
10
  import {
@@ -22,7 +22,7 @@ const Entity = cmp(function Entity(props: any) {
22
22
  // Collision-free entity CLASS name (see entityClassName): normally
23
23
  // `<Name>Entity`, disambiguated when it would clash with another entity's
24
24
  // data-type name. The snake-cased source-file name is unaffected.
25
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
25
+ const entityColl = entityCollection(model)
26
26
  const cls = entityClassName(entity, entityColl)
27
27
 
28
28
  const entrep = {
@@ -38,7 +38,7 @@ const Entity = cmp(function Entity(props: any) {
38
38
 
39
39
  File({ name: entity.name + '_entity.' + target.ext }, () => {
40
40
 
41
- const opnames = Object.keys(entity.op)
41
+ const opnames = Object.keys(entity.op || {})
42
42
 
43
43
  const opfrags =
44
44
  (['load', 'list', 'create', 'update', 'remove']
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- import { cmp, Content, entityClassName } from '@voxgig/sdkgen'
3
+ import { cmp, Content, entityClassName, entityCollection } from '@voxgig/sdkgen'
4
4
 
5
5
  import {
6
6
  KIT,
@@ -20,7 +20,7 @@ const MainEntity = cmp(async function MainEntity(props: any) {
20
20
 
21
21
  // Collision-free entity CLASS name (entityClassName); the accessor METHOD name
22
22
  // (below) is unchanged so callers still write $client->${entity.Name}().
23
- const cls = entityClassName(entity, getModelPath(model, `main.${KIT}.entity`))
23
+ const cls = entityClassName(entity, entityCollection(model))
24
24
 
25
25
  // Canonical facade method name is the PascalCase entity Name
26
26
  // (`$client->${entity.Name}()`). PHP method names are case-insensitive, so
@@ -49,7 +49,7 @@ const Package = cmp(async function Package(props: any) {
49
49
  "require": {
50
50
  "php": ">=8.2"`)
51
51
 
52
- for (const d of collectDeps(model, target.name, target.deps)) {
52
+ for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
53
53
  const v = d.source === 'target' ? (d.version || '0.0') : d.version
54
54
  Content(`,
55
55
  "${d.name}": "^${v}"`)
@@ -70,7 +70,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
70
70
  ? `\n "apikey" => $env["${PROJECTNAME}_APIKEY"],`
71
71
  : ''
72
72
 
73
- const opnames = Object.keys(entity.op)
73
+ const opnames = Object.keys(entity.op || {})
74
74
  const hasLoad = opnames.includes('load')
75
75
  const hasList = opnames.includes('list')
76
76
 
@@ -78,8 +78,8 @@ const TestDirect = cmp(function TestDirect(props: any) {
78
78
  return
79
79
  }
80
80
 
81
- const loadOp = entity.op.load
82
- const listOp = entity.op.list
81
+ const loadOp = entity.op?.load
82
+ const listOp = entity.op?.list
83
83
 
84
84
  const loadPoint = loadOp?.points?.[0]
85
85
  const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
@@ -53,7 +53,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
53
53
  // it only applies to entities that declare a list op. Others (e.g. Batch =
54
54
  // create/load) have no list endpoint — make_point errors and the stream
55
55
  // yields nothing — so skip the stream test for them.
56
- const hasList = !!(entity.op && (entity.op as any).list)
56
+ const hasList = !!(entity.op && (entity.op as any)?.list)
57
57
 
58
58
  const basicflow: ModelEntityFlow | undefined =
59
59
  getModelPath(model, `main.${KIT}.flow.Basic${nom(entity, 'Name')}Flow`)
@@ -36,7 +36,7 @@ import {
36
36
  File, Content,
37
37
  } from '@voxgig/sdkgen'
38
38
 
39
- import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
39
+ import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
40
40
 
41
41
  import {
42
42
  KIT,
@@ -142,11 +142,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
142
142
  // filter, so inactive entities still get class files referencing these
143
143
  // typed names. Filter on `name` (always present), NOT `active` — parity
144
144
  // with the go emitter's fix.
145
- const entityList = each(entity).filter((e: any) => e && null != e.name)
145
+ const entityList = deriveEntityNames(entity)
146
146
  // Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
147
147
  // entity not yet named (e.g. a fieldless placeholder) would otherwise read
148
148
  // `Name = undefined` below. Parity with the go emitter's fix.
149
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
150
149
 
151
150
  // Surface duplicate generated type names (two entities with the same
152
151
  // PascalCase Name) — they would redeclare a type in statically-typed