@reventlessdev/reventless-aws 3.0.0-alpha.177 → 3.0.0-alpha.178

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 (68) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/docker-compose.postgres.yml +21 -0
  3. package/package.json +6 -5
  4. package/scripts/run-pg-integration-tests.sh +58 -0
  5. package/src/Platform.res +37 -0
  6. package/src/Platform.res.mjs +41 -4
  7. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +7 -1
  8. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +10 -1
  9. package/src/adapter/Api/Platform_UIFragments_Lambda.res +7 -1
  10. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +10 -1
  11. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +6 -21
  12. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +2 -17
  13. package/src/adapter/DcbEventLog/DcbBackend.res +4 -0
  14. package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res +2 -2
  15. package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res.mjs +7 -1
  16. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -11
  17. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +2 -24
  18. package/src/adapter/Postgres/PgConnection.res +70 -10
  19. package/src/adapter/Postgres/PgConnection.res.mjs +48 -3
  20. package/src/adapter/Postgres/PgMigration_Builder.res +127 -0
  21. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +51 -0
  22. package/src/adapter/QueryDb/PgQueryResolver_Builder.res +11 -13
  23. package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +5 -24
  24. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +51 -3
  25. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +48 -18
  26. package/src/adapter/QueryDb/QueryDbBackend.res +8 -0
  27. package/src/adapter/QueryDb/QueryDbBackend.res.mjs +4 -1
  28. package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +1 -1
  29. package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +31 -7
  30. package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res.mjs +16 -10
  31. package/src/adapter/QueryDb/QueryDbStorage.res +6 -1
  32. package/src/adapter/QueryDb/QueryDbStorage.res.mjs +2 -1
  33. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +1 -1
  34. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +3 -14
  35. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +2 -25
  36. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +3 -14
  37. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +2 -25
  38. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +7 -1
  39. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +78 -15
  40. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +43 -26
  41. package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +40 -0
  42. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +17 -5
  43. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +10 -0
  44. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +3 -0
  45. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +9 -14
  46. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +2 -25
  47. package/src/adapter/Runtime/StateTopicPublish.mjs +204 -0
  48. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +17 -3
  49. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +86 -17
  50. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +50 -33
  51. package/src/adapter/StateTopic/StateTopic_AppSync.res +7 -1
  52. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +10 -1
  53. package/src/plugin/cloner/ClonerRunner_Fargate.res +7 -1
  54. package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +10 -1
  55. package/src/util/Util_Bundle.res +71 -1
  56. package/src/util/Util_Bundle.res.mjs +49 -1
  57. package/tests/PgChangeFeedRelay_IntegrationTest.res +3 -3
  58. package/tests/PgConnectionJsonTest.res +84 -0
  59. package/tests/PgConnectionJsonTest.res.mjs +68 -0
  60. package/tests/PgMigrationEntryPoint_IntegrationTest.res +82 -0
  61. package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +63 -0
  62. package/tests/PgPipeline_IntegrationTest.res +3 -1
  63. package/tests/PgQueryResolver_LambdaTest.res +59 -0
  64. package/tests/PgQueryResolver_LambdaTest.res.mjs +95 -16
  65. package/tests/StateTopicPublishTest.res +162 -0
  66. package/tests/StateTopicPublishTest.res.mjs +158 -0
  67. package/tests/Util_BundleEsmLoaderTest.res +57 -0
  68. package/tests/Util_BundleEsmLoaderTest.res.mjs +44 -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.178 (2026-07-06)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **reventless-aws:** make deployed ESM Lambdas self-contained via layer resolver hook ([3fc768c](https://github.com/ReventlessDev/reventless-core/commit/3fc768cba5ef72fca6e4a706eb855f67ac7f0b30))
11
+ ### Features
12
+
13
+ * **reventless-aws:** auth-table pipeline for group-restricted Postgres indexes (B3.2c) ([52be859](https://github.com/ReventlessDev/reventless-core/commit/52be8590ed934a2ffbc898cef381ca4ce50d8cda))
14
+ * **reventless-aws:** deploy-time Postgres schema provisioning via in-VPC migration Lambda (A3) ([44c8eee](https://github.com/ReventlessDev/reventless-core/commit/44c8eeebd351e883f2d2e21460108e2963a061ce))
15
+ * **reventless-aws:** expose DCB Postgres lock-strategy knob (C2) ([3991862](https://github.com/ReventlessDev/reventless-core/commit/3991862aa50eb939796413236a04deae80a61aa3))
16
+ * **reventless-aws:** Postgres read-model live updates via projection Lambda (B3.3) ([ff8fc43](https://github.com/ReventlessDev/reventless-core/commit/ff8fc43049a8fc7134e340a473c2a1665a139140))
17
+ * **reventless-aws:** provision the Postgres read path in plugin-stack mode (B3.2) ([719efbc](https://github.com/ReventlessDev/reventless-core/commit/719efbc20ab6e8bf5bb46499150fe07e54d9eca5))
18
+
19
+
6
20
  # 3.0.0-alpha.177 (2026-07-06)
7
21
 
8
22
  ### Features
@@ -0,0 +1,21 @@
1
+ # Postgres sidecar for the PG_URL-gated integration suites (change-feed relay,
2
+ # projection pipeline, schema-migration entry point).
3
+ # Booted/torn down by scripts/run-pg-integration-tests.sh.
4
+ #
5
+ # tmpfs keeps the data dir in RAM (no on-disk state to clean up); the suites
6
+ # ensureSchema + truncateAll themselves, and run serially, so one shared DB is
7
+ # fine.
8
+ services:
9
+ postgres:
10
+ image: postgres:16
11
+ environment:
12
+ POSTGRES_PASSWORD: postgres
13
+ POSTGRES_USER: postgres
14
+ POSTGRES_DB: postgres
15
+ command: "-c fsync=off -c full_page_writes=off"
16
+ tmpfs:
17
+ - /var/lib/postgresql/data
18
+ # Non-standard host port so the sidecar never collides with a dev's own
19
+ # Postgres on 5432 (the run script exports a matching PG_URL).
20
+ ports:
21
+ - "55433:5432"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.177",
3
+ "version": "3.0.0-alpha.178",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -9,15 +9,15 @@
9
9
  "sury": "11.0.0-alpha.4",
10
10
  "uuid": "^13.0.0",
11
11
  "@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.20",
12
- "@reventlessdev/rescript-jest": "1.0.0-alpha.6",
13
12
  "@reventlessdev/rescript-effect": "0.1.0-alpha.25",
14
- "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.45",
13
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.6",
15
14
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
15
+ "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.46",
16
16
  "@reventlessdev/rescript-uuid": "1.1.0-alpha.14",
17
17
  "@reventlessdev/reventless-core": "3.0.0-alpha.143",
18
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.24",
18
19
  "@reventlessdev/reventless-infra": "3.0.0-alpha.89",
19
20
  "@reventlessdev/reventless-postgres": "3.0.0-alpha.6",
20
- "@reventlessdev/reventless-interop": "3.0.0-alpha.24",
21
21
  "@reventlessdev/reventless-spec": "3.0.0-alpha.67"
22
22
  },
23
23
  "devDependencies": {
@@ -36,7 +36,8 @@
36
36
  ],
37
37
  "testPathIgnorePatterns": [
38
38
  "/node_modules/",
39
- "<rootDir>/tests/integration/"
39
+ "<rootDir>/tests/integration/",
40
+ "Pg.*IntegrationTest\\.res\\.mjs$"
40
41
  ],
41
42
  "moduleFileExtensions": [
42
43
  "js",
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env bash
2
+ # Boot a Postgres sidecar, run the PG_URL integration suites against it (serially,
3
+ # via jest.integration.pg.config.js), tear down. Safe to run anywhere: if Docker
4
+ # is unavailable the suites are skipped (the CI step is continue-on-error, and not
5
+ # every dev machine has Docker running).
6
+ #
7
+ # If PG_URL is already set (a dev's own Postgres), the sidecar is skipped and the
8
+ # suites run against that database instead.
9
+ set -euo pipefail
10
+
11
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
12
+ COMPOSE="$ROOT/reventless/reventless-aws/docker-compose.postgres.yml"
13
+
14
+ run_jest() {
15
+ cd "$ROOT"
16
+ NODE_OPTIONS='--experimental-vm-modules --no-warnings' \
17
+ npx jest --config jest.integration.pg.config.js "$@"
18
+ }
19
+
20
+ # Honour a pre-set PG_URL (dev's own database) — no sidecar needed.
21
+ if [ -n "${PG_URL:-}" ]; then
22
+ echo "▶ Using existing PG_URL — running PG integration suites"
23
+ run_jest "$@"
24
+ exit $?
25
+ fi
26
+
27
+ if ! command -v docker >/dev/null 2>&1; then
28
+ echo "⏭ Docker not available — skipping PG integration tests"
29
+ exit 0
30
+ fi
31
+ if ! docker info >/dev/null 2>&1; then
32
+ echo "⏭ Docker daemon not running — skipping PG integration tests"
33
+ exit 0
34
+ fi
35
+
36
+ cleanup() { docker compose -f "$COMPOSE" down -v >/dev/null 2>&1 || true; }
37
+ trap cleanup EXIT
38
+
39
+ echo "▶ Starting Postgres…"
40
+ docker compose -f "$COMPOSE" up -d
41
+
42
+ echo "⏳ Waiting for Postgres…"
43
+ for i in $(seq 1 30); do
44
+ if docker compose -f "$COMPOSE" exec -T postgres pg_isready -U postgres >/dev/null 2>&1; then
45
+ echo "✓ Postgres is up"
46
+ break
47
+ fi
48
+ if [ "$i" -eq 30 ]; then
49
+ echo "✗ Postgres did not start within 30s"
50
+ exit 1
51
+ fi
52
+ sleep 1
53
+ done
54
+
55
+ # Host port matches docker-compose.postgres.yml's mapping (55433 → container 5432),
56
+ # chosen to avoid colliding with a dev's own Postgres on 5432.
57
+ export PG_URL="postgres://postgres:postgres@localhost:55433/postgres"
58
+ run_jest "$@"
package/src/Platform.res CHANGED
@@ -91,6 +91,7 @@ module MakeWithConfig = (
91
91
  connectionConfig: pg.connectionConfig,
92
92
  securityGroupId: pg.securityGroupId,
93
93
  subnetIds: pg.subnetIds,
94
+ lockStrategy: pg.lockStrategy,
94
95
  })
95
96
  EventLogBackend.set({
96
97
  connectionConfig: pg.connectionConfig,
@@ -332,6 +333,25 @@ module MakeWithConfig = (
332
333
  Some(eventsApi)
333
334
  }
334
335
 
336
+ // B3.3: publish the events-API endpoint + ARN to the projection-Lambda runtime
337
+ // builders, so a subscription-enabled Postgres read model / view slice gets the
338
+ // APPSYNC_ENDPOINT env + appsync:EventPublish IAM at their `finish`. Set once
339
+ // here (before plugins build) in both monolithic and plugin-stack mode — the
340
+ // phantom events API carries apiArn + dns, so httpEndpoint resolves either way.
341
+ switch domainEventsApiOpt {
342
+ | Some(eventsApi) =>
343
+ let cfg = {
344
+ EventCollectorRuntime_Builder_Single.endpoint: AppSync_EventsApi.httpEndpoint(eventsApi),
345
+ apiArn: eventsApi.api.apiArn,
346
+ }
347
+ EventCollectorRuntime_Builder_Single.setEventsApiConfig(cfg)
348
+ StateViewSliceRuntime_Builder_Single.setEventsApiConfig({
349
+ StateViewSliceRuntime_Builder_Single.endpoint: AppSync_EventsApi.httpEndpoint(eventsApi),
350
+ apiArn: eventsApi.api.apiArn,
351
+ })
352
+ | None => ()
353
+ }
354
+
335
355
  // Returns the AppSync API/role to attach resolvers to for the current deploy target.
336
356
  // Hoisted above ApiConfig so the module can reference them as thunks.
337
357
  let resolveTargetApi = () =>
@@ -2046,6 +2066,23 @@ module MakeWithConfig = (
2046
2066
  // plugin's Postgres DCB log(s) + collector queue were registered during P.make()).
2047
2067
  provisionPgChangeFeedRelay()
2048
2068
 
2069
+ // B3.2b/c: provision this plugin stack's PgQueryResolver Lambda + AppSync data
2070
+ // source for its Postgres-backed read models (registered during P.make()). App
2071
+ // read model resolvers attach to the Domain API. Per-plugin Lambda + auto-named
2072
+ // data source, so plugin stacks don't collide. The node(id) resolver is skipped
2073
+ // in plugin-stack mode — it's a single shared Query.node field only one stack
2074
+ // may own (monolithic-only; see PgQueryResolver_Builder.provision).
2075
+ switch QueryDbBackend.get() {
2076
+ | Some(sel) =>
2077
+ PgQueryResolver_Builder.provision(
2078
+ ~api=domainApi,
2079
+ ~selection=sel,
2080
+ ~opts={},
2081
+ ~createNodeResolver=false,
2082
+ )
2083
+ | None => ()
2084
+ }
2085
+
2049
2086
  // Zero-downtime handover (Part 3.1): fire ONE synthetic heartbeat for the
2050
2087
  // just-deployed plugin so its new version runs the connect handshake within
2051
2088
  // seconds instead of waiting for the CloudWatch heartbeat rule's first
@@ -89,6 +89,7 @@ import * as EventTopicPublisher_DynamoDbStream$ReventlessAws from "./adapter/Eve
89
89
  import * as InboundTranslationResolvers_AppSync$ReventlessAws from "./adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res.mjs";
90
90
  import * as PluginExtensionPointRuntime_Builder$ReventlessAws from "./plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs";
91
91
  import * as ReadModel_Builder_NoResolver_Stream$ReventlessAws from "./components/ReadModel_Builder_NoResolver_Stream.res.mjs";
92
+ import * as EventCollectorRuntime_Builder_Single$ReventlessAws from "./adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs";
92
93
  import * as Platform_ComponentDefinitions_Lambda$ReventlessAws from "./adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs";
93
94
  import * as StateViewSliceRuntime_Builder_Single$ReventlessAws from "./adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs";
94
95
  import * as AggregateRuntime_Builder_Single_Async$ReventlessAws from "./adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs";
@@ -128,7 +129,8 @@ function MakeWithConfig(Config) {
128
129
  DcbBackend$ReventlessAws.set({
129
130
  connectionConfig: pg.connectionConfig,
130
131
  securityGroupId: pg.securityGroupId,
131
- subnetIds: pg.subnetIds
132
+ subnetIds: pg.subnetIds,
133
+ lockStrategy: pg.lockStrategy
132
134
  });
133
135
  EventLogBackend$ReventlessAws.set({
134
136
  connectionConfig: pg.connectionConfig,
@@ -274,6 +276,19 @@ function MakeWithConfig(Config) {
274
276
  let awsRegion = Stdlib_Option.getOr(new Pulumi.Config("aws").get("region"), "unknown");
275
277
  domainEventsApiOpt = AppSync_EventsApi$ReventlessAws.make("DomainEventsApi", cognitoPool.poolId, awsRegion, {});
276
278
  }
279
+ if (domainEventsApiOpt !== undefined) {
280
+ let cfg_endpoint = AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt);
281
+ let cfg_apiArn = domainEventsApiOpt.api.apiArn;
282
+ let cfg = {
283
+ endpoint: cfg_endpoint,
284
+ apiArn: cfg_apiArn
285
+ };
286
+ EventCollectorRuntime_Builder_Single$ReventlessAws.setEventsApiConfig(cfg);
287
+ StateViewSliceRuntime_Builder_Single$ReventlessAws.setEventsApiConfig({
288
+ endpoint: AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt),
289
+ apiArn: domainEventsApiOpt.api.apiArn
290
+ });
291
+ }
277
292
  let resolveTargetApi = () => {
278
293
  let match = currentDeployTarget.contents;
279
294
  if (match === "Domain") {
@@ -984,7 +999,7 @@ function MakeWithConfig(Config) {
984
999
  provisionPgChangeFeedRelay();
985
1000
  let sel = QueryDbBackend$ReventlessAws.get();
986
1001
  if (sel !== undefined) {
987
- PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {});
1002
+ PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {}, undefined);
988
1003
  }
989
1004
  if (Config.splitApi) {
990
1005
  Pulumi$Pulumi.$$export("platformApiId", Output$Pulumi.flatMap(platformApi, api => api.id));
@@ -1260,6 +1275,10 @@ function MakeWithConfig(Config) {
1260
1275
  let pluginOutputs = Component$ReventlessCore.outputs(pluginComponent);
1261
1276
  Plugin_Helpers$ReventlessCore.exportPluginOutputs(pluginOutputs);
1262
1277
  provisionPgChangeFeedRelay();
1278
+ let sel = QueryDbBackend$ReventlessAws.get();
1279
+ if (sel !== undefined) {
1280
+ PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {}, false);
1281
+ }
1263
1282
  let hbConfig = PluginRuntime_Builder$ReventlessAws.heartbeatConfigRef.contents;
1264
1283
  let match = IndexJs.isDryRun();
1265
1284
  let match$1 = hbConfig.epQueueUrl;
@@ -1342,7 +1361,8 @@ function Make($star) {
1342
1361
  DcbBackend$ReventlessAws.set({
1343
1362
  connectionConfig: pg.connectionConfig,
1344
1363
  securityGroupId: pg.securityGroupId,
1345
- subnetIds: pg.subnetIds
1364
+ subnetIds: pg.subnetIds,
1365
+ lockStrategy: pg.lockStrategy
1346
1366
  });
1347
1367
  EventLogBackend$ReventlessAws.set({
1348
1368
  connectionConfig: pg.connectionConfig,
@@ -1488,6 +1508,19 @@ function Make($star) {
1488
1508
  let awsRegion = Stdlib_Option.getOr(new Pulumi.Config("aws").get("region"), "unknown");
1489
1509
  domainEventsApiOpt = AppSync_EventsApi$ReventlessAws.make("DomainEventsApi", cognitoPool.poolId, awsRegion, {});
1490
1510
  }
1511
+ if (domainEventsApiOpt !== undefined) {
1512
+ let cfg_endpoint = AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt);
1513
+ let cfg_apiArn = domainEventsApiOpt.api.apiArn;
1514
+ let cfg = {
1515
+ endpoint: cfg_endpoint,
1516
+ apiArn: cfg_apiArn
1517
+ };
1518
+ EventCollectorRuntime_Builder_Single$ReventlessAws.setEventsApiConfig(cfg);
1519
+ StateViewSliceRuntime_Builder_Single$ReventlessAws.setEventsApiConfig({
1520
+ endpoint: AppSync_EventsApi$ReventlessAws.httpEndpoint(domainEventsApiOpt),
1521
+ apiArn: domainEventsApiOpt.api.apiArn
1522
+ });
1523
+ }
1491
1524
  let resolveTargetApi = () => {
1492
1525
  let match = currentDeployTarget.contents;
1493
1526
  if (match === "Domain") {
@@ -2192,7 +2225,7 @@ function Make($star) {
2192
2225
  provisionPgChangeFeedRelay();
2193
2226
  let sel = QueryDbBackend$ReventlessAws.get();
2194
2227
  if (sel !== undefined) {
2195
- PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {});
2228
+ PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {}, undefined);
2196
2229
  }
2197
2230
  Pulumi$Pulumi.$$export("platformApiId", Output$Pulumi.flatMap(platformApi, api => api.id));
2198
2231
  Pulumi$Pulumi.$$export("platformApiEndpoint", Output$Pulumi.flatMap(platformApi, api => api.uris.apply(uris => uris.GRAPHQL)));
@@ -2450,6 +2483,10 @@ function Make($star) {
2450
2483
  let pluginOutputs = Component$ReventlessCore.outputs(pluginComponent);
2451
2484
  Plugin_Helpers$ReventlessCore.exportPluginOutputs(pluginOutputs);
2452
2485
  provisionPgChangeFeedRelay();
2486
+ let sel = QueryDbBackend$ReventlessAws.get();
2487
+ if (sel !== undefined) {
2488
+ PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {}, false);
2489
+ }
2453
2490
  let hbConfig = PluginRuntime_Builder$ReventlessAws.heartbeatConfigRef.contents;
2454
2491
  let match = IndexJs.isDryRun();
2455
2492
  let match$1 = hbConfig.epQueueUrl;
@@ -147,8 +147,12 @@ let make = (
147
147
  "index.mjs",
148
148
  Pulumi.Asset.stringAsset(handlerCodeStub)->Pulumi.Archive.assetToAssetOrArchive,
149
149
  )
150
+ // ESM self-containment: the handler imports @aws-sdk/* bare specifiers the
151
+ // nodejs22.x runtime provides only under /var/runtime — unreachable from
152
+ // /var/task ESM without the resolver hook. Ship the loader + set its env vars.
153
+ let loaderHash = Util_Bundle.addEsmLoaderAssets(archiveContents)
150
154
  let code = Pulumi.Archive.assetArchive(archiveContents)
151
- let sourceCodeHash = Util_Bundle.hashString(handlerCodeStub)
155
+ let sourceCodeHash = Util_Bundle.hashString(handlerCodeStub ++ "\n---\n" ++ loaderHash)
152
156
 
153
157
  let layers =
154
158
  Lambda.reventlessLayerArn
@@ -173,6 +177,8 @@ let make = (
173
177
  Lambda.Function.variables: Dict.fromArray([
174
178
  ("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
175
179
  ("PLUGIN_RM_TABLE", pluginReadModelTableName->Pulumi.Output.asInput),
180
+ ("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
181
+ ("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
176
182
  ]),
177
183
  }: Lambda.Function.functionEnvironment
178
184
  )->Pulumi.Input.make,
@@ -119,8 +119,9 @@ function make(api, pluginReadModelTableName, opts) {
119
119
  let archiveContents = {};
120
120
  let handlerCodeStub = makeHandlerCode("", adminEntryJson);
121
121
  archiveContents["index.mjs"] = new (Pulumi.asset.StringAsset)(handlerCodeStub);
122
+ let loaderHash = Util_Bundle$ReventlessAws.addEsmLoaderAssets(archiveContents);
122
123
  let code = new (Pulumi.asset.AssetArchive)(archiveContents);
123
- let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(handlerCodeStub);
124
+ let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(handlerCodeStub + "\n---\n" + loaderHash);
124
125
  let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
125
126
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
126
127
  handler: "index.handler",
@@ -140,6 +141,14 @@ function make(api, pluginReadModelTableName, opts) {
140
141
  [
141
142
  "PLUGIN_RM_TABLE",
142
143
  pluginReadModelTableName
144
+ ],
145
+ [
146
+ "NODE_OPTIONS",
147
+ Util_Bundle$ReventlessAws.esmLoaderNodeOptions
148
+ ],
149
+ [
150
+ "ESM_FALLBACK_DIRS",
151
+ Util_Bundle$ReventlessAws.esmFallbackDirs
143
152
  ]
144
153
  ])
145
154
  },
@@ -138,8 +138,12 @@ let make = (
138
138
  "index.mjs",
139
139
  Pulumi.Asset.stringAsset(handlerCodeStub)->Pulumi.Archive.assetToAssetOrArchive,
140
140
  )
141
+ // ESM self-containment: the handler imports @aws-sdk/* bare specifiers the
142
+ // nodejs22.x runtime provides only under /var/runtime — unreachable from
143
+ // /var/task ESM without the resolver hook. Ship the loader + set its env vars.
144
+ let loaderHash = Util_Bundle.addEsmLoaderAssets(archiveContents)
141
145
  let code = Pulumi.Archive.assetArchive(archiveContents)
142
- let sourceCodeHash = Util_Bundle.hashString(handlerCodeStub)
146
+ let sourceCodeHash = Util_Bundle.hashString(handlerCodeStub ++ "\n---\n" ++ loaderHash)
143
147
 
144
148
  let layers =
145
149
  Lambda.reventlessLayerArn
@@ -164,6 +168,8 @@ let make = (
164
168
  Lambda.Function.variables: Dict.fromArray([
165
169
  ("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
166
170
  ("UI_FRAGMENT_RM_TABLE", uiFragmentRegistryTableName->Pulumi.Output.asInput),
171
+ ("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
172
+ ("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
167
173
  ]),
168
174
  }: Lambda.Function.functionEnvironment
169
175
  )->Pulumi.Input.make,
@@ -185,8 +185,9 @@ export async function handler() {
185
185
  }
186
186
  `;
187
187
  archiveContents["index.mjs"] = new (Pulumi.asset.StringAsset)(handlerCodeStub);
188
+ let loaderHash = Util_Bundle$ReventlessAws.addEsmLoaderAssets(archiveContents);
188
189
  let code = new (Pulumi.asset.AssetArchive)(archiveContents);
189
- let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(handlerCodeStub);
190
+ let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(handlerCodeStub + "\n---\n" + loaderHash);
190
191
  let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
191
192
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
192
193
  handler: "index.handler",
@@ -206,6 +207,14 @@ export async function handler() {
206
207
  [
207
208
  "UI_FRAGMENT_RM_TABLE",
208
209
  uiFragmentRegistryTableName
210
+ ],
211
+ [
212
+ "NODE_OPTIONS",
213
+ Util_Bundle$ReventlessAws.esmLoaderNodeOptions
214
+ ],
215
+ [
216
+ "ESM_FALLBACK_DIRS",
217
+ Util_Bundle$ReventlessAws.esmFallbackDirs
209
218
  ]
210
219
  ])
211
220
  },
@@ -47,27 +47,12 @@ let make: ReventlessCore.Counter_Adapter.handlerMaker = (
47
47
  packageDirs->Dict.set(specPkg, Util_Bundle.resolvePackageRoot(specPkg))
48
48
  packageDirs->Dict.set(mappingsPkg, Util_Bundle.resolvePackageRoot(mappingsPkg))
49
49
 
50
- let reExportCode = `export { handler } from "@reventlessdev/reventless-aws/src/adapter/Runtime/CounterEntryPoint.mjs";`
51
-
52
- let archiveContents: dict<Pulumi.Archive.assetOrArchive> = Dict.make()
53
- archiveContents->Dict.set(
54
- "index.mjs",
55
- Pulumi.Asset.stringAsset(reExportCode)->Pulumi.Archive.assetToAssetOrArchive,
56
- )
57
- let packageContentHashes: ref<array<string>> = ref([])
58
- packageDirs->Dict.forEachWithKey((pkgRoot, pkgName) => {
59
- let (archive, contentHash) = Util_Bundle.createFilteredPackageArchive(pkgRoot)
60
- archiveContents->Dict.set(
61
- `node_modules/${pkgName}`,
62
- archive->Pulumi.Archive.archiveToAssetOrArchive,
63
- )
64
- packageContentHashes.contents->Array.push(`${pkgName}:${contentHash}`)
65
- })
66
-
67
- let code = Pulumi.Archive.assetArchive(archiveContents)
68
- packageContentHashes.contents->Array.sort(String.compare)
69
- let sourceCodeHash = Util_Bundle.hashString(
70
- reExportCode ++ packageContentHashes.contents->Array.join(","),
50
+ // buildCodeArchive centralises the entry-point re-export, package bundling, and
51
+ // the ESM self-containment loader (register-hook.mjs / layer-resolver.mjs) that
52
+ // makeFromCodeAsset's NODE_OPTIONS=--import depends on.
53
+ let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
54
+ ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/CounterEntryPoint.mjs",
55
+ ~packageDirs,
71
56
  )
72
57
 
73
58
  let componentOpts: Pulumi.ComponentResource.options = {parent: ?opts.parent}
@@ -1,9 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
4
3
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
4
  import * as Pulumi from "@pulumi/pulumi";
6
- import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
7
5
  import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
8
6
  import * as Util_DynamoDbStream$ReventlessAws from "../../util/Util_DynamoDbStream.res.mjs";
9
7
  import * as Util_EventSourceMapping$ReventlessAws from "../../util/Util_EventSourceMapping.res.mjs";
@@ -34,25 +32,12 @@ function make(name, referencesName, referencesDb, countsName, countsDb, param, s
34
32
  let mappingsPkg = Util_Bundle$ReventlessAws.extractPackageName(mappingsModulePath);
35
33
  packageDirs[specPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(specPkg);
36
34
  packageDirs[mappingsPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(mappingsPkg);
37
- let reExportCode = `export { handler } from "@reventlessdev/reventless-aws/src/adapter/Runtime/CounterEntryPoint.mjs";`;
38
- let archiveContents = {};
39
- archiveContents["index.mjs"] = new (Pulumi.asset.StringAsset)(reExportCode);
40
- let packageContentHashes = {
41
- contents: []
42
- };
43
- Stdlib_Dict.forEachWithKey(packageDirs, (pkgRoot, pkgName) => {
44
- let match = Util_Bundle$ReventlessAws.createFilteredPackageArchive(pkgRoot);
45
- archiveContents[`node_modules/` + pkgName] = match[0];
46
- packageContentHashes.contents.push(pkgName + `:` + match[1]);
47
- });
48
- let code = new (Pulumi.asset.AssetArchive)(archiveContents);
49
- packageContentHashes.contents.sort(Primitive_string.compare);
50
- let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(reExportCode + packageContentHashes.contents.join(","));
35
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/CounterEntryPoint.mjs", packageDirs, undefined);
51
36
  let componentOpts_parent = opts.parent;
52
37
  let componentOpts = {
53
38
  parent: componentOpts_parent
54
39
  };
55
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, code, sourceCodeHash, envVars, 1024, 30, undefined, undefined, undefined, undefined, undefined, componentOpts);
40
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, 1024, 30, undefined, undefined, undefined, undefined, undefined, componentOpts);
56
41
  let lambda = runtime.parts.lambda;
57
42
  let subscribe = (sourceName, source) => Util_EventSourceMapping$ReventlessAws.subscribe(undefined, lambda, name, sourceName, source, opts);
58
43
  subscribe(referencesName, referencesStream);
@@ -19,6 +19,10 @@ type selection = {
19
19
  securityGroupId: Pulumi.Output.t<string>,
20
20
  /** Private subnets for the in-VPC DCB/relay Lambdas (PgConnection.subnetIds). */
21
21
  subnetIds: array<Pulumi.Input.t<string>>,
22
+ /** DCB append lock strategy (C2), carried from `PgConnection.lockStrategy` and
23
+ serialized into the DCB command Lambda's HANDLER_CONFIG. Default
24
+ `#AdvisoryLocks`; `#RowLocks` for RDS-Proxy-fronted deployments. */
25
+ lockStrategy: ReventlessPostgres.DcbEventLogStorage_Postgres.lockStrategy,
22
26
  }
23
27
 
24
28
  let selectionRef: ref<option<selection>> = ref(None)
@@ -26,9 +26,9 @@ let make: ReventlessCore.DcbEventLog_Adapter.storageMaker = (
26
26
  DcbBackend.registerRelayLog(~logName=name, ~partitionTag)
27
27
 
28
28
  let operations = switch DcbBackend.get() {
29
- | Some({connectionConfig}) =>
29
+ | Some({connectionConfig, lockStrategy}) =>
30
30
  connectionConfig->Pulumi.Output.apply(config =>
31
- DcbEventLogStorage_Postgres_Runtime.opsFor(config, ~logName=name)
31
+ DcbEventLogStorage_Postgres_Runtime.opsFor(config, ~logName=name, ~lockStrategy)
32
32
  )
33
33
  | None =>
34
34
  // Selectable only routes here when a selection is set; guard defensively.
@@ -8,7 +8,13 @@ function make(name, param, partitionTag, $staropt$star, param$1) {
8
8
  $staropt$star !== undefined;
9
9
  DcbBackend$ReventlessAws.registerRelayLog(name, partitionTag);
10
10
  let match = DcbBackend$ReventlessAws.get();
11
- let operations = match !== undefined ? match.connectionConfig.apply(config => DcbEventLogStorage_Postgres_Runtime$ReventlessAws.opsFor(config, name, undefined)) : Stdlib_JsError.throwWithMessage("DcbEventLogStorage_Postgres.make called without a DcbBackend selection");
11
+ let operations;
12
+ if (match !== undefined) {
13
+ let lockStrategy = match.lockStrategy;
14
+ operations = match.connectionConfig.apply(config => DcbEventLogStorage_Postgres_Runtime$ReventlessAws.opsFor(config, name, lockStrategy));
15
+ } else {
16
+ operations = Stdlib_JsError.throwWithMessage("DcbEventLogStorage_Postgres.make called without a DcbBackend selection");
17
+ }
12
18
  return {
13
19
  resources: [],
14
20
  operations: operations
@@ -58,18 +58,8 @@ let make = (
58
58
  (l.connectionConfig, l.targetQueueUrl)
59
59
  ->Pulumi.Output.all2
60
60
  ->Pulumi.Output.apply(((cc, queueUrl)) => {
61
- let pgConnectionJson =
62
- [
63
- ("host", cc.host->JSON.Encode.string),
64
- ("port", cc.port->Int.toFloat->JSON.Encode.float),
65
- ("database", cc.database->JSON.Encode.string),
66
- ("username", cc.username->JSON.Encode.string),
67
- ("secretArn", cc.secretArn->JSON.Encode.string),
68
- ]
69
- ->Dict.fromArray
70
- ->JSON.Encode.object
71
61
  let entries = [
72
- ("pgConnection", pgConnectionJson),
62
+ ("pgConnection", cc->PgConnection.connectionConfigToJson),
73
63
  ("logName", l.logName->JSON.Encode.string),
74
64
  ("subscriber", l.subscriber->JSON.Encode.string),
75
65
  ("targetQueueUrl", queueUrl->JSON.Encode.string),
@@ -12,6 +12,7 @@ import * as Adapter$ReventlessCore from "@reventlessdev/reventless-core/src/adap
12
12
  import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
13
13
  import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
14
14
  import * as Util_Lambda$ReventlessAws from "../../util/Util_Lambda.res.mjs";
15
+ import * as PgConnection$ReventlessAws from "./PgConnection.res.mjs";
15
16
  import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
16
17
  import * as Util_Cloudwatch$ReventlessAws from "../../util/Util_Cloudwatch.res.mjs";
17
18
  import * as Cloudwatch_EventRule$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Cloudwatch/Cloudwatch_EventRule.res.mjs";
@@ -26,33 +27,10 @@ function make(name, logs, securityGroupId, subnetIds, intervalMinutesOpt, opts)
26
27
  l.connectionConfig,
27
28
  l.targetQueueUrl
28
29
  ]).apply(param => {
29
- let cc = param[0];
30
- let pgConnectionJson = Object.fromEntries([
31
- [
32
- "host",
33
- cc.host
34
- ],
35
- [
36
- "port",
37
- cc.port
38
- ],
39
- [
40
- "database",
41
- cc.database
42
- ],
43
- [
44
- "username",
45
- cc.username
46
- ],
47
- [
48
- "secretArn",
49
- cc.secretArn
50
- ]
51
- ]);
52
30
  let entries = [
53
31
  [
54
32
  "pgConnection",
55
- pgConnectionJson
33
+ PgConnection$ReventlessAws.connectionConfigToJson(undefined, param[0])
56
34
  ],
57
35
  [
58
36
  "logName",