or3-provider-sqlite 0.0.4 → 0.0.6

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 (27) hide show
  1. package/README.md +112 -19
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/server/admin/adapters/sync-sqlite.js +90 -16
  4. package/dist/runtime/server/admin/stores/sqlite-store.js +7 -8
  5. package/dist/runtime/server/auth/sqlite-auth-workspace-store.d.ts +5 -0
  6. package/dist/runtime/server/auth/sqlite-auth-workspace-store.js +368 -40
  7. package/dist/runtime/server/connect/sqlite-connect-store.js +19 -5
  8. package/dist/runtime/server/db/d1-dialect.d.ts +39 -0
  9. package/dist/runtime/server/db/d1-dialect.js +70 -0
  10. package/dist/runtime/server/db/d1.d.ts +13 -0
  11. package/dist/runtime/server/db/d1.js +30 -0
  12. package/dist/runtime/server/db/kysely.d.ts +53 -15
  13. package/dist/runtime/server/db/kysely.js +374 -16
  14. package/dist/runtime/server/db/migrate.js +3 -1
  15. package/dist/runtime/server/db/migrations/016_connect_runtime_binding.d.ts +4 -0
  16. package/dist/runtime/server/db/migrations/016_connect_runtime_binding.js +28 -0
  17. package/dist/runtime/server/db/schema.d.ts +3 -0
  18. package/dist/runtime/server/plugins/register.js +33 -13
  19. package/dist/runtime/server/rate-limit/sqlite-provider.d.ts +2 -0
  20. package/dist/runtime/server/rate-limit/sqlite-provider.js +70 -1
  21. package/dist/runtime/server/sync/maintenance-state.d.ts +26 -0
  22. package/dist/runtime/server/sync/maintenance-state.js +66 -0
  23. package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.d.ts +15 -4
  24. package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.js +780 -21
  25. package/dist/runtime/server/webhooks/sqlite-webhook-store.d.ts +3 -2
  26. package/dist/runtime/server/webhooks/sqlite-webhook-store.js +90 -39
  27. package/package.json +10 -6
@@ -1,7 +1,7 @@
1
- import { getRawDb, getSqliteDb } from "../db/kysely.js";
2
- import { randomUUID } from "node:crypto";
1
+ import { getRawDb, getSqliteDb, isD1Driver } from "../db/kysely.js";
2
+ import { d1All, d1Batch, d1Run } from "../db/d1.js";
3
3
  function uid() {
4
- return randomUUID();
4
+ return globalThis.crypto.randomUUID();
5
5
  }
6
6
  function nowEpoch() {
7
7
  return Math.floor(Date.now() / 1e3);
@@ -16,6 +16,9 @@ export class SqliteAuthWorkspaceStore {
16
16
  onInviteProvisionStep(_step) {
17
17
  }
18
18
  async getOrCreateUser(input) {
19
+ if (isD1Driver()) {
20
+ return this.getOrCreateUserInD1(input);
21
+ }
19
22
  this.db;
20
23
  const raw = getRawDb();
21
24
  const userId = raw.transaction(() => {
@@ -116,25 +119,46 @@ export class SqliteAuthWorkspaceStore {
116
119
  const memberId = uid();
117
120
  const now = nowEpoch();
118
121
  const name = "My Workspace";
119
- await db.transaction().execute(async (tx) => {
120
- await tx.insertInto("workspaces").values({
121
- id: workspaceId,
122
- name,
123
- description: null,
124
- owner_user_id: userId,
125
- created_at: now,
126
- deleted: 0,
127
- deleted_at: null
128
- }).execute();
129
- await tx.insertInto("workspace_members").values({
130
- id: memberId,
131
- workspace_id: workspaceId,
132
- user_id: userId,
133
- role: "owner",
134
- created_at: now
135
- }).execute();
136
- await tx.updateTable("users").set({ active_workspace_id: workspaceId }).where("id", "=", userId).execute();
137
- });
122
+ if (isD1Driver()) {
123
+ await d1Batch([
124
+ {
125
+ sql: `INSERT INTO workspaces (
126
+ id, name, description, owner_user_id, created_at, deleted, deleted_at
127
+ ) VALUES (?, ?, NULL, ?, ?, 0, NULL)`,
128
+ parameters: [workspaceId, name, userId, now]
129
+ },
130
+ {
131
+ sql: `INSERT INTO workspace_members (
132
+ id, workspace_id, user_id, role, created_at
133
+ ) VALUES (?, ?, ?, 'owner', ?)`,
134
+ parameters: [memberId, workspaceId, userId, now]
135
+ },
136
+ {
137
+ sql: "UPDATE users SET active_workspace_id = ? WHERE id = ?",
138
+ parameters: [workspaceId, userId]
139
+ }
140
+ ]);
141
+ } else {
142
+ await db.transaction().execute(async (tx) => {
143
+ await tx.insertInto("workspaces").values({
144
+ id: workspaceId,
145
+ name,
146
+ description: null,
147
+ owner_user_id: userId,
148
+ created_at: now,
149
+ deleted: 0,
150
+ deleted_at: null
151
+ }).execute();
152
+ await tx.insertInto("workspace_members").values({
153
+ id: memberId,
154
+ workspace_id: workspaceId,
155
+ user_id: userId,
156
+ role: "owner",
157
+ created_at: now
158
+ }).execute();
159
+ await tx.updateTable("users").set({ active_workspace_id: workspaceId }).where("id", "=", userId).execute();
160
+ });
161
+ }
138
162
  return { workspaceId, workspaceName: name, created: true };
139
163
  }
140
164
  async getWorkspaceRole(input) {
@@ -168,24 +192,47 @@ export class SqliteAuthWorkspaceStore {
168
192
  const workspaceId = uid();
169
193
  const memberId = uid();
170
194
  const now = nowEpoch();
171
- await db.transaction().execute(async (tx) => {
172
- await tx.insertInto("workspaces").values({
173
- id: workspaceId,
174
- name: input.name,
175
- description: input.description ?? null,
176
- owner_user_id: input.userId,
177
- created_at: now,
178
- deleted: 0,
179
- deleted_at: null
180
- }).execute();
181
- await tx.insertInto("workspace_members").values({
182
- id: memberId,
183
- workspace_id: workspaceId,
184
- user_id: input.userId,
185
- role: "owner",
186
- created_at: now
187
- }).execute();
188
- });
195
+ if (isD1Driver()) {
196
+ await d1Batch([
197
+ {
198
+ sql: `INSERT INTO workspaces (
199
+ id, name, description, owner_user_id, created_at, deleted, deleted_at
200
+ ) VALUES (?, ?, ?, ?, ?, 0, NULL)`,
201
+ parameters: [
202
+ workspaceId,
203
+ input.name,
204
+ input.description ?? null,
205
+ input.userId,
206
+ now
207
+ ]
208
+ },
209
+ {
210
+ sql: `INSERT INTO workspace_members (
211
+ id, workspace_id, user_id, role, created_at
212
+ ) VALUES (?, ?, ?, 'owner', ?)`,
213
+ parameters: [memberId, workspaceId, input.userId, now]
214
+ }
215
+ ]);
216
+ } else {
217
+ await db.transaction().execute(async (tx) => {
218
+ await tx.insertInto("workspaces").values({
219
+ id: workspaceId,
220
+ name: input.name,
221
+ description: input.description ?? null,
222
+ owner_user_id: input.userId,
223
+ created_at: now,
224
+ deleted: 0,
225
+ deleted_at: null
226
+ }).execute();
227
+ await tx.insertInto("workspace_members").values({
228
+ id: memberId,
229
+ workspace_id: workspaceId,
230
+ user_id: input.userId,
231
+ role: "owner",
232
+ created_at: now
233
+ }).execute();
234
+ });
235
+ }
189
236
  return { workspaceId };
190
237
  }
191
238
  async updateWorkspace(input) {
@@ -206,6 +253,31 @@ export class SqliteAuthWorkspaceStore {
206
253
  throw new Error("Forbidden: only owner can remove workspace");
207
254
  }
208
255
  const now = nowEpoch();
256
+ if (isD1Driver()) {
257
+ await d1Batch([
258
+ {
259
+ sql: "UPDATE workspaces SET deleted = 1, deleted_at = ? WHERE id = ?",
260
+ parameters: [now, input.workspaceId]
261
+ },
262
+ {
263
+ sql: `UPDATE users
264
+ SET active_workspace_id = (
265
+ SELECT workspace_members.workspace_id
266
+ FROM workspace_members
267
+ INNER JOIN workspaces
268
+ ON workspaces.id = workspace_members.workspace_id
269
+ WHERE workspace_members.user_id = users.id
270
+ AND workspaces.deleted = 0
271
+ AND workspaces.id <> ?
272
+ ORDER BY workspace_members.created_at ASC
273
+ LIMIT 1
274
+ )
275
+ WHERE active_workspace_id = ?`,
276
+ parameters: [input.workspaceId, input.workspaceId]
277
+ }
278
+ ]);
279
+ return;
280
+ }
209
281
  await db.transaction().execute(async (tx) => {
210
282
  await tx.updateTable("workspaces").set({ deleted: 1, deleted_at: now }).where("id", "=", input.workspaceId).execute();
211
283
  const affectedUsers = await tx.selectFrom("users").select("id").where("active_workspace_id", "=", input.workspaceId).execute();
@@ -285,6 +357,9 @@ export class SqliteAuthWorkspaceStore {
285
357
  void input.revokedByUserId;
286
358
  }
287
359
  async validateInvite(input) {
360
+ if (isD1Driver()) {
361
+ return this.validateInviteInD1(input);
362
+ }
288
363
  this.db;
289
364
  const raw = getRawDb();
290
365
  const now = nowEpoch();
@@ -306,6 +381,9 @@ export class SqliteAuthWorkspaceStore {
306
381
  return { ok: true, role: invite.role };
307
382
  }
308
383
  async acceptInviteAndProvisionUser(input) {
384
+ if (isD1Driver()) {
385
+ return this.acceptInviteAndProvisionUserInD1(input);
386
+ }
309
387
  this.db;
310
388
  const raw = getRawDb();
311
389
  const now = nowEpoch();
@@ -389,6 +467,9 @@ export class SqliteAuthWorkspaceStore {
389
467
  }).immediate();
390
468
  }
391
469
  async consumeInvite(input) {
470
+ if (isD1Driver()) {
471
+ return this.consumeInviteInD1(input);
472
+ }
392
473
  this.db;
393
474
  const raw = getRawDb();
394
475
  const now = nowEpoch();
@@ -454,6 +535,253 @@ export class SqliteAuthWorkspaceStore {
454
535
  return { ok: true, role: invite.role };
455
536
  }).immediate();
456
537
  }
538
+ async getOrCreateUserInD1(input) {
539
+ const candidateUserId = uid();
540
+ const now = nowEpoch();
541
+ const results = await d1Batch([
542
+ {
543
+ sql: `INSERT OR IGNORE INTO users (
544
+ id, email, display_name, active_workspace_id, created_at
545
+ ) VALUES (?, ?, ?, NULL, ?)`,
546
+ parameters: [
547
+ candidateUserId,
548
+ input.email ?? null,
549
+ input.displayName ?? null,
550
+ now
551
+ ]
552
+ },
553
+ {
554
+ sql: `INSERT OR IGNORE INTO auth_accounts (
555
+ id, user_id, provider, provider_user_id, created_at
556
+ ) VALUES (?, ?, ?, ?, ?)`,
557
+ parameters: [
558
+ uid(),
559
+ candidateUserId,
560
+ input.provider,
561
+ input.providerUserId,
562
+ now
563
+ ]
564
+ },
565
+ {
566
+ sql: `SELECT user_id
567
+ FROM auth_accounts
568
+ WHERE provider = ? AND provider_user_id = ?`,
569
+ parameters: [input.provider, input.providerUserId]
570
+ }
571
+ ]);
572
+ const winner = (results[2]?.results ?? [])[0];
573
+ if (!winner?.user_id) {
574
+ throw new Error("Failed to resolve auth account after get-or-create attempt");
575
+ }
576
+ if (winner.user_id !== candidateUserId) {
577
+ await d1Run(
578
+ `DELETE FROM users
579
+ WHERE id = ?
580
+ AND NOT EXISTS (
581
+ SELECT 1 FROM auth_accounts WHERE user_id = ?
582
+ )`,
583
+ candidateUserId,
584
+ candidateUserId
585
+ );
586
+ }
587
+ return { userId: winner.user_id };
588
+ }
589
+ async validateInviteInD1(input) {
590
+ const now = nowEpoch();
591
+ const [invite] = await d1All(
592
+ `SELECT role, status, email, expires_at
593
+ FROM auth_invites
594
+ WHERE workspace_id = ? AND token_hash = ?
595
+ LIMIT 1`,
596
+ input.workspaceId,
597
+ input.tokenHash
598
+ );
599
+ if (!invite) return { ok: false, reason: "not_found" };
600
+ if (invite.email !== normalizeEmail(input.email)) {
601
+ return { ok: false, reason: "email_mismatch" };
602
+ }
603
+ if (invite.status === "accepted") return { ok: false, reason: "already_used" };
604
+ if (invite.status === "revoked") return { ok: false, reason: "revoked" };
605
+ if (invite.status === "expired" || invite.expires_at <= now) {
606
+ return { ok: false, reason: "expired" };
607
+ }
608
+ return { ok: true, role: invite.role };
609
+ }
610
+ async acceptInviteAndProvisionUserInD1(input) {
611
+ const now = nowEpoch();
612
+ const normalizedEmail = normalizeEmail(input.email);
613
+ const validation = await this.validateInviteInD1({
614
+ workspaceId: input.workspaceId,
615
+ email: normalizedEmail,
616
+ tokenHash: input.tokenHash
617
+ });
618
+ if (!validation.ok) return validation;
619
+ this.onInviteProvisionStep("invite_validated");
620
+ const [existingAccount] = await d1All(
621
+ `SELECT user_id FROM auth_accounts
622
+ WHERE provider = ? AND provider_user_id = ?
623
+ LIMIT 1`,
624
+ input.provider,
625
+ input.providerUserId
626
+ );
627
+ const { userId } = await this.getOrCreateUserInD1({
628
+ provider: input.provider,
629
+ providerUserId: input.providerUserId,
630
+ email: normalizedEmail,
631
+ displayName: input.displayName
632
+ });
633
+ const createdUser = !existingAccount;
634
+ this.onInviteProvisionStep("user_created");
635
+ const results = await d1Batch([
636
+ {
637
+ sql: `UPDATE auth_invites
638
+ SET status = 'accepted', accepted_at = ?, accepted_user_id = ?, updated_at = ?
639
+ WHERE workspace_id = ?
640
+ AND token_hash = ?
641
+ AND email = ?
642
+ AND status = 'pending'
643
+ AND expires_at > ?`,
644
+ parameters: [
645
+ now,
646
+ userId,
647
+ now,
648
+ input.workspaceId,
649
+ input.tokenHash,
650
+ normalizedEmail,
651
+ now
652
+ ]
653
+ },
654
+ {
655
+ sql: `INSERT INTO workspace_members (
656
+ id, workspace_id, user_id, role, created_at
657
+ )
658
+ SELECT ?, ?, ?, role, ?
659
+ FROM auth_invites
660
+ WHERE workspace_id = ?
661
+ AND token_hash = ?
662
+ AND status = 'accepted'
663
+ AND accepted_user_id = ?
664
+ ON CONFLICT(workspace_id, user_id) DO UPDATE SET role = excluded.role`,
665
+ parameters: [
666
+ uid(),
667
+ input.workspaceId,
668
+ userId,
669
+ now,
670
+ input.workspaceId,
671
+ input.tokenHash,
672
+ userId
673
+ ]
674
+ },
675
+ {
676
+ sql: `UPDATE users SET active_workspace_id = ?
677
+ WHERE id = ?
678
+ AND EXISTS (
679
+ SELECT 1 FROM workspace_members
680
+ WHERE workspace_id = ? AND user_id = ?
681
+ )`,
682
+ parameters: [input.workspaceId, userId, input.workspaceId, userId]
683
+ }
684
+ ]);
685
+ if ((results[0]?.meta?.changes ?? 0) !== 1) {
686
+ return this.inviteProvisionFailureInD1(input.workspaceId, normalizedEmail, input.tokenHash);
687
+ }
688
+ this.onInviteProvisionStep("membership_created");
689
+ this.onInviteProvisionStep("invite_consumed");
690
+ return { ok: true, userId, role: validation.role, createdUser };
691
+ }
692
+ async consumeInviteInD1(input) {
693
+ const now = nowEpoch();
694
+ const normalized = normalizeEmail(input.email);
695
+ await d1Run(
696
+ `UPDATE auth_invites SET status = 'expired', updated_at = ?
697
+ WHERE workspace_id = ?
698
+ AND status = 'pending'
699
+ AND expires_at <= ?`,
700
+ now,
701
+ input.workspaceId,
702
+ now
703
+ );
704
+ const [invite] = await d1All(
705
+ `SELECT * FROM auth_invites
706
+ WHERE workspace_id = ? AND email = ?
707
+ ORDER BY created_at ASC
708
+ LIMIT 1`,
709
+ input.workspaceId,
710
+ normalized
711
+ );
712
+ if (!invite) return { ok: false, reason: "not_found" };
713
+ if (invite.status === "revoked") return { ok: false, reason: "revoked" };
714
+ if (invite.status === "accepted") return { ok: false, reason: "already_used" };
715
+ if (invite.status === "expired" || invite.expires_at <= now) {
716
+ return { ok: false, reason: "expired" };
717
+ }
718
+ if (invite.token_hash !== input.tokenHash) {
719
+ return { ok: false, reason: "token_mismatch" };
720
+ }
721
+ const results = await d1Batch([
722
+ {
723
+ sql: `UPDATE auth_invites
724
+ SET status = 'accepted', accepted_at = ?, accepted_user_id = ?, updated_at = ?
725
+ WHERE id = ? AND status = 'pending' AND token_hash = ?`,
726
+ parameters: [
727
+ now,
728
+ input.acceptedUserId,
729
+ now,
730
+ invite.id,
731
+ input.tokenHash
732
+ ]
733
+ },
734
+ {
735
+ sql: `INSERT INTO workspace_members (
736
+ id, workspace_id, user_id, role, created_at
737
+ )
738
+ SELECT ?, ?, ?, role, ?
739
+ FROM auth_invites
740
+ WHERE id = ?
741
+ AND status = 'accepted'
742
+ AND accepted_user_id = ?
743
+ ON CONFLICT(workspace_id, user_id) DO UPDATE SET role = excluded.role`,
744
+ parameters: [
745
+ uid(),
746
+ input.workspaceId,
747
+ input.acceptedUserId,
748
+ now,
749
+ invite.id,
750
+ input.acceptedUserId
751
+ ]
752
+ },
753
+ {
754
+ sql: `UPDATE users SET active_workspace_id = ?
755
+ WHERE id = ?
756
+ AND EXISTS (
757
+ SELECT 1 FROM workspace_members
758
+ WHERE workspace_id = ? AND user_id = ?
759
+ )`,
760
+ parameters: [
761
+ input.workspaceId,
762
+ input.acceptedUserId,
763
+ input.workspaceId,
764
+ input.acceptedUserId
765
+ ]
766
+ }
767
+ ]);
768
+ if ((results[0]?.meta?.changes ?? 0) !== 1) {
769
+ const [latest] = await d1All("SELECT status FROM auth_invites WHERE id = ?", invite.id);
770
+ if (!latest) return { ok: false, reason: "not_found" };
771
+ if (latest.status === "revoked") return { ok: false, reason: "revoked" };
772
+ if (latest.status === "accepted") return { ok: false, reason: "already_used" };
773
+ return { ok: false, reason: "expired" };
774
+ }
775
+ return { ok: true, role: invite.role };
776
+ }
777
+ async inviteProvisionFailureInD1(workspaceId, email, tokenHash) {
778
+ const validation = await this.validateInviteInD1({
779
+ workspaceId,
780
+ email,
781
+ tokenHash
782
+ });
783
+ return validation.ok ? { ok: false, reason: "already_used" } : validation;
784
+ }
457
785
  }
458
786
  export function createSqliteAuthWorkspaceStore() {
459
787
  return new SqliteAuthWorkspaceStore();
@@ -1,10 +1,10 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import {
3
2
  ConnectStoreError
4
3
  } from "~~/server/connect/store/types";
5
4
  import { getRawDb } from "../db/kysely.js";
6
5
  const ENVIRONMENT_COLUMNS = `
7
- id, user_id, workspace_id, name, platform, architecture, host_id,
6
+ id, user_id, workspace_id, name, platform, architecture, driver, runtime,
7
+ base_path, host_id,
8
8
  signing_public_key, noise_public_key, authorization_id, hostname,
9
9
  tunnel_id, dns_record_id, control_token_hash, access_credential_ciphertext,
10
10
  tunnel_secret_ciphertext, status, lifecycle_attempts,
@@ -40,7 +40,7 @@ class SqliteConnectStore {
40
40
  status, host_json, expires_at, created_at, updated_at
41
41
  ) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?)`
42
42
  ).run(
43
- randomUUID(),
43
+ globalThis.crypto.randomUUID(),
44
44
  input.deviceCodeHash,
45
45
  input.userCodeHash,
46
46
  JSON.stringify(input.host),
@@ -188,10 +188,14 @@ class SqliteConnectStore {
188
188
  db.prepare(
189
189
  `INSERT INTO connect_environments (
190
190
  id, user_id, workspace_id, name, platform, architecture,
191
+ driver, runtime, base_path,
191
192
  host_id, signing_public_key, noise_public_key, hostname,
192
193
  tunnel_id, dns_record_id, control_token_hash,
193
194
  access_credential_ciphertext, status, created_at, updated_at
194
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)`
195
+ ) VALUES (
196
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
197
+ 'active', ?, ?
198
+ )`
195
199
  ).run(
196
200
  environment.id,
197
201
  input.userId,
@@ -199,6 +203,9 @@ class SqliteConnectStore {
199
203
  environment.name,
200
204
  environment.platform,
201
205
  environment.architecture,
206
+ environment.driver ?? null,
207
+ environment.runtime ?? null,
208
+ environment.base_path ?? null,
202
209
  environment.host_id ?? null,
203
210
  environment.signing_public_key ?? null,
204
211
  environment.noise_public_key ?? null,
@@ -277,6 +284,7 @@ class SqliteConnectStore {
277
284
  db.prepare(
278
285
  `INSERT INTO connect_environments (
279
286
  id, user_id, workspace_id, name, platform, architecture,
287
+ driver, runtime, base_path,
280
288
  host_id, signing_public_key, noise_public_key,
281
289
  authorization_id, hostname, tunnel_id, dns_record_id,
282
290
  control_token_hash, access_credential_ciphertext,
@@ -286,7 +294,7 @@ class SqliteConnectStore {
286
294
  activation_deadline_at, activation_claimed_at,
287
295
  created_at, updated_at
288
296
  ) VALUES (
289
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', '', '', ?, ?, ?,
297
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', '', '', ?, ?, ?,
290
298
  'provisioning', 0, ?, ?, ?, ?, ?, NULL, ?, ?
291
299
  )`
292
300
  ).run(
@@ -296,6 +304,9 @@ class SqliteConnectStore {
296
304
  environment.name,
297
305
  environment.platform,
298
306
  environment.architecture,
307
+ environment.driver ?? null,
308
+ environment.runtime ?? null,
309
+ environment.base_path ?? null,
299
310
  environment.host_id ?? null,
300
311
  environment.signing_public_key ?? null,
301
312
  environment.noise_public_key ?? null,
@@ -835,6 +846,9 @@ function toEnvironmentRecord(row) {
835
846
  name: row.name,
836
847
  platform: row.platform,
837
848
  architecture: row.architecture,
849
+ driver: row.driver ?? void 0,
850
+ runtime: row.runtime ?? void 0,
851
+ base_path: row.base_path ?? void 0,
838
852
  host_id: row.host_id ?? void 0,
839
853
  signing_public_key: row.signing_public_key ?? void 0,
840
854
  noise_public_key: row.noise_public_key ?? void 0,
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Small Kysely dialect for a Cloudflare D1 binding.
3
+ *
4
+ * D1 exposes an asynchronous prepared-statement API. Keeping this adapter
5
+ * local avoids pulling a Node-only database driver into a Workers bundle.
6
+ */
7
+ import { SqliteAdapter, type DatabaseIntrospector, type Dialect, type Driver, type Kysely, type QueryCompiler } from 'kysely';
8
+ export interface D1Result<T = unknown> {
9
+ success?: boolean;
10
+ results?: T[];
11
+ meta?: {
12
+ changes?: number;
13
+ last_row_id?: number | string | null;
14
+ };
15
+ error?: string;
16
+ }
17
+ export interface D1PreparedStatement {
18
+ bind(...values: unknown[]): D1PreparedStatement;
19
+ all<T = unknown>(): Promise<D1Result<T>>;
20
+ run<T = unknown>(): Promise<D1Result<T>>;
21
+ }
22
+ export interface D1Database {
23
+ prepare(query: string): D1PreparedStatement;
24
+ batch?<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
25
+ }
26
+ /**
27
+ * D1 does not expose SQL BEGIN/COMMIT transactions. Callers that require an
28
+ * atomic write use D1Database.batch(), which is intentionally kept outside
29
+ * the general-purpose Kysely transaction API.
30
+ */
31
+ export declare const D1_TRANSACTION_ERROR = "Cloudflare D1 does not support Kysely transactions. Use the D1-specific atomic operation instead.";
32
+ export declare class D1Dialect implements Dialect {
33
+ private readonly database;
34
+ constructor(database: D1Database);
35
+ createAdapter(): SqliteAdapter;
36
+ createDriver(): Driver;
37
+ createQueryCompiler(): QueryCompiler;
38
+ createIntrospector(db: Kysely<unknown>): DatabaseIntrospector;
39
+ }
@@ -0,0 +1,70 @@
1
+ import {
2
+ SqliteAdapter,
3
+ SqliteIntrospector,
4
+ SqliteQueryCompiler
5
+ } from "kysely";
6
+ export const D1_TRANSACTION_ERROR = "Cloudflare D1 does not support Kysely transactions. Use the D1-specific atomic operation instead.";
7
+ export class D1Dialect {
8
+ constructor(database) {
9
+ this.database = database;
10
+ }
11
+ createAdapter() {
12
+ return new SqliteAdapter();
13
+ }
14
+ createDriver() {
15
+ return new D1Driver(this.database);
16
+ }
17
+ createQueryCompiler() {
18
+ return new SqliteQueryCompiler();
19
+ }
20
+ createIntrospector(db) {
21
+ return new SqliteIntrospector(db);
22
+ }
23
+ }
24
+ class D1Driver {
25
+ connection;
26
+ constructor(database) {
27
+ this.connection = new D1Connection(database);
28
+ }
29
+ async init() {
30
+ }
31
+ async acquireConnection() {
32
+ return this.connection;
33
+ }
34
+ async beginTransaction() {
35
+ throw new Error(D1_TRANSACTION_ERROR);
36
+ }
37
+ async commitTransaction() {
38
+ throw new Error(D1_TRANSACTION_ERROR);
39
+ }
40
+ async rollbackTransaction() {
41
+ throw new Error(D1_TRANSACTION_ERROR);
42
+ }
43
+ async releaseConnection() {
44
+ }
45
+ async destroy() {
46
+ }
47
+ }
48
+ class D1Connection {
49
+ constructor(database) {
50
+ this.database = database;
51
+ }
52
+ async executeQuery(compiledQuery) {
53
+ const statement = this.database.prepare(compiledQuery.sql).bind(...compiledQuery.parameters);
54
+ const result = await statement.all();
55
+ if (result.success === false) {
56
+ throw new Error(result.error ?? "Cloudflare D1 query failed.");
57
+ }
58
+ if (result.error) throw new Error(result.error);
59
+ const changes = result.meta?.changes;
60
+ const lastRowId = result.meta?.last_row_id;
61
+ return {
62
+ rows: result.results ?? [],
63
+ numAffectedRows: changes === void 0 || changes === null ? void 0 : BigInt(changes),
64
+ insertId: lastRowId === void 0 || lastRowId === null ? void 0 : BigInt(lastRowId)
65
+ };
66
+ }
67
+ async *streamQuery(compiledQuery) {
68
+ yield await this.executeQuery(compiledQuery);
69
+ }
70
+ }
@@ -0,0 +1,13 @@
1
+ /** Native helpers for the D1 operations that need batch-level atomicity. */
2
+ import type { D1Result } from './d1-dialect.js';
3
+ export type D1SqlStatement = {
4
+ sql: string;
5
+ parameters?: readonly unknown[];
6
+ };
7
+ export declare function d1All<T = Record<string, unknown>>(sql: string, ...parameters: unknown[]): Promise<T[]>;
8
+ export declare function d1Run(sql: string, ...parameters: unknown[]): Promise<D1Result>;
9
+ /**
10
+ * D1 runs all statements in a batch sequentially and atomically. It is the
11
+ * native replacement for the local BEGIN IMMEDIATE paths in this provider.
12
+ */
13
+ export declare function d1Batch(statements: readonly D1SqlStatement[]): Promise<D1Result[]>;