@voxgig/sdkgen 4.17.2 → 4.17.4

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 CHANGED
@@ -8,7 +8,7 @@ const { Shape, One, Skip } = require('shape')
8
8
 
9
9
  const { SdkGen } = require('../dist/sdkgen.js')
10
10
 
11
- const VERSION = '4.17.2'
11
+ const VERSION = '4.17.4'
12
12
  const KONSOLE = console
13
13
 
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/sdkgen",
3
- "version": "4.17.2",
3
+ "version": "4.17.4",
4
4
  "main": "dist/sdkgen.js",
5
5
  "type": "commonjs",
6
6
  "engines": {
@@ -1233,7 +1233,19 @@ main: kit: feature: secrets: {
1233
1233
  # The secret to resolve, for an API whose credential is not
1234
1234
  # apikey-shaped. The resolved value is written to options.apikey
1235
1235
  # regardless, because that is where prepareAuth looks.
1236
- name: 'apikey'
1236
+ #
1237
+ # A DEFAULT, not a fixed value: the secret's name is a fact about the
1238
+ # project's vault, not about sdkgen. univec resolves `sdk:univec` and so
1239
+ # sets `name: 'univec'` in its project.aon — which a concrete value here
1240
+ # cannot accept, because two concrete scalars conflict rather than yield:
1241
+ #
1242
+ # Cannot unify values at path $.main.kit.feature.secrets.config.options.name
1243
+ #
1244
+ # That SDK worked only because its copy of this file had been hand-edited
1245
+ # to star this line, and regeneration overwrites generated model files, so
1246
+ # the edit was lost on every regen. An option a project must set has to be
1247
+ # settable.
1248
+ name: *'apikey' | string
1237
1249
 
1238
1250
  # sekreto caches resolved values by default. Off means every
1239
1251
  # resolve() asks the chain again; redaction still tracks values.
@@ -227,9 +227,7 @@ func driveEntityOpUntil(t *testing.T, client *sdk.ProjectNameSDK, what string, s
227
227
  clientval := reflect.ValueOf(client)
228
228
 
229
229
  for name := range entities {
230
- runes := []rune(name)
231
- runes[0] = unicode.ToUpper(runes[0])
232
- accessor := clientval.MethodByName(string(runes))
230
+ accessor := clientval.MethodByName(exported(name))
233
231
  if !accessor.IsValid() || 1 != accessor.Type().NumIn() {
234
232
  continue
235
233
  }
@@ -258,6 +256,96 @@ func driveEntityOpUntil(t *testing.T, client *sdk.ProjectNameSDK, what string, s
258
256
  t.Fatalf("no entity operation %s - nothing to assert on", what)
259
257
  }
260
258
 
259
+ // exported is the accessor name for an entity: the model spells entities in
260
+ // lower case, the generated client exports them capitalised.
261
+ func exported(name string) string {
262
+ runes := []rune(name)
263
+ if 0 < len(runes) {
264
+ runes[0] = unicode.ToUpper(runes[0])
265
+ }
266
+ return string(runes)
267
+ }
268
+
269
+ // routableOp names an entity operation that actually REACHES THE TRANSPORT.
270
+ //
271
+ // A generated entity exposes List, Load and Create whether or not the model
272
+ // declares them, so "drive the first entity the map yields" drives a dead
273
+ // end whenever the model does not declare that one. Go randomises map
274
+ // iteration, so which entity that is changes per run, and a test asserting
275
+ // on what reached the wire then passes or fails by lottery.
276
+ //
277
+ // Discover a real route ONCE, against a throwaway client whose provider
278
+ // always succeeds so nothing gates it, and drive that everywhere a test
279
+ // needs a request to come out.
280
+ func routableOp(t *testing.T) (string, string) {
281
+ t.Helper()
282
+
283
+ w := makewire()
284
+ client := secretsClient(w, map[string]any{
285
+ "feature": map[string]any{"secrets": map[string]any{
286
+ "active": true,
287
+ "providers": []any{
288
+ &customProvider{
289
+ lookup: func(name string) (string, bool, error) { return "PROBE01", true, nil },
290
+ },
291
+ },
292
+ }},
293
+ })
294
+
295
+ entities, _ := client.OptionsMap()["entity"].(map[string]any)
296
+ clientval := reflect.ValueOf(client)
297
+
298
+ for name := range entities {
299
+ accessor := clientval.MethodByName(exported(name))
300
+ if !accessor.IsValid() || 1 != accessor.Type().NumIn() {
301
+ continue
302
+ }
303
+ ent := accessor.Call([]reflect.Value{reflect.Zero(accessor.Type().In(0))})[0]
304
+
305
+ for _, opname := range []string{"List", "Load"} {
306
+ op := ent.MethodByName(opname)
307
+ if !op.IsValid() {
308
+ continue
309
+ }
310
+ before := len(w.api())
311
+ in := make([]reflect.Value, op.Type().NumIn())
312
+ for i := range in {
313
+ in[i] = reflect.Zero(op.Type().In(i))
314
+ }
315
+ op.Call(in)
316
+ if before < len(w.api()) {
317
+ return name, opname
318
+ }
319
+ }
320
+ }
321
+
322
+ t.Fatal("no entity operation reaches the transport - nothing to assert on")
323
+ return "", ""
324
+ }
325
+
326
+ // driveNamedOp drives ONE operation already known to be routable.
327
+ //
328
+ // Safe to call from a spawned goroutine, unlike driveEntityOpUntil: it never
329
+ // calls t.Fatal, which Go permits only from the test goroutine.
330
+ func driveNamedOp(t *testing.T, client *sdk.ProjectNameSDK, entity string, opname string) {
331
+ t.Helper()
332
+
333
+ accessor := reflect.ValueOf(client).MethodByName(exported(entity))
334
+ if !accessor.IsValid() || 1 != accessor.Type().NumIn() {
335
+ return
336
+ }
337
+ ent := accessor.Call([]reflect.Value{reflect.Zero(accessor.Type().In(0))})[0]
338
+ op := ent.MethodByName(opname)
339
+ if !op.IsValid() {
340
+ return
341
+ }
342
+ in := make([]reflect.Value, op.Type().NumIn())
343
+ for i := range in {
344
+ in[i] = reflect.Zero(op.Type().In(i))
345
+ }
346
+ op.Call(in)
347
+ }
348
+
261
349
  // driveEntityOp drives ops until one request reached the recorder.
262
350
  func driveEntityOp(t *testing.T, client *sdk.ProjectNameSDK, w *wire) {
263
351
  t.Helper()
@@ -975,6 +1063,8 @@ func TestSecretsMalformedProviderEntry(t *testing.T) {
975
1063
  // SUCCEEDS reopens it and every waiting operation goes out with the FRESH
976
1064
  // credential - never the stale pre-failure header, and never nothing.
977
1065
  func TestSecretsGateRecovery(t *testing.T) {
1066
+ entity, opname := routableOp(t)
1067
+
978
1068
  var mu sync.Mutex
979
1069
  mode := "fail"
980
1070
  release := make(chan struct{})
@@ -1004,7 +1094,12 @@ func TestSecretsGateRecovery(t *testing.T) {
1004
1094
  })
1005
1095
 
1006
1096
  // 1. The failure closes the gate: nothing reaches the wire.
1007
- driveEntityOpUntil(t, client, "was refused by the gate", func() bool { return true })
1097
+ //
1098
+ // The operation is known to be routable, so an empty wire PROVES the gate
1099
+ // refused it. Driving an arbitrary entity would pass here for the wrong
1100
+ // reason: an operation the model never declared reaches no transport
1101
+ // whether the gate is open or shut.
1102
+ driveNamedOp(t, client, entity, opname)
1008
1103
  if 0 != len(w.api()) {
1009
1104
  t.Fatalf("a failed resolution must keep the wire silent, saw %d calls", len(w.api()))
1010
1105
  }
@@ -1020,7 +1115,7 @@ func TestSecretsGateRecovery(t *testing.T) {
1020
1115
  wg.Add(1)
1021
1116
  go func() {
1022
1117
  defer wg.Done()
1023
- driveEntityOpUntil(t, client, "recovered", func() bool { return true })
1118
+ driveNamedOp(t, client, entity, opname)
1024
1119
  }()
1025
1120
  }
1026
1121
  go func() {
@@ -1032,7 +1127,10 @@ func TestSecretsGateRecovery(t *testing.T) {
1032
1127
  for _, call := range w.api() {
1033
1128
  credentialIs(t, call.auth, "FRESH01")
1034
1129
  }
1035
- if 0 == len(w.api()) {
1036
- t.Fatal("recovery must let the operations out")
1130
+ // BOTH, not "at least one": the claim is that a concurrent retry serves
1131
+ // every caller waiting on it, so one call reaching the wire while the
1132
+ // other is dropped is exactly the failure this guards.
1133
+ if 2 != len(w.api()) {
1134
+ t.Fatalf("recovery must let both operations out, saw %d", len(w.api()))
1037
1135
  }
1038
1136
  }
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen",
6
- "version": "4.17.2",
6
+ "version": "4.17.4",
7
7
  "provides": {
8
8
  "target": [
9
9
  "c",