@reventlessdev/reventless-core 3.0.0-alpha.249 → 3.0.0-alpha.251

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,38 @@
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.251 (2026-08-27)
7
+
8
+ * feat(spec)!: reflect the command direction across a port, both halves ([f2fe258](https://github.com/ReventlessDev/reventless-core/commit/f2fe258d195b74f4a61488edee305665341020ea))
9
+ * feat(spec)!: drop the plugin protocol's reconnect event, which nothing published ([7549db6](https://github.com/ReventlessDev/reventless-core/commit/7549db6aac6ad3f94dbb8d72dac4b4e783756ad1))
10
+ * feat(spec)!: read the port's translation table off the mapping's arms ([956348a](https://github.com/ReventlessDev/reventless-core/commit/956348a9fcb256cfff9db51809bdc27d73360e6c))
11
+
12
+ ### BREAKING CHANGES
13
+
14
+ * `ExtensionPointMapping.Mapping` gains `acceptedCommands` and
15
+ `ExtensionMapping.Mapping` gains `issuedCommands`; both are derived, so a
16
+ mapping the ppx can read needs no source change, and one it cannot names itself
17
+ at compile time. Definitions persisted before the two new def fields must be
18
+ re-emitted before a consumer can read them as present — and until then they
19
+ decode as None, which means unknown, not "issues nothing".
20
+ * `PluginExtensionPointSpec.event` no longer declares
21
+ `PluginReconnected`. An extension matching on it was matching an event it could
22
+ never receive; drop the arm.
23
+ * `ExtensionPointMapping.Mapping` requires `publishedEvents` and
24
+ `ExtensionMapping.Mapping` requires `handledEvents`; the ppx injects both for
25
+ mappings it can read, so app code must build against the matching ppx. Plugin
26
+ definitions persisted before the new fields must be re-emitted before a consumer
27
+ reads them as present.
28
+
29
+
30
+
31
+ # 3.0.0-alpha.250 (2026-08-23)
32
+
33
+ ### Bug Fixes
34
+
35
+ * **querydb:** serve the previous page instead of refusing it ([48341c9](https://github.com/ReventlessDev/reventless-core/commit/48341c9f61b5155b441deeb0edc9abf461948346))
36
+
37
+
6
38
  # 3.0.0-alpha.249 (2026-08-23)
7
39
 
8
40
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.249",
3
+ "version": "3.0.0-alpha.251",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -29,21 +29,21 @@
29
29
  "sury": "11.0.0-rc.2",
30
30
  "uuid": "^13.0.0",
31
31
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
32
- "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.8",
33
- "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
34
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
35
32
  "@reventlessdev/rescript-node": "2.0.0-alpha.8",
36
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
33
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
34
+ "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
35
+ "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.8",
37
36
  "@reventlessdev/rescript-ssh2": "2.0.0-alpha.8",
37
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
38
38
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
39
- "@reventlessdev/reventless-interop": "3.0.0-alpha.33",
40
- "@reventlessdev/reventless-infra": "3.0.0-alpha.151",
41
- "@reventlessdev/reventless-spec": "3.0.0-alpha.123"
39
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.152",
40
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.34",
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.124"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "12.3.0",
45
45
  "sury-ppx": "11.0.0-rc.2",
46
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.73"
46
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.74"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "rescript": "12.3.0"
@@ -33,7 +33,11 @@ let sdlTypes: array<string> = [
33
33
  `type Platform_AutomationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n producedCommandTypes: [String!]!\n targetName: String\n chapter: String\n}`,
34
34
  `type Platform_OutboundTranslationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n inboundCommandTypes: [String!]!\n targetName: String\n externalSystem: String\n chapter: String\n}`,
35
35
  `type Platform_InboundTranslationSliceDef {\n name: String!\n commandTypes: [String!]!\n targetName: String\n externalSystem: String\n chapter: String\n}`,
36
- `type Platform_ExtensionDef {\n name: String!\n delegateNames: [String!]!\n eventTypes: [String!]!\n commandTypes: [String!]!\n}`,
36
+ // The subscriber's half of the port's translation table — see `handledEventDef`.
37
+ `type Platform_HandledEventDef {\n name: String!\n toCommandTypes: [String!]!\n}`,
38
+ // Its command-direction mirror — see `issuedCommandDef`.
39
+ `type Platform_IssuedCommandDef {\n name: String!\n fromEventTypes: [String!]!\n}`,
40
+ `type Platform_ExtensionDef {\n name: String!\n delegateNames: [String!]!\n eventTypes: [String!]!\n commandTypes: [String!]!\n handledEvents: [Platform_HandledEventDef!]\n issuedCommands: [Platform_IssuedCommandDef!]\n}`,
37
41
  // `internalQueryables` is the complement of the `readModels` / `stateViewSlices`
38
42
  // filter, not an addition to either: it carries exactly the components the
39
43
  // filter removed. A consumer enumerating surfaces reads the two filtered lists
@@ -208,6 +212,32 @@ let encodeExtensionDef = (e: extensionDef): JSON.t =>
208
212
  ("delegateNames", encodeStrings(e.delegateNames)),
209
213
  ("eventTypes", encodeStrings(e.eventTypes)),
210
214
  ("commandTypes", encodeStrings(e.commandTypes)),
215
+ (
216
+ "handledEvents",
217
+ e.handledEvents->Option.mapOr(JSON.Encode.null, handled =>
218
+ handled
219
+ ->Array.map(h =>
220
+ Dict.fromArray([
221
+ ("name", JSON.Encode.string(h.name)),
222
+ ("toCommandTypes", encodeStrings(h.toCommandTypes)),
223
+ ])->JSON.Encode.object
224
+ )
225
+ ->JSON.Encode.array
226
+ ),
227
+ ),
228
+ (
229
+ "issuedCommands",
230
+ e.issuedCommands->Option.mapOr(JSON.Encode.null, issued =>
231
+ issued
232
+ ->Array.map(i =>
233
+ Dict.fromArray([
234
+ ("name", JSON.Encode.string(i.name)),
235
+ ("fromEventTypes", encodeStrings(i.fromEventTypes)),
236
+ ])->JSON.Encode.object
237
+ )
238
+ ->JSON.Encode.array
239
+ ),
240
+ ),
211
241
  ])->JSON.Encode.object
212
242
 
213
243
  // `derived` is curation, not structure: it says which of the pages a shell
@@ -14,7 +14,9 @@ let sdlTypes = [
14
14
  `type Platform_AutomationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n producedCommandTypes: [String!]!\n targetName: String\n chapter: String\n}`,
15
15
  `type Platform_OutboundTranslationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n inboundCommandTypes: [String!]!\n targetName: String\n externalSystem: String\n chapter: String\n}`,
16
16
  `type Platform_InboundTranslationSliceDef {\n name: String!\n commandTypes: [String!]!\n targetName: String\n externalSystem: String\n chapter: String\n}`,
17
- `type Platform_ExtensionDef {\n name: String!\n delegateNames: [String!]!\n eventTypes: [String!]!\n commandTypes: [String!]!\n}`,
17
+ `type Platform_HandledEventDef {\n name: String!\n toCommandTypes: [String!]!\n}`,
18
+ `type Platform_IssuedCommandDef {\n name: String!\n fromEventTypes: [String!]!\n}`,
19
+ `type Platform_ExtensionDef {\n name: String!\n delegateNames: [String!]!\n eventTypes: [String!]!\n commandTypes: [String!]!\n handledEvents: [Platform_HandledEventDef!]\n issuedCommands: [Platform_IssuedCommandDef!]\n}`,
18
20
  `type Platform_ComponentDefinitionEntry {\n pluginId: String!\n readModels: [Platform_ReadSideDef!]!\n stateViewSlices: [Platform_ReadSideDef!]!\n internalQueryables: [Platform_ReadSideDef!]!\n stateChangeSlices: [Platform_WriteSideDef!]!\n aggregates: [Platform_WriteSideDef!]!\n automationSlices: [Platform_AutomationSliceDef!]!\n outboundTranslationSlices: [Platform_OutboundTranslationSliceDef!]!\n inboundTranslationSlices: [Platform_InboundTranslationSliceDef!]!\n extensions: [Platform_ExtensionDef!]!\n}`
19
21
  ];
20
22
 
@@ -347,6 +349,32 @@ function encodeExtensionDef(e) {
347
349
  [
348
350
  "commandTypes",
349
351
  e.commandTypes.map(prim => prim)
352
+ ],
353
+ [
354
+ "handledEvents",
355
+ Stdlib_Option.mapOr(e.handledEvents, null, handled => handled.map(h => Object.fromEntries([
356
+ [
357
+ "name",
358
+ h.name
359
+ ],
360
+ [
361
+ "toCommandTypes",
362
+ h.toCommandTypes.map(prim => prim)
363
+ ]
364
+ ])))
365
+ ],
366
+ [
367
+ "issuedCommands",
368
+ Stdlib_Option.mapOr(e.issuedCommands, null, issued => issued.map(i => Object.fromEntries([
369
+ [
370
+ "name",
371
+ i.name
372
+ ],
373
+ [
374
+ "fromEventTypes",
375
+ i.fromEventTypes.map(prim => prim)
376
+ ]
377
+ ])))
350
378
  ]
351
379
  ]);
352
380
  }
@@ -28,9 +28,13 @@
28
28
  open Reventless.Plugin
29
29
 
30
30
  let sdlTypes: array<string> = [
31
+ // The port's translation table — see `publishedEventDef`.
32
+ `type Platform_PublishedEventDef {\n name: String!\n fromEventTypes: [String!]!\n}`,
33
+ // Its command-direction mirror — see `acceptedCommandDef`.
34
+ `type Platform_AcceptedCommandDef {\n name: String!\n toCommandTypes: [String!]!\n}`,
31
35
  // Producer side of an extension point. `commandTypes` is null for a
32
36
  // notification-only (`command = unit`) extension point — see `extensionPointDef`.
33
- `type Platform_ExtensionPointDef {\n name: String!\n delegateNames: [String!]!\n sourceEventTypes: [String!]!\n commandTypes: [String!]\n}`,
37
+ `type Platform_ExtensionPointDef {\n name: String!\n delegateNames: [String!]!\n sourceEventTypes: [String!]!\n commandTypes: [String!]\n publishedEvents: [Platform_PublishedEventDef!]\n acceptedCommands: [Platform_AcceptedCommandDef!]\n}`,
34
38
  `type Platform_RequiredStoreDeclaration {\n store: String!\n component: String!\n field: String!\n annotation: String\n}`,
35
39
  // `extensionPoints` / `requiredStores` / `requiredStoreDeclarations` are nullable
36
40
  // lists, not `[T!]!`: all three are optional on `pluginStructure` so that plugin
@@ -52,6 +56,38 @@ let encodeExtensionPointDef = (e: extensionPointDef): JSON.t =>
52
56
  "commandTypes",
53
57
  e.commandTypes->Option.mapOr(JSON.Encode.null, Platform_ComponentDefinitionsApi.encodeStrings),
54
58
  ),
59
+ (
60
+ "publishedEvents",
61
+ e.publishedEvents->Option.mapOr(JSON.Encode.null, published =>
62
+ published
63
+ ->Array.map(p =>
64
+ Dict.fromArray([
65
+ ("name", JSON.Encode.string(p.name)),
66
+ (
67
+ "fromEventTypes",
68
+ Platform_ComponentDefinitionsApi.encodeStrings(p.fromEventTypes),
69
+ ),
70
+ ])->JSON.Encode.object
71
+ )
72
+ ->JSON.Encode.array
73
+ ),
74
+ ),
75
+ (
76
+ "acceptedCommands",
77
+ e.acceptedCommands->Option.mapOr(JSON.Encode.null, accepted =>
78
+ accepted
79
+ ->Array.map(a =>
80
+ Dict.fromArray([
81
+ ("name", JSON.Encode.string(a.name)),
82
+ (
83
+ "toCommandTypes",
84
+ Platform_ComponentDefinitionsApi.encodeStrings(a.toCommandTypes),
85
+ ),
86
+ ])->JSON.Encode.object
87
+ )
88
+ ->JSON.Encode.array
89
+ ),
90
+ ),
55
91
  ])->JSON.Encode.object
56
92
 
57
93
  let encodeRequiredStoreDeclaration = (d: requiredStoreDeclaration): JSON.t =>
@@ -5,7 +5,9 @@ import * as Plugin$ReventlessCore from "../plugin/component/Plugin.res.mjs";
5
5
  import * as Platform_ComponentDefinitionsApi$ReventlessCore from "./Platform_ComponentDefinitionsApi.res.mjs";
6
6
 
7
7
  let sdlTypes = [
8
- `type Platform_ExtensionPointDef {\n name: String!\n delegateNames: [String!]!\n sourceEventTypes: [String!]!\n commandTypes: [String!]\n}`,
8
+ `type Platform_PublishedEventDef {\n name: String!\n fromEventTypes: [String!]!\n}`,
9
+ `type Platform_AcceptedCommandDef {\n name: String!\n toCommandTypes: [String!]!\n}`,
10
+ `type Platform_ExtensionPointDef {\n name: String!\n delegateNames: [String!]!\n sourceEventTypes: [String!]!\n commandTypes: [String!]\n publishedEvents: [Platform_PublishedEventDef!]\n acceptedCommands: [Platform_AcceptedCommandDef!]\n}`,
9
11
  `type Platform_RequiredStoreDeclaration {\n store: String!\n component: String!\n field: String!\n annotation: String\n}`,
10
12
  `type Platform_PluginStructureEntry {\n pluginId: String!\n readModels: [Platform_ReadSideDef!]!\n stateViewSlices: [Platform_ReadSideDef!]!\n stateChangeSlices: [Platform_WriteSideDef!]!\n aggregates: [Platform_WriteSideDef!]!\n automationSlices: [Platform_AutomationSliceDef!]!\n outboundTranslationSlices: [Platform_OutboundTranslationSliceDef!]!\n inboundTranslationSlices: [Platform_InboundTranslationSliceDef!]!\n extensions: [Platform_ExtensionDef!]!\n extensionPoints: [Platform_ExtensionPointDef!]\n requiredStores: [String!]\n requiredStoreDeclarations: [Platform_RequiredStoreDeclaration!]\n}`
11
13
  ];
@@ -27,6 +29,32 @@ function encodeExtensionPointDef(e) {
27
29
  [
28
30
  "commandTypes",
29
31
  Stdlib_Option.mapOr(e.commandTypes, null, Platform_ComponentDefinitionsApi$ReventlessCore.encodeStrings)
32
+ ],
33
+ [
34
+ "publishedEvents",
35
+ Stdlib_Option.mapOr(e.publishedEvents, null, published => published.map(p => Object.fromEntries([
36
+ [
37
+ "name",
38
+ p.name
39
+ ],
40
+ [
41
+ "fromEventTypes",
42
+ Platform_ComponentDefinitionsApi$ReventlessCore.encodeStrings(p.fromEventTypes)
43
+ ]
44
+ ])))
45
+ ],
46
+ [
47
+ "acceptedCommands",
48
+ Stdlib_Option.mapOr(e.acceptedCommands, null, accepted => accepted.map(a => Object.fromEntries([
49
+ [
50
+ "name",
51
+ a.name
52
+ ],
53
+ [
54
+ "toCommandTypes",
55
+ Platform_ComponentDefinitionsApi$ReventlessCore.encodeStrings(a.toCommandTypes)
56
+ ]
57
+ ])))
30
58
  ]
31
59
  ]);
32
60
  }
@@ -265,10 +265,22 @@ let run = (
265
265
 
266
266
  let first =
267
267
  argsDict->Dict.get("first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
268
- let after = argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string)
268
+ // An empty cursor is no cursor. The host shell clears the opposite argument by
269
+ // setting it to "" rather than dropping it, and `Some("")` read as a cursor
270
+ // bounds nothing while still selecting the branch that expects one.
271
+ let cursorArg = key =>
272
+ argsDict
273
+ ->Dict.get(key)
274
+ ->Option.flatMap(JSON.Decode.string)
275
+ ->Option.flatMap(s => s == "" ? None : Some(s))
276
+ let after = cursorArg("after")
269
277
  let last = argsDict->Dict.get("last")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
270
- let before = argsDict->Dict.get("before")->Option.flatMap(JSON.Decode.string)
271
- let isBackward = last->Option.isSome
278
+ let before = cursorArg("before")
279
+ // `before` decides the direction, not `last`. Relay pairs `last` with `before`,
280
+ // but a client that keeps its page size in `first` and only moves the cursor
281
+ // sends `before` alone — and keying on `last` made that a FORWARD read bounded
282
+ // by nothing, which answers page one and calls it the previous page.
283
+ let isBackward = last->Option.isSome || before->Option.isSome
272
284
  let cursorField = orderByField->Option.getOr("id")
273
285
  let getCursorValue = item => getFieldString(item, cursorField)->Option.getOr(getId(item))
274
286
 
@@ -288,8 +300,12 @@ let run = (
288
300
  | _ => sorted
289
301
  }
290
302
 
303
+ // `last` when given, else the page size the caller has been paging with.
291
304
  let pageSize = if isBackward {
292
- last->Option.getOr(defaultListPageSize)
305
+ switch last {
306
+ | Some(n) => n
307
+ | None => first->Option.getOr(defaultListPageSize)
308
+ }
293
309
  } else {
294
310
  first->Option.getOr(defaultListPageSize)
295
311
  }
@@ -311,10 +327,14 @@ let run = (
311
327
  (arr->Array.slice(~start=0, ~end=pageSize), hasMore)
312
328
  }
313
329
 
330
+ // Both directions report both flags. A backward page was cut from ahead, so a
331
+ // next page provably exists — the one the caller came from — and a forward page
332
+ // reached by a cursor has one behind it. Reporting only the flag that matches
333
+ // the direction made Prev vanish the moment it was used.
314
334
  buildConnection(
315
335
  ~pageItems,
316
- ~hasNextPage=!isBackward && hasMore,
317
- ~hasPreviousPage=isBackward && hasMore,
336
+ ~hasNextPage=isBackward ? true : hasMore,
337
+ ~hasPreviousPage=isBackward ? hasMore : after->Option.isSome,
318
338
  ~cursorValueOf=getCursorValue,
319
339
  )
320
340
  }
@@ -154,10 +154,17 @@ function run(items, argsDict, capability, labelField, decodeLocalIdOpt, ownerSco
154
154
  });
155
155
  }
156
156
  let first = Stdlib_Option.map(Stdlib_Option.flatMap(argsDict["first"], Stdlib_JSON.Decode.float), prim => prim | 0);
157
- let after = Stdlib_Option.flatMap(argsDict["after"], Stdlib_JSON.Decode.string);
157
+ let cursorArg = key => Stdlib_Option.flatMap(Stdlib_Option.flatMap(argsDict[key], Stdlib_JSON.Decode.string), s => {
158
+ if (s === "") {
159
+ return;
160
+ } else {
161
+ return s;
162
+ }
163
+ });
164
+ let after = cursorArg("after");
158
165
  let last = Stdlib_Option.map(Stdlib_Option.flatMap(argsDict["last"], Stdlib_JSON.Decode.float), prim => prim | 0);
159
- let before = Stdlib_Option.flatMap(argsDict["before"], Stdlib_JSON.Decode.string);
160
- let isBackward = Stdlib_Option.isSome(last);
166
+ let before = cursorArg("before");
167
+ let isBackward = Stdlib_Option.isSome(last) || Stdlib_Option.isSome(before);
161
168
  let cursorField = Stdlib_Option.getOr(orderByField, "id");
162
169
  let getCursorValue = item => Stdlib_Option.getOr(getFieldString(item, cursorField), getId(item));
163
170
  let cursorBounded;
@@ -188,7 +195,7 @@ function run(items, argsDict, capability, labelField, decodeLocalIdOpt, ownerSco
188
195
  } else {
189
196
  cursorBounded = sorted;
190
197
  }
191
- let pageSize = isBackward ? Stdlib_Option.getOr(last, 50) : Stdlib_Option.getOr(first, 50);
198
+ let pageSize = isBackward && last !== undefined ? last : Stdlib_Option.getOr(first, 50);
192
199
  let take = pageSize + 1 | 0;
193
200
  let match;
194
201
  if (isBackward) {
@@ -210,7 +217,7 @@ function run(items, argsDict, capability, labelField, decodeLocalIdOpt, ownerSco
210
217
  ];
211
218
  }
212
219
  let hasMore$2 = match[1];
213
- return buildConnection(match[0], !isBackward && hasMore$2, isBackward && hasMore$2, getCursorValue);
220
+ return buildConnection(match[0], isBackward ? true : hasMore$2, isBackward ? hasMore$2 : Stdlib_Option.isSome(after), getCursorValue);
214
221
  }
215
222
 
216
223
  let defaultListPageSize = 50;