Skip to content

Commit

Permalink
Merge pull request #52 from jeremygottfried/master
Browse files Browse the repository at this point in the history
Add option for sessionModelName
  • Loading branch information
William Sedlacek committed Aug 16, 2021
2 parents 35940ab + 3804db0 commit f2ebe72
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 33 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ model Session {
data String
expiresAt DateTime
}

model OtherSession {
id String @id
sid String @unique
data String
expiresAt DateTime
}
8 changes: 7 additions & 1 deletion src/@types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type TTLFactory = (
/**
* PrismaSessionStore options to alter the way the store behaves
*/
export interface IOptions {
export interface IOptions<M extends string = 'session'> {
/**
* Interval, in ms, at which PrismaSessionStore will automatically remove
* expired sessions. Disabled by default; set to something reasonable.
Expand Down Expand Up @@ -91,6 +91,12 @@ export interface IOptions {
*/
ttl?: number | TTLFactory;

/**
* "Session Table Name"
* defines session table name. Defaults to sessions
*/
sessionModelName?: Exclude<M, `$${string}`>;

/**
* A function to generate the Prisma Record ID for a given session ID
*
Expand Down
10 changes: 6 additions & 4 deletions src/@types/prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ interface IDeleteArgs {
where: { sid: string };
}

export interface IPrisma {
session: {
export type IPrisma<M extends string = 'session'> = Record<
Exclude<M, `$${string}`>,
{
create(args: ICreateArgs): Promise<IPrismaSession>;
delete(args: IDeleteArgs): Promise<IPrismaSession>;
deleteMany(args?: unknown): Promise<unknown>;
findMany(args?: IFindManyArgs): Promise<IPrismaSession[]>;
findUnique(args: IFindUniqueArgs): Promise<IPrismaSession | null>;
update(args: IUpdateArgs): Promise<IPrismaSession>;
};
}
> & {
$connect(): Promise<void>;
$disconnect(): Promise<void>;
}
};
20 changes: 18 additions & 2 deletions src/lib/prisma-session-store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <M extends 'session' | 'otherSession' = 'session'>(
options: IOptions<M> = {}
) => {
const [prisma, mocks] = createPrismaMock();

const store = new PrismaSessionStore(prisma, {
const store = new PrismaSessionStore<M>(prisma, {
logger: false,
dbRecordIdIsSessionId: !options.dbRecordIdFunction,
...options,
Expand Down Expand Up @@ -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));
});
});
43 changes: 26 additions & 17 deletions src/lib/prisma-session-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { createExpiration, defer, getTTL } from './utils';
* );
* ```
*/
export class PrismaSessionStore extends Store {
export class PrismaSessionStore<M extends string = 'session'> extends Store {
/**
* Initialize PrismaSessionStore with the given `prisma` and (optional) `options`.
*
Expand All @@ -54,8 +54,8 @@ export class PrismaSessionStore extends Store {
* ```
*/
constructor(
private readonly prisma: IPrisma,
private readonly options: IOptions
private readonly prisma: IPrisma<M>,
private readonly options: IOptions<M>
) {
super();
this.startInterval();
Expand Down Expand Up @@ -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<M, `$${string}`> =
this.options.sessionModelName ?? ('session' as Exclude<M, `$${string}`>);

/**
* Attempts to connect to Prisma, displaying a pretty error if the connection is not possible.
*/
Expand Down Expand Up @@ -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 },
});

Expand Down Expand Up @@ -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) {
Expand All @@ -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(
Expand All @@ -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 },
})
Expand Down Expand Up @@ -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 },
});

Expand Down Expand Up @@ -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.
});

Expand All @@ -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,
Expand All @@ -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 },
});
}
Expand Down Expand Up @@ -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 },
})
Expand All @@ -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 },
});
}
Expand Down Expand Up @@ -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 },
});

Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/utils/get-ttl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IOptions, 'ttl'>,
export const getTTL = <M extends string>(
options: Pick<IOptions<M>, 'ttl'>,
session: PartialDeep<SessionData>,
sid: string
) => {
Expand Down
22 changes: 22 additions & 0 deletions src/mocks/prisma.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -41,6 +57,12 @@ export const createPrismaMock = () => {
findManyMock,
findUniqueMock,
updateMock,
otherCreateMock,
otherDeleteMock,
otherDeleteManyMock,
otherFindManyMock,
otherFindUniqueMock,
otherUpdateMock,
},
] as const;
};
13 changes: 7 additions & 6 deletions tests/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('integration testing', () => {
saveUninitialized: false,
store: new PrismaSessionStore(prisma, {
logger: false,
sessionModelName: 'otherSession',
}),
})
);
Expand Down Expand Up @@ -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');
});
Expand All @@ -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');
});
Expand All @@ -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);
});
});
Expand All @@ -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',
Expand All @@ -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',
Expand Down

0 comments on commit f2ebe72

Please sign in to comment.