@reventlessdev/reventless-aws 3.0.0-alpha.281 → 3.0.0-alpha.282
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 +7 -0
- package/package.json +5 -5
- package/src/adapter/QueryDb/QueryInterceptor_Lambda.res +15 -9
- package/src/adapter/QueryDb/QueryInterceptor_Provisioning.res +39 -6
- package/src/adapter/QueryDb/QueryInterceptor_Provisioning.res.mjs +7 -2
- package/src/adapter/Runtime/QueryInterceptorEntryPoint.mjs +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
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.282 (2026-08-10)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** load query-interceptor extensions in init, and let it log ([c93612b](https://github.com/ReventlessDev/reventless-core/commit/c93612b3d2208cdc3f1c4b4dbef615b974c5e74a))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.281 (2026-08-10)
|
|
7
14
|
|
|
8
15
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.282",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
"uuid": "^13.0.0",
|
|
15
15
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.6",
|
|
16
16
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
17
18
|
"@reventlessdev/rescript-node": "2.0.0-alpha.3",
|
|
18
19
|
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.69",
|
|
19
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
20
20
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
21
21
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
22
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.223",
|
|
23
22
|
"@reventlessdev/reventless-infra": "3.0.0-alpha.133",
|
|
24
|
-
"@reventlessdev/reventless-interop": "3.0.0-alpha.30",
|
|
25
23
|
"@reventlessdev/reventless-postgres": "3.0.0-alpha.87",
|
|
26
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.107"
|
|
24
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.107",
|
|
25
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.30",
|
|
26
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.223"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"rescript": "12.3.0",
|
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
// Query resolver — one pipeline step whose only job is to consult the hook.
|
|
3
3
|
//
|
|
4
4
|
// The hook is a module-level `ref`, and in a deployed runtime the only thing that
|
|
5
|
-
// ever fills it is a `RuntimeExtension`'s `onColdStart`.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
5
|
+
// ever fills it is a `RuntimeExtension`'s `onColdStart`. So the handler awaits
|
|
6
|
+
// `runtimeExtensionsReady` before reading it. Without that the extensions the
|
|
7
|
+
// archive already carries are never imported, the hook stays `None`, and
|
|
8
|
+
// interception degrades to a passthrough that costs an invocation on every read
|
|
9
|
+
// and observes nothing — the one failure mode this path cannot afford, because it
|
|
10
|
+
// is silent and its whole price has already been paid.
|
|
11
|
+
//
|
|
12
|
+
// The await is kept here even though `QueryInterceptorEntryPoint.mjs` now awaits
|
|
13
|
+
// the same promise at top level, because the two are answering different
|
|
14
|
+
// questions. The shell's await decides WHEN the seam fires — init rather than
|
|
15
|
+
// invoke, which is what keeps the load off the read path. This one is the
|
|
16
|
+
// module's own contract: it does not consult a hook it has not waited for,
|
|
17
|
+
// however it was entered. On the path they share, the second await is a settled
|
|
18
|
+
// promise and costs a microtask.
|
|
13
19
|
//
|
|
14
20
|
// Awaited from the small module rather than from `HandlerFactoryHelpers`, which
|
|
15
21
|
// re-exports the same binding behind the Effect runtime and the DynamoDB clients:
|
|
16
|
-
//
|
|
22
|
+
// there is no reason to pull that graph in to reach one promise.
|
|
17
23
|
|
|
18
24
|
type payload = {
|
|
19
25
|
readModelName: string,
|
|
@@ -64,23 +64,56 @@ let buildInterceptor = (~api: Types.AppSync.api, ~opts) => {
|
|
|
64
64
|
),
|
|
65
65
|
])
|
|
66
66
|
let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
67
|
-
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/
|
|
67
|
+
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/QueryInterceptorEntryPoint.mjs",
|
|
68
68
|
~packageDirs,
|
|
69
69
|
)
|
|
70
70
|
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
71
|
+
// Framework default sizing, deliberately, and it took an outage to get here.
|
|
72
|
+
//
|
|
73
|
+
// What this runtime DECIDES is trivial — consult a hook, return a verdict — and
|
|
74
|
+
// it was once sized for exactly that: 256MB and a 10-second timeout, on the
|
|
75
|
+
// reasoning that a read path cannot afford anything more. But a runtime is not
|
|
76
|
+
// sized for what it decides, it is sized for what it LOADS, and this one loads
|
|
77
|
+
// whatever module graph the registered extensions drag behind them. An
|
|
78
|
+
// accounting extension reaching a DynamoDB client is enough to put the whole
|
|
79
|
+
// cloud SDK in that graph. Lambda CPU scales with memory, so at 256MB the load
|
|
80
|
+
// ran at a fraction of a core and did not finish inside ten seconds — and
|
|
81
|
+
// because an unfinished import neither completes nor throws, the failure was a
|
|
82
|
+
// bare timeout on EVERY read, with an empty log group to explain it.
|
|
83
|
+
//
|
|
84
|
+
// The defaults are also close to cost-neutral for the thing being paid for
|
|
85
|
+
// here. Lambda bills memory×duration, so a decision that fits in single-digit
|
|
86
|
+
// milliseconds costs about the same at either size; what changes is whether the
|
|
87
|
+
// cold start fits. The entry shell spends it in init (see its own docstring),
|
|
88
|
+
// where the timeout is headroom for the retry Lambda performs when init
|
|
89
|
+
// overruns its own budget, rather than a ceiling on the read.
|
|
74
90
|
let runtime = RuntimeEnvironment_Lambda.makeFromCodeAsset(
|
|
75
91
|
~name,
|
|
76
92
|
~unitKind=ReventlessCore.Monitoring.Other("QueryInterceptor"),
|
|
77
93
|
~componentKind=ReventlessCore.ComponentType.Plugin,
|
|
78
94
|
~code,
|
|
79
95
|
~sourceCodeHash,
|
|
80
|
-
~memorySize=256,
|
|
81
|
-
~timeout=10,
|
|
82
96
|
~opts=componentOpts,
|
|
83
97
|
)
|
|
98
|
+
|
|
99
|
+
// The execution role's logging grant, which `makeFromCodeAsset` does NOT give
|
|
100
|
+
// it: every runtime builder in the framework attaches this at its own call
|
|
101
|
+
// site, and this one — hand-rolled rather than grown from a builder — was the
|
|
102
|
+
// one that forgot. The result was a component sitting in front of every read on
|
|
103
|
+
// the platform whose log group never had a single stream, so the timeout above
|
|
104
|
+
// could only be found by invoking the function by hand and reading the tail of
|
|
105
|
+
// a response. Silence on the read path is not a small defect; it is the thing
|
|
106
|
+
// that decides how long the next outage lasts.
|
|
107
|
+
let _logging = IAM.RolePolicy.make(
|
|
108
|
+
~name=name ++ "Logging",
|
|
109
|
+
~args={
|
|
110
|
+
IAM.RolePolicy.policy: PulumiAws.Lambda.defaultLoggingPolicyDocument
|
|
111
|
+
->PolicyDocument.toJsonString
|
|
112
|
+
->Pulumi.Input.make,
|
|
113
|
+
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
114
|
+
},
|
|
115
|
+
~opts,
|
|
116
|
+
)
|
|
84
117
|
let lambdaArn = runtime.parts.lambda->Pulumi.Output.flatMap(lambda => lambda.arn)
|
|
85
118
|
|
|
86
119
|
let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
|
|
@@ -5,6 +5,7 @@ import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.r
|
|
|
5
5
|
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
6
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
7
|
import * as Pulumi from "@pulumi/pulumi";
|
|
8
|
+
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
8
9
|
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
9
10
|
import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
|
|
10
11
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
@@ -38,11 +39,15 @@ function buildInterceptor(api, opts) {
|
|
|
38
39
|
"@reventlessdev/reventless-aws",
|
|
39
40
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
40
41
|
]]);
|
|
41
|
-
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/
|
|
42
|
+
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/QueryInterceptorEntryPoint.mjs", packageDirs, undefined, undefined);
|
|
42
43
|
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, {
|
|
43
44
|
TAG: "Other",
|
|
44
45
|
_0: "QueryInterceptor"
|
|
45
|
-
}, "Plugin", match.code, match.sourceCodeHash, undefined,
|
|
46
|
+
}, "Plugin", match.code, match.sourceCodeHash, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, componentOpts);
|
|
47
|
+
new (Aws.iam.RolePolicy)(name + "Logging", {
|
|
48
|
+
policy: PolicyDocument$PulumiAws.toJsonString(Lambda$PulumiAws.defaultLoggingPolicyDocument),
|
|
49
|
+
role: runtime.parts.lambdaRole.id
|
|
50
|
+
}, opts);
|
|
46
51
|
let lambdaArn = Output$Pulumi.flatMap(runtime.parts.lambda, lambda => lambda.arn);
|
|
47
52
|
let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), AWS_Tags$ReventlessAws.make(name + "DataSource", "Plugin", "Identity", "Plugin", undefined, undefined, undefined, undefined), opts);
|
|
48
53
|
Pulumi.all([
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Query-interceptor Lambda entry point.
|
|
2
|
+
//
|
|
3
|
+
// One job, and it is a scheduling one: pull the runtime-extension cold start
|
|
4
|
+
// into the INIT phase.
|
|
5
|
+
//
|
|
6
|
+
// `runtimeExtensionsReady` is an async IIFE started at module load and awaited
|
|
7
|
+
// by nobody, so evaluating a module that merely imports it does not wait for it.
|
|
8
|
+
// Awaiting it from the handler instead — which is what this runtime did before
|
|
9
|
+
// there was a shell — leaves the whole extension load in the INVOKE phase, where
|
|
10
|
+
// it runs without the init CPU boost and against the function timeout. An
|
|
11
|
+
// extension whose module graph reaches a cloud SDK does not finish inside a
|
|
12
|
+
// budget sized for a decision, and the way that fails is the worst shape
|
|
13
|
+
// available: the load neither completes nor throws, so every read times out
|
|
14
|
+
// having logged nothing at all.
|
|
15
|
+
//
|
|
16
|
+
// A top-level await is what moves it. It propagates to the generated `index.mjs`
|
|
17
|
+
// re-export, so init is not finished until the seam has fired — which is also
|
|
18
|
+
// the only way the handler's own await can be a formality rather than the place
|
|
19
|
+
// the work happens.
|
|
20
|
+
//
|
|
21
|
+
// It cannot reject: the seam catches and reports each extension's load failure
|
|
22
|
+
// individually, and resolves having skipped it. So a broken extension still
|
|
23
|
+
// costs interception nothing more than the counting it was going to do.
|
|
24
|
+
|
|
25
|
+
import { runtimeExtensionsReady } from "./RuntimeExtensionsReady.mjs";
|
|
26
|
+
|
|
27
|
+
await runtimeExtensionsReady;
|
|
28
|
+
|
|
29
|
+
export { handler } from "../QueryDb/QueryInterceptor_Lambda.res.mjs";
|