@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
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
// instantiate features named in the options (mirrors Config_rust).
|
|
32
32
|
const Config = cmp(async function Config(props: any) {
|
|
33
33
|
const ctx$ = props.ctx$
|
|
34
|
+
const target = props.target
|
|
34
35
|
|
|
35
36
|
const model: Model = ctx$.model
|
|
36
37
|
|
|
@@ -72,20 +73,25 @@ const Config = cmp(async function Config(props: any) {
|
|
|
72
73
|
relations: n.relations,
|
|
73
74
|
}, true), a), {})
|
|
74
75
|
|
|
76
|
+
// The same config as an OBJECT, built by the shared helper so this target's
|
|
77
|
+
// literal and the data that replaces it above the threshold are the same
|
|
78
|
+
// config by construction. The JSON is what the threshold is measured on -
|
|
79
|
+
// emitted source size varies by language, the model does not. Passing
|
|
80
|
+
// target.name opts this target into the main slug/version/target identity
|
|
81
|
+
// fields (read by station's descriptor - see configDefinition).
|
|
82
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
83
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
84
|
+
|
|
75
85
|
const config = {
|
|
76
|
-
main
|
|
86
|
+
// main from configDefinition's def, not re-derived here, so the literal
|
|
87
|
+
// rep and the data rep cannot disagree on identity (the Config_ts
|
|
88
|
+
// #MainMeta discipline): name plus slug/version/target.
|
|
89
|
+
main: configDef.main,
|
|
77
90
|
feature: featureConfig,
|
|
78
91
|
options,
|
|
79
92
|
entity: entityConfig,
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
// The same config as an OBJECT, built by the shared helper so this target's
|
|
83
|
-
// literal and the data that replaces it above the threshold are the same
|
|
84
|
-
// config by construction. The JSON is what the threshold is measured on -
|
|
85
|
-
// emitted source size varies by language, the model does not.
|
|
86
|
-
const { json: configJson } = configDefinition(model)
|
|
87
|
-
const asData = isConfigData(configJson, configReprSetting(model))
|
|
88
|
-
|
|
89
95
|
File({ name: 'config.c' }, () => {
|
|
90
96
|
|
|
91
97
|
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
@@ -137,8 +143,6 @@ voxgig_value* shared_config(void) {
|
|
|
137
143
|
}
|
|
138
144
|
return shared_config_val;
|
|
139
145
|
}
|
|
140
|
-
|
|
141
|
-
Feature* make_feature(const char* name) {
|
|
142
146
|
`)
|
|
143
147
|
}
|
|
144
148
|
else {
|
|
@@ -174,8 +178,6 @@ voxgig_value* shared_config(void) {
|
|
|
174
178
|
}
|
|
175
179
|
return shared_config_val;
|
|
176
180
|
}
|
|
177
|
-
|
|
178
|
-
Feature* make_feature(const char* name) {
|
|
179
181
|
`)
|
|
180
182
|
}
|
|
181
183
|
|
|
@@ -192,7 +194,26 @@ Feature* make_feature(const char* name) {
|
|
|
192
194
|
each(feature, (f: any) => {
|
|
193
195
|
if (f.name && f.name !== 'base') featureNames.add(f.name)
|
|
194
196
|
})
|
|
195
|
-
|
|
197
|
+
const sortedNames = Array.from(featureNames).sort()
|
|
198
|
+
|
|
199
|
+
// Constructor prototypes for every declared feature. sdk.h declares the
|
|
200
|
+
// BUNDLED set, but an externally-installed feature (e.g. station, whose
|
|
201
|
+
// source arrives by package overlay) is not in sdk.h — and without a
|
|
202
|
+
// prototype its make_feature arm is an implicit int-returning
|
|
203
|
+
// declaration: a warning that happens to link on gcc 13, a hard error on
|
|
204
|
+
// gcc 14+/clang 15+. Redeclaring the bundled ones is identical-prototype
|
|
205
|
+
// C, which is legal and keeps this a single sorted list.
|
|
206
|
+
Content(`
|
|
207
|
+
`)
|
|
208
|
+
for (const fname of sortedNames) {
|
|
209
|
+
Content(`Feature* feature_${fname}_new(void);
|
|
210
|
+
`)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
Content(`
|
|
214
|
+
Feature* make_feature(const char* name) {
|
|
215
|
+
`)
|
|
216
|
+
for (const fname of sortedNames) {
|
|
196
217
|
Content(` if (strcmp(name, "${fname}") == 0) return feature_${fname}_new();
|
|
197
218
|
`)
|
|
198
219
|
}
|
|
@@ -3,9 +3,8 @@ import {
|
|
|
3
3
|
Content,
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
|
+
configDefinition,
|
|
6
7
|
each,
|
|
7
|
-
isAuthActive,
|
|
8
|
-
resolveAuthPrefix,
|
|
9
8
|
} from '@voxgig/sdkgen'
|
|
10
9
|
|
|
11
10
|
|
|
@@ -17,7 +16,6 @@ import {
|
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
import {
|
|
20
|
-
cleanModel,
|
|
21
19
|
cppConfigLiterals,
|
|
22
20
|
} from './utility_cpp'
|
|
23
21
|
|
|
@@ -32,50 +30,15 @@ const Config = cmp(async function Config(props: any) {
|
|
|
32
30
|
|
|
33
31
|
const model: Model = ctx$.model
|
|
34
32
|
|
|
35
|
-
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
36
33
|
const feature = getModelPath(model, `main.${KIT}.feature`)
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const featureConfig: Record<string, any> = {}
|
|
47
|
-
each(feature, (f: any) => {
|
|
48
|
-
featureConfig[f.name] = cleanModel(f.config || {})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const optionsEntity: Record<string, any> = {}
|
|
52
|
-
each(entity, (ent: any) => {
|
|
53
|
-
optionsEntity[ent.name] = {}
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const options: Record<string, any> = {
|
|
57
|
-
base: baseUrl,
|
|
58
|
-
}
|
|
59
|
-
if (authActive) {
|
|
60
|
-
options.auth = { prefix: authPrefix }
|
|
61
|
-
}
|
|
62
|
-
options.headers = headers
|
|
63
|
-
options.entity = optionsEntity
|
|
64
|
-
|
|
65
|
-
const entityConfig = Object.values(entity || {}).reduce((a: any, n: any) => (
|
|
66
|
-
a[n.name] = cleanModel({
|
|
67
|
-
fields: n.fields,
|
|
68
|
-
name: n.name,
|
|
69
|
-
op: n.op,
|
|
70
|
-
relations: n.relations,
|
|
71
|
-
}, true), a), {})
|
|
72
|
-
|
|
73
|
-
const config = {
|
|
74
|
-
main: { name: model.const.Name },
|
|
75
|
-
feature: featureConfig,
|
|
76
|
-
options,
|
|
77
|
-
entity: entityConfig,
|
|
78
|
-
}
|
|
35
|
+
// The embedded config, built by the shared helper so this target's shapes
|
|
36
|
+
// and identity fields stay in step with the ts reference by construction.
|
|
37
|
+
// Passing target.name opts cpp into main.slug/version/target (the station
|
|
38
|
+
// descriptor identity, ts/src/utility.ts configDefinition) - cpp has only
|
|
39
|
+
// the data rep (one chunked JSON literal), so this is the whole #MainMeta
|
|
40
|
+
// story for this target.
|
|
41
|
+
const { def: configDef } = configDefinition(model, target.name)
|
|
79
42
|
|
|
80
43
|
File({ name: 'config.' + target.ext }, () => {
|
|
81
44
|
|
|
@@ -104,7 +67,7 @@ namespace sdk {
|
|
|
104
67
|
|
|
105
68
|
inline const char* config_json() {
|
|
106
69
|
return
|
|
107
|
-
${cppConfigLiterals(
|
|
70
|
+
${cppConfigLiterals(configDef)};
|
|
108
71
|
}
|
|
109
72
|
|
|
110
73
|
inline Value makeConfig() { return vs::parse_json(config_json()); }
|
|
@@ -39,7 +39,13 @@ const Config = cmp(async function Config(props: any) {
|
|
|
39
39
|
// literal and the data that replaces it above the threshold are the same
|
|
40
40
|
// config by construction. The JSON is what the threshold is measured on -
|
|
41
41
|
// emitted source size varies by language, the model does not.
|
|
42
|
-
|
|
42
|
+
//
|
|
43
|
+
// Passing target.name opts this target into main.slug / main.version /
|
|
44
|
+
// main.target (the three station descriptor fields, station design §4):
|
|
45
|
+
// both reps flow from this one call - the literal via formatCsMap(configDef)
|
|
46
|
+
// and the data rep via csStringLiteral(configJson) - so they cannot
|
|
47
|
+
// disagree on identity (mirrors Config_ts's #MainMeta block).
|
|
48
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
43
49
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
44
50
|
|
|
45
51
|
File({ name: 'Config.' + target.ext }, () => {
|
|
@@ -71,8 +71,10 @@ const Config = cmp(async function Config(props: any) {
|
|
|
71
71
|
// The same config as an OBJECT, built by the shared helper so this target's
|
|
72
72
|
// literal and the data that replaces it above the threshold are the same
|
|
73
73
|
// config by construction. The JSON is what the threshold is measured on -
|
|
74
|
-
// emitted source size varies by language, the model does not.
|
|
75
|
-
|
|
74
|
+
// emitted source size varies by language, the model does not. Passing
|
|
75
|
+
// target.name opts this target into the main slug/version/target identity
|
|
76
|
+
// fields (read by station's descriptor - see configDefinition).
|
|
77
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
76
78
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
77
79
|
|
|
78
80
|
File({ name: 'Config.' + target.ext }, () => {
|
|
@@ -115,6 +117,16 @@ const Config = cmp(async function Config(props: any) {
|
|
|
115
117
|
// these; this one did not.
|
|
116
118
|
...ctx$.stdrep,
|
|
117
119
|
|
|
120
|
+
// Identity beyond the camel Name: slug/version/target (station
|
|
121
|
+
// descriptor inputs). Values from configDefinition's def, not
|
|
122
|
+
// re-derived here, so the literal rep and the data rep cannot
|
|
123
|
+
// disagree (the Config_ts #MainMeta discipline).
|
|
124
|
+
'// #MainMeta': () => {
|
|
125
|
+
Line(` 'slug': ${dartValue(configDef.main.slug)},`)
|
|
126
|
+
Line(` 'version': ${dartValue(configDef.main.version)},`)
|
|
127
|
+
Line(` 'target': ${dartValue(configDef.main.target)},`)
|
|
128
|
+
},
|
|
129
|
+
|
|
118
130
|
// The whole options map from the canonical definition. Assembling it
|
|
119
131
|
// slot by slot lost `options.server` entirely, so a spec with a
|
|
120
132
|
// templated server URL described a different config either side of the
|
|
@@ -52,6 +52,11 @@ class Config {
|
|
|
52
52
|
return fc();
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// False for a feature added at runtime via options.extend (station's
|
|
56
|
+
// adopt path) - the constructor uses this to skip makeFeature for names
|
|
57
|
+
// no generated class backs.
|
|
58
|
+
bool hasFeature(String fn) => null != FEATURE_CLASS[fn];
|
|
59
|
+
|
|
55
60
|
// The same fields the literal declares - same names, same types, same
|
|
56
61
|
// finalness - so callers cannot tell which representation they were given.
|
|
57
62
|
final Map<String, dynamic> main;
|
|
@@ -16,8 +16,14 @@ class Config {
|
|
|
16
16
|
return fc();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// False for a feature added at runtime via options.extend (station's
|
|
20
|
+
// adopt path) - the constructor uses this to skip makeFeature for names
|
|
21
|
+
// no generated class backs.
|
|
22
|
+
bool hasFeature(String fn) => null != FEATURE_CLASS[fn];
|
|
23
|
+
|
|
19
24
|
final Map<String, dynamic> main = <String, dynamic>{
|
|
20
25
|
'name': 'ProjectName',
|
|
26
|
+
// #MainMeta
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
final Map<String, dynamic> feature = <String, dynamic>{
|
|
@@ -69,19 +69,28 @@ class ProjectNameSDK {
|
|
|
69
69
|
// `test` feature installs the base mock transport and the transport
|
|
70
70
|
// features (retry/cache/netsim/proxy/ratelimit) wrap whatever is current,
|
|
71
71
|
// so `test` must be added before them to sit at the base of the chain.
|
|
72
|
+
final List extendList =
|
|
73
|
+
_options['extend'] is List ? _options['extend'] : [];
|
|
74
|
+
|
|
72
75
|
final featureorder =
|
|
73
76
|
struct.getpath(_options, '__derived__.featureorder') ?? [];
|
|
74
77
|
for (final fname in featureorder) {
|
|
75
78
|
final fopts = _options['feature'][fname];
|
|
76
79
|
if (fopts is Map && true == fopts['active']) {
|
|
80
|
+
// An active name with no generated class is legal when an
|
|
81
|
+
// extend-supplied instance carries that name (station's adopt
|
|
82
|
+
// path): the instance is added below, positioned by its own
|
|
83
|
+
// __after__ entry, so skip it here rather than fail construction.
|
|
84
|
+
if (!config.hasFeature(fname.toString()) &&
|
|
85
|
+
extendList.any((f) => fname.toString() == f.name.toString())) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
77
88
|
featureAdd(rootctx, config.makeFeature(fname.toString()));
|
|
78
89
|
}
|
|
79
90
|
}
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
featureAdd(rootctx, f);
|
|
84
|
-
}
|
|
92
|
+
for (final f in extendList) {
|
|
93
|
+
featureAdd(rootctx, f);
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
for (final f in features) {
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
|
|
30
30
|
const Config = cmp(async function Config(props: any) {
|
|
31
31
|
const ctx$ = props.ctx$
|
|
32
|
+
const target = props.target
|
|
32
33
|
const model: Model = ctx$.model
|
|
33
34
|
|
|
34
35
|
const Name = model.const.Name
|
|
@@ -60,7 +61,11 @@ const Config = cmp(async function Config(props: any) {
|
|
|
60
61
|
// target's literal and the data that replaces it above the threshold are
|
|
61
62
|
// the same config by construction. The JSON is what the threshold is
|
|
62
63
|
// measured on - emitted source size varies by language, the model does not.
|
|
63
|
-
|
|
64
|
+
// Passing target.name opts in to the main slug/version/target identity
|
|
65
|
+
// fields (station descriptor input, mirrors Config_ts) - both reps below
|
|
66
|
+
// render from this same def, so the data and literal branches pick the
|
|
67
|
+
// fields up together.
|
|
68
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
64
69
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
65
70
|
|
|
66
71
|
File({ name: 'config.ex' }, () => {
|
|
@@ -159,7 +164,12 @@ end
|
|
|
159
164
|
defmodule ${Name}.Config do
|
|
160
165
|
def make_config do
|
|
161
166
|
${Name}.Helpers.deep(%{
|
|
162
|
-
"main" => %{
|
|
167
|
+
"main" => %{
|
|
168
|
+
"name" => ${elixirString(Name)},
|
|
169
|
+
"slug" => ${elixirString(String(configDef.main.slug))},
|
|
170
|
+
"version" => ${elixirString(String(configDef.main.version))},
|
|
171
|
+
"target" => ${elixirString(String(configDef.main.target))}
|
|
172
|
+
},
|
|
163
173
|
"feature" => %{
|
|
164
174
|
`)
|
|
165
175
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Content,
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
|
+
collectDeps,
|
|
6
7
|
pkgDescription,
|
|
7
8
|
repoInfo,
|
|
8
9
|
packageVersion,
|
|
@@ -15,7 +16,11 @@ import type {
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
// Generate mix.exs for the Elixir SDK. The vendored struct library has zero
|
|
18
|
-
// third-party runtime deps, so
|
|
19
|
+
// third-party runtime deps, so a bare SDK ships with none - but dependencies
|
|
20
|
+
// declared by the model DO flow: the target's own `deps` block plus any
|
|
21
|
+
// feature-declared elixir deps (e.g. the station feature's voxgig_station),
|
|
22
|
+
// via the shared collectDeps helper, exactly like rust/dart/go. A model with
|
|
23
|
+
// no deps emits the byte-identical `defp deps, do: []` it always has.
|
|
19
24
|
const Package = cmp(async function Package(props: any) {
|
|
20
25
|
const ctx$ = props.ctx$
|
|
21
26
|
const target = props.target
|
|
@@ -27,6 +32,31 @@ const Package = cmp(async function Package(props: any) {
|
|
|
27
32
|
const app = String(model.const.name).replace(/-/g, '_')
|
|
28
33
|
const { repoUrl } = repoInfo(model)
|
|
29
34
|
|
|
35
|
+
// Render one mix deps entry per collected dependency. Hex package names
|
|
36
|
+
// share the atom alphabet, but a hyphen in a model dep name is mapped to
|
|
37
|
+
// '_' rather than emitted broken. Version -> Mix requirement: an explicit
|
|
38
|
+
// operator is kept (spaced, the mix convention: '>=0.0.1' -> '>= 0.0.1');
|
|
39
|
+
// a bare version gets hex's customary '~>'; a target dep with no version
|
|
40
|
+
// accepts anything. kind: 'dev' scopes to [:dev, :test]; 'peer'/'prod'
|
|
41
|
+
// (and anything else) are runtime deps - mix has no peer notion.
|
|
42
|
+
const entries: string[] = []
|
|
43
|
+
for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
|
|
44
|
+
const atom = String(d.name).replace(/-/g, '_')
|
|
45
|
+
const version = null == d.version || '' === String(d.version).trim()
|
|
46
|
+
? '>= 0.0.0'
|
|
47
|
+
: mixRequirement(String(d.version).trim())
|
|
48
|
+
const scope = 'dev' === (d.raw as any)?.kind ? ', only: [:dev, :test]' : ''
|
|
49
|
+
entries.push(`{:${atom}, ${JSON.stringify(version)}${scope}}`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const depsBlock = 0 === entries.length
|
|
53
|
+
? ` defp deps, do: []`
|
|
54
|
+
: ` defp deps do
|
|
55
|
+
[
|
|
56
|
+
${entries.join(',\n ')}
|
|
57
|
+
]
|
|
58
|
+
end`
|
|
59
|
+
|
|
30
60
|
File({ name: 'mix.exs' }, () => {
|
|
31
61
|
Content(`defmodule ${Name}.MixProject do
|
|
32
62
|
use Mix.Project
|
|
@@ -46,7 +76,7 @@ const Package = cmp(async function Package(props: any) {
|
|
|
46
76
|
|
|
47
77
|
def application, do: [extra_applications: [:inets, :ssl]]
|
|
48
78
|
|
|
49
|
-
|
|
79
|
+
${depsBlock}
|
|
50
80
|
|
|
51
81
|
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
|
52
82
|
defp elixirc_paths(_), do: ["lib"]
|
|
@@ -63,6 +93,15 @@ end
|
|
|
63
93
|
})
|
|
64
94
|
|
|
65
95
|
|
|
96
|
+
function mixRequirement(version: string): string {
|
|
97
|
+
const m = version.match(/^(>=|<=|~>|==|!=|>|<)\s*(.+)$/)
|
|
98
|
+
if (null != m) {
|
|
99
|
+
return m[1] + ' ' + m[2]
|
|
100
|
+
}
|
|
101
|
+
return '~> ' + version
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
66
105
|
export {
|
|
67
106
|
Package
|
|
68
107
|
}
|
|
@@ -69,7 +69,7 @@ const Config = cmp(async function Config(props: any) {
|
|
|
69
69
|
// literal and the data that replaces it above the threshold are the same
|
|
70
70
|
// config by construction. The JSON is what the threshold is measured on -
|
|
71
71
|
// emitted source size varies by language, the model does not.
|
|
72
|
-
const { json: configJson } = configDefinition(model)
|
|
72
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
73
73
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
74
74
|
|
|
75
75
|
File({ name: 'config.' + target.ext }, () => {
|
|
@@ -154,6 +154,10 @@ import (
|
|
|
154
154
|
|
|
155
155
|
`)
|
|
156
156
|
|
|
157
|
+
// main slug/version/target come from configDefinition's def, not
|
|
158
|
+
// re-derived here, so the literal rep and the data rep cannot disagree
|
|
159
|
+
// on identity (mirrors Config_ts's #MainMeta block; station's
|
|
160
|
+
// descriptor reads all three).
|
|
157
161
|
Content(`// MakeConfig builds a fresh, fully materialised config map. Every call
|
|
158
162
|
// rebuilds the whole structure, so prefer SharedConfig unless you need a
|
|
159
163
|
// private copy you intend to mutate.
|
|
@@ -161,6 +165,9 @@ func MakeConfig() map[string]any {
|
|
|
161
165
|
return map[string]any{
|
|
162
166
|
"main": map[string]any{
|
|
163
167
|
"name": "${model.const.Name}",
|
|
168
|
+
"slug": ${JSON.stringify(configDef.main.slug)},
|
|
169
|
+
"version": ${JSON.stringify(configDef.main.version)},
|
|
170
|
+
"target": ${JSON.stringify(configDef.main.target)},
|
|
164
171
|
},
|
|
165
172
|
"feature": map[string]any{
|
|
166
173
|
`)
|
|
@@ -37,6 +37,24 @@ const ReadmeIntro = cmp(function ReadmeIntro(props: any) {
|
|
|
37
37
|
const opList = (opNames.length ? opNames : ['list', 'load'])
|
|
38
38
|
.map((o) => '`' + cap(o) + '`').join(', ')
|
|
39
39
|
|
|
40
|
+
// Sibling surfaces, MODEL-DRIVEN. This line used to hardcode "the CLI, and
|
|
41
|
+
// MCP server" — those are the `go-cli` and `go-mcp` targets, and this project
|
|
42
|
+
// has neither. So both per-language READMEs advertised surfaces nothing here
|
|
43
|
+
// generates, while the top-level Surfaces table (which does gate on active
|
|
44
|
+
// targets) listed only ts/ and go/. Name what is actually active, and drop
|
|
45
|
+
// the sentence entirely when this is the only surface, so it cannot outrun
|
|
46
|
+
// the model again.
|
|
47
|
+
const targets = getModelPath(model, `main.${KIT}.target`) || {}
|
|
48
|
+
const siblings = Object.entries(targets)
|
|
49
|
+
.filter(([name, t]: any) => name !== target.name && false !== t.active)
|
|
50
|
+
.map(([name]: any) => name)
|
|
51
|
+
.sort()
|
|
52
|
+
|
|
53
|
+
const siblingNote = 0 === siblings.length ? '' : `
|
|
54
|
+
> Also generated from this model: ${siblings.map((s: string) => '`' + s + '`').join(', ')} — see
|
|
55
|
+
> the [top-level README](../README.md).
|
|
56
|
+
`
|
|
57
|
+
|
|
40
58
|
Content(`# ${model.Name} ${target.title} SDK
|
|
41
59
|
|
|
42
60
|
${tagline}
|
|
@@ -44,10 +62,7 @@ ${tagline}
|
|
|
44
62
|
The ${target.title} SDK for the ${model.Name} API — an entity-oriented client using standard Go conventions. No generics required; data flows as \`map[string]any\`.
|
|
45
63
|
|
|
46
64
|
It exposes the API as capitalised, semantic **Entities** — e.g. \`client.${eName}(nil)\` — each with the same small set of operations (${opList}) instead of raw URL paths and query strings. You call meaning, not endpoints, which keeps the cognitive load low.
|
|
47
|
-
|
|
48
|
-
> Other languages, the CLI, and MCP server live alongside this one — see
|
|
49
|
-
> the [top-level README](../README.md).
|
|
50
|
-
|
|
65
|
+
${siblingNote}
|
|
51
66
|
`)
|
|
52
67
|
})
|
|
53
68
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Content,
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
|
+
configDefinition,
|
|
6
7
|
each,
|
|
7
8
|
isAuthActive,
|
|
8
9
|
resolveAuthPrefix,
|
|
@@ -75,8 +76,14 @@ const Config = cmp(async function Config(props: any) {
|
|
|
75
76
|
relations: n.relations,
|
|
76
77
|
}, true), a), {})
|
|
77
78
|
|
|
79
|
+
// Identity comes from configDefinition's def, not re-derived here, so
|
|
80
|
+
// this target cannot disagree with the shared emitter on main.slug /
|
|
81
|
+
// main.version / main.target (the three station descriptor fields,
|
|
82
|
+
// station design §4) — passing target.name is what opts this target in.
|
|
83
|
+
const { def: configDef } = configDefinition(model, target.name)
|
|
84
|
+
|
|
78
85
|
const config = {
|
|
79
|
-
main:
|
|
86
|
+
main: configDef.main,
|
|
80
87
|
feature: featureConfig,
|
|
81
88
|
options,
|
|
82
89
|
entity: entityConfig,
|
|
@@ -81,7 +81,7 @@ const Config = cmp(async function Config(props: any) {
|
|
|
81
81
|
// literal and the data that replaces it above the threshold are the same
|
|
82
82
|
// config by construction. The JSON is what the threshold is measured on -
|
|
83
83
|
// emitted source size varies by language, the model does not.
|
|
84
|
-
const { json: configJson } = configDefinition(model)
|
|
84
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
85
85
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
86
86
|
|
|
87
87
|
File({ name: 'Config.' + target.ext }, () => {
|
|
@@ -128,6 +128,14 @@ const Config = cmp(async function Config(props: any) {
|
|
|
128
128
|
`require('./feature/${f.name}/${nom(f, 'Name')}Feature')`)
|
|
129
129
|
}),
|
|
130
130
|
|
|
131
|
+
// Values from configDefinition's def, not re-derived here, so the
|
|
132
|
+
// literal rep and the data rep cannot disagree on identity.
|
|
133
|
+
'// #MainMeta': () => {
|
|
134
|
+
Line(` slug: ${JSON.stringify(configDef.main.slug)},`)
|
|
135
|
+
Line(` version: ${JSON.stringify(configDef.main.version)},`)
|
|
136
|
+
Line(` target: ${JSON.stringify(configDef.main.target)},`)
|
|
137
|
+
},
|
|
138
|
+
|
|
131
139
|
'// #FeatureClasses': () => each(feature, (f: any) => {
|
|
132
140
|
// Trailing comma: the map has one entry per feature, so entries
|
|
133
141
|
// must be comma-separated (a single feature hid this until now).
|
|
@@ -34,6 +34,13 @@ class Config {
|
|
|
34
34
|
return fi
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// False for a feature added at runtime via options.extend (station's
|
|
38
|
+
// adopt path) - the constructor uses this to skip makeFeature for names
|
|
39
|
+
// no generated class backs.
|
|
40
|
+
hasFeature(fn) {
|
|
41
|
+
return null != FEATURE_CLASS[fn]
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
|
|
@@ -17,9 +17,17 @@ class Config {
|
|
|
17
17
|
return fi
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// False for a feature added at runtime via options.extend (station's
|
|
21
|
+
// adopt path) - the constructor uses this to skip makeFeature for names
|
|
22
|
+
// no generated class backs.
|
|
23
|
+
hasFeature(fn) {
|
|
24
|
+
return null != FEATURE_CLASS[fn]
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
|
|
21
28
|
main = {
|
|
22
29
|
name: '$$const.Name$$',
|
|
30
|
+
// #MainMeta
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
|
|
@@ -50,18 +50,26 @@ class ProjectNameSDK {
|
|
|
50
50
|
// the `test` feature installs the base mock transport and the transport
|
|
51
51
|
// features (retry/cache/netsim/proxy/ratelimit) wrap whatever is current,
|
|
52
52
|
// so `test` must be added before them to sit at the base of the chain.
|
|
53
|
+
const extend = this._options.extend || []
|
|
54
|
+
|
|
53
55
|
const featureorder = getpath(this._options, '__derived__.featureorder') || []
|
|
54
56
|
for (const fname of featureorder) {
|
|
55
57
|
const fopts = this._options.feature[fname] || {}
|
|
56
58
|
if (fopts.active) {
|
|
59
|
+
// An active name with no generated class is legal when an
|
|
60
|
+
// extend-supplied instance carries that name (station's adopt
|
|
61
|
+
// path): the instance is added below, positioned by its own
|
|
62
|
+
// __after__ entry, so skip it here rather than fail construction.
|
|
63
|
+
if (!this._rootctx.config.hasFeature(fname) &&
|
|
64
|
+
extend.some((f) => fname === f.name)) {
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
57
67
|
featureAdd(this._rootctx, this._rootctx.config.makeFeature(fname))
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
featureAdd(this._rootctx, f)
|
|
64
|
-
}
|
|
71
|
+
for (let f of extend) {
|
|
72
|
+
featureAdd(this._rootctx, f)
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
for (let f of this._features) {
|
|
@@ -185,7 +193,27 @@ class ProjectNameSDK {
|
|
|
185
193
|
}
|
|
186
194
|
|
|
187
195
|
const status = fetched.status
|
|
188
|
-
|
|
196
|
+
|
|
197
|
+
// No body responses (204 No Content, 304 Not Modified) and explicit
|
|
198
|
+
// zero content-length must skip JSON parsing — fetched.json() would
|
|
199
|
+
// throw `Unexpected end of JSON input` on an empty body.
|
|
200
|
+
const headers = fetched.headers
|
|
201
|
+
const contentLength = headers && 'function' === typeof headers.get
|
|
202
|
+
? headers.get('content-length')
|
|
203
|
+
: (headers || {})['content-length']
|
|
204
|
+
const noBody = 204 === status || 304 === status || '0' === String(contentLength)
|
|
205
|
+
|
|
206
|
+
let json = undefined
|
|
207
|
+
if (!noBody) {
|
|
208
|
+
try {
|
|
209
|
+
json = 'function' === typeof fetched.json ? await fetched.json() : fetched.json
|
|
210
|
+
}
|
|
211
|
+
catch (parseErr) {
|
|
212
|
+
// Body wasn't valid JSON — surface the raw response rather than
|
|
213
|
+
// throwing. data stays undefined; callers can inspect status/headers.
|
|
214
|
+
json = undefined
|
|
215
|
+
}
|
|
216
|
+
}
|
|
189
217
|
|
|
190
218
|
return {
|
|
191
219
|
ok: status >= 200 && status < 300,
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Content,
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
|
+
configDefinition,
|
|
6
7
|
each,
|
|
7
8
|
isAuthActive,
|
|
8
9
|
resolveAuthPrefix,
|
|
@@ -72,8 +73,14 @@ const Config = cmp(async function Config(props: any) {
|
|
|
72
73
|
relations: n.relations,
|
|
73
74
|
}, true), a), {})
|
|
74
75
|
|
|
76
|
+
// Identity comes from configDefinition's def, not re-derived here, so
|
|
77
|
+
// this target cannot disagree with the shared emitter on main.slug /
|
|
78
|
+
// main.version / main.target (the three station descriptor fields,
|
|
79
|
+
// station design §4) — passing target.name is what opts this target in.
|
|
80
|
+
const { def: configDef } = configDefinition(model, target.name)
|
|
81
|
+
|
|
75
82
|
const config = {
|
|
76
|
-
main:
|
|
83
|
+
main: configDef.main,
|
|
77
84
|
feature: featureConfig,
|
|
78
85
|
options,
|
|
79
86
|
entity: entityConfig,
|
|
@@ -68,8 +68,11 @@ const Config = cmp(async function Config(props: any) {
|
|
|
68
68
|
// The same config as an OBJECT, built by the shared helper so this target's
|
|
69
69
|
// literal and the data that replaces it above the threshold are the same
|
|
70
70
|
// config by construction. The JSON is what the threshold is measured on -
|
|
71
|
-
// emitted source size varies by language, the model does not.
|
|
72
|
-
|
|
71
|
+
// emitted source size varies by language, the model does not. Passing the
|
|
72
|
+
// target name opts this target into the main slug/version/target identity
|
|
73
|
+
// fields (station descriptor v1 reads all three) - the literal branch
|
|
74
|
+
// below emits them too, so the two representations cannot diverge.
|
|
75
|
+
const { def: configDef, json: configJson } = configDefinition(model, target.name)
|
|
73
76
|
const asData = isConfigData(configJson, configReprSetting(model))
|
|
74
77
|
|
|
75
78
|
File({ name: 'config.' + target.ext }, () => {
|
|
@@ -112,6 +115,9 @@ end
|
|
|
112
115
|
}
|
|
113
116
|
else {
|
|
114
117
|
|
|
118
|
+
// Identity values from configDefinition's def, not re-derived here, so
|
|
119
|
+
// the literal rep and the data rep cannot disagree (the ts #MainMeta
|
|
120
|
+
// discipline).
|
|
115
121
|
Content(`-- Build a fresh, fully materialised config table. Every call rebuilds the
|
|
116
122
|
-- whole structure, so prefer require("config_shared") unless you need a
|
|
117
123
|
-- private copy you intend to mutate.
|
|
@@ -119,6 +125,9 @@ local function make_config()
|
|
|
119
125
|
return {
|
|
120
126
|
main = {
|
|
121
127
|
name = "${model.const.Name}",
|
|
128
|
+
slug = ${JSON.stringify(configDef.main.slug)},
|
|
129
|
+
version = ${JSON.stringify(configDef.main.version)},
|
|
130
|
+
target = ${JSON.stringify(configDef.main.target)},
|
|
122
131
|
},
|
|
123
132
|
feature = {
|
|
124
133
|
`)
|