@reventlessdev/reventless-aws 3.0.0-alpha.309 → 3.0.0-alpha.311
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/CHANGELOG.md +14 -0
- package/package.json +6 -6
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +12 -12
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +3 -1
- package/src/util/Util_DynamoDb.res +7 -2
- package/src/util/Util_DynamoDb.res.mjs +2 -2
- package/tests/EventCollectorPluginDefinitionAssetTest.res +104 -0
- package/tests/EventCollectorPluginDefinitionAssetTest.res.mjs +129 -0
- package/tests/Util_DynamoDbTest.res +44 -0
- package/tests/Util_DynamoDbTest.res.mjs +54 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.311 (2026-08-19)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** decode the plugin definition asset instead of casting it ([5ac8a75](https://github.com/ReventlessDev/reventless-core/commit/5ac8a758673da63479049ba17658c8f91c5d72c5))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.310 (2026-08-18)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **aws:** normalize the null a table without a sort key resolves to ([8fb15ac](https://github.com/ReventlessDev/reventless-core/commit/8fb15ac066679b6799f4287c2c8d332e2367f599))
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# 3.0.0-alpha.309 (2026-08-18)
|
|
7
21
|
|
|
8
22
|
* feat(core)!: tell the Monitoring seam where a unit's logs live ([6cbd595](https://github.com/ReventlessDev/reventless-core/commit/6cbd595e2474fce3f87b7720a332e82583899bae))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.311",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
"sury": "11.0.0-rc.1",
|
|
14
14
|
"uuid": "^13.0.0",
|
|
15
15
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.12",
|
|
16
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
16
|
"@reventlessdev/rescript-node": "2.0.0-alpha.8",
|
|
18
17
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
19
|
-
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.
|
|
18
|
+
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.1",
|
|
19
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
20
20
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
21
21
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
22
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.146",
|
|
23
|
-
"@reventlessdev/reventless-interop": "3.0.0-alpha.32",
|
|
24
22
|
"@reventlessdev/reventless-core": "3.0.0-alpha.240",
|
|
23
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.32",
|
|
25
24
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.118",
|
|
26
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.104"
|
|
25
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.104",
|
|
26
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.146"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"rescript": "12.3.0",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
// Plugin_Callback wiring, and the Lambda dispatch boundary.
|
|
13
13
|
//
|
|
14
14
|
// Runtime-pure: no `open PulumiAws` values — Pulumi appears in type positions
|
|
15
|
-
// only (erased). The
|
|
16
|
-
//
|
|
15
|
+
// only (erased). The one `%identity` cast below sits on a documented JSON
|
|
16
|
+
// contract (same pattern as AggregateEntryPoint_Ops.asConnectionConfig).
|
|
17
17
|
|
|
18
18
|
// ── Shim bindings (HandlerFactoryHelpers.mjs) ───────────────────────────────
|
|
19
19
|
// The structured-log + Effect dispatch boundary shared by every deployed entry
|
|
@@ -325,8 +325,8 @@ let projectPluginRow = (row: JSON.t): option<pluginProjection> =>
|
|
|
325
325
|
|
|
326
326
|
// A pluginDefinition viewed as a projection (the manage logic reads only the
|
|
327
327
|
// shared subset; `status` is never read for the acting plugin). Array/option
|
|
328
|
-
// fields
|
|
329
|
-
//
|
|
328
|
+
// fields stay js_nullable-normalized: a definition arriving over the wire, rather
|
|
329
|
+
// than from loadPluginDefinition's decode, still carries null for an absent field.
|
|
330
330
|
let projectionOfDefinition = (d: Reventless.Plugin.pluginDefinition): pluginProjection => {
|
|
331
331
|
id: d.id,
|
|
332
332
|
status: "",
|
|
@@ -671,16 +671,16 @@ let warnSkippedExtension = (ext: extensionConfig) =>
|
|
|
671
671
|
|
|
672
672
|
let assetPath = (fileName: string): string => NodePath.join([NodeProcess.cwd(), fileName])
|
|
673
673
|
|
|
674
|
-
// pluginDefinition.json is written
|
|
675
|
-
//
|
|
676
|
-
//
|
|
677
|
-
//
|
|
678
|
-
//
|
|
679
|
-
external asPluginDefinition: JSON.t => Reventless.Plugin.pluginDefinition = "%identity"
|
|
680
|
-
|
|
674
|
+
// pluginDefinition.json is written through the schema's encoder (Plugin_Helpers),
|
|
675
|
+
// so it holds the wire form, which is not the record's runtime shape: an @offload
|
|
676
|
+
// field is {"$offload": …} on the wire and Offloaded(…) in ReScript. Decode it
|
|
677
|
+
// back through the same schema — a cast leaves the wire form in a value typed as
|
|
678
|
+
// the record, and the next encode of it (the Connect handshake) throws.
|
|
681
679
|
let loadPluginDefinition = (): Reventless.Plugin.pluginDefinition =>
|
|
682
680
|
try {
|
|
683
|
-
assetPath("pluginDefinition.json")
|
|
681
|
+
assetPath("pluginDefinition.json")
|
|
682
|
+
->NodeFs.readFileSync
|
|
683
|
+
->Reventless.Util_Sury.fromJsonString(Reventless.Plugin.pluginDefinitionSchema)
|
|
684
684
|
} catch {
|
|
685
685
|
| exn =>
|
|
686
686
|
JsError.throwWithMessage(
|
|
@@ -8,8 +8,10 @@ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
|
8
8
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
9
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
10
10
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
11
|
+
import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
|
|
11
12
|
import * as SNS_Helpers$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/SNS_Helpers.res.mjs";
|
|
12
13
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
14
|
+
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
13
15
|
import * as HandlerFactoryHelpersMjs from "./HandlerFactoryHelpers.mjs";
|
|
14
16
|
import * as Plugin_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Callback.res.mjs";
|
|
15
17
|
import * as Util_PluginMessage_Runtime$ReventlessAws from "../../plugin/runtime/Util_PluginMessage_Runtime.res.mjs";
|
|
@@ -519,7 +521,7 @@ function assetPath(fileName) {
|
|
|
519
521
|
|
|
520
522
|
function loadPluginDefinition() {
|
|
521
523
|
try {
|
|
522
|
-
return
|
|
524
|
+
return Util_Sury$Reventless.fromJsonString(Nodefs.readFileSync(assetPath("pluginDefinition.json"), "utf8"), Plugin$Reventless.pluginDefinitionSchema);
|
|
523
525
|
} catch (raw_exn) {
|
|
524
526
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
525
527
|
return Stdlib_JsError.throwWithMessage("Failed to load pluginDefinition.json from asset zip: " + exnMessage(exn));
|
|
@@ -7,7 +7,12 @@ let log = ReventlessCore.Logger.fromEnv()
|
|
|
7
7
|
let toResourceInfo: table => Pulumi.Output.t<ReventlessInfra.Adapter.resourceInfo> = ({hashKey, rangeKey}) =>
|
|
8
8
|
(hashKey, rangeKey)
|
|
9
9
|
->Pulumi.Output.all2
|
|
10
|
-
->Pulumi.Output.apply(((hashKey, rangeKey)) => ReventlessInfra.Adapter.StorageKeys({
|
|
10
|
+
->Pulumi.Output.apply(((hashKey, rangeKey)) => ReventlessInfra.Adapter.StorageKeys({
|
|
11
|
+
partitionKey: hashKey,
|
|
12
|
+
// Normalize here: a null reaching the plugin-structure export fails sury's
|
|
13
|
+
// encode-side validation, which expects `option`'s undefined.
|
|
14
|
+
sortKey: rangeKey->Nullable.toOption,
|
|
15
|
+
}))
|
|
11
16
|
|
|
12
17
|
let toResolvedTableOutput = ({name, id, arn, hashKey, rangeKey}) =>
|
|
13
18
|
(name, id, arn, hashKey, rangeKey)
|
|
@@ -17,7 +22,7 @@ let toResolvedTableOutput = ({name, id, arn, hashKey, rangeKey}) =>
|
|
|
17
22
|
name,
|
|
18
23
|
arn,
|
|
19
24
|
hashKey,
|
|
20
|
-
rangeKey: ?(rangeKey->Option.map(
|
|
25
|
+
rangeKey: ?(rangeKey->Nullable.toOption->Option.map(Nullable.make)),
|
|
21
26
|
})
|
|
22
27
|
|
|
23
28
|
let toResource = (~tags=?, {id, name, arn} as table) =>
|
|
@@ -24,7 +24,7 @@ function toResourceInfo(param) {
|
|
|
24
24
|
]).apply(param => ({
|
|
25
25
|
TAG: "StorageKeys",
|
|
26
26
|
partitionKey: param[0],
|
|
27
|
-
sortKey: param[1]
|
|
27
|
+
sortKey: Primitive_option.fromNullable(param[1])
|
|
28
28
|
}));
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -40,7 +40,7 @@ function toResolvedTableOutput(param) {
|
|
|
40
40
|
name: param[0],
|
|
41
41
|
arn: param[2],
|
|
42
42
|
hashKey: param[3],
|
|
43
|
-
rangeKey: Stdlib_Option.map(param[4],
|
|
43
|
+
rangeKey: Stdlib_Option.map(Primitive_option.fromNullable(param[4]), prim => prim)
|
|
44
44
|
}));
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Guards the write/read symmetry of the `pluginDefinition.json` asset.
|
|
2
|
+
//
|
|
3
|
+
// The deploy side writes it with the schema's encoder (Plugin_Helpers); the
|
|
4
|
+
// runtime side (EventCollectorEntryPoint_Ops.loadPluginDefinition) reads it back
|
|
5
|
+
// and republishes the value inside the Connect handshake. Those two have to be
|
|
6
|
+
// the same schema in opposite directions.
|
|
7
|
+
//
|
|
8
|
+
// They stopped being that once a field carried @offload, whose wire form is
|
|
9
|
+
// deliberately untagged: {"$offload": …} on the wire, Offloaded(…) in ReScript.
|
|
10
|
+
// The read side cast the parsed JSON instead of decoding it, so the wire form
|
|
11
|
+
// survived inside a value typed as the record and the *next* encode — publishing
|
|
12
|
+
// ConnectPlugin — threw. Registration then froze at the previous version with the
|
|
13
|
+
// deploy reporting success, so this asserts the round trip rather than the read.
|
|
14
|
+
|
|
15
|
+
open JestGlobals
|
|
16
|
+
|
|
17
|
+
let offloadedFragment: Reventless.Plugin.pluginDefinition = {
|
|
18
|
+
id: "Catalog@1.0.0-alpha.215",
|
|
19
|
+
name: "Catalog",
|
|
20
|
+
version: "1.0.0-alpha.215",
|
|
21
|
+
extensionPoints: [],
|
|
22
|
+
extensions: [],
|
|
23
|
+
eventCollector: "arn:aws:sqs:eu-west-1:1:CatalogPluginEventColl",
|
|
24
|
+
extensionProtocols: [],
|
|
25
|
+
apiSchemaFragment: Some(
|
|
26
|
+
Offloaded({
|
|
27
|
+
store: "pluginApiFragments",
|
|
28
|
+
key: "sha256/a3db053a9ca51d63982811b0c1faa42cbb83ef6011ea140841375c7b1c9e9695",
|
|
29
|
+
hash: "a3db053a9ca51d63982811b0c1faa42cbb83ef6011ea140841375c7b1c9e9695",
|
|
30
|
+
bytes: 14594,
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
apiTarget: Some("Domain"),
|
|
34
|
+
structure: None,
|
|
35
|
+
dcbEventLog: None,
|
|
36
|
+
kind: Domain,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let inlineFragment: Reventless.Plugin.pluginDefinition = {
|
|
40
|
+
...offloadedFragment,
|
|
41
|
+
apiSchemaFragment: Some(Inline({encoded: "{\"types\":[]}", protocol: "1"})),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The asset as it lands in the code archive — the deploy side, verbatim.
|
|
45
|
+
let writeAsset = (def: Reventless.Plugin.pluginDefinition): string =>
|
|
46
|
+
def->Reventless.Util_Sury.toJsonString(Reventless.Plugin.pluginDefinitionSchema)
|
|
47
|
+
|
|
48
|
+
// The runtime side, through the real entry point: loadPluginDefinition resolves
|
|
49
|
+
// the asset against process.cwd(), so the file has to be laid down where it looks
|
|
50
|
+
// rather than the decode re-implemented here — re-implementing it is what would
|
|
51
|
+
// let a cast come back green.
|
|
52
|
+
let readAsset = (json: string): Reventless.Plugin.pluginDefinition => {
|
|
53
|
+
let dir = NodeFs.mkdtempSync(NodePath.join([NodeOs.tmpdir(), "plugindef-"]))
|
|
54
|
+
let previousCwd = NodeProcess.cwd()
|
|
55
|
+
NodeFs.writeFileSync(NodePath.join([dir, "pluginDefinition.json"]), json)
|
|
56
|
+
NodeProcess.chdir(dir)
|
|
57
|
+
let restore = () => {
|
|
58
|
+
NodeProcess.chdir(previousCwd)
|
|
59
|
+
NodeFs.rmSync(dir, {recursive: true, force: true})
|
|
60
|
+
}
|
|
61
|
+
switch EventCollectorEntryPoint_Ops.loadPluginDefinition() {
|
|
62
|
+
| def =>
|
|
63
|
+
restore()
|
|
64
|
+
def
|
|
65
|
+
| exception exn =>
|
|
66
|
+
restore()
|
|
67
|
+
throw(exn)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe("pluginDefinition.json write/read symmetry", () => {
|
|
72
|
+
testSync("an offloaded field reaches the runtime as Offloaded, not as its wire form", () => {
|
|
73
|
+
let def = offloadedFragment->writeAsset->readAsset
|
|
74
|
+
switch def.apiSchemaFragment {
|
|
75
|
+
| Some(Offloaded({store, bytes})) =>
|
|
76
|
+
expect(store)->toBe("pluginApiFragments")
|
|
77
|
+
expect(bytes)->toBe(14594)
|
|
78
|
+
| Some(Inline(_)) => fail("offloaded fragment decoded as Inline")
|
|
79
|
+
| None => fail("offloaded fragment decoded as None")
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
testSync("a definition read back from the asset can be published to the Connect handshake", () => {
|
|
84
|
+
// The encode the old cast broke. Both arms, because only Offloaded diverges
|
|
85
|
+
// between wire and runtime shape and an Inline-only test would stay green.
|
|
86
|
+
[offloadedFragment, inlineFragment]->Array.forEach(def => {
|
|
87
|
+
let published =
|
|
88
|
+
ReventlessInfra.PluginExtensionPointSpec.ConnectPlugin(def->writeAsset->readAsset)
|
|
89
|
+
->Reventless.Util_Sury.toJson(ReventlessInfra.PluginExtensionPointSpec.commandSchema)
|
|
90
|
+
expect(published->JSON.Decode.object->Option.isSome)->toBe(true)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
testSync("the asset round-trips byte-identically, so no stored message shifts", () => {
|
|
95
|
+
let once = offloadedFragment->writeAsset
|
|
96
|
+
expect(once->readAsset->writeAsset)->toBe(once)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
testSync("an absent optional reaches the runtime as None, not as null", () => {
|
|
100
|
+
let def = offloadedFragment->writeAsset->readAsset
|
|
101
|
+
expect(def.structure->Option.isNone)->toBe(true)
|
|
102
|
+
expect(def.dcbEventLog->Option.isNone)->toBe(true)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Nodefs from "node:fs";
|
|
4
|
+
import * as Nodeos from "node:os";
|
|
5
|
+
import * as Nodepath from "node:path";
|
|
6
|
+
import * as JestGlobals from "@reventlessdev/rescript-jest/src/JestGlobals.res.mjs";
|
|
7
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
8
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
|
+
import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
|
|
10
|
+
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
11
|
+
import * as PluginExtensionPointSpec$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/PluginExtensionPointSpec.res.mjs";
|
|
12
|
+
import * as EventCollectorEntryPoint_Ops$ReventlessAws from "../src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs";
|
|
13
|
+
|
|
14
|
+
let offloadedFragment = {
|
|
15
|
+
id: "Catalog@1.0.0-alpha.215",
|
|
16
|
+
name: "Catalog",
|
|
17
|
+
version: "1.0.0-alpha.215",
|
|
18
|
+
extensionPoints: [],
|
|
19
|
+
extensions: [],
|
|
20
|
+
eventCollector: "arn:aws:sqs:eu-west-1:1:CatalogPluginEventColl",
|
|
21
|
+
extensionProtocols: [],
|
|
22
|
+
apiSchemaFragment: {
|
|
23
|
+
TAG: "Offloaded",
|
|
24
|
+
_0: {
|
|
25
|
+
store: "pluginApiFragments",
|
|
26
|
+
key: "sha256/a3db053a9ca51d63982811b0c1faa42cbb83ef6011ea140841375c7b1c9e9695",
|
|
27
|
+
hash: "a3db053a9ca51d63982811b0c1faa42cbb83ef6011ea140841375c7b1c9e9695",
|
|
28
|
+
bytes: 14594
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
apiTarget: "Domain",
|
|
32
|
+
structure: undefined,
|
|
33
|
+
dcbEventLog: undefined,
|
|
34
|
+
kind: "Domain"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let inlineFragment = {
|
|
38
|
+
id: offloadedFragment.id,
|
|
39
|
+
name: offloadedFragment.name,
|
|
40
|
+
version: offloadedFragment.version,
|
|
41
|
+
extensionPoints: offloadedFragment.extensionPoints,
|
|
42
|
+
extensions: offloadedFragment.extensions,
|
|
43
|
+
eventCollector: offloadedFragment.eventCollector,
|
|
44
|
+
extensionProtocols: offloadedFragment.extensionProtocols,
|
|
45
|
+
apiSchemaFragment: {
|
|
46
|
+
TAG: "Inline",
|
|
47
|
+
_0: {
|
|
48
|
+
encoded: "{\"types\":[]}",
|
|
49
|
+
protocol: "1"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
apiTarget: offloadedFragment.apiTarget,
|
|
53
|
+
structure: offloadedFragment.structure,
|
|
54
|
+
dcbEventLog: offloadedFragment.dcbEventLog,
|
|
55
|
+
kind: offloadedFragment.kind
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function writeAsset(def) {
|
|
59
|
+
return Util_Sury$Reventless.toJsonString(def, Plugin$Reventless.pluginDefinitionSchema, undefined);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readAsset(json) {
|
|
63
|
+
let dir = Nodefs.mkdtempSync(Nodepath.join(Nodeos.tmpdir(), "plugindef-"));
|
|
64
|
+
let previousCwd = process.cwd();
|
|
65
|
+
Nodefs.writeFileSync(Nodepath.join(dir, "pluginDefinition.json"), json, "utf8");
|
|
66
|
+
process.chdir(dir);
|
|
67
|
+
let restore = () => {
|
|
68
|
+
process.chdir(previousCwd);
|
|
69
|
+
Nodefs.rmSync(dir, {
|
|
70
|
+
recursive: true,
|
|
71
|
+
force: true
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
let def;
|
|
75
|
+
try {
|
|
76
|
+
def = EventCollectorEntryPoint_Ops$ReventlessAws.loadPluginDefinition();
|
|
77
|
+
} catch (exn) {
|
|
78
|
+
restore();
|
|
79
|
+
throw exn;
|
|
80
|
+
}
|
|
81
|
+
restore();
|
|
82
|
+
return def;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
globalThis.describe("pluginDefinition.json write/read symmetry", () => {
|
|
86
|
+
globalThis.test("an offloaded field reaches the runtime as Offloaded, not as its wire form", () => {
|
|
87
|
+
let def = readAsset(Util_Sury$Reventless.toJsonString(offloadedFragment, Plugin$Reventless.pluginDefinitionSchema, undefined));
|
|
88
|
+
let match = def.apiSchemaFragment;
|
|
89
|
+
if (match === undefined) {
|
|
90
|
+
return JestGlobals.fail("offloaded fragment decoded as None");
|
|
91
|
+
}
|
|
92
|
+
if (match.TAG === "Inline") {
|
|
93
|
+
return JestGlobals.fail("offloaded fragment decoded as Inline");
|
|
94
|
+
}
|
|
95
|
+
let match$1 = match._0;
|
|
96
|
+
globalThis.expect(match$1.store).toBe("pluginApiFragments");
|
|
97
|
+
globalThis.expect(match$1.bytes).toBe(14594);
|
|
98
|
+
});
|
|
99
|
+
globalThis.test("a definition read back from the asset can be published to the Connect handshake", () => {
|
|
100
|
+
[
|
|
101
|
+
offloadedFragment,
|
|
102
|
+
inlineFragment
|
|
103
|
+
].forEach(def => {
|
|
104
|
+
let published = Util_Sury$Reventless.toJson({
|
|
105
|
+
TAG: "ConnectPlugin",
|
|
106
|
+
_0: readAsset(Util_Sury$Reventless.toJsonString(def, Plugin$Reventless.pluginDefinitionSchema, undefined))
|
|
107
|
+
}, PluginExtensionPointSpec$ReventlessInfra.commandSchema);
|
|
108
|
+
globalThis.expect(Stdlib_Option.isSome(Stdlib_JSON.Decode.object(published))).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
globalThis.test("the asset round-trips byte-identically, so no stored message shifts", () => {
|
|
112
|
+
let once = Util_Sury$Reventless.toJsonString(offloadedFragment, Plugin$Reventless.pluginDefinitionSchema, undefined);
|
|
113
|
+
let def = readAsset(once);
|
|
114
|
+
globalThis.expect(Util_Sury$Reventless.toJsonString(def, Plugin$Reventless.pluginDefinitionSchema, undefined)).toBe(once);
|
|
115
|
+
});
|
|
116
|
+
globalThis.test("an absent optional reaches the runtime as None, not as null", () => {
|
|
117
|
+
let def = readAsset(Util_Sury$Reventless.toJsonString(offloadedFragment, Plugin$Reventless.pluginDefinitionSchema, undefined));
|
|
118
|
+
globalThis.expect(Stdlib_Option.isNone(def.structure)).toBe(true);
|
|
119
|
+
globalThis.expect(Stdlib_Option.isNone(def.dcbEventLog)).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
offloadedFragment,
|
|
125
|
+
inlineFragment,
|
|
126
|
+
writeAsset,
|
|
127
|
+
readAsset,
|
|
128
|
+
}
|
|
129
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// Guards the null a table without a sort key resolves to.
|
|
4
|
+
//
|
|
5
|
+
// Pulumi resolves an absent `rangeKey` to `null`, and ReScript's `option` reads
|
|
6
|
+
// None as undefined — so the null travelled into `resourceInfo` unconverted and
|
|
7
|
+
// the plugin-structure export threw `Expected string | undefined, received null`
|
|
8
|
+
// out of sury's encode-side validation, failing the deploy after it had already
|
|
9
|
+
// applied part of the stack.
|
|
10
|
+
|
|
11
|
+
let resolve = (output: Pulumi.Output.t<'a>): promise<'a> =>
|
|
12
|
+
Promise.make((resolve, _) => {
|
|
13
|
+
let _ = output->Pulumi.Output.apply(value => resolve(value))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
let table = (~rangeKey): PulumiAws.DynamoDb.Table.t => {
|
|
17
|
+
arn: "arn:aws:dynamodb:eu-west-1:123456789012:table/UiFragments-abc123"->Pulumi.Output.make,
|
|
18
|
+
name: "UiFragments-abc123"->Pulumi.Output.make,
|
|
19
|
+
id: "UiFragments-abc123"->Pulumi.Output.make,
|
|
20
|
+
hashKey: "id"->Pulumi.Output.make,
|
|
21
|
+
rangeKey: rangeKey->Pulumi.Output.make,
|
|
22
|
+
streamEnabled: None->Pulumi.Output.make,
|
|
23
|
+
streamArn: ""->Pulumi.Output.make,
|
|
24
|
+
streamLabel: ""->Pulumi.Output.make,
|
|
25
|
+
ttl: {PulumiAws.DynamoDb.Table.attributeName: ""}->Pulumi.Output.make,
|
|
26
|
+
pointInTimeRecovery: {PulumiAws.DynamoDb.Table.enabled: true}->Pulumi.Output.make,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("Util_DynamoDb.toResourceInfo", () => {
|
|
30
|
+
test("reads a table without a sort key as None", async () => {
|
|
31
|
+
let resourceInfo = await table(~rangeKey=Nullable.null)->Util_DynamoDb.toResourceInfo->resolve
|
|
32
|
+
expect(resourceInfo)->toEqual(
|
|
33
|
+
ReventlessInfra.Adapter.StorageKeys({partitionKey: "id", sortKey: None}),
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("carries a sort key when the table has one", async () => {
|
|
38
|
+
let resourceInfo =
|
|
39
|
+
await table(~rangeKey=Nullable.make("seq"))->Util_DynamoDb.toResourceInfo->resolve
|
|
40
|
+
expect(resourceInfo)->toEqual(
|
|
41
|
+
ReventlessInfra.Adapter.StorageKeys({partitionKey: "id", sortKey: Some("seq")}),
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
4
|
+
import * as Util_DynamoDb$ReventlessAws from "../src/util/Util_DynamoDb.res.mjs";
|
|
5
|
+
|
|
6
|
+
function resolve(output) {
|
|
7
|
+
return new Promise((resolve, param) => {
|
|
8
|
+
output.apply(value => resolve(value));
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function table(rangeKey) {
|
|
13
|
+
return {
|
|
14
|
+
arn: Pulumi.output("arn:aws:dynamodb:eu-west-1:123456789012:table/UiFragments-abc123"),
|
|
15
|
+
name: Pulumi.output("UiFragments-abc123"),
|
|
16
|
+
id: Pulumi.output("UiFragments-abc123"),
|
|
17
|
+
hashKey: Pulumi.output("id"),
|
|
18
|
+
rangeKey: Pulumi.output(rangeKey),
|
|
19
|
+
streamEnabled: Pulumi.output(undefined),
|
|
20
|
+
streamArn: Pulumi.output(""),
|
|
21
|
+
streamLabel: Pulumi.output(""),
|
|
22
|
+
ttl: Pulumi.output({
|
|
23
|
+
attributeName: ""
|
|
24
|
+
}),
|
|
25
|
+
pointInTimeRecovery: Pulumi.output({
|
|
26
|
+
enabled: true
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
globalThis.describe("Util_DynamoDb.toResourceInfo", () => {
|
|
32
|
+
globalThis.test("reads a table without a sort key as None", async () => {
|
|
33
|
+
let resourceInfo = await resolve(Util_DynamoDb$ReventlessAws.toResourceInfo(table(null)));
|
|
34
|
+
globalThis.expect(resourceInfo).toEqual({
|
|
35
|
+
TAG: "StorageKeys",
|
|
36
|
+
partitionKey: "id",
|
|
37
|
+
sortKey: undefined
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
globalThis.test("carries a sort key when the table has one", async () => {
|
|
41
|
+
let resourceInfo = await resolve(Util_DynamoDb$ReventlessAws.toResourceInfo(table("seq")));
|
|
42
|
+
globalThis.expect(resourceInfo).toEqual({
|
|
43
|
+
TAG: "StorageKeys",
|
|
44
|
+
partitionKey: "id",
|
|
45
|
+
sortKey: "seq"
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
resolve,
|
|
52
|
+
table,
|
|
53
|
+
}
|
|
54
|
+
/* Not a pure module */
|