UNPKG

@foal/typeorm

Version:

FoalTS integration of TypeORM

214 lines (213 loc) 8.33 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); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TypeORMStore = exports.DatabaseSession = void 0; // 3p const core_1 = require("@foal/core"); const typeorm_1 = require("typeorm"); let DatabaseSession = class DatabaseSession extends typeorm_1.BaseEntity { id; // Use snake case because camelCase does not work well with PostgreSQL. // tslint:disable-next-line: variable-name user_id; // Use snake case because camelCase does not work well with PostgreSQL. // tslint:disable-next-line: variable-name user_id_str; content; flash; // Use snake case because camelCase does not work well with PostgreSQL. // tslint:disable-next-line: variable-name updated_at; // Use snake case because camelCase does not work well with PostgreSQL. // tslint:disable-next-line: variable-name created_at; }; exports.DatabaseSession = DatabaseSession; __decorate([ (0, typeorm_1.PrimaryColumn)({ length: 44 }), __metadata("design:type", String) ], DatabaseSession.prototype, "id", void 0); __decorate([ (0, typeorm_1.Column)({ nullable: true, type: 'int' }) // Use snake case because camelCase does not work well with PostgreSQL. // tslint:disable-next-line: variable-name , __metadata("design:type", Object) ], DatabaseSession.prototype, "user_id", void 0); __decorate([ (0, typeorm_1.Column)({ nullable: true, type: 'varchar', length: 64 }) // Use snake case because camelCase does not work well with PostgreSQL. // tslint:disable-next-line: variable-name , __metadata("design:type", Object) ], DatabaseSession.prototype, "user_id_str", void 0); __decorate([ (0, typeorm_1.Column)({ type: 'text' }), __metadata("design:type", String) ], DatabaseSession.prototype, "content", void 0); __decorate([ (0, typeorm_1.Column)({ type: 'text' }), __metadata("design:type", String) ], DatabaseSession.prototype, "flash", void 0); __decorate([ (0, typeorm_1.Column)() // Use snake case because camelCase does not work well with PostgreSQL. // tslint:disable-next-line: variable-name , __metadata("design:type", Number) ], DatabaseSession.prototype, "updated_at", void 0); __decorate([ (0, typeorm_1.Column)() // Use snake case because camelCase does not work well with PostgreSQL. // tslint:disable-next-line: variable-name , __metadata("design:type", Number) ], DatabaseSession.prototype, "created_at", void 0); exports.DatabaseSession = DatabaseSession = __decorate([ (0, typeorm_1.Entity)({ name: 'sessions' }) ], DatabaseSession); /** * TypeORM store. * * @export * @class TypeORMStore * @extends {SessionStore} */ class TypeORMStore extends core_1.SessionStore { get repository() { return DatabaseSession.getRepository(); } async save(state, maxInactivity) { if (typeof state.userId === 'string' && state.userId.length > 64) { throw new Error('[TypeORMStore] Impossible to save the session. The user ID is too long (max 64 characters).'); } const userIdNumber = typeof state.userId === 'number' ? state.userId : null; const userIdStr = typeof state.userId === 'string' ? state.userId : null; try { await this.repository .createQueryBuilder() .insert() .values({ content: JSON.stringify(state.content), created_at: state.createdAt, flash: JSON.stringify(state.flash), id: state.id, updated_at: state.updatedAt, user_id: userIdNumber ?? undefined, user_id_str: userIdStr ?? undefined, }) .execute(); } catch (error) { // SQLite, MySQL, PostgreSQL if (['SQLITE_CONSTRAINT', 'ER_DUP_ENTRY', '23505'].includes(error.code) || error.message === 'SqliteError: UNIQUE constraint failed: sessions.id') { throw new core_1.SessionAlreadyExists(); } // TODO: test this line. throw error; } } async read(id) { const session = await this.repository.findOneBy({ id }); if (!session) { return null; } return { content: JSON.parse(session.content), createdAt: session.created_at, flash: JSON.parse(session.flash), id: session.id, updatedAt: session.updated_at, userId: session.user_id ?? session.user_id_str, }; } async update(state, maxInactivity) { if (typeof state.userId === 'string' && state.userId.length > 64) { throw new Error('[TypeORMStore] Impossible to save the session. The user ID is too long (max 64 characters).'); } const userIdNumber = typeof state.userId === 'number' ? state.userId : null; const userIdStr = typeof state.userId === 'string' ? state.userId : null; const dbSession = this.repository.create({ content: JSON.stringify(state.content), created_at: state.createdAt, flash: JSON.stringify(state.flash), id: state.id, updated_at: state.updatedAt, user_id: userIdNumber ?? undefined, user_id_str: userIdStr ?? undefined, }); // The "save" method performs an UPSERT. await this.repository.save(dbSession); } async destroy(sessionID) { await this.repository .delete({ id: sessionID }); } async clear() { await this.repository .clear(); } async cleanUpExpiredSessions(maxInactivity, maxLifeTime) { await this.repository .createQueryBuilder() .delete() .where([ { created_at: (0, typeorm_1.LessThan)(Math.trunc(Date.now() / 1000) - maxLifeTime) }, { updated_at: (0, typeorm_1.LessThan)(Math.trunc(Date.now() / 1000) - maxInactivity) }, ]) .execute(); } async getAuthenticatedUserIds() { const [numberSessions, stringSessions] = await Promise.all([ this.repository .createQueryBuilder() .select('DISTINCT user_id') .where({ user_id: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()) }) .getRawMany(), this.repository .createQueryBuilder() .select('DISTINCT user_id_str') .where({ user_id_str: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()) }) .getRawMany(), ]); return [ ...numberSessions.map(({ user_id }) => user_id), ...stringSessions.map(({ user_id_str }) => user_id_str), ]; } async destroyAllSessionsOf(userId) { if (typeof userId === 'number') { await this.repository.delete({ user_id: userId }); } else { await this.repository.delete({ user_id_str: userId }); } } async getSessionIDsOf(userId) { const where = typeof userId === 'number' ? { user_id: userId } : { user_id_str: userId }; const databaseSessions = await this.repository.find({ // Do not select unused fields. select: { id: true }, where, }); return databaseSessions.map(dbSession => dbSession.id); } } exports.TypeORMStore = TypeORMStore;