@opble/repository-dynamodb 1.0.0 → 1.0.2

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/dist/index.cjs CHANGED
@@ -1,324 +1,2 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- AbstractDynamoDBRepository: () => AbstractDynamoDBRepository,
24
- QueryAllSchema: () => QueryAllSchema,
25
- QueryWithPaginationSchema: () => QueryWithPaginationSchema,
26
- QueryWithSortingSchema: () => QueryWithSortingSchema,
27
- ScanFilterSchema: () => ScanFilterSchema,
28
- TableKeychema: () => TableKeychema,
29
- TableSpecSchema: () => TableSpecSchema
30
- });
31
- module.exports = __toCommonJS(index_exports);
32
-
33
- // src/repository.ts
34
- var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
35
- var import_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
36
- var import_debug = require("@opble/debug");
37
- var import_entity = require("@opble/entity");
38
- var debug = (0, import_debug.createDebug)("opble:repository-dynamodb");
39
- var SORT_ASCENDING = "asc";
40
- var BATCH_SIZE = 50;
41
- function now() {
42
- return Math.floor(Date.now() / 1e3);
43
- }
44
- var AbstractDynamoDBRepository = class {
45
- constructor(table, factory) {
46
- this.table = table;
47
- this.factory = factory;
48
- this.client = import_lib_dynamodb.DynamoDBDocumentClient.from(new import_client_dynamodb.DynamoDBClient({}));
49
- }
50
- client;
51
- getKey(id) {
52
- return {
53
- [this.table.keys.hashKey]: id.hashKey,
54
- ...this.table.keys.sortKey ? { [this.table.keys.sortKey]: id.sortKey } : {}
55
- };
56
- }
57
- async query(hashKey, query, pagination, sorting) {
58
- let lastEvaluatedKey;
59
- let currentPage = 1;
60
- let resultItems = [];
61
- const filterReturnedItemsExcludeSortKey = (items) => {
62
- return query?.excludeSortKey && this.table.keys.sortKey ? items.filter(
63
- (item) => item[this.table.keys.sortKey] !== query.excludeSortKey
64
- ) : items;
65
- };
66
- do {
67
- const command = new import_lib_dynamodb.QueryCommand({
68
- TableName: this.table.name,
69
- KeyConditionExpression: `#${this.table.keys.hashKey} = :hashKeyValue`,
70
- ExpressionAttributeNames: {
71
- [`#${this.table.keys.hashKey}`]: this.table.keys.hashKey
72
- },
73
- ExpressionAttributeValues: {
74
- ":hashKeyValue": hashKey
75
- },
76
- ExclusiveStartKey: lastEvaluatedKey
77
- });
78
- if (pagination) {
79
- command.input.Limit = pagination.limit;
80
- }
81
- if (sorting) {
82
- command.input.ScanIndexForward = sorting.sortOrder === "asc";
83
- }
84
- if (query) {
85
- if (query.index) {
86
- command.input.IndexName = query.index.name;
87
- command.input.KeyConditionExpression = `#${query.index.keys.hashKey} = :hashKeyValue`;
88
- command.input.ExpressionAttributeNames = {
89
- [`#${query.index.keys.hashKey}`]: query.index.keys.hashKey
90
- };
91
- }
92
- const sortKey = query.index?.keys.sortKey ?? this.table.keys.sortKey;
93
- if (query.sortKeyBeginsWith && sortKey) {
94
- command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND begins_with(#${sortKey}, :sortKeyBeginsWith)`;
95
- command.input.ExpressionAttributeNames ??= {};
96
- command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;
97
- command.input.ExpressionAttributeValues ??= {};
98
- command.input.ExpressionAttributeValues[":sortKeyBeginsWith"] = query.sortKeyBeginsWith;
99
- } else if (query.sortKey && sortKey) {
100
- command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} = :sortKeyValue`;
101
- command.input.ExpressionAttributeNames ??= {};
102
- command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;
103
- command.input.ExpressionAttributeValues ??= {};
104
- command.input.ExpressionAttributeValues[":sortKeyValue"] = query.sortKey;
105
- } else if (query.sortKeyBetween && sortKey) {
106
- command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} BETWEEN :sortKeyStart AND :sortKeyEnd`;
107
- command.input.ExpressionAttributeNames ??= {};
108
- command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;
109
- command.input.ExpressionAttributeValues ??= {};
110
- command.input.ExpressionAttributeValues[":sortKeyStart"] = query.sortKeyBetween.start;
111
- command.input.ExpressionAttributeValues[":sortKeyEnd"] = query.sortKeyBetween.end;
112
- }
113
- }
114
- debug("command#input", command.input);
115
- const result = await this.client.send(command);
116
- lastEvaluatedKey = result.LastEvaluatedKey;
117
- if (!pagination) {
118
- resultItems.push(
119
- ...filterReturnedItemsExcludeSortKey(result.Items ?? [])
120
- );
121
- } else if (currentPage === pagination.page) {
122
- resultItems = filterReturnedItemsExcludeSortKey(result.Items ?? []);
123
- break;
124
- }
125
- currentPage++;
126
- } while (lastEvaluatedKey);
127
- const strict = !query || !query.index;
128
- return resultItems.map((item) => this.factory.create(item, { strict }));
129
- }
130
- async queryInBulk(keys, query) {
131
- const chunks = [];
132
- for (let i = 0; i < keys.length; i += BATCH_SIZE) {
133
- chunks.push(keys.slice(i, i + BATCH_SIZE));
134
- }
135
- const results = await Promise.all(
136
- chunks.map(async (chunk) => {
137
- const command = new import_lib_dynamodb.BatchGetCommand({
138
- RequestItems: {
139
- [this.table.name]: { Keys: chunk }
140
- }
141
- });
142
- const response = await this.client.send(command);
143
- return response.Responses?.[this.table.name] ?? [];
144
- })
145
- );
146
- const mergedItems = results.flat();
147
- const items = mergedItems.map((item) => this.factory.create(item));
148
- if (query && query.sortBy) {
149
- const sortOrder = query.sortOrder ?? SORT_ASCENDING;
150
- items.sort((a, b) => {
151
- if (a[query.sortBy] < b[query.sortBy])
152
- return sortOrder === SORT_ASCENDING ? -1 : 1;
153
- if (a[query.sortBy] > b[query.sortBy])
154
- return sortOrder === SORT_ASCENDING ? 1 : -1;
155
- return 0;
156
- });
157
- }
158
- return items;
159
- }
160
- async scan(pagination, filter) {
161
- let lastEvaluatedKey;
162
- let currentPage = 1;
163
- let resultItems = [];
164
- do {
165
- const command = new import_lib_dynamodb.ScanCommand({
166
- TableName: this.table.name,
167
- ExclusiveStartKey: lastEvaluatedKey
168
- });
169
- if (pagination) {
170
- command.input.Limit = pagination.limit;
171
- }
172
- if (filter) {
173
- command.input.FilterExpression = filter.filterExpression;
174
- command.input.ExpressionAttributeNames = filter.expressionAttributeNames;
175
- command.input.ExpressionAttributeValues = filter.expressionAttributeValues;
176
- }
177
- const result = await this.client.send(command);
178
- lastEvaluatedKey = result.LastEvaluatedKey;
179
- if (!pagination) {
180
- resultItems.push(...result.Items ?? []);
181
- } else if (currentPage === pagination.page) {
182
- resultItems = result.Items ?? [];
183
- break;
184
- }
185
- currentPage++;
186
- } while (lastEvaluatedKey);
187
- return resultItems.map((item) => this.factory.create(item));
188
- }
189
- async deleteAll(hashKey) {
190
- const items = await this.query(hashKey);
191
- const keys = items.map((item) => {
192
- if (this.table.keys.sortKey) {
193
- return {
194
- [this.table.keys.hashKey]: hashKey,
195
- [this.table.keys.sortKey]: item[this.table.keys.sortKey]
196
- };
197
- } else {
198
- return {
199
- [this.table.keys.hashKey]: hashKey
200
- };
201
- }
202
- });
203
- await this.deleteInBulk(keys);
204
- }
205
- async deleteInBulk(keys) {
206
- const batches = [];
207
- while (keys.length > 0) {
208
- batches.push(keys.splice(0, BATCH_SIZE));
209
- }
210
- for (const batch of batches) {
211
- const deleteRequests = batch.map((key) => ({
212
- DeleteRequest: { Key: key }
213
- }));
214
- const command = new import_lib_dynamodb.BatchWriteCommand({
215
- RequestItems: {
216
- [this.table.name]: deleteRequests
217
- }
218
- });
219
- try {
220
- const response = await this.client.send(command);
221
- debug("[INFO] Batch delete response:", response);
222
- if (response.UnprocessedItems && response.UnprocessedItems[this.table.name]) {
223
- debug("[WARN] Unprocessed items found. Retrying...");
224
- const unprocessedItems = response.UnprocessedItems[this.table.name];
225
- if (unprocessedItems === void 0) {
226
- continue;
227
- }
228
- const unprocessedKeys = unprocessedItems.map((item) => item.DeleteRequest?.Key).filter((key) => key != null);
229
- await this.deleteInBulk(unprocessedKeys);
230
- }
231
- } catch (error) {
232
- debug("[ERROR] Error deleting items in batch:", error);
233
- }
234
- }
235
- }
236
- async get(id) {
237
- const command = new import_lib_dynamodb.GetCommand({
238
- TableName: this.table.name,
239
- Key: this.getKey(id)
240
- });
241
- const result = await this.client.send(command);
242
- return result.Item ? this.factory.create(result.Item) : null;
243
- }
244
- /**
245
- * Allow to manipulate the data before saving
246
- * @param item
247
- * @returns
248
- */
249
- beforeSave(item) {
250
- if ((0, import_entity.isTimestamps)(item)) {
251
- item.updatedAt = now();
252
- } else if ((0, import_entity.isTimestamp)(item)) {
253
- item.timestamp = now();
254
- }
255
- return item;
256
- }
257
- async save(item) {
258
- item = this.beforeSave(item);
259
- const command = new import_lib_dynamodb.PutCommand({
260
- TableName: this.table.name,
261
- Item: { ...item }
262
- });
263
- await this.client.send(command);
264
- return item;
265
- }
266
- async delete(id) {
267
- await this.client.send(
268
- new import_lib_dynamodb.DeleteCommand({
269
- TableName: this.table.name,
270
- Key: this.getKey(id)
271
- })
272
- );
273
- }
274
- };
275
-
276
- // src/types.ts
277
- var import_zod = require("zod");
278
- var TableKeychema = import_zod.z.object({
279
- hashKey: import_zod.z.string(),
280
- sortKey: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional()
281
- });
282
- var TableSpecSchema = import_zod.z.object({
283
- name: import_zod.z.string(),
284
- keys: import_zod.z.object({
285
- hashKey: import_zod.z.string(),
286
- sortKey: import_zod.z.string().optional()
287
- })
288
- });
289
- var QueryWithPaginationSchema = import_zod.z.object({
290
- page: import_zod.z.string().optional().transform((val) => val ? parseInt(val, 10) : 1).refine((val) => val >= 1, { message: "Page must be at least 1" }),
291
- limit: import_zod.z.string().optional().transform((val) => val ? parseInt(val, 10) : 25).refine((val) => val >= 5 && val <= 100, {
292
- message: "Limit must be between 5 and 100"
293
- })
294
- });
295
- var QueryWithSortingSchema = import_zod.z.object({
296
- sortBy: import_zod.z.string().optional(),
297
- sortOrder: import_zod.z.enum(["asc", "desc"]).optional().transform((val) => val ?? "asc")
298
- });
299
- var QueryAllSchema = import_zod.z.object({
300
- excludeSortKey: import_zod.z.string().optional(),
301
- sortKeyBetween: import_zod.z.object({
302
- start: import_zod.z.string(),
303
- end: import_zod.z.string()
304
- }).optional(),
305
- sortKeyBeginsWith: import_zod.z.string().optional(),
306
- sortKey: import_zod.z.any().optional(),
307
- index: TableSpecSchema.optional()
308
- });
309
- var ScanFilterSchema = import_zod.z.object({
310
- filterExpression: import_zod.z.string(),
311
- expressionAttributeNames: import_zod.z.record(import_zod.z.string(), import_zod.z.string()),
312
- expressionAttributeValues: import_zod.z.record(import_zod.z.string(), import_zod.z.any())
313
- });
314
- // Annotate the CommonJS export names for ESM import in node:
315
- 0 && (module.exports = {
316
- AbstractDynamoDBRepository,
317
- QueryAllSchema,
318
- QueryWithPaginationSchema,
319
- QueryWithSortingSchema,
320
- ScanFilterSchema,
321
- TableKeychema,
322
- TableSpecSchema
323
- });
1
+ 'use strict';var clientDynamodb=require('@aws-sdk/client-dynamodb'),libDynamodb=require('@aws-sdk/lib-dynamodb'),debug=require('@opble/debug'),entity=require('@opble/entity'),zod=require('zod');var h=debug.createDebug("opble:repository-dynamodb"),d="asc",b=50;function K(){return Math.floor(Date.now()/1e3)}var f=class{constructor(t,e){this.table=t;this.factory=e;this.client=libDynamodb.DynamoDBDocumentClient.from(new clientDynamodb.DynamoDBClient({}));}client;getKey(t){return {[this.table.keys.hashKey]:t.hashKey,...this.table.keys.sortKey?{[this.table.keys.sortKey]:t.sortKey}:{}}}async query(t,e,o,p){let y,i=1,r=[],m=s=>e?.excludeSortKey&&this.table.keys.sortKey?s.filter(u=>u[this.table.keys.sortKey]!==e.excludeSortKey):s;do{let s=new libDynamodb.QueryCommand({TableName:this.table.name,KeyConditionExpression:`#${this.table.keys.hashKey} = :hashKeyValue`,ExpressionAttributeNames:{[`#${this.table.keys.hashKey}`]:this.table.keys.hashKey},ExpressionAttributeValues:{":hashKeyValue":t},ExclusiveStartKey:y});if(o&&(s.input.Limit=o.limit),p&&(s.input.ScanIndexForward=p.sortOrder==="asc"),e){e.index&&(s.input.IndexName=e.index.name,s.input.KeyConditionExpression=`#${e.index.keys.hashKey} = :hashKeyValue`,s.input.ExpressionAttributeNames={[`#${e.index.keys.hashKey}`]:e.index.keys.hashKey});let l=e.index?.keys.sortKey??this.table.keys.sortKey;e.sortKeyBeginsWith&&l?(s.input.KeyConditionExpression=`${s.input.KeyConditionExpression} AND begins_with(#${l}, :sortKeyBeginsWith)`,s.input.ExpressionAttributeNames??={},s.input.ExpressionAttributeNames[`#${l}`]=l,s.input.ExpressionAttributeValues??={},s.input.ExpressionAttributeValues[":sortKeyBeginsWith"]=e.sortKeyBeginsWith):e.sortKey&&l?(s.input.KeyConditionExpression=`${s.input.KeyConditionExpression} AND #${l} = :sortKeyValue`,s.input.ExpressionAttributeNames??={},s.input.ExpressionAttributeNames[`#${l}`]=l,s.input.ExpressionAttributeValues??={},s.input.ExpressionAttributeValues[":sortKeyValue"]=e.sortKey):e.sortKeyBetween&&l&&(s.input.KeyConditionExpression=`${s.input.KeyConditionExpression} AND #${l} BETWEEN :sortKeyStart AND :sortKeyEnd`,s.input.ExpressionAttributeNames??={},s.input.ExpressionAttributeNames[`#${l}`]=l,s.input.ExpressionAttributeValues??={},s.input.ExpressionAttributeValues[":sortKeyStart"]=e.sortKeyBetween.start,s.input.ExpressionAttributeValues[":sortKeyEnd"]=e.sortKeyBetween.end);}h("command#input",s.input);let u=await this.client.send(s);if(y=u.LastEvaluatedKey,!o)r.push(...m(u.Items??[]));else if(i===o.page){r=m(u.Items??[]);break}i++;}while(y);let c=!e||!e.index;return r.map(s=>this.factory.create(s,{strict:c}))}async queryInBulk(t,e){let o=[];for(let r=0;r<t.length;r+=b)o.push(t.slice(r,r+b));let i=(await Promise.all(o.map(async r=>{let m=new libDynamodb.BatchGetCommand({RequestItems:{[this.table.name]:{Keys:r}}});return (await this.client.send(m)).Responses?.[this.table.name]??[]}))).flat().map(r=>this.factory.create(r));if(e&&e.sortBy){let r=e.sortOrder??d;i.sort((m,c)=>m[e.sortBy]<c[e.sortBy]?r===d?-1:1:m[e.sortBy]>c[e.sortBy]?r===d?1:-1:0);}return i}async scan(t,e){let o,p=1,y=[];do{let i=new libDynamodb.ScanCommand({TableName:this.table.name,ExclusiveStartKey:o});t&&(i.input.Limit=t.limit),e&&(i.input.FilterExpression=e.filterExpression,i.input.ExpressionAttributeNames=e.expressionAttributeNames,i.input.ExpressionAttributeValues=e.expressionAttributeValues);let r=await this.client.send(i);if(o=r.LastEvaluatedKey,!t)y.push(...r.Items??[]);else if(p===t.page){y=r.Items??[];break}p++;}while(o);return y.map(i=>this.factory.create(i))}async deleteAll(t){let o=(await this.query(t)).map(p=>this.table.keys.sortKey?{[this.table.keys.hashKey]:t,[this.table.keys.sortKey]:p[this.table.keys.sortKey]}:{[this.table.keys.hashKey]:t});await this.deleteInBulk(o);}async deleteInBulk(t){let e=[];for(;t.length>0;)e.push(t.splice(0,b));for(let o of e){let p=o.map(i=>({DeleteRequest:{Key:i}})),y=new libDynamodb.BatchWriteCommand({RequestItems:{[this.table.name]:p}});try{let i=await this.client.send(y);if(h("[INFO] Batch delete response:",i),i.UnprocessedItems&&i.UnprocessedItems[this.table.name]){h("[WARN] Unprocessed items found. Retrying...");let r=i.UnprocessedItems[this.table.name];if(r===void 0)continue;let m=r.map(c=>c.DeleteRequest?.Key).filter(c=>c!=null);await this.deleteInBulk(m);}}catch(i){h("[ERROR] Error deleting items in batch:",i);}}}async get(t){let e=new libDynamodb.GetCommand({TableName:this.table.name,Key:this.getKey(t)}),o=await this.client.send(e);return o.Item?this.factory.create(o.Item):null}beforeSave(t){return entity.isTimestamps(t)?t.updatedAt=K():entity.isTimestamp(t)&&(t.timestamp=K()),t}async save(t){t=this.beforeSave(t);let e=new libDynamodb.PutCommand({TableName:this.table.name,Item:{...t}});return await this.client.send(e),t}async delete(t){await this.client.send(new libDynamodb.DeleteCommand({TableName:this.table.name,Key:this.getKey(t)}));}};var $=zod.z.object({hashKey:zod.z.string(),sortKey:zod.z.union([zod.z.string(),zod.z.number()]).optional()}),R=zod.z.object({name:zod.z.string(),keys:zod.z.object({hashKey:zod.z.string(),sortKey:zod.z.string().optional()})}),F=zod.z.object({page:zod.z.string().optional().transform(a=>a?parseInt(a,10):1).refine(a=>a>=1,{message:"Page must be at least 1"}),limit:zod.z.string().optional().transform(a=>a?parseInt(a,10):25).refine(a=>a>=5&&a<=100,{message:"Limit must be between 5 and 100"})}),O=zod.z.object({sortBy:zod.z.string().optional(),sortOrder:zod.z.enum(["asc","desc"]).optional().transform(a=>a??"asc")}),j=zod.z.object({excludeSortKey:zod.z.string().optional(),sortKeyBetween:zod.z.object({start:zod.z.string(),end:zod.z.string()}).optional(),sortKeyBeginsWith:zod.z.string().optional(),sortKey:zod.z.any().optional(),index:R.optional()}),z=zod.z.object({filterExpression:zod.z.string(),expressionAttributeNames:zod.z.record(zod.z.string(),zod.z.string()),expressionAttributeValues:zod.z.record(zod.z.string(),zod.z.any())});exports.AbstractDynamoDBRepository=f;exports.QueryAllSchema=j;exports.QueryWithPaginationSchema=F;exports.QueryWithSortingSchema=O;exports.ScanFilterSchema=z;exports.TableKeychema=$;exports.TableSpecSchema=R;//# sourceMappingURL=index.cjs.map
324
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/repository.ts","../src/types.ts"],"sourcesContent":["export * from './repository';\nexport * from './types';\n","import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport {\n BatchGetCommand,\n BatchWriteCommand,\n DeleteCommand,\n DynamoDBDocumentClient,\n GetCommand,\n PutCommand,\n QueryCommand,\n ScanCommand,\n} from '@aws-sdk/lib-dynamodb';\nimport { createDebug } from '@opble/debug';\nimport { Factory, HashMap } from '@opble/types';\nimport { isTimestamp, isTimestamps } from '@opble/entity';\nimport {\n DynamoDBRepository,\n QueryAll,\n QueryWithPagination,\n QueryWithSorting,\n ScanFilter,\n TableKey,\n TableSpec,\n} from './types';\n\nconst debug = createDebug('opble:repository-dynamodb');\nconst SORT_ASCENDING = 'asc';\nconst BATCH_SIZE = 50;\n\nfunction now(): number {\n return Math.floor(Date.now() / 1000);\n}\n\nexport class AbstractDynamoDBRepository<\n T extends HashMap,\n ID extends TableKey = TableKey,\n> implements DynamoDBRepository<T, ID> {\n protected client: DynamoDBDocumentClient;\n\n constructor(\n protected table: TableSpec,\n protected factory: Factory<T>\n ) {\n this.client = DynamoDBDocumentClient.from(new DynamoDBClient({}));\n }\n\n protected getKey(id: ID): HashMap {\n return {\n [this.table.keys.hashKey]: id.hashKey,\n ...(this.table.keys.sortKey\n ? { [this.table.keys.sortKey]: id.sortKey }\n : {}),\n };\n }\n\n async query(\n hashKey: unknown,\n query?: QueryAll,\n pagination?: QueryWithPagination,\n sorting?: Pick<QueryWithSorting, 'sortOrder'>\n ): Promise<T[]> {\n /**\n * Internal state for pagination\n * - lastEvaluatedKey tells DynamoDB where to resume from\n * - currentPage helps us skip earlier pages\n * - resultItems will store the final page’s items\n */\n let lastEvaluatedKey: Record<string, unknown> | undefined;\n let currentPage = 1;\n let resultItems: HashMap[] = [];\n const filterReturnedItemsExcludeSortKey = (items: HashMap[]) => {\n /**\n * Business rule: Exclude a specific sortKey (if requested)\n * This is used to skip “parent” or “header” records within the same partition.\n */\n return query?.excludeSortKey && this.table.keys.sortKey\n ? items.filter(\n (item) => item[this.table.keys.sortKey!] !== query.excludeSortKey\n )\n : items;\n };\n\n do {\n /**\n * Prepare the QueryCommand\n * - Basic condition: match all items with the given hash key\n * - Add pagination via Limit + ExclusiveStartKey\n */\n const command = new QueryCommand({\n TableName: this.table.name,\n KeyConditionExpression: `#${this.table.keys.hashKey} = :hashKeyValue`,\n ExpressionAttributeNames: {\n [`#${this.table.keys.hashKey}`]: this.table.keys.hashKey,\n },\n ExpressionAttributeValues: {\n ':hashKeyValue': hashKey,\n },\n ExclusiveStartKey: lastEvaluatedKey,\n });\n\n if (pagination) {\n command.input.Limit = pagination.limit;\n }\n if (sorting) {\n command.input.ScanIndexForward = sorting.sortOrder === 'asc';\n }\n\n /**\n * Apply optional filters if provided\n */\n if (query) {\n // If querying a secondary index\n if (query.index) {\n command.input.IndexName = query.index.name;\n command.input.KeyConditionExpression = `#${query.index.keys.hashKey} = :hashKeyValue`;\n command.input.ExpressionAttributeNames = {\n [`#${query.index.keys.hashKey}`]: query.index.keys.hashKey,\n };\n }\n\n // If we want to match only sort keys starting with a specific prefix\n const sortKey = query.index?.keys.sortKey ?? this.table.keys.sortKey;\n if (query.sortKeyBeginsWith && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND begins_with(#${sortKey}, :sortKeyBeginsWith)`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyBeginsWith'] =\n query.sortKeyBeginsWith;\n } else if (query.sortKey && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} = :sortKeyValue`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyValue'] =\n query.sortKey;\n } else if (query.sortKeyBetween && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} BETWEEN :sortKeyStart AND :sortKeyEnd`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyStart'] =\n query.sortKeyBetween.start;\n command.input.ExpressionAttributeValues[':sortKeyEnd'] =\n query.sortKeyBetween.end;\n }\n }\n debug('command#input', command.input);\n\n // Execute the query\n const result = await this.client.send(command);\n\n // Keep the pagination pointer (if DynamoDB says there’s more data)\n lastEvaluatedKey = result.LastEvaluatedKey;\n\n if (!pagination) {\n // pagination is disabled, fetch until no results found\n resultItems.push(\n ...filterReturnedItemsExcludeSortKey(result.Items ?? [])\n );\n } else if (currentPage === pagination.page) {\n /**\n * Once we reach the desired page, capture those items.\n * DynamoDB doesn’t support direct page jumps — we simulate it by looping\n * until we reach the correct page number.\n */\n\n resultItems = filterReturnedItemsExcludeSortKey(result.Items ?? []);\n break; // Stop — we’ve collected the requested page\n }\n\n currentPage++; // Move to next page and continue querying\n } while (lastEvaluatedKey);\n\n /**\n * Map each DynamoDB item into an instance of your model class (T)\n * When querrying with index, turn strict to false\n */\n const strict = !query || !query.index;\n return resultItems.map((item) => this.factory.create(item, { strict }));\n }\n\n async queryInBulk(keys: HashMap[], query?: QueryWithSorting): Promise<T[]> {\n // Split keys into chunks of BATCH_SIZE (DynamoDB BatchGet limit)\n const chunks: HashMap[][] = [];\n for (let i = 0; i < keys.length; i += BATCH_SIZE) {\n chunks.push(keys.slice(i, i + BATCH_SIZE));\n }\n\n // Execute all batch requests concurrently\n const results = await Promise.all(\n chunks.map(async (chunk) => {\n const command = new BatchGetCommand({\n RequestItems: {\n [this.table.name]: { Keys: chunk },\n },\n });\n const response = await this.client.send(command);\n return response.Responses?.[this.table.name] ?? [];\n })\n );\n\n // Merge all items from all responses\n const mergedItems = results.flat();\n\n // Convert each item to T using factory\n const items = mergedItems.map((item) => this.factory.create(item));\n\n // Sort if query.sortBy is specified\n if (query && query.sortBy) {\n const sortOrder = query.sortOrder ?? SORT_ASCENDING;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n items.sort((a: any, b: any) => {\n if (a[query.sortBy!] < b[query.sortBy!])\n return sortOrder === SORT_ASCENDING ? -1 : 1;\n if (a[query.sortBy!] > b[query.sortBy!])\n return sortOrder === SORT_ASCENDING ? 1 : -1;\n return 0;\n });\n }\n\n return items;\n }\n\n async scan(\n pagination?: QueryWithPagination,\n filter?: ScanFilter\n ): Promise<T[]> {\n /**\n * Internal state for pagination\n * - lastEvaluatedKey tells DynamoDB where to resume from\n * - currentPage helps us skip earlier pages\n * - resultItems will store the final page’s items\n */\n let lastEvaluatedKey: Record<string, unknown> | undefined;\n let currentPage = 1;\n let resultItems: HashMap[] = [];\n\n do {\n /**\n * Prepare the ScanCommand\n * - Add pagination via Limit + ExclusiveStartKey\n */\n const command = new ScanCommand({\n TableName: this.table.name,\n ExclusiveStartKey: lastEvaluatedKey,\n });\n\n if (pagination) {\n command.input.Limit = pagination.limit;\n }\n\n if (filter) {\n command.input.FilterExpression = filter.filterExpression;\n command.input.ExpressionAttributeNames =\n filter.expressionAttributeNames;\n command.input.ExpressionAttributeValues =\n filter.expressionAttributeValues;\n }\n\n /**\n * Execute the scan\n */\n const result = await this.client.send(command);\n\n // Keep the pagination pointer (if DynamoDB says there’s more data)\n lastEvaluatedKey = result.LastEvaluatedKey;\n\n if (!pagination) {\n // pagination is disabled, fetch until no results found\n resultItems.push(...(result.Items ?? []));\n } else if (currentPage === pagination.page) {\n /**\n * Once we reach the desired page, capture those items.\n * DynamoDB doesn’t support direct page jumps — we simulate it by looping\n * until we reach the correct page number.\n */\n\n resultItems = result.Items ?? [];\n break; // Stop — we’ve collected the requested page\n }\n\n currentPage++; // Move to next page and continue querying\n } while (lastEvaluatedKey);\n\n /**\n * Map each DynamoDB item into an instance of your model class (T)\n */\n return resultItems.map((item) => this.factory.create(item));\n }\n\n async deleteAll(hashKey: unknown): Promise<void> {\n const items = await this.query(hashKey);\n const keys = items.map((item) => {\n if (this.table.keys.sortKey) {\n return {\n [this.table.keys.hashKey]: hashKey,\n [this.table.keys.sortKey]:\n item[this.table.keys.sortKey as keyof typeof item],\n };\n } else {\n return {\n [this.table.keys.hashKey]: hashKey,\n };\n }\n });\n\n await this.deleteInBulk(keys);\n }\n\n async deleteInBulk(keys: HashMap[]): Promise<void> {\n const batches = [];\n while (keys.length > 0) {\n batches.push(keys.splice(0, BATCH_SIZE));\n }\n\n for (const batch of batches) {\n // Create delete requests for each item in the batch\n const deleteRequests = batch.map((key) => ({\n DeleteRequest: { Key: key },\n }));\n\n // Execute the BatchWriteCommand\n const command = new BatchWriteCommand({\n RequestItems: {\n [this.table.name]: deleteRequests,\n },\n });\n\n try {\n const response = await this.client.send(command);\n debug('[INFO] Batch delete response:', response);\n\n // Check if there are unprocessed items and retry them if necessary\n if (\n response.UnprocessedItems &&\n response.UnprocessedItems[this.table.name]\n ) {\n debug('[WARN] Unprocessed items found. Retrying...');\n const unprocessedItems = response.UnprocessedItems[this.table.name];\n if (unprocessedItems === undefined) {\n continue;\n }\n const unprocessedKeys = unprocessedItems\n .map((item) => item.DeleteRequest?.Key)\n .filter((key): key is Record<string, unknown> => key != null); // Filter out undefined keys\n\n await this.deleteInBulk(unprocessedKeys);\n }\n } catch (error) {\n debug('[ERROR] Error deleting items in batch:', error);\n }\n }\n }\n\n async get(id: ID): Promise<T | null> {\n const command = new GetCommand({\n TableName: this.table.name,\n Key: this.getKey(id),\n });\n\n const result = await this.client.send(command);\n return result.Item ? this.factory.create(result.Item) : null;\n }\n\n /**\n * Allow to manipulate the data before saving\n * @param item\n * @returns\n */\n protected beforeSave(item: T): T {\n if (isTimestamps(item)) {\n item.updatedAt = now();\n } else if (isTimestamp(item)) {\n item.timestamp = now();\n }\n\n return item;\n }\n\n async save(item: T): Promise<T> {\n item = this.beforeSave(item);\n const command = new PutCommand({\n TableName: this.table.name,\n Item: { ...item },\n });\n await this.client.send(command);\n\n return item;\n }\n\n async delete(id: ID): Promise<void | T> {\n await this.client.send(\n new DeleteCommand({\n TableName: this.table.name,\n Key: this.getKey(id),\n })\n );\n }\n}\n","import { z } from 'zod';\nimport {\n DeletableRepository,\n HashMap,\n ReadableRepository,\n SavableRepository,\n} from '@opble/types';\n\nexport const TableKeychema = z.object({\n hashKey: z.string(),\n sortKey: z.union([z.string(), z.number()]).optional(),\n});\n\nexport const TableSpecSchema = z.object({\n name: z.string(),\n keys: z.object({\n hashKey: z.string(),\n sortKey: z.string().optional(),\n }),\n});\n\nexport const QueryWithPaginationSchema = z.object({\n page: z\n .string()\n .optional()\n .transform((val) => (val ? parseInt(val, 10) : 1)) // default 1\n .refine((val) => val >= 1, { message: 'Page must be at least 1' }),\n\n limit: z\n .string()\n .optional()\n .transform((val) => (val ? parseInt(val, 10) : 25)) // default 25\n .refine((val) => val >= 5 && val <= 100, {\n message: 'Limit must be between 5 and 100',\n }),\n});\n\nexport const QueryWithSortingSchema = z.object({\n sortBy: z.string().optional(),\n sortOrder: z\n .enum(['asc', 'desc'])\n .optional()\n .transform((val) => val ?? 'asc'),\n});\n\nexport const QueryAllSchema = z.object({\n excludeSortKey: z.string().optional(),\n sortKeyBetween: z\n .object({\n start: z.string(),\n end: z.string(),\n })\n .optional(),\n sortKeyBeginsWith: z.string().optional(),\n sortKey: z.any().optional(),\n index: TableSpecSchema.optional(),\n});\n\nexport const ScanFilterSchema = z.object({\n filterExpression: z.string(),\n expressionAttributeNames: z.record(z.string(), z.string()),\n expressionAttributeValues: z.record(z.string(), z.any()),\n});\n\nexport type TableKey = z.infer<typeof TableKeychema>;\nexport type TableSpec = z.infer<typeof TableSpecSchema>;\n\nexport type QueryWithPagination = z.infer<typeof QueryWithPaginationSchema>;\nexport type QueryWithSorting = z.infer<typeof QueryWithSortingSchema>;\nexport type QueryAll = z.infer<typeof QueryAllSchema>;\nexport type ScanFilter = z.infer<typeof ScanFilterSchema>;\n\n/**\n * A generic repository interface specifically designed for DynamoDB operations.\n * It extends the basic CRUD repositories (Readable, Savable, Deletable) and adds\n * DynamoDB-specific methods for efficient querying, scanning, bulk operations,\n * and deletion patterns commonly used with DynamoDB's key structure.\n *\n * @template T - The type of the entity/document stored in DynamoDB (must be a HashMap / Record<string, unknown>)\n * @template ID - The type used to identify items. Defaults to `TableKey` (hash + optional sort key).\n */\nexport interface DynamoDBRepository<T extends HashMap, ID = TableKey>\n extends\n ReadableRepository<T, ID>,\n SavableRepository<T>,\n DeletableRepository<T, ID> {\n /**\n * Queries items using a partition key (hashKey) and optional sort key conditions.\n * This is the most common and efficient way to read data from a DynamoDB table\n * when you know the partition key.\n *\n * @param hashKey - The value of the partition key (hash key)\n * @param query - Optional advanced query conditions for sort key (begins_with, between, specific value, etc.)\n * @param pagination - Optional pagination parameters (page & limit)\n * @param sorting - Optional sort direction (only 'sortOrder' is used)\n * @returns Promise of array of matching items (T[])\n *\n * @example\n * repo.query(\"user#123\", { sortKeyBeginsWith: \"order#\" }, { limit: 20 }, { sortOrder: \"desc\" })\n */\n query(\n hashKey: unknown,\n query?: QueryAll,\n pagination?: QueryWithPagination,\n sorting?: Pick<QueryWithSorting, 'sortOrder'>\n ): Promise<T[]>;\n\n /**\n * Batch retrieves multiple items using their full composite keys (hash + sort).\n * This method uses `BatchGetItem` under the hood — much more efficient than individual gets\n * when fetching many items by primary key.\n *\n * @param keys - Array of objects containing at least `{ hashKey, sortKey? }`\n * @param query - Optional sorting preferences (rarely used in batch get)\n * @returns Promise of array of found items (in the order requested, missing items are omitted)\n *\n * @example\n * repo.queryInBulk([\n * { hashKey: \"user#abc\", sortKey: \"profile\" },\n * { hashKey: \"user#abc\", sortKey: \"settings\" }\n * ])\n */\n queryInBulk(keys: HashMap[], query?: QueryWithSorting): Promise<T[]>;\n\n /**\n * Performs a full table or index scan with optional filtering and pagination.\n * Scans are less efficient than queries — use only when you cannot use a partition key.\n *\n * @param pagination - Optional page & limit controls\n * @param filter - Optional raw filter expression (FilterExpression + attribute names/values)\n * @returns Promise of array of matching items\n *\n * @warning Scans can be expensive and slow on large tables — prefer query() when possible\n */\n scan(pagination?: QueryWithPagination, filter?: ScanFilter): Promise<T[]>;\n\n /**\n * Deletes **all items** that share the same partition key (hashKey).\n * Useful for cleaning up all records related to a specific entity (e.g. all orders of a user).\n *\n * @param hashKey - The partition key value whose items should be deleted\n * @returns Promise that resolves when deletion is complete\n *\n * @warning This operation may consume many write capacity units if the partition is large.\n * Consider using Query + BatchWriteItem in production for very large partitions.\n */\n deleteAll(hashKey: unknown): Promise<void>;\n\n /**\n * Deletes multiple items in a single BatchWriteItem call using their full keys.\n * Most efficient way to delete many known items at once.\n *\n * @param keys - Array of objects with hashKey and sortKey\n * @returns Promise that resolves when all deletions are complete\n *\n * @example\n * repo.deleteInBulk([\n * { hashKey: \"user#123\", sortKey: \"session#abc123\" },\n * { hashKey: \"user#123\", sortKey: \"session#def456\" }\n * ])\n */\n deleteInBulk(keys: HashMap[]): Promise<void>;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/repository.ts","../src/types.ts"],"names":["debug","createDebug","SORT_ASCENDING","BATCH_SIZE","now","AbstractDynamoDBRepository","table","factory","DynamoDBDocumentClient","DynamoDBClient","id","hashKey","query","pagination","sorting","lastEvaluatedKey","currentPage","resultItems","filterReturnedItemsExcludeSortKey","items","item","command","QueryCommand","sortKey","result","strict","keys","chunks","i","chunk","BatchGetCommand","sortOrder","a","b","filter","ScanCommand","batches","batch","deleteRequests","key","BatchWriteCommand","response","unprocessedItems","unprocessedKeys","error","GetCommand","isTimestamps","isTimestamp","PutCommand","DeleteCommand","TableKeychema","z","TableSpecSchema","QueryWithPaginationSchema","val","QueryWithSortingSchema","QueryAllSchema","ScanFilterSchema"],"mappings":"kMAyBA,IAAMA,CAAAA,CAAQC,iBAAAA,CAAY,2BAA2B,CAAA,CAC/CC,CAAAA,CAAiB,KAAA,CACjBC,CAAAA,CAAa,EAAA,CAEnB,SAASC,CAAAA,EAAc,CACrB,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,EAAI,CAAI,GAAI,CACrC,CAEO,IAAeC,CAAAA,CAAf,KAGgC,CAGrC,WAAA,CACYC,CAAAA,CACAC,EACV,CAFU,IAAA,CAAA,KAAA,CAAAD,CAAAA,CACA,IAAA,CAAA,OAAA,CAAAC,CAAAA,CAEV,IAAA,CAAK,MAAA,CAASC,kCAAAA,CAAuB,KAAK,IAAIC,6BAAAA,CAAe,EAAE,CAAC,EAClE,CAPU,MAAA,CASA,OAAOC,CAAAA,CAAiB,CAChC,OAAO,CACL,CAAC,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAAO,EAAGA,CAAAA,CAAG,OAAA,CAC9B,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAAA,CAChB,CAAE,CAAC,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAAO,EAAGA,CAAAA,CAAG,OAAQ,EACxC,EACN,CACF,CAEA,MAAM,KAAA,CACJC,CAAAA,CACAC,CAAAA,CACAC,EACAC,CAAAA,CACc,CAOd,IAAIC,CAAAA,CACAC,CAAAA,CAAc,CAAA,CACdC,CAAAA,CAAyB,GACvBC,CAAAA,CAAqCC,CAAAA,EAKlCP,CAAAA,EAAO,cAAA,EAAkB,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAAA,CAC5CO,EAAM,MAAA,CACHC,CAAAA,EAASA,CAAAA,CAAK,IAAA,CAAK,MAAM,IAAA,CAAK,OAAQ,CAAA,GAAMR,CAAAA,CAAM,cACrD,CAAA,CACAO,CAAAA,CAGN,EAAG,CAMD,IAAME,CAAAA,CAAU,IAAIC,wBAAAA,CAAa,CAC/B,SAAA,CAAW,IAAA,CAAK,KAAA,CAAM,IAAA,CACtB,sBAAA,CAAwB,CAAA,CAAA,EAAI,IAAA,CAAK,KAAA,CAAM,KAAK,OAAO,CAAA,gBAAA,CAAA,CACnD,wBAAA,CAA0B,CACxB,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,KAAA,CAAM,KAAK,OAAO,CAAA,CAAE,EAAG,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OACnD,CAAA,CACA,0BAA2B,CACzB,eAAA,CAAiBX,CACnB,CAAA,CACA,iBAAA,CAAmBI,CACrB,CAAC,CAAA,CAYD,GAVIF,CAAAA,GACFQ,CAAAA,CAAQ,KAAA,CAAM,KAAA,CAAQR,CAAAA,CAAW,KAAA,CAAA,CAE/BC,CAAAA,GACFO,CAAAA,CAAQ,MAAM,gBAAA,CAAmBP,CAAAA,CAAQ,SAAA,GAAc,KAAA,CAAA,CAMrDF,CAAAA,CAAO,CAELA,CAAAA,CAAM,KAAA,GACRS,EAAQ,KAAA,CAAM,SAAA,CAAYT,CAAAA,CAAM,KAAA,CAAM,KACtCS,CAAAA,CAAQ,KAAA,CAAM,sBAAA,CAAyB,CAAA,CAAA,EAAIT,EAAM,KAAA,CAAM,IAAA,CAAK,OAAO,CAAA,gBAAA,CAAA,CACnES,CAAAA,CAAQ,KAAA,CAAM,wBAAA,CAA2B,CACvC,CAAC,CAAA,CAAA,EAAIT,CAAAA,CAAM,KAAA,CAAM,IAAA,CAAK,OAAO,CAAA,CAAE,EAAGA,CAAAA,CAAM,MAAM,IAAA,CAAK,OACrD,CAAA,CAAA,CAIF,IAAMW,CAAAA,CAAUX,CAAAA,CAAM,KAAA,EAAO,IAAA,CAAK,SAAW,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAAA,CACzDA,CAAAA,CAAM,iBAAA,EAAqBW,CAAAA,EAC7BF,CAAAA,CAAQ,MAAM,sBAAA,CAAyB,CAAA,EAAGA,CAAAA,CAAQ,KAAA,CAAM,sBAAsB,CAAA,kBAAA,EAAqBE,CAAO,CAAA,qBAAA,CAAA,CAC1GF,EAAQ,KAAA,CAAM,wBAAA,GAA6B,EAAC,CAC5CA,CAAAA,CAAQ,KAAA,CAAM,wBAAA,CAAyB,CAAA,CAAA,EAAIE,CAAO,CAAA,CAAE,CAAA,CAAIA,CAAAA,CACxDF,CAAAA,CAAQ,KAAA,CAAM,yBAAA,GAA8B,EAAC,CAC7CA,EAAQ,KAAA,CAAM,yBAAA,CAA0B,oBAAoB,CAAA,CAC1DT,EAAM,iBAAA,EACCA,CAAAA,CAAM,OAAA,EAAWW,CAAAA,EAC1BF,EAAQ,KAAA,CAAM,sBAAA,CAAyB,CAAA,EAAGA,CAAAA,CAAQ,KAAA,CAAM,sBAAsB,CAAA,MAAA,EAASE,CAAO,mBAC9FF,CAAAA,CAAQ,KAAA,CAAM,wBAAA,GAA6B,EAAC,CAC5CA,CAAAA,CAAQ,KAAA,CAAM,wBAAA,CAAyB,IAAIE,CAAO,CAAA,CAAE,CAAA,CAAIA,CAAAA,CACxDF,CAAAA,CAAQ,KAAA,CAAM,yBAAA,GAA8B,GAC5CA,CAAAA,CAAQ,KAAA,CAAM,yBAAA,CAA0B,eAAe,CAAA,CACrDT,CAAAA,CAAM,OAAA,EACCA,CAAAA,CAAM,gBAAkBW,CAAAA,GACjCF,CAAAA,CAAQ,KAAA,CAAM,sBAAA,CAAyB,CAAA,EAAGA,CAAAA,CAAQ,KAAA,CAAM,sBAAsB,SAASE,CAAO,CAAA,sCAAA,CAAA,CAC9FF,CAAAA,CAAQ,KAAA,CAAM,wBAAA,GAA6B,EAAC,CAC5CA,CAAAA,CAAQ,MAAM,wBAAA,CAAyB,CAAA,CAAA,EAAIE,CAAO,CAAA,CAAE,CAAA,CAAIA,CAAAA,CACxDF,CAAAA,CAAQ,KAAA,CAAM,4BAA8B,EAAC,CAC7CA,CAAAA,CAAQ,KAAA,CAAM,yBAAA,CAA0B,eAAe,CAAA,CACrDT,CAAAA,CAAM,eAAe,KAAA,CACvBS,CAAAA,CAAQ,KAAA,CAAM,yBAAA,CAA0B,aAAa,CAAA,CACnDT,CAAAA,CAAM,cAAA,CAAe,KAE3B,CACAZ,CAAAA,CAAM,eAAA,CAAiBqB,CAAAA,CAAQ,KAAK,CAAA,CAGpC,IAAMG,CAAAA,CAAS,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAKH,CAAO,CAAA,CAK7C,GAFAN,CAAAA,CAAmBS,CAAAA,CAAO,iBAEtB,CAACX,CAAAA,CAEHI,CAAAA,CAAY,IAAA,CACV,GAAGC,CAAAA,CAAkCM,CAAAA,CAAO,KAAA,EAAS,EAAE,CACzD,CAAA,CAAA,KAAA,GACSR,CAAAA,GAAgBH,CAAAA,CAAW,IAAA,CAAM,CAO1CI,CAAAA,CAAcC,EAAkCM,CAAAA,CAAO,KAAA,EAAS,EAAE,CAAA,CAClE,KACF,CAEAR,CAAAA,GACF,OAASD,CAAAA,EAMT,IAAMU,CAAAA,CAAS,CAACb,CAAAA,EAAS,CAACA,CAAAA,CAAM,KAAA,CAChC,OAAOK,CAAAA,CAAY,GAAA,CAAKG,CAAAA,EAAS,IAAA,CAAK,QAAQ,MAAA,CAAOA,CAAAA,CAAM,CAAE,MAAA,CAAAK,CAAO,CAAC,CAAC,CACxE,CAEA,MAAM,WAAA,CAAYC,CAAAA,CAAiBd,CAAAA,CAAwC,CAEzE,IAAMe,CAAAA,CAAsB,EAAC,CAC7B,IAAA,IAASC,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAIF,EAAK,MAAA,CAAQE,CAAAA,EAAKzB,CAAAA,CACpCwB,CAAAA,CAAO,IAAA,CAAKD,CAAAA,CAAK,KAAA,CAAME,CAAAA,CAAGA,EAAIzB,CAAU,CAAC,CAAA,CAoB3C,IAAMgB,CAAAA,CAAAA,CAhBU,MAAM,OAAA,CAAQ,GAAA,CAC5BQ,EAAO,GAAA,CAAI,MAAOE,CAAAA,EAAU,CAC1B,IAAMR,CAAAA,CAAU,IAAIS,2BAAAA,CAAgB,CAClC,YAAA,CAAc,CACZ,CAAC,IAAA,CAAK,KAAA,CAAM,IAAI,EAAG,CAAE,KAAMD,CAAM,CACnC,CACF,CAAC,CAAA,CAED,OAAA,CADiB,MAAM,IAAA,CAAK,OAAO,IAAA,CAAKR,CAAO,CAAA,EAC/B,SAAA,GAAY,KAAK,KAAA,CAAM,IAAI,CAAA,EAAK,EAClD,CAAC,CACH,CAAA,EAG4B,IAAA,EAAK,CAGP,GAAA,CAAKD,CAAAA,EAAS,IAAA,CAAK,QAAQ,MAAA,CAAOA,CAAI,CAAC,CAAA,CAGjE,GAAIR,CAAAA,EAASA,CAAAA,CAAM,MAAA,CAAQ,CACzB,IAAMmB,CAAAA,CAAYnB,CAAAA,CAAM,SAAA,EAAaV,CAAAA,CAErCiB,CAAAA,CAAM,IAAA,CAAK,CAACa,EAAQC,CAAAA,GACdD,CAAAA,CAAEpB,CAAAA,CAAM,MAAO,CAAA,CAAIqB,CAAAA,CAAErB,CAAAA,CAAM,MAAO,EAC7BmB,CAAAA,GAAc7B,CAAAA,CAAiB,EAAA,CAAK,CAAA,CACzC8B,CAAAA,CAAEpB,CAAAA,CAAM,MAAO,CAAA,CAAIqB,EAAErB,CAAAA,CAAM,MAAO,CAAA,CAC7BmB,CAAAA,GAAc7B,CAAAA,CAAiB,CAAA,CAAI,EAAA,CACrC,CACR,EACH,CAEA,OAAOiB,CACT,CAEA,MAAM,IAAA,CACJN,CAAAA,CACAqB,CAAAA,CACc,CAOd,IAAInB,CAAAA,CACAC,CAAAA,CAAc,CAAA,CACdC,EAAyB,EAAC,CAE9B,EAAG,CAKD,IAAMI,CAAAA,CAAU,IAAIc,uBAAAA,CAAY,CAC9B,SAAA,CAAW,IAAA,CAAK,KAAA,CAAM,IAAA,CACtB,kBAAmBpB,CACrB,CAAC,CAAA,CAEGF,CAAAA,GACFQ,CAAAA,CAAQ,KAAA,CAAM,KAAA,CAAQR,CAAAA,CAAW,OAG/BqB,CAAAA,GACFb,CAAAA,CAAQ,KAAA,CAAM,gBAAA,CAAmBa,CAAAA,CAAO,gBAAA,CACxCb,CAAAA,CAAQ,KAAA,CAAM,yBACZa,CAAAA,CAAO,wBAAA,CACTb,CAAAA,CAAQ,KAAA,CAAM,yBAAA,CACZa,CAAAA,CAAO,yBAAA,CAAA,CAMX,IAAMV,EAAS,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAKH,CAAO,CAAA,CAK7C,GAFAN,CAAAA,CAAmBS,EAAO,gBAAA,CAEtB,CAACX,CAAAA,CAEHI,CAAAA,CAAY,IAAA,CAAK,GAAIO,CAAAA,CAAO,KAAA,EAAS,EAAG,CAAA,CAAA,KAAA,GAC/BR,CAAAA,GAAgBH,CAAAA,CAAW,IAAA,CAAM,CAO1CI,CAAAA,CAAcO,CAAAA,CAAO,OAAS,EAAC,CAC/B,KACF,CAEAR,CAAAA,GACF,CAAA,MAASD,CAAAA,EAKT,OAAOE,EAAY,GAAA,CAAKG,CAAAA,EAAS,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAOA,CAAI,CAAC,CAC5D,CAEA,MAAM,SAAA,CAAUT,CAAAA,CAAiC,CAE/C,IAAMe,CAAAA,CAAAA,CADQ,MAAM,IAAA,CAAK,MAAMf,CAAO,CAAA,EACnB,GAAA,CAAKS,CAAAA,EAClB,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAAA,CACX,CACL,CAAC,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAAO,EAAGT,CAAAA,CAC3B,CAAC,KAAK,KAAA,CAAM,IAAA,CAAK,OAAO,EACtBS,CAAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAA4B,CACrD,CAAA,CAEO,CACL,CAAC,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAAO,EAAGT,CAC7B,CAEH,CAAA,CAED,MAAM,IAAA,CAAK,YAAA,CAAae,CAAI,EAC9B,CAEA,MAAM,YAAA,CAAaA,CAAAA,CAAgC,CACjD,IAAMU,CAAAA,CAAU,EAAC,CACjB,KAAOV,EAAK,MAAA,CAAS,CAAA,EACnBU,CAAAA,CAAQ,IAAA,CAAKV,CAAAA,CAAK,MAAA,CAAO,CAAA,CAAGvB,CAAU,CAAC,CAAA,CAGzC,IAAA,IAAWkC,CAAAA,IAASD,CAAAA,CAAS,CAE3B,IAAME,CAAAA,CAAiBD,CAAAA,CAAM,IAAKE,CAAAA,GAAS,CACzC,aAAA,CAAe,CAAE,GAAA,CAAKA,CAAI,CAC5B,CAAA,CAAE,EAGIlB,CAAAA,CAAU,IAAImB,6BAAAA,CAAkB,CACpC,YAAA,CAAc,CACZ,CAAC,IAAA,CAAK,MAAM,IAAI,EAAGF,CACrB,CACF,CAAC,CAAA,CAED,GAAI,CACF,IAAMG,CAAAA,CAAW,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAKpB,CAAO,CAAA,CAI/C,GAHArB,EAAM,+BAAA,CAAiCyC,CAAQ,CAAA,CAI7CA,CAAAA,CAAS,gBAAA,EACTA,CAAAA,CAAS,gBAAA,CAAiB,IAAA,CAAK,MAAM,IAAI,CAAA,CACzC,CACAzC,CAAAA,CAAM,6CAA6C,CAAA,CACnD,IAAM0C,CAAAA,CAAmBD,CAAAA,CAAS,iBAAiB,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,CAClE,GAAIC,CAAAA,GAAqB,KAAA,CAAA,CACvB,SAEF,IAAMC,CAAAA,CAAkBD,CAAAA,CACrB,GAAA,CAAKtB,CAAAA,EAASA,CAAAA,CAAK,aAAA,EAAe,GAAG,CAAA,CACrC,OAAQmB,CAAAA,EAAwCA,CAAAA,EAAO,IAAI,CAAA,CAE9D,MAAM,IAAA,CAAK,YAAA,CAAaI,CAAe,EACzC,CACF,CAAA,MAASC,CAAAA,CAAO,CACd5C,CAAAA,CAAM,wCAAA,CAA0C4C,CAAK,EACvD,CACF,CACF,CAEA,MAAM,GAAA,CAAIlC,CAAAA,CAA2B,CACnC,IAAMW,CAAAA,CAAU,IAAIwB,sBAAAA,CAAW,CAC7B,SAAA,CAAW,IAAA,CAAK,KAAA,CAAM,IAAA,CACtB,GAAA,CAAK,IAAA,CAAK,OAAOnC,CAAE,CACrB,CAAC,CAAA,CAEKc,CAAAA,CAAS,MAAM,IAAA,CAAK,MAAA,CAAO,KAAKH,CAAO,CAAA,CAC7C,OAAOG,CAAAA,CAAO,KAAO,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAOA,CAAAA,CAAO,IAAI,CAAA,CAAI,IAC1D,CAOU,UAAA,CAAWJ,CAAAA,CAAY,CAC/B,OAAI0B,mBAAAA,CAAa1B,CAAI,CAAA,CACnBA,CAAAA,CAAK,SAAA,CAAYhB,CAAAA,EAAI,CACZ2C,kBAAAA,CAAY3B,CAAI,CAAA,GACzBA,EAAK,SAAA,CAAYhB,CAAAA,EAAI,CAAA,CAGhBgB,CACT,CAEA,MAAM,IAAA,CAAKA,CAAAA,CAAqB,CAC9BA,CAAAA,CAAO,IAAA,CAAK,UAAA,CAAWA,CAAI,CAAA,CAC3B,IAAMC,CAAAA,CAAU,IAAI2B,uBAAW,CAC7B,SAAA,CAAW,IAAA,CAAK,KAAA,CAAM,IAAA,CACtB,IAAA,CAAM,CAAE,GAAG5B,CAAK,CAClB,CAAC,CAAA,CACD,OAAA,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAKC,CAAO,EAEvBD,CACT,CAEA,MAAM,MAAA,CAAOV,CAAAA,CAA2B,CACtC,MAAM,IAAA,CAAK,OAAO,IAAA,CAChB,IAAIuC,yBAAAA,CAAc,CAChB,SAAA,CAAW,IAAA,CAAK,KAAA,CAAM,IAAA,CACtB,IAAK,IAAA,CAAK,MAAA,CAAOvC,CAAE,CACrB,CAAC,CACH,EACF,CACF,ECvYO,IAAMwC,CAAAA,CAAgBC,KAAAA,CAAE,MAAA,CAAO,CACpC,OAAA,CAASA,KAAAA,CAAE,MAAA,EAAO,CAClB,OAAA,CAASA,KAAAA,CAAE,KAAA,CAAM,CAACA,MAAE,MAAA,EAAO,CAAGA,KAAAA,CAAE,MAAA,EAAQ,CAAC,CAAA,CAAE,QAAA,EAC7C,CAAC,CAAA,CAEYC,CAAAA,CAAkBD,KAAAA,CAAE,MAAA,CAAO,CACtC,IAAA,CAAMA,KAAAA,CAAE,QAAO,CACf,IAAA,CAAMA,KAAAA,CAAE,MAAA,CAAO,CACb,OAAA,CAASA,KAAAA,CAAE,MAAA,GACX,OAAA,CAASA,KAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EACtB,CAAC,CACH,CAAC,CAAA,CAEYE,CAAAA,CAA4BF,KAAAA,CAAE,MAAA,CAAO,CAChD,IAAA,CAAMA,KAAAA,CACH,MAAA,EAAO,CACP,UAAS,CACT,SAAA,CAAWG,CAAAA,EAASA,CAAAA,CAAM,QAAA,CAASA,CAAAA,CAAK,EAAE,CAAA,CAAI,CAAE,CAAA,CAChD,MAAA,CAAQA,CAAAA,EAAQA,CAAAA,EAAO,CAAA,CAAG,CAAE,OAAA,CAAS,yBAA0B,CAAC,CAAA,CAEnE,KAAA,CAAOH,KAAAA,CACJ,MAAA,EAAO,CACP,QAAA,EAAS,CACT,SAAA,CAAWG,GAASA,CAAAA,CAAM,QAAA,CAASA,CAAAA,CAAK,EAAE,CAAA,CAAI,EAAG,CAAA,CACjD,MAAA,CAAQA,GAAQA,CAAAA,EAAO,CAAA,EAAKA,CAAAA,EAAO,GAAA,CAAK,CACvC,OAAA,CAAS,iCACX,CAAC,CACL,CAAC,CAAA,CAEYC,CAAAA,CAAyBJ,KAAAA,CAAE,MAAA,CAAO,CAC7C,MAAA,CAAQA,KAAAA,CAAE,QAAO,CAAE,QAAA,EAAS,CAC5B,SAAA,CAAWA,KAAAA,CACR,IAAA,CAAK,CAAC,KAAA,CAAO,MAAM,CAAC,CAAA,CACpB,QAAA,EAAS,CACT,UAAWG,CAAAA,EAAQA,CAAAA,EAAO,KAAK,CACpC,CAAC,CAAA,CAEYE,CAAAA,CAAiBL,KAAAA,CAAE,MAAA,CAAO,CACrC,cAAA,CAAgBA,KAAAA,CAAE,MAAA,GAAS,QAAA,EAAS,CACpC,cAAA,CAAgBA,KAAAA,CACb,MAAA,CAAO,CACN,KAAA,CAAOA,KAAAA,CAAE,QAAO,CAChB,GAAA,CAAKA,KAAAA,CAAE,MAAA,EACT,CAAC,CAAA,CACA,QAAA,GACH,iBAAA,CAAmBA,KAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS,CACvC,OAAA,CAASA,KAAAA,CAAE,KAAI,CAAE,QAAA,EAAS,CAC1B,KAAA,CAAOC,CAAAA,CAAgB,QAAA,EACzB,CAAC,EAEYK,CAAAA,CAAmBN,KAAAA,CAAE,MAAA,CAAO,CACvC,gBAAA,CAAkBA,KAAAA,CAAE,MAAA,EAAO,CAC3B,yBAA0BA,KAAAA,CAAE,MAAA,CAAOA,KAAAA,CAAE,MAAA,EAAO,CAAGA,KAAAA,CAAE,MAAA,EAAQ,EACzD,yBAAA,CAA2BA,KAAAA,CAAE,MAAA,CAAOA,KAAAA,CAAE,QAAO,CAAGA,KAAAA,CAAE,GAAA,EAAK,CACzD,CAAC","file":"index.cjs","sourcesContent":["import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport {\n BatchGetCommand,\n BatchWriteCommand,\n DeleteCommand,\n DynamoDBDocumentClient,\n GetCommand,\n PutCommand,\n QueryCommand,\n ScanCommand,\n} from '@aws-sdk/lib-dynamodb';\nimport { createDebug } from '@opble/debug';\nimport { isTimestamp, isTimestamps } from '@opble/entity';\nimport { Factory, HashMap } from '@opble/types';\n\nimport {\n DynamoDBRepository,\n QueryAll,\n QueryWithPagination,\n QueryWithSorting,\n ScanFilter,\n TableKey,\n TableSpec,\n} from './types';\n\nconst debug = createDebug('opble:repository-dynamodb');\nconst SORT_ASCENDING = 'asc';\nconst BATCH_SIZE = 50;\n\nfunction now(): number {\n return Math.floor(Date.now() / 1000);\n}\n\nexport abstract class AbstractDynamoDBRepository<\n T extends HashMap,\n ID extends TableKey = TableKey,\n> implements DynamoDBRepository<T, ID> {\n protected client: DynamoDBDocumentClient;\n\n constructor(\n protected table: TableSpec,\n protected factory: Factory<T>\n ) {\n this.client = DynamoDBDocumentClient.from(new DynamoDBClient({}));\n }\n\n protected getKey(id: ID): HashMap {\n return {\n [this.table.keys.hashKey]: id.hashKey,\n ...(this.table.keys.sortKey\n ? { [this.table.keys.sortKey]: id.sortKey }\n : {}),\n };\n }\n\n async query(\n hashKey: unknown,\n query?: QueryAll,\n pagination?: QueryWithPagination,\n sorting?: Pick<QueryWithSorting, 'sortOrder'>\n ): Promise<T[]> {\n /**\n * Internal state for pagination\n * - lastEvaluatedKey tells DynamoDB where to resume from\n * - currentPage helps us skip earlier pages\n * - resultItems will store the final page’s items\n */\n let lastEvaluatedKey: Record<string, unknown> | undefined;\n let currentPage = 1;\n let resultItems: HashMap[] = [];\n const filterReturnedItemsExcludeSortKey = (items: HashMap[]) => {\n /**\n * Business rule: Exclude a specific sortKey (if requested)\n * This is used to skip “parent” or “header” records within the same partition.\n */\n return query?.excludeSortKey && this.table.keys.sortKey\n ? items.filter(\n (item) => item[this.table.keys.sortKey!] !== query.excludeSortKey\n )\n : items;\n };\n\n do {\n /**\n * Prepare the QueryCommand\n * - Basic condition: match all items with the given hash key\n * - Add pagination via Limit + ExclusiveStartKey\n */\n const command = new QueryCommand({\n TableName: this.table.name,\n KeyConditionExpression: `#${this.table.keys.hashKey} = :hashKeyValue`,\n ExpressionAttributeNames: {\n [`#${this.table.keys.hashKey}`]: this.table.keys.hashKey,\n },\n ExpressionAttributeValues: {\n ':hashKeyValue': hashKey,\n },\n ExclusiveStartKey: lastEvaluatedKey,\n });\n\n if (pagination) {\n command.input.Limit = pagination.limit;\n }\n if (sorting) {\n command.input.ScanIndexForward = sorting.sortOrder === 'asc';\n }\n\n /**\n * Apply optional filters if provided\n */\n if (query) {\n // If querying a secondary index\n if (query.index) {\n command.input.IndexName = query.index.name;\n command.input.KeyConditionExpression = `#${query.index.keys.hashKey} = :hashKeyValue`;\n command.input.ExpressionAttributeNames = {\n [`#${query.index.keys.hashKey}`]: query.index.keys.hashKey,\n };\n }\n\n // If we want to match only sort keys starting with a specific prefix\n const sortKey = query.index?.keys.sortKey ?? this.table.keys.sortKey;\n if (query.sortKeyBeginsWith && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND begins_with(#${sortKey}, :sortKeyBeginsWith)`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyBeginsWith'] =\n query.sortKeyBeginsWith;\n } else if (query.sortKey && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} = :sortKeyValue`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyValue'] =\n query.sortKey;\n } else if (query.sortKeyBetween && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} BETWEEN :sortKeyStart AND :sortKeyEnd`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyStart'] =\n query.sortKeyBetween.start;\n command.input.ExpressionAttributeValues[':sortKeyEnd'] =\n query.sortKeyBetween.end;\n }\n }\n debug('command#input', command.input);\n\n // Execute the query\n const result = await this.client.send(command);\n\n // Keep the pagination pointer (if DynamoDB says there’s more data)\n lastEvaluatedKey = result.LastEvaluatedKey;\n\n if (!pagination) {\n // pagination is disabled, fetch until no results found\n resultItems.push(\n ...filterReturnedItemsExcludeSortKey(result.Items ?? [])\n );\n } else if (currentPage === pagination.page) {\n /**\n * Once we reach the desired page, capture those items.\n * DynamoDB doesn’t support direct page jumps — we simulate it by looping\n * until we reach the correct page number.\n */\n\n resultItems = filterReturnedItemsExcludeSortKey(result.Items ?? []);\n break; // Stop — we’ve collected the requested page\n }\n\n currentPage++; // Move to next page and continue querying\n } while (lastEvaluatedKey);\n\n /**\n * Map each DynamoDB item into an instance of your model class (T)\n * When querrying with index, turn strict to false\n */\n const strict = !query || !query.index;\n return resultItems.map((item) => this.factory.create(item, { strict }));\n }\n\n async queryInBulk(keys: HashMap[], query?: QueryWithSorting): Promise<T[]> {\n // Split keys into chunks of BATCH_SIZE (DynamoDB BatchGet limit)\n const chunks: HashMap[][] = [];\n for (let i = 0; i < keys.length; i += BATCH_SIZE) {\n chunks.push(keys.slice(i, i + BATCH_SIZE));\n }\n\n // Execute all batch requests concurrently\n const results = await Promise.all(\n chunks.map(async (chunk) => {\n const command = new BatchGetCommand({\n RequestItems: {\n [this.table.name]: { Keys: chunk },\n },\n });\n const response = await this.client.send(command);\n return response.Responses?.[this.table.name] ?? [];\n })\n );\n\n // Merge all items from all responses\n const mergedItems = results.flat();\n\n // Convert each item to T using factory\n const items = mergedItems.map((item) => this.factory.create(item));\n\n // Sort if query.sortBy is specified\n if (query && query.sortBy) {\n const sortOrder = query.sortOrder ?? SORT_ASCENDING;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n items.sort((a: any, b: any) => {\n if (a[query.sortBy!] < b[query.sortBy!])\n return sortOrder === SORT_ASCENDING ? -1 : 1;\n if (a[query.sortBy!] > b[query.sortBy!])\n return sortOrder === SORT_ASCENDING ? 1 : -1;\n return 0;\n });\n }\n\n return items;\n }\n\n async scan(\n pagination?: QueryWithPagination,\n filter?: ScanFilter\n ): Promise<T[]> {\n /**\n * Internal state for pagination\n * - lastEvaluatedKey tells DynamoDB where to resume from\n * - currentPage helps us skip earlier pages\n * - resultItems will store the final page’s items\n */\n let lastEvaluatedKey: Record<string, unknown> | undefined;\n let currentPage = 1;\n let resultItems: HashMap[] = [];\n\n do {\n /**\n * Prepare the ScanCommand\n * - Add pagination via Limit + ExclusiveStartKey\n */\n const command = new ScanCommand({\n TableName: this.table.name,\n ExclusiveStartKey: lastEvaluatedKey,\n });\n\n if (pagination) {\n command.input.Limit = pagination.limit;\n }\n\n if (filter) {\n command.input.FilterExpression = filter.filterExpression;\n command.input.ExpressionAttributeNames =\n filter.expressionAttributeNames;\n command.input.ExpressionAttributeValues =\n filter.expressionAttributeValues;\n }\n\n /**\n * Execute the scan\n */\n const result = await this.client.send(command);\n\n // Keep the pagination pointer (if DynamoDB says there’s more data)\n lastEvaluatedKey = result.LastEvaluatedKey;\n\n if (!pagination) {\n // pagination is disabled, fetch until no results found\n resultItems.push(...(result.Items ?? []));\n } else if (currentPage === pagination.page) {\n /**\n * Once we reach the desired page, capture those items.\n * DynamoDB doesn’t support direct page jumps — we simulate it by looping\n * until we reach the correct page number.\n */\n\n resultItems = result.Items ?? [];\n break; // Stop — we’ve collected the requested page\n }\n\n currentPage++; // Move to next page and continue querying\n } while (lastEvaluatedKey);\n\n /**\n * Map each DynamoDB item into an instance of your model class (T)\n */\n return resultItems.map((item) => this.factory.create(item));\n }\n\n async deleteAll(hashKey: unknown): Promise<void> {\n const items = await this.query(hashKey);\n const keys = items.map((item) => {\n if (this.table.keys.sortKey) {\n return {\n [this.table.keys.hashKey]: hashKey,\n [this.table.keys.sortKey]:\n item[this.table.keys.sortKey as keyof typeof item],\n };\n } else {\n return {\n [this.table.keys.hashKey]: hashKey,\n };\n }\n });\n\n await this.deleteInBulk(keys);\n }\n\n async deleteInBulk(keys: HashMap[]): Promise<void> {\n const batches = [];\n while (keys.length > 0) {\n batches.push(keys.splice(0, BATCH_SIZE));\n }\n\n for (const batch of batches) {\n // Create delete requests for each item in the batch\n const deleteRequests = batch.map((key) => ({\n DeleteRequest: { Key: key },\n }));\n\n // Execute the BatchWriteCommand\n const command = new BatchWriteCommand({\n RequestItems: {\n [this.table.name]: deleteRequests,\n },\n });\n\n try {\n const response = await this.client.send(command);\n debug('[INFO] Batch delete response:', response);\n\n // Check if there are unprocessed items and retry them if necessary\n if (\n response.UnprocessedItems &&\n response.UnprocessedItems[this.table.name]\n ) {\n debug('[WARN] Unprocessed items found. Retrying...');\n const unprocessedItems = response.UnprocessedItems[this.table.name];\n if (unprocessedItems === undefined) {\n continue;\n }\n const unprocessedKeys = unprocessedItems\n .map((item) => item.DeleteRequest?.Key)\n .filter((key): key is Record<string, unknown> => key != null); // Filter out undefined keys\n\n await this.deleteInBulk(unprocessedKeys);\n }\n } catch (error) {\n debug('[ERROR] Error deleting items in batch:', error);\n }\n }\n }\n\n async get(id: ID): Promise<T | null> {\n const command = new GetCommand({\n TableName: this.table.name,\n Key: this.getKey(id),\n });\n\n const result = await this.client.send(command);\n return result.Item ? this.factory.create(result.Item) : null;\n }\n\n /**\n * Allow to manipulate the data before saving\n * @param item\n * @returns\n */\n protected beforeSave(item: T): T {\n if (isTimestamps(item)) {\n item.updatedAt = now();\n } else if (isTimestamp(item)) {\n item.timestamp = now();\n }\n\n return item;\n }\n\n async save(item: T): Promise<T> {\n item = this.beforeSave(item);\n const command = new PutCommand({\n TableName: this.table.name,\n Item: { ...item },\n });\n await this.client.send(command);\n\n return item;\n }\n\n async delete(id: ID): Promise<void | T> {\n await this.client.send(\n new DeleteCommand({\n TableName: this.table.name,\n Key: this.getKey(id),\n })\n );\n }\n}\n","import {\n DeletableRepository,\n HashMap,\n ReadableRepository,\n SavableRepository,\n} from '@opble/types';\nimport { z } from 'zod';\n\nexport const TableKeychema = z.object({\n hashKey: z.string(),\n sortKey: z.union([z.string(), z.number()]).optional(),\n});\n\nexport const TableSpecSchema = z.object({\n name: z.string(),\n keys: z.object({\n hashKey: z.string(),\n sortKey: z.string().optional(),\n }),\n});\n\nexport const QueryWithPaginationSchema = z.object({\n page: z\n .string()\n .optional()\n .transform((val) => (val ? parseInt(val, 10) : 1)) // default 1\n .refine((val) => val >= 1, { message: 'Page must be at least 1' }),\n\n limit: z\n .string()\n .optional()\n .transform((val) => (val ? parseInt(val, 10) : 25)) // default 25\n .refine((val) => val >= 5 && val <= 100, {\n message: 'Limit must be between 5 and 100',\n }),\n});\n\nexport const QueryWithSortingSchema = z.object({\n sortBy: z.string().optional(),\n sortOrder: z\n .enum(['asc', 'desc'])\n .optional()\n .transform((val) => val ?? 'asc'),\n});\n\nexport const QueryAllSchema = z.object({\n excludeSortKey: z.string().optional(),\n sortKeyBetween: z\n .object({\n start: z.string(),\n end: z.string(),\n })\n .optional(),\n sortKeyBeginsWith: z.string().optional(),\n sortKey: z.any().optional(),\n index: TableSpecSchema.optional(),\n});\n\nexport const ScanFilterSchema = z.object({\n filterExpression: z.string(),\n expressionAttributeNames: z.record(z.string(), z.string()),\n expressionAttributeValues: z.record(z.string(), z.any()),\n});\n\nexport type TableKey = z.infer<typeof TableKeychema>;\nexport type TableSpec = z.infer<typeof TableSpecSchema>;\n\nexport type QueryWithPagination = z.infer<typeof QueryWithPaginationSchema>;\nexport type QueryWithSorting = z.infer<typeof QueryWithSortingSchema>;\nexport type QueryAll = z.infer<typeof QueryAllSchema>;\nexport type ScanFilter = z.infer<typeof ScanFilterSchema>;\n\n/**\n * A generic repository interface specifically designed for DynamoDB operations.\n * It extends the basic CRUD repositories (Readable, Savable, Deletable) and adds\n * DynamoDB-specific methods for efficient querying, scanning, bulk operations,\n * and deletion patterns commonly used with DynamoDB's key structure.\n *\n * @template T - The type of the entity/document stored in DynamoDB (must be a HashMap / Record<string, unknown>)\n * @template ID - The type used to identify items. Defaults to `TableKey` (hash + optional sort key).\n */\nexport interface DynamoDBRepository<T extends HashMap, ID = TableKey>\n extends\n ReadableRepository<T, ID>,\n SavableRepository<T>,\n DeletableRepository<T, ID> {\n /**\n * Queries items using a partition key (hashKey) and optional sort key conditions.\n * This is the most common and efficient way to read data from a DynamoDB table\n * when you know the partition key.\n *\n * @param hashKey - The value of the partition key (hash key)\n * @param query - Optional advanced query conditions for sort key (begins_with, between, specific value, etc.)\n * @param pagination - Optional pagination parameters (page & limit)\n * @param sorting - Optional sort direction (only 'sortOrder' is used)\n * @returns Promise of array of matching items (T[])\n *\n * @example\n * repo.query(\"user#123\", { sortKeyBeginsWith: \"order#\" }, { limit: 20 }, { sortOrder: \"desc\" })\n */\n query(\n hashKey: unknown,\n query?: QueryAll,\n pagination?: QueryWithPagination,\n sorting?: Pick<QueryWithSorting, 'sortOrder'>\n ): Promise<T[]>;\n\n /**\n * Batch retrieves multiple items using their full composite keys (hash + sort).\n * This method uses `BatchGetItem` under the hood — much more efficient than individual gets\n * when fetching many items by primary key.\n *\n * @param keys - Array of objects containing at least `{ hashKey, sortKey? }`\n * @param query - Optional sorting preferences (rarely used in batch get)\n * @returns Promise of array of found items (in the order requested, missing items are omitted)\n *\n * @example\n * repo.queryInBulk([\n * { hashKey: \"user#abc\", sortKey: \"profile\" },\n * { hashKey: \"user#abc\", sortKey: \"settings\" }\n * ])\n */\n queryInBulk(keys: HashMap[], query?: QueryWithSorting): Promise<T[]>;\n\n /**\n * Performs a full table or index scan with optional filtering and pagination.\n * Scans are less efficient than queries — use only when you cannot use a partition key.\n *\n * @param pagination - Optional page & limit controls\n * @param filter - Optional raw filter expression (FilterExpression + attribute names/values)\n * @returns Promise of array of matching items\n *\n * @warning Scans can be expensive and slow on large tables — prefer query() when possible\n */\n scan(pagination?: QueryWithPagination, filter?: ScanFilter): Promise<T[]>;\n\n /**\n * Deletes **all items** that share the same partition key (hashKey).\n * Useful for cleaning up all records related to a specific entity (e.g. all orders of a user).\n *\n * @param hashKey - The partition key value whose items should be deleted\n * @returns Promise that resolves when deletion is complete\n *\n * @warning This operation may consume many write capacity units if the partition is large.\n * Consider using Query + BatchWriteItem in production for very large partitions.\n */\n deleteAll(hashKey: unknown): Promise<void>;\n\n /**\n * Deletes multiple items in a single BatchWriteItem call using their full keys.\n * Most efficient way to delete many known items at once.\n *\n * @param keys - Array of objects with hashKey and sortKey\n * @returns Promise that resolves when all deletions are complete\n *\n * @example\n * repo.deleteInBulk([\n * { hashKey: \"user#123\", sortKey: \"session#abc123\" },\n * { hashKey: \"user#123\", sortKey: \"session#def456\" }\n * ])\n */\n deleteInBulk(keys: HashMap[]): Promise<void>;\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -130,7 +130,7 @@ interface DynamoDBRepository<T extends HashMap, ID = TableKey> extends ReadableR
130
130
  deleteInBulk(keys: HashMap[]): Promise<void>;
131
131
  }
132
132
 
133
- declare class AbstractDynamoDBRepository<T extends HashMap, ID extends TableKey = TableKey> implements DynamoDBRepository<T, ID> {
133
+ declare abstract class AbstractDynamoDBRepository<T extends HashMap, ID extends TableKey = TableKey> implements DynamoDBRepository<T, ID> {
134
134
  protected table: TableSpec;
135
135
  protected factory: Factory<T>;
136
136
  protected client: DynamoDBDocumentClient;
package/dist/index.d.ts CHANGED
@@ -130,7 +130,7 @@ interface DynamoDBRepository<T extends HashMap, ID = TableKey> extends ReadableR
130
130
  deleteInBulk(keys: HashMap[]): Promise<void>;
131
131
  }
132
132
 
133
- declare class AbstractDynamoDBRepository<T extends HashMap, ID extends TableKey = TableKey> implements DynamoDBRepository<T, ID> {
133
+ declare abstract class AbstractDynamoDBRepository<T extends HashMap, ID extends TableKey = TableKey> implements DynamoDBRepository<T, ID> {
134
134
  protected table: TableSpec;
135
135
  protected factory: Factory<T>;
136
136
  protected client: DynamoDBDocumentClient;
package/dist/index.js CHANGED
@@ -1,300 +1,2 @@
1
- // src/repository.ts
2
- import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
3
- import {
4
- BatchGetCommand,
5
- BatchWriteCommand,
6
- DeleteCommand,
7
- DynamoDBDocumentClient,
8
- GetCommand,
9
- PutCommand,
10
- QueryCommand,
11
- ScanCommand
12
- } from "@aws-sdk/lib-dynamodb";
13
- import { createDebug } from "@opble/debug";
14
- import { isTimestamp, isTimestamps } from "@opble/entity";
15
- var debug = createDebug("opble:repository-dynamodb");
16
- var SORT_ASCENDING = "asc";
17
- var BATCH_SIZE = 50;
18
- function now() {
19
- return Math.floor(Date.now() / 1e3);
20
- }
21
- var AbstractDynamoDBRepository = class {
22
- constructor(table, factory) {
23
- this.table = table;
24
- this.factory = factory;
25
- this.client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
26
- }
27
- client;
28
- getKey(id) {
29
- return {
30
- [this.table.keys.hashKey]: id.hashKey,
31
- ...this.table.keys.sortKey ? { [this.table.keys.sortKey]: id.sortKey } : {}
32
- };
33
- }
34
- async query(hashKey, query, pagination, sorting) {
35
- let lastEvaluatedKey;
36
- let currentPage = 1;
37
- let resultItems = [];
38
- const filterReturnedItemsExcludeSortKey = (items) => {
39
- return query?.excludeSortKey && this.table.keys.sortKey ? items.filter(
40
- (item) => item[this.table.keys.sortKey] !== query.excludeSortKey
41
- ) : items;
42
- };
43
- do {
44
- const command = new QueryCommand({
45
- TableName: this.table.name,
46
- KeyConditionExpression: `#${this.table.keys.hashKey} = :hashKeyValue`,
47
- ExpressionAttributeNames: {
48
- [`#${this.table.keys.hashKey}`]: this.table.keys.hashKey
49
- },
50
- ExpressionAttributeValues: {
51
- ":hashKeyValue": hashKey
52
- },
53
- ExclusiveStartKey: lastEvaluatedKey
54
- });
55
- if (pagination) {
56
- command.input.Limit = pagination.limit;
57
- }
58
- if (sorting) {
59
- command.input.ScanIndexForward = sorting.sortOrder === "asc";
60
- }
61
- if (query) {
62
- if (query.index) {
63
- command.input.IndexName = query.index.name;
64
- command.input.KeyConditionExpression = `#${query.index.keys.hashKey} = :hashKeyValue`;
65
- command.input.ExpressionAttributeNames = {
66
- [`#${query.index.keys.hashKey}`]: query.index.keys.hashKey
67
- };
68
- }
69
- const sortKey = query.index?.keys.sortKey ?? this.table.keys.sortKey;
70
- if (query.sortKeyBeginsWith && sortKey) {
71
- command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND begins_with(#${sortKey}, :sortKeyBeginsWith)`;
72
- command.input.ExpressionAttributeNames ??= {};
73
- command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;
74
- command.input.ExpressionAttributeValues ??= {};
75
- command.input.ExpressionAttributeValues[":sortKeyBeginsWith"] = query.sortKeyBeginsWith;
76
- } else if (query.sortKey && sortKey) {
77
- command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} = :sortKeyValue`;
78
- command.input.ExpressionAttributeNames ??= {};
79
- command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;
80
- command.input.ExpressionAttributeValues ??= {};
81
- command.input.ExpressionAttributeValues[":sortKeyValue"] = query.sortKey;
82
- } else if (query.sortKeyBetween && sortKey) {
83
- command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} BETWEEN :sortKeyStart AND :sortKeyEnd`;
84
- command.input.ExpressionAttributeNames ??= {};
85
- command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;
86
- command.input.ExpressionAttributeValues ??= {};
87
- command.input.ExpressionAttributeValues[":sortKeyStart"] = query.sortKeyBetween.start;
88
- command.input.ExpressionAttributeValues[":sortKeyEnd"] = query.sortKeyBetween.end;
89
- }
90
- }
91
- debug("command#input", command.input);
92
- const result = await this.client.send(command);
93
- lastEvaluatedKey = result.LastEvaluatedKey;
94
- if (!pagination) {
95
- resultItems.push(
96
- ...filterReturnedItemsExcludeSortKey(result.Items ?? [])
97
- );
98
- } else if (currentPage === pagination.page) {
99
- resultItems = filterReturnedItemsExcludeSortKey(result.Items ?? []);
100
- break;
101
- }
102
- currentPage++;
103
- } while (lastEvaluatedKey);
104
- const strict = !query || !query.index;
105
- return resultItems.map((item) => this.factory.create(item, { strict }));
106
- }
107
- async queryInBulk(keys, query) {
108
- const chunks = [];
109
- for (let i = 0; i < keys.length; i += BATCH_SIZE) {
110
- chunks.push(keys.slice(i, i + BATCH_SIZE));
111
- }
112
- const results = await Promise.all(
113
- chunks.map(async (chunk) => {
114
- const command = new BatchGetCommand({
115
- RequestItems: {
116
- [this.table.name]: { Keys: chunk }
117
- }
118
- });
119
- const response = await this.client.send(command);
120
- return response.Responses?.[this.table.name] ?? [];
121
- })
122
- );
123
- const mergedItems = results.flat();
124
- const items = mergedItems.map((item) => this.factory.create(item));
125
- if (query && query.sortBy) {
126
- const sortOrder = query.sortOrder ?? SORT_ASCENDING;
127
- items.sort((a, b) => {
128
- if (a[query.sortBy] < b[query.sortBy])
129
- return sortOrder === SORT_ASCENDING ? -1 : 1;
130
- if (a[query.sortBy] > b[query.sortBy])
131
- return sortOrder === SORT_ASCENDING ? 1 : -1;
132
- return 0;
133
- });
134
- }
135
- return items;
136
- }
137
- async scan(pagination, filter) {
138
- let lastEvaluatedKey;
139
- let currentPage = 1;
140
- let resultItems = [];
141
- do {
142
- const command = new ScanCommand({
143
- TableName: this.table.name,
144
- ExclusiveStartKey: lastEvaluatedKey
145
- });
146
- if (pagination) {
147
- command.input.Limit = pagination.limit;
148
- }
149
- if (filter) {
150
- command.input.FilterExpression = filter.filterExpression;
151
- command.input.ExpressionAttributeNames = filter.expressionAttributeNames;
152
- command.input.ExpressionAttributeValues = filter.expressionAttributeValues;
153
- }
154
- const result = await this.client.send(command);
155
- lastEvaluatedKey = result.LastEvaluatedKey;
156
- if (!pagination) {
157
- resultItems.push(...result.Items ?? []);
158
- } else if (currentPage === pagination.page) {
159
- resultItems = result.Items ?? [];
160
- break;
161
- }
162
- currentPage++;
163
- } while (lastEvaluatedKey);
164
- return resultItems.map((item) => this.factory.create(item));
165
- }
166
- async deleteAll(hashKey) {
167
- const items = await this.query(hashKey);
168
- const keys = items.map((item) => {
169
- if (this.table.keys.sortKey) {
170
- return {
171
- [this.table.keys.hashKey]: hashKey,
172
- [this.table.keys.sortKey]: item[this.table.keys.sortKey]
173
- };
174
- } else {
175
- return {
176
- [this.table.keys.hashKey]: hashKey
177
- };
178
- }
179
- });
180
- await this.deleteInBulk(keys);
181
- }
182
- async deleteInBulk(keys) {
183
- const batches = [];
184
- while (keys.length > 0) {
185
- batches.push(keys.splice(0, BATCH_SIZE));
186
- }
187
- for (const batch of batches) {
188
- const deleteRequests = batch.map((key) => ({
189
- DeleteRequest: { Key: key }
190
- }));
191
- const command = new BatchWriteCommand({
192
- RequestItems: {
193
- [this.table.name]: deleteRequests
194
- }
195
- });
196
- try {
197
- const response = await this.client.send(command);
198
- debug("[INFO] Batch delete response:", response);
199
- if (response.UnprocessedItems && response.UnprocessedItems[this.table.name]) {
200
- debug("[WARN] Unprocessed items found. Retrying...");
201
- const unprocessedItems = response.UnprocessedItems[this.table.name];
202
- if (unprocessedItems === void 0) {
203
- continue;
204
- }
205
- const unprocessedKeys = unprocessedItems.map((item) => item.DeleteRequest?.Key).filter((key) => key != null);
206
- await this.deleteInBulk(unprocessedKeys);
207
- }
208
- } catch (error) {
209
- debug("[ERROR] Error deleting items in batch:", error);
210
- }
211
- }
212
- }
213
- async get(id) {
214
- const command = new GetCommand({
215
- TableName: this.table.name,
216
- Key: this.getKey(id)
217
- });
218
- const result = await this.client.send(command);
219
- return result.Item ? this.factory.create(result.Item) : null;
220
- }
221
- /**
222
- * Allow to manipulate the data before saving
223
- * @param item
224
- * @returns
225
- */
226
- beforeSave(item) {
227
- if (isTimestamps(item)) {
228
- item.updatedAt = now();
229
- } else if (isTimestamp(item)) {
230
- item.timestamp = now();
231
- }
232
- return item;
233
- }
234
- async save(item) {
235
- item = this.beforeSave(item);
236
- const command = new PutCommand({
237
- TableName: this.table.name,
238
- Item: { ...item }
239
- });
240
- await this.client.send(command);
241
- return item;
242
- }
243
- async delete(id) {
244
- await this.client.send(
245
- new DeleteCommand({
246
- TableName: this.table.name,
247
- Key: this.getKey(id)
248
- })
249
- );
250
- }
251
- };
252
-
253
- // src/types.ts
254
- import { z } from "zod";
255
- var TableKeychema = z.object({
256
- hashKey: z.string(),
257
- sortKey: z.union([z.string(), z.number()]).optional()
258
- });
259
- var TableSpecSchema = z.object({
260
- name: z.string(),
261
- keys: z.object({
262
- hashKey: z.string(),
263
- sortKey: z.string().optional()
264
- })
265
- });
266
- var QueryWithPaginationSchema = z.object({
267
- page: z.string().optional().transform((val) => val ? parseInt(val, 10) : 1).refine((val) => val >= 1, { message: "Page must be at least 1" }),
268
- limit: z.string().optional().transform((val) => val ? parseInt(val, 10) : 25).refine((val) => val >= 5 && val <= 100, {
269
- message: "Limit must be between 5 and 100"
270
- })
271
- });
272
- var QueryWithSortingSchema = z.object({
273
- sortBy: z.string().optional(),
274
- sortOrder: z.enum(["asc", "desc"]).optional().transform((val) => val ?? "asc")
275
- });
276
- var QueryAllSchema = z.object({
277
- excludeSortKey: z.string().optional(),
278
- sortKeyBetween: z.object({
279
- start: z.string(),
280
- end: z.string()
281
- }).optional(),
282
- sortKeyBeginsWith: z.string().optional(),
283
- sortKey: z.any().optional(),
284
- index: TableSpecSchema.optional()
285
- });
286
- var ScanFilterSchema = z.object({
287
- filterExpression: z.string(),
288
- expressionAttributeNames: z.record(z.string(), z.string()),
289
- expressionAttributeValues: z.record(z.string(), z.any())
290
- });
291
- export {
292
- AbstractDynamoDBRepository,
293
- QueryAllSchema,
294
- QueryWithPaginationSchema,
295
- QueryWithSortingSchema,
296
- ScanFilterSchema,
297
- TableKeychema,
298
- TableSpecSchema
299
- };
1
+ import {DynamoDBClient}from'@aws-sdk/client-dynamodb';import {DynamoDBDocumentClient,QueryCommand,BatchGetCommand,ScanCommand,BatchWriteCommand,GetCommand,PutCommand,DeleteCommand}from'@aws-sdk/lib-dynamodb';import {createDebug}from'@opble/debug';import {isTimestamps,isTimestamp}from'@opble/entity';import {z as z$1}from'zod';var h=createDebug("opble:repository-dynamodb"),d="asc",b=50;function K(){return Math.floor(Date.now()/1e3)}var f=class{constructor(t,e){this.table=t;this.factory=e;this.client=DynamoDBDocumentClient.from(new DynamoDBClient({}));}client;getKey(t){return {[this.table.keys.hashKey]:t.hashKey,...this.table.keys.sortKey?{[this.table.keys.sortKey]:t.sortKey}:{}}}async query(t,e,o,p){let y,i=1,r=[],m=s=>e?.excludeSortKey&&this.table.keys.sortKey?s.filter(u=>u[this.table.keys.sortKey]!==e.excludeSortKey):s;do{let s=new QueryCommand({TableName:this.table.name,KeyConditionExpression:`#${this.table.keys.hashKey} = :hashKeyValue`,ExpressionAttributeNames:{[`#${this.table.keys.hashKey}`]:this.table.keys.hashKey},ExpressionAttributeValues:{":hashKeyValue":t},ExclusiveStartKey:y});if(o&&(s.input.Limit=o.limit),p&&(s.input.ScanIndexForward=p.sortOrder==="asc"),e){e.index&&(s.input.IndexName=e.index.name,s.input.KeyConditionExpression=`#${e.index.keys.hashKey} = :hashKeyValue`,s.input.ExpressionAttributeNames={[`#${e.index.keys.hashKey}`]:e.index.keys.hashKey});let l=e.index?.keys.sortKey??this.table.keys.sortKey;e.sortKeyBeginsWith&&l?(s.input.KeyConditionExpression=`${s.input.KeyConditionExpression} AND begins_with(#${l}, :sortKeyBeginsWith)`,s.input.ExpressionAttributeNames??={},s.input.ExpressionAttributeNames[`#${l}`]=l,s.input.ExpressionAttributeValues??={},s.input.ExpressionAttributeValues[":sortKeyBeginsWith"]=e.sortKeyBeginsWith):e.sortKey&&l?(s.input.KeyConditionExpression=`${s.input.KeyConditionExpression} AND #${l} = :sortKeyValue`,s.input.ExpressionAttributeNames??={},s.input.ExpressionAttributeNames[`#${l}`]=l,s.input.ExpressionAttributeValues??={},s.input.ExpressionAttributeValues[":sortKeyValue"]=e.sortKey):e.sortKeyBetween&&l&&(s.input.KeyConditionExpression=`${s.input.KeyConditionExpression} AND #${l} BETWEEN :sortKeyStart AND :sortKeyEnd`,s.input.ExpressionAttributeNames??={},s.input.ExpressionAttributeNames[`#${l}`]=l,s.input.ExpressionAttributeValues??={},s.input.ExpressionAttributeValues[":sortKeyStart"]=e.sortKeyBetween.start,s.input.ExpressionAttributeValues[":sortKeyEnd"]=e.sortKeyBetween.end);}h("command#input",s.input);let u=await this.client.send(s);if(y=u.LastEvaluatedKey,!o)r.push(...m(u.Items??[]));else if(i===o.page){r=m(u.Items??[]);break}i++;}while(y);let c=!e||!e.index;return r.map(s=>this.factory.create(s,{strict:c}))}async queryInBulk(t,e){let o=[];for(let r=0;r<t.length;r+=b)o.push(t.slice(r,r+b));let i=(await Promise.all(o.map(async r=>{let m=new BatchGetCommand({RequestItems:{[this.table.name]:{Keys:r}}});return (await this.client.send(m)).Responses?.[this.table.name]??[]}))).flat().map(r=>this.factory.create(r));if(e&&e.sortBy){let r=e.sortOrder??d;i.sort((m,c)=>m[e.sortBy]<c[e.sortBy]?r===d?-1:1:m[e.sortBy]>c[e.sortBy]?r===d?1:-1:0);}return i}async scan(t,e){let o,p=1,y=[];do{let i=new ScanCommand({TableName:this.table.name,ExclusiveStartKey:o});t&&(i.input.Limit=t.limit),e&&(i.input.FilterExpression=e.filterExpression,i.input.ExpressionAttributeNames=e.expressionAttributeNames,i.input.ExpressionAttributeValues=e.expressionAttributeValues);let r=await this.client.send(i);if(o=r.LastEvaluatedKey,!t)y.push(...r.Items??[]);else if(p===t.page){y=r.Items??[];break}p++;}while(o);return y.map(i=>this.factory.create(i))}async deleteAll(t){let o=(await this.query(t)).map(p=>this.table.keys.sortKey?{[this.table.keys.hashKey]:t,[this.table.keys.sortKey]:p[this.table.keys.sortKey]}:{[this.table.keys.hashKey]:t});await this.deleteInBulk(o);}async deleteInBulk(t){let e=[];for(;t.length>0;)e.push(t.splice(0,b));for(let o of e){let p=o.map(i=>({DeleteRequest:{Key:i}})),y=new BatchWriteCommand({RequestItems:{[this.table.name]:p}});try{let i=await this.client.send(y);if(h("[INFO] Batch delete response:",i),i.UnprocessedItems&&i.UnprocessedItems[this.table.name]){h("[WARN] Unprocessed items found. Retrying...");let r=i.UnprocessedItems[this.table.name];if(r===void 0)continue;let m=r.map(c=>c.DeleteRequest?.Key).filter(c=>c!=null);await this.deleteInBulk(m);}}catch(i){h("[ERROR] Error deleting items in batch:",i);}}}async get(t){let e=new GetCommand({TableName:this.table.name,Key:this.getKey(t)}),o=await this.client.send(e);return o.Item?this.factory.create(o.Item):null}beforeSave(t){return isTimestamps(t)?t.updatedAt=K():isTimestamp(t)&&(t.timestamp=K()),t}async save(t){t=this.beforeSave(t);let e=new PutCommand({TableName:this.table.name,Item:{...t}});return await this.client.send(e),t}async delete(t){await this.client.send(new DeleteCommand({TableName:this.table.name,Key:this.getKey(t)}));}};var F=z$1.object({hashKey:z$1.string(),sortKey:z$1.union([z$1.string(),z$1.number()]).optional()}),W=z$1.object({name:z$1.string(),keys:z$1.object({hashKey:z$1.string(),sortKey:z$1.string().optional()})}),O=z$1.object({page:z$1.string().optional().transform(a=>a?parseInt(a,10):1).refine(a=>a>=1,{message:"Page must be at least 1"}),limit:z$1.string().optional().transform(a=>a?parseInt(a,10):25).refine(a=>a>=5&&a<=100,{message:"Limit must be between 5 and 100"})}),j=z$1.object({sortBy:z$1.string().optional(),sortOrder:z$1.enum(["asc","desc"]).optional().transform(a=>a??"asc")}),z=z$1.object({excludeSortKey:z$1.string().optional(),sortKeyBetween:z$1.object({start:z$1.string(),end:z$1.string()}).optional(),sortKeyBeginsWith:z$1.string().optional(),sortKey:z$1.any().optional(),index:W.optional()}),L=z$1.object({filterExpression:z$1.string(),expressionAttributeNames:z$1.record(z$1.string(),z$1.string()),expressionAttributeValues:z$1.record(z$1.string(),z$1.any())});export{f as AbstractDynamoDBRepository,z as QueryAllSchema,O as QueryWithPaginationSchema,j as QueryWithSortingSchema,L as ScanFilterSchema,F as TableKeychema,W as TableSpecSchema};//# sourceMappingURL=index.js.map
300
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/repository.ts","../src/types.ts"],"sourcesContent":["import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport {\n BatchGetCommand,\n BatchWriteCommand,\n DeleteCommand,\n DynamoDBDocumentClient,\n GetCommand,\n PutCommand,\n QueryCommand,\n ScanCommand,\n} from '@aws-sdk/lib-dynamodb';\nimport { createDebug } from '@opble/debug';\nimport { Factory, HashMap } from '@opble/types';\nimport { isTimestamp, isTimestamps } from '@opble/entity';\nimport {\n DynamoDBRepository,\n QueryAll,\n QueryWithPagination,\n QueryWithSorting,\n ScanFilter,\n TableKey,\n TableSpec,\n} from './types';\n\nconst debug = createDebug('opble:repository-dynamodb');\nconst SORT_ASCENDING = 'asc';\nconst BATCH_SIZE = 50;\n\nfunction now(): number {\n return Math.floor(Date.now() / 1000);\n}\n\nexport class AbstractDynamoDBRepository<\n T extends HashMap,\n ID extends TableKey = TableKey,\n> implements DynamoDBRepository<T, ID> {\n protected client: DynamoDBDocumentClient;\n\n constructor(\n protected table: TableSpec,\n protected factory: Factory<T>\n ) {\n this.client = DynamoDBDocumentClient.from(new DynamoDBClient({}));\n }\n\n protected getKey(id: ID): HashMap {\n return {\n [this.table.keys.hashKey]: id.hashKey,\n ...(this.table.keys.sortKey\n ? { [this.table.keys.sortKey]: id.sortKey }\n : {}),\n };\n }\n\n async query(\n hashKey: unknown,\n query?: QueryAll,\n pagination?: QueryWithPagination,\n sorting?: Pick<QueryWithSorting, 'sortOrder'>\n ): Promise<T[]> {\n /**\n * Internal state for pagination\n * - lastEvaluatedKey tells DynamoDB where to resume from\n * - currentPage helps us skip earlier pages\n * - resultItems will store the final page’s items\n */\n let lastEvaluatedKey: Record<string, unknown> | undefined;\n let currentPage = 1;\n let resultItems: HashMap[] = [];\n const filterReturnedItemsExcludeSortKey = (items: HashMap[]) => {\n /**\n * Business rule: Exclude a specific sortKey (if requested)\n * This is used to skip “parent” or “header” records within the same partition.\n */\n return query?.excludeSortKey && this.table.keys.sortKey\n ? items.filter(\n (item) => item[this.table.keys.sortKey!] !== query.excludeSortKey\n )\n : items;\n };\n\n do {\n /**\n * Prepare the QueryCommand\n * - Basic condition: match all items with the given hash key\n * - Add pagination via Limit + ExclusiveStartKey\n */\n const command = new QueryCommand({\n TableName: this.table.name,\n KeyConditionExpression: `#${this.table.keys.hashKey} = :hashKeyValue`,\n ExpressionAttributeNames: {\n [`#${this.table.keys.hashKey}`]: this.table.keys.hashKey,\n },\n ExpressionAttributeValues: {\n ':hashKeyValue': hashKey,\n },\n ExclusiveStartKey: lastEvaluatedKey,\n });\n\n if (pagination) {\n command.input.Limit = pagination.limit;\n }\n if (sorting) {\n command.input.ScanIndexForward = sorting.sortOrder === 'asc';\n }\n\n /**\n * Apply optional filters if provided\n */\n if (query) {\n // If querying a secondary index\n if (query.index) {\n command.input.IndexName = query.index.name;\n command.input.KeyConditionExpression = `#${query.index.keys.hashKey} = :hashKeyValue`;\n command.input.ExpressionAttributeNames = {\n [`#${query.index.keys.hashKey}`]: query.index.keys.hashKey,\n };\n }\n\n // If we want to match only sort keys starting with a specific prefix\n const sortKey = query.index?.keys.sortKey ?? this.table.keys.sortKey;\n if (query.sortKeyBeginsWith && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND begins_with(#${sortKey}, :sortKeyBeginsWith)`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyBeginsWith'] =\n query.sortKeyBeginsWith;\n } else if (query.sortKey && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} = :sortKeyValue`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyValue'] =\n query.sortKey;\n } else if (query.sortKeyBetween && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} BETWEEN :sortKeyStart AND :sortKeyEnd`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyStart'] =\n query.sortKeyBetween.start;\n command.input.ExpressionAttributeValues[':sortKeyEnd'] =\n query.sortKeyBetween.end;\n }\n }\n debug('command#input', command.input);\n\n // Execute the query\n const result = await this.client.send(command);\n\n // Keep the pagination pointer (if DynamoDB says there’s more data)\n lastEvaluatedKey = result.LastEvaluatedKey;\n\n if (!pagination) {\n // pagination is disabled, fetch until no results found\n resultItems.push(\n ...filterReturnedItemsExcludeSortKey(result.Items ?? [])\n );\n } else if (currentPage === pagination.page) {\n /**\n * Once we reach the desired page, capture those items.\n * DynamoDB doesn’t support direct page jumps — we simulate it by looping\n * until we reach the correct page number.\n */\n\n resultItems = filterReturnedItemsExcludeSortKey(result.Items ?? []);\n break; // Stop — we’ve collected the requested page\n }\n\n currentPage++; // Move to next page and continue querying\n } while (lastEvaluatedKey);\n\n /**\n * Map each DynamoDB item into an instance of your model class (T)\n * When querrying with index, turn strict to false\n */\n const strict = !query || !query.index;\n return resultItems.map((item) => this.factory.create(item, { strict }));\n }\n\n async queryInBulk(keys: HashMap[], query?: QueryWithSorting): Promise<T[]> {\n // Split keys into chunks of BATCH_SIZE (DynamoDB BatchGet limit)\n const chunks: HashMap[][] = [];\n for (let i = 0; i < keys.length; i += BATCH_SIZE) {\n chunks.push(keys.slice(i, i + BATCH_SIZE));\n }\n\n // Execute all batch requests concurrently\n const results = await Promise.all(\n chunks.map(async (chunk) => {\n const command = new BatchGetCommand({\n RequestItems: {\n [this.table.name]: { Keys: chunk },\n },\n });\n const response = await this.client.send(command);\n return response.Responses?.[this.table.name] ?? [];\n })\n );\n\n // Merge all items from all responses\n const mergedItems = results.flat();\n\n // Convert each item to T using factory\n const items = mergedItems.map((item) => this.factory.create(item));\n\n // Sort if query.sortBy is specified\n if (query && query.sortBy) {\n const sortOrder = query.sortOrder ?? SORT_ASCENDING;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n items.sort((a: any, b: any) => {\n if (a[query.sortBy!] < b[query.sortBy!])\n return sortOrder === SORT_ASCENDING ? -1 : 1;\n if (a[query.sortBy!] > b[query.sortBy!])\n return sortOrder === SORT_ASCENDING ? 1 : -1;\n return 0;\n });\n }\n\n return items;\n }\n\n async scan(\n pagination?: QueryWithPagination,\n filter?: ScanFilter\n ): Promise<T[]> {\n /**\n * Internal state for pagination\n * - lastEvaluatedKey tells DynamoDB where to resume from\n * - currentPage helps us skip earlier pages\n * - resultItems will store the final page’s items\n */\n let lastEvaluatedKey: Record<string, unknown> | undefined;\n let currentPage = 1;\n let resultItems: HashMap[] = [];\n\n do {\n /**\n * Prepare the ScanCommand\n * - Add pagination via Limit + ExclusiveStartKey\n */\n const command = new ScanCommand({\n TableName: this.table.name,\n ExclusiveStartKey: lastEvaluatedKey,\n });\n\n if (pagination) {\n command.input.Limit = pagination.limit;\n }\n\n if (filter) {\n command.input.FilterExpression = filter.filterExpression;\n command.input.ExpressionAttributeNames =\n filter.expressionAttributeNames;\n command.input.ExpressionAttributeValues =\n filter.expressionAttributeValues;\n }\n\n /**\n * Execute the scan\n */\n const result = await this.client.send(command);\n\n // Keep the pagination pointer (if DynamoDB says there’s more data)\n lastEvaluatedKey = result.LastEvaluatedKey;\n\n if (!pagination) {\n // pagination is disabled, fetch until no results found\n resultItems.push(...(result.Items ?? []));\n } else if (currentPage === pagination.page) {\n /**\n * Once we reach the desired page, capture those items.\n * DynamoDB doesn’t support direct page jumps — we simulate it by looping\n * until we reach the correct page number.\n */\n\n resultItems = result.Items ?? [];\n break; // Stop — we’ve collected the requested page\n }\n\n currentPage++; // Move to next page and continue querying\n } while (lastEvaluatedKey);\n\n /**\n * Map each DynamoDB item into an instance of your model class (T)\n */\n return resultItems.map((item) => this.factory.create(item));\n }\n\n async deleteAll(hashKey: unknown): Promise<void> {\n const items = await this.query(hashKey);\n const keys = items.map((item) => {\n if (this.table.keys.sortKey) {\n return {\n [this.table.keys.hashKey]: hashKey,\n [this.table.keys.sortKey]:\n item[this.table.keys.sortKey as keyof typeof item],\n };\n } else {\n return {\n [this.table.keys.hashKey]: hashKey,\n };\n }\n });\n\n await this.deleteInBulk(keys);\n }\n\n async deleteInBulk(keys: HashMap[]): Promise<void> {\n const batches = [];\n while (keys.length > 0) {\n batches.push(keys.splice(0, BATCH_SIZE));\n }\n\n for (const batch of batches) {\n // Create delete requests for each item in the batch\n const deleteRequests = batch.map((key) => ({\n DeleteRequest: { Key: key },\n }));\n\n // Execute the BatchWriteCommand\n const command = new BatchWriteCommand({\n RequestItems: {\n [this.table.name]: deleteRequests,\n },\n });\n\n try {\n const response = await this.client.send(command);\n debug('[INFO] Batch delete response:', response);\n\n // Check if there are unprocessed items and retry them if necessary\n if (\n response.UnprocessedItems &&\n response.UnprocessedItems[this.table.name]\n ) {\n debug('[WARN] Unprocessed items found. Retrying...');\n const unprocessedItems = response.UnprocessedItems[this.table.name];\n if (unprocessedItems === undefined) {\n continue;\n }\n const unprocessedKeys = unprocessedItems\n .map((item) => item.DeleteRequest?.Key)\n .filter((key): key is Record<string, unknown> => key != null); // Filter out undefined keys\n\n await this.deleteInBulk(unprocessedKeys);\n }\n } catch (error) {\n debug('[ERROR] Error deleting items in batch:', error);\n }\n }\n }\n\n async get(id: ID): Promise<T | null> {\n const command = new GetCommand({\n TableName: this.table.name,\n Key: this.getKey(id),\n });\n\n const result = await this.client.send(command);\n return result.Item ? this.factory.create(result.Item) : null;\n }\n\n /**\n * Allow to manipulate the data before saving\n * @param item\n * @returns\n */\n protected beforeSave(item: T): T {\n if (isTimestamps(item)) {\n item.updatedAt = now();\n } else if (isTimestamp(item)) {\n item.timestamp = now();\n }\n\n return item;\n }\n\n async save(item: T): Promise<T> {\n item = this.beforeSave(item);\n const command = new PutCommand({\n TableName: this.table.name,\n Item: { ...item },\n });\n await this.client.send(command);\n\n return item;\n }\n\n async delete(id: ID): Promise<void | T> {\n await this.client.send(\n new DeleteCommand({\n TableName: this.table.name,\n Key: this.getKey(id),\n })\n );\n }\n}\n","import { z } from 'zod';\nimport {\n DeletableRepository,\n HashMap,\n ReadableRepository,\n SavableRepository,\n} from '@opble/types';\n\nexport const TableKeychema = z.object({\n hashKey: z.string(),\n sortKey: z.union([z.string(), z.number()]).optional(),\n});\n\nexport const TableSpecSchema = z.object({\n name: z.string(),\n keys: z.object({\n hashKey: z.string(),\n sortKey: z.string().optional(),\n }),\n});\n\nexport const QueryWithPaginationSchema = z.object({\n page: z\n .string()\n .optional()\n .transform((val) => (val ? parseInt(val, 10) : 1)) // default 1\n .refine((val) => val >= 1, { message: 'Page must be at least 1' }),\n\n limit: z\n .string()\n .optional()\n .transform((val) => (val ? parseInt(val, 10) : 25)) // default 25\n .refine((val) => val >= 5 && val <= 100, {\n message: 'Limit must be between 5 and 100',\n }),\n});\n\nexport const QueryWithSortingSchema = z.object({\n sortBy: z.string().optional(),\n sortOrder: z\n .enum(['asc', 'desc'])\n .optional()\n .transform((val) => val ?? 'asc'),\n});\n\nexport const QueryAllSchema = z.object({\n excludeSortKey: z.string().optional(),\n sortKeyBetween: z\n .object({\n start: z.string(),\n end: z.string(),\n })\n .optional(),\n sortKeyBeginsWith: z.string().optional(),\n sortKey: z.any().optional(),\n index: TableSpecSchema.optional(),\n});\n\nexport const ScanFilterSchema = z.object({\n filterExpression: z.string(),\n expressionAttributeNames: z.record(z.string(), z.string()),\n expressionAttributeValues: z.record(z.string(), z.any()),\n});\n\nexport type TableKey = z.infer<typeof TableKeychema>;\nexport type TableSpec = z.infer<typeof TableSpecSchema>;\n\nexport type QueryWithPagination = z.infer<typeof QueryWithPaginationSchema>;\nexport type QueryWithSorting = z.infer<typeof QueryWithSortingSchema>;\nexport type QueryAll = z.infer<typeof QueryAllSchema>;\nexport type ScanFilter = z.infer<typeof ScanFilterSchema>;\n\n/**\n * A generic repository interface specifically designed for DynamoDB operations.\n * It extends the basic CRUD repositories (Readable, Savable, Deletable) and adds\n * DynamoDB-specific methods for efficient querying, scanning, bulk operations,\n * and deletion patterns commonly used with DynamoDB's key structure.\n *\n * @template T - The type of the entity/document stored in DynamoDB (must be a HashMap / Record<string, unknown>)\n * @template ID - The type used to identify items. Defaults to `TableKey` (hash + optional sort key).\n */\nexport interface DynamoDBRepository<T extends HashMap, ID = TableKey>\n extends\n ReadableRepository<T, ID>,\n SavableRepository<T>,\n DeletableRepository<T, ID> {\n /**\n * Queries items using a partition key (hashKey) and optional sort key conditions.\n * This is the most common and efficient way to read data from a DynamoDB table\n * when you know the partition key.\n *\n * @param hashKey - The value of the partition key (hash key)\n * @param query - Optional advanced query conditions for sort key (begins_with, between, specific value, etc.)\n * @param pagination - Optional pagination parameters (page & limit)\n * @param sorting - Optional sort direction (only 'sortOrder' is used)\n * @returns Promise of array of matching items (T[])\n *\n * @example\n * repo.query(\"user#123\", { sortKeyBeginsWith: \"order#\" }, { limit: 20 }, { sortOrder: \"desc\" })\n */\n query(\n hashKey: unknown,\n query?: QueryAll,\n pagination?: QueryWithPagination,\n sorting?: Pick<QueryWithSorting, 'sortOrder'>\n ): Promise<T[]>;\n\n /**\n * Batch retrieves multiple items using their full composite keys (hash + sort).\n * This method uses `BatchGetItem` under the hood — much more efficient than individual gets\n * when fetching many items by primary key.\n *\n * @param keys - Array of objects containing at least `{ hashKey, sortKey? }`\n * @param query - Optional sorting preferences (rarely used in batch get)\n * @returns Promise of array of found items (in the order requested, missing items are omitted)\n *\n * @example\n * repo.queryInBulk([\n * { hashKey: \"user#abc\", sortKey: \"profile\" },\n * { hashKey: \"user#abc\", sortKey: \"settings\" }\n * ])\n */\n queryInBulk(keys: HashMap[], query?: QueryWithSorting): Promise<T[]>;\n\n /**\n * Performs a full table or index scan with optional filtering and pagination.\n * Scans are less efficient than queries — use only when you cannot use a partition key.\n *\n * @param pagination - Optional page & limit controls\n * @param filter - Optional raw filter expression (FilterExpression + attribute names/values)\n * @returns Promise of array of matching items\n *\n * @warning Scans can be expensive and slow on large tables — prefer query() when possible\n */\n scan(pagination?: QueryWithPagination, filter?: ScanFilter): Promise<T[]>;\n\n /**\n * Deletes **all items** that share the same partition key (hashKey).\n * Useful for cleaning up all records related to a specific entity (e.g. all orders of a user).\n *\n * @param hashKey - The partition key value whose items should be deleted\n * @returns Promise that resolves when deletion is complete\n *\n * @warning This operation may consume many write capacity units if the partition is large.\n * Consider using Query + BatchWriteItem in production for very large partitions.\n */\n deleteAll(hashKey: unknown): Promise<void>;\n\n /**\n * Deletes multiple items in a single BatchWriteItem call using their full keys.\n * Most efficient way to delete many known items at once.\n *\n * @param keys - Array of objects with hashKey and sortKey\n * @returns Promise that resolves when all deletions are complete\n *\n * @example\n * repo.deleteInBulk([\n * { hashKey: \"user#123\", sortKey: \"session#abc123\" },\n * { hashKey: \"user#123\", sortKey: \"session#def456\" }\n * ])\n */\n deleteInBulk(keys: HashMap[]): Promise<void>;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/repository.ts","../src/types.ts"],"names":["debug","createDebug","SORT_ASCENDING","BATCH_SIZE","now","AbstractDynamoDBRepository","table","factory","DynamoDBDocumentClient","DynamoDBClient","id","hashKey","query","pagination","sorting","lastEvaluatedKey","currentPage","resultItems","filterReturnedItemsExcludeSortKey","items","item","command","QueryCommand","sortKey","result","strict","keys","chunks","i","chunk","BatchGetCommand","sortOrder","a","b","filter","ScanCommand","batches","batch","deleteRequests","key","BatchWriteCommand","response","unprocessedItems","unprocessedKeys","error","GetCommand","isTimestamps","isTimestamp","PutCommand","DeleteCommand","TableKeychema","z","TableSpecSchema","QueryWithPaginationSchema","val","QueryWithSortingSchema","QueryAllSchema","ScanFilterSchema"],"mappings":"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","file":"index.js","sourcesContent":["import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport {\n BatchGetCommand,\n BatchWriteCommand,\n DeleteCommand,\n DynamoDBDocumentClient,\n GetCommand,\n PutCommand,\n QueryCommand,\n ScanCommand,\n} from '@aws-sdk/lib-dynamodb';\nimport { createDebug } from '@opble/debug';\nimport { isTimestamp, isTimestamps } from '@opble/entity';\nimport { Factory, HashMap } from '@opble/types';\n\nimport {\n DynamoDBRepository,\n QueryAll,\n QueryWithPagination,\n QueryWithSorting,\n ScanFilter,\n TableKey,\n TableSpec,\n} from './types';\n\nconst debug = createDebug('opble:repository-dynamodb');\nconst SORT_ASCENDING = 'asc';\nconst BATCH_SIZE = 50;\n\nfunction now(): number {\n return Math.floor(Date.now() / 1000);\n}\n\nexport abstract class AbstractDynamoDBRepository<\n T extends HashMap,\n ID extends TableKey = TableKey,\n> implements DynamoDBRepository<T, ID> {\n protected client: DynamoDBDocumentClient;\n\n constructor(\n protected table: TableSpec,\n protected factory: Factory<T>\n ) {\n this.client = DynamoDBDocumentClient.from(new DynamoDBClient({}));\n }\n\n protected getKey(id: ID): HashMap {\n return {\n [this.table.keys.hashKey]: id.hashKey,\n ...(this.table.keys.sortKey\n ? { [this.table.keys.sortKey]: id.sortKey }\n : {}),\n };\n }\n\n async query(\n hashKey: unknown,\n query?: QueryAll,\n pagination?: QueryWithPagination,\n sorting?: Pick<QueryWithSorting, 'sortOrder'>\n ): Promise<T[]> {\n /**\n * Internal state for pagination\n * - lastEvaluatedKey tells DynamoDB where to resume from\n * - currentPage helps us skip earlier pages\n * - resultItems will store the final page’s items\n */\n let lastEvaluatedKey: Record<string, unknown> | undefined;\n let currentPage = 1;\n let resultItems: HashMap[] = [];\n const filterReturnedItemsExcludeSortKey = (items: HashMap[]) => {\n /**\n * Business rule: Exclude a specific sortKey (if requested)\n * This is used to skip “parent” or “header” records within the same partition.\n */\n return query?.excludeSortKey && this.table.keys.sortKey\n ? items.filter(\n (item) => item[this.table.keys.sortKey!] !== query.excludeSortKey\n )\n : items;\n };\n\n do {\n /**\n * Prepare the QueryCommand\n * - Basic condition: match all items with the given hash key\n * - Add pagination via Limit + ExclusiveStartKey\n */\n const command = new QueryCommand({\n TableName: this.table.name,\n KeyConditionExpression: `#${this.table.keys.hashKey} = :hashKeyValue`,\n ExpressionAttributeNames: {\n [`#${this.table.keys.hashKey}`]: this.table.keys.hashKey,\n },\n ExpressionAttributeValues: {\n ':hashKeyValue': hashKey,\n },\n ExclusiveStartKey: lastEvaluatedKey,\n });\n\n if (pagination) {\n command.input.Limit = pagination.limit;\n }\n if (sorting) {\n command.input.ScanIndexForward = sorting.sortOrder === 'asc';\n }\n\n /**\n * Apply optional filters if provided\n */\n if (query) {\n // If querying a secondary index\n if (query.index) {\n command.input.IndexName = query.index.name;\n command.input.KeyConditionExpression = `#${query.index.keys.hashKey} = :hashKeyValue`;\n command.input.ExpressionAttributeNames = {\n [`#${query.index.keys.hashKey}`]: query.index.keys.hashKey,\n };\n }\n\n // If we want to match only sort keys starting with a specific prefix\n const sortKey = query.index?.keys.sortKey ?? this.table.keys.sortKey;\n if (query.sortKeyBeginsWith && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND begins_with(#${sortKey}, :sortKeyBeginsWith)`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyBeginsWith'] =\n query.sortKeyBeginsWith;\n } else if (query.sortKey && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} = :sortKeyValue`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyValue'] =\n query.sortKey;\n } else if (query.sortKeyBetween && sortKey) {\n command.input.KeyConditionExpression = `${command.input.KeyConditionExpression} AND #${sortKey} BETWEEN :sortKeyStart AND :sortKeyEnd`;\n command.input.ExpressionAttributeNames ??= {};\n command.input.ExpressionAttributeNames[`#${sortKey}`] = sortKey;\n command.input.ExpressionAttributeValues ??= {};\n command.input.ExpressionAttributeValues[':sortKeyStart'] =\n query.sortKeyBetween.start;\n command.input.ExpressionAttributeValues[':sortKeyEnd'] =\n query.sortKeyBetween.end;\n }\n }\n debug('command#input', command.input);\n\n // Execute the query\n const result = await this.client.send(command);\n\n // Keep the pagination pointer (if DynamoDB says there’s more data)\n lastEvaluatedKey = result.LastEvaluatedKey;\n\n if (!pagination) {\n // pagination is disabled, fetch until no results found\n resultItems.push(\n ...filterReturnedItemsExcludeSortKey(result.Items ?? [])\n );\n } else if (currentPage === pagination.page) {\n /**\n * Once we reach the desired page, capture those items.\n * DynamoDB doesn’t support direct page jumps — we simulate it by looping\n * until we reach the correct page number.\n */\n\n resultItems = filterReturnedItemsExcludeSortKey(result.Items ?? []);\n break; // Stop — we’ve collected the requested page\n }\n\n currentPage++; // Move to next page and continue querying\n } while (lastEvaluatedKey);\n\n /**\n * Map each DynamoDB item into an instance of your model class (T)\n * When querrying with index, turn strict to false\n */\n const strict = !query || !query.index;\n return resultItems.map((item) => this.factory.create(item, { strict }));\n }\n\n async queryInBulk(keys: HashMap[], query?: QueryWithSorting): Promise<T[]> {\n // Split keys into chunks of BATCH_SIZE (DynamoDB BatchGet limit)\n const chunks: HashMap[][] = [];\n for (let i = 0; i < keys.length; i += BATCH_SIZE) {\n chunks.push(keys.slice(i, i + BATCH_SIZE));\n }\n\n // Execute all batch requests concurrently\n const results = await Promise.all(\n chunks.map(async (chunk) => {\n const command = new BatchGetCommand({\n RequestItems: {\n [this.table.name]: { Keys: chunk },\n },\n });\n const response = await this.client.send(command);\n return response.Responses?.[this.table.name] ?? [];\n })\n );\n\n // Merge all items from all responses\n const mergedItems = results.flat();\n\n // Convert each item to T using factory\n const items = mergedItems.map((item) => this.factory.create(item));\n\n // Sort if query.sortBy is specified\n if (query && query.sortBy) {\n const sortOrder = query.sortOrder ?? SORT_ASCENDING;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n items.sort((a: any, b: any) => {\n if (a[query.sortBy!] < b[query.sortBy!])\n return sortOrder === SORT_ASCENDING ? -1 : 1;\n if (a[query.sortBy!] > b[query.sortBy!])\n return sortOrder === SORT_ASCENDING ? 1 : -1;\n return 0;\n });\n }\n\n return items;\n }\n\n async scan(\n pagination?: QueryWithPagination,\n filter?: ScanFilter\n ): Promise<T[]> {\n /**\n * Internal state for pagination\n * - lastEvaluatedKey tells DynamoDB where to resume from\n * - currentPage helps us skip earlier pages\n * - resultItems will store the final page’s items\n */\n let lastEvaluatedKey: Record<string, unknown> | undefined;\n let currentPage = 1;\n let resultItems: HashMap[] = [];\n\n do {\n /**\n * Prepare the ScanCommand\n * - Add pagination via Limit + ExclusiveStartKey\n */\n const command = new ScanCommand({\n TableName: this.table.name,\n ExclusiveStartKey: lastEvaluatedKey,\n });\n\n if (pagination) {\n command.input.Limit = pagination.limit;\n }\n\n if (filter) {\n command.input.FilterExpression = filter.filterExpression;\n command.input.ExpressionAttributeNames =\n filter.expressionAttributeNames;\n command.input.ExpressionAttributeValues =\n filter.expressionAttributeValues;\n }\n\n /**\n * Execute the scan\n */\n const result = await this.client.send(command);\n\n // Keep the pagination pointer (if DynamoDB says there’s more data)\n lastEvaluatedKey = result.LastEvaluatedKey;\n\n if (!pagination) {\n // pagination is disabled, fetch until no results found\n resultItems.push(...(result.Items ?? []));\n } else if (currentPage === pagination.page) {\n /**\n * Once we reach the desired page, capture those items.\n * DynamoDB doesn’t support direct page jumps — we simulate it by looping\n * until we reach the correct page number.\n */\n\n resultItems = result.Items ?? [];\n break; // Stop — we’ve collected the requested page\n }\n\n currentPage++; // Move to next page and continue querying\n } while (lastEvaluatedKey);\n\n /**\n * Map each DynamoDB item into an instance of your model class (T)\n */\n return resultItems.map((item) => this.factory.create(item));\n }\n\n async deleteAll(hashKey: unknown): Promise<void> {\n const items = await this.query(hashKey);\n const keys = items.map((item) => {\n if (this.table.keys.sortKey) {\n return {\n [this.table.keys.hashKey]: hashKey,\n [this.table.keys.sortKey]:\n item[this.table.keys.sortKey as keyof typeof item],\n };\n } else {\n return {\n [this.table.keys.hashKey]: hashKey,\n };\n }\n });\n\n await this.deleteInBulk(keys);\n }\n\n async deleteInBulk(keys: HashMap[]): Promise<void> {\n const batches = [];\n while (keys.length > 0) {\n batches.push(keys.splice(0, BATCH_SIZE));\n }\n\n for (const batch of batches) {\n // Create delete requests for each item in the batch\n const deleteRequests = batch.map((key) => ({\n DeleteRequest: { Key: key },\n }));\n\n // Execute the BatchWriteCommand\n const command = new BatchWriteCommand({\n RequestItems: {\n [this.table.name]: deleteRequests,\n },\n });\n\n try {\n const response = await this.client.send(command);\n debug('[INFO] Batch delete response:', response);\n\n // Check if there are unprocessed items and retry them if necessary\n if (\n response.UnprocessedItems &&\n response.UnprocessedItems[this.table.name]\n ) {\n debug('[WARN] Unprocessed items found. Retrying...');\n const unprocessedItems = response.UnprocessedItems[this.table.name];\n if (unprocessedItems === undefined) {\n continue;\n }\n const unprocessedKeys = unprocessedItems\n .map((item) => item.DeleteRequest?.Key)\n .filter((key): key is Record<string, unknown> => key != null); // Filter out undefined keys\n\n await this.deleteInBulk(unprocessedKeys);\n }\n } catch (error) {\n debug('[ERROR] Error deleting items in batch:', error);\n }\n }\n }\n\n async get(id: ID): Promise<T | null> {\n const command = new GetCommand({\n TableName: this.table.name,\n Key: this.getKey(id),\n });\n\n const result = await this.client.send(command);\n return result.Item ? this.factory.create(result.Item) : null;\n }\n\n /**\n * Allow to manipulate the data before saving\n * @param item\n * @returns\n */\n protected beforeSave(item: T): T {\n if (isTimestamps(item)) {\n item.updatedAt = now();\n } else if (isTimestamp(item)) {\n item.timestamp = now();\n }\n\n return item;\n }\n\n async save(item: T): Promise<T> {\n item = this.beforeSave(item);\n const command = new PutCommand({\n TableName: this.table.name,\n Item: { ...item },\n });\n await this.client.send(command);\n\n return item;\n }\n\n async delete(id: ID): Promise<void | T> {\n await this.client.send(\n new DeleteCommand({\n TableName: this.table.name,\n Key: this.getKey(id),\n })\n );\n }\n}\n","import {\n DeletableRepository,\n HashMap,\n ReadableRepository,\n SavableRepository,\n} from '@opble/types';\nimport { z } from 'zod';\n\nexport const TableKeychema = z.object({\n hashKey: z.string(),\n sortKey: z.union([z.string(), z.number()]).optional(),\n});\n\nexport const TableSpecSchema = z.object({\n name: z.string(),\n keys: z.object({\n hashKey: z.string(),\n sortKey: z.string().optional(),\n }),\n});\n\nexport const QueryWithPaginationSchema = z.object({\n page: z\n .string()\n .optional()\n .transform((val) => (val ? parseInt(val, 10) : 1)) // default 1\n .refine((val) => val >= 1, { message: 'Page must be at least 1' }),\n\n limit: z\n .string()\n .optional()\n .transform((val) => (val ? parseInt(val, 10) : 25)) // default 25\n .refine((val) => val >= 5 && val <= 100, {\n message: 'Limit must be between 5 and 100',\n }),\n});\n\nexport const QueryWithSortingSchema = z.object({\n sortBy: z.string().optional(),\n sortOrder: z\n .enum(['asc', 'desc'])\n .optional()\n .transform((val) => val ?? 'asc'),\n});\n\nexport const QueryAllSchema = z.object({\n excludeSortKey: z.string().optional(),\n sortKeyBetween: z\n .object({\n start: z.string(),\n end: z.string(),\n })\n .optional(),\n sortKeyBeginsWith: z.string().optional(),\n sortKey: z.any().optional(),\n index: TableSpecSchema.optional(),\n});\n\nexport const ScanFilterSchema = z.object({\n filterExpression: z.string(),\n expressionAttributeNames: z.record(z.string(), z.string()),\n expressionAttributeValues: z.record(z.string(), z.any()),\n});\n\nexport type TableKey = z.infer<typeof TableKeychema>;\nexport type TableSpec = z.infer<typeof TableSpecSchema>;\n\nexport type QueryWithPagination = z.infer<typeof QueryWithPaginationSchema>;\nexport type QueryWithSorting = z.infer<typeof QueryWithSortingSchema>;\nexport type QueryAll = z.infer<typeof QueryAllSchema>;\nexport type ScanFilter = z.infer<typeof ScanFilterSchema>;\n\n/**\n * A generic repository interface specifically designed for DynamoDB operations.\n * It extends the basic CRUD repositories (Readable, Savable, Deletable) and adds\n * DynamoDB-specific methods for efficient querying, scanning, bulk operations,\n * and deletion patterns commonly used with DynamoDB's key structure.\n *\n * @template T - The type of the entity/document stored in DynamoDB (must be a HashMap / Record<string, unknown>)\n * @template ID - The type used to identify items. Defaults to `TableKey` (hash + optional sort key).\n */\nexport interface DynamoDBRepository<T extends HashMap, ID = TableKey>\n extends\n ReadableRepository<T, ID>,\n SavableRepository<T>,\n DeletableRepository<T, ID> {\n /**\n * Queries items using a partition key (hashKey) and optional sort key conditions.\n * This is the most common and efficient way to read data from a DynamoDB table\n * when you know the partition key.\n *\n * @param hashKey - The value of the partition key (hash key)\n * @param query - Optional advanced query conditions for sort key (begins_with, between, specific value, etc.)\n * @param pagination - Optional pagination parameters (page & limit)\n * @param sorting - Optional sort direction (only 'sortOrder' is used)\n * @returns Promise of array of matching items (T[])\n *\n * @example\n * repo.query(\"user#123\", { sortKeyBeginsWith: \"order#\" }, { limit: 20 }, { sortOrder: \"desc\" })\n */\n query(\n hashKey: unknown,\n query?: QueryAll,\n pagination?: QueryWithPagination,\n sorting?: Pick<QueryWithSorting, 'sortOrder'>\n ): Promise<T[]>;\n\n /**\n * Batch retrieves multiple items using their full composite keys (hash + sort).\n * This method uses `BatchGetItem` under the hood — much more efficient than individual gets\n * when fetching many items by primary key.\n *\n * @param keys - Array of objects containing at least `{ hashKey, sortKey? }`\n * @param query - Optional sorting preferences (rarely used in batch get)\n * @returns Promise of array of found items (in the order requested, missing items are omitted)\n *\n * @example\n * repo.queryInBulk([\n * { hashKey: \"user#abc\", sortKey: \"profile\" },\n * { hashKey: \"user#abc\", sortKey: \"settings\" }\n * ])\n */\n queryInBulk(keys: HashMap[], query?: QueryWithSorting): Promise<T[]>;\n\n /**\n * Performs a full table or index scan with optional filtering and pagination.\n * Scans are less efficient than queries — use only when you cannot use a partition key.\n *\n * @param pagination - Optional page & limit controls\n * @param filter - Optional raw filter expression (FilterExpression + attribute names/values)\n * @returns Promise of array of matching items\n *\n * @warning Scans can be expensive and slow on large tables — prefer query() when possible\n */\n scan(pagination?: QueryWithPagination, filter?: ScanFilter): Promise<T[]>;\n\n /**\n * Deletes **all items** that share the same partition key (hashKey).\n * Useful for cleaning up all records related to a specific entity (e.g. all orders of a user).\n *\n * @param hashKey - The partition key value whose items should be deleted\n * @returns Promise that resolves when deletion is complete\n *\n * @warning This operation may consume many write capacity units if the partition is large.\n * Consider using Query + BatchWriteItem in production for very large partitions.\n */\n deleteAll(hashKey: unknown): Promise<void>;\n\n /**\n * Deletes multiple items in a single BatchWriteItem call using their full keys.\n * Most efficient way to delete many known items at once.\n *\n * @param keys - Array of objects with hashKey and sortKey\n * @returns Promise that resolves when all deletions are complete\n *\n * @example\n * repo.deleteInBulk([\n * { hashKey: \"user#123\", sortKey: \"session#abc123\" },\n * { hashKey: \"user#123\", sortKey: \"session#def456\" }\n * ])\n */\n deleteInBulk(keys: HashMap[]): Promise<void>;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@opble/repository-dynamodb",
3
3
  "description": "Provide DynamoDB repository implementation.",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
@@ -21,6 +21,11 @@
21
21
  "dynamodb",
22
22
  "repository"
23
23
  ],
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
24
29
  "bugs": {
25
30
  "url": "https://github.com/opble/node-packages/issues"
26
31
  },
@@ -29,12 +34,8 @@
29
34
  "access": "public",
30
35
  "provenance": false
31
36
  },
32
- "files": [
33
- "dist",
34
- "README.md",
35
- "LICENSE"
36
- ],
37
37
  "devDependencies": {
38
+ "aws-sdk-client-mock": "^4.1.0",
38
39
  "@opble/typescript-config": "0.0.0",
39
40
  "@opble/eslint-config": "0.0.0"
40
41
  },
@@ -49,7 +50,7 @@
49
50
  "scripts": {
50
51
  "build": "tsup",
51
52
  "clean": "rm -rf dist",
52
- "lint": "eslint \"src/**/*.ts*\" --max-warnings 0",
53
- "test": "true"
53
+ "lint": "eslint \"{src,test}/**/*.ts*\" --max-warnings 0",
54
+ "test": "vitest run"
54
55
  }
55
56
  }