@reventlessdev/reventless-aws 3.0.0-alpha.302 → 3.0.0-alpha.304
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 +17 -0
- package/package.json +8 -8
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +21 -0
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +20 -0
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +41 -3
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +27 -6
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +34 -4
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +16 -5
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +12 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +10 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +4 -1
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +16 -2
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +4 -2
- package/src/adapter/Runtime/StateTopicPublish.mjs +38 -5
- package/src/adapter/StateTopic/StateTopic_AppSync.res +49 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +28 -3
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +73 -2
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +39 -4
- package/tests/PgQueryResolver_LambdaTest.res +16 -1
- package/tests/PgQueryResolver_LambdaTest.res.mjs +46 -32
- package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res +65 -0
- package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res.mjs +36 -0
- package/tests/QueryDbResolvers_AppSyncTest.res.mjs +3 -3
- package/tests/StateChangeDescriptorParityTest.res +82 -1
- package/tests/StateChangeDescriptorParityTest.res.mjs +97 -1
- package/tests/stateChangeDescriptorParity.mjs +72 -3
|
@@ -92,6 +92,10 @@ type streamEntry = {
|
|
|
92
92
|
tableName: Pulumi.Output.t<string>,
|
|
93
93
|
streamArn: Pulumi.Output.t<string>,
|
|
94
94
|
topicName: string,
|
|
95
|
+
// The read model's `@retired` field, resolved at deploy time because the relay
|
|
96
|
+
// Lambda has no plugin registry in process to resolve it at request time.
|
|
97
|
+
retiredField: option<string>,
|
|
98
|
+
retiredValues: option<array<string>>,
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
// One registry per events API, keyed by `eventsApi.name` (the static Pulumi
|
|
@@ -134,6 +138,8 @@ let makeForTable = (
|
|
|
134
138
|
~streamArn: Pulumi.Output.t<string>,
|
|
135
139
|
~partitionKeyName: Pulumi.Output.t<string>,
|
|
136
140
|
~topicName: string,
|
|
141
|
+
~retiredField: option<string>=?,
|
|
142
|
+
~retiredValues: option<array<string>>=?,
|
|
137
143
|
~eventsApi: AppSync_EventsApi.t,
|
|
138
144
|
~opts as _: Pulumi.CustomResourceOptions.t,
|
|
139
145
|
) => {
|
|
@@ -150,7 +156,12 @@ let makeForTable = (
|
|
|
150
156
|
|
|
151
157
|
let key = eventsApi.name
|
|
152
158
|
let entries = registry->Dict.get(key)->Option.getOr([])
|
|
153
|
-
registry->Dict.set(
|
|
159
|
+
registry->Dict.set(
|
|
160
|
+
key,
|
|
161
|
+
entries->Array.concat([
|
|
162
|
+
{tableName: checkedTableName, streamArn, topicName, retiredField, retiredValues},
|
|
163
|
+
]),
|
|
164
|
+
)
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
let make = (
|
|
@@ -176,6 +187,18 @@ let make = (
|
|
|
176
187
|
// `makeForTable` and can be keyed anything.
|
|
177
188
|
~partitionKeyName=StateTopic_AppSync_Helpers.entityKeyPartitionAttribute->Pulumi.Output.make,
|
|
178
189
|
~topicName,
|
|
190
|
+
// Resolved here, where the read model's name is known and its spec is
|
|
191
|
+
// registered in this process. The relay Lambda has neither.
|
|
192
|
+
~retiredField=?ReventlessCore.Plugin_Helpers.stateSchemaRegistry
|
|
193
|
+
->Dict.get(readModelName)
|
|
194
|
+
->Option.flatMap(Reventless.StateAnnotations.getSpec)
|
|
195
|
+
->Option.flatMap(spec => spec.retired)
|
|
196
|
+
->Option.map(r => r.field),
|
|
197
|
+
~retiredValues=?ReventlessCore.Plugin_Helpers.stateSchemaRegistry
|
|
198
|
+
->Dict.get(readModelName)
|
|
199
|
+
->Option.flatMap(Reventless.StateAnnotations.getSpec)
|
|
200
|
+
->Option.flatMap(spec => spec.retired)
|
|
201
|
+
->Option.flatMap(r => r.values),
|
|
179
202
|
~eventsApi,
|
|
180
203
|
~opts,
|
|
181
204
|
)
|
|
@@ -270,6 +293,30 @@ let finish = (
|
|
|
270
293
|
dict->JSON.Encode.object->JSON.stringify
|
|
271
294
|
})
|
|
272
295
|
|
|
296
|
+
// STATE_RETIRED_MAP env var — `{ <tableName>: {field, values?} }`, carrying
|
|
297
|
+
// only the tables that declare retirement. Built from the same awaited table
|
|
298
|
+
// names as the topic map so the two cannot describe different sets of tables.
|
|
299
|
+
// The whole predicate travels: the relay Lambda has no plugin registry, so a
|
|
300
|
+
// field without its states would leave it unable to tell the two forms apart.
|
|
301
|
+
let retiredMapJson =
|
|
302
|
+
entries
|
|
303
|
+
->Array.map(e => e.tableName)
|
|
304
|
+
->Pulumi.Output.all
|
|
305
|
+
->Pulumi.Output.apply(tableNames => {
|
|
306
|
+
let dict = Dict.make()
|
|
307
|
+
tableNames->Array.forEachWithIndex((tableName, i) => {
|
|
308
|
+
let entry = entries->Array.getUnsafe(i)
|
|
309
|
+
entry.retiredField->Option.forEach(f => {
|
|
310
|
+
let obj = Dict.fromArray([("field", f->JSON.Encode.string)])
|
|
311
|
+
entry.retiredValues->Option.forEach(vs =>
|
|
312
|
+
obj->Dict.set("values", vs->Array.map(JSON.Encode.string)->JSON.Encode.array)
|
|
313
|
+
)
|
|
314
|
+
dict->Dict.set(tableName, obj->JSON.Encode.object)
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
dict->JSON.Encode.object->JSON.stringify
|
|
318
|
+
})
|
|
319
|
+
|
|
273
320
|
// Shared Lambda — the compiled `_Ops` handler is identical for every
|
|
274
321
|
// stream-enabled RM; routing is per-record via STATE_TOPIC_MAP env var.
|
|
275
322
|
// Bundle reventless-aws (the handler + its node:crypto signer + the
|
|
@@ -325,6 +372,7 @@ let finish = (
|
|
|
325
372
|
("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
|
|
326
373
|
("APPSYNC_ENDPOINT", appsyncEndpoint->Pulumi.Output.asInput),
|
|
327
374
|
("STATE_TOPIC_MAP", topicMapJson->Pulumi.Output.asInput),
|
|
375
|
+
("STATE_RETIRED_MAP", retiredMapJson->Pulumi.Output.asInput),
|
|
328
376
|
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
329
377
|
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
330
378
|
Util_LambdaLogging.logLevelEntry(),
|
|
@@ -10,6 +10,8 @@ import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
|
|
|
10
10
|
import * as QueryDb$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb.res.mjs";
|
|
11
11
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
12
12
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
13
|
+
import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
|
|
14
|
+
import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
|
|
13
15
|
import * as Util_ReadModel$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_ReadModel.res.mjs";
|
|
14
16
|
import * as AppSync_EventsApi$ReventlessAws from "../Api/AppSync_EventsApi.res.mjs";
|
|
15
17
|
import * as Util_LambdaLogging$ReventlessAws from "../../util/Util_LambdaLogging.res.mjs";
|
|
@@ -18,7 +20,7 @@ import * as StateTopic_AppSync_Helpers$ReventlessAws from "./StateTopic_AppSync_
|
|
|
18
20
|
|
|
19
21
|
let registry = {};
|
|
20
22
|
|
|
21
|
-
function makeForTable(tableName, streamArn, partitionKeyName, topicName, eventsApi, param) {
|
|
23
|
+
function makeForTable(tableName, streamArn, partitionKeyName, topicName, retiredField, retiredValues, eventsApi, param) {
|
|
22
24
|
let checkedTableName = Pulumi.all([
|
|
23
25
|
tableName,
|
|
24
26
|
partitionKeyName
|
|
@@ -32,13 +34,15 @@ function makeForTable(tableName, streamArn, partitionKeyName, topicName, eventsA
|
|
|
32
34
|
registry[key] = entries.concat([{
|
|
33
35
|
tableName: checkedTableName,
|
|
34
36
|
streamArn: streamArn,
|
|
35
|
-
topicName: topicName
|
|
37
|
+
topicName: topicName,
|
|
38
|
+
retiredField: retiredField,
|
|
39
|
+
retiredValues: retiredValues
|
|
36
40
|
}]);
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
function make(readModelName, topicName, allQueryDbs, eventsApi, opts) {
|
|
40
44
|
let streamResource = Util_DynamoDbStream$ReventlessAws.findResource(Util_ReadModel$ReventlessCore.queryDbStorageResources(allQueryDbs, readModelName));
|
|
41
|
-
makeForTable(streamResource.name, Util_DynamoDbStream$ReventlessAws.streamArnFromDynamoDbTableResource(streamResource), Pulumi.output(StateTopic_AppSync_Helpers$ReventlessAws.entityKeyPartitionAttribute), topicName, eventsApi, opts);
|
|
45
|
+
makeForTable(streamResource.name, Util_DynamoDbStream$ReventlessAws.streamArnFromDynamoDbTableResource(streamResource), Pulumi.output(StateTopic_AppSync_Helpers$ReventlessAws.entityKeyPartitionAttribute), topicName, Stdlib_Option.map(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Plugin_Helpers$ReventlessCore.stateSchemaRegistry[readModelName], StateAnnotations$Reventless.getSpec), spec => spec.retired), r => r.field), Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Plugin_Helpers$ReventlessCore.stateSchemaRegistry[readModelName], StateAnnotations$Reventless.getSpec), spec => spec.retired), r => r.values), eventsApi, opts);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
function finish(eventsApi, opts) {
|
|
@@ -94,6 +98,23 @@ function finish(eventsApi, opts) {
|
|
|
94
98
|
});
|
|
95
99
|
return JSON.stringify(dict);
|
|
96
100
|
});
|
|
101
|
+
let retiredMapJson = Pulumi.all(entries.map(e => e.tableName)).apply(tableNames => {
|
|
102
|
+
let dict = {};
|
|
103
|
+
tableNames.forEach((tableName, i) => {
|
|
104
|
+
let entry = entries[i];
|
|
105
|
+
Stdlib_Option.forEach(entry.retiredField, f => {
|
|
106
|
+
let obj = Object.fromEntries([[
|
|
107
|
+
"field",
|
|
108
|
+
f
|
|
109
|
+
]]);
|
|
110
|
+
Stdlib_Option.forEach(entry.retiredValues, vs => {
|
|
111
|
+
obj["values"] = vs.map(prim => prim);
|
|
112
|
+
});
|
|
113
|
+
dict[tableName] = obj;
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
return JSON.stringify(dict);
|
|
117
|
+
});
|
|
97
118
|
let packageDirs = Object.fromEntries([[
|
|
98
119
|
"@reventlessdev/reventless-aws",
|
|
99
120
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
@@ -125,6 +146,10 @@ function finish(eventsApi, opts) {
|
|
|
125
146
|
"STATE_TOPIC_MAP",
|
|
126
147
|
topicMapJson
|
|
127
148
|
],
|
|
149
|
+
[
|
|
150
|
+
"STATE_RETIRED_MAP",
|
|
151
|
+
retiredMapJson
|
|
152
|
+
],
|
|
128
153
|
[
|
|
129
154
|
"NODE_OPTIONS",
|
|
130
155
|
Util_Bundle$ReventlessAws.esmLoaderNodeOptions
|
|
@@ -53,6 +53,55 @@ type event = {@as("Records") records: array<record>}
|
|
|
53
53
|
|
|
54
54
|
// ── Per-record derivations (ports of the former inline JS helpers) ───────────
|
|
55
55
|
|
|
56
|
+
// STATE_RETIRED_MAP: `{ <tableName>: {field, values?} }`, injected at deploy time
|
|
57
|
+
// beside STATE_TOPIC_MAP. This handler runs with no plugin registry in process —
|
|
58
|
+
// there is nothing here that could read a state schema — so the whole predicate
|
|
59
|
+
// has to arrive the same way the topic routing does. A table absent from the map
|
|
60
|
+
// declares no retirement, which is every table until one does.
|
|
61
|
+
//
|
|
62
|
+
// `values` absent is the boolean form. Carried as an object rather than as an
|
|
63
|
+
// encoded string because a `field=Value` convention is a parser this side and a
|
|
64
|
+
// writer the other, and the two drift the first time a state name contains the
|
|
65
|
+
// separator — an argument that only gets stronger now that a lifecycle can name
|
|
66
|
+
// several states.
|
|
67
|
+
let retiredMap: dict<Reventless.OwnerScope.retiredScope> =
|
|
68
|
+
switch NodeProcess.env
|
|
69
|
+
->Dict.get("STATE_RETIRED_MAP")
|
|
70
|
+
->Option.getOr("{}")
|
|
71
|
+
->JSON.parseOrThrow
|
|
72
|
+
->JSON.Decode.object {
|
|
73
|
+
| Some(d) =>
|
|
74
|
+
let out = Dict.make()
|
|
75
|
+
d->Dict.forEachWithKey((v, k) =>
|
|
76
|
+
v
|
|
77
|
+
->JSON.Decode.object
|
|
78
|
+
->Option.flatMap(o =>
|
|
79
|
+
o
|
|
80
|
+
->Dict.get("field")
|
|
81
|
+
->Option.flatMap(JSON.Decode.string)
|
|
82
|
+
->Option.map(field => {
|
|
83
|
+
Reventless.OwnerScope.field,
|
|
84
|
+
values: o
|
|
85
|
+
->Dict.get("values")
|
|
86
|
+
->Option.flatMap(JSON.Decode.array)
|
|
87
|
+
->Option.map(vs => vs->Array.filterMap(JSON.Decode.string)),
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
->Option.forEach(scope => out->Dict.set(k, scope))
|
|
91
|
+
)
|
|
92
|
+
out
|
|
93
|
+
| None => Dict.make()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let tableNameFromEventSourceArn = (arn: string): option<string> => {
|
|
97
|
+
let parts = arn->String.split("/")
|
|
98
|
+
switch (parts->Array.get(0), parts->Array.get(1), parts->Array.get(2)) {
|
|
99
|
+
| (Some(prefix), Some(tableName), Some("stream")) if prefix->String.endsWith(":table") =>
|
|
100
|
+
Some(tableName)
|
|
101
|
+
| _ => None
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
56
105
|
// Map record.eventSourceARN → topicRoot via STATE_TOPIC_MAP. Stream ARNs look
|
|
57
106
|
// like arn:aws:dynamodb:<region>:<acct>:table/<TableName>/stream/<ts>; split on
|
|
58
107
|
// "/" gives [ "...:table", "<TableName>", "stream", "<ts>" ].
|
|
@@ -131,18 +180,34 @@ let makeDescriptor = (
|
|
|
131
180
|
~entityKey: string,
|
|
132
181
|
~image: dict<JSON.t>,
|
|
133
182
|
~seq: option<string>,
|
|
183
|
+
~retiredField: option<string>=?,
|
|
184
|
+
~retiredValues: option<array<string>>=?,
|
|
134
185
|
): JSON.t => {
|
|
135
186
|
let removed = changeKind == "Removed"
|
|
187
|
+
// A retired row publishes as metadata only, for the reason the other two
|
|
188
|
+
// implementations do it: this channel reaches every subscriber of the view,
|
|
189
|
+
// and a payload would deliver the row to the callers the resolvers refuse it
|
|
190
|
+
// to. `Updated` with no state is the shape an oversized row already takes.
|
|
191
|
+
// Both forms are the one question `isRetiredValue` answers, so nothing here
|
|
192
|
+
// branches on which the view declared.
|
|
193
|
+
let retired = switch retiredField {
|
|
194
|
+
| Some(field) =>
|
|
195
|
+
!removed &&
|
|
196
|
+
{Reventless.OwnerScope.field, values: retiredValues}->Reventless.OwnerScope.isRetiredValue(
|
|
197
|
+
image->Dict.get(field),
|
|
198
|
+
)
|
|
199
|
+
| None => false
|
|
200
|
+
}
|
|
136
201
|
let descriptor = Dict.make()
|
|
137
202
|
descriptor->Dict.set("changeKind", JSON.Encode.string(changeKind))
|
|
138
203
|
descriptor->Dict.set("id", JSON.Encode.string(entityKey))
|
|
139
|
-
if !removed {
|
|
204
|
+
if !removed && !retired {
|
|
140
205
|
pickSortKeyValue(image)->Option.forEach(v =>
|
|
141
206
|
descriptor->Dict.set("sortKeyValue", JSON.Encode.string(v))
|
|
142
207
|
)
|
|
143
208
|
}
|
|
144
209
|
seq->Option.forEach(s => descriptor->Dict.set("seq", JSON.Encode.string(s)))
|
|
145
|
-
if !removed {
|
|
210
|
+
if !removed && !retired {
|
|
146
211
|
let state = image->JSON.Encode.object
|
|
147
212
|
let encoded = state->JSON.stringify
|
|
148
213
|
if encoded->String.length <= maxStateChars {
|
|
@@ -191,6 +256,12 @@ let processRecord = async (
|
|
|
191
256
|
~entityKey,
|
|
192
257
|
~image=unmarshalled,
|
|
193
258
|
~seq=dynamodb.sequenceNumber,
|
|
259
|
+
~retiredField=?tableNameFromEventSourceArn(record.eventSourceARN)
|
|
260
|
+
->Option.flatMap(t => retiredMap->Dict.get(t))
|
|
261
|
+
->Option.map(scope => scope.Reventless.OwnerScope.field),
|
|
262
|
+
~retiredValues=?tableNameFromEventSourceArn(record.eventSourceARN)
|
|
263
|
+
->Option.flatMap(t => retiredMap->Dict.get(t))
|
|
264
|
+
->Option.flatMap(scope => scope.Reventless.OwnerScope.values),
|
|
194
265
|
)
|
|
195
266
|
let body =
|
|
196
267
|
Dict.fromArray([
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
3
4
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
5
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
6
|
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
@@ -7,6 +8,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
7
8
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
8
9
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
9
10
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
11
|
+
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
10
12
|
import * as DynamoDb_Util_Helpers$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/DynamoDb_Util_Helpers.res.mjs";
|
|
11
13
|
import * as AppSyncEventsSigner_Ops$ReventlessAws from "../Api/AppSyncEventsSigner_Ops.res.mjs";
|
|
12
14
|
|
|
@@ -22,6 +24,33 @@ let topicMap = obj !== undefined ? Object.fromEntries(Stdlib_Array.filterMap(Obj
|
|
|
22
24
|
]);
|
|
23
25
|
})) : ({});
|
|
24
26
|
|
|
27
|
+
let d = Stdlib_JSON.Decode.object(JSON.parse(Stdlib_Option.getOr(process.env["STATE_RETIRED_MAP"], "{}")));
|
|
28
|
+
|
|
29
|
+
let retiredMap;
|
|
30
|
+
|
|
31
|
+
if (d !== undefined) {
|
|
32
|
+
let out = {};
|
|
33
|
+
Stdlib_Dict.forEachWithKey(d, (v, k) => Stdlib_Option.forEach(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(v), o => Stdlib_Option.map(Stdlib_Option.flatMap(o["field"], Stdlib_JSON.Decode.string), field => ({
|
|
34
|
+
field: field,
|
|
35
|
+
values: Stdlib_Option.map(Stdlib_Option.flatMap(o["values"], Stdlib_JSON.Decode.array), vs => Stdlib_Array.filterMap(vs, Stdlib_JSON.Decode.string))
|
|
36
|
+
}))), scope => {
|
|
37
|
+
out[k] = scope;
|
|
38
|
+
}));
|
|
39
|
+
retiredMap = out;
|
|
40
|
+
} else {
|
|
41
|
+
retiredMap = {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function tableNameFromEventSourceArn(arn) {
|
|
45
|
+
let parts = arn.split("/");
|
|
46
|
+
let match = parts[0];
|
|
47
|
+
let match$1 = parts[1];
|
|
48
|
+
let match$2 = parts[2];
|
|
49
|
+
if (match !== undefined && match$1 !== undefined && match$2 !== undefined && match$2 === "stream" && match.endsWith(":table")) {
|
|
50
|
+
return match$1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
25
54
|
function topicRootFromEventSourceArn(arn) {
|
|
26
55
|
let parts = arn.split("/");
|
|
27
56
|
let match = parts[0];
|
|
@@ -89,12 +118,16 @@ function pickSortKeyValue(image) {
|
|
|
89
118
|
}
|
|
90
119
|
}
|
|
91
120
|
|
|
92
|
-
function makeDescriptor(changeKind, entityKey, image, seq) {
|
|
121
|
+
function makeDescriptor(changeKind, entityKey, image, seq, retiredField, retiredValues) {
|
|
93
122
|
let removed = changeKind === "Removed";
|
|
123
|
+
let retired = retiredField !== undefined ? !removed && OwnerScope$Reventless.isRetiredValue({
|
|
124
|
+
field: retiredField,
|
|
125
|
+
values: retiredValues
|
|
126
|
+
}, image[retiredField]) : false;
|
|
94
127
|
let descriptor = {};
|
|
95
128
|
descriptor["changeKind"] = changeKind;
|
|
96
129
|
descriptor["id"] = entityKey;
|
|
97
|
-
if (!removed) {
|
|
130
|
+
if (!removed && !retired) {
|
|
98
131
|
Stdlib_Option.forEach(pickSortKeyValue(image), v => {
|
|
99
132
|
descriptor["sortKeyValue"] = v;
|
|
100
133
|
});
|
|
@@ -102,7 +135,7 @@ function makeDescriptor(changeKind, entityKey, image, seq) {
|
|
|
102
135
|
Stdlib_Option.forEach(seq, s => {
|
|
103
136
|
descriptor["seq"] = s;
|
|
104
137
|
});
|
|
105
|
-
if (!removed) {
|
|
138
|
+
if (!removed && !retired) {
|
|
106
139
|
let encoded = JSON.stringify(image);
|
|
107
140
|
if (encoded.length <= 61440) {
|
|
108
141
|
descriptor["state"] = image;
|
|
@@ -127,7 +160,7 @@ async function processRecord(record, region, creds) {
|
|
|
127
160
|
let entityKey = entityKeyFromRecord(dynamodb);
|
|
128
161
|
let channel = `/default/` + topicRoot + `/` + AppSyncEventsSigner_Ops$ReventlessAws.pathSegment(entityKey);
|
|
129
162
|
let unmarshalled = DynamoDb_Util_Helpers$AwsSdk.unmarshallDict(undefined, image);
|
|
130
|
-
let descriptor = makeDescriptor(changeKindFor(record.eventName), entityKey, unmarshalled, dynamodb.SequenceNumber);
|
|
163
|
+
let descriptor = makeDescriptor(changeKindFor(record.eventName), entityKey, unmarshalled, dynamodb.SequenceNumber, Stdlib_Option.map(Stdlib_Option.flatMap(tableNameFromEventSourceArn(record.eventSourceARN), t => retiredMap[t]), scope => scope.field), Stdlib_Option.flatMap(Stdlib_Option.flatMap(tableNameFromEventSourceArn(record.eventSourceARN), t => retiredMap[t]), scope => scope.values));
|
|
131
164
|
let body = JSON.stringify(Object.fromEntries([
|
|
132
165
|
[
|
|
133
166
|
"id",
|
|
@@ -193,6 +226,8 @@ let maxStateChars = 61440;
|
|
|
193
226
|
export {
|
|
194
227
|
endpoint,
|
|
195
228
|
topicMap,
|
|
229
|
+
retiredMap,
|
|
230
|
+
tableNameFromEventSourceArn,
|
|
196
231
|
topicRootFromEventSourceArn,
|
|
197
232
|
jsonToString,
|
|
198
233
|
entityKeyFromRecord,
|
|
@@ -52,6 +52,8 @@ let ops: QueryDb_Adapter.operations = {
|
|
|
52
52
|
deleteBatch: Obj.magic(0),
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
let lastRetiredScope: ref<option<Reventless.OwnerScope.retiredScope>> = ref(None)
|
|
56
|
+
|
|
55
57
|
let pushdowns: PgQueryResolver_Lambda.pushdowns = {
|
|
56
58
|
indexLookup: async (~readModelName as _, field, value) =>
|
|
57
59
|
allItems()->Array.filter(i => fieldEq(i, field, value)),
|
|
@@ -63,11 +65,20 @@ let pushdowns: PgQueryResolver_Lambda.pushdowns = {
|
|
|
63
65
|
~capability as _,
|
|
64
66
|
~labelField as _,
|
|
65
67
|
~ownerScope: option<(string, string)>=?,
|
|
68
|
+
~retiredScope: option<Reventless.OwnerScope.retiredScope>=?,
|
|
66
69
|
) => {
|
|
67
70
|
lastOwnerScope := ownerScope
|
|
71
|
+
lastRetiredScope := retiredScope
|
|
68
72
|
listPageReturn.contents
|
|
69
73
|
},
|
|
70
|
-
itemsPage: async (
|
|
74
|
+
itemsPage: async (
|
|
75
|
+
~readModelName as _,
|
|
76
|
+
~subIdField as _,
|
|
77
|
+
~id,
|
|
78
|
+
~argsDict as _,
|
|
79
|
+
~ownerScope as _=?,
|
|
80
|
+
~retiredScope as _=?,
|
|
81
|
+
) =>
|
|
71
82
|
// Sentinel echoing the requested id, so the dispatch routing is observable.
|
|
72
83
|
JSON.Encode.object(Dict.fromArray([("itemsFor", JSON.Encode.string(id))])),
|
|
73
84
|
scanAll: async (~readModelName as _) => allItems(),
|
|
@@ -82,6 +93,8 @@ let makeBinding = (
|
|
|
82
93
|
~authorization=Reventless.Authorization.AllowAnonymous,
|
|
83
94
|
~subIdField=None,
|
|
84
95
|
~ownerField=None,
|
|
96
|
+
~retiredField=None,
|
|
97
|
+
~retiredValues=None,
|
|
85
98
|
(),
|
|
86
99
|
): PgQueryResolver_Lambda.binding => {
|
|
87
100
|
ops,
|
|
@@ -96,6 +109,8 @@ let makeBinding = (
|
|
|
96
109
|
includeIdParam: true,
|
|
97
110
|
authorization,
|
|
98
111
|
ownerField,
|
|
112
|
+
retiredField,
|
|
113
|
+
retiredValues,
|
|
99
114
|
}
|
|
100
115
|
|
|
101
116
|
let mkPayload = (
|