@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.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/__test__/client-with-cursor-encryption.test.d.ts +1 -0
- package/dist/cjs/__test__/client-with-cursor-encryption.test.js +1911 -0
- package/dist/cjs/__test__/client-with-cursor-encryption.test.js.map +1 -0
- package/dist/cjs/__test__/client.test.js +77 -56
- package/dist/cjs/__test__/client.test.js.map +1 -1
- package/dist/cjs/client.d.ts +14 -2
- package/dist/cjs/client.js +28 -9
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/dynamodb-model.d.ts +4 -0
- package/dist/cjs/pagination.d.ts +48 -3
- package/dist/cjs/pagination.js +65 -23
- package/dist/cjs/pagination.js.map +1 -1
- package/dist/cjs/provider.d.ts +21 -20
- package/dist/cjs/provider.js +5 -1
- package/dist/cjs/provider.js.map +1 -1
- package/dist/esm/__test__/client-with-cursor-encryption.test.d.ts +1 -0
- package/dist/esm/__test__/client-with-cursor-encryption.test.js +1909 -0
- package/dist/esm/__test__/client-with-cursor-encryption.test.js.map +1 -0
- package/dist/esm/__test__/client.test.js +78 -57
- package/dist/esm/__test__/client.test.js.map +1 -1
- package/dist/esm/client.d.ts +14 -2
- package/dist/esm/client.js +28 -9
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/dynamodb-model.d.ts +4 -0
- package/dist/esm/pagination.d.ts +48 -3
- package/dist/esm/pagination.js +64 -23
- package/dist/esm/pagination.js.map +1 -1
- package/dist/esm/provider.d.ts +21 -20
- package/dist/esm/provider.js +5 -1
- package/dist/esm/provider.js.map +1 -1
- package/package.json +2 -2
- package/src/__test__/client-with-cursor-encryption.test.ts +2445 -0
- package/src/__test__/client.test.ts +92 -57
- package/src/client.ts +54 -13
- package/src/dynamodb-model.ts +4 -0
- package/src/pagination.ts +76 -19
- package/src/provider.ts +5 -2
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
ItemNotFoundError,
|
|
9
9
|
ConditionalCheckFailedError,
|
|
10
10
|
RaceConditionError,
|
|
11
|
-
BulkWriteTransactionError
|
|
11
|
+
BulkWriteTransactionError,
|
|
12
12
|
} from "../errors"
|
|
13
13
|
|
|
14
14
|
const client = new Client({ tableName: "table" })
|
|
@@ -16,7 +16,7 @@ const provider = getProvider(client)
|
|
|
16
16
|
|
|
17
17
|
const SIMPLE_CODEC = t.type({
|
|
18
18
|
foo: t.string,
|
|
19
|
-
bar: t.number
|
|
19
|
+
bar: t.number,
|
|
20
20
|
})
|
|
21
21
|
|
|
22
22
|
class Simple extends model("Simple", SIMPLE_CODEC, provider) {
|
|
@@ -331,7 +331,7 @@ describe("put", () => {
|
|
|
331
331
|
|
|
332
332
|
await expect(
|
|
333
333
|
MultiGSI.put(new MultiGSI({ foo: "yes", bar: 42 }), {
|
|
334
|
-
IgnoreExistence: true
|
|
334
|
+
IgnoreExistence: true,
|
|
335
335
|
})
|
|
336
336
|
).resolves.toBeInstanceOf(MultiGSI)
|
|
337
337
|
})
|
|
@@ -351,7 +351,7 @@ describe("get", () => {
|
|
|
351
351
|
|
|
352
352
|
const result = await Simple.get({
|
|
353
353
|
PK: item.keys().PK,
|
|
354
|
-
SK: item.keys().SK
|
|
354
|
+
SK: item.keys().SK,
|
|
355
355
|
})
|
|
356
356
|
|
|
357
357
|
expect(result.values()).toMatchInlineSnapshot(`
|
|
@@ -417,8 +417,8 @@ describe("delete", () => {
|
|
|
417
417
|
_model: Simple,
|
|
418
418
|
key: {
|
|
419
419
|
PK: item.keys().PK,
|
|
420
|
-
SK: item.keys().SK
|
|
421
|
-
}
|
|
420
|
+
SK: item.keys().SK,
|
|
421
|
+
},
|
|
422
422
|
})
|
|
423
423
|
|
|
424
424
|
expect(result).toBeNull()
|
|
@@ -451,7 +451,7 @@ describe("delete", () => {
|
|
|
451
451
|
|
|
452
452
|
const result = await Simple.delete({
|
|
453
453
|
PK: item.keys().PK,
|
|
454
|
-
SK: item.keys().SK
|
|
454
|
+
SK: item.keys().SK,
|
|
455
455
|
})
|
|
456
456
|
|
|
457
457
|
expect(result).toBeNull()
|
|
@@ -826,7 +826,7 @@ describe("query", () => {
|
|
|
826
826
|
await client.query(
|
|
827
827
|
{
|
|
828
828
|
KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
|
|
829
|
-
ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT" }
|
|
829
|
+
ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT" },
|
|
830
830
|
},
|
|
831
831
|
{ a: A, b: B, union: Union }
|
|
832
832
|
)
|
|
@@ -850,7 +850,7 @@ describe("query", () => {
|
|
|
850
850
|
await client.query(
|
|
851
851
|
{
|
|
852
852
|
KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
|
|
853
|
-
ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" }
|
|
853
|
+
ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" },
|
|
854
854
|
},
|
|
855
855
|
{ a: A, b: B, union: Union }
|
|
856
856
|
)
|
|
@@ -886,7 +886,7 @@ describe("query", () => {
|
|
|
886
886
|
const { a, b, union, _unknown, meta } = await client.query(
|
|
887
887
|
{
|
|
888
888
|
KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
|
|
889
|
-
ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" }
|
|
889
|
+
ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" },
|
|
890
890
|
},
|
|
891
891
|
{ a: A, b: B, union: Union }
|
|
892
892
|
)
|
|
@@ -894,9 +894,9 @@ describe("query", () => {
|
|
|
894
894
|
expect({
|
|
895
895
|
meta: meta,
|
|
896
896
|
_unknown: _unknown,
|
|
897
|
-
a: a.map(item => item.values()),
|
|
898
|
-
b: b.map(item => item.values()),
|
|
899
|
-
union: union.map(item => item.values())
|
|
897
|
+
a: a.map((item) => item.values()),
|
|
898
|
+
b: b.map((item) => item.values()),
|
|
899
|
+
union: union.map((item) => item.values()),
|
|
900
900
|
}).toMatchInlineSnapshot(`
|
|
901
901
|
Object {
|
|
902
902
|
"_unknown": Array [
|
|
@@ -959,7 +959,7 @@ describe("query", () => {
|
|
|
959
959
|
{
|
|
960
960
|
KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
|
|
961
961
|
ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" },
|
|
962
|
-
Limit: 30
|
|
962
|
+
Limit: 30,
|
|
963
963
|
},
|
|
964
964
|
{ a: A, b: B }
|
|
965
965
|
)
|
|
@@ -974,7 +974,7 @@ describe("query", () => {
|
|
|
974
974
|
KeyConditionExpression: `PK = :pk and begins_with(SK, :sk)`,
|
|
975
975
|
ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" },
|
|
976
976
|
Limit: 30,
|
|
977
|
-
ExclusiveStartKey: firstPage.meta.lastEvaluatedKey
|
|
977
|
+
ExclusiveStartKey: firstPage.meta.lastEvaluatedKey,
|
|
978
978
|
},
|
|
979
979
|
{ a: A, b: B }
|
|
980
980
|
)
|
|
@@ -1002,7 +1002,7 @@ describe("query", () => {
|
|
|
1002
1002
|
ExpressionAttributeValues: { ":pk": "abc", ":sk": "SORT#" },
|
|
1003
1003
|
FetchAllPages: true,
|
|
1004
1004
|
// You wouldn't set a limit in a real-world use case here to optimize fetching all items.
|
|
1005
|
-
Limit: 10
|
|
1005
|
+
Limit: 10,
|
|
1006
1006
|
},
|
|
1007
1007
|
{ a: A, b: B }
|
|
1008
1008
|
)
|
|
@@ -1039,16 +1039,16 @@ describe("bulk", () => {
|
|
|
1039
1039
|
new B({
|
|
1040
1040
|
pk: "PK#UPDATE",
|
|
1041
1041
|
sk: "SK#UPDATE",
|
|
1042
|
-
b: "bar"
|
|
1042
|
+
b: "bar",
|
|
1043
1043
|
}).operation("update", { b: "baz" }),
|
|
1044
1044
|
new B({
|
|
1045
1045
|
pk: "PK#COND",
|
|
1046
1046
|
sk: "SK#COND",
|
|
1047
|
-
b: "cond"
|
|
1047
|
+
b: "cond",
|
|
1048
1048
|
}).operation("condition", {
|
|
1049
1049
|
ConditionExpression: "b = :cond",
|
|
1050
|
-
ExpressionAttributeValues: { ":cond": "cond" }
|
|
1051
|
-
})
|
|
1050
|
+
ExpressionAttributeValues: { ":cond": "cond" },
|
|
1051
|
+
}),
|
|
1052
1052
|
])
|
|
1053
1053
|
|
|
1054
1054
|
expect(await sandbox.diff(before)).toMatchInlineSnapshot(`
|
|
@@ -1168,7 +1168,7 @@ describe("bulk", () => {
|
|
|
1168
1168
|
"updateRaw",
|
|
1169
1169
|
{ PK: "PK#nicetry", SK: "SK#nope" },
|
|
1170
1170
|
{ a: 234 }
|
|
1171
|
-
)
|
|
1171
|
+
),
|
|
1172
1172
|
])
|
|
1173
1173
|
).rejects.toBeInstanceOf(BulkWriteTransactionError)
|
|
1174
1174
|
|
|
@@ -1196,11 +1196,11 @@ describe("bulk", () => {
|
|
|
1196
1196
|
new B({
|
|
1197
1197
|
pk: "PK#UPDATE",
|
|
1198
1198
|
sk: "SK#UPDATE",
|
|
1199
|
-
b: "bar"
|
|
1199
|
+
b: "bar",
|
|
1200
1200
|
}).operation("update", { b: "baz" }),
|
|
1201
1201
|
...Array.from({ length: 25 }).map((_, i) =>
|
|
1202
1202
|
new A({ pk: `PK#A${i}`, sk: `SK#A${i}`, a: i }).operation("put")
|
|
1203
|
-
)
|
|
1203
|
+
),
|
|
1204
1204
|
])
|
|
1205
1205
|
|
|
1206
1206
|
//#region snapshot
|
|
@@ -1512,7 +1512,7 @@ describe("bulk", () => {
|
|
|
1512
1512
|
"condition",
|
|
1513
1513
|
{ PK: "nicetry", SK: "nope" },
|
|
1514
1514
|
{ ConditionExpression: "attribute_exists(PK)" }
|
|
1515
|
-
)
|
|
1515
|
+
),
|
|
1516
1516
|
])
|
|
1517
1517
|
).rejects.toBeInstanceOf(BulkWriteTransactionError)
|
|
1518
1518
|
|
|
@@ -1546,7 +1546,7 @@ describe("batchGet", () => {
|
|
|
1546
1546
|
two: A.operation("get", { PK: "PK#2", SK: "SK#2" }),
|
|
1547
1547
|
three: A.operation("get", { PK: "PK#3", SK: "SK#3" }),
|
|
1548
1548
|
four: A.operation("get", { PK: "PK#4", SK: "SK#4" }),
|
|
1549
|
-
duplicate: A.operation("get", { PK: "PK#1", SK: "SK#1" })
|
|
1549
|
+
duplicate: A.operation("get", { PK: "PK#1", SK: "SK#1" }),
|
|
1550
1550
|
})
|
|
1551
1551
|
).rejects.toBeInstanceOf(ItemNotFoundError)
|
|
1552
1552
|
})
|
|
@@ -1563,7 +1563,7 @@ describe("batchGet", () => {
|
|
|
1563
1563
|
two: A.operation("get", { PK: "PK#2", SK: "SK#2" }),
|
|
1564
1564
|
duplicate: A.operation("get", { PK: "PK#1", SK: "SK#1" }),
|
|
1565
1565
|
error: A.operation("get", { PK: "PK#error", SK: "SK#error" }),
|
|
1566
|
-
error2: A.operation("get", { PK: "PK#error2", SK: "SK#error2" })
|
|
1566
|
+
error2: A.operation("get", { PK: "PK#error2", SK: "SK#error2" }),
|
|
1567
1567
|
},
|
|
1568
1568
|
{ individualErrors: true }
|
|
1569
1569
|
)
|
|
@@ -1587,7 +1587,7 @@ describe("batchGet", () => {
|
|
|
1587
1587
|
two: A.operation("get", { PK: "PK#2", SK: "SK#2" }),
|
|
1588
1588
|
three: A.operation("get", { PK: "PK#3", SK: "SK#3" }),
|
|
1589
1589
|
four: A.operation("get", { PK: "PK#4", SK: "SK#4" }),
|
|
1590
|
-
duplicate: A.operation("get", { PK: "PK#1", SK: "SK#1" })
|
|
1590
|
+
duplicate: A.operation("get", { PK: "PK#1", SK: "SK#1" }),
|
|
1591
1591
|
})
|
|
1592
1592
|
|
|
1593
1593
|
expect(
|
|
@@ -1640,6 +1640,41 @@ describe("load", () => {
|
|
|
1640
1640
|
).resolves.toBeNull()
|
|
1641
1641
|
})
|
|
1642
1642
|
|
|
1643
|
+
test("it recovers a soft deleted item", async () => {
|
|
1644
|
+
const item = new A({ pk: "PK#1", sk: "SK#1", a: 1 })
|
|
1645
|
+
|
|
1646
|
+
await sandbox.seed(item)
|
|
1647
|
+
|
|
1648
|
+
await item.softDelete()
|
|
1649
|
+
|
|
1650
|
+
const recovered = await client.load(
|
|
1651
|
+
A.operation("get", { PK: "PK#1", SK: "SK#1" }),
|
|
1652
|
+
{
|
|
1653
|
+
recover: true,
|
|
1654
|
+
}
|
|
1655
|
+
)
|
|
1656
|
+
|
|
1657
|
+
expect(recovered).toBeInstanceOf(A)
|
|
1658
|
+
expect(recovered.isDeleted).toBe(true)
|
|
1659
|
+
})
|
|
1660
|
+
|
|
1661
|
+
test("it throws if no item or soft deleted item exists", async () => {
|
|
1662
|
+
await expect(
|
|
1663
|
+
client.load(A.operation("get", { PK: "PK", SK: "SK" }), {
|
|
1664
|
+
recover: true,
|
|
1665
|
+
})
|
|
1666
|
+
).rejects.toBeInstanceOf(ItemNotFoundError)
|
|
1667
|
+
})
|
|
1668
|
+
|
|
1669
|
+
test("it returns null instead of throwing if no item or soft deleted item exists", async () => {
|
|
1670
|
+
await expect(
|
|
1671
|
+
client.load(A.operation("get", { PK: "PK", SK: "SK" }), {
|
|
1672
|
+
recover: true,
|
|
1673
|
+
null: true,
|
|
1674
|
+
})
|
|
1675
|
+
).resolves.toBeNull()
|
|
1676
|
+
})
|
|
1677
|
+
|
|
1643
1678
|
test("it fetches >100 items", async () => {
|
|
1644
1679
|
const items = Array.from({ length: 234 }).map((_, i) =>
|
|
1645
1680
|
i < 100
|
|
@@ -1730,8 +1765,8 @@ describe("load", () => {
|
|
|
1730
1765
|
)
|
|
1731
1766
|
|
|
1732
1767
|
expect(results.length).toBe(234)
|
|
1733
|
-
expect(results.filter(item => item instanceof C).length).toBe(123)
|
|
1734
|
-
expect(results.filter(item => item instanceof D).length).toBe(111)
|
|
1768
|
+
expect(results.filter((item) => item instanceof C).length).toBe(123)
|
|
1769
|
+
expect(results.filter((item) => item instanceof D).length).toBe(111)
|
|
1735
1770
|
expect(spy).toHaveBeenCalledTimes(3)
|
|
1736
1771
|
|
|
1737
1772
|
spy.mockReset()
|
|
@@ -1806,8 +1841,8 @@ describe("loadMany", () => {
|
|
|
1806
1841
|
)
|
|
1807
1842
|
|
|
1808
1843
|
expect(results.length).toBe(234)
|
|
1809
|
-
expect(results.filter(item => item instanceof C).length).toBe(123)
|
|
1810
|
-
expect(results.filter(item => item instanceof D).length).toBe(111)
|
|
1844
|
+
expect(results.filter((item) => item instanceof C).length).toBe(123)
|
|
1845
|
+
expect(results.filter((item) => item instanceof D).length).toBe(111)
|
|
1811
1846
|
expect(spy).toHaveBeenCalledTimes(3)
|
|
1812
1847
|
|
|
1813
1848
|
spy.mockReset()
|
|
@@ -1832,7 +1867,7 @@ describe("paginate", () => {
|
|
|
1832
1867
|
{},
|
|
1833
1868
|
{
|
|
1834
1869
|
KeyConditionExpression: "PK = :pk",
|
|
1835
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
1870
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
1836
1871
|
}
|
|
1837
1872
|
)
|
|
1838
1873
|
expect(page1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -1852,7 +1887,7 @@ describe("paginate", () => {
|
|
|
1852
1887
|
{ after: page1.pageInfo.endCursor },
|
|
1853
1888
|
{
|
|
1854
1889
|
KeyConditionExpression: "PK = :pk",
|
|
1855
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
1890
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
1856
1891
|
}
|
|
1857
1892
|
)
|
|
1858
1893
|
expect(page2.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -1872,7 +1907,7 @@ describe("paginate", () => {
|
|
|
1872
1907
|
{ after: page2.pageInfo.endCursor },
|
|
1873
1908
|
{
|
|
1874
1909
|
KeyConditionExpression: "PK = :pk",
|
|
1875
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
1910
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
1876
1911
|
}
|
|
1877
1912
|
)
|
|
1878
1913
|
expect(page3.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -1893,7 +1928,7 @@ describe("paginate", () => {
|
|
|
1893
1928
|
{ before: page3.pageInfo.startCursor },
|
|
1894
1929
|
{
|
|
1895
1930
|
KeyConditionExpression: "PK = :pk",
|
|
1896
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
1931
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
1897
1932
|
}
|
|
1898
1933
|
)
|
|
1899
1934
|
expect(backwardsPage2.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -1913,7 +1948,7 @@ describe("paginate", () => {
|
|
|
1913
1948
|
{ before: backwardsPage2.pageInfo.startCursor },
|
|
1914
1949
|
{
|
|
1915
1950
|
KeyConditionExpression: "PK = :pk",
|
|
1916
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
1951
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
1917
1952
|
}
|
|
1918
1953
|
)
|
|
1919
1954
|
expect(backwardsPage1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -1944,7 +1979,7 @@ describe("paginate", () => {
|
|
|
1944
1979
|
{},
|
|
1945
1980
|
{
|
|
1946
1981
|
KeyConditionExpression: "PK = :pk",
|
|
1947
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
1982
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
1948
1983
|
}
|
|
1949
1984
|
)
|
|
1950
1985
|
expect(page1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -1964,7 +1999,7 @@ describe("paginate", () => {
|
|
|
1964
1999
|
{ after: page1.pageInfo.endCursor },
|
|
1965
2000
|
{
|
|
1966
2001
|
KeyConditionExpression: "PK = :pk",
|
|
1967
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2002
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
1968
2003
|
}
|
|
1969
2004
|
)
|
|
1970
2005
|
expect(page2.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -1984,7 +2019,7 @@ describe("paginate", () => {
|
|
|
1984
2019
|
{ after: page2.pageInfo.endCursor },
|
|
1985
2020
|
{
|
|
1986
2021
|
KeyConditionExpression: "PK = :pk",
|
|
1987
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2022
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
1988
2023
|
}
|
|
1989
2024
|
)
|
|
1990
2025
|
expect(page3.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2005,7 +2040,7 @@ describe("paginate", () => {
|
|
|
2005
2040
|
{ before: page3.pageInfo.startCursor },
|
|
2006
2041
|
{
|
|
2007
2042
|
KeyConditionExpression: "PK = :pk",
|
|
2008
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2043
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2009
2044
|
}
|
|
2010
2045
|
)
|
|
2011
2046
|
expect(backwardsPage2.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2025,7 +2060,7 @@ describe("paginate", () => {
|
|
|
2025
2060
|
{ before: backwardsPage2.pageInfo.startCursor },
|
|
2026
2061
|
{
|
|
2027
2062
|
KeyConditionExpression: "PK = :pk",
|
|
2028
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2063
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2029
2064
|
}
|
|
2030
2065
|
)
|
|
2031
2066
|
expect(backwardsPage1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2055,7 +2090,7 @@ describe("paginate", () => {
|
|
|
2055
2090
|
{ first: 10 },
|
|
2056
2091
|
{
|
|
2057
2092
|
KeyConditionExpression: "PK = :pk",
|
|
2058
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2093
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2059
2094
|
}
|
|
2060
2095
|
)
|
|
2061
2096
|
expect(page.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2085,7 +2120,7 @@ describe("paginate", () => {
|
|
|
2085
2120
|
{ first: 60 },
|
|
2086
2121
|
{
|
|
2087
2122
|
KeyConditionExpression: "PK = :pk",
|
|
2088
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2123
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2089
2124
|
}
|
|
2090
2125
|
)
|
|
2091
2126
|
expect(page1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2116,7 +2151,7 @@ describe("paginate", () => {
|
|
|
2116
2151
|
{},
|
|
2117
2152
|
{
|
|
2118
2153
|
KeyConditionExpression: "PK = :pk",
|
|
2119
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2154
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2120
2155
|
}
|
|
2121
2156
|
)
|
|
2122
2157
|
expect(page1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2135,7 +2170,7 @@ describe("paginate", () => {
|
|
|
2135
2170
|
{ after: page1.pageInfo.endCursor },
|
|
2136
2171
|
{
|
|
2137
2172
|
KeyConditionExpression: "PK = :pk",
|
|
2138
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2173
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2139
2174
|
}
|
|
2140
2175
|
)
|
|
2141
2176
|
expect(page2.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2154,7 +2189,7 @@ describe("paginate", () => {
|
|
|
2154
2189
|
{ after: page2.pageInfo.endCursor },
|
|
2155
2190
|
{
|
|
2156
2191
|
KeyConditionExpression: "PK = :pk",
|
|
2157
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2192
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2158
2193
|
}
|
|
2159
2194
|
)
|
|
2160
2195
|
expect(page3.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2174,7 +2209,7 @@ describe("paginate", () => {
|
|
|
2174
2209
|
{ before: page3.pageInfo.startCursor },
|
|
2175
2210
|
{
|
|
2176
2211
|
KeyConditionExpression: "PK = :pk",
|
|
2177
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2212
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2178
2213
|
}
|
|
2179
2214
|
)
|
|
2180
2215
|
expect(backwardsPage2.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2193,7 +2228,7 @@ describe("paginate", () => {
|
|
|
2193
2228
|
{ before: backwardsPage2.pageInfo.startCursor },
|
|
2194
2229
|
{
|
|
2195
2230
|
KeyConditionExpression: "PK = :pk",
|
|
2196
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2231
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2197
2232
|
}
|
|
2198
2233
|
)
|
|
2199
2234
|
expect(backwardsPage1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2222,7 +2257,7 @@ describe("paginate", () => {
|
|
|
2222
2257
|
{ first: 10 },
|
|
2223
2258
|
{
|
|
2224
2259
|
KeyConditionExpression: "PK = :pk",
|
|
2225
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2260
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2226
2261
|
}
|
|
2227
2262
|
)
|
|
2228
2263
|
expect(page.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2251,7 +2286,7 @@ describe("paginate", () => {
|
|
|
2251
2286
|
{ first: 60 },
|
|
2252
2287
|
{
|
|
2253
2288
|
KeyConditionExpression: "PK = :pk",
|
|
2254
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2289
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2255
2290
|
}
|
|
2256
2291
|
)
|
|
2257
2292
|
expect(page1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2283,7 +2318,7 @@ describe("paginate", () => {
|
|
|
2283
2318
|
{},
|
|
2284
2319
|
{
|
|
2285
2320
|
KeyConditionExpression: "PK = :pk",
|
|
2286
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2321
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2287
2322
|
}
|
|
2288
2323
|
)
|
|
2289
2324
|
expect(page1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2302,7 +2337,7 @@ describe("paginate", () => {
|
|
|
2302
2337
|
{ after: page1.pageInfo.endCursor },
|
|
2303
2338
|
{
|
|
2304
2339
|
KeyConditionExpression: "PK = :pk",
|
|
2305
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2340
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2306
2341
|
}
|
|
2307
2342
|
)
|
|
2308
2343
|
expect(page2.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2321,7 +2356,7 @@ describe("paginate", () => {
|
|
|
2321
2356
|
{ after: page2.pageInfo.endCursor },
|
|
2322
2357
|
{
|
|
2323
2358
|
KeyConditionExpression: "PK = :pk",
|
|
2324
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2359
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2325
2360
|
}
|
|
2326
2361
|
)
|
|
2327
2362
|
expect(page3.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2341,7 +2376,7 @@ describe("paginate", () => {
|
|
|
2341
2376
|
{ before: page3.pageInfo.startCursor },
|
|
2342
2377
|
{
|
|
2343
2378
|
KeyConditionExpression: "PK = :pk",
|
|
2344
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2379
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2345
2380
|
}
|
|
2346
2381
|
)
|
|
2347
2382
|
expect(backwardsPage2.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2360,7 +2395,7 @@ describe("paginate", () => {
|
|
|
2360
2395
|
{ before: backwardsPage2.pageInfo.startCursor },
|
|
2361
2396
|
{
|
|
2362
2397
|
KeyConditionExpression: "PK = :pk",
|
|
2363
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2398
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2364
2399
|
}
|
|
2365
2400
|
)
|
|
2366
2401
|
expect(backwardsPage1.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2390,7 +2425,7 @@ describe("paginate", () => {
|
|
|
2390
2425
|
{ first: 10 },
|
|
2391
2426
|
{
|
|
2392
2427
|
KeyConditionExpression: "PK = :pk",
|
|
2393
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2428
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2394
2429
|
}
|
|
2395
2430
|
)
|
|
2396
2431
|
expect(page.pageInfo).toMatchInlineSnapshot(`
|
|
@@ -2420,7 +2455,7 @@ describe("paginate", () => {
|
|
|
2420
2455
|
{ first: 60 },
|
|
2421
2456
|
{
|
|
2422
2457
|
KeyConditionExpression: "PK = :pk",
|
|
2423
|
-
ExpressionAttributeValues: { ":pk": "PK" }
|
|
2458
|
+
ExpressionAttributeValues: { ":pk": "PK" },
|
|
2424
2459
|
}
|
|
2425
2460
|
)
|
|
2426
2461
|
expect(page1.pageInfo).toMatchInlineSnapshot(`
|
package/src/client.ts
CHANGED
|
@@ -79,6 +79,12 @@ export interface ClientProps
|
|
|
79
79
|
ServiceConfigurationOptions,
|
|
80
80
|
ClientApiVersions {
|
|
81
81
|
tableName: string
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The encryption key used to encrypt cursors using AES-256-CTR.
|
|
85
|
+
* Must be a 32 character string, 256 bits.
|
|
86
|
+
*/
|
|
87
|
+
cursorEncryptionKey?: Buffer
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
export interface Key {
|
|
@@ -90,9 +96,11 @@ export class Client {
|
|
|
90
96
|
tableName: string
|
|
91
97
|
documentClient: DocumentClient
|
|
92
98
|
dataLoader: DataLoader<GetOperation<Decodable>, DynamoDBModelInstance, string>
|
|
99
|
+
cursorEncryptionKey?: Buffer
|
|
93
100
|
|
|
94
101
|
constructor(props: ClientProps) {
|
|
95
102
|
this.tableName = props?.tableName
|
|
103
|
+
this.cursorEncryptionKey = props?.cursorEncryptionKey
|
|
96
104
|
this.documentClient = new DocumentClient(props)
|
|
97
105
|
this.dataLoader = new DataLoader<
|
|
98
106
|
GetOperation<Decodable>,
|
|
@@ -189,7 +197,7 @@ export class Client {
|
|
|
189
197
|
const { Item } = await this.documentClient
|
|
190
198
|
.get({
|
|
191
199
|
TableName: this.tableName,
|
|
192
|
-
Key: key,
|
|
200
|
+
Key: { PK: key.PK, SK: key.SK },
|
|
193
201
|
...params,
|
|
194
202
|
})
|
|
195
203
|
.promise()
|
|
@@ -199,15 +207,45 @@ export class Client {
|
|
|
199
207
|
return (_model as M & DynamoDBInternals<M>).__dynamoDBDecode(Item) as any
|
|
200
208
|
}
|
|
201
209
|
|
|
202
|
-
async load<
|
|
210
|
+
async load<
|
|
211
|
+
M extends Decodable,
|
|
212
|
+
Null extends boolean = false,
|
|
213
|
+
Recover extends boolean = false
|
|
214
|
+
>(
|
|
203
215
|
operation: GetOperation<M>,
|
|
204
|
-
params?: { null?: Null }
|
|
216
|
+
params?: { null?: Null; recover?: Recover }
|
|
205
217
|
): Promise<
|
|
206
|
-
|
|
218
|
+
Recover extends true
|
|
219
|
+
? Null extends true
|
|
220
|
+
? (DecodableInstance<M> & { isDeleted?: true }) | null
|
|
221
|
+
: DecodableInstance<M> & { isDeleted?: true }
|
|
222
|
+
: Null extends true
|
|
223
|
+
? DecodableInstance<M> | null
|
|
224
|
+
: DecodableInstance<M>
|
|
207
225
|
> {
|
|
208
226
|
const item = await this.dataLoader.load(operation).catch((e) => {
|
|
209
227
|
// Maybe return null instead of throwing
|
|
210
|
-
if (e instanceof ItemNotFoundError
|
|
228
|
+
if (e instanceof ItemNotFoundError) {
|
|
229
|
+
// Try to recover a deleted item
|
|
230
|
+
if (params?.recover)
|
|
231
|
+
return this.dataLoader
|
|
232
|
+
.load({
|
|
233
|
+
...operation,
|
|
234
|
+
key: {
|
|
235
|
+
PK: `$$DELETED$$${operation.key.PK}`,
|
|
236
|
+
SK: `$$DELETED$$${operation.key.SK}`,
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
.then((item) => Object.assign(item, { isDeleted: true }))
|
|
240
|
+
.catch((e2) => {
|
|
241
|
+
if (e2 instanceof ItemNotFoundError && params.null) return null
|
|
242
|
+
|
|
243
|
+
// Throw the original error
|
|
244
|
+
throw e
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
if (params?.null) return null
|
|
248
|
+
}
|
|
211
249
|
|
|
212
250
|
throw e
|
|
213
251
|
})
|
|
@@ -494,7 +532,16 @@ export class Client {
|
|
|
494
532
|
...params,
|
|
495
533
|
// Fetch one additional item to test for a next page
|
|
496
534
|
Limit: limit + 1,
|
|
497
|
-
ExclusiveStartKey: cursor
|
|
535
|
+
ExclusiveStartKey: cursor
|
|
536
|
+
? decodeDDBCursor(
|
|
537
|
+
cursor,
|
|
538
|
+
// GSI1 is the inverse index and uses PK and SK (switched around)
|
|
539
|
+
params.IndexName && params.IndexName !== "GSI1"
|
|
540
|
+
? (params.IndexName as "GSI2" | "GSI3" | "GSI4" | "GSI5")
|
|
541
|
+
: undefined,
|
|
542
|
+
this.cursorEncryptionKey
|
|
543
|
+
)
|
|
544
|
+
: undefined,
|
|
498
545
|
ScanIndexForward: direction === PaginationDirection.FORWARD,
|
|
499
546
|
},
|
|
500
547
|
{ results: model }
|
|
@@ -509,13 +556,7 @@ export class Client {
|
|
|
509
556
|
// Build edges
|
|
510
557
|
const edges = slice.map((item: any) => ({
|
|
511
558
|
node: item,
|
|
512
|
-
cursor: encodeDDBCursor(
|
|
513
|
-
item,
|
|
514
|
-
// GSI1 is the inverse index and uses PK and SK (switched around)
|
|
515
|
-
params.IndexName && params.IndexName !== "GSI1"
|
|
516
|
-
? (params.IndexName as "GSI2" | "GSI3")
|
|
517
|
-
: undefined
|
|
518
|
-
),
|
|
559
|
+
cursor: encodeDDBCursor(item, this.cursorEncryptionKey),
|
|
519
560
|
}))
|
|
520
561
|
|
|
521
562
|
return {
|
package/src/dynamodb-model.ts
CHANGED