@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.
Files changed (34) hide show
  1. package/dist/cmp/ReadmeTop.js +10 -1
  2. package/dist/cmp/ReadmeTop.js.map +1 -1
  3. package/dist/helpers/opExample.js +10 -1
  4. package/dist/helpers/opExample.js.map +1 -1
  5. package/dist/sdkgen.d.ts +2 -2
  6. package/dist/sdkgen.js +10 -2
  7. package/dist/sdkgen.js.map +1 -1
  8. package/dist/tsconfig.tsbuildinfo +1 -1
  9. package/dist/utility.d.ts +12 -1
  10. package/dist/utility.js +206 -1
  11. package/dist/utility.js.map +1 -1
  12. package/model/sdkgen.aontu +11 -0
  13. package/package.json +1 -1
  14. package/project/.sdk/src/cmp/c/Config_c.ts +45 -0
  15. package/project/.sdk/src/cmp/c/utility_c.ts +36 -0
  16. package/project/.sdk/src/cmp/go/Config_go.ts +86 -2
  17. package/project/.sdk/src/cmp/js/Config_js.ts +49 -1
  18. package/project/.sdk/src/cmp/js/fragment/Config.data.fragment.js +50 -0
  19. package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +1 -1
  20. package/project/.sdk/src/cmp/lua/Config_lua.ts +51 -1
  21. package/project/.sdk/src/cmp/lua/utility_lua.ts +21 -0
  22. package/project/.sdk/src/cmp/php/Config_php.ts +86 -0
  23. package/project/.sdk/src/cmp/py/Config_py.ts +47 -1
  24. package/project/.sdk/src/cmp/rb/Config_rb.ts +46 -2
  25. package/project/.sdk/src/cmp/rust/Config_rust.ts +50 -35
  26. package/project/.sdk/src/cmp/rust/utility_rust.ts +17 -0
  27. package/project/.sdk/src/cmp/ts/Config_ts.ts +49 -1
  28. package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +50 -0
  29. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +1 -1
  30. package/project/.sdk/src/cmp/zig/Config_zig.ts +47 -40
  31. package/src/cmp/ReadmeTop.ts +10 -1
  32. package/src/helpers/opExample.ts +11 -1
  33. package/src/sdkgen.ts +11 -1
  34. 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
- -- Build a fresh, fully materialised config table. Every call rebuilds the
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
- # Build a fresh, fully materialised config hash. Every call rebuilds the
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
- // Assemble the whole config as a JSON-shaped object, then render it once
47
- // via formatRustValue (byte-stable; each() sorted-key iteration).
48
- const featureConfig: any = {}
49
- each(feature, (f: any) => {
50
- featureConfig[f.name] = f.config || {}
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
- const entityOptions: any = {}
54
- each(entity, (ent: any) => {
55
- entityOptions[ent.name] = {}
56
- })
57
+ File({ name: 'config.' + target.ext }, () => {
57
58
 
58
- const options: any = {
59
- base: baseUrl,
60
- headers,
61
- entity: entityOptions,
62
- }
63
- if (authActive) {
64
- options.auth = { prefix: authPrefix }
65
- }
66
-
67
- const entityConfig = Object.values(entity || {}).reduce((a: any, n: any) => (
68
- a[n.name] = clean({
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
- File({ name: 'config.' + target.ext }, () => {
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
+ }
@@ -29,7 +29,7 @@ class Config {
29
29
 
30
30
 
31
31
  options = {
32
- base: '$$main.kit.info.servers.0.url$$',
32
+ base: 'BASEURL',
33
33
 
34
34
  'SERVERBLOCK''AUTHBLOCK'headers: 'HEADERS',
35
35