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.
- package/README.md +112 -19
- package/dist/module.json +1 -1
- package/dist/runtime/server/admin/adapters/sync-sqlite.js +90 -16
- package/dist/runtime/server/admin/stores/sqlite-store.js +7 -8
- package/dist/runtime/server/auth/sqlite-auth-workspace-store.d.ts +5 -0
- package/dist/runtime/server/auth/sqlite-auth-workspace-store.js +368 -40
- package/dist/runtime/server/connect/sqlite-connect-store.js +19 -5
- package/dist/runtime/server/db/d1-dialect.d.ts +39 -0
- package/dist/runtime/server/db/d1-dialect.js +70 -0
- package/dist/runtime/server/db/d1.d.ts +13 -0
- package/dist/runtime/server/db/d1.js +30 -0
- package/dist/runtime/server/db/kysely.d.ts +53 -15
- package/dist/runtime/server/db/kysely.js +364 -16
- package/dist/runtime/server/db/migrate.js +3 -1
- package/dist/runtime/server/db/migrations/016_connect_runtime_binding.d.ts +4 -0
- package/dist/runtime/server/db/migrations/016_connect_runtime_binding.js +28 -0
- package/dist/runtime/server/db/schema.d.ts +3 -0
- package/dist/runtime/server/plugins/register.js +33 -13
- package/dist/runtime/server/rate-limit/sqlite-provider.d.ts +2 -0
- package/dist/runtime/server/rate-limit/sqlite-provider.js +70 -1
- package/dist/runtime/server/sync/maintenance-state.d.ts +26 -0
- package/dist/runtime/server/sync/maintenance-state.js +66 -0
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.d.ts +15 -4
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.js +780 -21
- package/dist/runtime/server/webhooks/sqlite-webhook-store.d.ts +3 -2
- package/dist/runtime/server/webhooks/sqlite-webhook-store.js +90 -39
- 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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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;
|