@reventlessdev/reventless-aws 3.0.0-alpha.399 → 3.0.0-alpha.401

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 CHANGED
@@ -3,6 +3,30 @@
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.401 (2026-10-29)
7
+
8
+ * refactor!: drop `dcbSources`, the cross-plugin DCB subscription nobody declared ([cf3b66d](https://github.com/ReventlessDev/reventless-core/commit/cf3b66d8c1b5d87673197b64eaee174598bb8253))
9
+ * refactor!: a read model hears another plugin only through an Extension and a slice ([15c7cae](https://github.com/ReventlessDev/reventless-core/commit/15c7cae214d5aa04404779fcd87a8429aad1965c))
10
+
11
+ ### BREAKING CHANGES
12
+
13
+ * `Reventless.Plugin.extensionDefinition` has no `dcbSources`
14
+ field, and `Platform_PluginExtensions.dcbSources` is gone from the GraphQL
15
+ schema. A query that selects it fails as a whole; drop the selection.
16
+ * `ReventlessInfra.Extension.T.make` and the core
17
+ `Extension_Builder.make` no longer take `~readModelNamesForSourceName` and
18
+ `~publishToReadModels`. Both arguments did nothing; drop them at the call site.
19
+
20
+
21
+
22
+ # 3.0.0-alpha.400 (2026-10-25)
23
+
24
+ **Note:** Version bump only for package @reventlessdev/reventless-aws
25
+
26
+
27
+
28
+
29
+
6
30
  # 3.0.0-alpha.399 (2026-10-25)
7
31
 
8
32
  **Note:** Version bump only for package @reventlessdev/reventless-aws
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.399",
3
+ "version": "3.0.0-alpha.401",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -23,18 +23,18 @@
23
23
  "uuid": "^13.0.0",
24
24
  "yaml": "^2.8.3",
25
25
  "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.36",
26
- "@reventlessdev/rescript-node": "2.0.0-alpha.23",
26
+ "@reventlessdev/rescript-effect": "1.0.0-alpha.1",
27
27
  "@reventlessdev/rescript-jest": "1.0.0-alpha.13",
28
+ "@reventlessdev/rescript-node": "2.0.0-alpha.23",
28
29
  "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.34",
29
- "@reventlessdev/rescript-effect": "1.0.0-alpha.1",
30
30
  "@reventlessdev/rescript-pulumi-pulumi": "3.0.0-alpha.0",
31
- "@reventlessdev/rescript-web": "1.0.0-alpha.8",
32
31
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
33
- "@reventlessdev/reventless-infra": "3.0.0-alpha.206",
34
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.173",
32
+ "@reventlessdev/rescript-web": "1.0.0-alpha.8",
33
+ "@reventlessdev/reventless-core": "3.0.0-alpha.311",
34
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.208",
35
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.175",
35
36
  "@reventlessdev/reventless-interop": "3.0.0-alpha.42",
36
- "@reventlessdev/reventless-spec": "3.0.0-alpha.177",
37
- "@reventlessdev/reventless-core": "3.0.0-alpha.309"
37
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.179"
38
38
  },
39
39
  "devDependencies": {
40
40
  "rescript": "12.3.1",
@@ -61,7 +61,7 @@ Usage: deploy-app up|down [--manifest <path>] [--stack <name>]
61
61
  Deploy an app to AWS, or remove it again.
62
62
 
63
63
  deploy-app up Deploy the platform and every plugin, bake the component
64
- manifest, create the accounts in .reventless/users.<stack>.yaml,
64
+ manifest, create the accounts in .reventless/accounts.<stack>.yaml,
65
65
  and print the web address and the sign-ins.
66
66
  deploy-app down Remove every stack, and the Lambda layer and SSM parameter
67
67
  \`up\` created.
@@ -495,12 +495,23 @@ let outputString = (outputs: dict<Automation.outputValue>, name: string): option
495
495
  /** The stack's own accounts file beside the platform, started from the app's
496
496
  template, or `None` when the app has no template — then there is nothing to
497
497
  create. Per stack because the file records the ids one pool minted: sharing
498
- `users.yaml` would overwrite another deployment's record of its accounts. */
499
- let accountsFileName = stack => `users.${stack}.yaml`
498
+ `accounts.yaml` would overwrite another deployment's record of its accounts. A stack deployed
499
+ before the file was renamed keeps its `users.<stack>.yaml` until it is renamed. */
500
+ let accountsFileName = stack => `accounts.${stack}.yaml`
501
+ let oldAccountsFileName = stack => `users.${stack}.yaml`
500
502
 
501
503
  let accountsFile = (~platformDir: string, ~stack: string): option<string> => {
502
- let file = NodePath.join([platformDir, ".reventless", accountsFileName(stack)])
503
- let template = NodePath.join([platformDir, Manifest.templateName])
504
+ let file = {
505
+ let current = NodePath.join([platformDir, ".reventless", accountsFileName(stack)])
506
+ let old = NodePath.join([platformDir, ".reventless", oldAccountsFileName(stack)])
507
+ if !NodeFs.existsSync(current) && NodeFs.existsSync(old) {
508
+ Manifest.noticeOldName(old)
509
+ old
510
+ } else {
511
+ current
512
+ }
513
+ }
514
+ let template = Manifest.templateIn(platformDir)
504
515
  switch (NodeFs.existsSync(file), NodeFs.existsSync(template)) {
505
516
  | (false, false) => None
506
517
  | (true, _) => Some(file)
@@ -87,7 +87,7 @@ Usage: deploy-app up|down [--manifest <path>] [--stack <name>]
87
87
  Deploy an app to AWS, or remove it again.
88
88
 
89
89
  deploy-app up Deploy the platform and every plugin, bake the component
90
- manifest, create the accounts in .reventless/users.<stack>.yaml,
90
+ manifest, create the accounts in .reventless/accounts.<stack>.yaml,
91
91
  and print the web address and the sign-ins.
92
92
  deploy-app down Remove every stack, and the Lambda layer and SSM parameter
93
93
  \`up\` created.
@@ -611,12 +611,18 @@ function outputString(outputs, name) {
611
611
  }
612
612
 
613
613
  function accountsFileName(stack) {
614
+ return `accounts.` + stack + `.yaml`;
615
+ }
616
+
617
+ function oldAccountsFileName(stack) {
614
618
  return `users.` + stack + `.yaml`;
615
619
  }
616
620
 
617
621
  function accountsFile(platformDir, stack) {
618
- let file = Nodepath.join(platformDir, ".reventless", accountsFileName(stack));
619
- let template = Nodepath.join(platformDir, AccountsManifest$Reventless.templateName);
622
+ let current = Nodepath.join(platformDir, ".reventless", accountsFileName(stack));
623
+ let old = Nodepath.join(platformDir, ".reventless", oldAccountsFileName(stack));
624
+ let file = !Nodefs.existsSync(current) && Nodefs.existsSync(old) ? (AccountsManifest$Reventless.noticeOldName(old), old) : current;
625
+ let template = AccountsManifest$Reventless.templateIn(platformDir);
620
626
  let match = Nodefs.existsSync(file);
621
627
  let match$1 = Nodefs.existsSync(template);
622
628
  if (match) {
@@ -946,6 +952,7 @@ export {
946
952
  deploy,
947
953
  outputString,
948
954
  accountsFileName,
955
+ oldAccountsFileName,
949
956
  accountsFile,
950
957
  signInLines,
951
958
  up,
@@ -17,7 +17,7 @@ one alone is enough.
17
17
  pnpm exec provision-accounts
18
18
  ```
19
19
 
20
- It reads `.reventless/users.yaml`, and knows nothing about any particular
20
+ It reads `.reventless/accounts.yaml`, and knows nothing about any particular
21
21
  application's cast: the manifest belongs to whoever is deploying. A demo's four
22
22
  roles and a company's twelve staff are the same run. Which pool it writes to is
23
23
  [ProvisionProvider]'s question, and usually needs no argument either.
@@ -76,7 +76,7 @@ Turn a declared cast of accounts into working sign-ins.
76
76
  created the pool or was handed one.
77
77
  --stack <name> Read that output from this stack instead of the selected
78
78
  one. The run always names the stack it used.
79
- --file <path> The manifest. Defaults to .reventless/users.yaml relative
79
+ --file <path> The manifest. Defaults to .reventless/accounts.yaml relative
80
80
  to the working directory, which is where both platforms
81
81
  keep it.
82
82
 
@@ -39,7 +39,7 @@ Turn a declared cast of accounts into working sign-ins.
39
39
  created the pool or was handed one.
40
40
  --stack <name> Read that output from this stack instead of the selected
41
41
  one. The run always names the stack it used.
42
- --file <path> The manifest. Defaults to .reventless/users.yaml relative
42
+ --file <path> The manifest. Defaults to .reventless/accounts.yaml relative
43
43
  to the working directory, which is where both platforms
44
44
  keep it.
45
45
 
@@ -198,7 +198,7 @@ ${passwordNote}
198
198
  This script bootstraps the first account only, which is the one that cannot be
199
199
  made through a signed-in session because there is no signed-in session yet. The
200
200
  rest of the cast is a list rather than a command: declare it in
201
- .reventless/users.yaml and run \`pnpm exec provision-accounts\`.`
201
+ .reventless/accounts.yaml and run \`pnpm exec provision-accounts\`.`
202
202
 
203
203
  type request = {email: string, providerId?: string, stack?: string}
204
204
 
@@ -119,7 +119,7 @@ The first administrator is ready.
119
119
  This script bootstraps the first account only, which is the one that cannot be
120
120
  made through a signed-in session because there is no signed-in session yet. The
121
121
  rest of the cast is a list rather than a command: declare it in
122
- .reventless/users.yaml and run \`pnpm exec provision-accounts\`.`;
122
+ .reventless/accounts.yaml and run \`pnpm exec provision-accounts\`.`;
123
123
  }
124
124
 
125
125
  function parseRequest(argv) {
@@ -1,7 +1,7 @@
1
1
  // The identity provider a deployed platform provisions: principals in its
2
2
  // Cognito pool, and each new one's domain id in the identity store the pool's
3
3
  // pre-token trigger reads. The AWS counterpart of the local platform's
4
- // `Capability_IdentityProvider_UserStore`, and reached the same way — on the
4
+ // `Capability_IdentityProvider_AccountStore`, and reached the same way — on the
5
5
  // capability record a slice's `translate` receives.
6
6
  //
7
7
  // Pulumi-free so it ships in the slice Lambda. The pool calls go through an
@@ -42,16 +42,12 @@
42
42
  // "mappingsModule": string, // dynamic import for the user extension file (`module Mapping`)
43
43
  // "delegateModule": string, // dynamic import for the Delegate spec (aggregate / slice)
44
44
  // "extensionPointName": string, // service key for incomingExtensionEventHandlers (e.g. "Ordering.Orders")
45
- // "aggregateNames": string[], // outgoing service keys + filter into top-level publishToAggregates
46
- // "readModelNames": string[] // RMs this extension may enqueue events into (filter into readModelQueueUrls)
45
+ // "aggregateNames": string[] // outgoing service keys + filter into top-level publishToAggregates
47
46
  // }
48
- // ],
49
- // (publishToAggregates / readModelQueueUrls moved to the queueUrls.json asset —
50
- // one entry per target, so they outgrew the 4KB environment. Each still maps a
51
- // target name to the PTA_/PRM_ env var holding its SQS URL.)
52
- // "readModelNamesForSourceName": { // deploy-side inversion of RM.sourceNames; sourceServiceName → [rmName]
53
- // "Ordering.Orders": ["ProductDemands"]
54
- // }
47
+ // ]
48
+ // (publishToAggregates moved to the queueUrls.json asset — one entry per
49
+ // target, so it outgrew the 4KB environment. It maps a target name to the
50
+ // PTA_ env var holding its SQS URL.)
55
51
  // }
56
52
  //
57
53
  // Why this shape and not separate entry points per Lambda: at deploy time both the
@@ -187,8 +183,6 @@ async function buildHandler() {
187
183
  const ops = extensionOperationsMake(specMod)(extBuilder.ConnectPluginMappings)({
188
184
  publishToAggregates,
189
185
  publishToPluginExtensionPoint,
190
- readModelNamesForSourceName: {},
191
- publishToReadModels: {},
192
186
  queryEngine,
193
187
  });
194
188
  connectExtensionHandler = {
@@ -229,12 +223,10 @@ async function buildHandler() {
229
223
  mappings: [transformedMapping],
230
224
  };
231
225
 
232
- const dicts = Ops.extensionPublishDicts(config, queueUrlNames, ext);
226
+ const dicts = Ops.extensionPublishDicts(queueUrlNames, ext);
233
227
  const ops = extensionOperationsMake(epSpec)(mappingsModule)({
234
228
  publishToAggregates: dicts.publishToAggregates,
235
229
  publishToPluginExtensionPoint,
236
- readModelNamesForSourceName: dicts.readModelNamesForSourceName,
237
- publishToReadModels: dicts.publishToReadModels,
238
230
  queryEngine,
239
231
  });
240
232
 
@@ -61,15 +61,6 @@ external scanByTableName: (
61
61
  let exnMessage = (exn: exn): string =>
62
62
  exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown error")
63
63
 
64
- // ── Absent-field normalization ──────────────────────────────────────────────
65
- // pluginDefinition's optionals omit the key, so a plain JSON.parse (not a sury
66
- // parse) yields `undefined` and ReScript reads it as None unaided. Kept because
67
- // the same records also arrive from stores and peers written when an absent
68
- // field was `null`, which ReScript's option would misread as Some.
69
-
70
- external optionAsNullable: option<'a> => Nullable.t<'a> = "%identity"
71
- let jsOption = (v: option<'a>): option<'a> => v->optionAsNullable->Nullable.toOption
72
-
73
64
  // ── HANDLER_CONFIG ──────────────────────────────────────────────────────────
74
65
  // Written by PluginRuntime_Builder.forPluginEventCollector; the field-by-field
75
66
  // schema is documented in EventCollectorEntryPoint.mjs. Explicitly decoded (no
@@ -94,7 +85,6 @@ type extensionConfig = {
94
85
  delegateModule: string,
95
86
  extensionPointName: string,
96
87
  aggregateNames: array<string>,
97
- readModelNames: array<string>,
98
88
  }
99
89
  type handlerConfig = {
100
90
  queueUrl: string,
@@ -108,17 +98,13 @@ type handlerConfig = {
108
98
  extensionPoints: array<extensionPointConfig>,
109
99
  connectExtension?: connectExtensionConfig,
110
100
  extensions: array<extensionConfig>,
111
- readModelNamesForSourceName: dict<array<string>>,
112
101
  }
113
102
 
114
- // The target→env-var maps that used to ride HANDLER_CONFIG, now read from
103
+ // The target→env-var map that used to ride HANDLER_CONFIG, now read from
115
104
  // queueUrls.json (PluginRuntime_Builder). Values are env var NAMES, not URLs —
116
105
  // the URLs stay in the environment because the builder cannot resolve those
117
106
  // Outputs in bulk.
118
- type queueUrlNames = {
119
- publishToAggregates: dict<string>,
120
- readModelQueueUrls: dict<string>,
121
- }
107
+ type queueUrlNames = {publishToAggregates: dict<string>}
122
108
 
123
109
  let strOf = (obj: dict<JSON.t>, key: string): option<string> =>
124
110
  obj->Dict.get(key)->Option.flatMap(JSON.Decode.string)
@@ -196,7 +182,6 @@ let parseHandlerConfig = (rawJson: string): handlerConfig => {
196
182
  delegateModule: ext->strOf("delegateModule")->Option.getOr(""),
197
183
  extensionPointName,
198
184
  aggregateNames: ext->strArrOf("aggregateNames"),
199
- readModelNames: ext->strArrOf("readModelNames"),
200
185
  }
201
186
  })
202
187
  )
@@ -211,17 +196,6 @@ let parseHandlerConfig = (rawJson: string): handlerConfig => {
211
196
  mappingsModule: ce->strOf("mappingsModule")->Option.getOr(""),
212
197
  extensionPointName: ce->strOf("extensionPointName")->Option.getOr(""),
213
198
  })
214
- let readModelNamesForSourceName =
215
- obj
216
- ->Dict.get("readModelNamesForSourceName")
217
- ->Option.flatMap(JSON.Decode.object)
218
- ->Option.getOr(Dict.make())
219
- ->Dict.toArray
220
- ->Array.map(((k, v)) => (
221
- k,
222
- v->JSON.Decode.array->Option.getOr([])->Array.filterMap(JSON.Decode.string),
223
- ))
224
- ->Dict.fromArray
225
199
  {
226
200
  queueUrl: obj->strOf("queueUrl")->Option.getOr(""),
227
201
  pluginExtensionPointCmdTopicUrl: obj
@@ -236,7 +210,6 @@ let parseHandlerConfig = (rawJson: string): handlerConfig => {
236
210
  extensionPoints,
237
211
  ?connectExtension,
238
212
  extensions,
239
- readModelNamesForSourceName,
240
213
  }
241
214
  }
242
215
 
@@ -253,7 +226,6 @@ type pluginProjection = {
253
226
  eventCollector: string,
254
227
  extensions: array<Reventless.Plugin.extensionDefinition>,
255
228
  extensionPoints: array<Reventless.Plugin.extensionPointDefinition>,
256
- dcbEventLog?: Reventless.Plugin.dcbEventLogDefinition,
257
229
  }
258
230
 
259
231
  let projectPluginRow = (row: JSON.t): option<pluginProjection> =>
@@ -262,17 +234,6 @@ let projectPluginRow = (row: JSON.t): option<pluginProjection> =>
262
234
  | Some(obj) =>
263
235
  switch (obj->strOf("id"), obj->strOf("status")) {
264
236
  | (Some(id), Some(status)) =>
265
- let dcbEventLog =
266
- obj
267
- ->Dict.get("dcbEventLog")
268
- ->Option.flatMap(JSON.Decode.object)
269
- ->Option.flatMap(d =>
270
- switch (d->strOf("name"), d->strOf("eventTopicArn")) {
271
- | (Some(name), Some(eventTopicArn)) =>
272
- Some(({name, eventTopicArn}: Reventless.Plugin.dcbEventLogDefinition))
273
- | _ => None
274
- }
275
- )
276
237
  let extensions =
277
238
  obj
278
239
  ->Dict.get("extensions")
@@ -288,7 +249,6 @@ let projectPluginRow = (row: JSON.t): option<pluginProjection> =>
288
249
  (extensionPointName): Reventless.Plugin.extensionDefinition => {
289
250
  name: eo->strOf("name")->Option.getOr(""),
290
251
  extensionPointName,
291
- dcbSources: eo->strArrOf("dcbSources"),
292
252
  },
293
253
  )
294
254
  )
@@ -323,30 +283,26 @@ let projectPluginRow = (row: JSON.t): option<pluginProjection> =>
323
283
  eventCollector: obj->strOf("eventCollector")->Option.getOr(""),
324
284
  extensions,
325
285
  extensionPoints,
326
- ?dcbEventLog,
327
286
  })
328
287
  | _ => None
329
288
  }
330
289
  }
331
290
 
332
291
  // A pluginDefinition viewed as a projection (the manage logic reads only the
333
- // shared subset; `status` is never read for the acting plugin). Array/option
334
- // fields stay normalized: a definition arriving over the wire, rather than from
335
- // loadPluginDefinition's decode, may still carry null for an absent field.
292
+ // shared subset; `status` is never read for the acting plugin).
336
293
  let projectionOfDefinition = (d: Reventless.Plugin.pluginDefinition): pluginProjection => {
337
294
  id: d.id,
338
295
  status: "",
339
296
  eventCollector: d.eventCollector,
340
297
  extensions: d.extensions,
341
298
  extensionPoints: d.extensionPoints,
342
- dcbEventLog: ?(d.dcbEventLog->jsOption),
343
299
  }
344
300
 
345
301
  // ── Cross-plugin SNS subscription manager ───────────────────────────────────
346
302
  // When DoConnectPlugin / DoDisconnectPlugin fires on the admin EP mapping, scan
347
303
  // the Plugin RM for currently-Connected peers and wire (or tear down) SNS
348
304
  // subscriptions between this plugin's EventCollector queue and peer plugins'
349
- // EP/DCB event topics — in both directions. subscribeQueueToTopic is idempotent
305
+ // EP event topics — in both directions. subscribeQueueToTopic is idempotent
350
306
  // (lists existing subscriptions first); unsubscribeQueueFromTopic is
351
307
  // best-effort. Failures are logged but do not throw — admin's RM projection
352
308
  // still proceeds and cold-start reconciliation re-attempts missed work.
@@ -453,50 +409,6 @@ let makeManageSubscriptions = (tableName: string): option<
453
409
  }
454
410
  }
455
411
  }
456
- // DCB sources this plugin's extensions consume: owning peer's DCB
457
- // EventTopic → this EC queue.
458
- for i in 0 to pluginDef.extensions->Array.length - 1 {
459
- let ext = pluginDef.extensions->Array.getUnsafe(i)
460
- for j in 0 to ext.dcbSources->Array.length - 1 {
461
- let dcbSourceName = ext.dcbSources->Array.getUnsafe(j)
462
- switch peers->Array.find(p =>
463
- switch p.dcbEventLog {
464
- | Some(l) => l.name == dcbSourceName
465
- | None => false
466
- }
467
- ) {
468
- | Some(peer) =>
469
- switch peer.dcbEventLog {
470
- | Some(l) =>
471
- await op(
472
- pluginDef.eventCollector,
473
- l.eventTopicArn,
474
- `dcb ${dcbSourceName} -> ${pluginDef.id}`,
475
- )
476
- | None => ()
477
- }
478
- | None => ()
479
- }
480
- }
481
- }
482
- // If THIS plugin has a DCB EventLog: its topic → each consuming
483
- // peer's EC queue.
484
- switch pluginDef.dcbEventLog {
485
- | Some(myDcb) =>
486
- for j in 0 to peers->Array.length - 1 {
487
- let peer = peers->Array.getUnsafe(j)
488
- let consuming =
489
- peer.extensions->Array.some(e => e.dcbSources->Array.includes(myDcb.name))
490
- if consuming {
491
- await op(
492
- peer.eventCollector,
493
- myDcb.eventTopicArn,
494
- `dcb ${myDcb.name} -> ${peer.id}`,
495
- )
496
- }
497
- }
498
- | None => ()
499
- }
500
412
  }
501
413
  },
502
414
  )
@@ -630,20 +542,12 @@ let buildPublishToAggregates = (map: dict<string>): dict<
630
542
  ))
631
543
  ->Dict.fromArray
632
544
 
633
- // Per-extension publish dicts, filtered to the extension's declared aggregate /
634
- // read-model names; entries with no env-var mapping or an empty URL are skipped
635
- // (same as the former JS `continue`s).
636
- type extensionDicts = {
637
- publishToAggregates: dict<ReventlessCore.CommandTopic.publishJsons>,
638
- publishToReadModels: dict<ReventlessCore.CommandTopic.publishJsons>,
639
- readModelNamesForSourceName: dict<array<string>>,
640
- }
545
+ // Per-extension publish dict, filtered to the extension's declared aggregate
546
+ // names; entries with no env-var mapping or an empty URL are skipped (same as
547
+ // the former JS `continue`s).
548
+ type extensionDicts = {publishToAggregates: dict<ReventlessCore.CommandTopic.publishJsons>}
641
549
 
642
- let extensionPublishDicts = (
643
- config: handlerConfig,
644
- names: queueUrlNames,
645
- ext: extensionConfig,
646
- ): extensionDicts => {
550
+ let extensionPublishDicts = (names: queueUrlNames, ext: extensionConfig): extensionDicts => {
647
551
  let resolve = (names: array<string>, envVarNames: dict<string>) =>
648
552
  names
649
553
  ->Array.filterMap(name =>
@@ -654,18 +558,7 @@ let extensionPublishDicts = (
654
558
  ->Option.map(queueUrl => (name, makePublishJsons(queueUrl)))
655
559
  )
656
560
  ->Dict.fromArray
657
- {
658
- publishToAggregates: resolve(ext.aggregateNames, names.publishToAggregates),
659
- publishToReadModels: resolve(ext.readModelNames, names.readModelQueueUrls),
660
- // Scoped to this extension's EP — Extension_Operations uses this dict to
661
- // decide which RMs receive each incoming event.
662
- readModelNamesForSourceName: Dict.fromArray([
663
- (
664
- ext.extensionPointName,
665
- config.readModelNamesForSourceName->Dict.get(ext.extensionPointName)->Option.getOr([]),
666
- ),
667
- ]),
668
- }
561
+ {publishToAggregates: resolve(ext.aggregateNames, names.publishToAggregates)}
669
562
  }
670
563
 
671
564
  let warnSkippedExtension = (ext: extensionConfig) =>
@@ -702,8 +595,8 @@ let loadPluginDefinition = (): Reventless.Plugin.pluginDefinition =>
702
595
  )
703
596
  }
704
597
 
705
- // The target→env-var maps, read once at cold start. A plugin that publishes
706
- // nowhere writes empty maps rather than omitting the file, so a read failure is
598
+ // The target→env-var map, read once at cold start. A plugin that publishes
599
+ // nowhere writes an empty map rather than omitting the file, so a read failure is
707
600
  // a genuinely broken archive and not an empty-map case worth guessing at.
708
601
  let loadQueueUrlNames = (): queueUrlNames =>
709
602
  try {
@@ -713,10 +606,7 @@ let loadQueueUrlNames = (): queueUrlNames =>
713
606
  ->JSON.parseOrThrow
714
607
  ->JSON.Decode.object
715
608
  ->Option.getOr(Dict.make())
716
- {
717
- publishToAggregates: obj->strDictOf("publishToAggregates"),
718
- readModelQueueUrls: obj->strDictOf("readModelQueueUrls"),
719
- }
609
+ {publishToAggregates: obj->strDictOf("publishToAggregates")}
720
610
  } catch {
721
611
  | exn =>
722
612
  JsError.throwWithMessage("Failed to load queueUrls.json from asset zip: " ++ exnMessage(exn))
@@ -7,7 +7,6 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
7
7
  import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
8
8
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
9
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
- import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
11
10
  import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
12
11
  import * as SNS_Helpers$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/SNS_Helpers.res.mjs";
13
12
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
@@ -64,14 +63,6 @@ function exnMessage(exn) {
64
63
  return Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown error");
65
64
  }
66
65
 
67
- function jsOption(v) {
68
- if (v == null) {
69
- return;
70
- } else {
71
- return Primitive_option.some(v);
72
- }
73
- }
74
-
75
66
  function strOf(obj, key) {
76
67
  return Stdlib_Option.flatMap(obj[key], Stdlib_JSON.Decode.string);
77
68
  }
@@ -135,8 +126,7 @@ function parseHandlerConfig(rawJson) {
135
126
  mappingsModule: Stdlib_Option.getOr(strOf(ext, "mappingsModule"), ""),
136
127
  delegateModule: Stdlib_Option.getOr(strOf(ext, "delegateModule"), ""),
137
128
  extensionPointName: extensionPointName,
138
- aggregateNames: strArrOf(ext, "aggregateNames"),
139
- readModelNames: strArrOf(ext, "readModelNames")
129
+ aggregateNames: strArrOf(ext, "aggregateNames")
140
130
  };
141
131
  })) : Stdlib_JsError.throwWithMessage("HANDLER_CONFIG.extensions must be an array");
142
132
  let connectExtension = Stdlib_Option.map(Stdlib_Option.flatMap(obj["connectExtension"], Stdlib_JSON.Decode.object), ce => ({
@@ -144,10 +134,6 @@ function parseHandlerConfig(rawJson) {
144
134
  mappingsModule: Stdlib_Option.getOr(strOf(ce, "mappingsModule"), ""),
145
135
  extensionPointName: Stdlib_Option.getOr(strOf(ce, "extensionPointName"), "")
146
136
  }));
147
- let readModelNamesForSourceName = Object.fromEntries(Object.entries(Stdlib_Option.getOr(Stdlib_Option.flatMap(obj["readModelNamesForSourceName"], Stdlib_JSON.Decode.object), {})).map(param => [
148
- param[0],
149
- Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_JSON.Decode.array(param[1]), []), Stdlib_JSON.Decode.string)
150
- ]));
151
137
  return {
152
138
  queueUrl: Stdlib_Option.getOr(strOf(obj, "queueUrl"), ""),
153
139
  pluginExtensionPointCmdTopicUrl: Stdlib_Option.getOr(strOf(obj, "pluginExtensionPointCmdTopicUrl"), ""),
@@ -159,8 +145,7 @@ function parseHandlerConfig(rawJson) {
159
145
  schedulerQueueName: Stdlib_Option.getOr(strOf(obj, "schedulerQueueName"), ""),
160
146
  extensionPoints: extensionPoints,
161
147
  connectExtension: connectExtension,
162
- extensions: extensions,
163
- readModelNamesForSourceName: readModelNamesForSourceName
148
+ extensions: extensions
164
149
  };
165
150
  }
166
151
 
@@ -177,20 +162,9 @@ function projectPluginRow(row) {
177
162
  if (match$1 === undefined) {
178
163
  return;
179
164
  }
180
- let dcbEventLog = Stdlib_Option.flatMap(Stdlib_Option.flatMap(obj["dcbEventLog"], Stdlib_JSON.Decode.object), d => {
181
- let match = strOf(d, "name");
182
- let match$1 = strOf(d, "eventTopicArn");
183
- if (match !== undefined && match$1 !== undefined) {
184
- return {
185
- name: match,
186
- eventTopicArn: match$1
187
- };
188
- }
189
- });
190
165
  let extensions = Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(obj["extensions"], Stdlib_JSON.Decode.array), []), e => Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(e), eo => Stdlib_Option.map(strOf(eo, "extensionPointName"), extensionPointName => ({
191
166
  name: Stdlib_Option.getOr(strOf(eo, "name"), ""),
192
- extensionPointName: extensionPointName,
193
- dcbSources: strArrOf(eo, "dcbSources")
167
+ extensionPointName: extensionPointName
194
168
  }))));
195
169
  let extensionPoints = Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(obj["extensionPoints"], Stdlib_JSON.Decode.array), []), ep => Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(ep), epo => {
196
170
  let match = strOf(epo, "name");
@@ -208,8 +182,7 @@ function projectPluginRow(row) {
208
182
  status: match$1,
209
183
  eventCollector: Stdlib_Option.getOr(strOf(obj, "eventCollector"), ""),
210
184
  extensions: extensions,
211
- extensionPoints: extensionPoints,
212
- dcbEventLog: dcbEventLog
185
+ extensionPoints: extensionPoints
213
186
  };
214
187
  }
215
188
 
@@ -219,8 +192,7 @@ function projectionOfDefinition(d) {
219
192
  status: "",
220
193
  eventCollector: d.eventCollector,
221
194
  extensions: d.extensions,
222
- extensionPoints: d.extensionPoints,
223
- dcbEventLog: Primitive_option.fromNullable(d.dcbEventLog)
195
+ extensionPoints: d.extensionPoints
224
196
  };
225
197
  }
226
198
 
@@ -334,37 +306,6 @@ function makeManageSubscriptions(tableName) {
334
306
  }
335
307
  }
336
308
  }
337
- for (let i$2 = 0, i_finish$2 = pluginDef.extensions.length; i$2 < i_finish$2; ++i$2) {
338
- let ext$1 = pluginDef.extensions[i$2];
339
- for (let j$2 = 0, j_finish$2 = ext$1.dcbSources.length; j$2 < j_finish$2; ++j$2) {
340
- let dcbSourceName = ext$1.dcbSources[j$2];
341
- let peer$2 = peers.find(p => {
342
- let l = p.dcbEventLog;
343
- if (l !== undefined) {
344
- return l.name === dcbSourceName;
345
- } else {
346
- return false;
347
- }
348
- });
349
- if (peer$2 !== undefined) {
350
- let l = peer$2.dcbEventLog;
351
- if (l !== undefined) {
352
- await op(pluginDef.eventCollector, l.eventTopicArn, `dcb ` + dcbSourceName + ` -> ` + pluginDef.id);
353
- }
354
- }
355
- }
356
- }
357
- let myDcb = pluginDef.dcbEventLog;
358
- if (myDcb === undefined) {
359
- return;
360
- }
361
- for (let j$3 = 0, j_finish$3 = peers.length; j$3 < j_finish$3; ++j$3) {
362
- let peer$3 = peers[j$3];
363
- let consuming = peer$3.extensions.some(e => e.dcbSources.includes(myDcb.name));
364
- if (consuming) {
365
- await op(peer$3.eventCollector, myDcb.eventTopicArn, `dcb ` + myDcb.name + ` -> ` + peer$3.id);
366
- }
367
- }
368
309
  };
369
310
  }
370
311
  }
@@ -494,18 +435,13 @@ function buildPublishToAggregates(map) {
494
435
  ]));
495
436
  }
496
437
 
497
- function extensionPublishDicts(config, names, ext) {
438
+ function extensionPublishDicts(names, ext) {
498
439
  let resolve = (names, envVarNames) => Object.fromEntries(Stdlib_Array.filterMap(names, name => Stdlib_Option.map(Stdlib_Option.filter(Stdlib_Option.flatMap(envVarNames[name], envVarName => process.env[envVarName]), queueUrl => queueUrl !== ""), queueUrl => [
499
440
  name,
500
441
  makePublishJsons(queueUrl)
501
442
  ])));
502
443
  return {
503
- publishToAggregates: resolve(ext.aggregateNames, names.publishToAggregates),
504
- publishToReadModels: resolve(ext.readModelNames, names.readModelQueueUrls),
505
- readModelNamesForSourceName: Object.fromEntries([[
506
- ext.extensionPointName,
507
- Stdlib_Option.getOr(config.readModelNamesForSourceName[ext.extensionPointName], [])
508
- ]])
444
+ publishToAggregates: resolve(ext.aggregateNames, names.publishToAggregates)
509
445
  };
510
446
  }
511
447
 
@@ -532,8 +468,7 @@ function loadQueueUrlNames() {
532
468
  try {
533
469
  let obj = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(Nodefs.readFileSync(assetPath("queueUrls.json"), "utf8"))), {});
534
470
  return {
535
- publishToAggregates: strDictOf(obj, "publishToAggregates"),
536
- readModelQueueUrls: strDictOf(obj, "readModelQueueUrls")
471
+ publishToAggregates: strDictOf(obj, "publishToAggregates")
537
472
  };
538
473
  } catch (raw_exn) {
539
474
  let exn = Primitive_exceptions.internalToException(raw_exn);
@@ -632,7 +567,6 @@ export {
632
567
  logError,
633
568
  scanByTableName,
634
569
  exnMessage,
635
- jsOption,
636
570
  strOf,
637
571
  strArrOf,
638
572
  strDictOf,
@@ -15,11 +15,11 @@ let additionalIamPolicies: array<iamPolicy> = []
15
15
 
16
16
  // CallbackFunction path — the last serialized-closure Lambda builder on AWS.
17
17
  //
18
- // REACHABLE, despite most deployments going through makeFromCodeAsset. Three
18
+ // REACHABLE, despite most deployments going through makeFromCodeAsset. Two
19
19
  // component builders instantiate ReventlessCore.EventCollectorRuntime_Builder_
20
- // PerEventCollector.Make over this module — ForeignReadModel_Builder,
21
- // SideEffectHandler_PerSideEffectHandler and Task_Builder_PerBucket — and that
22
- // functor's forEventCollector calls `make` below. The live route is a Task whose
20
+ // PerEventCollector.Make over this module — SideEffectHandler_PerSideEffectHandler
21
+ // and Task_Builder_PerBucket — and that functor's forEventCollector calls `make`
22
+ // below. The live route is a Task whose
23
23
  // spec returns `Task.sideEffects`: Task_Builder then constructs a
24
24
  // SideEffectHandler, which provisions its collector Lambda here.
25
25
  //
@@ -29,7 +29,7 @@ let additionalIamPolicies: array<iamPolicy> = []
29
29
  // where an ordering-aws deploy came up four Lambdas short). Where it does
30
30
  // serialize, it then resolves @aws-sdk against whatever the layer and the runtime
31
31
  // each supply, carrying the cold-start SDK-skew failure compiled entry points
32
- // were introduced to avoid. Converting these three builders to a compiled entry
32
+ // were introduced to avoid. Converting these two builders to a compiled entry
33
33
  // point is the fix; until then a side-effect-bearing Task is the exposed surface.
34
34
  //
35
35
  // `make` cannot simply be dropped: Runtime.Environment requires environmentMaker
@@ -346,8 +346,6 @@ module Make = (
346
346
  extensions: [],
347
347
  pluginExtensionPointCmdTopicUrl: Pulumi.Output.make(""),
348
348
  publishToAggregates: Dict.make(),
349
- readModelQueueUrls: Dict.make(),
350
- readModelNamesForSourceName: Dict.make(),
351
349
  }
352
350
  }
353
351
 
@@ -427,18 +425,6 @@ module Make = (
427
425
  envVars->Dict.set(envVarName, queueUrlOutput->Pulumi.Output.asInput)
428
426
  })
429
427
 
430
- // Same indirection for ReadModel EventCollector SQS URLs — only populated
431
- // when user extensions enqueue directly into RMs (Extension_Operations
432
- // publishToReadModels path). Empty for the admin Lambda and for the
433
- // current Orders_Extension fixture (its RM is fed via the slice EventLog →
434
- // RM EventColl subscription chain instead).
435
- context.readModelQueueUrls
436
- ->Dict.toArray
437
- ->Array.forEach(((_rmName, queueUrlOutput)) => {
438
- let envVarName = `PRM_${_rmName}_QUEUE_URL`
439
- envVars->Dict.set(envVarName, queueUrlOutput->Pulumi.Output.asInput)
440
- })
441
-
442
428
  // Outgoing arrays first — resolve EP eventTopicArns, then bundle everything
443
429
  // into one Output.apply that builds the JSON.
444
430
  let epEventTopicArnsOutput =
@@ -523,8 +509,7 @@ module Make = (
523
509
  // User-declared extensions — one entry per merged extension group.
524
510
  // Each entry carries enough metadata for the bundled handler to
525
511
  // dynamic-import the spec / mapping modules and filter the top-level
526
- // publishToAggregates + readModelQueueUrls maps down to the subset
527
- // this extension actually uses.
512
+ // publishToAggregates map down to the subset this extension actually uses.
528
513
  // Sort by extension name so the serialized JSON is stable across deploys.
529
514
  let extensionsArr =
530
515
  context.extensions
@@ -540,43 +525,26 @@ module Make = (
540
525
  "aggregateNames",
541
526
  ext.aggregateNames->Array.map(JSON.Encode.string)->JSON.Encode.array,
542
527
  )
543
- entryDict->Dict.set(
544
- "readModelNames",
545
- ext.readModelNames->Array.map(JSON.Encode.string)->JSON.Encode.array,
546
- )
547
528
  JSON.Encode.object(entryDict)
548
529
  })
549
530
  dict->Dict.set("extensions", JSON.Encode.array(extensionsArr))
550
531
 
551
- // publishToAggregates / readModelQueueUrls live in queueUrls.json (see the
552
- // asset bundle below). Both are one entry per target, so they grow with the
553
- // plugin, and together with the PTA_/PRM_ vars they name they carried the
554
- // catalog EventCollector past Lambda's 4KB environment ceiling. Only the
555
- // maps move: they are built from Dict.keysToArray, so they hold no Pulumi
556
- // Output and can be serialized here. The URLs themselves cannot follow —
557
- // see the note on the aggregate walk further down.
558
-
559
- let rmNamesForSourceDict = Dict.make()
560
- context.readModelNamesForSourceName
561
- ->Dict.toArray
562
- ->Array.forEach(((sourceName, rmNames)) =>
563
- rmNamesForSourceDict->Dict.set(
564
- sourceName,
565
- rmNames->Array.map(JSON.Encode.string)->JSON.Encode.array,
566
- )
567
- )
568
- dict->Dict.set("readModelNamesForSourceName", JSON.Encode.object(rmNamesForSourceDict))
532
+ // publishToAggregates lives in queueUrls.json (see the asset bundle below).
533
+ // It is one entry per target, so it grows with the plugin, and together with
534
+ // the PTA_ vars it names it carried the catalog EventCollector past Lambda's
535
+ // 4KB environment ceiling. Only the map moves: it is built from
536
+ // Dict.keysToArray, so it holds no Pulumi Output and can be serialized here.
537
+ // The URLs themselves cannot follow — see the note on the aggregate walk
538
+ // further down.
569
539
 
570
540
  let json = JSON.Encode.object(dict)->JSON.stringify
571
- // The PTA_/PRM_ vars are the rest of this Lambda's environment and the
572
- // half that grows with the plugin. Their names are known here; their
573
- // values are still Outputs, so the budget check estimates those.
574
- let queueUrlKeys = Array.concat(
541
+ // The PTA_ vars are the rest of this Lambda's environment and the half
542
+ // that grows with the plugin. Their names are known here; their values
543
+ // are still Outputs, so the budget check estimates those.
544
+ let queueUrlKeys =
575
545
  context.publishToAggregates
576
546
  ->Dict.keysToArray
577
- ->Array.map(n => `PTA_${n}_QUEUE_URL`),
578
- context.readModelQueueUrls->Dict.keysToArray->Array.map(n => `PRM_${n}_QUEUE_URL`),
579
- )
547
+ ->Array.map(n => `PTA_${n}_QUEUE_URL`)
580
548
  Util_LambdaEnvBudget.check(
581
549
  ~lambdaName=name,
582
550
  ~handlerConfigJson=json,
@@ -660,10 +628,6 @@ module Make = (
660
628
  "publishToAggregates",
661
629
  nameMap(context.publishToAggregates->Dict.keysToArray, "PTA"),
662
630
  )
663
- queueUrlsDict->Dict.set(
664
- "readModelQueueUrls",
665
- nameMap(context.readModelQueueUrls->Dict.keysToArray, "PRM"),
666
- )
667
631
  extraStringAssets->Dict.set(
668
632
  "queueUrls.json",
669
633
  JSON.Encode.object(queueUrlsDict)->JSON.stringify,
@@ -268,9 +268,7 @@ function Make(EventCollectorChannel) {
268
268
  }],
269
269
  extensions: [],
270
270
  pluginExtensionPointCmdTopicUrl: Pulumi.output(""),
271
- publishToAggregates: {},
272
- readModelQueueUrls: {},
273
- readModelNamesForSourceName: {}
271
+ publishToAggregates: {}
274
272
  };
275
273
  };
276
274
  let forPluginEventCollector = (param, eventTopics, resources, memorySizeOpt, timeoutOpt, eventCollector) => {
@@ -303,10 +301,6 @@ function Make(EventCollectorChannel) {
303
301
  let envVarName = `PTA_` + param[0] + `_QUEUE_URL`;
304
302
  envVars[envVarName] = param[1];
305
303
  });
306
- Object.entries(context.readModelQueueUrls).forEach(param => {
307
- let envVarName = `PRM_` + param[0] + `_QUEUE_URL`;
308
- envVars[envVarName] = param[1];
309
- });
310
304
  let epEventTopicArnsOutput = Pulumi.all(context.extensionPoints.map(ep => ep.eventTopicArn));
311
305
  let handlerConfigJson = Pulumi.all([
312
306
  queue.id,
@@ -371,17 +365,11 @@ function Make(EventCollectorChannel) {
371
365
  entryDict["delegateModule"] = ext.delegateModule;
372
366
  entryDict["extensionPointName"] = ext.extensionPointName;
373
367
  entryDict["aggregateNames"] = ext.aggregateNames.map(prim => prim);
374
- entryDict["readModelNames"] = ext.readModelNames.map(prim => prim);
375
368
  return entryDict;
376
369
  });
377
370
  dict["extensions"] = extensionsArr;
378
- let rmNamesForSourceDict = {};
379
- Object.entries(context.readModelNamesForSourceName).forEach(param => {
380
- rmNamesForSourceDict[param[0]] = param[1].map(prim => prim);
381
- });
382
- dict["readModelNamesForSourceName"] = rmNamesForSourceDict;
383
371
  let json = JSON.stringify(dict);
384
- let queueUrlKeys = Object.keys(context.publishToAggregates).map(n => `PTA_` + n + `_QUEUE_URL`).concat(Object.keys(context.readModelQueueUrls).map(n => `PRM_` + n + `_QUEUE_URL`));
372
+ let queueUrlKeys = Object.keys(context.publishToAggregates).map(n => `PTA_` + n + `_QUEUE_URL`);
385
373
  Util_LambdaEnvBudget$ReventlessAws.check(name, json, queueUrlKeys);
386
374
  return json;
387
375
  });
@@ -428,7 +416,6 @@ function Make(EventCollectorChannel) {
428
416
  };
429
417
  let queueUrlsDict = {};
430
418
  queueUrlsDict["publishToAggregates"] = nameMap(Object.keys(context.publishToAggregates), "PTA");
431
- queueUrlsDict["readModelQueueUrls"] = nameMap(Object.keys(context.readModelQueueUrls), "PRM");
432
419
  extraStringAssets["queueUrls.json"] = JSON.stringify(queueUrlsDict);
433
420
  extraStringAssets["uiFragments.json"] = param[1];
434
421
  return Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/EventCollectorEntryPoint.mjs", packageDirs, extraStringAssets, undefined);
@@ -32,9 +32,10 @@ describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
32
32
  expect(p.eventCollector)->toBe("") // defaulted when absent
33
33
  expect(p.extensions->Array.length)->toBe(0)
34
34
  expect(p.extensionPoints->Array.length)->toBe(0)
35
- expect(p.dcbEventLog->Option.isNone)->toBe(true)
36
35
  })
37
36
 
37
+ // A row written before `dcbSources` left the extension definition still
38
+ // carries the key; the projection reads past it.
38
39
  testSync("decodes extensions, dropping entries without extensionPointName", () => {
39
40
  let row = obj([
40
41
  ("id", str("Ordering@2")),
@@ -46,7 +47,7 @@ describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
46
47
  obj([
47
48
  ("name", str("OrdersExt")),
48
49
  ("extensionPointName", str("Catalog.Products")),
49
- ("dcbSources", JSON.Encode.array([str("prod"), JSON.Encode.int(7)])),
50
+ ("dcbSources", JSON.Encode.array([])),
50
51
  ]),
51
52
  obj([("name", str("Malformed"))]), // no extensionPointName -> dropped
52
53
  ]),
@@ -58,11 +59,9 @@ describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
58
59
  let e = p.extensions->Array.getUnsafe(0)
59
60
  expect(e.name)->toBe("OrdersExt")
60
61
  expect(e.extensionPointName)->toBe("Catalog.Products")
61
- // Non-string dcbSources elements are filtered out.
62
- expect(e.dcbSources)->toEqual(["prod"])
63
62
  })
64
63
 
65
- testSync("decodes extensionPoints and dcbEventLog; drops EPs without name/eventTopic", () => {
64
+ testSync("decodes extensionPoints; drops EPs without name/eventTopic", () => {
66
65
  let row = obj([
67
66
  ("id", str("Catalog@1")),
68
67
  ("status", str("Connected")),
@@ -73,6 +72,7 @@ describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
73
72
  obj([("name", str("NoTopic"))]), // no eventTopic -> dropped
74
73
  ]),
75
74
  ),
75
+ // Still on older rows; the subscription manager no longer reads it.
76
76
  ("dcbEventLog", obj([("name", str("catalog-dcb")), ("eventTopicArn", str("dcb-arn"))])),
77
77
  ])
78
78
  let p = project(row)->Option.getOrThrow
@@ -81,18 +81,6 @@ describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
81
81
  expect(ep.name)->toBe("Catalog.Products")
82
82
  expect(ep.commandTopic)->toBe("") // defaulted
83
83
  expect(ep.eventTopic)->toBe("t-arn")
84
- let dcb = p.dcbEventLog->Option.getOrThrow
85
- expect(dcb.name)->toBe("catalog-dcb")
86
- expect(dcb.eventTopicArn)->toBe("dcb-arn")
87
- })
88
-
89
- testSync("drops a dcbEventLog missing eventTopicArn", () => {
90
- let row = obj([
91
- ("id", str("Catalog@1")),
92
- ("status", str("Connected")),
93
- ("dcbEventLog", obj([("name", str("catalog-dcb"))])),
94
- ])
95
- expect((project(row)->Option.getOrThrow).dcbEventLog->Option.isNone)->toBe(true)
96
84
  })
97
85
  })
98
86
 
@@ -146,7 +134,7 @@ describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
146
134
  let c = EventCollectorEntryPoint_Ops.parseHandlerConfig(minimalConfig([]))
147
135
  expect(c.queueUrl)->toBe("q-url")
148
136
  expect(c.connectExtension->Option.isNone)->toBe(true) // absent → None
149
- expect(c.readModelNamesForSourceName->Dict.keysToArray->Array.length)->toBe(0)
137
+ expect(c.extensions->Array.length)->toBe(0)
150
138
  })
151
139
 
152
140
  testSync("connectExtension: JSON null (admin) maps to None; a record decodes", () => {
@@ -177,7 +165,7 @@ describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
177
165
  (
178
166
  "extensions",
179
167
  JSON.Encode.array([
180
- // no name, no module specifiers, no aggregate/readModel lists
168
+ // no name, no module specifiers, no aggregate list
181
169
  obj([("extensionPointName", str("Ordering.Orders"))]),
182
170
  ]),
183
171
  ),
@@ -187,10 +175,9 @@ describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
187
175
  expect(ext.name)->toBe("Ordering.Orders") // name falls back to the EP name
188
176
  expect(ext.specModule)->toBe("")
189
177
  expect(ext.aggregateNames)->toEqual([])
190
- expect(ext.readModelNames)->toEqual([])
191
178
  })
192
179
 
193
- testSync("decodes extensionPoints entries and the env-var maps", () => {
180
+ testSync("decodes extensionPoints entries", () => {
194
181
  let c = EventCollectorEntryPoint_Ops.parseHandlerConfig(
195
182
  minimalConfig([
196
183
  (
@@ -204,30 +191,38 @@ describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
204
191
  ]),
205
192
  ]),
206
193
  ),
207
- (
208
- "readModelNamesForSourceName",
209
- obj([("Ordering.Orders", JSON.Encode.array([str("ProductDemands")]))]),
210
- ),
211
194
  ]),
212
195
  )
213
196
  let ep = c.extensionPoints->Array.getUnsafe(0)
214
197
  expect(ep.eventTopicArn)->toBe("arn:ep")
215
198
  expect(ep.aggregateNames)->toEqual(["Plugin"])
216
- expect(c.readModelNamesForSourceName->Dict.get("Ordering.Orders"))->toEqual(
217
- Some(["ProductDemands"]),
218
- )
219
199
  })
220
200
 
221
- // The target→env-var maps left HANDLER_CONFIG for queueUrls.json, so a config
222
- // still carrying them must parse rather than trip the required-field check —
223
- // that is what a Lambda running an older archive sends.
224
- testSync("ignores publishToAggregates left over in HANDLER_CONFIG", () => {
201
+ // Keys HANDLER_CONFIG no longer carries — the target→env-var maps that left for
202
+ // queueUrls.json, and the read-model forwarding index with the per-extension
203
+ // `readModelNames` — must parse rather than trip the required-field check: that
204
+ // is what a Lambda running an older archive sends.
205
+ testSync("ignores keys an older HANDLER_CONFIG still carries", () => {
225
206
  let c = EventCollectorEntryPoint_Ops.parseHandlerConfig(
226
207
  minimalConfig([
227
208
  ("publishToAggregates", obj([("Plugin", str("PTA_Plugin_QUEUE_URL"))])),
228
209
  ("readModelQueueUrls", obj([("ProductDemands", str("PRM_ProductDemands_QUEUE_URL"))])),
210
+ (
211
+ "readModelNamesForSourceName",
212
+ obj([("Ordering.Orders", JSON.Encode.array([str("ProductDemands")]))]),
213
+ ),
214
+ (
215
+ "extensions",
216
+ JSON.Encode.array([
217
+ obj([
218
+ ("extensionPointName", str("Ordering.Orders")),
219
+ ("readModelNames", JSON.Encode.array([str("ProductDemands")])),
220
+ ]),
221
+ ]),
222
+ ),
229
223
  ]),
230
224
  )
231
225
  expect(c.queueUrl)->toBe("q-url")
226
+ expect((c.extensions->Array.getUnsafe(0)).extensionPointName)->toBe("Ordering.Orders")
232
227
  })
233
228
  })
@@ -43,7 +43,6 @@ globalThis.describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
43
43
  globalThis.expect(p.eventCollector).toBe("");
44
44
  globalThis.expect(p.extensions.length).toBe(0);
45
45
  globalThis.expect(p.extensionPoints.length).toBe(0);
46
- globalThis.expect(Stdlib_Option.isNone(p.dcbEventLog)).toBe(true);
47
46
  });
48
47
  globalThis.test("decodes extensions, dropping entries without extensionPointName", () => {
49
48
  let row = Object.fromEntries([
@@ -73,10 +72,7 @@ globalThis.describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
73
72
  ],
74
73
  [
75
74
  "dcbSources",
76
- [
77
- "prod",
78
- 7
79
- ]
75
+ []
80
76
  ]
81
77
  ]),
82
78
  Object.fromEntries([[
@@ -92,9 +88,8 @@ globalThis.describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
92
88
  let e = p.extensions[0];
93
89
  globalThis.expect(e.name).toBe("OrdersExt");
94
90
  globalThis.expect(e.extensionPointName).toBe("Catalog.Products");
95
- globalThis.expect(e.dcbSources).toEqual(["prod"]);
96
91
  });
97
- globalThis.test("decodes extensionPoints and dcbEventLog; drops EPs without name/eventTopic", () => {
92
+ globalThis.test("decodes extensionPoints; drops EPs without name/eventTopic", () => {
98
93
  let row = Object.fromEntries([
99
94
  [
100
95
  "id",
@@ -143,29 +138,6 @@ globalThis.describe("EventCollectorEntryPoint_Ops.projectPluginRow", () => {
143
138
  globalThis.expect(ep.name).toBe("Catalog.Products");
144
139
  globalThis.expect(ep.commandTopic).toBe("");
145
140
  globalThis.expect(ep.eventTopic).toBe("t-arn");
146
- let dcb = Stdlib_Option.getOrThrow(p.dcbEventLog, undefined);
147
- globalThis.expect(dcb.name).toBe("catalog-dcb");
148
- globalThis.expect(dcb.eventTopicArn).toBe("dcb-arn");
149
- });
150
- globalThis.test("drops a dcbEventLog missing eventTopicArn", () => {
151
- let row = Object.fromEntries([
152
- [
153
- "id",
154
- "Catalog@1"
155
- ],
156
- [
157
- "status",
158
- "Connected"
159
- ],
160
- [
161
- "dcbEventLog",
162
- Object.fromEntries([[
163
- "name",
164
- "catalog-dcb"
165
- ]])
166
- ]
167
- ]);
168
- globalThis.expect(Stdlib_Option.isNone(Stdlib_Option.getOrThrow(EventCollectorEntryPoint_Ops$ReventlessAws.projectPluginRow(row), undefined).dcbEventLog)).toBe(true);
169
141
  });
170
142
  });
171
143
 
@@ -234,7 +206,7 @@ globalThis.describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
234
206
  let c = EventCollectorEntryPoint_Ops$ReventlessAws.parseHandlerConfig(minimalConfig([]));
235
207
  globalThis.expect(c.queueUrl).toBe("q-url");
236
208
  globalThis.expect(Stdlib_Option.isNone(c.connectExtension)).toBe(true);
237
- globalThis.expect(Object.keys(c.readModelNamesForSourceName).length).toBe(0);
209
+ globalThis.expect(c.extensions.length).toBe(0);
238
210
  });
239
211
  globalThis.test("connectExtension: JSON null (admin) maps to None; a record decodes", () => {
240
212
  let asNull = EventCollectorEntryPoint_Ops$ReventlessAws.parseHandlerConfig(minimalConfig([[
@@ -274,11 +246,9 @@ globalThis.describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
274
246
  globalThis.expect(ext.name).toBe("Ordering.Orders");
275
247
  globalThis.expect(ext.specModule).toBe("");
276
248
  globalThis.expect(ext.aggregateNames).toEqual([]);
277
- globalThis.expect(ext.readModelNames).toEqual([]);
278
249
  });
279
- globalThis.test("decodes extensionPoints entries and the env-var maps", () => {
280
- let c = EventCollectorEntryPoint_Ops$ReventlessAws.parseHandlerConfig(minimalConfig([
281
- [
250
+ globalThis.test("decodes extensionPoints entries", () => {
251
+ let c = EventCollectorEntryPoint_Ops$ReventlessAws.parseHandlerConfig(minimalConfig([[
282
252
  "extensionPoints",
283
253
  [Object.fromEntries([
284
254
  [
@@ -298,21 +268,12 @@ globalThis.describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
298
268
  ["Plugin"]
299
269
  ]
300
270
  ])]
301
- ],
302
- [
303
- "readModelNamesForSourceName",
304
- Object.fromEntries([[
305
- "Ordering.Orders",
306
- ["ProductDemands"]
307
- ]])
308
- ]
309
- ]));
271
+ ]]));
310
272
  let ep = c.extensionPoints[0];
311
273
  globalThis.expect(ep.eventTopicArn).toBe("arn:ep");
312
274
  globalThis.expect(ep.aggregateNames).toEqual(["Plugin"]);
313
- globalThis.expect(c.readModelNamesForSourceName["Ordering.Orders"]).toEqual(["ProductDemands"]);
314
275
  });
315
- globalThis.test("ignores publishToAggregates left over in HANDLER_CONFIG", () => {
276
+ globalThis.test("ignores keys an older HANDLER_CONFIG still carries", () => {
316
277
  let c = EventCollectorEntryPoint_Ops$ReventlessAws.parseHandlerConfig(minimalConfig([
317
278
  [
318
279
  "publishToAggregates",
@@ -327,9 +288,30 @@ globalThis.describe("EventCollectorEntryPoint_Ops.parseHandlerConfig", () => {
327
288
  "ProductDemands",
328
289
  "PRM_ProductDemands_QUEUE_URL"
329
290
  ]])
291
+ ],
292
+ [
293
+ "readModelNamesForSourceName",
294
+ Object.fromEntries([[
295
+ "Ordering.Orders",
296
+ ["ProductDemands"]
297
+ ]])
298
+ ],
299
+ [
300
+ "extensions",
301
+ [Object.fromEntries([
302
+ [
303
+ "extensionPointName",
304
+ "Ordering.Orders"
305
+ ],
306
+ [
307
+ "readModelNames",
308
+ ["ProductDemands"]
309
+ ]
310
+ ])]
330
311
  ]
331
312
  ]));
332
313
  globalThis.expect(c.queueUrl).toBe("q-url");
314
+ globalThis.expect(c.extensions[0].extensionPointName).toBe("Ordering.Orders");
333
315
  });
334
316
  });
335
317
 
@@ -147,7 +147,7 @@ describe("ProvisionAccounts.provision", () => {
147
147
 
148
148
  test("a refused pool leaves the manifest untouched", async () => {
149
149
  let dir = NodeFs.mkdtempSync(NodePath.join([NodeOs.tmpdir(), "provision-accounts-"]))
150
- let file = NodePath.join([dir, "users.yaml"])
150
+ let file = NodePath.join([dir, "accounts.yaml"])
151
151
  NodeFs.writeFileSync(file, manifest)
152
152
  let refuse = async (~providerId as _, ~usernames as _) => Error("pool signs in on email")
153
153
  let outcome = await Provision.provision(
@@ -147,7 +147,7 @@ globalThis.describe("ProvisionAccounts.provision", () => {
147
147
  `;
148
148
  globalThis.test("a refused pool leaves the manifest untouched", async () => {
149
149
  let dir = Nodefs.mkdtempSync(Nodepath.join(Nodeos.tmpdir(), "provision-accounts-"));
150
- let file = Nodepath.join(dir, "users.yaml");
150
+ let file = Nodepath.join(dir, "accounts.yaml");
151
151
  Nodefs.writeFileSync(file, manifest, "utf8");
152
152
  let refuse = async (param, param$1) => ({
153
153
  TAG: "Error",
@@ -1,19 +0,0 @@
1
- module EventCollectorChannel = EventCollectorChannel.SQS
2
- module RuntimeEnvironment = RuntimeEnvironment.Lambda
3
- module EventCollectorRuntimeBuilder = ReventlessCore.EventCollectorRuntime_Builder_PerEventCollector.Make(
4
- RuntimeEnvironment,
5
- EventCollectorChannel,
6
- )
7
-
8
- module Make = (
9
- Spec: Reventless.ReadModel.Spec,
10
- Mappings: Reventless.Projection.Mappings with module Target := Spec,
11
- ) => ReventlessCore.ReadModel_Builder.Make(
12
- Spec,
13
- Mappings,
14
- RuntimeEnvironment,
15
- QueryDbStorage.DynamoDb,
16
- QueryDbResolvers.AppSync,
17
- EventCollectorChannel,
18
- EventCollectorRuntimeBuilder,
19
- )
@@ -1,54 +0,0 @@
1
- // Generated by ReScript, PLEASE EDIT WITH CARE
2
-
3
- import * as ReadModel_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/ReadModel/ReadModel_Builder.res.mjs";
4
- import * as QueryDbStorage_DynamoDb$ReventlessAws from "../adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs";
5
- import * as QueryDbResolvers_AppSync$ReventlessAws from "../adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs";
6
- import * as EventCollectorChannel_SQS$ReventlessAws from "../adapter/EventCollector/EventCollectorChannel_SQS.res.mjs";
7
- import * as RuntimeEnvironment_Lambda$ReventlessAws from "../adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs";
8
- import * as EventCollectorRuntime_Builder_PerEventCollector$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs";
9
-
10
- let EventCollectorRuntimeBuilder = EventCollectorRuntime_Builder_PerEventCollector$ReventlessCore.Make({
11
- make: RuntimeEnvironment_Lambda$ReventlessAws.make,
12
- groupBySource: RuntimeEnvironment_Lambda$ReventlessAws.groupBySource,
13
- extractCorrelationId: RuntimeEnvironment_Lambda$ReventlessAws.extractCorrelationId,
14
- extractCausationId: RuntimeEnvironment_Lambda$ReventlessAws.extractCausationId,
15
- extractSentTimestamp: RuntimeEnvironment_Lambda$ReventlessAws.extractSentTimestamp,
16
- extractRetryCount: RuntimeEnvironment_Lambda$ReventlessAws.extractRetryCount,
17
- asEventHandler: prim => prim,
18
- asEffectHandler: prim => prim
19
- })({
20
- make: EventCollectorChannel_SQS$ReventlessAws.make,
21
- connect: EventCollectorChannel_SQS$ReventlessAws.connect
22
- });
23
-
24
- function Make(Spec) {
25
- return Mappings => ReadModel_Builder$ReventlessCore.Make(Spec)(Mappings)({
26
- make: RuntimeEnvironment_Lambda$ReventlessAws.make,
27
- groupBySource: RuntimeEnvironment_Lambda$ReventlessAws.groupBySource,
28
- extractCorrelationId: RuntimeEnvironment_Lambda$ReventlessAws.extractCorrelationId,
29
- extractCausationId: RuntimeEnvironment_Lambda$ReventlessAws.extractCausationId,
30
- extractSentTimestamp: RuntimeEnvironment_Lambda$ReventlessAws.extractSentTimestamp,
31
- extractRetryCount: RuntimeEnvironment_Lambda$ReventlessAws.extractRetryCount,
32
- asEventHandler: prim => prim,
33
- asEffectHandler: prim => prim
34
- })({
35
- make: QueryDbStorage_DynamoDb$ReventlessAws.make
36
- })({
37
- make: QueryDbResolvers_AppSync$ReventlessAws.make
38
- })({
39
- make: EventCollectorChannel_SQS$ReventlessAws.make,
40
- connect: EventCollectorChannel_SQS$ReventlessAws.connect
41
- })(EventCollectorRuntimeBuilder);
42
- }
43
-
44
- let EventCollectorChannel;
45
-
46
- let RuntimeEnvironment;
47
-
48
- export {
49
- EventCollectorChannel,
50
- RuntimeEnvironment,
51
- EventCollectorRuntimeBuilder,
52
- Make,
53
- }
54
- /* EventCollectorRuntimeBuilder Not a pure module */