@voxgig/sdkgen 4.30.3 → 4.31.1

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 (74) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/package.d.ts +2 -1
  3. package/dist/action/package.js +8 -1
  4. package/dist/action/package.js.map +1 -1
  5. package/dist/cmp/ReadmeTop.js +6 -5
  6. package/dist/cmp/ReadmeTop.js.map +1 -1
  7. package/dist/helpers/definitionPlan.d.ts +27 -0
  8. package/dist/helpers/definitionPlan.js +219 -0
  9. package/dist/helpers/definitionPlan.js.map +1 -0
  10. package/dist/sdkgen.d.ts +2 -1
  11. package/dist/sdkgen.js +5 -3
  12. package/dist/sdkgen.js.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +2 -2
  15. package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +5 -3
  16. package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +7 -5
  17. package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +5 -3
  18. package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +6 -4
  19. package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +8 -6
  20. package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +7 -9
  21. package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +5 -3
  22. package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +6 -4
  23. package/project/.sdk/src/cmp/js/ReadmeInstall_js.ts +22 -5
  24. package/project/.sdk/src/cmp/js/TestDefinition_js.ts +40 -0
  25. package/project/.sdk/src/cmp/js/Test_js.ts +2 -0
  26. package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +5 -3
  27. package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +7 -3
  28. package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +7 -5
  29. package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +6 -3
  30. package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +5 -3
  31. package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +11 -5
  32. package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +5 -3
  33. package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +5 -3
  34. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +14 -4
  35. package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +5 -3
  36. package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +5 -3
  37. package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +5 -3
  38. package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +6 -4
  39. package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +10 -4
  40. package/project/.sdk/src/cmp/ts/TestDefinition_ts.ts +40 -0
  41. package/project/.sdk/src/cmp/ts/Test_ts.ts +2 -0
  42. package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +5 -3
  43. package/project/.sdk/tm/c/utility/prepare_query.c +34 -2
  44. package/project/.sdk/tm/clojure/src/sdk/core.clj +20 -2
  45. package/project/.sdk/tm/cpp/utility/pipeline.hpp +31 -2
  46. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +32 -4
  47. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +35 -1
  48. package/project/.sdk/tm/go/utility/prepare_query.go +31 -1
  49. package/project/.sdk/tm/java/utility/PrepareQuery.java +29 -5
  50. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +16 -2
  51. package/project/.sdk/tm/js/test/definition-runner.js +161 -0
  52. package/project/.sdk/tm/js/test/pipeline.test.js +43 -3
  53. package/project/.sdk/tm/js/test/sdk-test-control.json +4 -1
  54. package/project/.sdk/tm/kotlin/utility/Prepare.kt +29 -5
  55. package/project/.sdk/tm/lua/utility/prepare_query.lua +31 -2
  56. package/project/.sdk/tm/ocaml/sdk_runtime.ml +22 -1
  57. package/project/.sdk/tm/perl/utility/prepare_query.pm +25 -2
  58. package/project/.sdk/tm/php/utility/PrepareQuery.php +28 -1
  59. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +23 -2
  60. package/project/.sdk/tm/rb/utility/prepare_query.rb +24 -2
  61. package/project/.sdk/tm/rust/utility/prepare_query.rs +34 -2
  62. package/project/.sdk/tm/scala/utility/Prepare.scala +31 -4
  63. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +23 -5
  64. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +16 -2
  65. package/project/.sdk/tm/ts/test/definition-runner.ts +179 -0
  66. package/project/.sdk/tm/ts/test/pipeline.test.ts +43 -3
  67. package/project/.sdk/tm/ts/test/sdk-test-control.json +4 -1
  68. package/project/.sdk/tm/ts/test/utility.ts +3 -2
  69. package/project/.sdk/tm/zig/core/utility.zig +29 -1
  70. package/project/sdkgen-package.json +1 -1
  71. package/src/action/package.ts +10 -1
  72. package/src/cmp/ReadmeTop.ts +7 -6
  73. package/src/helpers/definitionPlan.ts +265 -0
  74. package/src/sdkgen.ts +2 -0
@@ -0,0 +1,265 @@
1
+ /* Copyright (c) 2026 Voxgig Ltd, MIT License */
2
+
3
+ import { KIT, nom } from '@voxgig/apidef'
4
+
5
+ import { entityCollection } from './opShape'
6
+ import { pointSegments } from './pointPath'
7
+ import { pointFacts } from './resolved'
8
+
9
+
10
+ // What a generated definition test checks an operation against: the API
11
+ // definition's own facts, never the model inferred from it. A test generated
12
+ // from the model agrees with the model, so it cannot see a wrong unwrap, a
13
+ // dropped credential or a path parameter echoed into the query.
14
+
15
+ type Credential = { in: string, name: string, scheme?: string }
16
+
17
+ type DefinitionPoint = {
18
+ entity: string
19
+ accessor: string
20
+ op: string
21
+ method: string
22
+ path: string
23
+ action?: string
24
+ args: { name: string, wire: string, value: any }[]
25
+ select: Record<string, any>
26
+ query: string[]
27
+ auth: Credential[][] | null
28
+ status: number
29
+ sample: any
30
+ idField: string
31
+ }
32
+
33
+
34
+ const MAX_ITEMS = 3
35
+ const MAX_DEPTH = 8
36
+ const MAX_SAMPLE = 32 * 1024
37
+
38
+
39
+ function definitionPlan(ctx$: any): DefinitionPoint[] {
40
+ const model = ctx$?.model
41
+ const plan: DefinitionPoint[] = []
42
+ const unchecked = false === model?.main?.[KIT]?.config?.auth?.active ||
43
+ false === model?.main?.[KIT]?.info?.auth
44
+
45
+ for (const entity of Object.values(entityCollection(model)) as any[]) {
46
+ if (false === entity.active) continue
47
+
48
+ for (const [op, operation] of Object.entries(entity.op || {}) as any[]) {
49
+ // An inactive operation stays in the model but gets no method.
50
+ if (false === operation?.active) continue
51
+
52
+ const points = (operation?.points || []).filter((p: any) => false !== p.a)
53
+
54
+ for (const point of points) {
55
+ if ('graphql' === point.k) continue
56
+
57
+ const facts = pointFacts(ctx$, point)
58
+ if ('http' !== facts?.protocol) continue
59
+
60
+ // Points the SDK cannot tell apart are selected by order, not input.
61
+ const select = point.q || {}
62
+ const same = points.filter((p: any) =>
63
+ JSON.stringify(p.q || {}) === JSON.stringify(select))
64
+ if (1 !== same.length) continue
65
+
66
+ const params: any[] = Array.isArray(facts.parameters) ? facts.parameters : []
67
+ const placed = pathPlaceholders(point)
68
+ const args = (point.g?.params || []).map((arg: any, i: number) => {
69
+ const wire = placed[arg.n] ?? (arg.or || arg.n)
70
+ const def = params.find((p: any) => 'path' === p?.in && wire === p?.name)
71
+ return { name: arg.n, wire, value: scalar(def?.example ?? def?.schema?.example) ?? 'p' + (i + 1) }
72
+ })
73
+
74
+ const selected: Record<string, any> = {}
75
+ for (const key of select.exist || []) {
76
+ if (args.some((a: any) => a.name === key)) continue
77
+ const def = params.find((p: any) => key === p?.name)
78
+ selected[key] = scalar(def?.example ?? def?.schema?.example) ?? 'v1'
79
+ }
80
+
81
+ // Every query argument is sent too, so a name that goes out in the
82
+ // model's spelling fails even where no point selects on it. One that
83
+ // another point selects on would move the SDK to that point.
84
+ const elsewhere = new Set(points.filter((p: any) => p !== point)
85
+ .flatMap((p: any) => p.q?.exist || []))
86
+ for (const arg of point.g?.query || []) {
87
+ if (false === arg.a || undefined !== selected[arg.n] || elsewhere.has(arg.n)) continue
88
+ const def = params.find((p: any) => 'query' === p?.in && (arg.or || arg.n) === p?.name)
89
+ selected[arg.n] = scalar(arg.ex ?? def?.example ?? def?.schema?.example) ?? 'v1'
90
+ }
91
+
92
+ const success = successResponse(facts.responses)
93
+ const media = null == success ? undefined : jsonMedia(success.response)
94
+
95
+ plan.push({
96
+ entity: entity.name,
97
+ accessor: nom(entity, 'Name'),
98
+ op,
99
+ method: String(point.m).toUpperCase(),
100
+ path: point.o,
101
+ ...(null == select.$action ? {} : { action: select.$action }),
102
+ args,
103
+ select: selected,
104
+ query: params.filter((p: any) => 'query' === p?.in).map((p: any) => p.name),
105
+ auth: unchecked ? null : credentialSets(facts),
106
+ status: success?.status ?? 200,
107
+ sample: null == media ? null : boundedSample(sampleOf(media)),
108
+ idField: entity.id?.field || 'id',
109
+ })
110
+ }
111
+ }
112
+ }
113
+
114
+ return plan
115
+ }
116
+
117
+
118
+ // The definition's name for each path parameter, keyed by the model's. The
119
+ // model may rename a placeholder, but it keeps the placeholder's place in the
120
+ // path, so position pairs each with its model name whatever `or` holds.
121
+ function pathPlaceholders(point: any): Record<string, string> {
122
+ const orig = String(point.o || '').split('/').filter((part) => '' !== part)
123
+ const segments = pointSegments(point)
124
+ const out: Record<string, string> = {}
125
+ if (orig.length !== segments.length) return out
126
+ segments.forEach((seg: any, i: number) => {
127
+ const m = /^\{([^}]+)\}$/.exec(orig[i])
128
+ if (null != seg.var && null != m) out[seg.var] = m[1]
129
+ })
130
+ return out
131
+ }
132
+
133
+
134
+ function scalar(value: any): any {
135
+ return 'string' === typeof value || 'number' === typeof value ? value : undefined
136
+ }
137
+
138
+
139
+ function successResponse(responses: any): { status: number, response: any } | undefined {
140
+ if (null == responses || 'object' !== typeof responses) return undefined
141
+ const code = Object.keys(responses).filter((c) => /^2\d\d$/.test(c)).sort()[0] ??
142
+ (null != responses['2XX'] ? '2XX' : undefined)
143
+ return null == code ? undefined :
144
+ { status: '2XX' === code ? 200 : Number(code), response: responses[code] }
145
+ }
146
+
147
+
148
+ function jsonMedia(response: any): any {
149
+ const content = response?.content
150
+ if (null != content && 'object' === typeof content) {
151
+ const type = Object.keys(content).find((t) => /json/i.test(t) || '*/*' === t)
152
+ return null == type ? undefined : content[type]
153
+ }
154
+ // Swagger puts the schema and examples on the response itself.
155
+ if (null != response?.schema || null != response?.examples) {
156
+ return { schema: response.schema, example: response.examples?.['application/json'] }
157
+ }
158
+ return undefined
159
+ }
160
+
161
+
162
+ function sampleOf(media: any): any {
163
+ if (undefined !== media.example) return media.example
164
+ const named = Object.values(media.examples || {}).find((e: any) => undefined !== e?.value) as any
165
+ if (null != named) return named.value
166
+ if (undefined !== media.schema?.example) return media.schema.example
167
+ return synthesize(media.schema, 0)
168
+ }
169
+
170
+
171
+ // Schema-shaped data where the definition gives no example: every property,
172
+ // one item per array, the first branch of a union.
173
+ function synthesize(schema: any, depth: number): any {
174
+ if (null == schema || 'object' !== typeof schema || depth > 6) return undefined
175
+ if (undefined !== schema.example) return schema.example
176
+ if (Array.isArray(schema.enum) && 0 < schema.enum.length) return schema.enum[0]
177
+
178
+ if (Array.isArray(schema.allOf)) {
179
+ const parts = schema.allOf.map((s: any) => synthesize(s, depth + 1))
180
+ .filter((v: any) => null != v && 'object' === typeof v && !Array.isArray(v))
181
+ return Object.assign({}, ...parts)
182
+ }
183
+
184
+ const union = schema.oneOf ?? schema.anyOf
185
+ if (Array.isArray(union) && 0 < union.length) return synthesize(union[0], depth + 1)
186
+
187
+ const type = Array.isArray(schema.type) ?
188
+ schema.type.find((t: string) => 'null' !== t) : schema.type
189
+
190
+ if ('array' === type || null != schema.items) {
191
+ const item = synthesize(schema.items, depth + 1)
192
+ return undefined === item ? [] : [item]
193
+ }
194
+ if ('object' === type || null != schema.properties) {
195
+ const out: any = {}
196
+ for (const [key, prop] of Object.entries(schema.properties || {})) {
197
+ const value = synthesize(prop, depth + 1)
198
+ if (undefined !== value) out[key] = value
199
+ }
200
+ return out
201
+ }
202
+ if ('integer' === type || 'number' === type) return 1
203
+ if ('boolean' === type) return true
204
+ if ('string' === type) {
205
+ return 'date-time' === schema.format ? '2026-01-01T00:00:00Z' :
206
+ 'date' === schema.format ? '2026-01-01' : 'x'
207
+ }
208
+ return undefined
209
+ }
210
+
211
+
212
+ function boundedSample(sample: any): any {
213
+ const bound = (node: any, depth: number): any => {
214
+ if (null == node || 'object' !== typeof node) return node
215
+ if (depth > MAX_DEPTH) return null
216
+ if (Array.isArray(node)) return node.slice(0, MAX_ITEMS).map((v) => bound(v, depth + 1))
217
+ return Object.fromEntries(Object.entries(node).map(([k, v]) => [k, bound(v, depth + 1)]))
218
+ }
219
+ const out = bound(sample, 0)
220
+ return undefined === out || MAX_SAMPLE < JSON.stringify(out).length ? null : out
221
+ }
222
+
223
+
224
+ // Alternatives of credentials, every one of a set needed together. Empty is a
225
+ // public operation; null is one whose schemes no SDK option can express.
226
+ function credentialSets(facts: any): Credential[][] | null {
227
+ const security = facts.security
228
+ if (!Array.isArray(security) || 0 === security.length) return []
229
+ if (security.some((req: any) => null == req || 0 === Object.keys(req).length)) return []
230
+
231
+ const schemes = facts.securitySchemes || {}
232
+ const sets: Credential[][] = []
233
+ for (const req of security) {
234
+ const set = Object.keys(req).map((name) => credentialOf(schemes[name]))
235
+ if (set.every((c) => null != c)) sets.push(set as Credential[])
236
+ }
237
+ return 0 === sets.length ? null : sets
238
+ }
239
+
240
+
241
+ function credentialOf(scheme: any): Credential | undefined {
242
+ const type = String(scheme?.type || '').toLowerCase()
243
+ const http = String(scheme?.scheme || '').toLowerCase()
244
+
245
+ if (('http' === type && 'basic' === http) || 'basic' === type) {
246
+ return { in: 'header', name: 'authorization', scheme: 'basic' }
247
+ }
248
+ if (('http' === type && 'bearer' === http) || 'oauth2' === type || 'openidconnect' === type) {
249
+ return { in: 'header', name: 'authorization', scheme: 'bearer' }
250
+ }
251
+ if ('apikey' === type && 'string' === typeof scheme.name &&
252
+ ['header', 'query', 'cookie'].includes(scheme.in)) {
253
+ return { in: scheme.in, name: 'header' === scheme.in ? scheme.name.toLowerCase() : scheme.name }
254
+ }
255
+ return undefined
256
+ }
257
+
258
+
259
+ export type {
260
+ DefinitionPoint,
261
+ }
262
+
263
+ export {
264
+ definitionPlan,
265
+ }
package/src/sdkgen.ts CHANGED
@@ -56,6 +56,7 @@ import { FeatureHook } from './cmp/FeatureHook'
56
56
  import { registerComponent } from './cmp/Registered'
57
57
 
58
58
  import { resolvedFor, liveHint, pointFacts, boundedFacts, hasLiveScenarios } from './helpers/resolved'
59
+ import { definitionPlan } from './helpers/definitionPlan'
59
60
  import type { RegisterOptions } from './cmp/Registered'
60
61
 
61
62
  import { buildIdNames, entityRelationName, flowSteps } from './helpers/buildIdNames'
@@ -987,6 +988,7 @@ export {
987
988
  pointFacts,
988
989
  boundedFacts,
989
990
  hasLiveScenarios,
991
+ definitionPlan,
990
992
 
991
993
  Jostraca,
992
994
  SdkGen,