@voxgig/sdkgen 4.2.3 → 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/rust/ReadmeEntity_rust.ts +2 -2
- package/project/.sdk/src/cmp/rust/ReadmeHowto_rust.ts +2 -2
- package/project/.sdk/src/cmp/rust/ReadmeIntro_rust.ts +2 -2
- package/project/.sdk/src/cmp/rust/ReadmeModel_rust.ts +2 -2
- package/project/.sdk/src/cmp/rust/ReadmeQuick_rust.ts +3 -3
- package/project/.sdk/src/cmp/rust/ReadmeRef_rust.ts +3 -3
- package/project/.sdk/src/cmp/rust/ReadmeTopQuick_rust.ts +2 -2
- package/project/.sdk/src/cmp/rust/ReadmeTopTest_rust.ts +2 -2
- package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +7 -7
- 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/.sdk/tm/elixir/lib/utility/struct/voxgig_struct.ex +66 -12
- package/project/.sdk/tm/zig/Makefile +20 -0
- package/project/.sdk/tm/zig/feature/test.zig +1 -1
- 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
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
nom,
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
|
-
import { crateIdent, rustVarName } from './utility_rust'
|
|
10
|
+
import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
@@ -58,7 +58,7 @@ let client = ${ctor};
|
|
|
58
58
|
const eName = nom(exampleEntity, 'Name')
|
|
59
59
|
const article = /^[aeiou]/i.test(eName) ? 'an' : 'a'
|
|
60
60
|
const eVar = rustVarName(exampleEntity.name)
|
|
61
|
-
const method =
|
|
61
|
+
const method = rustMethodName(exampleEntity.name)
|
|
62
62
|
const opnames = entityOps(exampleEntity)
|
|
63
63
|
// Model-driven id key: `idF` is the entity's id-like MATCH field name, or
|
|
64
64
|
// null. `dataIdF` is the id on the RETURNED record's data type.
|
|
@@ -91,7 +91,7 @@ match client.${method}(Value::Noval).list(Value::Noval, Value::Noval) {
|
|
|
91
91
|
const neName = nom(nestedEntity, 'Name')
|
|
92
92
|
const neArticle = /^[aeiou]/i.test(neName) ? 'an' : 'a'
|
|
93
93
|
const neVar = rustVarName(nestedEntity.name)
|
|
94
|
-
const neMethod =
|
|
94
|
+
const neMethod = rustMethodName(nestedEntity.name)
|
|
95
95
|
|
|
96
96
|
const neIdF = entityIdField(nestedEntity)
|
|
97
97
|
const neRequired = opRequestShape(nestedEntity, 'load').items
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getModelPath,
|
|
7
7
|
} from '@voxgig/apidef'
|
|
8
8
|
|
|
9
|
-
import { crateIdent, rustVarName } from './utility_rust'
|
|
9
|
+
import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
// Type names come from the shared canonToType 'rust' column (single source of truth).
|
|
@@ -120,7 +120,7 @@ let client = test_sdk(Value::Noval, Value::Noval);
|
|
|
120
120
|
|
|
121
121
|
// Entity factory methods
|
|
122
122
|
publishedEntities.map((ent: any) => {
|
|
123
|
-
Content(`#### \`${
|
|
123
|
+
Content(`#### \`${rustMethodName(ent.name)}(entopts: Value) -> Rc<${ent.Name}Entity>\`
|
|
124
124
|
|
|
125
125
|
Create a new \`${ent.Name}Entity\` instance. Pass \`Value::Noval\` for no
|
|
126
126
|
initial options.
|
|
@@ -168,7 +168,7 @@ Prepare a fetch definition without sending. Returns the fetchdef on \`Ok\`.
|
|
|
168
168
|
const fields = ent.fields || []
|
|
169
169
|
const idF = entityIdField(ent)
|
|
170
170
|
const eVar = rustVarName(ent.name)
|
|
171
|
-
const method =
|
|
171
|
+
const method = rustMethodName(ent.name)
|
|
172
172
|
|
|
173
173
|
Content(`
|
|
174
174
|
---
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
nom,
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
|
-
import { crateIdent, rustVarName } from './utility_rust'
|
|
10
|
+
import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
// A type-correct rust expression constructing a voxgig struct Value for a
|
|
@@ -46,7 +46,7 @@ let client = ${ctor};
|
|
|
46
46
|
if (exampleEntity) {
|
|
47
47
|
const eName = nom(exampleEntity, 'Name')
|
|
48
48
|
const eVar = rustVarName(exampleEntity.name)
|
|
49
|
-
const method =
|
|
49
|
+
const method = rustMethodName(exampleEntity.name)
|
|
50
50
|
const opnames = Object.keys(exampleEntity.op || {})
|
|
51
51
|
// Model-driven id key: null when the entity has no id-like field, in which
|
|
52
52
|
// case the load example takes no match argument.
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
nom,
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
|
-
import { crateIdent, rustVarName } from './utility_rust'
|
|
10
|
+
import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
// A type-correct rust expression constructing a voxgig struct Value for a
|
|
@@ -40,7 +40,7 @@ let client = test_sdk(Value::Noval, Value::Noval);
|
|
|
40
40
|
|
|
41
41
|
if (exampleEntity && primaryOp) {
|
|
42
42
|
const eName = nom(exampleEntity, 'Name')
|
|
43
|
-
const method =
|
|
43
|
+
const method = rustMethodName(exampleEntity.name)
|
|
44
44
|
const idF = entityIdField(exampleEntity)
|
|
45
45
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
46
46
|
let arg = 'Value::Noval'
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from '@voxgig/sdkgen'
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
import { rustVarName } from './utility_rust'
|
|
24
|
+
import { rustVarName, rustMethodName } from './utility_rust'
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
// Rust's GenCtx mirrors the shared shape (see TestEntity_ts.ts) plus the
|
|
@@ -100,7 +100,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
100
100
|
}
|
|
101
101
|
`
|
|
102
102
|
|
|
103
|
-
const method =
|
|
103
|
+
const method = rustMethodName(entity.name)
|
|
104
104
|
const evar = rustVarName(entity.name)
|
|
105
105
|
|
|
106
106
|
File({ name: entity.name + '_entity_test.' + target.ext }, () => {
|
|
@@ -330,7 +330,7 @@ const generateCreate: OpGen = (ctx, step, index) => {
|
|
|
330
330
|
Content(` // CREATE
|
|
331
331
|
`)
|
|
332
332
|
if (needsEnt) {
|
|
333
|
-
Content(` let ${entvar} = client.${
|
|
333
|
+
Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
|
|
334
334
|
`)
|
|
335
335
|
}
|
|
336
336
|
|
|
@@ -381,7 +381,7 @@ const generateList: OpGen = (ctx, step, index) => {
|
|
|
381
381
|
Content(` // LIST
|
|
382
382
|
`)
|
|
383
383
|
if (needsEnt) {
|
|
384
|
-
Content(` let ${entvar} = client.${
|
|
384
|
+
Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
|
|
385
385
|
`)
|
|
386
386
|
}
|
|
387
387
|
|
|
@@ -464,7 +464,7 @@ const generateUpdate: OpGen = (ctx, step, index) => {
|
|
|
464
464
|
Content(` // UPDATE
|
|
465
465
|
`)
|
|
466
466
|
if (needsEnt) {
|
|
467
|
-
Content(` let ${entvar} = client.${
|
|
467
|
+
Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
|
|
468
468
|
`)
|
|
469
469
|
}
|
|
470
470
|
Content(` let ${datavar}_up = Value::empty_map();
|
|
@@ -568,7 +568,7 @@ const generateLoad: OpGen = (ctx, step, index) => {
|
|
|
568
568
|
Content(` // LOAD
|
|
569
569
|
`)
|
|
570
570
|
if (!hasEntVar) {
|
|
571
|
-
Content(` let ${entvar} = client.${
|
|
571
|
+
Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
|
|
572
572
|
`)
|
|
573
573
|
}
|
|
574
574
|
if (!hasSrcData && hasEntId) {
|
|
@@ -626,7 +626,7 @@ const generateRemove: OpGen = (ctx, step, index) => {
|
|
|
626
626
|
Content(` // REMOVE
|
|
627
627
|
`)
|
|
628
628
|
if (needsEnt) {
|
|
629
|
-
Content(` let ${entvar} = client.${
|
|
629
|
+
Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
|
|
630
630
|
`)
|
|
631
631
|
}
|
|
632
632
|
// Always match the prior-created entity by id to avoid mock-order flakes.
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
getModelPath,
|
|
33
33
|
} from '@voxgig/apidef'
|
|
34
34
|
|
|
35
|
-
import { swiftVarName } from './utility_swift'
|
|
35
|
+
import { swiftVarName , swiftTargetDir, swiftTestDir } from './utility_swift'
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
const LANG = 'swift'
|
|
@@ -89,7 +89,7 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
|
|
|
89
89
|
warnEntityTypeCollisions(entity, log, LANG)
|
|
90
90
|
|
|
91
91
|
Folder({ name: 'Sources' }, () => {
|
|
92
|
-
Folder({ name:
|
|
92
|
+
Folder({ name: swiftTargetDir(model) }, () => {
|
|
93
93
|
Folder({ name: 'entity' }, () => {
|
|
94
94
|
|
|
95
95
|
File({ name: model.const.Name + 'Types.' + target.ext }, () => {
|
|
@@ -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, camelify, names,
|
|
6
8
|
Content, File, Folder, Fragment,
|
|
@@ -33,7 +35,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
33
35
|
|
|
34
36
|
// Entity files join the copied runtime under Sources/ProjectNameSDK/entity.
|
|
35
37
|
Folder({ name: 'Sources' }, () => {
|
|
36
|
-
Folder({ name:
|
|
38
|
+
Folder({ name: swiftTargetDir(model) }, () => {
|
|
37
39
|
Folder({ name: 'entity' }, () => {
|
|
38
40
|
|
|
39
41
|
File({ name: cls + '.' + target.ext }, () => {
|
|
@@ -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 }, () => {
|
|
@@ -26,17 +26,74 @@ defmodule Voxgig.Struct do
|
|
|
26
26
|
# ---------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
@heap :vox_struct_heap
|
|
29
|
-
|
|
29
|
+
@heap_owner :vox_struct_heap_owner
|
|
30
|
+
|
|
31
|
+
# A named ETS table is owned by the process that created it and dies with
|
|
32
|
+
# that process. The values here are HANDLES into it - {:vmap, id} - and they
|
|
33
|
+
# are passed across process boundaries freely: ExUnit runs every test in its
|
|
34
|
+
# own process, and a generated SDK caches its config in :persistent_term,
|
|
35
|
+
# which outlives all of them. So the table has to outlive its creator, or a
|
|
36
|
+
# handle that is still very much alive resolves into a fresh empty table.
|
|
37
|
+
#
|
|
38
|
+
# Adding ensure_heap to hget stopped that raising ArgumentError, but a silent
|
|
39
|
+
# nil turned out to be worse than the raise: the generated config's staleness
|
|
40
|
+
# check (`usable?`) was written to read the raise as "rebuild me", so once the
|
|
41
|
+
# raise stopped, a dead handle was cached as good and clone/1 crashed further
|
|
42
|
+
# down with "Enumerable not implemented for nil of type Atom" - nothing in the
|
|
43
|
+
# message pointing at the heap.
|
|
44
|
+
#
|
|
45
|
+
# The table is therefore owned by a dedicated process that never exits, and
|
|
46
|
+
# every id stays valid for the life of the VM.
|
|
30
47
|
defp ensure_heap do
|
|
31
48
|
case :ets.whereis(@heap) do
|
|
32
|
-
:undefined ->
|
|
49
|
+
:undefined -> start_heap()
|
|
50
|
+
_ -> :ok
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
defp start_heap do
|
|
55
|
+
caller = self()
|
|
56
|
+
|
|
57
|
+
spawn(fn ->
|
|
58
|
+
owner =
|
|
33
59
|
try do
|
|
34
|
-
|
|
60
|
+
Process.register(self(), @heap_owner)
|
|
61
|
+
true
|
|
35
62
|
rescue
|
|
36
|
-
ArgumentError ->
|
|
63
|
+
ArgumentError -> false
|
|
37
64
|
end
|
|
38
65
|
|
|
39
|
-
|
|
66
|
+
if owner do
|
|
67
|
+
case :ets.whereis(@heap) do
|
|
68
|
+
:undefined -> :ets.new(@heap, [:set, :public, :named_table])
|
|
69
|
+
_ -> :ok
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
send(caller, :vox_heap_ready)
|
|
74
|
+
|
|
75
|
+
# Hold the table for the life of the VM. This process does nothing else.
|
|
76
|
+
if owner, do: Process.sleep(:infinity)
|
|
77
|
+
end)
|
|
78
|
+
|
|
79
|
+
receive do
|
|
80
|
+
:vox_heap_ready -> :ok
|
|
81
|
+
after
|
|
82
|
+
5_000 -> :ok
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
await_heap(1_000)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The loser of a registration race reports ready before the winner has
|
|
89
|
+
# necessarily reached :ets.new, so wait on the table, not on the message.
|
|
90
|
+
defp await_heap(0), do: :ok
|
|
91
|
+
|
|
92
|
+
defp await_heap(n) do
|
|
93
|
+
case :ets.whereis(@heap) do
|
|
94
|
+
:undefined ->
|
|
95
|
+
Process.sleep(1)
|
|
96
|
+
await_heap(n - 1)
|
|
40
97
|
|
|
41
98
|
_ ->
|
|
42
99
|
:ok
|
|
@@ -50,13 +107,10 @@ defmodule Voxgig.Struct do
|
|
|
50
107
|
id
|
|
51
108
|
end
|
|
52
109
|
|
|
53
|
-
# ensure_heap here too, not only in alloc
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# to an existing ETS table") from whichever unrelated test happened to run
|
|
58
|
-
# second, which is why this surfaced as a pipeline/netsim failure rather than
|
|
59
|
-
# as anything to do with the heap.
|
|
110
|
+
# ensure_heap here too, not only in alloc, so a read can never hit a missing
|
|
111
|
+
# table. With a permanent owner (above) that should not arise; before there
|
|
112
|
+
# was one, it raised ArgumentError from whichever unrelated test happened to
|
|
113
|
+
# run second.
|
|
60
114
|
defp hget(id) do
|
|
61
115
|
ensure_heap()
|
|
62
116
|
|
|
@@ -8,10 +8,30 @@ build:
|
|
|
8
8
|
# tests pass, due to *MapRef/*ListRef cross-references in arena teardown. We
|
|
9
9
|
# filter the output: if "N/N tests passed" with N==total, treat as success.
|
|
10
10
|
# `--summary all` is required so the count is printed.
|
|
11
|
+
#
|
|
12
|
+
# A TEST THAT DID NOT COMPILE IS NOT IN THE TALLY. The tolerance above is for a
|
|
13
|
+
# crash AFTER the tests ran, and it must never cover a build that failed: a
|
|
14
|
+
# compile error means those test binaries never ran, so they never reach
|
|
15
|
+
# "total", and "60/66 passed; 6 skipped" reads as a clean 66/66 while five of
|
|
16
|
+
# thirteen build steps are red. That is exactly how a `.undef` tag that does
|
|
17
|
+
# not exist in JsonValue shipped in 4.2.2 and stayed green in every sweep
|
|
18
|
+
# afterwards. So: if any build step failed, this target fails, whatever the
|
|
19
|
+
# test counts say.
|
|
11
20
|
test:
|
|
12
21
|
@out=$$(zig build test --summary all 2>&1); code=$$?; \
|
|
13
22
|
echo "$$out"; \
|
|
14
23
|
if [ $$code -eq 0 ]; then exit 0; fi; \
|
|
24
|
+
steps=$$(echo "$$out" | grep -oE '[0-9]+/[0-9]+ steps succeeded' | tail -1); \
|
|
25
|
+
sdone=$$(echo "$$steps" | sed -E 's#([0-9]+)/([0-9]+).*#\1#'); \
|
|
26
|
+
stotal=$$(echo "$$steps" | sed -E 's#([0-9]+)/([0-9]+).*#\2#'); \
|
|
27
|
+
if [ -n "$$steps" ] && [ "$$sdone" != "$$stotal" ]; then \
|
|
28
|
+
echo "zig: $$steps — a build step failed, so the test tally is incomplete"; \
|
|
29
|
+
exit 1; \
|
|
30
|
+
fi; \
|
|
31
|
+
if echo "$$out" | grep -q 'error: the following command failed with .* compilation errors'; then \
|
|
32
|
+
echo "zig: compilation errors — not a teardown crash"; \
|
|
33
|
+
exit 1; \
|
|
34
|
+
fi; \
|
|
15
35
|
summary=$$(echo "$$out" | grep -oE '[0-9]+/[0-9]+ tests passed(; [0-9]+ skipped)?' | tail -1); \
|
|
16
36
|
passed=$$(echo "$$summary" | sed -E 's#([0-9]+)/([0-9]+).*#\1#'); \
|
|
17
37
|
total=$$(echo "$$summary" | sed -E 's#([0-9]+)/([0-9]+).*#\2#'); \
|
|
@@ -105,7 +105,7 @@ fn fixIds(_: Allocator, key: ?[]const u8, val: Value, _: Value, path: []const []
|
|
|
105
105
|
// while the fixture plainly held two. Mirrors the go/ts/lua/php mocks, which
|
|
106
106
|
// already wrap; rust, c and zig were the three that did not.
|
|
107
107
|
fn envelope(ctx: *Context, data: Value) Value {
|
|
108
|
-
if (data == .
|
|
108
|
+
if (data == .null) return data;
|
|
109
109
|
const restf = h.getpath(&.{ "transform", "res" }, ctx.point);
|
|
110
110
|
if (restf != .string) return data;
|
|
111
111
|
const spec = restf.string;
|