@reventlessdev/reventless-core 3.0.0-alpha.260 → 3.0.0-alpha.262
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 +24 -0
- package/package.json +10 -10
- package/src/Behavior.res +1 -4
- package/src/Message.res +17 -14
- package/src/Projection.res +51 -25
- package/src/RequestContext.res +8 -9
- package/src/ResourceAttribution.res +28 -14
- package/src/adapter/Adapter.res +48 -4
- package/src/adapter/EventLogProvisioning/EventLogProvisioning.res +5 -5
- package/src/adapter/Messaging/Messaging_Log_Backend.res +10 -7
- package/src/adapter/Messaging/Messaging_Log_Backend.res.mjs +2 -4
- package/src/adapter/Monitoring/Monitoring.res +16 -17
- package/src/adapter/QueryInterception/QueryInterception.res +0 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Common.res +12 -5
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +11 -7
- package/src/adapter/Runtime/PluginRuntime_Builder_Micro.res +1 -7
- package/src/adapter/Runtime/PluginRuntime_Builder_Single.res +1 -7
- package/src/adapter/RuntimeExtension/RuntimeExtension.res +0 -1
- package/src/adapter/Secrets/Secrets_Node_Backend.res +60 -0
- package/src/adapter/Secrets/Secrets_Node_Backend.res.mjs +69 -0
- package/src/admin/Platform_Admin.res +17 -6
- package/src/admin/Platform_AdminApi.res +14 -9
- package/src/admin/Platform_Admin_Callback.res +1 -1
- package/src/admin/Platform_BakedManifest.res +34 -34
- package/src/admin/Platform_ComponentDefinitionsApi.res +9 -21
- package/src/admin/Platform_PluginStructuresApi.res +15 -12
- package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res +1 -1
- package/src/components/Aggregate/Aggregate.res +13 -12
- package/src/components/Aggregate/Aggregate_Builder.res +22 -21
- package/src/components/Aggregate/Aggregate_Callback.res +91 -86
- package/src/components/Api/ApiNoApiHelpers.res +1 -3
- package/src/components/Api/Api_Ids.res +0 -1
- package/src/components/Api/Api_Naming.res +41 -18
- package/src/components/Api/GraphQL_FragmentGenerator.res +38 -51
- package/src/components/Api/GraphQL_SchemaInspector.res +5 -3
- package/src/components/Api/GraphQL_Stitcher.res +9 -10
- package/src/components/Api/MCP_SchemaGenerator.res +8 -12
- package/src/components/Api/OwnerScopeDiagnostics.res +1 -2
- package/src/components/Api/QueryDbListQuery.res +13 -6
- package/src/components/Api/SchemaType.res +28 -23
- package/src/components/Api/SuryToJsonSchema.res +9 -10
- package/src/components/AutomationSlice/AutomationSlice.res +4 -4
- package/src/components/AutomationSlice/AutomationSlice_Builder.res +15 -14
- package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -3
- package/src/components/Builder_Helpers.res +12 -23
- package/src/components/CommandGenerator/CommandGenerator.res +6 -1
- package/src/components/CommandGenerator/CommandGenerator_Builder.res +29 -21
- package/src/components/CommandGenerator/CommandGenerator_Callback.res +17 -21
- package/src/components/CommandTopic/CommandTopic.res +3 -3
- package/src/components/CommandTopic/CommandTopic_Builder.res +10 -10
- package/src/components/CommandTopic/CommandTopic_Callback.res +1 -4
- package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -2
- package/src/components/Counter/Counter_Builder.res +1 -6
- package/src/components/Counter/Counter_Callback.res +3 -3
- package/src/components/Dcb/Dcb_Builder.res +970 -956
- package/src/components/DcbEventLog/DcbEventLog.res +4 -4
- package/src/components/DcbEventLog/DcbEventLog_Builder.res +0 -1
- package/src/components/DcbEventLog/DcbEventLog_Operations.res +17 -18
- package/src/components/EventCollector/EventCollector_Builder.res +2 -1
- package/src/components/EventLog/EventLog.res +5 -1
- package/src/components/EventLog/EventLog_Operations.res +12 -8
- package/src/components/EventMapper/EventMapper.res +8 -10
- package/src/components/EventMapper/EventMapper_Builder.res +4 -1
- package/src/components/EventMapper/EventMapper_Callback.res +46 -31
- package/src/components/EventTopic/EventTopic.res +3 -3
- package/src/components/EventTopic/EventTopic_Builder.res +1 -1
- package/src/components/Extension/Extension.res +4 -1
- package/src/components/Extension/Extension_Operations.res +11 -3
- package/src/components/ExtensionPoint/ExtensionPoint.res +3 -3
- package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +9 -10
- package/src/components/ExtensionPoint/ExtensionPoint_Callback.res +2 -6
- package/src/components/ExtensionPoint/ExtensionPoint_Operations.res +13 -5
- package/src/components/Heartbeat/Heartbeat_Callback.res +3 -3
- package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +4 -4
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +7 -10
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice.res +4 -4
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +15 -14
- package/src/components/QueryDb/QueryDb_Adapter.res +1 -3
- package/src/components/QueryDb/QueryDb_Operations.res +10 -10
- package/src/components/ReadModel/ReadModel.res +3 -3
- package/src/components/ReadModel/ReadModel_Builder.res +1 -2
- package/src/components/ReadModel/ReadModel_Callback.res +2 -1
- package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +5 -2
- package/src/components/StateChangeSlice/StateChangeSlice.res +3 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +23 -4
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +22 -6
- package/src/components/StateTopic.res +1 -3
- package/src/components/StateViewSlice/StateViewSlice.res +4 -4
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +79 -68
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
- package/src/components/Task/Task.res +3 -3
- package/src/components/Task/Task_Adapter.res +4 -1
- package/src/components/Task/Task_Builder.res +24 -21
- package/src/plugin/api/PluginBaseFragment.res +9 -3
- package/src/plugin/component/Capability_Inference.res +8 -10
- package/src/plugin/component/Plugin.res +6 -6
- package/src/plugin/component/Plugin_Builder.res +54 -50
- package/src/plugin/component/Plugin_Callback.res +10 -6
- package/src/plugin/component/Plugin_Helpers.res +264 -230
- package/src/plugin/component/Plugin_Structure.res +370 -324
- package/src/plugin/component/Plugin_SubscriptionSchema.res +8 -6
- package/src/plugin/component/SchemaWalker.res +11 -12
- package/src/plugin/connect/PluginConnectExtension_Mapping.res +45 -50
- package/src/plugin/lifecycle/PluginBehavior.res +3 -4
- package/src/plugin/lifecycle/PluginSpec.res +1 -2
- package/src/plugin/lifecycle/PluginsProjection.res +16 -5
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
- package/src/util/CommandPublisher.res +9 -2
- package/src/util/CsvStream.res +3 -3
- package/src/util/Interstack.res +7 -15
- package/src/util/Interstack.resi +3 -3
- package/src/util/LogFormat.res +5 -9
- package/src/util/Logger.res +1 -2
- package/src/util/Lru.res +2 -1
- package/src/util/Lru.resi +2 -3
- package/src/util/ScheduleOps.res +24 -27
- package/src/util/StorePrefixCollision.res +6 -9
- package/src/util/Util_Promise.res +4 -1
- package/src/util/Util_Promise.res.mjs +1 -1
- package/tests/IdentityTest.res +166 -113
- package/tests/ResourceAttributionTest.res +28 -25
- package/tests/Secrets_Node_BackendTest.res +141 -0
- package/tests/Secrets_Node_BackendTest.res.mjs +152 -0
- package/tests/StorePrefixCollisionTest.res +2 -1
- package/tests/TaskBucketNamingTest.res +3 -2
- package/tests/adapter/EventLogProvisioningTest.res +51 -48
- package/tests/adapter/MonitoringTest.res +81 -38
- package/tests/adapter/QueryInterceptionTest.res +2 -2
- package/tests/adapter/RefusalVocabularyTest.res +31 -23
- package/tests/adapter/RuntimeExtensionHookReachTest.res +9 -8
- package/tests/adapter/RuntimeExtensionTest.res +5 -3
- package/tests/admin/AdminApiSchemaDriftTest.res +22 -16
- package/tests/admin/Platform_Admin_StructureTest.res +22 -14
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +4 -3
- package/tests/admin/Platform_BakedManifestTest.res +31 -35
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +18 -15
- package/tests/admin/Platform_PluginStructuresApiTest.res +5 -3
- package/tests/admin/Platform_UIFragmentsApiTest.res +6 -3
- package/tests/aggregate/AggregateCacheTest.res +10 -12
- package/tests/aggregate/AggregateCallbackTest.res +75 -46
- package/tests/aggregate/AggregateCausationTest.res +19 -20
- package/tests/aggregate/AggregateConflictTest.res +41 -19
- package/tests/aggregate/AggregateFixtures.res +15 -5
- package/tests/aggregate/AggregateRejectionTest.res +97 -80
- package/tests/aggregate/AggregateSnapshotTest.res +44 -16
- package/tests/api/Api_IdsTest.res +10 -4
- package/tests/api/BrandedMarkerCompositionTest.res +48 -36
- package/tests/api/GeolocationSemanticTest.res +3 -3
- package/tests/api/GraphQL_FragmentGeneratorTest.res +66 -54
- package/tests/api/OwnerScopeDiagnosticsTest.res +31 -27
- package/tests/api/StorageRefFieldsTest.res +30 -18
- package/tests/api/SuryToJsonSchemaTest.res +1315 -1014
- package/tests/api/TaggedUnionSchemaTest.res +17 -24
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res +235 -179
- package/tests/commandgenerator/CommandGeneratorFixtures.res +8 -13
- package/tests/commandgenerator/OwnerStampingTest.res +91 -76
- package/tests/commandtopic/CommandOutcomeHookTest.res +198 -155
- package/tests/commandtopic/CommandTopicCallbackFixtures.res +5 -4
- package/tests/commandtopic/CommandTopicCallbackTest.res +61 -43
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +70 -55
- package/tests/counter/CounterCallbackTest.res +40 -34
- package/tests/counter/CounterFixtures.res +4 -4
- package/tests/dcb/DcbDecodeTest.res +115 -84
- package/tests/dcb/DcbEventLogOperationsTest.res +194 -143
- package/tests/dcb/DcbEventLogSchemaTest.res +13 -13
- package/tests/dcb/DcbFixtures.res +4 -5
- package/tests/dcb/DcbScopeInferenceTest.res +173 -114
- package/tests/dcb/DcbStateChangeSliceTest.res +391 -262
- package/tests/dcb/DcbTagTest.res +22 -11
- package/tests/dcb/DcbValidationTest.res +288 -218
- package/tests/eventlog/EventLogFixtures.res +4 -4
- package/tests/eventlog/EventLogOperationsTest.res +102 -67
- package/tests/eventlog/EventLogRetryTest.res +104 -65
- package/tests/eventmapper/EventMapperCallbackTest.res +118 -83
- package/tests/eventmapper/EventMapperFixtures.res +15 -23
- package/tests/eventmapper/EventMapperTest.res +54 -38
- package/tests/eventtopic/EventTopicFixtures.res +3 -4
- package/tests/eventtopic/EventTopicOperationsTest.res +41 -29
- package/tests/extensionpoint/ExtensionPointCallbackTest.res +58 -45
- package/tests/extensionpoint/ExtensionPointFixtures.res +16 -20
- package/tests/extensionpoint/ExtensionPointOperationsTest.res +70 -49
- package/tests/extensionpoint/PluginDisconnectScheduleTest.res +2 -1
- package/tests/logger/LogFormatTest.res +104 -82
- package/tests/logger/LoggerLazyTest.res +21 -12
- package/tests/message/MessageTest.res +96 -93
- package/tests/message/MetaEnvelopeTest.res +26 -30
- package/tests/plugin/HeartbeatDisconnectGraceTest.res +31 -25
- package/tests/plugin/ManifestVisibilityTest.res +20 -12
- package/tests/plugin/PluginDefinitionScalars.res +1 -2
- package/tests/plugin/PluginLifecycleCorpusTest.res +15 -12
- package/tests/plugin/PluginSpecExperiment.res +0 -1
- package/tests/plugin/PluginStructureTest.res +1529 -1045
- package/tests/plugin/Plugin_EventQuerySchemaTest.res +21 -10
- package/tests/plugin/SchemaWalkerTest.res +116 -70
- package/tests/plugin/StateChange/PsReserveStock.res +4 -4
- package/tests/plugin/StateView/PsAnnotatedView.res +4 -4
- package/tests/plugin/StateView/PsCustomersView.res +4 -4
- package/tests/plugin/StateView/PsShipmentsView.res +3 -2
- package/tests/projection/LifecycleTrailTest.res +1 -4
- package/tests/projection/ProjectionOptimizeTest.res +13 -9
- package/tests/querydb/QueryDbFixtures.res +0 -1
- package/tests/querydb/QueryDbOperationsTest.res +99 -64
- package/tests/querydb/TaggedUnionStampTest.res +10 -8
- package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res +50 -35
- package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +4 -10
- package/tests/util/CommandPublisherTest.res +0 -1
- package/tests/util/CsvStreamTest.res +3 -3
- package/tests/util/LruTest.res +84 -56
|
@@ -21,54 +21,66 @@ describe("a field marker on a branded semantic:", () => {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
describe("@owner on an Email field:", () => {
|
|
24
|
-
testSync(
|
|
25
|
-
|
|
24
|
+
testSync(
|
|
25
|
+
"marks the field as the owner",
|
|
26
|
+
() =>
|
|
27
|
+
expect(fieldOf("email")->Option.mapOr(false, Reventless.Owner.isFieldOwner))->toBe(true),
|
|
26
28
|
)
|
|
27
29
|
|
|
28
|
-
testSync(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
testSync(
|
|
31
|
+
"keeps the email semantic",
|
|
32
|
+
() =>
|
|
33
|
+
expect(
|
|
34
|
+
fieldOf("email")
|
|
35
|
+
->Option.flatMap(Reventless.Semantic.getFrom)
|
|
36
|
+
->Option.map(s => s.id),
|
|
37
|
+
)->toEqual(Some("email")),
|
|
34
38
|
)
|
|
35
39
|
|
|
36
40
|
// The regression a substituting fix would land, and it lands in silence:
|
|
37
41
|
// the row still scopes to its owner, and the field accepts anything.
|
|
38
|
-
testSync(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
switch
|
|
43
|
-
|
|
|
44
|
-
|
|
42
|
+
testSync(
|
|
43
|
+
"still rejects what is not an address",
|
|
44
|
+
() => {
|
|
45
|
+
let parses = raw =>
|
|
46
|
+
switch fieldOf("email") {
|
|
47
|
+
| Some(schema) =>
|
|
48
|
+
switch raw->JSON.Encode.string->S.parseOrThrow(~to=schema) {
|
|
49
|
+
| _ => true
|
|
50
|
+
| exception _ => false
|
|
51
|
+
}
|
|
52
|
+
| None => false
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
})
|
|
54
|
+
expect((parses("buyer@example.com"), parses("buyer")))->toEqual((true, false))
|
|
55
|
+
},
|
|
56
|
+
)
|
|
50
57
|
})
|
|
51
58
|
|
|
52
59
|
describe("@displayName on a DateTime field:", () => {
|
|
53
|
-
testSync(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
testSync(
|
|
61
|
+
"names the row by it",
|
|
62
|
+
() =>
|
|
63
|
+
expect(
|
|
64
|
+
S.Metadata.get(stateSchema, ~id=Reventless.DisplayName.displayNameId)->Option.map(
|
|
65
|
+
d => d.fields,
|
|
66
|
+
),
|
|
67
|
+
)->toEqual(Some(["openedAt"])),
|
|
59
68
|
)
|
|
60
69
|
|
|
61
|
-
testSync(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
switch
|
|
66
|
-
|
|
|
67
|
-
|
|
70
|
+
testSync(
|
|
71
|
+
"and the instant grammar survives",
|
|
72
|
+
() => {
|
|
73
|
+
let parses = raw =>
|
|
74
|
+
switch fieldOf("openedAt") {
|
|
75
|
+
| Some(schema) =>
|
|
76
|
+
switch raw->JSON.Encode.string->S.parseOrThrow(~to=schema) {
|
|
77
|
+
| _ => true
|
|
78
|
+
| exception _ => false
|
|
79
|
+
}
|
|
80
|
+
| None => false
|
|
68
81
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
})
|
|
82
|
+
expect((parses("2026-03-02T09:00:00Z"), parses("tomorrow")))->toEqual((true, false))
|
|
83
|
+
},
|
|
84
|
+
)
|
|
73
85
|
})
|
|
74
86
|
})
|
|
@@ -101,8 +101,8 @@ describe("the Geolocation semantic emits its three member types", () => {
|
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
testSync("the field is typed as the union", () => {
|
|
104
|
-
expect(
|
|
105
|
-
|
|
106
|
-
)
|
|
104
|
+
expect(
|
|
105
|
+
typeDefFor("type SemanticGeoCustomer")->String.includes("geolocation: Geolocation"),
|
|
106
|
+
)->toBe(true)
|
|
107
107
|
})
|
|
108
108
|
})
|
|
@@ -102,25 +102,23 @@ describe("resolveKeyField — the ladder", () => {
|
|
|
102
102
|
})
|
|
103
103
|
|
|
104
104
|
testSync("several `*Id` fields are disambiguated by the component name", () => {
|
|
105
|
-
let schema = S.schema(
|
|
106
|
-
{"productId": s.matches(S.string), "categoryId": s.matches(S.string)}
|
|
105
|
+
let schema = S.schema(
|
|
106
|
+
s => {"productId": s.matches(S.string), "categoryId": s.matches(S.string)},
|
|
107
107
|
)
|
|
108
108
|
expect(rungFor(~entityName="Products", schema))->toEqual(Some(("productId", "convention")))
|
|
109
109
|
})
|
|
110
110
|
|
|
111
111
|
// The `-ies` plural the naive singulariser gets wrong, on the key side too.
|
|
112
112
|
testSync("the convention rung singularises the way Api_Naming does", () => {
|
|
113
|
-
let schema = S.schema(s =>
|
|
114
|
-
{"categoryId": s.matches(S.string), "ownerId": s.matches(S.string)}
|
|
115
|
-
)
|
|
113
|
+
let schema = S.schema(s => {"categoryId": s.matches(S.string), "ownerId": s.matches(S.string)})
|
|
116
114
|
expect(rungFor(~entityName="Categories", schema))->toEqual(Some(("categoryId", "convention")))
|
|
117
115
|
})
|
|
118
116
|
|
|
119
117
|
// ProductDemand: two `*Id` fields, and the name yields `productDemandId`, which
|
|
120
118
|
// is not a field. Declining is the honest answer — this is what `@id` is for.
|
|
121
119
|
testSync("several `*Id` fields with no name match declines", () => {
|
|
122
|
-
let schema = S.schema(
|
|
123
|
-
{"productId": s.matches(S.string), "categoryId": s.matches(S.string)}
|
|
120
|
+
let schema = S.schema(
|
|
121
|
+
s => {"productId": s.matches(S.string), "categoryId": s.matches(S.string)},
|
|
124
122
|
)
|
|
125
123
|
expect(rungFor(~entityName="ProductDemand", schema))->toEqual(None)
|
|
126
124
|
})
|
|
@@ -144,14 +142,16 @@ describe("resolveKeyField — the ladder", () => {
|
|
|
144
142
|
// which of the two ways to lose it is worth saying out loud.
|
|
145
143
|
describe("keyFieldGapMessage — which gap is worth a warning", () => {
|
|
146
144
|
let gapFor = (~entityName, schema) =>
|
|
147
|
-
GraphQL_FragmentGenerator.classifyKeyField(
|
|
148
|
-
|
|
145
|
+
GraphQL_FragmentGenerator.classifyKeyField(
|
|
146
|
+
~entityName,
|
|
147
|
+
schema->S.castToUnknown,
|
|
148
|
+
)->GraphQL_FragmentGenerator.keyFieldGapMessage
|
|
149
149
|
|
|
150
150
|
// The accident §14b.2 named: the view had `productId` alone and somebody added
|
|
151
151
|
// `categoryId`, so `sole` stopped firing and the name matches neither.
|
|
152
152
|
testSync("a second `*Id` field taking the key away is named, with both fields", () => {
|
|
153
|
-
let schema = S.schema(
|
|
154
|
-
{"productId": s.matches(S.string), "categoryId": s.matches(S.string)}
|
|
153
|
+
let schema = S.schema(
|
|
154
|
+
s => {"productId": s.matches(S.string), "categoryId": s.matches(S.string)},
|
|
155
155
|
)
|
|
156
156
|
let message = gapFor(~entityName="ProductDemand", schema)->Option.getOr("")
|
|
157
157
|
expect(message->String.includes("productId, categoryId"))->toBe(true)
|
|
@@ -171,9 +171,9 @@ describe("keyFieldGapMessage — which gap is worth a warning", () => {
|
|
|
171
171
|
)
|
|
172
172
|
|
|
173
173
|
testSync("a resolved key says nothing", () =>
|
|
174
|
-
expect(
|
|
175
|
-
|
|
176
|
-
)
|
|
174
|
+
expect(gapFor(~entityName="Orders", S.schema(s => {"orderId": s.matches(S.string)})))->toEqual(
|
|
175
|
+
None,
|
|
176
|
+
)
|
|
177
177
|
)
|
|
178
178
|
|
|
179
179
|
testSync("an @id-less view whose name picks one of its `*Id` fields is fine", () =>
|
|
@@ -289,11 +289,12 @@ describe("semantic composites are named once, not once per field", () => {
|
|
|
289
289
|
let types = GraphQL_FragmentGenerator.deriveObjectTypeWithNested(
|
|
290
290
|
~typeName="Catalog_Product",
|
|
291
291
|
~includeIdParam=false,
|
|
292
|
-
S.schema(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
S.schema(
|
|
293
|
+
s =>
|
|
294
|
+
{
|
|
295
|
+
"productId": s.matches(S.string),
|
|
296
|
+
"price": s.matches(Reventless.Money.schema),
|
|
297
|
+
},
|
|
297
298
|
)->S.castToUnknown,
|
|
298
299
|
)
|
|
299
300
|
expect((
|
|
@@ -315,16 +316,15 @@ module PlaceOrder = {
|
|
|
315
316
|
// A single-payload command reaches the generator as a plain object schema —
|
|
316
317
|
// the `Object(_)` branch of `generate` — which is also the shape one variant
|
|
317
318
|
// of a union presents to `mutationArgTypes`.
|
|
318
|
-
let schema =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
)->S.castToUnknown
|
|
319
|
+
let schema = S.schema(s =>
|
|
320
|
+
{
|
|
321
|
+
"orderId": s.matches(S.string),
|
|
322
|
+
"shippingMethod": s.matches(shippingMethodSchema),
|
|
323
|
+
"total": s.matches(Reventless.Money.schema),
|
|
324
|
+
"tip": s.matches(S.option(Reventless.Money.schema)),
|
|
325
|
+
"itemCount": s.matches(S.int),
|
|
326
|
+
}
|
|
327
|
+
)->S.castToUnknown
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
describe("GraphQL_FragmentGenerator.mutationArgTypes", () => {
|
|
@@ -409,7 +409,7 @@ let refDoorFragment = (~subIdField=?) =>
|
|
|
409
409
|
stateSchema: RefDoorRow.stateSchema->S.castToUnknown,
|
|
410
410
|
authorization: None,
|
|
411
411
|
connectionSpec: true,
|
|
412
|
-
|
|
412
|
+
?subIdField,
|
|
413
413
|
},
|
|
414
414
|
],
|
|
415
415
|
)
|
|
@@ -417,7 +417,6 @@ let refDoorFragment = (~subIdField=?) =>
|
|
|
417
417
|
// What a caller holding a pointer to a withheld row may learn about it, and what
|
|
418
418
|
// the two doors that could never answer for one can now be asked.
|
|
419
419
|
describe("the reference door and the archive argument", () => {
|
|
420
|
-
|
|
421
420
|
// The narrowness is the type's, not a rule each backend re-implements: a caller
|
|
422
421
|
// cannot ask this door for a price, because there is no price on it.
|
|
423
422
|
testSync("projects a reference to id, label and the state that retired it", () => {
|
|
@@ -490,7 +489,9 @@ describe("the reference door and the archive argument", () => {
|
|
|
490
489
|
~typeName="Ordering_OrderLine",
|
|
491
490
|
~subIdField="lineNo",
|
|
492
491
|
),
|
|
493
|
-
)->toEqual(
|
|
492
|
+
)->toEqual(
|
|
493
|
+
" Ordering_OrderLine(id: ID!, lineNo: String!, includeRetired: Boolean): Ordering_OrderLine",
|
|
494
|
+
)
|
|
494
495
|
)
|
|
495
496
|
})
|
|
496
497
|
|
|
@@ -502,8 +503,8 @@ describe("the by-index door", () => {
|
|
|
502
503
|
let index = (~index, ~idField=?, ~subIdField=?): Reventless.ReadModel.indexConfig => {
|
|
503
504
|
index,
|
|
504
505
|
type_: "S",
|
|
505
|
-
|
|
506
|
-
|
|
506
|
+
?idField,
|
|
507
|
+
?subIdField,
|
|
507
508
|
projectionType: ALL,
|
|
508
509
|
}
|
|
509
510
|
|
|
@@ -591,8 +592,10 @@ describe("the by-index door", () => {
|
|
|
591
592
|
GraphQL_Stitcher.decode(
|
|
592
593
|
GraphQL_FragmentGenerator.generate(~mutationEntries=[], ~queryEntries=[entryWith(indexes)]),
|
|
593
594
|
).queries->Array.filter(q => q->String.includes("Ordering_OrderBy"))
|
|
594
|
-
expect((
|
|
595
|
-
|
|
595
|
+
expect((
|
|
596
|
+
doorsOf([derived])->Array.length,
|
|
597
|
+
doorsOf([authored, derived])->Array.length,
|
|
598
|
+
))->toEqual((0, 1))
|
|
596
599
|
})
|
|
597
600
|
})
|
|
598
601
|
|
|
@@ -681,7 +684,7 @@ module ResolvedFields = {
|
|
|
681
684
|
returnTypeName: "Catalog_Order",
|
|
682
685
|
stateSchema: orderSchema->S.castToUnknown,
|
|
683
686
|
authorization: None,
|
|
684
|
-
|
|
687
|
+
?resolvedFields,
|
|
685
688
|
},
|
|
686
689
|
{
|
|
687
690
|
singleFieldName: "Catalog_Product",
|
|
@@ -755,10 +758,13 @@ describe("@resolves / @resolvesMany reach the SDL", () => {
|
|
|
755
758
|
// it, not just this field. Refused where the declaration is, naming it.
|
|
756
759
|
testSync("a target this plugin does not expose is refused", () =>
|
|
757
760
|
expect(
|
|
758
|
-
refused(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
761
|
+
refused(
|
|
762
|
+
() =>
|
|
763
|
+
orderType(
|
|
764
|
+
~resolvedFields=Some([
|
|
765
|
+
{fieldName: "supplier", typeName: "Supply_Supplier", multi: false},
|
|
766
|
+
]),
|
|
767
|
+
),
|
|
762
768
|
),
|
|
763
769
|
)->toBe(true)
|
|
764
770
|
)
|
|
@@ -768,11 +774,12 @@ describe("@resolves / @resolvesMany reach the SDL", () => {
|
|
|
768
774
|
// handed over by the wider door.
|
|
769
775
|
testSync("a target guarded differently from the parent is refused", () =>
|
|
770
776
|
expect(
|
|
771
|
-
refused(
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
777
|
+
refused(
|
|
778
|
+
() =>
|
|
779
|
+
orderType(
|
|
780
|
+
~resolvedFields=Some(ResolvedFields.bothFields),
|
|
781
|
+
~targetPermission=AllowGroups(["Admin"]),
|
|
782
|
+
),
|
|
776
783
|
),
|
|
777
784
|
)->toBe(true)
|
|
778
785
|
)
|
|
@@ -780,20 +787,25 @@ describe("@resolves / @resolvesMany reach the SDL", () => {
|
|
|
780
787
|
// A target open to everyone can only narrow, so it needs no refusal.
|
|
781
788
|
testSync("a target open to everyone is allowed under an authenticated parent", () =>
|
|
782
789
|
expect(
|
|
783
|
-
refused(
|
|
784
|
-
|
|
790
|
+
refused(
|
|
791
|
+
() =>
|
|
792
|
+
orderType(
|
|
793
|
+
~resolvedFields=Some(ResolvedFields.bothFields),
|
|
794
|
+
~targetPermission=AllowAnonymous,
|
|
795
|
+
),
|
|
785
796
|
),
|
|
786
797
|
)->toBe(false)
|
|
787
798
|
)
|
|
788
799
|
|
|
789
800
|
testSync("a resolved field colliding with a state field is refused", () =>
|
|
790
801
|
expect(
|
|
791
|
-
refused(
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
802
|
+
refused(
|
|
803
|
+
() =>
|
|
804
|
+
orderType(
|
|
805
|
+
~resolvedFields=Some([
|
|
806
|
+
{fieldName: "productId", typeName: "Catalog_Product", multi: false},
|
|
807
|
+
]),
|
|
808
|
+
),
|
|
797
809
|
),
|
|
798
810
|
)->toBe(true)
|
|
799
811
|
)
|
|
@@ -38,9 +38,9 @@ let warnFor = (~view, ~ownerField, ~elevated) => {
|
|
|
38
38
|
|
|
39
39
|
describe("OwnerScopeDiagnostics:", () => {
|
|
40
40
|
testSync("an owner-scoped view with no elevated groups warns", () =>
|
|
41
|
-
expect(
|
|
42
|
-
|
|
43
|
-
)
|
|
41
|
+
expect(
|
|
42
|
+
warnFor(~view="Orders", ~ownerField=Some("customerId"), ~elevated=[])->Array.length,
|
|
43
|
+
)->toBe(1)
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
// The consequence is the whole point of the line — an operator reading
|
|
@@ -74,36 +74,40 @@ describe("OwnerScopeDiagnostics:", () => {
|
|
|
74
74
|
testSync("the same view warns once, not once per registration", () => {
|
|
75
75
|
OwnerScopeDiagnostics.resetWarnings()
|
|
76
76
|
Reventless.OwnerScope.setElevatedGroups([])
|
|
77
|
-
let lines = capture(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
let lines = capture(
|
|
78
|
+
() => {
|
|
79
|
+
OwnerScopeDiagnostics.warnIfNoElevatedGroups(
|
|
80
|
+
~comp="Test",
|
|
81
|
+
~view="Orders",
|
|
82
|
+
~ownerField=Some("customerId"),
|
|
83
|
+
)
|
|
84
|
+
OwnerScopeDiagnostics.warnIfNoElevatedGroups(
|
|
85
|
+
~comp="Test",
|
|
86
|
+
~view="Orders",
|
|
87
|
+
~ownerField=Some("customerId"),
|
|
88
|
+
)
|
|
89
|
+
},
|
|
90
|
+
)
|
|
89
91
|
expect(lines->Array.length)->toBe(1)
|
|
90
92
|
})
|
|
91
93
|
|
|
92
94
|
testSync("a second view gets its own warning", () => {
|
|
93
95
|
OwnerScopeDiagnostics.resetWarnings()
|
|
94
96
|
Reventless.OwnerScope.setElevatedGroups([])
|
|
95
|
-
let lines = capture(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
let lines = capture(
|
|
98
|
+
() => {
|
|
99
|
+
OwnerScopeDiagnostics.warnIfNoElevatedGroups(
|
|
100
|
+
~comp="Test",
|
|
101
|
+
~view="Orders",
|
|
102
|
+
~ownerField=Some("customerId"),
|
|
103
|
+
)
|
|
104
|
+
OwnerScopeDiagnostics.warnIfNoElevatedGroups(
|
|
105
|
+
~comp="Test",
|
|
106
|
+
~view="Invoices",
|
|
107
|
+
~ownerField=Some("payerId"),
|
|
108
|
+
)
|
|
109
|
+
},
|
|
110
|
+
)
|
|
107
111
|
expect(lines->Array.length)->toBe(2)
|
|
108
112
|
})
|
|
109
113
|
})
|
|
@@ -30,13 +30,14 @@ let ref_ = (~plugin: option<string>=?, ~store: string) =>
|
|
|
30
30
|
|
|
31
31
|
describe("StorageRefFields.fromEventSchema", () => {
|
|
32
32
|
testSync("finds a declared single-valued ref field and qualifies its store", () => {
|
|
33
|
-
let schema =
|
|
33
|
+
let schema =
|
|
34
|
+
S.union([
|
|
35
|
+
variant(~tag="ProductImageChanged", [("imageUrl", ref_(~store="productImages"))]),
|
|
36
|
+
])->S.castToUnknown
|
|
34
37
|
expect(Fields.fromEventSchema(~plugin="Catalog", schema))->toEqual([
|
|
35
38
|
{
|
|
36
39
|
Fields.eventType: "ProductImageChanged",
|
|
37
|
-
fields: [
|
|
38
|
-
{Fields.field: "imageUrl", arity: Single, store: "Catalog.productImages"},
|
|
39
|
-
],
|
|
40
|
+
fields: [{Fields.field: "imageUrl", arity: Single, store: "Catalog.productImages"}],
|
|
40
41
|
},
|
|
41
42
|
])
|
|
42
43
|
})
|
|
@@ -47,11 +48,15 @@ describe("StorageRefFields.fromEventSchema", () => {
|
|
|
47
48
|
testSync("a qualified declaration keeps the store it names", () => {
|
|
48
49
|
let schema =
|
|
49
50
|
S.union([
|
|
50
|
-
variant(
|
|
51
|
+
variant(
|
|
52
|
+
~tag="OrderPhotoAttached",
|
|
53
|
+
[("photo", ref_(~plugin="Catalog", ~store="productImages"))],
|
|
54
|
+
),
|
|
51
55
|
])->S.castToUnknown
|
|
52
56
|
expect(
|
|
53
|
-
Fields.fromEventSchema(~plugin="Ordering", schema)
|
|
54
|
-
|
|
57
|
+
Fields.fromEventSchema(~plugin="Ordering", schema)->Array.flatMap(
|
|
58
|
+
e => e.fields->Array.map(f => f.store),
|
|
59
|
+
),
|
|
55
60
|
)->toEqual(["Catalog.productImages"])
|
|
56
61
|
})
|
|
57
62
|
|
|
@@ -61,7 +66,10 @@ describe("StorageRefFields.fromEventSchema", () => {
|
|
|
61
66
|
testSync("finds a multi-valued ref field and records its arity", () => {
|
|
62
67
|
let schema =
|
|
63
68
|
S.union([
|
|
64
|
-
variant(
|
|
69
|
+
variant(
|
|
70
|
+
~tag="GalleryReplaced",
|
|
71
|
+
[("imageUrls", S.array(ref_(~store="productImages"))->S.castToUnknown)],
|
|
72
|
+
),
|
|
65
73
|
])->S.castToUnknown
|
|
66
74
|
expect(
|
|
67
75
|
Fields.fromEventSchema(~plugin="Catalog", schema)->Array.flatMap(e => e.fields),
|
|
@@ -77,13 +85,16 @@ describe("StorageRefFields.fromEventSchema", () => {
|
|
|
77
85
|
variant(~tag="ProductRenamed", [("name", S.string->S.castToUnknown)]),
|
|
78
86
|
variant(~tag="ProductImageChanged", [("imageUrl", ref_(~store="productImages"))]),
|
|
79
87
|
])->S.castToUnknown
|
|
80
|
-
expect(
|
|
81
|
-
"
|
|
82
|
-
])
|
|
88
|
+
expect(
|
|
89
|
+
Fields.fromEventSchema(~plugin="Catalog", schema)->Array.map(e => e.eventType),
|
|
90
|
+
)->toEqual(["ProductImageChanged"])
|
|
83
91
|
})
|
|
84
92
|
|
|
85
93
|
testSync("an event schema with no declaration anywhere yields nothing", () => {
|
|
86
|
-
let schema =
|
|
94
|
+
let schema =
|
|
95
|
+
S.union([
|
|
96
|
+
variant(~tag="ProductRenamed", [("name", S.string->S.castToUnknown)]),
|
|
97
|
+
])->S.castToUnknown
|
|
87
98
|
expect(Fields.fromEventSchema(~plugin="Catalog", schema))->toEqual([])
|
|
88
99
|
})
|
|
89
100
|
|
|
@@ -93,15 +104,17 @@ describe("StorageRefFields.fromEventSchema", () => {
|
|
|
93
104
|
// store the deploy actually provisioned.
|
|
94
105
|
testSync("an unannotated string field is not treated as a ref", () => {
|
|
95
106
|
let schema =
|
|
96
|
-
S.union([
|
|
107
|
+
S.union([
|
|
108
|
+
variant(~tag="ProductImported", [("imageUrl", S.string->S.castToUnknown)]),
|
|
109
|
+
])->S.castToUnknown
|
|
97
110
|
expect(Fields.fromEventSchema(~plugin="Catalog", schema))->toEqual([])
|
|
98
111
|
})
|
|
99
112
|
|
|
100
113
|
testSync("a single-variant (non-union) event schema is read the same way", () => {
|
|
101
114
|
let schema = variant(~tag="ProductImageChanged", [("imageUrl", ref_(~store="productImages"))])
|
|
102
|
-
expect(
|
|
103
|
-
"
|
|
104
|
-
])
|
|
115
|
+
expect(
|
|
116
|
+
Fields.fromEventSchema(~plugin="Catalog", schema)->Array.map(e => e.eventType),
|
|
117
|
+
)->toEqual(["ProductImageChanged"])
|
|
105
118
|
})
|
|
106
119
|
})
|
|
107
120
|
|
|
@@ -122,8 +135,7 @@ describe("StorageRefFields.toJson", () => {
|
|
|
122
135
|
},
|
|
123
136
|
]
|
|
124
137
|
expect(Fields.toJson(entries)->JSON.stringify)->toBe(
|
|
125
|
-
`{"ProductImageChanged":[{"field":"imageUrl","arity":"one","store":"Catalog.productImages"}],` ++
|
|
126
|
-
`"GalleryReplaced":[{"field":"imageUrls","arity":"many","store":"Catalog.productImages"}]}`,
|
|
138
|
+
`{"ProductImageChanged":[{"field":"imageUrl","arity":"one","store":"Catalog.productImages"}],` ++ `"GalleryReplaced":[{"field":"imageUrls","arity":"many","store":"Catalog.productImages"}]}`,
|
|
127
139
|
)
|
|
128
140
|
})
|
|
129
141
|
})
|