@model-ts/dynamodb 2.0.0 → 3.0.1

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 (62) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/__test__/client-with-cursor-encryption.test.js +298 -183
  3. package/dist/cjs/__test__/client-with-cursor-encryption.test.js.map +1 -1
  4. package/dist/cjs/__test__/client.test.js +101 -0
  5. package/dist/cjs/__test__/client.test.js.map +1 -1
  6. package/dist/cjs/__test__/pagination.test.d.ts +1 -0
  7. package/dist/cjs/__test__/pagination.test.js +241 -0
  8. package/dist/cjs/__test__/pagination.test.js.map +1 -0
  9. package/dist/cjs/client.d.ts +6 -1
  10. package/dist/cjs/client.js +7 -2
  11. package/dist/cjs/client.js.map +1 -1
  12. package/dist/cjs/dynamodb-model.d.ts +33 -8
  13. package/dist/cjs/gsi.d.ts +4 -0
  14. package/dist/cjs/gsi.js +24 -0
  15. package/dist/cjs/gsi.js.map +1 -0
  16. package/dist/cjs/operations.d.ts +4 -8
  17. package/dist/cjs/operations.js.map +1 -1
  18. package/dist/cjs/pagination.d.ts +48 -59
  19. package/dist/cjs/pagination.js +16 -26
  20. package/dist/cjs/pagination.js.map +1 -1
  21. package/dist/cjs/provider.d.ts +135 -17
  22. package/dist/cjs/provider.js +5 -12
  23. package/dist/cjs/provider.js.map +1 -1
  24. package/dist/cjs/sandbox.js +10 -43
  25. package/dist/cjs/sandbox.js.map +1 -1
  26. package/dist/esm/__test__/client-with-cursor-encryption.test.js +298 -183
  27. package/dist/esm/__test__/client-with-cursor-encryption.test.js.map +1 -1
  28. package/dist/esm/__test__/client.test.js +101 -0
  29. package/dist/esm/__test__/client.test.js.map +1 -1
  30. package/dist/esm/__test__/pagination.test.d.ts +1 -0
  31. package/dist/esm/__test__/pagination.test.js +238 -0
  32. package/dist/esm/__test__/pagination.test.js.map +1 -0
  33. package/dist/esm/client.d.ts +6 -1
  34. package/dist/esm/client.js +7 -2
  35. package/dist/esm/client.js.map +1 -1
  36. package/dist/esm/dynamodb-model.d.ts +33 -8
  37. package/dist/esm/gsi.d.ts +4 -0
  38. package/dist/esm/gsi.js +21 -0
  39. package/dist/esm/gsi.js.map +1 -0
  40. package/dist/esm/operations.d.ts +4 -8
  41. package/dist/esm/operations.js.map +1 -1
  42. package/dist/esm/pagination.d.ts +48 -59
  43. package/dist/esm/pagination.js +17 -26
  44. package/dist/esm/pagination.js.map +1 -1
  45. package/dist/esm/provider.d.ts +135 -17
  46. package/dist/esm/provider.js +5 -12
  47. package/dist/esm/provider.js.map +1 -1
  48. package/dist/esm/sandbox.js +10 -43
  49. package/dist/esm/sandbox.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/__test__/client-with-cursor-encryption.test.ts +365 -183
  52. package/src/__test__/client.test.ts +168 -0
  53. package/src/__test__/pagination.test.ts +300 -0
  54. package/src/client.ts +20 -19
  55. package/src/dynamodb-model.ts +31 -9
  56. package/src/gsi.ts +25 -0
  57. package/src/operations.ts +4 -10
  58. package/src/pagination.ts +39 -52
  59. package/src/provider.ts +9 -9
  60. package/src/sandbox.ts +10 -43
  61. package/tsconfig.esm.json +14 -4
  62. package/tsconfig.json +14 -4
@@ -134,6 +134,10 @@ beforeEach(async () => {
134
134
  sandbox = await createSandbox(client)
135
135
  })
136
136
 
137
+ afterEach(async () => {
138
+ await sandbox.destroy()
139
+ })
140
+
137
141
  describe("put", () => {
138
142
  describe("via instance", () => {
139
143
  test("it inserts a simple model", async () => {
@@ -2135,6 +2139,56 @@ describe("paginate", () => {
2135
2139
  expect(page1.edges[0].node.c).toBe("0")
2136
2140
  expect(page1.edges[49].node.c).toBe("49")
2137
2141
  })
2142
+
2143
+ test("it respects custom pagination default", async () => {
2144
+ client.paginationOptions = {
2145
+ default: 40,
2146
+ }
2147
+
2148
+ const items = Array.from({ length: 50 }).map(
2149
+ (_, i) =>
2150
+ new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) })
2151
+ )
2152
+
2153
+ await sandbox.seed(...items)
2154
+
2155
+ const page = await client.paginate(
2156
+ C,
2157
+ {},
2158
+ {
2159
+ KeyConditionExpression: "PK = :pk",
2160
+ ExpressionAttributeValues: { ":pk": "PK" },
2161
+ }
2162
+ )
2163
+ expect(page.edges.length).toBe(40)
2164
+
2165
+ delete client.paginationOptions
2166
+ })
2167
+
2168
+ test("it respects custom pagination limit", async () => {
2169
+ client.paginationOptions = {
2170
+ limit: 100,
2171
+ }
2172
+
2173
+ const items = Array.from({ length: 120 }).map(
2174
+ (_, i) =>
2175
+ new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) })
2176
+ )
2177
+
2178
+ await sandbox.seed(...items)
2179
+
2180
+ const page = await client.paginate(
2181
+ C,
2182
+ { first: 110 },
2183
+ {
2184
+ KeyConditionExpression: "PK = :pk",
2185
+ ExpressionAttributeValues: { ":pk": "PK" },
2186
+ }
2187
+ )
2188
+ expect(page.edges.length).toBe(100)
2189
+
2190
+ delete client.paginationOptions
2191
+ })
2138
2192
  })
2139
2193
 
2140
2194
  describe("model", () => {
@@ -2301,6 +2355,54 @@ describe("paginate", () => {
2301
2355
  expect(page1.edges[0].node.c).toBe("0")
2302
2356
  expect(page1.edges[49].node.c).toBe("49")
2303
2357
  })
2358
+
2359
+ test("it respects custom pagination default", async () => {
2360
+ client.paginationOptions = {
2361
+ default: 40,
2362
+ }
2363
+
2364
+ const items = Array.from({ length: 50 }).map(
2365
+ (_, i) =>
2366
+ new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) })
2367
+ )
2368
+
2369
+ await sandbox.seed(...items)
2370
+
2371
+ const page = await C.paginate(
2372
+ {},
2373
+ {
2374
+ KeyConditionExpression: "PK = :pk",
2375
+ ExpressionAttributeValues: { ":pk": "PK" },
2376
+ }
2377
+ )
2378
+ expect(page.edges.length).toBe(40)
2379
+
2380
+ delete client.paginationOptions
2381
+ })
2382
+
2383
+ test("it respects custom pagination limit", async () => {
2384
+ client.paginationOptions = {
2385
+ limit: 100,
2386
+ }
2387
+
2388
+ const items = Array.from({ length: 120 }).map(
2389
+ (_, i) =>
2390
+ new C({ pk: "PK", sk: String(i).padStart(3, "0"), c: String(i) })
2391
+ )
2392
+
2393
+ await sandbox.seed(...items)
2394
+
2395
+ const page = await C.paginate(
2396
+ { first: 110 },
2397
+ {
2398
+ KeyConditionExpression: "PK = :pk",
2399
+ ExpressionAttributeValues: { ":pk": "PK" },
2400
+ }
2401
+ )
2402
+ expect(page.edges.length).toBe(100)
2403
+
2404
+ delete client.paginationOptions
2405
+ })
2304
2406
  })
2305
2407
 
2306
2408
  describe("union", () => {
@@ -2470,5 +2572,71 @@ describe("paginate", () => {
2470
2572
  expect(page1.edges[0].node.SK).toBe("000")
2471
2573
  expect(page1.edges[49].node.SK).toBe("049")
2472
2574
  })
2575
+
2576
+ test("it respects custom pagination default", async () => {
2577
+ client.paginationOptions = {
2578
+ default: 40,
2579
+ }
2580
+
2581
+ const items = Array.from({ length: 50 }).map((_, i) =>
2582
+ i > 30
2583
+ ? new C({
2584
+ pk: "PK",
2585
+ sk: String(i).padStart(3, "0"),
2586
+ c: String(i),
2587
+ })
2588
+ : new D({
2589
+ pk: "PK",
2590
+ sk: String(i).padStart(3, "0"),
2591
+ d: String(i),
2592
+ })
2593
+ )
2594
+
2595
+ await sandbox.seed(...items)
2596
+
2597
+ const page = await Union.paginate(
2598
+ {},
2599
+ {
2600
+ KeyConditionExpression: "PK = :pk",
2601
+ ExpressionAttributeValues: { ":pk": "PK" },
2602
+ }
2603
+ )
2604
+ expect(page.edges.length).toBe(40)
2605
+
2606
+ delete client.paginationOptions
2607
+ })
2608
+
2609
+ test("it respects custom pagination limit", async () => {
2610
+ client.paginationOptions = {
2611
+ limit: 100,
2612
+ }
2613
+
2614
+ const items = Array.from({ length: 110 }).map((_, i) =>
2615
+ i > 30
2616
+ ? new C({
2617
+ pk: "PK",
2618
+ sk: String(i).padStart(3, "0"),
2619
+ c: String(i),
2620
+ })
2621
+ : new D({
2622
+ pk: "PK",
2623
+ sk: String(i).padStart(3, "0"),
2624
+ d: String(i),
2625
+ })
2626
+ )
2627
+
2628
+ await sandbox.seed(...items)
2629
+
2630
+ const page = await Union.paginate(
2631
+ { first: 110 },
2632
+ {
2633
+ KeyConditionExpression: "PK = :pk",
2634
+ ExpressionAttributeValues: { ":pk": "PK" },
2635
+ }
2636
+ )
2637
+ expect(page.edges.length).toBe(100)
2638
+
2639
+ delete client.paginationOptions
2640
+ })
2473
2641
  })
2474
2642
  })
@@ -0,0 +1,300 @@
1
+ import crypto from "crypto"
2
+ import { encodeDDBCursor, decodeDDBCursor } from "../pagination"
3
+ import { PaginationError } from "../errors"
4
+
5
+ describe("encodeDDBCursor", () => {
6
+ describe("basic encoding without encryption", () => {
7
+ it("should encode basic PK and SK", () => {
8
+ const result = encodeDDBCursor({
9
+ PK: "USER#123",
10
+ SK: "PROFILE#456",
11
+ })
12
+
13
+ expect(result).toMatchInlineSnapshot(
14
+ `"eyJQSyI6IlVTRVIjMTIzIiwiU0siOiJQUk9GSUxFIzQ1NiJ9"`
15
+ )
16
+ })
17
+
18
+ it("should encode with GSI2 values", () => {
19
+ const result = encodeDDBCursor({
20
+ PK: "USER#123",
21
+ SK: "PROFILE#456",
22
+ GSI2PK: "GSI2PK#user123",
23
+ GSI2SK: "GSI2SK#profile456",
24
+ })
25
+
26
+ expect(result).toMatchInlineSnapshot(
27
+ `"eyJQSyI6IlVTRVIjMTIzIiwiU0siOiJQUk9GSUxFIzQ1NiIsIkdTSTJQSyI6IkdTSTJQSyN1c2VyMTIzIiwiR1NJMlNLIjoiR1NJMlNLI3Byb2ZpbGU0NTYifQ=="`
28
+ )
29
+ })
30
+
31
+ it("should encode with multiple GSI values", () => {
32
+ const result = encodeDDBCursor({
33
+ PK: "USER#123",
34
+ SK: "PROFILE#456",
35
+ GSI2PK: "GSI2PK#user123",
36
+ GSI2SK: "GSI2SK#profile456",
37
+ GSI3PK: "GSI3PK#fixed",
38
+ GSI3SK: "GSI3SK#value",
39
+ })
40
+
41
+ expect(result).toMatchInlineSnapshot(
42
+ `"eyJQSyI6IlVTRVIjMTIzIiwiU0siOiJQUk9GSUxFIzQ1NiIsIkdTSTJQSyI6IkdTSTJQSyN1c2VyMTIzIiwiR1NJMlNLIjoiR1NJMlNLI3Byb2ZpbGU0NTYiLCJHU0kzUEsiOiJHU0kzUEsjZml4ZWQiLCJHU0kzU0siOiJHU0kzU0sjdmFsdWUifQ=="`
43
+ )
44
+ })
45
+ })
46
+
47
+ describe("encoding with encryption", () => {
48
+ const encryptionKey = crypto.randomBytes(32)
49
+
50
+ it("should encrypt the cursor when encryption key is provided", () => {
51
+ const result = encodeDDBCursor(
52
+ {
53
+ PK: "USER#123",
54
+ SK: "PROFILE#456",
55
+ },
56
+ encryptionKey
57
+ )
58
+
59
+ // The result should be encrypted and different from the unencrypted version
60
+ expect(result).not.toBe(
61
+ "eyJQSyI6IlVTRVIjMTIzIiwiU0siOiJQUk9GSUxFIzQ1NiJ9"
62
+ )
63
+ expect(result).toMatch(/^[A-Za-z0-9+/=]+$/) // Base64 format
64
+ })
65
+
66
+ it("should produce consistent encrypted results for same input", () => {
67
+ const input = {
68
+ PK: "USER#123",
69
+ SK: "PROFILE#456",
70
+ }
71
+
72
+ const result1 = encodeDDBCursor(input, encryptionKey)
73
+ const result2 = encodeDDBCursor(input, encryptionKey)
74
+
75
+ expect(result1).toBe(result2)
76
+ })
77
+
78
+ it("should encrypt with GSI values", () => {
79
+ const result = encodeDDBCursor(
80
+ {
81
+ PK: "USER#123",
82
+ SK: "PROFILE#456",
83
+ GSI2PK: "GSI2PK#user123",
84
+ GSI2SK: "GSI2SK#profile456",
85
+ },
86
+ encryptionKey
87
+ )
88
+
89
+ expect(result).toMatch(/^[A-Za-z0-9+/=]+$/)
90
+ })
91
+ })
92
+ })
93
+
94
+ describe("decodeDDBCursor", () => {
95
+ describe("basic decoding without encryption", () => {
96
+ it("should decode basic PK and SK", () => {
97
+ const encoded = "eyJQSyI6IlVTRVIjMTIzIiwiU0siOiJQUk9GSUxFIzQ1NiJ9"
98
+ const result = decodeDDBCursor(encoded)
99
+
100
+ expect(result).toMatchInlineSnapshot(`
101
+ Object {
102
+ "PK": "USER#123",
103
+ "SK": "PROFILE#456",
104
+ }
105
+ `)
106
+ })
107
+
108
+ it("should decode with GSI2 values", () => {
109
+ const encoded =
110
+ "eyJQSyI6IlVTRVIjMTIzIiwiU0siOiJQUk9GSUxFIzQ1NiIsIkdTSTJQSyI6IkdTSTJQSyN1c2VyMTIzIiwiR1NJMlNLIjoiR1NJMlNLI3Byb2ZpbGU0NTYifQ=="
111
+ const result = decodeDDBCursor(encoded, "GSI2")
112
+
113
+ expect(result).toMatchInlineSnapshot(`
114
+ Object {
115
+ "GSI2PK": "GSI2PK#user123",
116
+ "GSI2SK": "GSI2SK#profile456",
117
+ "PK": "USER#123",
118
+ "SK": "PROFILE#456",
119
+ }
120
+ `)
121
+ })
122
+
123
+ it("should decode with GSI3 values", () => {
124
+ const encoded =
125
+ "eyJQSyI6IlVTRVIjMTIzIiwiU0siOiJQUk9GSUxFIzQ1NiIsIkdTSTJQSyI6IkdTSTJQSyN1c2VyMTIzIiwiR1NJMlNLIjoiR1NJMlNLI3Byb2ZpbGU0NTYiLCJHU0kzUEsiOiJHU0kzUEsjZml4ZWQiLCJHU0kzU0siOiJHU0kzU0sjdmFsdWUifQ=="
126
+ const result = decodeDDBCursor(encoded, "GSI3")
127
+
128
+ expect(result).toMatchInlineSnapshot(`
129
+ Object {
130
+ "GSI3PK": "GSI3PK#fixed",
131
+ "GSI3SK": "GSI3SK#value",
132
+ "PK": "USER#123",
133
+ "SK": "PROFILE#456",
134
+ }
135
+ `)
136
+ })
137
+ })
138
+
139
+ describe("decoding with encryption", () => {
140
+ const encryptionKey = crypto.randomBytes(32)
141
+
142
+ it("should decode encrypted cursor", () => {
143
+ const input = {
144
+ PK: "USER#123",
145
+ SK: "PROFILE#456",
146
+ }
147
+ const encoded = encodeDDBCursor(input, encryptionKey)
148
+ const result = decodeDDBCursor(encoded, undefined, encryptionKey)
149
+
150
+ expect(result).toMatchInlineSnapshot(`
151
+ Object {
152
+ "PK": "USER#123",
153
+ "SK": "PROFILE#456",
154
+ }
155
+ `)
156
+ })
157
+
158
+ it("should decode encrypted cursor with GSI values", () => {
159
+ const input = {
160
+ PK: "USER#123",
161
+ SK: "PROFILE#456",
162
+ GSI2PK: "GSI2PK#user123",
163
+ GSI2SK: "GSI2SK#profile456",
164
+ }
165
+ const encoded = encodeDDBCursor(input, encryptionKey)
166
+ const result = decodeDDBCursor(encoded, "GSI2", encryptionKey)
167
+
168
+ expect(result).toMatchInlineSnapshot(`
169
+ Object {
170
+ "GSI2PK": "GSI2PK#user123",
171
+ "GSI2SK": "GSI2SK#profile456",
172
+ "PK": "USER#123",
173
+ "SK": "PROFILE#456",
174
+ }
175
+ `)
176
+ })
177
+
178
+ it("should throw PaginationError when decryption fails", () => {
179
+ const wrongKey = crypto.randomBytes(32)
180
+ const input = {
181
+ PK: "USER#123",
182
+ SK: "PROFILE#456",
183
+ }
184
+ const encoded = encodeDDBCursor(input, encryptionKey)
185
+
186
+ expect(() => {
187
+ decodeDDBCursor(encoded, undefined, wrongKey)
188
+ }).toThrow(PaginationError)
189
+ expect(() => {
190
+ decodeDDBCursor(encoded, undefined, wrongKey)
191
+ }).toThrow("Couldn't decode cursor")
192
+ })
193
+ })
194
+
195
+ describe("error handling", () => {
196
+ it("should throw PaginationError for invalid base64", () => {
197
+ expect(() => {
198
+ decodeDDBCursor("invalid-base64")
199
+ }).toThrow(PaginationError)
200
+ expect(() => {
201
+ decodeDDBCursor("invalid-base64")
202
+ }).toThrow("Couldn't decode cursor")
203
+ })
204
+
205
+ it("should throw PaginationError for invalid JSON", () => {
206
+ const invalidJson = Buffer.from("invalid json").toString("base64")
207
+ expect(() => {
208
+ decodeDDBCursor(invalidJson)
209
+ }).toThrow(PaginationError)
210
+ })
211
+
212
+ it("should throw PaginationError when PK is missing", () => {
213
+ const invalidData = Buffer.from(
214
+ JSON.stringify({ SK: "PROFILE#456" })
215
+ ).toString("base64")
216
+ expect(() => {
217
+ decodeDDBCursor(invalidData)
218
+ }).toThrow(PaginationError)
219
+ })
220
+
221
+ it("should throw PaginationError when SK is missing", () => {
222
+ const invalidData = Buffer.from(
223
+ JSON.stringify({ PK: "USER#123" })
224
+ ).toString("base64")
225
+ expect(() => {
226
+ decodeDDBCursor(invalidData)
227
+ }).toThrow(PaginationError)
228
+ })
229
+
230
+ it("should throw PaginationError when PK is not a string", () => {
231
+ const invalidData = Buffer.from(
232
+ JSON.stringify({ PK: 123, SK: "PROFILE#456" })
233
+ ).toString("base64")
234
+ expect(() => {
235
+ decodeDDBCursor(invalidData)
236
+ }).toThrow(PaginationError)
237
+ })
238
+
239
+ it("should throw PaginationError when SK is not a string", () => {
240
+ const invalidData = Buffer.from(
241
+ JSON.stringify({ PK: "USER#123", SK: 456 })
242
+ ).toString("base64")
243
+ expect(() => {
244
+ decodeDDBCursor(invalidData)
245
+ }).toThrow(PaginationError)
246
+ })
247
+ })
248
+
249
+ describe("round-trip encoding and decoding", () => {
250
+ it("should round-trip basic values", () => {
251
+ const input = {
252
+ PK: "USER#123",
253
+ SK: "PROFILE#456",
254
+ }
255
+ const encoded = encodeDDBCursor(input)
256
+ const decoded = decodeDDBCursor(encoded)
257
+
258
+ expect(decoded).toEqual(input)
259
+ })
260
+
261
+ it("should round-trip with GSI values", () => {
262
+ const input = {
263
+ PK: "USER#123",
264
+ SK: "PROFILE#456",
265
+ GSI2PK: "GSI2PK#user123",
266
+ GSI2SK: "GSI2SK#profile456",
267
+ GSI3PK: "GSI3PK#fixed",
268
+ GSI3SK: "GSI3SK#value",
269
+ }
270
+ const encoded = encodeDDBCursor(input)
271
+ const decoded = decodeDDBCursor(encoded, "GSI2")
272
+
273
+ expect(decoded).toEqual({
274
+ PK: input.PK,
275
+ SK: input.SK,
276
+ GSI2PK: input.GSI2PK,
277
+ GSI2SK: input.GSI2SK,
278
+ })
279
+ })
280
+
281
+ it("should round-trip with encryption", () => {
282
+ const encryptionKey = crypto.randomBytes(32)
283
+ const input = {
284
+ PK: "USER#123",
285
+ SK: "PROFILE#456",
286
+ GSI2PK: "GSI2PK#user123",
287
+ GSI2SK: "GSI2SK#profile456",
288
+ }
289
+ const encoded = encodeDDBCursor(input, encryptionKey)
290
+ const decoded = decodeDDBCursor(encoded, "GSI2", encryptionKey)
291
+
292
+ expect(decoded).toEqual({
293
+ PK: input.PK,
294
+ SK: input.SK,
295
+ GSI2PK: input.GSI2PK,
296
+ GSI2SK: input.GSI2SK,
297
+ })
298
+ })
299
+ })
300
+ })
package/src/client.ts CHANGED
@@ -40,8 +40,10 @@ import {
40
40
  encodeDDBCursor,
41
41
  PaginationDirection,
42
42
  PaginationInput,
43
+ PaginationOptions,
43
44
  PaginationResult,
44
45
  } from "./pagination"
46
+ import { GSI, GSI_NAMES, GSIPK, GSISK } from "./gsi"
45
47
 
46
48
  export type QueryParams = Omit<
47
49
  DocumentClient.QueryInput,
@@ -85,6 +87,11 @@ export interface ClientProps
85
87
  * Must be a 32 character string, 256 bits.
86
88
  */
87
89
  cursorEncryptionKey?: Buffer
90
+
91
+ /**
92
+ * Defaults for pagination.
93
+ */
94
+ paginationOptions?: PaginationOptions
88
95
  }
89
96
 
90
97
  export interface Key {
@@ -97,10 +104,12 @@ export class Client {
97
104
  documentClient: DocumentClient
98
105
  dataLoader: DataLoader<GetOperation<Decodable>, DynamoDBModelInstance, string>
99
106
  cursorEncryptionKey?: Buffer
107
+ paginationOptions?: PaginationOptions
100
108
 
101
109
  constructor(props: ClientProps) {
102
110
  this.tableName = props?.tableName
103
111
  this.cursorEncryptionKey = props?.cursorEncryptionKey
112
+ this.paginationOptions = props?.paginationOptions
104
113
  this.documentClient = new DocumentClient(props)
105
114
  this.dataLoader = new DataLoader<
106
115
  GetOperation<Decodable>,
@@ -525,7 +534,10 @@ export class Client {
525
534
  args: PaginationInput,
526
535
  params: PaginationParams
527
536
  ): Promise<PaginationResult<DecodableInstance<M>>> {
528
- const { cursor, limit, direction } = decodePagination(args)
537
+ const { cursor, limit, direction } = decodePagination(
538
+ args,
539
+ this.paginationOptions
540
+ )
529
541
 
530
542
  const { results } = await this.query(
531
543
  {
@@ -537,7 +549,7 @@ export class Client {
537
549
  cursor,
538
550
  // GSI1 is the inverse index and uses PK and SK (switched around)
539
551
  params.IndexName && params.IndexName !== "GSI1"
540
- ? (params.IndexName as "GSI2" | "GSI3" | "GSI4" | "GSI5")
552
+ ? (params.IndexName as GSI)
541
553
  : undefined,
542
554
  this.cursorEncryptionKey
543
555
  )
@@ -888,15 +900,8 @@ export class Client {
888
900
  T extends {
889
901
  PK: string
890
902
  SK: string
891
- GSI2PK?: string
892
- GSI2SK?: string
893
- GSI3PK?: string
894
- GSI3SK?: string
895
- GSI4PK?: string
896
- GSI4SK?: string
897
- GSI5PK?: string
898
- GSI5SK?: string
899
- }
903
+ } & { [key in GSIPK]?: string } &
904
+ { [key in GSISK]?: string }
900
905
  >(item: T): T {
901
906
  const prefix = "$$DELETED$$"
902
907
 
@@ -908,14 +913,10 @@ export class Client {
908
913
  ...item,
909
914
  PK: maybeWithPrefix(item.PK),
910
915
  SK: maybeWithPrefix(item.SK),
911
- GSI2PK: maybeWithPrefix(item.GSI2PK),
912
- GSI2SK: maybeWithPrefix(item.GSI2SK),
913
- GSI3PK: maybeWithPrefix(item.GSI3PK),
914
- GSI3SK: maybeWithPrefix(item.GSI3SK),
915
- GSI4PK: maybeWithPrefix(item.GSI4PK),
916
- GSI4SK: maybeWithPrefix(item.GSI4SK),
917
- GSI5PK: maybeWithPrefix(item.GSI5PK),
918
- GSI5SK: maybeWithPrefix(item.GSI5SK),
916
+ ...GSI_NAMES.map((GSI) => ({
917
+ [`${GSI}PK`]: maybeWithPrefix(item[`${GSI}PK` as const]),
918
+ [`${GSI}SK`]: maybeWithPrefix(item[`${GSI}SK` as const]),
919
+ })).reduce((acc, cur) => Object.assign(acc, cur), {}),
919
920
  }
920
921
  }
921
922
  }
@@ -1,4 +1,5 @@
1
1
  import { ModelInstance, ModelConstructor, Union } from "@model-ts/core"
2
+ import { GSIPK, GSISK } from "./gsi"
2
3
 
3
4
  export interface DynamoDBModelInstance extends ModelInstance<string, any> {
4
5
  /**
@@ -7,15 +8,8 @@ export interface DynamoDBModelInstance extends ModelInstance<string, any> {
7
8
  keys(): {
8
9
  PK: string
9
10
  SK: string
10
- GSI2PK?: string
11
- GSI2SK?: string
12
- GSI3PK?: string
13
- GSI3SK?: string
14
- GSI4PK?: string
15
- GSI4SK?: string
16
- GSI5PK?: string
17
- GSI5SK?: string
18
- }
11
+ } & { [key in GSIPK]?: string } &
12
+ { [key in GSISK]?: string }
19
13
 
20
14
  PK: string
21
15
  SK: string
@@ -27,6 +21,34 @@ export interface DynamoDBModelInstance extends ModelInstance<string, any> {
27
21
  GSI4SK?: string
28
22
  GSI5PK?: string
29
23
  GSI5SK?: string
24
+ GSI6PK?: string
25
+ GSI6SK?: string
26
+ GSI7PK?: string
27
+ GSI7SK?: string
28
+ GSI8PK?: string
29
+ GSI8SK?: string
30
+ GSI9PK?: string
31
+ GSI9SK?: string
32
+ GSI10PK?: string
33
+ GSI10SK?: string
34
+ GSI11PK?: string
35
+ GSI11SK?: string
36
+ GSI12PK?: string
37
+ GSI12SK?: string
38
+ GSI13PK?: string
39
+ GSI13SK?: string
40
+ GSI14PK?: string
41
+ GSI14SK?: string
42
+ GSI15PK?: string
43
+ GSI15SK?: string
44
+ GSI16PK?: string
45
+ GSI16SK?: string
46
+ GSI17PK?: string
47
+ GSI17SK?: string
48
+ GSI18PK?: string
49
+ GSI18SK?: string
50
+ GSI19PK?: string
51
+ GSI19SK?: string
30
52
  }
31
53
 
32
54
  // export interface DynamoDBModel {}
package/src/gsi.ts ADDED
@@ -0,0 +1,25 @@
1
+ export const GSI_NAMES = [
2
+ "GSI2",
3
+ "GSI3",
4
+ "GSI4",
5
+ "GSI5",
6
+ "GSI6",
7
+ "GSI7",
8
+ "GSI8",
9
+ "GSI9",
10
+ "GSI10",
11
+ "GSI11",
12
+ "GSI12",
13
+ "GSI13",
14
+ "GSI14",
15
+ "GSI15",
16
+ "GSI16",
17
+ "GSI17",
18
+ "GSI18",
19
+ "GSI19",
20
+ ] as const
21
+
22
+ export type GSI = typeof GSI_NAMES[number]
23
+
24
+ export type GSIPK = `${GSI}PK`
25
+ export type GSISK = `${GSI}SK`
package/src/operations.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  Decodable,
7
7
  } from "./dynamodb-model"
8
8
  import { Key } from "./client"
9
+ import { GSIPK, GSISK } from "./gsi"
9
10
 
10
11
  export type Operation<
11
12
  T extends DynamoDBModelInstance,
@@ -57,16 +58,9 @@ export interface UpdateRawOperation<M extends DynamoDBModelConstructor<any>>
57
58
  _operation: "updateRaw"
58
59
  _model: M
59
60
  key: Key
60
- attributes: Partial<TypeOf<M>> & {
61
- GSI2PK?: string | null
62
- GSI2SK?: string | null
63
- GSI3PK?: string | null
64
- GSI3SK?: string | null
65
- GSI4PK?: string | null
66
- GSI4SK?: string | null
67
- GSI5PK?: string | null
68
- GSI5SK?: string | null
69
- }
61
+ attributes: Partial<TypeOf<M>> &
62
+ { [key in GSIPK]?: string | null } &
63
+ { [key in GSISK]?: string | null }
70
64
  }
71
65
 
72
66
  // -------------------------------------------------------------------------------------