@reventlessdev/reventless-local 3.0.0-alpha.249 → 3.0.0-alpha.250
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 +8 -0
- package/package.json +13 -13
- package/src/BakedManifest.res +5 -6
- package/src/EmitCapabilities.res +3 -1
- package/src/LocalPlatformRegistry.res +3 -1
- package/src/LocalPlatformStart.res +7 -6
- package/src/LocalSeedTarget.res +3 -1
- package/src/Platform.res +291 -215
- package/src/ShellConfig.res +10 -17
- package/src/UiHints.res +8 -14
- package/src/UiSlots.res +10 -17
- package/src/adapter/Api/LocalEvents_Server.res +5 -3
- package/src/adapter/Api/LocalGraphQL_Adapter.res +4 -4
- package/src/adapter/Auth/LocalAuth.res +11 -15
- package/src/adapter/Auth/UserStore.res +5 -7
- package/src/adapter/Backend.res +1 -2
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
- package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
- package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
- package/src/adapter/Counter/LocalCounterHandler.res +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
- package/src/adapter/DomainGraphQL_Server.res +35 -34
- package/src/adapter/DomainMCP_Server.res +45 -41
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
- package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
- package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
- package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
- package/src/adapter/LocalBus.res +12 -12
- package/src/adapter/LocalCapabilities.res.mjs +1 -1
- package/src/adapter/LocalGeocodeResolvers.res +1 -4
- package/src/adapter/LocalSideEffectHandler.res +1 -1
- package/src/adapter/LocalStateChangeDescriptor.res +2 -1
- package/src/adapter/MCP_ServerInstance.res +51 -39
- package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
- package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
- package/src/adapter/PlatformGraphQL_Server.res +9 -5
- package/src/adapter/ProjectionCheckpoint.res +8 -1
- package/src/adapter/ProjectionPending.res +1 -3
- package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
- package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
- package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
- package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
- package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
- package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
- package/src/adapter/SqliteDriver.res +16 -4
- package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
- package/src/components/Aggregate_Builder.res +6 -1
- package/src/components/Counter_Builder.res +1 -5
- package/src/components/DcbEventLog_Builder.res +5 -3
- package/src/components/ExtensionPoint_Builder.res +13 -15
- package/src/components/InboundTranslationSlice_Builder.res +3 -2
- package/src/components/OutboundTranslationSlice_Builder.res +3 -2
- package/src/components/ReadModel_Builder.res +18 -20
- package/src/components/StateViewSlice_Builder.res +3 -2
- package/src/components/Task_Builder.res +12 -13
- package/src/reset/LocalSeedReset.res +2 -5
- package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
- package/src/test/Mocks/MockEventLogStorage.res +8 -3
- package/src/test/Mocks/MockQueryDbStorage.res +3 -2
- package/src/test/TestRunner.res +1 -1
- package/tests/BakedManifestFilesTest.res +7 -10
- package/tests/LocalEventTapTest.res +26 -21
- package/tests/LocalPlatformStartTest.res +33 -28
- package/tests/PluginEventDecodeTest.res +3 -3
- package/tests/ShellConfigTest.res +30 -21
- package/tests/SplitApiFixtures.res +34 -10
- package/tests/SplitApiTest.res +92 -62
- package/tests/adapter/BackendParityTest.res +180 -130
- package/tests/adapter/CollectNEventsTest.res +6 -4
- package/tests/adapter/CommandAuthorizationTest.res +47 -41
- package/tests/adapter/CommandTopicChannelTest.res +163 -139
- package/tests/adapter/CounterHandlerTest.res +95 -77
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
- package/tests/adapter/DcbEventLogStorageTest.res +264 -207
- package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
- package/tests/adapter/EventCollectorChannelTest.res +153 -124
- package/tests/adapter/EventHistoryResolverTest.res +46 -18
- package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
- package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
- package/tests/adapter/EventTapTest.res +37 -20
- package/tests/adapter/EventTopicPublisherTest.res +74 -50
- package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
- package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
- package/tests/adapter/HeartbeatRunnerTest.res +99 -81
- package/tests/adapter/InboundTranslationMutationTest.res +4 -1
- package/tests/adapter/LocalAuthHttpTest.res +79 -85
- package/tests/adapter/LocalAuthLoginTest.res +42 -50
- package/tests/adapter/LocalAuthTest.res +8 -16
- package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
- package/tests/adapter/LocalBusBoundedTest.res +187 -117
- package/tests/adapter/LocalBusPubSubTest.res +118 -76
- package/tests/adapter/LocalDispatchContextTest.res +1 -2
- package/tests/adapter/LocalEvents_ServerTest.res +242 -187
- package/tests/adapter/PgProjectionCatchupTest.res +44 -10
- package/tests/adapter/ProjectionCheckpointTest.res +261 -206
- package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
- package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
- package/tests/adapter/QueryDbListResolverTest.res +225 -208
- package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
- package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
- package/tests/adapter/QueryDbStorageTest.res +163 -117
- package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
- package/tests/adapter/QueryEngineTest.res +179 -126
- package/tests/adapter/ScheduledPublisherTest.res +171 -135
- package/tests/adapter/ServedBucketHttpTest.res +65 -59
- package/tests/adapter/SortKey_FilterTest.res +19 -3
- package/tests/adapter/SqliteDriverTest.res +13 -10
- package/tests/adapter/TaskBucketTest.res +94 -80
- package/tests/components/aggregate/AggregateTest.res +31 -18
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
- package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
- package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
- package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
- package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
- package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
- package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
- package/tests/components/commandtopic/CommandTopicTest.res +24 -15
- package/tests/components/counter/CounterFixtures.res +6 -8
- package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
- package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
- package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
- package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
- package/tests/components/dcb/DcbFixtures.res +4 -1
- package/tests/components/dcb/DcbTest.res +9 -6
- package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
- package/tests/components/eventcollector/EventCollectorTest.res +6 -3
- package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
- package/tests/components/eventlog/EventLogConflictTest.res +8 -4
- package/tests/components/eventlog/EventLogFixtures.res +3 -3
- package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
- package/tests/components/eventlog/EventLogStreamTest.res +119 -56
- package/tests/components/eventlog/EventLogTest.res +20 -4
- package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
- package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
- package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
- package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
- package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
- package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
- package/tests/components/heartbeat/HeartbeatTest.res +56 -47
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
- package/tests/components/querydb/QueryDbFixtures.res +1 -5
- package/tests/components/querydb/QueryDbTest.res +2 -4
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
- package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
- package/tests/components/readmodel/ReadModelFixtures.res +8 -8
- package/tests/components/scheduler/SchedulerTest.res +76 -58
- package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
- package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
- package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
- package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
- package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
- package/tests/components/task/TaskFixtures.res +5 -5
- package/tests/components/task/TaskTest.res +95 -80
- package/tests/plugin/PluginsProjection_GWT.res +3 -1
- package/tests/plugin/UiFragments_GWT.res +6 -1
- package/tests/reset/LocalSeedResetTest.res +1 -0
|
@@ -73,7 +73,8 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
73
73
|
~ttl as _=?,
|
|
74
74
|
~api as _,
|
|
75
75
|
~apiRole as _,
|
|
76
|
-
~owner as _,
|
|
76
|
+
~owner as _,
|
|
77
|
+
~opts as _,
|
|
77
78
|
) => {
|
|
78
79
|
let store: ref<dict<dict<JSON.t>>> = ref(Dict.make())
|
|
79
80
|
// Lazy flattened snapshot for the registered scan/stream closures. The old
|
|
@@ -199,7 +200,9 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
199
200
|
~state=Some(state),
|
|
200
201
|
~seq=LocalStateChangeDescriptor.nextSequence(),
|
|
201
202
|
~retiredField=?LocalStateChangeDescriptor.retiredSpecFor(name)->Option.map(r => r.field),
|
|
202
|
-
|
|
203
|
+
~retiredValues=?LocalStateChangeDescriptor.retiredSpecFor(name)->Option.flatMap(r =>
|
|
204
|
+
r.values
|
|
205
|
+
),
|
|
203
206
|
)
|
|
204
207
|
Bus.publishStateChange(~name, ~descriptor)
|
|
205
208
|
}
|
|
@@ -367,5 +370,4 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
367
370
|
operations: Pulumi.Output.make(ops),
|
|
368
371
|
}
|
|
369
372
|
}
|
|
370
|
-
|
|
371
373
|
}
|
|
@@ -31,8 +31,8 @@ let sanitizeIdent = (s: string): string =>
|
|
|
31
31
|
let code = ch->String.codePointAt(0)->Option.getOr(0)
|
|
32
32
|
let isAlphaNum =
|
|
33
33
|
(code >= 48 && code <= 57) ||
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
code >= 65 && code <= 90 ||
|
|
35
|
+
code >= 97 && code <= 122 ||
|
|
36
36
|
code == 95
|
|
37
37
|
isAlphaNum ? ch : "_"
|
|
38
38
|
})
|
|
@@ -64,8 +64,7 @@ let compositeExpr = (fields: array<string>, sep: string): string => {
|
|
|
64
64
|
// Uses pkFields if present, else falls back to idField; same for sub key.
|
|
65
65
|
let indexExpressions = (idx: indexConfig): (string, option<string>) => {
|
|
66
66
|
let pkExpr = switch idx.pkFields {
|
|
67
|
-
| Some(fields) if fields->Array.length > 0 =>
|
|
68
|
-
compositeExpr(fields, idx.pkSep->Option.getOr("/"))
|
|
67
|
+
| Some(fields) if fields->Array.length > 0 => compositeExpr(fields, idx.pkSep->Option.getOr("/"))
|
|
69
68
|
| _ =>
|
|
70
69
|
switch idx.idField {
|
|
71
70
|
| Some(field) => jsonField(field)
|
|
@@ -168,30 +167,32 @@ let makeStorage = (
|
|
|
168
167
|
ensureExpiresColumn(~db, ~table)
|
|
169
168
|
ensureIndexes(~db, ~table, ~indexes)
|
|
170
169
|
|
|
171
|
-
let upsertStmt =
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
let scanAllStmt =
|
|
184
|
-
|
|
185
|
-
|
|
170
|
+
let upsertStmt =
|
|
171
|
+
db->SqliteDriver.prepare(
|
|
172
|
+
`INSERT INTO ${table}(partition_key, sub_key, item, expires_at) VALUES(?,?,?,?) ON CONFLICT(partition_key, sub_key) DO UPDATE SET item = excluded.item, expires_at = excluded.expires_at`,
|
|
173
|
+
)
|
|
174
|
+
let selectByPartitionStmt =
|
|
175
|
+
db->SqliteDriver.prepare(
|
|
176
|
+
`SELECT partition_key, sub_key, item FROM ${table} WHERE partition_key = ? AND ${notExpiredClause} ORDER BY sub_key ASC`,
|
|
177
|
+
)
|
|
178
|
+
let deleteByPartitionStmt =
|
|
179
|
+
db->SqliteDriver.prepare(`DELETE FROM ${table} WHERE partition_key = ?`)
|
|
180
|
+
let deleteBySubKeyStmt =
|
|
181
|
+
db->SqliteDriver.prepare(`DELETE FROM ${table} WHERE partition_key = ? AND sub_key = ?`)
|
|
182
|
+
let scanAllStmt =
|
|
183
|
+
db->SqliteDriver.prepare(
|
|
184
|
+
`SELECT partition_key, sub_key, item FROM ${table} WHERE ${notExpiredClause} ORDER BY partition_key, sub_key`,
|
|
185
|
+
)
|
|
186
186
|
// First-insert detection. DynamoDB streams hand the AWS StateTopic Lambda an
|
|
187
187
|
// eventName that separates INSERT from MODIFY; an upsert reports neither, so
|
|
188
188
|
// ask the table before writing. Carries `notExpiredClause` because that is what
|
|
189
189
|
// a reader sees: a row aged past its TTL is invisible to them, so overwriting
|
|
190
190
|
// it is an insert — the same descriptor pair (Removed, then Added) DynamoDB
|
|
191
191
|
// emits when a TTL delete is followed by a fresh Put.
|
|
192
|
-
let existsStmt =
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
let existsStmt =
|
|
193
|
+
db->SqliteDriver.prepare(
|
|
194
|
+
`SELECT 1 FROM ${table} WHERE partition_key = ? AND sub_key = ? AND ${notExpiredClause} LIMIT 1`,
|
|
195
|
+
)
|
|
195
196
|
|
|
196
197
|
let rowsFor = (id: string): array<JSON.t> =>
|
|
197
198
|
selectByPartitionStmt
|
|
@@ -222,9 +223,10 @@ let makeStorage = (
|
|
|
222
223
|
// Escape single quotes in the JSON path literal so an odd field name can't
|
|
223
224
|
// break out of the string (bound `?` already covers the value).
|
|
224
225
|
let escapedField = field->String.replaceAll("'", "''")
|
|
225
|
-
let s =
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
let s =
|
|
227
|
+
db->SqliteDriver.prepare(
|
|
228
|
+
`SELECT partition_key, sub_key, item FROM ${table} WHERE json_extract(item, '$.${escapedField}') = ? AND ${notExpiredClause}`,
|
|
229
|
+
)
|
|
228
230
|
indexLookupStmts->Dict.set(field, s)
|
|
229
231
|
s
|
|
230
232
|
}
|
|
@@ -265,7 +267,9 @@ let makeStorage = (
|
|
|
265
267
|
~state=Some(state),
|
|
266
268
|
~seq=LocalStateChangeDescriptor.nextSequence(),
|
|
267
269
|
~retiredField=?LocalStateChangeDescriptor.retiredSpecFor(name)->Option.map(r => r.field),
|
|
268
|
-
~retiredValues=?LocalStateChangeDescriptor.retiredSpecFor(name)->Option.flatMap(r =>
|
|
270
|
+
~retiredValues=?LocalStateChangeDescriptor.retiredSpecFor(name)->Option.flatMap(r =>
|
|
271
|
+
r.values
|
|
272
|
+
),
|
|
269
273
|
)
|
|
270
274
|
bus.publishStateChange(~name, ~descriptor)
|
|
271
275
|
}
|
|
@@ -358,10 +362,7 @@ let makeStorage = (
|
|
|
358
362
|
switch subIdOpt {
|
|
359
363
|
| None => deleteByPartitionStmt->SqliteDriver.run([JSON.Encode.string(id)])
|
|
360
364
|
| Some((_, subValue)) =>
|
|
361
|
-
deleteBySubKeyStmt->SqliteDriver.run([
|
|
362
|
-
JSON.Encode.string(id),
|
|
363
|
-
JSON.Encode.string(subValue),
|
|
364
|
-
])
|
|
365
|
+
deleteBySubKeyStmt->SqliteDriver.run([JSON.Encode.string(id), JSON.Encode.string(subValue)])
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
let delete: QueryDb.delete<string> = async (id, subIdOpt) => {
|
|
@@ -436,12 +437,24 @@ let makeStorage = (
|
|
|
436
437
|
let filterDict =
|
|
437
438
|
argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
|
|
438
439
|
let strNonEmpty = k =>
|
|
439
|
-
filterDict
|
|
440
|
+
filterDict
|
|
441
|
+
->Dict.get(k)
|
|
442
|
+
->Option.flatMap(JSON.Decode.string)
|
|
443
|
+
->Option.mapOr(false, s => s->String.length > 0)
|
|
440
444
|
let hasIds =
|
|
441
|
-
filterDict
|
|
445
|
+
filterDict
|
|
446
|
+
->Dict.get("ids")
|
|
447
|
+
->Option.flatMap(JSON.Decode.array)
|
|
448
|
+
->Option.mapOr(false, a => a->Array.length > 0)
|
|
442
449
|
let last = argsDict->Dict.get("last")->Option.flatMap(JSON.Decode.float)
|
|
443
450
|
let before = argsDict->Dict.get("before")->Option.flatMap(JSON.Decode.string)
|
|
444
|
-
if
|
|
451
|
+
if (
|
|
452
|
+
strNonEmpty("search") ||
|
|
453
|
+
strNonEmpty("searchPrefix") ||
|
|
454
|
+
hasIds ||
|
|
455
|
+
last->Option.isSome ||
|
|
456
|
+
before->Option.isSome
|
|
457
|
+
) {
|
|
445
458
|
None
|
|
446
459
|
} else {
|
|
447
460
|
let whereParts = [notExpiredClause]
|
|
@@ -495,20 +508,20 @@ let makeStorage = (
|
|
|
495
508
|
}
|
|
496
509
|
capability.filterFields->Array.forEach(f => {
|
|
497
510
|
switch filterDict->Dict.get(f.name ++ "Eq") {
|
|
498
|
-
| Some(v)
|
|
511
|
+
| Some(v) if v != JSON.Encode.null =>
|
|
499
512
|
whereParts->Array.push(`${jsonText(f.name)} = ?`)
|
|
500
513
|
params->Array.push(JSON.Encode.string(valString(v)->Option.getOr("")))
|
|
501
514
|
| _ => ()
|
|
502
515
|
}
|
|
503
516
|
if f.range {
|
|
504
517
|
switch filterDict->Dict.get(f.name ++ "From") {
|
|
505
|
-
| Some(v)
|
|
518
|
+
| Some(v) if v != JSON.Encode.null =>
|
|
506
519
|
whereParts->Array.push(`${jsonText(f.name)} >= ?`)
|
|
507
520
|
params->Array.push(JSON.Encode.string(valString(v)->Option.getOr("")))
|
|
508
521
|
| _ => ()
|
|
509
522
|
}
|
|
510
523
|
switch filterDict->Dict.get(f.name ++ "To") {
|
|
511
|
-
| Some(v)
|
|
524
|
+
| Some(v) if v != JSON.Encode.null =>
|
|
512
525
|
whereParts->Array.push(`${jsonText(f.name)} <= ?`)
|
|
513
526
|
params->Array.push(JSON.Encode.string(valString(v)->Option.getOr("")))
|
|
514
527
|
| _ => ()
|
|
@@ -551,7 +564,9 @@ let makeStorage = (
|
|
|
551
564
|
->Option.flatMap(JSON.Decode.float)
|
|
552
565
|
->Option.map(Float.toInt)
|
|
553
566
|
->Option.getOr(QueryDbListQuery.defaultListPageSize)
|
|
554
|
-
let sql = `SELECT partition_key, item FROM ${table} WHERE ${whereParts->Array.join(
|
|
567
|
+
let sql = `SELECT partition_key, item FROM ${table} WHERE ${whereParts->Array.join(
|
|
568
|
+
" AND ",
|
|
569
|
+
)} ORDER BY ${orderClause} LIMIT ?`
|
|
555
570
|
let rows =
|
|
556
571
|
preparedList(sql)
|
|
557
572
|
->SqliteDriver.all(params->Array.concat([JSON.Encode.int(pageSize + 1)]))
|
|
@@ -560,7 +575,9 @@ let makeStorage = (
|
|
|
560
575
|
let pageItems = rows->Array.slice(~start=0, ~end=pageSize)
|
|
561
576
|
let cursorField = orderByField->Option.getOr("id")
|
|
562
577
|
let cursorValueOf = item =>
|
|
563
|
-
QueryDbListQuery.getFieldString(item, cursorField)->Option.getOr(
|
|
578
|
+
QueryDbListQuery.getFieldString(item, cursorField)->Option.getOr(
|
|
579
|
+
QueryDbListQuery.getId(item),
|
|
580
|
+
)
|
|
564
581
|
Some(
|
|
565
582
|
QueryDbListQuery.buildConnection(
|
|
566
583
|
~pageItems,
|
|
@@ -583,7 +600,12 @@ let makeStorage = (
|
|
|
583
600
|
}
|
|
584
601
|
}
|
|
585
602
|
|
|
586
|
-
module Make = (
|
|
603
|
+
module Make = (
|
|
604
|
+
Bus: LocalBus.T,
|
|
605
|
+
DbProvider: {
|
|
606
|
+
let db: SqliteDriver.t
|
|
607
|
+
},
|
|
608
|
+
) => {
|
|
587
609
|
type api = unit
|
|
588
610
|
type role = unit
|
|
589
611
|
|
|
@@ -603,6 +625,7 @@ module Make = (Bus: LocalBus.T, DbProvider: {let db: SqliteDriver.t}) => {
|
|
|
603
625
|
~ttl as _=?,
|
|
604
626
|
~api as _,
|
|
605
627
|
~apiRole as _,
|
|
606
|
-
~owner as _,
|
|
628
|
+
~owner as _,
|
|
629
|
+
~opts as _,
|
|
607
630
|
) => makeStorage(~db=DbProvider.db, ~bus=busCallbacks, ~name, ~indexes, ~subIdField)
|
|
608
631
|
}
|
|
@@ -9,7 +9,11 @@ type result = {
|
|
|
9
9
|
|
|
10
10
|
/** Extract the sort key string from a JSON item using the given field name. */
|
|
11
11
|
let getSk = (item, skField) =>
|
|
12
|
-
item
|
|
12
|
+
item
|
|
13
|
+
->JSON.Decode.object
|
|
14
|
+
->Option.flatMap(d => d->Dict.get(skField))
|
|
15
|
+
->Option.flatMap(JSON.Decode.string)
|
|
16
|
+
->Option.getOr("")
|
|
13
17
|
|
|
14
18
|
/** Apply sort key conditions, ordering, and pagination to a list of JSON items.
|
|
15
19
|
- `items` must already be sorted ascending by sort key.
|
|
@@ -28,12 +32,16 @@ let apply = (
|
|
|
28
32
|
let filtered = items->Array.filter(item => {
|
|
29
33
|
let sk = getSk(item, skField)
|
|
30
34
|
let okPrefix = prefix->Option.map(p => sk->String.startsWith(p))->Option.getOr(true)
|
|
31
|
-
let okFrom
|
|
32
|
-
let okTo
|
|
33
|
-
let okEq
|
|
35
|
+
let okFrom = from->Option.map(f => sk >= f)->Option.getOr(true)
|
|
36
|
+
let okTo = to_->Option.map(t => sk <= t)->Option.getOr(true)
|
|
37
|
+
let okEq = eq->Option.map(e => sk == e)->Option.getOr(true)
|
|
34
38
|
okPrefix && okFrom && okTo && okEq
|
|
35
39
|
})
|
|
36
|
-
let ordered = if reverse {
|
|
40
|
+
let ordered = if reverse {
|
|
41
|
+
filtered->Array.toReversed
|
|
42
|
+
} else {
|
|
43
|
+
filtered
|
|
44
|
+
}
|
|
37
45
|
let sliced = ordered->Array.slice(~start=offset, ~end=ordered->Array.length)
|
|
38
46
|
let (page, hasMore) = switch limit {
|
|
39
47
|
| Some(n) => (sliced->Array.slice(~start=0, ~end=n), sliced->Array.length > n)
|
|
@@ -44,5 +52,5 @@ let apply = (
|
|
|
44
52
|
} else {
|
|
45
53
|
None
|
|
46
54
|
}
|
|
47
|
-
{
|
|
55
|
+
{items: page, nextToken}
|
|
48
56
|
}
|
|
@@ -32,7 +32,9 @@ module Make = (
|
|
|
32
32
|
// runtime so the type is satisfied and future callers that need handlerRef will work.
|
|
33
33
|
let resource = commandGenerator->ReventlessCore.Component.toPulumiResource
|
|
34
34
|
let name =
|
|
35
|
-
resource.name->ReventlessCore.ComponentType.nameOpt(
|
|
35
|
+
resource.name->ReventlessCore.ComponentType.nameOpt(
|
|
36
|
+
ReventlessCore.CommandGenerator.componentType,
|
|
37
|
+
)
|
|
36
38
|
let runtime = LocalRuntimeEnvironment.make(
|
|
37
39
|
~name,
|
|
38
40
|
~handler=handler->Pulumi.Output.apply(h =>
|
|
@@ -50,13 +52,7 @@ module Make = (
|
|
|
50
52
|
connect(~runtime)
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
let forCommandTopic = (
|
|
54
|
-
~handler,
|
|
55
|
-
~connect,
|
|
56
|
-
~memorySize as _=?,
|
|
57
|
-
~timeout as _=?,
|
|
58
|
-
commandTopic,
|
|
59
|
-
) => {
|
|
55
|
+
let forCommandTopic = (~handler, ~connect, ~memorySize as _=?, ~timeout as _=?, commandTopic) => {
|
|
60
56
|
let resource = commandTopic->ReventlessCore.Component.toPulumiResource
|
|
61
57
|
let name =
|
|
62
58
|
resource.name->ReventlessCore.ComponentType.nameOpt(ReventlessCore.CommandTopic.componentType)
|
|
@@ -87,7 +83,9 @@ module Make = (
|
|
|
87
83
|
) => {
|
|
88
84
|
let resource = eventCollector->ReventlessCore.Component.toPulumiResource
|
|
89
85
|
let name =
|
|
90
|
-
resource.name->ReventlessCore.ComponentType.nameOpt(
|
|
86
|
+
resource.name->ReventlessCore.ComponentType.nameOpt(
|
|
87
|
+
ReventlessCore.EventCollector.componentType,
|
|
88
|
+
)
|
|
91
89
|
// Same `comp` shape as the deployed dispatchers — see EventCollectorRuntime_Builder_Single.
|
|
92
90
|
let comp = `EventCollector(${resource.name->Option.getOr("Unnamed")})`
|
|
93
91
|
let opts = {Pulumi.ComponentResource.parent: resource}
|
|
@@ -108,7 +106,11 @@ module Make = (
|
|
|
108
106
|
let _connectResources = EventCollectorChannel.connect(
|
|
109
107
|
~name,
|
|
110
108
|
~channelSpecs=[
|
|
111
|
-
{
|
|
109
|
+
{
|
|
110
|
+
channel: eventCollector->ReventlessCore.EventCollector_Adapter.channel,
|
|
111
|
+
eventTopics,
|
|
112
|
+
resources,
|
|
113
|
+
},
|
|
112
114
|
],
|
|
113
115
|
~runtime,
|
|
114
116
|
~opts,
|
|
@@ -25,7 +25,9 @@ module Make = (
|
|
|
25
25
|
) => {
|
|
26
26
|
let resource = eventCollector->ReventlessCore.Component.toPulumiResource
|
|
27
27
|
let name =
|
|
28
|
-
resource.name->ReventlessCore.ComponentType.nameOpt(
|
|
28
|
+
resource.name->ReventlessCore.ComponentType.nameOpt(
|
|
29
|
+
ReventlessCore.EventCollector.componentType,
|
|
30
|
+
)
|
|
29
31
|
// Same `comp` shape as the deployed dispatchers — see EventCollectorRuntime_Builder_Single.
|
|
30
32
|
let comp = `EventCollector(${resource.name->Option.getOr("Unnamed")})`
|
|
31
33
|
let opts = {Pulumi.ComponentResource.parent: resource}
|
|
@@ -46,7 +48,11 @@ module Make = (
|
|
|
46
48
|
let _connectResources = EventCollectorChannel.connect(
|
|
47
49
|
~name,
|
|
48
50
|
~channelSpecs=[
|
|
49
|
-
{
|
|
51
|
+
{
|
|
52
|
+
channel: eventCollector->ReventlessCore.EventCollector_Adapter.channel,
|
|
53
|
+
eventTopics,
|
|
54
|
+
resources,
|
|
55
|
+
},
|
|
50
56
|
],
|
|
51
57
|
~runtime,
|
|
52
58
|
~opts,
|
|
@@ -4,8 +4,5 @@
|
|
|
4
4
|
module Make = (Bus: LocalBus.T) => {
|
|
5
5
|
module ECChannel = LocalEventCollectorChannel.Make(Bus)
|
|
6
6
|
|
|
7
|
-
include ReventlessCore.PluginRuntime_Builder_Micro.Make(
|
|
8
|
-
LocalRuntimeEnvironment,
|
|
9
|
-
ECChannel,
|
|
10
|
-
)
|
|
7
|
+
include ReventlessCore.PluginRuntime_Builder_Micro.Make(LocalRuntimeEnvironment, ECChannel)
|
|
11
8
|
}
|
|
@@ -60,14 +60,12 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
60
60
|
| json => json
|
|
61
61
|
| exception _ => schedule.payload->JSON.Encode.string
|
|
62
62
|
}
|
|
63
|
-
let fire = () =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
setIntervalJs(fire, rateToMs(schedule.rate))
|
|
70
|
-
}
|
|
63
|
+
let fire = () => Bus.publishEvent(topicName, "Scheduler", scheduleMeta, payloadJson)->ignore
|
|
64
|
+
let handle = if schedule.rate->isSingleShot {
|
|
65
|
+
setTimeoutJs(fire, 0)
|
|
66
|
+
} else {
|
|
67
|
+
setIntervalJs(fire, rateToMs(schedule.rate))
|
|
68
|
+
}
|
|
71
69
|
activeTimers.contents->Dict.set(schedule.name, handle)
|
|
72
70
|
}
|
|
73
71
|
|
|
@@ -87,10 +85,12 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
87
85
|
~urn=""->Pulumi.Output.make,
|
|
88
86
|
~service="memory:InMemory"->Pulumi.Output.make,
|
|
89
87
|
),
|
|
90
|
-
operations: (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
operations: (
|
|
89
|
+
{
|
|
90
|
+
createSchedule,
|
|
91
|
+
deleteSchedule,
|
|
92
|
+
}: ReventlessInfra.Scheduler.operations
|
|
93
|
+
)->Pulumi.Output.make,
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -14,10 +14,22 @@ external openDatabaseSync: string => t = "DatabaseSync"
|
|
|
14
14
|
// node:sqlite accepts ...positional bound values. Use Function.prototype.apply
|
|
15
15
|
// via a small inline helper so the array is splatted without ReScript's
|
|
16
16
|
// `@send` syntax accidentally treating `run.apply` as a single property name.
|
|
17
|
-
let _runApply: (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
let _runApply: (
|
|
18
|
+
statement,
|
|
19
|
+
array<JSON.t>,
|
|
20
|
+
) => unit = %raw(`function(s, a) { return s.run.apply(s, a); }`)
|
|
21
|
+
let _getApply: (
|
|
22
|
+
statement,
|
|
23
|
+
array<JSON.t>,
|
|
24
|
+
) => Nullable.t<dict<JSON.t>> = %raw(`function(s, a) { return s.get.apply(s, a); }`)
|
|
25
|
+
let _allApply: (
|
|
26
|
+
statement,
|
|
27
|
+
array<JSON.t>,
|
|
28
|
+
) => array<dict<JSON.t>> = %raw(`function(s, a) { return s.all.apply(s, a); }`)
|
|
29
|
+
let _iterateApply: (
|
|
30
|
+
statement,
|
|
31
|
+
array<JSON.t>,
|
|
32
|
+
) => Iterator.t<dict<JSON.t>> = %raw(`function(s, a) { return s.iterate.apply(s, a); }`)
|
|
21
33
|
|
|
22
34
|
let openDb = (~path) => {
|
|
23
35
|
if path !== ":memory:" {
|
|
@@ -21,9 +21,10 @@ let ensureSchema = (db: SqliteDriver.t) =>
|
|
|
21
21
|
|
|
22
22
|
let put = (~db: SqliteDriver.t, ~bucket: string, ~key: string, ~body: string): unit => {
|
|
23
23
|
ensureSchema(db)
|
|
24
|
-
let stmt =
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
let stmt =
|
|
25
|
+
db->SqliteDriver.prepare(
|
|
26
|
+
"INSERT INTO task_object(bucket, key, body) VALUES(?, ?, ?) ON CONFLICT(bucket, key) DO UPDATE SET body = excluded.body",
|
|
27
|
+
)
|
|
27
28
|
stmt->SqliteDriver.run([
|
|
28
29
|
JSON.Encode.string(bucket),
|
|
29
30
|
JSON.Encode.string(key),
|
|
@@ -33,9 +34,7 @@ let put = (~db: SqliteDriver.t, ~bucket: string, ~key: string, ~body: string): u
|
|
|
33
34
|
|
|
34
35
|
let get = (~db: SqliteDriver.t, ~bucket: string, ~key: string): option<string> => {
|
|
35
36
|
ensureSchema(db)
|
|
36
|
-
let stmt = db->SqliteDriver.prepare(
|
|
37
|
-
"SELECT body FROM task_object WHERE bucket = ? AND key = ?",
|
|
38
|
-
)
|
|
37
|
+
let stmt = db->SqliteDriver.prepare("SELECT body FROM task_object WHERE bucket = ? AND key = ?")
|
|
39
38
|
switch stmt->SqliteDriver.get([JSON.Encode.string(bucket), JSON.Encode.string(key)]) {
|
|
40
39
|
| Some(row) =>
|
|
41
40
|
switch row->Dict.get("body") {
|
|
@@ -52,5 +52,10 @@ module MakeWithHooks = (
|
|
|
52
52
|
|
|
53
53
|
// Make — simple version for standalone tests and examples (no hook callbacks).
|
|
54
54
|
module Make = (Bus: LocalBus.T) => {
|
|
55
|
-
include MakeWithHooks(
|
|
55
|
+
include MakeWithHooks(
|
|
56
|
+
Bus,
|
|
57
|
+
{
|
|
58
|
+
let hooks = noHooks
|
|
59
|
+
},
|
|
60
|
+
)
|
|
56
61
|
}
|
|
@@ -6,9 +6,5 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
6
6
|
let api = () => ()
|
|
7
7
|
let apiRole = () => ()
|
|
8
8
|
}
|
|
9
|
-
include ReventlessCore.Counter_Builder.Make(
|
|
10
|
-
QueryDbStorage,
|
|
11
|
-
Api,
|
|
12
|
-
LocalCounterHandler,
|
|
13
|
-
)
|
|
9
|
+
include ReventlessCore.Counter_Builder.Make(QueryDbStorage, Api, LocalCounterHandler)
|
|
14
10
|
}
|
|
@@ -16,9 +16,11 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
16
16
|
~indexes: array<string>=?,
|
|
17
17
|
~partitionTag: Reventless.DcbTag.derivedPartitionTag,
|
|
18
18
|
~opts: Pulumi.ComponentResource.options=?,
|
|
19
|
-
) => component =
|
|
19
|
+
) => component =
|
|
20
|
+
Inner.make->Obj.magic
|
|
20
21
|
// Expose `operations` so callers can resolve the Output chain
|
|
21
22
|
// (e.g. in beforeAllAsync to register handlers).
|
|
22
|
-
let operations: component => Pulumi.Output.t<
|
|
23
|
-
ReventlessInfra.
|
|
23
|
+
let operations: component => Pulumi.Output.t<
|
|
24
|
+
ReventlessInfra.DcbEventLog.operations,
|
|
25
|
+
> = ReventlessInfra.Component.operations
|
|
24
26
|
}
|
|
@@ -4,11 +4,10 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
4
4
|
module RuntimeEnvironment = LocalRuntimeEnvironment
|
|
5
5
|
module CommandTopicChannel = LocalCommandTopicChannel.Make(Bus)
|
|
6
6
|
module EventTopicPublisher = LocalEventTopicPublisher.Make(Bus)
|
|
7
|
-
module ExtensionPointRuntimeBuilder =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
)
|
|
7
|
+
module ExtensionPointRuntimeBuilder = ReventlessCore.ExtensionPointRuntime_Builder_PerExtensionPoint.Make(
|
|
8
|
+
RuntimeEnvironment,
|
|
9
|
+
CommandTopicChannel,
|
|
10
|
+
)
|
|
12
11
|
|
|
13
12
|
// The in-memory runtime ignores memorySize; timeout mirrors the AWS EP floor.
|
|
14
13
|
module Defaults: ReventlessInfra.RuntimeDefaults.T = {
|
|
@@ -19,14 +18,13 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
19
18
|
module Make = (
|
|
20
19
|
Spec: ReventlessInfra.ExtensionPointMapping.Spec,
|
|
21
20
|
Mappings: ReventlessInfra.ExtensionPoint.Mappings with module Spec := Spec,
|
|
22
|
-
): ReventlessInfra.ExtensionPoint.T =>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
21
|
+
): ReventlessInfra.ExtensionPoint.T => ReventlessCore.ExtensionPoint_Builder.Make(
|
|
22
|
+
Spec,
|
|
23
|
+
Mappings,
|
|
24
|
+
RuntimeEnvironment,
|
|
25
|
+
CommandTopicChannel,
|
|
26
|
+
EventTopicPublisher,
|
|
27
|
+
ExtensionPointRuntimeBuilder,
|
|
28
|
+
Defaults,
|
|
29
|
+
)
|
|
32
30
|
}
|
|
@@ -28,7 +28,8 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
28
28
|
let queryDbName = Inner.queryDbName
|
|
29
29
|
let make = Inner.make
|
|
30
30
|
// Re-expose operations for test resolution
|
|
31
|
-
let operations: component => Pulumi.Output.t<
|
|
32
|
-
ReventlessCore.
|
|
31
|
+
let operations: component => Pulumi.Output.t<
|
|
32
|
+
ReventlessCore.InboundTranslationSlice.operations,
|
|
33
|
+
> = ReventlessCore.Component.operations
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -38,7 +38,8 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
38
38
|
let queryDbName = Inner.queryDbName
|
|
39
39
|
let make = Inner.make
|
|
40
40
|
// Re-expose operations for test resolution
|
|
41
|
-
let operations: component => Pulumi.Output.t<
|
|
42
|
-
ReventlessCore.
|
|
41
|
+
let operations: component => Pulumi.Output.t<
|
|
42
|
+
ReventlessCore.OutboundTranslationSlice.operations,
|
|
43
|
+
> = ReventlessCore.Component.operations
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -15,16 +15,15 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
15
15
|
module Make = (
|
|
16
16
|
Spec: Reventless.ReadModel.Spec,
|
|
17
17
|
Mappings: Reventless.Projection.Mappings with module Target := Spec,
|
|
18
|
-
) =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)
|
|
18
|
+
) => ReventlessCore.ReadModel_Builder.Make(
|
|
19
|
+
Spec,
|
|
20
|
+
Mappings,
|
|
21
|
+
RuntimeEnvironment,
|
|
22
|
+
QueryDbStorage,
|
|
23
|
+
QueryDbResolvers,
|
|
24
|
+
EventCollectorChannel,
|
|
25
|
+
EventCollectorRuntimeBuilder,
|
|
26
|
+
)
|
|
28
27
|
|
|
29
28
|
// Resolver-free variant: registers the QueryDb store in the Bus and wires the
|
|
30
29
|
// EventCollector subscription, but does NOT register GraphQL query resolvers.
|
|
@@ -36,14 +35,13 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
36
35
|
module MakeNoResolver = (
|
|
37
36
|
Spec: Reventless.ReadModel.Spec,
|
|
38
37
|
Mappings: Reventless.Projection.Mappings with module Target := Spec,
|
|
39
|
-
) =>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
38
|
+
) => ReventlessCore.ReadModel_Builder.Make(
|
|
39
|
+
Spec,
|
|
40
|
+
Mappings,
|
|
41
|
+
RuntimeEnvironment,
|
|
42
|
+
QueryDbStorage,
|
|
43
|
+
NoResolvers,
|
|
44
|
+
EventCollectorChannel,
|
|
45
|
+
EventCollectorRuntimeBuilder,
|
|
46
|
+
)
|
|
49
47
|
}
|
|
@@ -39,7 +39,8 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
39
39
|
type component = Inner.component
|
|
40
40
|
let make = Inner.make
|
|
41
41
|
// Re-expose operations for test resolution
|
|
42
|
-
let operations: component => Pulumi.Output.t<
|
|
43
|
-
ReventlessCore.
|
|
42
|
+
let operations: component => Pulumi.Output.t<
|
|
43
|
+
ReventlessCore.StateViewSlice.operations,
|
|
44
|
+
> = ReventlessCore.Component.operations
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -19,17 +19,16 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
19
19
|
let timeout = 600
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
module Make = (
|
|
23
|
-
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
22
|
+
module Make = (Spec: ReventlessCore.Task.Spec): (
|
|
23
|
+
ReventlessCore.Task.T with module Spec = Spec
|
|
24
|
+
) => ReventlessCore.Task_Builder.Make(
|
|
25
|
+
Spec,
|
|
26
|
+
RuntimeEnvironment,
|
|
27
|
+
EventCollectorChannel,
|
|
28
|
+
EventCollectorRuntimeBuilder,
|
|
29
|
+
TaskRuntimeBuilder,
|
|
30
|
+
LocalTaskBucket,
|
|
31
|
+
SideEffectHandler,
|
|
32
|
+
Defaults,
|
|
33
|
+
)
|
|
35
34
|
}
|