or3-provider-sqlite 0.0.4 → 0.0.7

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 +364 -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,8 +1,12 @@
1
- import { getRawDb } from "../db/kysely.js";
1
+ import { getRawDb, isD1Driver } from "../db/kysely.js";
2
+ import { d1All } from "../db/d1.js";
2
3
  export class SqliteRateLimitProvider {
3
4
  name = "sqlite";
4
5
  async checkAndRecord(key, config) {
5
6
  requireConfig(config);
7
+ if (isD1Driver()) {
8
+ return this.checkAndRecordInD1(key, config);
9
+ }
6
10
  const now = Date.now();
7
11
  const db = getRawDb();
8
12
  return db.transaction(() => {
@@ -43,6 +47,9 @@ export class SqliteRateLimitProvider {
43
47
  }
44
48
  async getStats(key, config) {
45
49
  requireConfig(config);
50
+ if (isD1Driver()) {
51
+ return this.getStatsInD1(key, config);
52
+ }
46
53
  const now = Date.now();
47
54
  const row = getRawDb().prepare(
48
55
  `SELECT count, window_started_at, expires_at
@@ -61,6 +68,68 @@ export class SqliteRateLimitProvider {
61
68
  resetMs: Math.max(0, row.expires_at - now)
62
69
  };
63
70
  }
71
+ async checkAndRecordInD1(key, config) {
72
+ const now = Date.now();
73
+ const [row] = await d1All(
74
+ `INSERT INTO rate_limits (key, count, window_started_at, expires_at)
75
+ VALUES (?, 1, ?, ?)
76
+ ON CONFLICT(key) DO UPDATE SET
77
+ count = CASE
78
+ WHEN rate_limits.expires_at <= ? THEN 1
79
+ WHEN rate_limits.count < ? THEN rate_limits.count + 1
80
+ ELSE ?
81
+ END,
82
+ window_started_at = CASE
83
+ WHEN rate_limits.expires_at <= ? THEN excluded.window_started_at
84
+ ELSE rate_limits.window_started_at
85
+ END,
86
+ expires_at = CASE
87
+ WHEN rate_limits.expires_at <= ? THEN excluded.expires_at
88
+ ELSE rate_limits.expires_at
89
+ END
90
+ RETURNING count, window_started_at, expires_at`,
91
+ key,
92
+ now,
93
+ now + config.windowMs,
94
+ now,
95
+ config.maxRequests,
96
+ config.maxRequests + 1,
97
+ now,
98
+ now
99
+ );
100
+ if (!row) throw new Error("D1 rate-limit upsert did not return a row.");
101
+ if (row.count > config.maxRequests) {
102
+ return {
103
+ allowed: false,
104
+ remaining: 0,
105
+ retryAfterMs: Math.max(0, row.expires_at - now)
106
+ };
107
+ }
108
+ return {
109
+ allowed: true,
110
+ remaining: config.maxRequests - row.count
111
+ };
112
+ }
113
+ async getStatsInD1(key, config) {
114
+ const now = Date.now();
115
+ const [row] = await d1All(
116
+ `SELECT count, window_started_at, expires_at
117
+ FROM rate_limits WHERE key = ?`,
118
+ key
119
+ );
120
+ if (!row || row.expires_at <= now) {
121
+ return {
122
+ limit: config.maxRequests,
123
+ remaining: config.maxRequests,
124
+ resetMs: config.windowMs
125
+ };
126
+ }
127
+ return {
128
+ limit: config.maxRequests,
129
+ remaining: Math.max(0, config.maxRequests - row.count),
130
+ resetMs: Math.max(0, row.expires_at - now)
131
+ };
132
+ }
64
133
  }
65
134
  export const sqliteRateLimitProvider = new SqliteRateLimitProvider();
66
135
  function requireConfig(config) {
@@ -0,0 +1,26 @@
1
+ import type { SyncMaintenanceState } from '~~/server/sync/gateway/types';
2
+ /**
3
+ * Retention window used by maintenance GC. No OR3_SYNC_* retention knob exists
4
+ * today, so we deliberately do NOT invent a new env var; we use the maximum
5
+ * bounded window allowed by the gateway (365 days) and document it here.
6
+ */
7
+ export declare const SYNC_MAINTENANCE_RETENTION_SECONDS: number;
8
+ export declare function getSyncMaintenanceState(): SyncMaintenanceState;
9
+ export declare function beginSyncMaintenanceRun(): void;
10
+ export declare function completeSyncMaintenanceRun(input: {
11
+ lastRun: string;
12
+ backlog: number;
13
+ }): void;
14
+ export declare function failSyncMaintenanceRun(error: string): void;
15
+ export declare function getCachedSyncMaintenanceBacklog(): number | undefined;
16
+ /**
17
+ * Count change-log entries eligible for GC across all workspaces: older than
18
+ * the retention window AND behind every active device cursor for their
19
+ * workspace. Cursors idle for a whole retention window intentionally do not
20
+ * block collection because those clients snapshot-bootstrap on return.
21
+ * Cached with a short TTL. Pass `force` after a GC pass so the recorded
22
+ * backlog reflects the post-GC state.
23
+ */
24
+ export declare function computeSyncMaintenanceBacklog(retentionSeconds: number, force?: boolean): number;
25
+ /** Test-only reset. */
26
+ export declare function _resetSyncMaintenanceStateForTest(): void;
@@ -0,0 +1,66 @@
1
+ import { canRunSyncHistoryGc } from "./history-gc-policy.js";
2
+ import { getRawDb } from "../db/kysely.js";
3
+ export const SYNC_MAINTENANCE_RETENTION_SECONDS = 365 * 24 * 60 * 60;
4
+ const BACKLOG_TTL_MS = 6e4;
5
+ let state = { enabled: true, state: "idle" };
6
+ let backlogCache;
7
+ export function getSyncMaintenanceState() {
8
+ return { ...state, enabled: canRunSyncHistoryGc() };
9
+ }
10
+ export function beginSyncMaintenanceRun() {
11
+ state = { ...state, state: "running" };
12
+ }
13
+ export function completeSyncMaintenanceRun(input) {
14
+ state = {
15
+ ...state,
16
+ state: "idle",
17
+ lastRun: input.lastRun,
18
+ backlog: input.backlog,
19
+ lastError: void 0
20
+ };
21
+ }
22
+ export function failSyncMaintenanceRun(error) {
23
+ state = { ...state, state: "failed", lastError: error };
24
+ }
25
+ export function getCachedSyncMaintenanceBacklog() {
26
+ if (backlogCache && Date.now() - backlogCache.at < BACKLOG_TTL_MS) {
27
+ return backlogCache.value;
28
+ }
29
+ return void 0;
30
+ }
31
+ export function computeSyncMaintenanceBacklog(retentionSeconds, force = false) {
32
+ if (!force) {
33
+ const cached = getCachedSyncMaintenanceBacklog();
34
+ if (cached !== void 0) return cached;
35
+ }
36
+ const backlog = countEligibleChangeLog(retentionSeconds);
37
+ backlogCache = { value: backlog, at: Date.now() };
38
+ state = { ...state, backlog };
39
+ return backlog;
40
+ }
41
+ function countEligibleChangeLog(retentionSeconds) {
42
+ try {
43
+ const raw = getRawDb();
44
+ const cutoff = Math.floor(Date.now() / 1e3) - retentionSeconds;
45
+ const row = raw.prepare(
46
+ `WITH active_cursors AS (
47
+ SELECT workspace_id, MIN(last_seen_version) AS min_version
48
+ FROM device_cursors
49
+ WHERE updated_at >= ?
50
+ GROUP BY workspace_id
51
+ )
52
+ SELECT COUNT(*) AS cnt
53
+ FROM change_log cl
54
+ LEFT JOIN active_cursors ac ON ac.workspace_id = cl.workspace_id
55
+ WHERE cl.created_at < ?
56
+ AND (ac.min_version IS NULL OR cl.server_version <= ac.min_version)`
57
+ ).get(cutoff, cutoff);
58
+ return row.cnt;
59
+ } catch {
60
+ return 0;
61
+ }
62
+ }
63
+ export function _resetSyncMaintenanceStateForTest() {
64
+ state = { enabled: true, state: "idle" };
65
+ backlogCache = void 0;
66
+ }
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * SQLite implementation of SyncGatewayAdapter.
3
3
  *
4
- * Handles push/pull/updateCursor/gc using Kysely + better-sqlite3.
5
- * Uses raw DB transactions via better-sqlite3 for push atomicity
6
- * (BEGIN IMMEDIATE prevents concurrent server_version races).
4
+ * Handles push/pull/updateCursor/gc using the selected native SQLite runtime.
5
+ * Local runtimes use raw BEGIN IMMEDIATE transactions; Cloudflare D1 uses
6
+ * native atomic batches for its asynchronous binding.
7
7
  */
8
8
  import type { H3Event } from 'h3';
9
- import type { CanonicalStorageQueryRequest, CanonicalStorageQueryResponse, SyncGatewayAdapter, UploadIntentConsumptionRequest, UploadIntentReservationRequest } from '~~/server/sync/gateway/types';
9
+ import type { CanonicalStorageQueryRequest, CanonicalStorageQueryResponse, SyncGatewayAdapter, SyncMaintenanceState, UploadIntentConsumptionRequest, UploadIntentReservationRequest } from '~~/server/sync/gateway/types';
10
10
  import type { PullRequest, PullResponse, PushBatch, PushResult, SnapshotRequest, SnapshotResponse } from '~~/shared/sync/types';
11
11
  export declare class SqliteSyncGatewayAdapter implements SyncGatewayAdapter {
12
12
  id: string;
@@ -44,5 +44,16 @@ export declare class SqliteSyncGatewayAdapter implements SyncGatewayAdapter {
44
44
  };
45
45
  retentionSeconds: number;
46
46
  }): Promise<void>;
47
+ private reserveUploadIntentInD1;
48
+ private snapshotInD1;
49
+ private updateCursorInD1;
50
+ private pushInD1;
51
+ listWorkspaceIds(): Promise<string[]>;
52
+ getMaintenanceState(): SyncMaintenanceState;
53
+ beginMaintenanceRun(): void;
54
+ completeMaintenanceRun(input: {
55
+ lastRun: string;
56
+ }): void;
57
+ failMaintenanceRun(error: string): void;
47
58
  }
48
59
  export declare function createSqliteSyncGatewayAdapter(): SyncGatewayAdapter;