• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

u-wave / core / 20206618234

14 Dec 2025 10:29AM UTC coverage: 85.938% (-0.2%) from 86.187%
20206618234

Pull #728

github

web-flow
Merge f5bd07dd3 into 96ae4617b
Pull Request #728: Store express-session state in SQLite

998 of 1189 branches covered (83.94%)

Branch coverage included in aggregate %.

120 of 178 new or added lines in 3 files covered. (67.42%)

9 existing lines in 1 file now uncovered.

10394 of 12067 relevant lines covered (86.14%)

98.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.61
/src/utils/SqliteSessionStore.js
1
import { Store } from 'express-session';
1✔
2
import { callbackify } from 'node:util';
1✔
3
import {
1✔
4
  fromJson,
1✔
5
  json,
1✔
6
  jsonb,
1✔
7
  now,
1✔
8
} from './sqlite.js';
1✔
9
import { addHours, addMilliseconds, isBefore } from 'date-fns';
1✔
10

1✔
11
export default class SqliteSessionStore extends Store {
1✔
12
  #db;
154✔
13

154✔
14
  #logger;
154✔
15

154✔
16
  /**
154✔
17
   * @param {import('kysely').Kysely<import('../schema.js').Database>} db
154✔
18
   * @param {import('pino').Logger} logger
154✔
19
   */
154✔
20
  constructor(db, logger) {
154✔
21
    super();
154✔
22
    this.#db = db;
154✔
23
    this.#logger = logger;
154✔
24
  }
154✔
25

154✔
26
  /**
154✔
27
   * @param {import('express-session').SessionData} session
154✔
28
   */
154✔
29
  #sessionExpiration(session) {
154✔
30
    const { maxAge } = session.cookie;
4✔
31
    if (maxAge != null) {
4!
NEW
32
      return addMilliseconds(new Date(), maxAge);
×
NEW
33
    }
×
34
    return addHours(new Date(), 1);
4✔
35
  }
4✔
36

154✔
37
  async #cleanup() {
154✔
NEW
38
    const result = await this.#db.deleteFrom('sessions')
×
NEW
39
      .where('expiresAt', '<', now)
×
NEW
40
      .executeTakeFirst();
×
NEW
41

×
NEW
42
    if (result != null) {
×
NEW
43
      this.#logger.debug({ sessionsDeleted: result.numDeletedRows }, 'cleaned up stale express-sessions');
×
NEW
44
    }
×
NEW
45
  }
×
46

154✔
47
  /**
154✔
48
   * @param {string} sid
154✔
49
   * @param {(
154✔
50
   *   err: unknown,
154✔
51
   *   session?: import('express-session').SessionData | null,
154✔
52
   * ) => void} callback
154✔
53
   */
154✔
54
  get(sid, callback) {
154✔
NEW
55
    callbackify(async () => {
×
NEW
56
      const row = await this.#db.selectFrom('sessions')
×
NEW
57
        .where('id', '=', sid)
×
NEW
58
        .select(['expiresAt', (eb) => json(eb.ref('data')).as('data')])
×
NEW
59
        .executeTakeFirst();
×
NEW
60

×
NEW
61
      if (row != null) {
×
NEW
62
        if (isBefore(row.expiresAt, new Date())) {
×
NEW
63
          this.#cleanup().catch((err) => {
×
NEW
64
            this.#logger.warn({ err }, 'automatic express-session cleanup failed');
×
NEW
65
          });
×
NEW
66

×
NEW
67
          return null;
×
NEW
68
        }
×
NEW
69

×
NEW
70
        return /** @type {import('express-session').SessionData | null} */ (
×
NEW
71
          /** @type {unknown} */ (fromJson(row.data))
×
NEW
72
        );
×
NEW
73
      }
×
NEW
74

×
NEW
75
      return null;
×
NEW
76
    })(callback);
×
NEW
77
  }
×
78

154✔
79
  /**
154✔
80
   * @param {string} sid
154✔
81
   * @param {import('express-session').SessionData} session
154✔
82
   * @param {(err?: unknown) => void} callback
154✔
83
   */
154✔
84
  set(sid, session, callback) {
154✔
85
    const expiresAt = this.#sessionExpiration(session);
4✔
86

4✔
87
    callbackify(async () => {
4✔
88
      await this.#db.replaceInto('sessions')
4✔
89
        .values({
4✔
90
          id: sid,
4✔
91
          data: jsonb(/** @type {import('type-fest').JsonObject} */ (
4✔
92
            /** @type {unknown} */ (session)
4✔
93
          )),
4✔
94
          expiresAt,
4✔
95
        })
4✔
96
        .executeTakeFirstOrThrow();
4✔
97
    })(callback);
4✔
98
  }
4✔
99

154✔
100
  /**
154✔
101
   * @param {string} sid
154✔
102
   * @param {import('express-session').SessionData} session
154✔
103
   * @param {(err?: unknown) => void} callback
154✔
104
   */
154✔
105
  touch(sid, session, callback) {
154✔
NEW
106
    const expiresAt = this.#sessionExpiration(session);
×
NEW
107

×
NEW
108
    callbackify(async () => {
×
NEW
109
      await this.#db.updateTable('sessions')
×
NEW
110
        .where('id', '=', sid)
×
NEW
111
        .set({ expiresAt })
×
NEW
112
        .executeTakeFirstOrThrow();
×
NEW
113
    })(callback);
×
NEW
114
  }
×
115

154✔
116
  /**
154✔
117
   * @param {string} sid
154✔
118
   * @param {(err?: unknown) => void} callback
154✔
119
   */
154✔
120
  destroy(sid, callback) {
154✔
121
    callbackify(async () => {
2✔
122
      await this.#db.deleteFrom('sessions')
2✔
123
        .where('id', '=', sid)
2✔
124
        .executeTakeFirstOrThrow();
2✔
125
    })(callback);
2✔
126
  }
2✔
127

154✔
128
  /**
154✔
129
   * @param {(err: unknown, length?: number) => void} callback
154✔
130
   */
154✔
131
  length(callback) {
154✔
NEW
132
    callbackify(async () => {
×
NEW
133
      const { count } = await this.#db.selectFrom('sessions')
×
NEW
134
        .select((eb) => eb.fn.countAll().as('count'))
×
NEW
135
        .executeTakeFirstOrThrow();
×
NEW
136
      return Number(count);
×
NEW
137
    })(callback);
×
NEW
138
  }
×
139

154✔
140
  /**
154✔
141
   * @param {(err?: unknown) => void} callback
154✔
142
   */
154✔
143
  clear(callback) {
154✔
NEW
144
    callbackify(async () => {
×
NEW
145
      await this.#db.deleteFrom('sessions').execute();
×
NEW
146
    })(callback);
×
NEW
147
  }
×
148
}
154✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc