@reventlessdev/reventless-infra 3.0.0-alpha.161 → 3.0.0-alpha.163
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 +21 -0
- package/package.json +4 -4
- package/src/components/Api.res +9 -6
- package/src/components/CommandTopic.res +5 -1
- package/src/components/Component.res +1 -2
- package/src/components/DcbEventLog.res +5 -1
- package/src/components/DeployBootstrap.res +4 -5
- package/src/components/EventTopic.res +5 -1
- package/src/components/Scheduler.res +4 -1
- package/src/types/ExtensionMapping.res +18 -17
- package/src/types/ExtensionPointMapping.res +31 -11
- package/src/types/Platform.res +24 -28
- package/tests/DeployBootstrapTest.res +70 -52
- package/tests/RuntimeHintsTest.res +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,27 @@
|
|
|
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.163 (2026-09-11)
|
|
7
|
+
|
|
8
|
+
* feat(spec)!: making a principal is a capability, so the provider is replaceable ([3e59b9b](https://github.com/ReventlessDev/reventless-core/commit/3e59b9b665cc7e4b6b6f156b63b1b6bd0c6181ca))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* Capabilities.t gains an identityProvider member. A platform
|
|
13
|
+
that builds the record literally no longer compiles until it supplies one;
|
|
14
|
+
Capabilities.none.identityProvider is the refusing value to pass while no
|
|
15
|
+
backend exists. Code that spreads Capabilities.none is unaffected.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# 3.0.0-alpha.162 (2026-09-09)
|
|
20
|
+
|
|
21
|
+
**Note:** Version bump only for package @reventlessdev/reventless-infra
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
6
27
|
# 3.0.0-alpha.161 (2026-09-08)
|
|
7
28
|
|
|
8
29
|
**Note:** Version bump only for package @reventlessdev/reventless-infra
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-infra",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.163",
|
|
4
4
|
"description": "Infrastructure types for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
"sury": "11.0.0-rc.2",
|
|
17
17
|
"sury-ppx": "11.0.0-rc.2",
|
|
18
18
|
"uuid": "^13.0.0",
|
|
19
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.
|
|
19
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.33",
|
|
20
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.20",
|
|
20
21
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
21
|
-
"@reventlessdev/
|
|
22
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.133"
|
|
22
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.135"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"rescript": "12.3.0",
|
package/src/components/Api.res
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
let noApiId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(~namespace="api", ~name="noApi")
|
|
5
5
|
|
|
6
6
|
/** Internal sury metadata ID used to mark specific variant names excluded from API exposure. */
|
|
7
|
-
let noApiVariantsId: S.Metadata.Id.t<Set.t<string>> =
|
|
8
|
-
|
|
7
|
+
let noApiVariantsId: S.Metadata.Id.t<Set.t<string>> = S.Metadata.Id.make(
|
|
8
|
+
~namespace="api",
|
|
9
|
+
~name="noApiVariants",
|
|
10
|
+
)
|
|
9
11
|
|
|
10
12
|
/** The same exclusion, carried by the excluded *member* rather than by the union
|
|
11
13
|
it sits in — so it survives a variant spread.
|
|
@@ -16,12 +18,13 @@ let noApiVariantsId: S.Metadata.Id.t<Set.t<string>> =
|
|
|
16
18
|
host publishes a command its author marked internal. A mark on the member
|
|
17
19
|
travels, because the member is what is spliced — the host's union holds the
|
|
18
20
|
same schema object. */
|
|
19
|
-
let noApiVariantId: S.Metadata.Id.t<bool> =
|
|
20
|
-
|
|
21
|
+
let noApiVariantId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(
|
|
22
|
+
~namespace="api",
|
|
23
|
+
~name="noApiVariant",
|
|
24
|
+
)
|
|
21
25
|
|
|
22
26
|
/** PPX helper: attaches the noApi flag to a command schema. Called by generated code. */
|
|
23
|
-
let markNoApi = (schema: S.t<'a>): S.t<'a> =>
|
|
24
|
-
schema->S.Metadata.set(~id=noApiId, true)
|
|
27
|
+
let markNoApi = (schema: S.t<'a>): S.t<'a> => schema->S.Metadata.set(~id=noApiId, true)
|
|
25
28
|
|
|
26
29
|
/** The constructor a union member stands for: the `TAG` literal of a
|
|
27
30
|
payload-bearing variant, or the bare literal a payload-less one compiles to. */
|
|
@@ -42,7 +42,11 @@ type publishJsons = array<Reventless.Message.commandJson> => promise<unit>
|
|
|
42
42
|
Publishes a stream of serialized command envelopes as an `Effect.t`.
|
|
43
43
|
Use this for high-throughput or streaming command pipelines.
|
|
44
44
|
*/
|
|
45
|
-
type publishJsonsStream = Stream.t<Reventless.Message.commandJson, string, unit> => Effect.t<
|
|
45
|
+
type publishJsonsStream = Stream.t<Reventless.Message.commandJson, string, unit> => Effect.t<
|
|
46
|
+
unit,
|
|
47
|
+
string,
|
|
48
|
+
unit,
|
|
49
|
+
>
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
52
|
A command together with an idempotency reference string.
|
|
@@ -25,8 +25,7 @@ let _outputsStore = _makeWeakMap()
|
|
|
25
25
|
let _operationsStore = _makeWeakMap()
|
|
26
26
|
|
|
27
27
|
/** Access the deploy-time outputs record for this component. */
|
|
28
|
-
let outputs = (self: t<'component, 'outputs, 'operations>): 'outputs =>
|
|
29
|
-
_outputsStore->_get(self)
|
|
28
|
+
let outputs = (self: t<'component, 'outputs, 'operations>): 'outputs => _outputsStore->_get(self)
|
|
30
29
|
|
|
31
30
|
/**
|
|
32
31
|
Access the deploy-time outputs wrapped in an `Output.t` (resolved asynchronously).
|
|
@@ -126,5 +126,9 @@ type component = Component.t<t, outputs, operations>
|
|
|
126
126
|
|
|
127
127
|
module type T = {
|
|
128
128
|
type component = component
|
|
129
|
-
let make: (
|
|
129
|
+
let make: (
|
|
130
|
+
~name: string,
|
|
131
|
+
~indexes: array<string>=?,
|
|
132
|
+
~opts: Pulumi.ComponentResource.options=?,
|
|
133
|
+
) => component
|
|
130
134
|
}
|
|
@@ -14,18 +14,17 @@ Same shape as `ReventlessCore.Monitoring.use`: a module-level registry consulted
|
|
|
14
14
|
by an emitted call, so deploy-time extension becomes *registration*, not *file
|
|
15
15
|
editing*. See `docs/plans/done/deploy-bootstrap-seam.md`.
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
17
|
/**
|
|
19
18
|
Ordered phases at which bootstrap contributions run during a deploy program.
|
|
20
19
|
*/
|
|
21
20
|
type phase =
|
|
22
|
-
|
|
21
|
+
/** before `deployPlatform` / `deployPlugin` — seam registration and other
|
|
23
22
|
ordering-sensitive activations that must precede the platform/plugin
|
|
24
23
|
graph build */
|
|
25
|
-
PreDeploy
|
|
26
|
-
|
|
24
|
+
| PreDeploy
|
|
25
|
+
/** after the platform/plugin graph is registered — exports, cross-stack
|
|
27
26
|
output emission */
|
|
28
|
-
PostDeploy
|
|
27
|
+
| PostDeploy
|
|
29
28
|
|
|
30
29
|
type contribution = unit => unit
|
|
31
30
|
|
|
@@ -48,4 +48,8 @@ type publishJsonStreamItem = {
|
|
|
48
48
|
Publishes a stream of event items as an `Effect.t`.
|
|
49
49
|
Use this for high-throughput or streaming event pipelines.
|
|
50
50
|
*/
|
|
51
|
-
type publishJsonStream = Stream.t<publishJsonStreamItem, string, unit> => Effect.t<
|
|
51
|
+
type publishJsonStream = Stream.t<publishJsonStreamItem, string, unit> => Effect.t<
|
|
52
|
+
unit,
|
|
53
|
+
string,
|
|
54
|
+
unit,
|
|
55
|
+
>
|
|
@@ -10,7 +10,10 @@ Creates a schedule in the underlying scheduling service (e.g. EventBridge Schedu
|
|
|
10
10
|
- `array<Adapter.resolvedResource>` — resolved scheduler resources (ARNs, etc.)
|
|
11
11
|
- `Reventless.Schedule.schedule` — the schedule definition to create
|
|
12
12
|
*/
|
|
13
|
-
type createSchedule = (
|
|
13
|
+
type createSchedule = (
|
|
14
|
+
array<Adapter.resolvedResource>,
|
|
15
|
+
Reventless.Schedule.schedule,
|
|
16
|
+
) => promise<unit>
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
Deletes a schedule by name from the underlying scheduling service.
|
|
@@ -145,14 +145,12 @@ module NoDelegate = {
|
|
|
145
145
|
|
|
146
146
|
let commandSchema = S.unit
|
|
147
147
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
148
|
-
let commandAuthorization = (_: command): Reventless.Authorization.permission =>
|
|
149
|
-
AllowAuthenticated
|
|
148
|
+
let commandAuthorization = (_: command): Reventless.Authorization.permission => AllowAuthenticated
|
|
150
149
|
type lifecycleState = unit
|
|
151
150
|
let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> => Unrestricted
|
|
152
151
|
let traits: array<Reventless.Trait.t> = []
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
|
|
156
154
|
open PluginExtensionPointSpec
|
|
157
155
|
|
|
158
156
|
type extensionPointName = string
|
|
@@ -268,7 +266,12 @@ module Make = (MappingImpl: Mapping): (
|
|
|
268
266
|
}
|
|
269
267
|
|
|
270
268
|
let encodeExtensionPointCommandJson = (commandJson, ~id, ~extensionPointName, ~action) => {
|
|
271
|
-
compLog(
|
|
269
|
+
compLog(
|
|
270
|
+
`Extension(${extensionPointName})`,
|
|
271
|
+
`${action}: ${commandJson
|
|
272
|
+
->Reventless.Message.variantNameOfJson
|
|
273
|
+
->Reventless.AnsiStyle.bold}(${id})`,
|
|
274
|
+
)
|
|
272
275
|
{
|
|
273
276
|
Reventless.Message.id,
|
|
274
277
|
meta: encodeMeta(meta, extensionPointName),
|
|
@@ -284,10 +287,7 @@ module Make = (MappingImpl: Mapping): (
|
|
|
284
287
|
mapIncomingEventImpl(id, event, meta, pluginDef, queryEngine)->Array.map(x =>
|
|
285
288
|
switch x {
|
|
286
289
|
| PublishAggregateCommand(targetId, targetCmd) =>
|
|
287
|
-
AbstractPublishAggregateCommand(
|
|
288
|
-
delegateName,
|
|
289
|
-
targetCmd->encodeTargetCommandJson(targetId),
|
|
290
|
-
)
|
|
290
|
+
AbstractPublishAggregateCommand(delegateName, targetCmd->encodeTargetCommandJson(targetId))
|
|
291
291
|
| PublishAggregateCommandAsync(promise) =>
|
|
292
292
|
let toCommandJson = async promise => {
|
|
293
293
|
let (targetId, targetCmd) = await promise
|
|
@@ -319,8 +319,7 @@ module Make = (MappingImpl: Mapping): (
|
|
|
319
319
|
targetCmd->encodeTargetCommandJson(derivePartitionId(targetCmd)),
|
|
320
320
|
))
|
|
321
321
|
AbstractPublishAggregateCommandsAsync(promise->toCommandJsons)
|
|
322
|
-
| PublishExtensionPointCommand(id, command)
|
|
323
|
-
if Spec.name == PluginExtensionPointSpec.name =>
|
|
322
|
+
| PublishExtensionPointCommand(id, command) if Spec.name == PluginExtensionPointSpec.name =>
|
|
324
323
|
AbstractPublishPluginExtensionPointCommand(
|
|
325
324
|
command->encodeExtensionPointCommand(
|
|
326
325
|
~id,
|
|
@@ -364,17 +363,20 @@ module Make = (MappingImpl: Mapping): (
|
|
|
364
363
|
|
|
365
364
|
let doMapOutgoingEvent = (mapOutgoingEventImpl, targetEvent'Json, pluginDef) => {
|
|
366
365
|
let tag = variantTagOfEnvelope(targetEvent'Json)
|
|
366
|
+
|
|
367
367
|
// Not this mapping's concern — skip without decoding.
|
|
368
368
|
if !(acceptedTags->Array.includes(tag)) {
|
|
369
369
|
[]
|
|
370
370
|
} else {
|
|
371
371
|
let {id, meta, event} =
|
|
372
|
-
targetEvent'Json->Reventless.Message.decodeEvent'(
|
|
373
|
-
Delegate.Id.schema,
|
|
374
|
-
Delegate.eventSchema,
|
|
375
|
-
)
|
|
372
|
+
targetEvent'Json->Reventless.Message.decodeEvent'(Delegate.Id.schema, Delegate.eventSchema)
|
|
376
373
|
let encodeExtensionPointCommandJson = (commandJson, ~id, ~extensionPointName, ~action) => {
|
|
377
|
-
compLog(
|
|
374
|
+
compLog(
|
|
375
|
+
`Extension(${delegateName})`,
|
|
376
|
+
`${action}: ${commandJson
|
|
377
|
+
->Reventless.Message.variantNameOfJson
|
|
378
|
+
->Reventless.AnsiStyle.bold}(${id})`,
|
|
379
|
+
)
|
|
378
380
|
{
|
|
379
381
|
Reventless.Message.id,
|
|
380
382
|
meta: encodeMeta(meta, extensionPointName),
|
|
@@ -389,8 +391,7 @@ module Make = (MappingImpl: Mapping): (
|
|
|
389
391
|
|
|
390
392
|
mapOutgoingEventImpl(id->Delegate.Id.toString, event, meta, pluginDef)->Array.map(x =>
|
|
391
393
|
switch x {
|
|
392
|
-
| PublishExtensionPointCommand(id, command)
|
|
393
|
-
if Spec.name == PluginExtensionPointSpec.name =>
|
|
394
|
+
| PublishExtensionPointCommand(id, command) if Spec.name == PluginExtensionPointSpec.name =>
|
|
394
395
|
AbstractPublishPluginExtensionPointCommand(
|
|
395
396
|
command->encodeExtensionPointCommand(
|
|
396
397
|
~id,
|
|
@@ -119,7 +119,11 @@ module type T = {
|
|
|
119
119
|
|
|
120
120
|
/** Pre-encodes a batch of typed EP commands for the runtime. */
|
|
121
121
|
let mapIncomingCommands: (
|
|
122
|
-
array<
|
|
122
|
+
array<
|
|
123
|
+
CommandTopic.topicItem<
|
|
124
|
+
Reventless.Message.command'<Reventless.Id.String.t, ExtensionPoint.command>,
|
|
125
|
+
>,
|
|
126
|
+
>,
|
|
123
127
|
Reventless.Schedule.create,
|
|
124
128
|
Reventless.Schedule.delete,
|
|
125
129
|
Reventless.QueryEngine.operations,
|
|
@@ -162,12 +166,20 @@ module Make = (MappingImpl: Mapping): (
|
|
|
162
166
|
queryEngine,
|
|
163
167
|
) =>
|
|
164
168
|
topicItems
|
|
165
|
-
->Array.map(({
|
|
169
|
+
->Array.map(({
|
|
170
|
+
CommandTopic.reference: reference,
|
|
171
|
+
command: {Reventless.Message.id: id, command, meta},
|
|
172
|
+
}) =>
|
|
166
173
|
mapIncomingEventImpl(id->Reventless.Id.String.toString, command, meta)->Array.map(x =>
|
|
167
174
|
switch x {
|
|
168
175
|
| PublishCommand(targetId, targetCmd) =>
|
|
169
176
|
let cmdJson = targetCmd->Reventless.Message.encode(Delegate.commandSchema)
|
|
170
|
-
compLog(
|
|
177
|
+
compLog(
|
|
178
|
+
`ExtensionPoint(${extensionPointName})`,
|
|
179
|
+
`EP→${delegateName}: ${cmdJson
|
|
180
|
+
->Reventless.Message.variantNameOfJson
|
|
181
|
+
->Reventless.AnsiStyle.bold}(${targetId})`,
|
|
182
|
+
)
|
|
171
183
|
|
|
172
184
|
AbstractPublishCommand(
|
|
173
185
|
delegateName,
|
|
@@ -211,15 +223,13 @@ module Make = (MappingImpl: Mapping): (
|
|
|
211
223
|
queryEngine,
|
|
212
224
|
) => {
|
|
213
225
|
let tag = variantTagOfEnvelope(targetEventJson')
|
|
226
|
+
|
|
214
227
|
// Not this mapping's concern — skip without decoding.
|
|
215
228
|
if !(acceptedTags->Array.includes(tag)) {
|
|
216
229
|
[]
|
|
217
230
|
} else {
|
|
218
231
|
let {id, meta, event} =
|
|
219
|
-
targetEventJson'->Reventless.Message.decodeEvent'(
|
|
220
|
-
Delegate.Id.schema,
|
|
221
|
-
Delegate.eventSchema,
|
|
222
|
-
)
|
|
232
|
+
targetEventJson'->Reventless.Message.decodeEvent'(Delegate.Id.schema, Delegate.eventSchema)
|
|
223
233
|
mapOutgoingEventImpl(
|
|
224
234
|
id->Delegate.Id.toString,
|
|
225
235
|
event,
|
|
@@ -229,7 +239,12 @@ module Make = (MappingImpl: Mapping): (
|
|
|
229
239
|
switch eventAction {
|
|
230
240
|
| PublishEvent(id, event) =>
|
|
231
241
|
let eventJson = event->Reventless.Message.encode(Spec.eventSchema)
|
|
232
|
-
compLog(
|
|
242
|
+
compLog(
|
|
243
|
+
`ExtensionPoint(${extensionPointName})`,
|
|
244
|
+
`mapped ${delegateName} → ${eventJson
|
|
245
|
+
->Reventless.Message.variantNameOfJson
|
|
246
|
+
->Reventless.AnsiStyle.bold}(${id})`,
|
|
247
|
+
)
|
|
233
248
|
let meta = {
|
|
234
249
|
...meta,
|
|
235
250
|
service: Spec.name,
|
|
@@ -241,7 +256,12 @@ module Make = (MappingImpl: Mapping): (
|
|
|
241
256
|
let toEvent' = async promise => {
|
|
242
257
|
let (id, event) = await promise
|
|
243
258
|
let eventJson = event->Reventless.Message.encode(Spec.eventSchema)
|
|
244
|
-
compLog(
|
|
259
|
+
compLog(
|
|
260
|
+
`ExtensionPoint(${extensionPointName})`,
|
|
261
|
+
`mapped ${delegateName} → ${eventJson
|
|
262
|
+
->Reventless.Message.variantNameOfJson
|
|
263
|
+
->Reventless.AnsiStyle.bold}(${id}) (async)`,
|
|
264
|
+
)
|
|
245
265
|
let eventJson' = Reventless.Message.composeEventJson'(id, meta, eventJson) // TODO: check if meta is correct
|
|
246
266
|
(id, meta, eventJson')
|
|
247
267
|
}
|
|
@@ -249,8 +269,8 @@ module Make = (MappingImpl: Mapping): (
|
|
|
249
269
|
| HandleDirective(handler, directive) =>
|
|
250
270
|
compLog(`ExtensionPoint(${extensionPointName})`, `mapped ${delegateName} → directive`)
|
|
251
271
|
|
|
252
|
-
AbstractHandleDirective(
|
|
253
|
-
handler(createSchedule, deleteSchedule, queryEngine, directive)
|
|
272
|
+
AbstractHandleDirective(
|
|
273
|
+
() => handler(createSchedule, deleteSchedule, queryEngine, directive),
|
|
254
274
|
)
|
|
255
275
|
}
|
|
256
276
|
)
|
package/src/types/Platform.res
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
// remains accessible inside module type T even though T re-declares
|
|
21
21
|
// `module ExtensionPoint` with a different local type, which would otherwise
|
|
22
22
|
// shadow the package-level ExtensionPoint.outputs.
|
|
23
|
+
// Type alias so `deployPlugin` can reference `Plugin.outputs` inside module type T,
|
|
24
|
+
// where `module Plugin: Plugin.T` shadows the package-level Plugin module.
|
|
23
25
|
/**
|
|
24
26
|
Abstract factory interface for creating Reventless components without coupling
|
|
25
27
|
application code to a specific infrastructure provider.
|
|
@@ -44,9 +46,6 @@ module Platform = ReventlessAws.Platform.Make(AwsConfig)
|
|
|
44
46
|
module App = CatalogPlugin.Make(Platform)
|
|
45
47
|
```
|
|
46
48
|
*/
|
|
47
|
-
|
|
48
|
-
// Type alias so `deployPlugin` can reference `Plugin.outputs` inside module type T,
|
|
49
|
-
// where `module Plugin: Plugin.T` shadows the package-level Plugin module.
|
|
50
49
|
type pluginOutputs = Plugin.outputs
|
|
51
50
|
|
|
52
51
|
// A storage bucket a deployment declares to be served read-only to the UI under
|
|
@@ -110,6 +109,7 @@ type capability =
|
|
|
110
109
|
| ObjectStore({plugin: string, store: string})
|
|
111
110
|
| Geocoding
|
|
112
111
|
| Messaging
|
|
112
|
+
| IdentityProvider
|
|
113
113
|
|
|
114
114
|
// A provisioned geocoding place index, as returned by the framework's geocoding
|
|
115
115
|
// capability helper. A record rather than a bare name so the handle can grow
|
|
@@ -286,13 +286,13 @@ module type T = {
|
|
|
286
286
|
Spec: Reventless.Aggregate.Spec,
|
|
287
287
|
Behavior: Reventless.Behavior.T with module Spec := Spec,
|
|
288
288
|
EventMappings: EventMapper.Mappings with module Target := Spec,
|
|
289
|
-
) => Aggregate.T with type api = api
|
|
289
|
+
) => (Aggregate.T with type api = api)
|
|
290
290
|
/** Async variant — uses FIFO channel, returns `CommandPending`. */
|
|
291
291
|
module MakeAsync: (
|
|
292
292
|
Spec: Reventless.Aggregate.Spec,
|
|
293
293
|
Behavior: Reventless.Behavior.T with module Spec := Spec,
|
|
294
294
|
EventMappings: EventMapper.Mappings with module Target := Spec,
|
|
295
|
-
) => Aggregate.T with type api = api
|
|
295
|
+
) => (Aggregate.T with type api = api)
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
/** Factory for read model (query-side projection) components. */
|
|
@@ -300,7 +300,7 @@ module type T = {
|
|
|
300
300
|
module Make: (
|
|
301
301
|
Spec: Reventless.ReadModel.Spec,
|
|
302
302
|
Mappings: Reventless.Projection.Mappings with module Target := Spec,
|
|
303
|
-
) => ReadModel.T with module Spec = Spec and type api = api and type role = role
|
|
303
|
+
) => (ReadModel.T with module Spec = Spec and type api = api and type role = role)
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
/** Factory for stream-enabled read model components — identical to `ReadModel`
|
|
@@ -312,29 +312,24 @@ module type T = {
|
|
|
312
312
|
module Make: (
|
|
313
313
|
Spec: Reventless.ReadModel.Spec,
|
|
314
314
|
Mappings: Reventless.Projection.Mappings with module Target := Spec,
|
|
315
|
-
) => ReadModelComponentT with module Spec = Spec and type api = api and type role = role
|
|
315
|
+
) => (ReadModelComponentT with module Spec = Spec and type api = api and type role = role)
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
/** Factory for extension point components (single mapping). */
|
|
319
319
|
module ExtensionPoint: {
|
|
320
|
-
module Make: (
|
|
321
|
-
Mapping: ExtensionPointMapping.Mapping,
|
|
322
|
-
) => ExtensionPoint.T
|
|
320
|
+
module Make: (Mapping: ExtensionPointMapping.Mapping) => ExtensionPoint.T
|
|
323
321
|
|
|
324
322
|
/** Two-mapping variant — merges per-slice EP mappings. */
|
|
325
323
|
module Make2: (
|
|
326
324
|
Mapping1: ExtensionPointMapping.Mapping,
|
|
327
|
-
Mapping2: ExtensionPointMapping.Mapping
|
|
328
|
-
with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
325
|
+
Mapping2: ExtensionPointMapping.Mapping with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
329
326
|
) => ExtensionPoint.T
|
|
330
327
|
|
|
331
328
|
/** Three-mapping variant — merges per-slice EP mappings. */
|
|
332
329
|
module Make3: (
|
|
333
330
|
Mapping1: ExtensionPointMapping.Mapping,
|
|
334
|
-
Mapping2: ExtensionPointMapping.Mapping
|
|
335
|
-
|
|
336
|
-
Mapping3: ExtensionPointMapping.Mapping
|
|
337
|
-
with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
331
|
+
Mapping2: ExtensionPointMapping.Mapping with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
332
|
+
Mapping3: ExtensionPointMapping.Mapping with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
338
333
|
) => ExtensionPoint.T
|
|
339
334
|
|
|
340
335
|
/** Multi-mapping variant with full control over name and mappings array. */
|
|
@@ -349,14 +344,12 @@ module type T = {
|
|
|
349
344
|
using the plugin name for the extension's component name and auto-merging
|
|
350
345
|
blueprints that target the same extension point. */
|
|
351
346
|
module Extension: {
|
|
352
|
-
module Make: (
|
|
353
|
-
Mapping: ExtensionMapping.Mapping,
|
|
354
|
-
) => Extension.Blueprint
|
|
347
|
+
module Make: (Mapping: ExtensionMapping.Mapping) => Extension.Blueprint
|
|
355
348
|
}
|
|
356
349
|
|
|
357
350
|
/** Factory for task (background job / S3 trigger) components. */
|
|
358
351
|
module Task: {
|
|
359
|
-
module Make: (Spec: Task.Spec) => Task.T with module Spec = Spec
|
|
352
|
+
module Make: (Spec: Task.Spec) => (Task.T with module Spec = Spec)
|
|
360
353
|
}
|
|
361
354
|
|
|
362
355
|
/** Ready-to-use counter component (no Make required). */
|
|
@@ -367,12 +360,12 @@ module type T = {
|
|
|
367
360
|
module Make: (
|
|
368
361
|
Spec: Reventless.StateChangeSlice.Spec,
|
|
369
362
|
Behavior: Reventless.StateChangeSlice.Behavior with module Spec := Spec,
|
|
370
|
-
) => StateChangeSlice.T with module Spec = Spec
|
|
363
|
+
) => (StateChangeSlice.T with module Spec = Spec)
|
|
371
364
|
/** Async variant — uses FIFO channel, returns `CommandPending`. */
|
|
372
365
|
module MakeAsync: (
|
|
373
366
|
Spec: Reventless.StateChangeSlice.Spec,
|
|
374
367
|
Behavior: Reventless.StateChangeSlice.Behavior with module Spec := Spec,
|
|
375
|
-
) => StateChangeSlice.T with module Spec = Spec
|
|
368
|
+
) => (StateChangeSlice.T with module Spec = Spec)
|
|
376
369
|
}
|
|
377
370
|
|
|
378
371
|
/** Factory for DCB read-side state-view slice components. */
|
|
@@ -380,7 +373,7 @@ module type T = {
|
|
|
380
373
|
module Make: (
|
|
381
374
|
Spec: Reventless.StateViewSlice.Spec,
|
|
382
375
|
Projection: Reventless.StateViewSlice.Projection with module Spec := Spec,
|
|
383
|
-
) => StateViewSlice.T with module Spec = Spec
|
|
376
|
+
) => (StateViewSlice.T with module Spec = Spec)
|
|
384
377
|
}
|
|
385
378
|
|
|
386
379
|
/** Factory for stream-enabled state-view slice components (enables Source B subscriptions). */
|
|
@@ -388,7 +381,7 @@ module type T = {
|
|
|
388
381
|
module Make: (
|
|
389
382
|
Spec: Reventless.StateViewSlice.Spec,
|
|
390
383
|
Projection: Reventless.StateViewSlice.Projection with module Spec := Spec,
|
|
391
|
-
) => StateViewSliceComponentT with module Spec = Spec
|
|
384
|
+
) => (StateViewSliceComponentT with module Spec = Spec)
|
|
392
385
|
}
|
|
393
386
|
|
|
394
387
|
/** Factory for DCB automation slice components (TODO list pattern). */
|
|
@@ -396,7 +389,7 @@ module type T = {
|
|
|
396
389
|
module Make: (
|
|
397
390
|
Spec: Reventless.AutomationSlice.Spec,
|
|
398
391
|
Automation: Reventless.AutomationSlice.Automation with module Spec := Spec,
|
|
399
|
-
) => AutomationSlice.T with module Spec = Spec
|
|
392
|
+
) => (AutomationSlice.T with module Spec = Spec)
|
|
400
393
|
}
|
|
401
394
|
|
|
402
395
|
/** Factory for DCB outbound translation slice components (tracked external calls). */
|
|
@@ -404,7 +397,7 @@ module type T = {
|
|
|
404
397
|
module Make: (
|
|
405
398
|
Spec: Reventless.OutboundTranslationSlice.Spec,
|
|
406
399
|
Translation: Reventless.OutboundTranslationSlice.Translation with module Spec := Spec,
|
|
407
|
-
) => OutboundTranslationSlice.T with module Spec = Spec
|
|
400
|
+
) => (OutboundTranslationSlice.T with module Spec = Spec)
|
|
408
401
|
}
|
|
409
402
|
|
|
410
403
|
/** Factory for DCB inbound translation slice components (external input to commands). */
|
|
@@ -412,7 +405,7 @@ module type T = {
|
|
|
412
405
|
module Make: (
|
|
413
406
|
Spec: Reventless.InboundTranslationSlice.Spec,
|
|
414
407
|
Translation: Reventless.InboundTranslationSlice.Translation with module Spec := Spec,
|
|
415
|
-
) => InboundTranslationSlice.T with module Spec = Spec
|
|
408
|
+
) => (InboundTranslationSlice.T with module Spec = Spec)
|
|
416
409
|
}
|
|
417
410
|
|
|
418
411
|
/** Whether this platform supports MCP (Model Context Protocol) for AI agent access.
|
|
@@ -536,7 +529,10 @@ module type T = {
|
|
|
536
529
|
Pass `~apiTarget=Platform` to route the plugin's resolvers and schema to the Platform API.
|
|
537
530
|
Defaults to `Domain`.
|
|
538
531
|
Returns the Pulumi stack outputs dict for use as the ESM `default` export. */
|
|
539
|
-
let deployPlugin: (
|
|
532
|
+
let deployPlugin: (
|
|
533
|
+
~plugin: module(PluginMaker),
|
|
534
|
+
~apiTarget: apiTarget=?,
|
|
535
|
+
) => dict<Pulumi.Output.t<JSON.t>>
|
|
540
536
|
|
|
541
537
|
/** Start all servers after all makePlatform/deployPlugin calls are complete.
|
|
542
538
|
In split in-memory mode, servers are deferred until this is called so all
|
|
@@ -5,68 +5,86 @@ module DB = DeployBootstrap
|
|
|
5
5
|
describe("DeployBootstrap", () => {
|
|
6
6
|
// The seam holds module-level state, so each case resets first for isolation.
|
|
7
7
|
describe("run with no registrations", () => {
|
|
8
|
-
testSync(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
testSync(
|
|
9
|
+
"is a no-op that does not throw",
|
|
10
|
+
() => {
|
|
11
|
+
DB.reset()
|
|
12
|
+
DB.run(PreDeploy)
|
|
13
|
+
DB.run(PostDeploy)
|
|
14
|
+
expect(true)->toBe(true)
|
|
15
|
+
},
|
|
16
|
+
)
|
|
14
17
|
})
|
|
15
18
|
|
|
16
19
|
describe("registration order within a phase", () => {
|
|
17
|
-
testSync(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
testSync(
|
|
21
|
+
"runs contributions in the order they were registered",
|
|
22
|
+
() => {
|
|
23
|
+
DB.reset()
|
|
24
|
+
let calls = []
|
|
25
|
+
DB.register(() => calls->Array.push("a"))
|
|
26
|
+
DB.register(() => calls->Array.push("b"))
|
|
27
|
+
DB.register(() => calls->Array.push("c"))
|
|
28
|
+
DB.run(PreDeploy)
|
|
29
|
+
expect(calls)->toEqual(["a", "b", "c"])
|
|
30
|
+
},
|
|
31
|
+
)
|
|
26
32
|
})
|
|
27
33
|
|
|
28
34
|
describe("phase isolation", () => {
|
|
29
|
-
testSync(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
testSync(
|
|
36
|
+
"run(PreDeploy) fires only PreDeploy contributions",
|
|
37
|
+
() => {
|
|
38
|
+
DB.reset()
|
|
39
|
+
let calls = []
|
|
40
|
+
DB.register(~phase=PreDeploy, () => calls->Array.push("pre"))
|
|
41
|
+
DB.register(~phase=PostDeploy, () => calls->Array.push("post"))
|
|
42
|
+
DB.run(PreDeploy)
|
|
43
|
+
expect(calls)->toEqual(["pre"])
|
|
44
|
+
DB.run(PostDeploy)
|
|
45
|
+
expect(calls)->toEqual(["pre", "post"])
|
|
46
|
+
},
|
|
47
|
+
)
|
|
39
48
|
|
|
40
|
-
testSync(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
testSync(
|
|
50
|
+
"defaults to PreDeploy when no phase is given",
|
|
51
|
+
() => {
|
|
52
|
+
DB.reset()
|
|
53
|
+
let calls = []
|
|
54
|
+
DB.register(() => calls->Array.push("default"))
|
|
55
|
+
DB.run(PostDeploy)
|
|
56
|
+
expect(calls)->toEqual([])
|
|
57
|
+
DB.run(PreDeploy)
|
|
58
|
+
expect(calls)->toEqual(["default"])
|
|
59
|
+
},
|
|
60
|
+
)
|
|
49
61
|
})
|
|
50
62
|
|
|
51
63
|
describe("idempotent run per phase", () => {
|
|
52
|
-
testSync(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
testSync(
|
|
65
|
+
"running an already-run phase does nothing",
|
|
66
|
+
() => {
|
|
67
|
+
DB.reset()
|
|
68
|
+
let count = ref(0)
|
|
69
|
+
DB.register(() => count := count.contents + 1)
|
|
70
|
+
DB.run(PreDeploy)
|
|
71
|
+
DB.run(PreDeploy)
|
|
72
|
+
DB.run(PreDeploy)
|
|
73
|
+
expect(count.contents)->toBe(1)
|
|
74
|
+
},
|
|
75
|
+
)
|
|
61
76
|
|
|
62
|
-
testSync(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
testSync(
|
|
78
|
+
"a contribution registered after run does not fire",
|
|
79
|
+
() => {
|
|
80
|
+
DB.reset()
|
|
81
|
+
let calls = []
|
|
82
|
+
DB.register(() => calls->Array.push("before"))
|
|
83
|
+
DB.run(PreDeploy)
|
|
84
|
+
DB.register(() => calls->Array.push("after"))
|
|
85
|
+
DB.run(PreDeploy)
|
|
86
|
+
expect(calls)->toEqual(["before"])
|
|
87
|
+
},
|
|
88
|
+
)
|
|
71
89
|
})
|
|
72
90
|
})
|
|
@@ -12,10 +12,14 @@ describe("RuntimeHints.resolveMemory", () => {
|
|
|
12
12
|
expect(RH.resolveMemory(None, ~default=1024))->toBe(1024)
|
|
13
13
|
})
|
|
14
14
|
testSync("an override above the default wins", () => {
|
|
15
|
-
expect(RH.resolveMemory(Some({memorySize: Some(2048), timeout: None}), ~default=1024))->toBe(
|
|
15
|
+
expect(RH.resolveMemory(Some({memorySize: Some(2048), timeout: None}), ~default=1024))->toBe(
|
|
16
|
+
2048,
|
|
17
|
+
)
|
|
16
18
|
})
|
|
17
19
|
testSync("an override below the default is clamped up to the floor", () => {
|
|
18
|
-
expect(RH.resolveMemory(Some({memorySize: Some(256), timeout: None}), ~default=1024))->toBe(
|
|
20
|
+
expect(RH.resolveMemory(Some({memorySize: Some(256), timeout: None}), ~default=1024))->toBe(
|
|
21
|
+
1024,
|
|
22
|
+
)
|
|
19
23
|
})
|
|
20
24
|
testSync("an absent memorySize field falls through to the default", () => {
|
|
21
25
|
expect(RH.resolveMemory(Some({memorySize: None, timeout: Some(60)}), ~default=1024))->toBe(1024)
|