@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.
- package/CHANGELOG.md +14 -0
- package/docker-compose.postgres.yml +21 -0
- package/package.json +6 -5
- package/scripts/run-pg-integration-tests.sh +58 -0
- package/src/Platform.res +37 -0
- package/src/Platform.res.mjs +41 -4
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +7 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +10 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +7 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +10 -1
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +6 -21
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +2 -17
- package/src/adapter/DcbEventLog/DcbBackend.res +4 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res +2 -2
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res.mjs +7 -1
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -11
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +2 -24
- package/src/adapter/Postgres/PgConnection.res +70 -10
- package/src/adapter/Postgres/PgConnection.res.mjs +48 -3
- package/src/adapter/Postgres/PgMigration_Builder.res +127 -0
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +51 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +11 -13
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +5 -24
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +51 -3
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +48 -18
- package/src/adapter/QueryDb/QueryDbBackend.res +8 -0
- package/src/adapter/QueryDb/QueryDbBackend.res.mjs +4 -1
- package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +1 -1
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +31 -7
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res.mjs +16 -10
- package/src/adapter/QueryDb/QueryDbStorage.res +6 -1
- package/src/adapter/QueryDb/QueryDbStorage.res.mjs +2 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +1 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +3 -14
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +2 -25
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +3 -14
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +2 -25
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +7 -1
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +78 -15
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +43 -26
- package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +40 -0
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +17 -5
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +10 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +3 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +9 -14
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +2 -25
- package/src/adapter/Runtime/StateTopicPublish.mjs +204 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +17 -3
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +86 -17
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +50 -33
- package/src/adapter/StateTopic/StateTopic_AppSync.res +7 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +10 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res +7 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +10 -1
- package/src/util/Util_Bundle.res +71 -1
- package/src/util/Util_Bundle.res.mjs +49 -1
- package/tests/PgChangeFeedRelay_IntegrationTest.res +3 -3
- package/tests/PgConnectionJsonTest.res +84 -0
- package/tests/PgConnectionJsonTest.res.mjs +68 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res +82 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +63 -0
- package/tests/PgPipeline_IntegrationTest.res +3 -1
- package/tests/PgQueryResolver_LambdaTest.res +59 -0
- package/tests/PgQueryResolver_LambdaTest.res.mjs +95 -16
- package/tests/StateTopicPublishTest.res +162 -0
- package/tests/StateTopicPublishTest.res.mjs +158 -0
- package/tests/Util_BundleEsmLoaderTest.res +57 -0
- package/tests/Util_BundleEsmLoaderTest.res.mjs +44 -0
|
@@ -13,6 +13,9 @@ type handlerEntry = {
|
|
|
13
13
|
projectionModule: string,
|
|
14
14
|
queryDbTableName: string,
|
|
15
15
|
sourceUrn: string,
|
|
16
|
+
// B3.3: AppSync Events channel root for a subscription-enabled Postgres view
|
|
17
|
+
// slice; "" when the slice isn't live-enabled (compact key `t`, omitted then).
|
|
18
|
+
stateTopicName: string,
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
// Longest common prefix of a set of strings. Used to factor the shared
|
|
@@ -40,6 +43,27 @@ type sliceInfo = {
|
|
|
40
43
|
|
|
41
44
|
let sliceInfos: dict<sliceInfo> = Dict.make()
|
|
42
45
|
|
|
46
|
+
// B3.3: the AppSync Events API a Postgres-backed stream view slice publishes live
|
|
47
|
+
// updates to. Set once by `Platform.makePlatform` before plugins build (mirrors
|
|
48
|
+
// EventCollectorRuntime_Builder_Single). None → no live updates.
|
|
49
|
+
type eventsApiConfig = {
|
|
50
|
+
endpoint: Pulumi.Output.t<string>,
|
|
51
|
+
apiArn: Pulumi.Output.t<string>,
|
|
52
|
+
}
|
|
53
|
+
let eventsApiConfig: ref<option<eventsApiConfig>> = ref(None)
|
|
54
|
+
let setEventsApiConfig = (cfg: eventsApiConfig) => eventsApiConfig := Some(cfg)
|
|
55
|
+
|
|
56
|
+
// Channel root for a subscription-enabled Postgres view slice (the plural LIST
|
|
57
|
+
// field name, same source as the DynamoDB subscriptionInfraHook); "" when the
|
|
58
|
+
// slice isn't stream-routed or there's no events API.
|
|
59
|
+
let stateTopicNameFor = (name: string): string =>
|
|
60
|
+
QueryDbBackend.postgresStreamRegistry->Set.has(name) && eventsApiConfig.contents->Option.isSome
|
|
61
|
+
? ReventlessCore.Plugin_Helpers.queryFieldNamesRegistry
|
|
62
|
+
->Dict.get(name)
|
|
63
|
+
->Option.map(qn => qn.listFieldName)
|
|
64
|
+
->Option.getOr(name)
|
|
65
|
+
: ""
|
|
66
|
+
|
|
43
67
|
let registerStateViewSlice = (
|
|
44
68
|
~name,
|
|
45
69
|
~specModulePath,
|
|
@@ -130,6 +154,9 @@ let buildLambda = (
|
|
|
130
154
|
~packageDirs,
|
|
131
155
|
~channelSpecs,
|
|
132
156
|
~feedQueue: option<PulumiAws.SQS.Queue.t>=None,
|
|
157
|
+
// B3.3: Some when any slice on this Lambda is a subscription-enabled Postgres
|
|
158
|
+
// view slice — arms the APPSYNC_ENDPOINT env + appsync:EventPublish IAM.
|
|
159
|
+
~pgStreamConfig: option<eventsApiConfig>=None,
|
|
133
160
|
~memorySize=1024,
|
|
134
161
|
~timeout=30,
|
|
135
162
|
) => {
|
|
@@ -141,20 +168,9 @@ let buildLambda = (
|
|
|
141
168
|
let qdbSelection = QueryDbBackend.get()
|
|
142
169
|
let pgConnectionFragment = switch qdbSelection {
|
|
143
170
|
| Some(sel) =>
|
|
144
|
-
sel.connectionConfig->Pulumi.Output.apply(cc =>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
("host", cc.host->JSON.Encode.string),
|
|
148
|
-
("port", cc.port->Int.toFloat->JSON.Encode.float),
|
|
149
|
-
("database", cc.database->JSON.Encode.string),
|
|
150
|
-
("username", cc.username->JSON.Encode.string),
|
|
151
|
-
("secretArn", cc.secretArn->JSON.Encode.string),
|
|
152
|
-
]
|
|
153
|
-
->Dict.fromArray
|
|
154
|
-
->JSON.Encode.object
|
|
155
|
-
->JSON.stringify
|
|
156
|
-
`,"pgConnection":${pgConnectionJson}`
|
|
157
|
-
})
|
|
171
|
+
sel.connectionConfig->Pulumi.Output.apply(cc =>
|
|
172
|
+
`,"pgConnection":${cc->PgConnection.connectionConfigToJson->JSON.stringify}`
|
|
173
|
+
)
|
|
158
174
|
| None => Pulumi.Output.make("")
|
|
159
175
|
}
|
|
160
176
|
|
|
@@ -191,11 +207,16 @@ let buildLambda = (
|
|
|
191
207
|
->JSON.stringifyAny
|
|
192
208
|
->Option.getOr(`""`)
|
|
193
209
|
let q = h.queryDbTableName->JSON.stringifyAny->Option.getOr(`""`)
|
|
210
|
+
// B3.3: compact key `t` = live-update channel root; omit when empty.
|
|
211
|
+
let t =
|
|
212
|
+
h.stateTopicName == ""
|
|
213
|
+
? ""
|
|
214
|
+
: `,"t":${h.stateTopicName->JSON.Encode.string->JSON.stringify}`
|
|
194
215
|
if sharedUrn != "" {
|
|
195
|
-
`{"s":${s},"p":${p},"q":${q}}`
|
|
216
|
+
`{"s":${s},"p":${p},"q":${q}${t}}`
|
|
196
217
|
} else {
|
|
197
218
|
let u = h.sourceUrn->JSON.stringifyAny->Option.getOr(`""`)
|
|
198
|
-
`{"s":${s},"p":${p},"q":${q},"u":${u}}`
|
|
219
|
+
`{"s":${s},"p":${p},"q":${q},"u":${u}${t}}`
|
|
199
220
|
}
|
|
200
221
|
})
|
|
201
222
|
->Array.join(",")
|
|
@@ -207,6 +228,13 @@ let buildLambda = (
|
|
|
207
228
|
let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
|
|
208
229
|
envVars->Dict.set("HANDLER_CONFIG", handlerConfigOutput->Pulumi.Output.asInput)
|
|
209
230
|
|
|
231
|
+
// B3.3: give the projection Lambda the AppSync Events endpoint when any of its
|
|
232
|
+
// slices publish live updates (IAM added below).
|
|
233
|
+
switch pgStreamConfig {
|
|
234
|
+
| Some(cfg) => envVars->Dict.set("APPSYNC_ENDPOINT", cfg.endpoint->Pulumi.Output.asInput)
|
|
235
|
+
| None => ()
|
|
236
|
+
}
|
|
237
|
+
|
|
210
238
|
// Bundle the framework packages alongside the entry point so the deployed
|
|
211
239
|
// Lambda picks up local edits without waiting for a Lambda Layer rebuild
|
|
212
240
|
// (the layer fetches @reventlessdev/reventless-* from GitHub Packages, not
|
|
@@ -286,6 +314,35 @@ let buildLambda = (
|
|
|
286
314
|
| None => ()
|
|
287
315
|
}
|
|
288
316
|
|
|
317
|
+
// B3.3: grant appsync:EventPublish so subscription-enabled Postgres view slices
|
|
318
|
+
// can push live-update descriptors from this Lambda.
|
|
319
|
+
switch pgStreamConfig {
|
|
320
|
+
| Some(cfg) =>
|
|
321
|
+
let _ = cfg.apiArn->Pulumi.Output.apply(apiArn => {
|
|
322
|
+
open PulumiAws.PolicyDocument
|
|
323
|
+
PulumiAws.IAM.RolePolicy.make(
|
|
324
|
+
~name="AllStateViewSlices-appsyncPublish",
|
|
325
|
+
~args={
|
|
326
|
+
policy: PulumiAws.PolicyDocument.make(
|
|
327
|
+
~id="AllStateViewSlices-appsyncPublishPolicy",
|
|
328
|
+
~statements=[
|
|
329
|
+
{
|
|
330
|
+
sid: "AllowPublishAppSyncEvents",
|
|
331
|
+
effect: Allow,
|
|
332
|
+
actions: Action("appsync:EventPublish"),
|
|
333
|
+
resources: Resource(apiArn ++ "/*"),
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
)
|
|
337
|
+
->PulumiAws.PolicyDocument.toJsonString
|
|
338
|
+
->Pulumi.Input.make,
|
|
339
|
+
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
340
|
+
},
|
|
341
|
+
)
|
|
342
|
+
})
|
|
343
|
+
| None => ()
|
|
344
|
+
}
|
|
345
|
+
|
|
289
346
|
let _connectResources = EventCollectorChannel.connect(
|
|
290
347
|
~name="AllStateViewSlices",
|
|
291
348
|
~channelSpecs,
|
|
@@ -347,7 +404,7 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
|
|
|
347
404
|
let packageDirs: dict<string> = Dict.make()
|
|
348
405
|
let allQueryDbResources: array<ReventlessInfra.Adapter.resource> = []
|
|
349
406
|
|
|
350
|
-
sliceInfos->Dict.forEachWithKey((info,
|
|
407
|
+
sliceInfos->Dict.forEachWithKey((info, name) => {
|
|
351
408
|
info.queryDbResources->Array.forEach(r => allQueryDbResources->Array.push(r)->ignore)
|
|
352
409
|
let specPkg = Util_Bundle.extractPackageName(info.specModulePath)
|
|
353
410
|
packageDirs->Dict.set(specPkg, Util_Bundle.resolvePackageRoot(specPkg))
|
|
@@ -369,18 +426,24 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
|
|
|
369
426
|
projectionModule: info.projectionModulePath,
|
|
370
427
|
queryDbTableName: tableName,
|
|
371
428
|
sourceUrn: urn,
|
|
429
|
+
stateTopicName: stateTopicNameFor(name),
|
|
372
430
|
}
|
|
373
431
|
entry
|
|
374
432
|
})
|
|
375
433
|
let _ = handlerOutputs->Array.push(handlerJson)
|
|
376
434
|
})
|
|
377
435
|
|
|
436
|
+
let anyPgStream =
|
|
437
|
+
sliceInfos
|
|
438
|
+
->Dict.keysToArray
|
|
439
|
+
->Array.some(n => QueryDbBackend.postgresStreamRegistry->Set.has(n))
|
|
378
440
|
buildLambda(
|
|
379
441
|
~parent,
|
|
380
442
|
~handlerOutputs,
|
|
381
443
|
~packageDirs,
|
|
382
444
|
~channelSpecs=[{channel: channel, eventTopics, resources: allQueryDbResources}],
|
|
383
445
|
~feedQueue,
|
|
446
|
+
~pgStreamConfig=anyPgStream ? eventsApiConfig.contents : None,
|
|
384
447
|
)
|
|
385
448
|
| None =>
|
|
386
449
|
log.warn(
|
|
@@ -445,6 +508,7 @@ let finish = () =>
|
|
|
445
508
|
queryDbTableName: tableName,
|
|
446
509
|
// No stream resource (Postgres) → dispatch on the feed queue ARN.
|
|
447
510
|
sourceUrn: urns->Array.get(0)->Option.getOr(feedArn),
|
|
511
|
+
stateTopicName: stateTopicNameFor(spec.componentName),
|
|
448
512
|
}
|
|
449
513
|
entry
|
|
450
514
|
})
|
|
@@ -457,12 +521,17 @@ let finish = () =>
|
|
|
457
521
|
}
|
|
458
522
|
})
|
|
459
523
|
|
|
524
|
+
let anyPgStream =
|
|
525
|
+
storedSpecs->Array.some(spec =>
|
|
526
|
+
QueryDbBackend.postgresStreamRegistry->Set.has(spec.componentName)
|
|
527
|
+
)
|
|
460
528
|
buildLambda(
|
|
461
529
|
~parent,
|
|
462
530
|
~handlerOutputs,
|
|
463
531
|
~packageDirs,
|
|
464
532
|
~channelSpecs=storedSpecs->Array.map(({channelSpec}) => channelSpec),
|
|
465
533
|
~feedQueue,
|
|
534
|
+
~pgStreamConfig=anyPgStream ? eventsApiConfig.contents : None,
|
|
466
535
|
~memorySize=maxMemorySize,
|
|
467
536
|
~timeout=maxTimeout,
|
|
468
537
|
)
|
|
@@ -13,8 +13,10 @@ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/co
|
|
|
13
13
|
import * as DcbBackend$ReventlessAws from "../DcbEventLog/DcbBackend.res.mjs";
|
|
14
14
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
15
15
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
16
|
+
import * as PgConnection$ReventlessAws from "../Postgres/PgConnection.res.mjs";
|
|
16
17
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
17
18
|
import * as QueryDbBackend$ReventlessAws from "../QueryDb/QueryDbBackend.res.mjs";
|
|
19
|
+
import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
|
|
18
20
|
import * as PgProjectionFeed$ReventlessAws from "../Postgres/PgProjectionFeed.res.mjs";
|
|
19
21
|
import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
|
|
20
22
|
import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
|
|
@@ -38,6 +40,22 @@ function commonPrefix(strings) {
|
|
|
38
40
|
|
|
39
41
|
let sliceInfos = {};
|
|
40
42
|
|
|
43
|
+
let eventsApiConfig = {
|
|
44
|
+
contents: undefined
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function setEventsApiConfig(cfg) {
|
|
48
|
+
eventsApiConfig.contents = cfg;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function stateTopicNameFor(name) {
|
|
52
|
+
if (QueryDbBackend$ReventlessAws.postgresStreamRegistry.has(name) && Stdlib_Option.isSome(eventsApiConfig.contents)) {
|
|
53
|
+
return Stdlib_Option.getOr(Stdlib_Option.map(Plugin_Helpers$ReventlessCore.queryFieldNamesRegistry[name], qn => qn.listFieldName), name);
|
|
54
|
+
} else {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
41
59
|
function registerStateViewSlice(name, specModulePath, projectionModulePath, queryDbTableName, queryDbResourcesOpt) {
|
|
42
60
|
let queryDbResources = queryDbResourcesOpt !== undefined ? queryDbResourcesOpt : [];
|
|
43
61
|
sliceInfos[name] = {
|
|
@@ -88,8 +106,9 @@ let finished = {
|
|
|
88
106
|
contents: false
|
|
89
107
|
};
|
|
90
108
|
|
|
91
|
-
function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, feedQueueOpt, memorySizeOpt, timeoutOpt) {
|
|
109
|
+
function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, feedQueueOpt, pgStreamConfigOpt, memorySizeOpt, timeoutOpt) {
|
|
92
110
|
let feedQueue = feedQueueOpt !== undefined ? Primitive_option.valFromOption(feedQueueOpt) : undefined;
|
|
111
|
+
let pgStreamConfig = pgStreamConfigOpt !== undefined ? Primitive_option.valFromOption(pgStreamConfigOpt) : undefined;
|
|
93
112
|
let memorySize = memorySizeOpt !== undefined ? memorySizeOpt : 1024;
|
|
94
113
|
let timeout = timeoutOpt !== undefined ? timeoutOpt : 30;
|
|
95
114
|
let opts_parent = parent;
|
|
@@ -97,31 +116,7 @@ function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, feedQueu
|
|
|
97
116
|
parent: opts_parent
|
|
98
117
|
};
|
|
99
118
|
let qdbSelection = QueryDbBackend$ReventlessAws.get();
|
|
100
|
-
let pgConnectionFragment = qdbSelection !== undefined ? qdbSelection.connectionConfig.apply(cc =>
|
|
101
|
-
let pgConnectionJson = JSON.stringify(Object.fromEntries([
|
|
102
|
-
[
|
|
103
|
-
"host",
|
|
104
|
-
cc.host
|
|
105
|
-
],
|
|
106
|
-
[
|
|
107
|
-
"port",
|
|
108
|
-
cc.port
|
|
109
|
-
],
|
|
110
|
-
[
|
|
111
|
-
"database",
|
|
112
|
-
cc.database
|
|
113
|
-
],
|
|
114
|
-
[
|
|
115
|
-
"username",
|
|
116
|
-
cc.username
|
|
117
|
-
],
|
|
118
|
-
[
|
|
119
|
-
"secretArn",
|
|
120
|
-
cc.secretArn
|
|
121
|
-
]
|
|
122
|
-
]));
|
|
123
|
-
return `,"pgConnection":` + pgConnectionJson;
|
|
124
|
-
}) : Pulumi.output("");
|
|
119
|
+
let pgConnectionFragment = qdbSelection !== undefined ? qdbSelection.connectionConfig.apply(cc => `,"pgConnection":` + JSON.stringify(PgConnection$ReventlessAws.connectionConfigToJson(undefined, cc))) : Pulumi.output("");
|
|
125
120
|
let handlerConfigOutput = Pulumi.all([
|
|
126
121
|
Pulumi.all(handlerOutputs),
|
|
127
122
|
pgConnectionFragment
|
|
@@ -140,11 +135,12 @@ function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, feedQueu
|
|
|
140
135
|
let s = Stdlib_Option.getOr(JSON.stringify(h.specModule.slice(baseLen, h.specModule.length)), `""`);
|
|
141
136
|
let p = Stdlib_Option.getOr(JSON.stringify(h.projectionModule.slice(baseLen, h.projectionModule.length)), `""`);
|
|
142
137
|
let q = Stdlib_Option.getOr(JSON.stringify(h.queryDbTableName), `""`);
|
|
138
|
+
let t = h.stateTopicName === "" ? "" : `,"t":` + JSON.stringify(h.stateTopicName);
|
|
143
139
|
if (sharedUrn !== "") {
|
|
144
|
-
return `{"s":` + s + `,"p":` + p + `,"q":` + q + `}`;
|
|
140
|
+
return `{"s":` + s + `,"p":` + p + `,"q":` + q + t + `}`;
|
|
145
141
|
}
|
|
146
142
|
let u = Stdlib_Option.getOr(JSON.stringify(h.sourceUrn), `""`);
|
|
147
|
-
return `{"s":` + s + `,"p":` + p + `,"q":` + q + `,"u":` + u + `}`;
|
|
143
|
+
return `{"s":` + s + `,"p":` + p + `,"q":` + q + `,"u":` + u + t + `}`;
|
|
148
144
|
}).join(",");
|
|
149
145
|
let baseJson = Stdlib_Option.getOr(JSON.stringify(base), `""`);
|
|
150
146
|
let urnJson = Stdlib_Option.getOr(JSON.stringify(sharedUrn), `""`);
|
|
@@ -152,6 +148,9 @@ function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, feedQueu
|
|
|
152
148
|
});
|
|
153
149
|
let envVars = {};
|
|
154
150
|
envVars["HANDLER_CONFIG"] = handlerConfigOutput;
|
|
151
|
+
if (pgStreamConfig !== undefined) {
|
|
152
|
+
envVars["APPSYNC_ENDPOINT"] = pgStreamConfig.endpoint;
|
|
153
|
+
}
|
|
155
154
|
packageDirs["@reventlessdev/reventless-aws"] = Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-aws");
|
|
156
155
|
packageDirs["@reventlessdev/reventless-core"] = Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-core");
|
|
157
156
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/StateViewSliceEntryPoint.mjs", packageDirs, undefined);
|
|
@@ -171,6 +170,17 @@ function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, feedQueu
|
|
|
171
170
|
role: runtime.parts.lambdaRole.id
|
|
172
171
|
}));
|
|
173
172
|
}
|
|
173
|
+
if (pgStreamConfig !== undefined) {
|
|
174
|
+
pgStreamConfig.apiArn.apply(apiArn => new (Aws.iam.RolePolicy)("AllStateViewSlices-appsyncPublish", {
|
|
175
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "AllStateViewSlices-appsyncPublishPolicy", [{
|
|
176
|
+
Sid: "AllowPublishAppSyncEvents",
|
|
177
|
+
Effect: "Allow",
|
|
178
|
+
Action: "appsync:EventPublish",
|
|
179
|
+
Resource: apiArn + "/*"
|
|
180
|
+
}])),
|
|
181
|
+
role: runtime.parts.lambdaRole.id
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
174
184
|
EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllStateViewSlices", channelSpecs, runtime, opts);
|
|
175
185
|
if (feedQueue !== undefined) {
|
|
176
186
|
return PgProjectionFeed$ReventlessAws.connect("AllStateViewSlicesFeed", feedQueue, runtime.parts.lambda, runtime.parts.lambdaRole, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts));
|
|
@@ -201,7 +211,7 @@ function finishWithDcbEventLog(dcbEventLog) {
|
|
|
201
211
|
let handlerOutputs = [];
|
|
202
212
|
let packageDirs = {};
|
|
203
213
|
let allQueryDbResources = [];
|
|
204
|
-
Stdlib_Dict.forEachWithKey(sliceInfos, (info,
|
|
214
|
+
Stdlib_Dict.forEachWithKey(sliceInfos, (info, name) => {
|
|
205
215
|
info.queryDbResources.forEach(r => {
|
|
206
216
|
allQueryDbResources.push(r);
|
|
207
217
|
});
|
|
@@ -219,15 +229,17 @@ function finishWithDcbEventLog(dcbEventLog) {
|
|
|
219
229
|
specModule: info.specModulePath,
|
|
220
230
|
projectionModule: info.projectionModulePath,
|
|
221
231
|
queryDbTableName: param[0],
|
|
222
|
-
sourceUrn: param[1]
|
|
232
|
+
sourceUrn: param[1],
|
|
233
|
+
stateTopicName: stateTopicNameFor(name)
|
|
223
234
|
}));
|
|
224
235
|
handlerOutputs.push(handlerJson);
|
|
225
236
|
});
|
|
237
|
+
let anyPgStream = Object.keys(sliceInfos).some(n => QueryDbBackend$ReventlessAws.postgresStreamRegistry.has(n));
|
|
226
238
|
buildLambda(parent, handlerOutputs, packageDirs, [{
|
|
227
239
|
channel: channel,
|
|
228
240
|
eventTopics: eventTopics,
|
|
229
241
|
resources: allQueryDbResources
|
|
230
|
-
}], Primitive_option.some(feedQueue), undefined, undefined);
|
|
242
|
+
}], Primitive_option.some(feedQueue), Primitive_option.some(anyPgStream ? eventsApiConfig.contents : undefined), undefined, undefined);
|
|
231
243
|
} else {
|
|
232
244
|
log.warn("StateViewSliceRuntime_Builder_Single", undefined, "finishWithDcbEventLog: DCB EventLog has no parent");
|
|
233
245
|
}
|
|
@@ -273,11 +285,13 @@ function finish() {
|
|
|
273
285
|
specModule: info.specModulePath,
|
|
274
286
|
projectionModule: info.projectionModulePath,
|
|
275
287
|
queryDbTableName: param[0],
|
|
276
|
-
sourceUrn: Stdlib_Option.getOr(param[1][0], param[2])
|
|
288
|
+
sourceUrn: Stdlib_Option.getOr(param[1][0], param[2]),
|
|
289
|
+
stateTopicName: stateTopicNameFor(spec.componentName)
|
|
277
290
|
}));
|
|
278
291
|
handlerOutputs.push(handlerJson);
|
|
279
292
|
});
|
|
280
|
-
|
|
293
|
+
let anyPgStream = storedSpecs.some(spec => QueryDbBackend$ReventlessAws.postgresStreamRegistry.has(spec.componentName));
|
|
294
|
+
buildLambda(parent, handlerOutputs, packageDirs, storedSpecs.map(param => param.channelSpec), Primitive_option.some(feedQueue), Primitive_option.some(anyPgStream ? eventsApiConfig.contents : undefined), match[0], match[1]);
|
|
281
295
|
} else {
|
|
282
296
|
log.warn("StateViewSliceRuntime_Builder_Single", undefined, `finish: grandParent not set`);
|
|
283
297
|
}
|
|
@@ -295,6 +309,9 @@ export {
|
|
|
295
309
|
log,
|
|
296
310
|
commonPrefix,
|
|
297
311
|
sliceInfos,
|
|
312
|
+
eventsApiConfig,
|
|
313
|
+
setEventsApiConfig,
|
|
314
|
+
stateTopicNameFor,
|
|
298
315
|
registerStateViewSlice,
|
|
299
316
|
storedSpecs,
|
|
300
317
|
grandParent,
|
|
@@ -354,8 +354,12 @@ let finish = (
|
|
|
354
354
|
"index.mjs",
|
|
355
355
|
Pulumi.Asset.stringAsset(handlerCode)->Pulumi.Archive.assetToAssetOrArchive,
|
|
356
356
|
)
|
|
357
|
+
// ESM self-containment: the handler imports @aws-sdk/util-dynamodb, provided
|
|
358
|
+
// by the nodejs22.x runtime only under /var/runtime — unreachable from
|
|
359
|
+
// /var/task ESM without the resolver hook. Ship the loader + set its env vars.
|
|
360
|
+
let loaderHash = Util_Bundle.addEsmLoaderAssets(archiveContents)
|
|
357
361
|
let code = Pulumi.Archive.assetArchive(archiveContents)
|
|
358
|
-
let sourceCodeHash = Util_Bundle.hashString(handlerCode)
|
|
362
|
+
let sourceCodeHash = Util_Bundle.hashString(handlerCode ++ "\n---\n" ++ loaderHash)
|
|
359
363
|
|
|
360
364
|
let layers =
|
|
361
365
|
Lambda.reventlessLayerArn
|
|
@@ -383,6 +387,8 @@ let finish = (
|
|
|
383
387
|
("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
|
|
384
388
|
("APPSYNC_ENDPOINT", appsyncEndpoint->Pulumi.Output.asInput),
|
|
385
389
|
("STATE_TOPIC_MAP", topicMapJson->Pulumi.Output.asInput),
|
|
390
|
+
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
391
|
+
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
386
392
|
]),
|
|
387
393
|
}: Lambda.Function.functionEnvironment
|
|
388
394
|
)->Pulumi.Input.make,
|
|
@@ -243,8 +243,9 @@ function finish(eventsApi, opts) {
|
|
|
243
243
|
});
|
|
244
244
|
let archiveContents = {};
|
|
245
245
|
archiveContents["index.mjs"] = new (Pulumi.asset.StringAsset)(handlerCode);
|
|
246
|
+
let loaderHash = Util_Bundle$ReventlessAws.addEsmLoaderAssets(archiveContents);
|
|
246
247
|
let code = new (Pulumi.asset.AssetArchive)(archiveContents);
|
|
247
|
-
let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(handlerCode);
|
|
248
|
+
let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(handlerCode + "\n---\n" + loaderHash);
|
|
248
249
|
let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
|
|
249
250
|
let appsyncEndpoint = AppSync_EventsApi$ReventlessAws.httpEndpoint(eventsApi);
|
|
250
251
|
let lambda = new (Aws.lambda.Function)(name + "StateTopicPublisher", {
|
|
@@ -269,6 +270,14 @@ function finish(eventsApi, opts) {
|
|
|
269
270
|
[
|
|
270
271
|
"STATE_TOPIC_MAP",
|
|
271
272
|
topicMapJson
|
|
273
|
+
],
|
|
274
|
+
[
|
|
275
|
+
"NODE_OPTIONS",
|
|
276
|
+
Util_Bundle$ReventlessAws.esmLoaderNodeOptions
|
|
277
|
+
],
|
|
278
|
+
[
|
|
279
|
+
"ESM_FALLBACK_DIRS",
|
|
280
|
+
Util_Bundle$ReventlessAws.esmFallbackDirs
|
|
272
281
|
]
|
|
273
282
|
])
|
|
274
283
|
},
|
|
@@ -116,8 +116,12 @@ export const handler = async (event) => {
|
|
|
116
116
|
"index.mjs",
|
|
117
117
|
Pulumi.Asset.stringAsset(entryPointCode)->Pulumi.Archive.assetToAssetOrArchive,
|
|
118
118
|
)
|
|
119
|
+
// ESM self-containment: the handler imports @aws-sdk/client-ecs, provided by the
|
|
120
|
+
// nodejs22.x runtime only under /var/runtime — unreachable from /var/task ESM
|
|
121
|
+
// without the resolver hook. Ship the loader + set its env vars on the Lambda.
|
|
122
|
+
let loaderHash = Util_Bundle.addEsmLoaderAssets(clonerArchiveContents)
|
|
119
123
|
let code = Pulumi.Archive.assetArchive(clonerArchiveContents)
|
|
120
|
-
let sourceCodeHash = Util_Bundle.hashString(entryPointCode)
|
|
124
|
+
let sourceCodeHash = Util_Bundle.hashString(entryPointCode ++ "\n---\n" ++ loaderHash)
|
|
121
125
|
|
|
122
126
|
let layers =
|
|
123
127
|
Lambda.reventlessLayerArn
|
|
@@ -231,6 +235,8 @@ export const handler = async (event) => {
|
|
|
231
235
|
->Pulumi.Input.make,
|
|
232
236
|
),
|
|
233
237
|
("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
|
|
238
|
+
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
239
|
+
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
234
240
|
]),
|
|
235
241
|
}: Lambda.Function.functionEnvironment
|
|
236
242
|
)->Pulumi.Input.make,
|
|
@@ -81,8 +81,9 @@ export const handler = async (event) => {
|
|
|
81
81
|
};`;
|
|
82
82
|
let clonerArchiveContents = {};
|
|
83
83
|
clonerArchiveContents["index.mjs"] = new (Pulumi.asset.StringAsset)(entryPointCode);
|
|
84
|
+
let loaderHash = Util_Bundle$ReventlessAws.addEsmLoaderAssets(clonerArchiveContents);
|
|
84
85
|
let code = new (Pulumi.asset.AssetArchive)(clonerArchiveContents);
|
|
85
|
-
let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(entryPointCode);
|
|
86
|
+
let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(entryPointCode + "\n---\n" + loaderHash);
|
|
86
87
|
let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
|
|
87
88
|
let vpcStackName = Stdlib_Option.getOrThrow(new Pulumi.Config("vpc").get("stack"), undefined);
|
|
88
89
|
let vpcConfig = Util_Vpc$ReventlessAws.getVpcConfig(vpcStackName, "vpc");
|
|
@@ -168,6 +169,14 @@ export const handler = async (event) => {
|
|
|
168
169
|
[
|
|
169
170
|
"Environment",
|
|
170
171
|
Pulumi.getStack()
|
|
172
|
+
],
|
|
173
|
+
[
|
|
174
|
+
"NODE_OPTIONS",
|
|
175
|
+
Util_Bundle$ReventlessAws.esmLoaderNodeOptions
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
"ESM_FALLBACK_DIRS",
|
|
179
|
+
Util_Bundle$ReventlessAws.esmFallbackDirs
|
|
171
180
|
]
|
|
172
181
|
])
|
|
173
182
|
},
|
package/src/util/Util_Bundle.res
CHANGED
|
@@ -112,6 +112,72 @@ let resolvePackageRoot = (packageName: string): string => {
|
|
|
112
112
|
let hashString = (str: string): string =>
|
|
113
113
|
createHash("sha256")->update(str)->digest("base64")
|
|
114
114
|
|
|
115
|
+
// ── ESM self-containment loader (Option C) ──────────────────────────────────
|
|
116
|
+
// Deployed Lambda entry points are ESM (`.mjs`) and statically/dynamically import
|
|
117
|
+
// bare specifiers (`@reventlessdev/*`, `effect`, `sury`, `@aws-sdk/*`) that live in
|
|
118
|
+
// the attached layer at /opt/nodejs/node_modules or the runtime SDK dir. ESM
|
|
119
|
+
// `import` ignores NODE_PATH and /opt, so those imports fail from /var/task with
|
|
120
|
+
// ERR_MODULE_NOT_FOUND. These two files install a `resolve` hook that, on an
|
|
121
|
+
// unresolved bare specifier, retries against each dir named in ESM_FALLBACK_DIRS
|
|
122
|
+
// (set on the Lambda env by RuntimeEnvironment_Lambda.makeFromCodeAsset). Shipped
|
|
123
|
+
// at the root of every code archive; NODE_OPTIONS=--import points Node at
|
|
124
|
+
// register-hook.mjs. Validated on a Node 22.17.1 rig; see
|
|
125
|
+
// docs/plans/deployed-lambda-esm-self-containment.md.
|
|
126
|
+
let registerHookFileName = "register-hook.mjs"
|
|
127
|
+
let layerResolverFileName = "layer-resolver.mjs"
|
|
128
|
+
|
|
129
|
+
let registerHookSource = `import { register } from "node:module";
|
|
130
|
+
register("./${layerResolverFileName}", import.meta.url);
|
|
131
|
+
`
|
|
132
|
+
|
|
133
|
+
let layerResolverSource = `// Fallback dirs (real Lambda: /opt/nodejs for the layer, /var/runtime for the SDK).
|
|
134
|
+
// Passed via env so the same hook is testable locally.
|
|
135
|
+
const FALLBACKS = (process.env.ESM_FALLBACK_DIRS || "").split(":").filter(Boolean);
|
|
136
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
137
|
+
try {
|
|
138
|
+
return await nextResolve(specifier, context);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
const bare = !/^[./]|^file:|^node:/.test(specifier);
|
|
141
|
+
if (err?.code !== "ERR_MODULE_NOT_FOUND" || !bare) throw err;
|
|
142
|
+
for (const dir of FALLBACKS) {
|
|
143
|
+
try {
|
|
144
|
+
// Re-resolve as if imported from a virtual file inside the fallback dir,
|
|
145
|
+
// so Node's own node_modules walk finds it (honours package.json exports).
|
|
146
|
+
const parentURL = new URL("file://" + dir + "/__resolver__.mjs").href;
|
|
147
|
+
return await nextResolve(specifier, { ...context, parentURL });
|
|
148
|
+
} catch { /* try next fallback */ }
|
|
149
|
+
}
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
`
|
|
154
|
+
|
|
155
|
+
// Env vars that activate the loader. Any Lambda whose archive includes the loader
|
|
156
|
+
// files (via addEsmLoaderAssets / buildCodeArchive) MUST also set these two; any
|
|
157
|
+
// that does NOT ship the loader MUST NOT set NODE_OPTIONS (it would --import a
|
|
158
|
+
// missing file and fail cold start). --import registers the resolve hook;
|
|
159
|
+
// ESM_FALLBACK_DIRS names the layer (/opt/nodejs) and runtime-SDK (/var/runtime)
|
|
160
|
+
// node_modules dirs the hook retries against for @aws-sdk/*.
|
|
161
|
+
let esmLoaderNodeOptions = `--import file:///var/task/${registerHookFileName}`
|
|
162
|
+
let esmFallbackDirs = "/opt/nodejs/node_modules:/var/runtime/node_modules"
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Add the ESM self-containment loader files to an archive-contents dict and return
|
|
166
|
+
* a hash fragment so callers can fold them into their sourceCodeHash. Every code
|
|
167
|
+
* archive whose Lambda sets the esmLoader env vars MUST include these files.
|
|
168
|
+
*/
|
|
169
|
+
let addEsmLoaderAssets = (archiveContents: dict<Pulumi.Archive.assetOrArchive>): string => {
|
|
170
|
+
archiveContents->Dict.set(
|
|
171
|
+
registerHookFileName,
|
|
172
|
+
Pulumi.Asset.stringAsset(registerHookSource)->Pulumi.Archive.assetToAssetOrArchive,
|
|
173
|
+
)
|
|
174
|
+
archiveContents->Dict.set(
|
|
175
|
+
layerResolverFileName,
|
|
176
|
+
Pulumi.Asset.stringAsset(layerResolverSource)->Pulumi.Archive.assetToAssetOrArchive,
|
|
177
|
+
)
|
|
178
|
+
hashString(registerHookSource ++ "\n---\n" ++ layerResolverSource)
|
|
179
|
+
}
|
|
180
|
+
|
|
115
181
|
let isSkippedDir = (n: string) =>
|
|
116
182
|
n == "node_modules" ||
|
|
117
183
|
n == "lib" ||
|
|
@@ -207,6 +273,8 @@ let buildCodeArchive = (
|
|
|
207
273
|
"index.mjs",
|
|
208
274
|
Pulumi.Asset.stringAsset(reExportCode)->Pulumi.Archive.assetToAssetOrArchive,
|
|
209
275
|
)
|
|
276
|
+
// ESM self-containment loader — shipped in every archive (see addEsmLoaderAssets).
|
|
277
|
+
let loaderHash = addEsmLoaderAssets(archiveContents)
|
|
210
278
|
extraStringAssets->Dict.forEachWithKey((content, fileName) => {
|
|
211
279
|
archiveContents->Dict.set(
|
|
212
280
|
fileName,
|
|
@@ -249,7 +317,9 @@ let buildCodeArchive = (
|
|
|
249
317
|
"\n---\n" ++
|
|
250
318
|
packageContentHashes.contents->Array.join(",") ++
|
|
251
319
|
"\n---\n" ++
|
|
252
|
-
extraHashEntries->Array.join("\n")
|
|
320
|
+
extraHashEntries->Array.join("\n") ++
|
|
321
|
+
"\n---\n" ++
|
|
322
|
+
loaderHash,
|
|
253
323
|
)
|
|
254
324
|
{code, sourceCodeHash}
|
|
255
325
|
}
|
|
@@ -78,6 +78,44 @@ function hashString(str) {
|
|
|
78
78
|
return Crypto.createHash("sha256").update(str).digest("base64");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
let registerHookFileName = "register-hook.mjs";
|
|
82
|
+
|
|
83
|
+
let layerResolverFileName = "layer-resolver.mjs";
|
|
84
|
+
|
|
85
|
+
let registerHookSource = `import { register } from "node:module";
|
|
86
|
+
register("./` + layerResolverFileName + `", import.meta.url);
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
let layerResolverSource = `// Fallback dirs (real Lambda: /opt/nodejs for the layer, /var/runtime for the SDK).
|
|
90
|
+
// Passed via env so the same hook is testable locally.
|
|
91
|
+
const FALLBACKS = (process.env.ESM_FALLBACK_DIRS || "").split(":").filter(Boolean);
|
|
92
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
93
|
+
try {
|
|
94
|
+
return await nextResolve(specifier, context);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
const bare = !/^[./]|^file:|^node:/.test(specifier);
|
|
97
|
+
if (err?.code !== "ERR_MODULE_NOT_FOUND" || !bare) throw err;
|
|
98
|
+
for (const dir of FALLBACKS) {
|
|
99
|
+
try {
|
|
100
|
+
// Re-resolve as if imported from a virtual file inside the fallback dir,
|
|
101
|
+
// so Node's own node_modules walk finds it (honours package.json exports).
|
|
102
|
+
const parentURL = new URL("file://" + dir + "/__resolver__.mjs").href;
|
|
103
|
+
return await nextResolve(specifier, { ...context, parentURL });
|
|
104
|
+
} catch { /* try next fallback */ }
|
|
105
|
+
}
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
let esmLoaderNodeOptions = `--import file:///var/task/` + registerHookFileName;
|
|
112
|
+
|
|
113
|
+
function addEsmLoaderAssets(archiveContents) {
|
|
114
|
+
archiveContents[registerHookFileName] = new (Pulumi.asset.StringAsset)(registerHookSource);
|
|
115
|
+
archiveContents[layerResolverFileName] = new (Pulumi.asset.StringAsset)(layerResolverSource);
|
|
116
|
+
return hashString(registerHookSource + "\n---\n" + layerResolverSource);
|
|
117
|
+
}
|
|
118
|
+
|
|
81
119
|
function isSkippedDir(n) {
|
|
82
120
|
if (n === "node_modules" || n === "lib" || n === "cjs" || n === "dts" || n === "tests" || n === "test" || n === "__mocks__" || n === "__tests__" || n === ".git") {
|
|
83
121
|
return true;
|
|
@@ -129,6 +167,7 @@ function buildCodeArchive(entryPointModule, packageDirs, extraStringAssetsOpt) {
|
|
|
129
167
|
let reExportCode = `export { handler } from "` + entryPointModule + `";`;
|
|
130
168
|
let archiveContents = {};
|
|
131
169
|
archiveContents["index.mjs"] = new (Pulumi.asset.StringAsset)(reExportCode);
|
|
170
|
+
let loaderHash = addEsmLoaderAssets(archiveContents);
|
|
132
171
|
Stdlib_Dict.forEachWithKey(extraStringAssets, (content, fileName) => {
|
|
133
172
|
archiveContents[fileName] = new (Pulumi.asset.StringAsset)(content);
|
|
134
173
|
});
|
|
@@ -157,13 +196,15 @@ function buildCodeArchive(entryPointModule, packageDirs, extraStringAssetsOpt) {
|
|
|
157
196
|
});
|
|
158
197
|
let code = new (Pulumi.asset.AssetArchive)(archiveContents);
|
|
159
198
|
packageContentHashes.contents.sort(Primitive_string.compare);
|
|
160
|
-
let sourceCodeHash = hashString(reExportCode + "\n---\n" + packageContentHashes.contents.join(",") + "\n---\n" + extraHashEntries.join("\n"));
|
|
199
|
+
let sourceCodeHash = hashString(reExportCode + "\n---\n" + packageContentHashes.contents.join(",") + "\n---\n" + extraHashEntries.join("\n") + "\n---\n" + loaderHash);
|
|
161
200
|
return {
|
|
162
201
|
code: code,
|
|
163
202
|
sourceCodeHash: sourceCodeHash
|
|
164
203
|
};
|
|
165
204
|
}
|
|
166
205
|
|
|
206
|
+
let esmFallbackDirs = "/opt/nodejs/node_modules:/var/runtime/node_modules";
|
|
207
|
+
|
|
167
208
|
export {
|
|
168
209
|
packageRootCache,
|
|
169
210
|
localRequire,
|
|
@@ -171,6 +212,13 @@ export {
|
|
|
171
212
|
extractPackageName,
|
|
172
213
|
resolvePackageRoot,
|
|
173
214
|
hashString,
|
|
215
|
+
registerHookFileName,
|
|
216
|
+
layerResolverFileName,
|
|
217
|
+
registerHookSource,
|
|
218
|
+
layerResolverSource,
|
|
219
|
+
esmLoaderNodeOptions,
|
|
220
|
+
esmFallbackDirs,
|
|
221
|
+
addEsmLoaderAssets,
|
|
174
222
|
isSkippedDir,
|
|
175
223
|
walkDir,
|
|
176
224
|
createFilteredPackageArchive,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Live-Postgres integration suite for the change-feed relay (B2.4).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// Run via `pnpm run test:integration:pg` (boots a Postgres sidecar, runs the PG
|
|
4
|
+
// suites serially, tears down). Excluded from the default parallel `pnpm test`;
|
|
5
|
+
// self-skips unless PG_URL is set (the script exports it).
|
|
6
6
|
//
|
|
7
7
|
// Exercises the deployed propagation path end-to-end minus AWS: DCB events are
|
|
8
8
|
// appended to a real `dcb_event` log (classic events to `event_log`), then
|