@firtoz/drizzle-durable-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/CHANGELOG.md +16 -0
- package/README.md +211 -0
- package/package.json +82 -0
- package/src/durable-sqlite-collection.ts +138 -0
- package/src/index.ts +10 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @firtoz/drizzle-durable-sqlite
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`b714ebb`](https://github.com/firtoz/fullstack-toolkit/commit/b714ebbb62ec0e3c3aa56c4105e7499fac11d1e5) Thanks [@firtoz](https://github.com/firtoz)! - Extract shared SQLite TanStack sync backend (`createSqliteTableSyncBackend`, IR→Drizzle helpers, `SQLOperation` types) into `@firtoz/drizzle-utils`. Add `@firtoz/drizzle-durable-sqlite` for Durable Object SQLite collections (`durableSqliteCollectionOptions`). Refactor `@firtoz/drizzle-sqlite-wasm` to use the shared backend with `driverMode: "async"`. `durableSqliteCollectionOptions` accepts optional `readyPromise` (defaults to immediate readiness). README documents the class-field Hono pattern, `app.fetch(request, env)` for bindings, optional `on-demand` + `preload` vs eager + `onFirstReady`, and `honoDoFetcherWithName` without a separate exported app type. Restore JSDoc on `DrizzleSqliteCollectionConfig` (`debug`, `checkpoint`, `interceptor`) for editor tooltips. Align `createStandaloneCollection` generics with `InsertToSelectSchema` from `@firtoz/drizzle-utils`.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`b714ebb`](https://github.com/firtoz/fullstack-toolkit/commit/b714ebbb62ec0e3c3aa56c4105e7499fac11d1e5)]:
|
|
12
|
+
- @firtoz/drizzle-utils@1.1.0
|
|
13
|
+
|
|
14
|
+
## 0.1.0
|
|
15
|
+
|
|
16
|
+
Initial release: TanStack DB collection options for Drizzle on Cloudflare Durable Object SQLite.
|
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# @firtoz/drizzle-durable-sqlite
|
|
2
|
+
|
|
3
|
+
TanStack DB collection configuration for **Drizzle ORM** on **Cloudflare Durable Object SQLite** (`drizzle-orm/durable-sqlite`). This mirrors [`@firtoz/drizzle-sqlite-wasm`](../drizzle-sqlite-wasm) for the browser (SQLite WASM + workers), but targets Workers/DOs only—no React provider, no OPFS, no Web Workers.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @firtoz/drizzle-durable-sqlite @firtoz/drizzle-utils drizzle-orm @tanstack/db
|
|
9
|
+
bun add -d drizzle-kit @cloudflare/workers-types
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Optional** (only for the [Hono + Zod example](#tanstack-db-collection-and-crud-from-a-durable-object) and a typed [`honoDoFetcher`](https://github.com/firtoz/fullstack-toolkit/tree/main/packages/hono-fetcher) client):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add hono zod @hono/zod-validator @firtoz/hono-fetcher
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Drizzle Kit
|
|
19
|
+
|
|
20
|
+
Use the durable-sqlite driver so generated migrations match DO storage:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { defineConfig } from "drizzle-kit";
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
schema: "./src/schema.ts",
|
|
27
|
+
out: "./drizzle",
|
|
28
|
+
dialect: "sqlite",
|
|
29
|
+
driver: "durable-sqlite",
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Wrangler
|
|
34
|
+
|
|
35
|
+
- Import SQL as text for the migrator ([Drizzle DO docs](https://orm.drizzle.team/docs/connect-cloudflare-do)).
|
|
36
|
+
- Use `new_sqlite_classes` for SQLite-backed Durable Objects.
|
|
37
|
+
|
|
38
|
+
```jsonc
|
|
39
|
+
{
|
|
40
|
+
"rules": [
|
|
41
|
+
{ "type": "Text", "globs": ["**/*.sql"], "fallthrough": true }
|
|
42
|
+
],
|
|
43
|
+
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Durable Object initialization
|
|
48
|
+
|
|
49
|
+
**Recommended for generic DOs:** run migrations inside `ctx.blockConcurrencyWhile` so the schema is ready before `fetch` or alarms. Example:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { DurableObject } from "cloudflare:workers";
|
|
53
|
+
import { drizzle } from "drizzle-orm/durable-sqlite";
|
|
54
|
+
import { migrate } from "drizzle-orm/durable-sqlite/migrator";
|
|
55
|
+
import migrations from "../drizzle/migrations.js";
|
|
56
|
+
import * as schema from "./schema";
|
|
57
|
+
|
|
58
|
+
export class MyDurableObject extends DurableObject {
|
|
59
|
+
private db!: ReturnType<typeof drizzle<typeof schema>>;
|
|
60
|
+
|
|
61
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
62
|
+
super(ctx, env);
|
|
63
|
+
this.ctx.blockConcurrencyWhile(async () => {
|
|
64
|
+
const db = drizzle(ctx.storage, { schema });
|
|
65
|
+
migrate(db, migrations);
|
|
66
|
+
this.db = db;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
[`@firtoz/chat-agent-drizzle`](../chat-agent-drizzle) uses a different pattern: `ChatAgentBase` calls a synchronous `dbInitialize()` hook (see `DrizzleChatAgent`). Use `blockConcurrencyWhile` when you are not on that agent base class.
|
|
73
|
+
|
|
74
|
+
## TanStack DB collection and CRUD from a Durable Object
|
|
75
|
+
|
|
76
|
+
Use `durableSqliteCollectionOptions` with tables built via `syncableTable` from `@firtoz/drizzle-utils` (same as WASM). The DO SQLite driver is **sync**; the shared backend uses synchronous transactions and `.all()` / `.run()` for mutations (see `@firtoz/drizzle-utils` `createSqliteTableSyncBackend` with `driverMode: "sync"`).
|
|
77
|
+
|
|
78
|
+
`tableName` must be the **property name** on your Drizzle schema object (e.g. `export const schema = { todosTable }` → `tableName: "todosTable"`), not the SQLite table name string.
|
|
79
|
+
|
|
80
|
+
If something else must finish before sync runs (e.g. a migration promise), pass `readyPromise`. When omitted, the collection treats storage as ready immediately (same as `Promise.resolve()`).
|
|
81
|
+
|
|
82
|
+
Example `schema.ts`:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { syncableTable } from "@firtoz/drizzle-utils";
|
|
86
|
+
import { text } from "drizzle-orm/sqlite-core";
|
|
87
|
+
|
|
88
|
+
export const todosTable = syncableTable("todos", {
|
|
89
|
+
title: text("title").notNull(),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export const schema = { todosTable };
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Example Durable Object: migrate in `blockConcurrencyWhile`, create the TanStack collection, then define a [Hono](https://hono.dev/) app as a **class field** (chained `.get(…)`, `.post(…)`, …) and forward `fetch` to it—the same shape as the [Durable Object snippet in `@firtoz/hono-fetcher`](../hono-fetcher/README.md).
|
|
96
|
+
|
|
97
|
+
Route handlers only run when a request is handled; Workers finish your constructor’s `blockConcurrencyWhile` work **before** the first `fetch`, so `this.todos` is always assigned before any handler runs. Pass **`this.env`** into `app.fetch` so `c.env` matches `Bindings: Env` (middleware, secrets, etc.). Use [`zValidator`](https://github.com/honojs/middleware/tree/main/packages/zod-validator) so JSON bodies and `:id` params are validated; [`honoDoFetcherWithName`](../hono-fetcher/README.md) can infer request/response types from that app without a separate exported `App` type.
|
|
98
|
+
|
|
99
|
+
For `syncMode: "on-demand"`, `await collection.preload()` inside `blockConcurrencyWhile` if you want rows loaded before serving. For `syncMode: "eager"`, you can wait for the first sync with `await new Promise<void>((resolve) => collection.onFirstReady(() => resolve()))` after `preload()`.
|
|
100
|
+
|
|
101
|
+
A minimal vitest + DO setup lives in [`tests/drizzle-durable-sqlite-test`](https://github.com/firtoz/fullstack-toolkit/tree/main/tests/drizzle-durable-sqlite-test).
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { DurableObject } from "cloudflare:workers";
|
|
105
|
+
import { zValidator } from "@hono/zod-validator";
|
|
106
|
+
import { createCollection } from "@tanstack/db";
|
|
107
|
+
import type { DrizzleSqliteDODatabase } from "drizzle-orm/durable-sqlite";
|
|
108
|
+
import { drizzle } from "drizzle-orm/durable-sqlite";
|
|
109
|
+
import { migrate } from "drizzle-orm/durable-sqlite/migrator";
|
|
110
|
+
import { Hono } from "hono";
|
|
111
|
+
import { z } from "zod";
|
|
112
|
+
import { durableSqliteCollectionOptions } from "@firtoz/drizzle-durable-sqlite";
|
|
113
|
+
import migrations from "../drizzle/migrations.js";
|
|
114
|
+
import * as schema from "./schema";
|
|
115
|
+
|
|
116
|
+
type TodosCollection = ReturnType<typeof createCollection>;
|
|
117
|
+
|
|
118
|
+
export class TodosDurableObject extends DurableObject<Env> {
|
|
119
|
+
private db!: DrizzleSqliteDODatabase<typeof schema>;
|
|
120
|
+
private todos!: TodosCollection;
|
|
121
|
+
app = new Hono<{ Bindings: Env }>()
|
|
122
|
+
.get("/todos", (c) => c.json({ todos: this.todos.toArray }))
|
|
123
|
+
.post(
|
|
124
|
+
"/todos",
|
|
125
|
+
zValidator("json", z.object({ title: z.string() })),
|
|
126
|
+
async (c) => {
|
|
127
|
+
const { title } = c.req.valid("json");
|
|
128
|
+
const tx = this.todos.insert([{ title }]);
|
|
129
|
+
await tx.isPersisted.promise;
|
|
130
|
+
return c.json({ ok: true as const }, 201);
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
.patch(
|
|
134
|
+
"/todos/:id",
|
|
135
|
+
zValidator("param", z.object({ id: z.string() })),
|
|
136
|
+
zValidator(
|
|
137
|
+
"json",
|
|
138
|
+
z.object({ title: z.string().optional() }),
|
|
139
|
+
),
|
|
140
|
+
async (c) => {
|
|
141
|
+
const { id } = c.req.valid("param");
|
|
142
|
+
const { title } = c.req.valid("json");
|
|
143
|
+
const tx = this.todos.update(id, (draft) => {
|
|
144
|
+
if (title !== undefined) draft.title = title;
|
|
145
|
+
});
|
|
146
|
+
await tx.isPersisted.promise;
|
|
147
|
+
return c.json(this.todos.state.get(id) ?? null);
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
.delete(
|
|
151
|
+
"/todos/:id",
|
|
152
|
+
zValidator("param", z.object({ id: z.string() })),
|
|
153
|
+
async (c) => {
|
|
154
|
+
const { id } = c.req.valid("param");
|
|
155
|
+
const tx = this.todos.delete([id]);
|
|
156
|
+
await tx.isPersisted.promise;
|
|
157
|
+
return new Response(null, { status: 204 });
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
.notFound((c) => c.text("Not found", 404));
|
|
161
|
+
|
|
162
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
163
|
+
super(ctx, env);
|
|
164
|
+
|
|
165
|
+
this.ctx.blockConcurrencyWhile(async () => {
|
|
166
|
+
const db = drizzle(ctx.storage, { schema });
|
|
167
|
+
migrate(db, migrations);
|
|
168
|
+
this.db = db;
|
|
169
|
+
|
|
170
|
+
const todos = createCollection(
|
|
171
|
+
durableSqliteCollectionOptions({
|
|
172
|
+
drizzle: db,
|
|
173
|
+
tableName: "todosTable",
|
|
174
|
+
syncMode: "eager",
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
this.todos = todos;
|
|
178
|
+
todos.preload(); // Preload to ensure the data's in the collection from storage.
|
|
179
|
+
await new Promise<void>((resolve) => todos.onFirstReady(() => resolve()));
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fetch(request: Request) {
|
|
184
|
+
return this.app.fetch(request, this.env);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Typed client from your worker** (same idea as [Durable Objects in `@firtoz/hono-fetcher`](../hono-fetcher/README.md)):
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { honoDoFetcherWithName } from "@firtoz/hono-fetcher";
|
|
193
|
+
|
|
194
|
+
const api = honoDoFetcherWithName(env.TODOS, "my-list");
|
|
195
|
+
|
|
196
|
+
await api.post({
|
|
197
|
+
url: "/todos",
|
|
198
|
+
body: { title: "Buy milk" },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const list = await api.get({ url: "/todos" });
|
|
202
|
+
const data = await list.json();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`body` / response inference follows your `zValidator` + `c.json` shapes; use `params: { id: "…" }` on `/todos/:id` routes. If inference ever fails to pick up the DO’s `app` type, pass an explicit generic, e.g. `honoDoFetcherWithName<InstanceType<typeof TodosDurableObject>["app"]>(…)`.
|
|
206
|
+
|
|
207
|
+
Adjust paths (`../drizzle/migrations.js`, `./schema`) and `Env` to match your worker. To return the created row from `POST`, read from `this.todos.state` / `toArray` after `isPersisted` and return `c.json(…)` with a shape that matches what you want the fetcher to infer.
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@firtoz/drizzle-durable-sqlite",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "TanStack DB collections backed by Drizzle on Cloudflare Durable Object SQLite",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"module": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"import": "./src/index.ts",
|
|
13
|
+
"require": "./src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"./durableSqliteCollectionOptions": {
|
|
16
|
+
"types": "./src/durable-sqlite-collection.ts",
|
|
17
|
+
"import": "./src/durable-sqlite-collection.ts",
|
|
18
|
+
"require": "./src/durable-sqlite-collection.ts"
|
|
19
|
+
},
|
|
20
|
+
"./*": {
|
|
21
|
+
"types": "./src/*.ts",
|
|
22
|
+
"import": "./src/*.ts",
|
|
23
|
+
"require": "./src/*.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"src/**/*.ts",
|
|
28
|
+
"!src/**/*.test.ts",
|
|
29
|
+
"README.md",
|
|
30
|
+
"CHANGELOG.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"typecheck": "tsc --noEmit -p ./tsconfig.json",
|
|
34
|
+
"lint": "biome check --write src",
|
|
35
|
+
"lint:ci": "biome ci src",
|
|
36
|
+
"format": "biome format src --write"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"typescript",
|
|
40
|
+
"cloudflare",
|
|
41
|
+
"durable-objects",
|
|
42
|
+
"sqlite",
|
|
43
|
+
"drizzle",
|
|
44
|
+
"tanstack-db"
|
|
45
|
+
],
|
|
46
|
+
"author": "Firtina Ozbalikchi <firtoz@github.com>",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"homepage": "https://github.com/firtoz/fullstack-toolkit#readme",
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/firtoz/fullstack-toolkit.git",
|
|
52
|
+
"directory": "packages/drizzle-durable-sqlite"
|
|
53
|
+
},
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/firtoz/fullstack-toolkit/issues"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18.0.0"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@cloudflare/workers-types": "^4.20260317.1",
|
|
65
|
+
"@tanstack/db": "^0.5.33",
|
|
66
|
+
"drizzle-orm": "^0.45.1",
|
|
67
|
+
"drizzle-valibot": ">=0.4.0",
|
|
68
|
+
"valibot": ">=1.3.1"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@firtoz/db-helpers": "^2.0.0",
|
|
72
|
+
"@firtoz/drizzle-utils": "^1.1.0"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@cloudflare/workers-types": "^4.20260317.1",
|
|
76
|
+
"@tanstack/db": "^0.5.33",
|
|
77
|
+
"drizzle-orm": "^0.45.1",
|
|
78
|
+
"drizzle-valibot": "^0.4.2",
|
|
79
|
+
"typescript": "^6.0.2",
|
|
80
|
+
"valibot": "^1.3.1"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InferSchemaOutput,
|
|
3
|
+
SyncMode,
|
|
4
|
+
CollectionConfig,
|
|
5
|
+
} from "@tanstack/db";
|
|
6
|
+
import type { Table } from "drizzle-orm";
|
|
7
|
+
import type { DrizzleSqliteDODatabase } from "drizzle-orm/durable-sqlite";
|
|
8
|
+
import type { CollectionUtils } from "@firtoz/db-helpers";
|
|
9
|
+
import type {
|
|
10
|
+
SelectSchema,
|
|
11
|
+
InsertToSelectSchema,
|
|
12
|
+
TableWithRequiredFields,
|
|
13
|
+
BaseSyncConfig,
|
|
14
|
+
} from "@firtoz/drizzle-utils";
|
|
15
|
+
import {
|
|
16
|
+
createSyncFunction,
|
|
17
|
+
createInsertSchemaWithIdDefault,
|
|
18
|
+
createGetKeyFunction,
|
|
19
|
+
createCollectionConfig,
|
|
20
|
+
createSqliteTableSyncBackend,
|
|
21
|
+
type SQLOperation,
|
|
22
|
+
type SQLInterceptor,
|
|
23
|
+
} from "@firtoz/drizzle-utils";
|
|
24
|
+
export type { SQLOperation, SQLInterceptor };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Drizzle database type for `drizzle-orm/durable-sqlite` (Cloudflare DO SQLite).
|
|
28
|
+
*/
|
|
29
|
+
export type AnyDurableSqliteDatabase = DrizzleSqliteDODatabase<
|
|
30
|
+
Record<string, unknown>
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
export type DurableDrizzleSchema<TDrizzle extends AnyDurableSqliteDatabase> =
|
|
34
|
+
TDrizzle["_"]["fullSchema"];
|
|
35
|
+
|
|
36
|
+
export interface DurableSqliteCollectionConfig<
|
|
37
|
+
TDrizzle extends AnyDurableSqliteDatabase,
|
|
38
|
+
TTableName extends ValidTableNames<DurableDrizzleSchema<TDrizzle>>,
|
|
39
|
+
> {
|
|
40
|
+
drizzle: TDrizzle;
|
|
41
|
+
tableName: ValidTableNames<DurableDrizzleSchema<TDrizzle>> extends never
|
|
42
|
+
? {
|
|
43
|
+
$error: "The schema needs to include at least one table that uses the syncableTable function.";
|
|
44
|
+
}
|
|
45
|
+
: TTableName;
|
|
46
|
+
/**
|
|
47
|
+
* Await before running sync queries (e.g. migrations finishing). Omit or leave undefined to use an already-resolved promise (no extra wait).
|
|
48
|
+
*/
|
|
49
|
+
readyPromise?: Promise<void>;
|
|
50
|
+
syncMode?: SyncMode;
|
|
51
|
+
debug?: boolean;
|
|
52
|
+
interceptor?: SQLInterceptor;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ValidTableNames<TSchema extends Record<string, unknown>> = {
|
|
56
|
+
[K in keyof TSchema]: TSchema[K] extends TableWithRequiredFields ? K : never;
|
|
57
|
+
}[keyof TSchema];
|
|
58
|
+
|
|
59
|
+
export type DurableSqliteCollectionConfigResult<TTable extends Table> = Omit<
|
|
60
|
+
CollectionConfig<
|
|
61
|
+
InferSchemaOutput<SelectSchema<TTable>>,
|
|
62
|
+
string,
|
|
63
|
+
InsertToSelectSchema<TTable>
|
|
64
|
+
>,
|
|
65
|
+
"utils"
|
|
66
|
+
> & {
|
|
67
|
+
schema: InsertToSelectSchema<TTable>;
|
|
68
|
+
utils: CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* TanStack DB collection configuration for a table stored in Durable Object SQLite via Drizzle.
|
|
73
|
+
*
|
|
74
|
+
* Uses `driverMode: "sync"` internally: DO SQLite runs `transactionSync`, so mutations use
|
|
75
|
+
* `.all()` / `.run()` inside a synchronous transaction callback (see `createSqliteTableSyncBackend` in `@firtoz/drizzle-utils`).
|
|
76
|
+
*/
|
|
77
|
+
export function durableSqliteCollectionOptions<
|
|
78
|
+
const TDrizzle extends AnyDurableSqliteDatabase,
|
|
79
|
+
const TTableName extends string &
|
|
80
|
+
ValidTableNames<DurableDrizzleSchema<TDrizzle>>,
|
|
81
|
+
TTable extends DurableDrizzleSchema<TDrizzle>[TTableName] &
|
|
82
|
+
TableWithRequiredFields,
|
|
83
|
+
>(
|
|
84
|
+
config: DurableSqliteCollectionConfig<TDrizzle, TTableName>,
|
|
85
|
+
): DurableSqliteCollectionConfigResult<TTable> {
|
|
86
|
+
const tableName = config.tableName as string &
|
|
87
|
+
ValidTableNames<DurableDrizzleSchema<TDrizzle>>;
|
|
88
|
+
|
|
89
|
+
const table = config.drizzle._.fullSchema[tableName] as TTable;
|
|
90
|
+
|
|
91
|
+
const backend = createSqliteTableSyncBackend({
|
|
92
|
+
drizzle: config.drizzle,
|
|
93
|
+
table,
|
|
94
|
+
tableName: config.tableName as string,
|
|
95
|
+
debug: config.debug,
|
|
96
|
+
interceptor: config.interceptor,
|
|
97
|
+
driverMode: "sync",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const baseSyncConfig: BaseSyncConfig<TTable> = {
|
|
101
|
+
table,
|
|
102
|
+
readyPromise: config.readyPromise ?? Promise.resolve(),
|
|
103
|
+
syncMode: config.syncMode,
|
|
104
|
+
debug: config.debug,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const syncResult = createSyncFunction(baseSyncConfig, backend);
|
|
108
|
+
|
|
109
|
+
const schema = createInsertSchemaWithIdDefault(table);
|
|
110
|
+
|
|
111
|
+
return createCollectionConfig({
|
|
112
|
+
schema,
|
|
113
|
+
getKey: createGetKeyFunction<TTable>(),
|
|
114
|
+
syncResult,
|
|
115
|
+
onInsert: config.debug
|
|
116
|
+
? async (params) => {
|
|
117
|
+
console.log("onInsert", params);
|
|
118
|
+
// biome-ignore lint/style/noNonNullAssertion: defined when sync runs
|
|
119
|
+
await syncResult.onInsert!(params);
|
|
120
|
+
}
|
|
121
|
+
: undefined,
|
|
122
|
+
onUpdate: config.debug
|
|
123
|
+
? async (params) => {
|
|
124
|
+
console.log("onUpdate", params);
|
|
125
|
+
// biome-ignore lint/style/noNonNullAssertion: defined when sync runs
|
|
126
|
+
await syncResult.onUpdate!(params);
|
|
127
|
+
}
|
|
128
|
+
: undefined,
|
|
129
|
+
onDelete: config.debug
|
|
130
|
+
? async (params) => {
|
|
131
|
+
console.log("onDelete", params);
|
|
132
|
+
// biome-ignore lint/style/noNonNullAssertion: defined when sync runs
|
|
133
|
+
await syncResult.onDelete!(params);
|
|
134
|
+
}
|
|
135
|
+
: undefined,
|
|
136
|
+
syncMode: config.syncMode,
|
|
137
|
+
});
|
|
138
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
durableSqliteCollectionOptions,
|
|
3
|
+
type AnyDurableSqliteDatabase,
|
|
4
|
+
type DurableDrizzleSchema,
|
|
5
|
+
type DurableSqliteCollectionConfig,
|
|
6
|
+
type DurableSqliteCollectionConfigResult,
|
|
7
|
+
type ValidTableNames,
|
|
8
|
+
type SQLOperation,
|
|
9
|
+
type SQLInterceptor,
|
|
10
|
+
} from "./durable-sqlite-collection";
|