@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
@@ -59,7 +59,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
59
59
  // it only applies to entities that declare a list op. Others (e.g. Batch =
60
60
  // create/load) have no list endpoint — make_point errors and the stream
61
61
  // yields nothing — so skip the stream test for them.
62
- const hasList = !!(entity.op && (entity.op as any).list)
62
+ const hasList = !!(entity.op && (entity.op as any)?.list)
63
63
 
64
64
  const basicflow: ModelEntityFlow | undefined =
65
65
  getModelPath(model, `main.${KIT}.flow.Basic${nom(entity, 'Name')}Flow`)
@@ -7,6 +7,7 @@ import {
7
7
  canonKey,
8
8
  each,
9
9
  exampleVarName,
10
+ names,
10
11
  } from '@voxgig/sdkgen'
11
12
 
12
13
  import {
@@ -149,7 +150,31 @@ function clean(o: any) {
149
150
  }
150
151
 
151
152
 
153
+
154
+ // The Go identifier fragment for a feature's generated constructor
155
+ // (New<Fname>Feature / New<Fname>FeatureFunc).
156
+ //
157
+ // SHARED because the identifier is DECLARED in Main_go.ts (registry.go and the
158
+ // root init()) and REFERENCED in Config_go.ts (makeFeature). Two copies of the
159
+ // derivation is a latent undefined-identifier bug in the generated SDK: they
160
+ // were both `name.charAt(0).toUpperCase() + name.slice(1)`, consistently wrong
161
+ // for a name needing real normalisation but at least agreeing, until one side
162
+ // was fixed alone and `rate_limit` became NewRateLimitFeatureFunc in the
163
+ // registry and NewRate_limitFeatureFunc in config.
164
+ //
165
+ // Uses the jostraca-derived PascalCase `Name` (deriving it if absent), which is
166
+ // what Main_ts.ts uses, so a hyphenated or underscored feature name yields a
167
+ // legal Go identifier instead of `NewRate-limitFeatureFunc`.
168
+ function goFeatureName(feat: any): string {
169
+ if (null == feat.Name) {
170
+ names(feat, feat.name)
171
+ }
172
+ return feat.Name
173
+ }
174
+
175
+
152
176
  export {
177
+ goFeatureName,
153
178
  clean,
154
179
  exampleValue,
155
180
  formatGoMap,
@@ -2,7 +2,7 @@
2
2
  import * as Path from 'node:path'
3
3
 
4
4
  import {
5
- cmp, each,
5
+ cmp, each, deriveEntityNames,
6
6
  File, Content, Fragment, Slot,
7
7
  } from '@voxgig/sdkgen'
8
8
 
@@ -40,6 +40,14 @@ const Main = cmp(function Main(props: any) {
40
40
  const cliModule = `github.com/${org}/${model.name}-sdk/go-cli`
41
41
 
42
42
  const entityMap: any = getModelPath(model, `main.${KIT}.entity`)
43
+ // Derive each entity's PascalCase `Name` here rather than relying on another
44
+ // TARGET having run first. This target sets `phase.entity: { active: false }`
45
+ // (see model/target/go-{cli,mcp}.aontu), so create-sdkgen's Root.ts never
46
+ // calls names() for it — the accessor emitted below used to read `Name` off
47
+ // objects the `go` target happened to have named as a side effect, which
48
+ // yields `client.undefined(nil)` whenever this target is generated without
49
+ // `go`, or before it in target order.
50
+ deriveEntityNames(entityMap)
43
51
  const entityNames = Object.keys(entityMap).map(n => n.toLowerCase())
44
52
 
45
53
  const FRAGMENT = Path.normalize(__dirname + '/../../../src/cmp/go-cli/fragment')
@@ -2,7 +2,7 @@
2
2
  import * as Path from 'node:path'
3
3
 
4
4
  import {
5
- cmp, each,
5
+ cmp, each, deriveEntityNames,
6
6
  File, Content, Fragment, Slot,
7
7
  } from '@voxgig/sdkgen'
8
8
 
@@ -54,6 +54,14 @@ const Main = cmp(function Main(props: any) {
54
54
  const mcpModule = `github.com/${org}/${model.name}-sdk/go-mcp`
55
55
 
56
56
  const entityMap: any = getModelPath(model, `main.${KIT}.entity`)
57
+ // Derive each entity's PascalCase `Name` here rather than relying on another
58
+ // TARGET having run first. This target sets `phase.entity: { active: false }`
59
+ // (see model/target/go-{cli,mcp}.aontu), so create-sdkgen's Root.ts never
60
+ // calls names() for it — the accessor emitted below used to read `Name` off
61
+ // objects the `go` target happened to have named as a side effect, which
62
+ // yields `client.undefined(nil)` whenever this target is generated without
63
+ // `go`, or before it in target order.
64
+ deriveEntityNames(entityMap)
57
65
  const entityNames = Object.keys(entityMap).map(n => n.toLowerCase())
58
66
  const entityHelp = entityNames.length > 0 ? entityNames.join(' | ') : '(none)'
59
67
 
@@ -67,35 +67,44 @@ readDoc path = do
67
67
  pure (either (const "") id r)
68
68
 
69
69
 
70
- -- Extract the body of every fenced Haskell block from markdown. The fence
71
- -- marker (three backticks) is built from its char code so this source stays
72
- -- backtick-free.
73
- haskellBlocks :: String -> [String]
70
+ -- Extract the body of every fenced Haskell block from markdown. Returns
71
+ -- (blocks, allClosed) where allClosed is False when a fence was opened but
72
+ -- never closed before EOF — a malformed doc that must fail rather than pass
73
+ -- silently on the unterminated block. The fence marker (three backticks) is
74
+ -- built from its char code so this source stays backtick-free.
75
+ haskellBlocks :: String -> ([String], Bool)
74
76
  haskellBlocks src = go (lines src)
75
77
  where
76
78
  fence3 = replicate 3 (toEnum 96 :: Char)
77
79
  fenceHs = fence3 ++ "haskell"
78
- go [] = []
80
+ go [] = ([], True)
79
81
  go (l : ls)
80
82
  | trimWs l == fenceHs =
81
- let (blk, rest) = break (\\x -> trimWs x == fence3) ls
82
- in unlines blk : go (drop 1 rest)
83
+ case break (\\x -> trimWs x == fence3) ls of
84
+ (blk, []) -> ([unlines blk], False)
85
+ (blk, _ : rest) -> let (bs, ok) = go rest in (unlines blk : bs, ok)
83
86
  | otherwise = go ls
84
87
 
85
88
 
86
- -- Balanced (), [], {} ignoring the contents of double-quoted string literals.
87
- -- The generated snippets never embed an escaped quote inside a string.
89
+ -- Balanced (), [], {} outside of double-quoted string literals, with each
90
+ -- closer matching the most-recent opener via a stack (so "([)]" is rejected,
91
+ -- not merely counted). The generated snippets never embed an escaped quote
92
+ -- inside a string.
88
93
  balanced :: String -> Bool
89
- balanced = go False (0 :: Int)
94
+ balanced = go False []
90
95
  where
91
- go inStr d [] = not inStr && d == 0
92
- go inStr d (c : cs)
93
- | c == '"' = go (not inStr) d cs
94
- | inStr = go inStr d cs
95
- | c == '(' || c == '[' || c == '{' = go inStr (d + 1) cs
96
+ go inStr stack [] = not inStr && null stack
97
+ go inStr stack (c : cs)
98
+ | c == '"' = go (not inStr) stack cs
99
+ | inStr = go inStr stack cs
100
+ | c == '(' = go inStr (')' : stack) cs
101
+ | c == '[' = go inStr (']' : stack) cs
102
+ | c == '{' = go inStr ('}' : stack) cs
96
103
  | c == ')' || c == ']' || c == '}' =
97
- if d <= 0 then False else go inStr (d - 1) cs
98
- | otherwise = go inStr d cs
104
+ case stack of
105
+ (top : rest) | top == c -> go inStr rest cs
106
+ _ -> False
107
+ | otherwise = go inStr stack cs
99
108
 
100
109
 
101
110
  -- The three documents held to the gate, tagged by human label.
@@ -111,7 +120,8 @@ tests :: Counters -> IO ()
111
120
  tests c =
112
121
  forM_ docs (\\(label, path) -> do
113
122
  src <- readDoc path
114
- let blocks = haskellBlocks src
123
+ let (blocks, closed) = haskellBlocks src
124
+ runTest c (label ++ " haskell fences closed") (pure closed)
115
125
  forM_ (zip [1 :: Int ..] blocks) (\\(i, blk) ->
116
126
  runTest c (label ++ " haskell block " ++ show i ++ " well-formed")
117
127
  (pure (not (null (trimWs blk)) && balanced blk))))
@@ -37,7 +37,7 @@ import {
37
37
  File, Content,
38
38
  } from '@voxgig/sdkgen'
39
39
 
40
- import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
40
+ import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
41
41
 
42
42
  import {
43
43
  KIT,
@@ -134,11 +134,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
134
134
  // filter, so inactive entities still get class files referencing these
135
135
  // typed names. Filter on `name` (always present), NOT `active` — parity
136
136
  // with the go emitter's fix.
137
- const entityList = each(entity).filter((e: any) => e && null != e.name)
137
+ const entityList = deriveEntityNames(entity)
138
138
  // Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
139
139
  // entity not yet named (e.g. a fieldless placeholder) would otherwise read
140
140
  // `Name = undefined` below. Parity with the go/py/csharp emitter's fix.
141
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
142
141
 
143
142
  // Surface duplicate generated type names (two entities with the same
144
143
  // 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 {
@@ -25,7 +25,7 @@ const Entity = cmp(function Entity(props: any) {
25
25
  // Collision-free entity CLASS name (see entityClassName): normally
26
26
  // `<Name>Entity`, but disambiguated (e.g. `<Name>EntityClient`) when it
27
27
  // would clash with another entity's data-type name.
28
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
28
+ const entityColl = entityCollection(model)
29
29
  const cls = entityClassName(entity, entityColl)
30
30
 
31
31
  const entrep = {
@@ -42,7 +42,7 @@ const Entity = cmp(function Entity(props: any) {
42
42
 
43
43
  File({ name: cls + '.' + target.ext }, () => {
44
44
 
45
- const opnames = Object.keys(entity.op)
45
+ const opnames = Object.keys(entity.op || {})
46
46
 
47
47
  // For each CRUD op: if the spec defines it, splice in the real
48
48
  // 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 javapackage = javaPackage(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 = javaVarName(entity.name)
24
24
 
@@ -31,7 +31,7 @@ const Package = cmp(async function Package(props: any) {
31
31
 
32
32
  // Dep name convention: "groupId:artifactId" with an explicit version.
33
33
  const deps: { groupId: string, artifactId: string, version: string, kind: string }[] = []
34
- for (const d of collectDeps(model, target.name, target.deps)) {
34
+ for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
35
35
  const [dgroup, dartifact] = String(d.name).split(':')
36
36
  if (dgroup && dartifact && d.version) {
37
37
  deps.push({
@@ -95,6 +95,23 @@ public class ReadmeExamplesTest {
95
95
  return blocks;
96
96
  }
97
97
 
98
+ // A well-formed doc has an even number of triple-backtick fence markers:
99
+ // every opening fence has a matching close. An odd count means a fence was
100
+ // opened but never closed — a truncated or malformed generated doc. The
101
+ // split-based javaBlocks() would silently treat the unterminated tail as a
102
+ // complete block, so this must be checked explicitly.
103
+ private static boolean fencesClosed(String text) {
104
+ String fence = String.valueOf((char) 96).repeat(3);
105
+ int count = 0;
106
+ int from = 0;
107
+ int idx;
108
+ while ((idx = text.indexOf(fence, from)) >= 0) {
109
+ count++;
110
+ from = idx + fence.length();
111
+ }
112
+ return count % 2 == 0;
113
+ }
114
+
98
115
  private static String readDoc(Path path, String label) {
99
116
  assertTrue(Files.exists(path), label + " not found: " + path);
100
117
  try {
@@ -158,7 +175,12 @@ public class ReadmeExamplesTest {
158
175
  if (!Files.exists(path)) {
159
176
  continue;
160
177
  }
161
- List<String> blocks = javaBlocks(readDoc(path, doc[0]));
178
+ String src = readDoc(path, doc[0]);
179
+ if (!fencesClosed(src)) {
180
+ failures.add(doc[0] +
181
+ " has an unclosed code fence (opened but never closed)");
182
+ }
183
+ List<String> blocks = javaBlocks(src);
162
184
  int balancedCount = 0;
163
185
  for (int i = 0; i < blocks.size(); i++) {
164
186
  if (balanced(blocks.get(i))) {
@@ -75,7 +75,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
75
75
 
76
76
  const authActive = isAuthActive(model)
77
77
 
78
- const opnames = Object.keys(entity.op)
78
+ const opnames = Object.keys(entity.op || {})
79
79
  const hasLoad = opnames.includes('load')
80
80
  const hasList = opnames.includes('list')
81
81
 
@@ -83,8 +83,8 @@ const TestDirect = cmp(function TestDirect(props: any) {
83
83
  return
84
84
  }
85
85
 
86
- const loadOp = entity.op.load
87
- const listOp = entity.op.list
86
+ const loadOp = entity.op?.load
87
+ const listOp = entity.op?.list
88
88
 
89
89
  // Get load point info
90
90
  const loadPoint = loadOp?.points?.[0]
@@ -26,7 +26,7 @@ import {
26
26
  File, Content,
27
27
  } from '@voxgig/sdkgen'
28
28
 
29
- import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
29
+ import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
30
30
 
31
31
  import {
32
32
  KIT,
@@ -57,11 +57,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
57
57
  // filter, so inactive entities still get class files referencing these
58
58
  // typed names. Filter on `name` (always present), NOT `active` — parity
59
59
  // with the go emitter's fix.
60
- const entityList = each(entity).filter((e: any) => e && null != e.name)
60
+ const entityList = deriveEntityNames(entity)
61
61
  // Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
62
62
  // entity not yet named (e.g. a fieldless placeholder) would otherwise read
63
63
  // `Name = undefined` below. Parity with the go emitter's fix.
64
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
65
64
 
66
65
  // Surface duplicate generated type names (two entities with the same
67
66
  // 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 class file name and the Main require path both use this.
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 = {
@@ -37,7 +37,7 @@ const Entity = cmp(function Entity(props: any) {
37
37
 
38
38
  File({ name: cls + '.' + target.name }, () => {
39
39
 
40
- const opnames = Object.keys(entity.op)
40
+ const opnames = Object.keys(entity.op || {})
41
41
 
42
42
  const opfrags =
43
43
  (['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 name
16
16
  // (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
  // Entity access: \`client.${entity.Name}().list()\` / \`client.${entity.Name}().load({ id })\`.
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, each, names, cmap,
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
- entityClassName,
7
+ entityClassName, entityCollection,
8
8
  } from '@voxgig/sdkgen'
9
9
 
10
10
 
@@ -57,7 +57,7 @@ const Main = cmp(async function Main(props: any) {
57
57
  Line(`// ${model.const.Name} ${target.Name} SDK\n`)
58
58
 
59
59
  List({ item: entity }, ({ item }: any) => {
60
- const cls = entityClassName(item, entity)
60
+ const cls = entityClassName(item, entityCollection(model))
61
61
  return Line(`const { ${cls} } = require('./entity/${cls}')`)
62
62
  })
63
63
 
@@ -48,7 +48,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
48
48
  apikey: env.${PROJECTNAME}_APIKEY,`
49
49
  : ''
50
50
 
51
- const opnames = Object.keys(entity.op)
51
+ const opnames = Object.keys(entity.op || {})
52
52
  const hasLoad = opnames.includes('load')
53
53
  const hasList = opnames.includes('list')
54
54
 
@@ -134,7 +134,7 @@ function directSetup(mockres) {
134
134
 
135
135
 
136
136
  function generateDirectLoad(model: Model, entity: ModelEntity) {
137
- const loadOp = entity.op.load
137
+ const loadOp = entity.op?.load
138
138
  const loadPoint: ModelPoint | undefined = loadOp?.points?.[0]
139
139
 
140
140
  if (null == loadPoint) {
@@ -145,7 +145,7 @@ function generateDirectLoad(model: Model, entity: ModelEntity) {
145
145
  const loadPath = normalizePathParams(loadPoint.parts || [], loadParams, loadPoint.rename?.param)
146
146
 
147
147
  // Get list info for live mode bootstrapping
148
- const listOp = entity.op.list
148
+ const listOp = entity.op?.list
149
149
  const listPoint = listOp?.points?.[0]
150
150
  const listParams = listPoint?.args?.params || []
151
151
  const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listParams, listPoint.rename?.param) : ''
@@ -231,7 +231,7 @@ ${paramAsserts} }
231
231
 
232
232
 
233
233
  function generateDirectList(model: Model, entity: ModelEntity) {
234
- const listOp = entity.op.list
234
+ const listOp = entity.op?.list
235
235
  const listPoint: ModelPoint | undefined = listOp?.points?.[0]
236
236
 
237
237
  if (null == listPoint) {
@@ -39,7 +39,7 @@ import {
39
39
  File, Content,
40
40
  } from '@voxgig/sdkgen'
41
41
 
42
- import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
42
+ import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
43
43
 
44
44
  import {
45
45
  KIT,
@@ -132,11 +132,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
132
132
  // filter, so inactive entities still get class files referencing these
133
133
  // typed names. Filter on `name` (always present), NOT `active` — parity
134
134
  // with the go emitter's fix.
135
- const entityList = each(entity).filter((e: any) => e && null != e.name)
135
+ const entityList = deriveEntityNames(entity)
136
136
  // Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
137
137
  // entity not yet named (e.g. a fieldless placeholder) would otherwise read
138
138
  // `Name = undefined` below. Parity with the go/py/csharp/java emitter's fix.
139
- entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
140
139
 
141
140
  // Surface duplicate generated type names (two entities with the same
142
141
  // 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 {
@@ -25,7 +25,7 @@ const Entity = cmp(function Entity(props: any) {
25
25
  // Collision-free entity CLASS name (see entityClassName): normally
26
26
  // `<Name>Entity`, but disambiguated (e.g. `<Name>EntityClient`) when it
27
27
  // would clash with another entity's data-type name.
28
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
28
+ const entityColl = entityCollection(model)
29
29
  const cls = entityClassName(entity, entityColl)
30
30
 
31
31
  const entrep = {
@@ -41,7 +41,7 @@ const Entity = cmp(function Entity(props: any) {
41
41
 
42
42
  File({ name: cls + '.' + target.ext }, () => {
43
43
 
44
- const opnames = Object.keys(entity.op)
44
+ const opnames = Object.keys(entity.op || {})
45
45
 
46
46
  // For each CRUD op: if the spec defines it, splice in the real
47
47
  // implementation. Otherwise emit a stub that satisfies the SdkEntity
@@ -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,
@@ -17,7 +17,7 @@ const MainEntity = cmp(async function MainEntity(props: any) {
17
17
  const { model } = props.ctx$
18
18
 
19
19
  const kotlinpackage = kotlinPackage(model)
20
- const entityColl = getModelPath(model, `main.${KIT}.entity`)
20
+ const entityColl = entityCollection(model)
21
21
  const cls = entityClassName(entity, entityColl)
22
22
  const accessor = kotlinVarName(entity.name)
23
23
 
@@ -30,7 +30,7 @@ const Package = cmp(async function Package(props: any) {
30
30
 
31
31
  // Dep name convention: "group:artifact" with an explicit version.
32
32
  const deps: { group: string, artifact: string, version: string, kind: string }[] = []
33
- for (const d of collectDeps(model, target.name, target.deps)) {
33
+ for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
34
34
  const [dgroup, dartifact] = String(d.name).split(':')
35
35
  if (dgroup && dartifact && d.version) {
36
36
  deps.push({
@@ -46,7 +46,9 @@ const ReadmeExamplesTest = cmp(function ReadmeExamplesTest(props: any) {
46
46
  // Checks, PER DOCUMENT:
47
47
  // 1. PRESENCE: the doc exists and holds at least one kotlin block.
48
48
  // 2. STRUCTURE: every kotlin block's braces / parens / brackets balance, so a
49
- // truncated or malformed generated snippet fails loudly.
49
+ // truncated or malformed generated snippet fails loudly. Every code fence
50
+ // is also terminated (even fence count), so an unclosed fence at EOF is
51
+ // caught rather than accepted as a complete block.
50
52
  //
51
53
  // GENERATED by @voxgig/sdkgen — do not edit by hand.
52
54
 
@@ -97,6 +99,22 @@ class ReadmeExamplesTest {
97
99
  return Files.readString(path)
98
100
  }
99
101
 
102
+ // Count triple-backtick fence markers. A well-formed document opens and
103
+ // closes every fenced block, so the total must be EVEN. An odd count means a
104
+ // fence was opened but never closed (a truncated / malformed doc) — the
105
+ // split-based extraction in kotlinBlocks() would otherwise accept the
106
+ // unterminated trailing segment as a complete block and pass silently.
107
+ private fun fenceCount(text: String): Int {
108
+ val fence = 96.toChar().toString().repeat(3)
109
+ var count = 0
110
+ var idx = text.indexOf(fence)
111
+ while (idx >= 0) {
112
+ count++
113
+ idx = text.indexOf(fence, idx + fence.length)
114
+ }
115
+ return count
116
+ }
117
+
100
118
  // True when every open/close delimiter balances (ignores the subtlety of
101
119
  // delimiters inside string literals — a generated snippet never nests an
102
120
  // unbalanced delimiter in a string, and this catches truncation).
@@ -145,7 +163,14 @@ class ReadmeExamplesTest {
145
163
  if (!Files.exists(path)) {
146
164
  continue
147
165
  }
148
- val blocks = kotlinBlocks(readDoc(path, doc[0]))
166
+ val src = readDoc(path, doc[0])
167
+ // A fence opened but never closed is a malformed doc — fail loudly
168
+ // rather than silently accepting the unterminated trailing segment as a
169
+ // complete block.
170
+ if (fenceCount(src) % 2 != 0) {
171
+ failures.add(doc[0] + " has an unterminated code fence")
172
+ }
173
+ val blocks = kotlinBlocks(src)
149
174
  var balancedCount = 0
150
175
  for (i in blocks.indices) {
151
176
  if (balanced(blocks[i])) {
@@ -72,7 +72,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
72
72
 
73
73
  const authActive = isAuthActive(model)
74
74
 
75
- const opnames = Object.keys(entity.op)
75
+ const opnames = Object.keys(entity.op || {})
76
76
  const hasLoad = opnames.includes('load')
77
77
  const hasList = opnames.includes('list')
78
78
 
@@ -80,8 +80,8 @@ const TestDirect = cmp(function TestDirect(props: any) {
80
80
  return
81
81
  }
82
82
 
83
- const loadOp = entity.op.load
84
- const listOp = entity.op.list
83
+ const loadOp = entity.op?.load
84
+ const listOp = entity.op?.list
85
85
 
86
86
  const loadPoint = loadOp?.points?.[0]
87
87
  const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''