@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
package/src/pagination.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import crypto from "crypto"
2
2
  import { PaginationError } from "./errors"
3
+ import { GSI, GSI_NAMES, GSIPK, GSISK } from "./gsi"
3
4
 
4
5
  const SIV = "Q05yyCR+0tyWl6glrZhlNw=="
5
6
  const ENCRYPTION_ALG = "aes-256-ctr"
@@ -33,13 +34,27 @@ export interface PaginationInput {
33
34
  after?: string | null
34
35
  }
35
36
 
36
- // TODO: make configurable
37
+ export interface PaginationOptions {
38
+ /**
39
+ * Maximum number of items to return.
40
+ */
41
+ limit?: number
42
+
43
+ /**
44
+ * Default number of items to return if no limit is provided.
45
+ */
46
+ default?: number
47
+ }
48
+
37
49
  const DEFAULT_OPTIONS = {
38
50
  limit: 50,
39
51
  default: 20,
40
52
  }
41
53
 
42
- export function decodePagination(pagination: PaginationInput): {
54
+ export function decodePagination(
55
+ pagination: PaginationInput,
56
+ paginationOptions: PaginationOptions = DEFAULT_OPTIONS
57
+ ): {
43
58
  cursor?: string
44
59
  limit: number
45
60
  direction: PaginationDirection
@@ -65,8 +80,8 @@ export function decodePagination(pagination: PaginationInput): {
65
80
  return {
66
81
  cursor: before ?? after ?? undefined,
67
82
  limit: Math.min(
68
- first ?? last ?? DEFAULT_OPTIONS.default,
69
- DEFAULT_OPTIONS.limit
83
+ first ?? last ?? paginationOptions.default ?? DEFAULT_OPTIONS.default,
84
+ paginationOptions.limit ?? DEFAULT_OPTIONS.limit
70
85
  ),
71
86
  direction:
72
87
  before || last
@@ -117,43 +132,21 @@ const decryptCursor = (key: Buffer, encryptedCursor: string) => {
117
132
  }
118
133
 
119
134
  export const encodeDDBCursor = (
120
- {
121
- PK,
122
- SK,
123
- GSI2PK,
124
- GSI2SK,
125
- GSI3PK,
126
- GSI3SK,
127
- GSI4PK,
128
- GSI4SK,
129
- GSI5PK,
130
- GSI5SK,
131
- }: {
135
+ item: {
132
136
  PK: string
133
137
  SK: string
134
- GSI2PK?: string
135
- GSI2SK?: string
136
- GSI3PK?: string
137
- GSI3SK?: string
138
- GSI4PK?: string
139
- GSI4SK?: string
140
- GSI5PK?: string
141
- GSI5SK?: string
142
- },
138
+ } & { [key in GSIPK]?: string } &
139
+ { [key in GSISK]?: string },
143
140
  encryptionKey?: Buffer
144
141
  ) => {
145
142
  const cursor = Buffer.from(
146
143
  JSON.stringify({
147
- PK,
148
- SK,
149
- GSI2PK,
150
- GSI2SK,
151
- GSI3PK,
152
- GSI3SK,
153
- GSI4PK,
154
- GSI4SK,
155
- GSI5PK,
156
- GSI5SK,
144
+ PK: item.PK,
145
+ SK: item.SK,
146
+ ...GSI_NAMES.map((GSI) => ({
147
+ [`${GSI}PK`]: item[`${GSI}PK` as const],
148
+ [`${GSI}SK`]: item[`${GSI}SK` as const],
149
+ })).reduce((acc, cur) => Object.assign(acc, cur), {}),
157
150
  })
158
151
  ).toString("base64")
159
152
 
@@ -164,7 +157,7 @@ export const encodeDDBCursor = (
164
157
 
165
158
  export const decodeDDBCursor = (
166
159
  encoded: string,
167
- index?: "GSI2" | "GSI3" | "GSI4" | "GSI5",
160
+ index?: GSI,
168
161
  encryptionKey?: Buffer
169
162
  ) => {
170
163
  try {
@@ -173,26 +166,20 @@ export const decodeDDBCursor = (
173
166
 
174
167
  if (!json) throw new Error("Couldn't decrypt cursor")
175
168
 
176
- const {
177
- PK,
178
- SK,
179
- GSI2PK,
180
- GSI2SK,
181
- GSI3PK,
182
- GSI3SK,
183
- GSI4PK,
184
- GSI4SK,
185
- GSI5PK,
186
- GSI5SK,
187
- } = JSON.parse(Buffer.from(json, "base64").toString())
169
+ const { PK, SK, ...values } = JSON.parse(
170
+ Buffer.from(json, "base64").toString()
171
+ )
188
172
 
189
173
  if (typeof PK !== "string" || typeof SK !== "string") throw new Error()
190
174
 
191
175
  if (!index) return { PK, SK }
192
- if (index === "GSI2") return { PK, SK, GSI2PK, GSI2SK }
193
- if (index === "GSI3") return { PK, SK, GSI3PK, GSI3SK }
194
- if (index === "GSI4") return { PK, SK, GSI4PK, GSI4SK }
195
- if (index === "GSI5") return { PK, SK, GSI5PK, GSI5SK }
176
+
177
+ return {
178
+ PK,
179
+ SK,
180
+ [`${index}PK`]: values[`${index}PK`],
181
+ [`${index}SK`]: values[`${index}SK`],
182
+ }
196
183
  } catch (error) {
197
184
  throw new PaginationError("Couldn't decode cursor")
198
185
  }
package/src/provider.ts CHANGED
@@ -18,6 +18,7 @@ import { OutputOf, TypeOf, ModelOf } from "@model-ts/core"
18
18
  import { RaceConditionError } from "./errors"
19
19
  import { absurd } from "fp-ts/lib/function"
20
20
  import { encodeDDBCursor, PaginationInput } from "./pagination"
21
+ import { GSI_NAMES, GSIPK, GSISK } from "./gsi"
21
22
 
22
23
  export interface DynamoDBInternals<M extends Decodable> {
23
24
  __dynamoDBDecode(
@@ -512,18 +513,17 @@ export const getProvider = (client: Client) => {
512
513
  },
513
514
  instanceProps: {
514
515
  dynamodb: client,
515
- keys<T extends DynamoDBModelInstance>(this: T) {
516
+ keys<T extends DynamoDBModelInstance>(
517
+ this: T
518
+ ): { PK: string; SK: string } & { [key in GSIPK]?: string } &
519
+ { [key in GSISK]?: string } {
516
520
  return {
517
521
  PK: this.PK,
518
522
  SK: this.SK,
519
- GSI2PK: this.GSI2PK,
520
- GSI2SK: this.GSI2SK,
521
- GSI3PK: this.GSI3PK,
522
- GSI3SK: this.GSI3SK,
523
- GSI4PK: this.GSI4PK,
524
- GSI4SK: this.GSI4SK,
525
- GSI5PK: this.GSI5PK,
526
- GSI5SK: this.GSI5SK,
523
+ ...GSI_NAMES.map((GSI) => ({
524
+ [`${GSI}PK`]: this[`${GSI}PK`],
525
+ [`${GSI}SK`]: this[`${GSI}SK`],
526
+ })).reduce((acc, cur) => Object.assign(acc, cur), {}),
527
527
  }
528
528
  },
529
529
  cursor<T extends DynamoDBModelInstance>(this: T) {
package/src/sandbox.ts CHANGED
@@ -3,6 +3,7 @@ import { chunksOf } from "fp-ts/lib/Array"
3
3
  import DynamoDB from "aws-sdk/clients/dynamodb"
4
4
  import diff from "snapshot-diff"
5
5
  import { Client } from "./client"
6
+ import { GSI_NAMES } from "./gsi"
6
7
 
7
8
  const ddb = new DynamoDB({
8
9
  accessKeyId: "xxx",
@@ -27,14 +28,10 @@ export const createTable = async () => {
27
28
  AttributeDefinitions: [
28
29
  { AttributeName: "PK", AttributeType: "S" },
29
30
  { AttributeName: "SK", AttributeType: "S" },
30
- { AttributeName: "GSI2PK", AttributeType: "S" },
31
- { AttributeName: "GSI2SK", AttributeType: "S" },
32
- { AttributeName: "GSI3PK", AttributeType: "S" },
33
- { AttributeName: "GSI3SK", AttributeType: "S" },
34
- { AttributeName: "GSI4PK", AttributeType: "S" },
35
- { AttributeName: "GSI4SK", AttributeType: "S" },
36
- { AttributeName: "GSI5PK", AttributeType: "S" },
37
- { AttributeName: "GSI5SK", AttributeType: "S" },
31
+ ...GSI_NAMES.flatMap((GSI) => [
32
+ { AttributeName: `${GSI}PK`, AttributeType: "S" },
33
+ { AttributeName: `${GSI}SK`, AttributeType: "S" },
34
+ ]),
38
35
  ],
39
36
  KeySchema: [
40
37
  { AttributeName: "PK", KeyType: "HASH" },
@@ -51,46 +48,16 @@ export const createTable = async () => {
51
48
  ProjectionType: "ALL",
52
49
  },
53
50
  },
54
- {
55
- IndexName: "GSI2",
56
- KeySchema: [
57
- { AttributeName: "GSI2PK", KeyType: "HASH" },
58
- { AttributeName: "GSI2SK", KeyType: "RANGE" },
59
- ],
60
- Projection: {
61
- ProjectionType: "ALL",
62
- },
63
- },
64
- {
65
- IndexName: "GSI3",
51
+ ...GSI_NAMES.map((GSI) => ({
52
+ IndexName: GSI,
66
53
  KeySchema: [
67
- { AttributeName: "GSI3PK", KeyType: "HASH" },
68
- { AttributeName: "GSI3SK", KeyType: "RANGE" },
54
+ { AttributeName: `${GSI}PK`, KeyType: "HASH" },
55
+ { AttributeName: `${GSI}SK`, KeyType: "RANGE" },
69
56
  ],
70
57
  Projection: {
71
58
  ProjectionType: "ALL",
72
59
  },
73
- },
74
- {
75
- IndexName: "GSI4",
76
- KeySchema: [
77
- { AttributeName: "GSI4PK", KeyType: "HASH" },
78
- { AttributeName: "GSI4SK", KeyType: "RANGE" },
79
- ],
80
- Projection: {
81
- ProjectionType: "ALL",
82
- },
83
- },
84
- {
85
- IndexName: "GSI5",
86
- KeySchema: [
87
- { AttributeName: "GSI5PK", KeyType: "HASH" },
88
- { AttributeName: "GSI5SK", KeyType: "RANGE" },
89
- ],
90
- Projection: {
91
- ProjectionType: "ALL",
92
- },
93
- },
60
+ })),
94
61
  ],
95
62
  BillingMode: "PAY_PER_REQUEST",
96
63
  })
package/tsconfig.esm.json CHANGED
@@ -5,9 +5,19 @@
5
5
  "module": "ES2015",
6
6
  "outDir": "./dist/esm",
7
7
  "paths": {
8
- "@model-ts/core": ["../core"]
8
+ "@model-ts/core": [
9
+ "../core"
10
+ ]
9
11
  }
10
12
  },
11
- "references": [{ "path": "../core/tsconfig.esm.json" }],
12
- "exclude": ["__test__", "test-utils"]
13
- }
13
+ "references": [
14
+ {
15
+ "path": "../core/tsconfig.esm.json"
16
+ }
17
+ ],
18
+ "exclude": [
19
+ "dist",
20
+ "__test__",
21
+ "test-utils"
22
+ ]
23
+ }
package/tsconfig.json CHANGED
@@ -4,9 +4,19 @@
4
4
  "rootDir": "./src",
5
5
  "outDir": "./dist/cjs",
6
6
  "paths": {
7
- "@model-ts/core": ["../core"]
7
+ "@model-ts/core": [
8
+ "../core"
9
+ ]
8
10
  }
9
11
  },
10
- "references": [{ "path": "../core" }],
11
- "exclude": ["__test__", "test-utils"]
12
- }
12
+ "references": [
13
+ {
14
+ "path": "../core"
15
+ }
16
+ ],
17
+ "exclude": [
18
+ "dist",
19
+ "__test__",
20
+ "test-utils"
21
+ ]
22
+ }