@voxgig/sdkgen 4.2.4 → 4.2.6

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 (75) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/AgentGuideTop.js +15 -7
  3. package/dist/cmp/AgentGuideTop.js.map +1 -1
  4. package/dist/cmp/ReadmeStation.js +88 -12
  5. package/dist/cmp/ReadmeStation.js.map +1 -1
  6. package/dist/helpers/station.d.ts +3 -0
  7. package/dist/helpers/station.js +67 -0
  8. package/dist/helpers/station.js.map +1 -0
  9. package/dist/sdkgen.d.ts +2 -1
  10. package/dist/sdkgen.js +41 -15
  11. package/dist/sdkgen.js.map +1 -1
  12. package/dist/tsconfig.tsbuildinfo +1 -1
  13. package/dist/types.d.ts +2 -0
  14. package/dist/utility.js +15 -1
  15. package/dist/utility.js.map +1 -1
  16. package/model/sdkgen.aon +20 -0
  17. package/package.json +1 -1
  18. package/project/.sdk/model/feature/audit.aon +1 -0
  19. package/project/.sdk/model/feature/cache.aon +1 -0
  20. package/project/.sdk/model/feature/clienttrack.aon +1 -0
  21. package/project/.sdk/model/feature/debug.aon +1 -0
  22. package/project/.sdk/model/feature/idempotency.aon +1 -0
  23. package/project/.sdk/model/feature/log.aon +1 -0
  24. package/project/.sdk/model/feature/metrics.aon +1 -0
  25. package/project/.sdk/model/feature/netsim.aon +1 -0
  26. package/project/.sdk/model/feature/paging.aon +1 -0
  27. package/project/.sdk/model/feature/proxy.aon +1 -0
  28. package/project/.sdk/model/feature/ratelimit.aon +1 -0
  29. package/project/.sdk/model/feature/rbac.aon +1 -0
  30. package/project/.sdk/model/feature/retry.aon +1 -0
  31. package/project/.sdk/model/feature/streaming.aon +1 -0
  32. package/project/.sdk/model/feature/telemetry.aon +1 -0
  33. package/project/.sdk/model/feature/test.aon +1 -0
  34. package/project/.sdk/model/feature/timeout.aon +1 -0
  35. package/project/.sdk/src/cmp/c/Config_c.ts +13 -10
  36. package/project/.sdk/src/cmp/clojure/Config_clojure.ts +4 -1
  37. package/project/.sdk/src/cmp/dart/Config_dart.ts +4 -1
  38. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +4 -1
  39. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +21 -2
  40. package/project/.sdk/src/cmp/go/Config_go.ts +4 -1
  41. package/project/.sdk/src/cmp/go/utility_go.ts +20 -7
  42. package/project/.sdk/src/cmp/java/Config_java.ts +11 -8
  43. package/project/.sdk/src/cmp/js/Config_js.ts +4 -1
  44. package/project/.sdk/src/cmp/js/Main_js.ts +20 -1
  45. package/project/.sdk/src/cmp/js/fragment/MainStation.fragment.js +36 -0
  46. package/project/.sdk/src/cmp/js/utility_js.ts +20 -7
  47. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +11 -7
  48. package/project/.sdk/src/cmp/lua/Config_lua.ts +4 -1
  49. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +3 -1
  50. package/project/.sdk/src/cmp/php/Config_php.ts +4 -1
  51. package/project/.sdk/src/cmp/php/ReadmeExamplesTest_php.ts +144 -12
  52. package/project/.sdk/src/cmp/py/Config_py.ts +4 -1
  53. package/project/.sdk/src/cmp/py/ReadmeExamplesTest_py.ts +88 -3
  54. package/project/.sdk/src/cmp/rb/Config_rb.ts +4 -1
  55. package/project/.sdk/src/cmp/rb/ReadmeExamplesTest_rb.ts +83 -10
  56. package/project/.sdk/src/cmp/swift/EntityTypes_swift.ts +2 -2
  57. package/project/.sdk/src/cmp/swift/Entity_swift.ts +3 -1
  58. package/project/.sdk/src/cmp/swift/Main_swift.ts +37 -2
  59. package/project/.sdk/src/cmp/swift/Package_swift.ts +9 -6
  60. package/project/.sdk/src/cmp/swift/ReadmeExamplesTest_swift.ts +1 -1
  61. package/project/.sdk/src/cmp/swift/ReadmeExplanation_swift.ts +5 -3
  62. package/project/.sdk/src/cmp/swift/Test_swift.ts +3 -1
  63. package/project/.sdk/src/cmp/swift/utility_swift.ts +16 -0
  64. package/project/.sdk/src/cmp/ts/Config_ts.ts +4 -1
  65. package/project/.sdk/src/cmp/ts/Main_ts.ts +20 -1
  66. package/project/.sdk/src/cmp/ts/fragment/MainStation.fragment.ts +36 -0
  67. package/project/.sdk/src/cmp/ts/utility_ts.ts +20 -7
  68. package/project/.sdk/src/cmp/zig/Config_zig.ts +4 -2
  69. package/project/sdkgen-package.json +1 -1
  70. package/src/cmp/AgentGuideTop.ts +15 -7
  71. package/src/cmp/ReadmeStation.ts +93 -13
  72. package/src/helpers/station.ts +80 -0
  73. package/src/sdkgen.ts +52 -15
  74. package/src/types.ts +6 -0
  75. package/src/utility.ts +15 -1
package/model/sdkgen.aon CHANGED
@@ -270,6 +270,15 @@ main: kit: target: &: {
270
270
  output: {
271
271
  path: *'' | string
272
272
 
273
+ # Whether generation may create a destination folder that does not yet
274
+ # exist. true preserves the ordinary behaviour: jostraca creates it and
275
+ # any missing parents. false keeps the target active but skips its
276
+ # out-of-tree generation pass until the destination folder exists — for
277
+ # example when a separate provider fleet is only checked out on some
278
+ # machines. An existing destination still goes through every ownership
279
+ # and overwrite-safety check below.
280
+ create: *true | boolean
281
+
273
282
  # Write into a destination that already holds content this generator did
274
283
  # not write.
275
284
  #
@@ -411,6 +420,16 @@ main: kit: feature: &: {
411
420
  # TODO: version '0.0.1' | semver # where semver is a string format type as per semver.org
412
421
  version: *'0.0.1' | string
413
422
 
423
+ # Transport role (station design §8.4): how the feature relates to the
424
+ # transport slot. 'base' REPLACES ctx.utility.fetcher outright (only the
425
+ # `test` feature); 'wrap' wraps it in init() (netsim, retry, cache,
426
+ # ratelimit, timeout, proxy - and station's own feature); 'none' is
427
+ # hook-only. Carried into the embedded config by configDefinition so
428
+ # station can validate the resolved feature order. Declared, never
429
+ # inferred: the obvious signal, an empty `hook: {}`, is wrong for a
430
+ # feature that both wraps AND dispatches hooks (station does).
431
+ transport: *'none' | 'base' | 'wrap'
432
+
414
433
  hook: &: {
415
434
  active: *false | boolean
416
435
  await: *false | boolean
@@ -480,6 +499,7 @@ main: kit: docs: &: {
480
499
  output: {
481
500
  path: *'' | string
482
501
  repo: *'' | string
502
+ create: *true | boolean
483
503
  adopt: *false | boolean
484
504
  sdkrel: *'' | string
485
505
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/sdkgen",
3
- "version": "4.2.4",
3
+ "version": "4.2.6",
4
4
  "main": "dist/sdkgen.js",
5
5
  "type": "commonjs",
6
6
  "engines": {
@@ -6,6 +6,7 @@ main: kit: feature: audit: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: cache: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'wrap'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: clienttrack: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: debug: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: idempotency: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: log: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
 
11
12
  config: {
@@ -6,6 +6,7 @@ main: kit: feature: metrics: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: netsim: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'wrap'
9
10
 
10
11
  config: {
11
12
  # Test-support feature: enable per test via
@@ -6,6 +6,7 @@ main: kit: feature: paging: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: proxy: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'wrap'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: ratelimit: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'wrap'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: rbac: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: retry: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'wrap'
9
10
 
10
11
  config: {
11
12
  # Opt-in: enable per SDK via options.feature.retry.active = true
@@ -6,6 +6,7 @@ main: kit: feature: streaming: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: telemetry: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'none'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -6,6 +6,7 @@ main: kit: feature: test: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'base'
9
10
 
10
11
  config: {
11
12
  # Test mode is not active by default, unlike most features
@@ -6,6 +6,7 @@ main: kit: feature: timeout: {
6
6
  version: '0.0.1'
7
7
  active: true
8
8
  base: 'BASE'
9
+ transport: 'wrap'
9
10
 
10
11
  config: {
11
12
  options: {
@@ -46,9 +46,21 @@ const Config = cmp(async function Config(props: any) {
46
46
  let baseUrl = ''
47
47
  try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
48
48
 
49
+ // The same config as an OBJECT, built by the shared helper so this target's
50
+ // literal and the data that replaces it above the threshold are the same
51
+ // config by construction. The JSON is what the threshold is measured on -
52
+ // emitted source size varies by language, the model does not. Passing
53
+ // target.name opts this target into the main slug/version/target identity
54
+ // fields (read by station's descriptor - see configDefinition).
55
+ const { def: configDef, json: configJson } = configDefinition(model, target.name)
56
+ const asData = isConfigData(configJson, configReprSetting(model))
57
+
58
+ // The feature block comes from configDefinition's def, not from f.config,
59
+ // so the literal carries each feature's `transport` role (station design
60
+ // §8.4) beside its options and cannot drift from the data rep.
49
61
  const featureConfig: any = {}
50
62
  each(feature, (f: any) => {
51
- featureConfig[f.name] = f.config || {}
63
+ featureConfig[f.name] = configDef.feature[f.name] || {}
52
64
  })
53
65
 
54
66
  const entityOptions: any = {}
@@ -73,15 +85,6 @@ const Config = cmp(async function Config(props: any) {
73
85
  relations: n.relations,
74
86
  }, true), a), {})
75
87
 
76
- // The same config as an OBJECT, built by the shared helper so this target's
77
- // literal and the data that replaces it above the threshold are the same
78
- // config by construction. The JSON is what the threshold is measured on -
79
- // emitted source size varies by language, the model does not. Passing
80
- // target.name opts this target into the main slug/version/target identity
81
- // fields (read by station's descriptor - see configDefinition).
82
- const { def: configDef, json: configJson } = configDefinition(model, target.name)
83
- const asData = isConfigData(configJson, configReprSetting(model))
84
-
85
88
  const config = {
86
89
  // main from configDefinition's def, not re-derived here, so the literal
87
90
  // rep and the data rep cannot disagree on identity (the Config_ts
@@ -32,6 +32,7 @@ import {
32
32
  // N-feature-safe: an empty feature/entity map renders as (vs/jm).
33
33
  const Config = cmp(async function Config(props: any) {
34
34
  const ctx$ = props.ctx$
35
+ const target = props.target
35
36
 
36
37
  const model: Model = ctx$.model
37
38
 
@@ -39,7 +40,9 @@ const Config = cmp(async function Config(props: any) {
39
40
  // representations render from the same `def`, so they cannot describe
40
41
  // different configs - and this target picks up `options.server` (the OpenAPI
41
42
  // server-variable defaults), which the hand-rolled build here omitted.
42
- const { def: config, json: configJson } = configDefinition(model)
43
+ // Passing target.name opts this target into the main slug/version/target
44
+ // identity fields (read by station's descriptor - see configDefinition).
45
+ const { def: config, json: configJson } = configDefinition(model, target.name)
43
46
  const asData = isConfigData(configJson, configReprSetting(model))
44
47
 
45
48
  File({ name: 'config.clj' }, () => {
@@ -143,8 +143,11 @@ const Config = cmp(async function Config(props: any) {
143
143
  Line(` '${f.name}': () => ${nom(f, 'Name')}Feature(),`)
144
144
  }),
145
145
 
146
+ // Rendered from configDefinition's def, not from f.config, so the
147
+ // literal carries the feature's `transport` role (station design
148
+ // §8.4) beside its options and cannot drift from the data rep.
146
149
  '// #FeatureConfigs': () => each(feature, (f: any) => {
147
- Line(` '${f.name}': ${dartValue(f.config, 2)},`)
150
+ Line(` '${f.name}': ${dartValue(configDef.feature[f.name], 2)},`)
148
151
  }),
149
152
 
150
153
 
@@ -174,7 +174,10 @@ defmodule ${Name}.Config do
174
174
  `)
175
175
 
176
176
  each(feature, (f: any) => {
177
- Content(` ${elixirString(f.name)} => ${formatElixir(f.config || {}, 4)},
177
+ // From configDefinition's def, not f.config, so the literal carries
178
+ // the feature's `transport` role (station design §8.4) beside its
179
+ // options and cannot drift from the data rep.
180
+ Content(` ${elixirString(f.name)} => ${formatElixir(configDef.feature[f.name] || {}, 4)},
178
181
  `)
179
182
  })
180
183
 
@@ -48,15 +48,34 @@ const Main = cmp(async function Main(props: any) {
48
48
  // Generate the documented typespec module (lib/<app>_types.ex).
49
49
  EntityTypes({ target })
50
50
 
51
- // Copy tm/elixir verbatim (with ProjectName/projectname substitution).
51
+ // Copy tm/elixir (with ProjectName/projectname substitution).
52
+ //
53
+ // THE RUNTIME DIRECTORY HAS TO CARRY THE APP NAME. Copy substitutes file
54
+ // CONTENTS, never path components, so a blanket copy shipped the runtime as
55
+ // lib/projectname/ in every generated SDK. Nothing broke — Elixir resolves
56
+ // modules by their `defmodule`, not by path, and those were substituted
57
+ // correctly — so it compiled, tested green, and published looking wrong.
58
+ //
59
+ // Copy's `to` prop names the destination, so lib/projectname is copied
60
+ // explicitly under the app's own name and excluded from the blanket copy.
52
61
  Copy({
53
62
  from: 'tm/' + target.name,
54
- exclude: [/src\//],
63
+ exclude: [/src\//, /lib\/projectname\//],
55
64
  replace: {
56
65
  ...stdrep,
57
66
  }
58
67
  })
59
68
 
69
+ Folder({ name: 'lib' }, () => {
70
+ Copy({
71
+ from: 'tm/' + target.name + '/lib/projectname',
72
+ to: model.const.name,
73
+ replace: {
74
+ ...stdrep,
75
+ }
76
+ })
77
+ })
78
+
60
79
  Folder({ name: 'lib' }, () => {
61
80
 
62
81
  // Main SDK module (entity factory fns injected at the slot).
@@ -173,7 +173,10 @@ func MakeConfig() map[string]any {
173
173
  `)
174
174
 
175
175
  each(feature, (f: any) => {
176
- const fconfig = f.config || {}
176
+ // From configDefinition's def, not f.config, so the literal carries
177
+ // the feature's `transport` role (station design §8.4) beside its
178
+ // options and cannot drift from the data rep.
179
+ const fconfig = configDef.feature[f.name] || {}
177
180
  Content(` "${f.name}": ${formatGoMap(fconfig, 3)},
178
181
  `)
179
182
  })
@@ -7,6 +7,7 @@ import {
7
7
  canonKey,
8
8
  canonScalarKey,
9
9
  each,
10
+ opParams,
10
11
  exampleVarName,
11
12
  names,
12
13
  } from '@voxgig/sdkgen'
@@ -36,13 +37,25 @@ function projectPath(suffix?: string): string {
36
37
  // has no params and the request shape mirrors the entity fields). Returns
37
38
  // undefined when neither is present.
38
39
  function paramCanonType(entity: any, op: any, paramName: string): unknown {
39
- const points = op && op.points ? each(op.points) : []
40
- for (const pt of points as any[]) {
41
- const params = pt && pt.args && pt.args.params ? each(pt.args.params) : []
42
- const found = (params as any[]).find((p: any) => p && p.name === paramName)
43
- if (found) {
44
- return found.type
45
- }
40
+ // opParams, NOT a raw walk of op.points.
41
+ //
42
+ // opParams drops points flagged with select['$action'] and merges what is
43
+ // left; the typed-model generator reaches the op's params through it, so
44
+ // anything else is a DIFFERENT set of params wearing the same name.
45
+ //
46
+ // A raw walk returned the first match on ANY point, action points included.
47
+ // github's `action` entity has a field `owner` (`$OBJECT`, "A GitHub user")
48
+ // and seven create points whose path carries `{owner}` (a string) — so the
49
+ // type said Record<string, any> and the doc example said 'example_owner',
50
+ // and ts/README.md stopped compiling. Same for workflow_id: `number` in the
51
+ // type, quoted string in the example.
52
+ //
53
+ // Deriving from opParams is what actually makes good on the promise below —
54
+ // that the docs and the generated types cannot disagree.
55
+ const params = op ? each(opParams(op)) : []
56
+ const found = (params as any[]).find((p: any) => p && p.name === paramName)
57
+ if (found) {
58
+ return found.type
46
59
  }
47
60
  const field = (entity && entity.fields ? each(entity.fields) : [])
48
61
  .find((f: any) => f && f.name === paramName) as any
@@ -48,10 +48,19 @@ const Config = cmp(async function Config(props: any) {
48
48
  let baseUrl = ''
49
49
  try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
50
50
 
51
- // Assemble the config shape (mirrors Config_go's emitted map).
51
+ // Identity comes from configDefinition's def, not re-derived here, so
52
+ // this target cannot disagree with the shared emitter on main.slug /
53
+ // main.version / main.target (the three station descriptor fields,
54
+ // station design §4) — passing target.name is what opts this target in.
55
+ const { def: configDef } = configDefinition(model, target.name)
56
+
57
+ // Assemble the config shape (mirrors Config_go's emitted map). The
58
+ // feature block comes from configDefinition's def, not from f.config,
59
+ // so it carries each feature's `transport` role (station design §8.4)
60
+ // beside its options and cannot drift from the shared emitter.
52
61
  const featureConfig: Record<string, any> = {}
53
62
  each(feature, (f: any) => {
54
- featureConfig[f.name] = cleanModel(f.config || {})
63
+ featureConfig[f.name] = cleanModel(configDef.feature[f.name] || {})
55
64
  })
56
65
 
57
66
  const optionsEntity: Record<string, any> = {}
@@ -76,12 +85,6 @@ const Config = cmp(async function Config(props: any) {
76
85
  relations: n.relations,
77
86
  }, true), a), {})
78
87
 
79
- // Identity comes from configDefinition's def, not re-derived here, so
80
- // this target cannot disagree with the shared emitter on main.slug /
81
- // main.version / main.target (the three station descriptor fields,
82
- // station design §4) — passing target.name is what opts this target in.
83
- const { def: configDef } = configDefinition(model, target.name)
84
-
85
88
  const config = {
86
89
  main: configDef.main,
87
90
  feature: featureConfig,
@@ -142,8 +142,11 @@ const Config = cmp(async function Config(props: any) {
142
142
  Line(` ${f.name}: ${nom(f, 'Name')}Feature,`)
143
143
  }),
144
144
 
145
+ // Rendered from configDefinition's def, not from f.config, so the
146
+ // literal carries the feature's `transport` role (station design
147
+ // §8.4) beside its options and cannot drift from the data rep.
145
148
  '// #FeatureConfigs': () => each(feature, (f: any) => {
146
- Line(` ${f.name}: ${formatJson(f.config, { margin: 4 })},`)
149
+ Line(` ${f.name}: ${formatJson(configDef.feature[f.name], { margin: 4 })},`)
147
150
  }),
148
151
 
149
152
 
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, each, names, cmap,
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
- entityClassName, entityCollection, srcFeatureExcludes,
7
+ entityClassName, entityCollection, srcFeatureExcludes, stationLibrary,
8
8
  } from '@voxgig/sdkgen'
9
9
 
10
10
 
@@ -102,6 +102,25 @@ if (fres instanceof Promise) { await fres }
102
102
  MainEntity(entprops)
103
103
  })
104
104
  })
105
+
106
+ // Station self-registration (station design §6.2 path 1;
107
+ // station-declarative-config §11 item 2): emitted ONLY when the model
108
+ // carries an ACTIVE station feature (installed via
109
+ // `package add @voxgig/sdkgen-station`). The library package name
110
+ // comes from the feature model's own `deps.js` block — the same entry
111
+ // collectDeps flows into package.json — so the manifest dependency
112
+ // and the emitted require cannot disagree.
113
+ const stationPkg = stationLibrary(model, target.name)
114
+ if (null != stationPkg) {
115
+ Fragment({
116
+ from: Path.normalize(
117
+ __dirname + '/../../../src/cmp/js/fragment/MainStation.fragment.js'),
118
+ replace: {
119
+ ...props.ctx$.stdrep,
120
+ "'STATIONPKG'": JSON.stringify(stationPkg),
121
+ }
122
+ })
123
+ }
105
124
  })
106
125
 
107
126
  Config({ target })
@@ -0,0 +1,36 @@
1
+
2
+
3
+ // Station self-registration (voxgig/station design §6.2, path 1): linking
4
+ // this package is the whole bootstrap. When the station library is
5
+ // installed beside this SDK, the {construct, config} factory pair is
6
+ // registered under the api slug (config.main.slug, the descriptor slug) at
7
+ // module init, so `station.sdk('<name>')` needs no imports in application
8
+ // code. Registration is SOFT — the station library is a peer dependency of
9
+ // the station feature, and a project without it skips registration and
10
+ // gets exactly the SDK it always had. Only a genuine module-not-found is
11
+ // swallowed: a station that RESOLVES but throws while loading must
12
+ // propagate (a silently empty factory table has no visible cause), and so
13
+ // must station_factory_conflict — two different builds of one SDK in one
14
+ // process is not a thing to pick between quietly.
15
+ let stationInstalled = true
16
+ try { require.resolve('STATIONPKG') }
17
+ catch (err) {
18
+ // MODULE_NOT_FOUND is the absent case. Anything else - an invalid
19
+ // package config, a root entry the package does not export - is a
20
+ // real failure to load a station that IS installed, and swallowing
21
+ // it would leave an empty factory table with no visible cause.
22
+ if ('MODULE_NOT_FOUND' !== (err && err.code)) { throw err }
23
+ stationInstalled = false
24
+ }
25
+
26
+ if (stationInstalled) {
27
+ const { provide } = require('STATIONPKG')
28
+ // A station library predating the factory table has no provide() —
29
+ // there is nothing to register with, which is not this SDK's error.
30
+ if ('function' === typeof provide) {
31
+ provide(config.main.slug, {
32
+ construct: (options) => new ProjectNameSDK(options),
33
+ config,
34
+ })
35
+ }
36
+ }
@@ -6,6 +6,7 @@ import {
6
6
  canonKey,
7
7
  canonScalarKey,
8
8
  each,
9
+ opParams,
9
10
  } from '@voxgig/sdkgen'
10
11
 
11
12
  import {
@@ -28,13 +29,25 @@ import {
28
29
  // does. Falls back to the entity field of the same name (used when the op
29
30
  // has no params). Returns undefined when neither is present.
30
31
  function paramCanonType(entity: any, op: any, paramName: string): unknown {
31
- const points = op && op.points ? each(op.points) : []
32
- for (const pt of points as any[]) {
33
- const params = pt && pt.args && pt.args.params ? each(pt.args.params) : []
34
- const found = (params as any[]).find((p: any) => p && p.name === paramName)
35
- if (found) {
36
- return found.type
37
- }
32
+ // opParams, NOT a raw walk of op.points.
33
+ //
34
+ // opParams drops points flagged with select['$action'] and merges what is
35
+ // left; the typed-model generator reaches the op's params through it, so
36
+ // anything else is a DIFFERENT set of params wearing the same name.
37
+ //
38
+ // A raw walk returned the first match on ANY point, action points included.
39
+ // github's `action` entity has a field `owner` (`$OBJECT`, "A GitHub user")
40
+ // and seven create points whose path carries `{owner}` (a string) — so the
41
+ // type said Record<string, any> and the doc example said 'example_owner',
42
+ // and ts/README.md stopped compiling. Same for workflow_id: `number` in the
43
+ // type, quoted string in the example.
44
+ //
45
+ // Deriving from opParams is what actually makes good on the promise below —
46
+ // that the docs and the generated types cannot disagree.
47
+ const params = op ? each(opParams(op)) : []
48
+ const found = (params as any[]).find((p: any) => p && p.name === paramName)
49
+ if (found) {
50
+ return found.type
38
51
  }
39
52
  const field = (entity && entity.fields ? each(entity.fields) : [])
40
53
  .find((f: any) => f && f.name === paramName) as any
@@ -46,9 +46,19 @@ const Config = cmp(async function Config(props: any) {
46
46
  let baseUrl = ''
47
47
  try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
48
48
 
49
+ // Identity comes from configDefinition's def, not re-derived here, so
50
+ // this target cannot disagree with the shared emitter on main.slug /
51
+ // main.version / main.target (the three station descriptor fields,
52
+ // station design §4) — passing target.name is what opts this target in.
53
+ const { def: configDef } = configDefinition(model, target.name)
54
+
55
+ // The feature block comes from configDefinition's def, not from
56
+ // f.config, so it carries each feature's `transport` role (station
57
+ // design §8.4) beside its options and cannot drift from the shared
58
+ // emitter.
49
59
  const featureConfig: Record<string, any> = {}
50
60
  each(feature, (f: any) => {
51
- featureConfig[f.name] = cleanModel(f.config || {})
61
+ featureConfig[f.name] = cleanModel(configDef.feature[f.name] || {})
52
62
  })
53
63
 
54
64
  const optionsEntity: Record<string, any> = {}
@@ -73,12 +83,6 @@ const Config = cmp(async function Config(props: any) {
73
83
  relations: n.relations,
74
84
  }, true), a), {})
75
85
 
76
- // Identity comes from configDefinition's def, not re-derived here, so
77
- // this target cannot disagree with the shared emitter on main.slug /
78
- // main.version / main.target (the three station descriptor fields,
79
- // station design §4) — passing target.name is what opts this target in.
80
- const { def: configDef } = configDefinition(model, target.name)
81
-
82
86
  const config = {
83
87
  main: configDef.main,
84
88
  feature: featureConfig,
@@ -133,7 +133,10 @@ local function make_config()
133
133
  `)
134
134
 
135
135
  each(feature, (f: any) => {
136
- const fconfig = f.config || {}
136
+ // From configDefinition's def, not f.config, so the literal carries
137
+ // the feature's `transport` role (station design §8.4) beside its
138
+ // options and cannot drift from the data rep.
139
+ const fconfig = configDef.feature[f.name] || {}
137
140
  Content(` ["${f.name}"] = ${formatLuaTable(fconfig, 3)},
138
141
  `)
139
142
  })
@@ -40,7 +40,9 @@ const Config = cmp(async function Config(props: any) {
40
40
  // representations render from the same `def`, so they cannot describe
41
41
  // different configs - and this target picks up `options.server` (the OpenAPI
42
42
  // server-variable defaults), which the hand-rolled build here omitted.
43
- const { def: config, json: configJson } = configDefinition(model)
43
+ // Passing target.name opts this target into the main slug/version/target
44
+ // identity fields (read by station's descriptor - see configDefinition).
45
+ const { def: config, json: configJson } = configDefinition(model, target.name)
44
46
  const asData = isConfigData(configJson, configReprSetting(model))
45
47
 
46
48
  File({ name: 'sdk_config.' + target.ext }, () => {
@@ -197,7 +197,10 @@ class ${model.const.Name}Config
197
197
  `)
198
198
 
199
199
  each(feature, (f: any) => {
200
- const fconfig = f.config || {}
200
+ // From configDefinition's def, not f.config, so the literal carries
201
+ // the feature's `transport` role (station design §8.4) beside its
202
+ // options and cannot drift from the data rep.
203
+ const fconfig = configDef.feature[f.name] || {}
201
204
  Content(` "${f.name}" => ${formatPhpArray(fconfig, 4)},
202
205
  `)
203
206
  })