@model-ts/dynamodb 1.0.0 → 1.2.0

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 (38) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/__test__/client-with-cursor-encryption.test.d.ts +1 -0
  3. package/dist/cjs/__test__/client-with-cursor-encryption.test.js +1911 -0
  4. package/dist/cjs/__test__/client-with-cursor-encryption.test.js.map +1 -0
  5. package/dist/cjs/__test__/client.test.js +77 -56
  6. package/dist/cjs/__test__/client.test.js.map +1 -1
  7. package/dist/cjs/client.d.ts +14 -2
  8. package/dist/cjs/client.js +28 -9
  9. package/dist/cjs/client.js.map +1 -1
  10. package/dist/cjs/dynamodb-model.d.ts +4 -0
  11. package/dist/cjs/pagination.d.ts +48 -3
  12. package/dist/cjs/pagination.js +65 -23
  13. package/dist/cjs/pagination.js.map +1 -1
  14. package/dist/cjs/provider.d.ts +21 -20
  15. package/dist/cjs/provider.js +5 -1
  16. package/dist/cjs/provider.js.map +1 -1
  17. package/dist/esm/__test__/client-with-cursor-encryption.test.d.ts +1 -0
  18. package/dist/esm/__test__/client-with-cursor-encryption.test.js +1909 -0
  19. package/dist/esm/__test__/client-with-cursor-encryption.test.js.map +1 -0
  20. package/dist/esm/__test__/client.test.js +78 -57
  21. package/dist/esm/__test__/client.test.js.map +1 -1
  22. package/dist/esm/client.d.ts +14 -2
  23. package/dist/esm/client.js +28 -9
  24. package/dist/esm/client.js.map +1 -1
  25. package/dist/esm/dynamodb-model.d.ts +4 -0
  26. package/dist/esm/pagination.d.ts +48 -3
  27. package/dist/esm/pagination.js +64 -23
  28. package/dist/esm/pagination.js.map +1 -1
  29. package/dist/esm/provider.d.ts +21 -20
  30. package/dist/esm/provider.js +5 -1
  31. package/dist/esm/provider.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/__test__/client-with-cursor-encryption.test.ts +2445 -0
  34. package/src/__test__/client.test.ts +92 -57
  35. package/src/client.ts +54 -13
  36. package/src/dynamodb-model.ts +4 -0
  37. package/src/pagination.ts +76 -19
  38. package/src/provider.ts +5 -2
@@ -0,0 +1,1909 @@
1
+ import { __awaiter } from "tslib";
2
+ import * as t from "io-ts";
3
+ import { model, RuntimeTypeValidationError, union } from "@model-ts/core";
4
+ import { createSandbox } from "../sandbox";
5
+ import { Client } from "../client";
6
+ import { getProvider } from "../provider";
7
+ import { KeyExistsError, ItemNotFoundError, ConditionalCheckFailedError, RaceConditionError, BulkWriteTransactionError } from "../errors";
8
+ const client = new Client({
9
+ tableName: "table",
10
+ cursorEncryptionKey: Buffer.from("0tpsnnd7+k7xD5pMxK9TXAEkB6c/GYkkW3HEy7ZKBOs=", "base64")
11
+ });
12
+ const provider = getProvider(client);
13
+ const SIMPLE_CODEC = t.type({
14
+ foo: t.string,
15
+ bar: t.number
16
+ });
17
+ class Simple extends model("Simple", SIMPLE_CODEC, provider) {
18
+ get PK() {
19
+ return `PK#${this.foo}`;
20
+ }
21
+ get SK() {
22
+ return `SK#${this.bar}`;
23
+ }
24
+ }
25
+ class SingleGSI extends model("SingleGSI", SIMPLE_CODEC, provider) {
26
+ get PK() {
27
+ return `PK#${this.foo}`;
28
+ }
29
+ get SK() {
30
+ return `SK#${this.bar}`;
31
+ }
32
+ get GSI2PK() {
33
+ return `GSI2PK#${this.foo}${this.foo}`;
34
+ }
35
+ get GSI2SK() {
36
+ return `GSI2SK#FIXED`;
37
+ }
38
+ }
39
+ class MultiGSI extends model("MultiGSI", SIMPLE_CODEC, provider) {
40
+ get PK() {
41
+ return `PK#${this.foo}`;
42
+ }
43
+ get SK() {
44
+ return `SK#${this.bar}`;
45
+ }
46
+ get GSI2PK() {
47
+ return `GSI2PK#${this.foo}${this.foo}`;
48
+ }
49
+ get GSI2SK() {
50
+ return `GSI2SK#FIXED`;
51
+ }
52
+ get GSI3PK() {
53
+ return `GSI3PK#FIXED`;
54
+ }
55
+ get GSI3SK() {
56
+ return `GSI3SK#${this.bar}${this.bar}`;
57
+ }
58
+ get GSI4PK() {
59
+ return `GSI4PK#FIXED`;
60
+ }
61
+ get GSI4SK() {
62
+ return `GSI4SK#${this.bar}${this.bar}`;
63
+ }
64
+ get GSI5PK() {
65
+ return `GSI5PK#FIXED`;
66
+ }
67
+ get GSI5SK() {
68
+ return `GSI5SK#${this.bar}${this.bar}`;
69
+ }
70
+ }
71
+ class A extends model("A", t.type({ pk: t.string, sk: t.string, a: t.number }), provider) {
72
+ get PK() {
73
+ return this.pk;
74
+ }
75
+ get SK() {
76
+ return this.sk;
77
+ }
78
+ }
79
+ class B extends model("B", t.type({ pk: t.string, sk: t.string, b: t.string }), provider) {
80
+ get PK() {
81
+ return this.pk;
82
+ }
83
+ get SK() {
84
+ return this.sk;
85
+ }
86
+ }
87
+ class C extends model("C", t.type({ pk: t.string, sk: t.string, c: t.string }), provider) {
88
+ get PK() {
89
+ return this.pk;
90
+ }
91
+ get SK() {
92
+ return this.sk;
93
+ }
94
+ }
95
+ class D extends model("D", t.type({ pk: t.string, sk: t.string, d: t.string }), provider) {
96
+ get PK() {
97
+ return this.pk;
98
+ }
99
+ get SK() {
100
+ return this.sk;
101
+ }
102
+ }
103
+ class Union extends union([C, D], provider) {
104
+ }
105
+ let sandbox;
106
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
107
+ sandbox = yield createSandbox(client);
108
+ }));
109
+ describe("put", () => {
110
+ describe("via instance", () => {
111
+ test("it inserts a simple model", () => __awaiter(void 0, void 0, void 0, function* () {
112
+ const before = yield sandbox.snapshot();
113
+ yield new Simple({ foo: "hi", bar: 42 }).put();
114
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
115
+ Snapshot Diff:
116
+ - First value
117
+ + Second value
118
+
119
+ - Object {}
120
+ + Object {
121
+ + "PK#hi__SK#42": Object {
122
+ + "PK": "PK#hi",
123
+ + "SK": "SK#42",
124
+ + "_docVersion": 0,
125
+ + "_tag": "Simple",
126
+ + "bar": 42,
127
+ + "foo": "hi",
128
+ + },
129
+ + }
130
+ `);
131
+ }));
132
+ test("it inserts a model with single gsi", () => __awaiter(void 0, void 0, void 0, function* () {
133
+ const before = yield sandbox.snapshot();
134
+ yield new SingleGSI({ foo: "yes", bar: 42 }).put();
135
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
136
+ Snapshot Diff:
137
+ - First value
138
+ + Second value
139
+
140
+ - Object {}
141
+ + Object {
142
+ + "PK#yes__SK#42": Object {
143
+ + "GSI2PK": "GSI2PK#yesyes",
144
+ + "GSI2SK": "GSI2SK#FIXED",
145
+ + "PK": "PK#yes",
146
+ + "SK": "SK#42",
147
+ + "_docVersion": 0,
148
+ + "_tag": "SingleGSI",
149
+ + "bar": 42,
150
+ + "foo": "yes",
151
+ + },
152
+ + }
153
+ `);
154
+ }));
155
+ test("it inserts a model with multiple gsi", () => __awaiter(void 0, void 0, void 0, function* () {
156
+ const before = yield sandbox.snapshot();
157
+ yield new MultiGSI({ foo: "yes", bar: 42 }).put();
158
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
159
+ Snapshot Diff:
160
+ - First value
161
+ + Second value
162
+
163
+ - Object {}
164
+ + Object {
165
+ + "PK#yes__SK#42": Object {
166
+ + "GSI2PK": "GSI2PK#yesyes",
167
+ + "GSI2SK": "GSI2SK#FIXED",
168
+ + "GSI3PK": "GSI3PK#FIXED",
169
+ + "GSI3SK": "GSI3SK#4242",
170
+ + "GSI4PK": "GSI4PK#FIXED",
171
+ + "GSI4SK": "GSI4SK#4242",
172
+ + "GSI5PK": "GSI5PK#FIXED",
173
+ + "GSI5SK": "GSI5SK#4242",
174
+ + "PK": "PK#yes",
175
+ + "SK": "SK#42",
176
+ + "_docVersion": 0,
177
+ + "_tag": "MultiGSI",
178
+ + "bar": 42,
179
+ + "foo": "yes",
180
+ + },
181
+ + }
182
+ `);
183
+ }));
184
+ test("it throws KeyExistsError if item exists", () => __awaiter(void 0, void 0, void 0, function* () {
185
+ yield new MultiGSI({ foo: "yes", bar: 42 }).put();
186
+ yield expect(new MultiGSI({ foo: "yes", bar: 42 }).put()).rejects.toBeInstanceOf(KeyExistsError);
187
+ }));
188
+ test("it overwrites item if `ignoreExistence` is set", () => __awaiter(void 0, void 0, void 0, function* () {
189
+ yield new MultiGSI({ foo: "yes", bar: 42 }).put();
190
+ yield expect(new MultiGSI({ foo: "yes", bar: 42 }).put({ IgnoreExistence: true })).resolves.toBeInstanceOf(MultiGSI);
191
+ }));
192
+ });
193
+ describe("via model", () => {
194
+ test("it inserts a simple model", () => __awaiter(void 0, void 0, void 0, function* () {
195
+ const before = yield sandbox.snapshot();
196
+ yield Simple.put(new Simple({ foo: "hi", bar: 42 }));
197
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
198
+ Snapshot Diff:
199
+ - First value
200
+ + Second value
201
+
202
+ - Object {}
203
+ + Object {
204
+ + "PK#hi__SK#42": Object {
205
+ + "PK": "PK#hi",
206
+ + "SK": "SK#42",
207
+ + "_docVersion": 0,
208
+ + "_tag": "Simple",
209
+ + "bar": 42,
210
+ + "foo": "hi",
211
+ + },
212
+ + }
213
+ `);
214
+ }));
215
+ test("it inserts a model with single gsi", () => __awaiter(void 0, void 0, void 0, function* () {
216
+ const before = yield sandbox.snapshot();
217
+ yield SingleGSI.put(new SingleGSI({ foo: "yes", bar: 42 }));
218
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
219
+ Snapshot Diff:
220
+ - First value
221
+ + Second value
222
+
223
+ - Object {}
224
+ + Object {
225
+ + "PK#yes__SK#42": Object {
226
+ + "GSI2PK": "GSI2PK#yesyes",
227
+ + "GSI2SK": "GSI2SK#FIXED",
228
+ + "PK": "PK#yes",
229
+ + "SK": "SK#42",
230
+ + "_docVersion": 0,
231
+ + "_tag": "SingleGSI",
232
+ + "bar": 42,
233
+ + "foo": "yes",
234
+ + },
235
+ + }
236
+ `);
237
+ }));
238
+ test("it inserts a model with multiple gsi", () => __awaiter(void 0, void 0, void 0, function* () {
239
+ const before = yield sandbox.snapshot();
240
+ yield MultiGSI.put(new MultiGSI({ foo: "yes", bar: 42 }));
241
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
242
+ Snapshot Diff:
243
+ - First value
244
+ + Second value
245
+
246
+ - Object {}
247
+ + Object {
248
+ + "PK#yes__SK#42": Object {
249
+ + "GSI2PK": "GSI2PK#yesyes",
250
+ + "GSI2SK": "GSI2SK#FIXED",
251
+ + "GSI3PK": "GSI3PK#FIXED",
252
+ + "GSI3SK": "GSI3SK#4242",
253
+ + "GSI4PK": "GSI4PK#FIXED",
254
+ + "GSI4SK": "GSI4SK#4242",
255
+ + "GSI5PK": "GSI5PK#FIXED",
256
+ + "GSI5SK": "GSI5SK#4242",
257
+ + "PK": "PK#yes",
258
+ + "SK": "SK#42",
259
+ + "_docVersion": 0,
260
+ + "_tag": "MultiGSI",
261
+ + "bar": 42,
262
+ + "foo": "yes",
263
+ + },
264
+ + }
265
+ `);
266
+ }));
267
+ test("it throws KeyExistsError if item exists", () => __awaiter(void 0, void 0, void 0, function* () {
268
+ yield MultiGSI.put(new MultiGSI({ foo: "yes", bar: 42 }));
269
+ yield expect(new MultiGSI({ foo: "yes", bar: 42 }).put()).rejects.toBeInstanceOf(KeyExistsError);
270
+ }));
271
+ test("it overwrites item if `ignoreExistence` is set", () => __awaiter(void 0, void 0, void 0, function* () {
272
+ yield MultiGSI.put(new MultiGSI({ foo: "yes", bar: 42 }));
273
+ yield expect(MultiGSI.put(new MultiGSI({ foo: "yes", bar: 42 }), {
274
+ IgnoreExistence: true
275
+ })).resolves.toBeInstanceOf(MultiGSI);
276
+ }));
277
+ });
278
+ });
279
+ describe("get", () => {
280
+ describe("via model", () => {
281
+ test("it throws `ItemNotFoundError` if item doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
282
+ yield expect(Simple.get({ PK: "any", SK: "thing" })).rejects.toBeInstanceOf(ItemNotFoundError);
283
+ }));
284
+ test("it returns the item", () => __awaiter(void 0, void 0, void 0, function* () {
285
+ const item = yield new Simple({ foo: "hi", bar: 432 }).put();
286
+ const result = yield Simple.get({
287
+ PK: item.keys().PK,
288
+ SK: item.keys().SK
289
+ });
290
+ expect(result.values()).toMatchInlineSnapshot(`
291
+ Object {
292
+ "bar": 432,
293
+ "foo": "hi",
294
+ }
295
+ `);
296
+ expect(result.encode()).toEqual(item.encode());
297
+ }));
298
+ test("it throws `RuntimeTypeError` if item can't be decoded", () => __awaiter(void 0, void 0, void 0, function* () {
299
+ yield sandbox.seed({ PK: "A", SK: "A", c: 324 });
300
+ yield expect(Simple.get({ PK: "A", SK: "A" })).rejects.toBeInstanceOf(RuntimeTypeValidationError);
301
+ }));
302
+ });
303
+ describe("via union", () => {
304
+ test("it throws `ItemNotFoundError` if item doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
305
+ yield expect(Union.get({ PK: "any", SK: "thing" })).rejects.toBeInstanceOf(ItemNotFoundError);
306
+ }));
307
+ test("it returns the item", () => __awaiter(void 0, void 0, void 0, function* () {
308
+ const item = yield new C({ pk: "PK#0", sk: "SK#0", c: "0" }).put();
309
+ const result = yield Union.get(item.keys());
310
+ expect(result).toBeInstanceOf(C);
311
+ expect(result.values()).toMatchInlineSnapshot(`
312
+ Object {
313
+ "c": "0",
314
+ "pk": "PK#0",
315
+ "sk": "SK#0",
316
+ }
317
+ `);
318
+ }));
319
+ test("it throws `RuntimeTypeError` if item can't be decoded", () => __awaiter(void 0, void 0, void 0, function* () {
320
+ yield sandbox.seed({ PK: "A", SK: "A", a: 324 });
321
+ yield expect(Union.get({ PK: "A", SK: "A" })).rejects.toBeInstanceOf(RuntimeTypeValidationError);
322
+ }));
323
+ });
324
+ });
325
+ describe("delete", () => {
326
+ describe("via client", () => {
327
+ test("it deletes the item and returns null", () => __awaiter(void 0, void 0, void 0, function* () {
328
+ const item = yield new Simple({ foo: "hi", bar: 432 }).put();
329
+ const before = yield sandbox.snapshot();
330
+ const result = yield client.delete({
331
+ _operation: "delete",
332
+ _model: Simple,
333
+ key: {
334
+ PK: item.keys().PK,
335
+ SK: item.keys().SK
336
+ }
337
+ });
338
+ expect(result).toBeNull();
339
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
340
+ Snapshot Diff:
341
+ - First value
342
+ + Second value
343
+
344
+ - Object {
345
+ - "PK#hi__SK#432": Object {
346
+ - "PK": "PK#hi",
347
+ - "SK": "SK#432",
348
+ - "_docVersion": 0,
349
+ - "_tag": "Simple",
350
+ - "bar": 432,
351
+ - "foo": "hi",
352
+ - },
353
+ - }
354
+ + Object {}
355
+ `);
356
+ }));
357
+ });
358
+ describe("via model", () => {
359
+ test("it deletes the item and returns null", () => __awaiter(void 0, void 0, void 0, function* () {
360
+ const item = yield new Simple({ foo: "hi", bar: 432 }).put();
361
+ const before = yield sandbox.snapshot();
362
+ const result = yield Simple.delete({
363
+ PK: item.keys().PK,
364
+ SK: item.keys().SK
365
+ });
366
+ expect(result).toBeNull();
367
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
368
+ Snapshot Diff:
369
+ - First value
370
+ + Second value
371
+
372
+ - Object {
373
+ - "PK#hi__SK#432": Object {
374
+ - "PK": "PK#hi",
375
+ - "SK": "SK#432",
376
+ - "_docVersion": 0,
377
+ - "_tag": "Simple",
378
+ - "bar": 432,
379
+ - "foo": "hi",
380
+ - },
381
+ - }
382
+ + Object {}
383
+ `);
384
+ }));
385
+ });
386
+ describe("via instance", () => {
387
+ test("it deletes the item and returns null", () => __awaiter(void 0, void 0, void 0, function* () {
388
+ const item = yield new Simple({ foo: "hi", bar: 432 }).put();
389
+ const before = yield sandbox.snapshot();
390
+ const result = yield item.delete();
391
+ expect(result).toBeNull();
392
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
393
+ Snapshot Diff:
394
+ - First value
395
+ + Second value
396
+
397
+ - Object {
398
+ - "PK#hi__SK#432": Object {
399
+ - "PK": "PK#hi",
400
+ - "SK": "SK#432",
401
+ - "_docVersion": 0,
402
+ - "_tag": "Simple",
403
+ - "bar": 432,
404
+ - "foo": "hi",
405
+ - },
406
+ - }
407
+ + Object {}
408
+ `);
409
+ }));
410
+ });
411
+ });
412
+ describe("softDelete", () => {
413
+ describe("via client", () => {
414
+ test("it soft-deletes the item", () => __awaiter(void 0, void 0, void 0, function* () {
415
+ const item = yield new Simple({ foo: "hi", bar: 432 }).put();
416
+ const withGSI = yield new MultiGSI({ foo: "hello", bar: 42 }).put();
417
+ const before = yield sandbox.snapshot();
418
+ const simpleResult = yield client.softDelete(item);
419
+ const withGSIResult = yield client.softDelete(withGSI);
420
+ expect(simpleResult.values()).toMatchInlineSnapshot(`
421
+ Object {
422
+ "bar": 432,
423
+ "foo": "hi",
424
+ }
425
+ `);
426
+ expect(withGSIResult.values()).toMatchInlineSnapshot(`
427
+ Object {
428
+ "bar": 42,
429
+ "foo": "hello",
430
+ }
431
+ `);
432
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
433
+ Snapshot Diff:
434
+ - First value
435
+ + Second value
436
+
437
+ @@ -1,25 +1,27 @@
438
+ Object {
439
+ - "PK#hello__SK#42": Object {
440
+ - "GSI2PK": "GSI2PK#hellohello",
441
+ - "GSI2SK": "GSI2SK#FIXED",
442
+ - "GSI3PK": "GSI3PK#FIXED",
443
+ - "GSI3SK": "GSI3SK#4242",
444
+ - "GSI4PK": "GSI4PK#FIXED",
445
+ - "GSI4SK": "GSI4SK#4242",
446
+ - "GSI5PK": "GSI5PK#FIXED",
447
+ - "GSI5SK": "GSI5SK#4242",
448
+ - "PK": "PK#hello",
449
+ - "SK": "SK#42",
450
+ + "$$DELETED$$PK#hello__$$DELETED$$SK#42": Object {
451
+ + "GSI2PK": "$$DELETED$$GSI2PK#hellohello",
452
+ + "GSI2SK": "$$DELETED$$GSI2SK#FIXED",
453
+ + "GSI3PK": "$$DELETED$$GSI3PK#FIXED",
454
+ + "GSI3SK": "$$DELETED$$GSI3SK#4242",
455
+ + "GSI4PK": "$$DELETED$$GSI4PK#FIXED",
456
+ + "GSI4SK": "$$DELETED$$GSI4SK#4242",
457
+ + "GSI5PK": "$$DELETED$$GSI5PK#FIXED",
458
+ + "GSI5SK": "$$DELETED$$GSI5SK#4242",
459
+ + "PK": "$$DELETED$$PK#hello",
460
+ + "SK": "$$DELETED$$SK#42",
461
+ + "_deletedAt": "2021-05-01T08:00:00.000Z",
462
+ "_docVersion": 0,
463
+ "_tag": "MultiGSI",
464
+ "bar": 42,
465
+ "foo": "hello",
466
+ },
467
+ - "PK#hi__SK#432": Object {
468
+ - "PK": "PK#hi",
469
+ - "SK": "SK#432",
470
+ + "$$DELETED$$PK#hi__$$DELETED$$SK#432": Object {
471
+ + "PK": "$$DELETED$$PK#hi",
472
+ + "SK": "$$DELETED$$SK#432",
473
+ + "_deletedAt": "2021-05-01T08:00:00.000Z",
474
+ "_docVersion": 0,
475
+ "_tag": "Simple",
476
+ "bar": 432,
477
+ "foo": "hi",
478
+ },
479
+ `);
480
+ }));
481
+ });
482
+ describe("via model", () => {
483
+ test("it soft-deletes the item", () => __awaiter(void 0, void 0, void 0, function* () {
484
+ const item = yield new Simple({ foo: "hi", bar: 432 }).put();
485
+ const before = yield sandbox.snapshot();
486
+ const result = yield Simple.softDelete(item);
487
+ expect(result.values()).toMatchInlineSnapshot(`
488
+ Object {
489
+ "bar": 432,
490
+ "foo": "hi",
491
+ }
492
+ `);
493
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
494
+ Snapshot Diff:
495
+ - First value
496
+ + Second value
497
+
498
+ @@ -1,9 +1,10 @@
499
+ Object {
500
+ - "PK#hi__SK#432": Object {
501
+ - "PK": "PK#hi",
502
+ - "SK": "SK#432",
503
+ + "$$DELETED$$PK#hi__$$DELETED$$SK#432": Object {
504
+ + "PK": "$$DELETED$$PK#hi",
505
+ + "SK": "$$DELETED$$SK#432",
506
+ + "_deletedAt": "2021-05-01T08:00:00.000Z",
507
+ "_docVersion": 0,
508
+ "_tag": "Simple",
509
+ "bar": 432,
510
+ "foo": "hi",
511
+ },
512
+ `);
513
+ }));
514
+ });
515
+ describe("via instance", () => {
516
+ test("it soft-deletes the item", () => __awaiter(void 0, void 0, void 0, function* () {
517
+ const item = yield new Simple({ foo: "hi", bar: 432 }).put();
518
+ const before = yield sandbox.snapshot();
519
+ const result = yield item.softDelete();
520
+ expect(result.values()).toMatchInlineSnapshot(`
521
+ Object {
522
+ "bar": 432,
523
+ "foo": "hi",
524
+ }
525
+ `);
526
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
527
+ Snapshot Diff:
528
+ - First value
529
+ + Second value
530
+
531
+ @@ -1,9 +1,10 @@
532
+ Object {
533
+ - "PK#hi__SK#432": Object {
534
+ - "PK": "PK#hi",
535
+ - "SK": "SK#432",
536
+ + "$$DELETED$$PK#hi__$$DELETED$$SK#432": Object {
537
+ + "PK": "$$DELETED$$PK#hi",
538
+ + "SK": "$$DELETED$$SK#432",
539
+ + "_deletedAt": "2021-05-01T08:00:00.000Z",
540
+ "_docVersion": 0,
541
+ "_tag": "Simple",
542
+ "bar": 432,
543
+ "foo": "hi",
544
+ },
545
+ `);
546
+ }));
547
+ });
548
+ });
549
+ describe("updateRaw", () => {
550
+ test("it throws `ItemNotFoundError` if item doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
551
+ yield expect(Simple.updateRaw({ PK: "not", SK: "existent" }, { foo: "new foo" })).rejects.toBeInstanceOf(ItemNotFoundError);
552
+ }));
553
+ test("it throws `ConditionalCheckFailedError` if custom condition expression fails", () => __awaiter(void 0, void 0, void 0, function* () {
554
+ yield expect(Simple.updateRaw({ PK: "not", SK: "existent" }, { foo: "new foo" }, { ConditionExpression: "PK = somethingelse" })).rejects.toBeInstanceOf(ConditionalCheckFailedError);
555
+ }));
556
+ test("IT DOES NOT UPDATE KEYS AUTOMATICALLY", () => __awaiter(void 0, void 0, void 0, function* () {
557
+ const item = yield new Simple({ foo: "old", bar: 43 }).put();
558
+ const result = yield Simple.updateRaw({ PK: item.PK, SK: item.SK }, { foo: "new foo" });
559
+ // NOTE: although the result of updateRaw seems to hold the correct keys, it's important to note
560
+ // that it is not reflected in the DB!
561
+ expect(result.PK).toEqual(`PK#new foo`);
562
+ expect(yield sandbox.snapshot()).toMatchInlineSnapshot(`
563
+ Object {
564
+ "PK#old__SK#43": Object {
565
+ "PK": "PK#old",
566
+ "SK": "SK#43",
567
+ "_docVersion": 0,
568
+ "_tag": "Simple",
569
+ "bar": 43,
570
+ "foo": "new foo",
571
+ },
572
+ }
573
+ `);
574
+ }));
575
+ });
576
+ describe("update", () => {
577
+ describe("in-place", () => {
578
+ class InPlace extends model("InPlace", t.type({ foo: t.string, bar: t.number }), provider) {
579
+ get PK() {
580
+ return "FIXEDPK";
581
+ }
582
+ get SK() {
583
+ return "FIXEDSK";
584
+ }
585
+ }
586
+ test("it puts the item if it wasn't stored before", () => __awaiter(void 0, void 0, void 0, function* () {
587
+ const item = new InPlace({ foo: "hello", bar: 1 });
588
+ yield item.update({ foo: "ciao" });
589
+ expect(yield sandbox.snapshot()).toMatchInlineSnapshot(`
590
+ Object {
591
+ "FIXEDPK__FIXEDSK": Object {
592
+ "PK": "FIXEDPK",
593
+ "SK": "FIXEDSK",
594
+ "_docVersion": 1,
595
+ "_tag": "InPlace",
596
+ "bar": 1,
597
+ "foo": "ciao",
598
+ },
599
+ }
600
+ `);
601
+ }));
602
+ test("it throws `RaceConditionError` if item was manipulated inbetween", () => __awaiter(void 0, void 0, void 0, function* () {
603
+ const item = yield new InPlace({ foo: "hello", bar: 1 }).put();
604
+ yield item.update({ foo: "ciao" });
605
+ yield expect(item.update({ foo: "good luck" })).rejects.toBeInstanceOf(RaceConditionError);
606
+ }));
607
+ test("it updates an item in-place", () => __awaiter(void 0, void 0, void 0, function* () {
608
+ const item = yield new InPlace({ foo: "hello", bar: 1 }).put();
609
+ const before = yield sandbox.snapshot();
610
+ expect((yield item.update({ foo: "ciao" })).values())
611
+ .toMatchInlineSnapshot(`
612
+ Object {
613
+ "bar": 1,
614
+ "foo": "ciao",
615
+ }
616
+ `);
617
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
618
+ Snapshot Diff:
619
+ - First value
620
+ + Second value
621
+
622
+ Object {
623
+ "FIXEDPK__FIXEDSK": Object {
624
+ "PK": "FIXEDPK",
625
+ "SK": "FIXEDSK",
626
+ - "_docVersion": 0,
627
+ + "_docVersion": 1,
628
+ "_tag": "InPlace",
629
+ "bar": 1,
630
+ - "foo": "hello",
631
+ + "foo": "ciao",
632
+ },
633
+ }
634
+ `);
635
+ }));
636
+ });
637
+ });
638
+ describe("applyUpdate", () => {
639
+ test("it returns the updated item and update operation", () => __awaiter(void 0, void 0, void 0, function* () {
640
+ const item = yield new A({ pk: "PK", sk: "SK", a: 1 }).put();
641
+ const before = yield sandbox.snapshot();
642
+ const [updatedItem, updateOp] = item.applyUpdate({ a: 2 });
643
+ expect(updatedItem.values()).toMatchInlineSnapshot(`
644
+ Object {
645
+ "a": 2,
646
+ "pk": "PK",
647
+ "sk": "SK",
648
+ }
649
+ `);
650
+ yield client.bulk([updateOp]);
651
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
652
+ Snapshot Diff:
653
+ - First value
654
+ + Second value
655
+
656
+ Object {
657
+ "PK__SK": Object {
658
+ "PK": "PK",
659
+ "SK": "SK",
660
+ - "_docVersion": 0,
661
+ + "_docVersion": 1,
662
+ "_tag": "A",
663
+ - "a": 1,
664
+ + "a": 2,
665
+ "pk": "PK",
666
+ "sk": "SK",
667
+ },
668
+ }
669
+ `);
670
+ }));
671
+ });
672
+ describe("query", () => {
673
+ test("it returns empty results", () => __awaiter(void 0, void 0, void 0, function* () {
674
+ expect(yield client.query({
675
+ KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
676
+ ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT" }
677
+ }, { a: A, b: B, union: Union })).toMatchInlineSnapshot(`
678
+ Object {
679
+ "_unknown": Array [],
680
+ "a": Array [],
681
+ "b": Array [],
682
+ "meta": Object {
683
+ "lastEvaluatedKey": undefined,
684
+ },
685
+ "union": Array [],
686
+ }
687
+ `);
688
+ }));
689
+ test("it returns unknown results", () => __awaiter(void 0, void 0, void 0, function* () {
690
+ yield sandbox.seed({ PK: "abc", SK: "SORT#1", doesnt: "match" });
691
+ expect(yield client.query({
692
+ KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
693
+ ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" }
694
+ }, { a: A, b: B, union: Union })).toMatchInlineSnapshot(`
695
+ Object {
696
+ "_unknown": Array [
697
+ Object {
698
+ "PK": "abc",
699
+ "SK": "SORT#1",
700
+ "doesnt": "match",
701
+ },
702
+ ],
703
+ "a": Array [],
704
+ "b": Array [],
705
+ "meta": Object {
706
+ "lastEvaluatedKey": undefined,
707
+ },
708
+ "union": Array [],
709
+ }
710
+ `);
711
+ }));
712
+ test("it returns results", () => __awaiter(void 0, void 0, void 0, function* () {
713
+ yield sandbox.seed(new A({ pk: "abc", sk: "SORT#1", a: 1 }), new A({ pk: "abc", sk: "SORT#2", a: 2 }), new B({ pk: "abc", sk: "SORT#3", b: "hi" }), { PK: "abc", SK: "SORT#4", probably: "unknown" }, new C({ pk: "abc", sk: "SORT#5", c: "hi" }), new D({ pk: "abc", sk: "SORT#6", d: "hi" }));
714
+ const { a, b, union, _unknown, meta } = yield client.query({
715
+ KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
716
+ ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" }
717
+ }, { a: A, b: B, union: Union });
718
+ expect({
719
+ meta: meta,
720
+ _unknown: _unknown,
721
+ a: a.map(item => item.values()),
722
+ b: b.map(item => item.values()),
723
+ union: union.map(item => item.values())
724
+ }).toMatchInlineSnapshot(`
725
+ Object {
726
+ "_unknown": Array [
727
+ Object {
728
+ "PK": "abc",
729
+ "SK": "SORT#4",
730
+ "probably": "unknown",
731
+ },
732
+ ],
733
+ "a": Array [
734
+ Object {
735
+ "a": 1,
736
+ "pk": "abc",
737
+ "sk": "SORT#1",
738
+ },
739
+ Object {
740
+ "a": 2,
741
+ "pk": "abc",
742
+ "sk": "SORT#2",
743
+ },
744
+ ],
745
+ "b": Array [
746
+ Object {
747
+ "b": "hi",
748
+ "pk": "abc",
749
+ "sk": "SORT#3",
750
+ },
751
+ ],
752
+ "meta": Object {
753
+ "lastEvaluatedKey": undefined,
754
+ },
755
+ "union": Array [
756
+ Object {
757
+ "c": "hi",
758
+ "pk": "abc",
759
+ "sk": "SORT#5",
760
+ },
761
+ Object {
762
+ "d": "hi",
763
+ "pk": "abc",
764
+ "sk": "SORT#6",
765
+ },
766
+ ],
767
+ }
768
+ `);
769
+ }));
770
+ test("it paginates", () => __awaiter(void 0, void 0, void 0, function* () {
771
+ yield sandbox.seed(...Array.from({ length: 20 }).map((_, i) => new A({ pk: "abc", sk: `SORT#${String(i).padStart(2, "0")}`, a: i })), ...Array.from({ length: 20 }).map((_, i) => new B({ pk: "abc", sk: `SORT#${i + 20}`, b: "bar" })));
772
+ const firstPage = yield client.query({
773
+ KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
774
+ ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" },
775
+ Limit: 30
776
+ }, { a: A, b: B });
777
+ expect(firstPage.a.length).toBe(20);
778
+ expect(firstPage.b.length).toBe(10);
779
+ expect(firstPage._unknown.length).toBe(0);
780
+ expect(firstPage.meta.lastEvaluatedKey).not.toBeUndefined();
781
+ const secondPage = yield client.query({
782
+ KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
783
+ ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" },
784
+ Limit: 30,
785
+ ExclusiveStartKey: firstPage.meta.lastEvaluatedKey
786
+ }, { a: A, b: B });
787
+ expect(secondPage.a.length).toBe(0);
788
+ expect(secondPage.b.length).toBe(10);
789
+ expect(secondPage._unknown.length).toBe(0);
790
+ expect(secondPage.meta.lastEvaluatedKey).toBeUndefined();
791
+ }));
792
+ test("it fetches all pages automatically", () => __awaiter(void 0, void 0, void 0, function* () {
793
+ yield sandbox.seed(...Array.from({ length: 20 }).map((_, i) => new A({ pk: "abc", sk: `SORT#${String(i).padStart(2, "0")}`, a: i })), ...Array.from({ length: 20 }).map((_, i) => new B({ pk: "abc", sk: `SORT#${i + 20}`, b: "bar" })));
794
+ const { a, b, meta, _unknown } = yield client.query({
795
+ KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
796
+ ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" },
797
+ FetchAllPages: true,
798
+ // You wouldn't set a limit in a real-world use case here to optimize fetching all items.
799
+ Limit: 10
800
+ }, { a: A, b: B });
801
+ expect(a.length).toBe(20);
802
+ expect(b.length).toBe(20);
803
+ expect(_unknown.length).toBe(0);
804
+ expect(meta.lastEvaluatedKey).toBeUndefined();
805
+ }));
806
+ });
807
+ describe("bulk", () => {
808
+ describe("< 25 elements (true transaction)", () => {
809
+ test("it succeeds", () => __awaiter(void 0, void 0, void 0, function* () {
810
+ const softDeleteTarget = new B({ pk: "PK#3", sk: "SK#3", b: "bar" });
811
+ yield sandbox.seed(new A({ pk: "PK#1", sk: "SK#1", a: 1 }), new A({ pk: "PK#2", sk: "SK#2", a: 2 }), softDeleteTarget, new B({ pk: "PK#UPDATE", sk: "SK#UPDATE", b: "bar" }), new B({ pk: "PK#COND", sk: "SK#COND", b: "cond" }));
812
+ const before = yield sandbox.snapshot();
813
+ yield client.bulk([
814
+ new A({ pk: "PK4", sk: "PK4", a: 4 }).operation("put"),
815
+ A.operation("put", new A({ pk: "PK5", sk: "PK5", a: 5 })),
816
+ new B({ pk: "PK6", sk: "SK6", b: "baz" }).operation("put"),
817
+ A.operation("updateRaw", { PK: "PK#1", SK: "SK#1" }, { a: -1 }),
818
+ new A({ pk: "PK#2", sk: "SK#2", a: 2 }).operation("delete"),
819
+ B.operation("softDelete", softDeleteTarget),
820
+ new B({
821
+ pk: "PK#UPDATE",
822
+ sk: "SK#UPDATE",
823
+ b: "bar"
824
+ }).operation("update", { b: "baz" }),
825
+ new B({
826
+ pk: "PK#COND",
827
+ sk: "SK#COND",
828
+ b: "cond"
829
+ }).operation("condition", {
830
+ ConditionExpression: "b = :cond",
831
+ ExpressionAttributeValues: { ":cond": "cond" }
832
+ })
833
+ ]);
834
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
835
+ Snapshot Diff:
836
+ - First value
837
+ + Second value
838
+
839
+ @@ -1,32 +1,24 @@
840
+ Object {
841
+ + "$$DELETED$$PK#3__$$DELETED$$SK#3": Object {
842
+ + "PK": "$$DELETED$$PK#3",
843
+ + "SK": "$$DELETED$$SK#3",
844
+ + "_deletedAt": "2021-05-01T08:00:00.000Z",
845
+ + "_docVersion": 0,
846
+ + "_tag": "B",
847
+ + "b": "bar",
848
+ + "pk": "PK#3",
849
+ + "sk": "SK#3",
850
+ + },
851
+ "PK#1__SK#1": Object {
852
+ "PK": "PK#1",
853
+ "SK": "SK#1",
854
+ "_docVersion": 0,
855
+ "_tag": "A",
856
+ - "a": 1,
857
+ + "a": -1,
858
+ "pk": "PK#1",
859
+ "sk": "SK#1",
860
+ - },
861
+ - "PK#2__SK#2": Object {
862
+ - "PK": "PK#2",
863
+ - "SK": "SK#2",
864
+ - "_docVersion": 0,
865
+ - "_tag": "A",
866
+ - "a": 2,
867
+ - "pk": "PK#2",
868
+ - "sk": "SK#2",
869
+ - },
870
+ - "PK#3__SK#3": Object {
871
+ - "PK": "PK#3",
872
+ - "SK": "SK#3",
873
+ - "_docVersion": 0,
874
+ - "_tag": "B",
875
+ - "b": "bar",
876
+ - "pk": "PK#3",
877
+ - "sk": "SK#3",
878
+ },
879
+ "PK#COND__SK#COND": Object {
880
+ "PK": "PK#COND",
881
+ "SK": "SK#COND",
882
+ "_docVersion": 0,
883
+ @@ -36,12 +28,39 @@
884
+ "sk": "SK#COND",
885
+ },
886
+ "PK#UPDATE__SK#UPDATE": Object {
887
+ "PK": "PK#UPDATE",
888
+ "SK": "SK#UPDATE",
889
+ - "_docVersion": 0,
890
+ + "_docVersion": 1,
891
+ "_tag": "B",
892
+ - "b": "bar",
893
+ + "b": "baz",
894
+ "pk": "PK#UPDATE",
895
+ "sk": "SK#UPDATE",
896
+ + },
897
+ + "PK4__PK4": Object {
898
+ + "PK": "PK4",
899
+ + "SK": "PK4",
900
+ + "_docVersion": 0,
901
+ + "_tag": "A",
902
+ + "a": 4,
903
+ + "pk": "PK4",
904
+ + "sk": "PK4",
905
+ + },
906
+ + "PK5__PK5": Object {
907
+ + "PK": "PK5",
908
+ + "SK": "PK5",
909
+ + "_docVersion": 0,
910
+ + "_tag": "A",
911
+ + "a": 5,
912
+ + "pk": "PK5",
913
+ + "sk": "PK5",
914
+ + },
915
+ + "PK6__SK6": Object {
916
+ + "PK": "PK6",
917
+ + "SK": "SK6",
918
+ + "_docVersion": 0,
919
+ + "_tag": "B",
920
+ + "b": "baz",
921
+ + "pk": "PK6",
922
+ + "sk": "SK6",
923
+ },
924
+ }
925
+ `);
926
+ }));
927
+ test("it fails", () => __awaiter(void 0, void 0, void 0, function* () {
928
+ yield sandbox.seed(new A({ pk: "PK#1", sk: "SK#1", a: 1 }), new A({ pk: "PK#2", sk: "SK#2", a: 2 }), new B({ pk: "PK#3", sk: "SK#3", b: "bar" }), new B({ pk: "PK#UPDATE", sk: "SK#UPDATE", b: "bar" }), new B({ pk: "PK#COND", sk: "SK#COND", b: "cond" }));
929
+ const before = yield sandbox.snapshot();
930
+ yield expect(client.bulk([
931
+ // Succeed
932
+ new A({ pk: "PK#4", sk: "PK#4", a: 4 }).operation("put"),
933
+ A.operation("put", new A({ pk: "PK5", sk: "PK5", a: 5 })),
934
+ new B({ pk: "PK#6", sk: "SK#6", b: "baz" }).operation("put"),
935
+ // Fails
936
+ A.operation("updateRaw", { PK: "PK#nicetry", SK: "SK#nope" }, { a: 234 })
937
+ ])).rejects.toBeInstanceOf(BulkWriteTransactionError);
938
+ expect(yield sandbox.snapshot()).toEqual(before);
939
+ }));
940
+ });
941
+ describe("> 25 items (pseudo transaction)", () => {
942
+ test("it succeeds", () => __awaiter(void 0, void 0, void 0, function* () {
943
+ yield sandbox.seed(new A({ pk: "PK#1", sk: "SK#1", a: 1 }), new A({ pk: "PK#2", sk: "SK#2", a: 2 }), new B({ pk: "PK#3", sk: "SK#3", b: "bar" }));
944
+ const before = yield sandbox.snapshot();
945
+ yield client.bulk([
946
+ new A({ pk: "PK4", sk: "PK4", a: 4 }).operation("put"),
947
+ A.operation("put", new A({ pk: "PK5", sk: "PK5", a: 5 })),
948
+ new B({ pk: "PK6", sk: "SK6", b: "baz" }).operation("put"),
949
+ A.operation("updateRaw", { PK: "PK#1", SK: "SK#1" }, { a: -1 }),
950
+ new A({ pk: "PK#2", sk: "SK#2", a: 2 }).operation("delete"),
951
+ B.operation("delete", { PK: "PK#3", SK: "SK#3" }),
952
+ new B({
953
+ pk: "PK#UPDATE",
954
+ sk: "SK#UPDATE",
955
+ b: "bar"
956
+ }).operation("update", { b: "baz" }),
957
+ ...Array.from({ length: 25 }).map((_, i) => new A({ pk: `PK#A${i}`, sk: `SK#A${i}`, a: i }).operation("put"))
958
+ ]);
959
+ //#region snapshot
960
+ expect(yield sandbox.diff(before)).toMatchInlineSnapshot(`
961
+ Snapshot Diff:
962
+ - First value
963
+ + Second value
964
+
965
+ @@ -2,28 +2,271 @@
966
+ "PK#1__SK#1": Object {
967
+ "PK": "PK#1",
968
+ "SK": "SK#1",
969
+ "_docVersion": 0,
970
+ "_tag": "A",
971
+ - "a": 1,
972
+ + "a": -1,
973
+ "pk": "PK#1",
974
+ "sk": "SK#1",
975
+ + },
976
+ + "PK#A0__SK#A0": Object {
977
+ + "PK": "PK#A0",
978
+ + "SK": "SK#A0",
979
+ + "_docVersion": 0,
980
+ + "_tag": "A",
981
+ + "a": 0,
982
+ + "pk": "PK#A0",
983
+ + "sk": "SK#A0",
984
+ + },
985
+ + "PK#A10__SK#A10": Object {
986
+ + "PK": "PK#A10",
987
+ + "SK": "SK#A10",
988
+ + "_docVersion": 0,
989
+ + "_tag": "A",
990
+ + "a": 10,
991
+ + "pk": "PK#A10",
992
+ + "sk": "SK#A10",
993
+ + },
994
+ + "PK#A11__SK#A11": Object {
995
+ + "PK": "PK#A11",
996
+ + "SK": "SK#A11",
997
+ + "_docVersion": 0,
998
+ + "_tag": "A",
999
+ + "a": 11,
1000
+ + "pk": "PK#A11",
1001
+ + "sk": "SK#A11",
1002
+ + },
1003
+ + "PK#A12__SK#A12": Object {
1004
+ + "PK": "PK#A12",
1005
+ + "SK": "SK#A12",
1006
+ + "_docVersion": 0,
1007
+ + "_tag": "A",
1008
+ + "a": 12,
1009
+ + "pk": "PK#A12",
1010
+ + "sk": "SK#A12",
1011
+ + },
1012
+ + "PK#A13__SK#A13": Object {
1013
+ + "PK": "PK#A13",
1014
+ + "SK": "SK#A13",
1015
+ + "_docVersion": 0,
1016
+ + "_tag": "A",
1017
+ + "a": 13,
1018
+ + "pk": "PK#A13",
1019
+ + "sk": "SK#A13",
1020
+ + },
1021
+ + "PK#A14__SK#A14": Object {
1022
+ + "PK": "PK#A14",
1023
+ + "SK": "SK#A14",
1024
+ + "_docVersion": 0,
1025
+ + "_tag": "A",
1026
+ + "a": 14,
1027
+ + "pk": "PK#A14",
1028
+ + "sk": "SK#A14",
1029
+ },
1030
+ - "PK#2__SK#2": Object {
1031
+ - "PK": "PK#2",
1032
+ - "SK": "SK#2",
1033
+ + "PK#A15__SK#A15": Object {
1034
+ + "PK": "PK#A15",
1035
+ + "SK": "SK#A15",
1036
+ + "_docVersion": 0,
1037
+ + "_tag": "A",
1038
+ + "a": 15,
1039
+ + "pk": "PK#A15",
1040
+ + "sk": "SK#A15",
1041
+ + },
1042
+ + "PK#A16__SK#A16": Object {
1043
+ + "PK": "PK#A16",
1044
+ + "SK": "SK#A16",
1045
+ + "_docVersion": 0,
1046
+ + "_tag": "A",
1047
+ + "a": 16,
1048
+ + "pk": "PK#A16",
1049
+ + "sk": "SK#A16",
1050
+ + },
1051
+ + "PK#A17__SK#A17": Object {
1052
+ + "PK": "PK#A17",
1053
+ + "SK": "SK#A17",
1054
+ + "_docVersion": 0,
1055
+ + "_tag": "A",
1056
+ + "a": 17,
1057
+ + "pk": "PK#A17",
1058
+ + "sk": "SK#A17",
1059
+ + },
1060
+ + "PK#A18__SK#A18": Object {
1061
+ + "PK": "PK#A18",
1062
+ + "SK": "SK#A18",
1063
+ + "_docVersion": 0,
1064
+ + "_tag": "A",
1065
+ + "a": 18,
1066
+ + "pk": "PK#A18",
1067
+ + "sk": "SK#A18",
1068
+ + },
1069
+ + "PK#A19__SK#A19": Object {
1070
+ + "PK": "PK#A19",
1071
+ + "SK": "SK#A19",
1072
+ + "_docVersion": 0,
1073
+ + "_tag": "A",
1074
+ + "a": 19,
1075
+ + "pk": "PK#A19",
1076
+ + "sk": "SK#A19",
1077
+ + },
1078
+ + "PK#A1__SK#A1": Object {
1079
+ + "PK": "PK#A1",
1080
+ + "SK": "SK#A1",
1081
+ + "_docVersion": 0,
1082
+ + "_tag": "A",
1083
+ + "a": 1,
1084
+ + "pk": "PK#A1",
1085
+ + "sk": "SK#A1",
1086
+ + },
1087
+ + "PK#A20__SK#A20": Object {
1088
+ + "PK": "PK#A20",
1089
+ + "SK": "SK#A20",
1090
+ + "_docVersion": 0,
1091
+ + "_tag": "A",
1092
+ + "a": 20,
1093
+ + "pk": "PK#A20",
1094
+ + "sk": "SK#A20",
1095
+ + },
1096
+ + "PK#A21__SK#A21": Object {
1097
+ + "PK": "PK#A21",
1098
+ + "SK": "SK#A21",
1099
+ + "_docVersion": 0,
1100
+ + "_tag": "A",
1101
+ + "a": 21,
1102
+ + "pk": "PK#A21",
1103
+ + "sk": "SK#A21",
1104
+ + },
1105
+ + "PK#A22__SK#A22": Object {
1106
+ + "PK": "PK#A22",
1107
+ + "SK": "SK#A22",
1108
+ + "_docVersion": 0,
1109
+ + "_tag": "A",
1110
+ + "a": 22,
1111
+ + "pk": "PK#A22",
1112
+ + "sk": "SK#A22",
1113
+ + },
1114
+ + "PK#A23__SK#A23": Object {
1115
+ + "PK": "PK#A23",
1116
+ + "SK": "SK#A23",
1117
+ + "_docVersion": 0,
1118
+ + "_tag": "A",
1119
+ + "a": 23,
1120
+ + "pk": "PK#A23",
1121
+ + "sk": "SK#A23",
1122
+ + },
1123
+ + "PK#A24__SK#A24": Object {
1124
+ + "PK": "PK#A24",
1125
+ + "SK": "SK#A24",
1126
+ + "_docVersion": 0,
1127
+ + "_tag": "A",
1128
+ + "a": 24,
1129
+ + "pk": "PK#A24",
1130
+ + "sk": "SK#A24",
1131
+ + },
1132
+ + "PK#A2__SK#A2": Object {
1133
+ + "PK": "PK#A2",
1134
+ + "SK": "SK#A2",
1135
+ "_docVersion": 0,
1136
+ "_tag": "A",
1137
+ "a": 2,
1138
+ - "pk": "PK#2",
1139
+ - "sk": "SK#2",
1140
+ + "pk": "PK#A2",
1141
+ + "sk": "SK#A2",
1142
+ + },
1143
+ + "PK#A3__SK#A3": Object {
1144
+ + "PK": "PK#A3",
1145
+ + "SK": "SK#A3",
1146
+ + "_docVersion": 0,
1147
+ + "_tag": "A",
1148
+ + "a": 3,
1149
+ + "pk": "PK#A3",
1150
+ + "sk": "SK#A3",
1151
+ + },
1152
+ + "PK#A4__SK#A4": Object {
1153
+ + "PK": "PK#A4",
1154
+ + "SK": "SK#A4",
1155
+ + "_docVersion": 0,
1156
+ + "_tag": "A",
1157
+ + "a": 4,
1158
+ + "pk": "PK#A4",
1159
+ + "sk": "SK#A4",
1160
+ + },
1161
+ + "PK#A5__SK#A5": Object {
1162
+ + "PK": "PK#A5",
1163
+ + "SK": "SK#A5",
1164
+ + "_docVersion": 0,
1165
+ + "_tag": "A",
1166
+ + "a": 5,
1167
+ + "pk": "PK#A5",
1168
+ + "sk": "SK#A5",
1169
+ + },
1170
+ + "PK#A6__SK#A6": Object {
1171
+ + "PK": "PK#A6",
1172
+ + "SK": "SK#A6",
1173
+ + "_docVersion": 0,
1174
+ + "_tag": "A",
1175
+ + "a": 6,
1176
+ + "pk": "PK#A6",
1177
+ + "sk": "SK#A6",
1178
+ + },
1179
+ + "PK#A7__SK#A7": Object {
1180
+ + "PK": "PK#A7",
1181
+ + "SK": "SK#A7",
1182
+ + "_docVersion": 0,
1183
+ + "_tag": "A",
1184
+ + "a": 7,
1185
+ + "pk": "PK#A7",
1186
+ + "sk": "SK#A7",
1187
+ + },
1188
+ + "PK#A8__SK#A8": Object {
1189
+ + "PK": "PK#A8",
1190
+ + "SK": "SK#A8",
1191
+ + "_docVersion": 0,
1192
+ + "_tag": "A",
1193
+ + "a": 8,
1194
+ + "pk": "PK#A8",
1195
+ + "sk": "SK#A8",
1196
+ + },
1197
+ + "PK#A9__SK#A9": Object {
1198
+ + "PK": "PK#A9",
1199
+ + "SK": "SK#A9",
1200
+ + "_docVersion": 0,
1201
+ + "_tag": "A",
1202
+ + "a": 9,
1203
+ + "pk": "PK#A9",
1204
+ + "sk": "SK#A9",
1205
+ },
1206
+ - "PK#3__SK#3": Object {
1207
+ - "PK": "PK#3",
1208
+ - "SK": "SK#3",
1209
+ + "PK#UPDATE__SK#UPDATE": Object {
1210
+ + "PK": "PK#UPDATE",
1211
+ + "SK": "SK#UPDATE",
1212
+ + "_docVersion": 1,
1213
+ + "_tag": "B",
1214
+ + "b": "baz",
1215
+ + "pk": "PK#UPDATE",
1216
+ + "sk": "SK#UPDATE",
1217
+ + },
1218
+ + "PK4__PK4": Object {
1219
+ + "PK": "PK4",
1220
+ + "SK": "PK4",
1221
+ + "_docVersion": 0,
1222
+ + "_tag": "A",
1223
+ + "a": 4,
1224
+ + "pk": "PK4",
1225
+ + "sk": "PK4",
1226
+ + },
1227
+ + "PK5__PK5": Object {
1228
+ + "PK": "PK5",
1229
+ + "SK": "PK5",
1230
+ "_docVersion": 0,
1231
+ + "_tag": "A",
1232
+ + "a": 5,
1233
+ + "pk": "PK5",
1234
+ + "sk": "PK5",
1235
+ + },
1236
+ + "PK6__SK6": Object {
1237
+ + "PK": "PK6",
1238
+ + "SK": "SK6",
1239
+ + "_docVersion": 0,
1240
+ "_tag": "B",
1241
+ - "b": "bar",
1242
+ - "pk": "PK#3",
1243
+ - "sk": "SK#3",
1244
+ + "b": "baz",
1245
+ + "pk": "PK6",
1246
+ + "sk": "SK6",
1247
+ },
1248
+ }
1249
+ `);
1250
+ //#endregion
1251
+ }));
1252
+ test("it fails and rolls back", () => __awaiter(void 0, void 0, void 0, function* () {
1253
+ const before = yield sandbox.snapshot();
1254
+ yield expect(client.bulk([
1255
+ // Succeeds
1256
+ ...Array.from({ length: 40 }).map((_, i) => new A({ pk: `PK#${i}`, sk: `SK#${i}`, a: i }).operation("put")),
1257
+ // Fails
1258
+ A.operation("condition", { PK: "nicetry", SK: "nope" }, { ConditionExpression: "attribute_exists(PK)" })
1259
+ ])).rejects.toBeInstanceOf(BulkWriteTransactionError);
1260
+ expect(yield sandbox.snapshot()).toEqual(before);
1261
+ }));
1262
+ });
1263
+ });
1264
+ describe("batchGet", () => {
1265
+ class A extends model("A", t.type({ pk: t.string, sk: t.string, a: t.number }), provider) {
1266
+ get PK() {
1267
+ return this.pk;
1268
+ }
1269
+ get SK() {
1270
+ return this.sk;
1271
+ }
1272
+ }
1273
+ test("it fetches an empty record", () => __awaiter(void 0, void 0, void 0, function* () {
1274
+ expect(yield client.batchGet({})).toEqual({});
1275
+ }));
1276
+ test("it throws if some items don't exist", () => __awaiter(void 0, void 0, void 0, function* () {
1277
+ yield expect(client.batchGet({
1278
+ one: A.operation("get", { PK: "PK#1", SK: "SK#1" }),
1279
+ two: A.operation("get", { PK: "PK#2", SK: "SK#2" }),
1280
+ three: A.operation("get", { PK: "PK#3", SK: "SK#3" }),
1281
+ four: A.operation("get", { PK: "PK#4", SK: "SK#4" }),
1282
+ duplicate: A.operation("get", { PK: "PK#1", SK: "SK#1" })
1283
+ })).rejects.toBeInstanceOf(ItemNotFoundError);
1284
+ }));
1285
+ test("it returns individual errors", () => __awaiter(void 0, void 0, void 0, function* () {
1286
+ yield sandbox.seed(new A({ pk: "PK#1", sk: "SK#1", a: 1 }), new A({ pk: "PK#2", sk: "SK#2", a: 2 }));
1287
+ const result = yield client.batchGet({
1288
+ one: A.operation("get", { PK: "PK#1", SK: "SK#1" }),
1289
+ two: A.operation("get", { PK: "PK#2", SK: "SK#2" }),
1290
+ duplicate: A.operation("get", { PK: "PK#1", SK: "SK#1" }),
1291
+ error: A.operation("get", { PK: "PK#error", SK: "SK#error" }),
1292
+ error2: A.operation("get", { PK: "PK#error2", SK: "SK#error2" })
1293
+ }, { individualErrors: true });
1294
+ expect(result.one).toBeInstanceOf(A);
1295
+ expect(result.two).toBeInstanceOf(A);
1296
+ expect(result.duplicate).toBeInstanceOf(A);
1297
+ expect(result.error).toBeInstanceOf(ItemNotFoundError);
1298
+ }));
1299
+ test("it fetches <=100 entries in one go", () => __awaiter(void 0, void 0, void 0, function* () {
1300
+ yield sandbox.seed(new A({ pk: "PK#1", sk: "SK#1", a: 1 }), new A({ pk: "PK#2", sk: "SK#2", a: 2 }), new A({ pk: "PK#3", sk: "SK#3", a: 3 }), new A({ pk: "PK#4", sk: "SK#4", a: 4 }));
1301
+ const results = yield client.batchGet({
1302
+ one: A.operation("get", { PK: "PK#1", SK: "SK#1" }),
1303
+ two: A.operation("get", { PK: "PK#2", SK: "SK#2" }),
1304
+ three: A.operation("get", { PK: "PK#3", SK: "SK#3" }),
1305
+ four: A.operation("get", { PK: "PK#4", SK: "SK#4" }),
1306
+ duplicate: A.operation("get", { PK: "PK#1", SK: "SK#1" })
1307
+ });
1308
+ expect(Object.fromEntries(Object.entries(results).map(([key, val]) => [key, val.values()]))).toMatchInlineSnapshot(`
1309
+ Object {
1310
+ "duplicate": Object {
1311
+ "a": 1,
1312
+ "pk": "PK#1",
1313
+ "sk": "SK#1",
1314
+ },
1315
+ "four": Object {
1316
+ "a": 4,
1317
+ "pk": "PK#4",
1318
+ "sk": "SK#4",
1319
+ },
1320
+ "one": Object {
1321
+ "a": 1,
1322
+ "pk": "PK#1",
1323
+ "sk": "SK#1",
1324
+ },
1325
+ "three": Object {
1326
+ "a": 3,
1327
+ "pk": "PK#3",
1328
+ "sk": "SK#3",
1329
+ },
1330
+ "two": Object {
1331
+ "a": 2,
1332
+ "pk": "PK#2",
1333
+ "sk": "SK#2",
1334
+ },
1335
+ }
1336
+ `);
1337
+ }));
1338
+ });
1339
+ describe("load", () => {
1340
+ describe("client", () => {
1341
+ test("it throws if item doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
1342
+ yield expect(client.load(A.operation("get", { PK: "PK", SK: "SK" }))).rejects.toBeInstanceOf(ItemNotFoundError);
1343
+ }));
1344
+ test("it returns null instead of throwing if item doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
1345
+ yield expect(client.load(A.operation("get", { PK: "PK", SK: "SK" }), { null: true })).resolves.toBeNull();
1346
+ }));
1347
+ test("it fetches >100 items", () => __awaiter(void 0, void 0, void 0, function* () {
1348
+ const items = Array.from({ length: 234 }).map((_, i) => i < 100
1349
+ ? new A({ pk: String(i), sk: String(i), a: i })
1350
+ : new B({ pk: String(i), sk: String(i), b: String(i) }));
1351
+ const spy = jest.spyOn(client, "batchGet");
1352
+ yield sandbox.seed(...items);
1353
+ const results = yield Promise.all(items.map(({ PK, SK }, i) => i < 100
1354
+ ? client.load(A.operation("get", { PK, SK }))
1355
+ : client.load(B.operation("get", { PK, SK }))));
1356
+ expect(results.length).toBe(234);
1357
+ expect(spy).toHaveBeenCalledTimes(3);
1358
+ spy.mockReset();
1359
+ spy.mockRestore();
1360
+ }));
1361
+ });
1362
+ describe("model", () => {
1363
+ test("it throws if item doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
1364
+ yield expect(A.load({ PK: "PK", SK: "SK" })).rejects.toBeInstanceOf(ItemNotFoundError);
1365
+ }));
1366
+ test("it returns null instead of throwing if item doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
1367
+ yield expect(A.load({ PK: "PK", SK: "SK" }, { null: true })).resolves.toBeNull();
1368
+ }));
1369
+ test("it fetches >100 items", () => __awaiter(void 0, void 0, void 0, function* () {
1370
+ const items = Array.from({ length: 234 }).map((_, i) => new A({ pk: String(i), sk: String(i), a: i }));
1371
+ const spy = jest.spyOn(client, "batchGet");
1372
+ yield sandbox.seed(...items);
1373
+ const results = yield Promise.all(items.map(({ PK, SK }, i) => A.load({ PK, SK })));
1374
+ expect(results.length).toBe(234);
1375
+ expect(spy).toHaveBeenCalledTimes(3);
1376
+ spy.mockReset();
1377
+ spy.mockRestore();
1378
+ }));
1379
+ });
1380
+ describe("union", () => {
1381
+ test("it throws if item doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
1382
+ yield expect(Union.load({ PK: "PK", SK: "SK" })).rejects.toBeInstanceOf(ItemNotFoundError);
1383
+ }));
1384
+ test("it returns null instead of throwing if item doesn't exist", () => __awaiter(void 0, void 0, void 0, function* () {
1385
+ yield expect(Union.load({ PK: "PK", SK: "SK" }, { null: true })).resolves.toBeNull();
1386
+ }));
1387
+ test("it fetches >100 items", () => __awaiter(void 0, void 0, void 0, function* () {
1388
+ const items = Array.from({ length: 234 }).map((_, i) => i < 123
1389
+ ? new C({ pk: String(i), sk: String(i), c: String(i) })
1390
+ : new D({ pk: String(i), sk: String(i), d: String(i) }));
1391
+ const spy = jest.spyOn(client, "batchGet");
1392
+ yield sandbox.seed(...items);
1393
+ const results = yield Promise.all(items.map(({ PK, SK }, i) => Union.load({ PK, SK })));
1394
+ expect(results.length).toBe(234);
1395
+ expect(results.filter(item => item instanceof C).length).toBe(123);
1396
+ expect(results.filter(item => item instanceof D).length).toBe(111);
1397
+ expect(spy).toHaveBeenCalledTimes(3);
1398
+ spy.mockReset();
1399
+ spy.mockRestore();
1400
+ }));
1401
+ });
1402
+ });
1403
+ describe("loadMany", () => {
1404
+ describe("client", () => {
1405
+ test("it fetches >100 items", () => __awaiter(void 0, void 0, void 0, function* () {
1406
+ const items = Array.from({ length: 234 }).map((_, i) => i < 100
1407
+ ? new A({ pk: String(i), sk: String(i), a: i })
1408
+ : new B({ pk: String(i), sk: String(i), b: String(i) }));
1409
+ const spy = jest.spyOn(client, "batchGet");
1410
+ yield sandbox.seed(...items);
1411
+ const results = yield client.loadMany(items.map(({ PK, SK }, i) => i < 100
1412
+ ? A.operation("get", { PK, SK })
1413
+ : B.operation("get", { PK, SK })));
1414
+ expect(results.length).toBe(234);
1415
+ expect(spy).toHaveBeenCalledTimes(3);
1416
+ spy.mockReset();
1417
+ spy.mockRestore();
1418
+ }));
1419
+ });
1420
+ describe("model", () => {
1421
+ test("it fetches >100 items", () => __awaiter(void 0, void 0, void 0, function* () {
1422
+ const items = Array.from({ length: 234 }).map((_, i) => new A({ pk: String(i), sk: String(i), a: i }));
1423
+ const spy = jest.spyOn(client, "batchGet");
1424
+ yield sandbox.seed(...items);
1425
+ const results = yield A.loadMany(items.map(({ PK, SK }) => ({ PK, SK })));
1426
+ expect(results.length).toBe(234);
1427
+ expect(spy).toHaveBeenCalledTimes(3);
1428
+ spy.mockReset();
1429
+ spy.mockRestore();
1430
+ }));
1431
+ });
1432
+ describe("union", () => {
1433
+ test("it fetches >100 items", () => __awaiter(void 0, void 0, void 0, function* () {
1434
+ const items = Array.from({ length: 234 }).map((_, i) => i < 123
1435
+ ? new C({ pk: String(i), sk: String(i), c: String(i) })
1436
+ : new D({ pk: String(i), sk: String(i), d: String(i) }));
1437
+ const spy = jest.spyOn(client, "batchGet");
1438
+ yield sandbox.seed(...items);
1439
+ const results = yield Union.loadMany(items.map(({ PK, SK }) => ({ PK, SK })));
1440
+ expect(results.length).toBe(234);
1441
+ expect(results.filter(item => item instanceof C).length).toBe(123);
1442
+ expect(results.filter(item => item instanceof D).length).toBe(111);
1443
+ expect(spy).toHaveBeenCalledTimes(3);
1444
+ spy.mockReset();
1445
+ spy.mockRestore();
1446
+ }));
1447
+ });
1448
+ });
1449
+ describe("paginate", () => {
1450
+ describe("client", () => {
1451
+ test("it paginates a regular model", () => __awaiter(void 0, void 0, void 0, function* () {
1452
+ const items = Array.from({ length: 60 }).map((_, i) => new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) }));
1453
+ yield sandbox.seed(...items);
1454
+ // Forwards
1455
+ const page1 = yield client.paginate(C, {}, {
1456
+ KeyConditionExpression: "PK = :pk",
1457
+ ExpressionAttributeValues: { ":pk": "PK" }
1458
+ });
1459
+ expect(page1.pageInfo).toMatchInlineSnapshot(`
1460
+ Object {
1461
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo7vfn9aOeA8=",
1462
+ "hasNextPage": true,
1463
+ "hasPreviousPage": false,
1464
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1465
+ }
1466
+ `);
1467
+ expect(page1.edges.length).toBe(20);
1468
+ expect(page1.edges[0].node.c).toBe("0");
1469
+ expect(page1.edges[19].node.c).toBe("19");
1470
+ const page2 = yield client.paginate(C, { after: page1.pageInfo.endCursor }, {
1471
+ KeyConditionExpression: "PK = :pk",
1472
+ ExpressionAttributeValues: { ":pk": "PK" }
1473
+ });
1474
+ expect(page2.pageInfo).toMatchInlineSnapshot(`
1475
+ Object {
1476
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo5Xfn9aOeA8=",
1477
+ "hasNextPage": true,
1478
+ "hasPreviousPage": false,
1479
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo4X1n9aOeA8=",
1480
+ }
1481
+ `);
1482
+ expect(page2.edges.length).toBe(20);
1483
+ expect(page2.edges[0].node.c).toBe("20");
1484
+ expect(page2.edges[19].node.c).toBe("39");
1485
+ const page3 = yield client.paginate(C, { after: page2.pageInfo.endCursor }, {
1486
+ KeyConditionExpression: "PK = :pk",
1487
+ ExpressionAttributeValues: { ":pk": "PK" }
1488
+ });
1489
+ expect(page3.pageInfo).toMatchInlineSnapshot(`
1490
+ Object {
1491
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoLvfn9aOeA8=",
1492
+ "hasNextPage": false,
1493
+ "hasPreviousPage": false,
1494
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoKv1n9aOeA8=",
1495
+ }
1496
+ `);
1497
+ expect(page3.edges.length).toBe(20);
1498
+ expect(page3.edges[0].node.c).toBe("40");
1499
+ expect(page3.edges[19].node.c).toBe("59");
1500
+ // Backwards
1501
+ const backwardsPage2 = yield client.paginate(C, { before: page3.pageInfo.startCursor }, {
1502
+ KeyConditionExpression: "PK = :pk",
1503
+ ExpressionAttributeValues: { ":pk": "PK" }
1504
+ });
1505
+ expect(backwardsPage2.pageInfo).toMatchInlineSnapshot(`
1506
+ Object {
1507
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo5Xfn9aOeA8=",
1508
+ "hasNextPage": false,
1509
+ "hasPreviousPage": true,
1510
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo4X1n9aOeA8=",
1511
+ }
1512
+ `);
1513
+ expect(backwardsPage2.edges.length).toBe(20);
1514
+ expect(backwardsPage2.edges[0].node.c).toBe("20");
1515
+ expect(backwardsPage2.edges[19].node.c).toBe("39");
1516
+ const backwardsPage1 = yield client.paginate(C, { before: backwardsPage2.pageInfo.startCursor }, {
1517
+ KeyConditionExpression: "PK = :pk",
1518
+ ExpressionAttributeValues: { ":pk": "PK" }
1519
+ });
1520
+ expect(backwardsPage1.pageInfo).toMatchInlineSnapshot(`
1521
+ Object {
1522
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo7vfn9aOeA8=",
1523
+ "hasNextPage": false,
1524
+ "hasPreviousPage": false,
1525
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1526
+ }
1527
+ `);
1528
+ expect(backwardsPage1.edges.length).toBe(20);
1529
+ expect(backwardsPage1.edges[0].node.c).toBe("0");
1530
+ expect(backwardsPage1.edges[19].node.c).toBe("19");
1531
+ }));
1532
+ test("it paginates a union model", () => __awaiter(void 0, void 0, void 0, function* () {
1533
+ const items = Array.from({ length: 60 }).map((_, i) => i > 30
1534
+ ? new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) })
1535
+ : new D({ pk: "PK", sk: String(i).padStart(3, "0"), d: String(i) }));
1536
+ yield sandbox.seed(...items);
1537
+ // Forwards
1538
+ const page1 = yield client.paginate(Union, {}, {
1539
+ KeyConditionExpression: "PK = :pk",
1540
+ ExpressionAttributeValues: { ":pk": "PK" }
1541
+ });
1542
+ expect(page1.pageInfo).toMatchInlineSnapshot(`
1543
+ Object {
1544
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo7vfn9aOeA8=",
1545
+ "hasNextPage": true,
1546
+ "hasPreviousPage": false,
1547
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1548
+ }
1549
+ `);
1550
+ expect(page1.edges.length).toBe(20);
1551
+ expect(page1.edges[0].node.SK).toBe("000");
1552
+ expect(page1.edges[19].node.SK).toBe("019");
1553
+ const page2 = yield client.paginate(Union, { after: page1.pageInfo.endCursor }, {
1554
+ KeyConditionExpression: "PK = :pk",
1555
+ ExpressionAttributeValues: { ":pk": "PK" }
1556
+ });
1557
+ expect(page2.pageInfo).toMatchInlineSnapshot(`
1558
+ Object {
1559
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo5Xfn9aOeA8=",
1560
+ "hasNextPage": true,
1561
+ "hasPreviousPage": false,
1562
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo4X1n9aOeA8=",
1563
+ }
1564
+ `);
1565
+ expect(page2.edges.length).toBe(20);
1566
+ expect(page2.edges[0].node.SK).toBe("020");
1567
+ expect(page2.edges[19].node.SK).toBe("039");
1568
+ const page3 = yield client.paginate(Union, { after: page2.pageInfo.endCursor }, {
1569
+ KeyConditionExpression: "PK = :pk",
1570
+ ExpressionAttributeValues: { ":pk": "PK" }
1571
+ });
1572
+ expect(page3.pageInfo).toMatchInlineSnapshot(`
1573
+ Object {
1574
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoLvfn9aOeA8=",
1575
+ "hasNextPage": false,
1576
+ "hasPreviousPage": false,
1577
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoKv1n9aOeA8=",
1578
+ }
1579
+ `);
1580
+ expect(page3.edges.length).toBe(20);
1581
+ expect(page3.edges[0].node.SK).toBe("040");
1582
+ expect(page3.edges[19].node.SK).toBe("059");
1583
+ // Backwards
1584
+ const backwardsPage2 = yield client.paginate(Union, { before: page3.pageInfo.startCursor }, {
1585
+ KeyConditionExpression: "PK = :pk",
1586
+ ExpressionAttributeValues: { ":pk": "PK" }
1587
+ });
1588
+ expect(backwardsPage2.pageInfo).toMatchInlineSnapshot(`
1589
+ Object {
1590
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo5Xfn9aOeA8=",
1591
+ "hasNextPage": false,
1592
+ "hasPreviousPage": true,
1593
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo4X1n9aOeA8=",
1594
+ }
1595
+ `);
1596
+ expect(backwardsPage2.edges.length).toBe(20);
1597
+ expect(backwardsPage2.edges[0].node.SK).toBe("020");
1598
+ expect(backwardsPage2.edges[19].node.SK).toBe("039");
1599
+ const backwardsPage1 = yield client.paginate(Union, { before: backwardsPage2.pageInfo.startCursor }, {
1600
+ KeyConditionExpression: "PK = :pk",
1601
+ ExpressionAttributeValues: { ":pk": "PK" }
1602
+ });
1603
+ expect(backwardsPage1.pageInfo).toMatchInlineSnapshot(`
1604
+ Object {
1605
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo7vfn9aOeA8=",
1606
+ "hasNextPage": false,
1607
+ "hasPreviousPage": false,
1608
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1609
+ }
1610
+ `);
1611
+ expect(backwardsPage1.edges.length).toBe(20);
1612
+ expect(backwardsPage1.edges[0].node.SK).toBe("000");
1613
+ expect(backwardsPage1.edges[19].node.SK).toBe("019");
1614
+ }));
1615
+ test("it respects a limit", () => __awaiter(void 0, void 0, void 0, function* () {
1616
+ const items = Array.from({ length: 60 }).map((_, i) => new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) }));
1617
+ yield sandbox.seed(...items);
1618
+ // Forwards
1619
+ const page = yield client.paginate(C, { first: 10 }, {
1620
+ KeyConditionExpression: "PK = :pk",
1621
+ ExpressionAttributeValues: { ":pk": "PK" }
1622
+ });
1623
+ expect(page.pageInfo).toMatchInlineSnapshot(`
1624
+ Object {
1625
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6vfn9aOeA8=",
1626
+ "hasNextPage": true,
1627
+ "hasPreviousPage": false,
1628
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1629
+ }
1630
+ `);
1631
+ expect(page.edges.length).toBe(10);
1632
+ expect(page.edges[0].node.c).toBe("0");
1633
+ expect(page.edges[9].node.c).toBe("9");
1634
+ }));
1635
+ test("it doesn't exceed the max limit", () => __awaiter(void 0, void 0, void 0, function* () {
1636
+ const items = Array.from({ length: 60 }).map((_, i) => new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) }));
1637
+ yield sandbox.seed(...items);
1638
+ // Forwards
1639
+ const page1 = yield client.paginate(C, { first: 60 }, {
1640
+ KeyConditionExpression: "PK = :pk",
1641
+ ExpressionAttributeValues: { ":pk": "PK" }
1642
+ });
1643
+ expect(page1.pageInfo).toMatchInlineSnapshot(`
1644
+ Object {
1645
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoKvfn9aOeA8=",
1646
+ "hasNextPage": true,
1647
+ "hasPreviousPage": false,
1648
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1649
+ }
1650
+ `);
1651
+ expect(page1.edges.length).toBe(50);
1652
+ expect(page1.edges[0].node.c).toBe("0");
1653
+ expect(page1.edges[49].node.c).toBe("49");
1654
+ }));
1655
+ });
1656
+ describe("model", () => {
1657
+ test("it paginates a regular model", () => __awaiter(void 0, void 0, void 0, function* () {
1658
+ const items = Array.from({ length: 60 }).map((_, i) => new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) }));
1659
+ yield sandbox.seed(...items);
1660
+ // Forwards
1661
+ const page1 = yield C.paginate({}, {
1662
+ KeyConditionExpression: "PK = :pk",
1663
+ ExpressionAttributeValues: { ":pk": "PK" }
1664
+ });
1665
+ expect(page1.pageInfo).toMatchInlineSnapshot(`
1666
+ Object {
1667
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo7vfn9aOeA8=",
1668
+ "hasNextPage": true,
1669
+ "hasPreviousPage": false,
1670
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1671
+ }
1672
+ `);
1673
+ expect(page1.edges.length).toBe(20);
1674
+ expect(page1.edges[0].node.c).toBe("0");
1675
+ expect(page1.edges[19].node.c).toBe("19");
1676
+ const page2 = yield C.paginate({ after: page1.pageInfo.endCursor }, {
1677
+ KeyConditionExpression: "PK = :pk",
1678
+ ExpressionAttributeValues: { ":pk": "PK" }
1679
+ });
1680
+ expect(page2.pageInfo).toMatchInlineSnapshot(`
1681
+ Object {
1682
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo5Xfn9aOeA8=",
1683
+ "hasNextPage": true,
1684
+ "hasPreviousPage": false,
1685
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo4X1n9aOeA8=",
1686
+ }
1687
+ `);
1688
+ expect(page2.edges.length).toBe(20);
1689
+ expect(page2.edges[0].node.c).toBe("20");
1690
+ expect(page2.edges[19].node.c).toBe("39");
1691
+ const page3 = yield C.paginate({ after: page2.pageInfo.endCursor }, {
1692
+ KeyConditionExpression: "PK = :pk",
1693
+ ExpressionAttributeValues: { ":pk": "PK" }
1694
+ });
1695
+ expect(page3.pageInfo).toMatchInlineSnapshot(`
1696
+ Object {
1697
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoLvfn9aOeA8=",
1698
+ "hasNextPage": false,
1699
+ "hasPreviousPage": false,
1700
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoKv1n9aOeA8=",
1701
+ }
1702
+ `);
1703
+ expect(page3.edges.length).toBe(20);
1704
+ expect(page3.edges[0].node.c).toBe("40");
1705
+ expect(page3.edges[19].node.c).toBe("59");
1706
+ // Backwards
1707
+ const backwardsPage2 = yield C.paginate({ before: page3.pageInfo.startCursor }, {
1708
+ KeyConditionExpression: "PK = :pk",
1709
+ ExpressionAttributeValues: { ":pk": "PK" }
1710
+ });
1711
+ expect(backwardsPage2.pageInfo).toMatchInlineSnapshot(`
1712
+ Object {
1713
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo5Xfn9aOeA8=",
1714
+ "hasNextPage": false,
1715
+ "hasPreviousPage": true,
1716
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo4X1n9aOeA8=",
1717
+ }
1718
+ `);
1719
+ expect(backwardsPage2.edges.length).toBe(20);
1720
+ expect(backwardsPage2.edges[0].node.c).toBe("20");
1721
+ expect(backwardsPage2.edges[19].node.c).toBe("39");
1722
+ const backwardsPage1 = yield C.paginate({ before: backwardsPage2.pageInfo.startCursor }, {
1723
+ KeyConditionExpression: "PK = :pk",
1724
+ ExpressionAttributeValues: { ":pk": "PK" }
1725
+ });
1726
+ expect(backwardsPage1.pageInfo).toMatchInlineSnapshot(`
1727
+ Object {
1728
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo7vfn9aOeA8=",
1729
+ "hasNextPage": false,
1730
+ "hasPreviousPage": false,
1731
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1732
+ }
1733
+ `);
1734
+ expect(backwardsPage1.edges.length).toBe(20);
1735
+ expect(backwardsPage1.edges[0].node.c).toBe("0");
1736
+ expect(backwardsPage1.edges[19].node.c).toBe("19");
1737
+ }));
1738
+ test("it respects a limit", () => __awaiter(void 0, void 0, void 0, function* () {
1739
+ const items = Array.from({ length: 60 }).map((_, i) => new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) }));
1740
+ yield sandbox.seed(...items);
1741
+ // Forwards
1742
+ const page = yield C.paginate({ first: 10 }, {
1743
+ KeyConditionExpression: "PK = :pk",
1744
+ ExpressionAttributeValues: { ":pk": "PK" }
1745
+ });
1746
+ expect(page.pageInfo).toMatchInlineSnapshot(`
1747
+ Object {
1748
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6vfn9aOeA8=",
1749
+ "hasNextPage": true,
1750
+ "hasPreviousPage": false,
1751
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1752
+ }
1753
+ `);
1754
+ expect(page.edges.length).toBe(10);
1755
+ expect(page.edges[0].node.c).toBe("0");
1756
+ expect(page.edges[9].node.c).toBe("9");
1757
+ }));
1758
+ test("it doesn't exceed the max limit", () => __awaiter(void 0, void 0, void 0, function* () {
1759
+ const items = Array.from({ length: 60 }).map((_, i) => new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) }));
1760
+ yield sandbox.seed(...items);
1761
+ // Forwards
1762
+ const page1 = yield C.paginate({ first: 60 }, {
1763
+ KeyConditionExpression: "PK = :pk",
1764
+ ExpressionAttributeValues: { ":pk": "PK" }
1765
+ });
1766
+ expect(page1.pageInfo).toMatchInlineSnapshot(`
1767
+ Object {
1768
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoKvfn9aOeA8=",
1769
+ "hasNextPage": true,
1770
+ "hasPreviousPage": false,
1771
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1772
+ }
1773
+ `);
1774
+ expect(page1.edges.length).toBe(50);
1775
+ expect(page1.edges[0].node.c).toBe("0");
1776
+ expect(page1.edges[49].node.c).toBe("49");
1777
+ }));
1778
+ });
1779
+ describe("union", () => {
1780
+ test("it paginates a union model", () => __awaiter(void 0, void 0, void 0, function* () {
1781
+ const items = Array.from({ length: 60 }).map((_, i) => i > 30
1782
+ ? new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) })
1783
+ : new D({ pk: "PK", sk: String(i).padStart(3, "0"), d: String(i) }));
1784
+ yield sandbox.seed(...items);
1785
+ // Forwards
1786
+ const page1 = yield Union.paginate({}, {
1787
+ KeyConditionExpression: "PK = :pk",
1788
+ ExpressionAttributeValues: { ":pk": "PK" }
1789
+ });
1790
+ expect(page1.pageInfo).toMatchInlineSnapshot(`
1791
+ Object {
1792
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo7vfn9aOeA8=",
1793
+ "hasNextPage": true,
1794
+ "hasPreviousPage": false,
1795
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1796
+ }
1797
+ `);
1798
+ expect(page1.edges.length).toBe(20);
1799
+ expect(page1.edges[0].node.SK).toBe("000");
1800
+ expect(page1.edges[19].node.SK).toBe("019");
1801
+ const page2 = yield Union.paginate({ after: page1.pageInfo.endCursor }, {
1802
+ KeyConditionExpression: "PK = :pk",
1803
+ ExpressionAttributeValues: { ":pk": "PK" }
1804
+ });
1805
+ expect(page2.pageInfo).toMatchInlineSnapshot(`
1806
+ Object {
1807
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo5Xfn9aOeA8=",
1808
+ "hasNextPage": true,
1809
+ "hasPreviousPage": false,
1810
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo4X1n9aOeA8=",
1811
+ }
1812
+ `);
1813
+ expect(page2.edges.length).toBe(20);
1814
+ expect(page2.edges[0].node.SK).toBe("020");
1815
+ expect(page2.edges[19].node.SK).toBe("039");
1816
+ const page3 = yield Union.paginate({ after: page2.pageInfo.endCursor }, {
1817
+ KeyConditionExpression: "PK = :pk",
1818
+ ExpressionAttributeValues: { ":pk": "PK" }
1819
+ });
1820
+ expect(page3.pageInfo).toMatchInlineSnapshot(`
1821
+ Object {
1822
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoLvfn9aOeA8=",
1823
+ "hasNextPage": false,
1824
+ "hasPreviousPage": false,
1825
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoKv1n9aOeA8=",
1826
+ }
1827
+ `);
1828
+ expect(page3.edges.length).toBe(20);
1829
+ expect(page3.edges[0].node.SK).toBe("040");
1830
+ expect(page3.edges[19].node.SK).toBe("059");
1831
+ // Backwards
1832
+ const backwardsPage2 = yield Union.paginate({ before: page3.pageInfo.startCursor }, {
1833
+ KeyConditionExpression: "PK = :pk",
1834
+ ExpressionAttributeValues: { ":pk": "PK" }
1835
+ });
1836
+ expect(backwardsPage2.pageInfo).toMatchInlineSnapshot(`
1837
+ Object {
1838
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo5Xfn9aOeA8=",
1839
+ "hasNextPage": false,
1840
+ "hasPreviousPage": true,
1841
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo4X1n9aOeA8=",
1842
+ }
1843
+ `);
1844
+ expect(backwardsPage2.edges.length).toBe(20);
1845
+ expect(backwardsPage2.edges[0].node.SK).toBe("020");
1846
+ expect(backwardsPage2.edges[19].node.SK).toBe("039");
1847
+ const backwardsPage1 = yield Union.paginate({ before: backwardsPage2.pageInfo.startCursor }, {
1848
+ KeyConditionExpression: "PK = :pk",
1849
+ ExpressionAttributeValues: { ":pk": "PK" }
1850
+ });
1851
+ expect(backwardsPage1.pageInfo).toMatchInlineSnapshot(`
1852
+ Object {
1853
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo7vfn9aOeA8=",
1854
+ "hasNextPage": false,
1855
+ "hasPreviousPage": false,
1856
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1857
+ }
1858
+ `);
1859
+ expect(backwardsPage1.edges.length).toBe(20);
1860
+ expect(backwardsPage1.edges[0].node.SK).toBe("000");
1861
+ expect(backwardsPage1.edges[19].node.SK).toBe("019");
1862
+ }));
1863
+ test("it respects a limit", () => __awaiter(void 0, void 0, void 0, function* () {
1864
+ const items = Array.from({ length: 60 }).map((_, i) => i > 30
1865
+ ? new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) })
1866
+ : new D({ pk: "PK", sk: String(i).padStart(3, "0"), d: String(i) }));
1867
+ yield sandbox.seed(...items);
1868
+ // Forwards
1869
+ const page = yield Union.paginate({ first: 10 }, {
1870
+ KeyConditionExpression: "PK = :pk",
1871
+ ExpressionAttributeValues: { ":pk": "PK" }
1872
+ });
1873
+ expect(page.pageInfo).toMatchInlineSnapshot(`
1874
+ Object {
1875
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6vfn9aOeA8=",
1876
+ "hasNextPage": true,
1877
+ "hasPreviousPage": false,
1878
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1879
+ }
1880
+ `);
1881
+ expect(page.edges.length).toBe(10);
1882
+ expect(page.edges[0].node.SK).toBe("000");
1883
+ expect(page.edges[9].node.SK).toBe("009");
1884
+ }));
1885
+ test("it doesn't exceed the max limit", () => __awaiter(void 0, void 0, void 0, function* () {
1886
+ const items = Array.from({ length: 60 }).map((_, i) => i > 30
1887
+ ? new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) })
1888
+ : new D({ pk: "PK", sk: String(i).padStart(3, "0"), d: String(i) }));
1889
+ yield sandbox.seed(...items);
1890
+ // Forwards
1891
+ const page1 = yield Union.paginate({ first: 60 }, {
1892
+ KeyConditionExpression: "PK = :pk",
1893
+ ExpressionAttributeValues: { ":pk": "PK" }
1894
+ });
1895
+ expect(page1.pageInfo).toMatchInlineSnapshot(`
1896
+ Object {
1897
+ "endCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOoKvfn9aOeA8=",
1898
+ "hasNextPage": true,
1899
+ "hasPreviousPage": false,
1900
+ "startCursor": "cC4wNVXawu0oBvB8vqW4J/RG6hbr3ndOo6v1n9aOeA8=",
1901
+ }
1902
+ `);
1903
+ expect(page1.edges.length).toBe(50);
1904
+ expect(page1.edges[0].node.SK).toBe("000");
1905
+ expect(page1.edges[49].node.SK).toBe("049");
1906
+ }));
1907
+ });
1908
+ });
1909
+ //# sourceMappingURL=client-with-cursor-encryption.test.js.map