@voxgig/sdkgen 3.6.0 → 3.7.2
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 +17 -0
- package/dist/cmp/AgentGuideTop.js.map +1 -1
- package/dist/cmp/Readme.js +2 -0
- package/dist/cmp/Readme.js.map +1 -1
- package/dist/cmp/ReadmeStation.d.ts +2 -0
- package/dist/cmp/ReadmeStation.js +57 -0
- package/dist/cmp/ReadmeStation.js.map +1 -0
- package/dist/cmp/ReadmeTop.js +11 -4
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/naming.d.ts +3 -1
- package/dist/helpers/naming.js +39 -2
- package/dist/helpers/naming.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +4 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +1 -1
- package/dist/utility.js +16 -2
- package/dist/utility.js.map +1 -1
- package/package.json +1 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +34 -13
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +9 -46
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +7 -1
- package/project/.sdk/src/cmp/dart/Config_dart.ts +14 -2
- package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +5 -0
- package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -0
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +13 -4
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +12 -2
- package/project/.sdk/src/cmp/elixir/Package_elixir.ts +41 -2
- package/project/.sdk/src/cmp/go/Config_go.ts +8 -1
- package/project/.sdk/src/cmp/go/ReadmeIntro_go.ts +19 -4
- package/project/.sdk/src/cmp/java/Config_java.ts +8 -1
- package/project/.sdk/src/cmp/js/Config_js.ts +9 -1
- package/project/.sdk/src/cmp/js/fragment/Config.data.fragment.js +7 -0
- package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +8 -0
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +33 -5
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +8 -1
- package/project/.sdk/src/cmp/lua/Config_lua.ts +11 -2
- package/project/.sdk/src/cmp/lua/Package_lua.ts +24 -1
- package/project/.sdk/src/cmp/perl/Config_perl.ts +8 -60
- package/project/.sdk/src/cmp/php/Config_php.ts +11 -2
- package/project/.sdk/src/cmp/php/Main_php.ts +25 -0
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +23 -6
- package/project/.sdk/src/cmp/py/Config_py.ts +11 -2
- package/project/.sdk/src/cmp/py/Main_py.ts +17 -7
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +23 -6
- package/project/.sdk/src/cmp/rb/Config_rb.ts +7 -1
- package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -2
- package/project/.sdk/src/cmp/swift/Config_swift.ts +8 -54
- package/project/.sdk/src/cmp/swift/Package_swift.ts +52 -10
- package/project/.sdk/src/cmp/ts/Config_ts.ts +9 -1
- package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +5 -2
- package/project/.sdk/src/cmp/ts/Entity_ts.ts +8 -2
- package/project/.sdk/src/cmp/ts/Package_ts.ts +3 -1
- package/project/.sdk/src/cmp/ts/ReadmeIntro_ts.ts +19 -4
- package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +7 -0
- package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +8 -0
- package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +1 -1
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +12 -4
- package/project/.sdk/tm/c/Makefile +6 -1
- package/project/.sdk/tm/c/utility/make_options.c +12 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +9 -1
- package/project/.sdk/tm/csharp/utility/Fetcher.cs +23 -5
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +13 -0
- package/project/.sdk/tm/dart/lib/utility/FetcherUtility.dart +9 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +17 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +39 -3
- package/project/.sdk/tm/elixir/test/feature_test.exs +19 -0
- package/project/.sdk/tm/elixir/test/primary_utility_test.exs +72 -0
- package/project/.sdk/tm/go/test/primary_utility_test.go +42 -20
- package/project/.sdk/tm/go/test/runner_test.go +1 -1
- package/project/.sdk/tm/go/utility/fetcher.go +14 -0
- package/project/.sdk/tm/go/utility/make_options.go +31 -4
- package/project/.sdk/tm/java/utility/Fetcher.java +11 -1
- package/project/.sdk/tm/java/utility/MakeOptions.java +11 -0
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +20 -2
- package/project/.sdk/tm/kotlin/utility/Fetcher.kt +10 -1
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +11 -0
- package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +13 -2
- package/project/.sdk/tm/lua/utility/make_options.lua +44 -0
- package/project/.sdk/tm/perl/utility/fetcher.pm +10 -1
- package/project/.sdk/tm/perl/utility/make_options.pm +36 -0
- package/project/.sdk/tm/php/test/PipelineTest.php +20 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +12 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +58 -0
- package/project/.sdk/tm/py/pkg/utility/fetcher.py +13 -1
- package/project/.sdk/tm/py/pkg/utility/make_options.py +23 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +18 -0
- package/project/.sdk/tm/rust/tests/common/mod.rs +1 -1
- package/project/.sdk/tm/rust/tests/feature_test.rs +27 -0
- package/project/.sdk/tm/rust/utility/fetcher.rs +8 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +15 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +24 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +12 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +20 -2
- package/project/.sdk/tm/ts/test/exists.test.ts +14 -3
- package/project/.sdk/tm/ts/test/utility/Corpus.test.ts +85 -0
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +10 -17
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/AgentGuideTop.ts +18 -0
- package/src/cmp/Readme.ts +2 -0
- package/src/cmp/ReadmeStation.ts +69 -0
- package/src/cmp/ReadmeTop.ts +12 -3
- package/src/helpers/naming.ts +45 -2
- package/src/sdkgen.ts +3 -1
- package/src/utility.ts +17 -2
|
@@ -228,6 +228,21 @@ pub fn make_options_util(ctx: &Rc<Context>) -> Value {
|
|
|
228
228
|
} else {
|
|
229
229
|
feature_order = names;
|
|
230
230
|
}
|
|
231
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
232
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
233
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
234
|
+
// or first, when no test entry exists. Without this the sorted
|
|
235
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
236
|
+
// features, turning its wire-truth events into fiction.
|
|
237
|
+
if let Some(si) = feature_order.iter().position(|n| n == "station") {
|
|
238
|
+
feature_order.remove(si);
|
|
239
|
+
let at = feature_order
|
|
240
|
+
.iter()
|
|
241
|
+
.position(|n| n == "test")
|
|
242
|
+
.map(|ti| ti + 1)
|
|
243
|
+
.unwrap_or(0);
|
|
244
|
+
feature_order.insert(at, "station".to_string());
|
|
245
|
+
}
|
|
231
246
|
}
|
|
232
247
|
}
|
|
233
248
|
let order_list =
|
|
@@ -6,6 +6,27 @@ import Foundation
|
|
|
6
6
|
import FoundationNetworking
|
|
7
7
|
#endif
|
|
8
8
|
|
|
9
|
+
// Non-following twin of URLSession.shared. The station feature's
|
|
10
|
+
// middleware sets `redirect: manual` on the fetch definition under a
|
|
11
|
+
// hosts egress policy: a 3xx must come back as a response like any
|
|
12
|
+
// other, because an automatic follow would carry injected credentials
|
|
13
|
+
// to a Location no policy approved (the ts donor's fetch honours the
|
|
14
|
+
// same key natively; redirect policy is per-delegate in URLSession,
|
|
15
|
+
// hence a second session).
|
|
16
|
+
private final class ManualRedirectDelegate: NSObject, URLSessionTaskDelegate {
|
|
17
|
+
func urlSession(
|
|
18
|
+
_ session: URLSession, task: URLSessionTask,
|
|
19
|
+
willPerformHTTPRedirection response: HTTPURLResponse,
|
|
20
|
+
newRequest request: URLRequest,
|
|
21
|
+
completionHandler: @escaping (URLRequest?) -> Void
|
|
22
|
+
) {
|
|
23
|
+
completionHandler(nil)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private let manualRedirectSession = URLSession(
|
|
28
|
+
configuration: .default, delegate: ManualRedirectDelegate(), delegateQueue: nil)
|
|
29
|
+
|
|
9
30
|
func defaultHttpFetch(_ fullurl: String, _ fetchdef: VMap) throws -> Value {
|
|
10
31
|
guard let url = URL(string: fullurl) else {
|
|
11
32
|
let m = VMap()
|
|
@@ -45,7 +66,9 @@ func defaultHttpFetch(_ fullurl: String, _ fetchdef: VMap) throws -> Value {
|
|
|
45
66
|
}
|
|
46
67
|
let box = FetchBox()
|
|
47
68
|
let sem = DispatchSemaphore(value: 0)
|
|
48
|
-
let
|
|
69
|
+
let session = gp(fetchdef, "redirect").asString == "manual"
|
|
70
|
+
? manualRedirectSession : URLSession.shared
|
|
71
|
+
let task = session.dataTask(with: req) { d, r, e in
|
|
49
72
|
box.data = d; box.resp = r; box.err = e; sem.signal()
|
|
50
73
|
}
|
|
51
74
|
task.resume()
|
|
@@ -139,6 +139,18 @@ func makeOptionsUtil(_ ctx: Context) -> VMap {
|
|
|
139
139
|
} else {
|
|
140
140
|
for n in names { featureorder.append(.string(n)) }
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
144
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
145
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
146
|
+
// or first, when no test entry exists. Without this the sorted
|
|
147
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
148
|
+
// features, turning its wire-truth events into fiction.
|
|
149
|
+
if let si = featureorder.firstIndex(where: { $0.asString == "station" }) {
|
|
150
|
+
featureorder.remove(at: si)
|
|
151
|
+
let ti = featureorder.firstIndex(where: { $0.asString == "test" })
|
|
152
|
+
featureorder.insert(.string("station"), at: (ti ?? -1) + 1)
|
|
153
|
+
}
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
let derived = VMap()
|
|
@@ -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
|
-
|
|
138
|
-
|
|
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__ = {
|
|
@@ -8,9 +8,20 @@ import { ProjectNameSDK } from '..'
|
|
|
8
8
|
|
|
9
9
|
describe('exists', async () => {
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
// NOT async, and the assertion is deliberate.
|
|
12
|
+
//
|
|
13
|
+
// ProjectNameSDK.test() is synchronous — it returns the client, not a promise
|
|
14
|
+
// — so the `await` here was a no-op. Worse, it hid the weakness of the
|
|
15
|
+
// assertion: `null !== testsdk` is trivially true for ANY non-null value,
|
|
16
|
+
// including the promise an `await` would have unwrapped. The test could not
|
|
17
|
+
// have failed short of test() returning null.
|
|
18
|
+
//
|
|
19
|
+
// instanceof is the real check: it fails if test() ever starts returning a
|
|
20
|
+
// promise, or anything other than a client.
|
|
21
|
+
test('test-mode', () => {
|
|
22
|
+
const testsdk = ProjectNameSDK.test()
|
|
23
|
+
equal(testsdk instanceof ProjectNameSDK, true,
|
|
24
|
+
'ProjectNameSDK.test() must return a client synchronously')
|
|
14
25
|
})
|
|
15
26
|
|
|
16
27
|
})
|
|
@@ -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', '
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
|
package/src/cmp/AgentGuideTop.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/cmp/ReadmeTop.ts
CHANGED
|
@@ -123,7 +123,7 @@ const ReadmeTop = cmp(function ReadmeTop(props: any) {
|
|
|
123
123
|
|
|
124
124
|
const hasCli = activeTargets.some((t: any) => t.name === 'go-cli')
|
|
125
125
|
const hasMcp = activeTargets.some((t: any) => t.name === 'go-mcp')
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
|
|
128
128
|
// Canonical docs order from main.kit.config.docs_order (with a
|
|
129
129
|
// schema-supplied default of ['ts','py','php','go','rb','lua']).
|
|
@@ -189,8 +189,17 @@ Learn more about Voxgig SDKs at [voxgig.com/sdk](${VOXGIG_SDK}).
|
|
|
189
189
|
if (sdkTargets.length > 1) {
|
|
190
190
|
const surfaces = []
|
|
191
191
|
surfaces.push(`${langList} SDKs`)
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
// Only surfaces a target actually GENERATES, each gated on the target
|
|
193
|
+
// that generates it.
|
|
194
|
+
//
|
|
195
|
+
// The REPL entry used to be gated on hasJsLike — a ts or js target merely
|
|
196
|
+
// EXISTING — which is wrong twice over: a ts/js SDK is a library and
|
|
197
|
+
// ships no REPL, so every project with one advertised a surface it did
|
|
198
|
+
// not have; and the target that really does emit a REPL is go-cli
|
|
199
|
+
// (cmp/go-cli/fragment/main.fragment.go: `func repl(...)`, a prompt, and
|
|
200
|
+
// /help and /quit), so a project WITH a REPL and without ts/js was not
|
|
201
|
+
// credited for it. Gating it on hasCli fixes both directions.
|
|
202
|
+
if (hasCli) surfaces.push('a CLI with an interactive REPL')
|
|
194
203
|
if (hasMcp) surfaces.push('an MCP server for AI agents')
|
|
195
204
|
const surfaceList = surfaces.length > 1
|
|
196
205
|
? surfaces.slice(0, -1).join(', ') + ', and ' + surfaces[surfaces.length - 1]
|
package/src/helpers/naming.ts
CHANGED
|
@@ -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
|
|
249
|
-
// only one of the
|
|
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
|
|
349
|
+
main,
|
|
335
350
|
feature: featureDefs,
|
|
336
351
|
options,
|
|
337
352
|
entity: entityDefs,
|