@voxgig/sdkgen 3.4.5 → 3.4.7
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/dist/cmp/ReadmeTop.js +5 -1
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/opExample.js +10 -1
- package/dist/helpers/opExample.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +6 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +8 -1
- package/dist/utility.js +144 -0
- package/dist/utility.js.map +1 -1
- package/package.json +1 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +45 -0
- package/project/.sdk/src/cmp/c/utility_c.ts +36 -0
- package/project/.sdk/src/cmp/dart/Config_dart.ts +15 -0
- package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +1 -1
- package/project/.sdk/src/cmp/go/Config_go.ts +9 -52
- package/project/.sdk/src/cmp/js/Config_js.ts +49 -1
- package/project/.sdk/src/cmp/js/fragment/Config.data.fragment.js +50 -0
- package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +1 -1
- package/project/.sdk/src/cmp/lua/Config_lua.ts +51 -1
- package/project/.sdk/src/cmp/lua/utility_lua.ts +21 -0
- package/project/.sdk/src/cmp/php/Config_php.ts +86 -0
- package/project/.sdk/src/cmp/py/Config_py.ts +47 -1
- package/project/.sdk/src/cmp/rb/Config_rb.ts +46 -2
- package/project/.sdk/src/cmp/rust/Config_rust.ts +50 -35
- package/project/.sdk/src/cmp/rust/utility_rust.ts +17 -0
- package/project/.sdk/src/cmp/ts/Config_ts.ts +10 -50
- package/project/.sdk/src/cmp/zig/Config_zig.ts +47 -40
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +28 -17
- package/project/.sdk/tm/zig/test/gotcha_test.zig +8 -8
- package/src/cmp/ReadmeTop.ts +6 -1
- package/src/helpers/opExample.ts +11 -1
- package/src/sdkgen.ts +6 -1
- package/src/utility.ts +157 -1
|
@@ -127,7 +127,28 @@ function clean(o: any, dropDefaults?: boolean): any {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
|
|
131
|
+
// A Lua LONG-BRACKET string holding `s` verbatim.
|
|
132
|
+
//
|
|
133
|
+
// Lua's quoted strings process escapes, so the JSON's own `\n` and `\uXXXX`
|
|
134
|
+
// would be consumed by the Lua lexer before the JSON decoder ever saw them -
|
|
135
|
+
// turning an escaped newline into a real one inside a JSON string (invalid
|
|
136
|
+
// JSON), and failing outright on `\uXXXX`, which Lua 5.1/5.2 do not accept at
|
|
137
|
+
// all. A long bracket processes nothing, so the JSON text survives byte for
|
|
138
|
+
// byte. The level is raised until its terminator does not occur in the text.
|
|
139
|
+
function luaLongString(s: string): string {
|
|
140
|
+
let level = 0
|
|
141
|
+
while (s.includes(']' + '='.repeat(level) + ']')) {
|
|
142
|
+
level++
|
|
143
|
+
}
|
|
144
|
+
const eq = '='.repeat(level)
|
|
145
|
+
// A long bracket swallows an immediately following newline, so start the
|
|
146
|
+
// content on the same line as the opener.
|
|
147
|
+
return '[' + eq + '[' + s + ']' + eq + ']'
|
|
148
|
+
}
|
|
149
|
+
|
|
130
150
|
export {
|
|
151
|
+
luaLongString,
|
|
131
152
|
clean,
|
|
132
153
|
formatLuaTable,
|
|
133
154
|
formatLuaValue,
|
|
@@ -8,8 +8,12 @@ import {
|
|
|
8
8
|
Fragment,
|
|
9
9
|
Line,
|
|
10
10
|
cmp,
|
|
11
|
+
configDefinition,
|
|
12
|
+
configReprSetting,
|
|
11
13
|
each,
|
|
12
14
|
isAuthActive,
|
|
15
|
+
isConfigData,
|
|
16
|
+
rawStringLiteral,
|
|
13
17
|
resolveAuthPrefix,
|
|
14
18
|
serverVariables,
|
|
15
19
|
} from '@voxgig/sdkgen'
|
|
@@ -63,6 +67,13 @@ const Config = cmp(async function Config(props: any) {
|
|
|
63
67
|
],\n`
|
|
64
68
|
: ''
|
|
65
69
|
|
|
70
|
+
// The same config as an OBJECT, built by the shared helper so this target's
|
|
71
|
+
// literal and the data that replaces it above the threshold are the same
|
|
72
|
+
// config by construction. The JSON is what the threshold is measured on -
|
|
73
|
+
// emitted source size varies by language, the model does not.
|
|
74
|
+
const { json: configJson } = configDefinition(model)
|
|
75
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
76
|
+
|
|
66
77
|
File({ name: 'config.' + target.ext }, () => {
|
|
67
78
|
|
|
68
79
|
Content(`<?php
|
|
@@ -91,7 +102,79 @@ class ${model.const.Name}Config
|
|
|
91
102
|
return self::$shared_config;
|
|
92
103
|
}
|
|
93
104
|
|
|
105
|
+
`)
|
|
106
|
+
|
|
107
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
108
|
+
//
|
|
109
|
+
// An array literal is compiled opcode by opcode and held in the opcache
|
|
110
|
+
// entry for this file; a string constant is one token, and `json_decode`
|
|
111
|
+
// (C) builds the array far faster than the equivalent literal.
|
|
112
|
+
//
|
|
113
|
+
// PHP cannot tell an empty list from an empty map, so `{}` and `[]` decode
|
|
114
|
+
// to the same value and the two representations have to AGREE about which
|
|
115
|
+
// one the literal would have produced. `formatPhpArray` emits `[]` for
|
|
116
|
+
// every empty map, and the literal branch hand-writes `(object)[]` in
|
|
117
|
+
// exactly two places - `entity` and `options.entity`, and only when the
|
|
118
|
+
// model declares no entities at all, because the SDK runtime validator
|
|
119
|
+
// wants a map there. This reproduces that rule rather than improving on
|
|
120
|
+
// it: an emission wart is not something the data path gets to fix
|
|
121
|
+
// unilaterally, or the two branches stop being interchangeable.
|
|
122
|
+
//
|
|
123
|
+
// A SINGLE-quoted literal, so the JSON survives verbatim: a double-quoted
|
|
124
|
+
// PHP string would interpolate any `$name` the model contains - and the
|
|
125
|
+
// model is full of them (`$STRING`, `$action`).
|
|
126
|
+
if (asData) {
|
|
127
|
+
Content(` /**
|
|
128
|
+
* THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
129
|
+
*
|
|
130
|
+
* Emitted only above a size threshold, or when \`main.kit.config.repr\`
|
|
131
|
+
* pins it: for a small model the array literal is smaller and far easier
|
|
132
|
+
* to read when debugging.
|
|
133
|
+
*/
|
|
134
|
+
private const CONFIG_DATA = ${rawStringLiteral(configJson)};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Decoded JSON in the shape the literal branch produces: every map
|
|
138
|
+
* becomes an array, including an empty one.
|
|
139
|
+
*/
|
|
140
|
+
private static function config_decode(mixed $v): mixed
|
|
141
|
+
{
|
|
142
|
+
if ($v instanceof \\stdClass) {
|
|
143
|
+
$out = [];
|
|
144
|
+
foreach (get_object_vars($v) as $k => $c) {
|
|
145
|
+
$out[$k] = self::config_decode($c);
|
|
146
|
+
}
|
|
147
|
+
return $out;
|
|
148
|
+
}
|
|
149
|
+
if (is_array($v)) {
|
|
150
|
+
return array_map([self::class, 'config_decode'], $v);
|
|
151
|
+
}
|
|
152
|
+
return $v;
|
|
153
|
+
}
|
|
154
|
+
|
|
94
155
|
/**
|
|
156
|
+
* Parse a fresh, fully materialised config array. Every call re-parses,
|
|
157
|
+
* so prefer shared_config unless you need a private copy.
|
|
158
|
+
*/
|
|
159
|
+
public static function make_config(): array
|
|
160
|
+
{
|
|
161
|
+
/** @var array<string,mixed> $out */
|
|
162
|
+
$out = self::config_decode(json_decode(self::CONFIG_DATA));
|
|
163
|
+
|
|
164
|
+
// The two map-shape exceptions the literal branch makes by hand.
|
|
165
|
+
if (count($out["entity"]) === 0) {
|
|
166
|
+
$out["entity"] = (object)[];
|
|
167
|
+
}
|
|
168
|
+
if (count($out["options"]["entity"]) === 0) {
|
|
169
|
+
$out["options"]["entity"] = (object)[];
|
|
170
|
+
}
|
|
171
|
+
return $out;
|
|
172
|
+
}
|
|
173
|
+
`)
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
|
|
177
|
+
Content(` /**
|
|
95
178
|
* Build a fresh, fully materialised config array. Every call rebuilds the
|
|
96
179
|
* whole structure, so prefer shared_config unless you need a private copy.
|
|
97
180
|
*/
|
|
@@ -151,7 +234,10 @@ ${serverBlock}${authBlock} "headers" => ${formatPhpArray(headers,
|
|
|
151
234
|
}
|
|
152
235
|
|
|
153
236
|
Content(` }
|
|
237
|
+
`)
|
|
238
|
+
}
|
|
154
239
|
|
|
240
|
+
Content(`
|
|
155
241
|
|
|
156
242
|
public static function make_feature(string $name)
|
|
157
243
|
{
|
|
@@ -8,8 +8,11 @@ import {
|
|
|
8
8
|
Fragment,
|
|
9
9
|
Line,
|
|
10
10
|
cmp,
|
|
11
|
+
configDefinition,
|
|
12
|
+
configReprSetting,
|
|
11
13
|
each,
|
|
12
14
|
isAuthActive,
|
|
15
|
+
isConfigData,
|
|
13
16
|
resolveAuthPrefix,
|
|
14
17
|
serverVariables,
|
|
15
18
|
} from '@voxgig/sdkgen'
|
|
@@ -61,10 +64,17 @@ const Config = cmp(async function Config(props: any) {
|
|
|
61
64
|
},\n`
|
|
62
65
|
: ''
|
|
63
66
|
|
|
67
|
+
// The same config as an OBJECT, built by the shared helper so this target's
|
|
68
|
+
// literal and the data that replaces it above the threshold are the same
|
|
69
|
+
// config by construction. The JSON is what the threshold is measured on -
|
|
70
|
+
// emitted source size varies by language, the model does not.
|
|
71
|
+
const { json: configJson } = configDefinition(model)
|
|
72
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
73
|
+
|
|
64
74
|
File({ name: 'config.' + target.ext }, () => {
|
|
65
75
|
|
|
66
76
|
Content(`# ${model.const.Name} SDK configuration
|
|
67
|
-
|
|
77
|
+
${asData ? '\nimport json\n' : ''}
|
|
68
78
|
|
|
69
79
|
_shared_config = None
|
|
70
80
|
|
|
@@ -84,7 +94,43 @@ def shared_config():
|
|
|
84
94
|
return _shared_config
|
|
85
95
|
|
|
86
96
|
|
|
97
|
+
`)
|
|
98
|
+
|
|
99
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
100
|
+
//
|
|
101
|
+
// A dict literal makes CPython build the whole structure opcode by opcode
|
|
102
|
+
// at import, and the compiler hold the entire literal in memory to produce
|
|
103
|
+
// that bytecode. A string constant is one object, and `json.loads` (the C
|
|
104
|
+
// scanner) builds the dict far faster than the equivalent literal.
|
|
105
|
+
//
|
|
106
|
+
// `json.loads` yields exactly what the literal did - str keys, int for
|
|
107
|
+
// whole numbers, True/False/None - so make_config's result is unchanged.
|
|
108
|
+
//
|
|
109
|
+
// JSON.stringify output is a valid Python string literal: every escape it
|
|
110
|
+
// emits (\\", \\\\, \\n, \\uXXXX) means the same thing in Python, it never emits
|
|
111
|
+
// \\/ (which Python would not treat as an escape), and Python 3 source is
|
|
112
|
+
// UTF-8 so non-ASCII needs no escaping.
|
|
113
|
+
if (asData) {
|
|
114
|
+
Content(`# THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
115
|
+
#
|
|
116
|
+
# Emitted only above a size threshold, or when \`main.kit.config.repr\` pins it:
|
|
117
|
+
# for a small model the dict literal is smaller and far easier to read when
|
|
118
|
+
# debugging.
|
|
119
|
+
_CONFIG_DATA = ${JSON.stringify(configJson)}
|
|
120
|
+
|
|
121
|
+
|
|
87
122
|
def make_config():
|
|
123
|
+
"""Parse a fresh, fully materialised config dict.
|
|
124
|
+
|
|
125
|
+
Every call re-parses, so prefer shared_config unless you need a private
|
|
126
|
+
copy you intend to mutate.
|
|
127
|
+
"""
|
|
128
|
+
return json.loads(_CONFIG_DATA)
|
|
129
|
+
`)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Content(`def make_config():
|
|
88
134
|
"""Build a fresh, fully materialised config dict.
|
|
89
135
|
|
|
90
136
|
Every call rebuilds the whole structure, so prefer shared_config unless
|
|
@@ -8,8 +8,12 @@ import {
|
|
|
8
8
|
Fragment,
|
|
9
9
|
Line,
|
|
10
10
|
cmp,
|
|
11
|
+
configDefinition,
|
|
12
|
+
configReprSetting,
|
|
11
13
|
each,
|
|
12
14
|
isAuthActive,
|
|
15
|
+
isConfigData,
|
|
16
|
+
rawStringLiteral,
|
|
13
17
|
resolveAuthPrefix,
|
|
14
18
|
serverVariables,
|
|
15
19
|
} from '@voxgig/sdkgen'
|
|
@@ -63,10 +67,17 @@ const Config = cmp(async function Config(props: any) {
|
|
|
63
67
|
},\n`
|
|
64
68
|
: ''
|
|
65
69
|
|
|
70
|
+
// The same config as an OBJECT, built by the shared helper so this target's
|
|
71
|
+
// literal and the data that replaces it above the threshold are the same
|
|
72
|
+
// config by construction. The JSON is what the threshold is measured on -
|
|
73
|
+
// emitted source size varies by language, the model does not.
|
|
74
|
+
const { json: configJson } = configDefinition(model)
|
|
75
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
76
|
+
|
|
66
77
|
File({ name: 'config.' + target.ext }, () => {
|
|
67
78
|
|
|
68
79
|
Content(`# ${model.const.Name} SDK configuration
|
|
69
|
-
|
|
80
|
+
${asData ? "\nrequire 'json'\n" : ''}
|
|
70
81
|
module ${model.const.Name}Config
|
|
71
82
|
# Return the process-wide config, built once on first use. The SDK reads
|
|
72
83
|
# the config on every request and never writes to it, so one instance is
|
|
@@ -79,7 +90,37 @@ module ${model.const.Name}Config
|
|
|
79
90
|
end
|
|
80
91
|
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
`)
|
|
94
|
+
|
|
95
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
96
|
+
//
|
|
97
|
+
// A hash literal makes the Ruby parser build a node per entry and the VM
|
|
98
|
+
// execute an instruction per entry on every load. A string constant is one
|
|
99
|
+
// token, and `JSON.parse` (a C extension) builds the hash far faster.
|
|
100
|
+
//
|
|
101
|
+
// `JSON.parse` yields exactly what the literal did - String keys, Integer
|
|
102
|
+
// for whole numbers, true/false/nil - so make_config's result is unchanged.
|
|
103
|
+
//
|
|
104
|
+
// A SINGLE-quoted literal, so the JSON survives verbatim: a double-quoted
|
|
105
|
+
// Ruby string would interpolate any `#{` the model happens to contain.
|
|
106
|
+
if (asData) {
|
|
107
|
+
Content(` # THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
108
|
+
#
|
|
109
|
+
# Emitted only above a size threshold, or when \`main.kit.config.repr\` pins
|
|
110
|
+
# it: for a small model the hash literal is smaller and far easier to read
|
|
111
|
+
# when debugging.
|
|
112
|
+
CONFIG_DATA = ${rawStringLiteral(configJson)}.freeze
|
|
113
|
+
|
|
114
|
+
# Parse a fresh, fully materialised config hash. Every call re-parses, so
|
|
115
|
+
# prefer shared_config unless you need a private copy you intend to mutate.
|
|
116
|
+
def self.make_config
|
|
117
|
+
JSON.parse(CONFIG_DATA)
|
|
118
|
+
end
|
|
119
|
+
`)
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
|
|
123
|
+
Content(` # Build a fresh, fully materialised config hash. Every call rebuilds the
|
|
83
124
|
# whole structure, so prefer shared_config unless you need a private copy
|
|
84
125
|
# you intend to mutate.
|
|
85
126
|
def self.make_config
|
|
@@ -119,7 +160,10 @@ ${serverBlock}${authBlock} "headers" => ${formatRubyHash(headers, 4)},
|
|
|
119
160
|
}, true), a), {}), 3)},
|
|
120
161
|
}
|
|
121
162
|
end
|
|
163
|
+
`)
|
|
164
|
+
}
|
|
122
165
|
|
|
166
|
+
Content(`
|
|
123
167
|
|
|
124
168
|
def self.make_feature(name)
|
|
125
169
|
require_relative 'features'
|
|
@@ -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,8 +20,8 @@ import {
|
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
import {
|
|
20
|
-
clean,
|
|
21
23
|
formatRustValue,
|
|
24
|
+
rustRawString,
|
|
22
25
|
} from './utility_rust'
|
|
23
26
|
|
|
24
27
|
|
|
@@ -43,43 +46,54 @@ const Config = cmp(async function Config(props: any) {
|
|
|
43
46
|
let baseUrl = ''
|
|
44
47
|
try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
|
|
45
48
|
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
})
|
|
49
|
+
// The canonical config OBJECT and its JSON, from the shared helper. Both
|
|
50
|
+
// representations render from the same `def`, so they cannot describe
|
|
51
|
+
// different configs - and this target picks up `options.server` (the
|
|
52
|
+
// OpenAPI server-variable defaults), which the hand-rolled build here
|
|
53
|
+
// omitted entirely.
|
|
54
|
+
const { def: config, json: configJson } = configDefinition(model)
|
|
55
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
each(entity, (ent: any) => {
|
|
55
|
-
entityOptions[ent.name] = {}
|
|
56
|
-
})
|
|
57
|
+
File({ name: 'config.' + target.ext }, () => {
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
fields: n.fields,
|
|
70
|
-
name: n.name,
|
|
71
|
-
op: n.op,
|
|
72
|
-
relations: n.relations,
|
|
73
|
-
}, true), a), {})
|
|
74
|
-
|
|
75
|
-
const config = {
|
|
76
|
-
main: { name: model.const.Name },
|
|
77
|
-
feature: featureConfig,
|
|
78
|
-
options,
|
|
79
|
-
entity: entityConfig,
|
|
80
|
-
}
|
|
59
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
60
|
+
//
|
|
61
|
+
// The literal is one deeply nested expression. rustc type-checks and
|
|
62
|
+
// monomorphises it as a single item, so compile time and memory grow with
|
|
63
|
+
// the whole model at once; a string constant is one token, and json_parse
|
|
64
|
+
// builds the same Value tree at runtime.
|
|
65
|
+
//
|
|
66
|
+
// No number-type question here, unlike Go: `Value::Num` is f64 in both
|
|
67
|
+
// representations, so there is nothing for the two paths to disagree about.
|
|
68
|
+
if (asData) {
|
|
69
|
+
Content(`// Generated API configuration (mirrors go core/config.go).
|
|
81
70
|
|
|
82
|
-
|
|
71
|
+
use std::cell::RefCell;
|
|
72
|
+
use std::rc::Rc;
|
|
73
|
+
|
|
74
|
+
use crate::core::types::FeatureRef;
|
|
75
|
+
use crate::utility::jsonparse::json_parse;
|
|
76
|
+
use crate::utility::voxgigstruct::Value;
|
|
77
|
+
|
|
78
|
+
/// THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
79
|
+
///
|
|
80
|
+
/// Emitted only above a size threshold, or when \`main.kit.config.repr\` pins
|
|
81
|
+
/// it: for a small model the literal is smaller and far easier to read when
|
|
82
|
+
/// debugging.
|
|
83
|
+
const CONFIG_DATA: &str = ${rustRawString(configJson)};
|
|
84
|
+
|
|
85
|
+
pub fn make_config() -> Value {
|
|
86
|
+
// Unreachable on error: the constant is generated by a JSON serialiser and
|
|
87
|
+
// never edited by hand. Panicking beats returning an empty config, which
|
|
88
|
+
// would fail far from the cause.
|
|
89
|
+
json_parse(CONFIG_DATA).expect("${model.const.Name}: embedded config is not valid JSON")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pub fn make_feature(name: &str) -> FeatureRef {
|
|
93
|
+
match name {
|
|
94
|
+
`)
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
83
97
|
|
|
84
98
|
Content(`// Generated API configuration (mirrors go core/config.go).
|
|
85
99
|
|
|
@@ -96,6 +110,7 @@ pub fn make_config() -> Value {
|
|
|
96
110
|
pub fn make_feature(name: &str) -> FeatureRef {
|
|
97
111
|
match name {
|
|
98
112
|
`)
|
|
113
|
+
}
|
|
99
114
|
|
|
100
115
|
each(feature, (f: any) => {
|
|
101
116
|
const fname = f.name.charAt(0).toUpperCase() + f.name.slice(1)
|
|
@@ -146,7 +146,24 @@ function clean(o: any, dropDefaults?: boolean): any {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
|
|
149
|
+
|
|
150
|
+
// The JSON as a Rust RAW string literal.
|
|
151
|
+
//
|
|
152
|
+
// A raw string processes no escapes, so the JSON's own `\n` and `\uXXXX`
|
|
153
|
+
// survive byte for byte and reach the JSON parser as written - which a normal
|
|
154
|
+
// Rust string would not, having consumed them itself. The hash level is raised
|
|
155
|
+
// until its terminator does not occur in the text.
|
|
156
|
+
function rustRawString(s: string): string {
|
|
157
|
+
let level = 0
|
|
158
|
+
while (s.includes('"' + '#'.repeat(level))) {
|
|
159
|
+
level++
|
|
160
|
+
}
|
|
161
|
+
const hashes = '#'.repeat(level)
|
|
162
|
+
return 'r' + hashes + '"' + s + '"' + hashes
|
|
163
|
+
}
|
|
164
|
+
|
|
149
165
|
export {
|
|
166
|
+
rustRawString,
|
|
150
167
|
clean,
|
|
151
168
|
crateIdent,
|
|
152
169
|
crateName,
|
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
Fragment,
|
|
9
9
|
Line,
|
|
10
10
|
cmp,
|
|
11
|
+
clean,
|
|
12
|
+
configDefinition,
|
|
13
|
+
configReprSetting,
|
|
11
14
|
each,
|
|
12
15
|
indent,
|
|
13
16
|
isAuthActive,
|
|
@@ -26,7 +29,6 @@ import {
|
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
import {
|
|
29
|
-
clean,
|
|
30
32
|
formatJson,
|
|
31
33
|
} from './utility_ts'
|
|
32
34
|
|
|
@@ -75,58 +77,16 @@ const Config = cmp(async function Config(props: any) {
|
|
|
75
77
|
baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`)
|
|
76
78
|
} catch (_e) { }
|
|
77
79
|
|
|
78
|
-
// The same config as an OBJECT,
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
entityDefs[e.name] = clean({
|
|
85
|
-
fields: e.fields,
|
|
86
|
-
name: e.name,
|
|
87
|
-
op: e.op,
|
|
88
|
-
relations: e.relations,
|
|
89
|
-
}, true)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
const featureDefs: any = {}
|
|
93
|
-
each(feature, (f: any) => {
|
|
94
|
-
featureDefs[f.name] = f.config
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
const entityStubs: any = {}
|
|
98
|
-
each(entity, (e: any) => {
|
|
99
|
-
entityStubs[e.name] = {}
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
const optionsDef: any = { base: baseUrl }
|
|
103
|
-
if (0 < svars.length) {
|
|
104
|
-
optionsDef.server = svars.reduce(
|
|
105
|
-
(a: any, v: any) => (a[v.name] = v.dflt, a), {})
|
|
106
|
-
}
|
|
107
|
-
if (authActive) {
|
|
108
|
-
optionsDef.auth = { prefix: authPrefix }
|
|
109
|
-
}
|
|
110
|
-
optionsDef.headers = headers
|
|
111
|
-
optionsDef.entity = entityStubs
|
|
112
|
-
|
|
113
|
-
const configDef = {
|
|
114
|
-
main: { name: model.const.Name },
|
|
115
|
-
feature: featureDefs,
|
|
116
|
-
options: optionsDef,
|
|
117
|
-
entity: entityDefs,
|
|
118
|
-
}
|
|
119
|
-
const configJson = JSON.stringify(configDef)
|
|
120
|
-
|
|
121
|
-
// `auto` decides by size; 'data'/'literal' pin it for this SDK.
|
|
122
|
-
let configReprSetting = 'auto'
|
|
123
|
-
try {
|
|
124
|
-
configReprSetting = getModelPath(model, `main.${KIT}.config.repr`) || 'auto'
|
|
125
|
-
} catch (_e) { }
|
|
80
|
+
// The same config as an OBJECT, built by the shared helper so this target's
|
|
81
|
+
// literal and the data that replaces it above the threshold are the same
|
|
82
|
+
// config by construction. The JSON is what the threshold is measured on -
|
|
83
|
+
// emitted source size varies by language, the model does not.
|
|
84
|
+
const { json: configJson } = configDefinition(model)
|
|
85
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
126
86
|
|
|
127
87
|
File({ name: 'Config.' + target.ext }, () => {
|
|
128
88
|
|
|
129
|
-
if (
|
|
89
|
+
if (asData) {
|
|
130
90
|
Fragment({
|
|
131
91
|
from: ff + 'Config.data.fragment.ts',
|
|
132
92
|
|
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
Content,
|
|
4
4
|
File,
|
|
5
5
|
cmp,
|
|
6
|
+
configDefinition,
|
|
7
|
+
configReprSetting,
|
|
6
8
|
each,
|
|
7
9
|
isAuthActive,
|
|
8
|
-
|
|
10
|
+
isConfigData,
|
|
9
11
|
} from '@voxgig/sdkgen'
|
|
10
12
|
|
|
11
13
|
|
|
@@ -17,7 +19,6 @@ import {
|
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
import {
|
|
20
|
-
clean,
|
|
21
22
|
formatZigValue,
|
|
22
23
|
} from './utility_zig'
|
|
23
24
|
|
|
@@ -31,52 +32,57 @@ const Config = cmp(async function Config(props: any) {
|
|
|
31
32
|
|
|
32
33
|
const model: Model = ctx$.model
|
|
33
34
|
|
|
34
|
-
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
35
35
|
const feature = getModelPath(model, `main.${KIT}.feature`)
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
// The canonical config OBJECT and its JSON, from the shared helper. Both
|
|
38
|
+
// representations render from the same `def`, so they cannot describe
|
|
39
|
+
// different configs - and this target picks up `options.server` (the
|
|
40
|
+
// OpenAPI server-variable defaults), which the hand-rolled build here
|
|
41
|
+
// omitted entirely.
|
|
42
|
+
const { def: config, json: configJson } = configDefinition(model)
|
|
43
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
const authPrefix = resolveAuthPrefix(model)
|
|
45
|
+
File({ name: 'config.' + target.ext }, () => {
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
48
|
+
//
|
|
49
|
+
// The literal is one nested expression that Zig's comptime evaluator has
|
|
50
|
+
// to walk in full at every build; a string constant is one token, and
|
|
51
|
+
// `json_parse` (std.json at the boundary, then fromStdJson) builds the
|
|
52
|
+
// same Value at runtime.
|
|
53
|
+
//
|
|
54
|
+
// The escaping is JSON.stringify's, which is valid Zig: it escapes every
|
|
55
|
+
// backslash, so the JSON's own `\uXXXX` reaches the file as `\\uXXXX` and
|
|
56
|
+
// no Zig escape sequence is ever formed from it.
|
|
57
|
+
if (asData) {
|
|
58
|
+
Content(`// Generated API configuration (mirrors go/rust core/config).
|
|
44
59
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
const std = @import("std");
|
|
61
|
+
const h = @import("helpers.zig");
|
|
62
|
+
const types = @import("types.zig");
|
|
63
|
+
const jsonparse = @import("../utility/jsonparse.zig");
|
|
64
|
+
const Value = h.Value;
|
|
65
|
+
const Feature = types.Feature;
|
|
49
66
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
/// THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
68
|
+
///
|
|
69
|
+
/// Emitted only above a size threshold, or when \`main.kit.config.repr\` pins
|
|
70
|
+
/// it: for a small model the literal is smaller and far easier to read when
|
|
71
|
+
/// debugging.
|
|
72
|
+
const CONFIG_DATA: []const u8 = ${JSON.stringify(configJson)};
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const entityConfig = Object.values(entity || {}).reduce((a: any, n: any) => (
|
|
65
|
-
a[n.name] = clean({
|
|
66
|
-
fields: n.fields,
|
|
67
|
-
name: n.name,
|
|
68
|
-
op: n.op,
|
|
69
|
-
relations: n.relations,
|
|
70
|
-
}, true), a), {})
|
|
71
|
-
|
|
72
|
-
const config = {
|
|
73
|
-
main: { name: model.const.Name },
|
|
74
|
-
feature: featureConfig,
|
|
75
|
-
options,
|
|
76
|
-
entity: entityConfig,
|
|
77
|
-
}
|
|
74
|
+
pub fn make_config() Value {
|
|
75
|
+
// Unreachable on error: the constant is generated by a JSON serialiser and
|
|
76
|
+
// never edited by hand. Panicking beats returning an empty config, which
|
|
77
|
+
// would fail far from the cause.
|
|
78
|
+
return jsonparse.json_parse(CONFIG_DATA) catch
|
|
79
|
+
@panic("${model.const.Name}: embedded config is not valid JSON");
|
|
80
|
+
}
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
pub fn make_feature(name: []const u8) Feature {
|
|
83
|
+
`)
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
80
86
|
|
|
81
87
|
Content(`// Generated API configuration (mirrors go/rust core/config).
|
|
82
88
|
|
|
@@ -92,6 +98,7 @@ pub fn make_config() Value {
|
|
|
92
98
|
|
|
93
99
|
pub fn make_feature(name: []const u8) Feature {
|
|
94
100
|
`)
|
|
101
|
+
}
|
|
95
102
|
|
|
96
103
|
// The factory must be able to instantiate any built-in feature by name,
|
|
97
104
|
// not just the ones the current API model configures — a caller can enable
|