@reventlessdev/reventless-core 3.0.0-alpha.219 → 3.0.0-alpha.220
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 +7 -7
- package/src/adapter/RuntimeExtension/RuntimeExtension.res +34 -0
- package/src/adapter/RuntimeExtension/RuntimeExtension.res.mjs +5 -0
- package/tests/adapter/RuntimeExtensionHookReachTest.res +1 -0
- package/tests/adapter/RuntimeExtensionHookReachTest.res.mjs +2 -0
- package/tests/adapter/RuntimeExtensionTest.res +31 -1
- package/tests/adapter/RuntimeExtensionTest.res.mjs +32 -8
- package/tests/commandtopic/CommandOutcomeHookTest.res +1 -0
- package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +2 -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.220 (2026-08-09)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **core,aws:** bundle a runtime extension's companion packages, guard imports at deploy ([e975175](https://github.com/ReventlessDev/reventless-core/commit/e9751758f51582a8e46db362219f725bb5f1bcde))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.219 (2026-08-09)
|
|
7
14
|
|
|
8
15
|
**Note:** Version bump only for package @reventlessdev/reventless-core
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.220",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
"sury": "11.0.0-alpha.4",
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
31
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
32
|
-
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.2",
|
|
33
32
|
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
|
|
34
33
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
35
|
-
"@reventlessdev/rescript-
|
|
36
|
-
"@reventlessdev/rescript-ssh2": "2.0.0-alpha.2",
|
|
34
|
+
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.3",
|
|
37
35
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
38
|
-
"@reventlessdev/
|
|
36
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.3",
|
|
37
|
+
"@reventlessdev/rescript-ssh2": "2.0.0-alpha.3",
|
|
39
38
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
40
|
-
"@reventlessdev/reventless-
|
|
41
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
39
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.131",
|
|
40
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.105",
|
|
41
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.30"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "12.3.0",
|
|
@@ -90,6 +90,28 @@ trade than a loud, skipped hook.
|
|
|
90
90
|
*/
|
|
91
91
|
module type Extension = {
|
|
92
92
|
let moduleUrl: string
|
|
93
|
+
/**
|
|
94
|
+
`import.meta.url` of one module per COMPANION package this extension imports at
|
|
95
|
+
runtime — packages that must ride into the code archive alongside the
|
|
96
|
+
extension's own. `[]` for an extension importing nothing outside the
|
|
97
|
+
framework-provided set.
|
|
98
|
+
|
|
99
|
+
Needed because a companion is invisible to every conventional lookup: the
|
|
100
|
+
ecosystem convention for split packages declares the neutral core as a
|
|
101
|
+
`peerDependency` (true in the deploy program's process, false in the Lambda),
|
|
102
|
+
so a `dependencies` walk finds nothing; and a companion named by bare package
|
|
103
|
+
name has no resolution root the framework could use — the one thing that
|
|
104
|
+
resolves robustly is a module URL the companion states about itself, exactly
|
|
105
|
+
like `moduleUrl`. A companion package exposes one by exporting
|
|
106
|
+
`let moduleUrl: string = %raw("import.meta.url")` from any module the
|
|
107
|
+
extension already imports (spec modules can lift the
|
|
108
|
+
`@@reventless.spec`-injected one).
|
|
109
|
+
|
|
110
|
+
Companions are bundle-only: the entry shell imports only the extension
|
|
111
|
+
modules, and Node resolves companions from the archive's `node_modules` like
|
|
112
|
+
any other import.
|
|
113
|
+
*/
|
|
114
|
+
let companionModuleUrls: array<string>
|
|
93
115
|
let onColdStart: coldStartHook
|
|
94
116
|
}
|
|
95
117
|
|
|
@@ -127,6 +149,18 @@ let moduleUrls = (): array<string> =>
|
|
|
127
149
|
E.moduleUrl
|
|
128
150
|
})
|
|
129
151
|
|
|
152
|
+
/**
|
|
153
|
+
Every registered extension's companion module URLs, flattened in registration
|
|
154
|
+
order. Read only by the provider's code-archive builder — unlike `moduleUrls`,
|
|
155
|
+
these never reach a runtime's config: companions are bundled so the extension's
|
|
156
|
+
own imports resolve, not imported by the entry shell.
|
|
157
|
+
*/
|
|
158
|
+
let companionModuleUrls = (): array<string> =>
|
|
159
|
+
extensions.contents->Array.flatMap(e => {
|
|
160
|
+
module E = unpack(e)
|
|
161
|
+
E.companionModuleUrls
|
|
162
|
+
})
|
|
163
|
+
|
|
130
164
|
let log = Logger.fromEnv()
|
|
131
165
|
|
|
132
166
|
/**
|
|
@@ -26,6 +26,10 @@ function moduleUrls() {
|
|
|
26
26
|
return extensions.contents.map(e => e.moduleUrl);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function companionModuleUrls() {
|
|
30
|
+
return extensions.contents.flatMap(e => e.companionModuleUrls);
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
let log = Logger$ReventlessCore.fromEnv();
|
|
30
34
|
|
|
31
35
|
function runHook(hook, index, runtimeKind, component, plugin, platform) {
|
|
@@ -52,6 +56,7 @@ export {
|
|
|
52
56
|
reset,
|
|
53
57
|
isEmpty,
|
|
54
58
|
moduleUrls,
|
|
59
|
+
companionModuleUrls,
|
|
55
60
|
log,
|
|
56
61
|
runHook,
|
|
57
62
|
notifyColdStartHooks,
|
|
@@ -47,6 +47,7 @@ describe("RuntimeExtension reaches the runtime callback hooks", () => {
|
|
|
47
47
|
let seenAtColdStart: array<string> = []
|
|
48
48
|
module Gatekeeper: RuntimeExtension.Extension = {
|
|
49
49
|
let moduleUrl = "file:///pkg/gatekeeper.res.mjs"
|
|
50
|
+
let companionModuleUrls = []
|
|
50
51
|
let onColdStart = (~runtimeKind as _, ~component, ~plugin as _, ~platform as _) => {
|
|
51
52
|
seenAtColdStart->Array.push(component)
|
|
52
53
|
CommandGenerator_Callback.registerCommandInterceptor(async (
|
|
@@ -53,6 +53,7 @@ globalThis.describe("RuntimeExtension reaches the runtime callback hooks", () =>
|
|
|
53
53
|
});
|
|
54
54
|
globalThis.test("an interceptor registered at cold start denies a later command", async () => {
|
|
55
55
|
let seenAtColdStart = [];
|
|
56
|
+
let companionModuleUrls = [];
|
|
56
57
|
let onColdStart = (param, component, param$1, param$2) => {
|
|
57
58
|
seenAtColdStart.push(component);
|
|
58
59
|
CommandGenerator_Callback$ReventlessCore.registerCommandInterceptor(async (param, componentName, param$1, tag, param$2) => ({
|
|
@@ -62,6 +63,7 @@ globalThis.describe("RuntimeExtension reaches the runtime callback hooks", () =>
|
|
|
62
63
|
};
|
|
63
64
|
let Gatekeeper = {
|
|
64
65
|
moduleUrl: "file:///pkg/gatekeeper.res.mjs",
|
|
66
|
+
companionModuleUrls: companionModuleUrls,
|
|
65
67
|
onColdStart: onColdStart
|
|
66
68
|
};
|
|
67
69
|
RuntimeExtension$ReventlessCore.use(Gatekeeper);
|
|
@@ -7,11 +7,12 @@ type call = {
|
|
|
7
7
|
platform: option<string>,
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
let recorderNamed = (label: string, into: array<(string, call)>): module(
|
|
10
|
+
let recorderNamed = (~companions: array<string>=[], label: string, into: array<(string, call)>): module(
|
|
11
11
|
RuntimeExtension.Extension
|
|
12
12
|
) => {
|
|
13
13
|
module R = {
|
|
14
14
|
let moduleUrl = `file:///pkg/${label}.res.mjs`
|
|
15
|
+
let companionModuleUrls = companions
|
|
15
16
|
let onColdStart = (~runtimeKind, ~component, ~plugin, ~platform) =>
|
|
16
17
|
into->Array.push((label, {runtimeKind, component, plugin, platform}))
|
|
17
18
|
}
|
|
@@ -32,6 +33,7 @@ describe("RuntimeExtension", () => {
|
|
|
32
33
|
testSync("empty registry is the default and notifyColdStart is silent", () => {
|
|
33
34
|
expect(RuntimeExtension.isEmpty())->toBe(true)
|
|
34
35
|
expect(RuntimeExtension.moduleUrls())->toEqual([])
|
|
36
|
+
expect(RuntimeExtension.companionModuleUrls())->toEqual([])
|
|
35
37
|
fireAggregate()
|
|
36
38
|
expect(true)->toBe(true)
|
|
37
39
|
})
|
|
@@ -77,10 +79,38 @@ describe("RuntimeExtension", () => {
|
|
|
77
79
|
])
|
|
78
80
|
})
|
|
79
81
|
|
|
82
|
+
testSync("companionModuleUrls flattens every registration's companions, in order", () => {
|
|
83
|
+
let calls: array<(string, call)> = []
|
|
84
|
+
RuntimeExtension.use(
|
|
85
|
+
recorderNamed(
|
|
86
|
+
~companions=["file:///pkg/companion-a.res.mjs", "file:///pkg/companion-b.res.mjs"],
|
|
87
|
+
"tracing",
|
|
88
|
+
calls,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
RuntimeExtension.use(recorderNamed("accounting", calls))
|
|
92
|
+
RuntimeExtension.use(
|
|
93
|
+
recorderNamed(~companions=["file:///pkg/companion-c.res.mjs"], "quota", calls),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
expect(RuntimeExtension.companionModuleUrls())->toEqual([
|
|
97
|
+
"file:///pkg/companion-a.res.mjs",
|
|
98
|
+
"file:///pkg/companion-b.res.mjs",
|
|
99
|
+
"file:///pkg/companion-c.res.mjs",
|
|
100
|
+
])
|
|
101
|
+
// Companions are bundle-only: the entry shell's import list is untouched.
|
|
102
|
+
expect(RuntimeExtension.moduleUrls())->toEqual([
|
|
103
|
+
"file:///pkg/tracing.res.mjs",
|
|
104
|
+
"file:///pkg/accounting.res.mjs",
|
|
105
|
+
"file:///pkg/quota.res.mjs",
|
|
106
|
+
])
|
|
107
|
+
})
|
|
108
|
+
|
|
80
109
|
testSync("a throwing extension is skipped; the runtime and its siblings survive", () => {
|
|
81
110
|
let calls: array<(string, call)> = []
|
|
82
111
|
module Broken: RuntimeExtension.Extension = {
|
|
83
112
|
let moduleUrl = "file:///pkg/broken.res.mjs"
|
|
113
|
+
let companionModuleUrls = []
|
|
84
114
|
let onColdStart = (~runtimeKind as _, ~component as _, ~plugin as _, ~platform as _) =>
|
|
85
115
|
JsError.throwWithMessage("extension blew up")
|
|
86
116
|
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
4
4
|
import * as RuntimeExtension$ReventlessCore from "../../src/adapter/RuntimeExtension/RuntimeExtension.res.mjs";
|
|
5
5
|
|
|
6
|
-
function recorderNamed(label, into) {
|
|
6
|
+
function recorderNamed(companionsOpt, label, into) {
|
|
7
|
+
let companions = companionsOpt !== undefined ? companionsOpt : [];
|
|
7
8
|
let moduleUrl = `file:///pkg/` + label + `.res.mjs`;
|
|
8
9
|
let onColdStart = (runtimeKind, component, plugin, platform) => {
|
|
9
10
|
into.push([
|
|
@@ -18,6 +19,7 @@ function recorderNamed(label, into) {
|
|
|
18
19
|
};
|
|
19
20
|
return {
|
|
20
21
|
moduleUrl: moduleUrl,
|
|
22
|
+
companionModuleUrls: companions,
|
|
21
23
|
onColdStart: onColdStart
|
|
22
24
|
};
|
|
23
25
|
}
|
|
@@ -31,12 +33,13 @@ globalThis.describe("RuntimeExtension", () => {
|
|
|
31
33
|
globalThis.test("empty registry is the default and notifyColdStart is silent", () => {
|
|
32
34
|
globalThis.expect(RuntimeExtension$ReventlessCore.isEmpty()).toBe(true);
|
|
33
35
|
globalThis.expect(RuntimeExtension$ReventlessCore.moduleUrls()).toEqual([]);
|
|
36
|
+
globalThis.expect(RuntimeExtension$ReventlessCore.companionModuleUrls()).toEqual([]);
|
|
34
37
|
fireAggregate();
|
|
35
38
|
globalThis.expect(true).toBe(true);
|
|
36
39
|
});
|
|
37
40
|
globalThis.test("a registered extension receives the runtime's full identity", () => {
|
|
38
41
|
let calls = [];
|
|
39
|
-
RuntimeExtension$ReventlessCore.use(recorderNamed("tracing", calls));
|
|
42
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed(undefined, "tracing", calls));
|
|
40
43
|
fireAggregate();
|
|
41
44
|
globalThis.expect(calls).toEqual([[
|
|
42
45
|
"tracing",
|
|
@@ -50,8 +53,8 @@ globalThis.describe("RuntimeExtension", () => {
|
|
|
50
53
|
});
|
|
51
54
|
globalThis.test("several extensions compose, in registration order", () => {
|
|
52
55
|
let calls = [];
|
|
53
|
-
RuntimeExtension$ReventlessCore.use(recorderNamed("tracing", calls));
|
|
54
|
-
RuntimeExtension$ReventlessCore.use(recorderNamed("accounting", calls));
|
|
56
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed(undefined, "tracing", calls));
|
|
57
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed(undefined, "accounting", calls));
|
|
55
58
|
fireAggregate();
|
|
56
59
|
globalThis.expect(calls.map(param => param[0])).toEqual([
|
|
57
60
|
"tracing",
|
|
@@ -60,23 +63,44 @@ globalThis.describe("RuntimeExtension", () => {
|
|
|
60
63
|
});
|
|
61
64
|
globalThis.test("moduleUrls reports every registration, in order", () => {
|
|
62
65
|
let calls = [];
|
|
63
|
-
RuntimeExtension$ReventlessCore.use(recorderNamed("tracing", calls));
|
|
64
|
-
RuntimeExtension$ReventlessCore.use(recorderNamed("accounting", calls));
|
|
66
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed(undefined, "tracing", calls));
|
|
67
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed(undefined, "accounting", calls));
|
|
65
68
|
globalThis.expect(RuntimeExtension$ReventlessCore.isEmpty()).toBe(false);
|
|
66
69
|
globalThis.expect(RuntimeExtension$ReventlessCore.moduleUrls()).toEqual([
|
|
67
70
|
"file:///pkg/tracing.res.mjs",
|
|
68
71
|
"file:///pkg/accounting.res.mjs"
|
|
69
72
|
]);
|
|
70
73
|
});
|
|
74
|
+
globalThis.test("companionModuleUrls flattens every registration's companions, in order", () => {
|
|
75
|
+
let calls = [];
|
|
76
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed([
|
|
77
|
+
"file:///pkg/companion-a.res.mjs",
|
|
78
|
+
"file:///pkg/companion-b.res.mjs"
|
|
79
|
+
], "tracing", calls));
|
|
80
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed(undefined, "accounting", calls));
|
|
81
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed(["file:///pkg/companion-c.res.mjs"], "quota", calls));
|
|
82
|
+
globalThis.expect(RuntimeExtension$ReventlessCore.companionModuleUrls()).toEqual([
|
|
83
|
+
"file:///pkg/companion-a.res.mjs",
|
|
84
|
+
"file:///pkg/companion-b.res.mjs",
|
|
85
|
+
"file:///pkg/companion-c.res.mjs"
|
|
86
|
+
]);
|
|
87
|
+
globalThis.expect(RuntimeExtension$ReventlessCore.moduleUrls()).toEqual([
|
|
88
|
+
"file:///pkg/tracing.res.mjs",
|
|
89
|
+
"file:///pkg/accounting.res.mjs",
|
|
90
|
+
"file:///pkg/quota.res.mjs"
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
71
93
|
globalThis.test("a throwing extension is skipped; the runtime and its siblings survive", () => {
|
|
72
94
|
let calls = [];
|
|
95
|
+
let companionModuleUrls = [];
|
|
73
96
|
let onColdStart = (param, param$1, param$2, param$3) => Stdlib_JsError.throwWithMessage("extension blew up");
|
|
74
97
|
let Broken = {
|
|
75
98
|
moduleUrl: "file:///pkg/broken.res.mjs",
|
|
99
|
+
companionModuleUrls: companionModuleUrls,
|
|
76
100
|
onColdStart: onColdStart
|
|
77
101
|
};
|
|
78
102
|
RuntimeExtension$ReventlessCore.use(Broken);
|
|
79
|
-
RuntimeExtension$ReventlessCore.use(recorderNamed("accounting", calls));
|
|
103
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed(undefined, "accounting", calls));
|
|
80
104
|
fireAggregate();
|
|
81
105
|
globalThis.expect(calls.map(param => param[0])).toEqual(["accounting"]);
|
|
82
106
|
});
|
|
@@ -96,7 +120,7 @@ globalThis.describe("RuntimeExtension", () => {
|
|
|
96
120
|
});
|
|
97
121
|
globalThis.test("reset clears the registry", () => {
|
|
98
122
|
let calls = [];
|
|
99
|
-
RuntimeExtension$ReventlessCore.use(recorderNamed("tracing", calls));
|
|
123
|
+
RuntimeExtension$ReventlessCore.use(recorderNamed(undefined, "tracing", calls));
|
|
100
124
|
RuntimeExtension$ReventlessCore.reset();
|
|
101
125
|
fireAggregate();
|
|
102
126
|
globalThis.expect(RuntimeExtension$ReventlessCore.isEmpty()).toBe(true);
|
|
@@ -228,6 +228,7 @@ describe("CommandTopic command-outcome hook:", () => {
|
|
|
228
228
|
// seam already reaches, so an extension picks it up alongside the other four.
|
|
229
229
|
module Accountant: RuntimeExtension.Extension = {
|
|
230
230
|
let moduleUrl = "file:///pkg/accountant.res.mjs"
|
|
231
|
+
let companionModuleUrls = []
|
|
231
232
|
let onColdStart = (~runtimeKind as _, ~component as _, ~plugin as _, ~platform as _) =>
|
|
232
233
|
record()
|
|
233
234
|
}
|
|
@@ -321,9 +321,11 @@ globalThis.describe("CommandTopic command-outcome hook:", () => {
|
|
|
321
321
|
globalThis.expect(observed.contents.length).toBe(0);
|
|
322
322
|
});
|
|
323
323
|
globalThis.test("an out-of-tree extension can register it from onColdStart", async () => {
|
|
324
|
+
let companionModuleUrls = [];
|
|
324
325
|
let onColdStart = (param, param$1, param$2, param$3) => record();
|
|
325
326
|
let Accountant = {
|
|
326
327
|
moduleUrl: "file:///pkg/accountant.res.mjs",
|
|
328
|
+
companionModuleUrls: companionModuleUrls,
|
|
327
329
|
onColdStart: onColdStart
|
|
328
330
|
};
|
|
329
331
|
RuntimeExtension$ReventlessCore.use(Accountant);
|