@voxgig/sdkgen 3.3.2 → 3.4.2

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 (86) hide show
  1. package/dist/cmp/Readme.js +2 -0
  2. package/dist/cmp/Readme.js.map +1 -1
  3. package/dist/cmp/ReadmeUnions.d.ts +3 -0
  4. package/dist/cmp/ReadmeUnions.js +74 -0
  5. package/dist/cmp/ReadmeUnions.js.map +1 -0
  6. package/dist/tsconfig.tsbuildinfo +1 -1
  7. package/package.json +1 -1
  8. package/project/.sdk/src/cmp/c/Config_c.ts +1 -1
  9. package/project/.sdk/src/cmp/c/utility_c.ts +57 -6
  10. package/project/.sdk/src/cmp/clojure/Config_clojure.ts +1 -1
  11. package/project/.sdk/src/cmp/clojure/utility_clojure.ts +57 -6
  12. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +1 -1
  13. package/project/.sdk/src/cmp/cpp/utility_cpp.ts +57 -6
  14. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +1 -1
  15. package/project/.sdk/src/cmp/csharp/utility_csharp.ts +57 -6
  16. package/project/.sdk/src/cmp/dart/Config_dart.ts +1 -1
  17. package/project/.sdk/src/cmp/dart/utility_dart.ts +57 -6
  18. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +1 -1
  19. package/project/.sdk/src/cmp/elixir/utility_elixir.ts +57 -6
  20. package/project/.sdk/src/cmp/go/Config_go.ts +27 -2
  21. package/project/.sdk/src/cmp/go/Main_go.ts +1 -0
  22. package/project/.sdk/src/cmp/go/TestEntity_go.ts +1 -1
  23. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +1 -1
  24. package/project/.sdk/src/cmp/go/utility_go.ts +57 -6
  25. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +1 -1
  26. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +57 -6
  27. package/project/.sdk/src/cmp/java/Config_java.ts +1 -1
  28. package/project/.sdk/src/cmp/java/utility_java.ts +57 -6
  29. package/project/.sdk/src/cmp/js/Config_js.ts +1 -1
  30. package/project/.sdk/src/cmp/js/utility_js.ts +57 -6
  31. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +1 -1
  32. package/project/.sdk/src/cmp/kotlin/utility_kotlin.ts +57 -6
  33. package/project/.sdk/src/cmp/lean/Config_lean.ts +1 -1
  34. package/project/.sdk/src/cmp/lean/utility_lean.ts +57 -6
  35. package/project/.sdk/src/cmp/lua/Config_lua.ts +30 -1
  36. package/project/.sdk/src/cmp/lua/Package_lua.ts +1 -0
  37. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +1 -1
  38. package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +1 -1
  39. package/project/.sdk/src/cmp/lua/utility_lua.ts +57 -6
  40. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +1 -1
  41. package/project/.sdk/src/cmp/ocaml/utility_ocaml.ts +57 -6
  42. package/project/.sdk/src/cmp/perl/Config_perl.ts +1 -1
  43. package/project/.sdk/src/cmp/perl/utility_perl.ts +57 -6
  44. package/project/.sdk/src/cmp/php/Config_php.ts +24 -1
  45. package/project/.sdk/src/cmp/php/TestEntity_php.ts +1 -1
  46. package/project/.sdk/src/cmp/php/fragment/Entity.fragment.php +22 -0
  47. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +1 -1
  48. package/project/.sdk/src/cmp/php/utility_php.ts +57 -6
  49. package/project/.sdk/src/cmp/py/Config_py.ts +24 -1
  50. package/project/.sdk/src/cmp/py/TestEntity_py.ts +2 -2
  51. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +2 -2
  52. package/project/.sdk/src/cmp/py/utility_py.ts +57 -6
  53. package/project/.sdk/src/cmp/rb/Config_rb.ts +15 -1
  54. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +1 -1
  55. package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +1 -1
  56. package/project/.sdk/src/cmp/rb/utility_rb.ts +57 -6
  57. package/project/.sdk/src/cmp/rust/Config_rust.ts +1 -1
  58. package/project/.sdk/src/cmp/rust/utility_rust.ts +57 -6
  59. package/project/.sdk/src/cmp/scala/Config_scala.ts +1 -1
  60. package/project/.sdk/src/cmp/scala/utility_scala.ts +57 -6
  61. package/project/.sdk/src/cmp/swift/Config_swift.ts +1 -1
  62. package/project/.sdk/src/cmp/swift/utility_swift.ts +57 -6
  63. package/project/.sdk/src/cmp/ts/Config_ts.ts +1 -1
  64. package/project/.sdk/src/cmp/ts/utility_ts.ts +57 -6
  65. package/project/.sdk/src/cmp/zig/Config_zig.ts +1 -1
  66. package/project/.sdk/src/cmp/zig/utility_zig.ts +57 -6
  67. package/project/.sdk/tm/c/core/point.c +2 -1
  68. package/project/.sdk/tm/csharp/core/Point.cs +3 -1
  69. package/project/.sdk/tm/dart/lib/Point.dart +2 -1
  70. package/project/.sdk/tm/go/test/feature_harness_test.go +1 -1
  71. package/project/.sdk/tm/go/utility/make_options.go +5 -1
  72. package/project/.sdk/tm/java/core/Point.java +3 -1
  73. package/project/.sdk/tm/js/src/Point.js +3 -1
  74. package/project/.sdk/tm/kotlin/core/Point.kt +3 -1
  75. package/project/.sdk/tm/lua/utility/make_options.lua +5 -1
  76. package/project/.sdk/tm/php/test/FeatureTest.php +1 -1
  77. package/project/.sdk/tm/py/pkg/utility/make_options.py +5 -1
  78. package/project/.sdk/tm/py/test/feature_harness.py +2 -2
  79. package/project/.sdk/tm/rb/test/feature_test.rb +1 -1
  80. package/project/.sdk/tm/rb/utility/make_options.rb +5 -1
  81. package/project/.sdk/tm/scala/core/Point.scala +2 -1
  82. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Point.swift +3 -1
  83. package/project/.sdk/tm/ts/src/Point.ts +3 -1
  84. package/project/.sdk/tm/zig/core/point.zig +2 -1
  85. package/src/cmp/Readme.ts +2 -0
  86. package/src/cmp/ReadmeUnions.ts +90 -0
@@ -14,13 +14,64 @@ function projectPath(suffix?: string): string {
14
14
 
15
15
  // Strip model-internal keys (ending in `$`) so generated config JSON only
16
16
  // carries API-shaped data.
17
- function clean(o: any) {
18
- return walk(clone(o), (k: any, v: any, p: any) => {
19
- if (null != k && k.endsWith('$')) {
20
- delete p[k]
17
+ // Emission-time normalisation of a model subtree (L0).
18
+ //
19
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
20
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
21
+ // default the runtime already assumes when the key is absent, which is pure
22
+ // payload — see CONFIG_DEFAULT.
23
+ //
24
+ // Rebuilds the tree rather than mutating during a walk. The previous
25
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
26
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
27
+ // the delete was undone on the way out and the helper silently did nothing.
28
+ // Returning `undefined` from the callback does not fix it either: setprop
29
+ // stores undefined rather than removing the key, which then emits as a null.
30
+ //
31
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
32
+ // `active` means something different in feature config, where absent reads as
33
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
34
+ // disable the feature.
35
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
36
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
37
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
38
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
39
+ const MODEL_META = ['index$', 'key$', 'val$']
40
+
41
+ // Keys whose value IS the default the runtime already assumes when the key is
42
+ // absent, so emitting them is pure payload.
43
+ const CONFIG_DEFAULT: Record<string, any> = {
44
+ active: true,
45
+ req: false,
46
+ reqd: false,
47
+ }
48
+
49
+ // Subtrees carrying user payload rather than schema. An active:true inside an
50
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
51
+ // keys and everything below them is passed through untouched.
52
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
53
+
54
+ function clean(o: any, dropDefaults?: boolean): any {
55
+ const prune = (node: any, defaults: boolean): any => {
56
+ if (Array.isArray(node)) {
57
+ return node.map((n: any) => prune(n, defaults))
58
+ }
59
+ if (null != node && 'object' === typeof node) {
60
+ const out: any = {}
61
+ for (const k of Object.keys(node)) {
62
+ if (MODEL_META.includes(k)) {
63
+ continue
64
+ }
65
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
66
+ continue
67
+ }
68
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
69
+ }
70
+ return out
21
71
  }
22
- return v
23
- })
72
+ return node
73
+ }
74
+ return prune(o, true === dropDefaults)
24
75
  }
25
76
 
26
77
 
@@ -72,6 +72,29 @@ declare(strict_types=1);
72
72
 
73
73
  class ${model.const.Name}Config
74
74
  {
75
+ /** @var array<string,mixed>|null */
76
+ private static ?array $shared_config = null;
77
+
78
+ /**
79
+ * Return the process-wide config, built once on first use. The SDK reads
80
+ * the config on every request and never writes to it, so one instance is
81
+ * shared by every client rather than rebuilt per client.
82
+ *
83
+ * PHP arrays are copy-on-write, so callers that do mutate the result get
84
+ * their own copy and cannot disturb the shared one.
85
+ */
86
+ public static function shared_config(): array
87
+ {
88
+ if (self::$shared_config === null) {
89
+ self::$shared_config = self::make_config();
90
+ }
91
+ return self::$shared_config;
92
+ }
93
+
94
+ /**
95
+ * Build a fresh, fully materialised config array. Every call rebuilds the
96
+ * whole structure, so prefer shared_config unless you need a private copy.
97
+ */
75
98
  public static function make_config(): array
76
99
  {
77
100
  return [
@@ -122,7 +145,7 @@ ${serverBlock}${authBlock} "headers" => ${formatPhpArray(headers,
122
145
  name: n.name,
123
146
  op: n.op,
124
147
  relations: n.relations,
125
- }), a), {}), 3)},
148
+ }, true), a), {}), 3)},
126
149
  ];
127
150
  `)
128
151
  }
@@ -137,7 +137,7 @@ ${hasList ? `
137
137
  $this->assertCount(3, $seen);
138
138
 
139
139
  // Inbound: streaming active -> yields each item from the feature.
140
- $cfg = ${model.const.Name}Config::make_config();
140
+ $cfg = ${model.const.Name}Config::shared_config();
141
141
  if (isset($cfg["feature"]) && is_array($cfg["feature"]) && isset($cfg["feature"]["streaming"])) {
142
142
  $sdk = ${model.const.Name}SDK::test($seed, ["feature" => ["streaming" => ["active" => true]]]);
143
143
  $got = [];
@@ -17,6 +17,7 @@ class EntyClass
17
17
  private array $_data;
18
18
  private array $_match;
19
19
  private $_entctx;
20
+ private bool $_deleted = false;
20
21
 
21
22
  public function __construct($client, ?array $entopts = null)
22
23
  {
@@ -49,6 +50,27 @@ class EntyClass
49
50
  return $this->_name;
50
51
  }
51
52
 
53
+ /**
54
+ * A `remove` marks the entity deleted. The instance KEEPS the data it
55
+ * held — a caller can still read what was removed — but it is no longer a
56
+ * live record. See AGENTS.md "Entity operations return ENTITIES".
57
+ *
58
+ * The remove path below already called markDeleted(); php was the one
59
+ * target that never declared it (cpp and swift both do), so any SDK whose
60
+ * entities have a `remove` op raised "Call to undefined method
61
+ * <Entity>::markDeleted()" the first time a remove succeeded. Nothing
62
+ * caught it because the fatal only fires when that path actually runs.
63
+ */
64
+ public function markDeleted(): void
65
+ {
66
+ $this->_deleted = true;
67
+ }
68
+
69
+ public function deleted(): bool
70
+ {
71
+ return $this->_deleted;
72
+ }
73
+
52
74
  public function make(): self
53
75
  {
54
76
  $opts = $this->_entopts;
@@ -40,7 +40,7 @@ class ProjectNameSDK
40
40
  $utility = new ProjectNameUtility();
41
41
  $this->_utility = $utility;
42
42
 
43
- $config = ProjectNameConfig::make_config();
43
+ $config = ProjectNameConfig::shared_config();
44
44
 
45
45
  $this->_rootctx = ($utility->make_context)([
46
46
  "client" => $this,
@@ -67,13 +67,64 @@ function formatPhpValue(val: any, indent: number = 0): string {
67
67
  }
68
68
 
69
69
 
70
- function clean(o: any) {
71
- return walk(clone(o), (k: any, v: any, p: any) => {
72
- if (null != k && k.endsWith('$')) {
73
- delete p[k]
70
+ // Emission-time normalisation of a model subtree (L0).
71
+ //
72
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
73
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
74
+ // default the runtime already assumes when the key is absent, which is pure
75
+ // payload — see CONFIG_DEFAULT.
76
+ //
77
+ // Rebuilds the tree rather than mutating during a walk. The previous
78
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
79
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
80
+ // the delete was undone on the way out and the helper silently did nothing.
81
+ // Returning `undefined` from the callback does not fix it either: setprop
82
+ // stores undefined rather than removing the key, which then emits as a null.
83
+ //
84
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
85
+ // `active` means something different in feature config, where absent reads as
86
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
87
+ // disable the feature.
88
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
89
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
90
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
91
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
92
+ const MODEL_META = ['index$', 'key$', 'val$']
93
+
94
+ // Keys whose value IS the default the runtime already assumes when the key is
95
+ // absent, so emitting them is pure payload.
96
+ const CONFIG_DEFAULT: Record<string, any> = {
97
+ active: true,
98
+ req: false,
99
+ reqd: false,
100
+ }
101
+
102
+ // Subtrees carrying user payload rather than schema. An active:true inside an
103
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
104
+ // keys and everything below them is passed through untouched.
105
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
106
+
107
+ function clean(o: any, dropDefaults?: boolean): any {
108
+ const prune = (node: any, defaults: boolean): any => {
109
+ if (Array.isArray(node)) {
110
+ return node.map((n: any) => prune(n, defaults))
74
111
  }
75
- return v
76
- })
112
+ if (null != node && 'object' === typeof node) {
113
+ const out: any = {}
114
+ for (const k of Object.keys(node)) {
115
+ if (MODEL_META.includes(k)) {
116
+ continue
117
+ }
118
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
119
+ continue
120
+ }
121
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
122
+ }
123
+ return out
124
+ }
125
+ return node
126
+ }
127
+ return prune(o, true === dropDefaults)
77
128
  }
78
129
 
79
130
 
@@ -66,7 +66,30 @@ const Config = cmp(async function Config(props: any) {
66
66
  Content(`# ${model.const.Name} SDK configuration
67
67
 
68
68
 
69
+ _shared_config = None
70
+
71
+
72
+ def shared_config():
73
+ """Return the process-wide config, built once on first use.
74
+
75
+ The SDK reads the config on every request and never writes to it, so one
76
+ instance is shared by every client rather than rebuilt per client.
77
+
78
+ The returned dict is shared: treat it as read-only. Callers that need to
79
+ mutate should use make_config, which always returns a fresh copy.
80
+ """
81
+ global _shared_config
82
+ if _shared_config is None:
83
+ _shared_config = make_config()
84
+ return _shared_config
85
+
86
+
69
87
  def make_config():
88
+ """Build a fresh, fully materialised config dict.
89
+
90
+ Every call rebuilds the whole structure, so prefer shared_config unless
91
+ you need a private copy you intend to mutate.
92
+ """
70
93
  return {
71
94
  "main": {
72
95
  "name": "${model.const.Name}",
@@ -100,7 +123,7 @@ ${serverBlock}${authBlock} "headers": ${formatPyDict(headers, 3)},
100
123
  name: n.name,
101
124
  op: n.op,
102
125
  relations: n.relations,
103
- }), a), {}), 2)},
126
+ }, true), a), {}), 2)},
104
127
  }
105
128
  `)
106
129
  })
@@ -130,8 +130,8 @@ ${hasList ? `
130
130
  assert len(seen) == 3
131
131
 
132
132
  # Inbound: streaming active -> yields each item from the feature.
133
- from ${model.const.Name.toLowerCase()}_sdk.config import make_config
134
- cfg = make_config()
133
+ from ${model.const.Name.toLowerCase()}_sdk.config import shared_config
134
+ cfg = shared_config()
135
135
  if isinstance(cfg.get("feature"), dict) and "streaming" in cfg["feature"]:
136
136
  sdk = ${model.const.Name}SDK.test(
137
137
  seed, {"feature": {"streaming": {"active": True}}})
@@ -23,8 +23,8 @@ class ProjectNameSDK:
23
23
  utility = ProjectNameUtility()
24
24
  self._utility = utility
25
25
 
26
- from projectname_sdk.config import make_config
27
- config = make_config()
26
+ from projectname_sdk.config import shared_config
27
+ config = shared_config()
28
28
 
29
29
  self._rootctx = utility.make_context({
30
30
  "client": self,
@@ -66,13 +66,64 @@ function formatPyValue(val: any, indent: number = 0): string {
66
66
  }
67
67
 
68
68
 
69
- function clean(o: any) {
70
- return walk(clone(o), (k: any, v: any, p: any) => {
71
- if (null != k && k.endsWith('$')) {
72
- delete p[k]
69
+ // Emission-time normalisation of a model subtree (L0).
70
+ //
71
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
72
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
73
+ // default the runtime already assumes when the key is absent, which is pure
74
+ // payload — see CONFIG_DEFAULT.
75
+ //
76
+ // Rebuilds the tree rather than mutating during a walk. The previous
77
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
78
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
79
+ // the delete was undone on the way out and the helper silently did nothing.
80
+ // Returning `undefined` from the callback does not fix it either: setprop
81
+ // stores undefined rather than removing the key, which then emits as a null.
82
+ //
83
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
84
+ // `active` means something different in feature config, where absent reads as
85
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
86
+ // disable the feature.
87
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
88
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
89
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
90
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
91
+ const MODEL_META = ['index$', 'key$', 'val$']
92
+
93
+ // Keys whose value IS the default the runtime already assumes when the key is
94
+ // absent, so emitting them is pure payload.
95
+ const CONFIG_DEFAULT: Record<string, any> = {
96
+ active: true,
97
+ req: false,
98
+ reqd: false,
99
+ }
100
+
101
+ // Subtrees carrying user payload rather than schema. An active:true inside an
102
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
103
+ // keys and everything below them is passed through untouched.
104
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
105
+
106
+ function clean(o: any, dropDefaults?: boolean): any {
107
+ const prune = (node: any, defaults: boolean): any => {
108
+ if (Array.isArray(node)) {
109
+ return node.map((n: any) => prune(n, defaults))
73
110
  }
74
- return v
75
- })
111
+ if (null != node && 'object' === typeof node) {
112
+ const out: any = {}
113
+ for (const k of Object.keys(node)) {
114
+ if (MODEL_META.includes(k)) {
115
+ continue
116
+ }
117
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
118
+ continue
119
+ }
120
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
121
+ }
122
+ return out
123
+ }
124
+ return node
125
+ }
126
+ return prune(o, true === dropDefaults)
76
127
  }
77
128
 
78
129
 
@@ -68,6 +68,20 @@ const Config = cmp(async function Config(props: any) {
68
68
  Content(`# ${model.const.Name} SDK configuration
69
69
 
70
70
  module ${model.const.Name}Config
71
+ # Return the process-wide config, built once on first use. The SDK reads
72
+ # the config on every request and never writes to it, so one instance is
73
+ # shared by every client rather than rebuilt per client.
74
+ #
75
+ # The returned hash is shared: treat it as read-only. Callers that need to
76
+ # mutate should use make_config, which always returns a fresh copy.
77
+ def self.shared_config
78
+ @shared_config ||= make_config
79
+ end
80
+
81
+
82
+ # Build a fresh, fully materialised config hash. Every call rebuilds the
83
+ # whole structure, so prefer shared_config unless you need a private copy
84
+ # you intend to mutate.
71
85
  def self.make_config
72
86
  {
73
87
  "main" => {
@@ -102,7 +116,7 @@ ${serverBlock}${authBlock} "headers" => ${formatRubyHash(headers, 4)},
102
116
  name: n.name,
103
117
  op: n.op,
104
118
  relations: n.relations,
105
- }), a), {}), 3)},
119
+ }, true), a), {}), 3)},
106
120
  }
107
121
  end
108
122
 
@@ -117,7 +117,7 @@ ${hasList ? `
117
117
  assert_equal 3, seen.length
118
118
 
119
119
  # Inbound: streaming active -> yields each item from the feature.
120
- cfg = ${model.const.Name}Config.make_config
120
+ cfg = ${model.const.Name}Config.shared_config
121
121
  if cfg["feature"].is_a?(Hash) && cfg["feature"].key?("streaming")
122
122
  sdk = ${model.const.Name}SDK.test(seed, { "feature" => { "streaming" => { "active" => true } } })
123
123
  got = []
@@ -28,7 +28,7 @@ class ProjectNameSDK
28
28
  utility = ProjectNameUtility.new
29
29
  @_utility = utility
30
30
 
31
- config = ProjectNameConfig.make_config
31
+ config = ProjectNameConfig.shared_config
32
32
 
33
33
  @_rootctx = utility.make_context.call({
34
34
  "client" => self,
@@ -66,13 +66,64 @@ function formatRubyValue(val: any, indent: number = 0): string {
66
66
  }
67
67
 
68
68
 
69
- function clean(o: any) {
70
- return walk(clone(o), (k: any, v: any, p: any) => {
71
- if (null != k && k.endsWith('$')) {
72
- delete p[k]
69
+ // Emission-time normalisation of a model subtree (L0).
70
+ //
71
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
72
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
73
+ // default the runtime already assumes when the key is absent, which is pure
74
+ // payload — see CONFIG_DEFAULT.
75
+ //
76
+ // Rebuilds the tree rather than mutating during a walk. The previous
77
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
78
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
79
+ // the delete was undone on the way out and the helper silently did nothing.
80
+ // Returning `undefined` from the callback does not fix it either: setprop
81
+ // stores undefined rather than removing the key, which then emits as a null.
82
+ //
83
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
84
+ // `active` means something different in feature config, where absent reads as
85
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
86
+ // disable the feature.
87
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
88
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
89
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
90
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
91
+ const MODEL_META = ['index$', 'key$', 'val$']
92
+
93
+ // Keys whose value IS the default the runtime already assumes when the key is
94
+ // absent, so emitting them is pure payload.
95
+ const CONFIG_DEFAULT: Record<string, any> = {
96
+ active: true,
97
+ req: false,
98
+ reqd: false,
99
+ }
100
+
101
+ // Subtrees carrying user payload rather than schema. An active:true inside an
102
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
103
+ // keys and everything below them is passed through untouched.
104
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
105
+
106
+ function clean(o: any, dropDefaults?: boolean): any {
107
+ const prune = (node: any, defaults: boolean): any => {
108
+ if (Array.isArray(node)) {
109
+ return node.map((n: any) => prune(n, defaults))
73
110
  }
74
- return v
75
- })
111
+ if (null != node && 'object' === typeof node) {
112
+ const out: any = {}
113
+ for (const k of Object.keys(node)) {
114
+ if (MODEL_META.includes(k)) {
115
+ continue
116
+ }
117
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
118
+ continue
119
+ }
120
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
121
+ }
122
+ return out
123
+ }
124
+ return node
125
+ }
126
+ return prune(o, true === dropDefaults)
76
127
  }
77
128
 
78
129
 
@@ -70,7 +70,7 @@ const Config = cmp(async function Config(props: any) {
70
70
  name: n.name,
71
71
  op: n.op,
72
72
  relations: n.relations,
73
- }), a), {})
73
+ }, true), a), {})
74
74
 
75
75
  const config = {
76
76
  main: { name: model.const.Name },
@@ -85,13 +85,64 @@ function formatRustValue(val: any, indent: number = 0): string {
85
85
 
86
86
 
87
87
  // Deep-remove meta keys (`foo$`) from a model subtree (twin of go's clean).
88
- function clean(o: any) {
89
- return walk(clone(o), (k: any, v: any, p: any) => {
90
- if (null != k && k.endsWith('$')) {
91
- delete p[k]
88
+ // Emission-time normalisation of a model subtree (L0).
89
+ //
90
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
91
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
92
+ // default the runtime already assumes when the key is absent, which is pure
93
+ // payload — see CONFIG_DEFAULT.
94
+ //
95
+ // Rebuilds the tree rather than mutating during a walk. The previous
96
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
97
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
98
+ // the delete was undone on the way out and the helper silently did nothing.
99
+ // Returning `undefined` from the callback does not fix it either: setprop
100
+ // stores undefined rather than removing the key, which then emits as a null.
101
+ //
102
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
103
+ // `active` means something different in feature config, where absent reads as
104
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
105
+ // disable the feature.
106
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
107
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
108
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
109
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
110
+ const MODEL_META = ['index$', 'key$', 'val$']
111
+
112
+ // Keys whose value IS the default the runtime already assumes when the key is
113
+ // absent, so emitting them is pure payload.
114
+ const CONFIG_DEFAULT: Record<string, any> = {
115
+ active: true,
116
+ req: false,
117
+ reqd: false,
118
+ }
119
+
120
+ // Subtrees carrying user payload rather than schema. An active:true inside an
121
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
122
+ // keys and everything below them is passed through untouched.
123
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
124
+
125
+ function clean(o: any, dropDefaults?: boolean): any {
126
+ const prune = (node: any, defaults: boolean): any => {
127
+ if (Array.isArray(node)) {
128
+ return node.map((n: any) => prune(n, defaults))
92
129
  }
93
- return v
94
- })
130
+ if (null != node && 'object' === typeof node) {
131
+ const out: any = {}
132
+ for (const k of Object.keys(node)) {
133
+ if (MODEL_META.includes(k)) {
134
+ continue
135
+ }
136
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
137
+ continue
138
+ }
139
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
140
+ }
141
+ return out
142
+ }
143
+ return node
144
+ }
145
+ return prune(o, true === dropDefaults)
95
146
  }
96
147
 
97
148
 
@@ -70,7 +70,7 @@ const Config = cmp(async function Config(props: any) {
70
70
  name: n.name,
71
71
  op: n.op,
72
72
  relations: n.relations,
73
- }), a), {})
73
+ }, true), a), {})
74
74
 
75
75
  const config = {
76
76
  main: { name: model.const.Name },
@@ -65,13 +65,64 @@ function scalaVarName(name: string): string {
65
65
 
66
66
 
67
67
  // Strip model bookkeeping keys (ending in $) from a config subtree.
68
- function cleanModel(o: any) {
69
- return walk(clone(o), (k: any, v: any, p: any) => {
70
- if (null != k && k.endsWith('$')) {
71
- delete p[k]
68
+ // Emission-time normalisation of a model subtree (L0).
69
+ //
70
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
71
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
72
+ // default the runtime already assumes when the key is absent, which is pure
73
+ // payload — see CONFIG_DEFAULT.
74
+ //
75
+ // Rebuilds the tree rather than mutating during a walk. The previous
76
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
77
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
78
+ // the delete was undone on the way out and the helper silently did nothing.
79
+ // Returning `undefined` from the callback does not fix it either: setprop
80
+ // stores undefined rather than removing the key, which then emits as a null.
81
+ //
82
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
83
+ // `active` means something different in feature config, where absent reads as
84
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
85
+ // disable the feature.
86
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
87
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
88
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
89
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
90
+ const MODEL_META = ['index$', 'key$', 'val$']
91
+
92
+ // Keys whose value IS the default the runtime already assumes when the key is
93
+ // absent, so emitting them is pure payload.
94
+ const CONFIG_DEFAULT: Record<string, any> = {
95
+ active: true,
96
+ req: false,
97
+ reqd: false,
98
+ }
99
+
100
+ // Subtrees carrying user payload rather than schema. An active:true inside an
101
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
102
+ // keys and everything below them is passed through untouched.
103
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
104
+
105
+ function cleanModel(o: any, dropDefaults?: boolean): any {
106
+ const prune = (node: any, defaults: boolean): any => {
107
+ if (Array.isArray(node)) {
108
+ return node.map((n: any) => prune(n, defaults))
109
+ }
110
+ if (null != node && 'object' === typeof node) {
111
+ const out: any = {}
112
+ for (const k of Object.keys(node)) {
113
+ if (MODEL_META.includes(k)) {
114
+ continue
115
+ }
116
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
117
+ continue
118
+ }
119
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
120
+ }
121
+ return out
72
122
  }
73
- return v
74
- })
123
+ return node
124
+ }
125
+ return prune(o, true === dropDefaults)
75
126
  }
76
127
 
77
128