@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
|
@@ -119,6 +119,19 @@ function lambdaConfigOf(input) {
|
|
|
119
119
|
return Stdlib_Option.flatMap(input["LambdaConfig"], Stdlib_JSON.Decode.object);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function v1Config(arn) {
|
|
123
|
+
return Object.fromEntries([
|
|
124
|
+
[
|
|
125
|
+
"LambdaArn",
|
|
126
|
+
arn
|
|
127
|
+
],
|
|
128
|
+
[
|
|
129
|
+
"LambdaVersion",
|
|
130
|
+
"V1_0"
|
|
131
|
+
]
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
|
|
122
135
|
globalThis.describe("Auth_ActiveRolePoolAttachment.mergedUpdateInput — settings survive the attach", () => {
|
|
123
136
|
let input = merged(triggerArn);
|
|
124
137
|
globalThis.test("MFA stays on rather than defaulting off", () => {
|
|
@@ -200,10 +213,84 @@ globalThis.describe("Auth_ActiveRolePoolAttachment.mergedUpdateInput — a pool
|
|
|
200
213
|
"Name",
|
|
201
214
|
"Bare"
|
|
202
215
|
]]), "eu-west-1_bare", triggerArn);
|
|
203
|
-
globalThis.expect(lambdaConfigOf(input)).toEqual(Object.fromEntries([
|
|
216
|
+
globalThis.expect(lambdaConfigOf(input)).toEqual(Object.fromEntries([
|
|
217
|
+
[
|
|
204
218
|
"PreTokenGeneration",
|
|
205
219
|
triggerArn
|
|
206
|
-
]
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
"PreTokenGenerationConfig",
|
|
223
|
+
v1Config(triggerArn)
|
|
224
|
+
]
|
|
225
|
+
]));
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
globalThis.describe("Auth_ActiveRolePoolAttachment.mergedUpdateInput — a pool already on the V2 field", () => {
|
|
230
|
+
let theirArn = "arn:aws:lambda:eu-west-1:1:function:TheirOwnPreToken";
|
|
231
|
+
let describedWithConfig = version => Object.fromEntries([
|
|
232
|
+
[
|
|
233
|
+
"Name",
|
|
234
|
+
"CustomerPool"
|
|
235
|
+
],
|
|
236
|
+
[
|
|
237
|
+
"LambdaConfig",
|
|
238
|
+
Object.fromEntries([
|
|
239
|
+
[
|
|
240
|
+
"PreSignUp",
|
|
241
|
+
"arn:aws:lambda:eu-west-1:1:function:TheirPreSignUp"
|
|
242
|
+
],
|
|
243
|
+
[
|
|
244
|
+
"PreTokenGeneration",
|
|
245
|
+
theirArn
|
|
246
|
+
],
|
|
247
|
+
[
|
|
248
|
+
"PreTokenGenerationConfig",
|
|
249
|
+
Object.fromEntries([
|
|
250
|
+
[
|
|
251
|
+
"LambdaArn",
|
|
252
|
+
theirArn
|
|
253
|
+
],
|
|
254
|
+
[
|
|
255
|
+
"LambdaVersion",
|
|
256
|
+
version
|
|
257
|
+
]
|
|
258
|
+
])
|
|
259
|
+
]
|
|
260
|
+
])
|
|
261
|
+
]
|
|
262
|
+
]);
|
|
263
|
+
let attachOnto = version => Auth_ActiveRolePoolAttachment$ReventlessAws.mergedUpdateInput(describedWithConfig(version), "eu-west-1_abc123", triggerArn);
|
|
264
|
+
globalThis.test("both fields name our trigger, and the same one", () => {
|
|
265
|
+
let config = lambdaConfigOf(attachOnto("V1_0"));
|
|
266
|
+
globalThis.expect([
|
|
267
|
+
Stdlib_Option.flatMap(config, c => c["PreTokenGeneration"]),
|
|
268
|
+
Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(config, c => c["PreTokenGenerationConfig"]), Stdlib_JSON.Decode.object), c => c["LambdaArn"])
|
|
269
|
+
]).toEqual([
|
|
270
|
+
triggerArn,
|
|
271
|
+
triggerArn
|
|
272
|
+
]);
|
|
273
|
+
});
|
|
274
|
+
globalThis.test("a pool that arrived on V2_0 is pinned back to the version we implement", () => {
|
|
275
|
+
globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(lambdaConfigOf(attachOnto("V2_0")), c => c["PreTokenGenerationConfig"]), Stdlib_JSON.Decode.object), c => c["LambdaVersion"])).toEqual("V1_0");
|
|
276
|
+
});
|
|
277
|
+
globalThis.test("the customer's other triggers still survive", () => {
|
|
278
|
+
globalThis.expect(Stdlib_Option.flatMap(lambdaConfigOf(attachOnto("V1_0")), c => c["PreSignUp"])).toEqual("arn:aws:lambda:eu-west-1:1:function:TheirPreSignUp");
|
|
279
|
+
});
|
|
280
|
+
globalThis.test("detaching removes both fields, not just the legacy one", () => {
|
|
281
|
+
let config = lambdaConfigOf(Auth_ActiveRolePoolAttachment$ReventlessAws.mergedUpdateInput(describedWithConfig("V1_0"), "eu-west-1_abc123", undefined));
|
|
282
|
+
globalThis.expect([
|
|
283
|
+
Stdlib_Option.flatMap(config, c => c["PreTokenGeneration"]),
|
|
284
|
+
Stdlib_Option.flatMap(config, c => c["PreTokenGenerationConfig"])
|
|
285
|
+
]).toEqual([
|
|
286
|
+
undefined,
|
|
287
|
+
undefined
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
globalThis.test("feeding the merge its own output back changes nothing", () => {
|
|
291
|
+
let once = attachOnto("V1_0");
|
|
292
|
+
let twice = Auth_ActiveRolePoolAttachment$ReventlessAws.mergedUpdateInput(once, "eu-west-1_abc123", triggerArn);
|
|
293
|
+
globalThis.expect(lambdaConfigOf(twice)).toEqual(lambdaConfigOf(once));
|
|
207
294
|
});
|
|
208
295
|
});
|
|
209
296
|
|
|
@@ -217,6 +304,33 @@ globalThis.describe("Auth_ActiveRolePoolAttachment.attachedTrigger", () => {
|
|
|
217
304
|
"Bare"
|
|
218
305
|
]]))).toEqual(undefined);
|
|
219
306
|
});
|
|
307
|
+
globalThis.test("reads the V2 field on a pool that only carries that one", () => {
|
|
308
|
+
globalThis.expect(Auth_ActiveRolePoolAttachment$ReventlessAws.attachedTrigger(Object.fromEntries([[
|
|
309
|
+
"LambdaConfig",
|
|
310
|
+
Object.fromEntries([[
|
|
311
|
+
"PreTokenGenerationConfig",
|
|
312
|
+
v1Config(triggerArn)
|
|
313
|
+
]])
|
|
314
|
+
]]))).toEqual(triggerArn);
|
|
315
|
+
});
|
|
316
|
+
globalThis.test("reports none for our trigger held at a version we do not implement", () => {
|
|
317
|
+
globalThis.expect(Auth_ActiveRolePoolAttachment$ReventlessAws.attachedTrigger(Object.fromEntries([[
|
|
318
|
+
"LambdaConfig",
|
|
319
|
+
Object.fromEntries([[
|
|
320
|
+
"PreTokenGenerationConfig",
|
|
321
|
+
Object.fromEntries([
|
|
322
|
+
[
|
|
323
|
+
"LambdaArn",
|
|
324
|
+
triggerArn
|
|
325
|
+
],
|
|
326
|
+
[
|
|
327
|
+
"LambdaVersion",
|
|
328
|
+
"V2_0"
|
|
329
|
+
]
|
|
330
|
+
])
|
|
331
|
+
]])
|
|
332
|
+
]]))).toEqual(undefined);
|
|
333
|
+
});
|
|
220
334
|
});
|
|
221
335
|
|
|
222
336
|
globalThis.describe("Auth_ActiveRolePoolAttachment.probeVerdict", () => {
|
|
@@ -259,5 +373,6 @@ export {
|
|
|
259
373
|
merged,
|
|
260
374
|
triggerArn,
|
|
261
375
|
lambdaConfigOf,
|
|
376
|
+
v1Config,
|
|
262
377
|
}
|
|
263
378
|
/* Not a pure module */
|
|
@@ -440,7 +440,7 @@ describe("Runtime.buildConditionalTransactItems — composite partition fences o
|
|
|
440
440
|
// pluginName}. The read is one exact `tag_composite` match, so the fence must be
|
|
441
441
|
// a SINGLE composite-key fence — not one per member. Per-member fencing made the
|
|
442
442
|
// low-cardinality prefix (`environment`, `platformName`) hot under a deploy
|
|
443
|
-
// fan-out. Plan: docs/plans/
|
|
443
|
+
// fan-out. Plan: docs/plans/done/dcb-hot-tag-fence-contention.md.
|
|
444
444
|
let event = (eventType, tags): ReventlessCore.DcbEventLog_Adapter.rawStoredEvent => {
|
|
445
445
|
eventType,
|
|
446
446
|
data: JSON.Object(Dict.make()),
|
|
@@ -563,7 +563,7 @@ describe("Runtime.toItem — an empty tag value skips its GSI attribute", () =>
|
|
|
563
563
|
// `tag_<key>` is a GSI hash key, and DynamoDB rejects an empty string for a key
|
|
564
564
|
// attribute — writing one fails the whole append. An empty value is legitimate
|
|
565
565
|
// (an absent composite-partition member), so the attribute is skipped and that
|
|
566
|
-
// index goes sparse. See docs/plans/dcb-empty-tag-values-break-append.md.
|
|
566
|
+
// index goes sparse. See docs/plans/done/dcb-empty-tag-values-break-append.md.
|
|
567
567
|
let tagOf = (key, value): Reventless.DcbTag.tag => {key, value}
|
|
568
568
|
let tags = [tagOf("environment", "prod"), tagOf("componentName", "")]
|
|
569
569
|
let storedEvent: ReventlessCore.DcbEventLog_Adapter.rawStoredEvent = {
|
|
@@ -57,7 +57,7 @@ describe("Runtime.buildTransactItems", () => {
|
|
|
57
57
|
})
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
// ─── Aggregate snapshots (docs/plans/aggregate-snapshotting.md) ───
|
|
60
|
+
// ─── Aggregate snapshots (docs/plans/done/aggregate-snapshotting.md) ───
|
|
61
61
|
|
|
62
62
|
describe("Runtime.replayQueryInput", () => {
|
|
63
63
|
testSync("default bounds cover exactly the zero-padded event range", () => {
|
|
@@ -95,6 +95,7 @@ let makeBinding = (
|
|
|
95
95
|
~ownerField=None,
|
|
96
96
|
~retiredField=None,
|
|
97
97
|
~retiredValues=None,
|
|
98
|
+
~namedWhenRetired=false,
|
|
98
99
|
(),
|
|
99
100
|
): PgQueryResolver_Lambda.binding => {
|
|
100
101
|
ops,
|
|
@@ -111,6 +112,7 @@ let makeBinding = (
|
|
|
111
112
|
ownerField,
|
|
112
113
|
retiredField,
|
|
113
114
|
retiredValues,
|
|
115
|
+
namedWhenRetired,
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
let mkPayload = (
|
|
@@ -121,12 +121,13 @@ let capability = {
|
|
|
121
121
|
sortFields: capability_sortFields
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
function makeBinding(authorizationOpt, subIdFieldOpt, ownerFieldOpt, retiredFieldOpt, retiredValuesOpt, param) {
|
|
124
|
+
function makeBinding(authorizationOpt, subIdFieldOpt, ownerFieldOpt, retiredFieldOpt, retiredValuesOpt, namedWhenRetiredOpt, param) {
|
|
125
125
|
let authorization = authorizationOpt !== undefined ? authorizationOpt : "AllowAnonymous";
|
|
126
126
|
let subIdField = subIdFieldOpt !== undefined ? Primitive_option.valFromOption(subIdFieldOpt) : undefined;
|
|
127
127
|
let ownerField = ownerFieldOpt !== undefined ? Primitive_option.valFromOption(ownerFieldOpt) : undefined;
|
|
128
128
|
let retiredField = retiredFieldOpt !== undefined ? Primitive_option.valFromOption(retiredFieldOpt) : undefined;
|
|
129
129
|
let retiredValues = retiredValuesOpt !== undefined ? Primitive_option.valFromOption(retiredValuesOpt) : undefined;
|
|
130
|
+
let namedWhenRetired = namedWhenRetiredOpt !== undefined ? namedWhenRetiredOpt : false;
|
|
130
131
|
return {
|
|
131
132
|
ops: ops,
|
|
132
133
|
pushdowns: pushdowns,
|
|
@@ -143,7 +144,8 @@ function makeBinding(authorizationOpt, subIdFieldOpt, ownerFieldOpt, retiredFiel
|
|
|
143
144
|
authorization: authorization,
|
|
144
145
|
ownerField: ownerField,
|
|
145
146
|
retiredField: retiredField,
|
|
146
|
-
retiredValues: retiredValues
|
|
147
|
+
retiredValues: retiredValues,
|
|
148
|
+
namedWhenRetired: namedWhenRetired
|
|
147
149
|
};
|
|
148
150
|
}
|
|
149
151
|
|
|
@@ -186,7 +188,7 @@ function ids(arr) {
|
|
|
186
188
|
|
|
187
189
|
globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
188
190
|
globalThis.test("getById returns the item (id-carrying)", async () => {
|
|
189
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
191
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
190
192
|
"id",
|
|
191
193
|
"p-2"
|
|
192
194
|
]]), undefined, undefined));
|
|
@@ -194,14 +196,14 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
194
196
|
globalThis.expect(str(r, "name")).toEqual("Bravo");
|
|
195
197
|
});
|
|
196
198
|
globalThis.test("getById miss → null", async () => {
|
|
197
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
199
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
198
200
|
"id",
|
|
199
201
|
"absent"
|
|
200
202
|
]]), undefined, undefined));
|
|
201
203
|
globalThis.expect(r).toBe(null);
|
|
202
204
|
});
|
|
203
205
|
globalThis.test("byIds returns matches, drops missing", async () => {
|
|
204
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
206
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
205
207
|
"ids",
|
|
206
208
|
[
|
|
207
209
|
"p-1",
|
|
@@ -215,7 +217,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
215
217
|
]);
|
|
216
218
|
});
|
|
217
219
|
globalThis.test("index routes to indexLookup on the idField", async () => {
|
|
218
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("index", "byStatus", Object.fromEntries([[
|
|
220
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("index", "byStatus", Object.fromEntries([[
|
|
219
221
|
"byStatus",
|
|
220
222
|
"active"
|
|
221
223
|
]]), undefined, undefined));
|
|
@@ -226,13 +228,13 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
226
228
|
});
|
|
227
229
|
globalThis.test("list with push-down → returns the connection listPage gives", async () => {
|
|
228
230
|
listPageReturn.contents = "PUSHED";
|
|
229
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined, undefined));
|
|
231
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined, undefined));
|
|
230
232
|
listPageReturn.contents = undefined;
|
|
231
233
|
globalThis.expect(r).toEqual("PUSHED");
|
|
232
234
|
});
|
|
233
235
|
globalThis.test("list without push-down → falls back to the shared spec", async () => {
|
|
234
236
|
listPageReturn.contents = undefined;
|
|
235
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined, undefined));
|
|
237
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined, undefined));
|
|
236
238
|
let edgeIds = Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(field(r, "edges"), Stdlib_JSON.Decode.array), []), e => Stdlib_Option.flatMap(field(e, "node"), n => str(n, "id")));
|
|
237
239
|
globalThis.expect(edgeIds).toEqual([
|
|
238
240
|
"p-1",
|
|
@@ -241,21 +243,21 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
241
243
|
]);
|
|
242
244
|
});
|
|
243
245
|
globalThis.test("items with subId routes to itemsPage (id echoed)", async () => {
|
|
244
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, "seq", undefined, undefined, undefined, undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
|
|
246
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, "seq", undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
|
|
245
247
|
"id",
|
|
246
248
|
"p-2"
|
|
247
249
|
]]), undefined, undefined));
|
|
248
250
|
globalThis.expect(str(r, "itemsFor")).toEqual("p-2");
|
|
249
251
|
});
|
|
250
252
|
globalThis.test("items without subId → empty connection", async () => {
|
|
251
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
|
|
253
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
|
|
252
254
|
"id",
|
|
253
255
|
"p-2"
|
|
254
256
|
]]), undefined, undefined));
|
|
255
257
|
globalThis.expect(Stdlib_Option.getOr(Stdlib_Option.flatMap(field(r, "edges"), Stdlib_JSON.Decode.array), []).length).toBe(0);
|
|
256
258
|
});
|
|
257
259
|
globalThis.test("authorization DenyAll → empty shape per kind", async () => {
|
|
258
|
-
let b = makeBinding("DenyAll", undefined, undefined, undefined, undefined, undefined);
|
|
260
|
+
let b = makeBinding("DenyAll", undefined, undefined, undefined, undefined, undefined, undefined);
|
|
259
261
|
let single = await PgQueryResolver_Lambda$ReventlessAws.dispatch(b, undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
260
262
|
"id",
|
|
261
263
|
"p-1"
|
|
@@ -274,7 +276,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
274
276
|
TAG: "Deny",
|
|
275
277
|
_0: "nope"
|
|
276
278
|
}));
|
|
277
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
279
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
278
280
|
"id",
|
|
279
281
|
"p-1"
|
|
280
282
|
]]), undefined, undefined));
|
|
@@ -300,7 +302,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
300
302
|
arguments: payload_arguments,
|
|
301
303
|
identity: Identity$Reventless.anonymous
|
|
302
304
|
};
|
|
303
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, payload);
|
|
305
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, payload);
|
|
304
306
|
globalThis.expect(str(r, "id")).toEqual("p-2");
|
|
305
307
|
});
|
|
306
308
|
globalThis.test("resolveOne multi via target index → array", async () => {
|
|
@@ -326,7 +328,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
326
328
|
arguments: payload_arguments,
|
|
327
329
|
identity: Identity$Reventless.anonymous
|
|
328
330
|
};
|
|
329
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, payload);
|
|
331
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, payload);
|
|
330
332
|
globalThis.expect(ids(r)).toEqual([
|
|
331
333
|
"p-1",
|
|
332
334
|
"p-3"
|
|
@@ -353,14 +355,14 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
353
355
|
arguments: payload_arguments,
|
|
354
356
|
identity: Identity$Reventless.anonymous
|
|
355
357
|
};
|
|
356
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, payload);
|
|
358
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, payload);
|
|
357
359
|
globalThis.expect(ids(r)).toEqual([
|
|
358
360
|
"p-1",
|
|
359
361
|
"p-3"
|
|
360
362
|
]);
|
|
361
363
|
});
|
|
362
364
|
globalThis.test("node decodes global id → __typename + item", async () => {
|
|
363
|
-
PgQueryResolver_Lambda$ReventlessAws.register("Things", makeBinding(undefined, undefined, undefined, undefined, undefined, undefined));
|
|
365
|
+
PgQueryResolver_Lambda$ReventlessAws.register("Things", makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined));
|
|
364
366
|
PgQueryResolver_Lambda$ReventlessAws.registerNodeType("Thing", "Things");
|
|
365
367
|
let payload_arguments = Object.fromEntries([[
|
|
366
368
|
"id",
|
|
@@ -410,7 +412,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
410
412
|
delete: 0,
|
|
411
413
|
deleteBatch: 0
|
|
412
414
|
};
|
|
413
|
-
let init = makeBinding(undefined, undefined, undefined, undefined, undefined, undefined);
|
|
415
|
+
let init = makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
414
416
|
let authBinding_pushdowns = init.pushdowns;
|
|
415
417
|
let authBinding_indexes = init.indexes;
|
|
416
418
|
let authBinding_subIdField = init.subIdField;
|
|
@@ -421,6 +423,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
421
423
|
let authBinding_ownerField = init.ownerField;
|
|
422
424
|
let authBinding_retiredField = init.retiredField;
|
|
423
425
|
let authBinding_retiredValues = init.retiredValues;
|
|
426
|
+
let authBinding_namedWhenRetired = init.namedWhenRetired;
|
|
424
427
|
let authBinding = {
|
|
425
428
|
ops: authOps,
|
|
426
429
|
pushdowns: authBinding_pushdowns,
|
|
@@ -432,7 +435,8 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
432
435
|
authorization: authBinding_authorization,
|
|
433
436
|
ownerField: authBinding_ownerField,
|
|
434
437
|
retiredField: authBinding_retiredField,
|
|
435
|
-
retiredValues: authBinding_retiredValues
|
|
438
|
+
retiredValues: authBinding_retiredValues,
|
|
439
|
+
namedWhenRetired: authBinding_namedWhenRetired
|
|
436
440
|
};
|
|
437
441
|
let lookupAuth = name => {
|
|
438
442
|
if (name === "AuthTable") {
|
|
@@ -458,18 +462,18 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
458
462
|
identity: ident(username, groups)
|
|
459
463
|
});
|
|
460
464
|
globalThis.test("auth index: group member who owns → results", async () => {
|
|
461
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("alice", ["Owner"]));
|
|
465
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("alice", ["Owner"]));
|
|
462
466
|
globalThis.expect(ids(r)).toEqual([
|
|
463
467
|
"p-1",
|
|
464
468
|
"p-3"
|
|
465
469
|
]);
|
|
466
470
|
});
|
|
467
471
|
globalThis.test("auth index: group member who does NOT own → empty", async () => {
|
|
468
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", ["Owner"]));
|
|
472
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", ["Owner"]));
|
|
469
473
|
globalThis.expect(Stdlib_Option.getOr(Stdlib_JSON.Decode.array(r), []).length).toBe(0);
|
|
470
474
|
});
|
|
471
475
|
globalThis.test("auth index: non-member passes through → results", async () => {
|
|
472
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", []));
|
|
476
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", []));
|
|
473
477
|
globalThis.expect(ids(r)).toEqual([
|
|
474
478
|
"p-1",
|
|
475
479
|
"p-3"
|
|
@@ -478,7 +482,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
478
482
|
globalThis.test("unmapped kind throws", async () => {
|
|
479
483
|
let threw;
|
|
480
484
|
try {
|
|
481
|
-
await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("frobnicate", undefined, undefined, undefined, undefined));
|
|
485
|
+
await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("frobnicate", undefined, undefined, undefined, undefined));
|
|
482
486
|
threw = false;
|
|
483
487
|
} catch (exn) {
|
|
484
488
|
threw = true;
|
|
@@ -489,35 +493,35 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
489
493
|
|
|
490
494
|
globalThis.describe("id form — the typed doors take either", () => {
|
|
491
495
|
globalThis.test("getById resolves a Relay global id", async () => {
|
|
492
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
496
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
493
497
|
"id",
|
|
494
498
|
Api_Ids$ReventlessCore.encode("Thing", "p-2")
|
|
495
499
|
]]), undefined, undefined));
|
|
496
500
|
globalThis.expect(str(r, "name")).toEqual("Bravo");
|
|
497
501
|
});
|
|
498
502
|
globalThis.test("getById reports the storage key it resolved to", async () => {
|
|
499
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
503
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
500
504
|
"id",
|
|
501
505
|
Api_Ids$ReventlessCore.encode("Thing", "p-2")
|
|
502
506
|
]]), undefined, undefined));
|
|
503
507
|
globalThis.expect(str(r, "id")).toEqual("p-2");
|
|
504
508
|
});
|
|
505
509
|
globalThis.test("getById still resolves a plain storage key", async () => {
|
|
506
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
510
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
507
511
|
"id",
|
|
508
512
|
"p-2"
|
|
509
513
|
]]), undefined, undefined));
|
|
510
514
|
globalThis.expect(str(r, "name")).toEqual("Bravo");
|
|
511
515
|
});
|
|
512
516
|
globalThis.test("getById on a global id for a row that does not exist is still null", async () => {
|
|
513
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
517
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
514
518
|
"id",
|
|
515
519
|
Api_Ids$ReventlessCore.encode("Thing", "absent")
|
|
516
520
|
]]), undefined, undefined));
|
|
517
521
|
globalThis.expect(r).toBe(null);
|
|
518
522
|
});
|
|
519
523
|
globalThis.test("byIds accepts the two forms mixed in one call", async () => {
|
|
520
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
524
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
521
525
|
"ids",
|
|
522
526
|
[
|
|
523
527
|
"p-1",
|
|
@@ -530,7 +534,7 @@ globalThis.describe("id form — the typed doors take either", () => {
|
|
|
530
534
|
]);
|
|
531
535
|
});
|
|
532
536
|
globalThis.test("byIds with every id found does not double-count", async () => {
|
|
533
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
537
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
534
538
|
"ids",
|
|
535
539
|
[
|
|
536
540
|
"p-1",
|
|
@@ -545,7 +549,7 @@ globalThis.describe("id form — the typed doors take either", () => {
|
|
|
545
549
|
});
|
|
546
550
|
|
|
547
551
|
globalThis.describe("owner scoping", () => {
|
|
548
|
-
let ownedBinding = () => makeBinding(undefined, undefined, "owner", undefined, undefined, undefined);
|
|
552
|
+
let ownedBinding = () => makeBinding(undefined, undefined, "owner", undefined, undefined, undefined, undefined);
|
|
549
553
|
let shopper = cognito("u-a", ["User"]);
|
|
550
554
|
let listWith = async (binding, identity) => {
|
|
551
555
|
lastOwnerScope.contents = undefined;
|
|
@@ -570,7 +574,7 @@ globalThis.describe("owner scoping", () => {
|
|
|
570
574
|
globalThis.expect(match[1]).toBe(undefined);
|
|
571
575
|
});
|
|
572
576
|
globalThis.test("a view with no owner field is never scoped", async () => {
|
|
573
|
-
let match = await listWith(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), shopper);
|
|
577
|
+
let match = await listWith(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined, undefined), shopper);
|
|
574
578
|
globalThis.expect(match[1]).toBe(undefined);
|
|
575
579
|
});
|
|
576
580
|
globalThis.test("an unidentified caller is refused before the push-down runs", async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
open JestGlobals
|
|
2
2
|
|
|
3
|
-
// Regression guard for docs/plans/platform-plugins-admin-connection-null-rows.md:
|
|
3
|
+
// Regression guard for docs/plans/done/platform-plugins-admin-connection-null-rows.md:
|
|
4
4
|
// the Plugins admin RM shares its DynamoDB table with internal bookkeeping rows
|
|
5
5
|
// (`deploy-schema:*`, `plugin-info:*`, `deploy-schema-hash:*`) that carry no `name`.
|
|
6
6
|
// The auto-generated Connection Scan must exclude them, or `name: String!` resolves
|
|
@@ -39,7 +39,7 @@ describe("AppSync_Resolver_Retrying.Functions.listAllItemsConnection", () => {
|
|
|
39
39
|
})
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
-
// Tripwires for docs/plans/aws-scan-connection-cursor-roundtrip.md. The Scan
|
|
42
|
+
// Tripwires for docs/plans/done/aws-scan-connection-cursor-roundtrip.md. The Scan
|
|
43
43
|
// resolver's cursor must round-trip DynamoDB's own continuation token, not a
|
|
44
44
|
// synthetic index — every list past page 1 was unreachable before this.
|
|
45
45
|
describe("listAllItemsConnection — Scan cursor round-trip (paging fixes 1–3)", () => {
|
|
@@ -4,7 +4,7 @@ open JestGlobals
|
|
|
4
4
|
// load-bearing contract: every code archive that ships the loader files MUST set
|
|
5
5
|
// NODE_OPTIONS=--import pointing at the shipped register-hook.mjs, and the two
|
|
6
6
|
// files must keep cross-referencing each other by the exact filenames used here.
|
|
7
|
-
// See docs/plans/deployed-lambda-esm-self-containment.md.
|
|
7
|
+
// See docs/plans/done/deployed-lambda-esm-self-containment.md.
|
|
8
8
|
|
|
9
9
|
describe("Util_Bundle ESM loader — env-var contract", () => {
|
|
10
10
|
testSync("NODE_OPTIONS --imports the shipped register-hook file by name", () => {
|
|
@@ -4,7 +4,7 @@ open JestGlobals
|
|
|
4
4
|
// packages its runtime import graph reaches (`companionModuleUrls`), the
|
|
5
5
|
// bundler carries them into the archive, and the archive build fails loudly on
|
|
6
6
|
// any statically imported package that would not resolve at cold start. See
|
|
7
|
-
// docs/plans/runtime-extension-companion-packages.md.
|
|
7
|
+
// docs/plans/done/runtime-extension-companion-packages.md.
|
|
8
8
|
|
|
9
9
|
// realpath because macOS hands out /var/... symlinks for tmpdir while the
|
|
10
10
|
// package-root walk reports paths as given.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// CommandAccepted -> Lambda:Unhandled.
|
|
12
12
|
//
|
|
13
13
|
// This is also the harness that unblocks moving the Aggregate functor wiring
|
|
14
|
-
// behind a typed core (docs/plans/minimize-lambda-entrypoint-mjs-shell.md,
|
|
14
|
+
// behind a typed core (docs/plans/done/minimize-lambda-entrypoint-mjs-shell.md,
|
|
15
15
|
// Tier 2.5): with it green, that refactor is verifiable end-to-end rather than
|
|
16
16
|
// only at compile time. Boots via the same Docker-gated
|
|
17
17
|
// `pnpm run test:integration` suite as the storage-runtime tests.
|
|
@@ -161,7 +161,7 @@ describe("DcbCommandTopicEntryPoint integration", () => {
|
|
|
161
161
|
)
|
|
162
162
|
|
|
163
163
|
// Residual composite-fence burst contention
|
|
164
|
-
// (docs/plans/dcb-composite-fence-residual-burst-contention.md).
|
|
164
|
+
// (docs/plans/done/dcb-composite-fence-residual-burst-contention.md).
|
|
165
165
|
//
|
|
166
166
|
// The entry point must derive `partitionTag = Composite(...)` and thread it
|
|
167
167
|
// into the DynamoDB `append`, so a `@compositePartitionTag` slice collapses to
|
|
@@ -331,7 +331,7 @@ describe("DCB DynamoDb integration — optimistic concurrency primitives", () =>
|
|
|
331
331
|
// on the WHOLE composite key, not on each member. Distinct entities sharing a
|
|
332
332
|
// low-cardinality prefix (environment/platformName/pluginName) must therefore NOT
|
|
333
333
|
// serialize on that prefix — the deploy-fan-out hot-fence regression. Plan:
|
|
334
|
-
// docs/plans/
|
|
334
|
+
// docs/plans/done/dcb-hot-tag-fence-contention.md § "Root-cause correction".
|
|
335
335
|
describe("DCB DynamoDb integration — composite partition hot-fence fix", () => {
|
|
336
336
|
let composite = (keys, seps): Reventless.DcbTag.derivedPartitionTag => Composite({keys, seps})
|
|
337
337
|
let specKeys = ["environment", "platformName", "pluginName", "resourceName"]
|
|
@@ -464,7 +464,7 @@ describe("DCB DynamoDb integration — per-type fence granularity", () => {
|
|
|
464
464
|
// DynamoDB used to reject the whole append: `tag_<key>` is a GSI hash key and a key
|
|
465
465
|
// attribute cannot hold an empty string. The adapter now skips that one attribute,
|
|
466
466
|
// leaving the index sparse — everything else about the event is unchanged.
|
|
467
|
-
// See docs/plans/dcb-empty-tag-values-break-append.md.
|
|
467
|
+
// See docs/plans/done/dcb-empty-tag-values-break-append.md.
|
|
468
468
|
describe("DCB DynamoDb integration — an empty tag value does not break the append", () => {
|
|
469
469
|
let tags = [tag("orderId", "O-empty"), tag("customerId", "")]
|
|
470
470
|
|