@copypatch/storage-sqlite 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +337 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Efe Arabacı
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @copypatch/storage-sqlite
|
|
2
|
+
|
|
3
|
+
SQLite persistence for CopyPatch v2. The adapter implements the asynchronous
|
|
4
|
+
`CopyPatchPersistence` contract while using transactional, synchronous SQLite
|
|
5
|
+
operations internally.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @copypatch/core @copypatch/storage-sqlite better-sqlite3
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Node.js 20 or newer is required.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createSQLitePersistence } from '@copypatch/storage-sqlite';
|
|
19
|
+
|
|
20
|
+
const persistence = createSQLitePersistence('./data/copypatch.sqlite');
|
|
21
|
+
await persistence.migrate();
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const snapshot = await persistence.readPublished('en');
|
|
25
|
+
console.log(snapshot);
|
|
26
|
+
} finally {
|
|
27
|
+
persistence.close();
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The factory also accepts `{ filename, busyTimeoutMs }`. Call `migrate()` before
|
|
32
|
+
the first operation on a new database. Migrations are versioned and idempotent.
|
|
33
|
+
|
|
34
|
+
Content mutations use atomic compare-and-swap checks against both published
|
|
35
|
+
and draft revisions. Sessions and rate limits are stored in SQLite so they
|
|
36
|
+
survive process restarts. Session callers must pass cryptographic token hashes;
|
|
37
|
+
the adapter never accepts or derives raw session tokens.
|
|
38
|
+
|
|
39
|
+
## Exports
|
|
40
|
+
|
|
41
|
+
- `createSQLitePersistence(filenameOrOptions)`
|
|
42
|
+
- `SQLitePersistence`
|
|
43
|
+
- `SQLitePersistenceOptions`
|
|
44
|
+
|
|
45
|
+
CopyPatch is ESM-only. See the
|
|
46
|
+
[CopyPatch repository](https://github.com/Woffluon/CopyPatch) for complete API
|
|
47
|
+
documentation and examples.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CopyPatchPersistence, DiscardDraftsCommand, DiscardDraftsResult, EditorSnapshot, PersistenceHealth, PersistenceMutationResult, PublishDraftsCommand, PublishDraftsResult, RateLimitDecision, RateLimitInput, SaveDraftsCommand, SaveDraftsResult, SessionTouch, StoredSession } from '@copypatch/core';
|
|
2
|
+
export interface SQLitePersistenceOptions {
|
|
3
|
+
filename: string;
|
|
4
|
+
busyTimeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class SQLitePersistence implements CopyPatchPersistence {
|
|
7
|
+
private readonly sqlite;
|
|
8
|
+
private closed;
|
|
9
|
+
constructor(filenameOrOptions: string | SQLitePersistenceOptions);
|
|
10
|
+
migrate(): Promise<void>;
|
|
11
|
+
health(): Promise<PersistenceHealth>;
|
|
12
|
+
readPublished(locale: string): Promise<{
|
|
13
|
+
revision: number;
|
|
14
|
+
content: Record<string, string>;
|
|
15
|
+
}>;
|
|
16
|
+
readEditor(locale: string): Promise<EditorSnapshot>;
|
|
17
|
+
saveDrafts(command: SaveDraftsCommand): Promise<PersistenceMutationResult<SaveDraftsResult>>;
|
|
18
|
+
publishDrafts(command: PublishDraftsCommand): Promise<PersistenceMutationResult<PublishDraftsResult>>;
|
|
19
|
+
discardDrafts(command: DiscardDraftsCommand): Promise<PersistenceMutationResult<DiscardDraftsResult>>;
|
|
20
|
+
createSession(session: StoredSession): Promise<void>;
|
|
21
|
+
readSession(tokenHash: string): Promise<StoredSession | null>;
|
|
22
|
+
touchSession(tokenHash: string, update: SessionTouch): Promise<StoredSession | null>;
|
|
23
|
+
deleteSession(tokenHash: string): Promise<void>;
|
|
24
|
+
consumeRateLimit(input: RateLimitInput): Promise<RateLimitDecision>;
|
|
25
|
+
close(): void;
|
|
26
|
+
private editorSnapshot;
|
|
27
|
+
private readState;
|
|
28
|
+
private writeState;
|
|
29
|
+
private revisionsMatch;
|
|
30
|
+
private sessionFromRow;
|
|
31
|
+
private positiveInteger;
|
|
32
|
+
private assertOpen;
|
|
33
|
+
}
|
|
34
|
+
export declare function createSQLitePersistence(filenameOrOptions: string | SQLitePersistenceOptions): SQLitePersistence;
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACd,MAAM,iBAAiB,CAAC;AAKzB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA6BD,qBAAa,iBAAkB,YAAW,oBAAoB;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,MAAM,CAAS;gBAEX,iBAAiB,EAAE,MAAM,GAAG,wBAAwB;IAqB1D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAsDxB,MAAM,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAcpC,aAAa,CAAC,MAAM,EAAE,MAAM;;;;IAkB5B,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAKnD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,yBAAyB,CAAC,gBAAgB,CAAC,CAAC;IAwB5F,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,yBAAyB,CAAC,mBAAmB,CAAC,CAAC;IA2BrG,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,yBAAyB,CAAC,mBAAmB,CAAC,CAAC;IAyBrG,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBpD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAW7D,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAiBpF,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/C,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0BzE,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,UAAU;CAGnB;AAED,wBAAgB,uBAAuB,CACrC,iBAAiB,EAAE,MAAM,GAAG,wBAAwB,GACnD,iBAAiB,CAEnB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
const SCHEMA_VERSION = 1;
|
|
5
|
+
const SHA256_HEX = /^[a-f0-9]{64}$/;
|
|
6
|
+
export class SQLitePersistence {
|
|
7
|
+
sqlite;
|
|
8
|
+
closed = false;
|
|
9
|
+
constructor(filenameOrOptions) {
|
|
10
|
+
const options = typeof filenameOrOptions === 'string'
|
|
11
|
+
? { filename: filenameOrOptions }
|
|
12
|
+
: filenameOrOptions;
|
|
13
|
+
if (!options.filename)
|
|
14
|
+
throw new TypeError('A SQLite filename is required.');
|
|
15
|
+
const filename = resolve(options.filename);
|
|
16
|
+
const directory = dirname(filename);
|
|
17
|
+
if (!existsSync(directory))
|
|
18
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
19
|
+
this.sqlite = new Database(filename);
|
|
20
|
+
this.sqlite.pragma(`busy_timeout = ${this.positiveInteger(options.busyTimeoutMs ?? 5_000, 'busyTimeoutMs')}`);
|
|
21
|
+
this.sqlite.pragma('journal_mode = WAL');
|
|
22
|
+
this.sqlite.pragma('synchronous = NORMAL');
|
|
23
|
+
this.sqlite.pragma('foreign_keys = ON');
|
|
24
|
+
try {
|
|
25
|
+
chmodSync(filename, 0o600);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Windows and some mounted filesystems do not expose POSIX file modes.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async migrate() {
|
|
32
|
+
this.assertOpen();
|
|
33
|
+
const current = this.sqlite.pragma('user_version', { simple: true });
|
|
34
|
+
if (current > SCHEMA_VERSION) {
|
|
35
|
+
throw new Error(`SQLite schema version ${current} is newer than supported version ${SCHEMA_VERSION}.`);
|
|
36
|
+
}
|
|
37
|
+
if (current === SCHEMA_VERSION)
|
|
38
|
+
return;
|
|
39
|
+
const managedTables = this.sqlite.prepare(`
|
|
40
|
+
SELECT name FROM sqlite_master
|
|
41
|
+
WHERE type = 'table' AND name IN ('content_state', 'content_entries', 'sessions', 'rate_limits')
|
|
42
|
+
`).pluck().all();
|
|
43
|
+
if (managedTables.length > 0) {
|
|
44
|
+
throw new Error('An unversioned or CopyPatch v1 database was detected. CopyPatch v1 data migration is not supported; configure a new v2 database.');
|
|
45
|
+
}
|
|
46
|
+
this.sqlite.transaction(() => {
|
|
47
|
+
this.sqlite.exec(`
|
|
48
|
+
CREATE TABLE IF NOT EXISTS content_state (
|
|
49
|
+
locale TEXT PRIMARY KEY,
|
|
50
|
+
published_revision INTEGER NOT NULL CHECK (published_revision >= 1),
|
|
51
|
+
draft_revision INTEGER NOT NULL CHECK (draft_revision >= 1)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS content_entries (
|
|
55
|
+
locale TEXT NOT NULL,
|
|
56
|
+
content_key TEXT NOT NULL,
|
|
57
|
+
published_text TEXT,
|
|
58
|
+
draft_text TEXT,
|
|
59
|
+
PRIMARY KEY (locale, content_key)
|
|
60
|
+
) WITHOUT ROWID;
|
|
61
|
+
|
|
62
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
63
|
+
token_hash TEXT PRIMARY KEY,
|
|
64
|
+
csrf_token_hash TEXT NOT NULL,
|
|
65
|
+
subject TEXT NOT NULL,
|
|
66
|
+
roles_json TEXT NOT NULL,
|
|
67
|
+
created_at INTEGER NOT NULL,
|
|
68
|
+
last_seen_at INTEGER NOT NULL,
|
|
69
|
+
idle_expires_at INTEGER NOT NULL,
|
|
70
|
+
absolute_expires_at INTEGER NOT NULL
|
|
71
|
+
) WITHOUT ROWID;
|
|
72
|
+
|
|
73
|
+
CREATE TABLE IF NOT EXISTS rate_limits (
|
|
74
|
+
key_hash TEXT PRIMARY KEY,
|
|
75
|
+
count INTEGER NOT NULL CHECK (count >= 1),
|
|
76
|
+
reset_at INTEGER NOT NULL
|
|
77
|
+
) WITHOUT ROWID;
|
|
78
|
+
|
|
79
|
+
PRAGMA user_version = 1;
|
|
80
|
+
`);
|
|
81
|
+
}).immediate();
|
|
82
|
+
}
|
|
83
|
+
async health() {
|
|
84
|
+
try {
|
|
85
|
+
this.assertOpen();
|
|
86
|
+
const version = this.sqlite.pragma('user_version', { simple: true });
|
|
87
|
+
if (version !== SCHEMA_VERSION) {
|
|
88
|
+
return { ok: false, message: `SQLite schema version is ${version}; expected ${SCHEMA_VERSION}.` };
|
|
89
|
+
}
|
|
90
|
+
this.sqlite.prepare('SELECT 1 FROM content_state LIMIT 1').get();
|
|
91
|
+
return { ok: true };
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return { ok: false, message: error instanceof Error ? error.message : 'SQLite health check failed.' };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async readPublished(locale) {
|
|
98
|
+
this.assertOpen();
|
|
99
|
+
return this.sqlite.transaction((value) => {
|
|
100
|
+
const state = this.readState(value);
|
|
101
|
+
const rows = this.sqlite.prepare(`
|
|
102
|
+
SELECT content_key, published_text, draft_text
|
|
103
|
+
FROM content_entries
|
|
104
|
+
WHERE locale = ? AND published_text IS NOT NULL
|
|
105
|
+
ORDER BY content_key
|
|
106
|
+
`).all(value);
|
|
107
|
+
const content = {};
|
|
108
|
+
for (const row of rows) {
|
|
109
|
+
if (row.published_text !== null)
|
|
110
|
+
content[row.content_key] = row.published_text;
|
|
111
|
+
}
|
|
112
|
+
return { revision: state.publishedRevision, content };
|
|
113
|
+
}).deferred(locale);
|
|
114
|
+
}
|
|
115
|
+
async readEditor(locale) {
|
|
116
|
+
this.assertOpen();
|
|
117
|
+
return this.sqlite.transaction((value) => this.editorSnapshot(value)).deferred(locale);
|
|
118
|
+
}
|
|
119
|
+
async saveDrafts(command) {
|
|
120
|
+
this.assertOpen();
|
|
121
|
+
return this.sqlite.transaction((input) => {
|
|
122
|
+
const current = this.editorSnapshot(input.locale);
|
|
123
|
+
if (!this.revisionsMatch(current, input.expectedPublishedRevision, input.expectedDraftRevision)) {
|
|
124
|
+
return { status: 'conflict', latest: current };
|
|
125
|
+
}
|
|
126
|
+
const write = this.sqlite.prepare(`
|
|
127
|
+
INSERT INTO content_entries (locale, content_key, published_text, draft_text)
|
|
128
|
+
VALUES (?, ?, NULL, ?)
|
|
129
|
+
ON CONFLICT (locale, content_key) DO UPDATE SET draft_text = excluded.draft_text
|
|
130
|
+
`);
|
|
131
|
+
for (const change of input.changes)
|
|
132
|
+
write.run(input.locale, change.key, change.text);
|
|
133
|
+
const draftRevision = current.draftRevision + 1;
|
|
134
|
+
this.writeState(input.locale, current.publishedRevision, draftRevision);
|
|
135
|
+
return {
|
|
136
|
+
status: 'ok',
|
|
137
|
+
value: { publishedRevision: current.publishedRevision, draftRevision },
|
|
138
|
+
};
|
|
139
|
+
}).immediate(command);
|
|
140
|
+
}
|
|
141
|
+
async publishDrafts(command) {
|
|
142
|
+
this.assertOpen();
|
|
143
|
+
return this.sqlite.transaction((input) => {
|
|
144
|
+
const current = this.editorSnapshot(input.locale);
|
|
145
|
+
if (!this.revisionsMatch(current, input.expectedPublishedRevision, input.expectedDraftRevision)) {
|
|
146
|
+
return { status: 'conflict', latest: current };
|
|
147
|
+
}
|
|
148
|
+
const promotedCount = this.sqlite.prepare(`
|
|
149
|
+
SELECT count(*) FROM content_entries WHERE locale = ? AND draft_text IS NOT NULL
|
|
150
|
+
`).pluck().get(input.locale);
|
|
151
|
+
this.sqlite.prepare(`
|
|
152
|
+
UPDATE content_entries
|
|
153
|
+
SET published_text = draft_text, draft_text = NULL
|
|
154
|
+
WHERE locale = ? AND draft_text IS NOT NULL
|
|
155
|
+
`).run(input.locale);
|
|
156
|
+
const publishedRevision = current.publishedRevision + 1;
|
|
157
|
+
const draftRevision = current.draftRevision + 1;
|
|
158
|
+
this.writeState(input.locale, publishedRevision, draftRevision);
|
|
159
|
+
return {
|
|
160
|
+
status: 'ok',
|
|
161
|
+
value: { publishedRevision, draftRevision, promotedCount },
|
|
162
|
+
};
|
|
163
|
+
}).immediate(command);
|
|
164
|
+
}
|
|
165
|
+
async discardDrafts(command) {
|
|
166
|
+
this.assertOpen();
|
|
167
|
+
return this.sqlite.transaction((input) => {
|
|
168
|
+
const current = this.editorSnapshot(input.locale);
|
|
169
|
+
if (!this.revisionsMatch(current, input.expectedPublishedRevision, input.expectedDraftRevision)) {
|
|
170
|
+
return { status: 'conflict', latest: current };
|
|
171
|
+
}
|
|
172
|
+
const discardedCount = this.sqlite.prepare(`
|
|
173
|
+
SELECT count(*) FROM content_entries WHERE locale = ? AND draft_text IS NOT NULL
|
|
174
|
+
`).pluck().get(input.locale);
|
|
175
|
+
this.sqlite.prepare(`
|
|
176
|
+
UPDATE content_entries SET draft_text = NULL
|
|
177
|
+
WHERE locale = ? AND draft_text IS NOT NULL
|
|
178
|
+
`).run(input.locale);
|
|
179
|
+
const draftRevision = current.draftRevision + 1;
|
|
180
|
+
this.writeState(input.locale, current.publishedRevision, draftRevision);
|
|
181
|
+
return {
|
|
182
|
+
status: 'ok',
|
|
183
|
+
value: { publishedRevision: current.publishedRevision, draftRevision, discardedCount },
|
|
184
|
+
};
|
|
185
|
+
}).immediate(command);
|
|
186
|
+
}
|
|
187
|
+
async createSession(session) {
|
|
188
|
+
this.assertOpen();
|
|
189
|
+
assertHash('tokenHash', session.tokenHash);
|
|
190
|
+
assertHash('csrfTokenHash', session.csrfTokenHash);
|
|
191
|
+
this.sqlite.prepare(`
|
|
192
|
+
INSERT INTO sessions (
|
|
193
|
+
token_hash, csrf_token_hash, subject, roles_json, created_at,
|
|
194
|
+
last_seen_at, idle_expires_at, absolute_expires_at
|
|
195
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
196
|
+
`).run(session.tokenHash, session.csrfTokenHash, session.subject, JSON.stringify(session.roles), session.createdAt, session.lastSeenAt, session.idleExpiresAt, session.absoluteExpiresAt);
|
|
197
|
+
}
|
|
198
|
+
async readSession(tokenHash) {
|
|
199
|
+
this.assertOpen();
|
|
200
|
+
assertHash('tokenHash', tokenHash);
|
|
201
|
+
const row = this.sqlite.prepare(`
|
|
202
|
+
SELECT token_hash, csrf_token_hash, subject, roles_json, created_at,
|
|
203
|
+
last_seen_at, idle_expires_at, absolute_expires_at
|
|
204
|
+
FROM sessions WHERE token_hash = ?
|
|
205
|
+
`).get(tokenHash);
|
|
206
|
+
return row ? this.sessionFromRow(row) : null;
|
|
207
|
+
}
|
|
208
|
+
async touchSession(tokenHash, update) {
|
|
209
|
+
this.assertOpen();
|
|
210
|
+
assertHash('tokenHash', tokenHash);
|
|
211
|
+
if (update.csrfTokenHash !== undefined)
|
|
212
|
+
assertHash('csrfTokenHash', update.csrfTokenHash);
|
|
213
|
+
const result = update.csrfTokenHash === undefined
|
|
214
|
+
? this.sqlite.prepare(`
|
|
215
|
+
UPDATE sessions SET last_seen_at = ?, idle_expires_at = ? WHERE token_hash = ?
|
|
216
|
+
`).run(update.lastSeenAt, update.idleExpiresAt, tokenHash)
|
|
217
|
+
: this.sqlite.prepare(`
|
|
218
|
+
UPDATE sessions
|
|
219
|
+
SET last_seen_at = ?, idle_expires_at = ?, csrf_token_hash = ?
|
|
220
|
+
WHERE token_hash = ?
|
|
221
|
+
`).run(update.lastSeenAt, update.idleExpiresAt, update.csrfTokenHash, tokenHash);
|
|
222
|
+
if (result.changes === 0)
|
|
223
|
+
return null;
|
|
224
|
+
return this.readSession(tokenHash);
|
|
225
|
+
}
|
|
226
|
+
async deleteSession(tokenHash) {
|
|
227
|
+
this.assertOpen();
|
|
228
|
+
assertHash('tokenHash', tokenHash);
|
|
229
|
+
this.sqlite.prepare('DELETE FROM sessions WHERE token_hash = ?').run(tokenHash);
|
|
230
|
+
}
|
|
231
|
+
async consumeRateLimit(input) {
|
|
232
|
+
this.assertOpen();
|
|
233
|
+
assertHash('keyHash', input.keyHash);
|
|
234
|
+
this.positiveInteger(input.limit, 'limit');
|
|
235
|
+
this.positiveInteger(input.windowMs, 'windowMs');
|
|
236
|
+
if (!Number.isSafeInteger(input.now))
|
|
237
|
+
throw new TypeError('now must be a safe integer.');
|
|
238
|
+
return this.sqlite.transaction((value) => {
|
|
239
|
+
const current = this.sqlite.prepare(`
|
|
240
|
+
SELECT count, reset_at FROM rate_limits WHERE key_hash = ?
|
|
241
|
+
`).get(value.keyHash);
|
|
242
|
+
const reset = current === undefined || current.reset_at <= value.now;
|
|
243
|
+
const count = reset ? 1 : current.count + 1;
|
|
244
|
+
const resetAt = reset ? value.now + value.windowMs : current.reset_at;
|
|
245
|
+
this.sqlite.prepare(`
|
|
246
|
+
INSERT INTO rate_limits (key_hash, count, reset_at) VALUES (?, ?, ?)
|
|
247
|
+
ON CONFLICT (key_hash) DO UPDATE SET count = excluded.count, reset_at = excluded.reset_at
|
|
248
|
+
`).run(value.keyHash, count, resetAt);
|
|
249
|
+
return {
|
|
250
|
+
allowed: count <= value.limit,
|
|
251
|
+
remaining: Math.max(0, value.limit - count),
|
|
252
|
+
resetAt,
|
|
253
|
+
};
|
|
254
|
+
}).immediate(input);
|
|
255
|
+
}
|
|
256
|
+
close() {
|
|
257
|
+
if (this.closed)
|
|
258
|
+
return;
|
|
259
|
+
this.sqlite.close();
|
|
260
|
+
this.closed = true;
|
|
261
|
+
}
|
|
262
|
+
editorSnapshot(locale) {
|
|
263
|
+
const state = this.readState(locale);
|
|
264
|
+
const rows = this.sqlite.prepare(`
|
|
265
|
+
SELECT content_key, published_text, draft_text
|
|
266
|
+
FROM content_entries
|
|
267
|
+
WHERE locale = ?
|
|
268
|
+
ORDER BY content_key
|
|
269
|
+
`).all(locale);
|
|
270
|
+
const published = {};
|
|
271
|
+
const drafts = {};
|
|
272
|
+
for (const row of rows) {
|
|
273
|
+
if (row.published_text !== null)
|
|
274
|
+
published[row.content_key] = row.published_text;
|
|
275
|
+
if (row.draft_text !== null)
|
|
276
|
+
drafts[row.content_key] = row.draft_text;
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
locale,
|
|
280
|
+
publishedRevision: state.publishedRevision,
|
|
281
|
+
draftRevision: state.draftRevision,
|
|
282
|
+
publishingMode: 'draft',
|
|
283
|
+
published,
|
|
284
|
+
drafts,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
readState(locale) {
|
|
288
|
+
const row = this.sqlite.prepare(`
|
|
289
|
+
SELECT published_revision, draft_revision FROM content_state WHERE locale = ?
|
|
290
|
+
`).get(locale);
|
|
291
|
+
return row
|
|
292
|
+
? { publishedRevision: row.published_revision, draftRevision: row.draft_revision }
|
|
293
|
+
: { publishedRevision: 1, draftRevision: 1 };
|
|
294
|
+
}
|
|
295
|
+
writeState(locale, publishedRevision, draftRevision) {
|
|
296
|
+
this.sqlite.prepare(`
|
|
297
|
+
INSERT INTO content_state (locale, published_revision, draft_revision)
|
|
298
|
+
VALUES (?, ?, ?)
|
|
299
|
+
ON CONFLICT (locale) DO UPDATE SET
|
|
300
|
+
published_revision = excluded.published_revision,
|
|
301
|
+
draft_revision = excluded.draft_revision
|
|
302
|
+
`).run(locale, publishedRevision, draftRevision);
|
|
303
|
+
}
|
|
304
|
+
revisionsMatch(snapshot, publishedRevision, draftRevision) {
|
|
305
|
+
return snapshot.publishedRevision === publishedRevision && snapshot.draftRevision === draftRevision;
|
|
306
|
+
}
|
|
307
|
+
sessionFromRow(row) {
|
|
308
|
+
const roles = JSON.parse(row.roles_json);
|
|
309
|
+
return {
|
|
310
|
+
tokenHash: row.token_hash,
|
|
311
|
+
csrfTokenHash: row.csrf_token_hash,
|
|
312
|
+
subject: row.subject,
|
|
313
|
+
roles,
|
|
314
|
+
createdAt: row.created_at,
|
|
315
|
+
lastSeenAt: row.last_seen_at,
|
|
316
|
+
idleExpiresAt: row.idle_expires_at,
|
|
317
|
+
absoluteExpiresAt: row.absolute_expires_at,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
positiveInteger(value, name) {
|
|
321
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
322
|
+
throw new TypeError(`${name} must be a positive safe integer.`);
|
|
323
|
+
return value;
|
|
324
|
+
}
|
|
325
|
+
assertOpen() {
|
|
326
|
+
if (this.closed)
|
|
327
|
+
throw new Error('SQLite persistence is closed.');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
export function createSQLitePersistence(filenameOrOptions) {
|
|
331
|
+
return new SQLitePersistence(filenameOrOptions);
|
|
332
|
+
}
|
|
333
|
+
function assertHash(name, value) {
|
|
334
|
+
if (!SHA256_HEX.test(value))
|
|
335
|
+
throw new TypeError(`${name} must be a lowercase SHA-256 hex digest.`);
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@copypatch/storage-sqlite",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "SQLite persistence adapter for CopyPatch",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://copypatch.vercel.app",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Woffluon/CopyPatch.git",
|
|
11
|
+
"directory": "packages/storage-sqlite"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Woffluon/CopyPatch/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"copypatch",
|
|
18
|
+
"sqlite",
|
|
19
|
+
"persistence",
|
|
20
|
+
"content-editing"
|
|
21
|
+
],
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"module": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20.0.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"registry": "https://registry.npmjs.org/"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -b tsconfig.json",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run --root ../.. packages/storage-sqlite/tests/storage-sqlite.test.ts",
|
|
46
|
+
"test:coverage": "vitest run --root ../.. --coverage packages/storage-sqlite/tests/storage-sqlite.test.ts",
|
|
47
|
+
"clean": "rm -rf dist"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@copypatch/core": "2.0.0",
|
|
51
|
+
"better-sqlite3": "^11.8.1"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
55
|
+
"@types/node": "^24.0.0",
|
|
56
|
+
"typescript": "^5.8.0",
|
|
57
|
+
"vitest": "^3.2.7"
|
|
58
|
+
}
|
|
59
|
+
}
|