@voxgig/sdkgen 4.5.2 → 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 (176) 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/cmp/ReadmeTop.js +1 -1
  21. package/dist/helpers/applicability.d.ts +6 -0
  22. package/dist/helpers/applicability.js +103 -0
  23. package/dist/helpers/applicability.js.map +1 -0
  24. package/dist/helpers/collectDeps.js +4 -2
  25. package/dist/helpers/collectDeps.js.map +1 -1
  26. package/dist/helpers/serverVars.d.ts +2 -1
  27. package/dist/helpers/serverVars.js +17 -0
  28. package/dist/helpers/serverVars.js.map +1 -1
  29. package/dist/sdkgen.d.ts +3 -2
  30. package/dist/sdkgen.js +9 -2
  31. package/dist/sdkgen.js.map +1 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/dist/utility.js +7 -1
  34. package/dist/utility.js.map +1 -1
  35. package/model/sdkgen.aon +39 -0
  36. package/package.json +1 -1
  37. package/project/.sdk/model/feature/feature-index.aon +1 -0
  38. package/project/.sdk/model/feature/secrets.aon +118 -0
  39. package/project/.sdk/model/target/ts.aon +8 -1
  40. package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
  41. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
  42. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
  43. package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
  44. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
  45. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
  46. package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
  47. package/project/.sdk/src/cmp/go/Main_go.ts +6 -2
  48. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
  49. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
  50. package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
  51. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
  52. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
  53. package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
  54. package/project/.sdk/src/cmp/java/Main_java.ts +5 -1
  55. package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
  56. package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
  57. package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
  58. package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
  59. package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
  60. package/project/.sdk/src/cmp/js/Main_js.ts +5 -1
  61. package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
  62. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
  63. package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
  64. package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
  65. package/project/.sdk/src/cmp/lua/Main_lua.ts +5 -1
  66. package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
  67. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
  68. package/project/.sdk/src/cmp/perl/Main_perl.ts +5 -1
  69. package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
  70. package/project/.sdk/src/cmp/php/Main_php.ts +5 -1
  71. package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
  72. package/project/.sdk/src/cmp/py/Main_py.ts +5 -1
  73. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
  74. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
  75. package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
  76. package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
  77. package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
  78. package/project/.sdk/src/cmp/rb/Main_rb.ts +5 -1
  79. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
  80. package/project/.sdk/src/cmp/rust/Main_rust.ts +5 -1
  81. package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
  82. package/project/.sdk/src/cmp/scala/Main_scala.ts +5 -1
  83. package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
  84. package/project/.sdk/src/cmp/ts/Config_ts.ts +5 -1
  85. package/project/.sdk/src/cmp/ts/Main_ts.ts +57 -1
  86. package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
  87. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
  88. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
  89. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
  90. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +67 -7
  91. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +27 -4
  92. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
  93. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
  94. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
  95. package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
  96. package/project/.sdk/tm/c/utility/make_options.c +22 -0
  97. package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
  98. package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
  99. package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
  100. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
  101. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
  102. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
  103. package/project/.sdk/tm/go/core/context.go +1 -1
  104. package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
  105. package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
  106. package/project/.sdk/tm/go/feature/test_feature.go +12 -5
  107. package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
  108. package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
  109. package/project/.sdk/tm/go/test/runner_test.go +46 -0
  110. package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
  111. package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
  112. package/project/.sdk/tm/go/utility/fetcher.go +2 -2
  113. package/project/.sdk/tm/go/utility/make_options.go +24 -5
  114. package/project/.sdk/tm/go/utility/make_point.go +1 -1
  115. package/project/.sdk/tm/go/utility/make_spec.go +17 -1
  116. package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
  117. package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
  118. package/project/.sdk/tm/go/utility/transform_request.go +29 -1
  119. package/project/.sdk/tm/go/utility/transform_response.go +20 -1
  120. package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
  121. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
  122. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
  123. package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
  124. package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
  125. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
  126. package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
  127. package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
  128. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
  129. package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
  130. package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
  131. package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
  132. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
  133. package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
  134. package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
  135. package/project/.sdk/tm/py/test/runner.py +32 -0
  136. package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
  137. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
  138. package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
  139. package/project/.sdk/tm/rust/utility/make_options.rs +25 -0
  140. package/project/.sdk/tm/scala/utility/Make.scala +17 -0
  141. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
  142. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
  143. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +1221 -0
  144. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +381 -0
  145. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +151 -0
  146. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +42 -0
  147. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
  148. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
  149. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
  150. package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
  151. package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
  152. package/project/.sdk/tm/ts/test/omni.ts +86 -0
  153. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
  154. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
  155. package/project/.sdk/tm/ts/test/utility.ts +122 -0
  156. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +692 -0
  157. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +238 -0
  158. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
  159. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
  160. package/project/.sdk/tm/zig/core/utility.zig +23 -0
  161. package/project/sdkgen-package.json +2 -1
  162. package/src/action/check.ts +18 -0
  163. package/src/cmp/AgentGuide.ts +1 -1
  164. package/src/cmp/AgentGuideContent.ts +9 -2
  165. package/src/cmp/Feature.ts +19 -0
  166. package/src/cmp/FeatureDocs.ts +7 -1
  167. package/src/cmp/ReadmeExplanation.ts +4 -1
  168. package/src/cmp/ReadmeFeatures.ts +1 -1
  169. package/src/cmp/ReadmeRefFeatures.ts +1 -1
  170. package/src/cmp/ReadmeTop.ts +1 -1
  171. package/src/helpers/applicability.ts +123 -0
  172. package/src/helpers/collectDeps.ts +5 -1
  173. package/src/helpers/serverVars.ts +19 -0
  174. package/src/sdkgen.ts +9 -1
  175. package/src/utility.ts +10 -1
  176. package/project/.sdk/tm/ts/test/runner.ts +0 -425
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, canonKey, canonScalarKey, entityIdField, opRequestShape, safeVarName, exampleVarName, matchArg, idLiteral } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, canonKey, canonScalarKey, entityIdField, opRequestShape, safeVarName, exampleVarName, matchArg, idLiteral , serverVariables} from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -41,10 +41,23 @@ const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
41
41
  const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
42
42
 
43
43
  const authActive = isAuthActive(model)
44
+
45
+ // Server variables (a templated server URL) are REQUIRED at construction
46
+ // - the SDK refuses rather than request a URL with a literal
47
+ // {account_id} in it - so a quickstart that omits them is a quickstart
48
+ // that fails on its first line.
49
+ const svars = serverVariables(model)
50
+ const pyServerField = 0 === svars.length ? '' :
51
+ `\n "server": {` +
52
+ svars.map((v: any) => `\n "${v.name}": "<${v.name}>",`).join('') +
53
+ `\n },`
54
+
44
55
  const apikeyImport = authActive ? `import os\n` : ''
45
56
  const ctor = authActive
46
- ? `${model.const.Name}SDK({\n "apikey": os.environ.get("${envName(model)}_APIKEY"),\n})`
47
- : `${model.const.Name}SDK()`
57
+ ? `${model.const.Name}SDK({\n "apikey": os.environ.get("${envName(model)}_APIKEY"),${pyServerField}\n})`
58
+ : ('' === pyServerField
59
+ ? `${model.const.Name}SDK()`
60
+ : `${model.const.Name}SDK({${pyServerField}\n})`)
48
61
 
49
62
  Content(`\`\`\`python
50
63
  ${apikeyImport}from ${model.const.Name.toLowerCase()}_sdk import ${model.const.Name}SDK
@@ -11,7 +11,9 @@ import {
11
11
  File,
12
12
  cmp,
13
13
  snakify,
14
- isAuthActive, envName, envToken
14
+ isAuthActive,
15
+ serverVarEnv,
16
+ serverVariables, envName, envToken
15
17
  } from '@voxgig/sdkgen'
16
18
 
17
19
 
@@ -64,12 +66,26 @@ const TestDirect = cmp(function TestDirect(props: any) {
64
66
 
65
67
  const authActive = isAuthActive(model)
66
68
  const apikeyEnvEntry = authActive
67
- ? `\n "${PROJECTNAME}_APIKEY": "NONE",`
69
+ ? `\n "${PROJECTNAME}_APIKEY": "",`
68
70
  : ''
69
71
  const apikeyLiveField = authActive
70
72
  ? `\n "apikey": env.get("${PROJECTNAME}_APIKEY"),`
71
73
  : ''
72
74
 
75
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
76
+ // impossible to construct without values: make_options raises rather than
77
+ // request a URL with a literal `{account_id}` in it. Taken from the
78
+ // environment, the same way the apikey is.
79
+ const svars = serverVariables(model)
80
+ const serverEnvEntry = svars
81
+ .map((v: any) => `\n "${serverVarEnv(PROJECTNAME, v.name)}": ${JSON.stringify(v.dflt)},`).join('')
82
+ const serverLiveField = 0 === svars.length ? '' :
83
+ `\n "server": {` +
84
+ svars.map((v: any) =>
85
+ `\n "${v.name}": env.get("${serverVarEnv(PROJECTNAME, v.name)}"),`).join('') +
86
+ `\n },`
87
+
88
+
73
89
  const opnames = Object.keys(entity.op || {})
74
90
  const hasLoad = opnames.includes('load')
75
91
  const hasList = opnames.includes('list')
@@ -337,14 +353,17 @@ def _${entity.name}_direct_setup(mockres):
337
353
 
338
354
  env = runner.env_override({
339
355
  "${entidEnvVar}": {},
340
- "${PROJECTNAME}_TEST_LIVE": "FALSE",${apikeyEnvEntry}
356
+ "${PROJECTNAME}_TEST_LIVE": "FALSE",${apikeyEnvEntry}${serverEnvEntry}
341
357
  })
342
358
 
343
359
  live = env.get("${PROJECTNAME}_TEST_LIVE") == "TRUE"
344
360
 
345
361
  if live:
346
- merged_opts = {${apikeyLiveField}
347
- }
362
+ # sdk-test-control.json's test.client.options seeds the live
363
+ # client; the generated fields below overwrite anything they name.
364
+ merged_opts = dict(runner.live_client_options())
365
+ merged_opts.update({${apikeyLiveField}${serverLiveField}
366
+ })
348
367
  client = ${model.const.Name}SDK(merged_opts)
349
368
  return {
350
369
  "client": client,
@@ -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
 
@@ -60,12 +62,26 @@ const TestEntity = cmp(function TestEntity(props: any) {
60
62
 
61
63
  const authActive = isAuthActive(model)
62
64
  const apikeyEnvEntry = authActive
63
- ? `\n "${PROJUPPER}_APIKEY": "NONE",`
65
+ ? `\n "${PROJUPPER}_APIKEY": "",`
64
66
  : ''
65
67
  const apikeyLiveField = authActive
66
68
  ? `\n "apikey": env.get("${PROJUPPER}_APIKEY"),`
67
69
  : ''
68
70
 
71
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
72
+ // impossible to construct without values: make_options raises rather than
73
+ // request a URL with a literal `{account_id}` in it. Taken from the
74
+ // environment, the same way the apikey is.
75
+ const svars = serverVariables(model)
76
+ const serverEnvEntry = svars
77
+ .map((v: any) => `\n "${serverVarEnv(PROJUPPER, v.name)}": ${JSON.stringify(v.dflt)},`).join('')
78
+ const serverLiveField = 0 === svars.length ? '' :
79
+ `\n "server": {` +
80
+ svars.map((v: any) =>
81
+ `\n "${v.name}": env.get("${serverVarEnv(PROJUPPER, v.name)}"),`).join('') +
82
+ `\n },`
83
+
84
+
69
85
  const idnames = buildIdNames(entity, basicflow)
70
86
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
71
87
 
@@ -235,7 +251,7 @@ def _${entity.name}_basic_setup(extra):
235
251
  env = runner.env_override({
236
252
  "${PROJUPPER}_TEST_${envToken(entity.name)}_ENTID": idmap,
237
253
  "${PROJUPPER}_TEST_LIVE": "FALSE",
238
- "${PROJUPPER}_TEST_EXPLAIN": "FALSE",${apikeyEnvEntry}
254
+ "${PROJUPPER}_TEST_EXPLAIN": "FALSE",${apikeyEnvEntry}${serverEnvEntry}
239
255
  })
240
256
 
241
257
  idmap_resolved = helpers.to_map(
@@ -254,7 +270,11 @@ def _${entity.name}_basic_setup(extra):
254
270
  Content(`
255
271
  if env.get("${PROJUPPER}_TEST_LIVE") == "TRUE":
256
272
  merged_opts = vs.merge([
257
- {${apikeyLiveField}
273
+ # FIRST, so the generated fields below win: sdk-test-control.json's
274
+ # test.client.options adds to the live client, it does not
275
+ # redirect it.
276
+ runner.live_client_options(),
277
+ {${apikeyLiveField}${serverLiveField}
258
278
  },
259
279
  extra or {},
260
280
  ])
@@ -16,6 +16,7 @@ import {
16
16
  rawStringLiteral,
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,6 +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
+ targetFeatures,
7
8
  } from '@voxgig/sdkgen'
8
9
 
9
10
 
@@ -31,7 +32,10 @@ const Main = cmp(async function Main(props: any) {
31
32
  const { model } = props.ctx$
32
33
 
33
34
  const entity: ModelEntity = getModelPath(model, `main.${KIT}.entity`)
34
- const feature = getModelPath(model, `main.${KIT}.feature`)
35
+ // Gated by the applicability tags, so this target never imports or
36
+ // registers a feature it has no source for. One rule, one place:
37
+ // helpers/applicability.
38
+ const feature = targetFeatures(model, target)
35
39
 
36
40
  Package({ target })
37
41
 
@@ -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
 
@@ -35,7 +36,10 @@ const Config = cmp(async function Config(props: any) {
35
36
  const model: Model = ctx$.model
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
 
@@ -5,6 +5,7 @@ import {
5
5
  cmp, each,
6
6
  File, Content, Copy, Folder, Fragment,
7
7
  entityClassName,
8
+ targetFeatures,
8
9
  } from '@voxgig/sdkgen'
9
10
 
10
11
 
@@ -35,7 +36,10 @@ const Main = cmp(async function Main(props: any) {
35
36
  const { model } = props.ctx$
36
37
 
37
38
  const entity: ModelEntity = 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
  // The rust crate identifier (RUSTCRATE placeholder), e.g. solar_sdk —
41
45
  // used in every `use <crate>::...` path in the test templates.
@@ -6,6 +6,7 @@ import {
6
6
  each,
7
7
  isAuthActive,
8
8
  resolveAuthPrefix,
9
+ targetFeatures,
9
10
  } from '@voxgig/sdkgen'
10
11
 
11
12
 
@@ -35,7 +36,10 @@ const Config = cmp(async function Config(props: any) {
35
36
  const scalapackage = scalaPackage(model)
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, Content, Copy, Folder, Fragment,
7
+ targetFeatures,
7
8
  } from '@voxgig/sdkgen'
8
9
 
9
10
 
@@ -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
  // The Scala package root for every runtime piece (like GOMODULE for go).
39
43
  const scalapackage = scalaPackage(model)
@@ -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
 
@@ -26,7 +27,10 @@ const Config = cmp(async function Config(props: any) {
26
27
 
27
28
  const model: Model = ctx$.model
28
29
 
29
- const feature = getModelPath(model, `main.${KIT}.feature`)
30
+ // Gated by the applicability tags, so this target never imports or
31
+ // registers a feature it has no source for. One rule, one place:
32
+ // helpers/applicability.
33
+ const feature = targetFeatures(model, target)
30
34
 
31
35
  // The config as data, built by the shared helper so every target embeds
32
36
  // the same model by construction. Passing target.name opts this target
@@ -18,6 +18,7 @@ import {
18
18
  isHttpBasicAuth,
19
19
  resolveAuthPrefix,
20
20
  serverVariables,
21
+ targetFeatures,
21
22
  } from '@voxgig/sdkgen'
22
23
 
23
24
 
@@ -41,7 +42,10 @@ const Config = cmp(async function Config(props: any) {
41
42
  const model: Model = ctx$.model
42
43
 
43
44
  const entity = getModelPath(model, `main.${KIT}.entity`)
44
- const feature = getModelPath(model, `main.${KIT}.feature`)
45
+ // Gated by the applicability tags, so this target never imports or
46
+ // registers a feature it has no source for. One rule, one place:
47
+ // helpers/applicability.
48
+ const feature = targetFeatures(model, target)
45
49
 
46
50
  const ff = Path.normalize(__dirname + '/../../../src/cmp/ts/fragment/')
47
51
 
@@ -5,6 +5,7 @@ import {
5
5
  cmp, each, names, cmap,
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
7
  entityClassName, entityCollection, srcFeatureExcludes, stationLibrary,
8
+ targetFeatures,
8
9
  } from '@voxgig/sdkgen'
9
10
 
10
11
 
@@ -35,7 +36,14 @@ const Main = cmp(async function Main(props: any) {
35
36
  const { model } = props.ctx$
36
37
 
37
38
  const entity: ModelEntity = 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)
43
+
44
+ // Does the secrets feature apply here and is it switched on? Both, since
45
+ // targetFeatures already dropped it for a target with no sekreto port.
46
+ const secrets = null != feature.secrets
39
47
 
40
48
  Package({ target })
41
49
 
@@ -74,6 +82,54 @@ const Main = cmp(async function Main(props: any) {
74
82
  replace: {
75
83
  ...props.ctx$.stdrep,
76
84
 
85
+ // SECRETS. All five slots are emitted only when the secrets
86
+ // feature applies to this target AND the model activates it.
87
+ // An unconditional edit here would land in every generated SDK
88
+ // and break the inactive-output gate: a model without the
89
+ // feature must generate byte-identically to pre-migration.
90
+ //
91
+ // `feature` is already gated by targetFeatures, so a target
92
+ // that does not provide 'sekreto' never reaches these.
93
+ '// #SecretsImport': () => secrets ?
94
+ Line(`import * as sekreto from './feature/secrets/sekreto'`) : undefined,
95
+
96
+ '// #SecretsField': ({ indent }: any) => secrets ?
97
+ Line({ indent }, '_secrets?: any') : undefined,
98
+
99
+ // The LIVE instance, not a clone: sekreto holds provider and
100
+ // cache state, so a clone would resolve into a copy that
101
+ // prepareAuth never sees.
102
+ '// #SecretsAccessor': ({ indent }: any) => secrets ?
103
+ Content({ indent }, `
104
+ secrets() {
105
+ return this._secrets && this._secrets.sekreto()
106
+ }
107
+ `) : undefined,
108
+
109
+ // prepare() bypasses the feature hook pipeline, so the PreSpec
110
+ // hook that resolves the secret for entity ops never runs on
111
+ // this path and the resolve has to be explicit.
112
+ //
113
+ // It RETURNS the Error rather than rejecting: _rawRequest
114
+ // awaits prepare() outside its try, and direct()/graphql() are
115
+ // documented to return a value or an Error, never reject. The
116
+ // prototype rejected here, which would have turned a broken
117
+ // vault into an unhandled rejection on the direct path.
118
+ '// #SecretsResolve': ({ indent }: any) => secrets ?
119
+ Content({ indent }, `
120
+ if (null != this._secrets) {
121
+ try {
122
+ await this._secrets.resolve()
123
+ }
124
+ catch (err: any) {
125
+ return err instanceof Error ? err : new Error(String(err))
126
+ }
127
+ }
128
+ `) : undefined,
129
+
130
+ '// #SecretsExport': ({ indent }: any) => secrets ?
131
+ Line({ indent }, 'sekreto,') : undefined,
132
+
77
133
  '#BuildFeatures': ({ indent }: any) => {
78
134
  List({ item: feature, line: false }, ({ item }: any) =>
79
135
  Line({ indent },
@@ -13,6 +13,7 @@ import {
13
13
  PUBLISHER_URL,
14
14
  packageVersion,
15
15
  authorInfo,
16
+ targetFeatures,
16
17
  } from '@voxgig/sdkgen'
17
18
 
18
19
 
@@ -36,7 +37,9 @@ const Package = cmp(async function Package(props: any) {
36
37
  // constant here did.
37
38
  const author = authorInfo(model, target.name)
38
39
 
39
- const feature = getModelPath(model, `main.${KIT}.feature`)
40
+ // Gated by applicability: a feature that does not apply to this
41
+ // target must not inject its deps into the generated manifest.
42
+ const feature = targetFeatures(model, target)
40
43
 
41
44
  const only = (kind: string, deps: any) =>
42
45
  omap(deps, ([k, v]: any) => [v.active && kind === v.kind ? k : undefined, v.version])
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, each, Content, isAuthActive, isHttpBasicAuth } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, isAuthActive, isHttpBasicAuth, serverVariables } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -61,12 +61,24 @@ const ReadmeModel = cmp(function ReadmeModel(props: any) {
61
61
  ? '| `secret` | `string` | API secret for authentication. |\n'
62
62
  : ''
63
63
 
64
+ // Server variables are not one option among many: without them the
65
+ // constructor THROWS, because the base URL is a template. Documented
66
+ // first for that reason, and named individually so a reader knows what
67
+ // to supply without going back to the spec.
68
+ const svars = serverVariables(model)
69
+ const serverOptionType = 0 === svars.length ? '' :
70
+ `\n server?: { ${svars.map((v: any) => `${v.name}: string`).join(', ')} }`
71
+ const serverOptionRow = 0 === svars.length ? '' :
72
+ '| `server` | `object` | **Required.** Values for the server-URL variables: ' +
73
+ svars.map((v: any) => '`' + v.name + '`').join(', ') +
74
+ '. The API base URL is a template over them. |\n'
75
+
64
76
  Content(`### ${model.const.Name}SDK
65
77
 
66
78
  #### Constructor
67
79
 
68
80
  \`\`\`ts
69
- new ${model.const.Name}SDK(options?: {${apikeyOptionType}${secretOptionType}
81
+ new ${model.const.Name}SDK(options?: {${apikeyOptionType}${secretOptionType}${serverOptionType}
70
82
  base?: string
71
83
  prefix?: string
72
84
  suffix?: string
@@ -77,7 +89,7 @@ new ${model.const.Name}SDK(options?: {${apikeyOptionType}${secretOptionType}
77
89
 
78
90
  | Option | Type | Description |
79
91
  | --- | --- | --- |
80
- ${apikeyOptionRow}${secretOptionRow}| \`base\` | \`string\` | Base URL of the API server. |
92
+ ${serverOptionRow}${apikeyOptionRow}${secretOptionRow}| \`base\` | \`string\` | Base URL of the API server. |
81
93
  | \`prefix\` | \`string\` | URL path prefix prepended to all requests. |
82
94
  | \`suffix\` | \`string\` | URL path suffix appended to all requests. |
83
95
  | \`feature\` | \`object\` | Feature activation flags (e.g. \`{ test: { active: true } }\`). |
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, each, Content, isAuthActive, isHttpBasicAuth, packageName, envName, opRequestShape, entityIdField, entityDataIdField, entityOps, safeVarName, exampleVarName, jsKey, matchArg, idLiteral } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, isAuthActive, isHttpBasicAuth, packageName, envName, serverVariables, opRequestShape, entityIdField, entityDataIdField, entityOps, safeVarName, exampleVarName, jsKey, matchArg, idLiteral } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -40,10 +40,23 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
40
40
  !it.optional && it.name !== entityIdField(e))
41
41
  ) as any
42
42
 
43
- const ctor = isAuthActive(model)
44
- ? `new ${model.const.Name}SDK({\n apikey: process.env.${envName(model)}_APIKEY,${
45
- isHttpBasicAuth(model) ? `\n secret: process.env.${envName(model)}_SECRET,` : ''}\n})`
46
- : `new ${model.const.Name}SDK()`
43
+ // Server variables (a templated server URL) are REQUIRED at construction
44
+ // makeOptions refuses rather than request a URL with a literal
45
+ // `{account_id}` in it — so a quickstart that omits them is a quickstart
46
+ // that throws on its first line.
47
+ const svarLines = serverVariables(model)
48
+ .map((v: any) => `\n ${v.name}: '<${v.name}>',`).join('')
49
+ const serverField = '' === svarLines ? '' :
50
+ `\n // Required: this API's server URL is templated on these.\n server: {${svarLines}\n },`
51
+
52
+ const ctorFields = (isAuthActive(model)
53
+ ? `\n apikey: process.env.${envName(model)}_APIKEY,${
54
+ isHttpBasicAuth(model) ? `\n secret: process.env.${envName(model)}_SECRET,` : ''}`
55
+ : '') + serverField
56
+
57
+ const ctor = '' === ctorFields
58
+ ? `new ${model.const.Name}SDK()`
59
+ : `new ${model.const.Name}SDK({${ctorFields}\n})`
47
60
 
48
61
  Content(`### 1. Create a client
49
62
 
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, isHttpBasicAuth, packageName, envName, entityIdField, entityOps, opRequestShape, safeVarName, exampleVarName, jsKey, matchArg, idLiteral } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, isHttpBasicAuth, packageName, envName, entityIdField, entityOps, opRequestShape, safeVarName, exampleVarName, jsKey, matchArg, idLiteral , serverVariables} from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -28,10 +28,24 @@ const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
28
28
  const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
29
29
 
30
30
  const authActive = isAuthActive(model)
31
- const ctor = authActive
32
- ? `new ${model.const.Name}SDK({\n apikey: process.env.${envName(model)}_APIKEY,${
33
- isHttpBasicAuth(model) ? `\n secret: process.env.${envName(model)}_SECRET,` : ''}\n})`
34
- : `new ${model.const.Name}SDK()`
31
+
32
+ // Server variables (a templated server URL) are REQUIRED at construction
33
+ // - makeOptions refuses rather than request a URL with a literal
34
+ // {account_id} in it - so a quickstart that omits them is a quickstart
35
+ // that throws on its first line.
36
+ const svarLines = serverVariables(model)
37
+ .map((v: any) => `\n ${v.name}: '<${v.name}>',`).join('')
38
+ const serverField = '' === svarLines ? '' :
39
+ `\n // Required: this API's server URL is templated on these.\n server: {${svarLines}\n },`
40
+
41
+ const ctorFields = (authActive
42
+ ? `\n apikey: process.env.${envName(model)}_APIKEY,${
43
+ isHttpBasicAuth(model) ? `\n secret: process.env.${envName(model)}_SECRET,` : ''}`
44
+ : '') + serverField
45
+
46
+ const ctor = '' === ctorFields
47
+ ? `new ${model.const.Name}SDK()`
48
+ : `new ${model.const.Name}SDK({${ctorFields}\n})`
35
49
 
36
50
  Content(`\`\`\`ts
37
51
  import { ${model.const.Name}SDK } from '${packageName(model, target.name)}'
@@ -17,6 +17,8 @@ import {
17
17
  cmp,
18
18
  snakify,
19
19
  isAuthActive,
20
+ serverVarEnv,
21
+ serverVariables,
20
22
  isHttpBasicAuth,
21
23
  jsProp,
22
24
  jsOptProp, envName, envToken, liveStrict
@@ -47,7 +49,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
47
49
  const authActive = isAuthActive(model)
48
50
  const authBasic = authActive && isHttpBasicAuth(model)
49
51
  const apikeyEnvEntry = authActive
50
- ? `\n '${PROJECTNAME}_APIKEY': 'NONE',${authBasic ? `\n '${PROJECTNAME}_SECRET': 'NONE',` : ''}`
52
+ ? `\n '${PROJECTNAME}_APIKEY': '',${authBasic ? `\n '${PROJECTNAME}_SECRET': 'NONE',` : ''}`
51
53
  : ''
52
54
  const apikeyLiveField = authActive
53
55
  ? `
@@ -55,6 +57,19 @@ const TestDirect = cmp(function TestDirect(props: any) {
55
57
  secret: env.${PROJECTNAME}_SECRET,` : ''}`
56
58
  : ''
57
59
 
60
+ // A templated server URL (OpenAPI server variables) makes a LIVE client
61
+ // impossible to construct without values: makeOptions raises rather than
62
+ // request a URL with a literal `{account_id}` in it. Taken from the
63
+ // environment, the same way the apikey is.
64
+ const svars = serverVariables(model)
65
+ const serverEnvEntry = svars
66
+ .map((v: any) => `\n '${serverVarEnv(PROJECTNAME, v.name)}': ${JSON.stringify(v.dflt)},`).join('')
67
+ const serverLiveField = 0 === svars.length ? '' : `
68
+ server: {${svars
69
+ .map((v: any) => `
70
+ ${v.name}: env.${serverVarEnv(PROJECTNAME, v.name)},`).join('')}
71
+ },`
72
+
58
73
  const opnames = Object.keys(entity.op || {})
59
74
  const hasLoad = opnames.includes('load')
60
75
  const hasList = opnames.includes('list')
@@ -86,14 +101,17 @@ function directSetup(mockres?: any) {
86
101
 
87
102
  const env = envOverride({
88
103
  '${entidEnvVar}': {},
89
- '${PROJECTNAME}_TEST_LIVE': 'FALSE',${apikeyEnvEntry}
104
+ '${PROJECTNAME}_TEST_LIVE': 'FALSE',${apikeyEnvEntry}${serverEnvEntry}
90
105
  })
91
106
 
92
107
  const live = 'TRUE' === env.${PROJECTNAME}_TEST_LIVE
93
108
 
94
109
  if (live) {
95
- const client = new ${nom(model.const, 'Name')}SDK({${apikeyLiveField}
96
- })
110
+ // Merged so the generated fields win: sdk-test-control.json's
111
+ // test.client.options adds to the live client, it does not redirect it.
112
+ const client = new ${nom(model.const, 'Name')}SDK(
113
+ Object.assign({}, liveClientOptions(), {${apikeyLiveField}${serverLiveField}
114
+ }))
97
115
 
98
116
  let idmap: any = env['${entidEnvVar}']
99
117
  if ('string' === typeof idmap && idmap.startsWith('{')) {
@@ -210,7 +228,21 @@ function generateDirectGraphql(
210
228
  ${varAsserts}`
211
229
 
212
230
  const checks = strict ?
213
- offlineChecks.replace(/^ {6}/gm, ' ').replace(/^ {4}$/gm, '') :
231
+ ` if (setup.live) {
232
+ // STRICT live mode: a non-2xx is a real failure - this project owns
233
+ // the server it points at, so there is nothing to be lenient about.
234
+ //
235
+ // What is NOT asserted here is the MOCK's own fixtures. \`direct01\`
236
+ // is a scripted id and \`calls\` records the mock transport; neither
237
+ // exists on a live run, so asserting them made strict mode mean
238
+ // "compare the live server against the mock's script" - a suite that
239
+ // could not pass against any real API, including this project's own.
240
+ assert(result.ok === true,
241
+ 'live request failed: ' + result.status + ' ' + JSON.stringify(result.data))
242
+ assert(result.status >= 200 && result.status < 300)
243
+ assert(null != result.data)
244
+ } else {
245
+ ${offlineChecks} }` :
214
246
  ` if (setup.live) {
215
247
  // Live mode is lenient: synthetic ids frequently fail server-side
216
248
  // validation. Skip rather than fail when the call doesn't come back
@@ -429,7 +461,21 @@ ${loadParams.map((p: any, i: number) => ` ${jsProp('params', p.name)} = 'di
429
461
  ${paramAsserts}`
430
462
 
431
463
  const loadChecks = strict ?
432
- offlineChecks.replace(/^ {6}/gm, ' ').replace(/^ {4}$/gm, '') :
464
+ ` if (setup.live) {
465
+ // STRICT live mode: a non-2xx is a real failure - this project owns
466
+ // the server it points at, so there is nothing to be lenient about.
467
+ //
468
+ // What is NOT asserted here is the MOCK's own fixtures. \`direct01\`
469
+ // is a scripted id and \`calls\` records the mock transport; neither
470
+ // exists on a live run, so asserting them made strict mode mean
471
+ // "compare the live server against the mock's script" - a suite that
472
+ // could not pass against any real API, including this project's own.
473
+ assert(result.ok === true,
474
+ 'live request failed: ' + result.status + ' ' + JSON.stringify(result.data))
475
+ assert(result.status >= 200 && result.status < 300)
476
+ assert(null != result.data)
477
+ } else {
478
+ ${offlineChecks} }` :
433
479
  ` if (setup.live) {
434
480
  // Live mode is lenient: synthetic IDs frequently 4xx. Skip rather
435
481
  // than fail when the load endpoint isn't reachable with the IDs we
@@ -543,7 +589,21 @@ ${mockLines}
543
589
  ${paramAsserts}`
544
590
 
545
591
  const listChecks = strict ?
546
- offlineChecks.replace(/^ {6}/gm, ' ').replace(/^ {4}$/gm, '') :
592
+ ` if (setup.live) {
593
+ // STRICT live mode: a non-2xx is a real failure - this project owns
594
+ // the server it points at, so there is nothing to be lenient about.
595
+ //
596
+ // What is NOT asserted here is the MOCK's own fixtures. \`direct01\`
597
+ // is a scripted id and \`calls\` records the mock transport; neither
598
+ // exists on a live run, so asserting them made strict mode mean
599
+ // "compare the live server against the mock's script" - a suite that
600
+ // could not pass against any real API, including this project's own.
601
+ assert(result.ok === true,
602
+ 'live request failed: ' + result.status + ' ' + JSON.stringify(result.data))
603
+ assert(result.status >= 200 && result.status < 300)
604
+ assert(null != result.data)
605
+ } else {
606
+ ${offlineChecks} }` :
547
607
  ` if (setup.live) {
548
608
  // Live mode is lenient: synthetic IDs frequently 4xx and the list-
549
609
  // response shape varies wildly across public APIs. Skip rather than