@syncular/server-cloudflare 0.0.1-60
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/dist/durable-object.d.ts +88 -0
- package/dist/durable-object.d.ts.map +1 -0
- package/dist/durable-object.js +170 -0
- package/dist/durable-object.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/r2.d.ts +140 -0
- package/dist/r2.d.ts.map +1 -0
- package/dist/r2.js +139 -0
- package/dist/r2.js.map +1 -0
- package/dist/worker.d.ts +42 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +59 -0
- package/dist/worker.js.map +1 -0
- package/package.json +77 -0
- package/src/durable-object.ts +241 -0
- package/src/index.ts +18 -0
- package/src/r2.ts +315 -0
- package/src/worker.ts +73 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/server-cloudflare - Durable Object handler (WebSocket + polling)
|
|
3
|
+
*
|
|
4
|
+
* Provides a base DurableObject class with Hono routing and WebSocket support.
|
|
5
|
+
* The DO's stateful nature allows it to hold persistent WebSocket connections,
|
|
6
|
+
* bridging Cloudflare's hibernation API to Hono's `upgradeWebSocket` interface.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { SyncDurableObject, createSyncWorkerWithDO } from '@syncular/server-cloudflare/durable-object';
|
|
11
|
+
* import { createD1Db } from '@syncular/dialect-d1';
|
|
12
|
+
* import { createSqliteServerDialect } from '@syncular/server-dialect-sqlite';
|
|
13
|
+
* import { ensureSyncSchema } from '@syncular/server';
|
|
14
|
+
* import { createSyncServer } from '@syncular/server-hono';
|
|
15
|
+
*
|
|
16
|
+
* type Env = { DB: D1Database; SYNC_DO: DurableObjectNamespace };
|
|
17
|
+
*
|
|
18
|
+
* export class SyncDO extends SyncDurableObject<Env> {
|
|
19
|
+
* setup(app, env, upgradeWebSocket) {
|
|
20
|
+
* const db = createD1Db(env.DB);
|
|
21
|
+
* const dialect = createSqliteServerDialect();
|
|
22
|
+
* const { syncRoutes, consoleRoutes } = createSyncServer({
|
|
23
|
+
* db, dialect,
|
|
24
|
+
* handlers: [tasksHandler],
|
|
25
|
+
* authenticate: async (c) => ({ actorId: c.req.header('x-user-id')! }),
|
|
26
|
+
* upgradeWebSocket,
|
|
27
|
+
* });
|
|
28
|
+
* app.route('/sync', syncRoutes);
|
|
29
|
+
* if (consoleRoutes) app.route('/console', consoleRoutes);
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* // Worker entry — routes all requests to the DO
|
|
34
|
+
* export default createSyncWorkerWithDO<Env>('SYNC_DO');
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
import { Hono } from 'hono';
|
|
38
|
+
import type { UpgradeWebSocket } from 'hono/ws';
|
|
39
|
+
/**
|
|
40
|
+
* Base class for a Syncular Durable Object with Hono routing and WebSocket.
|
|
41
|
+
*
|
|
42
|
+
* Subclass and implement `setup()` to configure routes.
|
|
43
|
+
*/
|
|
44
|
+
export declare abstract class SyncDurableObject<E extends object = Record<string, unknown>> {
|
|
45
|
+
protected ctx: DurableObjectState;
|
|
46
|
+
protected env: E;
|
|
47
|
+
private app;
|
|
48
|
+
private initPromise;
|
|
49
|
+
private doUpgradeWebSocket;
|
|
50
|
+
constructor(ctx: DurableObjectState, env: E);
|
|
51
|
+
/**
|
|
52
|
+
* Configure the Hono app with sync routes.
|
|
53
|
+
*
|
|
54
|
+
* Called once when the DO first receives a request.
|
|
55
|
+
* Use `upgradeWebSocket` when creating the sync server to enable realtime.
|
|
56
|
+
*/
|
|
57
|
+
abstract setup(app: Hono<{
|
|
58
|
+
Bindings: E;
|
|
59
|
+
}>, env: E, upgradeWebSocket: UpgradeWebSocket<WebSocket>): void | Promise<void>;
|
|
60
|
+
private getApp;
|
|
61
|
+
/** Handle incoming HTTP requests (and WebSocket upgrades). */
|
|
62
|
+
fetch(request: Request): Promise<Response>;
|
|
63
|
+
/** Dispatch incoming WebSocket messages to Hono event handlers. */
|
|
64
|
+
webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void>;
|
|
65
|
+
/** Dispatch WebSocket close events to Hono event handlers. */
|
|
66
|
+
webSocketClose(ws: WebSocket, code: number, reason: string, _wasClean: boolean): Promise<void>;
|
|
67
|
+
/** Dispatch WebSocket error events to Hono event handlers. */
|
|
68
|
+
webSocketError(ws: WebSocket, _error: unknown): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create a Worker export that routes all requests to a Durable Object.
|
|
72
|
+
*
|
|
73
|
+
* Uses a single DO instance (derived from a stable ID) to hold all
|
|
74
|
+
* connections. For multi-tenant setups, override `getStubId`.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* export default createSyncWorkerWithDO<Env>('SYNC_DO');
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function createSyncWorkerWithDO<E extends object>(bindingName: string & keyof E, options?: {
|
|
82
|
+
/**
|
|
83
|
+
* Derive a DurableObject ID from the request.
|
|
84
|
+
* Defaults to a single global instance via `idFromName('sync')`.
|
|
85
|
+
*/
|
|
86
|
+
getStubId?: (ns: DurableObjectNamespace, request: Request, env: E) => DurableObjectId;
|
|
87
|
+
}): ExportedHandler<E>;
|
|
88
|
+
//# sourceMappingURL=durable-object.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-object.d.ts","sourceRoot":"","sources":["../src/durable-object.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAY,MAAM,SAAS,CAAC;AAgE1D;;;;GAIG;AACH,8BAAsB,iBAAiB,CACrC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAE1C,SAAS,CAAC,GAAG,EAAE,kBAAkB,CAAC;IAClC,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC;IAEjB,OAAO,CAAC,GAAG,CAAsC;IACjD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,kBAAkB,CAA8B;IAExD,YAAY,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAAC,EAI1C;IAED;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CACZ,GAAG,EAAE,IAAI,CAAC;QAAE,QAAQ,EAAE,CAAC,CAAA;KAAE,CAAC,EAC1B,GAAG,EAAE,CAAC,EACN,gBAAgB,EAAE,gBAAgB,CAAC,SAAS,CAAC,GAC5C,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;YAEV,MAAM;IAcpB,8DAA8D;IACxD,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAG/C;IAED,mEAAmE;IAC7D,gBAAgB,CACpB,EAAE,EAAE,SAAS,EACb,OAAO,EAAE,MAAM,GAAG,WAAW,GAC5B,OAAO,CAAC,IAAI,CAAC,CAOf;IAED,8DAA8D;IACxD,cAAc,CAClB,EAAE,EAAE,SAAS,EACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,IAAI,CAAC,CAQf;IAED,8DAA8D;IACxD,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAOlE;CACF;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACrD,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,EAC7B,OAAO,CAAC,EAAE;IACR;;;OAGG;IACH,SAAS,CAAC,EAAE,CACV,EAAE,EAAE,sBAAsB,EAC1B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,CAAC,KACH,eAAe,CAAC;CACtB,GACA,eAAe,CAAC,CAAC,CAAC,CAiBpB"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/server-cloudflare - Durable Object handler (WebSocket + polling)
|
|
3
|
+
*
|
|
4
|
+
* Provides a base DurableObject class with Hono routing and WebSocket support.
|
|
5
|
+
* The DO's stateful nature allows it to hold persistent WebSocket connections,
|
|
6
|
+
* bridging Cloudflare's hibernation API to Hono's `upgradeWebSocket` interface.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { SyncDurableObject, createSyncWorkerWithDO } from '@syncular/server-cloudflare/durable-object';
|
|
11
|
+
* import { createD1Db } from '@syncular/dialect-d1';
|
|
12
|
+
* import { createSqliteServerDialect } from '@syncular/server-dialect-sqlite';
|
|
13
|
+
* import { ensureSyncSchema } from '@syncular/server';
|
|
14
|
+
* import { createSyncServer } from '@syncular/server-hono';
|
|
15
|
+
*
|
|
16
|
+
* type Env = { DB: D1Database; SYNC_DO: DurableObjectNamespace };
|
|
17
|
+
*
|
|
18
|
+
* export class SyncDO extends SyncDurableObject<Env> {
|
|
19
|
+
* setup(app, env, upgradeWebSocket) {
|
|
20
|
+
* const db = createD1Db(env.DB);
|
|
21
|
+
* const dialect = createSqliteServerDialect();
|
|
22
|
+
* const { syncRoutes, consoleRoutes } = createSyncServer({
|
|
23
|
+
* db, dialect,
|
|
24
|
+
* handlers: [tasksHandler],
|
|
25
|
+
* authenticate: async (c) => ({ actorId: c.req.header('x-user-id')! }),
|
|
26
|
+
* upgradeWebSocket,
|
|
27
|
+
* });
|
|
28
|
+
* app.route('/sync', syncRoutes);
|
|
29
|
+
* if (consoleRoutes) app.route('/console', consoleRoutes);
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* // Worker entry — routes all requests to the DO
|
|
34
|
+
* export default createSyncWorkerWithDO<Env>('SYNC_DO');
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
import { Hono } from 'hono';
|
|
38
|
+
import { defineWebSocketHelper, WSContext } from 'hono/ws';
|
|
39
|
+
/**
|
|
40
|
+
* WeakMap from server-side WebSocket → tag with event handlers.
|
|
41
|
+
* Populated on upgrade, read in webSocketMessage/webSocketClose.
|
|
42
|
+
*/
|
|
43
|
+
const socketTags = new WeakMap();
|
|
44
|
+
function createWSContext(ws) {
|
|
45
|
+
return new WSContext({
|
|
46
|
+
send(data) {
|
|
47
|
+
ws.send(data);
|
|
48
|
+
},
|
|
49
|
+
close(code, reason) {
|
|
50
|
+
ws.close(code, reason);
|
|
51
|
+
},
|
|
52
|
+
raw: ws,
|
|
53
|
+
get readyState() {
|
|
54
|
+
return ws.readyState;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create an `upgradeWebSocket` function backed by the Durable Object
|
|
60
|
+
* hibernation API (`state.acceptWebSocket`).
|
|
61
|
+
*
|
|
62
|
+
* Each accepted socket is tagged with its Hono `WSEvents` handlers so the
|
|
63
|
+
* DO's `webSocketMessage` / `webSocketClose` callbacks can dispatch to them.
|
|
64
|
+
*/
|
|
65
|
+
function createDOUpgradeWebSocket(doState) {
|
|
66
|
+
return defineWebSocketHelper((_c, events) => {
|
|
67
|
+
const pair = new WebSocketPair();
|
|
68
|
+
const [client, server] = Object.values(pair);
|
|
69
|
+
// Accept via hibernation API so the DO can wake on messages
|
|
70
|
+
doState.acceptWebSocket(server);
|
|
71
|
+
// Tag the server socket so webSocketMessage/webSocketClose can find handlers
|
|
72
|
+
socketTags.set(server, { events: events });
|
|
73
|
+
// Fire onOpen synchronously (socket is already accepted)
|
|
74
|
+
const wsCtx = createWSContext(server);
|
|
75
|
+
events.onOpen?.(new Event('open'), wsCtx);
|
|
76
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// SyncDurableObject base class
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
/**
|
|
83
|
+
* Base class for a Syncular Durable Object with Hono routing and WebSocket.
|
|
84
|
+
*
|
|
85
|
+
* Subclass and implement `setup()` to configure routes.
|
|
86
|
+
*/
|
|
87
|
+
export class SyncDurableObject {
|
|
88
|
+
ctx;
|
|
89
|
+
env;
|
|
90
|
+
app = null;
|
|
91
|
+
initPromise = null;
|
|
92
|
+
doUpgradeWebSocket;
|
|
93
|
+
constructor(ctx, env) {
|
|
94
|
+
this.ctx = ctx;
|
|
95
|
+
this.env = env;
|
|
96
|
+
this.doUpgradeWebSocket = createDOUpgradeWebSocket(ctx);
|
|
97
|
+
}
|
|
98
|
+
async getApp() {
|
|
99
|
+
if (this.app)
|
|
100
|
+
return this.app;
|
|
101
|
+
if (!this.initPromise) {
|
|
102
|
+
const honoApp = new Hono();
|
|
103
|
+
this.initPromise = Promise.resolve(this.setup(honoApp, this.env, this.doUpgradeWebSocket)).then(() => {
|
|
104
|
+
this.app = honoApp;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
await this.initPromise;
|
|
108
|
+
return this.app;
|
|
109
|
+
}
|
|
110
|
+
/** Handle incoming HTTP requests (and WebSocket upgrades). */
|
|
111
|
+
async fetch(request) {
|
|
112
|
+
const app = await this.getApp();
|
|
113
|
+
return app.fetch(request, this.env);
|
|
114
|
+
}
|
|
115
|
+
/** Dispatch incoming WebSocket messages to Hono event handlers. */
|
|
116
|
+
async webSocketMessage(ws, message) {
|
|
117
|
+
const tag = socketTags.get(ws);
|
|
118
|
+
if (!tag?.events.onMessage)
|
|
119
|
+
return;
|
|
120
|
+
const wsCtx = createWSContext(ws);
|
|
121
|
+
const evt = new MessageEvent('message', { data: message });
|
|
122
|
+
tag.events.onMessage(evt, wsCtx);
|
|
123
|
+
}
|
|
124
|
+
/** Dispatch WebSocket close events to Hono event handlers. */
|
|
125
|
+
async webSocketClose(ws, code, reason, _wasClean) {
|
|
126
|
+
const tag = socketTags.get(ws);
|
|
127
|
+
if (!tag?.events.onClose)
|
|
128
|
+
return;
|
|
129
|
+
const wsCtx = createWSContext(ws);
|
|
130
|
+
const evt = new CloseEvent('close', { code, reason });
|
|
131
|
+
tag.events.onClose(evt, wsCtx);
|
|
132
|
+
socketTags.delete(ws);
|
|
133
|
+
}
|
|
134
|
+
/** Dispatch WebSocket error events to Hono event handlers. */
|
|
135
|
+
async webSocketError(ws, _error) {
|
|
136
|
+
const tag = socketTags.get(ws);
|
|
137
|
+
if (!tag?.events.onError)
|
|
138
|
+
return;
|
|
139
|
+
const wsCtx = createWSContext(ws);
|
|
140
|
+
const evt = new Event('error');
|
|
141
|
+
tag.events.onError(evt, wsCtx);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Worker → DO router
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
/**
|
|
148
|
+
* Create a Worker export that routes all requests to a Durable Object.
|
|
149
|
+
*
|
|
150
|
+
* Uses a single DO instance (derived from a stable ID) to hold all
|
|
151
|
+
* connections. For multi-tenant setups, override `getStubId`.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* export default createSyncWorkerWithDO<Env>('SYNC_DO');
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export function createSyncWorkerWithDO(bindingName, options) {
|
|
159
|
+
return {
|
|
160
|
+
async fetch(request, env, _ctx) {
|
|
161
|
+
const ns = env[bindingName];
|
|
162
|
+
const id = options?.getStubId
|
|
163
|
+
? options.getStubId(ns, request, env)
|
|
164
|
+
: ns.idFromName('sync');
|
|
165
|
+
const stub = ns.get(id);
|
|
166
|
+
return stub.fetch(request);
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=durable-object.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-object.js","sourceRoot":"","sources":["../src/durable-object.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/server-cloudflare - Cloudflare adapters for Syncular
|
|
3
|
+
*
|
|
4
|
+
* Two deployment modes:
|
|
5
|
+
* - Worker (polling only): `@syncular/server-cloudflare/worker`
|
|
6
|
+
* - Durable Object (WebSocket + polling): `@syncular/server-cloudflare/durable-object`
|
|
7
|
+
*
|
|
8
|
+
* Blob storage:
|
|
9
|
+
* - R2 native: `@syncular/server-cloudflare/r2`
|
|
10
|
+
*
|
|
11
|
+
* Dialect is user-provided:
|
|
12
|
+
* - D1 + SQLite: `@syncular/dialect-d1` + `@syncular/server-dialect-sqlite`
|
|
13
|
+
* - Neon + Postgres: `@syncular/dialect-neon` + `@syncular/server-dialect-postgres`
|
|
14
|
+
*/
|
|
15
|
+
export * from './durable-object';
|
|
16
|
+
export * from './r2';
|
|
17
|
+
export * from './worker';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,MAAM,CAAC;AACrB,cAAc,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/server-cloudflare - Cloudflare adapters for Syncular
|
|
3
|
+
*
|
|
4
|
+
* Two deployment modes:
|
|
5
|
+
* - Worker (polling only): `@syncular/server-cloudflare/worker`
|
|
6
|
+
* - Durable Object (WebSocket + polling): `@syncular/server-cloudflare/durable-object`
|
|
7
|
+
*
|
|
8
|
+
* Blob storage:
|
|
9
|
+
* - R2 native: `@syncular/server-cloudflare/r2`
|
|
10
|
+
*
|
|
11
|
+
* Dialect is user-provided:
|
|
12
|
+
* - D1 + SQLite: `@syncular/dialect-d1` + `@syncular/server-dialect-sqlite`
|
|
13
|
+
* - Neon + Postgres: `@syncular/dialect-neon` + `@syncular/server-dialect-postgres`
|
|
14
|
+
*/
|
|
15
|
+
export * from './durable-object';
|
|
16
|
+
export * from './r2';
|
|
17
|
+
export * from './worker';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,MAAM,CAAC;AACrB,cAAc,UAAU,CAAC"}
|
package/dist/r2.d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R2 blob storage adapter using native R2Bucket binding.
|
|
3
|
+
*
|
|
4
|
+
* This adapter stores blobs in Cloudflare R2 using the native binding,
|
|
5
|
+
* without requiring the AWS SDK. Since R2 bindings don't support presigned URLs,
|
|
6
|
+
* this adapter generates signed tokens that allow uploads/downloads through
|
|
7
|
+
* the Worker's blob routes (similar to the database adapter).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Options for signing an upload URL.
|
|
11
|
+
*/
|
|
12
|
+
export interface BlobSignUploadOptions {
|
|
13
|
+
/** SHA-256 hash (for naming and checksum validation) */
|
|
14
|
+
hash: string;
|
|
15
|
+
/** Content size in bytes */
|
|
16
|
+
size: number;
|
|
17
|
+
/** MIME type */
|
|
18
|
+
mimeType: string;
|
|
19
|
+
/** URL expiration in seconds */
|
|
20
|
+
expiresIn: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Result of signing an upload URL.
|
|
24
|
+
*/
|
|
25
|
+
export interface BlobSignedUpload {
|
|
26
|
+
/** The URL to upload to */
|
|
27
|
+
url: string;
|
|
28
|
+
/** HTTP method */
|
|
29
|
+
method: 'PUT' | 'POST';
|
|
30
|
+
/** Required headers */
|
|
31
|
+
headers?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Options for signing a download URL.
|
|
35
|
+
*/
|
|
36
|
+
export interface BlobSignDownloadOptions {
|
|
37
|
+
/** SHA-256 hash */
|
|
38
|
+
hash: string;
|
|
39
|
+
/** URL expiration in seconds */
|
|
40
|
+
expiresIn: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Adapter for blob storage backends.
|
|
44
|
+
* Implements the same interface as @syncular/core BlobStorageAdapter.
|
|
45
|
+
*/
|
|
46
|
+
export interface BlobStorageAdapter {
|
|
47
|
+
/** Adapter name for logging/debugging */
|
|
48
|
+
readonly name: string;
|
|
49
|
+
/**
|
|
50
|
+
* Generate a presigned URL for uploading a blob.
|
|
51
|
+
*/
|
|
52
|
+
signUpload(options: BlobSignUploadOptions): Promise<BlobSignedUpload>;
|
|
53
|
+
/**
|
|
54
|
+
* Generate a presigned URL for downloading a blob.
|
|
55
|
+
*/
|
|
56
|
+
signDownload(options: BlobSignDownloadOptions): Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Check if a blob exists in storage.
|
|
59
|
+
*/
|
|
60
|
+
exists(hash: string): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Delete a blob (for garbage collection).
|
|
63
|
+
*/
|
|
64
|
+
delete(hash: string): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Get blob metadata from storage (optional).
|
|
67
|
+
*/
|
|
68
|
+
getMetadata?(hash: string): Promise<{
|
|
69
|
+
size: number;
|
|
70
|
+
mimeType?: string;
|
|
71
|
+
} | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Store blob data directly (for adapters that support direct storage).
|
|
74
|
+
*/
|
|
75
|
+
put?(hash: string, data: Uint8Array, metadata?: Record<string, unknown>): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Get blob data directly (for adapters that support direct retrieval).
|
|
78
|
+
*/
|
|
79
|
+
get?(hash: string): Promise<Uint8Array | null>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Token signer interface for creating/verifying upload/download tokens.
|
|
83
|
+
*/
|
|
84
|
+
export interface BlobTokenSigner {
|
|
85
|
+
/**
|
|
86
|
+
* Sign a token for blob upload/download authorization.
|
|
87
|
+
* @param payload The data to sign
|
|
88
|
+
* @param expiresIn Expiration time in seconds
|
|
89
|
+
* @returns A signed token string
|
|
90
|
+
*/
|
|
91
|
+
sign(payload: {
|
|
92
|
+
hash: string;
|
|
93
|
+
action: 'upload' | 'download';
|
|
94
|
+
expiresAt: number;
|
|
95
|
+
}, expiresIn: number): Promise<string>;
|
|
96
|
+
/**
|
|
97
|
+
* Verify and decode a signed token.
|
|
98
|
+
* @returns The payload if valid, null if invalid/expired
|
|
99
|
+
*/
|
|
100
|
+
verify(token: string): Promise<{
|
|
101
|
+
hash: string;
|
|
102
|
+
action: 'upload' | 'download';
|
|
103
|
+
expiresAt: number;
|
|
104
|
+
} | null>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a simple HMAC-based token signer.
|
|
108
|
+
*/
|
|
109
|
+
export declare function createHmacTokenSigner(secret: string): BlobTokenSigner;
|
|
110
|
+
export interface R2BlobStorageAdapterOptions {
|
|
111
|
+
/** R2 bucket binding */
|
|
112
|
+
bucket: R2Bucket;
|
|
113
|
+
/** Optional key prefix for all blobs */
|
|
114
|
+
keyPrefix?: string;
|
|
115
|
+
/** Base URL for the blob routes (e.g., "https://api.example.com/api/sync") */
|
|
116
|
+
baseUrl: string;
|
|
117
|
+
/** Token signer for authorization */
|
|
118
|
+
tokenSigner: BlobTokenSigner;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create an R2 blob storage adapter using native R2Bucket binding.
|
|
122
|
+
*
|
|
123
|
+
* Since R2 bindings don't support presigned URLs, this adapter generates
|
|
124
|
+
* signed tokens and uses Worker-proxied uploads/downloads.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* import { createR2BlobStorageAdapter, createHmacTokenSigner } from '@syncular/server-cloudflare/r2';
|
|
129
|
+
*
|
|
130
|
+
* type Env = { BLOBS: R2Bucket };
|
|
131
|
+
*
|
|
132
|
+
* const adapter = createR2BlobStorageAdapter({
|
|
133
|
+
* bucket: env.BLOBS,
|
|
134
|
+
* baseUrl: 'https://api.example.com/sync',
|
|
135
|
+
* tokenSigner: createHmacTokenSigner(env.BLOB_SECRET),
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export declare function createR2BlobStorageAdapter(options: R2BlobStorageAdapterOptions): BlobStorageAdapter;
|
|
140
|
+
//# sourceMappingURL=r2.d.ts.map
|
package/dist/r2.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../src/r2.ts"],"names":[],"mappings":"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"}
|
package/dist/r2.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R2 blob storage adapter using native R2Bucket binding.
|
|
3
|
+
*
|
|
4
|
+
* This adapter stores blobs in Cloudflare R2 using the native binding,
|
|
5
|
+
* without requiring the AWS SDK. Since R2 bindings don't support presigned URLs,
|
|
6
|
+
* this adapter generates signed tokens that allow uploads/downloads through
|
|
7
|
+
* the Worker's blob routes (similar to the database adapter).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Create a simple HMAC-based token signer.
|
|
11
|
+
*/
|
|
12
|
+
export function createHmacTokenSigner(secret) {
|
|
13
|
+
const encoder = new TextEncoder();
|
|
14
|
+
async function hmacSign(data) {
|
|
15
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
16
|
+
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(data));
|
|
17
|
+
return bufferToHex(new Uint8Array(signature));
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
async sign(payload, _expiresIn) {
|
|
21
|
+
const data = JSON.stringify(payload);
|
|
22
|
+
const dataB64 = btoa(data);
|
|
23
|
+
const sig = await hmacSign(dataB64);
|
|
24
|
+
return `${dataB64}.${sig}`;
|
|
25
|
+
},
|
|
26
|
+
async verify(token) {
|
|
27
|
+
const [dataB64, sig] = token.split('.');
|
|
28
|
+
if (!dataB64 || !sig)
|
|
29
|
+
return null;
|
|
30
|
+
const expectedSig = await hmacSign(dataB64);
|
|
31
|
+
if (sig !== expectedSig)
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const data = JSON.parse(atob(dataB64));
|
|
35
|
+
if (Date.now() > data.expiresAt)
|
|
36
|
+
return null;
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function bufferToHex(buffer) {
|
|
46
|
+
return Array.from(buffer)
|
|
47
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
48
|
+
.join('');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create an R2 blob storage adapter using native R2Bucket binding.
|
|
52
|
+
*
|
|
53
|
+
* Since R2 bindings don't support presigned URLs, this adapter generates
|
|
54
|
+
* signed tokens and uses Worker-proxied uploads/downloads.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* import { createR2BlobStorageAdapter, createHmacTokenSigner } from '@syncular/server-cloudflare/r2';
|
|
59
|
+
*
|
|
60
|
+
* type Env = { BLOBS: R2Bucket };
|
|
61
|
+
*
|
|
62
|
+
* const adapter = createR2BlobStorageAdapter({
|
|
63
|
+
* bucket: env.BLOBS,
|
|
64
|
+
* baseUrl: 'https://api.example.com/sync',
|
|
65
|
+
* tokenSigner: createHmacTokenSigner(env.BLOB_SECRET),
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function createR2BlobStorageAdapter(options) {
|
|
70
|
+
const { bucket, keyPrefix = '', baseUrl, tokenSigner } = options;
|
|
71
|
+
// Normalize base URL (remove trailing slash)
|
|
72
|
+
const normalizedBaseUrl = baseUrl.replace(/\/$/, '');
|
|
73
|
+
function getKey(hash) {
|
|
74
|
+
// Remove "sha256:" prefix and use hex as key
|
|
75
|
+
const hex = hash.startsWith('sha256:') ? hash.slice(7) : hash;
|
|
76
|
+
return `${keyPrefix}${hex}`;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
name: 'r2',
|
|
80
|
+
async signUpload(opts) {
|
|
81
|
+
const expiresAt = Date.now() + opts.expiresIn * 1000;
|
|
82
|
+
const token = await tokenSigner.sign({ hash: opts.hash, action: 'upload', expiresAt }, opts.expiresIn);
|
|
83
|
+
// URL points to server's blob upload endpoint
|
|
84
|
+
const url = `${normalizedBaseUrl}/blobs/${encodeURIComponent(opts.hash)}/upload?token=${encodeURIComponent(token)}`;
|
|
85
|
+
return {
|
|
86
|
+
url,
|
|
87
|
+
method: 'PUT',
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': opts.mimeType,
|
|
90
|
+
'Content-Length': String(opts.size),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
async signDownload(opts) {
|
|
95
|
+
const expiresAt = Date.now() + opts.expiresIn * 1000;
|
|
96
|
+
const token = await tokenSigner.sign({ hash: opts.hash, action: 'download', expiresAt }, opts.expiresIn);
|
|
97
|
+
return `${normalizedBaseUrl}/blobs/${encodeURIComponent(opts.hash)}/download?token=${encodeURIComponent(token)}`;
|
|
98
|
+
},
|
|
99
|
+
async exists(hash) {
|
|
100
|
+
const key = getKey(hash);
|
|
101
|
+
const head = await bucket.head(key);
|
|
102
|
+
return head !== null;
|
|
103
|
+
},
|
|
104
|
+
async delete(hash) {
|
|
105
|
+
const key = getKey(hash);
|
|
106
|
+
await bucket.delete(key);
|
|
107
|
+
},
|
|
108
|
+
async getMetadata(hash) {
|
|
109
|
+
const key = getKey(hash);
|
|
110
|
+
const head = await bucket.head(key);
|
|
111
|
+
if (!head)
|
|
112
|
+
return null;
|
|
113
|
+
return {
|
|
114
|
+
size: head.size,
|
|
115
|
+
mimeType: head.httpMetadata?.contentType,
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
async put(hash, data, metadata) {
|
|
119
|
+
const key = getKey(hash);
|
|
120
|
+
const mimeType = typeof metadata?.mimeType === 'string'
|
|
121
|
+
? metadata.mimeType
|
|
122
|
+
: 'application/octet-stream';
|
|
123
|
+
await bucket.put(key, data, {
|
|
124
|
+
httpMetadata: {
|
|
125
|
+
contentType: mimeType,
|
|
126
|
+
},
|
|
127
|
+
sha256: hash.startsWith('sha256:') ? hash.slice(7) : undefined,
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
async get(hash) {
|
|
131
|
+
const key = getKey(hash);
|
|
132
|
+
const object = await bucket.get(key);
|
|
133
|
+
if (!object)
|
|
134
|
+
return null;
|
|
135
|
+
return new Uint8Array(await object.arrayBuffer());
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=r2.js.map
|
package/dist/r2.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2.js","sourceRoot":"","sources":["../src/r2.ts"],"names":[],"mappings":"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"}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/server-cloudflare - Worker handler (polling only)
|
|
3
|
+
*
|
|
4
|
+
* Creates a stateless Cloudflare Worker that serves sync routes via Hono.
|
|
5
|
+
* No WebSocket support — use the Durable Object adapter for realtime.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createSyncWorker } from '@syncular/server-cloudflare/worker';
|
|
10
|
+
* import { createD1Db } from '@syncular/dialect-d1';
|
|
11
|
+
* import { createSqliteServerDialect } from '@syncular/server-dialect-sqlite';
|
|
12
|
+
* import { ensureSyncSchema } from '@syncular/server';
|
|
13
|
+
* import { createSyncServer } from '@syncular/server-hono';
|
|
14
|
+
*
|
|
15
|
+
* type Env = { DB: D1Database };
|
|
16
|
+
*
|
|
17
|
+
* export default createSyncWorker<Env>((app, env) => {
|
|
18
|
+
* const db = createD1Db(env.DB);
|
|
19
|
+
* const dialect = createSqliteServerDialect();
|
|
20
|
+
* const { syncRoutes, consoleRoutes } = createSyncServer({
|
|
21
|
+
* db, dialect,
|
|
22
|
+
* handlers: [tasksHandler],
|
|
23
|
+
* authenticate: async (c) => ({ actorId: c.req.header('x-user-id')! }),
|
|
24
|
+
* });
|
|
25
|
+
* app.route('/sync', syncRoutes);
|
|
26
|
+
* if (consoleRoutes) app.route('/console', consoleRoutes);
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
import { Hono } from 'hono';
|
|
31
|
+
type SyncWorkerSetup<B extends object> = (app: Hono<{
|
|
32
|
+
Bindings: B;
|
|
33
|
+
}>, env: B) => void | Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Create a Cloudflare Worker export that lazily initializes a Hono app.
|
|
36
|
+
*
|
|
37
|
+
* The `setup` callback is called once per isolate on the first request.
|
|
38
|
+
* It receives a fresh Hono app and the Worker env bindings.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createSyncWorker<Bindings extends object = Record<string, unknown>>(setup: SyncWorkerSetup<Bindings>): ExportedHandler<Bindings>;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,KAAK,eAAe,CAAC,CAAC,SAAS,MAAM,IAAI,CACvC,GAAG,EAAE,IAAI,CAAC;IAAE,QAAQ,EAAE,CAAC,CAAA;CAAE,CAAC,EAC1B,GAAG,EAAE,CAAC,KACH,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjD,KAAK,EAAE,eAAe,CAAC,QAAQ,CAAC,GAAG,eAAe,CAAC,QAAQ,CAAC,CA2B7D"}
|