@semics-tech/mongolite 1.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +179 -0
  3. package/dist/adapters/browser.d.ts +97 -0
  4. package/dist/adapters/browser.js +103 -0
  5. package/dist/adapters/browser.js.map +1 -0
  6. package/dist/adapters/cloudflare.d.ts +106 -0
  7. package/dist/adapters/cloudflare.js +94 -0
  8. package/dist/adapters/cloudflare.js.map +1 -0
  9. package/dist/bin/mongolite-debug.d.ts +2 -0
  10. package/dist/bin/mongolite-debug.js +115 -0
  11. package/dist/bin/mongolite-debug.js.map +1 -0
  12. package/dist/changeStream.d.ts +114 -0
  13. package/dist/changeStream.js +338 -0
  14. package/dist/changeStream.js.map +1 -0
  15. package/dist/cloudflare.d.ts +39 -0
  16. package/dist/cloudflare.js +39 -0
  17. package/dist/cloudflare.js.map +1 -0
  18. package/dist/collection.d.ts +274 -0
  19. package/dist/collection.js +1706 -0
  20. package/dist/collection.js.map +1 -0
  21. package/dist/cursors/findCursor.d.ts +101 -0
  22. package/dist/cursors/findCursor.js +757 -0
  23. package/dist/cursors/findCursor.js.map +1 -0
  24. package/dist/db.d.ts +116 -0
  25. package/dist/db.js +254 -0
  26. package/dist/db.js.map +1 -0
  27. package/dist/debugger/query-debugger.d.ts +22 -0
  28. package/dist/debugger/query-debugger.js +258 -0
  29. package/dist/debugger/query-debugger.js.map +1 -0
  30. package/dist/index.d.ts +30 -0
  31. package/dist/index.js +40 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/mongo-client.d.ts +56 -0
  34. package/dist/mongo-client.js +55 -0
  35. package/dist/mongo-client.js.map +1 -0
  36. package/dist/types.d.ts +318 -0
  37. package/dist/types.js +2 -0
  38. package/dist/types.js.map +1 -0
  39. package/dist/utils/indexing.d.ts +4 -0
  40. package/dist/utils/indexing.js +43 -0
  41. package/dist/utils/indexing.js.map +1 -0
  42. package/dist/utils/projection.d.ts +16 -0
  43. package/dist/utils/projection.js +98 -0
  44. package/dist/utils/projection.js.map +1 -0
  45. package/dist/utils/queryOperators.d.ts +16 -0
  46. package/dist/utils/queryOperators.js +138 -0
  47. package/dist/utils/queryOperators.js.map +1 -0
  48. package/package.json +92 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 YOUR_NAME
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,179 @@
1
+ # MongoLite
2
+
3
+ [![CI](https://github.com/semics-tech/mongolite/actions/workflows/ci.yml/badge.svg)](https://github.com/semics-tech/mongolite/actions/workflows/ci.yml)
4
+ [![NPM version](https://img.shields.io/npm/v/@semics-tech/mongolite.svg)](https://www.npmjs.com/package/@semics-tech/mongolite)
5
+ [![Codecov](https://codecov.io/gh/semics-tech/mongolite/branch/master/graph/badge.svg)](https://codecov.io/gh/semics-tech/mongolite)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
7
+
8
+ A MongoDB-like client backed by SQLite. Use a familiar MongoDB API with the simplicity of a local file-based database — no server required.
9
+
10
+ ## Why MongoLite?
11
+
12
+ - You want a MongoDB-style API without running a MongoDB server
13
+ - You need a lightweight, embedded database for local apps, CLIs, or testing
14
+ - You want simple file-based persistence with zero infrastructure overhead
15
+
16
+ ## Features
17
+
18
+ - **MongoDB-compatible API** — `insertOne`, `findOne`, `updateOne`, `deleteOne`, `find`, `aggregate`, and more
19
+ - **SQLite persistence** — single file, zero configuration, works offline
20
+ - **Automatic `_id` generation** — UUID assigned on insert if not provided
21
+ - **WAL mode** — Write-Ahead Logging for better concurrent read access
22
+ - **Rich query operators** — `$eq`, `$gt`, `$in`, `$and`, `$or`, `$elemMatch`, `$regex`, and more
23
+ - **Update operators** — `$set`, `$inc`, `$push`, `$pull`, `$addToSet`, `$mul`, and more
24
+ - **Indexing** — create, list, and drop indexes including unique and compound indexes
25
+ - **Change streams** — real-time change tracking via `collection.watch()`
26
+ - **JSON safety** — validates documents before insert and recovers from corrupted data
27
+ - **TypeScript** — fully typed with strict mode
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install @semics-tech/mongolite
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```typescript
38
+ import { MongoLite } from '@semics-tech/mongolite';
39
+
40
+ async function main() {
41
+ const client = new MongoLite('./myapp.sqlite');
42
+ // Use ':memory:' for an ephemeral in-memory database
43
+
44
+ const users = client.collection('users');
45
+
46
+ // Insert
47
+ const result = await users.insertOne({ name: 'Alice', age: 30 });
48
+
49
+ // Find
50
+ const user = await users.findOne({ name: 'Alice' });
51
+
52
+ // Update
53
+ await users.updateOne({ name: 'Alice' }, { $set: { age: 31 } });
54
+
55
+ // Delete
56
+ await users.deleteOne({ name: 'Alice' });
57
+
58
+ await client.close();
59
+ }
60
+
61
+ main();
62
+ ```
63
+
64
+ ## How does it compare?
65
+
66
+ | | MongoLite | lowdb | better-sqlite3 (raw) | NeDB | PouchDB | MongoDB |
67
+ |---|---|---|---|---|---|---|
68
+ | MongoDB query API (`$set`, `$elemMatch`, aggregation...) | ✅ | ❌ (plain object access) | ❌ (raw SQL) | ✅ | ❌ (Mango/CouchDB-style) | ✅ |
69
+ | Runs in the browser | ✅ (sql.js) | ✅ | ❌ (native binding) | ✅ | ✅ (IndexedDB) | ❌ |
70
+ | Runs on the edge (Cloudflare Durable Objects) | ✅ | ❌ | ❌ (native binding) | ❌ | ❌ | ❌ |
71
+ | Zero infrastructure (no server process) | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
72
+ | TypeScript, strict mode | ✅ | ✅ | ✅ | ⚠️ (community types) | ⚠️ (community types) | ✅ |
73
+ | Actively maintained | ✅ | ✅ | ✅ | ❌ (unmaintained) | ⚠️ (slow-moving) | ✅ |
74
+
75
+ MongoLite's niche: the MongoDB query API you already know, running anywhere SQLite runs — a local file, an in-memory test database, the browser, or a Cloudflare Durable Object — without standing up a MongoDB server.
76
+
77
+ ## Documentation
78
+
79
+ | Topic | Description |
80
+ |-------|-------------|
81
+ | [API Reference](./docs/API.md) | Full API docs: methods, query operators, update operators |
82
+ | [Change Streams](./docs/CHANGE_STREAMS.md) | Real-time change tracking with `collection.watch()` |
83
+ | [JSON Safety](./docs/JSON_SAFETY.md) | Document validation and corrupted data recovery |
84
+ | [Query Debugger](./docs/DEBUGGER.md) | Interactive CLI for debugging queries and inspecting SQL |
85
+ | [Benchmarks](./docs/BENCHMARKS.md) | Performance benchmarks and storage characteristics |
86
+ | [Cloudflare Durable Objects](./docs/CLOUDFLARE.md) | Using MongoLite inside a Cloudflare Durable Object |
87
+
88
+ ## Backend Examples
89
+
90
+ ### SQLite file (Node.js / Bun)
91
+
92
+ ```typescript
93
+ import { MongoLite } from '@semics-tech/mongolite';
94
+
95
+ const client = new MongoLite('./myapp.sqlite');
96
+ await client.connect();
97
+ const users = client.collection('users');
98
+ await users.insertOne({ name: 'Alice', age: 30 });
99
+ await client.close();
100
+ ```
101
+
102
+ ### In-memory (tests / ephemeral)
103
+
104
+ ```typescript
105
+ import { MongoLite } from '@semics-tech/mongolite';
106
+
107
+ const client = new MongoLite(':memory:');
108
+ await client.connect();
109
+ const users = client.collection('users');
110
+ await users.insertOne({ name: 'Alice', age: 30 });
111
+ // Data is discarded when the process exits
112
+ await client.close();
113
+ ```
114
+
115
+ ### Browser (via sql.js)
116
+
117
+ Requires [sql.js](https://www.npmjs.com/package/sql.js) (`npm install sql.js`).
118
+
119
+ ```typescript
120
+ import initSqlJs from 'sql.js';
121
+ import { MongoLite, BrowserSqliteAdapter } from '@semics-tech/mongolite';
122
+
123
+ const SQL = await initSqlJs({
124
+ locateFile: (file) => `https://cdn.jsdelivr.net/npm/sql.js/dist/${file}`,
125
+ });
126
+ const sqlJsDb = new SQL.Database(); // in-memory; use OPFS/IndexedDB for persistence
127
+
128
+ const client = new MongoLite(new BrowserSqliteAdapter(sqlJsDb));
129
+ await client.connect();
130
+ const users = client.collection('users');
131
+ await users.insertOne({ name: 'Alice', age: 30 });
132
+ console.log(await users.findOne({ name: 'Alice' }));
133
+ await client.close();
134
+ ```
135
+
136
+ ### Cloudflare Durable Objects
137
+
138
+ ```typescript
139
+ import { DurableObject } from 'cloudflare:workers';
140
+ import { MongoLite, CloudflareDurableObjectAdapter } from '@semics-tech/mongolite/cloudflare';
141
+
142
+ export class MyDurableObject extends DurableObject {
143
+ private client: MongoLite;
144
+
145
+ constructor(ctx: DurableObjectState, env: Env) {
146
+ super(ctx, env);
147
+ // Pass ctx.storage.sql — no file path needed
148
+ this.client = new MongoLite(new CloudflareDurableObjectAdapter(ctx.storage.sql));
149
+ ctx.blockConcurrencyWhile(() => this.client.collection('users').ensureTable());
150
+ }
151
+
152
+ async fetch(request: Request) {
153
+ const users = this.client.collection('users');
154
+ await users.insertOne({ name: 'Alice', age: 30 });
155
+ return Response.json(await users.findOne({ name: 'Alice' }));
156
+ }
157
+ }
158
+ ```
159
+
160
+ > See [docs/CLOUDFLARE.md](./docs/CLOUDFLARE.md) for the full guide, supported operations, and limitations.
161
+
162
+ ## Development
163
+
164
+ ```bash
165
+ git clone https://github.com/semics-tech/mongolite.git
166
+ cd mongolite
167
+ npm install
168
+ npm test # Run tests
169
+ npm run build # Compile TypeScript
170
+ npm run lint # Lint code
171
+ ```
172
+
173
+ ## Contributing
174
+
175
+ Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) before submitting a pull request.
176
+
177
+ ## License
178
+
179
+ [MIT](./LICENSE)
@@ -0,0 +1,97 @@
1
+ import type { IDatabaseAdapter } from '../db.js';
2
+ type SqlJsColumnType = string | number | bigint | null | Uint8Array;
3
+ type SqlJsBindParams = SqlJsColumnType[] | Record<string, SqlJsColumnType> | null;
4
+ /**
5
+ * Minimal interface representing a sql.js Statement object.
6
+ * @see https://sql.js.org/documentation/Statement.html
7
+ */
8
+ export interface SqlJsStatement {
9
+ bind(params?: SqlJsBindParams): boolean;
10
+ step(): boolean;
11
+ getAsObject(params?: SqlJsBindParams | null): Record<string, SqlJsColumnType>;
12
+ free(): boolean;
13
+ }
14
+ /**
15
+ * Minimal interface representing a sql.js Database object.
16
+ * @see https://sql.js.org/documentation/Database.html
17
+ */
18
+ export interface SqlJsDatabase {
19
+ run(sql: string, params?: SqlJsBindParams): SqlJsDatabase;
20
+ exec(sql: string): Array<{
21
+ columns: string[];
22
+ values: SqlJsColumnType[][];
23
+ }>;
24
+ prepare(sql: string): SqlJsStatement;
25
+ close(): void;
26
+ }
27
+ /**
28
+ * Database adapter that wraps a {@link SqlJsDatabase} instance, enabling MongoLite
29
+ * to run in browser environments via [sql.js](https://sql.js.org/).
30
+ *
31
+ * ## Usage
32
+ *
33
+ * Install [sql.js](https://www.npmjs.com/package/sql.js) and initialise it before
34
+ * constructing the adapter:
35
+ *
36
+ * ```ts
37
+ * import initSqlJs from 'sql.js';
38
+ * import { MongoLite, BrowserSqliteAdapter } from '@semics-tech/mongolite';
39
+ *
40
+ * const SQL = await initSqlJs({
41
+ * locateFile: (file) => `https://cdn.jsdelivr.net/npm/sql.js/dist/${file}`,
42
+ * });
43
+ * const sqlJsDb = new SQL.Database(); // in-memory; persist via OPFS or IndexedDB
44
+ *
45
+ * const client = new MongoLite(new BrowserSqliteAdapter(sqlJsDb));
46
+ * await client.connect();
47
+ * const users = client.collection('users');
48
+ * await users.insertOne({ name: 'Alice', age: 30 });
49
+ * await client.close();
50
+ * ```
51
+ *
52
+ * ## Limitations
53
+ *
54
+ * - **`$regex` queries are not supported** — sql.js does not ship with a `regexp()`
55
+ * SQL function. Use JS-side filtering as a workaround.
56
+ * - **`lastID` / `changes`** are not exposed by the sql.js API. `run()` always
57
+ * returns `{ lastID: 0, changes: 0 }`, which is safe because MongoLite does not
58
+ * use these values internally.
59
+ * - **Persistence** — by default sql.js stores data in memory. For durable storage
60
+ * use the OPFS back-end or export the database buffer to IndexedDB.
61
+ */
62
+ export declare class BrowserSqliteAdapter implements IDatabaseAdapter {
63
+ private readonly db;
64
+ /**
65
+ * @param db An initialised sql.js `Database` instance.
66
+ */
67
+ constructor(db: SqlJsDatabase);
68
+ /**
69
+ * No-op: sql.js Database is already open on construction.
70
+ */
71
+ connect(): Promise<void>;
72
+ /**
73
+ * Closes the underlying sql.js Database and frees its WASM memory.
74
+ */
75
+ close(): Promise<void>;
76
+ /**
77
+ * Executes a DML statement (INSERT, UPDATE, DELETE) with optional bound parameters.
78
+ * Returns `{ lastID: 0, changes: 0 }` — sql.js does not expose these values.
79
+ */
80
+ run(sql: string, params?: unknown[]): Promise<{
81
+ lastID: number;
82
+ changes: number;
83
+ }>;
84
+ /**
85
+ * Executes a SQL query and returns the first matching row, or `undefined`.
86
+ */
87
+ get<T>(sql: string, params?: unknown[]): Promise<T | undefined>;
88
+ /**
89
+ * Executes a SQL query and returns all matching rows.
90
+ */
91
+ all<T>(sql: string, params?: unknown[]): Promise<T[]>;
92
+ /**
93
+ * Executes one or more SQL statements (DDL, transactions, etc.).
94
+ */
95
+ exec(sql: string): Promise<void>;
96
+ }
97
+ export {};
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Database adapter that wraps a {@link SqlJsDatabase} instance, enabling MongoLite
3
+ * to run in browser environments via [sql.js](https://sql.js.org/).
4
+ *
5
+ * ## Usage
6
+ *
7
+ * Install [sql.js](https://www.npmjs.com/package/sql.js) and initialise it before
8
+ * constructing the adapter:
9
+ *
10
+ * ```ts
11
+ * import initSqlJs from 'sql.js';
12
+ * import { MongoLite, BrowserSqliteAdapter } from '@semics-tech/mongolite';
13
+ *
14
+ * const SQL = await initSqlJs({
15
+ * locateFile: (file) => `https://cdn.jsdelivr.net/npm/sql.js/dist/${file}`,
16
+ * });
17
+ * const sqlJsDb = new SQL.Database(); // in-memory; persist via OPFS or IndexedDB
18
+ *
19
+ * const client = new MongoLite(new BrowserSqliteAdapter(sqlJsDb));
20
+ * await client.connect();
21
+ * const users = client.collection('users');
22
+ * await users.insertOne({ name: 'Alice', age: 30 });
23
+ * await client.close();
24
+ * ```
25
+ *
26
+ * ## Limitations
27
+ *
28
+ * - **`$regex` queries are not supported** — sql.js does not ship with a `regexp()`
29
+ * SQL function. Use JS-side filtering as a workaround.
30
+ * - **`lastID` / `changes`** are not exposed by the sql.js API. `run()` always
31
+ * returns `{ lastID: 0, changes: 0 }`, which is safe because MongoLite does not
32
+ * use these values internally.
33
+ * - **Persistence** — by default sql.js stores data in memory. For durable storage
34
+ * use the OPFS back-end or export the database buffer to IndexedDB.
35
+ */
36
+ export class BrowserSqliteAdapter {
37
+ /**
38
+ * @param db An initialised sql.js `Database` instance.
39
+ */
40
+ constructor(db) {
41
+ this.db = db;
42
+ }
43
+ /**
44
+ * No-op: sql.js Database is already open on construction.
45
+ */
46
+ async connect() {
47
+ // Nothing to do — the sql.js Database is ready to use immediately.
48
+ }
49
+ /**
50
+ * Closes the underlying sql.js Database and frees its WASM memory.
51
+ */
52
+ async close() {
53
+ this.db.close();
54
+ }
55
+ /**
56
+ * Executes a DML statement (INSERT, UPDATE, DELETE) with optional bound parameters.
57
+ * Returns `{ lastID: 0, changes: 0 }` — sql.js does not expose these values.
58
+ */
59
+ async run(sql, params = []) {
60
+ this.db.run(sql, params);
61
+ return { lastID: 0, changes: 0 };
62
+ }
63
+ /**
64
+ * Executes a SQL query and returns the first matching row, or `undefined`.
65
+ */
66
+ async get(sql, params = []) {
67
+ const stmt = this.db.prepare(sql);
68
+ try {
69
+ stmt.bind(params);
70
+ if (stmt.step()) {
71
+ return stmt.getAsObject();
72
+ }
73
+ return undefined;
74
+ }
75
+ finally {
76
+ stmt.free();
77
+ }
78
+ }
79
+ /**
80
+ * Executes a SQL query and returns all matching rows.
81
+ */
82
+ async all(sql, params = []) {
83
+ const stmt = this.db.prepare(sql);
84
+ try {
85
+ stmt.bind(params);
86
+ const rows = [];
87
+ while (stmt.step()) {
88
+ rows.push(stmt.getAsObject());
89
+ }
90
+ return rows;
91
+ }
92
+ finally {
93
+ stmt.free();
94
+ }
95
+ }
96
+ /**
97
+ * Executes one or more SQL statements (DDL, transactions, etc.).
98
+ */
99
+ async exec(sql) {
100
+ this.db.exec(sql);
101
+ }
102
+ }
103
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/adapters/browser.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,OAAO,oBAAoB;IAG/B;;OAEG;IACH,YAAY,EAAiB;QAC3B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACX,mEAAmE;IACrE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,SAAoB,EAAE;QAC3C,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,MAA2B,CAAC,CAAC;QAC9C,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,SAAoB,EAAE;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,MAA2B,CAAC,CAAC;YACvC,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBAChB,OAAO,IAAI,CAAC,WAAW,EAAO,CAAC;YACjC,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,SAAoB,EAAE;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,MAA2B,CAAC,CAAC;YACvC,MAAM,IAAI,GAAQ,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBACnB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAO,CAAC,CAAC;YACrC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI,CAAC,GAAW;QACpB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC;CACF"}
@@ -0,0 +1,106 @@
1
+ import { IDatabaseAdapter } from '../db.js';
2
+ /**
3
+ * Allowed scalar binding values accepted by Cloudflare's SqlStorage.exec().
4
+ */
5
+ export type SqlStorageValue = string | number | boolean | null | ArrayBuffer;
6
+ /**
7
+ * Minimal interface for the cursor returned by SqlStorage.exec().
8
+ * This matches the Cloudflare Durable Objects SqlStorage API.
9
+ */
10
+ export interface SqlStorageCursor<T = Record<string, SqlStorageValue>> {
11
+ toArray(): T[];
12
+ one(): T;
13
+ readonly rowsRead: number;
14
+ readonly rowsWritten: number;
15
+ readonly columnNames: readonly string[];
16
+ }
17
+ /**
18
+ * Minimal interface for the Cloudflare Durable Objects SqlStorage API.
19
+ * Pass `ctx.storage.sql` from your Durable Object to the adapter constructor.
20
+ *
21
+ * @see https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/
22
+ */
23
+ export interface SqlStorage {
24
+ exec<T = Record<string, SqlStorageValue>>(query: string, ...bindings: SqlStorageValue[]): SqlStorageCursor<T>;
25
+ }
26
+ /**
27
+ * Database adapter that wraps the Cloudflare Durable Objects `SqlStorage` API,
28
+ * allowing MongoLite to be used inside a Cloudflare Durable Object.
29
+ *
30
+ * ## Usage
31
+ *
32
+ * ```ts
33
+ * import { DurableObject } from 'cloudflare:workers';
34
+ * import { MongoLite, CloudflareDurableObjectAdapter } from '@semics-tech/mongolite/cloudflare';
35
+ *
36
+ * export class MyDurableObject extends DurableObject {
37
+ * private client: MongoLite;
38
+ *
39
+ * constructor(ctx: DurableObjectState, env: Env) {
40
+ * super(ctx, env);
41
+ * const adapter = new CloudflareDurableObjectAdapter(ctx.storage.sql);
42
+ * this.client = new MongoLite(adapter);
43
+ * }
44
+ *
45
+ * async fetch(request: Request) {
46
+ * await this.client.connect();
47
+ * const users = this.client.collection('users');
48
+ * await users.insertOne({ name: 'Alice', age: 30 });
49
+ * const user = await users.findOne({ name: 'Alice' });
50
+ * return Response.json(user);
51
+ * }
52
+ * }
53
+ * ```
54
+ *
55
+ * ## Limitations
56
+ *
57
+ * - **`$regex` queries are not supported.** Cloudflare's SQLite does not allow custom
58
+ * SQL functions, so the `regexp()` UDF registered by the default Node.js adapter is
59
+ * unavailable. Queries using `$regex` will throw a SQL error at runtime.
60
+ * - `WAL` mode and most `PRAGMA` statements are not available in Cloudflare Durable
61
+ * Objects (Cloudflare manages this internally).
62
+ * - Change Streams (`collection.watch()`) use SQL triggers and a change-log table.
63
+ * They work in Cloudflare Durable Objects, but the polling mechanism relies on
64
+ * `setInterval`, which is supported in the Workers runtime.
65
+ */
66
+ export declare class CloudflareDurableObjectAdapter implements IDatabaseAdapter {
67
+ private readonly sql;
68
+ /**
69
+ * @param sql The `SqlStorage` instance from a Cloudflare Durable Object context
70
+ * (i.e. `ctx.storage.sql`).
71
+ */
72
+ constructor(sql: SqlStorage);
73
+ /**
74
+ * No-op: Cloudflare Durable Objects manage the storage lifecycle automatically.
75
+ */
76
+ connect(): Promise<void>;
77
+ /**
78
+ * No-op: Cloudflare Durable Objects manage the storage lifecycle automatically.
79
+ */
80
+ close(): Promise<void>;
81
+ /**
82
+ * Executes a SQL statement that does not return rows (INSERT, UPDATE, DELETE, DDL).
83
+ */
84
+ run(sql: string, params?: unknown[]): Promise<{
85
+ lastID: number;
86
+ changes: number;
87
+ }>;
88
+ /**
89
+ * Executes a SQL query and returns the first matching row, or `undefined`.
90
+ */
91
+ get<T>(sql: string, params?: unknown[]): Promise<T | undefined>;
92
+ /**
93
+ * Executes a SQL query and returns all matching rows.
94
+ */
95
+ all<T>(sql: string, params?: unknown[]): Promise<T[]>;
96
+ /**
97
+ * Executes a single SQL statement (DDL, transactions, etc.).
98
+ *
99
+ * Cloudflare's `SqlStorage.exec` only supports one statement at a time, and
100
+ * some valid statements (e.g. `CREATE TRIGGER ... BEGIN ...; ...; END;`)
101
+ * contain internal semicolons. Callers must therefore pass exactly one
102
+ * statement per call; this method will not attempt to split multi-statement
103
+ * SQL strings.
104
+ */
105
+ exec(sql: string): Promise<void>;
106
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Database adapter that wraps the Cloudflare Durable Objects `SqlStorage` API,
3
+ * allowing MongoLite to be used inside a Cloudflare Durable Object.
4
+ *
5
+ * ## Usage
6
+ *
7
+ * ```ts
8
+ * import { DurableObject } from 'cloudflare:workers';
9
+ * import { MongoLite, CloudflareDurableObjectAdapter } from '@semics-tech/mongolite/cloudflare';
10
+ *
11
+ * export class MyDurableObject extends DurableObject {
12
+ * private client: MongoLite;
13
+ *
14
+ * constructor(ctx: DurableObjectState, env: Env) {
15
+ * super(ctx, env);
16
+ * const adapter = new CloudflareDurableObjectAdapter(ctx.storage.sql);
17
+ * this.client = new MongoLite(adapter);
18
+ * }
19
+ *
20
+ * async fetch(request: Request) {
21
+ * await this.client.connect();
22
+ * const users = this.client.collection('users');
23
+ * await users.insertOne({ name: 'Alice', age: 30 });
24
+ * const user = await users.findOne({ name: 'Alice' });
25
+ * return Response.json(user);
26
+ * }
27
+ * }
28
+ * ```
29
+ *
30
+ * ## Limitations
31
+ *
32
+ * - **`$regex` queries are not supported.** Cloudflare's SQLite does not allow custom
33
+ * SQL functions, so the `regexp()` UDF registered by the default Node.js adapter is
34
+ * unavailable. Queries using `$regex` will throw a SQL error at runtime.
35
+ * - `WAL` mode and most `PRAGMA` statements are not available in Cloudflare Durable
36
+ * Objects (Cloudflare manages this internally).
37
+ * - Change Streams (`collection.watch()`) use SQL triggers and a change-log table.
38
+ * They work in Cloudflare Durable Objects, but the polling mechanism relies on
39
+ * `setInterval`, which is supported in the Workers runtime.
40
+ */
41
+ export class CloudflareDurableObjectAdapter {
42
+ /**
43
+ * @param sql The `SqlStorage` instance from a Cloudflare Durable Object context
44
+ * (i.e. `ctx.storage.sql`).
45
+ */
46
+ constructor(sql) {
47
+ this.sql = sql;
48
+ }
49
+ /**
50
+ * No-op: Cloudflare Durable Objects manage the storage lifecycle automatically.
51
+ */
52
+ async connect() {
53
+ // Nothing to do — the SqlStorage instance is already live.
54
+ }
55
+ /**
56
+ * No-op: Cloudflare Durable Objects manage the storage lifecycle automatically.
57
+ */
58
+ async close() {
59
+ // Nothing to do — storage is managed by the Durable Object runtime.
60
+ }
61
+ /**
62
+ * Executes a SQL statement that does not return rows (INSERT, UPDATE, DELETE, DDL).
63
+ */
64
+ async run(sql, params = []) {
65
+ const cursor = this.sql.exec(sql, ...params);
66
+ return { lastID: 0, changes: cursor.rowsWritten };
67
+ }
68
+ /**
69
+ * Executes a SQL query and returns the first matching row, or `undefined`.
70
+ */
71
+ async get(sql, params = []) {
72
+ const rows = this.sql.exec(sql, ...params).toArray();
73
+ return rows[0];
74
+ }
75
+ /**
76
+ * Executes a SQL query and returns all matching rows.
77
+ */
78
+ async all(sql, params = []) {
79
+ return this.sql.exec(sql, ...params).toArray();
80
+ }
81
+ /**
82
+ * Executes a single SQL statement (DDL, transactions, etc.).
83
+ *
84
+ * Cloudflare's `SqlStorage.exec` only supports one statement at a time, and
85
+ * some valid statements (e.g. `CREATE TRIGGER ... BEGIN ...; ...; END;`)
86
+ * contain internal semicolons. Callers must therefore pass exactly one
87
+ * statement per call; this method will not attempt to split multi-statement
88
+ * SQL strings.
89
+ */
90
+ async exec(sql) {
91
+ this.sql.exec(sql);
92
+ }
93
+ }
94
+ //# sourceMappingURL=cloudflare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,OAAO,8BAA8B;IAGzC;;;OAGG;IACH,YAAY,GAAe;QACzB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACX,2DAA2D;IAC7D,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,oEAAoE;IACtE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,SAAoB,EAAE;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAI,MAA4B,CAAC,CAAC;QACpE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;IACpD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,SAAoB,EAAE;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAI,GAAG,EAAE,GAAI,MAA4B,CAAC,CAAC,OAAO,EAAE,CAAC;QAC/E,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,SAAoB,EAAE;QAC9C,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAI,GAAG,EAAE,GAAI,MAA4B,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3E,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,IAAI,CAAC,GAAW;QACpB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};