@voxgig/sdkgen 3.4.7 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/voxgig-sdkgen +78 -10
- package/dist/action/action.d.ts +5 -2
- package/dist/action/action.js +113 -6
- package/dist/action/action.js.map +1 -1
- package/dist/action/check.d.ts +15 -0
- package/dist/action/check.js +347 -0
- package/dist/action/check.js.map +1 -0
- package/dist/action/dispatch.d.ts +8 -0
- package/dist/action/dispatch.js +74 -0
- package/dist/action/dispatch.js.map +1 -0
- package/dist/action/docs.d.ts +4 -0
- package/dist/action/docs.js +236 -0
- package/dist/action/docs.js.map +1 -0
- package/dist/action/doctor.d.ts +5 -2
- package/dist/action/doctor.js +526 -74
- package/dist/action/doctor.js.map +1 -1
- package/dist/action/feature.js +96 -17
- package/dist/action/feature.js.map +1 -1
- package/dist/action/kind.d.ts +30 -0
- package/dist/action/kind.js +258 -0
- package/dist/action/kind.js.map +1 -0
- package/dist/action/package.d.ts +25 -0
- package/dist/action/package.js +852 -0
- package/dist/action/package.js.map +1 -0
- package/dist/action/resolve.d.ts +18 -0
- package/dist/action/resolve.js +280 -0
- package/dist/action/resolve.js.map +1 -0
- package/dist/action/target.d.ts +7 -1
- package/dist/action/target.js +240 -97
- package/dist/action/target.js.map +1 -1
- package/dist/cmp/Docs.d.ts +4 -0
- package/dist/cmp/Docs.js +98 -0
- package/dist/cmp/Docs.js.map +1 -0
- package/dist/cmp/ExternalDocs.d.ts +2 -0
- package/dist/cmp/ExternalDocs.js +43 -0
- package/dist/cmp/ExternalDocs.js.map +1 -0
- package/dist/helpers/definition.d.ts +5 -0
- package/dist/helpers/definition.js +72 -0
- package/dist/helpers/definition.js.map +1 -0
- package/dist/helpers/featureSource.d.ts +8 -2
- package/dist/helpers/featureSource.js +61 -12
- package/dist/helpers/featureSource.js.map +1 -1
- package/dist/helpers/junk.d.ts +11 -0
- package/dist/helpers/junk.js +149 -0
- package/dist/helpers/junk.js.map +1 -0
- package/dist/helpers/manifest.d.ts +50 -0
- package/dist/helpers/manifest.js +328 -0
- package/dist/helpers/manifest.js.map +1 -0
- package/dist/helpers/modelcheck.d.ts +22 -0
- package/dist/helpers/modelcheck.js +203 -0
- package/dist/helpers/modelcheck.js.map +1 -0
- package/dist/helpers/semver.d.ts +4 -0
- package/dist/helpers/semver.js +174 -0
- package/dist/helpers/semver.js.map +1 -0
- package/dist/helpers/shipped.d.ts +4 -0
- package/dist/helpers/shipped.js +36 -0
- package/dist/helpers/shipped.js.map +1 -0
- package/dist/helpers/stdrep.d.ts +9 -1
- package/dist/helpers/stdrep.js +22 -0
- package/dist/helpers/stdrep.js.map +1 -1
- package/dist/sdkgen.d.ts +15 -3
- package/dist/sdkgen.js +233 -67
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +10 -2
- package/dist/utility.js +17 -0
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aontu +69 -0
- package/package.json +3 -2
- package/project/.sdk/model/feature/audit.aontu +1 -0
- package/project/.sdk/model/feature/cache.aontu +1 -0
- package/project/.sdk/model/feature/clienttrack.aontu +1 -0
- package/project/.sdk/model/feature/debug.aontu +1 -0
- package/project/.sdk/model/feature/idempotency.aontu +1 -0
- package/project/.sdk/model/feature/log.aontu +1 -0
- package/project/.sdk/model/feature/metrics.aontu +1 -0
- package/project/.sdk/model/feature/netsim.aontu +1 -0
- package/project/.sdk/model/feature/paging.aontu +1 -0
- package/project/.sdk/model/feature/proxy.aontu +1 -0
- package/project/.sdk/model/feature/ratelimit.aontu +1 -0
- package/project/.sdk/model/feature/rbac.aontu +1 -0
- package/project/.sdk/model/feature/retry.aontu +1 -0
- package/project/.sdk/model/feature/streaming.aontu +1 -0
- package/project/.sdk/model/feature/telemetry.aontu +1 -0
- package/project/.sdk/model/feature/test.aontu +1 -0
- package/project/.sdk/model/feature/timeout.aontu +1 -0
- package/project/.sdk/model/target/c.aontu +1 -0
- package/project/.sdk/model/target/clojure.aontu +1 -0
- package/project/.sdk/model/target/cpp.aontu +1 -0
- package/project/.sdk/model/target/dart.aontu +1 -0
- package/project/.sdk/model/target/elixir.aontu +1 -0
- package/project/.sdk/model/target/go-cli.aontu +1 -0
- package/project/.sdk/model/target/go-mcp.aontu +1 -0
- package/project/.sdk/model/target/go.aontu +1 -0
- package/project/.sdk/model/target/haskell.aontu +1 -0
- package/project/.sdk/model/target/java.aontu +1 -0
- package/project/.sdk/model/target/js.aontu +1 -0
- package/project/.sdk/model/target/kotlin.aontu +1 -0
- package/project/.sdk/model/target/lean.aontu +1 -0
- package/project/.sdk/model/target/lua.aontu +1 -0
- package/project/.sdk/model/target/ocaml.aontu +1 -0
- package/project/.sdk/model/target/perl.aontu +1 -0
- package/project/.sdk/model/target/php.aontu +1 -0
- package/project/.sdk/model/target/py-data.aontu +1 -0
- package/project/.sdk/model/target/py.aontu +1 -0
- package/project/.sdk/model/target/rb.aontu +1 -0
- package/project/.sdk/model/target/rust.aontu +1 -0
- package/project/.sdk/model/target/scala.aontu +1 -0
- package/project/.sdk/model/target/seneca-provider.aontu +1 -0
- package/project/.sdk/model/target/zig.aontu +1 -0
- package/project/.sdk/src/cmp/c/Config_c.ts +44 -0
- package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +3 -1
- package/project/.sdk/src/cmp/clojure/Config_clojure.ts +37 -42
- package/project/.sdk/src/cmp/clojure/utility_clojure.ts +42 -0
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +18 -0
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +125 -52
- package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +3 -1
- package/project/.sdk/src/cmp/csharp/utility_csharp.ts +94 -6
- package/project/.sdk/src/cmp/dart/Config_dart.ts +43 -5
- package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +74 -0
- package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -9
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +16 -5
- package/project/.sdk/src/cmp/dart/utility_dart.ts +18 -0
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +128 -13
- package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +3 -1
- package/project/.sdk/src/cmp/elixir/utility_elixir.ts +6 -0
- package/project/.sdk/src/cmp/haskell/Config_haskell.ts +35 -52
- package/project/.sdk/src/cmp/haskell/Package_haskell.ts +1 -0
- package/project/.sdk/src/cmp/haskell/Test_haskell.ts +1 -1
- package/project/.sdk/src/cmp/haskell/utility_haskell.ts +15 -5
- package/project/.sdk/src/cmp/java/Config_java.ts +21 -0
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +15 -0
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +42 -39
- package/project/.sdk/src/cmp/perl/Config_perl.ts +15 -0
- package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +3 -1
- package/project/.sdk/src/cmp/rust/Config_rust.ts +44 -0
- package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +4 -2
- package/project/.sdk/src/cmp/scala/Config_scala.ts +15 -0
- package/project/.sdk/src/cmp/swift/Config_swift.ts +17 -0
- package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +3 -1
- package/project/.sdk/src/cmp/zig/Config_zig.ts +44 -0
- package/project/.sdk/src/cmp/zig/TestEntity_zig.ts +9 -2
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +3 -1
- package/project/.sdk/tm/c/core/sdk.h +1 -0
- package/project/.sdk/tm/c/utility/make_options.c +5 -1
- package/project/.sdk/tm/clojure/src/sdk/core.clj +51 -3
- package/project/.sdk/tm/cpp/core/types.hpp +4 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +5 -1
- package/project/.sdk/tm/csharp/test/PipelineTest.cs +15 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +67 -1
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +43 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +58 -2
- package/project/.sdk/tm/haskell/{test/TestJson.hs → src/SdkJson.hs} +8 -4
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +55 -2
- package/project/.sdk/tm/haskell/test/Runner.hs +1 -1
- package/project/.sdk/tm/haskell/test/StructCorpus.hs +1 -1
- package/project/.sdk/tm/java/core/SdkClient.java +3 -1
- package/project/.sdk/tm/java/utility/MakeOptions.java +5 -1
- package/project/.sdk/tm/kotlin/core/SdkClient.kt +3 -1
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +5 -1
- package/project/.sdk/tm/ocaml/Makefile +3 -2
- package/project/.sdk/tm/ocaml/sdk_json.ml +106 -0
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +56 -0
- package/project/.sdk/tm/ocaml/test/struct_corpus.ml +4 -86
- package/project/.sdk/tm/perl/utility/make_options.pm +5 -1
- package/project/.sdk/tm/rust/utility/make_options.rs +79 -1
- package/project/.sdk/tm/scala/core/SdkClient.scala +3 -1
- package/project/.sdk/tm/scala/utility/Make.scala +5 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +5 -1
- package/project/.sdk/tm/zig/core/utility.zig +85 -1
- package/project/sdkgen-package.json +57 -0
- package/src/action/action.ts +136 -6
- package/src/action/check.ts +495 -0
- package/src/action/dispatch.ts +105 -0
- package/src/action/docs.ts +310 -0
- package/src/action/doctor.ts +692 -81
- package/src/action/feature.ts +109 -17
- package/src/action/kind.ts +375 -0
- package/src/action/package.ts +1173 -0
- package/src/action/resolve.ts +362 -0
- package/src/action/target.ts +290 -108
- package/src/cmp/Docs.ts +125 -0
- package/src/cmp/ExternalDocs.ts +54 -0
- package/src/helpers/definition.ts +82 -0
- package/src/helpers/featureSource.ts +83 -13
- package/src/helpers/junk.ts +168 -0
- package/src/helpers/manifest.ts +474 -0
- package/src/helpers/modelcheck.ts +262 -0
- package/src/helpers/semver.ts +211 -0
- package/src/helpers/shipped.ts +41 -0
- package/src/helpers/stdrep.ts +70 -0
- package/src/sdkgen.ts +294 -66
- package/src/types.ts +33 -3
- package/src/utility.ts +17 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import 'dart:convert';
|
|
2
|
+
|
|
3
|
+
import 'feature/base/BaseFeature.dart';
|
|
4
|
+
// #ImportFeatures
|
|
5
|
+
|
|
6
|
+
// ignore: non_constant_identifier_names
|
|
7
|
+
final Map<String, BaseFeature Function()> FEATURE_CLASS = {
|
|
8
|
+
// #FeatureClasses
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
12
|
+
//
|
|
13
|
+
// The literal form of this file declares the whole model as nested map
|
|
14
|
+
// literals. For a large API that is megabytes of source the Dart analyzer and
|
|
15
|
+
// compiler must parse and type every node of, on every build, and that the VM
|
|
16
|
+
// must build entry by entry on every load.
|
|
17
|
+
//
|
|
18
|
+
// As a single string constant it is one token, and `jsonDecode` builds the map
|
|
19
|
+
// far faster than the equivalent literal.
|
|
20
|
+
//
|
|
21
|
+
// Emitted only above a size threshold, or when `main.kit.config.repr` pins it:
|
|
22
|
+
// for a small model the literal is smaller, loads no slower, and is far easier
|
|
23
|
+
// to read when debugging.
|
|
24
|
+
const String _CONFIG_DATA = 'CONFIGJSON';
|
|
25
|
+
|
|
26
|
+
class Config {
|
|
27
|
+
// ONE decode per constructed Config, and each instance owns the maps it
|
|
28
|
+
// decoded. The literal representation builds fresh maps in every field
|
|
29
|
+
// initialiser, so a caller that constructs a second Config and mutates it
|
|
30
|
+
// does not touch the first - and `Config` is exported by Main.fragment.dart,
|
|
31
|
+
// so callers really can construct one.
|
|
32
|
+
//
|
|
33
|
+
// Sharing a single top-level parsed map would have been cheaper and was the
|
|
34
|
+
// first attempt; it made `Config().options['x'] = 1` mutate the `config`
|
|
35
|
+
// singleton every SDK client reads. Decoding per instance costs about what
|
|
36
|
+
// building the literal per instance cost, and the rung's win is untouched:
|
|
37
|
+
// L1 is about what the COMPILER has to walk, not what happens at load.
|
|
38
|
+
Config() : this._(jsonDecode(_CONFIG_DATA) as Map<String, dynamic>);
|
|
39
|
+
|
|
40
|
+
Config._(Map<String, dynamic> data)
|
|
41
|
+
: main = data['main'] as Map<String, dynamic>,
|
|
42
|
+
feature = data['feature'] as Map<String, dynamic>,
|
|
43
|
+
options = data['options'] as Map<String, dynamic>,
|
|
44
|
+
entity = data['entity'] as Map<String, dynamic>;
|
|
45
|
+
|
|
46
|
+
BaseFeature makeFeature(String fn) {
|
|
47
|
+
final fc = FEATURE_CLASS[fn];
|
|
48
|
+
if (null == fc) {
|
|
49
|
+
// TODO: errors etc
|
|
50
|
+
throw StateError('Unknown feature: ' + fn);
|
|
51
|
+
}
|
|
52
|
+
return fc();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The same fields the literal declares - same names, same types, same
|
|
56
|
+
// finalness - so callers cannot tell which representation they were given.
|
|
57
|
+
final Map<String, dynamic> main;
|
|
58
|
+
|
|
59
|
+
final Map<String, dynamic> feature;
|
|
60
|
+
|
|
61
|
+
final Map<String, dynamic> options;
|
|
62
|
+
|
|
63
|
+
final Map<String, dynamic> entity;
|
|
64
|
+
|
|
65
|
+
// The pipeline context carries the config as a plain map.
|
|
66
|
+
Map<String, dynamic> toMap() => <String, dynamic>{
|
|
67
|
+
'main': main,
|
|
68
|
+
'feature': feature,
|
|
69
|
+
'options': options,
|
|
70
|
+
'entity': entity,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
final config = Config();
|
|
@@ -24,15 +24,12 @@ class Config {
|
|
|
24
24
|
// #FeatureConfigs
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// #EntityConfigs
|
|
34
|
-
}
|
|
35
|
-
};
|
|
27
|
+
// Rendered whole from the canonical config definition rather than assembled
|
|
28
|
+
// slot by slot. Assembling it here meant `options.server` - the OpenAPI
|
|
29
|
+
// server-variable defaults - was simply absent from this branch, so a
|
|
30
|
+
// templated server URL produced a different config either side of the
|
|
31
|
+
// threshold.
|
|
32
|
+
final Map<String, dynamic> options = 'OPTIONSMAP';
|
|
36
33
|
|
|
37
34
|
final Map<String, dynamic> entity = 'ENTITYMAP';
|
|
38
35
|
|
|
@@ -9,20 +9,31 @@ import 'Spec.dart';
|
|
|
9
9
|
// a Dart `export` needs no matching `import`, so importing them here too is an
|
|
10
10
|
// unused_import. Keep only the imports actually referenced in this file.
|
|
11
11
|
import 'utility/ErrUtility.dart';
|
|
12
|
-
|
|
12
|
+
// PREFIXED, and deliberately not re-exported. The runtime helper class is
|
|
13
|
+
// named `Utility`, and so is the generated data class for an entity named
|
|
14
|
+
// `utility` (ProjectNameTypes.dart) — exporting both from this library makes
|
|
15
|
+
// the name ambiguous and NO dart SDK for such an API compiles:
|
|
16
|
+
//
|
|
17
|
+
// Error: 'Utility' is exported from both 'lib/ProjectNameTypes.dart'
|
|
18
|
+
// and 'lib/utility/Utility.dart'
|
|
19
|
+
//
|
|
20
|
+
// The entity type is the user-facing name, so it keeps the plain one; the
|
|
21
|
+
// runtime helper is internal plumbing and is reached through the prefix. Any
|
|
22
|
+
// entity name can collide with an internal class, so the prefix — not a
|
|
23
|
+
// rename — is what makes this safe for every API.
|
|
24
|
+
import 'utility/Utility.dart' as sdkutil;
|
|
13
25
|
|
|
14
26
|
export 'Config.dart' show Config, config;
|
|
15
27
|
export 'ProjectNameEntityBase.dart' show ProjectNameEntityBase;
|
|
16
28
|
export 'ProjectNameError.dart' show ProjectNameError;
|
|
17
29
|
export 'feature/base/BaseFeature.dart' show BaseFeature;
|
|
18
|
-
export 'utility/Utility.dart' show Utility;
|
|
19
30
|
|
|
20
|
-
final Utility stdutil = Utility();
|
|
31
|
+
final sdkutil.Utility stdutil = sdkutil.Utility();
|
|
21
32
|
|
|
22
33
|
class ProjectNameSDK {
|
|
23
34
|
String mode = 'live';
|
|
24
35
|
dynamic _options;
|
|
25
|
-
final Utility _utility = Utility();
|
|
36
|
+
final sdkutil.Utility _utility = sdkutil.Utility();
|
|
26
37
|
List<dynamic> features = [];
|
|
27
38
|
dynamic rootctx;
|
|
28
39
|
|
|
@@ -85,7 +96,7 @@ class ProjectNameSDK {
|
|
|
85
96
|
return _utility.struct.clone(_options);
|
|
86
97
|
}
|
|
87
98
|
|
|
88
|
-
Utility utility() {
|
|
99
|
+
sdkutil.Utility utility() {
|
|
89
100
|
return _utility;
|
|
90
101
|
}
|
|
91
102
|
|
|
@@ -129,7 +129,25 @@ function clean(o: any, dropDefaults?: boolean): any {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
|
|
132
|
+
|
|
133
|
+
// The JSON as a Dart string literal.
|
|
134
|
+
//
|
|
135
|
+
// Dart's ordinary string literals INTERPOLATE: `$name` and `${...}` are
|
|
136
|
+
// substitution, and the model is full of `$` - `$STRING`, `$NUMBER`,
|
|
137
|
+
// `$action`. A raw string (`r'...'`) would avoid that but cannot contain its
|
|
138
|
+
// own quote character and has no escape for it, and the JSON contains both
|
|
139
|
+
// quote characters in quantity.
|
|
140
|
+
//
|
|
141
|
+
// So: JSON.stringify's escaping, which already handles `"` and `\` and emits
|
|
142
|
+
// no raw control characters, plus `$` -> `\$`. That last step is unambiguous
|
|
143
|
+
// precisely because JSON.stringify has already escaped every backslash, so a
|
|
144
|
+
// `\$` here can only have come from this rule.
|
|
145
|
+
function dartStringLiteral(json: string): string {
|
|
146
|
+
return JSON.stringify(json).replace(/\$/g, '\\$')
|
|
147
|
+
}
|
|
148
|
+
|
|
132
149
|
export {
|
|
150
|
+
dartStringLiteral,
|
|
133
151
|
clean,
|
|
134
152
|
dartString,
|
|
135
153
|
dartValue,
|
|
@@ -4,8 +4,11 @@ import {
|
|
|
4
4
|
File,
|
|
5
5
|
Folder,
|
|
6
6
|
cmp,
|
|
7
|
+
configDefinition,
|
|
8
|
+
configReprSetting,
|
|
7
9
|
each,
|
|
8
10
|
isAuthActive,
|
|
11
|
+
isConfigData,
|
|
9
12
|
resolveAuthPrefix,
|
|
10
13
|
} from '@voxgig/sdkgen'
|
|
11
14
|
|
|
@@ -53,8 +56,101 @@ const Config = cmp(async function Config(props: any) {
|
|
|
53
56
|
}, true), a), {})
|
|
54
57
|
|
|
55
58
|
Folder({ name: 'lib' }, () => {
|
|
59
|
+
// The same config as an OBJECT, built by the shared helper so this
|
|
60
|
+
// target's literal and the data that replaces it above the threshold are
|
|
61
|
+
// the same config by construction. The JSON is what the threshold is
|
|
62
|
+
// measured on - emitted source size varies by language, the model does not.
|
|
63
|
+
const { def: configDef, json: configJson } = configDefinition(model)
|
|
64
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
65
|
+
|
|
56
66
|
File({ name: 'config.ex' }, () => {
|
|
57
67
|
|
|
68
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
69
|
+
//
|
|
70
|
+
// The literal is one nested `%{}` the Elixir compiler expands and holds
|
|
71
|
+
// in the module's constant pool; a binary is one token. `Json.parse`
|
|
72
|
+
// builds the vendored struct's heap nodes DIRECTLY - the same nodes
|
|
73
|
+
// `Helpers.deep/1` produces from a plain map - so make_config returns
|
|
74
|
+
// exactly what it returned before.
|
|
75
|
+
//
|
|
76
|
+
// `ProjectName.Json` is already the SDK's response decoder (see
|
|
77
|
+
// `safe_json` in utility.ex), so this adds no dependency.
|
|
78
|
+
if (asData) {
|
|
79
|
+
Content(`# ${Name} SDK configuration
|
|
80
|
+
#
|
|
81
|
+
# THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
82
|
+
#
|
|
83
|
+
# Emitted only above a size threshold, or when \`main.kit.config.repr\` pins
|
|
84
|
+
# it: for a small model the literal is smaller and far easier to read when
|
|
85
|
+
# debugging. Do not edit by hand.
|
|
86
|
+
|
|
87
|
+
defmodule ${Name}.Config do
|
|
88
|
+
@config_data ${elixirString(configJson)}
|
|
89
|
+
|
|
90
|
+
def make_config do
|
|
91
|
+
${Name}.Json.parse(@config_data)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# SHARED CONFIG (sdkgen rung L2).
|
|
95
|
+
#
|
|
96
|
+
# The SDK reads the config on every request and never writes to it, so one
|
|
97
|
+
# instance is shared by every client rather than rebuilt per client. Above the
|
|
98
|
+
# size threshold make_config re-parses the whole embedded JSON, so this is the
|
|
99
|
+
# difference between parsing the model once and once per client.
|
|
100
|
+
#
|
|
101
|
+
# :persistent_term because struct nodes are ETS-backed handles: the stored
|
|
102
|
+
# value is the handle, so every caller gets the same nodes. A concurrent first
|
|
103
|
+
# call may build twice and the last write wins - both results are valid
|
|
104
|
+
# configs, so the race is benign.
|
|
105
|
+
@shared_key {__MODULE__, :shared_config}
|
|
106
|
+
|
|
107
|
+
# The process-wide config, built once on first use.
|
|
108
|
+
#
|
|
109
|
+
# The returned node is SHARED: treat it as read-only. Callers that need to
|
|
110
|
+
# mutate should use make_config, which always returns a fresh copy.
|
|
111
|
+
#
|
|
112
|
+
# VALIDATED ON READ, and this is not belt-and-braces. The struct heap is a
|
|
113
|
+
# named ETS table created with no heir, so it is owned by whichever process
|
|
114
|
+
# first touched struct. If that was a short-lived one - a Task, a request, an
|
|
115
|
+
# ExUnit case - the table dies with it and every handle allocated in it goes
|
|
116
|
+
# stale. Caching a handle in :persistent_term makes that permanent: without
|
|
117
|
+
# this check the SDK hands out the dead handle for the life of the VM and
|
|
118
|
+
# every getprop raises ArgumentError. Reproduced:
|
|
119
|
+
#
|
|
120
|
+
# cached inside a task: {:vmap, 144}
|
|
121
|
+
# heap alive after task exit: :undefined
|
|
122
|
+
# getprop RAISED: ArgumentError
|
|
123
|
+
#
|
|
124
|
+
# Rebuilding on a dead handle costs one parse and restores exactly the
|
|
125
|
+
# pre-L2 behaviour, so the failure degrades to "no sharing" rather than to a
|
|
126
|
+
# broken SDK. The real fix is a durable owner for the heap, which belongs in
|
|
127
|
+
# the struct port rather than here.
|
|
128
|
+
def shared_config do
|
|
129
|
+
cached = :persistent_term.get(@shared_key, nil)
|
|
130
|
+
|
|
131
|
+
if cached != nil and usable?(cached) do
|
|
132
|
+
cached
|
|
133
|
+
else
|
|
134
|
+
cfg = make_config()
|
|
135
|
+
:persistent_term.put(@shared_key, cfg)
|
|
136
|
+
cfg
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Is this handle still backed by a live heap? Asked through the public API
|
|
141
|
+
# rather than by inspecting the table, so it stays correct if struct changes
|
|
142
|
+
# how nodes are stored.
|
|
143
|
+
defp usable?(cfg) do
|
|
144
|
+
Voxgig.Struct.getprop(cfg, "main")
|
|
145
|
+
true
|
|
146
|
+
rescue
|
|
147
|
+
ArgumentError -> false
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
`)
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
58
154
|
Content(`# ${Name} SDK configuration
|
|
59
155
|
#
|
|
60
156
|
# Returns the resolved SDK config as vendored-struct nodes (via
|
|
@@ -72,23 +168,42 @@ defmodule ${Name}.Config do
|
|
|
72
168
|
`)
|
|
73
169
|
})
|
|
74
170
|
|
|
171
|
+
// `options` rendered WHOLE from the canonical definition rather than
|
|
172
|
+
// assembled slot by slot. Assembling it meant `options.server` - the
|
|
173
|
+
// OpenAPI server-variable defaults - was simply absent from this branch,
|
|
174
|
+
// so a templated server URL described a different config either side of
|
|
175
|
+
// the threshold. Same fix as dart, rust and zig.
|
|
75
176
|
Content(` },
|
|
76
|
-
"options" =>
|
|
77
|
-
"base" => ${elixirString(baseUrl)},
|
|
78
|
-
${authBlock} "headers" => ${formatElixir(headers, 4)},
|
|
79
|
-
"entity" => %{
|
|
80
|
-
`)
|
|
81
|
-
|
|
82
|
-
each(entity, (e: any) => {
|
|
83
|
-
Content(` ${elixirString(e.name)} => %{},
|
|
84
|
-
`)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
Content(` }
|
|
88
|
-
},
|
|
177
|
+
"options" => ${formatElixir(configDef.options, 3)},
|
|
89
178
|
"entity" => ${formatElixir(entityClean, 3)}
|
|
90
179
|
})
|
|
91
180
|
end
|
|
181
|
+
|
|
182
|
+
# SHARED CONFIG (sdkgen rung L2). See the data branch for the rationale, and
|
|
183
|
+
# for why the cached handle is validated on read.
|
|
184
|
+
@shared_key {__MODULE__, :shared_config}
|
|
185
|
+
|
|
186
|
+
# The process-wide config, built once on first use. The returned node is
|
|
187
|
+
# SHARED: treat it as read-only. Callers that need to mutate should use
|
|
188
|
+
# make_config, which always returns a fresh copy.
|
|
189
|
+
def shared_config do
|
|
190
|
+
cached = :persistent_term.get(@shared_key, nil)
|
|
191
|
+
|
|
192
|
+
if cached != nil and usable?(cached) do
|
|
193
|
+
cached
|
|
194
|
+
else
|
|
195
|
+
cfg = make_config()
|
|
196
|
+
:persistent_term.put(@shared_key, cfg)
|
|
197
|
+
cfg
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
defp usable?(cfg) do
|
|
202
|
+
Voxgig.Struct.getprop(cfg, "main")
|
|
203
|
+
true
|
|
204
|
+
rescue
|
|
205
|
+
ArgumentError -> false
|
|
206
|
+
end
|
|
92
207
|
end
|
|
93
208
|
`)
|
|
94
209
|
})
|
|
@@ -18,7 +18,9 @@ defmodule ProjectName do
|
|
|
18
18
|
utility = Utility.new()
|
|
19
19
|
S.setprop(client, "_utility", utility)
|
|
20
20
|
|
|
21
|
-
config
|
|
21
|
+
# The process-wide config (sdkgen rung L2): read-only on the request path,
|
|
22
|
+
# so every client shares one rather than rebuilding it.
|
|
23
|
+
config = ProjectName.Config.shared_config()
|
|
22
24
|
|
|
23
25
|
rootctx =
|
|
24
26
|
Context.new(
|
|
@@ -91,6 +91,12 @@ function elixirString(s: string): string {
|
|
|
91
91
|
return '"' + String(s)
|
|
92
92
|
.replace(/\\/g, '\\\\')
|
|
93
93
|
.replace(/"/g, '\\"')
|
|
94
|
+
// `#` escaped because Elixir INTERPOLATES `\#{...}` inside a double-quoted
|
|
95
|
+
// string. Without this a model value containing `#{` is evaluated as code
|
|
96
|
+
// at compile time rather than emitted as text - which is both a wrong
|
|
97
|
+
// value and an arbitrary-expression hole. Escaping every `#` is simpler to
|
|
98
|
+
// reason about than escaping only `#{`, and `\#` is just `#` in Elixir.
|
|
99
|
+
.replace(/#/g, '\\#')
|
|
94
100
|
.replace(/\n/g, '\\n')
|
|
95
101
|
.replace(/\t/g, '\\t')
|
|
96
102
|
+ '"'
|
|
@@ -2,22 +2,20 @@ import {
|
|
|
2
2
|
Content,
|
|
3
3
|
File,
|
|
4
4
|
cmp,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
configDefinition,
|
|
6
|
+
configReprSetting,
|
|
7
|
+
isConfigData,
|
|
8
8
|
} from '@voxgig/sdkgen'
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
-
KIT,
|
|
13
12
|
Model,
|
|
14
|
-
getModelPath,
|
|
15
13
|
} from '@voxgig/apidef'
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
import {
|
|
19
|
-
clean,
|
|
20
17
|
formatHsValue,
|
|
18
|
+
hsString,
|
|
21
19
|
} from './utility_haskell'
|
|
22
20
|
|
|
23
21
|
|
|
@@ -38,50 +36,36 @@ const Config = cmp(async function Config(props: any) {
|
|
|
38
36
|
|
|
39
37
|
const model: Model = ctx$.model
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
// The same config as an OBJECT, built by the shared helper so this target's
|
|
40
|
+
// literal and the data that replaces it above the threshold are the same
|
|
41
|
+
// config by construction. The JSON is what the threshold is measured on -
|
|
42
|
+
// emitted source size varies by language, the model does not.
|
|
43
|
+
const { def: configDef, json: configJson } = configDefinition(model)
|
|
44
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
45
|
+
|
|
46
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
47
|
+
//
|
|
48
|
+
// The literal representation is one enormous CV expression. GHC type-checks
|
|
49
|
+
// and desugars every constructor application in it, and buildCV then walks
|
|
50
|
+
// the whole tree allocating an IORef per node. A string constant is one
|
|
51
|
+
// token, and jsonRead builds the same Value from it directly.
|
|
52
|
+
//
|
|
53
|
+
// Both branches return a FRESH Value per call - buildCV allocates new
|
|
54
|
+
// IORefs, jsonRead re-parses - so a caller that mutates one config cannot
|
|
55
|
+
// reach another's, exactly as before.
|
|
56
|
+
const configBody = asData
|
|
57
|
+
? `-- THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
58
|
+
--
|
|
59
|
+
-- Emitted only above a size threshold, or when \`main.kit.config.repr\` pins
|
|
60
|
+
-- it: for a small model the CV literal is smaller and far easier to read when
|
|
61
|
+
-- debugging.
|
|
62
|
+
configData :: String
|
|
63
|
+
configData = ${hsString(configJson)}
|
|
43
64
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
let baseUrl = ''
|
|
50
|
-
try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
|
|
51
|
-
|
|
52
|
-
const featureConfig: any = {}
|
|
53
|
-
each(feature, (f: any) => {
|
|
54
|
-
featureConfig[f.name] = f.config || {}
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const entityOptions: any = {}
|
|
58
|
-
each(entity, (ent: any) => {
|
|
59
|
-
entityOptions[ent.name] = {}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const options: any = {
|
|
63
|
-
base: baseUrl,
|
|
64
|
-
headers,
|
|
65
|
-
entity: entityOptions,
|
|
66
|
-
}
|
|
67
|
-
if (authActive) {
|
|
68
|
-
options.auth = { prefix: authPrefix }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const entityConfig = Object.values(entity || {}).reduce((a: any, n: any) => (
|
|
72
|
-
a[n.name] = clean({
|
|
73
|
-
fields: n.fields,
|
|
74
|
-
name: n.name,
|
|
75
|
-
op: n.op,
|
|
76
|
-
relations: n.relations,
|
|
77
|
-
}, true), a), {})
|
|
78
|
-
|
|
79
|
-
const config = {
|
|
80
|
-
main: { name: model.const.Name },
|
|
81
|
-
feature: featureConfig,
|
|
82
|
-
options,
|
|
83
|
-
entity: entityConfig,
|
|
84
|
-
}
|
|
65
|
+
makeConfig :: IO Value
|
|
66
|
+
makeConfig = jsonRead configData`
|
|
67
|
+
: `makeConfig :: IO Value
|
|
68
|
+
makeConfig = buildCV ${formatHsValue(configDef)}`
|
|
85
69
|
|
|
86
70
|
File({ name: 'SdkConfig.' + target.ext }, () => {
|
|
87
71
|
|
|
@@ -90,12 +74,11 @@ const Config = cmp(async function Config(props: any) {
|
|
|
90
74
|
module SdkConfig (makeConfig, makeFeature) where
|
|
91
75
|
|
|
92
76
|
import VoxgigStruct (Value)
|
|
93
|
-
import SdkHelpers (CV (..), buildCV)
|
|
77
|
+
import ${asData ? 'SdkJson (jsonRead)' : 'SdkHelpers (CV (..), buildCV)'}
|
|
94
78
|
import SdkTypes (Feature)
|
|
95
79
|
import qualified SdkFeatures as F
|
|
96
80
|
|
|
97
|
-
|
|
98
|
-
makeConfig = buildCV ${formatHsValue(config)}
|
|
81
|
+
${configBody}
|
|
99
82
|
|
|
100
83
|
makeFeature :: String -> IO Feature
|
|
101
84
|
makeFeature name = case name of
|
|
@@ -257,7 +257,7 @@ import qualified SdkFeatures as F
|
|
|
257
257
|
import qualified SdkClient as C
|
|
258
258
|
import qualified TReadmeExamples
|
|
259
259
|
import Testutil
|
|
260
|
-
import
|
|
260
|
+
import SdkJson (jsonRead)
|
|
261
261
|
|
|
262
262
|
-- Load an entity fixture (../.sdk/test/entity/<name>/<Name>TestData.json).
|
|
263
263
|
loadFixture :: String -> IO Value
|
|
@@ -54,9 +54,20 @@ function hsString(s: string): string {
|
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
// Render a JSON-shaped value as a Haskell `CV` literal (the generated
|
|
57
|
-
// SdkConfig realises it with buildCV).
|
|
58
|
-
//
|
|
59
|
-
//
|
|
57
|
+
// SdkConfig realises it with buildCV). Empty map/list render as
|
|
58
|
+
// (CVMap []) / (CVList []) — valid for 0, 1 or N entries (N-feature-safe).
|
|
59
|
+
//
|
|
60
|
+
// KEY ORDER IS INSERTION ORDER, not sorted. struct's Haskell `Value` holds a
|
|
61
|
+
// map as an ORDERED assoc list, so key order is observable — it survives into
|
|
62
|
+
// keysof, iteration and stringify. This used to sort, which was fine while
|
|
63
|
+
// this was the only representation, but above the size threshold the same
|
|
64
|
+
// config arrives via jsonRead in the JSON text's order. Sorting here would
|
|
65
|
+
// have made the two representations describe the same config in a different
|
|
66
|
+
// order, which is exactly what rung L1 promises cannot happen.
|
|
67
|
+
//
|
|
68
|
+
// Insertion order is still byte-stable: every nested level of the config comes
|
|
69
|
+
// from `each`, which iterates sorted, and the two levels built by hand
|
|
70
|
+
// (the root, and `options`) have a fixed order every target's literal follows.
|
|
60
71
|
function formatHsValue(val: any): string {
|
|
61
72
|
if (val === null || val === undefined) {
|
|
62
73
|
return 'CVNull'
|
|
@@ -75,8 +86,7 @@ function formatHsValue(val: any): string {
|
|
|
75
86
|
return '(CVList [' + items + '])'
|
|
76
87
|
}
|
|
77
88
|
if (typeof val === 'object') {
|
|
78
|
-
const
|
|
79
|
-
const items = keys
|
|
89
|
+
const items = Object.keys(val)
|
|
80
90
|
.map((k) => '(' + hsString(k) + ', ' + formatHsValue(val[k]) + ')')
|
|
81
91
|
.join(', ')
|
|
82
92
|
return '(CVMap [' + items + '])'
|
|
@@ -101,6 +101,27 @@ public final class Config {
|
|
|
101
101
|
return (Map<String, Object>) Json.parse(configJson());
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// SHARED CONFIG (sdkgen rung L2).
|
|
105
|
+
//
|
|
106
|
+
// The SDK reads the config on every request and never writes to it, so one
|
|
107
|
+
// instance is shared by every client rather than rebuilt per client - the
|
|
108
|
+
// difference between parsing the embedded JSON once and once per client.
|
|
109
|
+
//
|
|
110
|
+
// Initialization-on-demand holder: the JLS guarantees the class initializer
|
|
111
|
+
// runs once, lazily, and safely under concurrency, with no locking on the
|
|
112
|
+
// read path.
|
|
113
|
+
private static final class SharedHolder {
|
|
114
|
+
static final Map<String, Object> VALUE = makeConfig();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// The process-wide config, built once on first use.
|
|
118
|
+
//
|
|
119
|
+
// The returned map is SHARED: treat it as read-only. Callers that need to
|
|
120
|
+
// mutate should use makeConfig, which always parses a fresh copy.
|
|
121
|
+
public static Map<String, Object> sharedConfig() {
|
|
122
|
+
return SharedHolder.VALUE;
|
|
123
|
+
}
|
|
124
|
+
|
|
104
125
|
public static Feature makeFeature(String name) {
|
|
105
126
|
switch (name) {
|
|
106
127
|
`)
|
|
@@ -93,6 +93,21 @@ object Config {
|
|
|
93
93
|
return Json.parse(configJson()) as MutableMap<String, Any?>
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// SHARED CONFIG (sdkgen rung L2).
|
|
97
|
+
//
|
|
98
|
+
// The SDK reads the config on every request and never writes to it, so one
|
|
99
|
+
// instance is shared by every client rather than rebuilt per client - the
|
|
100
|
+
// difference between parsing the embedded JSON once and once per client.
|
|
101
|
+
//
|
|
102
|
+
// 'by lazy' defaults to LazyThreadSafetyMode.SYNCHRONIZED, so concurrent
|
|
103
|
+
// first calls build it exactly once.
|
|
104
|
+
//
|
|
105
|
+
// The returned map is SHARED: treat it as read-only. Callers that need to
|
|
106
|
+
// mutate should use makeConfig, which always parses a fresh copy.
|
|
107
|
+
private val sharedConfigVal: MutableMap<String, Any?> by lazy { makeConfig() }
|
|
108
|
+
|
|
109
|
+
fun sharedConfig(): MutableMap<String, Any?> = sharedConfigVal
|
|
110
|
+
|
|
96
111
|
fun makeFeature(name: String): Feature {
|
|
97
112
|
return when (name) {
|
|
98
113
|
`)
|