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