@voxgig/sdkgen 4.2.4 → 4.2.5
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.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/AgentGuideTop.js +15 -7
- package/dist/cmp/AgentGuideTop.js.map +1 -1
- package/dist/cmp/ReadmeStation.js +88 -12
- package/dist/cmp/ReadmeStation.js.map +1 -1
- package/dist/helpers/station.d.ts +3 -0
- package/dist/helpers/station.js +67 -0
- package/dist/helpers/station.js.map +1 -0
- package/dist/sdkgen.d.ts +2 -1
- package/dist/sdkgen.js +41 -15
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/utility.js +15 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +20 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/audit.aon +1 -0
- package/project/.sdk/model/feature/cache.aon +1 -0
- package/project/.sdk/model/feature/clienttrack.aon +1 -0
- package/project/.sdk/model/feature/debug.aon +1 -0
- package/project/.sdk/model/feature/idempotency.aon +1 -0
- package/project/.sdk/model/feature/log.aon +1 -0
- package/project/.sdk/model/feature/metrics.aon +1 -0
- package/project/.sdk/model/feature/netsim.aon +1 -0
- package/project/.sdk/model/feature/paging.aon +1 -0
- package/project/.sdk/model/feature/proxy.aon +1 -0
- package/project/.sdk/model/feature/ratelimit.aon +1 -0
- package/project/.sdk/model/feature/rbac.aon +1 -0
- package/project/.sdk/model/feature/retry.aon +1 -0
- package/project/.sdk/model/feature/streaming.aon +1 -0
- package/project/.sdk/model/feature/telemetry.aon +1 -0
- package/project/.sdk/model/feature/test.aon +1 -0
- package/project/.sdk/model/feature/timeout.aon +1 -0
- package/project/.sdk/src/cmp/c/Config_c.ts +13 -10
- package/project/.sdk/src/cmp/clojure/Config_clojure.ts +4 -1
- package/project/.sdk/src/cmp/dart/Config_dart.ts +4 -1
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +4 -1
- package/project/.sdk/src/cmp/elixir/Main_elixir.ts +21 -2
- package/project/.sdk/src/cmp/go/Config_go.ts +4 -1
- package/project/.sdk/src/cmp/java/Config_java.ts +11 -8
- package/project/.sdk/src/cmp/js/Config_js.ts +4 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +20 -1
- package/project/.sdk/src/cmp/js/fragment/MainStation.fragment.js +36 -0
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +11 -7
- package/project/.sdk/src/cmp/lua/Config_lua.ts +4 -1
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +3 -1
- package/project/.sdk/src/cmp/php/Config_php.ts +4 -1
- package/project/.sdk/src/cmp/py/Config_py.ts +4 -1
- package/project/.sdk/src/cmp/rb/Config_rb.ts +4 -1
- package/project/.sdk/src/cmp/swift/EntityTypes_swift.ts +2 -2
- package/project/.sdk/src/cmp/swift/Entity_swift.ts +3 -1
- package/project/.sdk/src/cmp/swift/Main_swift.ts +37 -2
- package/project/.sdk/src/cmp/swift/Package_swift.ts +9 -6
- package/project/.sdk/src/cmp/swift/ReadmeExamplesTest_swift.ts +1 -1
- package/project/.sdk/src/cmp/swift/ReadmeExplanation_swift.ts +5 -3
- package/project/.sdk/src/cmp/swift/Test_swift.ts +3 -1
- package/project/.sdk/src/cmp/swift/utility_swift.ts +16 -0
- package/project/.sdk/src/cmp/ts/Config_ts.ts +4 -1
- package/project/.sdk/src/cmp/ts/Main_ts.ts +20 -1
- package/project/.sdk/src/cmp/ts/fragment/MainStation.fragment.ts +36 -0
- package/project/.sdk/src/cmp/zig/Config_zig.ts +4 -2
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/AgentGuideTop.ts +15 -7
- package/src/cmp/ReadmeStation.ts +93 -13
- package/src/helpers/station.ts +80 -0
- package/src/sdkgen.ts +52 -15
- package/src/types.ts +6 -0
- package/src/utility.ts +15 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import * as Path from 'node:path'
|
|
3
3
|
|
|
4
|
+
import { swiftTargetDir, swiftTestDir } from './utility_swift'
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
cmp, each,
|
|
6
8
|
File, Content, Copy, Folder, Fragment,
|
|
@@ -40,18 +42,51 @@ const Main = cmp(async function Main(props: any) {
|
|
|
40
42
|
|
|
41
43
|
// Copy tm/swift files with replacements. `src/` holds only the per-feature
|
|
42
44
|
// extension folders (not shipped into the SDK output).
|
|
45
|
+
// THE COPIED TREE HAS TO CARRY THE API NAME TOO.
|
|
46
|
+
//
|
|
47
|
+
// Copy substitutes file CONTENTS, never path components, so a blanket copy
|
|
48
|
+
// of tm/swift landed the runtime in a directory literally called
|
|
49
|
+
// ProjectNameSDK. Package.swift papered over it with an explicit `path:`,
|
|
50
|
+
// so it compiled and every swift suite passed — while every published SDK
|
|
51
|
+
// shipped `Sources/ProjectNameSDK/`, and SwiftPM's own convention
|
|
52
|
+
// (Sources/<target>) was broken in all of them.
|
|
53
|
+
//
|
|
54
|
+
// Copy's `to` prop names the destination, so the two placeholder subtrees
|
|
55
|
+
// are copied explicitly and the rest of tm/swift blanket-copied as before.
|
|
43
56
|
Copy({
|
|
44
57
|
from: 'tm/' + target.name,
|
|
45
|
-
exclude: [/src\//],
|
|
58
|
+
exclude: [/src\//, /Sources\//, /Tests\//],
|
|
46
59
|
replace: {
|
|
47
60
|
...props.ctx$.stdrep,
|
|
48
61
|
ProjectName: model.const.Name,
|
|
49
62
|
}
|
|
50
63
|
})
|
|
51
64
|
|
|
65
|
+
Folder({ name: 'Sources' }, () => {
|
|
66
|
+
Copy({
|
|
67
|
+
from: 'tm/' + target.name + '/Sources/ProjectNameSDK',
|
|
68
|
+
to: swiftTargetDir(model),
|
|
69
|
+
replace: {
|
|
70
|
+
...props.ctx$.stdrep,
|
|
71
|
+
ProjectName: model.const.Name,
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
Folder({ name: 'Tests' }, () => {
|
|
77
|
+
Copy({
|
|
78
|
+
from: 'tm/' + target.name + '/Tests/ProjectNameSDKTests',
|
|
79
|
+
to: swiftTestDir(model),
|
|
80
|
+
replace: {
|
|
81
|
+
...props.ctx$.stdrep,
|
|
82
|
+
ProjectName: model.const.Name,
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
52
87
|
// Generated sources join the copied runtime under Sources/ProjectNameSDK.
|
|
53
88
|
Folder({ name: 'Sources' }, () => {
|
|
54
|
-
Folder({ name:
|
|
89
|
+
Folder({ name: swiftTargetDir(model) }, () => {
|
|
55
90
|
Folder({ name: 'core' }, () => {
|
|
56
91
|
|
|
57
92
|
// Main SDK client class, with entity accessors injected at the SLOT.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
|
|
2
|
+
import { swiftTargetDir, swiftTestDir } from './utility_swift'
|
|
3
|
+
|
|
2
4
|
import {
|
|
3
5
|
Content,
|
|
4
6
|
File,
|
|
@@ -14,10 +16,11 @@ import type {
|
|
|
14
16
|
|
|
15
17
|
// Emits Package.swift (the SwiftPM manifest; the Swift twin of Package_go's
|
|
16
18
|
// go.mod / Package_csharp's csproj). The library target compiles everything
|
|
17
|
-
// under Sources
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
19
|
+
// under Sources/<Name>Sdk (the copied runtime + generated sources); the test
|
|
20
|
+
// target compiles Tests/<Name>SdkTests. Both directories carry the API name:
|
|
21
|
+
// Copy does not rewrite path components, so Main_swift copies the two
|
|
22
|
+
// placeholder subtrees explicitly via Copy's `to` prop rather than letting a
|
|
23
|
+
// blanket copy ship them as ProjectNameSDK.
|
|
21
24
|
//
|
|
22
25
|
// Dependencies: the runtime itself is dependency-free (Foundation + the
|
|
23
26
|
// vendored struct), but declared target/feature deps flow into the manifest
|
|
@@ -91,11 +94,11 @@ let package = Package(
|
|
|
91
94
|
targets: [
|
|
92
95
|
.target(
|
|
93
96
|
name: "${Name}Sdk",${targetdeps}
|
|
94
|
-
path: "Sources
|
|
97
|
+
path: "Sources/${swiftTargetDir(model)}"),
|
|
95
98
|
.testTarget(
|
|
96
99
|
name: "${Name}SdkTests",
|
|
97
100
|
dependencies: ["${Name}Sdk"],
|
|
98
|
-
path: "Tests
|
|
101
|
+
path: "Tests/${swiftTestDir(model)}"),
|
|
99
102
|
]
|
|
100
103
|
)
|
|
101
104
|
`)
|
|
@@ -64,7 +64,7 @@ final class ReadmeExamplesTest: XCTestCase {
|
|
|
64
64
|
// The three documents to scan, resolved relative to this test file.
|
|
65
65
|
private func docs() -> [Doc] {
|
|
66
66
|
let here = URL(fileURLWithPath: #filePath)
|
|
67
|
-
.deletingLastPathComponent() //
|
|
67
|
+
.deletingLastPathComponent() // the test target dir
|
|
68
68
|
.deletingLastPathComponent() // Tests
|
|
69
69
|
.deletingLastPathComponent() // swift (module dir)
|
|
70
70
|
let project = here.deletingLastPathComponent() // project (holds root README)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
|
|
2
|
+
import { swiftTargetDir, swiftTestDir } from './utility_swift'
|
|
3
|
+
|
|
2
4
|
import { cmp, Content } from '@voxgig/sdkgen'
|
|
3
5
|
|
|
4
6
|
|
|
@@ -24,16 +26,16 @@ generated for editor documentation.
|
|
|
24
26
|
\`\`\`
|
|
25
27
|
swift/
|
|
26
28
|
├── Package.swift -- SwiftPM manifest (zero runtime deps)
|
|
27
|
-
├── Sources/
|
|
29
|
+
├── Sources/${swiftTargetDir(model)}/
|
|
28
30
|
│ ├── core/ -- Main client, config, entity base, error type
|
|
29
31
|
│ ├── entity/ -- Generated entity clients
|
|
30
32
|
│ ├── feature/ -- Built-in features (Base, Test, Log, ...)
|
|
31
33
|
│ ├── utility/ -- Utility functions
|
|
32
34
|
│ └── Struct/ -- Vendored Voxgig Struct port
|
|
33
|
-
└── Tests/
|
|
35
|
+
└── Tests/${swiftTestDir(model)}/ -- Test suites (XCTest)
|
|
34
36
|
\`\`\`
|
|
35
37
|
|
|
36
|
-
The main client class (\`${SDK}\`, under \`Sources/
|
|
38
|
+
The main client class (\`${SDK}\`, under \`Sources/${swiftTargetDir(model)}/core\`)
|
|
37
39
|
exposes the entity accessors. Reference entity or utility types directly only
|
|
38
40
|
when needed. The SDK is dependency-free: JSON parsing is the vendored
|
|
39
41
|
\`Struct/JSON.swift\`, HTTP transport is Foundation's \`URLSession\`, and the
|
|
@@ -3,6 +3,8 @@ import type {
|
|
|
3
3
|
ModelEntity
|
|
4
4
|
} from '@voxgig/apidef'
|
|
5
5
|
|
|
6
|
+
import { swiftTargetDir, swiftTestDir } from './utility_swift'
|
|
7
|
+
|
|
6
8
|
import { cmp, each, Folder, entityCollection } from '@voxgig/sdkgen'
|
|
7
9
|
|
|
8
10
|
|
|
@@ -20,7 +22,7 @@ const Test = cmp(function Test(props: any) {
|
|
|
20
22
|
// templates in tm/swift/Tests. Only the API-specific entity/direct tests
|
|
21
23
|
// are generated here.
|
|
22
24
|
Folder({ name: 'Tests' }, () => {
|
|
23
|
-
Folder({ name:
|
|
25
|
+
Folder({ name: swiftTestDir(model) }, () => {
|
|
24
26
|
const entity = each(entityCollection(model))
|
|
25
27
|
.filter((e: any) => false !== e.active)
|
|
26
28
|
|
|
@@ -132,10 +132,26 @@ function clean(o: any, dropDefaults?: boolean): any {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
|
|
135
|
+
// SwiftPM's convention is Sources/<target>/ and Tests/<target>Tests/, and the
|
|
136
|
+
// target is named <Name>Sdk in Package_swift. These name the two directories
|
|
137
|
+
// so the copied runtime lands under the API's own name rather than under the
|
|
138
|
+
// template's placeholder — see the note in Main_swift.
|
|
139
|
+
function swiftTargetDir(model: any): string {
|
|
140
|
+
return model.const.Name + 'Sdk'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
function swiftTestDir(model: any): string {
|
|
145
|
+
return swiftTargetDir(model) + 'Tests'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
135
149
|
export {
|
|
136
150
|
clean,
|
|
137
151
|
projectPath,
|
|
138
152
|
swiftPascalName,
|
|
153
|
+
swiftTargetDir,
|
|
154
|
+
swiftTestDir,
|
|
139
155
|
swiftVarName,
|
|
140
156
|
swiftString,
|
|
141
157
|
}
|
|
@@ -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.
|
|
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
|
|
|
@@ -106,6 +106,25 @@ if (fres instanceof Promise) { await fres }
|
|
|
106
106
|
MainEntity(entprops)
|
|
107
107
|
})
|
|
108
108
|
})
|
|
109
|
+
|
|
110
|
+
// Station self-registration (station design §6.2 path 1;
|
|
111
|
+
// station-declarative-config §11 item 2): emitted ONLY when the model
|
|
112
|
+
// carries an ACTIVE station feature (installed via
|
|
113
|
+
// `package add @voxgig/sdkgen-station`). The library package name
|
|
114
|
+
// comes from the feature model's own `deps.ts` block — the same entry
|
|
115
|
+
// collectDeps flows into package.json — so the manifest dependency
|
|
116
|
+
// and the emitted require cannot disagree.
|
|
117
|
+
const stationPkg = stationLibrary(model, target.name)
|
|
118
|
+
if (null != stationPkg) {
|
|
119
|
+
Fragment({
|
|
120
|
+
from: Path.normalize(
|
|
121
|
+
__dirname + '/../../../src/cmp/ts/fragment/MainStation.fragment.ts'),
|
|
122
|
+
replace: {
|
|
123
|
+
...props.ctx$.stdrep,
|
|
124
|
+
"'STATIONPKG'": JSON.stringify(stationPkg),
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
}
|
|
109
128
|
})
|
|
110
129
|
|
|
111
130
|
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: any) {
|
|
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?.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?: any) => new ProjectNameSDK(options),
|
|
33
|
+
config,
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -38,8 +38,10 @@ const Config = cmp(async function Config(props: any) {
|
|
|
38
38
|
// representations render from the same `def`, so they cannot describe
|
|
39
39
|
// different configs - and this target picks up `options.server` (the
|
|
40
40
|
// OpenAPI server-variable defaults), which the hand-rolled build here
|
|
41
|
-
// omitted entirely.
|
|
42
|
-
|
|
41
|
+
// omitted entirely. Passing target.name opts this target into the main
|
|
42
|
+
// slug/version/target identity fields (read by station's descriptor -
|
|
43
|
+
// see configDefinition).
|
|
44
|
+
const { def: config, json: configJson } = configDefinition(model, target.name)
|
|
43
45
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
44
46
|
|
|
45
47
|
File({ name: 'config.' + target.ext }, () => {
|
package/src/cmp/AgentGuideTop.ts
CHANGED
|
@@ -83,19 +83,27 @@ package (other languages). Each target's guide documents its features.
|
|
|
83
83
|
`)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
// Station paragraph (station design §9.4
|
|
87
|
-
// carries the feature: an agent working
|
|
88
|
-
// the runtime story without leaving
|
|
86
|
+
// Station paragraph (station design §9.4; declarative design §11
|
|
87
|
+
// item 4), only when the model carries the feature: an agent working
|
|
88
|
+
// on this repo should know the runtime story without leaving
|
|
89
|
+
// AGENTS.md — and, first, that the application's integrations are
|
|
90
|
+
// DECLARED in station.json, so that file is where to look.
|
|
89
91
|
if (features.some((f: any) => 'station' === f.name)) {
|
|
90
92
|
Content(`**Station**: this SDK is a
|
|
91
93
|
[voxgig/station](https://github.com/voxgig/station) plugin (the
|
|
92
|
-
\`station\` feature, off by default).
|
|
93
|
-
|
|
94
|
+
\`station\` feature, off by default). An application's outbound
|
|
95
|
+
integrations are **declared in \`station.json\`** at its repo root —
|
|
96
|
+
to learn what the application talks to, read that file: every SDK
|
|
97
|
+
instance (\`sdk\`), per-api default (\`api\`), feature setting, and
|
|
98
|
+
egress policy is declared there, and never a credential value.
|
|
99
|
+
\`station.sdk('<name>')\` builds a declared instance on first ask;
|
|
100
|
+
\`station.instances()\` lists them. Bound to an open \`Station\`,
|
|
101
|
+
the credential is resolved by sekreto under the instance's secret name
|
|
94
102
|
and injected at the transport seam — \`options()\` and
|
|
95
103
|
\`prepare()\` output hold only a placeholder, so both are safe to
|
|
96
104
|
inspect and log. \`station.tap(...)\`/\`station.events()\` show live
|
|
97
|
-
traffic; \`station.plugins()\` lists descriptors. See the "Use
|
|
98
|
-
Station" README section and \`src/feature/station/\` (or the
|
|
105
|
+
traffic; \`station.plugins()\` lists live descriptors. See the "Use
|
|
106
|
+
with Station" README section and \`src/feature/station/\` (or the
|
|
99
107
|
target's feature container) for the generated adapter.
|
|
100
108
|
|
|
101
109
|
`)
|
package/src/cmp/ReadmeStation.ts
CHANGED
|
@@ -6,20 +6,39 @@ import {
|
|
|
6
6
|
getModelPath
|
|
7
7
|
} from '../types'
|
|
8
8
|
|
|
9
|
-
import { envName } from '../helpers/packageMeta'
|
|
9
|
+
import { envName, packageName } from '../helpers/packageMeta'
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
// The "Use with Station" README section (station design §9.4): rendered
|
|
13
13
|
// ONLY when the project's model carries the station feature (installed
|
|
14
14
|
// via `package add @voxgig/sdkgen-station`) — a project without it sees
|
|
15
|
-
// nothing.
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// nothing. Leads with the DECLARATIVE flow — a `station.json` block and
|
|
16
|
+
// `station.sdk()` (declarative design §11 item 3) — keeps the imperative
|
|
17
|
+
// `connect()` form as the retrofit path, and documents the
|
|
18
|
+
// instance-derived secret/env-var name (declarative design §3.4, §5.1:
|
|
19
|
+
// the same envtoken grammar as sdkgen's envName, applied to the INSTANCE
|
|
20
|
+
// name, so the untagged instance keeps the env var this README already
|
|
21
|
+
// documents to the byte). Store configuration is sekreto's
|
|
22
|
+
// documentation, deliberately not restated here (one canonical source);
|
|
23
|
+
// the error codes live in sdkgen's one catalog page, linked rather than
|
|
24
|
+
// restated for the same reason.
|
|
18
25
|
|
|
19
26
|
// Targets where station.connect(SDK) is the idiomatic binding; everything
|
|
20
27
|
// else uses inverted binding through the SDK's own constructor.
|
|
21
28
|
const CONNECT_TARGETS = ['ts', 'js', 'py', 'rb', 'php', 'lua', 'perl']
|
|
22
29
|
|
|
30
|
+
// Targets whose module system has an init hook that actually runs
|
|
31
|
+
// (station design §6.2 path 1): there, linking the generated package
|
|
32
|
+
// fills the process-global factory table and `station.sdk()` needs no
|
|
33
|
+
// application code. Everywhere else the README must say
|
|
34
|
+
// `Station.provide` (path 2) plainly rather than imply an import is
|
|
35
|
+
// enough — a Java import is a compile-time alias that runs nothing.
|
|
36
|
+
const SELF_REGISTER_TARGETS =
|
|
37
|
+
['ts', 'js', 'go', 'py', 'rb', 'php', 'lua', 'perl', 'elixir', 'clojure']
|
|
38
|
+
|
|
39
|
+
const ERROR_CATALOG_URL =
|
|
40
|
+
'https://github.com/voxgig/sdkgen/blob/main/docs/reference/station-errors.md'
|
|
41
|
+
|
|
23
42
|
const ReadmeStation = cmp(function ReadmeStation(props: any) {
|
|
24
43
|
const { target } = props
|
|
25
44
|
const { model } = props.ctx$
|
|
@@ -30,9 +49,15 @@ const ReadmeStation = cmp(function ReadmeStation(props: any) {
|
|
|
30
49
|
.find((f: any) => 'station' === f?.name)
|
|
31
50
|
if (null == station) { return }
|
|
32
51
|
|
|
52
|
+
// The descriptor slug is the model's hyphenated name (carried as
|
|
53
|
+
// main.slug in the embedded config); an untagged instance ref IS the
|
|
54
|
+
// slug, so this is also the default instance name.
|
|
55
|
+
const slug = model.name
|
|
33
56
|
const env = envName(model)
|
|
34
|
-
const
|
|
57
|
+
const secretbase = env.toLowerCase()
|
|
58
|
+
const secretname = secretbase + '.apikey'
|
|
35
59
|
const connect = CONNECT_TARGETS.includes(target.name)
|
|
60
|
+
const selfreg = SELF_REGISTER_TARGETS.includes(target.name)
|
|
36
61
|
|
|
37
62
|
Content(`
|
|
38
63
|
## Use with Station
|
|
@@ -42,24 +67,79 @@ plugin: bind it to a local \`Station\` and outbound configuration,
|
|
|
42
67
|
credentials, and observability move to one place. The feature is
|
|
43
68
|
present but **off by default** — nothing changes until you bind.
|
|
44
69
|
|
|
70
|
+
### Declarative: \`station.json\` + \`station.sdk()\`
|
|
71
|
+
|
|
72
|
+
Declare an instance in \`station.json\` at the repo root (committable —
|
|
73
|
+
names and stores, never values):
|
|
74
|
+
|
|
75
|
+
\`\`\`json
|
|
76
|
+
{ "station": 1,
|
|
77
|
+
"profiles": { "default": {
|
|
78
|
+
"sdk": { "${slug}": {${selfreg ?
|
|
79
|
+
`\n "package": "${packageName(model, target.name)}"` : ''} } } } } }
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
Then get the client where you need it:
|
|
83
|
+
|
|
84
|
+
1. \`station = Station.open()\` — reads and validates \`station.json\`;
|
|
85
|
+
constructs nothing.
|
|
86
|
+
2. \`client = station.sdk('${slug}')\` — built on first ask and cached,
|
|
87
|
+
so the same name returns the same client.
|
|
88
|
+
|
|
89
|
+
${selfreg
|
|
90
|
+
? `Loading this package is the whole bootstrap: it registers its own
|
|
91
|
+
factory (constructor plus embedded config) with the station library at
|
|
92
|
+
module init, so \`station.sdk()\` needs no SDK import in application
|
|
93
|
+
code. That is what the \`package\` key above is for — it names the module
|
|
94
|
+
for station to load, since nothing else in this example would execute
|
|
95
|
+
it. An application that imports this SDK for its types anyway can drop
|
|
96
|
+
the key: that import is itself the bootstrap.`
|
|
97
|
+
: `In this language an import runs no code, so register the factory
|
|
98
|
+
once at startup — \`Station.provide('${slug}', ...)\`, one line — and
|
|
99
|
+
every other line of configuration stays in \`station.json\`.`}
|
|
100
|
+
|
|
101
|
+
A second instance of the same API is one more key — \`"${slug}$test"\`
|
|
102
|
+
beside \`"${slug}"\` — and \`station.sdk('${slug}$test')\` returns it.
|
|
103
|
+
SDK features are configured in the same file too: fleet-wide, per api,
|
|
104
|
+
or per instance. \`station.check()\` resolves and constructs every
|
|
105
|
+
active instance without sending a request — run it in CI.
|
|
106
|
+
|
|
107
|
+
### The secret name derives from the instance
|
|
108
|
+
|
|
109
|
+
The credential comes from [sekreto](https://github.com/voxgig/sekreto)
|
|
110
|
+
under a name derived from the **instance** name — the instance token
|
|
111
|
+
lowercased, plus \`.apikey\`. The untagged instance \`${slug}\` derives
|
|
112
|
+
\`${secretname}\` — by default the \`${env}_APIKEY\` environment
|
|
113
|
+
variable this README already documents, unchanged. A tagged instance
|
|
114
|
+
derives its own: \`${slug}$test\` → \`${secretbase}_test.apikey\` →
|
|
115
|
+
\`${env}_TEST_APIKEY\` — each instance is a separate credentialed use
|
|
116
|
+
of the API, and its env var is derivable from the name you chose. To
|
|
117
|
+
pin a name instead, set \`secret\` on the instance block, or at the
|
|
118
|
+
api level for several instances sharing one key. Point a profile at a
|
|
119
|
+
vault later and application code does not change; sekreto's own
|
|
120
|
+
documentation covers the stores. The key stays out of \`options()\` and
|
|
121
|
+
\`prepare()\` output; \`station.tap(...)\` shows live traffic.
|
|
122
|
+
|
|
123
|
+
### Imperative: ${connect ? '`connect()`' : 'inverted binding'} — the retrofit path
|
|
124
|
+
|
|
45
125
|
${connect
|
|
46
126
|
? `Bind by passing the SDK class to the station:
|
|
47
127
|
|
|
48
128
|
1. \`station = Station.open()\` — profile, env, and proxy all defaulted.
|
|
49
129
|
2. \`client = station.connect(${model.const.Name}SDK)\` — replaces direct
|
|
50
|
-
construction
|
|
130
|
+
construction; \`connect(${model.const.Name}SDK, { as: 'test' })\` binds
|
|
131
|
+
a second, tagged instance.`
|
|
51
132
|
: `Bind through the constructor this SDK already has (inverted
|
|
52
133
|
binding): open a station, then construct with station-built options —
|
|
53
134
|
\`station.options()\` merges the handle, the activation entry, and the
|
|
54
135
|
correct feature order into the plain options the constructor accepts.`}
|
|
55
136
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
shows live traffic.
|
|
137
|
+
With no \`station.json\` at all this runs solo with everything
|
|
138
|
+
defaulted — the two-line form is how an existing application starts.
|
|
139
|
+
|
|
140
|
+
Station failures surface through this SDK's own error path
|
|
141
|
+
(\`err.code\`, \`station_*\`); the codes are catalogued in
|
|
142
|
+
[station error codes](${ERROR_CATALOG_URL}).
|
|
63
143
|
`)
|
|
64
144
|
})
|
|
65
145
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// The station SELF-REGISTRATION seam (station design station.md §9 /
|
|
2
|
+
// station-declarative-config.md §6.2 path 1, §11 item 2): when the station
|
|
3
|
+
// feature is installed and active, the generated MAIN module registers the
|
|
4
|
+
// SDK's `{construct, config}` factory pair with the station library at
|
|
5
|
+
// module init, so `station.sdk('<name>')` needs no imports in application
|
|
6
|
+
// code. The registration key is the descriptor slug — `config.main.slug`,
|
|
7
|
+
// the same field station's `normalizeDescriptor` reads — so the generated
|
|
8
|
+
// code passes the embedded config's own value rather than re-deriving it.
|
|
9
|
+
//
|
|
10
|
+
// This helper answers the ONE question every Main_<lang> asks before
|
|
11
|
+
// emitting that registration: is the station feature ACTIVE in this model,
|
|
12
|
+
// and what is the station library package for this target? Both come from
|
|
13
|
+
// the model — the active-filtered feature map (exactly the view the Main
|
|
14
|
+
// components already emit from) and the station feature's own
|
|
15
|
+
// `deps.<target>` block, which is the same entry `collectDeps` flows into
|
|
16
|
+
// the generated manifest. ONE RULE, ONE PLACE: reading the package name
|
|
17
|
+
// here, never hardcoding it per language, means the manifest dependency
|
|
18
|
+
// and the emitted require can not disagree.
|
|
19
|
+
//
|
|
20
|
+
// Returns undefined when the station feature is absent, inactive, or
|
|
21
|
+
// declares no active station library dep for this target (the vendored
|
|
22
|
+
// targets carry the library inside their tm overlay instead of as a dep —
|
|
23
|
+
// station.md §9.2): no dep, no package to require, no registration to emit.
|
|
24
|
+
|
|
25
|
+
import { each } from 'jostraca'
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
KIT,
|
|
29
|
+
getModelPath,
|
|
30
|
+
} from '@voxgig/apidef'
|
|
31
|
+
|
|
32
|
+
import { SdkGenError } from '../utility'
|
|
33
|
+
|
|
34
|
+
import type { SdkModel, ModelFeature } from '../types'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
function stationLibrary(model: SdkModel, targetName: string): string | undefined {
|
|
38
|
+
// Active-filtered on purpose: an inactive feature ships no source, no
|
|
39
|
+
// embedded config entry and no manifest dep, so it must emit no
|
|
40
|
+
// registration either.
|
|
41
|
+
const feature = getModelPath(model, `main.${KIT}.feature`,
|
|
42
|
+
{ required: false }) || {}
|
|
43
|
+
|
|
44
|
+
const station: ModelFeature | undefined = feature.station
|
|
45
|
+
if (null == station) { return undefined }
|
|
46
|
+
|
|
47
|
+
const deps = station.deps?.[targetName]
|
|
48
|
+
if (null == deps) { return undefined }
|
|
49
|
+
|
|
50
|
+
// Feature deps count only when explicitly active — collectDeps semantics,
|
|
51
|
+
// so the require target is exactly the set the manifest carries.
|
|
52
|
+
const names = each(deps)
|
|
53
|
+
.filter((dep: any) => true === dep?.active)
|
|
54
|
+
.map((dep: any) => dep?.key$)
|
|
55
|
+
.filter((name: any) => null != name && '' !== name)
|
|
56
|
+
|
|
57
|
+
if (0 === names.length) { return undefined }
|
|
58
|
+
|
|
59
|
+
// EXACTLY ONE, or say so. Picking the first of several — each() sorts,
|
|
60
|
+
// so it would be the alphabetically first — means the generated main
|
|
61
|
+
// can `require` an unrelated helper package, find no `provide`, and
|
|
62
|
+
// leave the factory table silently empty. The station feature model
|
|
63
|
+
// declares one library per target; a second active dep is a model
|
|
64
|
+
// question only its author can answer.
|
|
65
|
+
if (1 < names.length) {
|
|
66
|
+
throw new SdkGenError(
|
|
67
|
+
'station: feature `station` declares ' + names.length + ' active ' +
|
|
68
|
+
'dependencies for target `' + targetName + '` (' +
|
|
69
|
+
names.map(String).sort().join(', ') + '), so the station library to ' +
|
|
70
|
+
'register with is ambiguous. Declare exactly one active dep per ' +
|
|
71
|
+
'target in the feature model, or mark which one is the library.')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return String(names[0])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
export {
|
|
79
|
+
stationLibrary,
|
|
80
|
+
}
|