@reventlessdev/reventless-infra 3.0.0-alpha.160 → 3.0.0-alpha.162
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 +16 -0
- package/package.json +5 -5
- 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 +23 -28
- package/tests/DeployBootstrapTest.res +70 -52
- package/tests/RuntimeHintsTest.res +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
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.162 (2026-09-09)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @reventlessdev/reventless-infra
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 3.0.0-alpha.161 (2026-09-08)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @reventlessdev/reventless-infra
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
# 3.0.0-alpha.160 (2026-09-08)
|
|
7
23
|
|
|
8
24
|
**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.162",
|
|
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-
|
|
20
|
-
"@reventlessdev/rescript-
|
|
21
|
-
"@reventlessdev/
|
|
22
|
-
"@reventlessdev/
|
|
19
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.33",
|
|
20
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.20",
|
|
21
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.134",
|
|
22
|
+
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0"
|
|
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
|
|
@@ -286,13 +285,13 @@ module type T = {
|
|
|
286
285
|
Spec: Reventless.Aggregate.Spec,
|
|
287
286
|
Behavior: Reventless.Behavior.T with module Spec := Spec,
|
|
288
287
|
EventMappings: EventMapper.Mappings with module Target := Spec,
|
|
289
|
-
) => Aggregate.T with type api = api
|
|
288
|
+
) => (Aggregate.T with type api = api)
|
|
290
289
|
/** Async variant — uses FIFO channel, returns `CommandPending`. */
|
|
291
290
|
module MakeAsync: (
|
|
292
291
|
Spec: Reventless.Aggregate.Spec,
|
|
293
292
|
Behavior: Reventless.Behavior.T with module Spec := Spec,
|
|
294
293
|
EventMappings: EventMapper.Mappings with module Target := Spec,
|
|
295
|
-
) => Aggregate.T with type api = api
|
|
294
|
+
) => (Aggregate.T with type api = api)
|
|
296
295
|
}
|
|
297
296
|
|
|
298
297
|
/** Factory for read model (query-side projection) components. */
|
|
@@ -300,7 +299,7 @@ module type T = {
|
|
|
300
299
|
module Make: (
|
|
301
300
|
Spec: Reventless.ReadModel.Spec,
|
|
302
301
|
Mappings: Reventless.Projection.Mappings with module Target := Spec,
|
|
303
|
-
) => ReadModel.T with module Spec = Spec and type api = api and type role = role
|
|
302
|
+
) => (ReadModel.T with module Spec = Spec and type api = api and type role = role)
|
|
304
303
|
}
|
|
305
304
|
|
|
306
305
|
/** Factory for stream-enabled read model components — identical to `ReadModel`
|
|
@@ -312,29 +311,24 @@ module type T = {
|
|
|
312
311
|
module Make: (
|
|
313
312
|
Spec: Reventless.ReadModel.Spec,
|
|
314
313
|
Mappings: Reventless.Projection.Mappings with module Target := Spec,
|
|
315
|
-
) => ReadModelComponentT with module Spec = Spec and type api = api and type role = role
|
|
314
|
+
) => (ReadModelComponentT with module Spec = Spec and type api = api and type role = role)
|
|
316
315
|
}
|
|
317
316
|
|
|
318
317
|
/** Factory for extension point components (single mapping). */
|
|
319
318
|
module ExtensionPoint: {
|
|
320
|
-
module Make: (
|
|
321
|
-
Mapping: ExtensionPointMapping.Mapping,
|
|
322
|
-
) => ExtensionPoint.T
|
|
319
|
+
module Make: (Mapping: ExtensionPointMapping.Mapping) => ExtensionPoint.T
|
|
323
320
|
|
|
324
321
|
/** Two-mapping variant — merges per-slice EP mappings. */
|
|
325
322
|
module Make2: (
|
|
326
323
|
Mapping1: ExtensionPointMapping.Mapping,
|
|
327
|
-
Mapping2: ExtensionPointMapping.Mapping
|
|
328
|
-
with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
324
|
+
Mapping2: ExtensionPointMapping.Mapping with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
329
325
|
) => ExtensionPoint.T
|
|
330
326
|
|
|
331
327
|
/** Three-mapping variant — merges per-slice EP mappings. */
|
|
332
328
|
module Make3: (
|
|
333
329
|
Mapping1: ExtensionPointMapping.Mapping,
|
|
334
|
-
Mapping2: ExtensionPointMapping.Mapping
|
|
335
|
-
|
|
336
|
-
Mapping3: ExtensionPointMapping.Mapping
|
|
337
|
-
with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
330
|
+
Mapping2: ExtensionPointMapping.Mapping with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
331
|
+
Mapping3: ExtensionPointMapping.Mapping with module ExtensionPoint = Mapping1.ExtensionPoint,
|
|
338
332
|
) => ExtensionPoint.T
|
|
339
333
|
|
|
340
334
|
/** Multi-mapping variant with full control over name and mappings array. */
|
|
@@ -349,14 +343,12 @@ module type T = {
|
|
|
349
343
|
using the plugin name for the extension's component name and auto-merging
|
|
350
344
|
blueprints that target the same extension point. */
|
|
351
345
|
module Extension: {
|
|
352
|
-
module Make: (
|
|
353
|
-
Mapping: ExtensionMapping.Mapping,
|
|
354
|
-
) => Extension.Blueprint
|
|
346
|
+
module Make: (Mapping: ExtensionMapping.Mapping) => Extension.Blueprint
|
|
355
347
|
}
|
|
356
348
|
|
|
357
349
|
/** Factory for task (background job / S3 trigger) components. */
|
|
358
350
|
module Task: {
|
|
359
|
-
module Make: (Spec: Task.Spec) => Task.T with module Spec = Spec
|
|
351
|
+
module Make: (Spec: Task.Spec) => (Task.T with module Spec = Spec)
|
|
360
352
|
}
|
|
361
353
|
|
|
362
354
|
/** Ready-to-use counter component (no Make required). */
|
|
@@ -367,12 +359,12 @@ module type T = {
|
|
|
367
359
|
module Make: (
|
|
368
360
|
Spec: Reventless.StateChangeSlice.Spec,
|
|
369
361
|
Behavior: Reventless.StateChangeSlice.Behavior with module Spec := Spec,
|
|
370
|
-
) => StateChangeSlice.T with module Spec = Spec
|
|
362
|
+
) => (StateChangeSlice.T with module Spec = Spec)
|
|
371
363
|
/** Async variant — uses FIFO channel, returns `CommandPending`. */
|
|
372
364
|
module MakeAsync: (
|
|
373
365
|
Spec: Reventless.StateChangeSlice.Spec,
|
|
374
366
|
Behavior: Reventless.StateChangeSlice.Behavior with module Spec := Spec,
|
|
375
|
-
) => StateChangeSlice.T with module Spec = Spec
|
|
367
|
+
) => (StateChangeSlice.T with module Spec = Spec)
|
|
376
368
|
}
|
|
377
369
|
|
|
378
370
|
/** Factory for DCB read-side state-view slice components. */
|
|
@@ -380,7 +372,7 @@ module type T = {
|
|
|
380
372
|
module Make: (
|
|
381
373
|
Spec: Reventless.StateViewSlice.Spec,
|
|
382
374
|
Projection: Reventless.StateViewSlice.Projection with module Spec := Spec,
|
|
383
|
-
) => StateViewSlice.T with module Spec = Spec
|
|
375
|
+
) => (StateViewSlice.T with module Spec = Spec)
|
|
384
376
|
}
|
|
385
377
|
|
|
386
378
|
/** Factory for stream-enabled state-view slice components (enables Source B subscriptions). */
|
|
@@ -388,7 +380,7 @@ module type T = {
|
|
|
388
380
|
module Make: (
|
|
389
381
|
Spec: Reventless.StateViewSlice.Spec,
|
|
390
382
|
Projection: Reventless.StateViewSlice.Projection with module Spec := Spec,
|
|
391
|
-
) => StateViewSliceComponentT with module Spec = Spec
|
|
383
|
+
) => (StateViewSliceComponentT with module Spec = Spec)
|
|
392
384
|
}
|
|
393
385
|
|
|
394
386
|
/** Factory for DCB automation slice components (TODO list pattern). */
|
|
@@ -396,7 +388,7 @@ module type T = {
|
|
|
396
388
|
module Make: (
|
|
397
389
|
Spec: Reventless.AutomationSlice.Spec,
|
|
398
390
|
Automation: Reventless.AutomationSlice.Automation with module Spec := Spec,
|
|
399
|
-
) => AutomationSlice.T with module Spec = Spec
|
|
391
|
+
) => (AutomationSlice.T with module Spec = Spec)
|
|
400
392
|
}
|
|
401
393
|
|
|
402
394
|
/** Factory for DCB outbound translation slice components (tracked external calls). */
|
|
@@ -404,7 +396,7 @@ module type T = {
|
|
|
404
396
|
module Make: (
|
|
405
397
|
Spec: Reventless.OutboundTranslationSlice.Spec,
|
|
406
398
|
Translation: Reventless.OutboundTranslationSlice.Translation with module Spec := Spec,
|
|
407
|
-
) => OutboundTranslationSlice.T with module Spec = Spec
|
|
399
|
+
) => (OutboundTranslationSlice.T with module Spec = Spec)
|
|
408
400
|
}
|
|
409
401
|
|
|
410
402
|
/** Factory for DCB inbound translation slice components (external input to commands). */
|
|
@@ -412,7 +404,7 @@ module type T = {
|
|
|
412
404
|
module Make: (
|
|
413
405
|
Spec: Reventless.InboundTranslationSlice.Spec,
|
|
414
406
|
Translation: Reventless.InboundTranslationSlice.Translation with module Spec := Spec,
|
|
415
|
-
) => InboundTranslationSlice.T with module Spec = Spec
|
|
407
|
+
) => (InboundTranslationSlice.T with module Spec = Spec)
|
|
416
408
|
}
|
|
417
409
|
|
|
418
410
|
/** Whether this platform supports MCP (Model Context Protocol) for AI agent access.
|
|
@@ -536,7 +528,10 @@ module type T = {
|
|
|
536
528
|
Pass `~apiTarget=Platform` to route the plugin's resolvers and schema to the Platform API.
|
|
537
529
|
Defaults to `Domain`.
|
|
538
530
|
Returns the Pulumi stack outputs dict for use as the ESM `default` export. */
|
|
539
|
-
let deployPlugin: (
|
|
531
|
+
let deployPlugin: (
|
|
532
|
+
~plugin: module(PluginMaker),
|
|
533
|
+
~apiTarget: apiTarget=?,
|
|
534
|
+
) => dict<Pulumi.Output.t<JSON.t>>
|
|
540
535
|
|
|
541
536
|
/** Start all servers after all makePlatform/deployPlugin calls are complete.
|
|
542
537
|
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)
|