@reventlessdev/reventless-core 3.0.0-alpha.259 → 3.0.0-alpha.261
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 +15 -0
- package/package.json +11 -11
- 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/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 +42 -52
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +11 -4
- 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 +40 -20
- package/src/components/Api/SchemaType.res.mjs +16 -0
- package/src/components/Api/SuryToJsonSchema.res +18 -10
- package/src/components/Api/SuryToJsonSchema.res.mjs +135 -121
- 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/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 +10 -11
- 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 +1338 -981
- package/tests/api/SuryToJsonSchemaTest.res.mjs +31 -0
- 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 +1533 -1030
- package/tests/plugin/PluginStructureTest.res.mjs +6 -0
- 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 +6 -4
- package/tests/plugin/StateView/PsAnnotatedView.res.mjs +7 -3
- 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
|
@@ -129,8 +129,7 @@ module Make = (
|
|
|
129
129
|
`OutboundTranslationSlice "${Spec.name}" declares sourceName "${sourceName}", ` ++
|
|
130
130
|
`but no EventTopic with that key exists. ` ++
|
|
131
131
|
`Available source names: [${availableNames}]. ` ++
|
|
132
|
-
`A source is an Aggregate Spec.name or a DCB source name ` ++
|
|
133
|
-
`(typically "<pluginName>DcbEventLog"); \`[]\` means this plugin's own DCB log.`,
|
|
132
|
+
`A source is an Aggregate Spec.name or a DCB source name ` ++ `(typically "<pluginName>DcbEventLog"); \`[]\` means this plugin's own DCB log.`,
|
|
134
133
|
)
|
|
135
134
|
}
|
|
136
135
|
)
|
|
@@ -200,18 +199,20 @@ module Make = (
|
|
|
200
199
|
let _ =
|
|
201
200
|
Callback.phase2(publishJsonsFn, ~capabilities=Capabilities.capabilities())
|
|
202
201
|
->Promise.then(() => syncToQueryDb(queryDbOps))
|
|
203
|
-
->Promise.catch(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
202
|
+
->Promise.catch(
|
|
203
|
+
exn => {
|
|
204
|
+
let errMsg =
|
|
205
|
+
exn
|
|
206
|
+
->JsExn.fromException
|
|
207
|
+
->Option.flatMap(JsExn.message)
|
|
208
|
+
->Option.getOr("unknown")
|
|
209
|
+
EffectLogger.logError(
|
|
210
|
+
~comp=`OutboundTranslationSlice(${Spec.name})`,
|
|
211
|
+
`detached phase 2 error: ${errMsg}`,
|
|
212
|
+
)->Effect.runSync
|
|
213
|
+
Promise.resolve()
|
|
214
|
+
},
|
|
215
|
+
)
|
|
215
216
|
},
|
|
216
217
|
)
|
|
217
218
|
)
|
|
@@ -25,9 +25,7 @@ module type Storage = {
|
|
|
25
25
|
let make: storageMaker<api, role>
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
type queryEngineMaker = dict<QueryDb.outputs> => Pulumi.Output.t<
|
|
29
|
-
Reventless.QueryEngine.operations,
|
|
30
|
-
>
|
|
28
|
+
type queryEngineMaker = dict<QueryDb.outputs> => Pulumi.Output.t<Reventless.QueryEngine.operations>
|
|
31
29
|
|
|
32
30
|
module type QueryEngineAdapter = {
|
|
33
31
|
let make: queryEngineMaker
|
|
@@ -19,8 +19,7 @@ module Make = (ReadModelSpec: Reventless.ReadModel.Spec, Ops: Ops) => {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
let loadStream = id =>
|
|
22
|
-
Ops.jsonOps.loadStream(id->ReadModelSpec.Id.toString)
|
|
23
|
-
->Stream.mapEffect(json =>
|
|
22
|
+
Ops.jsonOps.loadStream(id->ReadModelSpec.Id.toString)->Stream.mapEffect(json =>
|
|
24
23
|
switch decode(id, json) {
|
|
25
24
|
| Ok(s) => Effect.succeed(s)
|
|
26
25
|
| Error(e) => Effect.fail(e)
|
|
@@ -32,9 +31,7 @@ module Make = (ReadModelSpec: Reventless.ReadModel.Spec, Ops: Ops) => {
|
|
|
32
31
|
| result =>
|
|
33
32
|
result->Result.flatMap(states =>
|
|
34
33
|
states->Array.reduce(Ok([]), (acc, state) =>
|
|
35
|
-
acc->Result.flatMap(arr =>
|
|
36
|
-
decode(id, state)->Result.map(s => arr->Array.concat([s]))
|
|
37
|
-
)
|
|
34
|
+
acc->Result.flatMap(arr => decode(id, state)->Result.map(s => arr->Array.concat([s])))
|
|
38
35
|
)
|
|
39
36
|
)
|
|
40
37
|
}
|
|
@@ -51,7 +48,7 @@ module Make = (ReadModelSpec: Reventless.ReadModel.Spec, Ops: Ops) => {
|
|
|
51
48
|
dict->Dict.get(field)->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
52
49
|
ReadModelSpec.config.indexes->Array.forEach(idx => {
|
|
53
50
|
switch idx.pkFields {
|
|
54
|
-
| Some(fs)
|
|
51
|
+
| Some(fs) if fs->Array.length > 1 =>
|
|
55
52
|
let sep = idx.pkSep->Option.getOr("/")
|
|
56
53
|
let value = fs->Array.map(getStr)->Array.join(sep)
|
|
57
54
|
let attrName = idx.idField->Option.getOr("_pk")
|
|
@@ -59,7 +56,7 @@ module Make = (ReadModelSpec: Reventless.ReadModel.Spec, Ops: Ops) => {
|
|
|
59
56
|
| _ => ()
|
|
60
57
|
}
|
|
61
58
|
switch idx.skFields {
|
|
62
|
-
| Some(fs)
|
|
59
|
+
| Some(fs) if fs->Array.length > 1 =>
|
|
63
60
|
let sep = idx.skSep->Option.getOr("/")
|
|
64
61
|
let value = fs->Array.map(getStr)->Array.join(sep)
|
|
65
62
|
let attrName = idx.subIdField->Option.getOr("_sk")
|
|
@@ -76,8 +73,9 @@ module Make = (ReadModelSpec: Reventless.ReadModel.Spec, Ops: Ops) => {
|
|
|
76
73
|
// and left exactly as it was.
|
|
77
74
|
// The union fields this view declares, read once. Empty for most views, and
|
|
78
75
|
// the whole diagnostic below costs nothing when it is.
|
|
79
|
-
let declaredUnionFields =
|
|
80
|
-
|
|
76
|
+
let declaredUnionFields = Reventless.TaggedUnion.fieldsOf(
|
|
77
|
+
ReadModelSpec.stateSchema->S.castToUnknown,
|
|
78
|
+
)
|
|
81
79
|
|
|
82
80
|
// Logged once per instantiation, and unconditionally: "none" has to be
|
|
83
81
|
// distinguishable from "never instantiated", because a schema that resolves no
|
|
@@ -104,7 +102,9 @@ module Make = (ReadModelSpec: Reventless.ReadModel.Spec, Ops: Ops) => {
|
|
|
104
102
|
log.warn(
|
|
105
103
|
~comp="QueryDb",
|
|
106
104
|
`${ReadModelSpec.name}.${field}: stored a union value with no ${Reventless.TaggedUnion.typenameKey} ` ++
|
|
107
|
-
`(union ${unionName->Option.getOr(
|
|
105
|
+
`(union ${unionName->Option.getOr(
|
|
106
|
+
"<unnamed>",
|
|
107
|
+
)}). The field will resolve to null and take its ` ++
|
|
108
108
|
`parent with it. Union fields seen on this spec: ${declaredUnionFields
|
|
109
109
|
->Array.map(((f, _)) => f)
|
|
110
110
|
->Array.join(", ")}`,
|
|
@@ -30,9 +30,9 @@ module type T = {
|
|
|
30
30
|
let finish: unit => unit
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
let toResolvedOutputs = (
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
let toResolvedOutputs = (outputs: outputs): Pulumi.Output.t<
|
|
34
|
+
ReventlessInterop.ReadModel.resolvedOutputs,
|
|
35
|
+
> =>
|
|
36
36
|
outputs.queryDb.resources
|
|
37
37
|
->Adapter.resourcesToInterop
|
|
38
38
|
->Pulumi.Output.apply(queryDbResources => {
|
|
@@ -104,8 +104,7 @@ module Make = (
|
|
|
104
104
|
`ReadModel "${Spec.name}" has a Mapping with sourceName "${sourceName}", ` ++
|
|
105
105
|
`but no EventTopic with that key exists in allEventTopics. ` ++
|
|
106
106
|
`Available source names: [${availableNames}]. ` ++
|
|
107
|
-
`Check Mapping.Make's first arg matches an Aggregate Spec.name or a DCB ` ++
|
|
108
|
-
`source name (typically "<pluginName>DcbEventLog").`,
|
|
107
|
+
`Check Mapping.Make's first arg matches an Aggregate Spec.name or a DCB ` ++ `source name (typically "<pluginName>DcbEventLog").`,
|
|
109
108
|
)
|
|
110
109
|
}
|
|
111
110
|
)
|
|
@@ -42,7 +42,8 @@ module Make = (
|
|
|
42
42
|
acc->Effect.flatMap(
|
|
43
43
|
_ => {
|
|
44
44
|
Effect.promise(
|
|
45
|
-
() =>
|
|
45
|
+
() =>
|
|
46
|
+
Projection.handleAction(~comp, action, Spec.operations, ReadModelSpec.subIdConfig),
|
|
46
47
|
)->Effect.map(_ => ())
|
|
47
48
|
},
|
|
48
49
|
)
|
|
@@ -49,7 +49,10 @@ module Make = (Spec: Spec): T => {
|
|
|
49
49
|
| exception err =>
|
|
50
50
|
let errMsg =
|
|
51
51
|
err->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown")
|
|
52
|
-
EffectLogger.logError(
|
|
52
|
+
EffectLogger.logError(
|
|
53
|
+
~comp="SideEffects",
|
|
54
|
+
`map: Couldn't decode meta: ${errMsg}`,
|
|
55
|
+
)->Effect.runSync
|
|
53
56
|
None
|
|
54
57
|
| _ =>
|
|
55
58
|
EffectLogger.logError(~comp="SideEffects", "map: Invalid JSON object")->Effect.runSync
|
|
@@ -107,7 +110,7 @@ module Make = (Spec: Spec): T => {
|
|
|
107
110
|
)
|
|
108
111
|
}
|
|
109
112
|
)
|
|
110
|
-
->Effect.catchAll(errMsg => EffectLogger.logError(~comp="SideEffectHandler", errMsg))
|
|
113
|
+
->Effect.catchAll(errMsg => EffectLogger.logError(~comp="SideEffectHandler", errMsg)),
|
|
111
114
|
)
|
|
112
115
|
| None => Effect.succeed()
|
|
113
116
|
}
|
|
@@ -5,9 +5,9 @@ type outputs = ReventlessInfra.StateChangeSlice.outputs
|
|
|
5
5
|
type operations = ReventlessInfra.StateChangeSlice.operations
|
|
6
6
|
type component = Component.t<t, outputs, operations>
|
|
7
7
|
|
|
8
|
-
let toResolvedOutputs = (
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
let toResolvedOutputs = (outputs: outputs): Pulumi.Output.t<
|
|
9
|
+
ReventlessInterop.StateChangeSlice.resolvedOutputs,
|
|
10
|
+
> =>
|
|
11
11
|
outputs.resources
|
|
12
12
|
->Adapter.resourcesToInterop
|
|
13
13
|
->Pulumi.Output.apply(resources => {
|
|
@@ -10,7 +10,11 @@ module Make = (
|
|
|
10
10
|
type component = StateChangeSlice.component
|
|
11
11
|
module Callback = StateChangeSlice_Callback.Make(Spec, Behavior)
|
|
12
12
|
|
|
13
|
-
let makeJsonHandler = (
|
|
13
|
+
let makeJsonHandler = (
|
|
14
|
+
~tagKeysByEventType,
|
|
15
|
+
~crossPartitionTagKeys,
|
|
16
|
+
dcbEventLogOps: DcbEventLog.operations,
|
|
17
|
+
) => {
|
|
14
18
|
let handler: CommandTopic.jsonCommandsHandler = stream => {
|
|
15
19
|
let decodedStream =
|
|
16
20
|
stream
|
|
@@ -35,7 +39,12 @@ module Make = (
|
|
|
35
39
|
| None => Stream.empty
|
|
36
40
|
}
|
|
37
41
|
)
|
|
38
|
-
Callback.handleCommands(
|
|
42
|
+
Callback.handleCommands(
|
|
43
|
+
~tagKeysByEventType,
|
|
44
|
+
~crossPartitionTagKeys,
|
|
45
|
+
dcbEventLogOps,
|
|
46
|
+
decodedStream,
|
|
47
|
+
)
|
|
39
48
|
}
|
|
40
49
|
handler
|
|
41
50
|
}
|
|
@@ -55,7 +64,11 @@ module Make = (
|
|
|
55
64
|
dcbEventLog
|
|
56
65
|
->Component.operations
|
|
57
66
|
->Pulumi.Output.apply(dcbEventLogOps => {
|
|
58
|
-
let jsonHandler = makeJsonHandler(
|
|
67
|
+
let jsonHandler = makeJsonHandler(
|
|
68
|
+
~tagKeysByEventType,
|
|
69
|
+
~crossPartitionTagKeys,
|
|
70
|
+
dcbEventLogOps,
|
|
71
|
+
)
|
|
59
72
|
CommandTopic.registerHandler(
|
|
60
73
|
~schema=commandSchema,
|
|
61
74
|
~handler=jsonHandler,
|
|
@@ -91,7 +104,13 @@ module Make = (
|
|
|
91
104
|
Component.make(
|
|
92
105
|
~componentType=StateChangeSlice.componentType->ComponentType.toString,
|
|
93
106
|
~name=Spec.name,
|
|
94
|
-
~construct=construct(
|
|
107
|
+
~construct=construct(
|
|
108
|
+
~dcbEventLog,
|
|
109
|
+
~publishJsons,
|
|
110
|
+
~tagKeysByEventType,
|
|
111
|
+
~crossPartitionTagKeys,
|
|
112
|
+
...
|
|
113
|
+
),
|
|
95
114
|
~opts,
|
|
96
115
|
)
|
|
97
116
|
}
|
|
@@ -307,7 +307,9 @@ module Make = (
|
|
|
307
307
|
}
|
|
308
308
|
EffectLogger.logInfo(
|
|
309
309
|
~comp,
|
|
310
|
-
`read${cacheHit ? " (cached, delta)" : ""}: ${reads
|
|
310
|
+
`read${cacheHit ? " (cached, delta)" : ""}: ${reads
|
|
311
|
+
->Array.length
|
|
312
|
+
->Int.toString} event(s)${reads->Array.length == 0
|
|
311
313
|
? ""
|
|
312
314
|
: ` [${reads->Array.join(", ")}]`}`,
|
|
313
315
|
)
|
|
@@ -337,8 +339,8 @@ module Make = (
|
|
|
337
339
|
EffectLogger.logInfo(~comp, "no events produced")->Effect.map(_ => Ok("ok"))
|
|
338
340
|
| Ok(newEvents) =>
|
|
339
341
|
let rawEvents =
|
|
340
|
-
newEvents->Array.map(
|
|
341
|
-
encodeEvent(~parentMeta=command'.meta, ~tagKeysByEventType, e)
|
|
342
|
+
newEvents->Array.map(
|
|
343
|
+
e => encodeEvent(~parentMeta=command'.meta, ~tagKeysByEventType, e),
|
|
342
344
|
)
|
|
343
345
|
let eventCount = rawEvents->Array.length->Int.toString
|
|
344
346
|
let eventDetails =
|
|
@@ -393,7 +395,10 @@ module Make = (
|
|
|
393
395
|
// Typed classification (was substring-matching the error string,
|
|
394
396
|
// which disagreed with the backends' casing).
|
|
395
397
|
let (errorCode, errDetail) = switch err {
|
|
396
|
-
| ReventlessInfra.DcbEventLog.Conflict => (
|
|
398
|
+
| ReventlessInfra.DcbEventLog.Conflict => (
|
|
399
|
+
"Conflict",
|
|
400
|
+
"conflict: condition check failed",
|
|
401
|
+
)
|
|
397
402
|
| StorageFailure(msg) => ("AppendFailed", msg)
|
|
398
403
|
}
|
|
399
404
|
if retries > 0 {
|
|
@@ -413,6 +418,7 @@ module Make = (
|
|
|
413
418
|
// Retries exhausted — drop any cached snapshot so the next
|
|
414
419
|
// command for this entity takes the cold full-read path.
|
|
415
420
|
cacheInvalidate()
|
|
421
|
+
|
|
416
422
|
// A surfaced Conflict means the 3-retry loop could not absorb
|
|
417
423
|
// the contention — the operator signal for "this slice is hot"
|
|
418
424
|
// (consider sharding / async — Issue 10, §4 of the analysis).
|
|
@@ -465,10 +471,20 @@ module Make = (
|
|
|
465
471
|
|
|
466
472
|
// CommandTopic handler — processes each command sequentially through handleSingleCommand,
|
|
467
473
|
// returning Ok(reference) or Error(reference) per command.
|
|
468
|
-
let handleCommands = (
|
|
474
|
+
let handleCommands = (
|
|
475
|
+
~tagKeysByEventType=Dict.make(),
|
|
476
|
+
~crossPartitionTagKeys=[],
|
|
477
|
+
dcbEventLog,
|
|
478
|
+
stream,
|
|
479
|
+
) =>
|
|
469
480
|
stream
|
|
470
481
|
->Stream.mapEffect(({ReventlessInfra.CommandTopic.reference: reference, command}) =>
|
|
471
|
-
handleSingleCommand(
|
|
482
|
+
handleSingleCommand(
|
|
483
|
+
~tagKeysByEventType,
|
|
484
|
+
~crossPartitionTagKeys,
|
|
485
|
+
dcbEventLog,
|
|
486
|
+
command,
|
|
487
|
+
)->Effect.map(result =>
|
|
472
488
|
switch result {
|
|
473
489
|
| Ok(_) => Ok(reference)
|
|
474
490
|
| Error(_) => Error(reference)
|
|
@@ -5,9 +5,9 @@ type outputs = ReventlessInfra.StateViewSlice.outputs
|
|
|
5
5
|
type operations = ReventlessInfra.StateViewSlice.operations
|
|
6
6
|
type component = Component.t<t, outputs, operations>
|
|
7
7
|
|
|
8
|
-
let toResolvedOutputs = (
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
let toResolvedOutputs = (outputs: outputs): Pulumi.Output.t<
|
|
9
|
+
ReventlessInterop.StateViewSlice.resolvedOutputs,
|
|
10
|
+
> =>
|
|
11
11
|
(
|
|
12
12
|
outputs.resources->Adapter.resourcesToInterop,
|
|
13
13
|
outputs.queryDb.resources->Adapter.resourcesToInterop,
|
|
@@ -15,7 +15,7 @@ let toResolvedOutputs = (
|
|
|
15
15
|
->Pulumi.Output.all2
|
|
16
16
|
->Pulumi.Output.apply(((resources, queryDbResources)) => {
|
|
17
17
|
let resolved: ReventlessInterop.StateViewSlice.resolvedOutputs = {
|
|
18
|
-
resources
|
|
18
|
+
resources,
|
|
19
19
|
queryDb: {resources: queryDbResources},
|
|
20
20
|
}
|
|
21
21
|
resolved
|
|
@@ -38,9 +38,15 @@ module Make = (
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
module SpecificQueryDb = QueryDb_Builder.Make(SvQueryDbSpec, QueryDbStorage, QueryDbResolvers)
|
|
41
|
-
module SpecificEventCollector = EventCollector_Builder.Make(
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
module SpecificEventCollector = EventCollector_Builder.Make(
|
|
42
|
+
RuntimeEnvironment,
|
|
43
|
+
EventCollectorChannel,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
let toProjectionOps = (ops: SpecificQueryDb.operations): QueryDb.operations<
|
|
47
|
+
string,
|
|
48
|
+
Spec.state,
|
|
49
|
+
> => {
|
|
44
50
|
load: id => ops.load(id->Reventless.Id.String.makeFromString),
|
|
45
51
|
loadStream: id => ops.loadStream(id->Reventless.Id.String.makeFromString),
|
|
46
52
|
save: (id, s, sm, ttl) => ops.save(id->Reventless.Id.String.makeFromString, s, sm, ttl),
|
|
@@ -94,32 +100,31 @@ module Make = (
|
|
|
94
100
|
->Effect.flatMap(events => {
|
|
95
101
|
let total = events->Array.length->Int.toString
|
|
96
102
|
events
|
|
97
|
-
->Array.mapWithIndex(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
->Array.mapWithIndex(
|
|
104
|
+
(json, i) => {
|
|
105
|
+
let envelopeDict = json->JSON.Decode.object->Option.getOr(Dict.make())
|
|
106
|
+
let rawEvent = envelopeDict->Dict.get("event")->Option.getOr(json)
|
|
107
|
+
// Events arrive as `{id, meta, recordedAt, event}` envelopes
|
|
108
|
+
// (Message.composeEventJson' / ProjectionCheckpoint catch-up).
|
|
109
|
+
// Surface `meta` + `recordedAt` to the projection as `consumed`;
|
|
110
|
+
// fall back defensively so a malformed envelope never drops the event.
|
|
111
|
+
let meta = switch envelopeDict->Dict.get("meta") {
|
|
112
|
+
| Some(m) =>
|
|
113
|
+
switch m->Reventless.Util_Sury.fromJson(Message.metaSchema) {
|
|
114
|
+
| parsed => parsed
|
|
115
|
+
| exception _ => Message.generateMeta(~service=Spec.name)
|
|
116
|
+
}
|
|
117
|
+
| None => Message.generateMeta(~service=Spec.name)
|
|
109
118
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
->
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
| Some(event) =>
|
|
120
|
-
let actions =
|
|
121
|
-
try Projection.project({event, meta, recordedAt})
|
|
122
|
-
catch {
|
|
119
|
+
let recordedAt =
|
|
120
|
+
envelopeDict
|
|
121
|
+
->Dict.get("recordedAt")
|
|
122
|
+
->Option.flatMap(JSON.Decode.string)
|
|
123
|
+
->Option.getOr("")
|
|
124
|
+
let (eventType, dataDict) = rawEvent->Message.splitMessage
|
|
125
|
+
switch decoder.decode(~eventType, ~data=dataDict) {
|
|
126
|
+
| Some(event) =>
|
|
127
|
+
let actions = try Projection.project({event, meta, recordedAt}) catch {
|
|
123
128
|
| exn =>
|
|
124
129
|
let errMsg =
|
|
125
130
|
exn
|
|
@@ -132,46 +137,56 @@ module Make = (
|
|
|
132
137
|
)->Effect.runSync
|
|
133
138
|
[]
|
|
134
139
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
let idxStr = (i + 1)->Int.toString
|
|
141
|
+
let actionsStr = LogFormat.actionNames(actions)
|
|
142
|
+
let eventData = dataDict->JSON.Encode.object
|
|
143
|
+
let fieldsStr = {
|
|
144
|
+
let f =
|
|
145
|
+
dataDict
|
|
146
|
+
->Dict.toArray
|
|
147
|
+
->Array.map(((k, v)) => `${k}:${v->JSON.stringify}`)
|
|
148
|
+
->Array.join(",")
|
|
149
|
+
f == "" ? "" : `({${f}})`
|
|
150
|
+
}
|
|
151
|
+
EffectLogger.logInfo(
|
|
152
|
+
~comp,
|
|
153
|
+
~detail=eventData,
|
|
154
|
+
`handling event ${idxStr}/${total}: ${LogFormat.bold(
|
|
155
|
+
eventType,
|
|
156
|
+
)}${fieldsStr} ${actionsStr}`,
|
|
157
|
+
)->Effect.runSync
|
|
158
|
+
// A slice's state carries the same synthetic `displayName` the
|
|
159
|
+
// ppx injects for a read model's, and the same lifecycle trail.
|
|
160
|
+
// Rewritten here rather than at the apply below because the
|
|
161
|
+
// trail entry is stamped with this envelope's own time.
|
|
162
|
+
actions->Array.map(
|
|
163
|
+
FrameworkProjection.rewriteAction(_, ~at=meta.time, Spec.stateSchema),
|
|
164
|
+
)
|
|
165
|
+
| None => []
|
|
145
166
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
~detail=eventData,
|
|
149
|
-
`handling event ${idxStr}/${total}: ${LogFormat.bold(eventType)}${fieldsStr} ${actionsStr}`,
|
|
150
|
-
)->Effect.runSync
|
|
151
|
-
// A slice's state carries the same synthetic `displayName` the
|
|
152
|
-
// ppx injects for a read model's, and the same lifecycle trail.
|
|
153
|
-
// Rewritten here rather than at the apply below because the
|
|
154
|
-
// trail entry is stamped with this envelope's own time.
|
|
155
|
-
actions->Array.map(
|
|
156
|
-
FrameworkProjection.rewriteAction(_, ~at=meta.time, Spec.stateSchema),
|
|
157
|
-
)
|
|
158
|
-
| None => []
|
|
159
|
-
}
|
|
160
|
-
})
|
|
167
|
+
},
|
|
168
|
+
)
|
|
161
169
|
->Array.flat
|
|
162
|
-
->Array.reduce(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
->Array.reduce(
|
|
171
|
+
Effect.succeed(),
|
|
172
|
+
(acc, action) =>
|
|
173
|
+
acc->Effect.flatMap(
|
|
174
|
+
_ => {
|
|
175
|
+
Effect.promise(
|
|
176
|
+
() =>
|
|
177
|
+
FrameworkProjection.handleAction(
|
|
178
|
+
~comp,
|
|
179
|
+
action,
|
|
180
|
+
projectionOps,
|
|
181
|
+
Spec.subIdConfig,
|
|
182
|
+
),
|
|
183
|
+
)->Effect.map(_ => ())
|
|
184
|
+
},
|
|
185
|
+
),
|
|
168
186
|
)
|
|
169
187
|
})
|
|
170
188
|
|
|
171
|
-
let handler = SpecificEventCollector.makeHandler(
|
|
172
|
-
~eventCollector=ec,
|
|
173
|
-
~jsonEventsHandler,
|
|
174
|
-
)
|
|
189
|
+
let handler = SpecificEventCollector.makeHandler(~eventCollector=ec, ~jsonEventsHandler)
|
|
175
190
|
let resources = (queryDb->Component.outputs).resources
|
|
176
191
|
ec->EventCollectorRuntimeBuilder.forEventCollector(
|
|
177
192
|
~handler,
|
|
@@ -199,11 +214,7 @@ module Make = (
|
|
|
199
214
|
self->Component.setOutputs(outputs)
|
|
200
215
|
}
|
|
201
216
|
|
|
202
|
-
let make = (
|
|
203
|
-
~dcbEventLog,
|
|
204
|
-
~runtime=?,
|
|
205
|
-
~opts=?,
|
|
206
|
-
): StateViewSlice.component =>
|
|
217
|
+
let make = (~dcbEventLog, ~runtime=?, ~opts=?): StateViewSlice.component =>
|
|
207
218
|
Component.make(
|
|
208
219
|
~componentType=StateViewSlice.componentType->ComponentType.toString,
|
|
209
220
|
~name=Spec.name,
|
|
@@ -45,7 +45,9 @@ module Make = (
|
|
|
45
45
|
EffectLogger.logInfo(
|
|
46
46
|
~comp,
|
|
47
47
|
~detail=raw.data,
|
|
48
|
-
`handling event ${idx.contents->Int.toString}/${count}: ${LogFormat.bold(
|
|
48
|
+
`handling event ${idx.contents->Int.toString}/${count}: ${LogFormat.bold(
|
|
49
|
+
raw.eventType,
|
|
50
|
+
)}(${id}) ${actionsStr}`,
|
|
49
51
|
)->Effect.runSync
|
|
50
52
|
allActions->Array.pushMany(
|
|
51
53
|
actions->Array.map(
|
|
@@ -34,9 +34,9 @@ type maker = (
|
|
|
34
34
|
~opts: option<Pulumi.ComponentResource.options>,
|
|
35
35
|
) => component
|
|
36
36
|
|
|
37
|
-
let toResolvedOutputs = (
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
let toResolvedOutputs = (outputs: outputs): Pulumi.Output.t<
|
|
38
|
+
ReventlessInterop.Task.resolvedOutputs,
|
|
39
|
+
> =>
|
|
40
40
|
switch (outputs.bucketNames, outputs.sideEffectSources) {
|
|
41
41
|
| (Some(bucketNames), Some(sideEffectSources)) =>
|
|
42
42
|
bucketNames
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
type bucket<'bucketParts> = {
|
|
1
|
+
type bucket<'bucketParts> = {
|
|
2
|
+
resources: array<ReventlessInfra.Adapter.resource>,
|
|
3
|
+
parts: 'bucketParts,
|
|
4
|
+
}
|
|
2
5
|
|
|
3
6
|
type connect<'bucketParts, 'runtimeParts> = (
|
|
4
7
|
~name: string,
|
|
@@ -17,8 +17,8 @@ let pascalCase = s =>
|
|
|
17
17
|
// Split words out of a PascalCase/camelCase run so kebab-casing keeps the word
|
|
18
18
|
// boundaries. Two passes: `aB` splits an ordinary hump, `ABc` splits the tail of
|
|
19
19
|
// an acronym off the word that follows it ("HTTPServer" -> "HTTP-Server").
|
|
20
|
-
let humpBoundary =
|
|
21
|
-
let acronymBoundary =
|
|
20
|
+
let humpBoundary = /([a-z0-9])([A-Z])/g
|
|
21
|
+
let acronymBoundary = /([A-Z]+)([A-Z][a-z])/g
|
|
22
22
|
|
|
23
23
|
// Kebab-case an identifier for use in an S3 bucket name: "ImportProducts" and
|
|
24
24
|
// "product-imports" both reduce to "import-products".
|
|
@@ -119,7 +119,10 @@ module Make = (
|
|
|
119
119
|
// Task bucket callbacks default to the platform-supplied task-pod floor (a
|
|
120
120
|
// large envelope for bulk import/export work); a plugin.json `runtime`
|
|
121
121
|
// override raises memory above it and replaces timeout.
|
|
122
|
-
let memorySize = ReventlessInfra.RuntimeHints.resolveMemory(
|
|
122
|
+
let memorySize = ReventlessInfra.RuntimeHints.resolveMemory(
|
|
123
|
+
runtime,
|
|
124
|
+
~default=Defaults.memorySize,
|
|
125
|
+
)
|
|
123
126
|
let timeout = ReventlessInfra.RuntimeHints.resolveTimeout(runtime, ~default=Defaults.timeout)
|
|
124
127
|
let allCommandTopics = allAggregates->Aggregate.allCommandTopics
|
|
125
128
|
|
|
@@ -128,15 +131,14 @@ module Make = (
|
|
|
128
131
|
// first command-topic resource's `id` — bundled adapters interpret it as
|
|
129
132
|
// their channel address; adapters that don't need it (e.g. in-memory)
|
|
130
133
|
// ignore the dict in their TaskRuntime_Builder.
|
|
131
|
-
let publishToAggregatesQueueUrls =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
)
|
|
134
|
+
let publishToAggregatesQueueUrls = allAggregates->Dict.mapValues(agg =>
|
|
135
|
+
agg.commandTopic->Pulumi.Output.flatMap(ct =>
|
|
136
|
+
switch ct.resources->Array.get(0) {
|
|
137
|
+
| Some(r) => r.id
|
|
138
|
+
| None => Pulumi.Output.make("")
|
|
139
|
+
}
|
|
139
140
|
)
|
|
141
|
+
)
|
|
140
142
|
|
|
141
143
|
let publishCommands: Task.publishCommands = (aggregateName, cmdJsons) => {
|
|
142
144
|
(publishToAggregates->Dict.get(aggregateName)->Option.getOrThrow)(cmdJsons)
|
|
@@ -218,17 +220,18 @@ module Make = (
|
|
|
218
220
|
// scheduler's invoker URN. Bundled adapters thread these into the
|
|
219
221
|
// deployed handler so it can talk to the underlying scheduler service;
|
|
220
222
|
// adapters without a real scheduler ignore the dict.
|
|
221
|
-
let schedulerConfig: option<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
223
|
+
let schedulerConfig: option<
|
|
224
|
+
TaskRuntime_Builder.schedulerConfig,
|
|
225
|
+
> = sideEffectHandler->Option.flatMap(seh => {
|
|
226
|
+
let sehOutputs = seh->Component.outputs
|
|
227
|
+
sehOutputs.eventCollector.resources
|
|
228
|
+
->Array.get(0)
|
|
229
|
+
->Option.map(r => {
|
|
230
|
+
TaskRuntime_Builder.schedulerRoleUrn,
|
|
231
|
+
targetUrn: r.urn,
|
|
232
|
+
targetName: r.name,
|
|
231
233
|
})
|
|
234
|
+
})
|
|
232
235
|
|
|
233
236
|
let bucketNames = config.buckets->Option.map(buckets =>
|
|
234
237
|
buckets
|