@voxgig/sdkgen 3.4.3 → 3.4.6
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 +10 -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 +10 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +12 -1
- package/dist/utility.js +206 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aontu +11 -0
- 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/go/Config_go.ts +86 -2
- 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 +49 -1
- package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +50 -0
- package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +1 -1
- package/project/.sdk/src/cmp/zig/Config_zig.ts +47 -40
- package/src/cmp/ReadmeTop.ts +10 -1
- package/src/helpers/opExample.ts +11 -1
- package/src/sdkgen.ts +11 -1
- package/src/utility.ts +226 -1
|
@@ -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'
|
|
@@ -26,6 +29,7 @@ import {
|
|
|
26
29
|
import {
|
|
27
30
|
clean,
|
|
28
31
|
formatLuaTable,
|
|
32
|
+
luaLongString,
|
|
29
33
|
} from './utility_lua'
|
|
30
34
|
|
|
31
35
|
|
|
@@ -61,11 +65,54 @@ const Config = cmp(async function Config(props: any) {
|
|
|
61
65
|
},\n`
|
|
62
66
|
: ''
|
|
63
67
|
|
|
68
|
+
// The same config as an OBJECT, built by the shared helper so this target's
|
|
69
|
+
// literal and the data that replaces it above the threshold are the same
|
|
70
|
+
// config by construction. The JSON is what the threshold is measured on -
|
|
71
|
+
// emitted source size varies by language, the model does not.
|
|
72
|
+
const { json: configJson } = configDefinition(model)
|
|
73
|
+
const asData = isConfigData(configJson, configReprSetting(model))
|
|
74
|
+
|
|
64
75
|
File({ name: 'config.' + target.ext }, () => {
|
|
65
76
|
|
|
66
77
|
Content(`-- ${model.const.Name} SDK configuration
|
|
67
78
|
|
|
68
|
-
|
|
79
|
+
`)
|
|
80
|
+
|
|
81
|
+
// ABOVE THE THRESHOLD: emit the model as DATA.
|
|
82
|
+
//
|
|
83
|
+
// A table constructor makes the Lua parser emit a SETTABLE per entry and
|
|
84
|
+
// the VM run them all on every load; a long-bracket string is one token,
|
|
85
|
+
// and dkjson's decoder builds the table from it.
|
|
86
|
+
//
|
|
87
|
+
// dkjson is already a runtime dependency - `utility/fetcher.lua` decodes
|
|
88
|
+
// every HTTP response with it - so this adds nothing to the SDK.
|
|
89
|
+
//
|
|
90
|
+
// Null handling agrees between the branches by construction: dkjson maps
|
|
91
|
+
// JSON null to nil, and assigning nil to a table key removes it, which is
|
|
92
|
+
// exactly what the literal branch does when `formatLuaTable` emits `nil`.
|
|
93
|
+
if (asData) {
|
|
94
|
+
Content(`local json = require("dkjson")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
-- THE API MODEL, EMBEDDED AS DATA (sdkgen rung L1).
|
|
98
|
+
--
|
|
99
|
+
-- Emitted only above a size threshold, or when \`main.kit.config.repr\` pins
|
|
100
|
+
-- it: for a small model the table literal is smaller and far easier to read
|
|
101
|
+
-- when debugging.
|
|
102
|
+
local CONFIG_DATA = ${luaLongString(configJson)}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
-- Parse a fresh, fully materialised config table. Every call re-parses, so
|
|
106
|
+
-- prefer require("config_shared") unless you need a private copy you intend
|
|
107
|
+
-- to mutate.
|
|
108
|
+
local function make_config()
|
|
109
|
+
return json.decode(CONFIG_DATA)
|
|
110
|
+
end
|
|
111
|
+
`)
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
|
|
115
|
+
Content(`-- Build a fresh, fully materialised config table. Every call rebuilds the
|
|
69
116
|
-- whole structure, so prefer require("config_shared") unless you need a
|
|
70
117
|
-- private copy you intend to mutate.
|
|
71
118
|
local function make_config()
|
|
@@ -105,7 +152,10 @@ ${serverBlock}${authBlock} headers = ${formatLuaTable(headers, 3)},
|
|
|
105
152
|
}, true), a), {}), 2)},
|
|
106
153
|
}
|
|
107
154
|
end
|
|
155
|
+
`)
|
|
156
|
+
}
|
|
108
157
|
|
|
158
|
+
Content(`
|
|
109
159
|
|
|
110
160
|
local function make_feature(name)
|
|
111
161
|
local features = require("features")
|
|
@@ -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,9 +8,13 @@ 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,
|
|
17
|
+
isConfigData,
|
|
14
18
|
resolveAuthPrefix,
|
|
15
19
|
serverVariables,
|
|
16
20
|
} from '@voxgig/sdkgen'
|
|
@@ -25,7 +29,6 @@ import {
|
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
import {
|
|
28
|
-
clean,
|
|
29
32
|
formatJson,
|
|
30
33
|
} from './utility_ts'
|
|
31
34
|
|
|
@@ -62,13 +65,58 @@ const Config = cmp(async function Config(props: any) {
|
|
|
62
65
|
svars.map((v: any) => ` ${JSON.stringify(v.name)}: ${JSON.stringify(v.dflt)},\n`).join('') +
|
|
63
66
|
' },\n\n '
|
|
64
67
|
|
|
68
|
+
// Read the base URL here rather than leaving it to a `$$...$$` stdrep
|
|
69
|
+
// placeholder in the fragment. stdrep can only substitute a path the model
|
|
70
|
+
// actually has: a model with no `info.servers` left the placeholder itself in
|
|
71
|
+
// the generated source, so `options.base` came out as the literal string
|
|
72
|
+
// '$main.kit.info.servers.0.url$'. Reading it explicitly yields '' in that
|
|
73
|
+
// case, which is what every other target already emits, and is identical to
|
|
74
|
+
// the old output whenever the model does define a server.
|
|
75
|
+
let baseUrl = ''
|
|
76
|
+
try {
|
|
77
|
+
baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`)
|
|
78
|
+
} catch (_e) { }
|
|
79
|
+
|
|
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))
|
|
86
|
+
|
|
65
87
|
File({ name: 'Config.' + target.ext }, () => {
|
|
66
88
|
|
|
89
|
+
if (asData) {
|
|
90
|
+
Fragment({
|
|
91
|
+
from: ff + 'Config.data.fragment.ts',
|
|
92
|
+
|
|
93
|
+
replace: {
|
|
94
|
+
|
|
95
|
+
'// #ImportFeatures': () => each(feature, (f: any) => {
|
|
96
|
+
Line(`import { ${nom(f, 'Name')}Feature } from ` +
|
|
97
|
+
`'./feature/${f.name}/${nom(f, 'Name')}Feature'`)
|
|
98
|
+
}),
|
|
99
|
+
|
|
100
|
+
'// #FeatureClasses': () => each(feature, (f: any) => {
|
|
101
|
+
Line(` ${f.name}: ${nom(f, 'Name')}Feature,`)
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
// A JS string literal, so the JSON survives verbatim. JSON.stringify
|
|
105
|
+
// escapes the quotes and backslashes the model contains (values like
|
|
106
|
+
// `$STRING` carry backticks, which a template literal could not).
|
|
107
|
+
"'CONFIGJSON'": JSON.stringify(configJson),
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
67
113
|
Fragment({
|
|
68
114
|
from: ff + 'Config.fragment.ts',
|
|
69
115
|
|
|
70
116
|
replace: {
|
|
71
117
|
|
|
118
|
+
"'BASEURL'": JSON.stringify(baseUrl),
|
|
119
|
+
|
|
72
120
|
"'SERVERBLOCK'": serverBlock,
|
|
73
121
|
|
|
74
122
|
"'AUTHBLOCK'": authBlock,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
import { BaseFeature } from './feature/base/BaseFeature'
|
|
3
|
+
// #ImportFeatures
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const FEATURE_CLASS: Record<string, typeof BaseFeature> = {
|
|
7
|
+
// #FeatureClasses
|
|
8
|
+
}
|
|
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 object
|
|
14
|
+
// literals on the class. For a large API that is megabytes of source that the
|
|
15
|
+
// TypeScript compiler must parse and infer a type for on every build, and that
|
|
16
|
+
// the JavaScript engine must build node by node on every module load.
|
|
17
|
+
//
|
|
18
|
+
// As a single string constant it is one token to the compiler, and V8's JSON
|
|
19
|
+
// parser builds the object far faster than the equivalent literal - JSON.parse
|
|
20
|
+
// is the well-worn trick for exactly this shape of data.
|
|
21
|
+
//
|
|
22
|
+
// Emitted only above a size threshold, or when `main.kit.config.repr` pins it:
|
|
23
|
+
// for a small model the literal is smaller, loads no slower, and is far easier
|
|
24
|
+
// to read when debugging.
|
|
25
|
+
const CONFIG_DATA = 'CONFIGJSON'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Config {
|
|
29
|
+
|
|
30
|
+
makeFeature(this: any, fn: string) {
|
|
31
|
+
const fc = FEATURE_CLASS[fn]
|
|
32
|
+
const fi = new fc()
|
|
33
|
+
// TODO: errors etc
|
|
34
|
+
return fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// Parsed ONCE, at module load, exactly like the literal form was built once.
|
|
41
|
+
//
|
|
42
|
+
// The parsed data is assigned onto an instance rather than replacing it, so
|
|
43
|
+
// `config.main` / `.feature` / `.options` / `.entity` read exactly as they did
|
|
44
|
+
// when they were field initialisers, and `makeFeature` stays where it was on
|
|
45
|
+
// the prototype. Callers cannot tell which representation they were given.
|
|
46
|
+
const config: any = Object.assign(new Config(), JSON.parse(CONFIG_DATA))
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
config
|
|
50
|
+
}
|