@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.
- package/LICENSE +21 -0
- package/README.md +179 -0
- package/dist/adapters/browser.d.ts +97 -0
- package/dist/adapters/browser.js +103 -0
- package/dist/adapters/browser.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +106 -0
- package/dist/adapters/cloudflare.js +94 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/bin/mongolite-debug.d.ts +2 -0
- package/dist/bin/mongolite-debug.js +115 -0
- package/dist/bin/mongolite-debug.js.map +1 -0
- package/dist/changeStream.d.ts +114 -0
- package/dist/changeStream.js +338 -0
- package/dist/changeStream.js.map +1 -0
- package/dist/cloudflare.d.ts +39 -0
- package/dist/cloudflare.js +39 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/collection.d.ts +274 -0
- package/dist/collection.js +1706 -0
- package/dist/collection.js.map +1 -0
- package/dist/cursors/findCursor.d.ts +101 -0
- package/dist/cursors/findCursor.js +757 -0
- package/dist/cursors/findCursor.js.map +1 -0
- package/dist/db.d.ts +116 -0
- package/dist/db.js +254 -0
- package/dist/db.js.map +1 -0
- package/dist/debugger/query-debugger.d.ts +22 -0
- package/dist/debugger/query-debugger.js +258 -0
- package/dist/debugger/query-debugger.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/mongo-client.d.ts +56 -0
- package/dist/mongo-client.js +55 -0
- package/dist/mongo-client.js.map +1 -0
- package/dist/types.d.ts +318 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/indexing.d.ts +4 -0
- package/dist/utils/indexing.js +43 -0
- package/dist/utils/indexing.js.map +1 -0
- package/dist/utils/projection.d.ts +16 -0
- package/dist/utils/projection.js +98 -0
- package/dist/utils/projection.js.map +1 -0
- package/dist/utils/queryOperators.d.ts +16 -0
- package/dist/utils/queryOperators.js +138 -0
- package/dist/utils/queryOperators.js.map +1 -0
- 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
|
+
[](https://github.com/semics-tech/mongolite/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@semics-tech/mongolite)
|
|
5
|
+
[](https://codecov.io/gh/semics-tech/mongolite)
|
|
6
|
+
[](./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"}
|