@foal/typeorm
Version:
FoalTS integration of TypeORM
181 lines (180 loc) • 6.79 kB
JavaScript
;
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;
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 })
// Use snake case because camelCase does not work well with PostgreSQL.
// tslint:disable-next-line: variable-name
,
__metadata("design:type", Number)
], DatabaseSession.prototype, "user_id", 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') {
throw new Error('[TypeORMStore] Impossible to save the session. The user ID must be a number.');
}
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,
// tslint:disable-next-line
user_id: state.userId ?? 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,
// Note: session.user_id is actually a number or null (not undefined).
userId: session.user_id,
};
}
async update(state, maxInactivity) {
if (typeof state.userId === 'string') {
throw new Error('[TypeORMStore] Impossible to save the session. The user ID must be a number.');
}
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,
// tslint:disable-next-line
user_id: state.userId ?? 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 sessions = await this.repository
.createQueryBuilder()
.select('DISTINCT user_id')
.where({
user_id: (0, typeorm_1.Not)((0, typeorm_1.IsNull)())
})
.getRawMany();
return sessions.map(({ user_id }) => user_id);
}
async destroyAllSessionsOf(userId) {
await this.repository.delete({ user_id: userId });
}
async getSessionIDsOf(userId) {
const databaseSessions = await this.repository.find({
// Do not select unused fields.
select: { id: true },
where: { user_id: userId },
});
return databaseSessions.map(dbSession => dbSession.id);
}
}
exports.TypeORMStore = TypeORMStore;