@reventlessdev/reventless-core 3.0.0-alpha.189 → 3.0.0-alpha.190
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 +7 -0
- package/package.json +4 -4
- package/src/admin/Platform_Admin_Structure.res +1 -0
- package/src/admin/Platform_Admin_Structure.res.mjs +2 -1
- package/src/plugin/component/Plugin_Structure.res +68 -0
- package/src/plugin/component/Plugin_Structure.res.mjs +26 -1
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +2 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +8 -4
- package/tests/message/MessageTest.res +1 -0
- package/tests/message/MessageTest.res.mjs +2 -1
- package/tests/plugin/PluginStructureTest.res +46 -0
- package/tests/plugin/PluginStructureTest.res.mjs +51 -0
- package/tests/plugin/StateChangeSlice/PsAttachInvoice.res +39 -0
- package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +63 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
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.190 (2026-07-28)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **platform:** provision object stores from the fields that declare them ([b5e2a1e](https://github.com/ReventlessDev/reventless-core/commit/b5e2a1ec88099941c113e4963f9c4b346b96b0d6))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.189 (2026-07-28)
|
|
7
14
|
|
|
8
15
|
**Note:** Version bump only for package @reventlessdev/reventless-core
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.190",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.31",
|
|
32
32
|
"@reventlessdev/rescript-fast-csv": "1.2.0-alpha.14",
|
|
33
33
|
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.13",
|
|
34
|
-
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.13",
|
|
35
34
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.9",
|
|
35
|
+
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.13",
|
|
36
36
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
37
37
|
"@reventlessdev/rescript-ssh2": "1.1.0-alpha.14",
|
|
38
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.108",
|
|
38
39
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
|
|
39
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.107",
|
|
40
40
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.29",
|
|
41
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
41
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.83"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "12.3.0",
|
|
@@ -166,7 +166,8 @@ let structure = {
|
|
|
166
166
|
outboundTranslationSlices: structure_outboundTranslationSlices,
|
|
167
167
|
inboundTranslationSlices: structure_inboundTranslationSlices,
|
|
168
168
|
extensions: structure_extensions,
|
|
169
|
-
extensionPoints: undefined
|
|
169
|
+
extensionPoints: undefined,
|
|
170
|
+
requiredStores: undefined
|
|
170
171
|
};
|
|
171
172
|
|
|
172
173
|
let pluginName = pluginId;
|
|
@@ -387,6 +387,73 @@ let make = (
|
|
|
387
387
|
}
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
+
// ── Declared object stores ─────────────────────────────────────────────────
|
|
391
|
+
//
|
|
392
|
+
// A field typed as a storage ref states that the deployment needs the named
|
|
393
|
+
// store to exist. Read that declaration the same way `Reference.getTarget` is
|
|
394
|
+
// read for entity references, so the requirement travels with the plugin's
|
|
395
|
+
// structure instead of living only inside a field's schema, where the deploy
|
|
396
|
+
// would have to re-walk every component to find it.
|
|
397
|
+
//
|
|
398
|
+
// An unqualified store belongs to the declaring plugin, so every collected
|
|
399
|
+
// entry is qualified to `{plugin}.{store}` — one shape, and the string is
|
|
400
|
+
// directly the store's identity. Many fields legitimately name one store, so
|
|
401
|
+
// the result is deduplicated.
|
|
402
|
+
|
|
403
|
+
let storesFromProperties = (properties: dict<S.t<unknown>>): array<string> =>
|
|
404
|
+
properties
|
|
405
|
+
->Dict.toArray
|
|
406
|
+
->Array.filterMap(((_, fieldSchema)) =>
|
|
407
|
+
Reventless.StorageRef.getStore(fieldSchema)->Option.map(target =>
|
|
408
|
+
target.plugin->Option.getOr(name) ++ "." ++ target.store
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
// Commands and events are unions of per-variant objects; state is a single
|
|
413
|
+
// object. Both reduce to "the properties of every object in this schema".
|
|
414
|
+
let storesFromSchema = (schema: S.t<unknown>): array<string> => {
|
|
415
|
+
let fromVariant = v =>
|
|
416
|
+
switch v {
|
|
417
|
+
| S.Object({properties}) => storesFromProperties(properties)
|
|
418
|
+
| _ => []
|
|
419
|
+
}
|
|
420
|
+
switch schema {
|
|
421
|
+
| Union({anyOf}) => anyOf->Array.flatMap(fromVariant)
|
|
422
|
+
| other => fromVariant(other)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let requiredStores =
|
|
427
|
+
[
|
|
428
|
+
aggregates->Array.flatMap((module(A: ReventlessInfra.Aggregate.T with type api = api)) =>
|
|
429
|
+
Array.concat(
|
|
430
|
+
storesFromSchema(A.Spec.commandSchema->S.castToUnknown),
|
|
431
|
+
storesFromSchema(A.Spec.eventSchema->S.castToUnknown),
|
|
432
|
+
)
|
|
433
|
+
),
|
|
434
|
+
stateChangeSlices->Array.flatMap((module(SCS: ReventlessInfra.StateChangeSlice.T)) =>
|
|
435
|
+
Array.concat(
|
|
436
|
+
storesFromSchema(SCS.Spec.commandSchema->S.castToUnknown),
|
|
437
|
+
storesFromSchema(SCS.Spec.eventSchema->S.castToUnknown),
|
|
438
|
+
)
|
|
439
|
+
),
|
|
440
|
+
stateViewSlices->Array.flatMap((module(SVS: ReventlessInfra.StateViewSlice.T)) =>
|
|
441
|
+
storesFromSchema(SVS.Spec.stateSchema->S.castToUnknown)
|
|
442
|
+
),
|
|
443
|
+
readModels->Array.flatMap((
|
|
444
|
+
module(R: ReventlessInfra.ReadModel.T with type api = api and type role = role),
|
|
445
|
+
) => storesFromSchema(R.Spec.stateSchema->S.castToUnknown)),
|
|
446
|
+
automationSlices->Array.flatMap((module(AS: ReventlessInfra.AutomationSlice.T)) =>
|
|
447
|
+
storesFromSchema(AS.Spec.commandSchema->S.castToUnknown)
|
|
448
|
+
),
|
|
449
|
+
inboundTranslationSlices->Array.flatMap((
|
|
450
|
+
module(ITS: ReventlessInfra.InboundTranslationSlice.T),
|
|
451
|
+
) => storesFromSchema(ITS.Spec.commandSchema->S.castToUnknown)),
|
|
452
|
+
]
|
|
453
|
+
->Array.flat
|
|
454
|
+
->Belt.Set.String.fromArray
|
|
455
|
+
->Belt.Set.String.toArray
|
|
456
|
+
|
|
390
457
|
// ── Build queryable defs ───────────────────────────────────────────────────
|
|
391
458
|
//
|
|
392
459
|
// Internal ReadModels and StateViewSlices (marked `@@reventless.visibility(Internal)`)
|
|
@@ -608,5 +675,6 @@ let make = (
|
|
|
608
675
|
inboundTranslationSlices: inboundTranslationSliceDefs,
|
|
609
676
|
extensions: extensionDefs,
|
|
610
677
|
extensionPoints: Some(extensionPointDefs),
|
|
678
|
+
requiredStores: Some(requiredStores),
|
|
611
679
|
}
|
|
612
680
|
}
|
|
@@ -8,6 +8,7 @@ import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
|
|
|
8
8
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
9
9
|
import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
|
|
10
10
|
import * as Logger$ReventlessCore from "../../util/Logger.res.mjs";
|
|
11
|
+
import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
|
|
11
12
|
import * as DisplayName$Reventless from "@reventlessdev/reventless-spec/src/components/DisplayName.res.mjs";
|
|
12
13
|
import * as Api_Naming$ReventlessCore from "../../components/Api/Api_Naming.res.mjs";
|
|
13
14
|
import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
|
|
@@ -316,6 +317,29 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
316
317
|
let match$3 = scored[0];
|
|
317
318
|
return match$3[0];
|
|
318
319
|
};
|
|
320
|
+
let storesFromSchema = schema => {
|
|
321
|
+
let fromVariant = v => {
|
|
322
|
+
if (v.type === "object") {
|
|
323
|
+
let properties = v.properties;
|
|
324
|
+
return Stdlib_Array.filterMap(Object.entries(properties), param => Stdlib_Option.map(StorageRef$Reventless.getStore(param[1]), target => Stdlib_Option.getOr(target.plugin, name) + "." + target.store));
|
|
325
|
+
} else {
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
if (schema.type === "union") {
|
|
330
|
+
return schema.anyOf.flatMap(fromVariant);
|
|
331
|
+
} else {
|
|
332
|
+
return fromVariant(schema);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
let requiredStores = Belt_SetString.toArray(Belt_SetString.fromArray([
|
|
336
|
+
aggregates.flatMap(A => storesFromSchema(A.Spec.commandSchema).concat(storesFromSchema(A.Spec.eventSchema))),
|
|
337
|
+
stateChangeSlices.flatMap(SCS => storesFromSchema(SCS.Spec.commandSchema).concat(storesFromSchema(SCS.Spec.eventSchema))),
|
|
338
|
+
stateViewSlices.flatMap(SVS => storesFromSchema(SVS.Spec.stateSchema)),
|
|
339
|
+
readModels.flatMap(R => storesFromSchema(R.Spec.stateSchema)),
|
|
340
|
+
automationSlices.flatMap(AS => storesFromSchema(AS.Spec.commandSchema)),
|
|
341
|
+
inboundTranslationSlices.flatMap(ITS => storesFromSchema(ITS.Spec.commandSchema))
|
|
342
|
+
].flat()));
|
|
319
343
|
let visibilityTag = v => {
|
|
320
344
|
if (v === "Public") {
|
|
321
345
|
return;
|
|
@@ -458,7 +482,8 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
458
482
|
outboundTranslationSlices: outboundTranslationSliceDefs,
|
|
459
483
|
inboundTranslationSlices: inboundTranslationSliceDefs,
|
|
460
484
|
extensions: extensionDefs,
|
|
461
|
-
extensionPoints: extensionPointDefs
|
|
485
|
+
extensionPoints: extensionPointDefs,
|
|
486
|
+
requiredStores: requiredStores
|
|
462
487
|
};
|
|
463
488
|
}
|
|
464
489
|
|
|
@@ -85,6 +85,7 @@ let structure: pluginStructure = {
|
|
|
85
85
|
},
|
|
86
86
|
],
|
|
87
87
|
extensionPoints: None,
|
|
88
|
+
requiredStores: None,
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
let encoded = Platform_ComponentDefinitionsApi.encodePluginStructureEntry(
|
|
@@ -295,6 +296,7 @@ describe("allowedStates + statusField populated", () => {
|
|
|
295
296
|
inboundTranslationSlices: [],
|
|
296
297
|
extensions: [],
|
|
297
298
|
extensionPoints: None,
|
|
299
|
+
requiredStores: None,
|
|
298
300
|
}
|
|
299
301
|
|
|
300
302
|
let json =
|
|
@@ -118,7 +118,8 @@ let structure = {
|
|
|
118
118
|
outboundTranslationSlices: structure_outboundTranslationSlices,
|
|
119
119
|
inboundTranslationSlices: structure_inboundTranslationSlices,
|
|
120
120
|
extensions: structure_extensions,
|
|
121
|
-
extensionPoints: undefined
|
|
121
|
+
extensionPoints: undefined,
|
|
122
|
+
requiredStores: undefined
|
|
122
123
|
};
|
|
123
124
|
|
|
124
125
|
let encoded = Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Catalog", structure);
|
|
@@ -216,7 +217,8 @@ globalThis.describe("translation-slice externalSystem round-trip", () => {
|
|
|
216
217
|
outboundTranslationSlices: externalStructure_outboundTranslationSlices,
|
|
217
218
|
inboundTranslationSlices: externalStructure_inboundTranslationSlices,
|
|
218
219
|
extensions: externalStructure_extensions,
|
|
219
|
-
extensionPoints: undefined
|
|
220
|
+
extensionPoints: undefined,
|
|
221
|
+
requiredStores: undefined
|
|
220
222
|
};
|
|
221
223
|
let externalJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Catalog", externalStructure));
|
|
222
224
|
globalThis.test("surfaces Some outbound externalSystem as its inner string", () => {
|
|
@@ -264,7 +266,8 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
|
|
|
264
266
|
outboundTranslationSlices: mixed_outboundTranslationSlices,
|
|
265
267
|
inboundTranslationSlices: mixed_inboundTranslationSlices,
|
|
266
268
|
extensions: mixed_extensions,
|
|
267
|
-
extensionPoints: undefined
|
|
269
|
+
extensionPoints: undefined,
|
|
270
|
+
requiredStores: undefined
|
|
268
271
|
};
|
|
269
272
|
let mixedJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Ordering", mixed));
|
|
270
273
|
globalThis.test("excludes an Internal queryableDef from the encoded read-side", () => {
|
|
@@ -340,7 +343,8 @@ globalThis.describe("allowedStates + statusField populated", () => {
|
|
|
340
343
|
outboundTranslationSlices: structureWithStates_outboundTranslationSlices,
|
|
341
344
|
inboundTranslationSlices: structureWithStates_inboundTranslationSlices,
|
|
342
345
|
extensions: structureWithStates_extensions,
|
|
343
|
-
extensionPoints: undefined
|
|
346
|
+
extensionPoints: undefined,
|
|
347
|
+
requiredStores: undefined
|
|
344
348
|
};
|
|
345
349
|
let json = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Platform", structureWithStates));
|
|
346
350
|
globalThis.test("encodes populated allowedStates as a JSON array", () => {
|
|
@@ -117,7 +117,8 @@ globalThis.describe("Message should", () => {
|
|
|
117
117
|
outboundTranslationSlices: [],
|
|
118
118
|
inboundTranslationSlices: [],
|
|
119
119
|
extensions: [],
|
|
120
|
-
extensionPoints: undefined
|
|
120
|
+
extensionPoints: undefined,
|
|
121
|
+
requiredStores: undefined
|
|
121
122
|
},
|
|
122
123
|
dcbEventLog: undefined,
|
|
123
124
|
kind: "Domain"
|
|
@@ -79,6 +79,20 @@ module PsAnnotatedViewSlice: ReventlessInfra.StateViewSlice.T = {
|
|
|
79
79
|
let make = (~dcbEventLog as _, ~runtime as _=?, ~opts as _=?): component => Obj.magic(0)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
module PsAttachInvoiceSlice: ReventlessInfra.StateChangeSlice.T = {
|
|
83
|
+
module Spec = PsAttachInvoice
|
|
84
|
+
module Behavior = {
|
|
85
|
+
type state = PsAttachInvoice.state
|
|
86
|
+
let initialState = PsAttachInvoice.initialState
|
|
87
|
+
let evolve = PsAttachInvoice.evolve
|
|
88
|
+
let decide = PsAttachInvoice.decide
|
|
89
|
+
let moduleUrl = PsAttachInvoice.moduleUrl
|
|
90
|
+
}
|
|
91
|
+
let isAsync = false
|
|
92
|
+
type component = scsComponent
|
|
93
|
+
let make = (~dcbEventLog as _, ~publishJsons as _, ~tagKeysByEventType as _=?, ~crossPartitionTagKeys as _=?, ~runtime as _=?, ~opts as _=?): component => Obj.magic(0)
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
let structure = Plugin_Structure.make(
|
|
83
97
|
~name="TestPlugin",
|
|
84
98
|
~stateChangeSlices=[module(PsPlaceOrderSlice), module(PsShipOrderSlice)],
|
|
@@ -384,4 +398,36 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
384
398
|
expect(placeOrder.chapter)->toEqual(None)
|
|
385
399
|
})
|
|
386
400
|
})
|
|
401
|
+
|
|
402
|
+
// A field typed as a storage ref states that the deployment needs that store
|
|
403
|
+
// to exist. Collecting the requirement onto the structure is what lets the
|
|
404
|
+
// deploy read it without re-walking every component's schema.
|
|
405
|
+
describe("requiredStores", () => {
|
|
406
|
+
let withStores = Plugin_Structure.make(
|
|
407
|
+
~name="TestPlugin",
|
|
408
|
+
~stateChangeSlices=[module(PsAttachInvoiceSlice)],
|
|
409
|
+
)
|
|
410
|
+
let stores = withStores.requiredStores->Option.getOr([])
|
|
411
|
+
|
|
412
|
+
testSync("an unqualified store resolves against the declaring plugin", () =>
|
|
413
|
+
expect(stores->Array.includes("TestPlugin.documents"))->toBe(true)
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
testSync("a qualified store keeps its own plugin", () =>
|
|
417
|
+
expect(stores->Array.includes("branding.logos"))->toBe(true)
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
// `documents` is declared on both a command field and an event field.
|
|
421
|
+
testSync("a store declared by several fields is collected once", () =>
|
|
422
|
+
expect(stores->Array.filter(s => s == "TestPlugin.documents")->Array.length)->toBe(1)
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
testSync("only the declared stores are collected", () =>
|
|
426
|
+
expect(stores->Array.length)->toBe(2)
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
testSync("a plugin declaring no stores collects none — not None", () =>
|
|
430
|
+
expect(structure.requiredStores)->toEqual(Some([]))
|
|
431
|
+
)
|
|
432
|
+
})
|
|
387
433
|
})
|
|
@@ -7,6 +7,7 @@ import * as PsShipOrder$ReventlessCore from "./StateChangeSlice/PsShipOrder.res.
|
|
|
7
7
|
import * as PsOrdersView$ReventlessCore from "./StateViewSlice/PsOrdersView.res.mjs";
|
|
8
8
|
import * as PsPlaceOrder$ReventlessCore from "./StateChangeSlice/PsPlaceOrder.res.mjs";
|
|
9
9
|
import * as PsAnnotatedView$ReventlessCore from "./StateViewSlice/PsAnnotatedView.res.mjs";
|
|
10
|
+
import * as PsAttachInvoice$ReventlessCore from "./StateChangeSlice/PsAttachInvoice.res.mjs";
|
|
10
11
|
import * as PsCustomersView$ReventlessCore from "./StateViewSlice/PsCustomersView.res.mjs";
|
|
11
12
|
import * as Plugin_Structure$ReventlessCore from "../../src/plugin/component/Plugin_Structure.res.mjs";
|
|
12
13
|
import * as PsAvailableProductsView$ReventlessCore from "./StateViewSlice/PsAvailableProductsView.res.mjs";
|
|
@@ -175,6 +176,36 @@ let PsAnnotatedViewSlice = {
|
|
|
175
176
|
make: make$5
|
|
176
177
|
};
|
|
177
178
|
|
|
179
|
+
let Behavior$2 = {
|
|
180
|
+
initialState: false,
|
|
181
|
+
evolve: PsAttachInvoice$ReventlessCore.evolve,
|
|
182
|
+
decide: PsAttachInvoice$ReventlessCore.decide,
|
|
183
|
+
moduleUrl: PsAttachInvoice$ReventlessCore.moduleUrl
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
function make$6(param, param$1, param$2, param$3, param$4, param$5) {
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let PsAttachInvoiceSlice_Spec = {
|
|
191
|
+
name: PsAttachInvoice$ReventlessCore.name,
|
|
192
|
+
moduleUrl: PsAttachInvoice$ReventlessCore.moduleUrl,
|
|
193
|
+
Id: Id$Reventless.$$String,
|
|
194
|
+
consumedEventSchema: PsAttachInvoice$ReventlessCore.consumedEventSchema,
|
|
195
|
+
errorSchema: PsAttachInvoice$ReventlessCore.errorSchema,
|
|
196
|
+
eventSchema: PsAttachInvoice$ReventlessCore.eventSchema,
|
|
197
|
+
commandSchema: PsAttachInvoice$ReventlessCore.commandSchema,
|
|
198
|
+
commandAuthorization: PsAttachInvoice$ReventlessCore.commandAuthorization,
|
|
199
|
+
readConsistency: PsAttachInvoice$ReventlessCore.readConsistency
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
let PsAttachInvoiceSlice = {
|
|
203
|
+
Spec: PsAttachInvoiceSlice_Spec,
|
|
204
|
+
Behavior: Behavior$2,
|
|
205
|
+
isAsync: false,
|
|
206
|
+
make: make$6
|
|
207
|
+
};
|
|
208
|
+
|
|
178
209
|
let structure = Plugin_Structure$ReventlessCore.make("TestPlugin", undefined, undefined, [
|
|
179
210
|
PsOrdersViewSlice,
|
|
180
211
|
PsAvailableProductsViewSlice,
|
|
@@ -453,6 +484,25 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
453
484
|
globalThis.expect(placeOrder.chapter).toEqual(undefined);
|
|
454
485
|
});
|
|
455
486
|
});
|
|
487
|
+
globalThis.describe("requiredStores", () => {
|
|
488
|
+
let withStores = Plugin_Structure$ReventlessCore.make("TestPlugin", undefined, undefined, undefined, [PsAttachInvoiceSlice], undefined, undefined, undefined, undefined, undefined, undefined);
|
|
489
|
+
let stores = Stdlib_Option.getOr(withStores.requiredStores, []);
|
|
490
|
+
globalThis.test("an unqualified store resolves against the declaring plugin", () => {
|
|
491
|
+
globalThis.expect(stores.includes("TestPlugin.documents")).toBe(true);
|
|
492
|
+
});
|
|
493
|
+
globalThis.test("a qualified store keeps its own plugin", () => {
|
|
494
|
+
globalThis.expect(stores.includes("branding.logos")).toBe(true);
|
|
495
|
+
});
|
|
496
|
+
globalThis.test("a store declared by several fields is collected once", () => {
|
|
497
|
+
globalThis.expect(stores.filter(s => s === "TestPlugin.documents").length).toBe(1);
|
|
498
|
+
});
|
|
499
|
+
globalThis.test("only the declared stores are collected", () => {
|
|
500
|
+
globalThis.expect(stores.length).toBe(2);
|
|
501
|
+
});
|
|
502
|
+
globalThis.test("a plugin declaring no stores collects none — not None", () => {
|
|
503
|
+
globalThis.expect(structure.requiredStores).toEqual([]);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
456
506
|
});
|
|
457
507
|
|
|
458
508
|
export {
|
|
@@ -462,6 +512,7 @@ export {
|
|
|
462
512
|
PsAvailableProductsViewSlice,
|
|
463
513
|
PsCustomersViewSlice,
|
|
464
514
|
PsAnnotatedViewSlice,
|
|
515
|
+
PsAttachInvoiceSlice,
|
|
465
516
|
structure,
|
|
466
517
|
}
|
|
467
518
|
/* structure Not a pure module */
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Test fixture spec for declared-object-store collection.
|
|
2
|
+
//
|
|
3
|
+
// Carries `@storageRef` on a command field, on an event field, and once in the
|
|
4
|
+
// qualified `"<plugin>.<store>"` form — the three shapes Plugin_Structure has to
|
|
5
|
+
// resolve into one qualified identity per store. `documents` is declared twice
|
|
6
|
+
// on purpose so the deduplication is exercised by data rather than asserted in
|
|
7
|
+
// the abstract.
|
|
8
|
+
|
|
9
|
+
@@reventless.spec("AttachInvoice")
|
|
10
|
+
|
|
11
|
+
type state = bool
|
|
12
|
+
let initialState = false
|
|
13
|
+
|
|
14
|
+
@schema
|
|
15
|
+
type consumedEvent = OrderPlaced
|
|
16
|
+
|
|
17
|
+
let evolve = (_state, _event) => true
|
|
18
|
+
|
|
19
|
+
// Note the attribute position: `@storageRef(…)` precedes the field NAME. Placed
|
|
20
|
+
// after the colon it attaches to the type expression instead of the field, and
|
|
21
|
+
// the ppx ignores it silently — a declaration that compiles, deploys green, and
|
|
22
|
+
// provisions nothing.
|
|
23
|
+
@schema
|
|
24
|
+
type command =
|
|
25
|
+
| AttachInvoice({
|
|
26
|
+
orderId: string,
|
|
27
|
+
@storageRef("documents") documentUrl: string,
|
|
28
|
+
// Another plugin's store — qualified, so it must NOT resolve to this
|
|
29
|
+
// plugin.
|
|
30
|
+
@storageRef("branding.logos") logoUrl: string,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
@schema
|
|
34
|
+
type error = OrderNotFound
|
|
35
|
+
|
|
36
|
+
@schema
|
|
37
|
+
type event = InvoiceAttached({orderId: string, @storageRef("documents") documentUrl: string})
|
|
38
|
+
|
|
39
|
+
let decide = (_state, _command): result<array<event>, error> => Ok([])
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
5
|
+
import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
|
|
6
|
+
|
|
7
|
+
let consumedEventSchema = S.literal("OrderPlaced");
|
|
8
|
+
|
|
9
|
+
function evolve(_state, _event) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let commandSchema = S.schema(s => ({
|
|
14
|
+
TAG: "AttachInvoice",
|
|
15
|
+
orderId: s.m(DcbTag$Reventless.string),
|
|
16
|
+
documentUrl: s.m(StorageRef$Reventless.forStore(undefined, "documents")),
|
|
17
|
+
logoUrl: s.m(StorageRef$Reventless.forStore("branding", "logos"))
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
let errorSchema = S.literal("OrderNotFound");
|
|
21
|
+
|
|
22
|
+
let eventSchema = S.schema(s => ({
|
|
23
|
+
TAG: "InvoiceAttached",
|
|
24
|
+
orderId: s.m(DcbTag$Reventless.string),
|
|
25
|
+
documentUrl: s.m(StorageRef$Reventless.forStore(undefined, "documents"))
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
function decide(_state, _command) {
|
|
29
|
+
return {
|
|
30
|
+
TAG: "Ok",
|
|
31
|
+
_0: []
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function commandAuthorization(param) {
|
|
36
|
+
return "AllowAuthenticated";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let name = "AttachInvoice";
|
|
40
|
+
|
|
41
|
+
let Id;
|
|
42
|
+
|
|
43
|
+
let initialState = false;
|
|
44
|
+
|
|
45
|
+
let moduleUrl = "@reventlessdev/reventless-core/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs";
|
|
46
|
+
|
|
47
|
+
let readConsistency = "EscalateOnRetry";
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
name,
|
|
51
|
+
Id,
|
|
52
|
+
initialState,
|
|
53
|
+
consumedEventSchema,
|
|
54
|
+
evolve,
|
|
55
|
+
commandSchema,
|
|
56
|
+
errorSchema,
|
|
57
|
+
eventSchema,
|
|
58
|
+
decide,
|
|
59
|
+
moduleUrl,
|
|
60
|
+
commandAuthorization,
|
|
61
|
+
readConsistency,
|
|
62
|
+
}
|
|
63
|
+
/* consumedEventSchema Not a pure module */
|