@reventlessdev/reventless-core 3.0.0-alpha.155 → 3.0.0-alpha.157

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +49 -150
  3. package/package.json +13 -13
  4. package/src/admin/AdminApi.res +80 -10
  5. package/src/admin/AdminApi.res.mjs +45 -7
  6. package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry.res +65 -0
  7. package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry.res.mjs +87 -0
  8. package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry_Behavior.res +58 -0
  9. package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry_Behavior.res.mjs +135 -0
  10. package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments.res +44 -0
  11. package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments.res.mjs +74 -0
  12. package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments_Projection.res +50 -0
  13. package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments_Projection.res.mjs +80 -0
  14. package/src/admin/Platform_Admin.res +37 -1
  15. package/src/admin/Platform_Admin.res.mjs +18 -5
  16. package/src/admin/Platform_ApiFragmentsApi.res +32 -0
  17. package/src/admin/Platform_ApiFragmentsApi.res.mjs +52 -0
  18. package/src/admin/Platform_UIFragmentsApi.res +1 -1
  19. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res +50 -0
  20. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +70 -0
  21. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry_Behavior.res +33 -0
  22. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry_Behavior.res.mjs +80 -0
  23. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments.res +33 -0
  24. package/src/admin/{UIFragmentRegistryReadModelSpec.res.mjs → UiFragmentRegistry/StateViewSlice/UiFragments.res.mjs} +24 -2
  25. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res +34 -0
  26. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res.mjs +54 -0
  27. package/src/components/Api/Api_Naming.res +1 -1
  28. package/src/components/Api/GraphQL_FragmentGenerator.res +7 -11
  29. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +8 -22
  30. package/src/components/Api/GraphQL_PushPlanner.res +65 -0
  31. package/src/components/Api/GraphQL_PushPlanner.res.mjs +46 -0
  32. package/src/components/Api/GraphQL_Stitcher.res +72 -1
  33. package/src/components/Api/GraphQL_Stitcher.res.mjs +53 -2
  34. package/src/components/Dcb/Dcb_Builder.res +16 -6
  35. package/src/components/Dcb/Dcb_Builder.res.mjs +10 -7
  36. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +6 -1
  37. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +1 -1
  38. package/src/plugin/api/PluginBaseFragment.res +5 -6
  39. package/src/plugin/api/PluginBaseFragment.res.mjs +0 -1
  40. package/src/plugin/component/Plugin_Builder.res +4 -1
  41. package/src/plugin/component/Plugin_Builder.res.mjs +7 -6
  42. package/src/plugin/component/Plugin_Helpers.res +30 -0
  43. package/src/plugin/component/Plugin_Helpers.res.mjs +13 -3
  44. package/src/plugin/component/Plugin_SubscriptionSchema.res +19 -9
  45. package/src/plugin/component/Plugin_SubscriptionSchema.res.mjs +23 -14
  46. package/src/plugin/connect/PluginConnectExtension_Mapping.res +28 -6
  47. package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +34 -14
  48. package/src/plugin/connect/PluginExtensionPoint_Builder.res +7 -1
  49. package/src/plugin/connect/PluginExtensionPoint_Builder.res.mjs +5 -1
  50. package/src/plugin/connect/PluginExtensionPoint_Plugin.res +3 -1
  51. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +4 -2
  52. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res +52 -0
  53. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +74 -0
  54. package/src/plugin/lifecycle/PluginBehavior.res +9 -51
  55. package/src/plugin/lifecycle/PluginBehavior.res.mjs +39 -68
  56. package/src/plugin/lifecycle/PluginSpec.res +0 -18
  57. package/src/plugin/lifecycle/PluginSpec.res.mjs +0 -30
  58. package/src/plugin/lifecycle/PluginsProjection.res +2 -6
  59. package/src/plugin/lifecycle/PluginsProjection.res.mjs +0 -2
  60. package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
  61. package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +0 -1
  62. package/tests/admin/Platform_ApiFragmentsApiTest.res +66 -0
  63. package/tests/admin/Platform_ApiFragmentsApiTest.res.mjs +68 -0
  64. package/tests/admin/Platform_UIFragmentsApiTest.res +1 -1
  65. package/tests/api/GraphQL_PushPlannerTest.res +62 -0
  66. package/tests/api/GraphQL_PushPlannerTest.res.mjs +80 -0
  67. package/tests/api/GraphQL_StitcherTest.res +63 -0
  68. package/tests/api/GraphQL_StitcherTest.res.mjs +75 -0
  69. package/tests/logger/LogFormatTest.res +1 -2
  70. package/tests/logger/LogFormatTest.res.mjs +1 -2
  71. package/tests/message/MessageTest.res +0 -3
  72. package/tests/message/MessageTest.res.mjs +0 -3
  73. package/src/admin/UIFragmentRegistryProjection.res +0 -43
  74. package/src/admin/UIFragmentRegistryProjection.res.mjs +0 -81
  75. package/src/admin/UIFragmentRegistryReadModelSpec.res +0 -13
package/CHANGELOG.md CHANGED
@@ -3,6 +3,29 @@
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.157 (2026-07-12)
7
+
8
+ ### Features
9
+
10
+ * **admin:** event-source the API fragment registry as admin DCB slices (Phase 2b) ([3fc40d2](https://github.com/ReventlessDev/reventless-core/commit/3fc40d247c54bf66ed84d625959f08299aa04a98))
11
+ * **admin:** event-source the UI fragment registry as admin DCB slices ([711581e](https://github.com/ReventlessDev/reventless-core/commit/711581e77c626e7d0fc35db8ec351f62a70bd8f2))
12
+ * **admin:** expose the API fragment registry as system-callable mutations (Phase 2d) ([8b1ec49](https://github.com/ReventlessDev/reventless-core/commit/8b1ec49813e67f303bc86d60470c9b95d2a8408e))
13
+ * **admin:** make the API fragment registry per-target (Domain | Platform) ([30491a9](https://github.com/ReventlessDev/reventless-core/commit/30491a9e14b4236c98cc756efb6de68ede1e77d7))
14
+ * **admin:** populate the API fragment registry on connect + status query (Phase 2c) ([106d96c](https://github.com/ReventlessDev/reventless-core/commit/106d96c904e135f8360d53e582f395c107db704a))
15
+ * **admin:** react to ApiFragment* events with a schema single writer (Phase 2e) ([59f9e46](https://github.com/ReventlessDev/reventless-core/commit/59f9e46c154d8ca2a34537974722014fc279de3e))
16
+ * **admin:** retire the Plugin-aggregate UI-fragment path in favour of the registry slices ([1dbc708](https://github.com/ReventlessDev/reventless-core/commit/1dbc708e7439b34ff970cc3d963d7835a8c6fd48))
17
+ * **admin:** serve Platform_UIFragments from the new UiFragmentRegistry slices ([7a47533](https://github.com/ReventlessDev/reventless-core/commit/7a47533acc95977fbeeb6375e94449056b655abc))
18
+ * **api:** neutralize core-emitted SDL — [@aws](https://github.com/aws)_subscribe moves to the AWS adapter (Phase 2a) ([9b27ec8](https://github.com/ReventlessDev/reventless-core/commit/9b27ec8f6ab2b7453b1c50b9891e89781ec3b31a))
19
+
20
+
21
+ # 3.0.0-alpha.156 (2026-07-11)
22
+
23
+ **Note:** Version bump only for package @reventlessdev/reventless-core
24
+
25
+
26
+
27
+
28
+
6
29
  # 3.0.0-alpha.155 (2026-07-11)
7
30
 
8
31
  ### Bug Fixes
package/README.md CHANGED
@@ -1,173 +1,72 @@
1
- [![npm version](https://img.shields.io/npm/v/@reventlessdev/reventless.svg?label=version)](https://www.npmjs.com/package/@reventlessdev/reventless)
2
- [![GitHub package version](https://img.shields.io/github/v/release/ReventlessDev/reventless-core?label=release)](https://github.com/ReventlessDev/reventless-core/releases)
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![Changelog](https://img.shields.io/badge/📋-Changelog-blue)](./CHANGELOG.md)
1
+ [![npm](https://img.shields.io/npm/v/@reventlessdev/reventless-core.svg?label=npm)](https://www.npmjs.com/package/@reventlessdev/reventless-core)
2
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
3
+ [![Docs](https://img.shields.io/badge/docs-reventless.dev-blue)](https://docs.reventless.dev)
5
4
 
6
- # Reventless
5
+ # @reventlessdev/reventless-core
7
6
 
8
- Reventless is a toolkit for event-sourced CQRS applications on serverless infrastructure written in ReasonML.
7
+ > ⚠️ **Alpha.** APIs and on-disk formats can change without notice between releases.
8
+ > Pin exact versions and expect breaking changes.
9
9
 
10
- It's based on the following technologies:
10
+ The **provider-agnostic core** of [Reventless](https://docs.reventless.dev) — a
11
+ spec-driven, event-sourced CQRS framework written in [ReScript](https://rescript-lang.org).
12
+ This package is the runtime you model your domain against: the type-safe building blocks
13
+ for commands, events, projections, and the behaviors that evolve state — with **no** cloud
14
+ or storage dependency. The provider-specific adapters live in separate packages
15
+ (see [Where it fits](#where-it-fits)).
11
16
 
12
- * [Serverless](https://serverless.com)
13
- * [ReasonML](https://reasonml.github.io/) using [Bucklescript](https://bucklescript.github.io/)
14
- * Event-Sourced
15
- * CQRS
17
+ ## What it provides
16
18
 
17
- ## Getting started developing Reventless
18
- ### 1. Install Node 12.x (LTS)
19
- We use `fnm` to manage the locally installed node-version.
20
- The currently used/tested node version is stated in the `.node-version` file.
19
+ ReScript modules, consumed by adding the package to your `rescript.json` `dependencies`:
21
20
 
22
- #### [fnm](https://github.com/Schniz/fnm)
23
- > Fast and simple Node.js version manager, built in native ReasonML
21
+ - **`Message`** — command / event message contracts and their service interface.
22
+ - **`Behavior`** — the write-side behavior spec: event-sourced state transitions
23
+ (`evolve(state, event) => state`).
24
+ - **`Projection`** / **`ProjectionMapper`** — read models projected from event streams.
25
+ - **`Mapper`** / **`Mapper1toN`** / **`MapperNto1`** — cross-component event translation
26
+ (encode/decode between a source and a target contract).
27
+ - **`ComponentType`** — the hierarchical component-kind vocabulary.
28
+ - **`PluginRuntimeOperations`** — the provider-agnostic interface a runtime adapter implements.
29
+ - **`RequestContext`** — per-invocation context threaded through the effect pipeline.
30
+ - **`Env`** — environment access.
24
31
 
25
- *Note: Currently this tool has no windows-support and is unlikely to be added in the near future.*
32
+ Domain code written against these modules is provider-agnostic: the same components run
33
+ unchanged on any adapter.
26
34
 
27
- Go to your reventless-directory and execute [`fnm use`](https://github.com/Schniz/fnm#fnm-use-version) in a shell to just set you node version to the one stated in `.node-version`. (maybe you want to set your default node version by calling [`fnm default <version>`](https://github.com/Schniz/fnm#fnm-default-version))
35
+ ## Where it fits
28
36
 
29
- ### 2. Install BuckleScript (which comes bundled with reason)
30
- Currently used and supported version is bs-platform 5.2.1.
37
+ `reventless-core` is one package in the Reventless framework. It builds on
38
+ [`@reventlessdev/reventless-spec`](https://www.npmjs.com/package/@reventlessdev/reventless-spec)
39
+ (type specifications) and is paired at runtime with a storage/cloud adapter:
31
40
 
32
- [Reason Docs](https://reasonml.github.io/docs/en/installation)
33
- [Unofficial Community Docs](https://reasonml.org/docs/manual/latest/installation)
41
+ - [`@reventlessdev/reventless-aws`](https://www.npmjs.com/package/@reventlessdev/reventless-aws) — AWS (DynamoDB, Lambda, SQS, SNS, S3)
42
+ - [`@reventlessdev/reventless-postgres`](https://www.npmjs.com/package/@reventlessdev/reventless-postgres) — Postgres event log + query DB
43
+ - [`@reventlessdev/reventless-local`](https://www.npmjs.com/package/@reventlessdev/reventless-local) — in-memory / SQLite platform for local dev and tests
34
44
 
35
- The docs suggest to globally install `bs-platform` (by running `npm install -g bs-platform`), but to avoid version-conflicts it's better to just locally install `bs-platform` when needed or run `npx`. Run the following command to install `bs-platform` locally without adding it to `package.json`:
36
- ```
37
- npm install bs-platform@5.2.1 --no-save
38
- ```
39
-
40
- ### 3. Install Dependencies
41
- ```
42
- npm install
43
- ```
44
-
45
- ### 3. Build Project
46
- ```
47
- npm run build
48
- ```
49
-
50
- Or in development, to run a watcher:
51
- ```
52
- npm run start
53
- ```
45
+ You normally obtain `reventless-core` transitively by scaffolding an app rather than
46
+ installing it on its own.
54
47
 
55
- If you use Visual Studio Code, use the [reason-vscode](https://marketplace.visualstudio.com/items?itemName=jaredly.reason-vscode) plugin, which auto-builds the project on save by default.
56
- Otherwise, see https://reasonml.github.io/docs/en/editor-plugins.
48
+ ## Install
57
49
 
58
- *Note: Don't run the watcher and use the editor-plugin at the same time, since they may conflict with each other.*
59
-
60
- ### 4. Test Project
61
- Run Test-Suite once:
62
- ```
63
- npm run test
64
- ```
65
-
66
- Run Test-Suite continously on file changes:
67
- ```
68
- npm run dev
50
+ ```bash
51
+ pnpm add @reventlessdev/reventless-core
69
52
  ```
70
53
 
71
- The Bucklescript bindings for [Jest](https://jestjs.io/) are used as dev-dependency: [bs-jest](https://github.com/glennsl/bs-jest)
72
-
54
+ Then register it as a ReScript dependency in `rescript.json`:
73
55
 
74
-
75
- #### Test Example
76
- This tests the function f in the file/module Try:
77
- ```
78
- // __tests__/try_test.re
79
- open Jest
80
-
81
- describe("Try should return...", () => {
82
- open Expect
83
- test("the same text given a count of 0", () => {
84
- expect(Try.f("test", 0)) |> toBe("test")
85
- })
86
-
87
- test("the text doubled given a count of 1", () => {
88
- expect(Try.f("test", 1)) |> toBe("testtest")
89
- })
90
- })
91
- ```
92
- File Try:
93
- ```
94
- // src/Try.re
95
- let rec f = (text: string, count: int) => {
96
- if(count <= 0) text
97
- else f(text ++ text, count - 1)
56
+ ```json
57
+ {
58
+ "dependencies": ["@reventlessdev/reventless-core"]
98
59
  }
99
60
  ```
100
61
 
101
- ### 5. Deployment
102
- **TODO: This should be moved to a separate chapter on how to use Reventless in a project.**
103
-
104
- Before running `npm run deploy`, make sure to update the `.env` file. (Copy your Pulumi Access Token from your profile preferences into the `.env` file.)
105
- You also need to set the environment variable like `export AWS_SDK_LOAD_CONFIG=1` and hava a file at `~/.aws/config`:
106
- ```
107
- [default]
108
- region = eu-west-1
109
- output = json
110
- ```
111
-
112
- This command will run 3 Shell-Scripts:
113
- * `./scripts/pre-deploy.sh`: Move everything in `./node_modules/bs-platform` to a tmp-directory, but the JS-lib
114
- * `./scripts/pulumi-up.sh`: Read `PULUMI_ACCESS_TOKEN` and `PULUMI_STACK` from the `.env` file and run `pulumi up` to actually deploy (This script will be only executed, if `pre-deploy` exited successfully.)
115
- * `./scripts/post-deploy.sh`: Move `bs-platform` back into place from tmp-directory.
116
-
117
-
118
- **NOTE**: The `./scripts/pulumi-up.sh` exports the environment variable `PULUMI_ACCESS_TOKEN` to make it to the Pulumi CLI available. Export another value and set a `#` before the apropriate line inside`.env` to use another token.
119
-
120
-
121
- ## Setup environment for local development of actual project and framework side by side -- DEPRECATED
122
-
123
-
124
- **🚨 We encountered some deployment issues, when using npm link. Therefore we discourage using this technic for the time being❗**
125
-
126
- ### Setup the new project
127
- * add a new "deploy key" in [gitlab](https://github.com/atos-austria/reason/reventless/settings/repository/deploy_token/create#js-deploy-tokens)
128
- * add "private repository" with the new deploy-token to the new project: `"reventless": "git+https://USER-TOKEN:PASSWORD-TOKEN@github.com/atos-austria/reason/reventless.git"`
129
- * run `npm install` for the new project
130
-
131
- ### Setup the framework project to use the local version in the new version
132
- * clone the framework repo into a local directory
133
- * run `npm link` inside the framework's directory
134
- * run `npm link reventless` inside the new project's directory
135
-
136
- ## Go back to using the actual framwork-repo as dependency
137
- ### Unlink
138
- [Medium Post](https://medium.com/@alexishevia/the-magic-behind-npm-link-d94dcb3a81af)
139
-
140
- * run `npm unlink --no-save reventless` in the new project's directory
141
- * run `npm unlink` in the framework's directory
142
-
143
- ### Alternative
144
- [Medium Post](https://medium.com/dailyjs/how-to-use-npm-link-7375b6219557)
145
-
146
- * run `npm uninstall --no-save reventless && npm install` inside the new project's directory
147
- * OPTIONAL: to delete the global symlink of the framework run `npm uninstall` inside the framework's directory
148
-
149
- ## Ressources
150
- * [Project Wiki](https://github.com/atos-austria/reason/reventless/wikis/home)
151
-
152
- # Coding Guidelines
153
-
154
- * `<Component>.Make.createComponent` (binding to `Component.js` constructor): Pass any additional parameters (besides `componentType`, `name`, `opts`) directly to the `construct` function (using partial application), like this: `createComponent(~componentType, ~name, ~construct=construct(~param1, ~param2), ~opts)`
155
-
156
- # Code-Smells
157
-
158
- ## ReasonML
159
-
160
- * `...->ignore`
161
-
162
- ## Pulumi
62
+ Requires ReScript `^12.3.0` (peer dependency).
163
63
 
164
- * `...->Pulumi.Output.apply(_, ...)`
165
- * `...->Pulumi.Output.all->Pulumi.Output.apply(...)`
166
- * type `option(Pulumi.Output.t('a))`: Rescript's js option encoding and Pulumi's Output lifting mechanics interact badly! - Therefore, such a type is not possible to use!
64
+ ## Links
167
65
 
168
- # Common issues
66
+ - 📚 Documentation — [docs.reventless.dev](https://docs.reventless.dev)
67
+ - 📦 Repository — [ReventlessDev/reventless-core](https://github.com/ReventlessDev/reventless-core)
68
+ - 📋 [Changelog](./CHANGELOG.md)
169
69
 
170
- ## `error: Duplicate resource URN 'urn:pulumi:dev::wm-raw::aws:sqs/queue:Queue::FIFODeadLetterQueue'; try giving it a unique name` during deployment
70
+ ## License
171
71
 
172
- This error usually occurs due to multiple different versions of `@reventless/reventless` or `@reventless/reventless-aws` being present in the dependencies (npm). To verify, call `npm ls @reventless/reventless` / `npm ls @reventless/reventless-aws` and verify there is only a single version present.
173
- To fix this use desired ranges in the dependencies definition (package.json) of the project and check upstream version ranges as well. (You can also use `npm why <packageName>` to see where a specific version is coming from.)
72
+ [Apache-2.0](https://opensource.org/licenses/Apache-2.0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.155",
3
+ "version": "3.0.0-alpha.157",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -28,22 +28,22 @@
28
28
  "dependencies": {
29
29
  "sury": "11.0.0-alpha.4",
30
30
  "uuid": "^13.0.0",
31
- "@reventlessdev/rescript-effect": "0.1.0-alpha.26",
32
- "@reventlessdev/rescript-fast-csv": "1.2.0-alpha.11",
33
- "@reventlessdev/rescript-hash-object": "1.2.0-alpha.10",
34
- "@reventlessdev/rescript-jest": "1.0.0-alpha.6",
35
- "@reventlessdev/rescript-node-streams": "1.1.0-alpha.10",
36
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
37
- "@reventlessdev/rescript-ssh2": "1.1.0-alpha.11",
38
- "@reventlessdev/rescript-uuid": "1.1.0-alpha.14",
39
- "@reventlessdev/reventless-infra": "3.0.0-alpha.96",
40
- "@reventlessdev/reventless-interop": "3.0.0-alpha.25",
41
- "@reventlessdev/reventless-spec": "3.0.0-alpha.74"
31
+ "@reventlessdev/rescript-effect": "0.1.0-alpha.27",
32
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.7",
33
+ "@reventlessdev/rescript-hash-object": "1.2.0-alpha.11",
34
+ "@reventlessdev/rescript-fast-csv": "1.2.0-alpha.12",
35
+ "@reventlessdev/rescript-node-streams": "1.1.0-alpha.11",
36
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.15",
37
+ "@reventlessdev/rescript-ssh2": "1.1.0-alpha.12",
38
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.98",
39
+ "@reventlessdev/rescript-uuid": "1.1.0-alpha.15",
40
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.26",
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.76"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "^12.3.0",
45
45
  "sury-ppx": "11.0.0-alpha.2",
46
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.52"
46
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.53"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "rescript": "^12.3.0"
@@ -25,9 +25,55 @@ let mutationEntries = (~cloner: bool) => {
25
25
 
26
26
  let queryEntries = PluginBaseFragment.queryEntries
27
27
 
28
+ // ApiFragmentRegistry admin mutations — the real GraphQL surface of the ApiFragmentRegistry
29
+ // StateChangeSlice: the plugin / standalone-service deploy calls `Platform_RegisterApiFragment` /
30
+ // `Platform_DeregisterApiFragment` as a SigV4 system caller. Field names + arg SDL + the nested
31
+ // `fragment` input type are derived from the slice's `commandSchema` via the SAME
32
+ // `Api_Naming.sliceMutationFields` (admin "Platform" prefix) + `GraphQL_FragmentGenerator` path
33
+ // that the DCB builder uses, so the field declared here in the STATIC admin base is byte-identical
34
+ // to the one the DCB resolver binds to (`RecordApiFragmentPush` is `@noApi`, filtered out).
35
+ //
36
+ // These live in `baseFragment` ONLY, never in the shared `mutationEntries`: only the static base is
37
+ // pushed to AppSync (`Platform.res` preAdminResolversSchemaHook), whereas the constructed admin
38
+ // fragment (`Platform_Admin.construct`) already carries them via `dcbResult.mutationEntries`. Adding
39
+ // them to `mutationEntries` — which feeds both — would duplicate the fields in the constructed
40
+ // fragment. They are folded into `baseFragment`'s own `generate` call (not a separate one) so its
41
+ // shared `seenTypes` dedups the shared `CommandResult` type family instead of re-emitting it.
42
+ let apiFragmentRegistryMutationEntries: array<mutationSchemaEntry> = {
43
+ let commandSchema = ApiFragmentRegistry.commandSchema->S.castToUnknown
44
+ let fieldSpecs = Api_Naming.sliceMutationFields(
45
+ ~plugin="Platform",
46
+ ~slice=ApiFragmentRegistry.name,
47
+ ~commandSchema,
48
+ )
49
+ if fieldSpecs->Array.length === 0 {
50
+ []
51
+ } else {
52
+ [
53
+ {
54
+ ReventlessInfra.Api.fieldNames: fieldSpecs->Array.map(((f, _)) => f),
55
+ commandSchema,
56
+ },
57
+ ]
58
+ }
59
+ }
60
+
61
+ // Admin fields a deploy-time system caller (machine credentials — SigV4/IAM on AWS) invokes: the
62
+ // two ApiFragmentRegistry mutations (the deploy's register/deregister write path) and
63
+ // `Platform_ApiFragments` (the deploy waiter's push-status poll). Provider-neutral by design — the
64
+ // AWS adapter threads these into `injectAwsAuthAll`'s `~iamFieldNames` so only these fields carry
65
+ // the dual-auth (`@aws_cognito_user_pools @aws_iam`) directive; every other admin field stays
66
+ // Cognito-only. Derived from the same mutation entries so the names never drift from the SDL.
67
+ let systemCallerFieldNames =
68
+ apiFragmentRegistryMutationEntries
69
+ ->Array.flatMap(e => e.fieldNames)
70
+ ->Array.concat([Platform_ApiFragmentsApi.queryFieldName])
71
+
28
72
  // UIFragment lifecycle Source C subscriptions.
29
- // Mutations are called by the backend when UIFragment events occur;
30
- // AppSync routes via @aws_subscribe to onUIFragmentChange subscribers.
73
+ // Mutations are called by the backend when UIFragment events occur; the
74
+ // subscription→mutation fan-in is carried as neutral `subscriptionSource`
75
+ // metadata (providers add their own routing — AppSync via `@aws_subscribe`
76
+ // appended at push time, the local platform via its PubSub bridge).
31
77
  // manifest is serialised as a JSON string (String scalar, not AWSJSON).
32
78
 
33
79
  let uiFragmentSubscriptionTypes = [
@@ -41,12 +87,21 @@ let uiFragmentMutationFields = [
41
87
  ` Platform_UIFragmentDeregistered(pluginId: ID!): UIFragmentChangeEvent`,
42
88
  ]
43
89
 
44
- let uiFragmentSubscriptionField = ` onUIFragmentChange: UIFragmentChangeEvent\n @aws_subscribe(mutations: ["Platform_UIFragmentRegistered", "Platform_UIFragmentUpdated", "Platform_UIFragmentDeregistered"])`
90
+ let uiFragmentSubscriptionField = ` onUIFragmentChange: UIFragmentChangeEvent`
91
+
92
+ let uiFragmentSubscriptionSource: GraphQL_Stitcher.subscriptionSource = {
93
+ field: "onUIFragmentChange",
94
+ mutations: [
95
+ "Platform_UIFragmentRegistered",
96
+ "Platform_UIFragmentUpdated",
97
+ "Platform_UIFragmentDeregistered",
98
+ ],
99
+ }
45
100
 
46
101
  // Plugin lifecycle Source C subscription. Mirrors the UIFragment pattern: the
47
102
  // platform invokes `Platform_PluginStatusChanged` after writing the new status
48
- // to the Plugin read model; AppSync routes that mutation via `@aws_subscribe`
49
- // to live `onPluginStatusChange` subscribers (host shell). The status enum
103
+ // to the Plugin read model; the provider routes that mutation to live
104
+ // `onPluginStatusChange` subscribers (host shell). The status enum
50
105
  // matches `PluginsReadModelSpec.status` so consumers can mirror tier 1 / tier 2
51
106
  // transitions exactly.
52
107
  let pluginStatusSubscriptionTypes = [
@@ -58,31 +113,46 @@ let pluginStatusMutationFields = [
58
113
  ` Platform_PluginStatusChanged(pluginId: ID!, status: PluginStatus!): PluginStatusChangeEvent`,
59
114
  ]
60
115
 
61
- let pluginStatusSubscriptionField = ` onPluginStatusChange: PluginStatusChangeEvent\n @aws_subscribe(mutations: ["Platform_PluginStatusChanged"])`
116
+ let pluginStatusSubscriptionField = ` onPluginStatusChange: PluginStatusChangeEvent`
117
+
118
+ let pluginStatusSubscriptionSource: GraphQL_Stitcher.subscriptionSource = {
119
+ field: "onPluginStatusChange",
120
+ mutations: ["Platform_PluginStatusChanged"],
121
+ }
62
122
 
63
123
  let baseFragment = (~cloner: bool) => {
64
- let base = GraphQL_FragmentGenerator.generate(~mutationEntries=mutationEntries(~cloner), ~queryEntries)
124
+ let base = GraphQL_FragmentGenerator.generate(
125
+ ~mutationEntries=Array.concat(mutationEntries(~cloner), apiFragmentRegistryMutationEntries),
126
+ ~queryEntries,
127
+ )
65
128
  let parts = GraphQL_Stitcher.decode(base)
66
129
  // Source C subscriptions for the admin Plugin aggregate mutations.
67
130
  // The standard auto-resolver flow (CommandGeneratorResolvers_AppSync.make)
68
131
  // always creates a Subscription.onX resolver per mutation field; without the
69
132
  // matching SDL fields, AppSync's CreateResolver fails with "Type not found".
70
- let pluginAggregateSubscriptionFields =
133
+ let (pluginAggregateSubscriptionFields, pluginAggregateSubscriptionSources) =
71
134
  Plugin_SubscriptionSchema.sourceCFields(~mutationEntries=PluginBaseFragment.pluginAggregateMutationEntries)
72
135
  GraphQL_Stitcher.encode({
73
136
  types: parts.types
74
137
  ->Array.concat(uiFragmentSubscriptionTypes)
75
138
  ->Array.concat(pluginStatusSubscriptionTypes)
76
139
  ->Array.concat(Platform_ComponentDefinitionsApi.sdlTypes)
77
- ->Array.concat(Platform_UIFragmentsApi.sdlTypes),
140
+ ->Array.concat(Platform_UIFragmentsApi.sdlTypes)
141
+ ->Array.concat(Platform_ApiFragmentsApi.sdlTypes),
78
142
  queries: Array.concat(
79
143
  parts.queries,
80
- [Platform_ComponentDefinitionsApi.sdlQueryField, Platform_UIFragmentsApi.sdlQueryField],
144
+ [
145
+ Platform_ComponentDefinitionsApi.sdlQueryField,
146
+ Platform_UIFragmentsApi.sdlQueryField,
147
+ Platform_ApiFragmentsApi.sdlQueryField,
148
+ ],
81
149
  ),
82
150
  mutations: parts.mutations
83
151
  ->Array.concat(uiFragmentMutationFields)
84
152
  ->Array.concat(pluginStatusMutationFields),
85
153
  subscriptions: [uiFragmentSubscriptionField, pluginStatusSubscriptionField]
86
154
  ->Array.concat(pluginAggregateSubscriptionFields),
155
+ subscriptionSources: [uiFragmentSubscriptionSource, pluginStatusSubscriptionSource]
156
+ ->Array.concat(pluginAggregateSubscriptionSources),
87
157
  })
88
158
  }
@@ -4,7 +4,9 @@ import * as S from "sury/src/S.res.mjs";
4
4
  import * as Api_Naming$ReventlessCore from "../components/Api/Api_Naming.res.mjs";
5
5
  import * as GraphQL_Stitcher$ReventlessCore from "../components/Api/GraphQL_Stitcher.res.mjs";
6
6
  import * as PluginBaseFragment$ReventlessCore from "../plugin/api/PluginBaseFragment.res.mjs";
7
+ import * as ApiFragmentRegistry$ReventlessCore from "./ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry.res.mjs";
7
8
  import * as Platform_UIFragmentsApi$ReventlessCore from "./Platform_UIFragmentsApi.res.mjs";
9
+ import * as Platform_ApiFragmentsApi$ReventlessCore from "./Platform_ApiFragmentsApi.res.mjs";
8
10
  import * as GraphQL_FragmentGenerator$ReventlessCore from "../components/Api/GraphQL_FragmentGenerator.res.mjs";
9
11
  import * as Plugin_SubscriptionSchema$ReventlessCore from "../plugin/component/Plugin_SubscriptionSchema.res.mjs";
10
12
  import * as Platform_ComponentDefinitionsApi$ReventlessCore from "./Platform_ComponentDefinitionsApi.res.mjs";
@@ -31,6 +33,15 @@ function mutationEntries(cloner) {
31
33
  }
32
34
  }
33
35
 
36
+ let fieldSpecs = Api_Naming$ReventlessCore.sliceMutationFields("Platform", ApiFragmentRegistry$ReventlessCore.name, ApiFragmentRegistry$ReventlessCore.commandSchema);
37
+
38
+ let apiFragmentRegistryMutationEntries = fieldSpecs.length === 0 ? [] : [{
39
+ fieldNames: fieldSpecs.map(param => param[0]),
40
+ commandSchema: ApiFragmentRegistry$ReventlessCore.commandSchema
41
+ }];
42
+
43
+ let systemCallerFieldNames = apiFragmentRegistryMutationEntries.flatMap(e => e.fieldNames).concat([Platform_ApiFragmentsApi$ReventlessCore.queryFieldName]);
44
+
34
45
  let uiFragmentSubscriptionTypes = [
35
46
  `enum UIFragmentChangeKind {\n Registered\n Updated\n Deregistered\n}`,
36
47
  `type UIFragmentChangeEvent {\n pluginId: ID!\n changeKind: UIFragmentChangeKind!\n manifest: String\n}`
@@ -42,7 +53,18 @@ let uiFragmentMutationFields = [
42
53
  ` Platform_UIFragmentDeregistered(pluginId: ID!): UIFragmentChangeEvent`
43
54
  ];
44
55
 
45
- let uiFragmentSubscriptionField = ` onUIFragmentChange: UIFragmentChangeEvent\n @aws_subscribe(mutations: ["Platform_UIFragmentRegistered", "Platform_UIFragmentUpdated", "Platform_UIFragmentDeregistered"])`;
56
+ let uiFragmentSubscriptionField = ` onUIFragmentChange: UIFragmentChangeEvent`;
57
+
58
+ let uiFragmentSubscriptionSource_mutations = [
59
+ "Platform_UIFragmentRegistered",
60
+ "Platform_UIFragmentUpdated",
61
+ "Platform_UIFragmentDeregistered"
62
+ ];
63
+
64
+ let uiFragmentSubscriptionSource = {
65
+ field: "onUIFragmentChange",
66
+ mutations: uiFragmentSubscriptionSource_mutations
67
+ };
46
68
 
47
69
  let pluginStatusSubscriptionTypes = [
48
70
  `enum PluginStatus {\n Connected\n Disconnected\n Inactive\n Retired\n}`,
@@ -51,23 +73,35 @@ let pluginStatusSubscriptionTypes = [
51
73
 
52
74
  let pluginStatusMutationFields = [` Platform_PluginStatusChanged(pluginId: ID!, status: PluginStatus!): PluginStatusChangeEvent`];
53
75
 
54
- let pluginStatusSubscriptionField = ` onPluginStatusChange: PluginStatusChangeEvent\n @aws_subscribe(mutations: ["Platform_PluginStatusChanged"])`;
76
+ let pluginStatusSubscriptionField = ` onPluginStatusChange: PluginStatusChangeEvent`;
77
+
78
+ let pluginStatusSubscriptionSource_mutations = ["Platform_PluginStatusChanged"];
79
+
80
+ let pluginStatusSubscriptionSource = {
81
+ field: "onPluginStatusChange",
82
+ mutations: pluginStatusSubscriptionSource_mutations
83
+ };
55
84
 
56
85
  function baseFragment(cloner) {
57
- let base = GraphQL_FragmentGenerator$ReventlessCore.generate(mutationEntries(cloner), PluginBaseFragment$ReventlessCore.queryEntries);
86
+ let base = GraphQL_FragmentGenerator$ReventlessCore.generate(mutationEntries(cloner).concat(apiFragmentRegistryMutationEntries), PluginBaseFragment$ReventlessCore.queryEntries);
58
87
  let parts = GraphQL_Stitcher$ReventlessCore.decode(base);
59
- let pluginAggregateSubscriptionFields = Plugin_SubscriptionSchema$ReventlessCore.sourceCFields(PluginBaseFragment$ReventlessCore.pluginAggregateMutationEntries);
88
+ let match = Plugin_SubscriptionSchema$ReventlessCore.sourceCFields(PluginBaseFragment$ReventlessCore.pluginAggregateMutationEntries);
60
89
  return GraphQL_Stitcher$ReventlessCore.encode({
61
- types: parts.types.concat(uiFragmentSubscriptionTypes).concat(pluginStatusSubscriptionTypes).concat(Platform_ComponentDefinitionsApi$ReventlessCore.sdlTypes).concat(Platform_UIFragmentsApi$ReventlessCore.sdlTypes),
90
+ types: parts.types.concat(uiFragmentSubscriptionTypes).concat(pluginStatusSubscriptionTypes).concat(Platform_ComponentDefinitionsApi$ReventlessCore.sdlTypes).concat(Platform_UIFragmentsApi$ReventlessCore.sdlTypes).concat(Platform_ApiFragmentsApi$ReventlessCore.sdlTypes),
62
91
  mutations: parts.mutations.concat(uiFragmentMutationFields).concat(pluginStatusMutationFields),
63
92
  queries: parts.queries.concat([
64
93
  Platform_ComponentDefinitionsApi$ReventlessCore.sdlQueryField,
65
- Platform_UIFragmentsApi$ReventlessCore.sdlQueryField
94
+ Platform_UIFragmentsApi$ReventlessCore.sdlQueryField,
95
+ Platform_ApiFragmentsApi$ReventlessCore.sdlQueryField
66
96
  ]),
67
97
  subscriptions: [
68
98
  uiFragmentSubscriptionField,
69
99
  pluginStatusSubscriptionField
70
- ].concat(pluginAggregateSubscriptionFields)
100
+ ].concat(match[0]),
101
+ subscriptionSources: [
102
+ uiFragmentSubscriptionSource,
103
+ pluginStatusSubscriptionSource
104
+ ].concat(match[1])
71
105
  });
72
106
  }
73
107
 
@@ -78,12 +112,16 @@ export {
78
112
  cloneMutationEntry,
79
113
  mutationEntries,
80
114
  queryEntries,
115
+ apiFragmentRegistryMutationEntries,
116
+ systemCallerFieldNames,
81
117
  uiFragmentSubscriptionTypes,
82
118
  uiFragmentMutationFields,
83
119
  uiFragmentSubscriptionField,
120
+ uiFragmentSubscriptionSource,
84
121
  pluginStatusSubscriptionTypes,
85
122
  pluginStatusMutationFields,
86
123
  pluginStatusSubscriptionField,
124
+ pluginStatusSubscriptionSource,
87
125
  baseFragment,
88
126
  }
89
127
  /* cloneArgsSchema Not a pure module */
@@ -0,0 +1,65 @@
1
+ // ApiFragmentRegistry StateChangeSlice — the write side of the platform API-schema
2
+ // fragment registry, replacing the raw `deploy-schema:*` keyspace (see
3
+ // docs/plans/event-sourced-fragment-registries.md).
4
+ //
5
+ // Unlike the UiFragmentRegistry (connect/disconnect-driven), this registry's transitions
6
+ // are driven by the DEPLOY / DESTROY of a plugin stack: the deploy registers its fragment
7
+ // as a SigV4 system caller against the Platform API, and only final retirement
8
+ // (`pulumi destroy`, resolvers gone) deregisters. Version supersession must NOT
9
+ // deregister — the registry is keyed by plugin NAME, so a successor's deploy overwrites
10
+ // its predecessor's fragment in place.
11
+ //
12
+ // The registry is idempotent: re-registering an identical fragment is a no-op,
13
+ // deregistering an absent fragment is a no-op — it never rejects a command.
14
+ //
15
+ // `RecordApiFragmentPush` is the write-back of the single-writer schema-push automation:
16
+ // after stitching and pushing the composed schema it records the outcome (ok / error +
17
+ // message) on the row of the plugin whose fragment change triggered the push, so a
18
+ // deploy waiter can poll the push status instead of timing out on introspection.
19
+ @@reventless.spec
20
+
21
+ open Reventless.Plugin
22
+
23
+ // `at` is the registration timestamp, stamped from the incoming command meta by the
24
+ // dispatching mapping — the StateViewSlice projection has no event meta, so time must
25
+ // ride the payload. Inline records let the ppx auto-tag `pluginId` (scoping the decision
26
+ // read to one plugin name). `message` is a plain string ("" = none) — option fields in
27
+ // variant payloads don't survive the T|null wire contract cleanly.
28
+ //
29
+ // `apiTarget` is the API this plugin's fields belong to — Domain (the default) or Platform.
30
+ // A plugin assigned to the Platform API (e.g. a platform inspector) stitches into the
31
+ // Platform-API schema, not the Domain-API one; the single-writer push automation groups
32
+ // fragments by this target and maintains one cumulative schema per API.
33
+ // `RegisterApiFragment` / `DeregisterApiFragment` ARE the GraphQL surface — the deploy calls them
34
+ // as a SigV4 system caller against the Platform API (exposed as `Platform_RegisterApiFragment` /
35
+ // `Platform_DeregisterApiFragment`). `RecordApiFragmentPush` is `@noApi`: it is the internal
36
+ // write-back of the single-writer schema-push automation, dispatched platform-side, never by a
37
+ // GraphQL caller.
38
+ @schema
39
+ type command =
40
+ | RegisterApiFragment({pluginId: string, fragment: apiSchemaFragment, apiTarget: apiTarget, at: string})
41
+ | DeregisterApiFragment({pluginId: string})
42
+ | @noApi RecordApiFragmentPush({pluginId: string, ok: bool, message: string, at: string})
43
+
44
+ // The registry never rejects (idempotent register/deregister/record); this variant is
45
+ // never returned.
46
+ @schema
47
+ type error = | RegistryError
48
+
49
+ @schema
50
+ type event =
51
+ | ApiFragmentRegistered({pluginId: string, fragment: apiSchemaFragment, apiTarget: apiTarget, at: string})
52
+ | ApiFragmentUpdated({
53
+ pluginId: string,
54
+ previousFragment: apiSchemaFragment,
55
+ newFragment: apiSchemaFragment,
56
+ apiTarget: apiTarget,
57
+ at: string,
58
+ })
59
+ | ApiFragmentDeregistered({pluginId: string})
60
+ | ApiFragmentPushRecorded({pluginId: string, ok: bool, message: string, at: string})
61
+
62
+ // This slice reads exactly the events it writes, so consumedEvent IS event — one inline
63
+ // event type, no duplicate-constructor collision, ppx auto-tags `pluginId` on both roles.
64
+ @schema
65
+ type consumedEvent = event