@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.
Files changed (26) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +8 -8
  3. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +21 -0
  4. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +20 -0
  5. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +41 -3
  6. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +27 -6
  7. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +34 -4
  8. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +16 -5
  9. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +12 -0
  10. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +10 -0
  11. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +4 -1
  12. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +16 -2
  13. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +4 -2
  14. package/src/adapter/Runtime/StateTopicPublish.mjs +38 -5
  15. package/src/adapter/StateTopic/StateTopic_AppSync.res +49 -1
  16. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +28 -3
  17. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +73 -2
  18. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +39 -4
  19. package/tests/PgQueryResolver_LambdaTest.res +16 -1
  20. package/tests/PgQueryResolver_LambdaTest.res.mjs +46 -32
  21. package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res +65 -0
  22. package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res.mjs +36 -0
  23. package/tests/QueryDbResolvers_AppSyncTest.res.mjs +3 -3
  24. package/tests/StateChangeDescriptorParityTest.res +82 -1
  25. package/tests/StateChangeDescriptorParityTest.res.mjs +97 -1
  26. package/tests/stateChangeDescriptorParity.mjs +72 -3
@@ -68,6 +68,10 @@ let ops = {
68
68
  deleteBatch: 0
69
69
  };
70
70
 
71
+ let lastRetiredScope = {
72
+ contents: undefined
73
+ };
74
+
71
75
  async function pushdowns_indexLookup(param, field, value) {
72
76
  return Object.values(store).filter(i => fieldEq(i, field, value));
73
77
  }
@@ -76,12 +80,13 @@ async function pushdowns_byIds(param, ids) {
76
80
  return Stdlib_Array.filterMap(ids, id => store[id]);
77
81
  }
78
82
 
79
- async function pushdowns_listPage(param, param$1, param$2, param$3, ownerScope) {
83
+ async function pushdowns_listPage(param, param$1, param$2, param$3, ownerScope, retiredScope) {
80
84
  lastOwnerScope.contents = ownerScope;
85
+ lastRetiredScope.contents = retiredScope;
81
86
  return listPageReturn.contents;
82
87
  }
83
88
 
84
- async function pushdowns_itemsPage(param, param$1, id, param$2, param$3) {
89
+ async function pushdowns_itemsPage(param, param$1, id, param$2, param$3, param$4) {
85
90
  return Object.fromEntries([[
86
91
  "itemsFor",
87
92
  id
@@ -116,10 +121,12 @@ let capability = {
116
121
  sortFields: capability_sortFields
117
122
  };
118
123
 
119
- function makeBinding(authorizationOpt, subIdFieldOpt, ownerFieldOpt, param) {
124
+ function makeBinding(authorizationOpt, subIdFieldOpt, ownerFieldOpt, retiredFieldOpt, retiredValuesOpt, param) {
120
125
  let authorization = authorizationOpt !== undefined ? authorizationOpt : "AllowAnonymous";
121
126
  let subIdField = subIdFieldOpt !== undefined ? Primitive_option.valFromOption(subIdFieldOpt) : undefined;
122
127
  let ownerField = ownerFieldOpt !== undefined ? Primitive_option.valFromOption(ownerFieldOpt) : undefined;
128
+ let retiredField = retiredFieldOpt !== undefined ? Primitive_option.valFromOption(retiredFieldOpt) : undefined;
129
+ let retiredValues = retiredValuesOpt !== undefined ? Primitive_option.valFromOption(retiredValuesOpt) : undefined;
123
130
  return {
124
131
  ops: ops,
125
132
  pushdowns: pushdowns,
@@ -134,7 +141,9 @@ function makeBinding(authorizationOpt, subIdFieldOpt, ownerFieldOpt, param) {
134
141
  labelField: "name",
135
142
  includeIdParam: true,
136
143
  authorization: authorization,
137
- ownerField: ownerField
144
+ ownerField: ownerField,
145
+ retiredField: retiredField,
146
+ retiredValues: retiredValues
138
147
  };
139
148
  }
140
149
 
@@ -177,7 +186,7 @@ function ids(arr) {
177
186
 
178
187
  globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
179
188
  globalThis.test("getById returns the item (id-carrying)", async () => {
180
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
189
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
181
190
  "id",
182
191
  "p-2"
183
192
  ]]), undefined, undefined));
@@ -185,14 +194,14 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
185
194
  globalThis.expect(str(r, "name")).toEqual("Bravo");
186
195
  });
187
196
  globalThis.test("getById miss → null", async () => {
188
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
197
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
189
198
  "id",
190
199
  "absent"
191
200
  ]]), undefined, undefined));
192
201
  globalThis.expect(r).toBe(null);
193
202
  });
194
203
  globalThis.test("byIds returns matches, drops missing", async () => {
195
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
204
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
196
205
  "ids",
197
206
  [
198
207
  "p-1",
@@ -206,7 +215,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
206
215
  ]);
207
216
  });
208
217
  globalThis.test("index routes to indexLookup on the idField", async () => {
209
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("index", "byStatus", Object.fromEntries([[
218
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("index", "byStatus", Object.fromEntries([[
210
219
  "byStatus",
211
220
  "active"
212
221
  ]]), undefined, undefined));
@@ -217,13 +226,13 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
217
226
  });
218
227
  globalThis.test("list with push-down → returns the connection listPage gives", async () => {
219
228
  listPageReturn.contents = "PUSHED";
220
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined, undefined));
229
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined, undefined));
221
230
  listPageReturn.contents = undefined;
222
231
  globalThis.expect(r).toEqual("PUSHED");
223
232
  });
224
233
  globalThis.test("list without push-down → falls back to the shared spec", async () => {
225
234
  listPageReturn.contents = undefined;
226
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined, undefined));
235
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined, undefined));
227
236
  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")));
228
237
  globalThis.expect(edgeIds).toEqual([
229
238
  "p-1",
@@ -232,21 +241,21 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
232
241
  ]);
233
242
  });
234
243
  globalThis.test("items with subId routes to itemsPage (id echoed)", async () => {
235
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, "seq", undefined, undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
244
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, "seq", undefined, undefined, undefined, undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
236
245
  "id",
237
246
  "p-2"
238
247
  ]]), undefined, undefined));
239
248
  globalThis.expect(str(r, "itemsFor")).toEqual("p-2");
240
249
  });
241
250
  globalThis.test("items without subId → empty connection", async () => {
242
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
251
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
243
252
  "id",
244
253
  "p-2"
245
254
  ]]), undefined, undefined));
246
255
  globalThis.expect(Stdlib_Option.getOr(Stdlib_Option.flatMap(field(r, "edges"), Stdlib_JSON.Decode.array), []).length).toBe(0);
247
256
  });
248
257
  globalThis.test("authorization DenyAll → empty shape per kind", async () => {
249
- let b = makeBinding("DenyAll", undefined, undefined, undefined);
258
+ let b = makeBinding("DenyAll", undefined, undefined, undefined, undefined, undefined);
250
259
  let single = await PgQueryResolver_Lambda$ReventlessAws.dispatch(b, undefined, mkPayload("getById", undefined, Object.fromEntries([[
251
260
  "id",
252
261
  "p-1"
@@ -265,7 +274,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
265
274
  TAG: "Deny",
266
275
  _0: "nope"
267
276
  }));
268
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
277
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
269
278
  "id",
270
279
  "p-1"
271
280
  ]]), undefined, undefined));
@@ -291,7 +300,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
291
300
  arguments: payload_arguments,
292
301
  identity: Identity$Reventless.anonymous
293
302
  };
294
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, payload);
303
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, payload);
295
304
  globalThis.expect(str(r, "id")).toEqual("p-2");
296
305
  });
297
306
  globalThis.test("resolveOne multi via target index → array", async () => {
@@ -317,7 +326,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
317
326
  arguments: payload_arguments,
318
327
  identity: Identity$Reventless.anonymous
319
328
  };
320
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, payload);
329
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, payload);
321
330
  globalThis.expect(ids(r)).toEqual([
322
331
  "p-1",
323
332
  "p-3"
@@ -344,14 +353,14 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
344
353
  arguments: payload_arguments,
345
354
  identity: Identity$Reventless.anonymous
346
355
  };
347
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, payload);
356
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, payload);
348
357
  globalThis.expect(ids(r)).toEqual([
349
358
  "p-1",
350
359
  "p-3"
351
360
  ]);
352
361
  });
353
362
  globalThis.test("node decodes global id → __typename + item", async () => {
354
- PgQueryResolver_Lambda$ReventlessAws.register("Things", makeBinding(undefined, undefined, undefined, undefined));
363
+ PgQueryResolver_Lambda$ReventlessAws.register("Things", makeBinding(undefined, undefined, undefined, undefined, undefined, undefined));
355
364
  PgQueryResolver_Lambda$ReventlessAws.registerNodeType("Thing", "Things");
356
365
  let payload_arguments = Object.fromEntries([[
357
366
  "id",
@@ -401,7 +410,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
401
410
  delete: 0,
402
411
  deleteBatch: 0
403
412
  };
404
- let init = makeBinding(undefined, undefined, undefined, undefined);
413
+ let init = makeBinding(undefined, undefined, undefined, undefined, undefined, undefined);
405
414
  let authBinding_pushdowns = init.pushdowns;
406
415
  let authBinding_indexes = init.indexes;
407
416
  let authBinding_subIdField = init.subIdField;
@@ -410,6 +419,8 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
410
419
  let authBinding_includeIdParam = init.includeIdParam;
411
420
  let authBinding_authorization = init.authorization;
412
421
  let authBinding_ownerField = init.ownerField;
422
+ let authBinding_retiredField = init.retiredField;
423
+ let authBinding_retiredValues = init.retiredValues;
413
424
  let authBinding = {
414
425
  ops: authOps,
415
426
  pushdowns: authBinding_pushdowns,
@@ -419,7 +430,9 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
419
430
  labelField: authBinding_labelField,
420
431
  includeIdParam: authBinding_includeIdParam,
421
432
  authorization: authBinding_authorization,
422
- ownerField: authBinding_ownerField
433
+ ownerField: authBinding_ownerField,
434
+ retiredField: authBinding_retiredField,
435
+ retiredValues: authBinding_retiredValues
423
436
  };
424
437
  let lookupAuth = name => {
425
438
  if (name === "AuthTable") {
@@ -445,18 +458,18 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
445
458
  identity: ident(username, groups)
446
459
  });
447
460
  globalThis.test("auth index: group member who owns → results", async () => {
448
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("alice", ["Owner"]));
461
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("alice", ["Owner"]));
449
462
  globalThis.expect(ids(r)).toEqual([
450
463
  "p-1",
451
464
  "p-3"
452
465
  ]);
453
466
  });
454
467
  globalThis.test("auth index: group member who does NOT own → empty", async () => {
455
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", ["Owner"]));
468
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", ["Owner"]));
456
469
  globalThis.expect(Stdlib_Option.getOr(Stdlib_JSON.Decode.array(r), []).length).toBe(0);
457
470
  });
458
471
  globalThis.test("auth index: non-member passes through → results", async () => {
459
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", []));
472
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", []));
460
473
  globalThis.expect(ids(r)).toEqual([
461
474
  "p-1",
462
475
  "p-3"
@@ -465,7 +478,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
465
478
  globalThis.test("unmapped kind throws", async () => {
466
479
  let threw;
467
480
  try {
468
- await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("frobnicate", undefined, undefined, undefined, undefined));
481
+ await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("frobnicate", undefined, undefined, undefined, undefined));
469
482
  threw = false;
470
483
  } catch (exn) {
471
484
  threw = true;
@@ -476,35 +489,35 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
476
489
 
477
490
  globalThis.describe("id form — the typed doors take either", () => {
478
491
  globalThis.test("getById resolves a Relay global id", async () => {
479
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
492
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
480
493
  "id",
481
494
  Api_Ids$ReventlessCore.encode("Thing", "p-2")
482
495
  ]]), undefined, undefined));
483
496
  globalThis.expect(str(r, "name")).toEqual("Bravo");
484
497
  });
485
498
  globalThis.test("getById reports the storage key it resolved to", async () => {
486
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
499
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
487
500
  "id",
488
501
  Api_Ids$ReventlessCore.encode("Thing", "p-2")
489
502
  ]]), undefined, undefined));
490
503
  globalThis.expect(str(r, "id")).toEqual("p-2");
491
504
  });
492
505
  globalThis.test("getById still resolves a plain storage key", async () => {
493
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
506
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
494
507
  "id",
495
508
  "p-2"
496
509
  ]]), undefined, undefined));
497
510
  globalThis.expect(str(r, "name")).toEqual("Bravo");
498
511
  });
499
512
  globalThis.test("getById on a global id for a row that does not exist is still null", async () => {
500
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
513
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
501
514
  "id",
502
515
  Api_Ids$ReventlessCore.encode("Thing", "absent")
503
516
  ]]), undefined, undefined));
504
517
  globalThis.expect(r).toBe(null);
505
518
  });
506
519
  globalThis.test("byIds accepts the two forms mixed in one call", async () => {
507
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
520
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
508
521
  "ids",
509
522
  [
510
523
  "p-1",
@@ -517,7 +530,7 @@ globalThis.describe("id form — the typed doors take either", () => {
517
530
  ]);
518
531
  });
519
532
  globalThis.test("byIds with every id found does not double-count", async () => {
520
- let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
533
+ let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
521
534
  "ids",
522
535
  [
523
536
  "p-1",
@@ -532,7 +545,7 @@ globalThis.describe("id form — the typed doors take either", () => {
532
545
  });
533
546
 
534
547
  globalThis.describe("owner scoping", () => {
535
- let ownedBinding = () => makeBinding(undefined, undefined, "owner", undefined);
548
+ let ownedBinding = () => makeBinding(undefined, undefined, "owner", undefined, undefined, undefined);
536
549
  let shopper = cognito("u-a", ["User"]);
537
550
  let listWith = async (binding, identity) => {
538
551
  lastOwnerScope.contents = undefined;
@@ -557,7 +570,7 @@ globalThis.describe("owner scoping", () => {
557
570
  globalThis.expect(match[1]).toBe(undefined);
558
571
  });
559
572
  globalThis.test("a view with no owner field is never scoped", async () => {
560
- let match = await listWith(makeBinding(undefined, undefined, undefined, undefined), shopper);
573
+ let match = await listWith(makeBinding(undefined, undefined, undefined, undefined, undefined, undefined), shopper);
561
574
  globalThis.expect(match[1]).toBe(undefined);
562
575
  });
563
576
  globalThis.test("an unidentified caller is refused before the push-down runs", async () => {
@@ -620,6 +633,7 @@ export {
620
633
  listPageReturn,
621
634
  lastOwnerScope,
622
635
  ops,
636
+ lastRetiredScope,
623
637
  pushdowns,
624
638
  capability,
625
639
  makeBinding,
@@ -113,6 +113,71 @@ describe("toEntryWith heals a structure persisted before a required list existed
113
113
  })
114
114
  })
115
115
 
116
+ // The `statusField` → `lifecycleField` rename has the same shape of problem one
117
+ // severity down: the SDL field is a nullable `String`, so an absent key answers
118
+ // null rather than blackholing the query. That is worse to find, not better —
119
+ // lists keep rendering while command-menu filtering, board columns, group
120
+ // sections, progress trackers and state diagrams all go quiet. Asserted because
121
+ // the shim is the one part of the rename with nothing to fail at compile time.
122
+ describe("toEntryWith reads a pre-rename structure's lifecycle field", () => {
123
+ let legacyStructure = Dict.fromArray([
124
+ (
125
+ "readModels",
126
+ JSON.Encode.array([
127
+ JSON.parseOrThrow(`{"name": "Customers", "statusField": "locationStatus"}`),
128
+ ]),
129
+ ),
130
+ (
131
+ "stateViewSlices",
132
+ JSON.Encode.array([JSON.parseOrThrow(`{"name": "Orders", "statusField": "status"}`)]),
133
+ ),
134
+ ])->JSON.Encode.object
135
+
136
+ let fieldOf = (~collection) =>
137
+ entry(~filter=false, legacyStructure)
138
+ ->Option.map(members(_, collection))
139
+ ->Option.flatMap(Array.get(_, 0))
140
+ ->Option.flatMap(c => c->Dict.get("lifecycleField"))
141
+
142
+ testSync("a read model's legacy key answers on the new name", () =>
143
+ expect(fieldOf(~collection="readModels"))->toEqual(
144
+ Some(JSON.Encode.string("locationStatus")),
145
+ )
146
+ )
147
+
148
+ testSync("so does a state view slice's", () =>
149
+ expect(fieldOf(~collection="stateViewSlices"))->toEqual(Some(JSON.Encode.string("status")))
150
+ )
151
+
152
+ testSync("a post-rename structure is left alone", () => {
153
+ let current = Dict.fromArray([
154
+ (
155
+ "readModels",
156
+ JSON.Encode.array([
157
+ JSON.parseOrThrow(`{"name": "Orders", "lifecycleField": "lifecycle", "statusField": "stale"}`),
158
+ ]),
159
+ ),
160
+ ])->JSON.Encode.object
161
+ let field =
162
+ entry(~filter=false, current)
163
+ ->Option.map(members(_, "readModels"))
164
+ ->Option.flatMap(Array.get(_, 0))
165
+ ->Option.flatMap(c => c->Dict.get("lifecycleField"))
166
+ expect(field)->toEqual(Some(JSON.Encode.string("lifecycle")))
167
+ })
168
+
169
+ testSync("a structure declaring neither still answers nothing", () => {
170
+ let neither = Dict.fromArray([
171
+ ("readModels", JSON.Encode.array([JSON.parseOrThrow(`{"name": "Products"}`)])),
172
+ ])->JSON.Encode.object
173
+ let component =
174
+ entry(~filter=false, neither)
175
+ ->Option.map(members(_, "readModels"))
176
+ ->Option.flatMap(Array.get(_, 0))
177
+ expect(component->Option.flatMap(c => c->Dict.get("lifecycleField")))->toEqual(None)
178
+ })
179
+ })
180
+
116
181
  // The persisted structure is pre-filter, so this handler is a twin of the
117
182
  // in-memory `encodePluginStructureEntry` rather than a consequence of it. A shell
118
183
  // reading `internalQueryables` against a platform that emits it on only one
@@ -67,6 +67,42 @@ globalThis.describe("toEntryWith heals a structure persisted before a required l
67
67
  });
68
68
  });
69
69
 
70
+ globalThis.describe("toEntryWith reads a pre-rename structure's lifecycle field", () => {
71
+ let legacyStructure = Object.fromEntries([
72
+ [
73
+ "readModels",
74
+ [JSON.parse(`{"name": "Customers", "statusField": "locationStatus"}`)]
75
+ ],
76
+ [
77
+ "stateViewSlices",
78
+ [JSON.parse(`{"name": "Orders", "statusField": "status"}`)]
79
+ ]
80
+ ]);
81
+ let fieldOf = collection => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.map(entry(false, legacyStructure), __x => members(__x, collection)), __x => __x[0]), c => c["lifecycleField"]);
82
+ globalThis.test("a read model's legacy key answers on the new name", () => {
83
+ globalThis.expect(fieldOf("readModels")).toEqual("locationStatus");
84
+ });
85
+ globalThis.test("so does a state view slice's", () => {
86
+ globalThis.expect(fieldOf("stateViewSlices")).toEqual("status");
87
+ });
88
+ globalThis.test("a post-rename structure is left alone", () => {
89
+ let current = Object.fromEntries([[
90
+ "readModels",
91
+ [JSON.parse(`{"name": "Orders", "lifecycleField": "lifecycle", "statusField": "stale"}`)]
92
+ ]]);
93
+ let field = Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.map(entry(false, current), __x => members(__x, "readModels")), __x => __x[0]), c => c["lifecycleField"]);
94
+ globalThis.expect(field).toEqual("lifecycle");
95
+ });
96
+ globalThis.test("a structure declaring neither still answers nothing", () => {
97
+ let neither = Object.fromEntries([[
98
+ "readModels",
99
+ [JSON.parse(`{"name": "Products"}`)]
100
+ ]]);
101
+ let component = Stdlib_Option.flatMap(Stdlib_Option.map(entry(false, neither), __x => members(__x, "readModels")), __x => __x[0]);
102
+ globalThis.expect(Stdlib_Option.flatMap(component, c => c["lifecycleField"])).toEqual(undefined);
103
+ });
104
+ });
105
+
70
106
  globalThis.describe("toEntryWith carries Internal queryables on the filtered field", () => {
71
107
  let mixed = Object.fromEntries([
72
108
  [
@@ -19,18 +19,18 @@ globalThis.describe("QueryDbResolvers_AppSync.internalRowRequiredAttr", () => {
19
19
 
20
20
  globalThis.describe("AppSync_Resolver_Retrying.Functions.listAllItemsConnection", () => {
21
21
  globalThis.test("with ~requireAttribute emits an attribute_exists filter that excludes internal rows", () => {
22
- let code = AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection("name", undefined, undefined, undefined, "name", undefined, undefined);
22
+ let code = AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection("name", undefined, undefined, undefined, "name", undefined, undefined, undefined, undefined);
23
23
  globalThis.expect(code.includes("attribute_exists(#name)")).toBe(true);
24
24
  globalThis.expect(code.includes("names['#name'] = 'name'")).toBe(true);
25
25
  });
26
26
  globalThis.test("without ~requireAttribute emits no attribute_exists filter (default read models unchanged)", () => {
27
- let code = AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection("name", undefined, undefined, undefined, undefined, undefined, undefined);
27
+ let code = AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection("name", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
28
28
  globalThis.expect(code.includes("attribute_exists")).toBe(false);
29
29
  });
30
30
  });
31
31
 
32
32
  globalThis.describe("listAllItemsConnection — Scan cursor round-trip (paging fixes 1–3)", () => {
33
- let code = AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection("name", undefined, undefined, undefined, undefined, undefined, undefined);
33
+ let code = AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection("name", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
34
34
  globalThis.test("Fix 1: response encodes the real nextToken as the cursor", () => {
35
35
  globalThis.expect(code.includes("const next = ctx.result?.nextToken ?? null;")).toBe(true);
36
36
  globalThis.expect(code.includes("util.base64Encode(JSON.stringify({ token: next, index: i }))")).toBe(true);
@@ -29,7 +29,7 @@ describe("state-change descriptor parity", () => {
29
29
  testAsync("every implementation builds the same descriptor", async () => {
30
30
  let cases = await buildAll()
31
31
  // A silent zero-case run would pass while asserting nothing.
32
- expect(cases->Array.length)->toBe(6)
32
+ expect(cases->Array.length)->toBe(11)
33
33
  cases->Array.forEach(c => {
34
34
  expect((c.name, c.relay))->toEqual((c.name, c.local))
35
35
  expect((c.name, c.postgres))->toEqual((c.name, c.local))
@@ -55,4 +55,85 @@ describe("state-change descriptor parity", () => {
55
55
  | None => expect("oversized case missing")->toBe("present")
56
56
  }
57
57
  })
58
+
59
+ // The live channel is the third place a retired row could reach a caller the
60
+ // resolvers refuse it to, and the only one that cannot be scoped per
61
+ // subscriber — the channel is keyed by view and entity, and everyone watching
62
+ // receives the frame. So the payload has to go, in all three implementations.
63
+ testAsync("a retired row publishes as metadata only, everywhere", async () => {
64
+ let cases = await buildAll()
65
+ let keyOf = (d: JSON.t, k) => d->JSON.Decode.object->Option.flatMap(o => o->Dict.get(k))
66
+ let byName = name => cases->Array.find(c => c.name == name)
67
+
68
+ switch byName("retired row degrades to metadata only") {
69
+ | Some(c) =>
70
+ // Not just the payload: the sort value is a timestamp off a row the
71
+ // subscriber may not read, and this row carries one.
72
+ expect((keyOf(c.local, "state"), keyOf(c.local, "sortKeyValue")))->toEqual((None, None))
73
+ expect((keyOf(c.relay, "state"), keyOf(c.relay, "sortKeyValue")))->toEqual((None, None))
74
+ expect((keyOf(c.postgres, "state"), keyOf(c.postgres, "sortKeyValue")))->toEqual((None, None))
75
+ // `Updated`, never `Removed`: the row is not gone. Saying so would be
76
+ // false for an elevated subscriber reading with `includeRetired`, and
77
+ // false again the moment the row is restored.
78
+ expect(keyOf(c.local, "changeKind"))->toEqual(Some(JSON.Encode.string("Updated")))
79
+ | None => expect("retired case missing")->toBe("present")
80
+ }
81
+
82
+ // The control that separates "drops a retired row" from "drops any row on a
83
+ // view that declares the flag".
84
+ switch byName("live row with a retirement flag carries the row") {
85
+ | Some(c) =>
86
+ expect(keyOf(c.local, "state")->Option.isSome)->toBe(true)
87
+ expect(keyOf(c.relay, "state")->Option.isSome)->toBe(true)
88
+ expect(keyOf(c.postgres, "state")->Option.isSome)->toBe(true)
89
+ | None => expect("live-with-flag case missing")->toBe("present")
90
+ }
91
+ })
92
+
93
+ // The state form, where the predicate is membership rather than equality. Both
94
+ // states are asserted, so an implementation comparing against only the first
95
+ // member of the set fails on the second — which is the shape of mistake a set
96
+ // invites and a single value could not.
97
+ testAsync("a row in any retired state publishes as metadata only", async () => {
98
+ let cases = await buildAll()
99
+ let keyOf = (d: JSON.t, k) => d->JSON.Decode.object->Option.flatMap(o => o->Dict.get(k))
100
+ let byName = name => cases->Array.find(c => c.name == name)
101
+
102
+ [
103
+ "row in the first retired state degrades to metadata only",
104
+ "row in the second retired state degrades to metadata only",
105
+ ]->Array.forEach(name =>
106
+ switch byName(name) {
107
+ | Some(c) =>
108
+ expect((name, keyOf(c.local, "state"), keyOf(c.local, "sortKeyValue")))->toEqual((
109
+ name,
110
+ None,
111
+ None,
112
+ ))
113
+ expect((name, keyOf(c.relay, "state"), keyOf(c.relay, "sortKeyValue")))->toEqual((
114
+ name,
115
+ None,
116
+ None,
117
+ ))
118
+ expect((name, keyOf(c.postgres, "state"), keyOf(c.postgres, "sortKeyValue")))->toEqual((
119
+ name,
120
+ None,
121
+ None,
122
+ ))
123
+ | None => expect(name ++ " missing")->toBe("present")
124
+ }
125
+ )
126
+
127
+ // The control for the state form: a live state on the same lifecycle, so an
128
+ // implementation dropping every row of a view that declares states fails.
129
+ switch byName("row in a live state carries the row") {
130
+ | Some(c) =>
131
+ expect((
132
+ keyOf(c.local, "state")->Option.isSome,
133
+ keyOf(c.relay, "state")->Option.isSome,
134
+ keyOf(c.postgres, "state")->Option.isSome,
135
+ ))->toEqual((true, true, true))
136
+ | None => expect("live-state case missing")->toBe("present")
137
+ }
138
+ })
58
139
  })
@@ -11,7 +11,7 @@ function buildAll(prim) {
11
11
  globalThis.describe("state-change descriptor parity", () => {
12
12
  globalThis.test("every implementation builds the same descriptor", async () => {
13
13
  let cases = await StateChangeDescriptorParityMjs.buildAll();
14
- globalThis.expect(cases.length).toBe(6);
14
+ globalThis.expect(cases.length).toBe(11);
15
15
  cases.forEach(c => {
16
16
  globalThis.expect([
17
17
  c.name,
@@ -52,6 +52,102 @@ globalThis.describe("state-change descriptor parity", () => {
52
52
  globalThis.expect("oversized case missing").toBe("present");
53
53
  }
54
54
  });
55
+ globalThis.test("a retired row publishes as metadata only, everywhere", async () => {
56
+ let cases = await StateChangeDescriptorParityMjs.buildAll();
57
+ let keyOf = (d, k) => Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(d), o => o[k]);
58
+ let byName = name => cases.find(c => c.name === name);
59
+ let c = byName("retired row degrades to metadata only");
60
+ if (c !== undefined) {
61
+ globalThis.expect([
62
+ keyOf(c.local, "state"),
63
+ keyOf(c.local, "sortKeyValue")
64
+ ]).toEqual([
65
+ undefined,
66
+ undefined
67
+ ]);
68
+ globalThis.expect([
69
+ keyOf(c.relay, "state"),
70
+ keyOf(c.relay, "sortKeyValue")
71
+ ]).toEqual([
72
+ undefined,
73
+ undefined
74
+ ]);
75
+ globalThis.expect([
76
+ keyOf(c.postgres, "state"),
77
+ keyOf(c.postgres, "sortKeyValue")
78
+ ]).toEqual([
79
+ undefined,
80
+ undefined
81
+ ]);
82
+ globalThis.expect(keyOf(c.local, "changeKind")).toEqual("Updated");
83
+ } else {
84
+ globalThis.expect("retired case missing").toBe("present");
85
+ }
86
+ let c$1 = byName("live row with a retirement flag carries the row");
87
+ if (c$1 !== undefined) {
88
+ globalThis.expect(Stdlib_Option.isSome(keyOf(c$1.local, "state"))).toBe(true);
89
+ globalThis.expect(Stdlib_Option.isSome(keyOf(c$1.relay, "state"))).toBe(true);
90
+ globalThis.expect(Stdlib_Option.isSome(keyOf(c$1.postgres, "state"))).toBe(true);
91
+ } else {
92
+ globalThis.expect("live-with-flag case missing").toBe("present");
93
+ }
94
+ });
95
+ globalThis.test("a row in any retired state publishes as metadata only", async () => {
96
+ let cases = await StateChangeDescriptorParityMjs.buildAll();
97
+ let keyOf = (d, k) => Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(d), o => o[k]);
98
+ let byName = name => cases.find(c => c.name === name);
99
+ [
100
+ "row in the first retired state degrades to metadata only",
101
+ "row in the second retired state degrades to metadata only"
102
+ ].forEach(name => {
103
+ let c = byName(name);
104
+ if (c !== undefined) {
105
+ globalThis.expect([
106
+ name,
107
+ keyOf(c.local, "state"),
108
+ keyOf(c.local, "sortKeyValue")
109
+ ]).toEqual([
110
+ name,
111
+ undefined,
112
+ undefined
113
+ ]);
114
+ globalThis.expect([
115
+ name,
116
+ keyOf(c.relay, "state"),
117
+ keyOf(c.relay, "sortKeyValue")
118
+ ]).toEqual([
119
+ name,
120
+ undefined,
121
+ undefined
122
+ ]);
123
+ globalThis.expect([
124
+ name,
125
+ keyOf(c.postgres, "state"),
126
+ keyOf(c.postgres, "sortKeyValue")
127
+ ]).toEqual([
128
+ name,
129
+ undefined,
130
+ undefined
131
+ ]);
132
+ } else {
133
+ globalThis.expect(name + " missing").toBe("present");
134
+ }
135
+ });
136
+ let c = byName("row in a live state carries the row");
137
+ if (c !== undefined) {
138
+ globalThis.expect([
139
+ Stdlib_Option.isSome(keyOf(c.local, "state")),
140
+ Stdlib_Option.isSome(keyOf(c.relay, "state")),
141
+ Stdlib_Option.isSome(keyOf(c.postgres, "state"))
142
+ ]).toEqual([
143
+ true,
144
+ true,
145
+ true
146
+ ]);
147
+ } else {
148
+ globalThis.expect("live-state case missing").toBe("present");
149
+ }
150
+ });
55
151
  });
56
152
 
57
153
  export {