@mbc-cqrs-serverless/core 1.1.6 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/command.module.js +3 -0
- package/dist/commands/command.module.js.map +1 -1
- package/dist/commands/command.service.d.ts +9 -1
- package/dist/commands/command.service.js +33 -2
- package/dist/commands/command.service.js.map +1 -1
- package/dist/commands/data.service.js +3 -26
- package/dist/commands/data.service.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/repository.d.ts +102 -0
- package/dist/commands/repository.js +280 -0
- package/dist/commands/repository.js.map +1 -0
- package/dist/data-store/data-store.module.js +3 -2
- package/dist/data-store/data-store.module.js.map +1 -1
- package/dist/data-store/index.d.ts +2 -0
- package/dist/data-store/index.js +2 -0
- package/dist/data-store/index.js.map +1 -1
- package/dist/data-store/session.interface.d.ts +10 -0
- package/dist/data-store/session.interface.js +3 -0
- package/dist/data-store/session.interface.js.map +1 -0
- package/dist/data-store/session.service.d.ts +52 -0
- package/dist/data-store/session.service.js +128 -0
- package/dist/data-store/session.service.js.map +1 -0
- package/dist/env.validation.d.ts +1 -0
- package/dist/env.validation.js +5 -0
- package/dist/env.validation.js.map +1 -1
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/helpers/key.d.ts +13 -0
- package/dist/helpers/key.js +26 -0
- package/dist/helpers/key.js.map +1 -1
- package/dist/helpers/transform.d.ts +7 -0
- package/dist/helpers/transform.js +36 -0
- package/dist/helpers/transform.js.map +1 -0
- package/dist/queue/index.d.ts +1 -0
- package/dist/queue/index.js +1 -0
- package/dist/queue/index.js.map +1 -1
- package/dist/queue/queue.module.js +4 -2
- package/dist/queue/queue.module.js.map +1 -1
- package/dist/queue/sns-client-factory.d.ts +2 -2
- package/dist/queue/sns-client-factory.js +4 -6
- package/dist/queue/sns-client-factory.js.map +1 -1
- package/dist/queue/sns.service.js +1 -1
- package/dist/queue/sns.service.js.map +1 -1
- package/dist/queue/sqs-client-factory.d.ts +8 -0
- package/dist/queue/sqs-client-factory.js +35 -0
- package/dist/queue/sqs-client-factory.js.map +1 -0
- package/dist/queue/sqs.service.d.ts +28 -0
- package/dist/queue/sqs.service.js +89 -0
- package/dist/queue/sqs.service.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var Repository_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.Repository = void 0;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const constants_1 = require("../constants");
|
|
19
|
+
const user_1 = require("../context/user");
|
|
20
|
+
const session_service_1 = require("../data-store/session.service");
|
|
21
|
+
const key_1 = require("../helpers/key");
|
|
22
|
+
const transform_1 = require("../helpers/transform");
|
|
23
|
+
const interfaces_1 = require("../interfaces");
|
|
24
|
+
const command_module_definition_1 = require("./command.module-definition");
|
|
25
|
+
const command_service_1 = require("./command.service");
|
|
26
|
+
const data_service_1 = require("./data.service");
|
|
27
|
+
let Repository = Repository_1 = class Repository {
|
|
28
|
+
constructor(options, dataService, commandService, sessionService) {
|
|
29
|
+
this.options = options;
|
|
30
|
+
this.dataService = dataService;
|
|
31
|
+
this.commandService = commandService;
|
|
32
|
+
this.sessionService = sessionService;
|
|
33
|
+
this.logger = new common_1.Logger(Repository_1.name);
|
|
34
|
+
this.moduleTableName = this.options.tableName;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get a single data item, merging a pending async command when a session exists.
|
|
38
|
+
*/
|
|
39
|
+
async getItem(key, options) {
|
|
40
|
+
const userContext = (0, user_1.getUserContext)(options.invokeContext);
|
|
41
|
+
const userId = userContext?.userId;
|
|
42
|
+
const tenantCode = (0, key_1.getTenantCode)(key.pk);
|
|
43
|
+
if (userId && tenantCode) {
|
|
44
|
+
const itemId = (0, key_1.generateId)(key.pk, key.sk);
|
|
45
|
+
const session = await this.sessionService.get(userId, tenantCode, this.moduleTableName, itemId);
|
|
46
|
+
if (session) {
|
|
47
|
+
this.logger.debug(`getItem session merge — version ${session.version}`, key);
|
|
48
|
+
const cmd = await this.commandService.getItem({
|
|
49
|
+
pk: key.pk,
|
|
50
|
+
sk: (0, key_1.addSortKeyVersion)(key.sk, session.version),
|
|
51
|
+
});
|
|
52
|
+
if (cmd) {
|
|
53
|
+
const existing = await this.dataService.getItem(key);
|
|
54
|
+
return (0, transform_1.transformCommandToData)(cmd, existing);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return this.dataService.getItem(key);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* List items from the DynamoDB data table with an optional merge of pending async commands.
|
|
62
|
+
*
|
|
63
|
+
* When `mergeOptions.latestFlg` is true, the result is augmented with any
|
|
64
|
+
* pending writes the current user has not yet seen reflected in the data table:
|
|
65
|
+
*
|
|
66
|
+
* - **update** — the existing item is replaced in-place, preserving its
|
|
67
|
+
* position in the list.
|
|
68
|
+
* - **delete** — the item is removed from the result.
|
|
69
|
+
* - **create-new** — the item is prepended to the top of the list, sorted by
|
|
70
|
+
* `updatedAt` descending when there are multiple pending creates.
|
|
71
|
+
*
|
|
72
|
+
* > **Sort-order note**: Prepended create-new items are not integrated into
|
|
73
|
+
* > the caller's sort order (e.g. by `name` or `code`). They will appear
|
|
74
|
+
* > at the top of the result until the async DynamoDB Stream sync completes and
|
|
75
|
+
* > the next read returns fully sorted data. This is intentional — the
|
|
76
|
+
* > guarantee is visibility, not sort position.
|
|
77
|
+
*/
|
|
78
|
+
async listItemsByPk(pk, opts, mergeOptions, options) {
|
|
79
|
+
const baseResult = await this.dataService.listItemsByPk(pk, opts);
|
|
80
|
+
if (!mergeOptions?.latestFlg || !options) {
|
|
81
|
+
return baseResult;
|
|
82
|
+
}
|
|
83
|
+
const userContext = (0, user_1.getUserContext)(options.invokeContext);
|
|
84
|
+
const userId = userContext?.userId;
|
|
85
|
+
if (!userId)
|
|
86
|
+
return baseResult;
|
|
87
|
+
const tenantCode = (0, key_1.getTenantCode)(pk);
|
|
88
|
+
if (!tenantCode) {
|
|
89
|
+
return baseResult;
|
|
90
|
+
}
|
|
91
|
+
const sessions = await this.sessionService.listByUser(userId, tenantCode, this.moduleTableName);
|
|
92
|
+
if (!sessions.length)
|
|
93
|
+
return baseResult;
|
|
94
|
+
this.logger.debug(`listItemsByPk merge — ${sessions.length} sessions`, {
|
|
95
|
+
pk,
|
|
96
|
+
});
|
|
97
|
+
// Map preserves insertion order for existing items
|
|
98
|
+
const itemMap = new Map(baseResult.items.map((item) => [item.id, item]));
|
|
99
|
+
// Separate array to collect newly created items so they can be prepended
|
|
100
|
+
const newItems = [];
|
|
101
|
+
const skPrefix = `${this.moduleTableName}${constants_1.KEY_SEPARATOR}`;
|
|
102
|
+
for (const session of sessions) {
|
|
103
|
+
if (!session.sk.startsWith(skPrefix)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const itemId = session.sk.slice(skPrefix.length);
|
|
107
|
+
const existing = itemMap.get(itemId);
|
|
108
|
+
const cmdPk = pk;
|
|
109
|
+
let skBase;
|
|
110
|
+
if (existing?.sk) {
|
|
111
|
+
skBase = (0, key_1.removeSortKeyVersion)(existing.sk);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Since we already know the exact PK from the method parameter,
|
|
115
|
+
// we can safely extract the skBase without strict 2-segment assumptions.
|
|
116
|
+
skBase = (0, key_1.sortKeyBaseFromId)(pk, itemId);
|
|
117
|
+
if (!skBase) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const cmd = await this.commandService.getItem({
|
|
122
|
+
pk: cmdPk,
|
|
123
|
+
sk: (0, key_1.addSortKeyVersion)(skBase, session.version),
|
|
124
|
+
});
|
|
125
|
+
if (!cmd)
|
|
126
|
+
continue;
|
|
127
|
+
const transformed = (0, transform_1.transformCommandToData)(cmd, existing);
|
|
128
|
+
if (cmd.isDeleted) {
|
|
129
|
+
// Delete the item from the map
|
|
130
|
+
itemMap.delete(itemId);
|
|
131
|
+
}
|
|
132
|
+
else if (itemMap.has(itemId)) {
|
|
133
|
+
// Update: preserves the original sort order in the Map
|
|
134
|
+
itemMap.set(itemId, new interfaces_1.DataEntity(transformed));
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Create-new: push to the newItems array
|
|
138
|
+
newItems.push(new interfaces_1.DataEntity(transformed));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Sort newly created items by createdAt descending
|
|
142
|
+
if (newItems.length > 1) {
|
|
143
|
+
newItems.sort((a, b) => {
|
|
144
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
145
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
146
|
+
return timeB - timeA;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return new interfaces_1.DataListEntity({
|
|
150
|
+
lastSk: baseResult.lastSk,
|
|
151
|
+
// Prepend newly created items to the top of the list
|
|
152
|
+
items: [...newItems, ...itemMap.values()],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* List items from an external source (e.g., RDS/Elasticsearch) with an
|
|
157
|
+
* optional merge of pending async commands.
|
|
158
|
+
*
|
|
159
|
+
* When `mergeOptions.latestFlg` is true, the result is augmented with any
|
|
160
|
+
* pending writes the current user has not yet seen reflected in the query
|
|
161
|
+
* source:
|
|
162
|
+
*
|
|
163
|
+
* - **update** — the existing item is replaced in-place via `transformCommand`,
|
|
164
|
+
* preserving its position. `existing` is passed to the transformer so join
|
|
165
|
+
* data (e.g. related RDS rows) can be carried over.
|
|
166
|
+
* - **delete** — the item is removed and `total` is decremented.
|
|
167
|
+
* - **create-new** — the item is transformed, optionally filtered by
|
|
168
|
+
* `matchesFilter`, and prepended to the top of the result sorted by
|
|
169
|
+
* `updatedAt` descending. `total` is incremented.
|
|
170
|
+
*
|
|
171
|
+
* > **Sort-order note**: Prepended create-new items are not integrated into
|
|
172
|
+
* > the caller's sort order (e.g. by `name` or `code`). They will appear
|
|
173
|
+
* > at the top of the result until the async Stream → RDS sync completes and
|
|
174
|
+
* > the next read returns fully sorted data. This is intentional — the
|
|
175
|
+
* > guarantee is visibility, not sort position.
|
|
176
|
+
*
|
|
177
|
+
* > **Pagination note**: `total` reflects the adjusted count after merge.
|
|
178
|
+
* > However, prepended items sit outside the RDS `LIMIT`/`OFFSET` window, so
|
|
179
|
+
* > on page 2+ the caller may see a one-item overlap or gap until sync
|
|
180
|
+
* > completes. For most UX patterns (redirect-after-write, optimistic UI)
|
|
181
|
+
* > this is not noticeable.
|
|
182
|
+
*/
|
|
183
|
+
async listItems(query, mergeOptions, options) {
|
|
184
|
+
const baseResult = await query();
|
|
185
|
+
if (!mergeOptions?.latestFlg || !options) {
|
|
186
|
+
return baseResult;
|
|
187
|
+
}
|
|
188
|
+
const userContext = (0, user_1.getUserContext)(options.invokeContext);
|
|
189
|
+
const userId = userContext?.userId;
|
|
190
|
+
if (!userId)
|
|
191
|
+
return baseResult;
|
|
192
|
+
const tenantCode = userContext.tenantCode;
|
|
193
|
+
if (!tenantCode)
|
|
194
|
+
return baseResult;
|
|
195
|
+
const sessions = await this.sessionService.listByUser(userId, tenantCode, this.moduleTableName);
|
|
196
|
+
if (!sessions.length)
|
|
197
|
+
return baseResult;
|
|
198
|
+
this.logger.debug(`listItems merge — ${sessions.length} sessions`, {
|
|
199
|
+
tenantCode,
|
|
200
|
+
});
|
|
201
|
+
const itemMap = new Map(baseResult.items.map((item) => [item.id, item]));
|
|
202
|
+
const newItems = [];
|
|
203
|
+
let adjustedTotal = baseResult.total;
|
|
204
|
+
const skPrefix = `${this.moduleTableName}${constants_1.KEY_SEPARATOR}`;
|
|
205
|
+
for (const session of sessions) {
|
|
206
|
+
if (!session.sk.startsWith(skPrefix)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const itemId = session.sk.slice(skPrefix.length);
|
|
210
|
+
const existing = itemMap.get(itemId);
|
|
211
|
+
let cmdPk;
|
|
212
|
+
let skBase;
|
|
213
|
+
const existingSk = existing?.sk;
|
|
214
|
+
const existingPk = existing?.pk;
|
|
215
|
+
if (existing && existingSk && existingPk) {
|
|
216
|
+
cmdPk = existingPk;
|
|
217
|
+
skBase = (0, key_1.removeSortKeyVersion)(existingSk);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
// Fallback: Strictly parse the ID assuming a 2-segment PK format ({type}#{tenantCode})
|
|
221
|
+
const parsed = (0, key_1.parseTwoSegmentPkSkFromId)(itemId);
|
|
222
|
+
if (!parsed) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
cmdPk = parsed.pk;
|
|
226
|
+
skBase = parsed.skBase;
|
|
227
|
+
}
|
|
228
|
+
if (!cmdPk || !skBase) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const cmd = await this.commandService.getItem({
|
|
232
|
+
pk: cmdPk,
|
|
233
|
+
sk: (0, key_1.addSortKeyVersion)(skBase, session.version),
|
|
234
|
+
});
|
|
235
|
+
if (!cmd)
|
|
236
|
+
continue;
|
|
237
|
+
const transformed = mergeOptions.transformCommand(cmd, existing);
|
|
238
|
+
if (cmd.isDeleted) {
|
|
239
|
+
// Delete the item from the map
|
|
240
|
+
if (itemMap.has(itemId)) {
|
|
241
|
+
itemMap.delete(itemId);
|
|
242
|
+
adjustedTotal--;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (itemMap.has(itemId)) {
|
|
246
|
+
// Update: preserves original position
|
|
247
|
+
itemMap.set(itemId, transformed);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Create-new: check if it satisfies the current query filters
|
|
251
|
+
if (!mergeOptions.matchesFilter ||
|
|
252
|
+
mergeOptions.matchesFilter(transformed)) {
|
|
253
|
+
newItems.push(transformed);
|
|
254
|
+
adjustedTotal++;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Sort newly created items by createdAt descending (safely fallback to 0 if field is missing)
|
|
259
|
+
if (newItems.length > 1) {
|
|
260
|
+
newItems.sort((a, b) => {
|
|
261
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
262
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
263
|
+
return timeB - timeA;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
total: adjustedTotal,
|
|
268
|
+
items: [...newItems, ...itemMap.values()],
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
exports.Repository = Repository;
|
|
273
|
+
exports.Repository = Repository = Repository_1 = __decorate([
|
|
274
|
+
(0, common_1.Injectable)(),
|
|
275
|
+
__param(0, (0, common_1.Inject)(command_module_definition_1.MODULE_OPTIONS_TOKEN)),
|
|
276
|
+
__metadata("design:paramtypes", [Object, data_service_1.DataService,
|
|
277
|
+
command_service_1.CommandService,
|
|
278
|
+
session_service_1.SessionService])
|
|
279
|
+
], Repository);
|
|
280
|
+
//# sourceMappingURL=repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/commands/repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,14 +10,15 @@ exports.DataStoreModule = void 0;
|
|
|
10
10
|
const common_1 = require("@nestjs/common");
|
|
11
11
|
const dynamodb_service_1 = require("./dynamodb.service");
|
|
12
12
|
const s3_service_1 = require("./s3.service");
|
|
13
|
+
const session_service_1 = require("./session.service");
|
|
13
14
|
let DataStoreModule = class DataStoreModule {
|
|
14
15
|
};
|
|
15
16
|
exports.DataStoreModule = DataStoreModule;
|
|
16
17
|
exports.DataStoreModule = DataStoreModule = __decorate([
|
|
17
18
|
(0, common_1.Global)(),
|
|
18
19
|
(0, common_1.Module)({
|
|
19
|
-
providers: [dynamodb_service_1.DynamoDbService, s3_service_1.S3Service],
|
|
20
|
-
exports: [dynamodb_service_1.DynamoDbService, s3_service_1.S3Service],
|
|
20
|
+
providers: [dynamodb_service_1.DynamoDbService, s3_service_1.S3Service, session_service_1.SessionService],
|
|
21
|
+
exports: [dynamodb_service_1.DynamoDbService, s3_service_1.S3Service, session_service_1.SessionService],
|
|
21
22
|
})
|
|
22
23
|
], DataStoreModule);
|
|
23
24
|
//# sourceMappingURL=data-store.module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-store.module.js","sourceRoot":"","sources":["../../src/data-store/data-store.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA+C;AAE/C,yDAAoD;AACpD,6CAAwC;
|
|
1
|
+
{"version":3,"file":"data-store.module.js","sourceRoot":"","sources":["../../src/data-store/data-store.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA+C;AAE/C,yDAAoD;AACpD,6CAAwC;AACxC,uDAAkD;AAO3C,IAAM,eAAe,GAArB,MAAM,eAAe;CAAG,CAAA;AAAlB,0CAAe;0BAAf,eAAe;IAL3B,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,kCAAe,EAAE,sBAAS,EAAE,gCAAc,CAAC;QACvD,OAAO,EAAE,CAAC,kCAAe,EAAE,sBAAS,EAAE,gCAAc,CAAC;KACtD,CAAC;GACW,eAAe,CAAG"}
|
package/dist/data-store/index.js
CHANGED
|
@@ -17,4 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./data-store.module"), exports);
|
|
18
18
|
__exportStar(require("./dynamodb.service"), exports);
|
|
19
19
|
__exportStar(require("./s3.service"), exports);
|
|
20
|
+
__exportStar(require("./session.interface"), exports);
|
|
21
|
+
__exportStar(require("./session.service"), exports);
|
|
20
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data-store/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAmC;AACnC,qDAAkC;AAClC,+CAA4B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data-store/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAmC;AACnC,qDAAkC;AAClC,+CAA4B;AAC5B,sDAAmC;AACnC,oDAAiC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SessionItem {
|
|
2
|
+
/** pk: {userId}#{tenantCode} */
|
|
3
|
+
pk: string;
|
|
4
|
+
/** sk: {moduleTableName}#{itemId} — module `tableName` from CommandModule */
|
|
5
|
+
sk: string;
|
|
6
|
+
/** Version of the command — used to fetch exact command record */
|
|
7
|
+
version: number;
|
|
8
|
+
/** Unix timestamp TTL — auto-expired by DynamoDB */
|
|
9
|
+
ttl: number;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.interface.js","sourceRoot":"","sources":["../../src/data-store/session.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ConfigService } from '@nestjs/config';
|
|
2
|
+
import { DynamoDbService } from './dynamodb.service';
|
|
3
|
+
import { SessionItem } from './session.interface';
|
|
4
|
+
export declare class SessionService {
|
|
5
|
+
private readonly dynamoDbService;
|
|
6
|
+
private readonly config;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
private readonly sessionTableName;
|
|
9
|
+
private readonly ttlSeconds;
|
|
10
|
+
/** When unset or invalid, session rows are not written (RYW read path still works on empty). */
|
|
11
|
+
private readonly sessionWritesEnabled;
|
|
12
|
+
constructor(dynamoDbService: DynamoDbService, config: ConfigService);
|
|
13
|
+
/**
|
|
14
|
+
* Build session pk: {userId}#{tenantCode}
|
|
15
|
+
*/
|
|
16
|
+
private buildPk;
|
|
17
|
+
/**
|
|
18
|
+
* Build session sk: {moduleTableName}#{itemId}
|
|
19
|
+
*/
|
|
20
|
+
private buildSk;
|
|
21
|
+
private calculateTtl;
|
|
22
|
+
/**
|
|
23
|
+
* Write a session entry after a successful async command publish.
|
|
24
|
+
* Called by CommandService after publishAsync only.
|
|
25
|
+
* No-ops when RYW session TTL is not configured (`sessionWritesEnabled` is false).
|
|
26
|
+
*/
|
|
27
|
+
put(userId: string, tenantCode: string, moduleTableName: string, itemId: string, version: number): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Get a single session entry for a specific item.
|
|
30
|
+
* Returns `null` without querying DynamoDB when session writes are disabled.
|
|
31
|
+
*/
|
|
32
|
+
get(userId: string, tenantCode: string, moduleTableName: string, itemId: string): Promise<SessionItem | null>;
|
|
33
|
+
/**
|
|
34
|
+
* List session entries for a user scoped to a command module.
|
|
35
|
+
*
|
|
36
|
+
* Queries the session table by `{userId}#{tenantCode}` and filters to entries
|
|
37
|
+
* whose sort key begins with `{moduleTableName}#`, returning only sessions
|
|
38
|
+
* that belong to the given module.
|
|
39
|
+
*
|
|
40
|
+
* @param userId - The ID of the requesting user (from JWT `sub` claim).
|
|
41
|
+
* @param tenantCode - The tenant the user is operating under.
|
|
42
|
+
* @param moduleTableName - The `tableName` from `CommandModuleOptions` — used
|
|
43
|
+
* as the sort key prefix to scope results to a single command module.
|
|
44
|
+
* @param limit - Maximum number of session entries to fetch. Defaults to
|
|
45
|
+
* `MAX_SESSION_ENTRIES` to prevent silent truncation from the underlying
|
|
46
|
+
* DynamoDB query's default limit of 10. Callers should rarely need to
|
|
47
|
+
* override this.
|
|
48
|
+
* @returns The matching session entries, or an empty array if none exist or
|
|
49
|
+
* session writes are disabled.
|
|
50
|
+
*/
|
|
51
|
+
listByUser(userId: string, tenantCode: string, moduleTableName: string, limit?: number): Promise<SessionItem[]>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var SessionService_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.SessionService = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const config_1 = require("@nestjs/config");
|
|
16
|
+
const constants_1 = require("../constants");
|
|
17
|
+
const dynamodb_service_1 = require("./dynamodb.service");
|
|
18
|
+
/**
|
|
19
|
+
* Upper-bound for session entries fetched per user/module in a single query.
|
|
20
|
+
* Sessions are short-lived (RYW_SESSION_TTL_MINUTES) so this stays cheap.
|
|
21
|
+
* Prevents silent truncation from DynamoDbService's default limit of 10.
|
|
22
|
+
*/
|
|
23
|
+
const MAX_SESSION_ENTRIES = 1000;
|
|
24
|
+
const SESSION_TABLE_SUFFIX = 'session';
|
|
25
|
+
let SessionService = SessionService_1 = class SessionService {
|
|
26
|
+
constructor(dynamoDbService, config) {
|
|
27
|
+
this.dynamoDbService = dynamoDbService;
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.logger = new common_1.Logger(SessionService_1.name);
|
|
30
|
+
const nodeEnv = this.config.get('NODE_ENV');
|
|
31
|
+
const appName = this.config.get('APP_NAME');
|
|
32
|
+
this.sessionTableName = `${nodeEnv}-${appName}-${SESSION_TABLE_SUFFIX}`;
|
|
33
|
+
const ttlMinutes = Number(this.config.get('RYW_SESSION_TTL_MINUTES'));
|
|
34
|
+
this.sessionWritesEnabled = !Number.isNaN(ttlMinutes) && ttlMinutes > 0;
|
|
35
|
+
this.ttlSeconds = this.sessionWritesEnabled ? ttlMinutes * 60 : 0;
|
|
36
|
+
if (this.sessionWritesEnabled) {
|
|
37
|
+
this.logger.log(`Session table: ${this.sessionTableName}, RYW TTL: ${ttlMinutes}m`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.logger.log(`Session table: ${this.sessionTableName}, RYW session writes disabled (set RYW_SESSION_TTL_MINUTES to a positive number to enable)`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build session pk: {userId}#{tenantCode}
|
|
45
|
+
*/
|
|
46
|
+
buildPk(userId, tenantCode) {
|
|
47
|
+
return `${userId}${constants_1.KEY_SEPARATOR}${tenantCode}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build session sk: {moduleTableName}#{itemId}
|
|
51
|
+
*/
|
|
52
|
+
buildSk(moduleTableName, itemId) {
|
|
53
|
+
return `${moduleTableName}${constants_1.KEY_SEPARATOR}${itemId}`;
|
|
54
|
+
}
|
|
55
|
+
calculateTtl() {
|
|
56
|
+
return Math.floor(Date.now() / 1000) + this.ttlSeconds;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Write a session entry after a successful async command publish.
|
|
60
|
+
* Called by CommandService after publishAsync only.
|
|
61
|
+
* No-ops when RYW session TTL is not configured (`sessionWritesEnabled` is false).
|
|
62
|
+
*/
|
|
63
|
+
async put(userId, tenantCode, moduleTableName, itemId, version) {
|
|
64
|
+
if (!this.sessionWritesEnabled) {
|
|
65
|
+
this.logger.debug('session put skipped: RYW_SESSION_TTL_MINUTES not enabled');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const item = {
|
|
69
|
+
pk: this.buildPk(userId, tenantCode),
|
|
70
|
+
sk: this.buildSk(moduleTableName, itemId),
|
|
71
|
+
version,
|
|
72
|
+
ttl: this.calculateTtl(),
|
|
73
|
+
};
|
|
74
|
+
this.logger.debug('session put::', item);
|
|
75
|
+
await this.dynamoDbService.putItem(this.sessionTableName, item);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get a single session entry for a specific item.
|
|
79
|
+
* Returns `null` without querying DynamoDB when session writes are disabled.
|
|
80
|
+
*/
|
|
81
|
+
async get(userId, tenantCode, moduleTableName, itemId) {
|
|
82
|
+
if (!this.sessionWritesEnabled) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const result = await this.dynamoDbService.getItem(this.sessionTableName, {
|
|
86
|
+
pk: this.buildPk(userId, tenantCode),
|
|
87
|
+
sk: this.buildSk(moduleTableName, itemId),
|
|
88
|
+
});
|
|
89
|
+
return result ?? null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* List session entries for a user scoped to a command module.
|
|
93
|
+
*
|
|
94
|
+
* Queries the session table by `{userId}#{tenantCode}` and filters to entries
|
|
95
|
+
* whose sort key begins with `{moduleTableName}#`, returning only sessions
|
|
96
|
+
* that belong to the given module.
|
|
97
|
+
*
|
|
98
|
+
* @param userId - The ID of the requesting user (from JWT `sub` claim).
|
|
99
|
+
* @param tenantCode - The tenant the user is operating under.
|
|
100
|
+
* @param moduleTableName - The `tableName` from `CommandModuleOptions` — used
|
|
101
|
+
* as the sort key prefix to scope results to a single command module.
|
|
102
|
+
* @param limit - Maximum number of session entries to fetch. Defaults to
|
|
103
|
+
* `MAX_SESSION_ENTRIES` to prevent silent truncation from the underlying
|
|
104
|
+
* DynamoDB query's default limit of 10. Callers should rarely need to
|
|
105
|
+
* override this.
|
|
106
|
+
* @returns The matching session entries, or an empty array if none exist or
|
|
107
|
+
* session writes are disabled.
|
|
108
|
+
*/
|
|
109
|
+
async listByUser(userId, tenantCode, moduleTableName, limit = MAX_SESSION_ENTRIES) {
|
|
110
|
+
if (!this.sessionWritesEnabled) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
const pk = this.buildPk(userId, tenantCode);
|
|
114
|
+
const skPrefix = `${moduleTableName}${constants_1.KEY_SEPARATOR}`;
|
|
115
|
+
const result = await this.dynamoDbService.listItemsByPk(this.sessionTableName, pk, {
|
|
116
|
+
skExpression: 'begins_with(sk, :skPrefix)',
|
|
117
|
+
skAttributeValues: { ':skPrefix': skPrefix },
|
|
118
|
+
}, undefined, limit);
|
|
119
|
+
return result?.items ?? [];
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
exports.SessionService = SessionService;
|
|
123
|
+
exports.SessionService = SessionService = SessionService_1 = __decorate([
|
|
124
|
+
(0, common_1.Injectable)(),
|
|
125
|
+
__metadata("design:paramtypes", [dynamodb_service_1.DynamoDbService,
|
|
126
|
+
config_1.ConfigService])
|
|
127
|
+
], SessionService);
|
|
128
|
+
//# sourceMappingURL=session.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.service.js","sourceRoot":"","sources":["../../src/data-store/session.service.ts"],"names":[],"mappings":"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"}
|
package/dist/env.validation.d.ts
CHANGED
|
@@ -27,5 +27,6 @@ export declare class EnvironmentVariables {
|
|
|
27
27
|
SES_REGION: string;
|
|
28
28
|
SES_FROM_EMAIL: string;
|
|
29
29
|
REQUEST_BODY_SIZE_LIMIT: string;
|
|
30
|
+
RYW_SESSION_TTL_MINUTES: number;
|
|
30
31
|
}
|
|
31
32
|
export declare function getValidateConfig<T extends EnvironmentVariables>(cls?: ClassConstructor<T>): (config: Record<string, unknown>) => EnvironmentVariables;
|
package/dist/env.validation.js
CHANGED
|
@@ -120,6 +120,11 @@ __decorate([
|
|
|
120
120
|
(0, class_validator_1.IsOptional)(),
|
|
121
121
|
__metadata("design:type", String)
|
|
122
122
|
], EnvironmentVariables.prototype, "REQUEST_BODY_SIZE_LIMIT", void 0);
|
|
123
|
+
__decorate([
|
|
124
|
+
(0, class_validator_1.IsNumber)(),
|
|
125
|
+
(0, class_validator_1.IsOptional)(),
|
|
126
|
+
__metadata("design:type", Number)
|
|
127
|
+
], EnvironmentVariables.prototype, "RYW_SESSION_TTL_MINUTES", void 0);
|
|
123
128
|
function getValidateConfig(cls) {
|
|
124
129
|
return function validate(config) {
|
|
125
130
|
const validatedConfig = (0, class_transformer_1.plainToInstance)(cls || EnvironmentVariables, config, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.validation.js","sourceRoot":"","sources":["../src/env.validation.ts"],"names":[],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"env.validation.js","sourceRoot":"","sources":["../src/env.validation.ts"],"names":[],"mappings":";;;;;;;;;;;;AAyFA,8CAoBC;AA7GD,yDAAqE;AACrE,qDAQwB;AAExB,IAAY,WAKX;AALD,WAAY,WAAW;IACrB,8BAAe,CAAA;IACf,kCAAmB,CAAA;IACnB,kCAAmB,CAAA;IACnB,8BAAe,CAAA;AACjB,CAAC,EALW,WAAW,2BAAX,WAAW,QAKtB;AAED,MAAa,oBAAoB;CAqEhC;AArED,oDAqEC;AAnEC;IADC,IAAA,wBAAM,EAAC,WAAW,CAAC;;sDACC;AAErB;IADC,IAAA,0BAAQ,GAAE;;sDACK;AAIhB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;sDACG;AAGhB;IADC,IAAA,2BAAS,GAAE;;mEACkB;AAG9B;IADC,IAAA,0BAAQ,GAAE;;uDACM;AAIjB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;+DACY;AAGzB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;6DACY;AAEvB;IADC,IAAA,4BAAU,GAAE;;kEACe;AAG5B;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;yDACM;AAGnB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;uDACI;AAEjB;IADC,IAAA,0BAAQ,GAAE;;4DACW;AAItB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;0DACO;AAGpB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;wDACK;AAElB;IADC,IAAA,0BAAQ,GAAE;;6DACY;AAIvB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;0DACO;AAGpB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;wDACK;AAIlB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;8DACW;AAIxB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;0DACO;AAGpB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;wDACK;AAElB;IADC,IAAA,0BAAQ,GAAE;;4DACW;AAItB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;qEACkB;AAI/B;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;qEACkB;AAGjC,SAAgB,iBAAiB,CAC/B,GAAyB;IAEzB,OAAO,SAAS,QAAQ,CAAC,MAA+B;QACtD,MAAM,eAAe,GAAG,IAAA,mCAAe,EACrC,GAAG,IAAI,oBAAoB,EAC3B,MAAM,EACN;YACE,wBAAwB,EAAE,IAAI;SAC/B,CACF,CAAA;QACD,MAAM,MAAM,GAAG,IAAA,8BAAY,EAAC,eAAe,EAAE;YAC3C,qBAAqB,EAAE,KAAK;SAC7B,CAAC,CAAA;QAEF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;QACpC,CAAC;QACD,OAAO,eAAe,CAAA;IACxB,CAAC,CAAA;AACH,CAAC"}
|
package/dist/helpers/index.d.ts
CHANGED
package/dist/helpers/index.js
CHANGED
|
@@ -21,6 +21,7 @@ __exportStar(require("./key"), exports);
|
|
|
21
21
|
__exportStar(require("./object"), exports);
|
|
22
22
|
__exportStar(require("./serializer"), exports);
|
|
23
23
|
__exportStar(require("./source"), exports);
|
|
24
|
+
__exportStar(require("./transform"), exports);
|
|
24
25
|
// Re-export serialization helpers for convenience
|
|
25
26
|
var serializer_1 = require("./serializer");
|
|
26
27
|
Object.defineProperty(exports, "deserializeToInternal", { enumerable: true, get: function () { return serializer_1.deserializeToInternal; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6CAA0B;AAC1B,+CAA4B;AAC5B,wCAAqB;AACrB,2CAAwB;AACxB,+CAA4B;AAC5B,2CAAwB;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6CAA0B;AAC1B,+CAA4B;AAC5B,wCAAqB;AACrB,2CAAwB;AACxB,+CAA4B;AAC5B,2CAAwB;AACxB,8CAA2B;AAE3B,kDAAkD;AAClD,2CAAyE;AAAhE,mHAAA,qBAAqB,OAAA;AAAE,iHAAA,mBAAmB,OAAA;AAEtC,QAAA,iBAAiB,GAC5B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB;IACtC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B;IACzC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAA"}
|
package/dist/helpers/key.d.ts
CHANGED
|
@@ -2,6 +2,19 @@ export declare function addSortKeyVersion(sk: string, version: number): string;
|
|
|
2
2
|
export declare function getSortKeyVersion(sk: string): number;
|
|
3
3
|
export declare function removeSortKeyVersion(sk: string): string;
|
|
4
4
|
export declare function generateId(pk: string, sk: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Inverse of {@link generateId}: extracts the base sort key from a composite id (`pk#skBase`).
|
|
7
|
+
*/
|
|
8
|
+
export declare function sortKeyBaseFromId(pk: string, itemId: string): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Parses a composite {@link generateId} when partition key is always
|
|
11
|
+
* `{type}#{tenantCode}` (exactly two `#`-separated segments). The remainder of
|
|
12
|
+
* `itemId` after that prefix is `skBase` (may contain `#`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseTwoSegmentPkSkFromId(itemId: string): {
|
|
15
|
+
pk: string;
|
|
16
|
+
skBase: string;
|
|
17
|
+
} | undefined;
|
|
5
18
|
export declare function getTenantCode(pk: string): string;
|
|
6
19
|
export declare function isS3AttributeKey(attributes: any): boolean;
|
|
7
20
|
export declare function toS3AttributeKey(bucket: string, key: string): string;
|