@mbc-cqrs-serverless/core
Version:
CQRS and event base core
146 lines • 6.72 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var SessionService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const constants_1 = require("../constants");
const dynamodb_service_1 = require("./dynamodb.service");
/**
* Upper-bound for session entries fetched per user/module in a single query.
* Sessions are short-lived (RYW_SESSION_TTL_MINUTES) so this stays cheap.
* Prevents silent truncation from DynamoDbService's default limit of 10.
*/
const MAX_SESSION_ENTRIES = 1000;
const SESSION_TABLE_SUFFIX = 'session';
let SessionService = SessionService_1 = class SessionService {
constructor(dynamoDbService, config) {
this.dynamoDbService = dynamoDbService;
this.config = config;
this.logger = new common_1.Logger(SessionService_1.name);
const nodeEnv = this.config.get('NODE_ENV');
const appName = this.config.get('APP_NAME');
this.sessionTableName = `${nodeEnv}-${appName}-${SESSION_TABLE_SUFFIX}`;
const ttlMinutes = Number(this.config.get('RYW_SESSION_TTL_MINUTES'));
this.sessionWritesEnabled = !Number.isNaN(ttlMinutes) && ttlMinutes > 0;
this.ttlSeconds = this.sessionWritesEnabled ? ttlMinutes * 60 : 0;
if (this.sessionWritesEnabled) {
this.logger.log(`Session table: ${this.sessionTableName}, RYW TTL: ${ttlMinutes}m`);
}
else {
this.logger.log(`Session table: ${this.sessionTableName}, RYW session writes disabled (set RYW_SESSION_TTL_MINUTES to a positive number to enable)`);
}
}
/**
* Build session pk: {userId}#{tenantCode}
*/
buildPk(userId, tenantCode) {
return `${userId}${constants_1.KEY_SEPARATOR}${tenantCode}`;
}
/**
* Build session sk: {moduleTableName}#{itemId}
*/
buildSk(moduleTableName, itemId) {
return `${moduleTableName}${constants_1.KEY_SEPARATOR}${itemId}`;
}
calculateTtl() {
return Math.floor(Date.now() / 1000) + this.ttlSeconds;
}
/**
* Write a session entry after a successful async command publish.
* Called by CommandService after publishAsync only.
* No-ops when RYW session TTL is not configured (`sessionWritesEnabled` is false).
*/
async put(userId, tenantCode, moduleTableName, itemId, version) {
if (!this.sessionWritesEnabled) {
this.logger.debug('session put skipped: RYW_SESSION_TTL_MINUTES not enabled');
return;
}
const item = {
pk: this.buildPk(userId, tenantCode),
sk: this.buildSk(moduleTableName, itemId),
version,
ttl: this.calculateTtl(),
};
this.logger.debug('session put::', item);
await this.dynamoDbService.putItem(this.sessionTableName, item);
}
/**
* Get a single session entry for a specific item.
* Returns `null` without querying DynamoDB when session writes are disabled.
*/
async get(userId, tenantCode, moduleTableName, itemId) {
if (!this.sessionWritesEnabled) {
return null;
}
const result = await this.dynamoDbService.getItem(this.sessionTableName, {
pk: this.buildPk(userId, tenantCode),
sk: this.buildSk(moduleTableName, itemId),
});
return result ?? null;
}
/**
* Delete a session entry.
* Used to clean up the session once the data table has caught up to the command version.
*/
async delete(userId, tenantCode, moduleTableName, itemId) {
if (!this.sessionWritesEnabled)
return;
const key = {
pk: this.buildPk(userId, tenantCode),
sk: this.buildSk(moduleTableName, itemId),
};
try {
await this.dynamoDbService.deleteItem(this.sessionTableName, key);
}
catch (error) {
this.logger.warn(`Failed to delete RYW session (non-fatal): ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* List session entries for a user scoped to a command module.
*
* Queries the session table by `{userId}#{tenantCode}` and filters to entries
* whose sort key begins with `{moduleTableName}#`, returning only sessions
* that belong to the given module.
*
* @param userId - The ID of the requesting user (from JWT `sub` claim).
* @param tenantCode - The tenant the user is operating under.
* @param moduleTableName - The `tableName` from `CommandModuleOptions` — used
* as the sort key prefix to scope results to a single command module.
* @param limit - Maximum number of session entries to fetch. Defaults to
* `MAX_SESSION_ENTRIES` to prevent silent truncation from the underlying
* DynamoDB query's default limit of 10. Callers should rarely need to
* override this.
* @returns The matching session entries, or an empty array if none exist or
* session writes are disabled.
*/
async listByUser(userId, tenantCode, moduleTableName, limit = MAX_SESSION_ENTRIES) {
if (!this.sessionWritesEnabled) {
return [];
}
const pk = this.buildPk(userId, tenantCode);
const skPrefix = `${moduleTableName}${constants_1.KEY_SEPARATOR}`;
const result = await this.dynamoDbService.listItemsByPk(this.sessionTableName, pk, {
skExpression: 'begins_with(sk, :skPrefix)',
skAttributeValues: { ':skPrefix': skPrefix },
}, undefined, limit);
return result?.items ?? [];
}
};
exports.SessionService = SessionService;
exports.SessionService = SessionService = SessionService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [dynamodb_service_1.DynamoDbService,
config_1.ConfigService])
], SessionService);
//# sourceMappingURL=session.service.js.map