@voxgig/sdkgen 3.4.6 → 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/cmp/ReadmeTop.js +5 -1
- package/dist/cmp/ReadmeTop.js.map +1 -1
- 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 +56 -3
- 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 +31 -18
- 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/.sdk/tm/zig/test/gotcha_test.zig +8 -8
- 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/cmp/ReadmeTop.ts +6 -1
- 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
|
@@ -3,8 +3,11 @@ import {
|
|
|
3
3
|
Content,
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
|
+
configDefinition,
|
|
7
|
+
configReprSetting,
|
|
6
8
|
each,
|
|
7
9
|
isAuthActive,
|
|
10
|
+
isConfigData,
|
|
8
11
|
resolveAuthPrefix,
|
|
9
12
|
} from '@voxgig/sdkgen'
|
|
10
13
|
|
|
@@ -17,7 +20,7 @@ import {
|
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
import {
|
|
20
|
-
|
|
23
|
+
cljStringChunks,
|
|
21
24
|
formatCljValue,
|
|
22
25
|
} from './utility_clojure'
|
|
23
26
|
|
|
@@ -32,52 +35,44 @@ const Config = cmp(async function Config(props: any) {
|
|
|
32
35
|
|
|
33
36
|
const model: Model = ctx$.model
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
// The canonical config OBJECT and its JSON, from the shared helper. Both
|
|
39
|
+
// representations render from the same `def`, so they cannot describe
|
|
40
|
+
// different configs - and this target picks up `options.server` (the OpenAPI
|
|
41
|
+
// server-variable defaults), which the hand-rolled build here omitted.
|
|
42
|
+
const { def: config, json: configJson } = configDefinition(model)
|
|
43
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
File({ name: 'config.clj' }, () => {
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
48
|
+
//
|
|
49
|
+
// The literal is one nested form the Clojure reader walks and the compiler
|
|
50
|
+
// turns into constructor calls at load; a string is one token, and
|
|
51
|
+
// `sdk.core/json-parse` builds the same LinkedHashMap/ArrayList nodes
|
|
52
|
+
// `vs/jm` and `vs/jt` produce. It is already the SDK's response decoder,
|
|
53
|
+
// so this adds no dependency.
|
|
54
|
+
//
|
|
55
|
+
// JSON.stringify output is a valid Clojure string literal: it escapes
|
|
56
|
+
// every backslash, so the JSON's own `\uXXXX` reaches the reader as
|
|
57
|
+
// `\\uXXXX` and no Clojure escape is ever formed from it.
|
|
58
|
+
if (asData) {
|
|
59
|
+
Content(`;; ${model.const.Name} SDK configuration (generated by @voxgig/sdkgen).
|
|
60
|
+
;;
|
|
61
|
+
;; THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1). Emitted only above a size
|
|
62
|
+
;; threshold, or when main.kit.config.repr pins it: for a small model the
|
|
63
|
+
;; literal is smaller and far easier to read when debugging.
|
|
64
|
+
(ns sdk.config
|
|
65
|
+
(:require [sdk.core :as core]))
|
|
42
66
|
|
|
43
|
-
|
|
44
|
-
|
|
67
|
+
(def ^:private config-data
|
|
68
|
+
(str ${cljStringChunks(configJson).map((c: string) => JSON.stringify(c)).join('\n ')}))
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const entityOptions: any = {}
|
|
52
|
-
each(entity, (ent: any) => {
|
|
53
|
-
entityOptions[ent.name] = {}
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const options: any = {
|
|
57
|
-
base: baseUrl,
|
|
58
|
-
headers,
|
|
59
|
-
entity: entityOptions,
|
|
60
|
-
}
|
|
61
|
-
if (authActive) {
|
|
62
|
-
options.auth = { prefix: authPrefix }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const entityConfig = Object.values(entity || {}).reduce((a: any, n: any) => (
|
|
66
|
-
a[n.name] = clean({
|
|
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
|
-
}
|
|
70
|
+
(defn make-config []
|
|
71
|
+
(core/json-parse config-data))
|
|
72
|
+
`)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
79
75
|
|
|
80
|
-
File({ name: 'config.clj' }, () => {
|
|
81
76
|
Content(`;; ${model.const.Name} SDK configuration (generated by @voxgig/sdkgen).
|
|
82
77
|
(ns sdk.config
|
|
83
78
|
(:require [voxgig.struct :as vs]))
|
|
@@ -125,7 +125,49 @@ function clean(o: any, dropDefaults?: boolean): any {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
|
|
129
|
+
// The JSON as Clojure string literals, CHUNKED, joined at load.
|
|
130
|
+
//
|
|
131
|
+
// A Clojure string literal becomes a JVM constant-pool UTF-8 entry, and those
|
|
132
|
+
// are capped at 65,535 bytes. One constant therefore cannot hold a config
|
|
133
|
+
// large enough to select the data representation at all - the threshold is
|
|
134
|
+
// 256 KB, so every model that reaches this branch would exceed the limit.
|
|
135
|
+
//
|
|
136
|
+
// The failure is AOT-only and so is easy to miss: loading from source is fine,
|
|
137
|
+
// and `clojure -M:test-compile` only `require`s. It appears the moment anyone
|
|
138
|
+
// compiles the SDK, which is the normal path for distribution:
|
|
139
|
+
//
|
|
140
|
+
// Execution error (IllegalArgumentException)
|
|
141
|
+
// at clojure.asm.ByteVector/putUTF8 (ByteVector.java:245)
|
|
142
|
+
//
|
|
143
|
+
// java/kotlin/scala already chunk for exactly this reason (see Config_java's
|
|
144
|
+
// jsonAppendLines). Chunks are measured in UTF-8 BYTES, not characters, with
|
|
145
|
+
// a wide margin: modified UTF-8 encodes a supplementary character as six
|
|
146
|
+
// bytes, so a character count is not a byte count. Surrogate pairs are kept
|
|
147
|
+
// whole, or a cut would emit a lone surrogate.
|
|
148
|
+
function cljStringChunks(json: string, maxBytes: number = 20000): string[] {
|
|
149
|
+
const chunks: string[] = []
|
|
150
|
+
let start = 0
|
|
151
|
+
let bytes = 0
|
|
152
|
+
for (let i = 0; i < json.length; i++) {
|
|
153
|
+
const code = json.charCodeAt(i)
|
|
154
|
+
// Worst case per UTF-16 unit under modified UTF-8.
|
|
155
|
+
bytes += code < 0x80 ? 1 : code < 0x800 ? 2 : 3
|
|
156
|
+
const high = 0xd800 <= code && code <= 0xdbff
|
|
157
|
+
if (maxBytes <= bytes && !high) {
|
|
158
|
+
chunks.push(json.slice(start, i + 1))
|
|
159
|
+
start = i + 1
|
|
160
|
+
bytes = 0
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (start < json.length) {
|
|
164
|
+
chunks.push(json.slice(start))
|
|
165
|
+
}
|
|
166
|
+
return 0 === chunks.length ? [''] : chunks
|
|
167
|
+
}
|
|
168
|
+
|
|
128
169
|
export {
|
|
170
|
+
cljStringChunks,
|
|
129
171
|
clean,
|
|
130
172
|
formatCljValue,
|
|
131
173
|
cljString,
|
|
@@ -109,6 +109,24 @@ ${cppConfigLiterals(config)};
|
|
|
109
109
|
|
|
110
110
|
inline Value makeConfig() { return vs::parse_json(config_json()); }
|
|
111
111
|
|
|
112
|
+
// SHARED CONFIG (sdkgen rung L2).
|
|
113
|
+
//
|
|
114
|
+
// The SDK reads the config on every request and never writes to it, so one
|
|
115
|
+
// instance is shared by every client rather than rebuilt per client - this is
|
|
116
|
+
// the difference between parsing the embedded JSON once and once per client.
|
|
117
|
+
//
|
|
118
|
+
// A function-local static in an inline function is one object across every
|
|
119
|
+
// translation unit, and its initialisation is thread-safe by the standard.
|
|
120
|
+
// Value holds shared_ptr nodes, so copying the returned Value shares the
|
|
121
|
+
// structure rather than duplicating it.
|
|
122
|
+
//
|
|
123
|
+
// The result is SHARED: treat it as read-only. Callers that need to mutate
|
|
124
|
+
// should use makeConfig, which always parses a fresh copy.
|
|
125
|
+
inline const Value& sharedConfig() {
|
|
126
|
+
static const Value shared = makeConfig();
|
|
127
|
+
return shared;
|
|
128
|
+
}
|
|
129
|
+
|
|
112
130
|
inline FeaturePtr makeFeature(const std::string& name) {
|
|
113
131
|
`)
|
|
114
132
|
|
|
@@ -3,9 +3,10 @@ import {
|
|
|
3
3
|
Content,
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
|
+
configDefinition,
|
|
7
|
+
configReprSetting,
|
|
6
8
|
each,
|
|
7
|
-
|
|
8
|
-
resolveAuthPrefix,
|
|
9
|
+
isConfigData,
|
|
9
10
|
} from '@voxgig/sdkgen'
|
|
10
11
|
|
|
11
12
|
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
import {
|
|
20
|
-
|
|
21
|
+
csStringLiteral,
|
|
21
22
|
formatCsMap,
|
|
22
23
|
} from './utility_csharp'
|
|
23
24
|
|
|
@@ -32,78 +33,150 @@ const Config = cmp(async function Config(props: any) {
|
|
|
32
33
|
|
|
33
34
|
const model: Model = ctx$.model
|
|
34
35
|
|
|
35
|
-
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
36
36
|
const feature = getModelPath(model, `main.${KIT}.feature`)
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
let baseUrl = ''
|
|
45
|
-
try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
|
|
46
|
-
|
|
47
|
-
const authBlock = authActive
|
|
48
|
-
? ` ["auth"] = new Dictionary<string, object?>
|
|
49
|
-
{
|
|
50
|
-
["prefix"] = "${authPrefix}",
|
|
51
|
-
},\n`
|
|
52
|
-
: ''
|
|
38
|
+
// The same config as an OBJECT, built by the shared helper so this target's
|
|
39
|
+
// literal and the data that replaces it above the threshold are the same
|
|
40
|
+
// config by construction. The JSON is what the threshold is measured on -
|
|
41
|
+
// emitted source size varies by language, the model does not.
|
|
42
|
+
const { def: configDef, json: configJson } = configDefinition(model)
|
|
43
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
53
44
|
|
|
54
45
|
File({ name: 'Config.' + target.ext }, () => {
|
|
55
46
|
|
|
56
47
|
Content(`// ${model.const.Name} SDK - generated model configuration and feature
|
|
57
48
|
// factory. GENERATED from the API model - do not edit by hand.
|
|
58
|
-
|
|
49
|
+
${asData ? '\nusing System.Text.Json;\n' : ''}
|
|
59
50
|
namespace ${model.const.Name}Sdk;
|
|
60
51
|
|
|
61
52
|
public static class SdkConfig
|
|
62
53
|
{
|
|
63
|
-
|
|
54
|
+
`)
|
|
55
|
+
|
|
56
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
57
|
+
//
|
|
58
|
+
// A composite Dictionary literal is a single expression the C# compiler
|
|
59
|
+
// must bind, type and lower node by node, and every entry becomes IL the
|
|
60
|
+
// JIT executes on first call. A string constant is one token, and
|
|
61
|
+
// System.Text.Json builds the same dictionary from it far faster.
|
|
62
|
+
//
|
|
63
|
+
// JSON.stringify output is ALMOST a valid C# string literal: every escape
|
|
64
|
+
// it emits (\\", \\\\, \\b, \\f, \\n, \\r, \\t, \\uXXXX) means the same thing in C#,
|
|
65
|
+
// and it never emits \\/ or \\0, neither of which C# would accept. What it
|
|
66
|
+
// does leave raw is U+0085/U+2028/U+2029, which C# counts as line
|
|
67
|
+
// terminators and forbids inside a quoted literal - hence csStringLiteral.
|
|
68
|
+
if (asData) {
|
|
69
|
+
Content(` // THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
70
|
+
//
|
|
71
|
+
// Emitted only above a size threshold, or when \`main.kit.config.repr\`
|
|
72
|
+
// pins it: for a small model the literal is smaller and far easier to
|
|
73
|
+
// read when debugging.
|
|
74
|
+
private const string ConfigData = ${csStringLiteral(configJson)};
|
|
75
|
+
|
|
76
|
+
// Boxed numerics compare by exact type - (object)5L does not Equals
|
|
77
|
+
// (object)5 - and MakeConfig is public API consumers read numbers out of,
|
|
78
|
+
// so the two representations must not disagree about the type of a whole
|
|
79
|
+
// number just because the model crossed a size threshold. This ladder
|
|
80
|
+
// (int, else long, else double) is exactly what the literal branch emits;
|
|
81
|
+
// see formatCsNumber in the generator.
|
|
82
|
+
//
|
|
83
|
+
// Deliberately NOT SdkUtility.JsonToNative: that helper's conditional
|
|
84
|
+
// operator gives \`TryGetInt64(out var l) ? l : el.GetDouble()\` the common
|
|
85
|
+
// type double, so it boxes every whole number as a double regardless of
|
|
86
|
+
// its own doc comment. Reusing it here would make the data branch
|
|
87
|
+
// disagree with the literal on every integer in the model.
|
|
88
|
+
private static object? ConfigValue(JsonElement el)
|
|
64
89
|
{
|
|
65
|
-
|
|
90
|
+
switch (el.ValueKind)
|
|
66
91
|
{
|
|
67
|
-
|
|
92
|
+
case JsonValueKind.Object:
|
|
68
93
|
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
94
|
+
var map = new Dictionary<string, object?>();
|
|
95
|
+
foreach (var prop in el.EnumerateObject())
|
|
96
|
+
{
|
|
97
|
+
map[prop.Name] = ConfigValue(prop.Value);
|
|
98
|
+
}
|
|
99
|
+
return map;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
case JsonValueKind.Array:
|
|
72
103
|
{
|
|
73
|
-
|
|
104
|
+
var list = new List<object?>();
|
|
105
|
+
foreach (var item in el.EnumerateArray())
|
|
106
|
+
{
|
|
107
|
+
list.Add(ConfigValue(item));
|
|
108
|
+
}
|
|
109
|
+
return list;
|
|
110
|
+
}
|
|
74
111
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Content(` ["${f.name}"] = ${formatCsMap(fconfig, 4)},
|
|
78
|
-
`)
|
|
79
|
-
})
|
|
112
|
+
case JsonValueKind.String:
|
|
113
|
+
return el.GetString();
|
|
80
114
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{
|
|
84
|
-
["base"] = "${baseUrl}",
|
|
85
|
-
${authBlock} ["headers"] = ${formatCsMap(headers, 4)},
|
|
86
|
-
["entity"] = new Dictionary<string, object?>
|
|
115
|
+
case JsonValueKind.Number:
|
|
116
|
+
if (el.TryGetInt32(out var i))
|
|
87
117
|
{
|
|
88
|
-
|
|
118
|
+
return i;
|
|
119
|
+
}
|
|
120
|
+
if (el.TryGetInt64(out var l))
|
|
121
|
+
{
|
|
122
|
+
return l;
|
|
123
|
+
}
|
|
124
|
+
return el.GetDouble();
|
|
125
|
+
|
|
126
|
+
case JsonValueKind.True:
|
|
127
|
+
return true;
|
|
128
|
+
|
|
129
|
+
case JsonValueKind.False:
|
|
130
|
+
return false;
|
|
89
131
|
|
|
90
|
-
|
|
91
|
-
|
|
132
|
+
default:
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Parses a fresh, fully materialised config dictionary. Every call
|
|
138
|
+
// re-parses, so hold the result if you need it more than once.
|
|
139
|
+
public static Dictionary<string, object?> MakeConfig()
|
|
140
|
+
{
|
|
141
|
+
return ConfigValue(JsonSerializer.Deserialize<JsonElement>(ConfigData))
|
|
142
|
+
as Dictionary<string, object?>
|
|
143
|
+
?? new Dictionary<string, object?>();
|
|
144
|
+
}
|
|
92
145
|
`)
|
|
93
|
-
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
Content(` public static Dictionary<string, object?> MakeConfig()
|
|
149
|
+
{
|
|
150
|
+
return ${formatCsMap(configDef, 2)};
|
|
151
|
+
}
|
|
152
|
+
`)
|
|
153
|
+
}
|
|
94
154
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
155
|
+
// SHARED CONFIG (sdkgen rung L2).
|
|
156
|
+
//
|
|
157
|
+
// The SDK reads the config on every request and never writes to it, so one
|
|
158
|
+
// instance is shared by every client rather than rebuilt per client. Above
|
|
159
|
+
// the size threshold MakeConfig re-parses the whole embedded JSON, so this
|
|
160
|
+
// is the difference between parsing the model once per process and once
|
|
161
|
+
// per client.
|
|
162
|
+
//
|
|
163
|
+
// Lazy<T> defaults to ExecutionAndPublication, so concurrent first calls
|
|
164
|
+
// build it exactly once - the C# twin of go's sync.Once.
|
|
165
|
+
Content(`
|
|
166
|
+
private static readonly Lazy<Dictionary<string, object?>> SharedConfigVal =
|
|
167
|
+
new(MakeConfig);
|
|
168
|
+
|
|
169
|
+
// The process-wide config, built once on first use.
|
|
170
|
+
//
|
|
171
|
+
// The returned dictionary is SHARED: treat it as read-only. Callers that
|
|
172
|
+
// need to mutate should use MakeConfig, which always returns a fresh copy.
|
|
173
|
+
public static Dictionary<string, object?> SharedConfig()
|
|
174
|
+
{
|
|
175
|
+
return SharedConfigVal.Value;
|
|
105
176
|
}
|
|
177
|
+
`)
|
|
106
178
|
|
|
179
|
+
Content(`
|
|
107
180
|
public static Feature.BaseFeature MakeFeature(string name)
|
|
108
181
|
{
|
|
109
182
|
switch (name)
|
|
@@ -27,7 +27,9 @@ public class ProjectNameSDK
|
|
|
27
27
|
{
|
|
28
28
|
_utility = new Utility();
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
// The process-wide config (sdkgen rung L2): read-only on the request
|
|
31
|
+
// path, so every client shares one rather than rebuilding it.
|
|
32
|
+
var config = global::ProjectNameSdk.SdkConfig.SharedConfig();
|
|
31
33
|
|
|
32
34
|
_rootctx = _utility.MakeContext(new Dictionary<string, object?>
|
|
33
35
|
{
|
|
@@ -86,13 +86,103 @@ function formatCsMap(obj: any, indent: number = 0): string {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
// C# NEW-LINE CHARACTERS, which a regular quoted string literal may not
|
|
90
|
+
// contain: U+000D, U+000A, U+0085 (NEL), U+2028 (LINE SEPARATOR) and U+2029
|
|
91
|
+
// (PARAGRAPH SEPARATOR) - C# language spec, "Line terminators".
|
|
92
|
+
//
|
|
93
|
+
// The first two everyone escapes. The other three are the trap: they are
|
|
94
|
+
// ordinary characters in JSON, in every other target language, and to the eye,
|
|
95
|
+
// so a model string carrying one (an OpenAPI description, default or example
|
|
96
|
+
// pasted from a word processor) emits a C# source file that fails to compile
|
|
97
|
+
// with `error CS1010: Newline in constant`. Verified against .NET 8.
|
|
98
|
+
//
|
|
99
|
+
// Written as ESCAPES, not as the characters themselves: U+2028 and U+2029
|
|
100
|
+
// are line terminators in JavaScript too, so a regex literal containing them
|
|
101
|
+
// verbatim does not parse ("Invalid regular expression: missing /") and this
|
|
102
|
+
// module fails to load. The same trap, one language up.
|
|
103
|
+
const CS_NEWLINE = /[\u0085\u2028\u2029]/g
|
|
104
|
+
|
|
105
|
+
function csEscapeNewlines(s: string): string {
|
|
106
|
+
return s.replace(CS_NEWLINE,
|
|
107
|
+
(c) => '\\u' + (c.codePointAt(0) as number).toString(16).padStart(4, '0'))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
89
111
|
function formatCsString(val: string): string {
|
|
90
|
-
return '"' + val
|
|
112
|
+
return '"' + csEscapeNewlines(val
|
|
91
113
|
.replace(/\\/g, '\\\\')
|
|
92
114
|
.replace(/"/g, '\\"')
|
|
93
115
|
.replace(/\n/g, '\\n')
|
|
94
116
|
.replace(/\r/g, '\\r')
|
|
95
|
-
.replace(/\t/g, '\\t') + '"'
|
|
117
|
+
.replace(/\t/g, '\\t')) + '"'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
// The whole config JSON as a C# string literal.
|
|
122
|
+
//
|
|
123
|
+
// JSON.stringify output is otherwise a valid C# literal - every escape it
|
|
124
|
+
// emits means the same thing in C#, and it never emits \/ or \0 - but it
|
|
125
|
+
// leaves U+0085/U+2028/U+2029 RAW, and those three end a C# string literal.
|
|
126
|
+
function csStringLiteral(s: string): string {
|
|
127
|
+
return csEscapeNewlines(JSON.stringify(s))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// A number as a C# literal whose BOXED TYPE is predictable.
|
|
132
|
+
//
|
|
133
|
+
// C# gives an unsuffixed integer literal the first type of int/uint/long/ulong
|
|
134
|
+
// that fits it, so `3000000000` boxes as uint while `5` boxes as int and
|
|
135
|
+
// `5000000000` as long. Boxed numerics compare by exact type - (object)5L does
|
|
136
|
+
// not Equals (object)5 - so the type a config number lands with is observable
|
|
137
|
+
// to every consumer, and the L1 data branch has to be able to reproduce it.
|
|
138
|
+
// Reproducing a uint band nothing else in the SDK ever produces is not worth
|
|
139
|
+
// it, so the suffix is made explicit instead and the ladder is just int -> long
|
|
140
|
+
// -> double, which SdkConfig.ConfigValue mirrors exactly.
|
|
141
|
+
//
|
|
142
|
+
// THE RANGE TEST IS ON THE EMITTED TEXT, not on the JS number.
|
|
143
|
+
//
|
|
144
|
+
// `String(val)` is exactly what JSON.stringify puts in the data branch, and it
|
|
145
|
+
// prints a double as the SHORTEST decimal that round-trips - which for a value
|
|
146
|
+
// near the 64-bit boundary is not the value's exact decimal expansion. The
|
|
147
|
+
// clearest case is long.MinValue: `String(-9223372036854775808)` is
|
|
148
|
+
// "-9223372036854776000", which is BELOW long.MinValue, so TryGetInt64 refuses
|
|
149
|
+
// it and the data branch yields a double. Deciding from the JS value instead
|
|
150
|
+
// emitted `long.MinValue` and the two branches disagreed at exactly that
|
|
151
|
+
// boundary. Comparing the text as a BigInt makes both sides ask the same
|
|
152
|
+
// question of the same digits.
|
|
153
|
+
//
|
|
154
|
+
// int.MinValue is written by name: `-2147483648` parses as unary minus applied
|
|
155
|
+
// to a literal that does not itself fit in an int, and relying on the special
|
|
156
|
+
// case for that is needless subtlety. There is deliberately no long.MinValue
|
|
157
|
+
// twin - no JS number prints as that text, so it is unreachable.
|
|
158
|
+
//
|
|
159
|
+
// A non-finite value emits `null`, because that is what JSON.stringify does
|
|
160
|
+
// with it - so the two representations still agree.
|
|
161
|
+
const CS_INT_MIN = -2147483648n
|
|
162
|
+
const CS_INT_MAX = 2147483647n
|
|
163
|
+
const CS_LONG_MIN = -9223372036854775808n
|
|
164
|
+
const CS_LONG_MAX = 9223372036854775807n
|
|
165
|
+
|
|
166
|
+
function formatCsNumber(val: number): string {
|
|
167
|
+
if (!Number.isFinite(val)) {
|
|
168
|
+
return 'null'
|
|
169
|
+
}
|
|
170
|
+
const text = String(val)
|
|
171
|
+
// An integer TOKEN, not merely an integral value: `String` switches to
|
|
172
|
+
// exponential form at 1e21, which is not an integer literal on either side.
|
|
173
|
+
if (Number.isInteger(val) && /^-?\d+$/.test(text)) {
|
|
174
|
+
const n = BigInt(text)
|
|
175
|
+
if (CS_INT_MIN <= n && n <= CS_INT_MAX) {
|
|
176
|
+
return CS_INT_MIN === n ? 'int.MinValue' : text
|
|
177
|
+
}
|
|
178
|
+
if (CS_LONG_MIN <= n && n <= CS_LONG_MAX) {
|
|
179
|
+
return text + 'L'
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Fractional, or outside the long range: a double literal. The `D` suffix is
|
|
183
|
+
// required for the whole-valued case, where the digits alone would be read
|
|
184
|
+
// as an integer literal (and rejected as too large).
|
|
185
|
+
return text + 'D'
|
|
96
186
|
}
|
|
97
187
|
|
|
98
188
|
|
|
@@ -104,10 +194,7 @@ function formatCsValue(val: any, indent: number = 0): string {
|
|
|
104
194
|
return formatCsString(val)
|
|
105
195
|
}
|
|
106
196
|
if (typeof val === 'number') {
|
|
107
|
-
|
|
108
|
-
return String(val)
|
|
109
|
-
}
|
|
110
|
-
return String(val)
|
|
197
|
+
return formatCsNumber(val)
|
|
111
198
|
}
|
|
112
199
|
if (typeof val === 'boolean') {
|
|
113
200
|
return val ? 'true' : 'false'
|
|
@@ -186,6 +273,7 @@ function clean(o: any, dropDefaults?: boolean): any {
|
|
|
186
273
|
export {
|
|
187
274
|
clean,
|
|
188
275
|
csPascalName,
|
|
276
|
+
csStringLiteral,
|
|
189
277
|
csVarName,
|
|
190
278
|
formatCsMap,
|
|
191
279
|
formatCsValue,
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
Fragment,
|
|
8
8
|
Line,
|
|
9
9
|
cmp,
|
|
10
|
+
configDefinition,
|
|
11
|
+
configReprSetting,
|
|
10
12
|
each,
|
|
13
|
+
isConfigData,
|
|
11
14
|
isAuthActive,
|
|
12
15
|
resolveAuthPrefix,
|
|
13
16
|
} from '@voxgig/sdkgen'
|
|
@@ -23,6 +26,7 @@ import {
|
|
|
23
26
|
|
|
24
27
|
import {
|
|
25
28
|
clean,
|
|
29
|
+
dartStringLiteral,
|
|
26
30
|
dartValue,
|
|
27
31
|
} from './utility_dart'
|
|
28
32
|
|
|
@@ -51,8 +55,55 @@ const Config = cmp(async function Config(props: any) {
|
|
|
51
55
|
`
|
|
52
56
|
: ''
|
|
53
57
|
|
|
58
|
+
// Read the base URL here rather than leaving it to a `$$...$$` stdrep
|
|
59
|
+
// placeholder in the fragment. stdrep can only substitute a path the model
|
|
60
|
+
// actually has: a model with no `info.servers` left the placeholder itself in
|
|
61
|
+
// the generated source, so `options.base` came out as the literal string
|
|
62
|
+
// '$main.kit.info.servers.0.url$'. Reading it explicitly yields '' in that
|
|
63
|
+
// case, which is what every other target already emits, and is identical to
|
|
64
|
+
// the old output whenever the model does define a server. Same defect, and
|
|
65
|
+
// same fix, as ts and js.
|
|
66
|
+
let baseUrl = ''
|
|
67
|
+
try {
|
|
68
|
+
baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`)
|
|
69
|
+
} catch (_e) { }
|
|
70
|
+
|
|
71
|
+
// The same config as an OBJECT, built by the shared helper so this target's
|
|
72
|
+
// literal and the data that replaces it above the threshold are the same
|
|
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
|
+
const { def: configDef, json: configJson } = configDefinition(model)
|
|
76
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
77
|
+
|
|
54
78
|
File({ name: 'Config.' + target.ext }, () => {
|
|
55
79
|
|
|
80
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
81
|
+
//
|
|
82
|
+
// `jsonDecode` yields exactly what the literal declared - Map<String,
|
|
83
|
+
// dynamic> for objects, List<dynamic> for arrays, and int for a whole
|
|
84
|
+
// number where Dart source would also have written an int - so the fields
|
|
85
|
+
// keep their types and callers cannot tell the representations apart.
|
|
86
|
+
if (asData) {
|
|
87
|
+
Fragment({
|
|
88
|
+
from: ff + 'Config.data.fragment.dart',
|
|
89
|
+
|
|
90
|
+
replace: {
|
|
91
|
+
...ctx$.stdrep,
|
|
92
|
+
|
|
93
|
+
'// #ImportFeatures': () => each(feature, (f: any) => {
|
|
94
|
+
Line(`import 'feature/${f.name}/${nom(f, 'Name')}Feature.dart';`)
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
'// #FeatureClasses': () => each(feature, (f: any) => {
|
|
98
|
+
Line(` '${f.name}': () => ${nom(f, 'Name')}Feature(),`)
|
|
99
|
+
}),
|
|
100
|
+
|
|
101
|
+
"'CONFIGJSON'": dartStringLiteral(configJson),
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
56
107
|
Fragment({
|
|
57
108
|
from: ff + 'Config.fragment.dart',
|
|
58
109
|
|
|
@@ -64,9 +115,11 @@ const Config = cmp(async function Config(props: any) {
|
|
|
64
115
|
// these; this one did not.
|
|
65
116
|
...ctx$.stdrep,
|
|
66
117
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
118
|
+
// The whole options map from the canonical definition. Assembling it
|
|
119
|
+
// slot by slot lost `options.server` entirely, so a spec with a
|
|
120
|
+
// templated server URL described a different config either side of the
|
|
121
|
+
// threshold.
|
|
122
|
+
"'OPTIONSMAP'": dartValue(configDef.options, 1),
|
|
70
123
|
|
|
71
124
|
'// #ImportFeatures': () => each(feature, (f: any) => {
|
|
72
125
|
Line(`import 'feature/${f.name}/${nom(f, 'Name')}Feature.dart';`)
|