diff --git a/README.md b/README.md index aafc716..cc070e4 100755 --- a/README.md +++ b/README.md @@ -145,7 +145,6 @@ If you are using a version of Prisma that supports [migrating with native types] Following version `2.0.0`, the `Session` `expires` field was renamed to `expiresAt` to match Prisma's naming convention for date fields. - ## Migrating from versions prior to `1.0.0` In `1.0.0` the public API of this library was reworked. Previously the default export that was a @@ -206,6 +205,8 @@ CUID will be used instead. - `serializer` An object containing `stringify` and `parse` methods compatible with Javascript's `JSON` to override the serializer used. +- `sessionModelName` By default, the session table is called `sessions` and the associated model is `session`, but you can provide a custom model name. This should be the camelCase version of the name. + Three new options were added apart from the work that was already done by [memorystore](https://github.com/roccomuso/memorystore), two of them relate to logging and allow you to inject your own logger object giving you flexibility to log outputs to something like NestJS or whenever you would like, even saving them to disk if that's what you want. And the third is used for testing to round the TTL so that it can be compared do another generated ttl during the assertion. - `logger` Where logs should be outputted to, by default `console`. If set to `false` then logging will be disabled diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 373d762..07da6af 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -16,3 +16,10 @@ model Session { data String expiresAt DateTime } + +model OtherSession { + id String @id + sid String @unique + data String + expiresAt DateTime +} \ No newline at end of file diff --git a/src/@types/options.ts b/src/@types/options.ts index 48e993e..fc6b259 100644 --- a/src/@types/options.ts +++ b/src/@types/options.ts @@ -18,7 +18,7 @@ export type TTLFactory = ( /** * PrismaSessionStore options to alter the way the store behaves */ -export interface IOptions { +export interface IOptions { /** * Interval, in ms, at which PrismaSessionStore will automatically remove * expired sessions. Disabled by default; set to something reasonable. @@ -91,6 +91,12 @@ export interface IOptions { */ ttl?: number | TTLFactory; + /** + * "Session Table Name" + * defines session table name. Defaults to sessions + */ + sessionModelName?: Exclude; + /** * A function to generate the Prisma Record ID for a given session ID * diff --git a/src/@types/prisma.ts b/src/@types/prisma.ts index 1de8ad7..69071df 100644 --- a/src/@types/prisma.ts +++ b/src/@types/prisma.ts @@ -43,15 +43,17 @@ interface IDeleteArgs { where: { sid: string }; } -export interface IPrisma { - session: { +export type IPrisma = Record< + Exclude, + { create(args: ICreateArgs): Promise; delete(args: IDeleteArgs): Promise; deleteMany(args?: unknown): Promise; findMany(args?: IFindManyArgs): Promise; findUnique(args: IFindUniqueArgs): Promise; update(args: IUpdateArgs): Promise; - }; + } +> & { $connect(): Promise; $disconnect(): Promise; -} +}; diff --git a/src/lib/prisma-session-store.spec.ts b/src/lib/prisma-session-store.spec.ts index c5581f3..97d7f2c 100755 --- a/src/lib/prisma-session-store.spec.ts +++ b/src/lib/prisma-session-store.spec.ts @@ -23,10 +23,12 @@ jest.mock('./utils/defer', () => ({ * Creates a new `PrismaSessionStore` and prisma mock * @param options any specific options related to what you are testing */ -const freshStore = (options: IOptions = {}) => { +const freshStore = ( + options: IOptions = {} +) => { const [prisma, mocks] = createPrismaMock(); - const store = new PrismaSessionStore(prisma, { + const store = new PrismaSessionStore(prisma, { logger: false, dbRecordIdIsSessionId: !options.dbRecordIdFunction, ...options, @@ -815,4 +817,18 @@ describe('PrismaSessionStore', () => { }); }); }); + + it('should run with other sessions', async () => { + const [store, { otherFindManyMock }] = freshStore({ + sessionModelName: 'otherSession', + }); + + const callback = jest.fn(); + + otherFindManyMock.mockResolvedValue(range(10).map((sid) => ({ sid }))); + + await store.ids(callback); + + expect(callback).toHaveBeenCalledWith(undefined, range(10)); + }); }); diff --git a/src/lib/prisma-session-store.ts b/src/lib/prisma-session-store.ts index 57a1db1..a958fc5 100755 --- a/src/lib/prisma-session-store.ts +++ b/src/lib/prisma-session-store.ts @@ -29,7 +29,7 @@ import { createExpiration, defer, getTTL } from './utils'; * ); * ``` */ -export class PrismaSessionStore extends Store { +export class PrismaSessionStore extends Store { /** * Initialize PrismaSessionStore with the given `prisma` and (optional) `options`. * @@ -54,8 +54,8 @@ export class PrismaSessionStore extends Store { * ``` */ constructor( - private readonly prisma: IPrisma, - private readonly options: IOptions + private readonly prisma: IPrisma, + private readonly options: IOptions ) { super(); this.startInterval(); @@ -94,6 +94,14 @@ export class PrismaSessionStore extends Store { */ private readonly serializer = this.options.serializer ?? JSON; + /** + * @description The name of the sessions model + * + * Defaults to `session` if `sessionModelName` in options is undefined + */ + private readonly sessionModelName: Exclude = + this.options.sessionModelName ?? ('session' as Exclude); + /** * Attempts to connect to Prisma, displaying a pretty error if the connection is not possible. */ @@ -148,7 +156,7 @@ export class PrismaSessionStore extends Store { if (!(await this.validateConnection())) return callback?.(); try { - const sessions = await this.prisma.session.findMany({ + const sessions = await this.prisma[this.sessionModelName].findMany({ select: { sid: true, data: true }, }); @@ -181,7 +189,7 @@ export class PrismaSessionStore extends Store { if (!(await this.validateConnection())) return callback?.(); try { - await this.prisma.session.deleteMany(); + await this.prisma[this.sessionModelName].deleteMany(); if (callback) defer(callback); } catch (e: unknown) { @@ -206,7 +214,7 @@ export class PrismaSessionStore extends Store { if (Array.isArray(sid)) { await Promise.all(sid.map(async (s) => this.destroy(s, callback))); } else { - await this.prisma.session.delete({ where: { sid } }); + await this.prisma[this.sessionModelName].delete({ where: { sid } }); } } catch (e: unknown) { this.logger.warn( @@ -229,8 +237,7 @@ export class PrismaSessionStore extends Store { callback?: (err?: unknown, val?: SessionData) => void ) => { if (!(await this.validateConnection())) return callback?.(); - - const session = await this.prisma.session + const session = await this.prisma[this.sessionModelName] .findUnique({ where: { sid }, }) @@ -263,7 +270,7 @@ export class PrismaSessionStore extends Store { // XXX More efficient way? XXX try { - const sessions = await this.prisma.session.findMany({ + const sessions = await this.prisma[this.sessionModelName].findMany({ select: { sid: true }, }); @@ -291,7 +298,7 @@ export class PrismaSessionStore extends Store { // XXX More efficient way? XXX try { - const sessions = await this.prisma.session.findMany({ + const sessions = await this.prisma[this.sessionModelName].findMany({ select: { sid: true }, // Limit what gets sent back; can't be empty. }); @@ -313,7 +320,7 @@ export class PrismaSessionStore extends Store { // XXX More efficient way? Maybe when filtering is fully implemented? XXX this.logger.log('Checking for any expired sessions...'); - const sessions = await this.prisma.session.findMany({ + const sessions = await this.prisma[this.sessionModelName].findMany({ select: { expiresAt: true, sid: true, @@ -327,7 +334,7 @@ export class PrismaSessionStore extends Store { if (now.valueOf() >= session.expiresAt.valueOf()) { this.logger.log(`Deleting session with sid: ${session.sid}`); - await this.prisma.session.delete({ + await this.prisma[this.sessionModelName].delete({ where: { sid: session.sid }, }); } @@ -364,7 +371,7 @@ export class PrismaSessionStore extends Store { return; } - const existingSession = await this.prisma.session + const existingSession = await this.prisma[this.sessionModelName] .findUnique({ where: { sid }, }) @@ -378,12 +385,12 @@ export class PrismaSessionStore extends Store { }; if (existingSession !== null) { - await this.prisma.session.update({ + await this.prisma[this.sessionModelName].update({ data, where: { sid }, }); } else { - await this.prisma.session.create({ + await this.prisma[this.sessionModelName].create({ data: { ...data, data: sessionString }, }); } @@ -441,7 +448,9 @@ export class PrismaSessionStore extends Store { }); try { - const existingSession = await this.prisma.session.findUnique({ + const existingSession = await this.prisma[ + this.sessionModelName + ].findUnique({ where: { sid }, }); @@ -451,7 +460,7 @@ export class PrismaSessionStore extends Store { cookie: session.cookie, }; - await this.prisma.session.update({ + await this.prisma[this.sessionModelName].update({ where: { sid: existingSession.sid }, data: { expiresAt, diff --git a/src/lib/utils/get-ttl.ts b/src/lib/utils/get-ttl.ts index f3d4955..5a2cadb 100644 --- a/src/lib/utils/get-ttl.ts +++ b/src/lib/utils/get-ttl.ts @@ -11,8 +11,8 @@ import { ONE_DAY_MS } from './constants'; * @param session the session data * @param sid the id of the current session */ -export const getTTL = ( - options: Pick, +export const getTTL = ( + options: Pick, 'ttl'>, session: PartialDeep, sid: string ) => { diff --git a/src/mocks/prisma.mock.ts b/src/mocks/prisma.mock.ts index dd9018e..436564e 100644 --- a/src/mocks/prisma.mock.ts +++ b/src/mocks/prisma.mock.ts @@ -9,6 +9,13 @@ export const createPrismaMock = () => { const findUniqueMock = jest.fn(); const updateMock = jest.fn(); + const otherCreateMock = jest.fn(); + const otherDeleteMock = jest.fn(); + const otherDeleteManyMock = jest.fn(); + const otherFindManyMock = jest.fn(); + const otherFindUniqueMock = jest.fn(); + const otherUpdateMock = jest.fn(); + const prisma = { $connect: connectMock, $disconnect: disconnectMock, @@ -20,6 +27,15 @@ export const createPrismaMock = () => { findUnique: findUniqueMock, update: updateMock, }, + + otherSession: { + create: otherCreateMock, + delete: otherDeleteMock, + deleteMany: otherDeleteManyMock, + findMany: otherFindManyMock, + findUnique: otherFindUniqueMock, + update: otherUpdateMock, + }, }; connectMock.mockResolvedValue(undefined); @@ -41,6 +57,12 @@ export const createPrismaMock = () => { findManyMock, findUniqueMock, updateMock, + otherCreateMock, + otherDeleteMock, + otherDeleteManyMock, + otherFindManyMock, + otherFindUniqueMock, + otherUpdateMock, }, ] as const; }; diff --git a/tests/integration.spec.ts b/tests/integration.spec.ts index b8582a9..a1f833e 100644 --- a/tests/integration.spec.ts +++ b/tests/integration.spec.ts @@ -32,6 +32,7 @@ describe('integration testing', () => { saveUninitialized: false, store: new PrismaSessionStore(prisma, { logger: false, + sessionModelName: 'otherSession', }), }) ); @@ -62,14 +63,14 @@ describe('integration testing', () => { }); beforeEach(async () => { - await prisma.session.deleteMany({}); + await prisma.otherSession.deleteMany({}); }); it('should not initialize a user session when the session is not modified', async () => { await request(app) .get('/') .expect(async ({ headers }) => { - const sessions = await prisma.session.findMany(); + const sessions = await prisma.otherSession.findMany(); expect(sessions).toHaveLength(0); expect(headers).not.toHaveProperty('set-cookie'); }); @@ -79,7 +80,7 @@ describe('integration testing', () => { await request(app) .post('/') .expect(async ({ headers }) => { - const sessions = await prisma.session.findMany(); + const sessions = await prisma.otherSession.findMany(); expect(sessions).toHaveLength(1); expect(headers).toHaveProperty('set-cookie'); }); @@ -94,7 +95,7 @@ describe('integration testing', () => { .delete('/') .set('Cookie', sessionCookie) .expect(async () => { - const sessions = await prisma.session.findMany(); + const sessions = await prisma.otherSession.findMany(); expect(sessions).toHaveLength(0); }); }); @@ -104,7 +105,7 @@ describe('integration testing', () => { .post('/') .then(async ({ headers }) => headers['set-cookie']); - const [newSession] = await prisma.session.findMany(); + const [newSession] = await prisma.otherSession.findMany(); expect(JSON.parse(newSession.data)).toStrictEqual( expect.objectContaining({ data: 'TESTING', @@ -113,7 +114,7 @@ describe('integration testing', () => { await request(app).put('/').set('Cookie', sessionCookie); - const [updatedSession] = await prisma.session.findMany(); + const [updatedSession] = await prisma.otherSession.findMany(); expect(JSON.parse(updatedSession.data)).toStrictEqual( expect.objectContaining({ data: 'UPDATED',