@rempays/shared-core 1.0.2-beta.10 → 1.0.2-beta.12
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.
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Cliente genérico de DynamoDB
|
|
3
|
-
* Wrapper type-safe sobre AWS SDK
|
|
4
|
-
*/
|
|
1
|
+
import type { PaginatedResult } from './modules/shared-types.js';
|
|
5
2
|
export interface DynamoDBConfig {
|
|
6
3
|
region?: string;
|
|
7
4
|
endpoint?: string;
|
|
@@ -25,6 +22,7 @@ export interface QueryOptions {
|
|
|
25
22
|
expressionAttributeValues?: Record<string, any>;
|
|
26
23
|
limit?: number;
|
|
27
24
|
scanIndexForward?: boolean;
|
|
25
|
+
lastEvaluatedKey?: Record<string, any>;
|
|
28
26
|
}
|
|
29
27
|
export declare class DynamoDBClientWrapper {
|
|
30
28
|
private client;
|
|
@@ -46,9 +44,33 @@ export declare class DynamoDBClientWrapper {
|
|
|
46
44
|
*/
|
|
47
45
|
delete(tableName: string, key: Record<string, any>): Promise<void>;
|
|
48
46
|
/**
|
|
49
|
-
* Query con índice o clave primaria
|
|
47
|
+
* Query con índice o clave primaria (con paginación)
|
|
50
48
|
*/
|
|
51
|
-
query<T extends Record<string, any>>(tableName: string, options: QueryOptions): Promise<T
|
|
49
|
+
query<T extends Record<string, any>>(tableName: string, options: QueryOptions): Promise<PaginatedResult<T>>;
|
|
50
|
+
/**
|
|
51
|
+
* Query simple por PK (Partition Key)
|
|
52
|
+
*/
|
|
53
|
+
queryByPK<T extends Record<string, any>>(tableName: string, pkName: string, pkValue: any, options?: {
|
|
54
|
+
limit?: number;
|
|
55
|
+
scanIndexForward?: boolean;
|
|
56
|
+
lastEvaluatedKey?: Record<string, any>;
|
|
57
|
+
}): Promise<PaginatedResult<T>>;
|
|
58
|
+
/**
|
|
59
|
+
* Query por PK y SK (Sort Key) con operador
|
|
60
|
+
*/
|
|
61
|
+
queryByPKAndSK<T extends Record<string, any>>(tableName: string, pkName: string, pkValue: any, skName: string, skOperator: '=' | '<' | '<=' | '>' | '>=' | 'BETWEEN' | 'begins_with', skValue: any, skValue2?: any, // Para BETWEEN
|
|
62
|
+
options?: {
|
|
63
|
+
limit?: number;
|
|
64
|
+
scanIndexForward?: boolean;
|
|
65
|
+
lastEvaluatedKey?: Record<string, any>;
|
|
66
|
+
indexName?: string;
|
|
67
|
+
}): Promise<PaginatedResult<T>>;
|
|
68
|
+
/**
|
|
69
|
+
* Query todos los items por PK (sin límite, con paginación automática)
|
|
70
|
+
*/
|
|
71
|
+
queryAllByPK<T extends Record<string, any>>(tableName: string, pkName: string, pkValue: any, options?: {
|
|
72
|
+
scanIndexForward?: boolean;
|
|
73
|
+
}): Promise<T[]>;
|
|
52
74
|
/**
|
|
53
75
|
* Scan completo de la tabla (usar con cuidado)
|
|
54
76
|
*/
|
|
@@ -64,4 +86,32 @@ export declare class DynamoDBClientWrapper {
|
|
|
64
86
|
* Batch get (obtener múltiples items)
|
|
65
87
|
*/
|
|
66
88
|
batchGet<T extends Record<string, any>>(tableName: string, keys: Array<Record<string, any>>): Promise<T[]>;
|
|
89
|
+
/**
|
|
90
|
+
* Verifica si un item existe
|
|
91
|
+
*/
|
|
92
|
+
exists(tableName: string, key: Record<string, any>): Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Incrementa un atributo numérico
|
|
95
|
+
*/
|
|
96
|
+
increment<T extends Record<string, any>>(tableName: string, key: Record<string, any>, attributeName: string, incrementBy?: number): Promise<T>;
|
|
97
|
+
/**
|
|
98
|
+
* Decrementa un atributo numérico
|
|
99
|
+
*/
|
|
100
|
+
decrement<T extends Record<string, any>>(tableName: string, key: Record<string, any>, attributeName: string, decrementBy?: number): Promise<T>;
|
|
101
|
+
/**
|
|
102
|
+
* Agrega items a una lista (array)
|
|
103
|
+
*/
|
|
104
|
+
appendToList<T extends Record<string, any>>(tableName: string, key: Record<string, any>, attributeName: string, values: any[]): Promise<T>;
|
|
105
|
+
/**
|
|
106
|
+
* Agrega items a un Set
|
|
107
|
+
*/
|
|
108
|
+
addToSet<T extends Record<string, any>>(tableName: string, key: Record<string, any>, attributeName: string, values: any[]): Promise<T>;
|
|
109
|
+
/**
|
|
110
|
+
* Elimina items de un Set
|
|
111
|
+
*/
|
|
112
|
+
removeFromSet<T extends Record<string, any>>(tableName: string, key: Record<string, any>, attributeName: string, values: any[]): Promise<T>;
|
|
113
|
+
/**
|
|
114
|
+
* Elimina un atributo
|
|
115
|
+
*/
|
|
116
|
+
removeAttribute<T extends Record<string, any>>(tableName: string, key: Record<string, any>, attributeName: string): Promise<T>;
|
|
67
117
|
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Cliente genérico de DynamoDB
|
|
4
|
-
* Wrapper type-safe sobre AWS SDK
|
|
5
|
-
*/
|
|
6
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
3
|
exports.DynamoDBClientWrapper = void 0;
|
|
8
4
|
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
@@ -80,7 +76,7 @@ class DynamoDBClientWrapper {
|
|
|
80
76
|
await this.client.send(command);
|
|
81
77
|
}
|
|
82
78
|
/**
|
|
83
|
-
* Query con índice o clave primaria
|
|
79
|
+
* Query con índice o clave primaria (con paginación)
|
|
84
80
|
*/
|
|
85
81
|
async query(tableName, options) {
|
|
86
82
|
const command = new client_dynamodb_1.QueryCommand({
|
|
@@ -100,12 +96,87 @@ class DynamoDBClientWrapper {
|
|
|
100
96
|
...(options.scanIndexForward !== undefined && {
|
|
101
97
|
ScanIndexForward: options.scanIndexForward,
|
|
102
98
|
}),
|
|
99
|
+
...(options.lastEvaluatedKey && {
|
|
100
|
+
ExclusiveStartKey: (0, util_dynamodb_1.marshall)(options.lastEvaluatedKey),
|
|
101
|
+
}),
|
|
103
102
|
});
|
|
104
103
|
const response = await this.client.send(command);
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
const items = response.Items?.map((item) => (0, util_dynamodb_1.unmarshall)(item)) || [];
|
|
105
|
+
const lastEvaluatedKey = response.LastEvaluatedKey
|
|
106
|
+
? (0, util_dynamodb_1.unmarshall)(response.LastEvaluatedKey)
|
|
107
|
+
: undefined;
|
|
108
|
+
return {
|
|
109
|
+
items,
|
|
110
|
+
lastEvaluatedKey,
|
|
111
|
+
hasMore: !!lastEvaluatedKey,
|
|
112
|
+
count: items.length,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Query simple por PK (Partition Key)
|
|
117
|
+
*/
|
|
118
|
+
async queryByPK(tableName, pkName, pkValue, options) {
|
|
119
|
+
return this.query(tableName, {
|
|
120
|
+
keyConditionExpression: `#pk = :pk`,
|
|
121
|
+
expressionAttributeNames: { '#pk': pkName },
|
|
122
|
+
expressionAttributeValues: { ':pk': pkValue },
|
|
123
|
+
...options,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Query por PK y SK (Sort Key) con operador
|
|
128
|
+
*/
|
|
129
|
+
async queryByPKAndSK(tableName, pkName, pkValue, skName, skOperator, skValue, skValue2, // Para BETWEEN
|
|
130
|
+
options) {
|
|
131
|
+
let keyConditionExpression;
|
|
132
|
+
let expressionAttributeValues;
|
|
133
|
+
if (skOperator === 'BETWEEN' && skValue2 !== undefined) {
|
|
134
|
+
keyConditionExpression = `#pk = :pk AND #sk BETWEEN :sk1 AND :sk2`;
|
|
135
|
+
expressionAttributeValues = {
|
|
136
|
+
':pk': pkValue,
|
|
137
|
+
':sk1': skValue,
|
|
138
|
+
':sk2': skValue2,
|
|
139
|
+
};
|
|
107
140
|
}
|
|
108
|
-
|
|
141
|
+
else if (skOperator === 'begins_with') {
|
|
142
|
+
keyConditionExpression = `#pk = :pk AND begins_with(#sk, :sk)`;
|
|
143
|
+
expressionAttributeValues = {
|
|
144
|
+
':pk': pkValue,
|
|
145
|
+
':sk': skValue,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
keyConditionExpression = `#pk = :pk AND #sk ${skOperator} :sk`;
|
|
150
|
+
expressionAttributeValues = {
|
|
151
|
+
':pk': pkValue,
|
|
152
|
+
':sk': skValue,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return this.query(tableName, {
|
|
156
|
+
keyConditionExpression,
|
|
157
|
+
expressionAttributeNames: {
|
|
158
|
+
'#pk': pkName,
|
|
159
|
+
'#sk': skName,
|
|
160
|
+
},
|
|
161
|
+
expressionAttributeValues,
|
|
162
|
+
...options,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Query todos los items por PK (sin límite, con paginación automática)
|
|
167
|
+
*/
|
|
168
|
+
async queryAllByPK(tableName, pkName, pkValue, options) {
|
|
169
|
+
const allItems = [];
|
|
170
|
+
let lastEvaluatedKey;
|
|
171
|
+
do {
|
|
172
|
+
const result = await this.queryByPK(tableName, pkName, pkValue, {
|
|
173
|
+
...options,
|
|
174
|
+
lastEvaluatedKey,
|
|
175
|
+
});
|
|
176
|
+
allItems.push(...result.items);
|
|
177
|
+
lastEvaluatedKey = result.lastEvaluatedKey;
|
|
178
|
+
} while (lastEvaluatedKey);
|
|
179
|
+
return allItems;
|
|
109
180
|
}
|
|
110
181
|
/**
|
|
111
182
|
* Scan completo de la tabla (usar con cuidado)
|
|
@@ -166,5 +237,87 @@ class DynamoDBClientWrapper {
|
|
|
166
237
|
}
|
|
167
238
|
return response.Responses[tableName].map((item) => (0, util_dynamodb_1.unmarshall)(item));
|
|
168
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Verifica si un item existe
|
|
242
|
+
*/
|
|
243
|
+
async exists(tableName, key) {
|
|
244
|
+
const item = await this.get(tableName, key);
|
|
245
|
+
return item !== null;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Incrementa un atributo numérico
|
|
249
|
+
*/
|
|
250
|
+
async increment(tableName, key, attributeName, incrementBy = 1) {
|
|
251
|
+
return this.update(tableName, key, {
|
|
252
|
+
updateExpression: `SET #attr = if_not_exists(#attr, :zero) + :inc`,
|
|
253
|
+
expressionAttributeNames: {
|
|
254
|
+
'#attr': attributeName,
|
|
255
|
+
},
|
|
256
|
+
expressionAttributeValues: {
|
|
257
|
+
':zero': 0,
|
|
258
|
+
':inc': incrementBy,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Decrementa un atributo numérico
|
|
264
|
+
*/
|
|
265
|
+
async decrement(tableName, key, attributeName, decrementBy = 1) {
|
|
266
|
+
return this.increment(tableName, key, attributeName, -decrementBy);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Agrega items a una lista (array)
|
|
270
|
+
*/
|
|
271
|
+
async appendToList(tableName, key, attributeName, values) {
|
|
272
|
+
return this.update(tableName, key, {
|
|
273
|
+
updateExpression: `SET #attr = list_append(if_not_exists(#attr, :empty), :vals)`,
|
|
274
|
+
expressionAttributeNames: {
|
|
275
|
+
'#attr': attributeName,
|
|
276
|
+
},
|
|
277
|
+
expressionAttributeValues: {
|
|
278
|
+
':empty': [],
|
|
279
|
+
':vals': values,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Agrega items a un Set
|
|
285
|
+
*/
|
|
286
|
+
async addToSet(tableName, key, attributeName, values) {
|
|
287
|
+
return this.update(tableName, key, {
|
|
288
|
+
updateExpression: `ADD #attr :vals`,
|
|
289
|
+
expressionAttributeNames: {
|
|
290
|
+
'#attr': attributeName,
|
|
291
|
+
},
|
|
292
|
+
expressionAttributeValues: {
|
|
293
|
+
':vals': new Set(values),
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Elimina items de un Set
|
|
299
|
+
*/
|
|
300
|
+
async removeFromSet(tableName, key, attributeName, values) {
|
|
301
|
+
return this.update(tableName, key, {
|
|
302
|
+
updateExpression: `DELETE #attr :vals`,
|
|
303
|
+
expressionAttributeNames: {
|
|
304
|
+
'#attr': attributeName,
|
|
305
|
+
},
|
|
306
|
+
expressionAttributeValues: {
|
|
307
|
+
':vals': new Set(values),
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Elimina un atributo
|
|
313
|
+
*/
|
|
314
|
+
async removeAttribute(tableName, key, attributeName) {
|
|
315
|
+
return this.update(tableName, key, {
|
|
316
|
+
updateExpression: `REMOVE #attr`,
|
|
317
|
+
expressionAttributeNames: {
|
|
318
|
+
'#attr': attributeName,
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
}
|
|
169
322
|
}
|
|
170
323
|
exports.DynamoDBClientWrapper = DynamoDBClientWrapper;
|
|
@@ -46,12 +46,9 @@ export declare class ChatService {
|
|
|
46
46
|
listChatsByBusiness(businessId: string, status: string, params?: PaginationParams): Promise<PaginatedResult<Chat>>;
|
|
47
47
|
/**
|
|
48
48
|
* Lista todos los chats de un business (todos los status)
|
|
49
|
+
* Nota: Hace múltiples queries, una por cada status común
|
|
49
50
|
*/
|
|
50
51
|
listAllChatsByBusiness(businessId: string, params?: PaginationParams): Promise<PaginatedResult<Chat>>;
|
|
51
|
-
/**
|
|
52
|
-
* Incrementa el contador de mensajes no leídos
|
|
53
|
-
*/
|
|
54
|
-
incrementUnreadCount(chatId: string): Promise<void>;
|
|
55
52
|
/**
|
|
56
53
|
* Marca todos los mensajes como leídos
|
|
57
54
|
*/
|
|
@@ -242,48 +242,33 @@ class ChatService {
|
|
|
242
242
|
}
|
|
243
243
|
/**
|
|
244
244
|
* Lista todos los chats de un business (todos los status)
|
|
245
|
+
* Nota: Hace múltiples queries, una por cada status común
|
|
245
246
|
*/
|
|
246
247
|
async listAllChatsByBusiness(businessId, params = {}) {
|
|
247
|
-
const { limit = 20
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
248
|
+
const { limit = 20 } = params;
|
|
249
|
+
const statuses = ['active', 'closed', 'pending', 'archived'];
|
|
250
|
+
const allChats = [];
|
|
251
|
+
// Query para cada status
|
|
252
|
+
for (const status of statuses) {
|
|
253
|
+
const result = await this.listChatsByBusiness(businessId, status, {
|
|
254
|
+
limit: Math.ceil(limit / statuses.length),
|
|
255
|
+
});
|
|
256
|
+
allChats.push(...result.items);
|
|
257
|
+
// Si ya tenemos suficientes, parar
|
|
258
|
+
if (allChats.length >= limit) {
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Ordenar por lastMessageTimestamp descendente
|
|
263
|
+
allChats.sort((a, b) => b.lastMessageTimestamp - a.lastMessageTimestamp);
|
|
264
|
+
// Limitar resultados
|
|
265
|
+
const items = allChats.slice(0, limit);
|
|
264
266
|
return {
|
|
265
267
|
items,
|
|
266
|
-
lastEvaluatedKey:
|
|
267
|
-
hasMore:
|
|
268
|
+
lastEvaluatedKey: undefined,
|
|
269
|
+
hasMore: allChats.length > limit,
|
|
268
270
|
};
|
|
269
271
|
}
|
|
270
|
-
/**
|
|
271
|
-
* Incrementa el contador de mensajes no leídos
|
|
272
|
-
*/
|
|
273
|
-
async incrementUnreadCount(chatId) {
|
|
274
|
-
await this.client.send(new client_dynamodb_1.UpdateItemCommand({
|
|
275
|
-
TableName: this.tableName,
|
|
276
|
-
Key: (0, util_dynamodb_1.marshall)({
|
|
277
|
-
PK: `CHAT#${chatId}`,
|
|
278
|
-
SK: 'METADATA',
|
|
279
|
-
}),
|
|
280
|
-
UpdateExpression: 'SET unreadCount = if_not_exists(unreadCount, :zero) + :inc',
|
|
281
|
-
ExpressionAttributeValues: (0, util_dynamodb_1.marshall)({
|
|
282
|
-
':inc': 1,
|
|
283
|
-
':zero': 0,
|
|
284
|
-
}),
|
|
285
|
-
}));
|
|
286
|
-
}
|
|
287
272
|
/**
|
|
288
273
|
* Marca todos los mensajes como leídos
|
|
289
274
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rempays/shared-core",
|
|
3
|
-
"version": "1.0.2-beta.
|
|
3
|
+
"version": "1.0.2-beta.12",
|
|
4
4
|
"description": "Core utilities layer for RemPays platform with AWS services integration (Cognito, S3, Secrets Manager, Textract, Facebook API, DynamoDB)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"build": "tsc",
|
|
39
39
|
"build:watch": "tsc --watch",
|
|
40
40
|
"prepublishOnly": "npm run build",
|
|
41
|
-
"dev": "tsc --watch"
|
|
41
|
+
"dev": "tsc --watch",
|
|
42
|
+
"test:chat": "tsx examples/test-chat-service.ts"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"@aws-sdk/client-cognito-identity-provider": "^3.454.0",
|
|
@@ -50,6 +51,8 @@
|
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/node": "^20.11.0",
|
|
54
|
+
"dotenv": "^17.3.1",
|
|
55
|
+
"tsx": "^4.7.0",
|
|
53
56
|
"typescript": "^5.3.0"
|
|
54
57
|
},
|
|
55
58
|
"files": [
|