@reventlessdev/reventless-aws 3.0.0-alpha.232 → 3.0.0-alpha.233

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 (104) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +8 -8
  3. package/src/adapter/Api/AppSyncEventsSigner_Ops.res +137 -0
  4. package/src/adapter/Api/AppSyncEventsSigner_Ops.res.mjs +121 -0
  5. package/src/adapter/Api/Platform_AdminScan_Ops.res +136 -0
  6. package/src/adapter/Api/Platform_AdminScan_Ops.res.mjs +98 -0
  7. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +12 -90
  8. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +11 -84
  9. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +86 -0
  10. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +79 -0
  11. package/src/adapter/Api/Platform_UIFragments_Lambda.res +9 -79
  12. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +7 -143
  13. package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res +48 -0
  14. package/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res.mjs +63 -0
  15. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +21 -80
  16. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +23 -68
  17. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res +70 -0
  18. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync_Ops.res.mjs +68 -0
  19. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -1
  20. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +1 -1
  21. package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
  22. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
  23. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +42 -126
  24. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +246 -0
  25. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +145 -0
  26. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +27 -2
  27. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +10 -3
  28. package/src/adapter/Runtime/CounterEntryPoint.mjs +19 -73
  29. package/src/adapter/Runtime/CounterEntryPoint_Ops.res +145 -0
  30. package/src/adapter/Runtime/CounterEntryPoint_Ops.res.mjs +129 -0
  31. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +11 -1
  32. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +9 -3
  33. package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +52 -510
  34. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +816 -0
  35. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +651 -0
  36. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +23 -67
  37. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +126 -0
  38. package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res.mjs +83 -0
  39. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +16 -63
  40. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +150 -0
  41. package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res.mjs +111 -0
  42. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res +96 -0
  43. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.res.mjs +70 -0
  44. package/src/adapter/Runtime/PgMigrationEntryPoint.res +54 -0
  45. package/src/adapter/Runtime/PgMigrationEntryPoint.res.mjs +37 -0
  46. package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +20 -55
  47. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +155 -0
  48. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +94 -0
  49. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +80 -0
  50. package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res.mjs +39 -0
  51. package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +4 -4
  52. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +34 -153
  53. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +114 -0
  54. package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res.mjs +83 -0
  55. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +23 -64
  56. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +71 -0
  57. package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res.mjs +57 -0
  58. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +34 -165
  59. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +206 -0
  60. package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res.mjs +105 -0
  61. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +156 -0
  62. package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res.mjs +123 -0
  63. package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +18 -138
  64. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +198 -0
  65. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +155 -0
  66. package/src/adapter/StateTopic/StateTopic_AppSync.res +12 -170
  67. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +7 -167
  68. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +208 -0
  69. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +183 -0
  70. package/src/components/AutomationSlice_Builder.res +2 -0
  71. package/src/components/AutomationSlice_Builder.res.mjs +1 -1
  72. package/src/components/OutboundTranslationSlice_Builder.res +1 -0
  73. package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
  74. package/tests/AppSyncEventsSigner_OpsTest.res +75 -0
  75. package/tests/AppSyncEventsSigner_OpsTest.res.mjs +44 -0
  76. package/tests/AppSyncEventsSigner_ParityFixture.mjs +36 -0
  77. package/tests/AutomationSliceEntryPoint_OpsTest.res +165 -0
  78. package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +280 -0
  79. package/tests/CounterEntryPoint_OpsTest.res +115 -0
  80. package/tests/CounterEntryPoint_OpsTest.res.mjs +150 -0
  81. package/tests/EventCollectorEntryPoint_OpsTest.res +216 -0
  82. package/tests/EventCollectorEntryPoint_OpsTest.res.mjs +339 -0
  83. package/tests/EventMapperEntryPoint_OpsTest.res +103 -0
  84. package/tests/EventMapperEntryPoint_OpsTest.res.mjs +120 -0
  85. package/tests/ExtensionPointEntryPoint_OpsTest.res +57 -0
  86. package/tests/ExtensionPointEntryPoint_OpsTest.res.mjs +54 -0
  87. package/tests/PgMigrationEntryPoint_IntegrationTest.res +22 -31
  88. package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +20 -23
  89. package/tests/PgQueryResolverEntryPoint_OpsTest.res +38 -0
  90. package/tests/PgQueryResolverEntryPoint_OpsTest.res.mjs +31 -0
  91. package/tests/Platform_AdminScan_OpsTest.res +80 -0
  92. package/tests/Platform_AdminScan_OpsTest.res.mjs +127 -0
  93. package/tests/ReadModelEntryPoint_OpsTest.res +141 -0
  94. package/tests/ReadModelEntryPoint_OpsTest.res.mjs +267 -0
  95. package/tests/SideEffectEntryPoint_OpsTest.res +79 -0
  96. package/tests/SideEffectEntryPoint_OpsTest.res.mjs +105 -0
  97. package/tests/StateViewSliceEntryPoint_OpsTest.res +167 -0
  98. package/tests/StateViewSliceEntryPoint_OpsTest.res.mjs +296 -0
  99. package/tests/TaskBucketEntryPoint_OpsTest.res +117 -0
  100. package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +165 -0
  101. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res +42 -13
  102. package/tests/integration/StateViewSliceEntryPoint_IntegrationTest.res.mjs +29 -6
  103. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +0 -57
  104. package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +0 -40
@@ -0,0 +1,111 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
5
+ import * as HandlerFactoryHelpersMjs from "./HandlerFactoryHelpers.mjs";
6
+ import * as CommandTopicChannel_SQS_Runtime$ReventlessAws from "../CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
7
+
8
+ function setRequestId(prim) {
9
+ HandlerFactoryHelpersMjs.setRequestId(prim);
10
+ }
11
+
12
+ function runEffect(prim0, prim1) {
13
+ return HandlerFactoryHelpersMjs.runEffect(prim0, prim1);
14
+ }
15
+
16
+ function extractMetaField(prim0, prim1) {
17
+ return HandlerFactoryHelpersMjs.extractMetaField(prim0, prim1);
18
+ }
19
+
20
+ function extractSentTimestamp(prim) {
21
+ return HandlerFactoryHelpersMjs.extractSentTimestamp(prim);
22
+ }
23
+
24
+ function extractRetryCount(prim) {
25
+ return HandlerFactoryHelpersMjs.extractRetryCount(prim);
26
+ }
27
+
28
+ function logDebug(prim0, prim1) {
29
+ HandlerFactoryHelpersMjs.log.debug(prim0, prim1);
30
+ }
31
+
32
+ function parseHandlerConfig(raw) {
33
+ return JSON.parse(raw);
34
+ }
35
+
36
+ function makeCallbackSpec(config) {
37
+ return {
38
+ publishToAggregates: Object.fromEntries(Object.entries(Stdlib_Option.getOr(config.publishToAggregates, {})).map(param => {
39
+ let queueUrl = Stdlib_Option.getOr(process.env[param[1]], "");
40
+ let queue = {
41
+ id: queueUrl,
42
+ name: queueUrl,
43
+ arn: ""
44
+ };
45
+ return [
46
+ param[0],
47
+ CommandTopicChannel_SQS_Runtime$ReventlessAws.publishJsons(queue, "SQS_FIFO")
48
+ ];
49
+ })),
50
+ commandTopicResources: [],
51
+ scheduler: {
52
+ createSchedule: (param, param$1) => Stdlib_JsError.throwWithMessage("Scheduler not available in bundled ExtensionPoint handler"),
53
+ deleteSchedule: (param, param$1) => Stdlib_JsError.throwWithMessage("Scheduler not available in bundled ExtensionPoint handler")
54
+ },
55
+ queryEngine: {
56
+ scan: (param, param$1, param$2) => Stdlib_JsError.throwWithMessage("QueryEngine not available in bundled ExtensionPoint handler"),
57
+ query: (param, param$1, param$2, param$3, param$4, param$5, param$6) => Stdlib_JsError.throwWithMessage("QueryEngine not available in bundled ExtensionPoint handler")
58
+ },
59
+ resourceNaming: {
60
+ validateName: n => n,
61
+ urnName: n => n
62
+ }
63
+ };
64
+ }
65
+
66
+ function makeBuilt(config, specName, handleJsonCommands) {
67
+ let queueUrl = Stdlib_Option.getOr(config.queueUrl, "");
68
+ let queue = {
69
+ id: queueUrl,
70
+ name: queueUrl,
71
+ arn: ""
72
+ };
73
+ return {
74
+ comp: `ExtensionPoint(` + specName + `)`,
75
+ sqsHandler: CommandTopicChannel_SQS_Runtime$ReventlessAws.handleQueueEvent(queue, handleJsonCommands)
76
+ };
77
+ }
78
+
79
+ function makeHandler(buildPromise) {
80
+ return async (event, context) => {
81
+ HandlerFactoryHelpersMjs.setRequestId(context.awsRequestId);
82
+ let match = await buildPromise;
83
+ let records = event.Records;
84
+ let prim0 = `processing ` + records.length.toString() + ` record(s)`;
85
+ HandlerFactoryHelpersMjs.log.debug(prim0, {
86
+ comp: "ExtensionPointRuntime"
87
+ });
88
+ await HandlerFactoryHelpersMjs.runEffect(match.sqsHandler(event, context), {
89
+ correlationId: HandlerFactoryHelpersMjs.extractMetaField(records, "correlationId"),
90
+ causationId: HandlerFactoryHelpersMjs.extractMetaField(records, "causationId"),
91
+ comp: match.comp,
92
+ timestamp: HandlerFactoryHelpersMjs.extractSentTimestamp(records),
93
+ retryCount: HandlerFactoryHelpersMjs.extractRetryCount(records)
94
+ });
95
+ return "";
96
+ };
97
+ }
98
+
99
+ export {
100
+ setRequestId,
101
+ runEffect,
102
+ extractMetaField,
103
+ extractSentTimestamp,
104
+ extractRetryCount,
105
+ logDebug,
106
+ parseHandlerConfig,
107
+ makeCallbackSpec,
108
+ makeBuilt,
109
+ makeHandler,
110
+ }
111
+ /* ./HandlerFactoryHelpers.mjs Not a pure module */
@@ -0,0 +1,96 @@
1
+ // PgChangeFeedRelay Lambda entry point (scheduled poll — B2.3) — compiled,
2
+ // type-checked ReScript (replaces the hand-written PgChangeFeedRelayEntryPoint.mjs
3
+ // shell; no dynamic user-module import, so no untyped seam is needed).
4
+ //
5
+ // Triggered on a schedule (EventBridge rate rule). Each invocation drains every
6
+ // Postgres log listed in HANDLER_CONFIG from its checkpoint — DCB (`dcb_event`)
7
+ // and classic aggregate (`event_log`) logs alike — and relays the events,
8
+ // transformed into the {id, meta, event} shape, onto the plugin EventCollector
9
+ // SQS queue — which then fans out to projections, aggregate command topics, and
10
+ // the cross-plugin SNS EventTopic.
11
+ // See docs/plans/done/aws-postgres-change-feed-bridge.md.
12
+ //
13
+ // Runtime-pure: `PgConnection.connectionConfig` is referenced as a type only
14
+ // (erased — no runtime import of the Pulumi-carrying PgConnection module);
15
+ // PgChangeFeedRelay_Runtime/Util_SQS_Runtime are runtime modules. Deploy-time
16
+ // wiring lives in PgChangeFeedRelay_Builder.
17
+ //
18
+ // HANDLER_CONFIG shape:
19
+ // { "logs": [ { "pgConnection": {host,port,database,username,secretArn},
20
+ // "logName": string, // dcb_event/event_log log_name
21
+ // "subscriber": string, // per-log checkpoint key
22
+ // "targetQueueUrl": string, // plugin EventCollector SQS URL
23
+ // "kind"?: "classic", // absent → DCB
24
+ // "partitionTag"?: <derivedPartitionTag> } ] } // DCB only
25
+
26
+ @val @scope("process") external processEnv: dict<string> = "env"
27
+
28
+ // Structured JSON logging shared by every deployed entry point (HandlerFactoryHelpers).
29
+ type logExtra = {comp?: string}
30
+ @module("./HandlerFactoryHelpers.mjs") @scope("log")
31
+ external logDebug: (string, logExtra) => unit = "debug"
32
+ @module("./HandlerFactoryHelpers.mjs") @scope("log")
33
+ external logWarn: (string, logExtra) => unit = "warn"
34
+
35
+ type relayLogConfig = {
36
+ pgConnection: PgConnection.connectionConfig,
37
+ logName: string,
38
+ subscriber: string,
39
+ targetQueueUrl: string,
40
+ // Absent → DCB (`dcb_event`); "classic" → aggregate `event_log`.
41
+ kind?: string,
42
+ // DCB only: the sury-encoded derived partition tag (parsed inside relayWithPool).
43
+ partitionTag?: JSON.t,
44
+ }
45
+ type handlerConfig = {logs?: array<relayLogConfig>}
46
+ @val @scope("JSON") external parseHandlerConfig: string => handlerConfig = "parse"
47
+
48
+ // Standard (non-FIFO) EventCollector queue — no message group id needed. The
49
+ // SQS handler parses each body straight back to JSON (Util_SQS_Runtime.parseSqsRecord).
50
+ let makeSendBatch = (targetQueueUrl: string): (array<JSON.t> => promise<unit>) => {
51
+ let queue: Util_SQS_Runtime.resolvedQueue = {id: targetQueueUrl, name: targetQueueUrl, arn: ""}
52
+ jsons =>
53
+ jsons
54
+ ->Array.map(json => Util_SQS_Runtime.sendMessage(queue, json->JSON.stringify))
55
+ ->Promise.all
56
+ ->Promise.thenResolve(_ => ())
57
+ }
58
+
59
+ // Drain one log and relay it. Log-and-continue on failure — a failed log this
60
+ // tick is retried next tick from its unchanged checkpoint (at-least-once;
61
+ // projections are idempotent).
62
+ let relayLog = async (l: relayLogConfig): unit => {
63
+ let sendBatch = makeSendBatch(l.targetQueueUrl)
64
+ try {
65
+ let count = switch l.kind {
66
+ | Some("classic") =>
67
+ await PgChangeFeedRelay_Runtime.relayClassic(
68
+ ~config=l.pgConnection,
69
+ ~logName=l.logName,
70
+ ~subscriber=l.subscriber,
71
+ ~sendBatch,
72
+ )
73
+ | _ =>
74
+ await PgChangeFeedRelay_Runtime.relay(
75
+ ~config=l.pgConnection,
76
+ ~logName=l.logName,
77
+ ~subscriber=l.subscriber,
78
+ ~partitionTagJson=?l.partitionTag,
79
+ ~sendBatch,
80
+ )
81
+ }
82
+ logDebug(`relayed ${count->Int.toString} event(s) for ${l.logName}`, {comp: "PgChangeFeedRelay"})
83
+ } catch {
84
+ | exn =>
85
+ let msg = exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown error")
86
+ logWarn(`relay failed for ${l.logName}: ${msg}`, {comp: "PgChangeFeedRelay"})
87
+ }
88
+ }
89
+
90
+ let handler = async (_event: JSON.t, _context: PulumiAws.Lambda.context) => {
91
+ let config =
92
+ processEnv->Dict.get("HANDLER_CONFIG")->Option.getOr(`{"logs":[]}`)->parseHandlerConfig
93
+ let logs = config.logs->Option.getOr([])
94
+ let _ = await logs->Array.map(relayLog)->Promise.all
95
+ ""
96
+ }
@@ -0,0 +1,70 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
6
+ import * as HandlerFactoryHelpersMjs from "./HandlerFactoryHelpers.mjs";
7
+ import * as Util_SQS_Runtime$ReventlessAws from "../../util/Util_SQS_Runtime.res.mjs";
8
+ import * as PgChangeFeedRelay_Runtime$ReventlessAws from "../Postgres/PgChangeFeedRelay_Runtime.res.mjs";
9
+
10
+ function logDebug(prim0, prim1) {
11
+ HandlerFactoryHelpersMjs.log.debug(prim0, prim1);
12
+ }
13
+
14
+ function logWarn(prim0, prim1) {
15
+ HandlerFactoryHelpersMjs.log.warn(prim0, prim1);
16
+ }
17
+
18
+ function makeSendBatch(targetQueueUrl) {
19
+ let queue = {
20
+ id: targetQueueUrl,
21
+ name: targetQueueUrl,
22
+ arn: ""
23
+ };
24
+ return jsons => Promise.all(jsons.map(json => Util_SQS_Runtime$ReventlessAws.sendMessage(queue, undefined, JSON.stringify(json)))).then(param => {});
25
+ }
26
+
27
+ async function relayLog(l) {
28
+ let sendBatch = makeSendBatch(l.targetQueueUrl);
29
+ try {
30
+ let match = l.kind;
31
+ let count;
32
+ let exit = 0;
33
+ if (match === "classic") {
34
+ count = await PgChangeFeedRelay_Runtime$ReventlessAws.relayClassic(l.pgConnection, l.logName, l.subscriber, sendBatch);
35
+ } else {
36
+ exit = 1;
37
+ }
38
+ if (exit === 1) {
39
+ count = await PgChangeFeedRelay_Runtime$ReventlessAws.relay(l.pgConnection, l.logName, l.subscriber, l.partitionTag, sendBatch);
40
+ }
41
+ let prim0 = `relayed ` + count.toString() + ` event(s) for ` + l.logName;
42
+ HandlerFactoryHelpersMjs.log.debug(prim0, {
43
+ comp: "PgChangeFeedRelay"
44
+ });
45
+ return;
46
+ } catch (raw_exn) {
47
+ let exn = Primitive_exceptions.internalToException(raw_exn);
48
+ let msg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown error");
49
+ HandlerFactoryHelpersMjs.log.warn(`relay failed for ` + l.logName + `: ` + msg, {
50
+ comp: "PgChangeFeedRelay"
51
+ });
52
+ return;
53
+ }
54
+ }
55
+
56
+ async function handler(_event, _context) {
57
+ let config = JSON.parse(Stdlib_Option.getOr(process.env["HANDLER_CONFIG"], `{"logs":[]}`));
58
+ let logs = Stdlib_Option.getOr(config.logs, []);
59
+ await Promise.all(logs.map(relayLog));
60
+ return "";
61
+ }
62
+
63
+ export {
64
+ logDebug,
65
+ logWarn,
66
+ makeSendBatch,
67
+ relayLog,
68
+ handler,
69
+ }
70
+ /* Stdlib_JsExn Not a pure module */
@@ -0,0 +1,54 @@
1
+ // One-shot Postgres schema-migration Lambda entry point (A3) — compiled,
2
+ // type-checked ReScript (replaces the hand-written PgMigrationEntryPoint.mjs
3
+ // shell; no dynamic user-module import, so no untyped seam is needed).
4
+ //
5
+ // Invoked once during `pulumi up` by an `aws.lambda.Invocation` resource, from
6
+ // INSIDE the DB's VPC, so the deploy runner needs no network path to a private
7
+ // RDS instance. Runs the exact idempotent `PgSchema.ensureSchema` the local
8
+ // backend runs at startup (reventless-local's `postgres` smart constructor), so
9
+ // the AWS schema is byte-identical to local — zero drift. Every statement is
10
+ // `IF NOT EXISTS`, so re-invocation (e.g. after a schema-DDL change bumps the
11
+ // Lambda layer) is safe.
12
+ //
13
+ // Runtime-pure: `PgConnection.connectionConfig` is referenced as a type only
14
+ // (erased — no runtime import of the Pulumi-carrying PgConnection module);
15
+ // PgRuntime/PgSchema are runtime modules. Deploy-time wiring lives in
16
+ // PgMigration_Builder.
17
+ //
18
+ // HANDLER_CONFIG shape:
19
+ // { "pgConnection": { host, port, database, username, secretArn } }
20
+
21
+ @val @scope("process") external processEnv: dict<string> = "env"
22
+
23
+ // Structured JSON logging shared by every deployed entry point (HandlerFactoryHelpers).
24
+ type logExtra = {comp?: string}
25
+ @module("./HandlerFactoryHelpers.mjs") @scope("log")
26
+ external logInfo: (string, logExtra) => unit = "info"
27
+
28
+ type handlerConfig = {pgConnection?: PgConnection.connectionConfig}
29
+ @val @scope("JSON") external parseHandlerConfig: string => handlerConfig = "parse"
30
+
31
+ // Config parse → pool → ensureSchema, with the pool source injectable so the
32
+ // orchestration is integration-testable against a real Postgres without the
33
+ // Secrets Manager round-trip (mirrors PgChangeFeedRelay_Runtime's
34
+ // relayWithPool/relay split). `~makePool` defaults to the production `poolFor`,
35
+ // which memoises one pool per secret ARN per container and resolves the
36
+ // RDS-managed password from Secrets Manager via a cached provider.
37
+ let runMigration = async (
38
+ config: handlerConfig,
39
+ ~makePool: PgConnection.connectionConfig => ReventlessPostgres.PgDriver.pool=PgRuntime.poolFor,
40
+ ): string => {
41
+ switch config.pgConnection {
42
+ | None => JsError.throwWithMessage("PgMigrationEntryPoint: HANDLER_CONFIG has no pgConnection")
43
+ | Some(conn) =>
44
+ let pool = makePool(conn)
45
+ await ReventlessPostgres.PgSchema.ensureSchema(pool)
46
+ logInfo(`ensureSchema completed for ${conn.database}`, {comp: "PgMigration"})
47
+ "ok"
48
+ }
49
+ }
50
+
51
+ let handler = async (_event: JSON.t, _context: PulumiAws.Lambda.context) => {
52
+ let config = processEnv->Dict.get("HANDLER_CONFIG")->Option.getOr("{}")->parseHandlerConfig
53
+ await runMigration(config)
54
+ }
@@ -0,0 +1,37 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
5
+ import * as PgRuntime$ReventlessAws from "../Postgres/PgRuntime.res.mjs";
6
+ import * as HandlerFactoryHelpersMjs from "./HandlerFactoryHelpers.mjs";
7
+ import * as PgSchema$ReventlessPostgres from "@reventlessdev/reventless-postgres/src/PgSchema.res.mjs";
8
+
9
+ function logInfo(prim0, prim1) {
10
+ HandlerFactoryHelpersMjs.log.info(prim0, prim1);
11
+ }
12
+
13
+ async function runMigration(config, makePoolOpt) {
14
+ let makePool = makePoolOpt !== undefined ? makePoolOpt : PgRuntime$ReventlessAws.poolFor;
15
+ let conn = config.pgConnection;
16
+ if (conn === undefined) {
17
+ return Stdlib_JsError.throwWithMessage("PgMigrationEntryPoint: HANDLER_CONFIG has no pgConnection");
18
+ }
19
+ let pool = makePool(conn);
20
+ await PgSchema$ReventlessPostgres.ensureSchema(pool);
21
+ HandlerFactoryHelpersMjs.log.info(`ensureSchema completed for ` + conn.database, {
22
+ comp: "PgMigration"
23
+ });
24
+ return "ok";
25
+ }
26
+
27
+ async function handler(_event, _context) {
28
+ let config = JSON.parse(Stdlib_Option.getOr(process.env["HANDLER_CONFIG"], "{}"));
29
+ return await runMigration(config, undefined);
30
+ }
31
+
32
+ export {
33
+ logInfo,
34
+ runMigration,
35
+ handler,
36
+ }
37
+ /* PgRuntime-ReventlessAws Not a pure module */
@@ -4,78 +4,43 @@
4
4
  // fields (they have no per-table AppSync data source). At cold start it reads
5
5
  // QUERY_RESOLVER_CONFIG (one shared pgConnection + one handler per read model),
6
6
  // dynamically imports each spec package, and registers a per-read-model
7
- // `binding` (ops + QueryEnginePostgres push-downs + indexes/subIdField/capability
8
- // + baked labelField/includeIdParam + authorization) into
9
- // PgQueryResolver_Lambda. AppSync invokes `handler` with the resolver payload
10
- // { readModelName, kind, index?, arguments, identity }, which
7
+ // `binding` into PgQueryResolver_Lambda. AppSync invokes `handler` with the
8
+ // resolver payload { readModelName, kind, index?, arguments, identity }, which
11
9
  // PgQueryResolver_Lambda.dispatch routes.
12
10
  //
13
- // ReScript labeled args compile positionally, so the engine push-downs
14
- // (indexLookup/byIds/listPage/scan) are assigned straight into the `pushdowns`
15
- // recordsame calling convention PgQueryResolver_Lambda.dispatch invokes them
16
- // with (verified against the compiled QueryEnginePostgres output).
17
-
18
- import { poolFor } from "@reventlessdev/reventless-aws/src/adapter/Postgres/PgRuntime.res.mjs";
19
- import { Make as makeEngine } from "@reventlessdev/reventless-postgres/src/QueryEnginePostgres.res.mjs";
20
- import { opsFor as pgQdbOpsFor } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res.mjs";
21
- import { deriveServerCapability } from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
22
- import { register, registerNodeType, handler as dispatchHandler } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs";
11
+ // Thin untyped shell ("typed core, thin shell"
12
+ // docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
13
+ // seams that are inherently untyped the dynamic `import()` of the spec
14
+ // packages named in QUERY_RESOLVER_CONFIG, the `patchSpecId` fix-up, and the
15
+ // reads of the runtime-loaded modules' exports. QUERY_RESOLVER_CONFIG parsing,
16
+ // the pool/engine construction, the push-down record, and binding registration
17
+ // live type-checked in PgQueryResolverEntryPoint_Ops.res.
18
+
19
+ import { handler as dispatchHandler } from "@reventlessdev/reventless-aws/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs";
23
20
  import { patchSpecId, log } from "./HandlerFactoryHelpers.mjs";
21
+ import * as Ops from "./PgQueryResolverEntryPoint_Ops.res.mjs";
24
22
 
25
23
  const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
26
24
 
27
- // A bounded full-materialisation for the list fallback (shapes listPage
28
- // declines). Kept high; the fallback is only hit for search/searchPrefix/ids/
29
- // backward pagination, which the AutoUI does not issue for large models.
30
- const SCAN_ALL_LIMIT = 100000;
31
-
32
25
  async function buildAllBindings() {
33
- const configStr = process.env["QUERY_RESOLVER_CONFIG"] || '{"handlers":[]}';
34
- const config = JSON.parse(configStr);
26
+ const config = Ops.parseResolverConfig(process.env["QUERY_RESOLVER_CONFIG"] || "");
35
27
  const pgConnection = config.pgConnection;
36
28
  if (!pgConnection) {
37
29
  log.warn("no pgConnection in QUERY_RESOLVER_CONFIG", { comp: "PgQueryResolver" });
38
30
  return;
39
31
  }
40
- // Container-lifetime pool (memoised by poolFor); the engine and the ops set
41
- // share it, so a Lambda holds one pool regardless of read model count.
42
- const pool = poolFor(pgConnection);
43
- const engine = makeEngine({ pool });
44
32
 
45
- // Relay node type → read-model map (B3.2c), baked into the config.
46
- for (const [typeName, rm] of Object.entries(config.nodeTypes || {})) {
47
- registerNodeType(typeName, rm);
48
- }
33
+ Ops.registerNodeTypes(config);
34
+ const pushdowns = Ops.makePushdowns(pgConnection);
49
35
 
50
- const pushdowns = {
51
- indexLookup: engine.indexLookup,
52
- byIds: engine.byIds,
53
- listPage: engine.listPage,
54
- itemsPage: engine.itemsPage,
55
- scanAll: (readModelName) => engine.scan(readModelName, [], SCAN_ALL_LIMIT),
56
- };
57
-
58
- await Promise.all((config.handlers || []).map(async h => {
36
+ await Promise.all(config.handlers.map(async h => {
59
37
  const specModule = patchSpecId(await dynamicImport(h.specModule));
60
- const indexes = (specModule.config && specModule.config.indexes) || [];
61
- const subIdField = specModule.subIdConfig ? specModule.subIdConfig.subIdField : undefined;
62
- const capability = deriveServerCapability(specModule.stateSchema);
63
- const ops = pgQdbOpsFor(pgConnection, h.readModelName, indexes, subIdField);
64
-
65
- register(h.readModelName, {
66
- ops,
67
- pushdowns,
68
- indexes,
69
- subIdField,
70
- capability,
71
- labelField: h.labelField,
72
- includeIdParam: h.includeIdParam,
73
- // Compiled string variant (e.g. "AllowAuthenticated"), injected on the
74
- // spec by @@reventless.spec — the shape Reventless.Authorization.isAllowed
75
- // matches on.
38
+ Ops.registerBinding(pushdowns, pgConnection, h, {
39
+ config: specModule.config,
40
+ subIdConfig: specModule.subIdConfig,
41
+ stateSchema: specModule.stateSchema,
76
42
  authorization: specModule.authorization,
77
43
  });
78
- log.debug("registered resolver binding for " + h.readModelName, { comp: "PgQueryResolver" });
79
44
  }));
80
45
  }
81
46
 
@@ -0,0 +1,155 @@
1
+ // Typed cold-start core for the shared PgQueryResolver Lambda entry point
2
+ // (B3.2b).
3
+ //
4
+ // The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
5
+ // PgQueryResolverEntryPoint.mjs keeps only the untyped seam — the dynamic
6
+ // `import()` of each spec package named in QUERY_RESOLVER_CONFIG, the
7
+ // `patchSpecId` fix-up, and the reads of the runtime-loaded module's exports
8
+ // (config / subIdConfig / stateSchema / authorization), handed back here as a
9
+ // typed `specInfo`. QUERY_RESOLVER_CONFIG parsing, the pool/engine
10
+ // construction (QueryEnginePostgres.Make over an inline module capturing the
11
+ // container-lifetime pool), the push-down record, and the per-read-model
12
+ // binding registration live here, fully type-checked.
13
+
14
+ @module("./HandlerFactoryHelpers.mjs") @scope("log")
15
+ external logDebug: (string, StreamRoutedEntryPoint_Ops.dispatchOpts) => unit = "debug"
16
+
17
+ // ── QUERY_RESOLVER_CONFIG ───────────────────────────────────────────────────
18
+ // Written by PgQueryResolver_Builder: one shared pgConnection + one handler
19
+ // per read model + the Relay node type → read-model map (B3.2c).
20
+
21
+ type handlerEntry = {
22
+ readModelName: string,
23
+ specModule: string,
24
+ labelField: string,
25
+ includeIdParam: bool,
26
+ }
27
+
28
+ type resolverConfig = {
29
+ pgConnection: option<PgConnection.connectionConfig>,
30
+ handlers: array<handlerEntry>,
31
+ nodeTypes: dict<string>,
32
+ }
33
+
34
+ // The builder writes `pgConnection` via PgConnection.connectionConfigToJson, so
35
+ // the parsed object IS the record shape — cast at the documented JSON contract
36
+ // (the AggregateEntryPoint_Ops.asConnectionConfig pattern).
37
+ external asPgConnection: JSON.t => PgConnection.connectionConfig = "%identity"
38
+
39
+ let strOf = (obj: dict<JSON.t>, key: string): option<string> =>
40
+ obj->Dict.get(key)->Option.flatMap(JSON.Decode.string)
41
+
42
+ let decodeEntry = (json: JSON.t): option<handlerEntry> =>
43
+ json
44
+ ->JSON.Decode.object
45
+ ->Option.map(h => {
46
+ readModelName: h->strOf("readModelName")->Option.getOr(""),
47
+ specModule: h->strOf("specModule")->Option.getOr(""),
48
+ labelField: h->strOf("labelField")->Option.getOr(""),
49
+ includeIdParam: h
50
+ ->Dict.get("includeIdParam")
51
+ ->Option.flatMap(JSON.Decode.bool)
52
+ ->Option.getOr(false),
53
+ })
54
+
55
+ let parseResolverConfig = (rawJson: string): resolverConfig => {
56
+ let obj =
57
+ (rawJson == "" ? `{"handlers":[]}` : rawJson)
58
+ ->JSON.parseOrThrow
59
+ ->JSON.Decode.object
60
+ ->Option.getOr(Dict.make())
61
+ {
62
+ pgConnection: obj
63
+ ->Dict.get("pgConnection")
64
+ ->Option.filter(j => j != JSON.Null)
65
+ ->Option.map(asPgConnection),
66
+ handlers: obj
67
+ ->Dict.get("handlers")
68
+ ->Option.flatMap(JSON.Decode.array)
69
+ ->Option.getOr([])
70
+ ->Array.filterMap(decodeEntry),
71
+ nodeTypes: obj
72
+ ->Dict.get("nodeTypes")
73
+ ->Option.flatMap(JSON.Decode.object)
74
+ ->Option.getOr(Dict.make())
75
+ ->Dict.toArray
76
+ ->Array.filterMap(((k, v)) => v->JSON.Decode.string->Option.map(s => (k, s)))
77
+ ->Dict.fromArray,
78
+ }
79
+ }
80
+
81
+ let registerNodeTypes = (config: resolverConfig): unit =>
82
+ config.nodeTypes
83
+ ->Dict.toArray
84
+ ->Array.forEach(((typeName, readModelName)) =>
85
+ PgQueryResolver_Lambda.registerNodeType(~typeName, ~readModelName)
86
+ )
87
+
88
+ // ── Push-downs ──────────────────────────────────────────────────────────────
89
+
90
+ // A bounded full-materialisation for the list fallback (shapes listPage
91
+ // declines). Kept high; the fallback is only hit for search/searchPrefix/ids/
92
+ // backward pagination, which the AutoUI does not issue for large models.
93
+ let scanAllLimit = 100000
94
+
95
+ // Container-lifetime pool (memoised by poolFor); the engine and each binding's
96
+ // ops set share it, so the Lambda holds one pool regardless of read model
97
+ // count.
98
+ let makePushdowns = (
99
+ pgConnection: PgConnection.connectionConfig,
100
+ ): PgQueryResolver_Lambda.pushdowns => {
101
+ module Engine = ReventlessPostgres.QueryEnginePostgres.Make({
102
+ let pool = PgRuntime.poolFor(pgConnection)
103
+ })
104
+ {
105
+ indexLookup: Engine.indexLookup,
106
+ byIds: Engine.byIds,
107
+ listPage: Engine.listPage,
108
+ itemsPage: Engine.itemsPage,
109
+ scanAll: (~readModelName) => Engine.scan(~readModelName, ~filterConfigs=[], ~limit=scanAllLimit),
110
+ }
111
+ }
112
+
113
+ // ── Binding registration ────────────────────────────────────────────────────
114
+ // The runtime-loaded spec module's exports, read by the shell and passed as a
115
+ // typed record; `authorization` is the compiled string variant (e.g.
116
+ // "AllowAuthenticated") injected on the spec by @@reventless.spec — the shape
117
+ // Reventless.Authorization.isAllowed matches on.
118
+
119
+ type specInfo = {
120
+ config: option<ProjectionEntryPoint_Ops.specConfig>,
121
+ subIdConfig: option<Reventless.ReadModel.subIdConfig<JSON.t>>,
122
+ stateSchema: S.t<unknown>,
123
+ authorization: Reventless.Authorization.permission,
124
+ }
125
+
126
+ let registerBinding = (
127
+ pushdowns: PgQueryResolver_Lambda.pushdowns,
128
+ pgConnection: PgConnection.connectionConfig,
129
+ entry: handlerEntry,
130
+ spec: specInfo,
131
+ ): unit => {
132
+ let indexes = ProjectionEntryPoint_Ops.indexesOf(spec.config)
133
+ let subIdField = ProjectionEntryPoint_Ops.subIdFieldOf(spec.subIdConfig)
134
+ PgQueryResolver_Lambda.register(
135
+ ~readModelName=entry.readModelName,
136
+ {
137
+ ops: QueryDbStorage_Postgres_Runtime.opsFor(
138
+ pgConnection,
139
+ ~name=entry.readModelName,
140
+ ~indexes,
141
+ ~subIdField=?subIdField,
142
+ ),
143
+ pushdowns,
144
+ indexes,
145
+ subIdField,
146
+ capability: ReventlessCore.GraphQL_FragmentGenerator.deriveServerCapability(
147
+ spec.stateSchema,
148
+ ),
149
+ labelField: entry.labelField,
150
+ includeIdParam: entry.includeIdParam,
151
+ authorization: spec.authorization,
152
+ },
153
+ )
154
+ logDebug("registered resolver binding for " ++ entry.readModelName, {comp: "PgQueryResolver"})
155
+ }