@reventlessdev/reventless-aws 3.0.0-alpha.305 → 3.0.0-alpha.307
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 +21 -0
- package/package.json +8 -8
- package/src/Platform.res +1 -1
- package/src/adapter/Api/AppSync_Resolver_Native.res +1 -1
- package/src/adapter/Api/AppSync_Resolver_Retrying.res +2 -2
- package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res +69 -11
- package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res.mjs +19 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +2 -2
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res +1 -1
- package/src/adapter/EventLog/EventLogStorage_DynamoDb_Runtime.res +1 -1
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +1 -1
- package/src/adapter/Postgres/PgProjectionFeed.res +1 -1
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +64 -0
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +54 -2
- package/src/adapter/QueryDb/QueryDbBackend.res +1 -1
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +138 -46
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +25 -21
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +19 -11
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res.mjs +11 -10
- package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res +1 -1
- package/src/adapter/Runtime/AggregateEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/AggregateEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/CounterEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/CounterEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +44 -6
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/EventMapperEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/EventMapperEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/ExtensionPointEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +7 -1
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +2 -1
- package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res +1 -1
- package/src/adapter/Runtime/ProjectionEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/QueryDbEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/ReadModelEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -1
- package/src/adapter/Runtime/SideEffectEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/SideEffectEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/StateViewSliceEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/StreamRoutedEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +1 -1
- package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +1 -1
- package/src/util/Util_Bundle.res +1 -1
- package/src/util/Util_DynamoDbStream_Runtime.res +1 -1
- package/tests/AWS_TagsTest.res +2 -2
- package/tests/AppSync_RetirementNarrowingTest.res +272 -0
- package/tests/AppSync_RetirementNarrowingTest.res.mjs +193 -0
- package/tests/Auth_ActiveRolePoolAttachmentTest.res +140 -1
- package/tests/Auth_ActiveRolePoolAttachmentTest.res.mjs +117 -2
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +2 -2
- package/tests/EventLogStorage_DynamoDb_RuntimeTest.res +1 -1
- package/tests/PgQueryResolver_LambdaTest.res +2 -0
- package/tests/PgQueryResolver_LambdaTest.res.mjs +34 -30
- package/tests/QueryDbResolvers_AppSyncTest.res +2 -2
- package/tests/Util_BundleEsmLoaderTest.res +1 -1
- package/tests/Util_BundleRuntimeExtensionTest.res +1 -1
- package/tests/integration/AggregateEntryPoint_IntegrationTest.res +1 -1
- package/tests/integration/DcbCommandTopicEntryPoint_IntegrationTest.res +1 -1
- package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res +2 -2
|
@@ -4,7 +4,7 @@ open PulumiAws.AppSync
|
|
|
4
4
|
// `NotFoundException: No field named X` (the schema-propagation race) with
|
|
5
5
|
// exponential backoff. Replaces AppSync_Resolver_Native (aws-native provider)
|
|
6
6
|
// which exhibited the same race in production.
|
|
7
|
-
// See docs/plans/appsync-resolver-aws-native-retry.md.
|
|
7
|
+
// See docs/plans/done/appsync-resolver-aws-native-retry.md.
|
|
8
8
|
module Resolver = AppSync_Resolver_Retrying
|
|
9
9
|
open Reventless.ReadModel
|
|
10
10
|
|
|
@@ -55,7 +55,7 @@ export function response(ctx) {
|
|
|
55
55
|
that carry no `name`. Returning `Some(attr)` makes the Connection Scan emit an
|
|
56
56
|
`attribute_exists(#attr)` filter so those rows never reach the non-null GraphQL schema.
|
|
57
57
|
`name` is the already-capitalized read-model name (spec name, e.g. "Plugins").
|
|
58
|
-
See docs/plans/platform-plugins-admin-connection-null-rows.md. */
|
|
58
|
+
See docs/plans/done/platform-plugins-admin-connection-null-rows.md. */
|
|
59
59
|
let internalRowRequiredAttr = (name: string): option<string> =>
|
|
60
60
|
name == "Plugins" ? Some("name") : None
|
|
61
61
|
|
|
@@ -69,7 +69,7 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
69
69
|
~idResolverConfigs: array<idResolverConfig>,
|
|
70
70
|
~idsResolverConfigs: array<idsResolverConfig>,
|
|
71
71
|
// AWS path enforces authorization via `@aws_cognito_user_pools(cognito_groups: …)` on the
|
|
72
|
-
// SDL fields (see Stage E in docs/plans/host-ui-login-core.md). Accepted but
|
|
72
|
+
// SDL fields (see Stage E in docs/plans/done/host-ui-login-core.md). Accepted but
|
|
73
73
|
// ignored here so the in-memory and AWS resolver maker signatures stay aligned.
|
|
74
74
|
~authorization as _: Reventless.Authorization.permission,
|
|
75
75
|
~opts,
|
|
@@ -167,13 +167,55 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
167
167
|
// This prevents a race condition where AppSync resolvers are deployed before
|
|
168
168
|
// the schema push completes and the new fields are ACTIVE in AppSync.
|
|
169
169
|
let resourcesMaker: ReventlessCore.QueryDb.resolversResourcesMaker = allQueryDbs => {
|
|
170
|
+
// The state's `@owner` field, derived from the same schema `capability` is
|
|
171
|
+
// derived from, so the two cannot disagree about this read model's fields.
|
|
172
|
+
//
|
|
173
|
+
// Read HERE, at the top, rather than beside the list resolver that first
|
|
174
|
+
// needed it. The by-id resolvers are built a few lines below and the lookup
|
|
175
|
+
// used to sit well after them, so they were generated without it: a list
|
|
176
|
+
// narrowed to the caller beside a by-id read that hands over any row it is
|
|
177
|
+
// asked for. That is not a partial delivery — the row is reachable by
|
|
178
|
+
// anyone who can name one, and the list gives no sign of it, because the
|
|
179
|
+
// list is correct.
|
|
180
|
+
let stateSchemaOpt = ReventlessCore.Plugin_Helpers.stateSchemaRegistry->Dict.get(name)
|
|
181
|
+
let ownerField = switch stateSchemaOpt {
|
|
182
|
+
| Some(s) => Reventless.Owner.fieldNames(s)->Array.get(0)
|
|
183
|
+
| None => None
|
|
184
|
+
}
|
|
185
|
+
let elevatedGroups = Reventless.OwnerScope.elevatedGroups()
|
|
186
|
+
|
|
187
|
+
// Read here for exactly the reason `ownerField` above is, and after making
|
|
188
|
+
// the same mistake: the by-id, by-ids and by-index resolvers are built below
|
|
189
|
+
// and this lookup used to sit past all of them, so they were generated
|
|
190
|
+
// without a retirement predicate — a list that withheld archived rows beside
|
|
191
|
+
// by-key doors that handed any one of them over on request.
|
|
192
|
+
let retiredSpec =
|
|
193
|
+
stateSchemaOpt
|
|
194
|
+
->Option.flatMap(Reventless.StateAnnotations.getSpec)
|
|
195
|
+
->Option.flatMap(spec => spec.retired)
|
|
196
|
+
let retiredField = retiredSpec->Option.map(r => r.field)
|
|
197
|
+
let retiredValues = retiredSpec->Option.flatMap(r => r.values)
|
|
198
|
+
|
|
170
199
|
let resolverByIdSingle = if includeIdParam {
|
|
171
200
|
makeQueryResolver(
|
|
172
201
|
~resolverName=fieldNameForSingle->String.capitalize,
|
|
173
202
|
~field=fieldNameForSingle->Pulumi.Input.make,
|
|
174
203
|
~code=switch subIdField {
|
|
175
|
-
| Some(sortField) =>
|
|
176
|
-
|
|
204
|
+
| Some(sortField) =>
|
|
205
|
+
Resolver.Functions.queryByIdSort(
|
|
206
|
+
sortField,
|
|
207
|
+
~ownerField?,
|
|
208
|
+
~elevatedGroups,
|
|
209
|
+
~retiredField?,
|
|
210
|
+
~retiredValues?,
|
|
211
|
+
)
|
|
212
|
+
| None =>
|
|
213
|
+
Resolver.Functions.getItemById(
|
|
214
|
+
~ownerField?,
|
|
215
|
+
~elevatedGroups,
|
|
216
|
+
~retiredField?,
|
|
217
|
+
~retiredValues?,
|
|
218
|
+
)
|
|
177
219
|
},
|
|
178
220
|
)
|
|
179
221
|
} else {
|
|
@@ -188,7 +230,11 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
188
230
|
makeQueryResolver(
|
|
189
231
|
~resolverName=fieldNameForSingle->String.capitalize ++ "Items",
|
|
190
232
|
~field=(fieldNameForSingle ++ "Items")->Pulumi.Input.make,
|
|
191
|
-
~code=Resolver.Functions.queryItemsWithSortConditions(
|
|
233
|
+
~code=Resolver.Functions.queryItemsWithSortConditions(
|
|
234
|
+
sortField,
|
|
235
|
+
~ownerField?,
|
|
236
|
+
~elevatedGroups,
|
|
237
|
+
),
|
|
192
238
|
)
|
|
193
239
|
)
|
|
194
240
|
} else {
|
|
@@ -203,7 +249,6 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
203
249
|
// registered or has no indexable fields — the resolver template emits the same
|
|
204
250
|
// SDL the in-memory adapter does, so the AWS Filter / OrderBy stays in lockstep
|
|
205
251
|
// with the SDL emitted by GraphQL_FragmentGenerator at runtime.
|
|
206
|
-
let stateSchemaOpt = ReventlessCore.Plugin_Helpers.stateSchemaRegistry->Dict.get(name)
|
|
207
252
|
let capability = switch stateSchemaOpt {
|
|
208
253
|
| Some(s) => ReventlessCore.GraphQL_FragmentGenerator.deriveServerCapability(~entityName=name, s)
|
|
209
254
|
| None => ReventlessCore.GraphQL_FragmentGenerator.emptyCapability
|
|
@@ -236,14 +281,8 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
236
281
|
// resolves `name: String!` to null → non-null violation that nulls the entire
|
|
237
282
|
// Platform_PluginConnection. Exclude them with an `attribute_exists(#name)` filter
|
|
238
283
|
// (prefix-agnostic: real plugin rows always carry `name`, internal rows never do).
|
|
239
|
-
// See docs/plans/platform-plugins-admin-connection-null-rows.md.
|
|
284
|
+
// See docs/plans/done/platform-plugins-admin-connection-null-rows.md.
|
|
240
285
|
let requireAttribute = internalRowRequiredAttr(name)
|
|
241
|
-
// Derived from the same schema `capability` came from, so the two cannot
|
|
242
|
-
// disagree about this read model's fields.
|
|
243
|
-
let ownerField = switch stateSchemaOpt {
|
|
244
|
-
| Some(s) => Reventless.Owner.fieldNames(s)->Array.get(0)
|
|
245
|
-
| None => None
|
|
246
|
-
}
|
|
247
286
|
// A DynamoDB FilterExpression is applied AFTER the page is read, so a scoped
|
|
248
287
|
// list over a table with no index on the owner field returns short pages —
|
|
249
288
|
// correct, but pathological once a caller owns a small fraction of the rows.
|
|
@@ -271,12 +310,6 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
271
310
|
}
|
|
272
311
|
| None => ()
|
|
273
312
|
}
|
|
274
|
-
let retiredSpec =
|
|
275
|
-
stateSchemaOpt
|
|
276
|
-
->Option.flatMap(Reventless.StateAnnotations.getSpec)
|
|
277
|
-
->Option.flatMap(spec => spec.retired)
|
|
278
|
-
let retiredField = retiredSpec->Option.map(r => r.field)
|
|
279
|
-
let retiredValues = retiredSpec->Option.flatMap(r => r.values)
|
|
280
313
|
// The same class of "works, but scans" mistake as the owner warning above,
|
|
281
314
|
// and the retirement case degrades the same way: the FilterExpression is
|
|
282
315
|
// applied after the page is read, so pages shrink as the archive's share of
|
|
@@ -310,7 +343,7 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
310
343
|
~sortFields=sortFieldNames,
|
|
311
344
|
~requireAttribute?,
|
|
312
345
|
~ownerField?,
|
|
313
|
-
~elevatedGroups
|
|
346
|
+
~elevatedGroups,
|
|
314
347
|
~retiredField?,
|
|
315
348
|
~retiredValues?,
|
|
316
349
|
)
|
|
@@ -319,26 +352,16 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
319
352
|
},
|
|
320
353
|
)
|
|
321
354
|
let resolversByIndex = indexes->Array.map(({index} as indexConfig) => {
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
let resolverName =
|
|
334
|
-
fieldNameForSingle->String.capitalize ++
|
|
335
|
-
"By" ++
|
|
336
|
-
(index->stripLeadingBy->String.capitalize)
|
|
337
|
-
let fieldName =
|
|
338
|
-
fieldNameForSingle ++
|
|
339
|
-
"By" ++
|
|
340
|
-
(index->stripLeadingBy->String.capitalize)
|
|
341
|
-
let idField = indexConfig.idField->Option.getOr(index)
|
|
355
|
+
// Name and key field both come from `GraphQL_FragmentGenerator`, which is
|
|
356
|
+
// where the SDL field this attaches to is derived. Deriving them here as
|
|
357
|
+
// well is how the two came to disagree: the emitted field declared `id`
|
|
358
|
+
// while this resolver read the index key, so the door could not be called.
|
|
359
|
+
let fieldName = ReventlessCore.GraphQL_FragmentGenerator.indexQueryFieldName(
|
|
360
|
+
~singleFieldName=fieldNameForSingle,
|
|
361
|
+
~index,
|
|
362
|
+
)
|
|
363
|
+
let resolverName = fieldName->String.capitalize
|
|
364
|
+
let idField = ReventlessCore.GraphQL_FragmentGenerator.indexKeyField(indexConfig)
|
|
342
365
|
switch indexConfig.authorization {
|
|
343
366
|
| None =>
|
|
344
367
|
makeQueryResolver(
|
|
@@ -346,8 +369,24 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
346
369
|
~field=fieldName->Pulumi.Input.make,
|
|
347
370
|
~code=switch indexConfig.subIdField {
|
|
348
371
|
| Some(sortField) =>
|
|
349
|
-
Resolver.Functions.queryByIndexSortFiltered(
|
|
350
|
-
|
|
372
|
+
Resolver.Functions.queryByIndexSortFiltered(
|
|
373
|
+
~index,
|
|
374
|
+
~idField,
|
|
375
|
+
~sortField,
|
|
376
|
+
~ownerField?,
|
|
377
|
+
~retiredField?,
|
|
378
|
+
~retiredValues?,
|
|
379
|
+
~elevatedGroups,
|
|
380
|
+
)
|
|
381
|
+
| None =>
|
|
382
|
+
Resolver.Functions.queryByIndexFiltered(
|
|
383
|
+
~index,
|
|
384
|
+
~idField,
|
|
385
|
+
~ownerField?,
|
|
386
|
+
~retiredField?,
|
|
387
|
+
~retiredValues?,
|
|
388
|
+
~elevatedGroups,
|
|
389
|
+
)
|
|
351
390
|
},
|
|
352
391
|
)
|
|
353
392
|
| Some({tableName, group}) =>
|
|
@@ -373,7 +412,20 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
373
412
|
~name=resolverName,
|
|
374
413
|
~api,
|
|
375
414
|
~dataSource=dataSourceName,
|
|
376
|
-
|
|
415
|
+
// No `~ownerField`, deliberately: this index declares its own
|
|
416
|
+
// authorization, and the rows it grants a group member are by
|
|
417
|
+
// construction owned by somebody else — an order assigned to a
|
|
418
|
+
// fulfilment operator belongs to the customer who placed it. Adding the
|
|
419
|
+
// owner predicate here would return nothing and revoke the access the
|
|
420
|
+
// auth table exists to grant. Retirement still applies: an archived row
|
|
421
|
+
// is withdrawn from everyone who has not asked, whoever owns it.
|
|
422
|
+
~code=Resolver.Functions.queryByIndexFiltered(
|
|
423
|
+
~index,
|
|
424
|
+
~idField,
|
|
425
|
+
~retiredField?,
|
|
426
|
+
~retiredValues?,
|
|
427
|
+
~elevatedGroups,
|
|
428
|
+
),
|
|
377
429
|
~opts,
|
|
378
430
|
)
|
|
379
431
|
// The interceptor leads the chain, as it does in every other Query
|
|
@@ -426,7 +478,43 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
426
478
|
~field=byIdsField->Pulumi.Input.make,
|
|
427
479
|
~code=generateCode(
|
|
428
480
|
~storageResource=storage,
|
|
429
|
-
~template=Resolver.Functions.batchGetItemsByIds
|
|
481
|
+
~template=Resolver.Functions.batchGetItemsByIds(
|
|
482
|
+
~ownerField?,
|
|
483
|
+
~retiredField?,
|
|
484
|
+
~retiredValues?,
|
|
485
|
+
~elevatedGroups,
|
|
486
|
+
),
|
|
487
|
+
),
|
|
488
|
+
),
|
|
489
|
+
)
|
|
490
|
+
} else {
|
|
491
|
+
None
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// The reference door. Registered under the same conditions as by-ids because
|
|
495
|
+
// it is the same read; what differs is the projection on the way out, and
|
|
496
|
+
// that a view which named its retired rows lets them through it.
|
|
497
|
+
//
|
|
498
|
+
// Not conditional on the annotation: `GraphQL_FragmentGenerator` emits the
|
|
499
|
+
// field for every view — one SDL serves every backend — so a view without a
|
|
500
|
+
// resolver here would be a field that errors rather than one that is absent.
|
|
501
|
+
let resolverRefs = if includeIdParam && subIdField === None {
|
|
502
|
+
let storage = storageResource(~pluginName=None, ~tableName=name)
|
|
503
|
+
let refsField = fieldNameForAll ++ "Refs"
|
|
504
|
+
Some(
|
|
505
|
+
makeQueryResolver(
|
|
506
|
+
~resolverName=refsField->String.capitalize,
|
|
507
|
+
~field=refsField->Pulumi.Input.make,
|
|
508
|
+
~code=generateCode(
|
|
509
|
+
~storageResource=storage,
|
|
510
|
+
~template=Resolver.Functions.refsByIds(
|
|
511
|
+
~labelField,
|
|
512
|
+
~retiredField,
|
|
513
|
+
~retiredValues,
|
|
514
|
+
~namedWhenRetired=retiredSpec->Option.mapOr(false, r => r.namedWhenRetired),
|
|
515
|
+
~ownerField?,
|
|
516
|
+
~elevatedGroups,
|
|
517
|
+
),
|
|
430
518
|
),
|
|
431
519
|
),
|
|
432
520
|
)
|
|
@@ -521,7 +609,11 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
521
609
|
| (None, Some(byIds)) => [resolverByIdSingle, resolverAll, byIds]
|
|
522
610
|
| (None, None) => [resolverByIdSingle, resolverAll]
|
|
523
611
|
}
|
|
524
|
-
|
|
612
|
+
// Appended rather than folded into `mainResolvers`' four-way switch: the door
|
|
613
|
+
// is independent of whether this view has a sub-id door, and adding a third
|
|
614
|
+
// dimension to that match would spell eight cases to say one thing.
|
|
615
|
+
let refsResolvers = resolverRefs->Option.mapOr([], r => [r])
|
|
616
|
+
Array.flat([mainResolvers, refsResolvers, resolversByIndex, idResolvers, idsResolvers])
|
|
525
617
|
->Array.map(Util.AppSync.toResourceNative)
|
|
526
618
|
}
|
|
527
619
|
|
|
@@ -91,10 +91,15 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
|
|
|
91
91
|
};
|
|
92
92
|
let fieldNameForAll = registryEntry !== undefined ? registryEntry.listFieldName : name$1 + "s";
|
|
93
93
|
let resourcesMaker = allQueryDbs => {
|
|
94
|
-
let resolverByIdSingle = includeIdParam ? makeQueryResolver(Stdlib_String.capitalize(fieldNameForSingle), fieldNameForSingle, subIdField !== undefined ? AppSync_Resolver_Functions$PulumiAws.queryByIdSort(subIdField) : AppSync_Resolver_Functions$PulumiAws.getItemById) : makeQueryResolver(Stdlib_String.capitalize(fieldNameForSingle), fieldNameForSingle, AppSync_Resolver_Functions$PulumiAws.listAllItems);
|
|
95
|
-
let resolverByIdMultiple = includeIdParam ? Stdlib_Option.map(subIdField, sortField => makeQueryResolver(Stdlib_String.capitalize(fieldNameForSingle) + "Items", fieldNameForSingle + "Items", AppSync_Resolver_Functions$PulumiAws.queryItemsWithSortConditions(sortField))) : undefined;
|
|
96
|
-
let labelField = registryEntry !== undefined ? Stdlib_Option.getOr(registryEntry.labelField, "id") : "id";
|
|
97
94
|
let stateSchemaOpt = Plugin_Helpers$ReventlessCore.stateSchemaRegistry[name$1];
|
|
95
|
+
let ownerField = stateSchemaOpt !== undefined ? Owner$Reventless.fieldNames(stateSchemaOpt)[0] : undefined;
|
|
96
|
+
let elevatedGroups = OwnerScope$Reventless.elevatedGroups();
|
|
97
|
+
let retiredSpec = Stdlib_Option.flatMap(Stdlib_Option.flatMap(stateSchemaOpt, StateAnnotations$Reventless.getSpec), spec => spec.retired);
|
|
98
|
+
let retiredField = Stdlib_Option.map(retiredSpec, r => r.field);
|
|
99
|
+
let retiredValues = Stdlib_Option.flatMap(retiredSpec, r => r.values);
|
|
100
|
+
let resolverByIdSingle = includeIdParam ? makeQueryResolver(Stdlib_String.capitalize(fieldNameForSingle), fieldNameForSingle, subIdField !== undefined ? AppSync_Resolver_Functions$PulumiAws.queryByIdSort(subIdField, ownerField, elevatedGroups, retiredField, retiredValues) : AppSync_Resolver_Functions$PulumiAws.getItemById(ownerField, elevatedGroups, retiredField, retiredValues)) : makeQueryResolver(Stdlib_String.capitalize(fieldNameForSingle), fieldNameForSingle, AppSync_Resolver_Functions$PulumiAws.listAllItems);
|
|
101
|
+
let resolverByIdMultiple = includeIdParam ? Stdlib_Option.map(subIdField, sortField => makeQueryResolver(Stdlib_String.capitalize(fieldNameForSingle) + "Items", fieldNameForSingle + "Items", AppSync_Resolver_Functions$PulumiAws.queryItemsWithSortConditions(sortField, ownerField, elevatedGroups))) : undefined;
|
|
102
|
+
let labelField = registryEntry !== undefined ? Stdlib_Option.getOr(registryEntry.labelField, "id") : "id";
|
|
98
103
|
let capability = stateSchemaOpt !== undefined ? GraphQL_FragmentGenerator$ReventlessCore.deriveServerCapability(name$1, stateSchemaOpt) : GraphQL_FragmentGenerator$ReventlessCore.emptyCapability;
|
|
99
104
|
let filterFieldNames = capability.filterFields.map(f => f.name);
|
|
100
105
|
let rangeFieldNames = Stdlib_Array.filterMap(capability.filterFields, f => {
|
|
@@ -110,7 +115,6 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
|
|
|
110
115
|
GraphQL_FragmentGenerator$ReventlessCore.validateScanSortAlignment(stateSchemaOpt, name$1, knownSortFields).forEach(msg => log.warn("QueryDbResolvers_AppSync", undefined, msg));
|
|
111
116
|
}
|
|
112
117
|
let requireAttribute = internalRowRequiredAttr(name$1);
|
|
113
|
-
let ownerField = stateSchemaOpt !== undefined ? Owner$Reventless.fieldNames(stateSchemaOpt)[0] : undefined;
|
|
114
118
|
OwnerScopeDiagnostics$ReventlessCore.warnIfNoElevatedGroups("QueryDbResolvers_AppSync", name$1, ownerField);
|
|
115
119
|
let isIndexed = f => {
|
|
116
120
|
if (indexes.some(ic => Stdlib_Option.getOr(ic.idField, ic.index) === f)) {
|
|
@@ -122,30 +126,20 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
|
|
|
122
126
|
if (ownerField !== undefined && !isIndexed(ownerField)) {
|
|
123
127
|
log.warn("QueryDbResolvers_AppSync", undefined, name$1 + `: @owner field "` + ownerField + `" is not the key of any index on this table. ` + "Owner-scoped reads will Scan and filter, so pages shrink as the caller's share of the rows falls. Add an @index on that field before this read model grows.");
|
|
124
128
|
}
|
|
125
|
-
let retiredSpec = Stdlib_Option.flatMap(Stdlib_Option.flatMap(stateSchemaOpt, StateAnnotations$Reventless.getSpec), spec => spec.retired);
|
|
126
|
-
let retiredField = Stdlib_Option.map(retiredSpec, r => r.field);
|
|
127
|
-
let retiredValues = Stdlib_Option.flatMap(retiredSpec, r => r.values);
|
|
128
129
|
if (retiredField !== undefined && !isIndexed(retiredField)) {
|
|
129
130
|
log.warn("QueryDbResolvers_AppSync", undefined, name$1 + `: @retired field "` + retiredField + `" is not the key of any index on this table. ` + "Reads that exclude retired rows will Scan and filter, so pages shrink as the archive's share of the rows grows. Add an @index on that field before this read model grows.");
|
|
130
131
|
}
|
|
131
|
-
let resolverAll = makeQueryResolver(Stdlib_String.capitalize(fieldNameForAll), fieldNameForAll, connectionSpec ? AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection(labelField, filterFieldNames, rangeFieldNames, sortFieldNames, requireAttribute, ownerField,
|
|
132
|
+
let resolverAll = makeQueryResolver(Stdlib_String.capitalize(fieldNameForAll), fieldNameForAll, connectionSpec ? AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection(labelField, filterFieldNames, rangeFieldNames, sortFieldNames, requireAttribute, ownerField, elevatedGroups, retiredField, retiredValues) : AppSync_Resolver_Functions$PulumiAws.listAllItems);
|
|
132
133
|
let resolversByIndex = indexes.map(indexConfig => {
|
|
133
134
|
let index = indexConfig.index;
|
|
134
|
-
let
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
} else {
|
|
138
|
-
return s;
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
let resolverName = Stdlib_String.capitalize(fieldNameForSingle) + "By" + Stdlib_String.capitalize(stripLeadingBy(index));
|
|
142
|
-
let fieldName = fieldNameForSingle + "By" + Stdlib_String.capitalize(stripLeadingBy(index));
|
|
143
|
-
let idField = Stdlib_Option.getOr(indexConfig.idField, index);
|
|
135
|
+
let fieldName = GraphQL_FragmentGenerator$ReventlessCore.indexQueryFieldName(fieldNameForSingle, index);
|
|
136
|
+
let resolverName = Stdlib_String.capitalize(fieldName);
|
|
137
|
+
let idField = GraphQL_FragmentGenerator$ReventlessCore.indexKeyField(indexConfig);
|
|
144
138
|
let match = indexConfig.authorization;
|
|
145
139
|
if (match !== undefined) {
|
|
146
140
|
let authDataSource = AppSync_DataSource$PulumiAws.makeDynamoDBDataSourceWithTableName(resolverName + "Auth", api, Util_DynamoDb$ReventlessAws.findResource(Util_QueryDb$ReventlessCore.getLocalStorageResources(allQueryDbs, match.tableName)).name, apiRole, opts);
|
|
147
141
|
let authFunction = AppSync_Function$PulumiAws.makeJs(resolverName + "Auth", api, authDataSource.name, AppSync_Resolver_Functions$PulumiAws.authorizeIndexedAccess(index, match.group), opts);
|
|
148
|
-
let queryFunction = AppSync_Function$PulumiAws.makeJs(resolverName, api, dataSourceName, AppSync_Resolver_Functions$PulumiAws.queryByIndexFiltered(index, idField), opts);
|
|
142
|
+
let queryFunction = AppSync_Function$PulumiAws.makeJs(resolverName, api, dataSourceName, AppSync_Resolver_Functions$PulumiAws.queryByIndexFiltered(index, idField, undefined, retiredField, retiredValues, elevatedGroups), opts);
|
|
149
143
|
let interceptorFn = interceptorFunction(resolverName);
|
|
150
144
|
return AppSync_Resolver_Retrying$ReventlessAws.makePipelineJsResolver(resolverName, api, "Query", fieldName, AppSync_Resolver_Functions$PulumiAws.pipelinePassThrough, interceptorFn !== undefined ? [
|
|
151
145
|
interceptorFn,
|
|
@@ -157,7 +151,7 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
|
|
|
157
151
|
], opts);
|
|
158
152
|
}
|
|
159
153
|
let sortField = indexConfig.subIdField;
|
|
160
|
-
return makeQueryResolver(resolverName, fieldName, sortField !== undefined ? AppSync_Resolver_Functions$PulumiAws.queryByIndexSortFiltered(index, idField, sortField) : AppSync_Resolver_Functions$PulumiAws.queryByIndexFiltered(index, idField));
|
|
154
|
+
return makeQueryResolver(resolverName, fieldName, sortField !== undefined ? AppSync_Resolver_Functions$PulumiAws.queryByIndexSortFiltered(index, idField, ownerField, sortField, retiredField, retiredValues, elevatedGroups) : AppSync_Resolver_Functions$PulumiAws.queryByIndexFiltered(index, idField, ownerField, retiredField, retiredValues, elevatedGroups));
|
|
161
155
|
});
|
|
162
156
|
let storageResource = (pluginName, tableName) => Adapter$ReventlessCore.outputToResource(Util_DynamoDb$ReventlessAws.findResourceInOutput(Plugin_Helpers$ReventlessCore.getStorageResources(allQueryDbs, pluginName, tableName)));
|
|
163
157
|
let generateCode = (storageResource, template) => storageResource.name.apply(realTableName => template(realTableName));
|
|
@@ -165,10 +159,18 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
|
|
|
165
159
|
if (includeIdParam && subIdField === undefined) {
|
|
166
160
|
let storage = storageResource(undefined, name$1);
|
|
167
161
|
let byIdsField = fieldNameForAll + "ByIds";
|
|
168
|
-
resolverByIds = makeQueryResolver(Stdlib_String.capitalize(byIdsField), byIdsField, generateCode(storage, AppSync_Resolver_Functions$PulumiAws.batchGetItemsByIds));
|
|
162
|
+
resolverByIds = makeQueryResolver(Stdlib_String.capitalize(byIdsField), byIdsField, generateCode(storage, AppSync_Resolver_Functions$PulumiAws.batchGetItemsByIds(ownerField, retiredField, retiredValues, elevatedGroups)));
|
|
169
163
|
} else {
|
|
170
164
|
resolverByIds = undefined;
|
|
171
165
|
}
|
|
166
|
+
let resolverRefs;
|
|
167
|
+
if (includeIdParam && subIdField === undefined) {
|
|
168
|
+
let storage$1 = storageResource(undefined, name$1);
|
|
169
|
+
let refsField = fieldNameForAll + "Refs";
|
|
170
|
+
resolverRefs = makeQueryResolver(Stdlib_String.capitalize(refsField), refsField, generateCode(storage$1, AppSync_Resolver_Functions$PulumiAws.refsByIds(labelField, retiredField, retiredValues, Stdlib_Option.mapOr(retiredSpec, false, r => r.namedWhenRetired), ownerField, elevatedGroups)));
|
|
171
|
+
} else {
|
|
172
|
+
resolverRefs = undefined;
|
|
173
|
+
}
|
|
172
174
|
let idResolvers = idResolverConfigs.map(config => {
|
|
173
175
|
let target = config.target;
|
|
174
176
|
let targetId = target.idField;
|
|
@@ -251,8 +253,10 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
|
|
|
251
253
|
resolverAll
|
|
252
254
|
]
|
|
253
255
|
);
|
|
256
|
+
let refsResolvers = Stdlib_Option.mapOr(resolverRefs, [], r => [r]);
|
|
254
257
|
return [
|
|
255
258
|
mainResolvers,
|
|
259
|
+
refsResolvers,
|
|
256
260
|
resolversByIndex,
|
|
257
261
|
idResolvers,
|
|
258
262
|
idsResolvers
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// Field names / includeIdParam / connectionSpec come from the same registry the
|
|
11
11
|
// AppSync path reads, so the SDL emitted by GraphQL_FragmentGenerator stays in
|
|
12
|
-
// lockstep. Kinds covered: getById, list (connection), index, byIds. Deferred to
|
|
12
|
+
// lockstep. Kinds covered: getById, list (connection), index, byIds, refs. Deferred to
|
|
13
13
|
// B3.2c: items (sub-id connection), @resolves/@resolvesMany, node, auth-table.
|
|
14
14
|
|
|
15
15
|
module Resolver = AppSync_Resolver_Retrying
|
|
@@ -150,13 +150,6 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
150
150
|
~opts,
|
|
151
151
|
)
|
|
152
152
|
|
|
153
|
-
let stripLeadingBy = s =>
|
|
154
|
-
if s->String.startsWith("by") && s->String.length > 2 {
|
|
155
|
-
s->String.slice(~start=2, ~end=s->String.length)
|
|
156
|
-
} else {
|
|
157
|
-
s
|
|
158
|
-
}
|
|
159
|
-
|
|
160
153
|
// Resolvers are deferred into resourcesMaker (created inside the schema-pushed
|
|
161
154
|
// builderOutputs.apply, like the AppSync path) — so they only exist after the
|
|
162
155
|
// fields are ACTIVE and after PgQueryResolver_Builder.provision has resolved
|
|
@@ -202,14 +195,28 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
202
195
|
[]
|
|
203
196
|
}
|
|
204
197
|
|
|
198
|
+
// The reference door — {list}Refs(ids). Same shape as by-ids and registered
|
|
199
|
+
// under the same conditions, because it is the same read: a set of keys in,
|
|
200
|
+
// rows out. What differs is on the way out, where the row is projected to
|
|
201
|
+
// what a reference needs and a retirement is lifted for a view that named its
|
|
202
|
+
// rows.
|
|
203
|
+
let refs = if includeIdParam && subIdField === None {
|
|
204
|
+
let refsField = fieldNameForAll ++ "Refs"
|
|
205
|
+
[mkResolver(~resolverName=refsField->String.capitalize, ~field=refsField, ~kind="refs")]
|
|
206
|
+
} else {
|
|
207
|
+
[]
|
|
208
|
+
}
|
|
209
|
+
|
|
205
210
|
// Per-index equality queries: {single}By{Index}. A group-restricted index
|
|
206
211
|
// (indexConfig.authorization) bakes the auth-table pipeline params — the
|
|
207
212
|
// Lambda verifies group membership + ownership against the auth read model.
|
|
208
213
|
let byIndex = indexes->Array.map((ic: indexConfig) => {
|
|
209
214
|
let index = ic.index
|
|
210
|
-
let
|
|
211
|
-
fieldNameForSingle
|
|
212
|
-
|
|
215
|
+
let field = ReventlessCore.GraphQL_FragmentGenerator.indexQueryFieldName(
|
|
216
|
+
~singleFieldName=fieldNameForSingle,
|
|
217
|
+
~index,
|
|
218
|
+
)
|
|
219
|
+
let resolverName = field->String.capitalize
|
|
213
220
|
switch ic.authorization {
|
|
214
221
|
| Some({tableName, group}) =>
|
|
215
222
|
mkResolver(
|
|
@@ -278,6 +285,7 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
278
285
|
[byId, all]
|
|
279
286
|
->Array.concat(items)
|
|
280
287
|
->Array.concat(byIds)
|
|
288
|
+
->Array.concat(refs)
|
|
281
289
|
->Array.concat(byIndex)
|
|
282
290
|
->Array.concat(idResolvers)
|
|
283
291
|
->Array.concat(idsResolvers)
|
|
@@ -7,6 +7,7 @@ import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/s
|
|
|
7
7
|
import * as AppSync_Resolver_Functions$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Resolver_Functions.res.mjs";
|
|
8
8
|
import * as PgQueryResolver_Builder$ReventlessAws from "./PgQueryResolver_Builder.res.mjs";
|
|
9
9
|
import * as AppSync_Resolver_Retrying$ReventlessAws from "../Api/AppSync_Resolver_Retrying.res.mjs";
|
|
10
|
+
import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
10
11
|
|
|
11
12
|
let identityBlock = `id != null && id.sub != null
|
|
12
13
|
? { userId: id.sub, username: id.username, groups: id.claims?.['cognito:groups'] ?? [], claims: id.claims, provider: 'Cognito' }
|
|
@@ -77,13 +78,6 @@ function make(name, api, param, dataSourceName, indexes, subIdField, idResolverC
|
|
|
77
78
|
PgQueryResolver_Builder$ReventlessAws.registerNodeType(returnTypeName, name$1);
|
|
78
79
|
}
|
|
79
80
|
let mkResolver = (resolverName, field, kind, index, authTable, authGroup) => AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(resolverName, api, dataSourceName, "Query", field, invokeTemplate(name$1, kind, index, authTable, authGroup), opts);
|
|
80
|
-
let stripLeadingBy = s => {
|
|
81
|
-
if (s.startsWith("by") && s.length > 2) {
|
|
82
|
-
return s.slice(2, s.length);
|
|
83
|
-
} else {
|
|
84
|
-
return s;
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
81
|
let resourcesMaker = _allQueryDbs => {
|
|
88
82
|
let byId = mkResolver(Stdlib_String.capitalize(fieldNameForSingle), fieldNameForSingle, includeIdParam ? "getById" : "list", undefined, undefined, undefined);
|
|
89
83
|
let all = mkResolver(Stdlib_String.capitalize(fieldNameForAll), fieldNameForAll, "list", undefined, undefined, undefined);
|
|
@@ -95,10 +89,17 @@ function make(name, api, param, dataSourceName, indexes, subIdField, idResolverC
|
|
|
95
89
|
} else {
|
|
96
90
|
byIds = [];
|
|
97
91
|
}
|
|
92
|
+
let refs;
|
|
93
|
+
if (includeIdParam && subIdField === undefined) {
|
|
94
|
+
let refsField = fieldNameForAll + "Refs";
|
|
95
|
+
refs = [mkResolver(Stdlib_String.capitalize(refsField), refsField, "refs", undefined, undefined, undefined)];
|
|
96
|
+
} else {
|
|
97
|
+
refs = [];
|
|
98
|
+
}
|
|
98
99
|
let byIndex = indexes.map(ic => {
|
|
99
100
|
let index = ic.index;
|
|
100
|
-
let
|
|
101
|
-
let
|
|
101
|
+
let field = GraphQL_FragmentGenerator$ReventlessCore.indexQueryFieldName(fieldNameForSingle, index);
|
|
102
|
+
let resolverName = Stdlib_String.capitalize(field);
|
|
102
103
|
let match = ic.authorization;
|
|
103
104
|
if (match !== undefined) {
|
|
104
105
|
return mkResolver(resolverName, field, "index", index, Stdlib_String.capitalize(match.tableName), match.group);
|
|
@@ -150,7 +151,7 @@ function make(name, api, param, dataSourceName, indexes, subIdField, idResolverC
|
|
|
150
151
|
return [
|
|
151
152
|
byId,
|
|
152
153
|
all
|
|
153
|
-
].concat(items).concat(byIds).concat(byIndex).concat(idResolvers).concat(idsResolvers).map(Util_AppSync$ReventlessAws.toResourceNative);
|
|
154
|
+
].concat(items).concat(byIds).concat(refs).concat(byIndex).concat(idResolvers).concat(idsResolvers).map(Util_AppSync$ReventlessAws.toResourceNative);
|
|
154
155
|
};
|
|
155
156
|
return {
|
|
156
157
|
resources: [],
|
|
@@ -147,7 +147,7 @@ let make: ReventlessCore.QueryDb_Adapter.queryEngineMaker = allQueryDbs => {
|
|
|
147
147
|
// B3.1: Postgres-backed read models have NO DynamoDB resource — exclude them
|
|
148
148
|
// instead of failing findResource at deploy time. They are not reachable via
|
|
149
149
|
// this engine (getRuntimeResource raises its explicit not-found error at
|
|
150
|
-
// query time); see docs/plans/aws-postgres-querydb-adapter.md § B3.1b.
|
|
150
|
+
// query time); see docs/plans/done/aws-postgres-querydb-adapter.md § B3.1b.
|
|
151
151
|
let dynamoDbQueryDbs =
|
|
152
152
|
allQueryDbs
|
|
153
153
|
->Dict.toArray
|
|
@@ -22,7 +22,7 @@ import { commandOutcomeToJson, runInlineAndCollect } from "@reventlessdev/revent
|
|
|
22
22
|
import { handleQueueEvent, publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
23
23
|
// Typed cold-start core — EventLog storage-ops selection/wiring, compiler-checked
|
|
24
24
|
// against the framework signatures (see the module header and
|
|
25
|
-
// docs/plans/minimize-lambda-entrypoint-mjs-shell.md).
|
|
25
|
+
// docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md).
|
|
26
26
|
import { makeStorageOps } from "./AggregateEntryPoint_Ops.res.mjs";
|
|
27
27
|
import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";
|
|
28
28
|
import { unmarshall } from "@aws-sdk/util-dynamodb";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Typed cold-start core for the Aggregate Lambda entry point.
|
|
2
2
|
//
|
|
3
|
-
// The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md).
|
|
3
|
+
// The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md).
|
|
4
4
|
// The `.mjs` shell owns the untyped seams — reading `HANDLER_CONFIG`, the dynamic
|
|
5
5
|
// `import()` of user Spec/Behavior modules, and the functor wiring that consumes
|
|
6
6
|
// those runtime-loaded modules (`EventLog_Operations.Make`, `Aggregate_Callback.Make`,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// by source URN.
|
|
5
5
|
//
|
|
6
6
|
// Thin untyped shell ("typed core, thin shell" —
|
|
7
|
-
// docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
|
|
7
|
+
// docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
|
|
8
8
|
// inherently-untyped seam — the dynamic `import()` of the spec + body modules
|
|
9
9
|
// named in HANDLER_CONFIG and the curried functor applications consuming them
|
|
10
10
|
// (AutomationSlice_Callback.Make(Spec)(Automation) /
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Typed cold-start core for the AutomationSlice / OutboundTranslationSlice
|
|
2
2
|
// Lambda entry point.
|
|
3
3
|
//
|
|
4
|
-
// The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
|
|
4
|
+
// The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
|
|
5
5
|
// AutomationSliceEntryPoint.mjs keeps only the inherently-untyped seam — the
|
|
6
6
|
// dynamic `import()` of the spec + body modules named in HANDLER_CONFIG and
|
|
7
7
|
// the curried callback-functor applications consuming them
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Routes DynamoDB Stream events from references and counts tables.
|
|
5
5
|
//
|
|
6
6
|
// Thin untyped shell ("typed core, thin shell" —
|
|
7
|
-
// docs/plans/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
|
|
7
|
+
// docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md): this file owns ONLY the
|
|
8
8
|
// seams that are inherently untyped — the dynamic `import()` of the
|
|
9
9
|
// target-spec/mappings modules named in HANDLER_CONFIG, the `patchSpecId`
|
|
10
10
|
// fix-up, and the MakeCounterHandler functor application consuming them.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Typed cold-start core for the Counter Lambda entry point.
|
|
2
2
|
//
|
|
3
|
-
// The "typed core, thin shell" split (docs/plans/minimize-lambda-entrypoint-mjs-shell.md):
|
|
3
|
+
// The "typed core, thin shell" split (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md):
|
|
4
4
|
// CounterEntryPoint.mjs keeps only the untyped seam — the dynamic `import()`
|
|
5
5
|
// of the target-spec/mappings modules named in HANDLER_CONFIG, the
|
|
6
6
|
// `patchSpecId` fix-up, and the EventMapper_Callback.MakeCounterHandler
|