@tangentfeed/adapter-sqlite 0.2.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 +91 -0
- package/dist/index.js +249 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sreeraj T A
|
|
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
|
+
# @tangentfeed/adapter-sqlite
|
|
2
|
+
|
|
3
|
+
SQLite storage adapter for [tangentfeed](https://github.com/sreerajta/tangentfeed). Runs
|
|
4
|
+
on Node, Electron, Bun, and React Native — your synced data becomes a real
|
|
5
|
+
SQLite database you can open with any client.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import Database from "better-sqlite3";
|
|
9
|
+
import { SqliteAdapter, betterSqliteDriver } from "@tangentfeed/adapter-sqlite";
|
|
10
|
+
import { SyncEngine } from "@tangentfeed/core";
|
|
11
|
+
|
|
12
|
+
const storage = SqliteAdapter.open(betterSqliteDriver(new Database("tasks.db")));
|
|
13
|
+
const engine = await SyncEngine.open({ deviceId, storage });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Drivers
|
|
17
|
+
|
|
18
|
+
The adapter targets a four-method interface, so the SQLite binding is your
|
|
19
|
+
choice:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
betterSqliteDriver(new Database("tasks.db")) // better-sqlite3
|
|
23
|
+
nodeSqliteDriver(new DatabaseSync("tasks.db")) // node:sqlite (Node 22+)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Bun's `bun:sqlite` and `expo-sqlite` fit the same shape; wrap them with a small
|
|
27
|
+
object exposing `exec`, `prepare`, and optionally `close`.
|
|
28
|
+
|
|
29
|
+
## Schema
|
|
30
|
+
|
|
31
|
+
| Table | Contents |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `ops` | The operation log. `id` is the HLC string, so PRIMARY KEY order is causal order. Indexed on `(device, hlc)` for frontier diffs |
|
|
34
|
+
| `cells` | Materialized state: winning op per `(table_name, row_id, column_name)` |
|
|
35
|
+
| `meta` | Frontier, persisted clock, recorded peer frontiers |
|
|
36
|
+
|
|
37
|
+
Both data tables are `WITHOUT ROWID`, since their primary keys are the natural
|
|
38
|
+
access paths.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
sqlite3 tasks.db "SELECT table_name, column_name, value FROM ops ORDER BY id;"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Writes run inside `BEGIN IMMEDIATE` transactions, satisfying the all-or-nothing
|
|
45
|
+
requirement in PROTOCOL.md §8.2.
|
|
46
|
+
|
|
47
|
+
Part of [tangentfeed](https://github.com/sreerajta/tangentfeed). MIT licensed.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { StorageAdapter, Op, Frontier, ClockState, DeviceKey, CompactionWrite, BatchWrite } from '@tangentfeed/core';
|
|
2
|
+
export { aboveFrontier } from '@tangentfeed/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SQLite storage adapter — PROTOCOL.md §8 on real tables.
|
|
6
|
+
*
|
|
7
|
+
* Schema (three tables, mirroring the IndexedDB layout):
|
|
8
|
+
*
|
|
9
|
+
* ops (id PK, table_name, row_id, column_name, value, hlc, device)
|
|
10
|
+
* The operation log. `id` is the 34-char HLC string, so PRIMARY KEY
|
|
11
|
+
* order IS causal order — the same trick the IndexedDB adapter uses,
|
|
12
|
+
* and the reason opsSince() can stream in order with no sort.
|
|
13
|
+
* Index on (device, hlc) makes frontier-diff queries index-only,
|
|
14
|
+
* which is the hot path during sync.
|
|
15
|
+
*
|
|
16
|
+
* cells (table_name, row_id, column_name PK, op_json)
|
|
17
|
+
* Materialized state: the currently winning op per cell. Composite
|
|
18
|
+
* primary key gives us per-table and per-row scans for free, without
|
|
19
|
+
* the string-concatenation keys IndexedDB forced on us.
|
|
20
|
+
*
|
|
21
|
+
* meta (key PK, value)
|
|
22
|
+
* Frontier, persisted clock state, recorded peer frontiers.
|
|
23
|
+
*
|
|
24
|
+
* Atomicity (§8.2): applyBatch and compact run inside a single IMMEDIATE
|
|
25
|
+
* transaction, so a crash mid-apply can never leave the log and materialized
|
|
26
|
+
* state disagreeing.
|
|
27
|
+
*
|
|
28
|
+
* Driver-agnostic: works with better-sqlite3, node:sqlite, bun:sqlite, or
|
|
29
|
+
* expo-sqlite by supplying a ~4-method adapter (see SqliteDriver). Ready-made
|
|
30
|
+
* wrappers for the first two are exported below.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
interface SqliteStatement {
|
|
34
|
+
all(...params: unknown[]): unknown[];
|
|
35
|
+
get(...params: unknown[]): unknown;
|
|
36
|
+
run(...params: unknown[]): void;
|
|
37
|
+
}
|
|
38
|
+
interface SqliteDriver {
|
|
39
|
+
exec(sql: string): void;
|
|
40
|
+
prepare(sql: string): SqliteStatement;
|
|
41
|
+
close?(): void;
|
|
42
|
+
}
|
|
43
|
+
/** Wrap a better-sqlite3 Database. */
|
|
44
|
+
declare function betterSqliteDriver(db: {
|
|
45
|
+
exec(sql: string): unknown;
|
|
46
|
+
prepare(sql: string): {
|
|
47
|
+
all(...p: unknown[]): unknown[];
|
|
48
|
+
get(...p: unknown[]): unknown;
|
|
49
|
+
run(...p: unknown[]): unknown;
|
|
50
|
+
};
|
|
51
|
+
close(): unknown;
|
|
52
|
+
}): SqliteDriver;
|
|
53
|
+
/** Wrap a node:sqlite DatabaseSync (Node 22+). */
|
|
54
|
+
declare function nodeSqliteDriver(db: {
|
|
55
|
+
exec(sql: string): unknown;
|
|
56
|
+
prepare(sql: string): {
|
|
57
|
+
all(...p: unknown[]): unknown[];
|
|
58
|
+
get(...p: unknown[]): unknown;
|
|
59
|
+
run(...p: unknown[]): unknown;
|
|
60
|
+
};
|
|
61
|
+
close(): unknown;
|
|
62
|
+
}): SqliteDriver;
|
|
63
|
+
declare class SqliteAdapter implements StorageAdapter {
|
|
64
|
+
private readonly db;
|
|
65
|
+
private readonly stmts;
|
|
66
|
+
private constructor();
|
|
67
|
+
static open(driver: SqliteDriver): SqliteAdapter;
|
|
68
|
+
close(): void;
|
|
69
|
+
getRow(table: string, row: string): Promise<ReadonlyMap<string, Op> | undefined>;
|
|
70
|
+
listRows(table: string): Promise<string[]>;
|
|
71
|
+
listTables(): Promise<string[]>;
|
|
72
|
+
hasOp(id: string): Promise<boolean>;
|
|
73
|
+
getWinner(table: string, row: string, column: string): Promise<Op | undefined>;
|
|
74
|
+
opsSince(frontier: Frontier): Promise<Op[]>;
|
|
75
|
+
getFrontier(): Promise<Frontier>;
|
|
76
|
+
getClock(): Promise<ClockState | undefined>;
|
|
77
|
+
getDeviceKey(): Promise<DeviceKey | undefined>;
|
|
78
|
+
setDeviceKey(key: DeviceKey): Promise<void>;
|
|
79
|
+
opCount(): Promise<number>;
|
|
80
|
+
allOps(): Promise<Op[]>;
|
|
81
|
+
getPeerFrontiers(): Promise<Record<string, Frontier>>;
|
|
82
|
+
setPeerFrontier(peer: string, frontier: Frontier): Promise<void>;
|
|
83
|
+
compact(write: CompactionWrite): Promise<void>;
|
|
84
|
+
applyBatch(batch: BatchWrite): Promise<void>;
|
|
85
|
+
/** All-or-nothing (§8.2). IMMEDIATE takes the write lock up front. */
|
|
86
|
+
private transaction;
|
|
87
|
+
private readMeta;
|
|
88
|
+
private writeMeta;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { SqliteAdapter, type SqliteDriver, type SqliteStatement, betterSqliteDriver, nodeSqliteDriver };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
aboveFrontier
|
|
4
|
+
} from "@tangentfeed/core";
|
|
5
|
+
function betterSqliteDriver(db) {
|
|
6
|
+
return {
|
|
7
|
+
exec: (sql) => void db.exec(sql),
|
|
8
|
+
prepare: (sql) => {
|
|
9
|
+
const stmt = db.prepare(sql);
|
|
10
|
+
return {
|
|
11
|
+
all: (...p) => stmt.all(...p),
|
|
12
|
+
get: (...p) => stmt.get(...p),
|
|
13
|
+
run: (...p) => void stmt.run(...p)
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
close: () => void db.close()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function nodeSqliteDriver(db) {
|
|
20
|
+
return betterSqliteDriver(db);
|
|
21
|
+
}
|
|
22
|
+
var SCHEMA = `
|
|
23
|
+
PRAGMA journal_mode = WAL;
|
|
24
|
+
PRAGMA foreign_keys = ON;
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS ops (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
table_name TEXT NOT NULL,
|
|
29
|
+
row_id TEXT NOT NULL,
|
|
30
|
+
column_name TEXT NOT NULL,
|
|
31
|
+
value TEXT NOT NULL, -- JSON-encoded, so NULL stays distinguishable
|
|
32
|
+
hlc TEXT NOT NULL,
|
|
33
|
+
device TEXT NOT NULL,
|
|
34
|
+
sig TEXT NOT NULL -- base64 Ed25519, section 12
|
|
35
|
+
) WITHOUT ROWID;
|
|
36
|
+
|
|
37
|
+
-- the sync hot path: "everything from device D above hlc H"
|
|
38
|
+
CREATE INDEX IF NOT EXISTS ops_device_hlc ON ops (device, hlc);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS cells (
|
|
41
|
+
table_name TEXT NOT NULL,
|
|
42
|
+
row_id TEXT NOT NULL,
|
|
43
|
+
column_name TEXT NOT NULL,
|
|
44
|
+
op_json TEXT NOT NULL,
|
|
45
|
+
PRIMARY KEY (table_name, row_id, column_name)
|
|
46
|
+
) WITHOUT ROWID;
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
49
|
+
key TEXT PRIMARY KEY,
|
|
50
|
+
value TEXT NOT NULL
|
|
51
|
+
) WITHOUT ROWID;
|
|
52
|
+
`;
|
|
53
|
+
var SqliteAdapter = class _SqliteAdapter {
|
|
54
|
+
db;
|
|
55
|
+
stmts;
|
|
56
|
+
constructor(db) {
|
|
57
|
+
this.db = db;
|
|
58
|
+
db.exec(SCHEMA);
|
|
59
|
+
this.stmts = {
|
|
60
|
+
insertOp: db.prepare(
|
|
61
|
+
`INSERT OR IGNORE INTO ops (id, table_name, row_id, column_name, value, hlc, device, sig)
|
|
62
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
63
|
+
),
|
|
64
|
+
deleteOp: db.prepare(`DELETE FROM ops WHERE id = ?`),
|
|
65
|
+
upsertCell: db.prepare(
|
|
66
|
+
`INSERT INTO cells (table_name, row_id, column_name, op_json) VALUES (?, ?, ?, ?)
|
|
67
|
+
ON CONFLICT(table_name, row_id, column_name) DO UPDATE SET op_json = excluded.op_json`
|
|
68
|
+
),
|
|
69
|
+
deleteCell: db.prepare(
|
|
70
|
+
`DELETE FROM cells WHERE table_name = ? AND row_id = ? AND column_name = ?`
|
|
71
|
+
),
|
|
72
|
+
getCell: db.prepare(
|
|
73
|
+
`SELECT op_json FROM cells WHERE table_name = ? AND row_id = ? AND column_name = ?`
|
|
74
|
+
),
|
|
75
|
+
rowCells: db.prepare(
|
|
76
|
+
`SELECT column_name, op_json FROM cells WHERE table_name = ? AND row_id = ?`
|
|
77
|
+
),
|
|
78
|
+
tableRows: db.prepare(`SELECT DISTINCT row_id FROM cells WHERE table_name = ?`),
|
|
79
|
+
tables: db.prepare(`SELECT DISTINCT table_name FROM cells`),
|
|
80
|
+
countOp: db.prepare(`SELECT 1 AS found FROM ops WHERE id = ?`),
|
|
81
|
+
countOps: db.prepare(`SELECT COUNT(*) AS n FROM ops`),
|
|
82
|
+
allOps: db.prepare(`SELECT * FROM ops ORDER BY id`),
|
|
83
|
+
opsAboveFor: db.prepare(
|
|
84
|
+
`SELECT * FROM ops WHERE device = ? AND hlc > ? ORDER BY hlc`
|
|
85
|
+
),
|
|
86
|
+
allOpsUnfiltered: db.prepare(`SELECT * FROM ops ORDER BY id`),
|
|
87
|
+
getMeta: db.prepare(`SELECT value FROM meta WHERE key = ?`),
|
|
88
|
+
setMeta: db.prepare(
|
|
89
|
+
`INSERT INTO meta (key, value) VALUES (?, ?)
|
|
90
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
91
|
+
)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
static open(driver) {
|
|
95
|
+
return new _SqliteAdapter(driver);
|
|
96
|
+
}
|
|
97
|
+
close() {
|
|
98
|
+
this.db.close?.();
|
|
99
|
+
}
|
|
100
|
+
// ---------- reads ----------
|
|
101
|
+
async getRow(table, row) {
|
|
102
|
+
const rows = this.stmts.rowCells.all(table, row);
|
|
103
|
+
if (rows.length === 0) return void 0;
|
|
104
|
+
const out = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const r of rows) out.set(r.column_name, JSON.parse(r.op_json));
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
async listRows(table) {
|
|
109
|
+
return this.stmts.tableRows.all(table).map((r) => r.row_id);
|
|
110
|
+
}
|
|
111
|
+
async listTables() {
|
|
112
|
+
return this.stmts.tables.all().map((r) => r.table_name);
|
|
113
|
+
}
|
|
114
|
+
async hasOp(id) {
|
|
115
|
+
return this.stmts.countOp.get(id) !== void 0;
|
|
116
|
+
}
|
|
117
|
+
async getWinner(table, row, column) {
|
|
118
|
+
const rec = this.stmts.getCell.get(table, row, column);
|
|
119
|
+
return rec ? JSON.parse(rec.op_json) : void 0;
|
|
120
|
+
}
|
|
121
|
+
async opsSince(frontier) {
|
|
122
|
+
const devices = Object.keys(frontier);
|
|
123
|
+
if (devices.length === 0) {
|
|
124
|
+
return this.stmts.allOpsUnfiltered.all().map(toOp);
|
|
125
|
+
}
|
|
126
|
+
const seen = /* @__PURE__ */ new Set();
|
|
127
|
+
const out = [];
|
|
128
|
+
for (const device of devices) {
|
|
129
|
+
for (const r of this.stmts.opsAboveFor.all(device, frontier[device])) {
|
|
130
|
+
seen.add(r.id);
|
|
131
|
+
out.push(toOp(r));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const r of this.stmts.allOpsUnfiltered.all()) {
|
|
135
|
+
if (frontier[r.device] === void 0 && !seen.has(r.id)) out.push(toOp(r));
|
|
136
|
+
}
|
|
137
|
+
out.sort((a, b) => a.hlc < b.hlc ? -1 : a.hlc > b.hlc ? 1 : 0);
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
async getFrontier() {
|
|
141
|
+
return this.readMeta("frontier") ?? {};
|
|
142
|
+
}
|
|
143
|
+
async getClock() {
|
|
144
|
+
return this.readMeta("clock");
|
|
145
|
+
}
|
|
146
|
+
// ---------- signing identity (§12) ----------
|
|
147
|
+
// Meta values are JSON, which has no byte-array type, so the keypair is
|
|
148
|
+
// stored hex-encoded rather than as an array of numbers — half the size and
|
|
149
|
+
// unambiguous to read back.
|
|
150
|
+
async getDeviceKey() {
|
|
151
|
+
const stored = this.readMeta("deviceKey");
|
|
152
|
+
if (!stored) return void 0;
|
|
153
|
+
return { publicKey: unhex(stored.publicKey), privateKey: unhex(stored.privateKey) };
|
|
154
|
+
}
|
|
155
|
+
async setDeviceKey(key) {
|
|
156
|
+
this.writeMeta("deviceKey", {
|
|
157
|
+
publicKey: hex(key.publicKey),
|
|
158
|
+
privateKey: hex(key.privateKey)
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// ---------- compaction support ----------
|
|
162
|
+
async opCount() {
|
|
163
|
+
return this.stmts.countOps.get().n;
|
|
164
|
+
}
|
|
165
|
+
async allOps() {
|
|
166
|
+
return this.stmts.allOps.all().map(toOp);
|
|
167
|
+
}
|
|
168
|
+
async getPeerFrontiers() {
|
|
169
|
+
return this.readMeta("peers") ?? {};
|
|
170
|
+
}
|
|
171
|
+
async setPeerFrontier(peer, frontier) {
|
|
172
|
+
const current = await this.getPeerFrontiers();
|
|
173
|
+
this.writeMeta("peers", { ...current, [peer]: frontier });
|
|
174
|
+
}
|
|
175
|
+
async compact(write) {
|
|
176
|
+
this.transaction(() => {
|
|
177
|
+
for (const id of write.opIds) this.stmts.deleteOp.run(id);
|
|
178
|
+
for (const key of write.cellKeys) {
|
|
179
|
+
const [table, row, column] = key.split("\0");
|
|
180
|
+
this.stmts.deleteCell.run(table, row, column);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// ---------- the one write path ----------
|
|
185
|
+
async applyBatch(batch) {
|
|
186
|
+
this.transaction(() => {
|
|
187
|
+
for (const op of batch.ops) {
|
|
188
|
+
this.stmts.insertOp.run(
|
|
189
|
+
op.id,
|
|
190
|
+
op.table,
|
|
191
|
+
op.row,
|
|
192
|
+
op.column,
|
|
193
|
+
JSON.stringify(op.value),
|
|
194
|
+
op.hlc,
|
|
195
|
+
op.device,
|
|
196
|
+
op.sig
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
for (const [key, op] of batch.winners) {
|
|
200
|
+
const [table, row, column] = key.split("\0");
|
|
201
|
+
this.stmts.upsertCell.run(table, row, column, JSON.stringify(op));
|
|
202
|
+
}
|
|
203
|
+
this.stmts.setMeta.run("frontier", JSON.stringify(batch.frontier));
|
|
204
|
+
this.stmts.setMeta.run("clock", JSON.stringify(batch.clock));
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// ---------- helpers ----------
|
|
208
|
+
/** All-or-nothing (§8.2). IMMEDIATE takes the write lock up front. */
|
|
209
|
+
transaction(fn) {
|
|
210
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
211
|
+
try {
|
|
212
|
+
fn();
|
|
213
|
+
this.db.exec("COMMIT");
|
|
214
|
+
} catch (err) {
|
|
215
|
+
try {
|
|
216
|
+
this.db.exec("ROLLBACK");
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
throw err;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
readMeta(key) {
|
|
223
|
+
const rec = this.stmts.getMeta.get(key);
|
|
224
|
+
return rec ? JSON.parse(rec.value) : void 0;
|
|
225
|
+
}
|
|
226
|
+
writeMeta(key, value) {
|
|
227
|
+
this.stmts.setMeta.run(key, JSON.stringify(value));
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
function toOp(r) {
|
|
231
|
+
return {
|
|
232
|
+
id: r.id,
|
|
233
|
+
table: r.table_name,
|
|
234
|
+
row: r.row_id,
|
|
235
|
+
column: r.column_name,
|
|
236
|
+
value: JSON.parse(r.value),
|
|
237
|
+
hlc: r.hlc,
|
|
238
|
+
device: r.device,
|
|
239
|
+
sig: r.sig
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
var hex = (b) => [...b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
243
|
+
var unhex = (s) => new Uint8Array((s.match(/../g) ?? []).map((h) => parseInt(h, 16)));
|
|
244
|
+
export {
|
|
245
|
+
SqliteAdapter,
|
|
246
|
+
aboveFrontier,
|
|
247
|
+
betterSqliteDriver,
|
|
248
|
+
nodeSqliteDriver
|
|
249
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tangentfeed/adapter-sqlite",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "SQLite storage adapter for tangentfeed (better-sqlite3, node:sqlite, bun:sqlite)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
22
|
+
"prepack": "npm run build",
|
|
23
|
+
"test": "vitest run"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@tangentfeed/core": "0.2.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
30
|
+
"@types/node": "^20.0.0",
|
|
31
|
+
"better-sqlite3": "^11.0.0",
|
|
32
|
+
"tsup": "^8.5.0",
|
|
33
|
+
"typescript": "^5.5.0",
|
|
34
|
+
"vitest": "^2.0.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
}
|
|
39
|
+
}
|