@voxgig/sdkgen 3.4.7 → 3.5.0

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 (194) hide show
  1. package/bin/voxgig-sdkgen +78 -10
  2. package/dist/action/action.d.ts +5 -2
  3. package/dist/action/action.js +113 -6
  4. package/dist/action/action.js.map +1 -1
  5. package/dist/action/check.d.ts +15 -0
  6. package/dist/action/check.js +347 -0
  7. package/dist/action/check.js.map +1 -0
  8. package/dist/action/dispatch.d.ts +8 -0
  9. package/dist/action/dispatch.js +74 -0
  10. package/dist/action/dispatch.js.map +1 -0
  11. package/dist/action/docs.d.ts +4 -0
  12. package/dist/action/docs.js +236 -0
  13. package/dist/action/docs.js.map +1 -0
  14. package/dist/action/doctor.d.ts +5 -2
  15. package/dist/action/doctor.js +526 -74
  16. package/dist/action/doctor.js.map +1 -1
  17. package/dist/action/feature.js +96 -17
  18. package/dist/action/feature.js.map +1 -1
  19. package/dist/action/kind.d.ts +30 -0
  20. package/dist/action/kind.js +258 -0
  21. package/dist/action/kind.js.map +1 -0
  22. package/dist/action/package.d.ts +25 -0
  23. package/dist/action/package.js +852 -0
  24. package/dist/action/package.js.map +1 -0
  25. package/dist/action/resolve.d.ts +18 -0
  26. package/dist/action/resolve.js +280 -0
  27. package/dist/action/resolve.js.map +1 -0
  28. package/dist/action/target.d.ts +7 -1
  29. package/dist/action/target.js +240 -97
  30. package/dist/action/target.js.map +1 -1
  31. package/dist/cmp/Docs.d.ts +4 -0
  32. package/dist/cmp/Docs.js +98 -0
  33. package/dist/cmp/Docs.js.map +1 -0
  34. package/dist/cmp/ExternalDocs.d.ts +2 -0
  35. package/dist/cmp/ExternalDocs.js +43 -0
  36. package/dist/cmp/ExternalDocs.js.map +1 -0
  37. package/dist/helpers/definition.d.ts +5 -0
  38. package/dist/helpers/definition.js +72 -0
  39. package/dist/helpers/definition.js.map +1 -0
  40. package/dist/helpers/featureSource.d.ts +8 -2
  41. package/dist/helpers/featureSource.js +61 -12
  42. package/dist/helpers/featureSource.js.map +1 -1
  43. package/dist/helpers/junk.d.ts +11 -0
  44. package/dist/helpers/junk.js +149 -0
  45. package/dist/helpers/junk.js.map +1 -0
  46. package/dist/helpers/manifest.d.ts +50 -0
  47. package/dist/helpers/manifest.js +328 -0
  48. package/dist/helpers/manifest.js.map +1 -0
  49. package/dist/helpers/modelcheck.d.ts +22 -0
  50. package/dist/helpers/modelcheck.js +203 -0
  51. package/dist/helpers/modelcheck.js.map +1 -0
  52. package/dist/helpers/semver.d.ts +4 -0
  53. package/dist/helpers/semver.js +174 -0
  54. package/dist/helpers/semver.js.map +1 -0
  55. package/dist/helpers/shipped.d.ts +4 -0
  56. package/dist/helpers/shipped.js +36 -0
  57. package/dist/helpers/shipped.js.map +1 -0
  58. package/dist/helpers/stdrep.d.ts +9 -1
  59. package/dist/helpers/stdrep.js +22 -0
  60. package/dist/helpers/stdrep.js.map +1 -1
  61. package/dist/sdkgen.d.ts +15 -3
  62. package/dist/sdkgen.js +233 -67
  63. package/dist/sdkgen.js.map +1 -1
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/dist/types.d.ts +10 -2
  66. package/dist/utility.js +17 -0
  67. package/dist/utility.js.map +1 -1
  68. package/model/sdkgen.aontu +69 -0
  69. package/package.json +3 -2
  70. package/project/.sdk/model/feature/audit.aontu +1 -0
  71. package/project/.sdk/model/feature/cache.aontu +1 -0
  72. package/project/.sdk/model/feature/clienttrack.aontu +1 -0
  73. package/project/.sdk/model/feature/debug.aontu +1 -0
  74. package/project/.sdk/model/feature/idempotency.aontu +1 -0
  75. package/project/.sdk/model/feature/log.aontu +1 -0
  76. package/project/.sdk/model/feature/metrics.aontu +1 -0
  77. package/project/.sdk/model/feature/netsim.aontu +1 -0
  78. package/project/.sdk/model/feature/paging.aontu +1 -0
  79. package/project/.sdk/model/feature/proxy.aontu +1 -0
  80. package/project/.sdk/model/feature/ratelimit.aontu +1 -0
  81. package/project/.sdk/model/feature/rbac.aontu +1 -0
  82. package/project/.sdk/model/feature/retry.aontu +1 -0
  83. package/project/.sdk/model/feature/streaming.aontu +1 -0
  84. package/project/.sdk/model/feature/telemetry.aontu +1 -0
  85. package/project/.sdk/model/feature/test.aontu +1 -0
  86. package/project/.sdk/model/feature/timeout.aontu +1 -0
  87. package/project/.sdk/model/target/c.aontu +1 -0
  88. package/project/.sdk/model/target/clojure.aontu +1 -0
  89. package/project/.sdk/model/target/cpp.aontu +1 -0
  90. package/project/.sdk/model/target/dart.aontu +1 -0
  91. package/project/.sdk/model/target/elixir.aontu +1 -0
  92. package/project/.sdk/model/target/go-cli.aontu +1 -0
  93. package/project/.sdk/model/target/go-mcp.aontu +1 -0
  94. package/project/.sdk/model/target/go.aontu +1 -0
  95. package/project/.sdk/model/target/haskell.aontu +1 -0
  96. package/project/.sdk/model/target/java.aontu +1 -0
  97. package/project/.sdk/model/target/js.aontu +1 -0
  98. package/project/.sdk/model/target/kotlin.aontu +1 -0
  99. package/project/.sdk/model/target/lean.aontu +1 -0
  100. package/project/.sdk/model/target/lua.aontu +1 -0
  101. package/project/.sdk/model/target/ocaml.aontu +1 -0
  102. package/project/.sdk/model/target/perl.aontu +1 -0
  103. package/project/.sdk/model/target/php.aontu +1 -0
  104. package/project/.sdk/model/target/py-data.aontu +1 -0
  105. package/project/.sdk/model/target/py.aontu +1 -0
  106. package/project/.sdk/model/target/rb.aontu +1 -0
  107. package/project/.sdk/model/target/rust.aontu +1 -0
  108. package/project/.sdk/model/target/scala.aontu +1 -0
  109. package/project/.sdk/model/target/seneca-provider.aontu +1 -0
  110. package/project/.sdk/model/target/zig.aontu +1 -0
  111. package/project/.sdk/src/cmp/c/Config_c.ts +44 -0
  112. package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +3 -1
  113. package/project/.sdk/src/cmp/clojure/Config_clojure.ts +37 -42
  114. package/project/.sdk/src/cmp/clojure/utility_clojure.ts +42 -0
  115. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +18 -0
  116. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +125 -52
  117. package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +3 -1
  118. package/project/.sdk/src/cmp/csharp/utility_csharp.ts +94 -6
  119. package/project/.sdk/src/cmp/dart/Config_dart.ts +43 -5
  120. package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +74 -0
  121. package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -9
  122. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +16 -5
  123. package/project/.sdk/src/cmp/dart/utility_dart.ts +18 -0
  124. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +128 -13
  125. package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +3 -1
  126. package/project/.sdk/src/cmp/elixir/utility_elixir.ts +6 -0
  127. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +35 -52
  128. package/project/.sdk/src/cmp/haskell/Package_haskell.ts +1 -0
  129. package/project/.sdk/src/cmp/haskell/Test_haskell.ts +1 -1
  130. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +15 -5
  131. package/project/.sdk/src/cmp/java/Config_java.ts +21 -0
  132. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +15 -0
  133. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +42 -39
  134. package/project/.sdk/src/cmp/perl/Config_perl.ts +15 -0
  135. package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +3 -1
  136. package/project/.sdk/src/cmp/rust/Config_rust.ts +44 -0
  137. package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +4 -2
  138. package/project/.sdk/src/cmp/scala/Config_scala.ts +15 -0
  139. package/project/.sdk/src/cmp/swift/Config_swift.ts +17 -0
  140. package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +3 -1
  141. package/project/.sdk/src/cmp/zig/Config_zig.ts +44 -0
  142. package/project/.sdk/src/cmp/zig/TestEntity_zig.ts +9 -2
  143. package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +3 -1
  144. package/project/.sdk/tm/c/core/sdk.h +1 -0
  145. package/project/.sdk/tm/c/utility/make_options.c +5 -1
  146. package/project/.sdk/tm/clojure/src/sdk/core.clj +51 -3
  147. package/project/.sdk/tm/cpp/core/types.hpp +4 -1
  148. package/project/.sdk/tm/cpp/utility/pipeline.hpp +5 -1
  149. package/project/.sdk/tm/csharp/test/PipelineTest.cs +15 -0
  150. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +67 -1
  151. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +43 -0
  152. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +58 -2
  153. package/project/.sdk/tm/haskell/{test/TestJson.hs → src/SdkJson.hs} +8 -4
  154. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +55 -2
  155. package/project/.sdk/tm/haskell/test/Runner.hs +1 -1
  156. package/project/.sdk/tm/haskell/test/StructCorpus.hs +1 -1
  157. package/project/.sdk/tm/java/core/SdkClient.java +3 -1
  158. package/project/.sdk/tm/java/utility/MakeOptions.java +5 -1
  159. package/project/.sdk/tm/kotlin/core/SdkClient.kt +3 -1
  160. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +5 -1
  161. package/project/.sdk/tm/ocaml/Makefile +3 -2
  162. package/project/.sdk/tm/ocaml/sdk_json.ml +106 -0
  163. package/project/.sdk/tm/ocaml/sdk_runtime.ml +56 -0
  164. package/project/.sdk/tm/ocaml/test/struct_corpus.ml +4 -86
  165. package/project/.sdk/tm/perl/utility/make_options.pm +5 -1
  166. package/project/.sdk/tm/rust/utility/make_options.rs +79 -1
  167. package/project/.sdk/tm/scala/core/SdkClient.scala +3 -1
  168. package/project/.sdk/tm/scala/utility/Make.scala +5 -1
  169. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +5 -1
  170. package/project/.sdk/tm/zig/core/utility.zig +85 -1
  171. package/project/sdkgen-package.json +57 -0
  172. package/src/action/action.ts +136 -6
  173. package/src/action/check.ts +495 -0
  174. package/src/action/dispatch.ts +105 -0
  175. package/src/action/docs.ts +310 -0
  176. package/src/action/doctor.ts +692 -81
  177. package/src/action/feature.ts +109 -17
  178. package/src/action/kind.ts +375 -0
  179. package/src/action/package.ts +1173 -0
  180. package/src/action/resolve.ts +362 -0
  181. package/src/action/target.ts +290 -108
  182. package/src/cmp/Docs.ts +125 -0
  183. package/src/cmp/ExternalDocs.ts +54 -0
  184. package/src/helpers/definition.ts +82 -0
  185. package/src/helpers/featureSource.ts +83 -13
  186. package/src/helpers/junk.ts +168 -0
  187. package/src/helpers/manifest.ts +474 -0
  188. package/src/helpers/modelcheck.ts +262 -0
  189. package/src/helpers/semver.ts +211 -0
  190. package/src/helpers/shipped.ts +41 -0
  191. package/src/helpers/stdrep.ts +70 -0
  192. package/src/sdkgen.ts +294 -66
  193. package/src/types.ts +33 -3
  194. package/src/utility.ts +17 -0
@@ -3,8 +3,11 @@ import {
3
3
  Content,
4
4
  File,
5
5
  cmp,
6
+ configDefinition,
7
+ configReprSetting,
6
8
  each,
7
9
  isAuthActive,
10
+ isConfigData,
8
11
  resolveAuthPrefix,
9
12
  } from '@voxgig/sdkgen'
10
13
 
@@ -17,7 +20,6 @@ import {
17
20
 
18
21
 
19
22
  import {
20
- clean,
21
23
  formatOcamlValue,
22
24
  ocamlString,
23
25
  } from './utility_ocaml'
@@ -32,52 +34,52 @@ const Config = cmp(async function Config(props: any) {
32
34
 
33
35
  const model: Model = ctx$.model
34
36
 
35
- const entity = getModelPath(model, `main.${KIT}.entity`)
36
37
  const feature = getModelPath(model, `main.${KIT}.feature`)
37
38
 
38
- const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
39
+ // The canonical config OBJECT and its JSON, from the shared helper. Both
40
+ // representations render from the same `def`, so they cannot describe
41
+ // different configs - and this target picks up `options.server` (the OpenAPI
42
+ // server-variable defaults), which the hand-rolled build here omitted.
43
+ const { def: config, json: configJson } = configDefinition(model)
44
+ const asData = isConfigData(configJson, configReprSetting(model))
39
45
 
40
- const authActive = isAuthActive(model)
41
- const authPrefix = resolveAuthPrefix(model)
46
+ File({ name: 'sdk_config.' + target.ext }, () => {
42
47
 
43
- let baseUrl = ''
44
- try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
48
+ // ABOVE THE THRESHOLD: emit the model as DATA.
49
+ //
50
+ // The literal is one nested expression the compiler type-checks as a
51
+ // single item; a string is one token, and `Sdk_json.json_read` builds the
52
+ // same value tree at runtime. That reader used to live only in the corpus
53
+ // test harness - this rung is why it now lives in the runtime, and the
54
+ // harness uses it from there rather than keeping a second copy.
55
+ //
56
+ // No number-type question: `json_read` yields `Num (float)` and
57
+ // `formatOcamlValue` emits `(Num (5.))`, so both branches agree.
58
+ if (asData) {
59
+ Content(`(* Generated API configuration (mirrors go core/config.go).
60
+ *
61
+ * THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1). Emitted only above a size
62
+ * threshold, or when main.kit.config.repr pins it: for a small model the
63
+ * literal is smaller and far easier to read when debugging.
64
+ *
65
+ * make_config () — the embedded API model as a voxgig struct value.
66
+ * make_feature name — the N-feature-safe factory the client uses. *)
45
67
 
46
- const featureConfig: any = {}
47
- each(feature, (f: any) => {
48
- featureConfig[f.name] = f.config || {}
49
- })
68
+ open Voxgig_struct
69
+ open Sdk_types
70
+ open Sdk_helpers
71
+ open Sdk_features
50
72
 
51
- const entityOptions: any = {}
52
- each(entity, (ent: any) => {
53
- entityOptions[ent.name] = {}
54
- })
73
+ let config_data = "${ocamlString(configJson)}"
55
74
 
56
- const options: any = {
57
- base: baseUrl,
58
- headers,
59
- entity: entityOptions,
60
- }
61
- if (authActive) {
62
- options.auth = { prefix: authPrefix }
63
- }
64
-
65
- const entityConfig = Object.values(entity || {}).reduce((a: any, n: any) => (
66
- a[n.name] = clean({
67
- fields: n.fields,
68
- name: n.name,
69
- op: n.op,
70
- relations: n.relations,
71
- }, true), a), {})
72
-
73
- const config = {
74
- main: { name: model.const.Name },
75
- feature: featureConfig,
76
- options,
77
- entity: entityConfig,
78
- }
75
+ let make_config () : value =
76
+ Sdk_json.json_read config_data
79
77
 
80
- File({ name: 'sdk_config.' + target.ext }, () => {
78
+ let make_feature (name : string) : feature =
79
+ match name with
80
+ `)
81
+ }
82
+ else {
81
83
 
82
84
  Content(`(* Generated API configuration (mirrors go core/config.go).
83
85
  *
@@ -95,6 +97,7 @@ let make_config () : value =
95
97
  let make_feature (name : string) : feature =
96
98
  match name with
97
99
  `)
100
+ }
98
101
 
99
102
  each(feature, (f: any) => {
100
103
  if (f.name !== 'base') {
@@ -110,6 +110,21 @@ sub make_config {
110
110
  return Voxgig::Struct::parse_json($CONFIG_JSON);
111
111
  }
112
112
 
113
+ # SHARED CONFIG (sdkgen rung L2).
114
+ #
115
+ # The SDK reads the config on every request and never writes to it, so one
116
+ # instance is shared by every client rather than rebuilt per client - the
117
+ # difference between parsing the embedded JSON once and once per client.
118
+ #
119
+ # The returned structure is SHARED: treat it as read-only. Callers that need to
120
+ # mutate should use make_config, which always parses a fresh copy.
121
+ my $SHARED_CONFIG;
122
+
123
+ sub shared_config {
124
+ $SHARED_CONFIG = make_config() unless defined $SHARED_CONFIG;
125
+ return $SHARED_CONFIG;
126
+ }
127
+
113
128
  sub make_feature {
114
129
  my ($name) = @_;
115
130
  require(Cwd::abs_path("$__dir/features.pm"));
@@ -41,7 +41,9 @@ sub new {
41
41
  my $utility = ProjectNameUtility->new;
42
42
  $self->{_utility} = $utility;
43
43
 
44
- my $config = ProjectNameConfig::make_config();
44
+ # The process-wide config (sdkgen rung L2): read-only on the request path,
45
+ # so every client shares one rather than rebuilding it.
46
+ my $config = ProjectNameConfig::shared_config();
45
47
 
46
48
  $self->{_rootctx} = $utility->{make_context}->({
47
49
  'client' => $self,
@@ -89,6 +89,28 @@ pub fn make_config() -> Value {
89
89
  json_parse(CONFIG_DATA).expect("${model.const.Name}: embedded config is not valid JSON")
90
90
  }
91
91
 
92
+ // SHARED CONFIG (sdkgen rung L2).
93
+ //
94
+ // The SDK reads the config on every request and never writes to it, so one
95
+ // instance is shared by every client rather than rebuilt per client. Above the
96
+ // size threshold make_config re-parses the whole embedded JSON, so this is the
97
+ // difference between parsing the model once and once per client.
98
+ //
99
+ // THREAD-LOCAL, not a global: Value is Rc/RefCell-backed and so is neither
100
+ // Send nor Sync. One config per thread is the widest scope that is sound here,
101
+ // and the clone is an Rc bump, not a deep copy.
102
+ thread_local! {
103
+ static SHARED_CONFIG: Value = make_config();
104
+ }
105
+
106
+ /// The per-thread config, built once on first use.
107
+ ///
108
+ /// The returned Value SHARES its nodes: treat it as read-only. Callers that
109
+ /// need to mutate should use make_config, which always returns a fresh copy.
110
+ pub fn shared_config() -> Value {
111
+ SHARED_CONFIG.with(|c| c.clone())
112
+ }
113
+
92
114
  pub fn make_feature(name: &str) -> FeatureRef {
93
115
  match name {
94
116
  `)
@@ -107,6 +129,28 @@ pub fn make_config() -> Value {
107
129
  ${formatRustValue(config, 1)}
108
130
  }
109
131
 
132
+ // SHARED CONFIG (sdkgen rung L2).
133
+ //
134
+ // The SDK reads the config on every request and never writes to it, so one
135
+ // instance is shared by every client rather than rebuilt per client. Above the
136
+ // size threshold make_config re-parses the whole embedded JSON, so this is the
137
+ // difference between parsing the model once and once per client.
138
+ //
139
+ // THREAD-LOCAL, not a global: Value is Rc/RefCell-backed and so is neither
140
+ // Send nor Sync. One config per thread is the widest scope that is sound here,
141
+ // and the clone is an Rc bump, not a deep copy.
142
+ thread_local! {
143
+ static SHARED_CONFIG: Value = make_config();
144
+ }
145
+
146
+ /// The per-thread config, built once on first use.
147
+ ///
148
+ /// The returned Value SHARES its nodes: treat it as read-only. Callers that
149
+ /// need to mutate should use make_config, which always returns a fresh copy.
150
+ pub fn shared_config() -> Value {
151
+ SHARED_CONFIG.with(|c| c.clone())
152
+ }
153
+
110
154
  pub fn make_feature(name: &str) -> FeatureRef {
111
155
  match name {
112
156
  `)
@@ -3,7 +3,7 @@
3
3
  use std::cell::RefCell;
4
4
  use std::rc::Rc;
5
5
 
6
- use crate::core::config::{make_config, make_feature};
6
+ use crate::core::config::{make_feature, shared_config};
7
7
  use crate::core::context::{Context, CtxSpec};
8
8
  use crate::core::error::ProjectNameError;
9
9
  use crate::core::helpers::{call_json, get_bool, get_str, getp, getpath, jo, setp, to_int, to_map};
@@ -31,7 +31,9 @@ impl ProjectNameSDK {
31
31
  rootctx: RefCell::new(None),
32
32
  });
33
33
 
34
- let config = make_config();
34
+ // The per-thread config (sdkgen rung L2): read-only on the request
35
+ // path, so every client shares one rather than rebuilding it.
36
+ let config = shared_config();
35
37
 
36
38
  let rootctx = sdk.utility.make_context(
37
39
  CtxSpec {
@@ -93,6 +93,21 @@ object Config {
93
93
  def makeConfig(): JMap[String, Object] =
94
94
  Json.parse(configJson()).asInstanceOf[JMap[String, Object]]
95
95
 
96
+ // SHARED CONFIG (sdkgen rung L2).
97
+ //
98
+ // The SDK reads the config on every request and never writes to it, so one
99
+ // instance is shared by every client rather than rebuilt per client - the
100
+ // difference between parsing the embedded JSON once and once per client.
101
+ //
102
+ // A lazy val is initialised once, on first use, under a lock, so
103
+ // concurrent first calls build it exactly once.
104
+ //
105
+ // The returned map is SHARED: treat it as read-only. Callers that need to
106
+ // mutate should use makeConfig, which always parses a fresh copy.
107
+ private lazy val sharedConfigVal: JMap[String, Object] = makeConfig()
108
+
109
+ def sharedConfig(): JMap[String, Object] = sharedConfigVal
110
+
96
111
  def makeFeature(name: String): Feature = name match {
97
112
  `)
98
113
 
@@ -101,6 +101,23 @@ ${configJson}
101
101
  return (try? JSON.parse(json))?.asMap ?? VMap()
102
102
  }
103
103
 
104
+ // SHARED CONFIG (sdkgen rung L2).
105
+ //
106
+ // The SDK reads the config on every request and never writes to it, so one
107
+ // instance is shared by every client rather than rebuilt per client - the
108
+ // difference between parsing the embedded JSON once and once per client.
109
+ //
110
+ // A static let in an enum is lazy and initialised exactly once, thread-safe
111
+ // via swift_once.
112
+ //
113
+ // The result is SHARED: treat it as read-only. Callers that need to mutate
114
+ // should use makeConfig, which always parses a fresh copy.
115
+ private static let sharedConfigVal: VMap = makeConfig()
116
+
117
+ public static func sharedConfig() -> VMap {
118
+ return sharedConfigVal
119
+ }
120
+
104
121
  public static func makeFeature(_ name: String) -> BaseFeature {
105
122
  switch name {
106
123
  `)
@@ -12,7 +12,9 @@ public final class ProjectNameSDK {
12
12
  public init(_ optionsIn: VMap? = nil) {
13
13
  utility = Utility()
14
14
 
15
- let config = SdkConfig.makeConfig()
15
+ // The process-wide config (sdkgen rung L2): read-only on the request path,
16
+ // so every client shares one rather than rebuilding it.
17
+ let config = SdkConfig.sharedConfig()
16
18
 
17
19
  var ctxmap: [String: Any?] = [
18
20
  "client": self,
@@ -79,6 +79,28 @@ pub fn make_config() Value {
79
79
  @panic("${model.const.Name}: embedded config is not valid JSON");
80
80
  }
81
81
 
82
+ // SHARED CONFIG (sdkgen rung L2).
83
+ //
84
+ // The SDK reads the config on every request and never writes to it, so one
85
+ // instance is shared by every client rather than rebuilt per client. Above the
86
+ // size threshold make_config re-parses the whole embedded JSON, so this is the
87
+ // difference between parsing the model once and once per client.
88
+ //
89
+ // Value nodes are arena-allocated and reference-stable, so the shared value is
90
+ // genuinely one structure, not a copy.
91
+ var shared_config_val: ?Value = null;
92
+
93
+ /// The process-wide config, built once on first use.
94
+ ///
95
+ /// The returned Value SHARES its nodes: treat it as read-only. Callers that
96
+ /// need to mutate should use make_config, which always returns a fresh copy.
97
+ pub fn shared_config() Value {
98
+ if (shared_config_val) |c| return c;
99
+ const c = make_config();
100
+ shared_config_val = c;
101
+ return c;
102
+ }
103
+
82
104
  pub fn make_feature(name: []const u8) Feature {
83
105
  `)
84
106
  }
@@ -96,6 +118,28 @@ pub fn make_config() Value {
96
118
  return ${formatZigValue(config, 1)};
97
119
  }
98
120
 
121
+ // SHARED CONFIG (sdkgen rung L2).
122
+ //
123
+ // The SDK reads the config on every request and never writes to it, so one
124
+ // instance is shared by every client rather than rebuilt per client. Above the
125
+ // size threshold make_config re-parses the whole embedded JSON, so this is the
126
+ // difference between parsing the model once and once per client.
127
+ //
128
+ // Value nodes are arena-allocated and reference-stable, so the shared value is
129
+ // genuinely one structure, not a copy.
130
+ var shared_config_val: ?Value = null;
131
+
132
+ /// The process-wide config, built once on first use.
133
+ ///
134
+ /// The returned Value SHARES its nodes: treat it as read-only. Callers that
135
+ /// need to mutate should use make_config, which always returns a fresh copy.
136
+ pub fn shared_config() Value {
137
+ if (shared_config_val) |c| return c;
138
+ const c = make_config();
139
+ shared_config_val = c;
140
+ return c;
141
+ }
142
+
99
143
  pub fn make_feature(name: []const u8) Feature {
100
144
  `)
101
145
  }
@@ -26,8 +26,15 @@ test "${method}_load_smoke" {
26
26
  const e = testsdk.${method}(vnull());
27
27
  const res = e.load(h.jo(&.{.{ "id", h.vstr("t01") }}), vnull());
28
28
  switch (res) {
29
- .ok => |data| {
30
- try std.testing.expect(std.mem.eql(u8, h.get_str(data, "id") orelse "", "t01"));
29
+ .ok => |ent| {
30
+ // EVERY operation resolves to the ENTITY, not the record: the
31
+ // payload of EntResult.ok is the entity pointer, and the record is
32
+ // reached through data(). Destructuring it as a Value was a
33
+ // compile error ("expected type 'struct.JsonValue', found
34
+ // '*entity.<name>.<Name>Entity'"), so no generated zig SDK with a
35
+ // loadable entity could build its own test suite.
36
+ const rec = ent.asEntity().data(null);
37
+ try std.testing.expect(std.mem.eql(u8, h.get_str(rec, "id") orelse "", "t01"));
31
38
  },
32
39
  .err => |er| {
33
40
  std.debug.print("${method} load failed: {s}\\n", .{er.msg});
@@ -49,7 +49,9 @@ pub const ProjectNameSDK = struct {
49
49
  .rootctx = null,
50
50
  };
51
51
 
52
- const cfg = config.make_config();
52
+ // The process-wide config (sdkgen rung L2): read-only on the request
53
+ // path, so every client shares one rather than rebuilding it.
54
+ const cfg = config.shared_config();
53
55
 
54
56
  const rootctx = sdk.sdkUtility.make_context(CtxSpec{
55
57
  .client = sdk,
@@ -445,6 +445,7 @@ voxgig_value* sdk_graphql(ProjectNameSDK* sdk, const char* query,
445
445
 
446
446
  // Generated config (core/config.c).
447
447
  voxgig_value* make_config(void);
448
+ voxgig_value* shared_config(void);
448
449
  Feature* make_feature(const char* name);
449
450
 
450
451
  // ===========================================================================
@@ -82,7 +82,11 @@ voxgig_value* make_options_util(Context* ctx) {
82
82
  // Preserve system.fetch before merge/validate (validation strips it).
83
83
  voxgig_value* sys_fetch = getpath2(opts, "system", "fetch");
84
84
 
85
- voxgig_value* mergelist = clist(3, v_map(), v_share(cfgopts), v_share(opts));
85
+ /* CLONE the config side, do not v_share it: `config` is a process-wide
86
+ * singleton (shared_config) and merge uses its nested maps as merge TARGETS,
87
+ * so sharing lets one client's options (headers, server, ...) be written into
88
+ * the shared config and inherited by every client built afterwards. */
89
+ voxgig_value* mergelist = clist(3, v_map(), voxgig_clone(cfgopts), v_share(opts));
86
90
  voxgig_value* merged = voxgig_merge(mergelist, VOXGIG_MAXDEPTH);
87
91
  voxgig_value* validated = voxgig_validate(merged, optspec, NULL);
88
92
  if (voxgig_is_map(validated)) {
@@ -80,7 +80,16 @@
80
80
  (aset pos 0 (inc (aget pos 0))))
81
81
  (let [tok (subs s start (aget pos 0))]
82
82
  (if (or (.contains tok ".") (.contains tok "e") (.contains tok "E"))
83
- (Double/parseDouble tok) (Long/parseLong tok)))))]
83
+ (Double/parseDouble tok)
84
+ ;; A JSON integer may exceed the signed 64-bit range - an
85
+ ;; OpenAPI `default`/`example` can carry any integer. The
86
+ ;; Clojure reader gives the literal branch a BigInt for
87
+ ;; such a token, so parseLong alone would make the two
88
+ ;; representations disagree by throwing on input the
89
+ ;; literal accepts.
90
+ (try (Long/parseLong tok)
91
+ (catch NumberFormatException _
92
+ (bigint (java.math.BigInteger. tok))))))))]
84
93
  (parse-val))))
85
94
 
86
95
  ;; ---------------------------------------------------------------------------
@@ -928,11 +937,50 @@
928
937
  "utility" (vs/jm)
929
938
  "system" (vs/jm)
930
939
  "test" (vs/jm "active" false "entity" (vs/jm "`$OPEN`" true))
931
- "clean" (vs/jm "keys" "key,token,id"))
940
+ "clean" (vs/jm "keys" "key,token,id")
941
+ ;; Server-variable values for a templated base URL (OpenAPI
942
+ ;; server variables): {name} placeholders in "base" are
943
+ ;; substituted from this map at construction. Spec defaults
944
+ ;; arrive via the generated config; user values override
945
+ ;; them. Mirrors go's make_options optspec.
946
+ "server" (vs/jm "`$CHILD`" ""))
932
947
  sys-fetch (vs/getpath opts0 "system.fetch")
933
948
  merged (vs/merge (vs/jt (vs/jm) cfgopts opts0))
934
949
  validated (vs/validate merged optspec)
935
- opts (if (vs/ismap validated) validated (vs/jm))]
950
+ opts (if (vs/ismap validated) validated (vs/jm))
951
+ ;; Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
952
+ ;; Every placeholder must resolve to a non-empty value: from
953
+ ;; options["server"] (user), else the config default. A placeholder
954
+ ;; that resolves to "" is a construction ERROR in live mode - the URL
955
+ ;; cannot work - but in test mode substitutes the deterministic value
956
+ ;; "test-<name>" so offline tests need no configuration.
957
+ ;;
958
+ ;; Accepting `server` in the option spec without this is a trap: the
959
+ ;; option validates and is then ignored, so a templated URL still
960
+ ;; reaches the wire with a literal {name} in it.
961
+ base (vs/getprop opts "base")
962
+ _ (when (and (string? base) (.contains ^String base "{"))
963
+ (let [testmode (or (true? (vs/getpath opts "test.active"))
964
+ (true? (vs/getpath opts "feature.test.active")))
965
+ server (let [s (vs/getprop opts "server")]
966
+ (if (vs/ismap s) s (vs/jm)))
967
+ sdkname (let [n (vs/getpath config "main.name")]
968
+ (if (and (string? n) (seq n)) n "SDK"))
969
+ resolved (clojure.string/replace
970
+ base #"\{([A-Za-z0-9_]+)\}"
971
+ (fn [[_ name]]
972
+ (let [v (vs/getprop server name)
973
+ v (if (string? v) v "")]
974
+ (cond
975
+ (seq v) v
976
+ testmode (str "test-" name)
977
+ :else
978
+ (throw (IllegalArgumentException.
979
+ (str sdkname ": the server variable '" name
980
+ "' is required: the API base URL is '" base
981
+ "' - pass {\"server\" {\"" name "\" \"...\"}}"
982
+ " in the SDK options")))))))]
983
+ (vs/setprop opts "base" resolved)))]
936
984
  (when sys-fetch
937
985
  (when (not (vs/ismap (vs/getprop opts "system"))) (.put ^java.util.Map opts "system" (vs/jm)))
938
986
  (.put ^java.util.Map (vs/getprop opts "system") "fetch" sys-fetch))
@@ -53,6 +53,7 @@ using OpMapPtr = std::shared_ptr<OpMap>;
53
53
 
54
54
  // Generated (core/config.hpp) — the embedded API config + feature factory.
55
55
  inline Value makeConfig();
56
+ inline const Value& sharedConfig();
56
57
  inline FeaturePtr makeFeature(const std::string& name);
57
58
 
58
59
  // Defined in utility/pipeline.hpp; wires the Utility function fields.
@@ -781,7 +782,9 @@ inline SdkErrorPtr Context::makeError(const std::string& code, const std::string
781
782
  inline SdkClient::SdkClient(const Value& options_) {
782
783
  utility = std::make_shared<Utility>();
783
784
 
784
- Value config = makeConfig();
785
+ // The process-wide config (sdkgen rung L2): read-only on the request path,
786
+ // so every client shares one rather than rebuilding it.
787
+ Value config = sharedConfig();
785
788
 
786
789
  CtxSpec cs;
787
790
  cs.client = this;
@@ -1095,7 +1095,11 @@ inline Value makeOptions(CtxPtr ctx) {
1095
1095
  // dropped by validate).
1096
1096
  Value sysFetch = Struct::getpath(opts, {"system", "fetch"});
1097
1097
 
1098
- Value mergeList = vlist({vmap(), cfgopts, opts});
1098
+ // CLONE the config side: `config` is a process-wide singleton (sharedConfig)
1099
+ // and merge uses its nested maps as merge TARGETS, so without this one
1100
+ // client's options (headers, server, ...) are written into the shared config
1101
+ // and inherited by every client constructed afterwards.
1102
+ Value mergeList = vlist({vmap(), Struct::clone(cfgopts), opts});
1099
1103
  Value merged = Struct::merge(mergeList);
1100
1104
 
1101
1105
  Value vopts = vmap();
@@ -150,6 +150,21 @@ public class PipelineTest
150
150
  }
151
151
 
152
152
  public object? Match(object? match = null) => null;
153
+
154
+ // The deletion half of the entity contract, mirroring
155
+ // ProjectNameEntityBase: `remove` marks the instance, which keeps the
156
+ // data it held but is no longer a live record.
157
+ private bool _deleted = false;
158
+
159
+ public void MarkDeleted()
160
+ {
161
+ this._deleted = true;
162
+ }
163
+
164
+ public bool Deleted()
165
+ {
166
+ return this._deleted;
167
+ }
153
168
  }
154
169
 
155
170
  // --- MakeResponse -------------------------------------------------------
@@ -1,12 +1,20 @@
1
1
  // ProjectName SDK utility: makeOptions - merge, validate and derive the
2
2
  // client options.
3
3
 
4
+ using System.Text.RegularExpressions;
5
+
4
6
  using Voxgig.Struct;
5
7
 
6
8
  namespace ProjectNameSdk.Util;
7
9
 
8
10
  public static partial class SdkUtility
9
11
  {
12
+ // {name} placeholders in a templated server URL (OpenAPI server
13
+ // variables). Compiled once: MakeOptions runs per client construction.
14
+ private static readonly Regex ServerVarRe =
15
+ new(@"\{([A-Za-z0-9_]+)\}", RegexOptions.Compiled);
16
+
17
+
10
18
  internal static Dictionary<string, object?> MakeOptionsUtil(Context ctx)
11
19
  {
12
20
  var options = ctx.Options ?? new Dictionary<string, object?>();
@@ -73,6 +81,14 @@ public static partial class SdkUtility
73
81
  {
74
82
  ["`$CHILD`"] = "`$STRING`",
75
83
  },
84
+ // OpenAPI server-variable defaults, carried by the generated
85
+ // config whenever the spec's server URL is templated. Accepted
86
+ // here so validation does not reject the SDK's own config; the
87
+ // {name} substitution into base is a separate concern.
88
+ ["server"] = new Dictionary<string, object?>
89
+ {
90
+ ["`$CHILD`"] = "",
91
+ },
76
92
  ["allow"] = new Dictionary<string, object?>
77
93
  {
78
94
  ["method"] = "GET,PUT,POST,PATCH,DELETE,OPTIONS",
@@ -118,12 +134,62 @@ public static partial class SdkUtility
118
134
  var merged = StructUtils.Merge(new List<object?>
119
135
  {
120
136
  new Dictionary<string, object?>(),
121
- cfgopts,
137
+ // CLONE the config side. `config` is a process-wide singleton
138
+ // (SdkConfig.SharedConfig), and Merge uses its nested maps as
139
+ // merge TARGETS - without this, one client's options (headers,
140
+ // server, ...) are written into the shared config and inherited by
141
+ // every client constructed afterwards.
142
+ StructUtils.Clone(cfgopts),
122
143
  opts,
123
144
  });
124
145
  var validated = StructUtils.Validate(merged, optspec);
125
146
  opts = validated as Dictionary<string, object?> ?? new Dictionary<string, object?>();
126
147
 
148
+ // Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
149
+ // Every placeholder must resolve to a non-empty value: from
150
+ // options.server (user), else the Config default. A placeholder that
151
+ // resolves to "" is a construction ERROR in live mode - the URL cannot
152
+ // work - but in test mode substitutes the deterministic value
153
+ // "test-<name>" so offline tests need no configuration. The SDK
154
+ // constructor has no error return, so a missing required variable
155
+ // THROWS: this is construction-time misconfiguration.
156
+ if (opts.TryGetValue("base", out var baseRaw) && baseRaw is string baseUrl &&
157
+ baseUrl.Contains('{'))
158
+ {
159
+ var testmode =
160
+ StructUtils.GetPath(opts, StructUtils.Jt("test", "active")) is bool ta && ta ||
161
+ StructUtils.GetPath(opts, StructUtils.Jt("feature", "test", "active"))
162
+ is bool fa && fa;
163
+
164
+ var server = opts.TryGetValue("server", out var sv) &&
165
+ sv is Dictionary<string, object?> svm
166
+ ? svm : new Dictionary<string, object?>();
167
+
168
+ var sdkname = StructUtils.GetPath(config, StructUtils.Jt("main", "name"))
169
+ is string mn && mn != "" ? mn : "SDK";
170
+
171
+ opts["base"] = ServerVarRe.Replace(baseUrl, m =>
172
+ {
173
+ var name = m.Groups[1].Value;
174
+ var val = server.TryGetValue(name, out var v) && v is string s ? s : "";
175
+ if (val == "")
176
+ {
177
+ if (testmode)
178
+ {
179
+ return "test-" + name;
180
+ }
181
+ throw new ProjectNameError("server_var_required",
182
+ sdkname + ": the server variable '" + name + "' is required: " +
183
+ "the API base URL is '" + baseUrl + "' - pass " +
184
+ "new Dictionary<string, object?> { [\"server\"] = new " +
185
+ "Dictionary<string, object?> { [\"" + name + "\"] = \"...\" } } " +
186
+ "in the SDK options",
187
+ ctx);
188
+ }
189
+ return val;
190
+ });
191
+ }
192
+
127
193
  // Restore system.fetch.
128
194
  if (sysFetch != null)
129
195
  {