@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
@@ -86,13 +86,64 @@ function formatHsValue(val: any): string {
86
86
 
87
87
 
88
88
  // Remove `$`-suffixed model annotation keys.
89
- function clean(o: any) {
90
- return walk(clone(o), (k: any, v: any, p: any) => {
91
- if (null != k && k.endsWith('$')) {
92
- delete p[k]
89
+ // Emission-time normalisation of a model subtree (L0).
90
+ //
91
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
92
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
93
+ // default the runtime already assumes when the key is absent, which is pure
94
+ // payload — see CONFIG_DEFAULT.
95
+ //
96
+ // Rebuilds the tree rather than mutating during a walk. The previous
97
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
98
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
99
+ // the delete was undone on the way out and the helper silently did nothing.
100
+ // Returning `undefined` from the callback does not fix it either: setprop
101
+ // stores undefined rather than removing the key, which then emits as a null.
102
+ //
103
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
104
+ // `active` means something different in feature config, where absent reads as
105
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
106
+ // disable the feature.
107
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
108
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
109
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
110
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
111
+ const MODEL_META = ['index$', 'key$', 'val$']
112
+
113
+ // Keys whose value IS the default the runtime already assumes when the key is
114
+ // absent, so emitting them is pure payload.
115
+ const CONFIG_DEFAULT: Record<string, any> = {
116
+ active: true,
117
+ req: false,
118
+ reqd: false,
119
+ }
120
+
121
+ // Subtrees carrying user payload rather than schema. An active:true inside an
122
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
123
+ // keys and everything below them is passed through untouched.
124
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
125
+
126
+ function clean(o: any, dropDefaults?: boolean): any {
127
+ const prune = (node: any, defaults: boolean): any => {
128
+ if (Array.isArray(node)) {
129
+ return node.map((n: any) => prune(n, defaults))
130
+ }
131
+ if (null != node && 'object' === typeof node) {
132
+ const out: any = {}
133
+ for (const k of Object.keys(node)) {
134
+ if (MODEL_META.includes(k)) {
135
+ continue
136
+ }
137
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
138
+ continue
139
+ }
140
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
141
+ }
142
+ return out
93
143
  }
94
- return v
95
- })
144
+ return node
145
+ }
146
+ return prune(o, true === dropDefaults)
96
147
  }
97
148
 
98
149
 
@@ -73,7 +73,7 @@ const Config = cmp(async function Config(props: any) {
73
73
  name: n.name,
74
74
  op: n.op,
75
75
  relations: n.relations,
76
- }), a), {})
76
+ }, true), a), {})
77
77
 
78
78
  const config = {
79
79
  main: { name: model.const.Name },
@@ -68,13 +68,64 @@ function javaVarName(name: string): string {
68
68
 
69
69
 
70
70
  // Strip model bookkeeping keys (ending in $) from a config subtree.
71
- function cleanModel(o: any) {
72
- return walk(clone(o), (k: any, v: any, p: any) => {
73
- if (null != k && k.endsWith('$')) {
74
- delete p[k]
71
+ // Emission-time normalisation of a model subtree (L0).
72
+ //
73
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
74
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
75
+ // default the runtime already assumes when the key is absent, which is pure
76
+ // payload — see CONFIG_DEFAULT.
77
+ //
78
+ // Rebuilds the tree rather than mutating during a walk. The previous
79
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
80
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
81
+ // the delete was undone on the way out and the helper silently did nothing.
82
+ // Returning `undefined` from the callback does not fix it either: setprop
83
+ // stores undefined rather than removing the key, which then emits as a null.
84
+ //
85
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
86
+ // `active` means something different in feature config, where absent reads as
87
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
88
+ // disable the feature.
89
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
90
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
91
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
92
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
93
+ const MODEL_META = ['index$', 'key$', 'val$']
94
+
95
+ // Keys whose value IS the default the runtime already assumes when the key is
96
+ // absent, so emitting them is pure payload.
97
+ const CONFIG_DEFAULT: Record<string, any> = {
98
+ active: true,
99
+ req: false,
100
+ reqd: false,
101
+ }
102
+
103
+ // Subtrees carrying user payload rather than schema. An active:true inside an
104
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
105
+ // keys and everything below them is passed through untouched.
106
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
107
+
108
+ function cleanModel(o: any, dropDefaults?: boolean): any {
109
+ const prune = (node: any, defaults: boolean): any => {
110
+ if (Array.isArray(node)) {
111
+ return node.map((n: any) => prune(n, defaults))
112
+ }
113
+ if (null != node && 'object' === typeof node) {
114
+ const out: any = {}
115
+ for (const k of Object.keys(node)) {
116
+ if (MODEL_META.includes(k)) {
117
+ continue
118
+ }
119
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
120
+ continue
121
+ }
122
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
123
+ }
124
+ return out
75
125
  }
76
- return v
77
- })
126
+ return node
127
+ }
128
+ return prune(o, true === dropDefaults)
78
129
  }
79
130
 
80
131
 
@@ -104,7 +104,7 @@ const Config = cmp(async function Config(props: any) {
104
104
  name: n.name,
105
105
  op: n.op,
106
106
  relations: n.relations,
107
- }), a), {}), { margin: 2 }).trim(),
107
+ }, true), a), {}), { margin: 2 }).trim(),
108
108
  }
109
109
  })
110
110
  })
@@ -102,13 +102,64 @@ function formatJson(obj: any, flags?: { line?: boolean, margin?: number }): stri
102
102
  }
103
103
 
104
104
 
105
- function clean(o: any) {
106
- return walk(clone(o), (k: any, v: any, p: any) => {
107
- if (null != k && k.endsWith('$')) {
108
- delete p[k]
105
+ // Emission-time normalisation of a model subtree (L0).
106
+ //
107
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
108
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
109
+ // default the runtime already assumes when the key is absent, which is pure
110
+ // payload — see CONFIG_DEFAULT.
111
+ //
112
+ // Rebuilds the tree rather than mutating during a walk. The previous
113
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
114
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
115
+ // the delete was undone on the way out and the helper silently did nothing.
116
+ // Returning `undefined` from the callback does not fix it either: setprop
117
+ // stores undefined rather than removing the key, which then emits as a null.
118
+ //
119
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
120
+ // `active` means something different in feature config, where absent reads as
121
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
122
+ // disable the feature.
123
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
124
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
125
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
126
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
127
+ const MODEL_META = ['index$', 'key$', 'val$']
128
+
129
+ // Keys whose value IS the default the runtime already assumes when the key is
130
+ // absent, so emitting them is pure payload.
131
+ const CONFIG_DEFAULT: Record<string, any> = {
132
+ active: true,
133
+ req: false,
134
+ reqd: false,
135
+ }
136
+
137
+ // Subtrees carrying user payload rather than schema. An active:true inside an
138
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
139
+ // keys and everything below them is passed through untouched.
140
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
141
+
142
+ function clean(o: any, dropDefaults?: boolean): any {
143
+ const prune = (node: any, defaults: boolean): any => {
144
+ if (Array.isArray(node)) {
145
+ return node.map((n: any) => prune(n, defaults))
109
146
  }
110
- return v
111
- })
147
+ if (null != node && 'object' === typeof node) {
148
+ const out: any = {}
149
+ for (const k of Object.keys(node)) {
150
+ if (MODEL_META.includes(k)) {
151
+ continue
152
+ }
153
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
154
+ continue
155
+ }
156
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
157
+ }
158
+ return out
159
+ }
160
+ return node
161
+ }
162
+ return prune(o, true === dropDefaults)
112
163
  }
113
164
 
114
165
 
@@ -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 },
@@ -64,13 +64,64 @@ function kotlinVarName(name: string): string {
64
64
 
65
65
 
66
66
  // Strip model bookkeeping keys (ending in $) from a config subtree.
67
- function cleanModel(o: any) {
68
- return walk(clone(o), (k: any, v: any, p: any) => {
69
- if (null != k && k.endsWith('$')) {
70
- delete p[k]
67
+ // Emission-time normalisation of a model subtree (L0).
68
+ //
69
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
70
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
71
+ // default the runtime already assumes when the key is absent, which is pure
72
+ // payload — see CONFIG_DEFAULT.
73
+ //
74
+ // Rebuilds the tree rather than mutating during a walk. The previous
75
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
76
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
77
+ // the delete was undone on the way out and the helper silently did nothing.
78
+ // Returning `undefined` from the callback does not fix it either: setprop
79
+ // stores undefined rather than removing the key, which then emits as a null.
80
+ //
81
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
82
+ // `active` means something different in feature config, where absent reads as
83
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
84
+ // disable the feature.
85
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
86
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
87
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
88
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
89
+ const MODEL_META = ['index$', 'key$', 'val$']
90
+
91
+ // Keys whose value IS the default the runtime already assumes when the key is
92
+ // absent, so emitting them is pure payload.
93
+ const CONFIG_DEFAULT: Record<string, any> = {
94
+ active: true,
95
+ req: false,
96
+ reqd: false,
97
+ }
98
+
99
+ // Subtrees carrying user payload rather than schema. An active:true inside an
100
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
101
+ // keys and everything below them is passed through untouched.
102
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
103
+
104
+ function cleanModel(o: any, dropDefaults?: boolean): any {
105
+ const prune = (node: any, defaults: boolean): any => {
106
+ if (Array.isArray(node)) {
107
+ return node.map((n: any) => prune(n, defaults))
108
+ }
109
+ if (null != node && 'object' === typeof node) {
110
+ const out: any = {}
111
+ for (const k of Object.keys(node)) {
112
+ if (MODEL_META.includes(k)) {
113
+ continue
114
+ }
115
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
116
+ continue
117
+ }
118
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
119
+ }
120
+ return out
71
121
  }
72
- return v
73
- })
122
+ return node
123
+ }
124
+ return prune(o, true === dropDefaults)
74
125
  }
75
126
 
76
127
 
@@ -59,7 +59,7 @@ const Config = cmp(async function Config(props: any) {
59
59
  name: n.name,
60
60
  op: n.op,
61
61
  relations: n.relations,
62
- }), a), {})
62
+ }, true), a), {})
63
63
 
64
64
  const config = {
65
65
  main: { name: model.const.Name },
@@ -59,13 +59,64 @@ function pkgName(model: any): string {
59
59
 
60
60
 
61
61
  // Remove `$`-suffixed model annotation keys (so the embedded config is clean).
62
- function clean(o: any) {
63
- return walk(clone(o), (k: any, v: any, p: any) => {
64
- if (null != k && k.endsWith('$')) {
65
- delete p[k]
62
+ // Emission-time normalisation of a model subtree (L0).
63
+ //
64
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
65
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
66
+ // default the runtime already assumes when the key is absent, which is pure
67
+ // payload — see CONFIG_DEFAULT.
68
+ //
69
+ // Rebuilds the tree rather than mutating during a walk. The previous
70
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
71
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
72
+ // the delete was undone on the way out and the helper silently did nothing.
73
+ // Returning `undefined` from the callback does not fix it either: setprop
74
+ // stores undefined rather than removing the key, which then emits as a null.
75
+ //
76
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
77
+ // `active` means something different in feature config, where absent reads as
78
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
79
+ // disable the feature.
80
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
81
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
82
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
83
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
84
+ const MODEL_META = ['index$', 'key$', 'val$']
85
+
86
+ // Keys whose value IS the default the runtime already assumes when the key is
87
+ // absent, so emitting them is pure payload.
88
+ const CONFIG_DEFAULT: Record<string, any> = {
89
+ active: true,
90
+ req: false,
91
+ reqd: false,
92
+ }
93
+
94
+ // Subtrees carrying user payload rather than schema. An active:true inside an
95
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
96
+ // keys and everything below them is passed through untouched.
97
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
98
+
99
+ function clean(o: any, dropDefaults?: boolean): any {
100
+ const prune = (node: any, defaults: boolean): any => {
101
+ if (Array.isArray(node)) {
102
+ return node.map((n: any) => prune(n, defaults))
103
+ }
104
+ if (null != node && 'object' === typeof node) {
105
+ const out: any = {}
106
+ for (const k of Object.keys(node)) {
107
+ if (MODEL_META.includes(k)) {
108
+ continue
109
+ }
110
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
111
+ continue
112
+ }
113
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
114
+ }
115
+ return out
66
116
  }
67
- return v
68
- })
117
+ return node
118
+ }
119
+ return prune(o, true === dropDefaults)
69
120
  }
70
121
 
71
122
 
@@ -65,6 +65,9 @@ const Config = cmp(async function Config(props: any) {
65
65
 
66
66
  Content(`-- ${model.const.Name} SDK configuration
67
67
 
68
+ -- Build a fresh, fully materialised config table. Every call rebuilds the
69
+ -- whole structure, so prefer require("config_shared") unless you need a
70
+ -- private copy you intend to mutate.
68
71
  local function make_config()
69
72
  return {
70
73
  main = {
@@ -99,7 +102,7 @@ ${serverBlock}${authBlock} headers = ${formatLuaTable(headers, 3)},
99
102
  name: n.name,
100
103
  op: n.op,
101
104
  relations: n.relations,
102
- }), a), {}), 2)},
105
+ }, true), a), {}), 2)},
103
106
  }
104
107
  end
105
108
 
@@ -121,6 +124,32 @@ end
121
124
 
122
125
 
123
126
  return make_config
127
+ `)
128
+ })
129
+
130
+ // A sibling module rather than a member of `config`: `config` returns a
131
+ // bare function (`require("config")()`), and turning it into a table would
132
+ // change its observable type for any consumer holding it as a factory.
133
+ File({ name: 'config_shared.' + target.ext }, () => {
134
+ Content(`-- ${model.const.Name} SDK shared configuration
135
+
136
+ local make_config = require("config")
137
+
138
+ local value = nil
139
+
140
+
141
+ -- Return the config for this Lua state, built once on first use. The SDK
142
+ -- reads the config on every request and never writes to it, so one instance
143
+ -- is shared by every client rather than rebuilt per client.
144
+ --
145
+ -- The returned table is shared: treat it as read-only. Callers that need to
146
+ -- mutate should use require("config")(), which always returns a fresh copy.
147
+ return function()
148
+ if value == nil then
149
+ value = make_config()
150
+ end
151
+ return value
152
+ end
124
153
  `)
125
154
  })
126
155
  })
@@ -72,6 +72,7 @@ build = {
72
72
  modules = {
73
73
  ["${model.name}_sdk"] = "${model.name}_sdk.lua",
74
74
  ["config"] = "config.lua",
75
+ ["config_shared"] = "config_shared.lua",
75
76
  ["features"] = "features.lua",
76
77
  }
77
78
  }
@@ -123,7 +123,7 @@ ${hasList ? `
123
123
  assert.are.equal(3, #seen)
124
124
 
125
125
  -- Inbound: streaming active -> yields each item from the feature.
126
- local config = require("config")()
126
+ local config = require("config_shared")()
127
127
  if type(config.feature) == "table" and config.feature.streaming ~= nil then
128
128
  local streamsdk = sdk.test(seed, { feature = { streaming = { active = true } } })
129
129
  local got = {}
@@ -39,7 +39,7 @@ function ProjectNameSDK.new(options)
39
39
  local utility = Utility.new()
40
40
  self._utility = utility
41
41
 
42
- local config = require("config")()
42
+ local config = require("config_shared")()
43
43
 
44
44
  self._rootctx = utility.make_context({
45
45
  client = self,
@@ -66,13 +66,64 @@ function formatLuaValue(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,7 +68,7 @@ const Config = cmp(async function Config(props: any) {
68
68
  name: n.name,
69
69
  op: n.op,
70
70
  relations: n.relations,
71
- }), a), {})
71
+ }, true), a), {})
72
72
 
73
73
  const config = {
74
74
  main: { name: model.const.Name },
@@ -109,13 +109,64 @@ function formatOcamlValue(val: any, indent: number = 0): string {
109
109
 
110
110
 
111
111
  // Deep-remove meta keys (`foo$`) from a model subtree (twin of go's clean).
112
- function clean(o: any) {
113
- return walk(clone(o), (k: any, v: any, p: any) => {
114
- if (null != k && String(k).endsWith('$')) {
115
- delete p[k]
112
+ // Emission-time normalisation of a model subtree (L0).
113
+ //
114
+ // Always drops jostraca's iteration metadata (`$`-suffixed keys: index$,
115
+ // key$, val$). With `dropDefaults`, also drops keys whose value IS the
116
+ // default the runtime already assumes when the key is absent, which is pure
117
+ // payload — see CONFIG_DEFAULT.
118
+ //
119
+ // Rebuilds the tree rather than mutating during a walk. The previous
120
+ // implementation walked a clone calling `delete p[k]`, but walk() assigns its
121
+ // callback's result back over the child (`setprop(out, ckey, walk(...))`), so
122
+ // the delete was undone on the way out and the helper silently did nothing.
123
+ // Returning `undefined` from the callback does not fix it either: setprop
124
+ // stores undefined rather than removing the key, which then emits as a null.
125
+ //
126
+ // `dropDefaults` is opt-in and must be passed ONLY for the entity subtree.
127
+ // `active` means something different in feature config, where absent reads as
128
+ // INACTIVE (see feature_init) — dropping `active: true` there would silently
129
+ // disable the feature.
130
+ // jostraca's iteration metadata, injected by each()/names() while it walks the
131
+ // model. Listed explicitly rather than matched by trailing-dollar suffix: a
132
+ // trailing dollar is not exclusive to jostraca -- Seneca uses entity$ as real
133
+ // data -- so a blanket suffix match can silently drop a legitimate API field.
134
+ const MODEL_META = ['index$', 'key$', 'val$']
135
+
136
+ // Keys whose value IS the default the runtime already assumes when the key is
137
+ // absent, so emitting them is pure payload.
138
+ const CONFIG_DEFAULT: Record<string, any> = {
139
+ active: true,
140
+ req: false,
141
+ reqd: false,
142
+ }
143
+
144
+ // Subtrees carrying user payload rather than schema. An active:true inside an
145
+ // OpenAPI example is DATA, not a default, so default-pruning stops at these
146
+ // keys and everything below them is passed through untouched.
147
+ const PAYLOAD_KEYS = ['default', 'example', 'examples']
148
+
149
+ function clean(o: any, dropDefaults?: boolean): any {
150
+ const prune = (node: any, defaults: boolean): any => {
151
+ if (Array.isArray(node)) {
152
+ return node.map((n: any) => prune(n, defaults))
116
153
  }
117
- return v
118
- })
154
+ if (null != node && 'object' === typeof node) {
155
+ const out: any = {}
156
+ for (const k of Object.keys(node)) {
157
+ if (MODEL_META.includes(k)) {
158
+ continue
159
+ }
160
+ if (defaults && k in CONFIG_DEFAULT && CONFIG_DEFAULT[k] === node[k]) {
161
+ continue
162
+ }
163
+ out[k] = prune(node[k], defaults && !PAYLOAD_KEYS.includes(k))
164
+ }
165
+ return out
166
+ }
167
+ return node
168
+ }
169
+ return prune(o, true === dropDefaults)
119
170
  }
120
171
 
121
172
 
@@ -63,7 +63,7 @@ const Config = cmp(async function Config(props: any) {
63
63
  name: ent.name,
64
64
  op: ent.op,
65
65
  relations: ent.relations,
66
- })
66
+ }, true)
67
67
  })
68
68
 
69
69
  const options: any = {