UNPKG

@mbc-cqrs-serverless/core

Version:
146 lines 6.72 kB
"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