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

snatalenko / node-cqrs / 21966362039

12 Feb 2026 10:15PM UTC coverage: 85.328% (-9.1%) from 94.396%
21966362039

Pull #28

github

web-flow
Merge 9adf24177 into 828e39903
Pull Request #28: TypeScript and event dispatching pipeline refactoring

671 of 1008 branches covered (66.57%)

927 of 1051 new or added lines in 67 files covered. (88.2%)

49 existing lines in 13 files now uncovered.

1262 of 1479 relevant lines covered (85.33%)

33.76 hits per line

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

77.42
/src/sqlite/SqliteObjectStorage.ts
1
import type { Statement, Database } from 'better-sqlite3';
2
import { guid } from './utils/index.ts';
8✔
3
import type { IContainer, IObjectStorage } from '../interfaces/index.ts';
4
import { AbstractSqliteAccessor } from './AbstractSqliteAccessor.ts';
8✔
5

6
export class SqliteObjectStorage<TRecord> extends AbstractSqliteAccessor implements IObjectStorage<TRecord> {
8✔
7

8
        #tableName: string;
9
        #getQuery!: Statement<[Buffer], { data: string, version: number }>;
10
        #insertQuery!: Statement<[Buffer, string], void>;
11
        #updateByIdAndVersionQuery!: Statement<[string, Buffer, number], void>;
12
        #deleteQuery!: Statement<[Buffer], void>;
13

14
        constructor(o: Pick<IContainer, 'viewModelSqliteDb' | 'viewModelSqliteDbFactory'> & {
15
                tableName: string
16
        }) {
17
                super(o);
26✔
18

19
                this.#tableName = o.tableName;
26✔
20
        }
21

22
        protected initialize(db: Database) {
23
                db.exec(`CREATE TABLE IF NOT EXISTS ${this.#tableName} (
24✔
24
                        id BLOB PRIMARY KEY,
25
                        version INTEGER DEFAULT 1,
26
                        data TEXT NOT NULL
27
                );`);
28

29
                this.#getQuery = db.prepare(`
24✔
30
                        SELECT data, version
31
                        FROM ${this.#tableName}
32
                        WHERE id = ?
33
                `);
34

35
                this.#insertQuery = db.prepare(`
24✔
36
                        INSERT INTO ${this.#tableName} (id, data)
37
                        VALUES (?, ?)
38
                `);
39

40
                this.#updateByIdAndVersionQuery = db.prepare(`
24✔
41
                        UPDATE ${this.#tableName}
42
                        SET
43
                                data = ?,
44
                                version = version + 1
45
                        WHERE
46
                                id = ?
47
                                AND version = ?
48
                `);
49

50
                this.#deleteQuery = db.prepare(`
24✔
51
                        DELETE FROM ${this.#tableName}
52
                        WHERE id = ?
53
                `);
54
        }
55

56
        async get(id: string): Promise<TRecord | undefined> {
57
                if (typeof id !== 'string' || !id.length)
24✔
58
                        throw new TypeError('id argument must be a non-empty String');
2✔
59

60
                await this.assertConnection();
22✔
61

62
                const r = this.#getQuery.get(guid(id));
22✔
63
                if (!r)
22✔
64
                        return undefined;
8✔
65

66
                return JSON.parse(r.data);
14✔
67
        }
68

69
        getSync(id: string): TRecord | undefined {
NEW
70
                if (typeof id !== 'string' || !id.length)
×
NEW
71
                        throw new TypeError('id argument must be a non-empty String');
×
72

NEW
73
                const r = this.#getQuery.get(guid(id));
×
NEW
74
                if (!r)
×
NEW
75
                        return undefined;
×
76

NEW
77
                return JSON.parse(r.data);
×
78
        }
79

80
        async create(id: string, data: TRecord) {
81
                if (typeof id !== 'string' || !id.length)
8!
NEW
82
                        throw new TypeError('id argument must be a non-empty String');
×
83

84
                await this.assertConnection();
8✔
85

86
                this.#createSync(id, data);
8✔
87
        }
88

89
        #createSync(id: string, data: TRecord) {
90
                const r = this.#insertQuery.run(guid(id), JSON.stringify(data));
12✔
91
                if (r.changes !== 1)
12!
NEW
92
                        throw new Error(`Record '${id}' could not be created`);
×
93
        }
94

95
        async update(id: string, update: (r: TRecord) => TRecord) {
96
                if (typeof id !== 'string' || !id.length)
4!
NEW
97
                        throw new TypeError('id argument must be a non-empty String');
×
98
                if (typeof update !== 'function')
4!
NEW
99
                        throw new TypeError('update argument must be a Function');
×
100

101
                await this.assertConnection();
4✔
102

103
                this.#updateSync(id, update);
4✔
104
        }
105

106
        #updateSync(id: string, update: (r: TRecord) => TRecord) {
107
                const gid = guid(id);
4✔
108
                const record = this.#getQuery.get(gid);
4✔
109
                if (!record)
4✔
110
                        throw new Error(`Record '${id}' does not exist`);
2✔
111

112
                this.#updateExistingSync(id, record, update);
2✔
113
        }
114

115
        #updateExistingSync(id: string, record: { data: string, version: number }, update: (r: TRecord) => TRecord) {
116
                const gid = guid(id);
102✔
117
                const data = JSON.parse(record.data);
102✔
118
                const updatedData = update(data);
102✔
119
                const updatedJson = JSON.stringify(updatedData);
102✔
120

121
                // Version check is implemented to ensure the record isn't modified by another process.
122
                // A conflict resolution strategy could potentially be passed as an option to this method,
123
                // but for now, conflict resolution should happen outside this class.
124
                const r = this.#updateByIdAndVersionQuery.run(updatedJson, gid, record.version);
102✔
125
                if (r.changes !== 1)
102!
NEW
126
                        throw new Error(`Record '${id}' could not be updated`);
×
127
        }
128

129
        async updateEnforcingNew(id: string, update: (r?: TRecord) => TRecord) {
130
                if (typeof id !== 'string' || !id.length)
104!
NEW
131
                        throw new TypeError('id argument must be a non-empty String');
×
132
                if (typeof update !== 'function')
104!
NEW
133
                        throw new TypeError('update argument must be a Function');
×
134

135
                await this.assertConnection();
104✔
136

137
                const record = this.#getQuery.get(guid(id));
104✔
138
                if (record)
104✔
139
                        this.#updateExistingSync(id, record, update as (r: TRecord) => TRecord);
100✔
140
                else
141
                        this.#createSync(id, update());
4✔
142
        }
143

144
        async delete(id: string): Promise<boolean> {
145
                if (typeof id !== 'string' || !id.length)
4!
NEW
146
                        throw new TypeError('id argument must be a non-empty String');
×
147

148
                await this.assertConnection();
4✔
149

150
                const r = this.#deleteQuery.run(guid(id));
4✔
151
                return r.changes === 1;
4✔
152
        }
153
}
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

© 2026 Coveralls, Inc