@voxgig/sdkgen 1.3.17 → 1.3.19

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 (267) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/Deploy.js +16 -16
  3. package/dist/cmp/Deploy.js.map +1 -1
  4. package/dist/cmp/Entity.js +5 -1
  5. package/dist/cmp/Entity.js.map +1 -1
  6. package/dist/cmp/Feature.js +2 -1
  7. package/dist/cmp/Feature.js.map +1 -1
  8. package/dist/cmp/ReadmeTop.js +13 -11
  9. package/dist/cmp/ReadmeTop.js.map +1 -1
  10. package/dist/helpers/canonType.js +8 -3
  11. package/dist/helpers/canonType.js.map +1 -1
  12. package/dist/helpers/collectDeps.d.ts +1 -1
  13. package/dist/helpers/collectDeps.js +36 -13
  14. package/dist/helpers/collectDeps.js.map +1 -1
  15. package/dist/helpers/naming.d.ts +5 -1
  16. package/dist/helpers/naming.js +118 -0
  17. package/dist/helpers/naming.js.map +1 -1
  18. package/dist/helpers/opShape.d.ts +3 -1
  19. package/dist/helpers/opShape.js +87 -8
  20. package/dist/helpers/opShape.js.map +1 -1
  21. package/dist/helpers/serverVars.d.ts +10 -0
  22. package/dist/helpers/serverVars.js +87 -0
  23. package/dist/helpers/serverVars.js.map +1 -0
  24. package/dist/sdkgen.d.ts +4 -3
  25. package/dist/sdkgen.js +11 -2
  26. package/dist/sdkgen.js.map +1 -1
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +15 -10
  29. package/project/.sdk/model/target/cpp.aontu +4 -4
  30. package/project/.sdk/model/target/go-cli.aontu +17 -17
  31. package/project/.sdk/model/target/go-mcp.aontu +11 -11
  32. package/project/.sdk/model/target/go.aontu +1 -1
  33. package/project/.sdk/model/target/java.aontu +4 -4
  34. package/project/.sdk/model/target/kotlin.aontu +4 -4
  35. package/project/.sdk/model/target/lean.aontu +51 -0
  36. package/project/.sdk/model/target/scala.aontu +4 -4
  37. package/project/.sdk/src/cmp/c/EntityTypes_c.ts +3 -4
  38. package/project/.sdk/src/cmp/c/Entity_c.ts +3 -3
  39. package/project/.sdk/src/cmp/c/ReadmeExamplesTest_c.ts +24 -7
  40. package/project/.sdk/src/cmp/c/TestDirect_c.ts +3 -3
  41. package/project/.sdk/src/cmp/c/TestEntity_c.ts +1 -1
  42. package/project/.sdk/src/cmp/clojure/Entity_clojure.ts +1 -1
  43. package/project/.sdk/src/cmp/clojure/ReadmeExamplesTest_clojure.ts +19 -8
  44. package/project/.sdk/src/cmp/cpp/EntityTypes_cpp.ts +2 -2
  45. package/project/.sdk/src/cmp/cpp/Entity_cpp.ts +3 -3
  46. package/project/.sdk/src/cmp/cpp/MainEntity_cpp.ts +2 -2
  47. package/project/.sdk/src/cmp/cpp/ReadmeExamplesTest_cpp.ts +136 -0
  48. package/project/.sdk/src/cmp/cpp/TestDirect_cpp.ts +3 -3
  49. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +4 -0
  50. package/project/.sdk/src/cmp/csharp/EntityTypes_csharp.ts +2 -3
  51. package/project/.sdk/src/cmp/csharp/Entity_csharp.ts +3 -3
  52. package/project/.sdk/src/cmp/csharp/MainEntity_csharp.ts +2 -2
  53. package/project/.sdk/src/cmp/csharp/Package_csharp.ts +1 -1
  54. package/project/.sdk/src/cmp/csharp/ReadmeExamplesTest_csharp.ts +13 -1
  55. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +3 -3
  56. package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +22 -15
  57. package/project/.sdk/src/cmp/dart/EntityTypes_dart.ts +2 -3
  58. package/project/.sdk/src/cmp/dart/Entity_dart.ts +3 -3
  59. package/project/.sdk/src/cmp/dart/MainEntity_dart.ts +2 -2
  60. package/project/.sdk/src/cmp/dart/Main_dart.ts +3 -3
  61. package/project/.sdk/src/cmp/dart/Package_dart.ts +1 -1
  62. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +4 -4
  63. package/project/.sdk/src/cmp/elixir/EntityTypes_elixir.ts +2 -3
  64. package/project/.sdk/src/cmp/elixir/Entity_elixir.ts +1 -1
  65. package/project/.sdk/src/cmp/elixir/ReadmeExamplesTest_elixir.ts +21 -3
  66. package/project/.sdk/src/cmp/go/Config_go.ts +14 -2
  67. package/project/.sdk/src/cmp/go/EntityTypes_go.ts +110 -18
  68. package/project/.sdk/src/cmp/go/Entity_go.ts +3 -3
  69. package/project/.sdk/src/cmp/go/Main_go.ts +7 -6
  70. package/project/.sdk/src/cmp/go/Package_go.ts +1 -1
  71. package/project/.sdk/src/cmp/go/TestDirect_go.ts +3 -3
  72. package/project/.sdk/src/cmp/go/TestEntity_go.ts +1 -1
  73. package/project/.sdk/src/cmp/go/utility_go.ts +25 -0
  74. package/project/.sdk/src/cmp/go-cli/Main_go-cli.ts +62 -35
  75. package/project/.sdk/src/cmp/go-cli/fragment/main.fragment.go +7 -7
  76. package/project/.sdk/src/cmp/go-cli/fragment/words.fragment.go +21 -18
  77. package/project/.sdk/src/cmp/go-mcp/Main_go-mcp.ts +9 -1
  78. package/project/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +28 -18
  79. package/project/.sdk/src/cmp/java/EntityTypes_java.ts +2 -3
  80. package/project/.sdk/src/cmp/java/Entity_java.ts +3 -3
  81. package/project/.sdk/src/cmp/java/MainEntity_java.ts +2 -2
  82. package/project/.sdk/src/cmp/java/Package_java.ts +1 -1
  83. package/project/.sdk/src/cmp/java/ReadmeExamplesTest_java.ts +23 -1
  84. package/project/.sdk/src/cmp/java/TestDirect_java.ts +3 -3
  85. package/project/.sdk/src/cmp/js/Config_js.ts +11 -0
  86. package/project/.sdk/src/cmp/js/EntityTypes_js.ts +2 -3
  87. package/project/.sdk/src/cmp/js/Entity_js.ts +3 -3
  88. package/project/.sdk/src/cmp/js/MainEntity_js.ts +2 -2
  89. package/project/.sdk/src/cmp/js/Main_js.ts +2 -2
  90. package/project/.sdk/src/cmp/js/TestDirect_js.ts +4 -4
  91. package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +2 -2
  92. package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +4 -0
  93. package/project/.sdk/src/cmp/kotlin/EntityTypes_kotlin.ts +2 -3
  94. package/project/.sdk/src/cmp/kotlin/Entity_kotlin.ts +3 -3
  95. package/project/.sdk/src/cmp/kotlin/MainEntity_kotlin.ts +2 -2
  96. package/project/.sdk/src/cmp/kotlin/Package_kotlin.ts +1 -1
  97. package/project/.sdk/src/cmp/kotlin/ReadmeExamplesTest_kotlin.ts +27 -2
  98. package/project/.sdk/src/cmp/kotlin/TestDirect_kotlin.ts +3 -3
  99. package/project/.sdk/src/cmp/lean/Config_lean.ts +88 -0
  100. package/project/.sdk/src/cmp/lean/Entity_lean.ts +9 -0
  101. package/project/.sdk/src/cmp/lean/Gitignore_lean.ts +18 -0
  102. package/project/.sdk/src/cmp/lean/Main_lean.ts +120 -0
  103. package/project/.sdk/src/cmp/lean/Package_lean.ts +68 -0
  104. package/project/.sdk/src/cmp/lean/ReadmeEntity_lean.ts +29 -0
  105. package/project/.sdk/src/cmp/lean/ReadmeExplanation_lean.ts +19 -0
  106. package/project/.sdk/src/cmp/lean/ReadmeHowto_lean.ts +25 -0
  107. package/project/.sdk/src/cmp/lean/ReadmeInstall_lean.ts +38 -0
  108. package/project/.sdk/src/cmp/lean/ReadmeIntro_lean.ts +25 -0
  109. package/project/.sdk/src/cmp/lean/ReadmeModel_lean.ts +24 -0
  110. package/project/.sdk/src/cmp/lean/ReadmeOptions_lean.ts +22 -0
  111. package/project/.sdk/src/cmp/lean/ReadmeQuick_lean.ts +50 -0
  112. package/project/.sdk/src/cmp/lean/ReadmeRef_lean.ts +16 -0
  113. package/project/.sdk/src/cmp/lean/ReadmeTopHowto_lean.ts +16 -0
  114. package/project/.sdk/src/cmp/lean/ReadmeTopQuick_lean.ts +23 -0
  115. package/project/.sdk/src/cmp/lean/ReadmeTopTest_lean.ts +18 -0
  116. package/project/.sdk/src/cmp/lean/Test_lean.ts +207 -0
  117. package/project/.sdk/src/cmp/lean/utility_lean.ts +78 -0
  118. package/project/.sdk/src/cmp/lua/Config_lua.ts +10 -1
  119. package/project/.sdk/src/cmp/lua/EntityTypes_lua.ts +2 -3
  120. package/project/.sdk/src/cmp/lua/Entity_lua.ts +3 -3
  121. package/project/.sdk/src/cmp/lua/Package_lua.ts +1 -1
  122. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +3 -3
  123. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +1 -1
  124. package/project/.sdk/src/cmp/ocaml/ReadmeExamplesTest_ocaml.ts +29 -14
  125. package/project/.sdk/src/cmp/perl/Entity_perl.ts +3 -3
  126. package/project/.sdk/src/cmp/perl/MainEntity_perl.ts +2 -2
  127. package/project/.sdk/src/cmp/perl/Package_perl.ts +1 -1
  128. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +3 -3
  129. package/project/.sdk/src/cmp/php/Config_php.ts +13 -2
  130. package/project/.sdk/src/cmp/php/EntityTypes_php.ts +2 -3
  131. package/project/.sdk/src/cmp/php/Entity_php.ts +3 -3
  132. package/project/.sdk/src/cmp/php/MainEntity_php.ts +2 -2
  133. package/project/.sdk/src/cmp/php/Package_php.ts +1 -1
  134. package/project/.sdk/src/cmp/php/TestDirect_php.ts +3 -3
  135. package/project/.sdk/src/cmp/php/TestEntity_php.ts +1 -1
  136. package/project/.sdk/src/cmp/py/Config_py.ts +10 -1
  137. package/project/.sdk/src/cmp/py/EntityTypes_py.ts +2 -3
  138. package/project/.sdk/src/cmp/py/Entity_py.ts +3 -3
  139. package/project/.sdk/src/cmp/py/MainEntity_py.ts +2 -2
  140. package/project/.sdk/src/cmp/py/Main_py.ts +2 -2
  141. package/project/.sdk/src/cmp/py/Package_py.ts +1 -1
  142. package/project/.sdk/src/cmp/py/TestDirect_py.ts +3 -3
  143. package/project/.sdk/src/cmp/py/TestEntity_py.ts +1 -1
  144. package/project/.sdk/src/cmp/rb/Config_rb.ts +12 -1
  145. package/project/.sdk/src/cmp/rb/EntityTypes_rb.ts +15 -5
  146. package/project/.sdk/src/cmp/rb/Entity_rb.ts +3 -3
  147. package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +2 -2
  148. package/project/.sdk/src/cmp/rb/Package_rb.ts +2 -2
  149. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +3 -3
  150. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +1 -1
  151. package/project/.sdk/src/cmp/rust/EntityBase_rust.ts +2 -2
  152. package/project/.sdk/src/cmp/rust/EntityTypes_rust.ts +3 -4
  153. package/project/.sdk/src/cmp/rust/Entity_rust.ts +3 -3
  154. package/project/.sdk/src/cmp/rust/MainEntity_rust.ts +2 -2
  155. package/project/.sdk/src/cmp/rust/Package_rust.ts +1 -1
  156. package/project/.sdk/src/cmp/rust/ReadmeExamplesTest_rust.ts +22 -10
  157. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +3 -3
  158. package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +25 -18
  159. package/project/.sdk/src/cmp/scala/EntityTypes_scala.ts +2 -3
  160. package/project/.sdk/src/cmp/scala/Entity_scala.ts +3 -3
  161. package/project/.sdk/src/cmp/scala/MainEntity_scala.ts +2 -2
  162. package/project/.sdk/src/cmp/scala/ReadmeExamplesTest_scala.ts +22 -3
  163. package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +3 -3
  164. package/project/.sdk/src/cmp/scala/Test_scala.ts +1 -1
  165. package/project/.sdk/src/cmp/swift/EntityTypes_swift.ts +12 -6
  166. package/project/.sdk/src/cmp/swift/Entity_swift.ts +3 -3
  167. package/project/.sdk/src/cmp/swift/MainEntity_swift.ts +2 -2
  168. package/project/.sdk/src/cmp/swift/Package_swift.ts +1 -1
  169. package/project/.sdk/src/cmp/swift/ReadmeExamplesTest_swift.ts +18 -1
  170. package/project/.sdk/src/cmp/swift/TestDirect_swift.ts +1 -1
  171. package/project/.sdk/src/cmp/ts/Config_ts.ts +11 -0
  172. package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +2 -3
  173. package/project/.sdk/src/cmp/ts/Entity_ts.ts +3 -3
  174. package/project/.sdk/src/cmp/ts/MainEntity_ts.ts +2 -2
  175. package/project/.sdk/src/cmp/ts/Main_ts.ts +2 -2
  176. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +4 -4
  177. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +2 -2
  178. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +4 -0
  179. package/project/.sdk/src/cmp/zig/Entity_zig.ts +3 -3
  180. package/project/.sdk/src/cmp/zig/MainEntity_zig.ts +2 -2
  181. package/project/.sdk/src/cmp/zig/Package_zig.ts +10 -2
  182. package/project/.sdk/tm/cpp/test/primary_utility_test.cpp +8 -16
  183. package/project/.sdk/tm/csharp/Makefile +6 -6
  184. package/project/.sdk/tm/csharp/test/PrimaryUtilityTest.cs +10 -28
  185. package/project/.sdk/tm/dart/Makefile +3 -3
  186. package/project/.sdk/tm/dart/test/primary_test.dart +7 -0
  187. package/project/.sdk/tm/go/Makefile +6 -6
  188. package/project/.sdk/tm/go/feature/test_feature.go +33 -1
  189. package/project/.sdk/tm/go/test/primary_utility_test.go +46 -42
  190. package/project/.sdk/tm/go/test/runner_test.go +30 -1
  191. package/project/.sdk/tm/go/utility/make_options.go +48 -0
  192. package/project/.sdk/tm/java/Makefile +6 -6
  193. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +7 -24
  194. package/project/.sdk/tm/js/Makefile +6 -6
  195. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +18 -1
  196. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +39 -1
  197. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +13 -0
  198. package/project/.sdk/tm/kotlin/Makefile +6 -6
  199. package/project/.sdk/tm/kotlin/test/PrimaryUtilityTest.kt +7 -20
  200. package/project/.sdk/tm/lean/LICENSE +21 -0
  201. package/project/.sdk/tm/lean/VERSION +1 -0
  202. package/project/.sdk/tm/lean/src/SdkFeature.lean +171 -0
  203. package/project/.sdk/tm/lean/src/SdkFeatures.lean +602 -0
  204. package/project/.sdk/tm/lean/src/SdkJson.lean +134 -0
  205. package/project/.sdk/tm/lean/src/SdkRuntime.lean +356 -0
  206. package/project/.sdk/tm/lean/src/SdkUtility.lean +617 -0
  207. package/project/.sdk/tm/lean/src/VoxgigStruct.lean +2880 -0
  208. package/project/.sdk/tm/lean/src/Vregex.lean +412 -0
  209. package/project/.sdk/tm/lean/src/feature/README.md +5 -0
  210. package/project/.sdk/tm/lean/src/feature/audit/.gitkeep +0 -0
  211. package/project/.sdk/tm/lean/src/feature/base/.gitkeep +0 -0
  212. package/project/.sdk/tm/lean/src/feature/cache/.gitkeep +0 -0
  213. package/project/.sdk/tm/lean/src/feature/clienttrack/.gitkeep +0 -0
  214. package/project/.sdk/tm/lean/src/feature/debug/.gitkeep +0 -0
  215. package/project/.sdk/tm/lean/src/feature/idempotency/.gitkeep +0 -0
  216. package/project/.sdk/tm/lean/src/feature/log/.gitkeep +0 -0
  217. package/project/.sdk/tm/lean/src/feature/metrics/.gitkeep +0 -0
  218. package/project/.sdk/tm/lean/src/feature/netsim/.gitkeep +0 -0
  219. package/project/.sdk/tm/lean/src/feature/paging/.gitkeep +0 -0
  220. package/project/.sdk/tm/lean/src/feature/proxy/.gitkeep +0 -0
  221. package/project/.sdk/tm/lean/src/feature/ratelimit/.gitkeep +0 -0
  222. package/project/.sdk/tm/lean/src/feature/rbac/.gitkeep +0 -0
  223. package/project/.sdk/tm/lean/src/feature/retry/.gitkeep +0 -0
  224. package/project/.sdk/tm/lean/src/feature/streaming/.gitkeep +0 -0
  225. package/project/.sdk/tm/lean/src/feature/telemetry/.gitkeep +0 -0
  226. package/project/.sdk/tm/lean/src/feature/test/.gitkeep +0 -0
  227. package/project/.sdk/tm/lean/src/feature/timeout/.gitkeep +0 -0
  228. package/project/.sdk/tm/lean/test/StructCorpus.lean +697 -0
  229. package/project/.sdk/tm/lean/test/TFeature.lean +268 -0
  230. package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +349 -0
  231. package/project/.sdk/tm/lua/Makefile +7 -7
  232. package/project/.sdk/tm/lua/feature/test_feature.lua +29 -1
  233. package/project/.sdk/tm/lua/test/primary_utility_test.lua +14 -8
  234. package/project/.sdk/tm/lua/utility/make_options.lua +39 -0
  235. package/project/.sdk/tm/perl/Makefile +6 -6
  236. package/project/.sdk/tm/perl/t/primary_utility.t +8 -17
  237. package/project/.sdk/tm/php/Makefile +6 -6
  238. package/project/.sdk/tm/php/feature/TestFeature.php +30 -2
  239. package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +14 -7
  240. package/project/.sdk/tm/php/utility/MakeOptions.php +44 -0
  241. package/project/.sdk/tm/py/Makefile +6 -6
  242. package/project/.sdk/tm/py/feature/test_feature.py +28 -1
  243. package/project/.sdk/tm/py/test/test_primary_utility.py +18 -18
  244. package/project/.sdk/tm/py/utility/make_options.py +42 -0
  245. package/project/.sdk/tm/rb/Makefile +6 -6
  246. package/project/.sdk/tm/rb/feature/test_feature.rb +24 -1
  247. package/project/.sdk/tm/rb/test/primary_utility_test.rb +11 -8
  248. package/project/.sdk/tm/rb/utility/make_options.rb +37 -0
  249. package/project/.sdk/tm/rust/Makefile +6 -6
  250. package/project/.sdk/tm/rust/tests/common/mod.rs +37 -1
  251. package/project/.sdk/tm/rust/tests/primary_utility_test.rs +12 -26
  252. package/project/.sdk/tm/swift/Makefile +6 -6
  253. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/PrimaryUtilityTest.swift +7 -8
  254. package/project/.sdk/tm/ts/Makefile +6 -6
  255. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +18 -1
  256. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +39 -1
  257. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +69 -21
  258. package/src/cmp/Deploy.ts +16 -16
  259. package/src/cmp/Entity.ts +5 -1
  260. package/src/cmp/Feature.ts +2 -1
  261. package/src/cmp/ReadmeTop.ts +13 -11
  262. package/src/helpers/canonType.ts +8 -3
  263. package/src/helpers/collectDeps.ts +39 -12
  264. package/src/helpers/naming.ts +130 -0
  265. package/src/helpers/opShape.ts +99 -12
  266. package/src/helpers/serverVars.ts +105 -0
  267. package/src/sdkgen.ts +12 -3
@@ -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`)
@@ -5,7 +5,7 @@ import {
5
5
  Folder,
6
6
  cmp,
7
7
  each,
8
- entityClassName,
8
+ entityClassName, entityCollection,
9
9
  } from '@voxgig/sdkgen'
10
10
 
11
11
 
@@ -27,7 +27,7 @@ const EntityBase = cmp(async function EntityBase(props: any) {
27
27
  const { model } = props.ctx$
28
28
 
29
29
  const entity = getModelPath(model, `main.${KIT}.entity`)
30
- const entityColl = entity
30
+ const entityColl = entityCollection(model)
31
31
 
32
32
  Folder({ name: 'entity' }, () => {
33
33
  File({ name: 'mod.' + target.ext }, () => {
@@ -25,11 +25,11 @@
25
25
  // types yet (parity intent, not dead-code churn).
26
26
 
27
27
  import {
28
- cmp, each, names,
28
+ cmp, each,
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,
@@ -85,8 +85,7 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
85
85
  // Emit for every entity that gets an entity file (filter on `name`, always
86
86
  // present; derive `Name` here so the struct set is deterministic — parity
87
87
  // with the go emitter's fix).
88
- const entityList = each(entity).filter((e: any) => e && null != e.name)
89
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
88
+ const entityList = deriveEntityNames(entity)
90
89
 
91
90
  // Surface duplicate generated type names (two entities with the same
92
91
  // 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, camelify,
6
6
  File, Content, Folder, Fragment,
7
- entityClassName,
7
+ entityClassName, entityCollection,
8
8
  } from '@voxgig/sdkgen'
9
9
 
10
10
  import {
@@ -23,7 +23,7 @@ const Entity = cmp(function Entity(props: any) {
23
23
  // Collision-free entity CLASS name (see entityClassName): normally
24
24
  // `<Name>Entity`, but disambiguated when it would clash with another
25
25
  // entity's data-type name.
26
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
26
+ const entityColl = entityCollection(model)
27
27
  const cls = entityClassName(entity, entityColl)
28
28
 
29
29
  const entrep = {
@@ -37,7 +37,7 @@ const Entity = cmp(function Entity(props: any) {
37
37
 
38
38
  File({ name: rustVarName(entity.name) + '.' + target.ext }, () => {
39
39
 
40
- const opnames = Object.keys(entity.op)
40
+ const opnames = Object.keys(entity.op || {})
41
41
 
42
42
  // For each CRUD op: if the spec defines it, splice in the real
43
43
  // implementation. Otherwise emit a stub that satisfies the static
@@ -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,
@@ -17,7 +17,7 @@ const MainEntity = cmp(async function MainEntity(props: any) {
17
17
  const { entity } = props
18
18
  const { model } = props.ctx$
19
19
 
20
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
20
+ const entityColl = entityCollection(model)
21
21
  const cls = entityClassName(entity, entityColl)
22
22
  const method = rustVarName(entity.name)
23
23
  const mod = rustVarName(entity.name)
@@ -43,7 +43,7 @@ path = "lib.rs"
43
43
 
44
44
  const prod: Record<string, string> = {}
45
45
  const dev: Record<string, string> = {}
46
- for (const d of collectDeps(model, target.name, target.deps)) {
46
+ for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
47
47
  // Target-level deps default to '*' when version is absent; feature
48
48
  // deps require an explicit version.
49
49
  const version = d.source === 'target' ? (d.version || '*') : d.version
@@ -33,7 +33,10 @@ use std::fs;
33
33
  use std::path::PathBuf;
34
34
 
35
35
  // Extract the body of every \`\`\`rust ... \`\`\` fenced block from markdown.
36
- fn extract_rust_blocks(md: &str) -> Vec<String> {
36
+ // Returns the blocks plus a flag that is true when the scan reached EOF with a
37
+ // fence still open (an unterminated \`\`\`rust block) — the caller fails on it
38
+ // rather than silently dropping the malformed block.
39
+ fn extract_rust_blocks(md: &str) -> (Vec<String>, bool) {
37
40
  let mut blocks: Vec<String> = Vec::new();
38
41
  let mut in_block = false;
39
42
  let mut cur: Vec<&str> = Vec::new();
@@ -53,14 +56,15 @@ fn extract_rust_blocks(md: &str) -> Vec<String> {
53
56
  }
54
57
  cur.push(line);
55
58
  }
56
- blocks
59
+ (blocks, in_block)
57
60
  }
58
61
 
59
- // Balanced (), [], {} outside of string literals. Naive quote toggle: the
60
- // generated snippets never embed an escaped quote inside a string.
62
+ // Balanced (), [], {} outside of string literals, with each closer matching the
63
+ // most-recent opener (so "([)]" is rejected, not just counted). Naive quote
64
+ // toggle: the generated snippets never embed an escaped quote inside a string.
61
65
  fn is_balanced(block: &str) -> bool {
62
66
  let mut in_str = false;
63
- let mut depth: i32 = 0;
67
+ let mut stack: Vec<char> = Vec::new();
64
68
  for c in block.chars() {
65
69
  if c == '"' {
66
70
  in_str = !in_str;
@@ -70,17 +74,18 @@ fn is_balanced(block: &str) -> bool {
70
74
  continue;
71
75
  }
72
76
  match c {
73
- '(' | '[' | '{' => depth += 1,
77
+ '(' => stack.push(')'),
78
+ '[' => stack.push(']'),
79
+ '{' => stack.push('}'),
74
80
  ')' | ']' | '}' => {
75
- depth -= 1;
76
- if depth < 0 {
81
+ if stack.pop() != Some(c) {
77
82
  return false;
78
83
  }
79
84
  }
80
85
  _ => {}
81
86
  }
82
87
  }
83
- depth == 0 && !in_str
88
+ stack.is_empty() && !in_str
84
89
  }
85
90
 
86
91
  #[test]
@@ -98,7 +103,14 @@ fn readme_rust_snippets_wellformed() {
98
103
  Ok(s) => s,
99
104
  Err(_) => continue, // doc not generated in this run — tolerate
100
105
  };
101
- let blocks = extract_rust_blocks(&src);
106
+ let (blocks, unterminated) = extract_rust_blocks(&src);
107
+ // A fence opened but never closed is a malformed doc — fail rather than
108
+ // silently skipping the unterminated block.
109
+ assert!(
110
+ !unterminated,
111
+ "{}: unterminated \`\`\`rust fence (block never closed)",
112
+ label
113
+ );
102
114
  for (i, block) in blocks.iter().enumerate() {
103
115
  assert!(
104
116
  !block.trim().is_empty(),
@@ -73,7 +73,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
73
73
  ? `("apikey", getp(&env, "${PROJECTNAME}_APIKEY"))`
74
74
  : ''
75
75
 
76
- const opnames = Object.keys(entity.op)
76
+ const opnames = Object.keys(entity.op || {})
77
77
  const hasLoad = opnames.includes('load')
78
78
  const hasList = opnames.includes('list')
79
79
 
@@ -81,8 +81,8 @@ const TestDirect = cmp(function TestDirect(props: any) {
81
81
  return
82
82
  }
83
83
 
84
- const loadOp = (entity.op as any).load
85
- const listOp = (entity.op as any).list
84
+ const loadOp = (entity.op as any)?.load
85
+ const listOp = (entity.op as any)?.list
86
86
 
87
87
  // Load point info.
88
88
  const loadPoint = loadOp?.points?.[0]
@@ -75,8 +75,30 @@ const TestEntity = cmp(function TestEntity(props: any) {
75
75
 
76
76
  const genCtx: GenCtx = { model, entity, rustcrate, flow: basicflow, PROJUPPER }
77
77
 
78
- const opsList = Array.from(new Set(allSteps.map((s: any) => s.op).filter(Boolean)))
79
- .map(o => `"${o}"`).join(', ')
78
+ const opNames = Array.from(new Set(allSteps.map((s: any) => s.op).filter(Boolean)))
79
+ const opsList = opNames.map(o => `"${o}"`).join(', ')
80
+
81
+ // Same hazard as the csharp target: an entity whose basic flow has no ops
82
+ // would emit `for op in []`, and rustc cannot infer the element type of an
83
+ // empty array literal — E0282, type annotations needed. The loop is dead
84
+ // code in that case, and so is the `mode` binding only it reads (which
85
+ // would then warn as unused). Emit neither.
86
+ const skipBlock = 0 === opNames.length ? '' : ` // Per-op sdk-test-control.json skip — the basic test exercises a flow
87
+ // with multiple ops; skipping any op skips the whole flow.
88
+ let mode = if setup.live { "live" } else { "unit" };
89
+ for op in [${opsList}] {
90
+ let (skip, reason) = is_control_skipped("entityOp", &format!("${entity.name}.{}", op), mode);
91
+ if skip {
92
+ let reason = if reason.is_empty() {
93
+ "skipped via sdk-test-control.json".to_string()
94
+ } else {
95
+ reason
96
+ };
97
+ eprintln!("skip: {}", reason);
98
+ return;
99
+ }
100
+ }
101
+ `
80
102
 
81
103
  const method = rustVarName(entity.name)
82
104
  const evar = rustVarName(entity.name)
@@ -158,22 +180,7 @@ fn ${evar}_entity_stream() {
158
180
  #[test]
159
181
  fn ${evar}_entity_basic() {
160
182
  let setup = ${evar}_basic_setup(Value::Noval);
161
- // Per-op sdk-test-control.json skip the basic test exercises a flow
162
- // with multiple ops; skipping any op skips the whole flow.
163
- let mode = if setup.live { "live" } else { "unit" };
164
- for op in [${opsList}] {
165
- let (skip, reason) = is_control_skipped("entityOp", &format!("${entity.name}.{}", op), mode);
166
- if skip {
167
- let reason = if reason.is_empty() {
168
- "skipped via sdk-test-control.json".to_string()
169
- } else {
170
- reason
171
- };
172
- eprintln!("skip: {}", reason);
173
- return;
174
- }
175
- }
176
- // The basic flow consumes synthetic IDs from the fixture. In live mode
183
+ ${skipBlock} // The basic flow consumes synthetic IDs from the fixture. In live mode
177
184
  // without an *_ENTID env override, those IDs hit the live API and 4xx.
178
185
  if setup.synthetic_only {
179
186
  eprintln!("skip: live entity test uses synthetic IDs from fixture — set ${PROJUPPER}_TEST_${ENTUPPER}_ENTID JSON to run live");
@@ -38,7 +38,7 @@ import {
38
38
  File, Content,
39
39
  } from '@voxgig/sdkgen'
40
40
 
41
- import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
41
+ import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
42
42
 
43
43
  import {
44
44
  KIT,
@@ -133,11 +133,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
133
133
  // filter, so inactive entities still get class files referencing these
134
134
  // typed names. Filter on `name` (always present), NOT `active` — parity
135
135
  // with the go emitter's fix.
136
- const entityList = each(entity).filter((e: any) => e && null != e.name)
136
+ const entityList = deriveEntityNames(entity)
137
137
  // Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
138
138
  // entity not yet named (e.g. a fieldless placeholder) would otherwise read
139
139
  // `Name = undefined` below. Parity with the go/py/java/csharp emitter's fix.
140
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
141
140
 
142
141
  // Surface duplicate generated type names (two entities with the same
143
142
  // 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, camelify, names,
6
6
  File, Content, Folder, Fragment,
7
- entityClassName,
7
+ entityClassName, entityCollection,
8
8
  } from '@voxgig/sdkgen'
9
9
 
10
10
  import {
@@ -23,7 +23,7 @@ const Entity = cmp(function Entity(props: any) {
23
23
  const scalapackage = scalaPackage(model)
24
24
 
25
25
  // Collision-free entity CLASS name.
26
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
26
+ const entityColl = entityCollection(model)
27
27
  const cls = entityClassName(entity, entityColl)
28
28
 
29
29
  const entrep = {
@@ -39,7 +39,7 @@ const Entity = cmp(function Entity(props: any) {
39
39
 
40
40
  File({ name: cls + '.' + target.ext }, () => {
41
41
 
42
- const opnames = Object.keys(entity.op)
42
+ const opnames = Object.keys(entity.op || {})
43
43
 
44
44
  // For each CRUD op: if the spec defines it, splice in the real
45
45
  // implementation. Otherwise emit a stub that satisfies the SdkEntity
@@ -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,
@@ -18,7 +18,7 @@ const MainEntity = cmp(async function MainEntity(props: any) {
18
18
  const { model } = props.ctx$
19
19
 
20
20
  const scalapackage = scalaPackage(model)
21
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
21
+ const entityColl = entityCollection(model)
22
22
  const cls = entityClassName(entity, entityColl)
23
23
  const accessor = scalaVarName(entity.name)
24
24
 
@@ -43,8 +43,11 @@ const ReadmeExamplesTest = cmp(function ReadmeExamplesTest(props: any) {
43
43
  //
44
44
  // Checks, PER DOCUMENT:
45
45
  // 1. PRESENCE: the doc exists and holds at least one scala block.
46
- // 2. STRUCTURE: every scala block's braces / parens / brackets balance, so a
47
- // truncated or malformed generated snippet fails loudly.
46
+ // 2. STRUCTURE: every fence is closed (an odd number of triple-backtick
47
+ // markers means a fence was opened but never closed — the doc is truncated
48
+ // and split-based extraction would mis-segment the tail), AND every scala
49
+ // block's braces / parens / brackets balance, so a truncated or malformed
50
+ // generated snippet fails loudly.
48
51
  //
49
52
  // GENERATED by @voxgig/sdkgen — do not edit by hand.
50
53
 
@@ -85,6 +88,17 @@ object ReadmeExamplesTest {
85
88
  private def readDoc(path: Path): String =
86
89
  new String(Files.readAllBytes(path), "UTF-8")
87
90
 
91
+ // True when every triple-backtick fence in the document is paired. split(-1)
92
+ // yields (fenceCount + 1) parts, so an even fence count (all fences closed)
93
+ // gives an odd number of parts. An odd fence count means a fence was opened
94
+ // but never closed: the block-extraction above would then treat everything
95
+ // after it as one big "block", and a truncated snippet that happens to
96
+ // balance would pass silently. Detecting it here lets the gate fail loudly.
97
+ private def fencesClosed(text: String): Boolean = {
98
+ val fence = 96.toChar.toString.repeat(3)
99
+ text.split(java.util.regex.Pattern.quote(fence), -1).length % 2 == 1
100
+ }
101
+
88
102
  // True when every open/close delimiter balances (ignores the subtlety of
89
103
  // delimiters inside string literals — a generated snippet never nests an
90
104
  // unbalanced delimiter in a string, and this catches truncation).
@@ -135,7 +149,12 @@ object ReadmeExamplesTest {
135
149
  val label = ds(dj)(0)
136
150
  val path = Paths.get(ds(dj)(1))
137
151
  if (Files.exists(path)) {
138
- val blocks = scalaBlocks(readDoc(path))
152
+ val text = readDoc(path)
153
+ // A fence opened but never closed leaves an odd number of fence
154
+ // markers; fail loudly rather than trusting the mis-segmented tail.
155
+ if (!fencesClosed(text))
156
+ failures.add(label + " has an unclosed scala code fence")
157
+ val blocks = scalaBlocks(text)
139
158
  var balancedCount = 0
140
159
  var bi = 0
141
160
  while (bi < blocks.size()) {
@@ -65,7 +65,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
65
65
  const EntityName = nom(entity, 'Name')
66
66
  const ENTLOWER = entity.name
67
67
 
68
- const opnames = Object.keys(entity.op)
68
+ const opnames = Object.keys(entity.op || {})
69
69
  const hasLoad = opnames.includes('load')
70
70
  const hasList = opnames.includes('list')
71
71
 
@@ -73,8 +73,8 @@ const TestDirect = cmp(function TestDirect(props: any) {
73
73
  return
74
74
  }
75
75
 
76
- const loadOp = (entity.op as any).load
77
- const listOp = (entity.op as any).list
76
+ const loadOp = (entity.op as any)?.load
77
+ const listOp = (entity.op as any)?.list
78
78
 
79
79
  // Load point info.
80
80
  const loadPoint = loadOp?.points?.[0]
@@ -40,7 +40,7 @@ const Test = cmp(function Test(props: any) {
40
40
  getModelPath(model, `main.${KIT}.flow.Basic${EntityName}Flow`)
41
41
  const hasEntity = null != basicflow && true === basicflow.active
42
42
 
43
- const opnames = Object.keys(entity.op)
43
+ const opnames = Object.keys(entity.op || {})
44
44
  const hasDirect = opnames.includes('load') || opnames.includes('list')
45
45
 
46
46
  if (hasEntity) {
@@ -21,11 +21,11 @@
21
21
  // the runtime's open shape.
22
22
 
23
23
  import {
24
- cmp, each, names,
24
+ cmp, each,
25
25
  File, Content, Folder,
26
26
  } from '@voxgig/sdkgen'
27
27
 
28
- import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
28
+ import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames, swiftSafeTypeName } from '@voxgig/sdkgen'
29
29
 
30
30
  import {
31
31
  KIT,
@@ -81,8 +81,7 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
81
81
  // Emit for every entity that gets an entity file (filter on `name`, always
82
82
  // present; derive `Name` here so the struct set is deterministic — parity
83
83
  // with the rust/go emitter's fix).
84
- const entityList = each(entity).filter((e: any) => e && null != e.name)
85
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
84
+ const entityList = deriveEntityNames(entity)
86
85
 
87
86
  // Surface duplicate generated type names (two entities with the same
88
87
  // PascalCase Name) — they would redeclare a type in statically-typed
@@ -115,9 +114,16 @@ import Foundation
115
114
  .filter((f: any) => f.active !== false)
116
115
 
117
116
  // Entity data model: one property per field. req:false -> optional.
117
+ //
118
+ // Swift has no intra-module namespacing, so a spec schema named
119
+ // after one of our own runtime types (e.g. `Response`) would be a
120
+ // redeclaration and fail the build. swiftSafeTypeName appends
121
+ // `Type` on collision only — `Response` -> `ResponseType` — so
122
+ // non-colliding SDKs generate byte-identical output to before.
123
+ const TypeName = swiftSafeTypeName(Name)
118
124
  emitStruct(
119
- `/// ${Name} is the typed data model for the ${ent.name} entity.`,
120
- Name,
125
+ `/// ${TypeName} is the typed data model for the ${ent.name} entity.`,
126
+ TypeName,
121
127
  fields.map((f: any) => ({ name: f.name, type: f.type, optional: false === f.req }))
122
128
  )
123
129
 
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, camelify, names,
6
6
  Content, File, Folder, Fragment,
7
- entityClassName,
7
+ entityClassName, entityCollection,
8
8
  } from '@voxgig/sdkgen'
9
9
 
10
10
  import {
@@ -20,7 +20,7 @@ const Entity = cmp(function Entity(props: any) {
20
20
  const { target, entity } = props
21
21
 
22
22
  // Collision-free entity CLASS name (see entityClassName).
23
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
23
+ const entityColl = entityCollection(model)
24
24
  const cls = entityClassName(entity, entityColl)
25
25
 
26
26
  const entrep = {
@@ -38,7 +38,7 @@ const Entity = cmp(function Entity(props: any) {
38
38
 
39
39
  File({ name: cls + '.' + target.ext }, () => {
40
40
 
41
- const opnames = Object.keys(entity.op)
41
+ const opnames = Object.keys(entity.op || {})
42
42
 
43
43
  // For each CRUD op: if the spec defines it, splice in the real
44
44
  // implementation (an override). Otherwise leave the base-class
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, entityClassName } from '@voxgig/sdkgen'
2
+ import { cmp, Content, entityClassName, entityCollection } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -12,7 +12,7 @@ const MainEntity = cmp(async function MainEntity(props: any) {
12
12
  const { model } = props.ctx$
13
13
 
14
14
  // Collision-free entity CLASS name (see entityClassName).
15
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
15
+ const entityColl = entityCollection(model)
16
16
  const cls = entityClassName(entity, entityColl)
17
17
 
18
18
  const Name = model.const.Name
@@ -28,7 +28,7 @@ const Package = cmp(async function Package(props: any) {
28
28
  const Name = model.const.Name
29
29
 
30
30
  const deps: Record<string, string> = {}
31
- for (const d of collectDeps(model, target.name, target.deps)) {
31
+ for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
32
32
  deps[d.name] = d.source === 'target' ? (d.version || '0.0.0') : d.version
33
33
  }
34
34
  // Vendored, dependency-free by design; declared deps (if any) are ignored
@@ -43,7 +43,9 @@ const ReadmeExamplesTest = cmp(function ReadmeExamplesTest(props: any) {
43
43
  // Checks, PER DOCUMENT that exists:
44
44
  // 1. PRESENCE: the doc holds at least one swift block.
45
45
  // 2. STRUCTURE: every swift block's braces / parens / brackets balance, so a
46
- // truncated or malformed generated snippet fails loudly.
46
+ // truncated or malformed generated snippet fails loudly. An unclosed fence
47
+ // (odd number of fence markers) also fails, rather than silently absorbing
48
+ // the trailing text as a block.
47
49
  //
48
50
  // GENERATED by @voxgig/sdkgen — do not edit by hand.
49
51
 
@@ -94,6 +96,16 @@ final class ReadmeExamplesTest: XCTestCase {
94
96
  return blocks
95
97
  }
96
98
 
99
+ // True when the doc has a fence that was opened but never closed. A
100
+ // well-formed doc holds fence markers in open/close pairs, so an ODD number
101
+ // of markers means the final fence dangles — the split-based scan above would
102
+ // otherwise silently treat the truncated tail as a block. This is the
103
+ // structural analogue of an at-EOF "still inside a block" check.
104
+ private func hasUnclosedFence(_ text: String) -> Bool {
105
+ let fenceCount = text.components(separatedBy: fence).count - 1
106
+ return fenceCount % 2 != 0
107
+ }
108
+
97
109
  // True when every open/close delimiter balances, ignoring characters inside
98
110
  // string literals and line comments (a generated snippet never nests an
99
111
  // unbalanced delimiter outside those, and this catches truncation).
@@ -149,6 +161,11 @@ final class ReadmeExamplesTest: XCTestCase {
149
161
  var failures: [String] = []
150
162
  for doc in docs() {
151
163
  guard let text = read(doc.path) else { continue }
164
+ // A fence opened but never closed is a malformed doc — fail loudly rather
165
+ // than silently absorbing the unterminated tail as a block.
166
+ if hasUnclosedFence(text) {
167
+ failures.append(doc.label + " has an unclosed swift code fence")
168
+ }
152
169
  let blocks = swiftBlocks(text)
153
170
  var balancedCount = 0
154
171
  for (i, b) in blocks.enumerated() {
@@ -24,7 +24,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
24
24
 
25
25
  const Name = model.const.Name
26
26
 
27
- const opnames = Object.keys(entity.op)
27
+ const opnames = Object.keys(entity.op || {})
28
28
  if (!opnames.includes('load') && !opnames.includes('list')) {
29
29
  return
30
30
  }
@@ -12,6 +12,7 @@ import {
12
12
  indent,
13
13
  isAuthActive,
14
14
  resolveAuthPrefix,
15
+ serverVariables,
15
16
  } from '@voxgig/sdkgen'
16
17
 
17
18
 
@@ -53,6 +54,14 @@ const Config = cmp(async function Config(props: any) {
53
54
  `
54
55
  : ''
55
56
 
57
+ // Templated server URL: emit the spec's server-variable defaults so the
58
+ // runtime can substitute {name} placeholders in base (see makeOptions).
59
+ const svars = serverVariables(model)
60
+ const serverBlock = 0 === svars.length ? '' :
61
+ 'server: {\n' +
62
+ svars.map((v: any) => ` ${JSON.stringify(v.name)}: ${JSON.stringify(v.dflt)},\n`).join('') +
63
+ ' },\n\n '
64
+
56
65
  File({ name: 'Config.' + target.ext }, () => {
57
66
 
58
67
  Fragment({
@@ -60,6 +69,8 @@ const Config = cmp(async function Config(props: any) {
60
69
 
61
70
  replace: {
62
71
 
72
+ "'SERVERBLOCK'": serverBlock,
73
+
63
74
  "'AUTHBLOCK'": authBlock,
64
75
 
65
76
  "'HEADERS'": indent(JSON.stringify(headers, null, 2), 4).trim(),
@@ -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,
@@ -51,11 +51,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
51
51
  // filter, so inactive entities still get class files referencing these
52
52
  // typed names. Filter on `name` (always present), NOT `active` — parity
53
53
  // with the go emitter's fix.
54
- const entityList = each(entity).filter((e: any) => e && null != e.name)
54
+ const entityList = deriveEntityNames(entity)
55
55
  // Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
56
56
  // entity not yet named (e.g. a fieldless placeholder) would otherwise read
57
57
  // `Name = undefined` below. Parity with the go emitter's fix.
58
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
59
58
 
60
59
  // Surface duplicate generated type names (two entities with the same
61
60
  // 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
- opTypeName, entityClassName,
7
+ opTypeName, 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
  // `<Name>Entity`, disambiguated when it would clash with another entity's
25
25
  // data-type name. The DATA type stays `<Name>`. The class file name and the
26
26
  // Main import path both use this, so they always agree.
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 = {
@@ -52,7 +52,7 @@ const Entity = cmp(function Entity(props: any) {
52
52
 
53
53
  File({ name: cls + '.' + target.name }, () => {
54
54
 
55
- const opnames = Object.keys(entity.op)
55
+ const opnames = Object.keys(entity.op || {})
56
56
 
57
57
  const opfrags =
58
58
  (['load', 'list', 'create', 'update', 'remove']