@kravc/dos-dynamodb 1.0.0-alpha.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 (136) hide show
  1. package/README.md +30 -0
  2. package/bin/table.js +88 -0
  3. package/config/default.yaml +36 -0
  4. package/config/test.yaml +0 -0
  5. package/dist/example/Activity.d.ts +34 -0
  6. package/dist/example/Activity.d.ts.map +1 -0
  7. package/dist/example/Activity.js +66 -0
  8. package/dist/example/Activity.js.map +1 -0
  9. package/dist/example/ActivityAttributes.d.ts +23 -0
  10. package/dist/example/ActivityAttributes.d.ts.map +1 -0
  11. package/dist/example/ActivityAttributes.js +54 -0
  12. package/dist/example/ActivityAttributes.js.map +1 -0
  13. package/dist/example/Asset.d.ts +36 -0
  14. package/dist/example/Asset.d.ts.map +1 -0
  15. package/dist/example/Asset.js +56 -0
  16. package/dist/example/Asset.js.map +1 -0
  17. package/dist/example/AssetAttributes.d.ts +28 -0
  18. package/dist/example/AssetAttributes.d.ts.map +1 -0
  19. package/dist/example/AssetAttributes.js +72 -0
  20. package/dist/example/AssetAttributes.js.map +1 -0
  21. package/dist/example/Organization.d.ts +19 -0
  22. package/dist/example/Organization.d.ts.map +1 -0
  23. package/dist/example/Organization.js +42 -0
  24. package/dist/example/Organization.js.map +1 -0
  25. package/dist/example/OrganizationAttributes.d.ts +13 -0
  26. package/dist/example/OrganizationAttributes.d.ts.map +1 -0
  27. package/dist/example/OrganizationAttributes.js +24 -0
  28. package/dist/example/OrganizationAttributes.js.map +1 -0
  29. package/dist/example/index.d.ts +5 -0
  30. package/dist/example/index.d.ts.map +1 -0
  31. package/dist/example/index.js +13 -0
  32. package/dist/example/index.js.map +1 -0
  33. package/dist/src/Document/Document.d.ts +67 -0
  34. package/dist/src/Document/Document.d.ts.map +1 -0
  35. package/dist/src/Document/Document.js +216 -0
  36. package/dist/src/Document/Document.js.map +1 -0
  37. package/dist/src/Document/DocumentWithHashId.d.ts +22 -0
  38. package/dist/src/Document/DocumentWithHashId.d.ts.map +1 -0
  39. package/dist/src/Document/DocumentWithHashId.js +73 -0
  40. package/dist/src/Document/DocumentWithHashId.js.map +1 -0
  41. package/dist/src/Document/__tests__/__helpers.d.ts +21 -0
  42. package/dist/src/Document/__tests__/__helpers.d.ts.map +1 -0
  43. package/dist/src/Document/__tests__/__helpers.js +92 -0
  44. package/dist/src/Document/__tests__/__helpers.js.map +1 -0
  45. package/dist/src/Document/helpers/composeIndexKeys.d.ts +11 -0
  46. package/dist/src/Document/helpers/composeIndexKeys.d.ts.map +1 -0
  47. package/dist/src/Document/helpers/composeIndexKeys.js +81 -0
  48. package/dist/src/Document/helpers/composeIndexKeys.js.map +1 -0
  49. package/dist/src/Document/helpers/index.d.ts +3 -0
  50. package/dist/src/Document/helpers/index.d.ts.map +1 -0
  51. package/dist/src/Document/helpers/index.js +9 -0
  52. package/dist/src/Document/helpers/index.js.map +1 -0
  53. package/dist/src/Table/Table.d.ts +56 -0
  54. package/dist/src/Table/Table.d.ts.map +1 -0
  55. package/dist/src/Table/Table.js +228 -0
  56. package/dist/src/Table/Table.js.map +1 -0
  57. package/dist/src/Table/helpers/buildConditionExpression.d.ts +22 -0
  58. package/dist/src/Table/helpers/buildConditionExpression.d.ts.map +1 -0
  59. package/dist/src/Table/helpers/buildConditionExpression.js +128 -0
  60. package/dist/src/Table/helpers/buildConditionExpression.js.map +1 -0
  61. package/dist/src/Table/helpers/buildQueryCommandInput.d.ts +12 -0
  62. package/dist/src/Table/helpers/buildQueryCommandInput.d.ts.map +1 -0
  63. package/dist/src/Table/helpers/buildQueryCommandInput.js +60 -0
  64. package/dist/src/Table/helpers/buildQueryCommandInput.js.map +1 -0
  65. package/dist/src/Table/helpers/buildQueryConditionExpression.d.ts +17 -0
  66. package/dist/src/Table/helpers/buildQueryConditionExpression.d.ts.map +1 -0
  67. package/dist/src/Table/helpers/buildQueryConditionExpression.js +77 -0
  68. package/dist/src/Table/helpers/buildQueryConditionExpression.js.map +1 -0
  69. package/dist/src/Table/helpers/buildTableSchema.d.ts +6 -0
  70. package/dist/src/Table/helpers/buildTableSchema.d.ts.map +1 -0
  71. package/dist/src/Table/helpers/buildTableSchema.js +100 -0
  72. package/dist/src/Table/helpers/buildTableSchema.js.map +1 -0
  73. package/dist/src/Table/helpers/buildUpdateExpression.d.ts +10 -0
  74. package/dist/src/Table/helpers/buildUpdateExpression.d.ts.map +1 -0
  75. package/dist/src/Table/helpers/buildUpdateExpression.js +69 -0
  76. package/dist/src/Table/helpers/buildUpdateExpression.js.map +1 -0
  77. package/dist/src/Table/helpers/filterConditionExpression.d.ts +5 -0
  78. package/dist/src/Table/helpers/filterConditionExpression.d.ts.map +1 -0
  79. package/dist/src/Table/helpers/filterConditionExpression.js +68 -0
  80. package/dist/src/Table/helpers/filterConditionExpression.js.map +1 -0
  81. package/dist/src/Table/helpers/getRawClientConfig.d.ts +5 -0
  82. package/dist/src/Table/helpers/getRawClientConfig.d.ts.map +1 -0
  83. package/dist/src/Table/helpers/getRawClientConfig.js +29 -0
  84. package/dist/src/Table/helpers/getRawClientConfig.js.map +1 -0
  85. package/dist/src/Table/helpers/getTableOptions.d.ts +51 -0
  86. package/dist/src/Table/helpers/getTableOptions.d.ts.map +1 -0
  87. package/dist/src/Table/helpers/getTableOptions.js +144 -0
  88. package/dist/src/Table/helpers/getTableOptions.js.map +1 -0
  89. package/dist/src/Table/helpers/index.d.ts +10 -0
  90. package/dist/src/Table/helpers/index.d.ts.map +1 -0
  91. package/dist/src/Table/helpers/index.js +21 -0
  92. package/dist/src/Table/helpers/index.js.map +1 -0
  93. package/dist/src/Table/index.d.ts +8 -0
  94. package/dist/src/Table/index.d.ts.map +1 -0
  95. package/dist/src/Table/index.js +13 -0
  96. package/dist/src/Table/index.js.map +1 -0
  97. package/dist/src/index.d.ts +7 -0
  98. package/dist/src/index.d.ts.map +1 -0
  99. package/dist/src/index.js +14 -0
  100. package/dist/src/index.js.map +1 -0
  101. package/docker-compose.yaml +10 -0
  102. package/eslint.config.mjs +35 -0
  103. package/example/Activity.ts +123 -0
  104. package/example/ActivityAttributes.ts +72 -0
  105. package/example/Asset.ts +78 -0
  106. package/example/AssetAttributes.ts +87 -0
  107. package/example/Organization.ts +61 -0
  108. package/example/OrganizationAttributes.ts +28 -0
  109. package/example/index.ts +9 -0
  110. package/jest.config.mjs +10 -0
  111. package/package.json +50 -0
  112. package/src/Document/DefaultAttributes.d.ts +16 -0
  113. package/src/Document/Document.ts +257 -0
  114. package/src/Document/DocumentWithHashId.ts +85 -0
  115. package/src/Document/__tests__/Document.test.ts +596 -0
  116. package/src/Document/__tests__/DocumentWithHashId.test.ts +81 -0
  117. package/src/Document/__tests__/__helpers.ts +115 -0
  118. package/src/Document/helpers/__tests__/composeIndexKeys.test.ts +40 -0
  119. package/src/Document/helpers/composeIndexKeys.ts +137 -0
  120. package/src/Document/helpers/index.ts +5 -0
  121. package/src/Table/Table.ts +354 -0
  122. package/src/Table/__tests__/Table.test.ts +64 -0
  123. package/src/Table/helpers/__tests__/buildQueryCommandInput.test.ts +14 -0
  124. package/src/Table/helpers/__tests__/buildTableSchema.test.ts +19 -0
  125. package/src/Table/helpers/buildConditionExpression.ts +151 -0
  126. package/src/Table/helpers/buildQueryCommandInput.ts +113 -0
  127. package/src/Table/helpers/buildQueryConditionExpression.ts +109 -0
  128. package/src/Table/helpers/buildTableSchema.ts +151 -0
  129. package/src/Table/helpers/buildUpdateExpression.ts +95 -0
  130. package/src/Table/helpers/filterConditionExpression.ts +87 -0
  131. package/src/Table/helpers/getRawClientConfig.ts +35 -0
  132. package/src/Table/helpers/getTableOptions.ts +228 -0
  133. package/src/Table/helpers/index.ts +21 -0
  134. package/src/Table/index.ts +18 -0
  135. package/src/index.ts +15 -0
  136. package/tsconfig.json +26 -0
@@ -0,0 +1,596 @@
1
+ import { ExpressionKey } from '../../../';
2
+ import { Activity, Asset } from '../../../example';
3
+ import { DocumentExistsError, DocumentNotFoundError } from '@kravc/dos';
4
+ import { createContext, createAsset, createActivity, createOrganization } from './__helpers';
5
+
6
+ const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
7
+ const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
8
+
9
+ describe('Document', () => {
10
+ beforeAll(async () => {
11
+ await Asset.table.reset();
12
+ });
13
+
14
+ afterAll(async () => {
15
+ await Asset.table.delete();
16
+ });
17
+
18
+ const context = createContext();
19
+
20
+ beforeEach(() => {
21
+ context.runtimeReset();
22
+ });
23
+
24
+ describe('Document.indexLimitMax', () => {
25
+ it('returns maximum value for the limit parameter of the index operation', async () => {
26
+ expect(Asset.indexLimitMax).toEqual(999);
27
+ });
28
+ });
29
+
30
+ describe('Document._create(attributes)', () => {
31
+ it('creates a document', async () => {
32
+ const asset = await createAsset();
33
+
34
+ expect(asset.attributes.name).toEqual('Text Document');
35
+ expect(asset.attributes.partition).toEqual('ORG_1');
36
+ expect(asset.attributes.createdAt).toBeDefined();
37
+ expect(asset.attributes.createdBy).toEqual('USR_1');
38
+ expect(asset.attributes.createdByUserName).toEqual('John Doe');
39
+ });
40
+
41
+ it('throws DocumentExistsError if document key is already taken', async () => {
42
+ const { id } = await createAsset();
43
+
44
+ await expect(createAsset({ id }))
45
+ .rejects
46
+ .toThrow(DocumentExistsError);
47
+ });
48
+ });
49
+
50
+ describe('Document._read(query)', () => {
51
+ it('throws DocumentNotFoundError if document not found', async () => {
52
+ await expect(Asset.read(context, { id: 'AST_BAD_ID' }))
53
+ .rejects
54
+ .toThrow(DocumentNotFoundError);
55
+ });
56
+
57
+ it('reads a document', async () => {
58
+ const { id } = await createAsset();
59
+ const asset = await Asset.read(context, { id });
60
+
61
+ expect(asset).toBeDefined();
62
+ });
63
+
64
+ it('supports query value', async () => {
65
+ const { id } = await createAsset();
66
+ const asset = await Asset.read(context, { id, name: 'Text Document' });
67
+
68
+ expect(asset).toBeDefined();
69
+ });
70
+
71
+ it('supports array query value', async () => {
72
+ const { id } = await createAsset();
73
+ const asset = await Asset.read(context, { id, name: [ 'Text Document', 'Text Document 2' ] });
74
+
75
+ expect(asset).toBeDefined();
76
+ });
77
+
78
+ it('supports "null" query attribute value', async () => {
79
+ const { id } = await createAsset();
80
+ await expect(Asset.read(context, { id, name: null }))
81
+ .rejects
82
+ .toThrow(DocumentNotFoundError);
83
+ });
84
+
85
+ it('supports :not expression key', async () => {
86
+ const { id } = await createAsset();
87
+ await expect(Asset.read(context, { id, [`type:${ExpressionKey.NOT}`]: 'CONTENT' }))
88
+ .rejects
89
+ .toThrow(DocumentNotFoundError);
90
+ });
91
+
92
+ it('supports :not expression key with "null" value', async () => {
93
+ const { id } = await createAsset();
94
+ await expect(Asset.read(context, { id, [`folderId:${ExpressionKey.NOT}`]: null }))
95
+ .rejects
96
+ .toThrow(DocumentNotFoundError);
97
+ });
98
+
99
+ it('supports :contains expression key', async () => {
100
+ const { id } = await createAsset();
101
+ const asset = await Asset.read(context, { id, [`tags:${ExpressionKey.CONTAIN}`]: 'Tag A' });
102
+
103
+ expect(asset).toBeDefined();
104
+ });
105
+
106
+ it('supports :not_contains expression key', async () => {
107
+ const { id } = await createAsset();
108
+ const asset = await Asset.read(context, { id, [`tags:${ExpressionKey.EXCLUDE}`]: 'Tag C' });
109
+
110
+ expect(asset).toBeDefined();
111
+ });
112
+ });
113
+
114
+ describe('Document._update(query, mutation, context, previousAttributes)', () => {
115
+ it('throws DocumentNotFoundError if document not found', async () => {
116
+ await expect(Asset.update(context, { id: 'AST_BAD_ID' }, { name: 'Text Document 2' }))
117
+ .rejects
118
+ .toThrow(DocumentNotFoundError);
119
+
120
+ await expect(Asset._update({ partition: 'ORG_1', id: 'AST_BAD_ID' }, { name: 'Text Document 2' }, context, {}))
121
+ .rejects
122
+ .toThrow(DocumentNotFoundError);
123
+ });
124
+
125
+ it('updates a document', async () => {
126
+ const { id } = await createAsset();
127
+ const asset = await Asset.update(context, { id }, { name: 'Text Document 2' });
128
+
129
+ expect(asset.attributes.name).toEqual('Text Document 2');
130
+ });
131
+
132
+ it('supports "null" mutation attribute value', async () => {
133
+ const { id } = await createAsset({ folderId: 'FLD_1' });
134
+
135
+ const asset = await Asset.update(context, { id }, { folderId: null });
136
+
137
+ expect(asset.attributes.folderId).toEqual(undefined);
138
+ });
139
+
140
+ it('supports "null" mutation attribute value exclusively', async () => {
141
+ const { id } = await createAsset({ folderId: 'FLD_1' });
142
+
143
+ const item = await Asset._update({ partition: 'ORG_1', id }, { folderId: null }, context, {}) as Record<string, undefined>;
144
+
145
+ expect(item.folderId).toBeUndefined();
146
+ });
147
+
148
+ it('supports :append mutation expression key', async () => {
149
+ const { id } = await createAsset({ tags: [ 'Tag B' ] });
150
+
151
+ const asset = await Asset.update(context, { id }, { 'tags:append': 'Tag C' });
152
+
153
+ expect(asset.attributes.tags).toEqual([ 'Tag B', 'Tag C' ]);
154
+ });
155
+
156
+ it('supports :prepend mutation expression key', async () => {
157
+ const { id } = await createAsset({ tags: [ 'Tag B' ] });
158
+
159
+ const asset = await Asset.update(context, { id }, { 'tags:prepend': 'Tag A' });
160
+
161
+ expect(asset.attributes.tags).toEqual([ 'Tag A', 'Tag B' ]);
162
+ });
163
+
164
+ it('supports array item by item index', async () => {
165
+ const { id } = await createAsset({ tags: [ 'Tag B' ] });
166
+
167
+ const asset = await Asset.update(context, { id }, { 'tags[0]': 'Tag A' });
168
+
169
+ expect(asset.attributes.tags).toEqual([ 'Tag A' ]);
170
+ });
171
+ });
172
+
173
+ describe('Document._delete(query)', () => {
174
+ it('throws DocumentNotFoundError if document not found', async () => {
175
+ await expect(Asset.delete(context, { id: 'AST_BAD_ID' }))
176
+ .rejects
177
+ .toThrow(DocumentNotFoundError);
178
+
179
+ await expect(Asset._delete({ partition: 'ORG_1', id: 'AST_BAD_ID' }, context, {}))
180
+ .rejects
181
+ .toThrow(DocumentNotFoundError);
182
+ });
183
+
184
+ it('deletes a document', async () => {
185
+ const { id } = await createAsset();
186
+
187
+ await Asset.delete(context, { id });
188
+
189
+ await expect(Asset.read(context, { id }))
190
+ .rejects
191
+ .toThrow(DocumentNotFoundError);
192
+ });
193
+ });
194
+
195
+ describe('Document.index(context, query, options)', () => {
196
+ beforeAll(async () => {
197
+ await Asset.table.reset();
198
+
199
+ for (let i = 0; i < 600 + 15; i++) {
200
+ await createAsset({ name: `Text Document ${i + 1}` });
201
+ }
202
+
203
+ await createActivity();
204
+ });
205
+
206
+ describe('Document._indexAll(query, options)', () => {
207
+ it('gets all documents from a table', async () => {
208
+ const { count } = await Asset.indexAll(context);
209
+ expect(count).toEqual(615);
210
+ });
211
+ });
212
+
213
+ describe('Document._index(query, options)', () => {
214
+ it('gets documents from a table with default options', async () => {
215
+ const {
216
+ count,
217
+ limit,
218
+ lastEvaluatedKey
219
+ } = await Asset.index(context);
220
+
221
+ expect(count).toEqual(20);
222
+ expect(limit).toEqual(20);
223
+ expect(lastEvaluatedKey).toBeDefined();
224
+
225
+ const {
226
+ count: count2,
227
+ limit: limit2,
228
+ lastEvaluatedKey: lastEvaluatedKey2
229
+ } = await Activity.indexWithDateRange(context);
230
+
231
+ expect(count2).toEqual(1);
232
+ expect(limit2).toEqual(20);
233
+ expect(lastEvaluatedKey2).toBeUndefined();
234
+ });
235
+
236
+ it('supports :lt condition via LT expression key', async () => {
237
+ const query = { [`createdAt:${ExpressionKey.LT}`]: tomorrow };
238
+ const { count } = await Asset.index(context, query);
239
+
240
+ expect(count).toBeGreaterThan(0);
241
+ });
242
+
243
+ it('supports :le condition via LE expression key', async () => {
244
+ const { objects } = await Asset.indexAll(context, {}, { sort: 'asc'});
245
+ const [ firstAsset ] = objects;
246
+ const { createdAt } = firstAsset.attributes;
247
+
248
+ const query = { [`createdAt:${ExpressionKey.LE}`]: createdAt };
249
+ const { count } = await Asset.index(context, query);
250
+
251
+ expect(count).toEqual(1);
252
+ });
253
+
254
+ it('supports :gt condition via GT expression key', async () => {
255
+ const query = { [`createdAt:${ExpressionKey.GT}`]: yesterday };
256
+ const { count } = await Asset.index(context, query);
257
+
258
+ expect(count).toBeGreaterThan(0);
259
+ });
260
+
261
+ it('supports :ge condition via GE expression key', async () => {
262
+ const { objects } = await Asset.indexAll(context);
263
+ const [ lastAsset ] = objects;
264
+ const { createdAt } = lastAsset.attributes;
265
+
266
+ const query = { [`createdAt:${ExpressionKey.GE}`]: createdAt };
267
+ const { count } = await Asset.index(context, query);
268
+
269
+ expect(count).toEqual(1);
270
+ });
271
+
272
+ it('supports :not condition via NOT expression key', async () => {
273
+ const query = { [`type:${ExpressionKey.NOT}`]: 'CONTENT' };
274
+ const { count } = await Asset.index(context, query);
275
+
276
+ expect(count).toEqual(0);
277
+ });
278
+
279
+ it('supports :contains condition via CONTAIN expression key', async () => {
280
+ const query = { [`tags:${ExpressionKey.CONTAIN}`]: 'Tag A' };
281
+ const { count } = await Asset.index(context, query);
282
+
283
+ expect(count).toBeGreaterThan(0);
284
+ });
285
+
286
+ it('supports :not_contains condition via EXCLUDE expression key', async () => {
287
+ const query = { [`tags:${ExpressionKey.EXCLUDE}`]: 'Tag C' };
288
+ const { count } = await Asset.index(context, query);
289
+
290
+ expect(count).toBeGreaterThan(0);
291
+ });
292
+
293
+ it('supports :in condition via array query value', async () => {
294
+ const query = { name: [ 'Text Document 1', 'Text Document 2' ] };
295
+ const { count } = await Asset.index(context, query);
296
+
297
+ expect(count).toEqual(2);
298
+ });
299
+
300
+ it('supports "null" condition via null query value', async () => {
301
+ const { count: count1 } = await Asset.index(context, { folderId: null });
302
+
303
+ expect(count1).toEqual(20);
304
+
305
+ const { count: count2 } = await Asset.index(context, { [`name:${ExpressionKey.NOT}`]: null });
306
+
307
+ expect(count2).toEqual(20);
308
+ });
309
+ });
310
+ });
311
+
312
+ describe('Document.composeIndexKeys', () => {
313
+ describe('Document._create(attributes)', () => {
314
+ let asset: Asset;
315
+
316
+ beforeAll(async () => {
317
+ const { id: assetId } = await createAsset();
318
+ asset = await Asset.read(context, { id: assetId });
319
+ });
320
+
321
+ it('creates a document with a local secondary index sort key', async () => {
322
+ const { createdAt } = asset.attributes;
323
+
324
+ expect(asset.attributes._lsi2SortKey).toEqual(`#ACTIVE#${createdAt}`);
325
+ });
326
+
327
+ it('creates a document with a global secondary index partition and sort keys', async () => {
328
+ const { partition, createdBy, createdAt } = asset.attributes;
329
+
330
+ expect(asset.attributes._gsi1PartitionKey).toEqual(`AST#${partition}#${createdBy}`);
331
+ expect(asset.attributes._gsi1SortKey).toEqual(`#ACTIVE#${createdAt}`);
332
+ });
333
+ });
334
+
335
+ describe('Document._update(query, options)', () => {
336
+ it('updates a document with updated local secondary index sort key', async () => {
337
+ const { id } = await createAsset();
338
+
339
+ const asset = await Asset.read(context, { id });
340
+ const { createdAt } = asset.attributes;
341
+
342
+ const archivedAsset = await Asset.archive(context, { id });
343
+
344
+ expect(archivedAsset.attributes.status).toEqual('ARCHIVED');
345
+ expect(archivedAsset.attributes._lsi2SortKey).toEqual(`#ARCHIVED#${createdAt}`);
346
+
347
+ const unarchivedAsset = await Asset.unarchive(context, { id });
348
+
349
+ expect(unarchivedAsset.attributes.status).toEqual('ACTIVE');
350
+ expect(unarchivedAsset.attributes._lsi2SortKey).toEqual(`#ACTIVE#${createdAt}`);
351
+ });
352
+ });
353
+ });
354
+
355
+ describe('Document.isLocalSecondaryIndex(indexName)', () => {
356
+ it('throws an exception if index is not defined', async () => {
357
+ expect(() => Asset.isLocalSecondaryIndex('customLocalIndexName'))
358
+ .toThrow('Index "customLocalIndexName" is not defined');
359
+ });
360
+ });
361
+
362
+ describe('Document.getLocalIndexSortKeyName(indexName)', () => {
363
+ beforeAll(async () => {
364
+ await Asset.table.reset();
365
+
366
+ await createActivity();
367
+ await createActivity({ operationId: 'UpdateAsset' });
368
+ await createActivity({ operationId: 'DeleteAsset' });
369
+
370
+ const { id } = await createAsset();
371
+ await Asset.archive(context, { id });
372
+
373
+ await createOrganization();
374
+ });
375
+
376
+ it('throws an exception if local secondary index is not defined', async () => {
377
+ expect(() => Asset.getLocalIndexSortKeyName('customLocalIndexName'))
378
+ .toThrow('Local secondary index "customLocalIndexName" is not defined');
379
+ });
380
+
381
+ describe('Document._index(query, options)', () => {
382
+ it('returns items from local secondary index with sort key = expression', async () => {
383
+ const { objects } = await Asset.indexArchivedAssets(context, {}, { limit: 1 });
384
+
385
+ const [ object ] = objects;
386
+ const { createdAt } = object.attributes;
387
+
388
+ const { indexName, sortKeyName } = Asset.getLocalIndexProps('lsi2');
389
+ const query = { [sortKeyName]: `#ARCHIVED#${createdAt}` };
390
+
391
+ const { count } = await Asset.index(context, query, { indexName });
392
+ expect(count).toEqual(1);
393
+ });
394
+
395
+ it('returns items from local secondary index with sort key :bw', async () => {
396
+ const { count: archivedCount } = await Asset.indexArchivedAssets(context);
397
+ expect(archivedCount).toEqual(1);
398
+
399
+ const { count: activeCount } = await Asset.indexActiveAssets(context);
400
+ expect(activeCount).toEqual(0);
401
+ });
402
+
403
+ it('returns items from local secondary index with sort key :ge expression', async () => {
404
+ const [ startDate ] = new Date().toISOString().split('T');
405
+
406
+ const { count } = await Activity.indexWithDateRange(context, { startDate });
407
+ expect(count).toEqual(3);
408
+ });
409
+
410
+ it('returns items from local secondary index with sort key :le expression', async () => {
411
+ const [ endDate ] = new Date().toISOString().split('T');
412
+
413
+ const { count } = await Activity.indexWithDateRange(context, { endDate });
414
+ expect(count).toEqual(3);
415
+ });
416
+
417
+ it('returns items from local secondary index with sort key :gt and :lt expressions', async () => {
418
+ const [ startDate ] = yesterday.split('T');
419
+ const [ endDate ] = tomorrow.split('T');
420
+
421
+ const { indexName, sortKeyName } = Asset.getLocalIndexProps('lsi3');
422
+
423
+ const query = {
424
+ [`${sortKeyName}:gt`]: `#${startDate}`,
425
+ [`${sortKeyName}:lt`]: `#${endDate}`,
426
+ };
427
+
428
+ const { count } = await Activity.index(context, query, { indexName });
429
+ expect(count).toEqual(3);
430
+ });
431
+
432
+ it('returns items from local secondary index with sort key :gt expression', async () => {
433
+ const [ startDate ] = yesterday.split('T');
434
+
435
+ const { indexName, sortKeyName } = Asset.getLocalIndexProps('lsi3');
436
+
437
+ const query = {
438
+ [`${sortKeyName}:gt`]: `#${startDate}`,
439
+ };
440
+
441
+ const { count } = await Activity.index(context, query, { indexName });
442
+ expect(count).toEqual(3);
443
+ });
444
+
445
+ it('returns items from local secondary index with sort key :lt expression', async () => {
446
+ const [ startDate ] = tomorrow.split('T');
447
+
448
+ const { indexName, sortKeyName } = Asset.getLocalIndexProps('lsi3');
449
+
450
+ const query = {
451
+ [`${sortKeyName}:lt`]: `#${startDate}`,
452
+ };
453
+
454
+ const { count } = await Activity.index(context, query, { indexName });
455
+ expect(count).toEqual(3);
456
+ });
457
+ });
458
+ });
459
+
460
+ describe('Document.getGlobalIndexPartitionKeyName(indexName)', () => {
461
+ it('throws an exception if global secondary index is not defined', async () => {
462
+ expect(() => Asset.getGlobalIndexPartitionKeyName('customGlobalIndexName'))
463
+ .toThrow('Global secondary index "customGlobalIndexName" is not defined');
464
+ });
465
+ });
466
+
467
+ describe('Document.getGlobalIndexSortKeyName(indexName)', () => {
468
+ beforeAll(async () => {
469
+ await Asset.table.reset();
470
+
471
+ await createActivity();
472
+ await createActivity({ operationId: 'UpdateAsset' });
473
+ await createActivity({ operationId: 'DeleteAsset' });
474
+
475
+ const { id } = await createAsset();
476
+ await Asset.archive(context, { id });
477
+
478
+ await createOrganization();
479
+ });
480
+
481
+ it('throws an exception if global secondary index is not defined', async () => {
482
+ expect(() => Asset.getGlobalIndexSortKeyName('customGlobalIndexName'))
483
+ .toThrow('Global secondary index "customGlobalIndexName" is not defined');
484
+ });
485
+
486
+ describe('Document._index(query, options)', () => {
487
+ it('returns items from primary table', async () => {
488
+ const { count } = await Activity.index(context);
489
+ expect(count).toEqual(3);
490
+ });
491
+
492
+ it('returns items from global index without sort key', async () => {
493
+ const {
494
+ indexName,
495
+ partitionKeyName,
496
+ } = Activity.getGlobalIndexProps('gsi2');
497
+
498
+ const query = {
499
+ [partitionKeyName]: 'ORG_1#UpdateAsset',
500
+ };
501
+
502
+ const { count } = await Activity.index(context, query, { indexName });
503
+ expect(count).toEqual(1);
504
+ });
505
+
506
+ it('returns items from global secondary index', async () => {
507
+ const query = { operationId: 'UpdateAsset' };
508
+
509
+ const { count } = await Activity.indexWithDateRange(context, query);
510
+ expect(count).toEqual(1);
511
+ });
512
+
513
+ it('returns items from global secondary index with sort key :bw expression', async () => {
514
+ const createdBy = 'USR_1';
515
+
516
+ const { count: archivedCount } = await Asset.indexArchivedAssets(context, { createdBy });
517
+ expect(archivedCount).toEqual(1);
518
+
519
+ const { count: activeCount } = await Asset.indexActiveAssets(context, { createdBy });
520
+ expect(activeCount).toEqual(0);
521
+ });
522
+
523
+ it('returns items from global secondary index with sort key :ge expression', async () => {
524
+ const operationId = 'UpdateAsset';
525
+ const [ startDate ] = new Date().toISOString().split('T');
526
+
527
+ const { count } = await Activity.indexWithDateRange(context, { operationId, startDate });
528
+ expect(count).toEqual(1);
529
+ });
530
+
531
+ it('returns items from global secondary index with sort key :le expression', async () => {
532
+ const userId = 'USR_1';
533
+ const [ endDate ] = new Date().toISOString().split('T');
534
+
535
+ const { count } = await Activity.indexWithDateRange(context, { userId, endDate });
536
+ expect(count).toEqual(3);
537
+ });
538
+
539
+ it('returns items from global secondary index with sort key :gt and :lt expressions', async () => {
540
+ const [ startDate ] = yesterday.split('T');
541
+ const [ endDate ] = tomorrow.split('T');
542
+
543
+ const {
544
+ indexName,
545
+ sortKeyName,
546
+ partitionKeyName,
547
+ } = Activity.getGlobalIndexProps('gsi2');
548
+
549
+ const query = {
550
+ [partitionKeyName]: 'ORG_1#UpdateAsset',
551
+ [`${sortKeyName}:gt`]: `#${startDate}`,
552
+ [`${sortKeyName}:lt`]: `#${endDate}`,
553
+ };
554
+
555
+ const { count } = await Activity.index(context, query, { indexName });
556
+ expect(count).toEqual(1);
557
+ });
558
+
559
+ it('returns items from global secondary index with sort key :gt expression', async () => {
560
+ const [ startDate ] = yesterday.split('T');
561
+
562
+ const {
563
+ indexName,
564
+ sortKeyName,
565
+ partitionKeyName,
566
+ } = Activity.getGlobalIndexProps('gsi2');
567
+
568
+ const query = {
569
+ [partitionKeyName]: 'ORG_1#UpdateAsset',
570
+ [`${sortKeyName}:gt`]: `#${startDate}`,
571
+ };
572
+
573
+ const { count } = await Activity.index(context, query, { indexName });
574
+ expect(count).toEqual(1);
575
+ });
576
+
577
+ it('returns items from global secondary index with sort key :lt expression', async () => {
578
+ const [ startDate ] = tomorrow.split('T');
579
+
580
+ const {
581
+ indexName,
582
+ sortKeyName,
583
+ partitionKeyName,
584
+ } = Activity.getGlobalIndexProps('gsi2');
585
+
586
+ const query = {
587
+ [partitionKeyName]: 'ORG_1#UpdateAsset',
588
+ [`${sortKeyName}:lt`]: `#${startDate}`,
589
+ };
590
+
591
+ const { count } = await Activity.index(context, query, { indexName });
592
+ expect(count).toEqual(1);
593
+ });
594
+ });
595
+ });
596
+ });
@@ -0,0 +1,81 @@
1
+ import { Organization } from '../../../example';
2
+ import {
3
+ resetTable,
4
+ createContext,
5
+ createAsset,
6
+ createActivity,
7
+ createOrganization,
8
+ } from './__helpers';
9
+
10
+ describe('DocumentWithHashId', () => {
11
+ const context = createContext();
12
+
13
+ beforeAll(async () => {
14
+ await resetTable();
15
+ });
16
+
17
+ describe('DocumentWithHashId._create(attributes, context)', () => {
18
+ it('creates first document with hash ID and number 1', async () => {
19
+ const organization = await createOrganization();
20
+
21
+ expect(organization.attributes.id).toEqual('O49');
22
+ expect(organization.attributes.number).toEqual(1);
23
+ });
24
+
25
+ it('creates next document with hash ID and next number', async () => {
26
+ const organization = await createOrganization({ name: 'Organization 2' });
27
+
28
+ expect(organization.attributes.id).toEqual('O6E');
29
+ expect(organization.attributes.number).toEqual(2);
30
+ });
31
+
32
+ it('creates next document with hash ID and next number if race condition happens', async () => {
33
+ const attributes = {
34
+ id: undefined,
35
+ name: 'Organization 3',
36
+ number: 2,
37
+ partition: 'PLATFORM',
38
+ };
39
+
40
+ await Organization._create(attributes, context);
41
+
42
+ expect(attributes.id).toEqual('O9D');
43
+ });
44
+ });
45
+
46
+ describe('DocumentWithHashId._getIndexQuery(query, options)', () => {
47
+ let id: string;
48
+
49
+ beforeAll(async () => {
50
+ await resetTable();
51
+
52
+ const organization = await createOrganization();
53
+ await createOrganization({ name: 'Organization 2' });
54
+ await createOrganization({ name: 'Organization 3' });
55
+
56
+ id = organization.id;
57
+ await Organization.disable(context, { id });
58
+
59
+ await createAsset();
60
+ await createActivity();
61
+ });
62
+
63
+ it('returns all documents using first local secondary index', async () => {
64
+ const { count } = await Organization.indexAll(context);
65
+
66
+ expect(count).toEqual(3);
67
+ });
68
+
69
+ it('returns documents using custom global secondary index', async () => {
70
+ const { count } = await Organization.listActiveOrganizations(context);
71
+
72
+ expect(count).toEqual(2);
73
+
74
+ await Organization.enable(context, { id });
75
+
76
+ const { count: count2 } = await Organization.listActiveOrganizations(context);
77
+
78
+ expect(count2).toEqual(3);
79
+ });
80
+ });
81
+ });