@reventlessdev/reventless-spec 3.0.0-alpha.109 → 3.0.0-alpha.111

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,20 @@
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.111 (2026-08-12)
7
+
8
+ ### Features
9
+
10
+ * **scripts:** publish the platform SDL instead of copying it by hand ([3f28ceb](https://github.com/ReventlessDev/reventless-core/commit/3f28cebe7f9f947894105592d7ff7e5c07043d8f))
11
+
12
+
13
+ # 3.0.0-alpha.110 (2026-08-12)
14
+
15
+ ### Features
16
+
17
+ * **ppx:** let a field say [@owner](https://github.com/owner) instead of spelling out its schema ([3bb0a4b](https://github.com/ReventlessDev/reventless-core/commit/3bb0a4bf3e5823fa929815fbe6f47203ba7958d7))
18
+
19
+
6
20
  # 3.0.0-alpha.109 (2026-08-12)
7
21
 
8
22
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-spec",
3
- "version": "3.0.0-alpha.109",
3
+ "version": "3.0.0-alpha.111",
4
4
  "description": "Specifications for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -0,0 +1,41 @@
1
+ # The platform admin API contract
2
+
3
+ `platform-api.graphql` is the SDL of the platform admin API — the `Platform_*`
4
+ half of what a UI shell talks to. It is **published with this package**, and a
5
+ shell compiles its Relay queries against it as a dependency rather than keeping a
6
+ copy of its own.
7
+
8
+ That is the whole point of it living here. A snapshot copied by hand into another
9
+ repo rots silently: the copy keeps compiling green against a schema no server
10
+ serves, and the shell fails only against a real backend. A snapshot that arrives
11
+ by dependency bump cannot get out of step with a version. Lerna versions this
12
+ repo off conventional commits, so this package bumps exactly when its contents
13
+ change — **the version a shell pins is the contract version it compiles
14
+ against.**
15
+
16
+ That also keeps a shell from running ahead of a platform. A hand-written GraphQL
17
+ document is a lockstep change, not an additive one: the server rejects the whole
18
+ document when it selects a field the schema does not declare, so a shell ahead of
19
+ its platform boots to nothing rather than degrading. Pinning a published version
20
+ means the contract a shell compiles against is one that exists.
21
+
22
+ ## It is generated, not written
23
+
24
+ Written by `pnpm run check:graphql` from the repo root, which boots the hybrid
25
+ example's local platform and introspects it. Do not edit it by hand.
26
+
27
+ The example is only a vehicle — nothing example-specific reaches this file. Every
28
+ type in it is `Platform_*` or framework-level (`CommandResult`, `PageInfo`,
29
+ `SortOrder`, …); no plugin of that example contributes anything. Its sibling
30
+ golden, `domain-api.graphql`, is the plugin-generated half and stays with the
31
+ example that produces it.
32
+
33
+ Sorted through `lexicographicSortSchema`, so it is a function of the schema alone
34
+ — the platform builds its type map from dicts, and unsorted output would diff on
35
+ iteration order rather than on a real contract change.
36
+
37
+ ## Changing it
38
+
39
+ Change the platform, then run `pnpm run check:graphql:update` and commit this
40
+ file alongside the change that moved it. The diff is the review artifact for a
41
+ wire-shape change.
@@ -0,0 +1,318 @@
1
+ type CommandAccepted {
2
+ entityId: ID
3
+ eventCount: Int!
4
+ msgId: ID!
5
+ }
6
+
7
+ type CommandPending {
8
+ msgId: ID!
9
+ }
10
+
11
+ type CommandRejected {
12
+ errorCode: String!
13
+ errorDetail: String
14
+ msgId: ID!
15
+ }
16
+
17
+ union CommandResult = CommandAccepted | CommandPending | CommandRejected
18
+
19
+ type Mutation {
20
+ Platform_PluginStatusChanged(pluginId: ID!, status: PluginStatus!): PluginStatusChangeEvent
21
+ Platform_Plugin_Activate(_0: String!, id: ID!): CommandResult!
22
+ Platform_Plugin_Deactivate(_0: String!, id: ID!): CommandResult!
23
+ Platform_Plugin_Retire(_0: String!, id: ID!): CommandResult!
24
+ Platform_UIFragmentDeregistered(pluginId: ID!): UIFragmentChangeEvent
25
+ Platform_UIFragmentRegistered(manifest: String, pluginId: ID!): UIFragmentChangeEvent
26
+ Platform_UIFragmentUpdated(manifest: String, pluginId: ID!): UIFragmentChangeEvent
27
+ }
28
+
29
+ interface Node {
30
+ id: ID!
31
+ }
32
+
33
+ type PageInfo {
34
+ endCursor: String
35
+ hasNextPage: Boolean!
36
+ hasPreviousPage: Boolean!
37
+ startCursor: String
38
+ }
39
+
40
+ type Platform_AutomationSliceDef {
41
+ chapter: String
42
+ consumedEventTypes: [String!]!
43
+ name: String!
44
+ producedCommandTypes: [String!]!
45
+ targetName: String
46
+ }
47
+
48
+ type Platform_CommandDef {
49
+ aggregateIdField: String
50
+ allowedStates: [String!]
51
+ apiExposed: Boolean
52
+ level: String!
53
+ mutationField: String!
54
+ name: String!
55
+ ownerField: String
56
+ references: [Platform_FieldReference!]!
57
+ requiredAccess: [String!]
58
+ schema: String!
59
+ targetState: String
60
+ }
61
+
62
+ type Platform_ComponentDefinitionEntry {
63
+ aggregates: [Platform_WriteSideDef!]!
64
+ automationSlices: [Platform_AutomationSliceDef!]!
65
+ extensions: [Platform_ExtensionDef!]!
66
+ inboundTranslationSlices: [Platform_InboundTranslationSliceDef!]!
67
+ internalQueryables: [Platform_ReadSideDef!]!
68
+ outboundTranslationSlices: [Platform_OutboundTranslationSliceDef!]!
69
+ pluginId: String!
70
+ readModels: [Platform_ReadSideDef!]!
71
+ stateChangeSlices: [Platform_WriteSideDef!]!
72
+ stateViewSlices: [Platform_ReadSideDef!]!
73
+ }
74
+
75
+ type Platform_ErrorDef {
76
+ name: String!
77
+ references: [Platform_FieldReference!]!
78
+ schema: String!
79
+ }
80
+
81
+ type Platform_EventDef {
82
+ name: String!
83
+ references: [Platform_FieldReference!]!
84
+ schema: String!
85
+ }
86
+
87
+ type Platform_ExtensionDef {
88
+ commandTypes: [String!]!
89
+ delegateNames: [String!]!
90
+ eventTypes: [String!]!
91
+ name: String!
92
+ }
93
+
94
+ type Platform_ExtensionPointDef {
95
+ commandTypes: [String!]
96
+ delegateNames: [String!]!
97
+ name: String!
98
+ sourceEventTypes: [String!]!
99
+ }
100
+
101
+ type Platform_FieldReference {
102
+ entity: String!
103
+ fieldName: String!
104
+ plugin: String
105
+ }
106
+
107
+ type Platform_InboundTranslationSliceDef {
108
+ chapter: String
109
+ commandTypes: [String!]!
110
+ externalSystem: String
111
+ name: String!
112
+ targetName: String
113
+ }
114
+
115
+ type Platform_OutboundTranslationSliceDef {
116
+ chapter: String
117
+ consumedEventTypes: [String!]!
118
+ externalSystem: String
119
+ inboundCommandTypes: [String!]!
120
+ name: String!
121
+ targetName: String
122
+ }
123
+
124
+ type Platform_Plugin implements Node {
125
+ apiSchemaFragment: String
126
+ apiTarget: String
127
+ dcbEventLog: Platform_PluginDcbEventLog
128
+ extensionPoints: [Platform_PluginExtensionPoints!]!
129
+ extensions: [Platform_PluginExtensions!]!
130
+ id: ID!
131
+ kind: Platform_PluginKind
132
+ name: String!
133
+ otherConnectedVersions: [String!]!
134
+ status: Platform_PluginStatus!
135
+ statusChange: Platform_PluginStatusChange!
136
+ structure: String
137
+ version: String!
138
+ }
139
+
140
+ type Platform_PluginConnection {
141
+ edges: [Platform_PluginEdge!]!
142
+ pageInfo: PageInfo!
143
+ }
144
+
145
+ type Platform_PluginDcbEventLog {
146
+ eventTopicArn: String!
147
+ name: String!
148
+ }
149
+
150
+ type Platform_PluginEdge {
151
+ cursor: String!
152
+ node: Platform_Plugin!
153
+ }
154
+
155
+ type Platform_PluginExtensionPoints {
156
+ commandTopic: String!
157
+ eventTopic: String!
158
+ name: String!
159
+ }
160
+
161
+ type Platform_PluginExtensions {
162
+ dcbSources: [String!]!
163
+ extensionPointName: String!
164
+ name: String!
165
+ }
166
+
167
+ input Platform_PluginFilter {
168
+ ids: [ID!]
169
+ kindEq: String
170
+ search: String
171
+ searchPrefix: String
172
+ }
173
+
174
+ enum Platform_PluginKind {
175
+ Commercial
176
+ Domain
177
+ Marketplace
178
+ PlatformInfrastructure
179
+ }
180
+
181
+ enum Platform_PluginStatus {
182
+ Connected
183
+ Disconnected
184
+ Inactive
185
+ Retired
186
+ }
187
+
188
+ type Platform_PluginStatusChange {
189
+ at: String!
190
+ by: String!
191
+ }
192
+
193
+ type Platform_PluginStructureEntry {
194
+ aggregates: [Platform_WriteSideDef!]!
195
+ automationSlices: [Platform_AutomationSliceDef!]!
196
+ extensionPoints: [Platform_ExtensionPointDef!]
197
+ extensions: [Platform_ExtensionDef!]!
198
+ inboundTranslationSlices: [Platform_InboundTranslationSliceDef!]!
199
+ outboundTranslationSlices: [Platform_OutboundTranslationSliceDef!]!
200
+ pluginId: String!
201
+ readModels: [Platform_ReadSideDef!]!
202
+ requiredStoreDeclarations: [Platform_RequiredStoreDeclaration!]
203
+ requiredStores: [String!]
204
+ stateChangeSlices: [Platform_WriteSideDef!]!
205
+ stateViewSlices: [Platform_ReadSideDef!]!
206
+ }
207
+
208
+ type Platform_ReadSideDef {
209
+ chapter: String
210
+ consumedEventTypes: [String!]!
211
+ idField: String
212
+ idFieldSource: String
213
+ labelField: String!
214
+ labelFieldSource: String
215
+ linkedWriteSide: [String!]!
216
+ name: String!
217
+ ownerField: String
218
+ queryField: String!
219
+ requiredAccess: [String!]
220
+ schema: String!
221
+ searchableFields: [String!]!
222
+ singleQueryField: String
223
+ statusField: String
224
+ visibility: String
225
+ }
226
+
227
+ type Platform_RequiredStoreDeclaration {
228
+ annotation: String
229
+ component: String!
230
+ field: String!
231
+ store: String!
232
+ }
233
+
234
+ type Platform_UIFragmentEntry {
235
+ pages: [Platform_UIPage!]!
236
+ panels: [Platform_UIPanel!]!
237
+ pluginId: String!
238
+ registeredAt: String!
239
+ remoteEntryUrl: String!
240
+ updatedAt: String!
241
+ }
242
+
243
+ type Platform_UIMenuEntry {
244
+ group: String
245
+ icon: String
246
+ label: String!
247
+ sortOrder: Int!
248
+ }
249
+
250
+ type Platform_UIPage {
251
+ fragmentId: String!
252
+ menuEntry: Platform_UIMenuEntry!
253
+ requiredAccess: String
254
+ title: String!
255
+ }
256
+
257
+ type Platform_UIPanel {
258
+ description: String!
259
+ fragmentId: String!
260
+ positions: [String!]!
261
+ requiredAccess: String
262
+ title: String!
263
+ }
264
+
265
+ type Platform_WriteSideDef {
266
+ chapter: String
267
+ commands: [Platform_CommandDef!]!
268
+ consistencyRead: String
269
+ consumedEventTypes: [String!]!
270
+ errors: [Platform_ErrorDef!]!
271
+ events: [Platform_EventDef!]!
272
+ linkedViews: [String!]!
273
+ name: String!
274
+ producedEventTypes: [String!]!
275
+ }
276
+
277
+ enum PluginStatus {
278
+ Connected
279
+ Disconnected
280
+ Inactive
281
+ Retired
282
+ }
283
+
284
+ type PluginStatusChangeEvent {
285
+ pluginId: ID!
286
+ status: PluginStatus!
287
+ }
288
+
289
+ type Query {
290
+ Platform_ComponentDefinitions: [Platform_ComponentDefinitionEntry!]!
291
+ Platform_Plugin(id: ID!): Platform_Plugin
292
+ Platform_PluginStructures: [Platform_PluginStructureEntry!]!
293
+ Platform_Plugins(after: String, before: String, filter: Platform_PluginFilter, first: Int, last: Int): Platform_PluginConnection!
294
+ Platform_PluginsByIds(ids: [String!]!): [Platform_Plugin!]!
295
+ Platform_UIFragments: [Platform_UIFragmentEntry!]!
296
+ }
297
+
298
+ enum SortOrder {
299
+ ASC
300
+ DESC
301
+ }
302
+
303
+ type Subscription {
304
+ onPluginStatusChange: PluginStatusChangeEvent
305
+ onUIFragmentChange: UIFragmentChangeEvent
306
+ }
307
+
308
+ type UIFragmentChangeEvent {
309
+ changeKind: UIFragmentChangeKind!
310
+ manifest: String
311
+ pluginId: ID!
312
+ }
313
+
314
+ enum UIFragmentChangeKind {
315
+ Deregistered
316
+ Registered
317
+ Updated
318
+ }
@@ -20,25 +20,47 @@ the field goes unstamped and the view goes unscoped, silently. That is why
20
20
  why it exists at all rather than leaving each consumer to look the marker up
21
21
  itself.
22
22
 
23
- There is no `@owner` ppx shorthand yet — `@s.matches(Owner.string)` is the
24
- authoring form, not a workaround for one. The shorthand is sugar over exactly
25
- this, the way `@ref` is sugar over `Reference.to_`, so it can be added without
26
- changing what any reader here does; until it exists, prefer the explicit form
27
- over inventing an attribute the ppx will reject.
23
+ `@owner` is the authoring form and is sugar over the constructors below, the way
24
+ `@ref` is sugar over `Reference.to_`. Write `@s.matches(Owner.string)` by hand
25
+ only where the ppx shorthand cannot reach — a file with no `@@reventless.spec`
26
+ annotation, where the attribute would survive into the compiler as an unknown
27
+ one.
28
28
 
29
29
  @example
30
30
  ```rescript
31
31
  @schema type command =
32
32
  PlaceOrder({
33
33
  @partitionTag orderId: string,
34
- customerId: @s.matches(Owner.string) string,
34
+ @owner customerId: string,
35
35
  })
36
36
  ```
37
37
  */
38
38
  let ownerId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(~namespace="reventless", ~name="owner")
39
39
 
40
+ /**
41
+ Layers the owner marker onto a schema that already says something else.
42
+
43
+ Owner-ness is independent of everything else a field declares: the same field
44
+ may be a DCB tag, a partition key, or a reference, and none of those implies or
45
+ is implied by owning. But a field carries at most one `@s.matches`, so the
46
+ shorthand composes by *wrapping* whatever schema the field already resolved to
47
+ rather than replacing it — replacing would silently drop the field's DCB tag,
48
+ and a dropped tag is a decision read that quietly misses events.
49
+ */
50
+ let mark = (schema: S.t<'a>): S.t<'a> => schema->S.Metadata.set(~id=ownerId, true)
51
+
40
52
  /** A string field declared as the record's owner. */
41
- let string: S.t<string> = S.string->S.Metadata.set(~id=ownerId, true)
53
+ let string: S.t<string> = S.string->mark
54
+
55
+ /**
56
+ An `option<string>` field declared as the record's owner.
57
+
58
+ Needed because `@s.matches` on an explicitly-`option`-typed field must supply
59
+ the whole field schema, wrapper included. The `f?: string` form needs nothing
60
+ extra: sury wraps the annotated inner schema itself, and `isFieldOwner` looks
61
+ through that wrapper either way.
62
+ */
63
+ let optionString: S.t<option<string>> = S.option(string)
42
64
 
43
65
  /** Whether this exact schema carries the marker. Does not look through wrappers. */
44
66
  let isOwner = (schema: S.t<unknown>): bool =>
@@ -7,7 +7,13 @@ import * as Semantic$Reventless from "../semantic/Semantic.res.mjs";
7
7
 
8
8
  let ownerId = S.Metadata.Id.make("reventless", "owner");
9
9
 
10
- let string = S.Metadata.set(S.string, ownerId, true);
10
+ function mark(schema) {
11
+ return S.Metadata.set(schema, ownerId, true);
12
+ }
13
+
14
+ let string = mark(S.string);
15
+
16
+ let optionString = S.option(string);
11
17
 
12
18
  function isOwner(schema) {
13
19
  return Stdlib_Option.getOr(S.Metadata.get(schema, ownerId), false);
@@ -86,7 +92,9 @@ function variantFieldNames(schema, variant) {
86
92
 
87
93
  export {
88
94
  ownerId,
95
+ mark,
89
96
  string,
97
+ optionString,
90
98
  isOwner,
91
99
  isFieldOwner,
92
100
  fieldNamesOfProperties,