@syncular/server-cloudflare 0.0.1-100

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.
@@ -0,0 +1,89 @@
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
+ private closeUntrackedSockets;
62
+ /** Handle incoming HTTP requests (and WebSocket upgrades). */
63
+ fetch(request: Request): Promise<Response>;
64
+ /** Dispatch incoming WebSocket messages to Hono event handlers. */
65
+ webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void>;
66
+ /** Dispatch WebSocket close events to Hono event handlers. */
67
+ webSocketClose(ws: WebSocket, code: number, reason: string, _wasClean: boolean): Promise<void>;
68
+ /** Dispatch WebSocket error events to Hono event handlers. */
69
+ webSocketError(ws: WebSocket, _error: unknown): Promise<void>;
70
+ }
71
+ /**
72
+ * Create a Worker export that routes all requests to a Durable Object.
73
+ *
74
+ * Uses a single DO instance (derived from a stable ID) to hold all
75
+ * connections. For multi-tenant setups, override `getStubId`.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * export default createSyncWorkerWithDO<Env>('SYNC_DO');
80
+ * ```
81
+ */
82
+ export declare function createSyncWorkerWithDO<E extends object>(bindingName: string & keyof E, options?: {
83
+ /**
84
+ * Derive a DurableObject ID from the request.
85
+ * Defaults to a single global instance via `idFromName('sync')`.
86
+ */
87
+ getStubId?: (ns: DurableObjectNamespace, request: Request, env: E) => DurableObjectId;
88
+ }): ExportedHandler<E>;
89
+ //# 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;AA4E1D;;;;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,EAK1C;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,OAAO,CAAC,qBAAqB;IAQ7B,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,CAUf;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,CAWf;IAED,8DAA8D;IACxD,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAUlE;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,195 @@
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
+ const STALE_SOCKET_CLOSE_CODE = 1012;
40
+ const STALE_SOCKET_CLOSE_REASON = 'WebSocket session expired; reconnect required';
41
+ /**
42
+ * WeakMap from server-side WebSocket → tag with event handlers.
43
+ * Populated on upgrade, read in webSocketMessage/webSocketClose.
44
+ */
45
+ const socketTags = new WeakMap();
46
+ function closeStaleSocket(ws) {
47
+ try {
48
+ ws.close(STALE_SOCKET_CLOSE_CODE, STALE_SOCKET_CLOSE_REASON);
49
+ }
50
+ catch {
51
+ // ignore
52
+ }
53
+ }
54
+ function createWSContext(ws) {
55
+ return new WSContext({
56
+ send(data) {
57
+ ws.send(data);
58
+ },
59
+ close(code, reason) {
60
+ ws.close(code, reason);
61
+ },
62
+ raw: ws,
63
+ get readyState() {
64
+ return ws.readyState;
65
+ },
66
+ });
67
+ }
68
+ /**
69
+ * Create an `upgradeWebSocket` function backed by the Durable Object
70
+ * hibernation API (`state.acceptWebSocket`).
71
+ *
72
+ * Each accepted socket is tagged with its Hono `WSEvents` handlers so the
73
+ * DO's `webSocketMessage` / `webSocketClose` callbacks can dispatch to them.
74
+ */
75
+ function createDOUpgradeWebSocket(doState) {
76
+ return defineWebSocketHelper((_c, events) => {
77
+ const pair = new WebSocketPair();
78
+ const [client, server] = Object.values(pair);
79
+ // Accept via hibernation API so the DO can wake on messages
80
+ doState.acceptWebSocket(server);
81
+ // Tag the server socket so webSocketMessage/webSocketClose can find handlers
82
+ socketTags.set(server, { events: events });
83
+ // Fire onOpen synchronously (socket is already accepted)
84
+ const wsCtx = createWSContext(server);
85
+ events.onOpen?.(new Event('open'), wsCtx);
86
+ return new Response(null, { status: 101, webSocket: client });
87
+ });
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // SyncDurableObject base class
91
+ // ---------------------------------------------------------------------------
92
+ /**
93
+ * Base class for a Syncular Durable Object with Hono routing and WebSocket.
94
+ *
95
+ * Subclass and implement `setup()` to configure routes.
96
+ */
97
+ export class SyncDurableObject {
98
+ ctx;
99
+ env;
100
+ app = null;
101
+ initPromise = null;
102
+ doUpgradeWebSocket;
103
+ constructor(ctx, env) {
104
+ this.ctx = ctx;
105
+ this.env = env;
106
+ this.doUpgradeWebSocket = createDOUpgradeWebSocket(ctx);
107
+ this.closeUntrackedSockets();
108
+ }
109
+ async getApp() {
110
+ if (this.app)
111
+ return this.app;
112
+ if (!this.initPromise) {
113
+ const honoApp = new Hono();
114
+ this.initPromise = Promise.resolve(this.setup(honoApp, this.env, this.doUpgradeWebSocket)).then(() => {
115
+ this.app = honoApp;
116
+ });
117
+ }
118
+ await this.initPromise;
119
+ return this.app;
120
+ }
121
+ closeUntrackedSockets() {
122
+ const sockets = this.ctx.getWebSockets();
123
+ for (const ws of sockets) {
124
+ if (socketTags.has(ws))
125
+ continue;
126
+ closeStaleSocket(ws);
127
+ }
128
+ }
129
+ /** Handle incoming HTTP requests (and WebSocket upgrades). */
130
+ async fetch(request) {
131
+ const app = await this.getApp();
132
+ return app.fetch(request, this.env);
133
+ }
134
+ /** Dispatch incoming WebSocket messages to Hono event handlers. */
135
+ async webSocketMessage(ws, message) {
136
+ const tag = socketTags.get(ws);
137
+ if (!tag?.events.onMessage) {
138
+ closeStaleSocket(ws);
139
+ return;
140
+ }
141
+ const wsCtx = createWSContext(ws);
142
+ const evt = new MessageEvent('message', { data: message });
143
+ tag.events.onMessage(evt, wsCtx);
144
+ }
145
+ /** Dispatch WebSocket close events to Hono event handlers. */
146
+ async webSocketClose(ws, code, reason, _wasClean) {
147
+ const tag = socketTags.get(ws);
148
+ if (!tag?.events.onClose) {
149
+ socketTags.delete(ws);
150
+ return;
151
+ }
152
+ const wsCtx = createWSContext(ws);
153
+ const evt = new CloseEvent('close', { code, reason });
154
+ tag.events.onClose(evt, wsCtx);
155
+ socketTags.delete(ws);
156
+ }
157
+ /** Dispatch WebSocket error events to Hono event handlers. */
158
+ async webSocketError(ws, _error) {
159
+ const tag = socketTags.get(ws);
160
+ if (!tag?.events.onError) {
161
+ closeStaleSocket(ws);
162
+ return;
163
+ }
164
+ const wsCtx = createWSContext(ws);
165
+ const evt = new Event('error');
166
+ tag.events.onError(evt, wsCtx);
167
+ }
168
+ }
169
+ // ---------------------------------------------------------------------------
170
+ // Worker → DO router
171
+ // ---------------------------------------------------------------------------
172
+ /**
173
+ * Create a Worker export that routes all requests to a Durable Object.
174
+ *
175
+ * Uses a single DO instance (derived from a stable ID) to hold all
176
+ * connections. For multi-tenant setups, override `getStubId`.
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * export default createSyncWorkerWithDO<Env>('SYNC_DO');
181
+ * ```
182
+ */
183
+ export function createSyncWorkerWithDO(bindingName, options) {
184
+ return {
185
+ async fetch(request, env, _ctx) {
186
+ const ns = env[bindingName];
187
+ const id = options?.getStubId
188
+ ? options.getStubId(ns, request, env)
189
+ : ns.idFromName('sync');
190
+ const stub = ns.get(id);
191
+ return stub.fetch(request);
192
+ },
193
+ };
194
+ }
195
+ //# 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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"}
@@ -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.js';
16
+ export * from './r2.js';
17
+ export * from './worker.js';
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,148 @@
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
+ * Store blob data directly from a stream.
78
+ */
79
+ putStream?(hash: string, stream: ReadableStream<Uint8Array>, metadata?: Record<string, unknown>): Promise<void>;
80
+ /**
81
+ * Get blob data directly (for adapters that support direct retrieval).
82
+ */
83
+ get?(hash: string): Promise<Uint8Array | null>;
84
+ /**
85
+ * Get blob data directly as a stream.
86
+ */
87
+ getStream?(hash: string): Promise<ReadableStream<Uint8Array> | null>;
88
+ }
89
+ /**
90
+ * Token signer interface for creating/verifying upload/download tokens.
91
+ */
92
+ export interface BlobTokenSigner {
93
+ /**
94
+ * Sign a token for blob upload/download authorization.
95
+ * @param payload The data to sign
96
+ * @param expiresIn Expiration time in seconds
97
+ * @returns A signed token string
98
+ */
99
+ sign(payload: {
100
+ hash: string;
101
+ action: 'upload' | 'download';
102
+ expiresAt: number;
103
+ }, expiresIn: number): Promise<string>;
104
+ /**
105
+ * Verify and decode a signed token.
106
+ * @returns The payload if valid, null if invalid/expired
107
+ */
108
+ verify(token: string): Promise<{
109
+ hash: string;
110
+ action: 'upload' | 'download';
111
+ expiresAt: number;
112
+ } | null>;
113
+ }
114
+ /**
115
+ * Create a simple HMAC-based token signer.
116
+ */
117
+ export declare function createHmacTokenSigner(secret: string): BlobTokenSigner;
118
+ export interface R2BlobStorageAdapterOptions {
119
+ /** R2 bucket binding */
120
+ bucket: R2Bucket;
121
+ /** Optional key prefix for all blobs */
122
+ keyPrefix?: string;
123
+ /** Base URL for the blob routes (e.g., "https://api.example.com/api/sync") */
124
+ baseUrl: string;
125
+ /** Token signer for authorization */
126
+ tokenSigner: BlobTokenSigner;
127
+ }
128
+ /**
129
+ * Create an R2 blob storage adapter using native R2Bucket binding.
130
+ *
131
+ * Since R2 bindings don't support presigned URLs, this adapter generates
132
+ * signed tokens and uses Worker-proxied uploads/downloads.
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * import { createR2BlobStorageAdapter, createHmacTokenSigner } from '@syncular/server-cloudflare/r2';
137
+ *
138
+ * type Env = { BLOBS: R2Bucket };
139
+ *
140
+ * const adapter = createR2BlobStorageAdapter({
141
+ * bucket: env.BLOBS,
142
+ * baseUrl: 'https://api.example.com/sync',
143
+ * tokenSigner: createHmacTokenSigner(env.BLOB_SECRET),
144
+ * });
145
+ * ```
146
+ */
147
+ export declare function createR2BlobStorageAdapter(options: R2BlobStorageAdapterOptions): BlobStorageAdapter;
148
+ //# sourceMappingURL=r2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../src/r2.ts"],"names":[],"mappings":"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"}