@voxgig/sdkgen 4.6.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/Feature.js +5 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/TestControl.d.ts +4 -0
- package/dist/cmp/TestControl.js +51 -0
- package/dist/cmp/TestControl.js.map +1 -0
- package/dist/helpers/featureSource.d.ts +3 -1
- package/dist/helpers/featureSource.js +105 -0
- package/dist/helpers/featureSource.js.map +1 -1
- package/dist/helpers/opShape.js +8 -11
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/helpers/pointPath.d.ts +10 -0
- package/dist/helpers/pointPath.js +54 -0
- package/dist/helpers/pointPath.js.map +1 -0
- package/dist/sdkgen.d.ts +6 -3
- package/dist/sdkgen.js +14 -4
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +78 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +58 -0
- package/package.json +2 -2
- package/project/.sdk/model/feature/secrets.aon +82 -0
- package/project/.sdk/src/cmp/c/Config_c.ts +7 -8
- package/project/.sdk/src/cmp/c/TestDirect_c.ts +4 -3
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
- package/project/.sdk/src/cmp/cpp/TestDirect_cpp.ts +4 -3
- package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
- package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +34 -9
- package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
- package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Config_dart.ts +7 -8
- package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
- package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +28 -10
- package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
- package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +9 -8
- package/project/.sdk/src/cmp/go/Config_go.ts +1 -7
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +5 -4
- package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
- package/project/.sdk/src/cmp/java/Config_java.ts +7 -7
- package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
- package/project/.sdk/src/cmp/java/TestDirect_java.ts +5 -4
- package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
- package/project/.sdk/src/cmp/js/Config_js.ts +7 -8
- package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +34 -8
- package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
- package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
- package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +7 -7
- package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
- package/project/.sdk/src/cmp/kotlin/TestDirect_kotlin.ts +5 -4
- package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lean/Config_lean.ts +9 -8
- package/project/.sdk/src/cmp/lua/Config_lua.ts +1 -7
- package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +32 -7
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
- package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
- package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
- package/project/.sdk/src/cmp/perl/Main_perl.ts +2 -1
- package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +29 -7
- package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
- package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
- package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
- package/project/.sdk/src/cmp/php/Config_php.ts +1 -7
- package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +28 -8
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
- package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Config_py.ts +1 -7
- package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +5 -4
- package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
- package/project/.sdk/src/cmp/rb/Config_rb.ts +1 -7
- package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +28 -8
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
- package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +2 -1
- package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +35 -7
- package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
- package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Config_scala.ts +9 -7
- package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
- package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +4 -3
- package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
- package/project/.sdk/src/cmp/seneca-provider/Extras_seneca-provider.ts +3 -1
- package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +9 -5
- package/project/.sdk/src/cmp/ts/Config_ts.ts +57 -16
- package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +15 -7
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +11 -3
- package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
- package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
- package/project/.sdk/tm/dart/test/utility.dart +39 -0
- package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/js/test/utility.js +91 -1
- package/project/.sdk/tm/lua/test/runner.lua +40 -0
- package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
- package/project/.sdk/tm/perl/t/runner.pm +30 -0
- package/project/.sdk/tm/php/test/Runner.php +36 -0
- package/project/.sdk/tm/rb/test/runner.rb +25 -0
- package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +8 -2
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +29 -5
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +30 -21
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +15 -13
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +25 -19
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +1 -1
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +1 -1
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/Feature.ts +5 -0
- package/src/cmp/TestControl.ts +64 -0
- package/src/helpers/featureSource.ts +130 -0
- package/src/helpers/opShape.ts +9 -14
- package/src/helpers/pointPath.ts +88 -0
- package/src/sdkgen.ts +18 -0
- package/src/utility.ts +92 -1
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
|
@@ -260,6 +260,134 @@ function srcFeatureExcludes(model: any): RegExp[] {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
|
|
263
|
+
// The same rule as srcFeatureExcludes, ONE LEVEL DEEPER: a feature's
|
|
264
|
+
// declared-but-inactive PLUGINS.
|
|
265
|
+
//
|
|
266
|
+
// A plugin is a feature's optional part — the `secrets` feature over
|
|
267
|
+
// sekreto has one per provider kind, and each carries its own platform
|
|
268
|
+
// cost (node:crypto for AWS request signing, fetch for seven HTTP vault
|
|
269
|
+
// clients). A project whose chain is `[dotenv, env]` should carry neither,
|
|
270
|
+
// and before the plugin trim it carried both, because every kind was
|
|
271
|
+
// reachable from a single import.
|
|
272
|
+
//
|
|
273
|
+
// Layout is `src/feature/<feature>/plugin/<plugin>/`, so a plugin's tree
|
|
274
|
+
// is excluded exactly as a feature's is. Only declared-but-inactive
|
|
275
|
+
// plugins are excluded: a directory the model never mentions is left
|
|
276
|
+
// alone, matching srcFeatureExcludes' treatment of `base`.
|
|
277
|
+
//
|
|
278
|
+
// Note this trims a plugin belonging to an ACTIVE feature. An inactive
|
|
279
|
+
// feature's whole tree — plugins included — is already gone via
|
|
280
|
+
// srcFeatureExcludes, so walking its plugins here would be redundant, and
|
|
281
|
+
// asking the model for them would fail on the `only_active` filter anyway.
|
|
282
|
+
// One feature's inactive-plugin patterns.
|
|
283
|
+
//
|
|
284
|
+
// Separate from pluginExcludes(model) because the two COPIES that need it
|
|
285
|
+
// see different things: Main copies the whole target tree and has the
|
|
286
|
+
// model, while the Feature component copies one feature's tree and has
|
|
287
|
+
// only that feature. Feature's copy is the one that actually matters —
|
|
288
|
+
// it runs for every ACTIVE feature, so without this an active feature's
|
|
289
|
+
// plugins were all copied regardless, and Main's exclude never saw them.
|
|
290
|
+
function pluginExcludesFor(model: any, fname: string): RegExp[] {
|
|
291
|
+
if (null == model || null == fname) {
|
|
292
|
+
return []
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// `only_active: false`, and this is the whole subtlety. The feature
|
|
296
|
+
// object a component is handed has ALREADY been filtered, so its
|
|
297
|
+
// `plugin` map contains only the ACTIVE plugins — and a function looking
|
|
298
|
+
// there for something inactive finds nothing and excludes nothing. That
|
|
299
|
+
// is exactly the trap srcFeatureExcludes documents, hit again one level
|
|
300
|
+
// down: the first cut of this read the filtered feature and silently
|
|
301
|
+
// trimmed nothing while every test passed.
|
|
302
|
+
const plugins = getModelPath(model,
|
|
303
|
+
`main.${KIT}.feature.${fname}.plugin`,
|
|
304
|
+
{ required: false, only_active: false }) || {}
|
|
305
|
+
|
|
306
|
+
const out: RegExp[] = []
|
|
307
|
+
const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
308
|
+
|
|
309
|
+
for (const pname of Object.keys(plugins).sort()) {
|
|
310
|
+
const plugin = plugins[pname]
|
|
311
|
+
if (false !== plugin?.active) continue
|
|
312
|
+
|
|
313
|
+
const declared = plugin.path || []
|
|
314
|
+
|
|
315
|
+
if (0 < declared.length) {
|
|
316
|
+
for (const one of declared) {
|
|
317
|
+
// FEATURE-RELATIVE, because jostraca tests a Copy's candidates
|
|
318
|
+
// against the path RELATIVE TO THAT COPY'S ROOT — `sekreto/
|
|
319
|
+
// provider/aws.ts`, not `src/feature/secrets/sekreto/provider/
|
|
320
|
+
// aws.ts`. The Feature component's Copy is rooted at
|
|
321
|
+
// `tm/<target>/src/feature/<feature>`, so a full declared path
|
|
322
|
+
// never matches and the trim silently does nothing.
|
|
323
|
+
//
|
|
324
|
+
// Worth stating because it is invisible: an exclude that matches
|
|
325
|
+
// nothing is indistinguishable from no exclude at all, and both
|
|
326
|
+
// the patterns and the model were correct while nothing was
|
|
327
|
+
// trimmed. Main's copy IS rooted at the target root, which is why
|
|
328
|
+
// pluginExcludes below keeps the full path.
|
|
329
|
+
const rel = String(one).replace(
|
|
330
|
+
new RegExp('^src/feature/' + esc(fname) + '/'), '')
|
|
331
|
+
const pat = esc(rel)
|
|
332
|
+
out.push(new RegExp('(^|/)' + pat.replace(/\\\/$/, '') +
|
|
333
|
+
(/\/$/.test(rel) ? '/' : '$')))
|
|
334
|
+
}
|
|
335
|
+
continue
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
out.push(new RegExp('(^|/)plugin/' + esc(pname) + '/'))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return out
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
function pluginExcludes(model: any): RegExp[] {
|
|
346
|
+
const active = getModelPath(model, `main.${KIT}.feature`,
|
|
347
|
+
{ required: false }) || {}
|
|
348
|
+
|
|
349
|
+
const out: RegExp[] = []
|
|
350
|
+
const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
351
|
+
|
|
352
|
+
for (const fname of Object.keys(active)) {
|
|
353
|
+
const all = getModelPath(model,
|
|
354
|
+
`main.${KIT}.feature.${fname}.plugin`,
|
|
355
|
+
{ required: false, only_active: false }) || {}
|
|
356
|
+
const on = getModelPath(model,
|
|
357
|
+
`main.${KIT}.feature.${fname}.plugin`,
|
|
358
|
+
{ required: false }) || {}
|
|
359
|
+
|
|
360
|
+
for (const pname of Object.keys(all)) {
|
|
361
|
+
if (null != on[pname]) continue
|
|
362
|
+
|
|
363
|
+
// DECLARED paths first. A plugin's files are often not free to
|
|
364
|
+
// move — sekreto's provider modules are vendored, and both the
|
|
365
|
+
// vendoring guard and their own relative imports pin them at
|
|
366
|
+
// upstream's directory depth — so a plugin says which paths it
|
|
367
|
+
// owns rather than being assumed to own a directory.
|
|
368
|
+
const declared = all[pname].path || []
|
|
369
|
+
|
|
370
|
+
if (0 < declared.length) {
|
|
371
|
+
for (const one of declared) {
|
|
372
|
+
const pat = esc(String(one))
|
|
373
|
+
// A trailing slash means a folder and everything under it;
|
|
374
|
+
// anything else matches that path exactly, as featureExcludes
|
|
375
|
+
// does for a file source.
|
|
376
|
+
out.push(new RegExp('(^|/)' + pat.replace(/\\\/$/, '') +
|
|
377
|
+
(/\/$/.test(String(one)) ? '/' : '$')))
|
|
378
|
+
}
|
|
379
|
+
continue
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
out.push(new RegExp(
|
|
383
|
+
'(^|/)src/feature/' + esc(fname) + '/plugin/' + esc(pname) + '/'))
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return out
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
|
|
263
391
|
export type {
|
|
264
392
|
FeatureSource,
|
|
265
393
|
FeatureEntry,
|
|
@@ -275,4 +403,6 @@ export {
|
|
|
275
403
|
featureExcludes,
|
|
276
404
|
fullsetExcludes,
|
|
277
405
|
srcFeatureExcludes,
|
|
406
|
+
pluginExcludes,
|
|
407
|
+
pluginExcludesFor,
|
|
278
408
|
}
|
package/src/helpers/opShape.ts
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
import { each, names } from 'jostraca'
|
|
30
30
|
import { KIT, getModelPath } from '@voxgig/apidef'
|
|
31
31
|
|
|
32
|
+
import { pointSegments, pointTerminalParam, pointPathKey } from './pointPath'
|
|
33
|
+
|
|
32
34
|
|
|
33
35
|
// THE entity collection for a model — resolved once, with a stable identity.
|
|
34
36
|
//
|
|
@@ -217,25 +219,19 @@ function entityPath(entity: any): string {
|
|
|
217
219
|
// no point's `select.exist` matches. It cannot be shared with them — a
|
|
218
220
|
// template ships standalone, outside this package — so it is written twice on
|
|
219
221
|
// purpose, and both sides must move together.
|
|
220
|
-
function terminalParam(point: any): boolean {
|
|
221
|
-
const parts: any[] = (point && point.parts) || []
|
|
222
|
-
const last = 0 < parts.length ? parts[parts.length - 1] : ''
|
|
223
|
-
return 'string' === typeof last && last.startsWith('{')
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
222
|
function ownPoint(points: any[]): any {
|
|
228
223
|
let best = points[0]
|
|
229
224
|
|
|
230
225
|
for (const pt of points) {
|
|
231
|
-
if (null == pt || null == pt.
|
|
226
|
+
if (null == pt || null == pt.segments || null == best || null == best.segments) {
|
|
232
227
|
continue
|
|
233
228
|
}
|
|
234
229
|
|
|
235
|
-
const ptterm =
|
|
236
|
-
const bestterm =
|
|
230
|
+
const ptterm = pointTerminalParam(pt)
|
|
231
|
+
const bestterm = pointTerminalParam(best)
|
|
237
232
|
|
|
238
|
-
if (ptterm !== bestterm ?
|
|
233
|
+
if (ptterm !== bestterm ?
|
|
234
|
+
ptterm : pointSegments(pt).length < pointSegments(best).length) {
|
|
239
235
|
best = pt
|
|
240
236
|
}
|
|
241
237
|
}
|
|
@@ -248,10 +244,9 @@ function ownPoint(points: any[]): any {
|
|
|
248
244
|
// selectors on one endpoint (the same path, chosen by different query
|
|
249
245
|
// params), not cross-references to different resources.
|
|
250
246
|
function samePath(points: any[]): boolean {
|
|
251
|
-
const first = (
|
|
247
|
+
const first = pointPathKey(points[0])
|
|
252
248
|
|
|
253
|
-
return points.every((pt: any) =>
|
|
254
|
-
first === ((pt && pt.parts) || []).join('/'))
|
|
249
|
+
return points.every((pt: any) => first === pointPathKey(pt))
|
|
255
250
|
}
|
|
256
251
|
|
|
257
252
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* Copyright (c) 2024-2025 Voxgig, MIT License */
|
|
2
|
+
|
|
3
|
+
// A point's path, as apidef resolves it.
|
|
4
|
+
//
|
|
5
|
+
// apidef ADR-003: the model carries the path as a typed segment vector —
|
|
6
|
+
// `[{ lit: 'element' }, { var: 'id' }]` — and no brace-templated string. A
|
|
7
|
+
// `var` names one of the point's `args.params` directly, so a consumer walks
|
|
8
|
+
// the vector instead of parsing anything.
|
|
9
|
+
//
|
|
10
|
+
// The generated SDK runtimes still speak the older braced-string form
|
|
11
|
+
// (`['element', '{id}']`), which this file reconstructs in ONE place. Every
|
|
12
|
+
// generation-time consumer and the embedded config go through here, so when
|
|
13
|
+
// the runtimes move onto segments there is a single call site to retire —
|
|
14
|
+
// rather than the twenty-odd hand-written brace parsers ADR-003 is about.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
type PathSegment = {
|
|
18
|
+
lit?: string
|
|
19
|
+
var?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
// The segment vector, defensively. GraphQL points address the single endpoint
|
|
24
|
+
// and carry no path, so an empty vector is normal, not a fault.
|
|
25
|
+
function pointSegments(point: any): PathSegment[] {
|
|
26
|
+
const segments = point && point.segments
|
|
27
|
+
return Array.isArray(segments) ? segments : []
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// The braced-string form the SDK runtimes still consume. A `lit` is emitted
|
|
32
|
+
// verbatim — including one that contains braces, which apidef leaves literal
|
|
33
|
+
// (a compound element like `{a}.{b}` names no single parameter). That is the
|
|
34
|
+
// lossiness ADR-003 removed from the model, and it survives here only because
|
|
35
|
+
// this is the OLD representation being reconstructed on the way out.
|
|
36
|
+
function pointParts(point: any): string[] {
|
|
37
|
+
return pointSegments(point).map((seg: PathSegment) =>
|
|
38
|
+
null == seg.var ? String(seg.lit ?? '') : '{' + seg.var + '}')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// Does the path end in a parameter?
|
|
43
|
+
//
|
|
44
|
+
// DELIBERATELY asked of the reconstructed part, not of the vector, even
|
|
45
|
+
// though the vector states it directly and more accurately.
|
|
46
|
+
//
|
|
47
|
+
// The same rule runs at RUNTIME, in all 21 languages' makePoint template
|
|
48
|
+
// (`0 === last.indexOf('{')` over `parts`), to pick a fallback route when no
|
|
49
|
+
// point's `select.exist` matches. Those templates ship standalone, outside
|
|
50
|
+
// this package, so the rule is written twice on purpose and BOTH SIDES MUST
|
|
51
|
+
// MOVE TOGETHER — see the note in opShape.ts and in each template.
|
|
52
|
+
//
|
|
53
|
+
// Reading the vector here would break that. A path ending in a LITERAL that
|
|
54
|
+
// contains braces (`/reports/{id}.json`) is not a terminal parameter by the
|
|
55
|
+
// vector, but every runtime still says it is, because from the reconstructed
|
|
56
|
+
// string it cannot tell. Generation-time `ownPoint` would then pick a
|
|
57
|
+
// different route than the SDK picks at request time — for the same model.
|
|
58
|
+
//
|
|
59
|
+
// So this stays bug-compatible with the runtimes until they move onto
|
|
60
|
+
// segments, at which point this becomes `null != last.var` and all 21 change
|
|
61
|
+
// with it.
|
|
62
|
+
function pointTerminalParam(point: any): boolean {
|
|
63
|
+
const parts = pointParts(point)
|
|
64
|
+
const last = 0 < parts.length ? parts[parts.length - 1] : ''
|
|
65
|
+
return 0 === last.indexOf('{')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// Do two points describe the same route? Compares the vectors, so a literal
|
|
70
|
+
// containing braces cannot be mistaken for a parameter of the same spelling.
|
|
71
|
+
function pointPathKey(point: any): string {
|
|
72
|
+
return pointSegments(point)
|
|
73
|
+
.map((seg: PathSegment) =>
|
|
74
|
+
null == seg.var ? 'l:' + String(seg.lit ?? '') : 'v:' + seg.var)
|
|
75
|
+
.join('/')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
export type {
|
|
80
|
+
PathSegment,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
pointSegments,
|
|
85
|
+
pointParts,
|
|
86
|
+
pointTerminalParam,
|
|
87
|
+
pointPathKey,
|
|
88
|
+
}
|
package/src/sdkgen.ts
CHANGED
|
@@ -40,6 +40,7 @@ import { License } from './cmp/License'
|
|
|
40
40
|
import { Security } from './cmp/Security'
|
|
41
41
|
import { Changelog } from './cmp/Changelog'
|
|
42
42
|
import { Test } from './cmp/Test'
|
|
43
|
+
import { TestControl, TEST_CONTROL_EXCLUDE } from './cmp/TestControl'
|
|
43
44
|
import { ReadmeInstall } from './cmp/ReadmeInstall'
|
|
44
45
|
import { ReadmeQuick } from './cmp/ReadmeQuick'
|
|
45
46
|
import { ReadmeErrors } from './cmp/ReadmeErrors'
|
|
@@ -66,6 +67,9 @@ import { serverVariables, hasServerVariables, serverVarEnv } from './helpers/ser
|
|
|
66
67
|
import { primaryOpCall, idLiteral, matchArg, dataArg, litFor } from './helpers/opExample'
|
|
67
68
|
import type { ExampleLang } from './helpers/opExample'
|
|
68
69
|
import { liveStrict } from './helpers/testPolicy'
|
|
70
|
+
import { pointSegments, pointParts, pointTerminalParam, pointPathKey }
|
|
71
|
+
from './helpers/pointPath'
|
|
72
|
+
import type { PathSegment } from './helpers/pointPath'
|
|
69
73
|
import {
|
|
70
74
|
featureOf,
|
|
71
75
|
availableFeatures,
|
|
@@ -73,6 +77,8 @@ import {
|
|
|
73
77
|
featureExcludes,
|
|
74
78
|
fullsetExcludes,
|
|
75
79
|
srcFeatureExcludes,
|
|
80
|
+
pluginExcludes,
|
|
81
|
+
pluginExcludesFor,
|
|
76
82
|
} from './helpers/featureSource'
|
|
77
83
|
import type { FeatureSource } from './helpers/featureSource'
|
|
78
84
|
import { stationLibrary } from './helpers/station'
|
|
@@ -1011,6 +1017,7 @@ export type {
|
|
|
1011
1017
|
SdkGenOptions,
|
|
1012
1018
|
ExampleLang,
|
|
1013
1019
|
DepEntry,
|
|
1020
|
+
PathSegment,
|
|
1014
1021
|
FeatureSource,
|
|
1015
1022
|
DoctorReport,
|
|
1016
1023
|
RegisterOptions,
|
|
@@ -1072,6 +1079,8 @@ export {
|
|
|
1072
1079
|
Entity,
|
|
1073
1080
|
Feature,
|
|
1074
1081
|
Test,
|
|
1082
|
+
TestControl,
|
|
1083
|
+
TEST_CONTROL_EXCLUDE,
|
|
1075
1084
|
Readme,
|
|
1076
1085
|
ReadmeTop,
|
|
1077
1086
|
AgentGuideTop,
|
|
@@ -1114,6 +1123,13 @@ export {
|
|
|
1114
1123
|
buildIdNames,
|
|
1115
1124
|
getMatchEntries,
|
|
1116
1125
|
collectDeps,
|
|
1126
|
+
|
|
1127
|
+
// apidef ADR-003: the model carries typed path segments, not braced
|
|
1128
|
+
// strings. Scaffold components read the path through these.
|
|
1129
|
+
pointSegments,
|
|
1130
|
+
pointParts,
|
|
1131
|
+
pointTerminalParam,
|
|
1132
|
+
pointPathKey,
|
|
1117
1133
|
canonToType,
|
|
1118
1134
|
canonToDtype,
|
|
1119
1135
|
canonKey,
|
|
@@ -1167,6 +1183,8 @@ export {
|
|
|
1167
1183
|
featureExcludes,
|
|
1168
1184
|
fullsetExcludes,
|
|
1169
1185
|
srcFeatureExcludes,
|
|
1186
|
+
pluginExcludes,
|
|
1187
|
+
pluginExcludesFor,
|
|
1170
1188
|
stationLibrary,
|
|
1171
1189
|
|
|
1172
1190
|
featureApplies,
|
package/src/utility.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { KIT, getModelPath } from '@voxgig/apidef'
|
|
|
8
8
|
import { targetFeatures } from './helpers/applicability'
|
|
9
9
|
|
|
10
10
|
import { serverVariables } from './helpers/serverVars'
|
|
11
|
+
import { pointParts } from './helpers/pointPath'
|
|
11
12
|
import { packageVersion } from './helpers/packageMeta'
|
|
12
13
|
|
|
13
14
|
|
|
@@ -82,6 +83,34 @@ function isHttpBasicAuth(model: any): boolean {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
|
|
86
|
+
// The API's ACCESS-TOKEN EXCHANGE, as apidef recorded it from the spec
|
|
87
|
+
// (main.kit.info.security.exchange): where the token endpoint lives, and the
|
|
88
|
+
// field names it sends and answers with. Null when the spec describes none,
|
|
89
|
+
// which is the common case.
|
|
90
|
+
//
|
|
91
|
+
// These are FACTS ABOUT THE API, so they belong in the generated config
|
|
92
|
+
// rather than in a project's hand-written model: an SDK whose spec says the
|
|
93
|
+
// exchange is at `auth/token` should not need to be told so again. A feature
|
|
94
|
+
// opts in to receiving them with `spec: { authexchange: '<options-key>' }`;
|
|
95
|
+
// configDefinition does the overlay.
|
|
96
|
+
//
|
|
97
|
+
// Deliberately NOT included: `active`. Whether a client performs the
|
|
98
|
+
// exchange is the project's decision (some resolve a static credential
|
|
99
|
+
// through the same feature on an API that also has a token endpoint), so
|
|
100
|
+
// apidef records only what the exchange IS, never that it should run.
|
|
101
|
+
function resolveAuthExchange(model: any): Record<string, any> | null {
|
|
102
|
+
const security = getModelPath(model, `main.${KIT}.info.security`,
|
|
103
|
+
{ only_active: false, required: false })
|
|
104
|
+
|
|
105
|
+
const exchange = security?.exchange
|
|
106
|
+
if (null == exchange || 'object' !== typeof exchange) {
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return exchange
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
85
114
|
function requirePath(ctx$: any, path: string, flags?: { ignore?: boolean }): any {
|
|
86
115
|
const fullpath = resolvePath(ctx$, path)
|
|
87
116
|
const ignore = null == flags?.ignore ? false : flags.ignore
|
|
@@ -118,6 +147,7 @@ export {
|
|
|
118
147
|
requirePath,
|
|
119
148
|
isAuthActive,
|
|
120
149
|
resolveAuthPrefix,
|
|
150
|
+
resolveAuthExchange,
|
|
121
151
|
isHttpBasicAuth,
|
|
122
152
|
SdkGenError,
|
|
123
153
|
CONFIG_DATA_THRESHOLD,
|
|
@@ -301,6 +331,15 @@ function rawStringLiteral(s: string): string {
|
|
|
301
331
|
}
|
|
302
332
|
|
|
303
333
|
|
|
334
|
+
// The closed vocabulary of spec-derived facts a feature may ask for by
|
|
335
|
+
// declaring `spec: { <fact>: <options-key> }`. Closed, and resolved through
|
|
336
|
+
// one function each, so a feature cannot reach arbitrarily into the model
|
|
337
|
+
// and a fact's shape is defined in exactly one place.
|
|
338
|
+
const SPEC_FACTS: Record<string, (model: any) => any> = {
|
|
339
|
+
authexchange: resolveAuthExchange,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
304
343
|
// THE CANONICAL CONFIG OBJECT, and the JSON the threshold is measured on.
|
|
305
344
|
//
|
|
306
345
|
// Every target builds its config from this one function, so the literal a
|
|
@@ -312,6 +351,35 @@ function rawStringLiteral(s: string): string {
|
|
|
312
351
|
//
|
|
313
352
|
// Key order is `each`'s order, which is sorted, so the JSON is byte-stable
|
|
314
353
|
// across runs exactly like the literal it replaces.
|
|
354
|
+
// The embedded config still speaks the braced-string path form
|
|
355
|
+
// (`['element', '{id}']`) that every generated runtime reads. apidef now
|
|
356
|
+
// emits the resolved vector instead (its ADR-003), so the old shape is
|
|
357
|
+
// reconstructed HERE — the single point at which a point reaches generated
|
|
358
|
+
// output — via the single reconstruction in helpers/pointPath.
|
|
359
|
+
//
|
|
360
|
+
// Both are written: `parts` for the runtimes as they stand, `segments`
|
|
361
|
+
// alongside it so a runtime can be moved over one language at a time. That
|
|
362
|
+
// duplication is deliberate and temporary, and it lives in GENERATED output,
|
|
363
|
+
// not in the model — which is what ADR-003 forbids. It ends when the last
|
|
364
|
+
// runtime reads segments and `parts` is dropped from this function.
|
|
365
|
+
function withPointParts(op: any): any {
|
|
366
|
+
if (null == op) {
|
|
367
|
+
return op
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const out: any = {}
|
|
371
|
+
each(op, (o: any, opname: string) => {
|
|
372
|
+
out[opname] = null == o || null == o.points ? o : {
|
|
373
|
+
...o,
|
|
374
|
+
points: each(o.points).map((pt: any) =>
|
|
375
|
+
null == pt ? pt : { ...pt, parts: pointParts(pt) }),
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
return out
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
|
|
315
383
|
function configDefinition(model: any, targetname?: string): { def: any, json: string } {
|
|
316
384
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
317
385
|
|
|
@@ -339,7 +407,7 @@ function configDefinition(model: any, targetname?: string): { def: any, json: st
|
|
|
339
407
|
entityDefs[e.name] = clean({
|
|
340
408
|
fields: e.fields,
|
|
341
409
|
name: e.name,
|
|
342
|
-
op: e.op,
|
|
410
|
+
op: withPointParts(e.op),
|
|
343
411
|
relations: e.relations,
|
|
344
412
|
}, true)
|
|
345
413
|
entityStubs[e.name] = {}
|
|
@@ -361,6 +429,29 @@ function configDefinition(model: any, targetname?: string): { def: any, json: st
|
|
|
361
429
|
if (null != f.transport && '' !== f.transport) {
|
|
362
430
|
fdef.transport = String(f.transport)
|
|
363
431
|
}
|
|
432
|
+
|
|
433
|
+
// SPEC-DERIVED OPTIONS. A feature declares `spec: { <fact>: <options
|
|
434
|
+
// key> }` to receive facts apidef recorded from the OpenAPI spec — the
|
|
435
|
+
// same declare-never-infer rule `needs` and `transport` follow, and for
|
|
436
|
+
// the same reason: the alternative is this function knowing feature
|
|
437
|
+
// names, which is exactly the coupling the generic feature loop exists
|
|
438
|
+
// to avoid.
|
|
439
|
+
//
|
|
440
|
+
// The fact overlays the feature's DECLARED DEFAULTS, and rightly: a
|
|
441
|
+
// default like `path: 'auth/token'` is a generic guess, while the spec
|
|
442
|
+
// states where this API's endpoint actually is. A project that needs
|
|
443
|
+
// something else still overrides at runtime through `options.feature`,
|
|
444
|
+
// which beats the embedded config either way.
|
|
445
|
+
for (const factname of Object.keys(f.spec || {}).sort()) {
|
|
446
|
+
const optkey = f.spec[factname]
|
|
447
|
+
const fact = SPEC_FACTS[factname]?.(model)
|
|
448
|
+
if (null == fact || null == optkey || '' === optkey) {
|
|
449
|
+
continue
|
|
450
|
+
}
|
|
451
|
+
fdef.options = { ...(fdef.options || {}) }
|
|
452
|
+
fdef.options[optkey] = { ...(fdef.options[optkey] || {}), ...fact }
|
|
453
|
+
}
|
|
454
|
+
|
|
364
455
|
featureDefs[f.name] = fdef
|
|
365
456
|
})
|
|
366
457
|
|