@voxgig/sdkgen 3.6.0 → 3.7.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 (100) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/AgentGuideTop.js +17 -0
  3. package/dist/cmp/AgentGuideTop.js.map +1 -1
  4. package/dist/cmp/Readme.js +2 -0
  5. package/dist/cmp/Readme.js.map +1 -1
  6. package/dist/cmp/ReadmeStation.d.ts +2 -0
  7. package/dist/cmp/ReadmeStation.js +57 -0
  8. package/dist/cmp/ReadmeStation.js.map +1 -0
  9. package/dist/helpers/naming.d.ts +3 -1
  10. package/dist/helpers/naming.js +39 -2
  11. package/dist/helpers/naming.js.map +1 -1
  12. package/dist/sdkgen.d.ts +2 -2
  13. package/dist/sdkgen.js +4 -2
  14. package/dist/sdkgen.js.map +1 -1
  15. package/dist/tsconfig.tsbuildinfo +1 -1
  16. package/dist/utility.d.ts +1 -1
  17. package/dist/utility.js +16 -2
  18. package/dist/utility.js.map +1 -1
  19. package/package.json +1 -1
  20. package/project/.sdk/src/cmp/c/Config_c.ts +34 -13
  21. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +9 -46
  22. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +7 -1
  23. package/project/.sdk/src/cmp/dart/Config_dart.ts +14 -2
  24. package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +5 -0
  25. package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -0
  26. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +13 -4
  27. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +12 -2
  28. package/project/.sdk/src/cmp/elixir/Package_elixir.ts +41 -2
  29. package/project/.sdk/src/cmp/go/Config_go.ts +8 -1
  30. package/project/.sdk/src/cmp/java/Config_java.ts +8 -1
  31. package/project/.sdk/src/cmp/js/Config_js.ts +9 -1
  32. package/project/.sdk/src/cmp/js/fragment/Config.data.fragment.js +7 -0
  33. package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +8 -0
  34. package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +33 -5
  35. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +8 -1
  36. package/project/.sdk/src/cmp/lua/Config_lua.ts +11 -2
  37. package/project/.sdk/src/cmp/lua/Package_lua.ts +24 -1
  38. package/project/.sdk/src/cmp/perl/Config_perl.ts +8 -60
  39. package/project/.sdk/src/cmp/php/Config_php.ts +11 -2
  40. package/project/.sdk/src/cmp/php/Main_php.ts +25 -0
  41. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +23 -6
  42. package/project/.sdk/src/cmp/py/Config_py.ts +11 -2
  43. package/project/.sdk/src/cmp/py/Main_py.ts +17 -7
  44. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +23 -6
  45. package/project/.sdk/src/cmp/rb/Config_rb.ts +7 -1
  46. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -2
  47. package/project/.sdk/src/cmp/swift/Config_swift.ts +8 -54
  48. package/project/.sdk/src/cmp/swift/Package_swift.ts +52 -10
  49. package/project/.sdk/src/cmp/ts/Config_ts.ts +9 -1
  50. package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +5 -2
  51. package/project/.sdk/src/cmp/ts/Entity_ts.ts +8 -2
  52. package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +7 -0
  53. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +8 -0
  54. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +1 -1
  55. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +12 -4
  56. package/project/.sdk/tm/c/Makefile +6 -1
  57. package/project/.sdk/tm/c/utility/make_options.c +12 -1
  58. package/project/.sdk/tm/cpp/utility/pipeline.hpp +9 -1
  59. package/project/.sdk/tm/csharp/utility/Fetcher.cs +23 -5
  60. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +13 -0
  61. package/project/.sdk/tm/dart/lib/utility/FetcherUtility.dart +9 -0
  62. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +17 -0
  63. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +39 -3
  64. package/project/.sdk/tm/elixir/test/feature_test.exs +19 -0
  65. package/project/.sdk/tm/elixir/test/primary_utility_test.exs +72 -0
  66. package/project/.sdk/tm/go/test/primary_utility_test.go +42 -20
  67. package/project/.sdk/tm/go/test/runner_test.go +1 -1
  68. package/project/.sdk/tm/go/utility/fetcher.go +14 -0
  69. package/project/.sdk/tm/go/utility/make_options.go +31 -4
  70. package/project/.sdk/tm/java/utility/Fetcher.java +11 -1
  71. package/project/.sdk/tm/java/utility/MakeOptions.java +11 -0
  72. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +20 -2
  73. package/project/.sdk/tm/kotlin/utility/Fetcher.kt +10 -1
  74. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +11 -0
  75. package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +13 -2
  76. package/project/.sdk/tm/lua/utility/make_options.lua +44 -0
  77. package/project/.sdk/tm/perl/utility/fetcher.pm +10 -1
  78. package/project/.sdk/tm/perl/utility/make_options.pm +36 -0
  79. package/project/.sdk/tm/php/test/PipelineTest.php +20 -0
  80. package/project/.sdk/tm/php/utility/Fetcher.php +12 -1
  81. package/project/.sdk/tm/php/utility/MakeOptions.php +58 -0
  82. package/project/.sdk/tm/py/pkg/utility/fetcher.py +13 -1
  83. package/project/.sdk/tm/py/pkg/utility/make_options.py +23 -0
  84. package/project/.sdk/tm/rb/utility/make_options.rb +18 -0
  85. package/project/.sdk/tm/rust/tests/common/mod.rs +1 -1
  86. package/project/.sdk/tm/rust/tests/feature_test.rs +27 -0
  87. package/project/.sdk/tm/rust/utility/fetcher.rs +8 -0
  88. package/project/.sdk/tm/rust/utility/make_options.rs +15 -0
  89. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +24 -1
  90. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +12 -0
  91. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +20 -2
  92. package/project/.sdk/tm/ts/test/utility/Corpus.test.ts +85 -0
  93. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +10 -17
  94. package/project/sdkgen-package.json +1 -1
  95. package/src/cmp/AgentGuideTop.ts +18 -0
  96. package/src/cmp/Readme.ts +2 -0
  97. package/src/cmp/ReadmeStation.ts +69 -0
  98. package/src/helpers/naming.ts +45 -2
  99. package/src/sdkgen.ts +3 -1
  100. package/src/utility.ts +17 -2
@@ -71,6 +71,12 @@ function makeOptions(ctx: Context) {
71
71
  }
72
72
  },
73
73
  utility: {},
74
+ // Feature INSTANCES supplied at construction (the station adopt
75
+ // path): consumed by the constructor's featureAdd loop, so they are
76
+ // class instances, not data - `$ANY` accepts them verbatim. Without
77
+ // this entry the seam is dead: the constructor reads
78
+ // options.extend, but validate rejected the key.
79
+ extend: '`$ANY`' as any,
74
80
  system: {
75
81
  fetch: undefined as any
76
82
  },
@@ -134,10 +140,22 @@ function makeOptions(ctx: Context) {
134
140
  // otherwise order the map test-first, then the remaining names sorted, so
135
141
  // the outcome is deterministic and `test` is always the base transport.
136
142
  if (0 === featureorder.length) {
137
- const names = Object.keys(opts.feature || {}).sort()
138
- featureorder = names.indexOf('test') < 0
143
+ let names = Object.keys(opts.feature || {}).sort()
144
+ names = names.indexOf('test') < 0
139
145
  ? names
140
146
  : ['test'].concat(names.filter((n: string) => 'test' !== n))
147
+ // Station special case, mirroring test's: its transport wrap must
148
+ // sit immediately outside the base transport (inside retry/cache/
149
+ // netsim), so map-form activation hoists it to just after test -
150
+ // or first, when no test entry exists. Without this the sorted
151
+ // default would init station last and wrap OUTSIDE the recording
152
+ // features, turning its wire-truth events into fiction.
153
+ const si = names.indexOf('station')
154
+ if (0 <= si) {
155
+ names.splice(si, 1)
156
+ names.splice(names.indexOf('test') + 1, 0, 'station')
157
+ }
158
+ featureorder = names
141
159
  }
142
160
 
143
161
  opts.__derived__ = {
@@ -0,0 +1,85 @@
1
+ import { test, describe } from 'node:test'
2
+ import { ok, equal } from 'node:assert'
3
+ import { readFileSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+
6
+ import { TEST_JSON_FILE } from './index'
7
+
8
+
9
+ // Guards the shared corpus AS A WHOLE, which the per-section guard in the
10
+ // language runners cannot do.
11
+ //
12
+ // That guard only fires for a section some test actually runs. Seven sections
13
+ // — fetcher, makeFetchDef, makePoint, makeResult, featureAdd, featureHook and
14
+ // featureInit — had no test calling them at all, so they sat at `set: []`
15
+ // through two reviews reporting nothing. A fixture nobody runs is
16
+ // indistinguishable from a fixture that passes.
17
+ //
18
+ // Deferral is therefore DATA (`basic.pending`), not a comment: comments do not
19
+ // survive compilation to test.json, so a marker written only in the .aontu
20
+ // source cannot be checked by the thing that consumes it. Being data, these
21
+ // invariants hold for every port, not just this one.
22
+ describe('Corpus', () => {
23
+
24
+ // Resolved the same way runner.ts resolves it — from dist-test/, one level
25
+ // up from this file's dist-test/utility/.
26
+ const corpus = JSON.parse(
27
+ readFileSync(join(__dirname, '..', TEST_JSON_FILE), 'utf8'))
28
+
29
+ const primary: Record<string, any> = corpus.primary || {}
30
+ const names = Object.keys(primary).sort()
31
+
32
+
33
+ test('the corpus is not empty', () => {
34
+ ok(0 < names.length, 'no primary sections found — check TEST_JSON_FILE')
35
+ })
36
+
37
+
38
+ // A section that compiles to zero cases must say why, in the fixture.
39
+ test('every empty section declares why it is deferred', () => {
40
+ const undeclared = names.filter((n) => {
41
+ const basic = primary[n]?.basic
42
+ return Array.isArray(basic?.set) && 0 === basic.set.length &&
43
+ 'string' !== typeof basic?.pending
44
+ })
45
+ equal(undeclared.join(','), '',
46
+ 'these sections compile to ZERO cases and carry no `basic: pending` ' +
47
+ 'reason — add cases, or state the blocker in .sdk/test/primary/<name>.aontu')
48
+ })
49
+
50
+
51
+ // "PENDING" with no reason is just a way to keep a hole open.
52
+ test('every deferral gives a real reason', () => {
53
+ for (const n of names) {
54
+ const pending = primary[n]?.basic?.pending
55
+ if (null == pending) continue
56
+ ok(20 < String(pending).length,
57
+ `${n}: 'pending' needs a reason, not just a marker`)
58
+ }
59
+ })
60
+
61
+
62
+ // The other direction: a section that has gained cases must drop its
63
+ // deferral, or the marker rots into a lie and the runners keep excusing it.
64
+ test('a deferred section that gained cases is promoted', () => {
65
+ const stale = names.filter((n) => {
66
+ const basic = primary[n]?.basic
67
+ return null != basic?.pending && Array.isArray(basic?.set) &&
68
+ 0 < basic.set.length
69
+ })
70
+ equal(stale.join(','), '',
71
+ 'these sections now have cases — remove `basic: pending` from the ' +
72
+ 'fixture and from the PENDING list in the language runners')
73
+ })
74
+
75
+
76
+ // Every section must actually be a section: `basic.set` is what the runners
77
+ // iterate, and a fixture that failed to compile silently loses it.
78
+ test('every section has a basic.set list', () => {
79
+ for (const n of names) {
80
+ ok(Array.isArray(primary[n]?.basic?.set),
81
+ `${n}: no basic.set list — the fixture did not compile as expected`)
82
+ }
83
+ })
84
+
85
+ })
@@ -35,7 +35,7 @@ describe('PrimaryUtility', async () => {
35
35
  // (.sdk/test/primary/<name>.aontu carries a PENDING header). Everything
36
36
  // else MUST contribute cases.
37
37
  const PENDING = new Set([
38
- 'fetcher', 'makeFetchDef', 'makePoint', 'makeResult',
38
+ 'fetcher', 'makeFetchDef', 'makeResult',
39
39
  'featureAdd', 'featureHook', 'featureInit',
40
40
  ])
41
41
 
@@ -279,22 +279,15 @@ describe('PrimaryUtility', async () => {
279
279
  })
280
280
 
281
281
 
282
- test('makePoint-single', () => {
283
- const ctx = makeCtx()
284
- const point = {
285
- parts: ['items', '{id}'],
286
- args: { params: [] },
287
- params: [],
288
- alias: {},
289
- select: {},
290
- active: true,
291
- transform: { req: undefined, res: undefined },
292
- }
293
- ctx.op.points = [point]
294
-
295
- const result = utility.makePoint(ctx)
296
- ok(!(result instanceof Error))
297
- equal(ctx.point, point)
282
+ // Was one hand-written case (the single-point path) covering one of this
283
+ // utility's seven branches, which is how the corpus fixture came to be
284
+ // marked deferred as "needs a real client". It does not: Context rebuilds
285
+ // `op` from opname + entity + config, and `options` can be supplied
286
+ // literally, so allow.op, the empty-points error, exist-selection,
287
+ // $action selection and the invalid-$action error are all expressible.
288
+ // Driven from the corpus now, so every port asserts the same branches.
289
+ test('makePoint-basic', async () => {
290
+ await runsection('makePoint', utility.makePoint)
298
291
  })
299
292
 
300
293
 
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen",
6
- "version": "3.6.0",
6
+ "version": "3.7.0",
7
7
  "provides": {
8
8
  "target": [
9
9
  "c",
@@ -80,6 +80,24 @@ package (other languages). Each target's guide documents its features.
80
80
  Content(`**Entities** (${entities.length}): `)
81
81
  Content(entities.map((e: any) => `\`${e.Name || e.name}\``).join(', ') + `.
82
82
 
83
+ `)
84
+ }
85
+
86
+ // Station paragraph (station design §9.4), only when the model
87
+ // carries the feature: an agent working on this repo should know
88
+ // the runtime story without leaving AGENTS.md.
89
+ if (features.some((f: any) => 'station' === f.name)) {
90
+ Content(`**Station**: this SDK is a
91
+ [voxgig/station](https://github.com/voxgig/station) plugin (the
92
+ \`station\` feature, off by default). Bound to an open \`Station\`,
93
+ the credential is resolved by sekreto under the plugin's secret name
94
+ and injected at the transport seam — \`options()\` and
95
+ \`prepare()\` output hold only a placeholder, so both are safe to
96
+ inspect and log. \`station.tap(...)\`/\`station.events()\` show live
97
+ traffic; \`station.plugins()\` lists descriptors. See the "Use with
98
+ Station" README section and \`src/feature/station/\` (or the
99
+ target's feature container) for the generated adapter.
100
+
83
101
  `)
84
102
  }
85
103
 
package/src/cmp/Readme.ts CHANGED
@@ -6,6 +6,7 @@ import { ReadmeIntro } from './ReadmeIntro'
6
6
  import { ReadmeInstall } from './ReadmeInstall'
7
7
  import { ReadmeQuick } from './ReadmeQuick'
8
8
  import { ReadmeErrors } from './ReadmeErrors'
9
+ import { ReadmeStation } from './ReadmeStation'
9
10
  import { ReadmeModel } from './ReadmeModel'
10
11
  import { ReadmeOptions } from './ReadmeOptions'
11
12
  import { ReadmeEntity } from './ReadmeEntity'
@@ -24,6 +25,7 @@ const Readme = cmp(function Readme(props: any) {
24
25
  ReadmeIntro({ target })
25
26
  ReadmeInstall({ target })
26
27
  ReadmeQuick({ target })
28
+ ReadmeStation({ target })
27
29
  ReadmeErrors({ target })
28
30
  ReadmeHowto({ target })
29
31
  ReadmeModel({ target })
@@ -0,0 +1,69 @@
1
+
2
+ import { cmp, Content } from 'jostraca'
3
+
4
+ import {
5
+ KIT,
6
+ getModelPath
7
+ } from '../types'
8
+
9
+ import { envName } from '../helpers/packageMeta'
10
+
11
+
12
+ // The "Use with Station" README section (station design §9.4): rendered
13
+ // ONLY when the project's model carries the station feature (installed
14
+ // via `package add @voxgig/sdkgen-station`) — a project without it sees
15
+ // nothing. Documents the binding forms and the secret name; store
16
+ // configuration is sekreto's documentation, deliberately not restated
17
+ // here (one canonical source).
18
+
19
+ // Targets where station.connect(SDK) is the idiomatic binding; everything
20
+ // else uses inverted binding through the SDK's own constructor.
21
+ const CONNECT_TARGETS = ['ts', 'js', 'py', 'rb', 'php', 'lua', 'perl']
22
+
23
+ const ReadmeStation = cmp(function ReadmeStation(props: any) {
24
+ const { target } = props
25
+ const { model } = props.ctx$
26
+
27
+ const features = getModelPath(model, `main.${KIT}.feature`,
28
+ { only_active: false, required: false }) || {}
29
+ const station = Object.values(features)
30
+ .find((f: any) => 'station' === f?.name)
31
+ if (null == station) { return }
32
+
33
+ const env = envName(model)
34
+ const secretname = env.toLowerCase() + '.apikey'
35
+ const connect = CONNECT_TARGETS.includes(target.name)
36
+
37
+ Content(`
38
+ ## Use with Station
39
+
40
+ This SDK ships as a [voxgig/station](https://github.com/voxgig/station)
41
+ plugin: bind it to a local \`Station\` and outbound configuration,
42
+ credentials, and observability move to one place. The feature is
43
+ present but **off by default** — nothing changes until you bind.
44
+
45
+ ${connect
46
+ ? `Bind by passing the SDK class to the station:
47
+
48
+ 1. \`station = Station.open()\` — profile, env, and proxy all defaulted.
49
+ 2. \`client = station.connect(${model.const.Name}SDK)\` — replaces direct
50
+ construction.`
51
+ : `Bind through the constructor this SDK already has (inverted
52
+ binding): open a station, then construct with station-built options —
53
+ \`station.options()\` merges the handle, the activation entry, and the
54
+ correct feature order into the plain options the constructor accepts.`}
55
+
56
+ The credential comes from [sekreto](https://github.com/voxgig/sekreto)
57
+ under the name \`${secretname}\` — by default the \`${env}_APIKEY\`
58
+ environment variable this README already documents, unchanged. Point a
59
+ profile in \`station.json\` at a vault later and application code does
60
+ not change; sekreto's own documentation covers the stores. The key
61
+ stays out of \`options()\` and \`prepare()\` output; \`station.tap(...)\`
62
+ shows live traffic.
63
+ `)
64
+ })
65
+
66
+
67
+ export {
68
+ ReadmeStation
69
+ }
@@ -216,6 +216,8 @@ const SWIFT_SDK_TYPES = new Set<string>([
216
216
  'OrderedDictionary', 'Sentinel', 'Value', 'VList', 'VMap', 'WalkApply',
217
217
  // function/callback aliases
218
218
  'FetcherFunc', 'Formatter', 'NativeCall0', 'NativeRef', 'SystemFetch',
219
+ // transport plumbing
220
+ 'ManualRedirectDelegate',
219
221
  // features
220
222
  'AuditFeature', 'BaseFeature', 'CacheFeature', 'ClienttrackFeature',
221
223
  'DebugFeature', 'IdempotencyFeature', 'LogFeature', 'MetricsBucket',
@@ -245,8 +247,8 @@ function swiftSafeTypeName(Name: string): string {
245
247
  }
246
248
 
247
249
 
248
- // PHP RESERVED WORDS THAT CANNOT BE A CLASS NAME — a fourth hazard, and the
249
- // only one of the four where the check must be CASE-INSENSITIVE.
250
+ // PHP RESERVED WORDS THAT CANNOT BE A CLASS NAME — a fifth hazard, and the
251
+ // only one of the five where the check must be CASE-INSENSITIVE.
250
252
  //
251
253
  // PHP's grammar refuses a reserved word where a class name is expected, so a
252
254
  // spec with a `Namespace` entity emitted a file that does not parse at all:
@@ -316,6 +318,45 @@ function phpSafeTypeName(Name: string): string {
316
318
  }
317
319
 
318
320
 
321
+ // TypeScript GLOBAL TYPE NAMES — a fourth hazard, same shape as the Ruby and
322
+ // Swift ones above but for TS/JS builtins, which are in scope everywhere
323
+ // with no import. An entity named `Record` emitted `export interface Record
324
+ // {...}` and canonToType's own generic-object mapping (`Record<string,
325
+ // any>`) then resolved to that flat interface INSIDE THE SAME FILE instead
326
+ // of the builtin: `error TS2315: Type 'Record' is not generic` — on a
327
+ // field of the `Record` entity itself.
328
+ //
329
+ // Utility types (`Record`, `Partial`, ...) and common global constructors
330
+ // (`Array`, `Map`, `Promise`, ...) that a REST entity name plausibly lands
331
+ // on. Mirrors RB_CORE_CONSTANTS / SWIFT_SDK_TYPES: only real builtins are
332
+ // listed, so a non-colliding SDK stays byte-identical to before.
333
+ const TS_RESERVED_TYPES = new Set<string>([
334
+ // utility types
335
+ 'Record', 'Partial', 'Required', 'Readonly', 'Pick', 'Omit', 'Exclude',
336
+ 'Extract', 'NonNullable', 'Parameters', 'ReturnType', 'InstanceType',
337
+ 'Awaited', 'ConstructorParameters',
338
+ // global constructors / builtins
339
+ 'Array', 'Object', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Date', 'Error',
340
+ 'Function', 'Promise', 'RegExp', 'String', 'Number', 'Boolean', 'Symbol',
341
+ 'ArrayBuffer', 'Proxy', 'Reflect', 'JSON', 'Math',
342
+ ])
343
+
344
+
345
+ // Does `Name` collide with a TS/JS global type?
346
+ function isTsReservedType(Name: string): boolean {
347
+ return TS_RESERVED_TYPES.has(Name)
348
+ }
349
+
350
+
351
+ // A collision-free TS type name for a generated type: unchanged, unless it
352
+ // shadows a TS/JS global, in which case `Type` is appended (`Record` ->
353
+ // `RecordType`). Mirrors rbSafeTypeName / swiftSafeTypeName. Applied ONLY
354
+ // to the bare entity data type, for the same reason as both of those.
355
+ function tsSafeTypeName(Name: string): string {
356
+ return isTsReservedType(Name) ? Name + 'Type' : Name
357
+ }
358
+
359
+
319
360
  // Is `name` a reserved word (an illegal identifier) in the target language?
320
361
  function isReservedName(name: string, lang: string): boolean {
321
362
  const set = RESERVED[lang]
@@ -455,6 +496,8 @@ export {
455
496
  swiftSafeTypeName,
456
497
  isPhpReservedType,
457
498
  phpSafeTypeName,
499
+ isTsReservedType,
500
+ tsSafeTypeName,
458
501
  jsProp,
459
502
  jsOptProp,
460
503
  jsKey,
package/src/sdkgen.ts CHANGED
@@ -59,7 +59,7 @@ import { collectDeps } from './helpers/collectDeps'
59
59
  import type { DepEntry } from './helpers/collectDeps'
60
60
  import { canonToType, canonToDtype, canonKey } from './helpers/canonType'
61
61
  import { OP_SUFFIX, opTypeName, opParams, opActions, entityActions, entityPath, opRequestShape, entityIdField, entityDataIdField, entityOps, entityPrimaryOp, pickExampleEntity, entityClassName, entityTypeCollisions, warnEntityTypeCollisions, deriveEntityNames, entityCollection } from './helpers/opShape'
62
- import { isReservedName, safeVarName, exampleVarName, phpEntityAccessor, entityCacheField, isRbCoreConstant, isRbSdkConstant, rbSafeTypeName, isSwiftSdkType, swiftSafeTypeName, isPhpReservedType, phpSafeTypeName, jsProp, jsOptProp, jsKey } from './helpers/naming'
62
+ import { isReservedName, safeVarName, exampleVarName, phpEntityAccessor, entityCacheField, isRbCoreConstant, isRbSdkConstant, rbSafeTypeName, isSwiftSdkType, swiftSafeTypeName, isPhpReservedType, phpSafeTypeName, isTsReservedType, tsSafeTypeName, jsProp, jsOptProp, jsKey } from './helpers/naming'
63
63
  import { serverVariables, hasServerVariables } from './helpers/serverVars'
64
64
  import { primaryOpCall, idLiteral, matchArg, dataArg, litFor } from './helpers/opExample'
65
65
  import type { ExampleLang } from './helpers/opExample'
@@ -1089,6 +1089,8 @@ export {
1089
1089
  swiftSafeTypeName,
1090
1090
  isPhpReservedType,
1091
1091
  phpSafeTypeName,
1092
+ isTsReservedType,
1093
+ tsSafeTypeName,
1092
1094
  serverVariables,
1093
1095
  hasServerVariables,
1094
1096
  liveStrict,
package/src/utility.ts CHANGED
@@ -6,6 +6,7 @@ import { JostracaResult, each } from 'jostraca'
6
6
  import { KIT, getModelPath } from '@voxgig/apidef'
7
7
 
8
8
  import { serverVariables } from './helpers/serverVars'
9
+ import { packageVersion } from './helpers/packageMeta'
9
10
 
10
11
 
11
12
  // Where a per-target component is loaded from: `<project>/.sdk/dist/<path>`.
@@ -290,7 +291,7 @@ function rawStringLiteral(s: string): string {
290
291
  //
291
292
  // Key order is `each`'s order, which is sorted, so the JSON is byte-stable
292
293
  // across runs exactly like the literal it replaces.
293
- function configDefinition(model: any): { def: any, json: string } {
294
+ function configDefinition(model: any, targetname?: string): { def: any, json: string } {
294
295
  const entity = getModelPath(model, `main.${KIT}.entity`)
295
296
  const feature = getModelPath(model, `main.${KIT}.feature`)
296
297
  const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
@@ -330,8 +331,22 @@ function configDefinition(model: any): { def: any, json: string } {
330
331
  options.headers = headers
331
332
  options.entity = entityStubs
332
333
 
334
+ // Identity beyond the camel Name: the hyphenated slug is CARRIED, never
335
+ // derived from the camel form downstream (deriving swallows hyphens - the
336
+ // packageMeta envToken defect), and version/target let a running SDK say
337
+ // what it is. Station's descriptor (voxgig/station) reads all three.
338
+ // Gated on targetname so a target that does not pass its name emits the
339
+ // exact config it always has - each target opts in when its literal
340
+ // emitter learns the fields too, keeping data and literal reps in step.
341
+ const main: any = { name: model.const.Name }
342
+ if (null != targetname) {
343
+ main.slug = model.name
344
+ main.version = packageVersion(model, targetname)
345
+ main.target = targetname
346
+ }
347
+
333
348
  const def = {
334
- main: { name: model.const.Name },
349
+ main,
335
350
  feature: featureDefs,
336
351
  options,
337
352
  entity: entityDefs,