@voxgig/sdkgen 4.5.3 → 4.6.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 (174) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/check.js +15 -0
  3. package/dist/action/check.js.map +1 -1
  4. package/dist/cmp/AgentGuide.js +1 -1
  5. package/dist/cmp/AgentGuide.js.map +1 -1
  6. package/dist/cmp/AgentGuideContent.d.ts +1 -1
  7. package/dist/cmp/AgentGuideContent.js +8 -2
  8. package/dist/cmp/AgentGuideContent.js.map +1 -1
  9. package/dist/cmp/Feature.js +17 -0
  10. package/dist/cmp/Feature.js.map +1 -1
  11. package/dist/cmp/FeatureDocs.d.ts +1 -1
  12. package/dist/cmp/FeatureDocs.js +6 -1
  13. package/dist/cmp/FeatureDocs.js.map +1 -1
  14. package/dist/cmp/ReadmeExplanation.js +3 -1
  15. package/dist/cmp/ReadmeExplanation.js.map +1 -1
  16. package/dist/cmp/ReadmeFeatures.js +1 -1
  17. package/dist/cmp/ReadmeFeatures.js.map +1 -1
  18. package/dist/cmp/ReadmeRefFeatures.js +1 -1
  19. package/dist/cmp/ReadmeRefFeatures.js.map +1 -1
  20. package/dist/helpers/applicability.d.ts +6 -0
  21. package/dist/helpers/applicability.js +103 -0
  22. package/dist/helpers/applicability.js.map +1 -0
  23. package/dist/helpers/collectDeps.js +4 -2
  24. package/dist/helpers/collectDeps.js.map +1 -1
  25. package/dist/helpers/serverVars.d.ts +2 -1
  26. package/dist/helpers/serverVars.js +17 -0
  27. package/dist/helpers/serverVars.js.map +1 -1
  28. package/dist/sdkgen.d.ts +3 -2
  29. package/dist/sdkgen.js +9 -2
  30. package/dist/sdkgen.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/dist/utility.js +7 -1
  33. package/dist/utility.js.map +1 -1
  34. package/model/sdkgen.aon +39 -0
  35. package/package.json +1 -1
  36. package/project/.sdk/model/feature/feature-index.aon +1 -0
  37. package/project/.sdk/model/feature/secrets.aon +118 -0
  38. package/project/.sdk/model/target/ts.aon +8 -1
  39. package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
  40. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
  41. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
  42. package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
  43. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
  44. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
  45. package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
  46. package/project/.sdk/src/cmp/go/Main_go.ts +6 -2
  47. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
  48. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
  49. package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
  50. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
  51. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
  52. package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
  53. package/project/.sdk/src/cmp/java/Main_java.ts +5 -1
  54. package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
  55. package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
  56. package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
  57. package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
  58. package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
  59. package/project/.sdk/src/cmp/js/Main_js.ts +5 -1
  60. package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
  61. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
  62. package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
  63. package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
  64. package/project/.sdk/src/cmp/lua/Main_lua.ts +5 -1
  65. package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
  66. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
  67. package/project/.sdk/src/cmp/perl/Main_perl.ts +5 -1
  68. package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
  69. package/project/.sdk/src/cmp/php/Main_php.ts +5 -1
  70. package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
  71. package/project/.sdk/src/cmp/py/Main_py.ts +5 -1
  72. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
  73. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
  74. package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
  75. package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
  76. package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
  77. package/project/.sdk/src/cmp/rb/Main_rb.ts +5 -1
  78. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
  79. package/project/.sdk/src/cmp/rust/Main_rust.ts +5 -1
  80. package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
  81. package/project/.sdk/src/cmp/scala/Main_scala.ts +5 -1
  82. package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
  83. package/project/.sdk/src/cmp/ts/Config_ts.ts +5 -1
  84. package/project/.sdk/src/cmp/ts/Main_ts.ts +57 -1
  85. package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
  86. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
  87. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
  88. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
  89. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +67 -7
  90. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +27 -4
  91. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
  92. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
  93. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
  94. package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
  95. package/project/.sdk/tm/c/utility/make_options.c +22 -0
  96. package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
  97. package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
  98. package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
  99. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
  100. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
  101. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
  102. package/project/.sdk/tm/go/core/context.go +1 -1
  103. package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
  104. package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
  105. package/project/.sdk/tm/go/feature/test_feature.go +12 -5
  106. package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
  107. package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
  108. package/project/.sdk/tm/go/test/runner_test.go +46 -0
  109. package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
  110. package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
  111. package/project/.sdk/tm/go/utility/fetcher.go +2 -2
  112. package/project/.sdk/tm/go/utility/make_options.go +24 -5
  113. package/project/.sdk/tm/go/utility/make_point.go +1 -1
  114. package/project/.sdk/tm/go/utility/make_spec.go +17 -1
  115. package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
  116. package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
  117. package/project/.sdk/tm/go/utility/transform_request.go +29 -1
  118. package/project/.sdk/tm/go/utility/transform_response.go +20 -1
  119. package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
  120. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
  121. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
  122. package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
  123. package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
  124. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
  125. package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
  126. package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
  127. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
  128. package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
  129. package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
  130. package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
  131. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
  132. package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
  133. package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
  134. package/project/.sdk/tm/py/test/runner.py +32 -0
  135. package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
  136. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
  137. package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
  138. package/project/.sdk/tm/rust/utility/make_options.rs +25 -0
  139. package/project/.sdk/tm/scala/utility/Make.scala +17 -0
  140. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
  141. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
  142. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +1221 -0
  143. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +381 -0
  144. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +151 -0
  145. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +42 -0
  146. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
  147. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
  148. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
  149. package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
  150. package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
  151. package/project/.sdk/tm/ts/test/omni.ts +86 -0
  152. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
  153. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
  154. package/project/.sdk/tm/ts/test/utility.ts +122 -0
  155. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +692 -0
  156. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +238 -0
  157. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
  158. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
  159. package/project/.sdk/tm/zig/core/utility.zig +23 -0
  160. package/project/sdkgen-package.json +2 -1
  161. package/src/action/check.ts +18 -0
  162. package/src/cmp/AgentGuide.ts +1 -1
  163. package/src/cmp/AgentGuideContent.ts +9 -2
  164. package/src/cmp/Feature.ts +19 -0
  165. package/src/cmp/FeatureDocs.ts +7 -1
  166. package/src/cmp/ReadmeExplanation.ts +4 -1
  167. package/src/cmp/ReadmeFeatures.ts +1 -1
  168. package/src/cmp/ReadmeRefFeatures.ts +1 -1
  169. package/src/helpers/applicability.ts +123 -0
  170. package/src/helpers/collectDeps.ts +5 -1
  171. package/src/helpers/serverVars.ts +19 -0
  172. package/src/sdkgen.ts +9 -1
  173. package/src/utility.ts +10 -1
  174. package/project/.sdk/tm/ts/test/runner.ts +0 -425
package/model/sdkgen.aon CHANGED
@@ -172,6 +172,27 @@ main: kit: target: &: {
172
172
  ext: string
173
173
  comment: line: string
174
174
 
175
+ # APPLICABILITY (docs/design/feature-tags.md). What this target can offer
176
+ # a feature; a feature applies here when its `needs` is a subset of this.
177
+ # Empty is the default, so a target says nothing until it has a reason to.
178
+ #
179
+ # THE VOCABULARY IS CLOSED — extended by editing this comment and the
180
+ # schema, never by a package inventing a string. Two authors spelling the
181
+ # same capability differently is the whole failure mode a free-form tag
182
+ # would reintroduce.
183
+ #
184
+ # sekreto a vendored sekreto port lives in this target's feature
185
+ # container, so a feature can resolve secrets through it
186
+ #
187
+ # Tags named in the design note but not yet load-bearing
188
+ # (`per-feature-file`, `deps`, `transport-wrap`, `hooks`, `delegates`)
189
+ # are deliberately absent until a feature actually gates on one: a tag
190
+ # whose answer is currently "everyone" buys nothing and has to be kept
191
+ # true across 27 targets.
192
+ #
193
+ # A map keyed by tag, matching `needs` — see the note there.
194
+ provides: &: boolean
195
+
175
196
  base: *'' | string
176
197
  origname: *'' | string
177
198
  package: *'' | string
@@ -420,6 +441,24 @@ main: kit: feature: &: {
420
441
  # TODO: version '0.0.1' | semver # where semver is a string format type as per semver.org
421
442
  version: *'0.0.1' | string
422
443
 
444
+ # APPLICABILITY (docs/design/feature-tags.md). What this feature requires
445
+ # of a target; it applies where `needs` is a subset of the target's
446
+ # `provides`. Empty means "applies anywhere", so every feature that
447
+ # predates the gate is unaffected — the gate is additive by construction.
448
+ #
449
+ # A tag, not a target list: a list has to be edited for targets the
450
+ # feature's author has never heard of, so a published package would
451
+ # starve every target added after its release.
452
+ #
453
+ # The vocabulary is closed and lives in `main: kit: target: &: provides`.
454
+ #
455
+ # A MAP keyed by tag, not a list, for the reason `contributor` is a map:
456
+ # aontu unifies maps by key, so a package can add one tag without
457
+ # restating the others. A list here also hangs the unifier outright on a
458
+ # real project's model (a defaulted list disjunction under this spread),
459
+ # which is what settled the shape.
460
+ needs: &: boolean
461
+
423
462
  # Transport role (station design §8.4): how the feature relates to the
424
463
  # transport slot. 'base' REPLACES ctx.utility.fetcher outright (only the
425
464
  # `test` feature); 'wrap' wraps it in init() (netsim, retry, cache,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/sdkgen",
3
- "version": "4.5.3",
3
+ "version": "4.6.0",
4
4
  "main": "dist/sdkgen.js",
5
5
  "type": "commonjs",
6
6
  "engines": {
@@ -3,6 +3,7 @@
3
3
  # Core
4
4
  @"test.aon"
5
5
  @"log.aon"
6
+ @"secrets.aon"
6
7
 
7
8
  # Enterprise capabilities
8
9
  @"retry.aon"
@@ -0,0 +1,118 @@
1
+
2
+ main: kit: feature: secrets: {
3
+
4
+ name: key()
5
+ title: "Secret access: resolve the API credential through a provider chain, and exchange a refresh token for short-lived access tokens"
6
+ version: '0.1.0'
7
+ base: 'BASE'
8
+
9
+ # `active` is deliberately NOT set here. The base schema defaults it
10
+ # (`active: *false | boolean`), and in aontu a concrete value does not
11
+ # yield to another concrete value — it CONFLICTS. Restating `active:
12
+ # false` here would make a project's `main: kit: feature: secrets:
13
+ # active: true` fail to unify, which is the same defect the publication
14
+ # keys carry a warning about in model/target/ts.aon.
15
+ #
16
+ # So the feature is OFF until a project turns it on, in its own overlay
17
+ # (model/project.aon), which `feature add` never overwrites.
18
+
19
+ # Applicability (docs/design/feature-tags.md). The feature is a thin
20
+ # layer over a VENDORED sekreto port, so it only applies to a target
21
+ # whose feature container carries one. Without this tag the feature
22
+ # would fan out to every target, and each one that generated would
23
+ # carry an import for a class it has no source for.
24
+ needs: { sekreto: true }
25
+
26
+ # Transport role (station design §8.4).
27
+ #
28
+ # 'wrap', because the access-token exchange below has to SEE the response:
29
+ # a spent token is discovered by the API answering 401, and the only place
30
+ # to notice that and try again is around the transport. It wraps only when
31
+ # `exchange.active` — with the exchange off this feature never touches the
32
+ # transport, exactly as when its role was 'none' — but the role is a
33
+ # DECLARATION of what the feature may do, not a report of what one
34
+ # configuration does, so it is declared at its widest.
35
+ transport: 'wrap'
36
+
37
+ config: {
38
+ options: {
39
+ active: false
40
+
41
+ # The sekreto provider chain, in first-hit order. Each entry is a
42
+ # sekreto ProviderSpec ({ kind, name, ... }) or a provider object.
43
+ # An explicit `apikey` option, when set, is placed AHEAD of these as
44
+ # a `memory` store named 'options', so an explicit credential still
45
+ # wins — by sekreto's own first-hit rule, not by special-casing.
46
+ providers: []
47
+
48
+ # The secret to resolve, for an API whose credential is not
49
+ # apikey-shaped. The resolved value is written to options.apikey
50
+ # regardless, because that is where prepareAuth looks.
51
+ name: 'apikey'
52
+
53
+ # sekreto caches resolved values by default. Off means every
54
+ # resolve() asks the chain again; redaction still tracks values.
55
+ cache: true
56
+
57
+ # ACCESS-TOKEN EXCHANGE (the OAuth2 refresh grant, in miniature).
58
+ #
59
+ # Some APIs do not accept a long-lived credential at all: the secret the
60
+ # chain above resolves is a REFRESH token, which buys a short-lived ACCESS
61
+ # token from a token endpoint, and that access token is what every request
62
+ # carries. The access token then expires, and the client is expected to buy
63
+ # another without human help.
64
+ #
65
+ # Off by default, and inert when off: with `exchange.active: false` this
66
+ # feature behaves exactly as it always did — resolve one secret, write it to
67
+ # options.apikey. That is what keeps the change additive for every SDK
68
+ # already using it.
69
+ #
70
+ # With it on, set `name` to the refresh secret (`name: 'refresh_token'`),
71
+ # because `name` is what the chain is asked for.
72
+ exchange: {
73
+ active: false
74
+
75
+ # The token endpoint, RELATIVE to options.base — the base already
76
+ # carries whatever account or tenant segment the server URL declares,
77
+ # so a relative path follows it without being told about it.
78
+ path: 'auth/token'
79
+ method: 'POST'
80
+
81
+ # The JSON field the refresh credential is SENT in, and the field the
82
+ # access token comes back in. Named rather than assumed: `access_token`
83
+ # and `refresh_token` are the OAuth2 spelling, and plenty of APIs use
84
+ # their own.
85
+ request: 'refresh_token'
86
+ response: 'access_token'
87
+
88
+ # An explicit refresh token, given in code. It becomes the FIRST
89
+ # provider in the chain, so it wins by sekreto's own first-hit rule —
90
+ # the same seat `options.apikey` takes when no exchange is configured.
91
+ # `options.apikey` cannot serve here: with an exchange configured it
92
+ # means "an access token I already hold", which is the opposite kind
93
+ # of credential.
94
+ refresh: ''
95
+
96
+ # Response statuses that mean "your access token is spent, buy
97
+ # another". 401 is the answer every API in this shape gives; 403 is
98
+ # listed by some, so it is configurable rather than assumed.
99
+ statuses: [401]
100
+
101
+ # How many times ONE request may buy a token and try again. One is
102
+ # right: a second refusal on a token bought moments ago is a real
103
+ # failure — a revoked refresh token, the wrong account — and retrying
104
+ # it turns that into a spin instead of an error.
105
+ retries: 1
106
+ }
107
+ }
108
+ }
109
+
110
+ # Resolution is ASYNC (a provider may do IO, and the exchange makes an
111
+ # HTTP request) but prepareAuth is sync.
112
+ # Entity ops await featureHook('PreSpec') before makeSpec, so this is
113
+ # the one seam where the lookup can happen and still land in time.
114
+ # Concurrent operations share the single in-flight promise.
115
+ hook: {
116
+ PreSpec: { active: true, await: true }
117
+ }
118
+ }
@@ -7,13 +7,20 @@ main: kit: target: ts: {
7
7
  module: name: '$$name$$'
8
8
  base: 'BASE'
9
9
 
10
+ # Applicability (docs/design/feature-tags.md). A vendored sekreto port
11
+ # lives in this target's feature container, so a feature that resolves
12
+ # secrets through sekreto can apply here.
13
+ provides: { sekreto: true }
14
+
10
15
  deps: &: {
11
16
  # TODO: fix aontu disjunctions!
12
17
  kind: *'prod' | string # 'peer' |'dev'
13
18
  }
14
19
 
15
20
  deps: {
16
- 'dotenv': { active: true, version: '^17.4.2', kind: dev }
21
+ # dotenv was dropped when the generated entity tests moved to
22
+ # `loadEnvLocal` (tm/ts/test/utility.ts) — the SDK's last non-tooling
23
+ # dependency of any kind.
17
24
  'typescript': { active: true, version: '^5.9.3', kind: dev }
18
25
  '@types/node': { active: true, version: '^25.6.0', kind: dev }
19
26
  }
@@ -9,6 +9,7 @@ import {
9
9
  isAuthActive,
10
10
  isConfigData,
11
11
  resolveAuthPrefix,
12
+ targetFeatures,
12
13
  } from '@voxgig/sdkgen'
13
14
 
14
15
 
@@ -36,7 +37,10 @@ const Config = cmp(async function Config(props: any) {
36
37
  const model: Model = ctx$.model
37
38
 
38
39
  const entity = getModelPath(model, `main.${KIT}.entity`)
39
- const feature = getModelPath(model, `main.${KIT}.feature`)
40
+ // Gated by the applicability tags, so this target never imports or
41
+ // registers a feature it has no source for. One rule, one place:
42
+ // helpers/applicability.
43
+ const feature = targetFeatures(model, target)
40
44
 
41
45
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
42
46
 
@@ -5,6 +5,7 @@ import {
5
5
  cmp,
6
6
  configDefinition,
7
7
  each,
8
+ targetFeatures,
8
9
  } from '@voxgig/sdkgen'
9
10
 
10
11
 
@@ -30,7 +31,10 @@ const Config = cmp(async function Config(props: any) {
30
31
 
31
32
  const model: Model = ctx$.model
32
33
 
33
- const feature = getModelPath(model, `main.${KIT}.feature`)
34
+ // Gated by the applicability tags, so this target never imports or
35
+ // registers a feature it has no source for. One rule, one place:
36
+ // helpers/applicability.
37
+ const feature = targetFeatures(model, target)
34
38
 
35
39
  // The embedded config, built by the shared helper so this target's shapes
36
40
  // and identity fields stay in step with the ts reference by construction.
@@ -7,6 +7,7 @@ import {
7
7
  configReprSetting,
8
8
  each,
9
9
  isConfigData,
10
+ targetFeatures,
10
11
  } from '@voxgig/sdkgen'
11
12
 
12
13
 
@@ -33,7 +34,10 @@ const Config = cmp(async function Config(props: any) {
33
34
 
34
35
  const model: Model = ctx$.model
35
36
 
36
- const feature = getModelPath(model, `main.${KIT}.feature`)
37
+ // Gated by the applicability tags, so this target never imports or
38
+ // registers a feature it has no source for. One rule, one place:
39
+ // helpers/applicability.
40
+ const feature = targetFeatures(model, target)
37
41
 
38
42
  // The same config as an OBJECT, built by the shared helper so this target's
39
43
  // literal and the data that replaces it above the threshold are the same
@@ -13,6 +13,7 @@ import {
13
13
  isConfigData,
14
14
  isAuthActive,
15
15
  resolveAuthPrefix,
16
+ targetFeatures,
16
17
  } from '@voxgig/sdkgen'
17
18
 
18
19
 
@@ -38,7 +39,10 @@ const Config = cmp(async function Config(props: any) {
38
39
  const model: Model = ctx$.model
39
40
 
40
41
  const entity = getModelPath(model, `main.${KIT}.entity`)
41
- const feature = getModelPath(model, `main.${KIT}.feature`)
42
+ // Gated by the applicability tags, so this target never imports or
43
+ // registers a feature it has no source for. One rule, one place:
44
+ // helpers/applicability.
45
+ const feature = targetFeatures(model, target)
42
46
 
43
47
  const ff = Path.normalize(__dirname + '/../../../src/cmp/dart/fragment/')
44
48
 
@@ -10,6 +10,7 @@ import {
10
10
  isAuthActive,
11
11
  isConfigData,
12
12
  resolveAuthPrefix,
13
+ targetFeatures,
13
14
  } from '@voxgig/sdkgen'
14
15
 
15
16
 
@@ -35,7 +36,10 @@ const Config = cmp(async function Config(props: any) {
35
36
  const Name = model.const.Name
36
37
 
37
38
  const entity = getModelPath(model, `main.${KIT}.entity`)
38
- const feature = getModelPath(model, `main.${KIT}.feature`)
39
+ // Gated by the applicability tags, so this target never imports or
40
+ // registers a feature it has no source for. One rule, one place:
41
+ // helpers/applicability.
42
+ const feature = targetFeatures(model, target)
39
43
 
40
44
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
41
45
 
@@ -4,6 +4,7 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, each,
6
6
  File, Folder, Content, Copy, Fragment,
7
+ targetFeatures,
7
8
  } from '@voxgig/sdkgen'
8
9
 
9
10
 
@@ -38,7 +39,10 @@ const Main = cmp(async function Main(props: any) {
38
39
 
39
40
  const Name = model.const.Name
40
41
  const entity = getModelPath(model, `main.${KIT}.entity`)
41
- const feature = getModelPath(model, `main.${KIT}.feature`)
42
+ // Gated by the applicability tags, so this target never imports or
43
+ // registers a feature it has no source for. One rule, one place:
44
+ // helpers/applicability.
45
+ const feature = targetFeatures(model, target)
42
46
 
43
47
  const ff = Path.normalize(__dirname + '/../../../src/cmp/elixir/fragment/')
44
48
 
@@ -16,6 +16,7 @@ import {
16
16
  isConfigData,
17
17
  resolveAuthPrefix,
18
18
  serverVariables,
19
+ targetFeatures,
19
20
  } from '@voxgig/sdkgen'
20
21
 
21
22
 
@@ -40,7 +41,10 @@ const Config = cmp(async function Config(props: any) {
40
41
  const model: Model = ctx$.model
41
42
 
42
43
  const entity = getModelPath(model, `main.${KIT}.entity`)
43
- const feature = getModelPath(model, `main.${KIT}.feature`)
44
+ // Gated by the applicability tags, so this target never imports or
45
+ // registers a feature it has no source for. One rule, one place:
46
+ // helpers/applicability.
47
+ const feature = targetFeatures(model, target)
44
48
 
45
49
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
46
50
 
@@ -4,7 +4,8 @@ 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, entityCollection, goModule, goPackageIdent
7
+ entityClassName, entityCollection, goModule, goPackageIdent,
8
+ targetFeatures,
8
9
  } from '@voxgig/sdkgen'
9
10
 
10
11
 
@@ -33,7 +34,10 @@ const Main = cmp(async function Main(props: any) {
33
34
  const { model } = props.ctx$
34
35
 
35
36
  const entity: ModelEntity = getModelPath(model, `main.${KIT}.entity`)
36
- const feature = getModelPath(model, `main.${KIT}.feature`)
37
+ // Gated by the applicability tags, so this target never imports or
38
+ // registers a feature it has no source for. One rule, one place:
39
+ // helpers/applicability.
40
+ const feature = targetFeatures(model, target)
37
41
 
38
42
  // Go module path == the repo path on GitHub (org from model.origin),
39
43
  // e.g. github.com/voxgig-sdk/<slug>-sdk. Used in go.mod and every import.
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, opRequestShape, entityIdField, entityOps, goModule } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, opRequestShape, entityIdField, entityOps, goModule , serverVariables} from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -25,9 +25,22 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
25
25
  const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
26
26
 
27
27
  const authActive = isAuthActive(model)
28
+
29
+ // Server variables (a templated server URL) are REQUIRED at construction
30
+ // - the SDK refuses rather than request a URL with a literal
31
+ // {account_id} in it - so a quickstart that omits them is a quickstart
32
+ // that fails on its first line.
33
+ const svars = serverVariables(model)
34
+ const goServerField = 0 === svars.length ? '' :
35
+ `\n "server": map[string]any{` +
36
+ svars.map((v: any) => `\n "${v.name}": "<${v.name}>",`).join('') +
37
+ `\n },`
38
+
28
39
  const ctor = authActive
29
- ? `sdk.New${model.const.Name}SDK(map[string]any{\n "apikey": os.Getenv("${envName(model)}_APIKEY"),\n })`
30
- : `sdk.New()`
40
+ ? `sdk.New${model.const.Name}SDK(map[string]any{\n "apikey": os.Getenv("${envName(model)}_APIKEY"),${goServerField}\n })`
41
+ : ('' === goServerField
42
+ ? `sdk.New()`
43
+ : `sdk.New${model.const.Name}SDK(map[string]any{${goServerField}\n })`)
31
44
 
32
45
  // Build the body of main() from the operations the example entity
33
46
  // supports. Each op names a fresh value var, so `:=` always declares a
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, entityIdField, entityOps, opRequestShape, goModule } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, entityIdField, entityOps, opRequestShape, goModule , serverVariables} from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -20,9 +20,22 @@ const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
20
20
  const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
21
21
 
22
22
  const authActive = isAuthActive(model)
23
+
24
+ // Server variables (a templated server URL) are REQUIRED at construction
25
+ // - the SDK refuses rather than request a URL with a literal
26
+ // {account_id} in it - so a quickstart that omits them is a quickstart
27
+ // that fails on its first line.
28
+ const svars = serverVariables(model)
29
+ const goServerField = 0 === svars.length ? '' :
30
+ `\n "server": map[string]any{` +
31
+ svars.map((v: any) => `\n "${v.name}": "<${v.name}>",`).join('') +
32
+ `\n },`
33
+
23
34
  const ctor = authActive
24
- ? `sdk.New${model.const.Name}SDK(map[string]any{\n "apikey": os.Getenv("${envName(model)}_APIKEY"),\n})`
25
- : `sdk.New()`
35
+ ? `sdk.New${model.const.Name}SDK(map[string]any{\n "apikey": os.Getenv("${envName(model)}_APIKEY"),${goServerField}\n})`
36
+ : ('' === goServerField
37
+ ? `sdk.New()`
38
+ : `sdk.New${model.const.Name}SDK(map[string]any{${goServerField}\n})`)
26
39
 
27
40
  Content(`\`\`\`go
28
41
  import sdk "${gomodule}"
@@ -11,7 +11,9 @@ import {
11
11
  File,
12
12
  cmp,
13
13
  snakify,
14
- isAuthActive, envName, envToken, liveStrict
14
+ isAuthActive,
15
+ serverVarEnv,
16
+ serverVariables, envName, envToken, liveStrict
15
17
  } from '@voxgig/sdkgen'
16
18
 
17
19
 
@@ -73,12 +75,26 @@ const TestDirect = cmp(function TestDirect(props: any) {
73
75
 
74
76
  const authActive = isAuthActive(model)
75
77
  const apikeyEnvEntry = authActive
76
- ? `\n\t\t"${PROJECTNAME}_APIKEY": "NONE",`
78
+ ? `\n\t\t"${PROJECTNAME}_APIKEY": "",`
77
79
  : ''
78
80
  const apikeyLiveField = authActive
79
81
  ? `\n\t\t\t"apikey": env["${PROJECTNAME}_APIKEY"],`
80
82
  : ''
81
83
 
84
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
85
+ // impossible to construct without values: makeOptions refuses rather than
86
+ // request a URL with a literal `{account_id}` in it. Taken from the
87
+ // environment, the same way the apikey is.
88
+ const svars = serverVariables(model)
89
+ const serverEnvEntry = svars
90
+ .map((v: any) => `\n\t\t"${serverVarEnv(PROJECTNAME, v.name)}": ${JSON.stringify(v.dflt)},`).join('')
91
+ const serverLiveField = 0 === svars.length ? '' :
92
+ `\n\t\t"server": map[string]any{` +
93
+ svars.map((v: any) =>
94
+ `\n\t\t\t"${v.name}": env["${serverVarEnv(PROJECTNAME, v.name)}"],`).join('') +
95
+ `\n\t\t},`
96
+
97
+
82
98
  const opnames = Object.keys(entity.op || {})
83
99
  const hasLoad = opnames.includes('load')
84
100
  const hasList = opnames.includes('list')
@@ -559,13 +575,21 @@ func ${entity.name}DirectSetup(mockres any) *${entity.name}DirectSetupResult {
559
575
 
560
576
  env := envOverride(map[string]any{
561
577
  "${entidEnvVar}": map[string]any{},
562
- "${PROJECTNAME}_TEST_LIVE": "FALSE",${apikeyEnvEntry}
578
+ "${PROJECTNAME}_TEST_LIVE": "FALSE",${apikeyEnvEntry}${serverEnvEntry}
563
579
  })
564
580
 
565
581
  live := env["${PROJECTNAME}_TEST_LIVE"] == "TRUE"
566
582
 
567
583
  if live {
568
- mergedOpts := map[string]any{${apikeyLiveField}
584
+ // sdk-test-control.json's test.client.options seeds the live
585
+ // client; the generated fields below overwrite anything they name.
586
+ mergedOpts := map[string]any{}
587
+ for k, v := range liveClientOptions() {
588
+ mergedOpts[k] = v
589
+ }
590
+ for k, v := range map[string]any{${apikeyLiveField}${serverLiveField}
591
+ } {
592
+ mergedOpts[k] = v
569
593
  }
570
594
  client := sdk.New${model.const.Name}SDK(mergedOpts)
571
595
 
@@ -24,6 +24,8 @@ import {
24
24
  buildIdNames,
25
25
  getMatchEntries,
26
26
  isAuthActive,
27
+ serverVarEnv,
28
+ serverVariables,
27
29
  entityDataIdField, envName, envToken
28
30
  } from '@voxgig/sdkgen'
29
31
 
@@ -71,12 +73,26 @@ const TestEntity = cmp(function TestEntity(props: any) {
71
73
 
72
74
  const authActive = isAuthActive(model)
73
75
  const apikeyEnvEntry = authActive
74
- ? `\n\t\t"${PROJUPPER}_APIKEY": "NONE",`
76
+ ? `\n\t\t"${PROJUPPER}_APIKEY": "",`
75
77
  : ''
76
78
  const apikeyLiveField = authActive
77
79
  ? `\n\t\t\t\t"apikey": env["${PROJUPPER}_APIKEY"],`
78
80
  : ''
79
81
 
82
+
83
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
84
+ // impossible to construct without values: makeOptions refuses rather than
85
+ // request a URL with a literal `{account_id}` in it. Taken from the
86
+ // environment, the same way the apikey is.
87
+ const svars = serverVariables(model)
88
+ const serverEnvEntry = svars
89
+ .map((v: any) => `\n\t\t"${serverVarEnv(PROJUPPER, v.name)}": ${JSON.stringify(v.dflt)},`).join('')
90
+ const serverLiveField = 0 === svars.length ? '' :
91
+ `\n\t\t\t\t"server": map[string]any{` +
92
+ svars.map((v: any) =>
93
+ `\n\t\t\t\t\t"${v.name}": env["${serverVarEnv(PROJUPPER, v.name)}"],`).join('') +
94
+ `\n\t\t\t\t},`
95
+
80
96
  const idnames = buildIdNames(entity, basicflow)
81
97
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
82
98
 
@@ -205,7 +221,7 @@ ${allSteps.length > 0 ? '\t\tclient := setup.client\n\n' : ''}`)
205
221
  const preambleRef = entity.name + '_ref01'
206
222
  const preambleVar = goVar(preambleRef)
207
223
  Content(` // Bootstrap entity data from existing test data (no create step in flow).
208
- ${preambleVar}DataRaw := vs.Items(core.ToMapAny(vs.GetPath("existing.${entity.name}", setup.data)))
224
+ ${preambleVar}DataRaw := vs.Items(core.ToMapAny(vs.GetPath(setup.data, "existing.${entity.name}")))
209
225
  var ${preambleVar}Data map[string]any
210
226
  if len(${preambleVar}DataRaw) > 0 {
211
227
  ${preambleVar}Data = core.ToMapAny(${preambleVar}DataRaw[0][1])
@@ -265,9 +281,14 @@ ${allSteps.length > 0 ? '\t\tclient := setup.client\n\n' : ''}`)
265
281
 
266
282
  `)
267
283
 
268
- // Generate idmap via vs.Transform
284
+ // Generate idmap via vs.Transform.
285
+ //
286
+ // The error return Transform gained in struct go 0.1.3 is discarded
287
+ // deliberately: this transforms the entity's own id names, which the
288
+ // generator produced, so an error would be a generator bug rather than
289
+ // anything the generated test can act on.
269
290
  Content('\t// Generate idmap via transform, matching TS pattern.\n')
270
- Content('\tidmap := vs.Transform(\n')
291
+ Content('\tidmap, _ := vs.Transform(\n')
271
292
  Content('\t\t[]any{' + idnamesStr + '},\n')
272
293
  Content('\t\tmap[string]any{\n')
273
294
  Content('\t\t\t"`$PACK`": []any{"", map[string]any{\n')
@@ -287,7 +308,7 @@ ${allSteps.length > 0 ? '\t\tclient := setup.client\n\n' : ''}`)
287
308
  env := envOverride(map[string]any{
288
309
  "${PROJUPPER}_TEST_${envToken(entity.name)}_ENTID": idmap,
289
310
  "${PROJUPPER}_TEST_LIVE": "FALSE",
290
- "${PROJUPPER}_TEST_EXPLAIN": "FALSE",${apikeyEnvEntry}
311
+ "${PROJUPPER}_TEST_EXPLAIN": "FALSE",${apikeyEnvEntry}${serverEnvEntry}
291
312
  })
292
313
 
293
314
  idmapResolved := core.ToMapAny(env["${PROJUPPER}_TEST_${envToken(entity.name)}_ENTID"])
@@ -307,10 +328,22 @@ ${allSteps.length > 0 ? '\t\tclient := setup.client\n\n' : ''}`)
307
328
 
308
329
  Content(`
309
330
  if env["${PROJUPPER}_TEST_LIVE"] == "TRUE" {
331
+ // An empty map, not a nil one: Merge returns nil when its last entry
332
+ // is nil, and BasicSetup is normally called with no extras - so a
333
+ // bare nil silently discarded the apikey and server values below.
334
+ extraOpts := extra
335
+ if extraOpts == nil {
336
+ extraOpts = map[string]any{}
337
+ }
338
+
310
339
  mergedOpts := vs.Merge([]any{
311
- map[string]any{${apikeyLiveField}
340
+ // liveClientOptions() FIRST, so the generated fields below win:
341
+ // sdk-test-control.json's test.client.options adds to the live
342
+ // client, it does not redirect it.
343
+ liveClientOptions(),
344
+ map[string]any{${apikeyLiveField}${serverLiveField}
312
345
  },
313
- extra,
346
+ extraOpts,
314
347
  })
315
348
  client = sdk.New${model.const.Name}SDK(core.ToMapAny(mergedOpts))
316
349
  }
@@ -361,11 +394,11 @@ const generateCreate: OpGen = (ctx, step, index) => {
361
394
  // Load data from test data file
362
395
  if (hasDatvar) {
363
396
  Content(` ${datavar} = core.ToMapAny(vs.GetProp(
364
- vs.GetPath([]any{"new", "${entity.name}"}, setup.data), "${ref}"))
397
+ vs.GetPath(setup.data, []any{"new", "${entity.name}"}), "${ref}"))
365
398
  `)
366
399
  } else {
367
400
  Content(` ${datavar} := core.ToMapAny(vs.GetProp(
368
- vs.GetPath([]any{"new", "${entity.name}"}, setup.data), "${ref}"))
401
+ vs.GetPath(setup.data, []any{"new", "${entity.name}"}), "${ref}"))
369
402
  `)
370
403
  }
371
404
 
@@ -606,7 +639,7 @@ const generateLoad: OpGen = (ctx, step, index) => {
606
639
  `)
607
640
  }
608
641
  if (!hasSrcData && hasEntId) {
609
- Content(` ${srcdatavar}Raw := vs.Items(core.ToMapAny(vs.GetPath("existing.${entity.name}", setup.data)))
642
+ Content(` ${srcdatavar}Raw := vs.Items(core.ToMapAny(vs.GetPath(setup.data, "existing.${entity.name}")))
610
643
  var ${srcdatavar} map[string]any
611
644
  if len(${srcdatavar}Raw) > 0 {
612
645
  ${srcdatavar} = core.ToMapAny(${srcdatavar}Raw[0][1])