@reventlessdev/reventless-seed-aws 1.0.0-alpha.28 → 1.0.0-alpha.29

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 (76) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/lib/bs/.compiler.log +2 -2
  3. package/lib/bs/compiler-info.json +1 -1
  4. package/lib/bs/src/ReventlessSeedAws.ast +0 -0
  5. package/lib/bs/src/ReventlessSeedAws.cmi +0 -0
  6. package/lib/bs/src/ReventlessSeedAws.cmj +0 -0
  7. package/lib/bs/src/ReventlessSeedAws.cmt +0 -0
  8. package/lib/bs/src/ReventlessSeedAws.res +111 -59
  9. package/lib/bs/src/ReventlessSeedAws.res.mjs +23 -1
  10. package/lib/bs/src/ReventlessSeedAws_Quiesce.ast +0 -0
  11. package/lib/bs/src/ReventlessSeedAws_Quiesce.cmi +0 -0
  12. package/lib/bs/src/ReventlessSeedAws_Quiesce.cmt +0 -0
  13. package/lib/bs/src/ReventlessSeedAws_Quiesce.res +11 -9
  14. package/lib/bs/src/ReventlessSeedAws_Reconcile.ast +0 -0
  15. package/lib/bs/src/ReventlessSeedAws_Reconcile.cmi +0 -0
  16. package/lib/bs/src/ReventlessSeedAws_Reconcile.cmt +0 -0
  17. package/lib/bs/src/ReventlessSeedAws_Reconcile.res +26 -20
  18. package/lib/bs/src/ReventlessSeedAws_Reset.ast +0 -0
  19. package/lib/bs/src/ReventlessSeedAws_Reset.cmi +0 -0
  20. package/lib/bs/src/ReventlessSeedAws_Reset.cmt +0 -0
  21. package/lib/bs/src/ReventlessSeedAws_Reset.res +51 -46
  22. package/lib/bs/tests/EndpointResolutionTest.cmi +0 -0
  23. package/lib/bs/tests/EndpointResolutionTest.cmt +0 -0
  24. package/lib/bs/tests/ObjectStoreReconcileTest.ast +0 -0
  25. package/lib/bs/tests/ObjectStoreReconcileTest.cmi +0 -0
  26. package/lib/bs/tests/ObjectStoreReconcileTest.cmt +0 -0
  27. package/lib/bs/tests/ObjectStoreReconcileTest.res +4 -5
  28. package/lib/bs/tests/ObjectStoreResetTest.ast +0 -0
  29. package/lib/bs/tests/ObjectStoreResetTest.cmi +0 -0
  30. package/lib/bs/tests/ObjectStoreResetTest.cmt +0 -0
  31. package/lib/bs/tests/ObjectStoreResetTest.res +27 -6
  32. package/lib/bs/tests/QuiesceResetTest.ast +0 -0
  33. package/lib/bs/tests/QuiesceResetTest.cmi +0 -0
  34. package/lib/bs/tests/QuiesceResetTest.cmt +0 -0
  35. package/lib/bs/tests/QuiesceResetTest.res +34 -22
  36. package/lib/ocaml/.compiler.log +2 -2
  37. package/lib/ocaml/EndpointResolutionTest.cmi +0 -0
  38. package/lib/ocaml/EndpointResolutionTest.cmt +0 -0
  39. package/lib/ocaml/ObjectStoreReconcileTest.ast +0 -0
  40. package/lib/ocaml/ObjectStoreReconcileTest.cmi +0 -0
  41. package/lib/ocaml/ObjectStoreReconcileTest.cmt +0 -0
  42. package/lib/ocaml/ObjectStoreReconcileTest.res +4 -5
  43. package/lib/ocaml/ObjectStoreResetTest.ast +0 -0
  44. package/lib/ocaml/ObjectStoreResetTest.cmi +0 -0
  45. package/lib/ocaml/ObjectStoreResetTest.cmt +0 -0
  46. package/lib/ocaml/ObjectStoreResetTest.res +27 -6
  47. package/lib/ocaml/QuiesceResetTest.ast +0 -0
  48. package/lib/ocaml/QuiesceResetTest.cmi +0 -0
  49. package/lib/ocaml/QuiesceResetTest.cmt +0 -0
  50. package/lib/ocaml/QuiesceResetTest.res +34 -22
  51. package/lib/ocaml/ReventlessSeedAws.ast +0 -0
  52. package/lib/ocaml/ReventlessSeedAws.cmi +0 -0
  53. package/lib/ocaml/ReventlessSeedAws.cmj +0 -0
  54. package/lib/ocaml/ReventlessSeedAws.cmt +0 -0
  55. package/lib/ocaml/ReventlessSeedAws.res +111 -59
  56. package/lib/ocaml/ReventlessSeedAws_Quiesce.ast +0 -0
  57. package/lib/ocaml/ReventlessSeedAws_Quiesce.cmi +0 -0
  58. package/lib/ocaml/ReventlessSeedAws_Quiesce.cmt +0 -0
  59. package/lib/ocaml/ReventlessSeedAws_Quiesce.res +11 -9
  60. package/lib/ocaml/ReventlessSeedAws_Reconcile.ast +0 -0
  61. package/lib/ocaml/ReventlessSeedAws_Reconcile.cmi +0 -0
  62. package/lib/ocaml/ReventlessSeedAws_Reconcile.cmt +0 -0
  63. package/lib/ocaml/ReventlessSeedAws_Reconcile.res +26 -20
  64. package/lib/ocaml/ReventlessSeedAws_Reset.ast +0 -0
  65. package/lib/ocaml/ReventlessSeedAws_Reset.cmi +0 -0
  66. package/lib/ocaml/ReventlessSeedAws_Reset.cmt +0 -0
  67. package/lib/ocaml/ReventlessSeedAws_Reset.res +51 -46
  68. package/package.json +4 -4
  69. package/src/ReventlessSeedAws.res +111 -59
  70. package/src/ReventlessSeedAws.res.mjs +23 -1
  71. package/src/ReventlessSeedAws_Quiesce.res +11 -9
  72. package/src/ReventlessSeedAws_Reconcile.res +26 -20
  73. package/src/ReventlessSeedAws_Reset.res +51 -46
  74. package/tests/ObjectStoreReconcileTest.res +4 -5
  75. package/tests/ObjectStoreResetTest.res +27 -6
  76. package/tests/QuiesceResetTest.res +34 -22
@@ -50,8 +50,6 @@ module S3 = AwsSdk.S3
50
50
  module Lambda = AwsSdk.Lambda
51
51
  module Quiesce = ReventlessSeedAws_Quiesce
52
52
 
53
-
54
-
55
53
  // The Pulumi project name, which the framework stamps as `reventless:platform` on
56
54
  // every resource (`Plugin.res`: `platformName = getProjectName()`). Discovery
57
55
  // MUST scope on this as well as the stack: `reventless:environment` carries only
@@ -72,7 +70,7 @@ let projectName = (~projectDir: string): string => {
72
70
  trimmed
73
71
  ->String.slice(~start=String.length("name:"), ~end=String.length(trimmed))
74
72
  ->String.trim
75
- ->String.replaceRegExp(%re("/^[\"']|[\"']$/g"), "")
73
+ ->String.replaceRegExp(/^[\"']|[\"']$/g, "")
76
74
  | None => throw(Seed.Failed(`could not find a \`name:\` field in ${path}.`))
77
75
  }
78
76
  }
@@ -106,7 +104,7 @@ let chunk = (arr: array<'a>, size: int): array<array<'a>> => {
106
104
 
107
105
  // Fail-closed name allowlist. A denylist ("everything except prod") fails open
108
106
  // the day a new prod-like stack is added and forgotten; this fails closed.
109
- let nameAllowlist = %re("/^(alpha|dev|pr-.+)$/")
107
+ let nameAllowlist = /^(alpha|dev|pr-.+)$/
110
108
 
111
109
  // The stack's fully-resolved Pulumi config, read once. Reading it whole (rather
112
110
  // than `pulumi config get <key>`, which exits non-zero for BOTH a missing key
@@ -114,21 +112,19 @@ let nameAllowlist = %re("/^(alpha|dev|pr-.+)$/")
114
112
  // unreadable config is a pulumi/backend problem; an absent flag is a missing
115
113
  // opt-in. `pulumi config --json` maps each key to `{value, secret}`.
116
114
  let readStackConfig = (~projectDir, ~backend, ~stack): result<dict<JSON.t>, string> =>
117
- switch (
118
- try Some(ReventlessSeedAws.pulumi(~projectDir, ~backend, ["config", "--json", "--stack", stack])) catch {
119
- | _ => None
120
- }
121
- ) {
115
+ switch try Some(
116
+ ReventlessSeedAws.pulumi(~projectDir, ~backend, ["config", "--json", "--stack", stack]),
117
+ ) catch {
118
+ | _ => None
119
+ } {
122
120
  | None =>
123
121
  Error(
124
122
  `could not read the Pulumi config for stack "${stack}" — is pulumi logged in to the right backend, and is the stack deployed?`,
125
123
  )
126
124
  | Some(raw) =>
127
- switch (
128
- try Some(JSON.parseOrThrow(raw)) catch {
129
- | _ => None
130
- }
131
- ) {
125
+ switch try Some(JSON.parseOrThrow(raw)) catch {
126
+ | _ => None
127
+ } {
132
128
  | Some(Object(obj)) => Ok(obj)
133
129
  | _ => Error(`could not parse \`pulumi config --json\` output for stack "${stack}".`)
134
130
  }
@@ -304,8 +300,7 @@ let parseObjectStores = (json: option<JSON.t>): result<array<objectStore>, strin
304
300
  Ok(Array.concat(stores, [{qualified, plugin, store, bucketName, keyPrefix}]))
305
301
  | _ =>
306
302
  Error(
307
- `the platform stack's \`objectStores\` output has a malformed entry for "${qualified}" — ` ++
308
- `expected a {plugin}.{store} key carrying bucketName and keyPrefix.`,
303
+ `the platform stack's \`objectStores\` output has a malformed entry for "${qualified}" — ` ++ `expected a {plugin}.{store} key carrying bucketName and keyPrefix.`,
309
304
  )
310
305
  }
311
306
  }
@@ -329,8 +324,7 @@ let validateStores = (stores: array<objectStore>): result<unit, string> =>
329
324
  switch stores->Array.find(s => s.keyPrefix == "" || s.store->String.includes("/")) {
330
325
  | Some(s) =>
331
326
  Error(
332
- `store "${s.qualified}" has an unusable key prefix ("${s.keyPrefix}") — ` ++
333
- `a store name may not be empty or contain "/".`,
327
+ `store "${s.qualified}" has an unusable key prefix ("${s.keyPrefix}") — ` ++ `a store name may not be empty or contain "/".`,
334
328
  )
335
329
  | None =>
336
330
  switch stores->Array.findMap(a =>
@@ -341,8 +335,7 @@ let validateStores = (stores: array<objectStore>): result<unit, string> =>
341
335
  Some(
342
336
  `stores "${a.qualified}" and "${b.qualified}" both live at ` ++
343
337
  `${a.bucketName}/${a.keyPrefix}/ — a prefix-scoped wipe cannot tell their objects ` ++
344
- `apart. Rename one store, or qualify the \`@storageRef\` annotation if they were ` ++
345
- `meant to be one shared store.`,
338
+ `apart. Rename one store, or qualify the \`@storageRef\` annotation if they were ` ++ `meant to be one shared store.`,
346
339
  )
347
340
  } else if b.keyPrefix->String.startsWith(a.keyPrefix ++ "/") {
348
341
  Some(
@@ -386,8 +379,8 @@ let countBucket = async (bucket: string, ~prefix: option<string>=?): int => {
386
379
  let out = await S3.ListObjectVersionsCommand.send(
387
380
  S3.ListObjectVersionsCommand.make({
388
381
  bucket,
389
- prefix: ?prefix,
390
- keyMarker: ?keyMarker,
382
+ ?prefix,
383
+ ?keyMarker,
391
384
  versionIdMarker: ?versionMarker,
392
385
  }),
393
386
  )
@@ -415,7 +408,13 @@ let rec sendBatch = async (
415
408
  ): unit =>
416
409
  if requests->Array.length > 0 {
417
410
  if attempt > 8 {
418
- throw(Seed.Failed(`table ${table}: ${(requests->Array.length)->Int.toString} item(s) still unprocessed after 8 retries.`))
411
+ throw(
412
+ Seed.Failed(
413
+ `table ${table}: ${requests
414
+ ->Array.length
415
+ ->Int.toString} item(s) still unprocessed after 8 retries.`,
416
+ ),
417
+ )
419
418
  }
420
419
  let out = await Ddb.DocumentClient.BatchWriteCommand.send(
421
420
  Ddb.DocumentClient.BatchWriteCommand.make({
@@ -456,7 +455,9 @@ let truncateTable = async (table: string): unit => {
456
455
  ->Option.getOr([])
457
456
  ->Array.map(item => {
458
457
  let key =
459
- keyAttrs->Array.filterMap(attr => item->field(attr)->Option.map(v => (attr, v)))->Dict.fromArray
458
+ keyAttrs
459
+ ->Array.filterMap(attr => item->field(attr)->Option.map(v => (attr, v)))
460
+ ->Dict.fromArray
460
461
  ({deleteRequest: {key: key}}: Ddb.DocumentClient.BatchWriteCommand.writeRequest)
461
462
  })
462
463
  let batches = chunk(requests, Ddb.DocumentClient.BatchWriteCommand.maxBatchSize)
@@ -481,15 +482,15 @@ let emptyBucket = async (bucket: string, ~prefix: option<string>=?): unit => {
481
482
  let out = await S3.ListObjectVersionsCommand.send(
482
483
  S3.ListObjectVersionsCommand.make({
483
484
  bucket,
484
- prefix: ?prefix,
485
- keyMarker: ?keyMarker,
485
+ ?prefix,
486
+ ?keyMarker,
486
487
  versionIdMarker: ?versionMarker,
487
488
  }),
488
489
  )
489
490
  let ids =
490
- Array.concat(out.versions->Option.getOr([]), out.deleteMarkers->Option.getOr([]))->Array.map(v => (
491
- {key: v.key, versionId: v.versionId}: S3.DeleteObjectsCommand.objectIdentifier
492
- ))
491
+ Array.concat(out.versions->Option.getOr([]), out.deleteMarkers->Option.getOr([]))->Array.map((
492
+ v
493
+ ): S3.DeleteObjectsCommand.objectIdentifier => {key: v.key, versionId: v.versionId})
493
494
  if ids->Array.length > 0 {
494
495
  let res = await S3.DeleteObjectsCommand.send(
495
496
  S3.DeleteObjectsCommand.make({
@@ -501,7 +502,7 @@ let emptyBucket = async (bucket: string, ~prefix: option<string>=?): unit => {
501
502
  | Some(errs) if errs->Array.length > 0 =>
502
503
  throw(
503
504
  Seed.Failed(
504
- `failed to delete ${(errs->Array.length)->Int.toString} object(s) from ${bucket}: ${errs
505
+ `failed to delete ${errs->Array.length->Int.toString} object(s) from ${bucket}: ${errs
505
506
  ->Array.get(0)
506
507
  ->Option.flatMap(e => e.message)
507
508
  ->Option.getOr("unknown")}`,
@@ -583,6 +584,7 @@ let chooseScope = async (~targets: array<target>): array<target> => {
583
584
  let options = []
584
585
  if domain->Array.length > 0 {
585
586
  options->Array.push((`domain — ${labelsOf(domain)}`, domain))
587
+
586
588
  // Single-plugin entries only when there is more than one, else they just
587
589
  // duplicate the `domain` entry.
588
590
  if domain->Array.length > 1 {
@@ -610,7 +612,9 @@ let gateTarget = (~target: target, ~backend, ~stack): string => {
610
612
  | Ok(c) => c
611
613
  | Error(message) => throw(Seed.Failed(message))
612
614
  }
613
- switch configValue(cfg, "reventless:wipeable")->Option.map(v => v->String.trim->String.toLowerCase) {
615
+ switch configValue(cfg, "reventless:wipeable")->Option.map(v =>
616
+ v->String.trim->String.toLowerCase
617
+ ) {
614
618
  | Some("true") => ()
615
619
  | _ =>
616
620
  throw(
@@ -666,13 +670,11 @@ let refillMessage = (~refilled: array<(string, int)>, ~heldCount: int): string =
666
670
  ->Array.join("")
667
671
  let diagnosis = if heldCount > 0 {
668
672
  `\n\n The wipe deleted these and something wrote them back. All ${heldCount->Int.toString} runtime(s) ` ++
669
- `in scope were held at zero concurrency for the whole wipe, so the writer was an invocation already ` ++
670
- `in flight when the hold took effect. Re-running clears it.`
673
+ `in scope were held at zero concurrency for the whole wipe, so the writer was an invocation already ` ++ `in flight when the hold took effect. Re-running clears it.`
671
674
  } else {
672
675
  `\n\n The wipe deleted these and a running runtime wrote them back. The stack was NOT held ` ++
673
676
  `(SEED_RESET_NO_QUIESCE), so nothing stopped it, and re-running will not help: the writer keeps the ` ++
674
- `state in memory and restores it on every invocation, identical each time. Re-run without ` ++
675
- `SEED_RESET_NO_QUIESCE so the runtimes are held and recycled.`
677
+ `state in memory and restores it on every invocation, identical each time. Re-run without ` ++ `SEED_RESET_NO_QUIESCE so the runtimes are held and recycled.`
676
678
  }
677
679
  `${total->Int.toString} item(s)/object(s) came back after the wipe:${lines}${diagnosis}`
678
680
  }
@@ -700,6 +702,7 @@ let reportAll = (resolvedList: array<resolved>, ~stack, ~region): int => {
700
702
  if r.bucketCounts->Array.length == 0 {
701
703
  Console.log(" (none)")
702
704
  }
705
+
703
706
  // Declared stores get their own section rather than being folded in with the
704
707
  // plain buckets: the unit is a prefix inside a bucket that other plugins also
705
708
  // write to, and the operator needs to see which is which before confirming.
@@ -770,8 +773,7 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
770
773
  // this resolution exists to fix.
771
774
  Console.log("")
772
775
  Console.log(
773
- "Note: no `platform` target is declared, so declared object stores could not be " ++
774
- "resolved — any uploaded objects will be left in place.",
776
+ "Note: no `platform` target is declared, so declared object stores could not be " ++ "resolved — any uploaded objects will be left in place.",
775
777
  )
776
778
  []
777
779
  | Some(pt) =>
@@ -910,8 +912,7 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
910
912
  let interactive = NodeProcess.stdin->NodeProcess.isTTY->Option.getOr(false)
911
913
  let confirmed = if interactive {
912
914
  let typed = await Seed.Prompt.ask(
913
- `About to permanently empty ${total->Int.toString} item(s)/object(s) across the selected scope of "${stack}". ` ++
914
- `Type the stack name to confirm, or press Enter to keep this a dry run: `,
915
+ `About to permanently empty ${total->Int.toString} item(s)/object(s) across the selected scope of "${stack}". ` ++ `Type the stack name to confirm, or press Enter to keep this a dry run: `,
915
916
  )
916
917
  typed->String.trim == stack
917
918
  } else {
@@ -932,9 +933,10 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
932
933
  // durable while the runtimes that own the data are running — see
933
934
  // ReventlessSeedAws_Quiesce for the mechanism and for why emptying the
934
935
  // upstream stores first does not substitute for this.
935
- let functionNames = resolvedList->Array.reduce([], (acc, r) =>
936
- Array.concat(acc, r.functions->Array.filter(f => !(acc->Array.includes(f))))
937
- )
936
+ let functionNames =
937
+ resolvedList->Array.reduce([], (acc, r) =>
938
+ Array.concat(acc, r.functions->Array.filter(f => !(acc->Array.includes(f))))
939
+ )
938
940
  let quiesce = !noQuiesce() && functionNames->Array.length > 0
939
941
  let lambdaClient = Lambda.client(~region, ())
940
942
  let held = if quiesce {
@@ -946,8 +948,7 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
946
948
  if functionNames->Array.length > 0 {
947
949
  Console.log("")
948
950
  Console.log(
949
- "Note: SEED_RESET_NO_QUIESCE is set — the stack's runtimes keep running through the wipe. " ++
950
- "A runtime that holds state across invocations can write it straight back over an emptied store.",
951
+ "Note: SEED_RESET_NO_QUIESCE is set — the stack's runtimes keep running through the wipe. " ++ "A runtime that holds state across invocations can write it straight back over an emptied store.",
951
952
  )
952
953
  }
953
954
  []
@@ -1078,7 +1079,9 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
1078
1079
  }
1079
1080
 
1080
1081
  Console.log("")
1081
- Console.log(`Reset complete — the selected scope of "${stack}" reads empty and is re-seedable.`)
1082
+ Console.log(
1083
+ `Reset complete — the selected scope of "${stack}" reads empty and is re-seedable.`,
1084
+ )
1082
1085
  NodeProcess.exit(0)
1083
1086
  } catch {
1084
1087
  | Seed.Failed(message) =>
@@ -1090,7 +1093,9 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
1090
1093
  Seed.Prompt.close()
1091
1094
  Console.error("")
1092
1095
  Console.error("Reset aborted with an unexpected error:")
1093
- Console.error(exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown"))
1096
+ Console.error(
1097
+ exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown"),
1098
+ )
1094
1099
  NodeProcess.exit(1)
1095
1100
  }
1096
1101
  }
@@ -45,7 +45,9 @@ describe("ReventlessSeedAws_Reconcile.collectRefs", () => {
45
45
  )
46
46
 
47
47
  testSync("finds a ref nested inside a record", () =>
48
- expect(collect(obj([("variant", obj([("photo", JSON.Encode.string(refIn))]))])))->toEqual([refIn])
48
+ expect(collect(obj([("variant", obj([("photo", JSON.Encode.string(refIn))]))])))->toEqual([
49
+ refIn,
50
+ ])
49
51
  )
50
52
 
51
53
  // Store isolation: one bucket holds several stores under distinct prefixes, so
@@ -75,10 +77,7 @@ describe("ReventlessSeedAws_Reconcile.collectRefs", () => {
75
77
  )
76
78
 
77
79
  testSync("collects each distinct ref once", () => {
78
- let json = obj([
79
- ("a", JSON.Encode.string(refIn)),
80
- ("b", JSON.Encode.string(refIn)),
81
- ])
80
+ let json = obj([("a", JSON.Encode.string(refIn)), ("b", JSON.Encode.string(refIn))])
82
81
  expect(collect(json)->Array.length)->toBe(1)
83
82
  })
84
83
  })
@@ -28,7 +28,9 @@ let store = (~qualified, ~bucket, ~prefix): Reset.objectStore => {
28
28
  describe("parseObjectStores", () => {
29
29
  testSync("splits the qualified key into plugin and store", () =>
30
30
  switch Reset.parseObjectStores(
31
- output([("Catalog.productImages", storeEntry(~bucket="alpha-stores", ~prefix="productImages"))]),
31
+ output([
32
+ ("Catalog.productImages", storeEntry(~bucket="alpha-stores", ~prefix="productImages")),
33
+ ]),
32
34
  ) {
33
35
  | Ok([s]) =>
34
36
  expect((s.plugin, s.store, s.bucketName, s.keyPrefix))->toEqual((
@@ -61,7 +63,9 @@ describe("parseObjectStores", () => {
61
63
  // A store the reset cannot read is a store it would silently leave behind, so
62
64
  // a malformed entry fails the run rather than being skipped.
63
65
  testSync("a malformed entry is an error, not a skip", () =>
64
- switch Reset.parseObjectStores(output([("Catalog.productImages", obj([("keyPrefix", str("x"))]))])) {
66
+ switch Reset.parseObjectStores(
67
+ output([("Catalog.productImages", obj([("keyPrefix", str("x"))]))]),
68
+ ) {
65
69
  | Ok(_) => fail("expected a malformed entry to be refused")
66
70
  | Error(message) => expect(message->String.includes("Catalog.productImages"))->toBe(true)
67
71
  }
@@ -108,7 +112,11 @@ describe("validateStores", () => {
108
112
  testSync("an enclosing prefix is refused", () =>
109
113
  switch Reset.validateStores([
110
114
  store(~qualified="Legacy.catalog", ~bucket="alpha-stores", ~prefix="Catalog"),
111
- store(~qualified="Catalog.productImages", ~bucket="alpha-stores", ~prefix="Catalog/productImages"),
115
+ store(
116
+ ~qualified="Catalog.productImages",
117
+ ~bucket="alpha-stores",
118
+ ~prefix="Catalog/productImages",
119
+ ),
112
120
  ]) {
113
121
  | Ok() => fail("expected an enclosing prefix to be refused")
114
122
  | Error(message) => expect(message->String.includes("encloses"))->toBe(true)
@@ -120,8 +128,16 @@ describe("validateStores", () => {
120
128
  testSync("one prefix in two different buckets is fine", () =>
121
129
  expect(
122
130
  Reset.validateStores([
123
- store(~qualified="Catalog.productImages", ~bucket="catalog-productImages", ~prefix="productImages"),
124
- store(~qualified="Ordering.productImages", ~bucket="ordering-productImages", ~prefix="productImages"),
131
+ store(
132
+ ~qualified="Catalog.productImages",
133
+ ~bucket="catalog-productImages",
134
+ ~prefix="productImages",
135
+ ),
136
+ store(
137
+ ~qualified="Ordering.productImages",
138
+ ~bucket="ordering-productImages",
139
+ ~prefix="productImages",
140
+ ),
125
141
  ]),
126
142
  )->toEqual(Ok())
127
143
  )
@@ -160,7 +176,12 @@ describe("pluginOf", () => {
160
176
  // never has to guess the relation between the two.
161
177
  testSync("prefers the declared plugin name over the label", () =>
162
178
  expect(
163
- Reset.pluginOf({projectDir: "../catalog-aws", label: "catalog", group: Domain, plugin: "Catalog"}),
179
+ Reset.pluginOf({
180
+ projectDir: "../catalog-aws",
181
+ label: "catalog",
182
+ group: Domain,
183
+ plugin: "Catalog",
184
+ }),
164
185
  )->toBe("Catalog")
165
186
  )
166
187
 
Binary file
Binary file
Binary file
@@ -20,29 +20,33 @@ module Quiesce = ReventlessSeedAws_Quiesce
20
20
 
21
21
  describe("classify", () => {
22
22
  testSync("reads the function name out of a Lambda ARN", () =>
23
- expect(Reset.classify("arn:aws:lambda:eu-west-1:123456789012:function:AllAutomationSlices-28b116c"))
24
- ->toEqual(Reset.Function("AllAutomationSlices-28b116c"))
23
+ expect(
24
+ Reset.classify("arn:aws:lambda:eu-west-1:123456789012:function:AllAutomationSlices-28b116c"),
25
+ )->toEqual(Reset.Function("AllAutomationSlices-28b116c"))
25
26
  )
26
27
 
27
28
  // The tagging API returns unqualified ARNs, but a qualified one must still
28
29
  // yield the function name — every Lambda control-plane call the hold makes
29
30
  // takes a name, and "Name:1" is not one.
30
31
  testSync("drops a version or alias qualifier", () =>
31
- expect(Reset.classify("arn:aws:lambda:eu-west-1:123456789012:function:AllReadModels-0287438:42"))
32
- ->toEqual(Reset.Function("AllReadModels-0287438"))
32
+ expect(
33
+ Reset.classify("arn:aws:lambda:eu-west-1:123456789012:function:AllReadModels-0287438:42"),
34
+ )->toEqual(Reset.Function("AllReadModels-0287438"))
33
35
  )
34
36
 
35
37
  testSync("still classifies tables and buckets", () => {
36
- expect(Reset.classify("arn:aws:dynamodb:eu-west-1:123456789012:table/Products-0ba6849"))
37
- ->toEqual(Reset.Table("Products-0ba6849"))
38
+ expect(
39
+ Reset.classify("arn:aws:dynamodb:eu-west-1:123456789012:table/Products-0ba6849"),
40
+ )->toEqual(Reset.Table("Products-0ba6849"))
38
41
  expect(Reset.classify("arn:aws:s3:::alpha-product-images"))->toEqual(
39
42
  Reset.Bucket("alpha-product-images"),
40
43
  )
41
44
  })
42
45
 
43
46
  testSync("ignores anything else the tag scope returns", () =>
44
- expect(Reset.classify("arn:aws:sqs:eu-west-1:123456789012:OrderingDcbCmdTopic-4b3658c"))
45
- ->toEqual(Reset.Other)
47
+ expect(
48
+ Reset.classify("arn:aws:sqs:eu-west-1:123456789012:OrderingDcbCmdTopic-4b3658c"),
49
+ )->toEqual(Reset.Other)
46
50
  )
47
51
  })
48
52
 
@@ -52,22 +56,28 @@ describe("mapBounded", () => {
52
56
  // shuffled array would restore the wrong concurrency to the wrong function.
53
57
  test("keeps results in input order under out-of-order completion", async () => {
54
58
  let delays = [40, 0, 30, 10, 20, 5]
55
- let out = await delays->Quiesce.mapBounded(~limit=3, async ms => {
56
- await ReventlessSeed.Seed.Client.sleep(ms)
57
- ms
58
- })
59
+ let out = await delays->Quiesce.mapBounded(
60
+ ~limit=3,
61
+ async ms => {
62
+ await ReventlessSeed.Seed.Client.sleep(ms)
63
+ ms
64
+ },
65
+ )
59
66
  expect(out)->toEqual(delays)
60
67
  })
61
68
 
62
69
  test("never runs more than the limit at once", async () => {
63
70
  let inFlight = ref(0)
64
71
  let peak = ref(0)
65
- let _ = await Array.make(~length=12, 0)->Quiesce.mapBounded(~limit=4, async _ => {
66
- inFlight := inFlight.contents + 1
67
- peak := Math.Int.max(peak.contents, inFlight.contents)
68
- await ReventlessSeed.Seed.Client.sleep(5)
69
- inFlight := inFlight.contents - 1
70
- })
72
+ let _ = await Array.make(~length=12, 0)->Quiesce.mapBounded(
73
+ ~limit=4,
74
+ async _ => {
75
+ inFlight := inFlight.contents + 1
76
+ peak := Math.Int.max(peak.contents, inFlight.contents)
77
+ await ReventlessSeed.Seed.Client.sleep(5)
78
+ inFlight := inFlight.contents - 1
79
+ },
80
+ )
71
81
  expect(peak.contents)->toBe(4)
72
82
  })
73
83
 
@@ -148,10 +158,12 @@ describe("noQuiesce", () => {
148
158
  })
149
159
 
150
160
  testSync("accepts the spellings an operator would actually type", () => {
151
- ["1", "true", "TRUE", " yes "]->Array.forEach(v => {
152
- set(v)
153
- expect(Reset.noQuiesce())->toBe(true)
154
- })
161
+ ["1", "true", "TRUE", " yes "]->Array.forEach(
162
+ v => {
163
+ set(v)
164
+ expect(Reset.noQuiesce())->toBe(true)
165
+ },
166
+ )
155
167
  set("")
156
168
  })
157
169
  })
@@ -1,2 +1,2 @@
1
- #Start(1788869322743)
2
- #Done(1788869322782)
1
+ #Start(1788992686668)
2
+ #Done(1788992686721)
@@ -45,7 +45,9 @@ describe("ReventlessSeedAws_Reconcile.collectRefs", () => {
45
45
  )
46
46
 
47
47
  testSync("finds a ref nested inside a record", () =>
48
- expect(collect(obj([("variant", obj([("photo", JSON.Encode.string(refIn))]))])))->toEqual([refIn])
48
+ expect(collect(obj([("variant", obj([("photo", JSON.Encode.string(refIn))]))])))->toEqual([
49
+ refIn,
50
+ ])
49
51
  )
50
52
 
51
53
  // Store isolation: one bucket holds several stores under distinct prefixes, so
@@ -75,10 +77,7 @@ describe("ReventlessSeedAws_Reconcile.collectRefs", () => {
75
77
  )
76
78
 
77
79
  testSync("collects each distinct ref once", () => {
78
- let json = obj([
79
- ("a", JSON.Encode.string(refIn)),
80
- ("b", JSON.Encode.string(refIn)),
81
- ])
80
+ let json = obj([("a", JSON.Encode.string(refIn)), ("b", JSON.Encode.string(refIn))])
82
81
  expect(collect(json)->Array.length)->toBe(1)
83
82
  })
84
83
  })
Binary file
Binary file
Binary file
@@ -28,7 +28,9 @@ let store = (~qualified, ~bucket, ~prefix): Reset.objectStore => {
28
28
  describe("parseObjectStores", () => {
29
29
  testSync("splits the qualified key into plugin and store", () =>
30
30
  switch Reset.parseObjectStores(
31
- output([("Catalog.productImages", storeEntry(~bucket="alpha-stores", ~prefix="productImages"))]),
31
+ output([
32
+ ("Catalog.productImages", storeEntry(~bucket="alpha-stores", ~prefix="productImages")),
33
+ ]),
32
34
  ) {
33
35
  | Ok([s]) =>
34
36
  expect((s.plugin, s.store, s.bucketName, s.keyPrefix))->toEqual((
@@ -61,7 +63,9 @@ describe("parseObjectStores", () => {
61
63
  // A store the reset cannot read is a store it would silently leave behind, so
62
64
  // a malformed entry fails the run rather than being skipped.
63
65
  testSync("a malformed entry is an error, not a skip", () =>
64
- switch Reset.parseObjectStores(output([("Catalog.productImages", obj([("keyPrefix", str("x"))]))])) {
66
+ switch Reset.parseObjectStores(
67
+ output([("Catalog.productImages", obj([("keyPrefix", str("x"))]))]),
68
+ ) {
65
69
  | Ok(_) => fail("expected a malformed entry to be refused")
66
70
  | Error(message) => expect(message->String.includes("Catalog.productImages"))->toBe(true)
67
71
  }
@@ -108,7 +112,11 @@ describe("validateStores", () => {
108
112
  testSync("an enclosing prefix is refused", () =>
109
113
  switch Reset.validateStores([
110
114
  store(~qualified="Legacy.catalog", ~bucket="alpha-stores", ~prefix="Catalog"),
111
- store(~qualified="Catalog.productImages", ~bucket="alpha-stores", ~prefix="Catalog/productImages"),
115
+ store(
116
+ ~qualified="Catalog.productImages",
117
+ ~bucket="alpha-stores",
118
+ ~prefix="Catalog/productImages",
119
+ ),
112
120
  ]) {
113
121
  | Ok() => fail("expected an enclosing prefix to be refused")
114
122
  | Error(message) => expect(message->String.includes("encloses"))->toBe(true)
@@ -120,8 +128,16 @@ describe("validateStores", () => {
120
128
  testSync("one prefix in two different buckets is fine", () =>
121
129
  expect(
122
130
  Reset.validateStores([
123
- store(~qualified="Catalog.productImages", ~bucket="catalog-productImages", ~prefix="productImages"),
124
- store(~qualified="Ordering.productImages", ~bucket="ordering-productImages", ~prefix="productImages"),
131
+ store(
132
+ ~qualified="Catalog.productImages",
133
+ ~bucket="catalog-productImages",
134
+ ~prefix="productImages",
135
+ ),
136
+ store(
137
+ ~qualified="Ordering.productImages",
138
+ ~bucket="ordering-productImages",
139
+ ~prefix="productImages",
140
+ ),
125
141
  ]),
126
142
  )->toEqual(Ok())
127
143
  )
@@ -160,7 +176,12 @@ describe("pluginOf", () => {
160
176
  // never has to guess the relation between the two.
161
177
  testSync("prefers the declared plugin name over the label", () =>
162
178
  expect(
163
- Reset.pluginOf({projectDir: "../catalog-aws", label: "catalog", group: Domain, plugin: "Catalog"}),
179
+ Reset.pluginOf({
180
+ projectDir: "../catalog-aws",
181
+ label: "catalog",
182
+ group: Domain,
183
+ plugin: "Catalog",
184
+ }),
164
185
  )->toBe("Catalog")
165
186
  )
166
187
 
Binary file
Binary file
Binary file