@verdant-web/cloudflare 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Grant Forrest
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Verdant Cloudflare Bindings
2
+
3
+ This library provides bindings for running a Verdant sync backend on Cloudflare using Workers, Durable Objects and (optionally) R2 Object Storage.
4
+
5
+ See: [Cloudflare Adapter docs](https://verdant.dev/docs/sync/cloudflare)
@@ -0,0 +1,27 @@
1
+ export interface VerdantWorkerConfig {
2
+ durableObjectBindingName: string;
3
+ tokenSecretBindingName: string;
4
+ }
5
+ /**
6
+ * Creates a Hono app configured to serve Verdant library traffic.
7
+ * This app 'router' should be mounted onto another Verdant app
8
+ * used by your main Cloudflare worker, either as a Hono router
9
+ * or by just calling app.fetch with the request.
10
+ * The Durable Object binding specified by `durableObjectBindingName`
11
+ * must be configured to point to the DurableObjectNamespace for
12
+ * the Verdant library Durable Object class as configured in your
13
+ * wrangler config.
14
+ */
15
+ export declare function createVerdantWorkerApp(config: VerdantWorkerConfig): import("hono/hono-base").HonoBase<{
16
+ Bindings: any;
17
+ Variables: any;
18
+ }, {
19
+ "/": {
20
+ $all: {
21
+ input: {};
22
+ output: {};
23
+ outputFormat: import("hono/types").ResponseFormat;
24
+ status: import("hono/utils/http-status").StatusCode;
25
+ };
26
+ };
27
+ }, "/">;
@@ -0,0 +1,43 @@
1
+ import { errorHandler, tokenMiddleware, TokenVerifier, } from '@verdant-web/server/internals';
2
+ import { Hono } from 'hono';
3
+ import { basePath } from 'hono/route';
4
+ /**
5
+ * Creates a Hono app configured to serve Verdant library traffic.
6
+ * This app 'router' should be mounted onto another Verdant app
7
+ * used by your main Cloudflare worker, either as a Hono router
8
+ * or by just calling app.fetch with the request.
9
+ * The Durable Object binding specified by `durableObjectBindingName`
10
+ * must be configured to point to the DurableObjectNamespace for
11
+ * the Verdant library Durable Object class as configured in your
12
+ * wrangler config.
13
+ */
14
+ export function createVerdantWorkerApp(config) {
15
+ // this really just determines which library DO to route to
16
+ // and the DO handles the rest
17
+ const app = new Hono()
18
+ .onError(errorHandler)
19
+ .use((ctx, next) => {
20
+ const tokenVerifier = new TokenVerifier({
21
+ secret: ctx.env[config.tokenSecretBindingName],
22
+ });
23
+ return tokenMiddleware(tokenVerifier)(ctx, next);
24
+ })
25
+ .all((ctx) => {
26
+ if (ctx.req.method === 'OPTIONS') {
27
+ // preflight request, np
28
+ return ctx.newResponse(null, { status: 204 });
29
+ }
30
+ const info = ctx.get('tokenInfo');
31
+ const id = ctx.env[config.durableObjectBindingName].idFromName(info.libraryId);
32
+ const obj = ctx.env[config.durableObjectBindingName].get(id);
33
+ // remove the base path so the DO sees the correct routing
34
+ const path = basePath(ctx);
35
+ const thisUrl = new URL(ctx.req.url);
36
+ thisUrl.pathname = thisUrl.pathname.replace(path, '') || '/';
37
+ const modifiedReq = new Request(thisUrl.toString(), ctx.req.raw);
38
+ console.log(`[verdant worker] routing to DO ${info.libraryId}:`, ctx.req.method, ctx.req.url, path, modifiedReq.url);
39
+ return obj.fetch(modifiedReq);
40
+ });
41
+ return app;
42
+ }
43
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,YAAY,EACZ,eAAe,EACf,aAAa,GACb,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAA2B;IACjE,2DAA2D;IAC3D,8BAA8B;IAC9B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAqC;SACvD,OAAO,CAAC,YAAY,CAAC;SACrB,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QAClB,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC;YACvC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,sBAAsB,CAAC;SAC9C,CAAC,CAAC;QACH,OAAO,eAAe,CAAC,aAAa,CAAC,CAAC,GAAU,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACZ,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAClC,wBAAwB;YACxB,OAAO,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAClC,MAAM,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,UAAU,CAC7D,IAAI,CAAC,SAAS,CACd,CAAC;QACF,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7D,0DAA0D;QAC1D,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;QAC7D,MAAM,WAAW,GAAY,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1E,OAAO,CAAC,GAAG,CACV,kCAAkC,IAAI,CAAC,SAAS,GAAG,EACnD,GAAG,CAAC,GAAG,CAAC,MAAM,EACd,GAAG,CAAC,GAAG,CAAC,GAAG,EACX,IAAI,EACJ,WAAW,CAAC,GAAG,CACf,CAAC;QACF,OAAO,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEJ,OAAO,GAAG,CAAC;AACZ,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { ClientMessage, EventSubscriber } from '@verdant-web/common';
2
+ import { FileStorage, TokenInfo, UserProfiles } from '@verdant-web/server';
3
+ import { ClientConnectionManager, Library, LibraryEvents } from '@verdant-web/server/internals';
4
+ export interface DurableObjectLibraryConfig {
5
+ tokenSecret: string;
6
+ disableRebasing?: boolean;
7
+ fileStorage: FileStorage;
8
+ log?: (level: string, ...args: any[]) => void;
9
+ profiles: UserProfiles<any>;
10
+ storageOptions?: {
11
+ fileDeleteExpirationDays?: number;
12
+ replicaTruancyTimeout?: number;
13
+ };
14
+ }
15
+ export declare class DurableObjectLibrary {
16
+ #private;
17
+ private ctx;
18
+ protected clientConnections: ClientConnectionManager;
19
+ protected library: Library;
20
+ protected events: EventSubscriber<LibraryEvents>;
21
+ constructor(ctx: DurableObjectState, verdant: DurableObjectLibraryConfig);
22
+ get log(): (level: string, ...args: any[]) => void;
23
+ initialize: (libraryId: string) => Promise<void>;
24
+ fetch: (request: Request) => Promise<Response>;
25
+ webSocketMessage: (ws: WebSocket, message: string | ArrayBuffer) => void | Promise<void>;
26
+ webSocketClose: (ws: WebSocket, code: number, reason: string, wasClean: boolean) => void | Promise<void>;
27
+ webSocketError: (ws: WebSocket, error: unknown) => void | Promise<void>;
28
+ handleMessage: (key: string, info: TokenInfo, message: ClientMessage) => Promise<void>;
29
+ getDocumentSnapshot: (collection: string, id: string) => Promise<{} | null>;
30
+ getFileInfo: (fileId: string) => Promise<{
31
+ libraryId: string;
32
+ id: string;
33
+ name: string;
34
+ type: string;
35
+ url: string | null;
36
+ } | null>;
37
+ evict: () => Promise<void>;
38
+ forceTruant: (replicaId: string) => Promise<void>;
39
+ getInfo: () => Promise<import("@verdant-web/server").LibraryInfo | null>;
40
+ }
@@ -0,0 +1,233 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _DurableObjectLibrary_app, _DurableObjectLibrary_socketInfoCache, _DurableObjectLibrary_initialized, _DurableObjectLibrary_config, _DurableObjectLibrary_noop, _DurableObjectLibrary_restore, _DurableObjectLibrary_getSocketInfo;
13
+ import { createOid, EventSubscriber, VerdantError, } from '@verdant-web/common';
14
+ import { ClientConnectionManager, errorHandler, FileStorageLibraryDelegate, Library, SqlShardStorage, tokenMiddleware, TokenVerifier, UserProfileLoader, } from '@verdant-web/server/internals';
15
+ import { Hono } from 'hono';
16
+ import { createDurableObjectSqliteExecutor } from './sql.js';
17
+ export class DurableObjectLibrary {
18
+ constructor(ctx, verdant) {
19
+ this.ctx = ctx;
20
+ _DurableObjectLibrary_app.set(this, void 0);
21
+ _DurableObjectLibrary_socketInfoCache.set(this, new WeakMap());
22
+ _DurableObjectLibrary_initialized.set(this, false);
23
+ _DurableObjectLibrary_config.set(this, void 0);
24
+ this.events = new EventSubscriber();
25
+ _DurableObjectLibrary_noop.set(this, () => { });
26
+ this.initialize = async (libraryId) => {
27
+ var _a, _b, _c, _d;
28
+ if (__classPrivateFieldGet(this, _DurableObjectLibrary_initialized, "f")) {
29
+ return;
30
+ }
31
+ const storedLibraryId = await this.ctx.storage.get('libraryId');
32
+ if (storedLibraryId && storedLibraryId !== libraryId) {
33
+ throw new VerdantError(VerdantError.Code.InvalidRequest, undefined, 'Library ID does not match existing storage');
34
+ }
35
+ await this.ctx.storage.put('libraryId', libraryId);
36
+ __classPrivateFieldSet(this, _DurableObjectLibrary_initialized, true, "f");
37
+ const db = createDurableObjectSqliteExecutor(this.ctx.storage, {
38
+ log: this.log,
39
+ });
40
+ await db.migrate();
41
+ const storage = new SqlShardStorage(db, {
42
+ libraryId,
43
+ fileDeleteExpirationDays: (_b = (_a = __classPrivateFieldGet(this, _DurableObjectLibrary_config, "f").storageOptions) === null || _a === void 0 ? void 0 : _a.fileDeleteExpirationDays) !== null && _b !== void 0 ? _b : 14,
44
+ replicaTruancyMinutes: (_d = (_c = __classPrivateFieldGet(this, _DurableObjectLibrary_config, "f").storageOptions) === null || _c === void 0 ? void 0 : _c.replicaTruancyTimeout) !== null && _d !== void 0 ? _d : 14 * 24 * 60,
45
+ });
46
+ this.library = new Library({
47
+ id: libraryId,
48
+ storage,
49
+ sender: this.clientConnections,
50
+ events: this.events,
51
+ disableRebasing: __classPrivateFieldGet(this, _DurableObjectLibrary_config, "f").disableRebasing,
52
+ fileStorage: new FileStorageLibraryDelegate(libraryId, __classPrivateFieldGet(this, _DurableObjectLibrary_config, "f").fileStorage),
53
+ log: this.log,
54
+ presence: this.clientConnections.presence,
55
+ });
56
+ };
57
+ _DurableObjectLibrary_restore.set(this, async () => {
58
+ if (__classPrivateFieldGet(this, _DurableObjectLibrary_initialized, "f")) {
59
+ return;
60
+ }
61
+ const storedLibraryId = await this.ctx.storage.get('libraryId');
62
+ if (storedLibraryId) {
63
+ await this.initialize(storedLibraryId);
64
+ }
65
+ });
66
+ this.fetch = async (request) => {
67
+ return __classPrivateFieldGet(this, _DurableObjectLibrary_app, "f").fetch(request);
68
+ };
69
+ _DurableObjectLibrary_getSocketInfo.set(this, (ws) => {
70
+ if (!__classPrivateFieldGet(this, _DurableObjectLibrary_socketInfoCache, "f").has(ws)) {
71
+ // check for serialized form
72
+ const info = ws.deserializeAttachment();
73
+ if (info) {
74
+ __classPrivateFieldGet(this, _DurableObjectLibrary_socketInfoCache, "f").set(ws, info);
75
+ return info;
76
+ }
77
+ throw new Error('No user token info associated with socket');
78
+ }
79
+ return __classPrivateFieldGet(this, _DurableObjectLibrary_socketInfoCache, "f").get(ws);
80
+ });
81
+ this.webSocketMessage = (ws, message) => {
82
+ const { tokenInfo, key } = __classPrivateFieldGet(this, _DurableObjectLibrary_getSocketInfo, "f").call(this, ws);
83
+ const msg = JSON.parse(message.toString());
84
+ if (!msg) {
85
+ throw new VerdantError(VerdantError.Code.InvalidRequest, undefined, 'Invalid message format');
86
+ }
87
+ this.library.handleMessage(msg, key, tokenInfo);
88
+ };
89
+ this.webSocketClose = (ws, code, reason, wasClean) => {
90
+ const { key } = __classPrivateFieldGet(this, _DurableObjectLibrary_getSocketInfo, "f").call(this, ws);
91
+ this.log('info', 'WebSocket closed', { key, code, reason, wasClean });
92
+ this.clientConnections.remove(key);
93
+ __classPrivateFieldGet(this, _DurableObjectLibrary_socketInfoCache, "f").delete(ws);
94
+ };
95
+ this.webSocketError = (ws, error) => {
96
+ this.log('error', 'WebSocket error', error);
97
+ const { key } = __classPrivateFieldGet(this, _DurableObjectLibrary_getSocketInfo, "f").call(this, ws);
98
+ this.clientConnections.remove(key);
99
+ __classPrivateFieldGet(this, _DurableObjectLibrary_socketInfoCache, "f").delete(ws);
100
+ };
101
+ this.handleMessage = async (key, info, message) => {
102
+ await __classPrivateFieldGet(this, _DurableObjectLibrary_restore, "f").call(this);
103
+ return this.library.handleMessage(message, key, info);
104
+ };
105
+ // public library API
106
+ this.getDocumentSnapshot = async (collection, id) => {
107
+ await __classPrivateFieldGet(this, _DurableObjectLibrary_restore, "f").call(this);
108
+ return this.library.getDocumentSnapshot(createOid(collection, id));
109
+ };
110
+ this.getFileInfo = async (fileId) => {
111
+ await __classPrivateFieldGet(this, _DurableObjectLibrary_restore, "f").call(this);
112
+ return this.library.getFileInfo(fileId);
113
+ };
114
+ this.evict = async () => {
115
+ var _a;
116
+ await __classPrivateFieldGet(this, _DurableObjectLibrary_restore, "f").call(this);
117
+ await this.clientConnections.disconnectAll();
118
+ await ((_a = this.library) === null || _a === void 0 ? void 0 : _a.destroy());
119
+ await this.ctx.storage.deleteAll();
120
+ __classPrivateFieldSet(this, _DurableObjectLibrary_initialized, false, "f");
121
+ };
122
+ this.forceTruant = async (replicaId) => {
123
+ await __classPrivateFieldGet(this, _DurableObjectLibrary_restore, "f").call(this);
124
+ return this.library.forceTruant(replicaId);
125
+ };
126
+ this.getInfo = async () => {
127
+ await __classPrivateFieldGet(this, _DurableObjectLibrary_restore, "f").call(this);
128
+ return this.library.getInfo();
129
+ };
130
+ __classPrivateFieldSet(this, _DurableObjectLibrary_config, verdant, "f");
131
+ this.clientConnections = new ClientConnectionManager({
132
+ profiles: new UserProfileLoader(__classPrivateFieldGet(this, _DurableObjectLibrary_config, "f").profiles),
133
+ });
134
+ ctx.getWebSockets().forEach((ws) => {
135
+ // restore socket info after hibernation
136
+ const info = ws.deserializeAttachment();
137
+ if (info) {
138
+ __classPrivateFieldGet(this, _DurableObjectLibrary_socketInfoCache, "f").set(ws, info);
139
+ }
140
+ });
141
+ __classPrivateFieldSet(this, _DurableObjectLibrary_app, new Hono()
142
+ .onError(errorHandler)
143
+ .use(async (ctx, next) => {
144
+ console.log(`[verdant object]`, ctx.req.url);
145
+ await next();
146
+ })
147
+ .use(tokenMiddleware(new TokenVerifier({ secret: verdant.tokenSecret })))
148
+ .use(async (ctx, next) => {
149
+ // only after authorization, migrate the database
150
+ await this.initialize(ctx.get('tokenInfo').libraryId);
151
+ return next();
152
+ })
153
+ .post('/', async (ctx) => {
154
+ const info = ctx.get('tokenInfo');
155
+ const body = (await ctx.req.json());
156
+ if (!body) {
157
+ throw new VerdantError(VerdantError.Code.BodyRequired, undefined, 'Invalid request body');
158
+ }
159
+ const finish = this.clientConnections.addFetch(ctx.get('key'), ctx.req.raw, info);
160
+ for (const message of body.messages) {
161
+ await this.library.handleMessage(message, ctx.get('key'), info);
162
+ }
163
+ const res = finish();
164
+ return res;
165
+ })
166
+ .post('/files/:fileId', async (ctx) => {
167
+ const info = ctx.get('tokenInfo');
168
+ const id = ctx.req.param('fileId');
169
+ if (!id) {
170
+ throw new VerdantError(VerdantError.Code.NotFound, undefined, 'Supply a file ID as a path parameters');
171
+ }
172
+ const form = await ctx.req.parseBody();
173
+ const file = form.file;
174
+ if (!file || !(file instanceof File)) {
175
+ throw new VerdantError(VerdantError.Code.InvalidRequest, undefined, 'File must be provided as form data');
176
+ }
177
+ const fileInfo = {
178
+ id,
179
+ libraryId: info.libraryId,
180
+ fileName: file.name,
181
+ type: file.type,
182
+ };
183
+ await verdant.fileStorage.put(file.stream(), fileInfo);
184
+ await this.library.putFileInfo(fileInfo);
185
+ return ctx.json({ success: true });
186
+ })
187
+ .get('/files/:fileId', async (ctx) => {
188
+ const id = ctx.req.param('fileId');
189
+ if (!id) {
190
+ throw new VerdantError(VerdantError.Code.NotFound, undefined, 'Supply a file ID as a path parameters');
191
+ }
192
+ const fileInfo = await this.library.getFileInfo(id);
193
+ if (!fileInfo) {
194
+ throw new VerdantError(VerdantError.Code.NotFound, undefined, 'File not found');
195
+ }
196
+ return ctx.json(fileInfo);
197
+ })
198
+ // add socket handler
199
+ .all('*', async (ctx) => {
200
+ this.log('debug', '[verdant object] socket upgrade request');
201
+ // WebSocket endpoint
202
+ const upgradeHeader = ctx.req.header('Upgrade');
203
+ if (upgradeHeader !== 'websocket') {
204
+ return ctx.json({ error: 'Upgrade to WebSocket required' }, 400);
205
+ }
206
+ const info = ctx.get('tokenInfo');
207
+ const websocketPair = new WebSocketPair();
208
+ const [client, server] = Object.values(websocketPair);
209
+ this.ctx.acceptWebSocket(server);
210
+ this.log('debug', 'WebSocket connection established');
211
+ const meta = {
212
+ tokenInfo: info,
213
+ key: ctx.get('key'),
214
+ };
215
+ server.serializeAttachment(meta);
216
+ __classPrivateFieldGet(this, _DurableObjectLibrary_socketInfoCache, "f").set(client, meta);
217
+ this.clientConnections.addSocket(meta.key, server, info);
218
+ return new Response(null, {
219
+ status: 101,
220
+ webSocket: client,
221
+ });
222
+ }), "f");
223
+ // set auto-response for pings
224
+ this.ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair(JSON.stringify({ type: 'heartbeat' }), JSON.stringify({
225
+ type: 'heartbeat-response',
226
+ })));
227
+ }
228
+ get log() {
229
+ return __classPrivateFieldGet(this, _DurableObjectLibrary_config, "f").log || __classPrivateFieldGet(this, _DurableObjectLibrary_noop, "f");
230
+ }
231
+ }
232
+ _DurableObjectLibrary_app = new WeakMap(), _DurableObjectLibrary_socketInfoCache = new WeakMap(), _DurableObjectLibrary_initialized = new WeakMap(), _DurableObjectLibrary_config = new WeakMap(), _DurableObjectLibrary_noop = new WeakMap(), _DurableObjectLibrary_restore = new WeakMap(), _DurableObjectLibrary_getSocketInfo = new WeakMap();
233
+ //# sourceMappingURL=doLibrary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doLibrary.js","sourceRoot":"","sources":["../../src/doLibrary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { FileInfo, FileStorage } from '@verdant-web/server';
2
+ import { Readable } from 'stream';
3
+ export declare class R2FileStorage implements FileStorage {
4
+ private bucket;
5
+ private host;
6
+ constructor({ host, bucket }: {
7
+ host: string;
8
+ bucket: R2Bucket;
9
+ });
10
+ private getDirectory;
11
+ private getPath;
12
+ put: (fileStream: Readable | ReadableStream, data: FileInfo) => Promise<void>;
13
+ getUrl: (data: FileInfo) => Promise<string>;
14
+ delete: (data: FileInfo) => Promise<void>;
15
+ }
@@ -0,0 +1,35 @@
1
+ import { Readable } from 'stream';
2
+ export class R2FileStorage {
3
+ constructor({ host, bucket }) {
4
+ this.getDirectory = (data) => {
5
+ return `${data.libraryId}/${data.id}`;
6
+ };
7
+ this.getPath = (data) => {
8
+ return `${this.getDirectory(data)}/${data.fileName}`;
9
+ };
10
+ this.put = async (fileStream, data) => {
11
+ const webStream = fileStream instanceof ReadableStream
12
+ ? fileStream
13
+ : Readable.toWeb(fileStream);
14
+ await this.bucket.put(this.getPath(data), webStream, {
15
+ httpMetadata: {
16
+ contentType: data.type,
17
+ },
18
+ customMetadata: {
19
+ libraryId: data.libraryId,
20
+ fileId: data.id,
21
+ fileName: data.fileName,
22
+ },
23
+ });
24
+ };
25
+ this.getUrl = async (data) => {
26
+ return this.host + '/' + this.getPath(data);
27
+ };
28
+ this.delete = async (data) => {
29
+ await this.bucket.delete(this.getPath(data));
30
+ };
31
+ this.bucket = bucket;
32
+ this.host = host;
33
+ }
34
+ }
35
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/files.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,MAAM,OAAO,aAAa;IAIzB,YAAY,EAAE,IAAI,EAAE,MAAM,EAAsC;QAIxD,iBAAY,GAAG,CAAC,IAAc,EAAE,EAAE;YACzC,OAAO,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;QACvC,CAAC,CAAC;QACM,YAAO,GAAG,CAAC,IAAc,EAAE,EAAE;YACpC,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtD,CAAC,CAAC;QACF,QAAG,GAAG,KAAK,EACV,UAAqC,EACrC,IAAc,EACE,EAAE;YAClB,MAAM,SAAS,GACd,UAAU,YAAY,cAAc;gBACnC,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,SAAgB,EAAE;gBAC3D,YAAY,EAAE;oBACb,WAAW,EAAE,IAAI,CAAC,IAAI;iBACtB;gBACD,cAAc,EAAE;oBACf,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,MAAM,EAAE,IAAI,CAAC,EAAE;oBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;iBACvB;aACD,CAAC,CAAC;QACJ,CAAC,CAAC;QACF,WAAM,GAAG,KAAK,EAAE,IAAc,EAAmB,EAAE;YAClD,OAAO,IAAI,CAAC,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC,CAAC;QACF,WAAM,GAAG,KAAK,EAAE,IAAc,EAAiB,EAAE;YAChD,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,CAAC,CAAC;QAjCD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CAgCD"}
@@ -0,0 +1,3 @@
1
+ export { createVerdantWorkerApp } from './api.js';
2
+ export { DurableObjectLibrary } from './doLibrary.js';
3
+ export { R2FileStorage } from './files.js';
@@ -0,0 +1,4 @@
1
+ export { createVerdantWorkerApp } from './api.js';
2
+ export { DurableObjectLibrary } from './doLibrary.js';
3
+ export { R2FileStorage } from './files.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { SqliteExecutor } from '@verdant-web/server/internals';
2
+ export declare function createDurableObjectSqliteExecutor(storage: DurableObjectStorage, { log }?: {
3
+ log?: (level: string, ...args: any[]) => void;
4
+ }): SqliteExecutor;
@@ -0,0 +1,23 @@
1
+ import { SqliteExecutor } from '@verdant-web/server/internals';
2
+ export function createDurableObjectSqliteExecutor(storage, { log } = {}) {
3
+ return new SqliteExecutor({
4
+ close: () => { },
5
+ query: (sql, params = []) => {
6
+ const cursor = storage.sql.exec(sql, ...params);
7
+ const results = [];
8
+ let result = cursor.next();
9
+ while (!result.done) {
10
+ results.push(result.value);
11
+ result = cursor.next();
12
+ }
13
+ return results;
14
+ },
15
+ createTransaction: (exec) => (cb) => storage.transactionSync(() => {
16
+ return cb(exec);
17
+ }),
18
+ exec: (sql, params = []) => {
19
+ storage.sql.exec(sql, ...params);
20
+ },
21
+ }, { log });
22
+ }
23
+ //# sourceMappingURL=sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,UAAU,iCAAiC,CAChD,OAA6B,EAC7B,EAAE,GAAG,KAAwD,EAAE;IAE/D,OAAO,IAAI,cAAc,CACxB;QACC,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;QACf,KAAK,EAAE,CACN,GAAW,EACX,SAA6B,EAAE,EAC9B,EAAE;YACH,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAI,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC;YACnD,MAAM,OAAO,GAAQ,EAAE,CAAC;YACxB,IAAI,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC3B,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;YACxB,CAAC;YACD,OAAO,OAAO,CAAC;QAChB,CAAC;QACD,iBAAiB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CACnC,OAAO,CAAC,eAAe,CAAC,GAAG,EAAE;YAC5B,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC,CAAC;QACH,IAAI,EAAE,CAAC,GAAW,EAAE,SAA6B,EAAE,EAAE,EAAE;YACtD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC;QAClC,CAAC;KACD,EACD,EAAE,GAAG,EAAE,CACP,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@verdant-web/cloudflare",
3
+ "version": "0.1.0",
4
+ "access": "public",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist/esm/index.js",
9
+ "types": "./dist/esm/index.d.ts"
10
+ }
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "files": [
16
+ "dist/",
17
+ "src/",
18
+ "bin/"
19
+ ],
20
+ "peerDependencies": {
21
+ "hono": "^4.7.2",
22
+ "@hono/node-server": "^1.13.8"
23
+ },
24
+ "dependencies": {
25
+ "@verdant-web/server": "4.0.0",
26
+ "@verdant-web/common": "3.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "cross-env": "7.0.3",
30
+ "typescript": "5.9.3",
31
+ "hono": "^4.9.9",
32
+ "@hono/node-server": "^1.19.5",
33
+ "@cloudflare/workers-types": "^4.20250224.0"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc",
37
+ "prepublish": "pnpm run build"
38
+ }
39
+ }
package/src/api.ts ADDED
@@ -0,0 +1,62 @@
1
+ import {
2
+ errorHandler,
3
+ tokenMiddleware,
4
+ TokenVerifier,
5
+ } from '@verdant-web/server/internals';
6
+ import { Hono } from 'hono';
7
+ import { basePath } from 'hono/route';
8
+
9
+ export interface VerdantWorkerConfig {
10
+ durableObjectBindingName: string;
11
+ tokenSecretBindingName: string;
12
+ }
13
+
14
+ /**
15
+ * Creates a Hono app configured to serve Verdant library traffic.
16
+ * This app 'router' should be mounted onto another Verdant app
17
+ * used by your main Cloudflare worker, either as a Hono router
18
+ * or by just calling app.fetch with the request.
19
+ * The Durable Object binding specified by `durableObjectBindingName`
20
+ * must be configured to point to the DurableObjectNamespace for
21
+ * the Verdant library Durable Object class as configured in your
22
+ * wrangler config.
23
+ */
24
+ export function createVerdantWorkerApp(config: VerdantWorkerConfig) {
25
+ // this really just determines which library DO to route to
26
+ // and the DO handles the rest
27
+ const app = new Hono<{ Bindings: any; Variables: any }>()
28
+ .onError(errorHandler)
29
+ .use((ctx, next) => {
30
+ const tokenVerifier = new TokenVerifier({
31
+ secret: ctx.env[config.tokenSecretBindingName],
32
+ });
33
+ return tokenMiddleware(tokenVerifier)(ctx as any, next);
34
+ })
35
+ .all((ctx) => {
36
+ if (ctx.req.method === 'OPTIONS') {
37
+ // preflight request, np
38
+ return ctx.newResponse(null, { status: 204 });
39
+ }
40
+
41
+ const info = ctx.get('tokenInfo');
42
+ const id = ctx.env[config.durableObjectBindingName].idFromName(
43
+ info.libraryId,
44
+ );
45
+ const obj = ctx.env[config.durableObjectBindingName].get(id);
46
+ // remove the base path so the DO sees the correct routing
47
+ const path = basePath(ctx);
48
+ const thisUrl = new URL(ctx.req.url);
49
+ thisUrl.pathname = thisUrl.pathname.replace(path, '') || '/';
50
+ const modifiedReq: Request = new Request(thisUrl.toString(), ctx.req.raw);
51
+ console.log(
52
+ `[verdant worker] routing to DO ${info.libraryId}:`,
53
+ ctx.req.method,
54
+ ctx.req.url,
55
+ path,
56
+ modifiedReq.url,
57
+ );
58
+ return obj.fetch(modifiedReq);
59
+ });
60
+
61
+ return app;
62
+ }
@@ -0,0 +1,346 @@
1
+ import {
2
+ ClientMessage,
3
+ createOid,
4
+ EventSubscriber,
5
+ VerdantError,
6
+ } from '@verdant-web/common';
7
+ import {
8
+ FileInfo,
9
+ FileStorage,
10
+ TokenInfo,
11
+ UserProfiles,
12
+ } from '@verdant-web/server';
13
+ import {
14
+ ClientConnectionManager,
15
+ errorHandler,
16
+ FileStorageLibraryDelegate,
17
+ Library,
18
+ LibraryEvents,
19
+ SqlShardStorage,
20
+ tokenMiddleware,
21
+ TokenVerifier,
22
+ UserProfileLoader,
23
+ } from '@verdant-web/server/internals';
24
+ import { Hono } from 'hono';
25
+ import { createDurableObjectSqliteExecutor } from './sql.js';
26
+
27
+ interface SocketMeta {
28
+ tokenInfo: TokenInfo;
29
+ key: string;
30
+ }
31
+
32
+ export interface DurableObjectLibraryConfig {
33
+ tokenSecret: string;
34
+ disableRebasing?: boolean;
35
+ fileStorage: FileStorage;
36
+ log?: (level: string, ...args: any[]) => void;
37
+ profiles: UserProfiles<any>;
38
+ storageOptions?: {
39
+ fileDeleteExpirationDays?: number;
40
+ replicaTruancyTimeout?: number;
41
+ };
42
+ }
43
+
44
+ export class DurableObjectLibrary {
45
+ #app: Hono<any>;
46
+ #socketInfoCache: WeakMap<WebSocket, SocketMeta> = new WeakMap();
47
+ #initialized = false;
48
+ #config: DurableObjectLibraryConfig;
49
+
50
+ protected clientConnections: ClientConnectionManager;
51
+ protected library!: Library;
52
+ protected events: EventSubscriber<LibraryEvents> = new EventSubscriber();
53
+
54
+ constructor(
55
+ private ctx: DurableObjectState,
56
+ verdant: DurableObjectLibraryConfig,
57
+ ) {
58
+ this.#config = verdant;
59
+
60
+ this.clientConnections = new ClientConnectionManager({
61
+ profiles: new UserProfileLoader(this.#config.profiles),
62
+ });
63
+
64
+ ctx.getWebSockets().forEach((ws) => {
65
+ // restore socket info after hibernation
66
+ const info = ws.deserializeAttachment() as SocketMeta | undefined;
67
+ if (info) {
68
+ this.#socketInfoCache.set(ws, info);
69
+ }
70
+ });
71
+
72
+ this.#app = new Hono()
73
+ .onError(errorHandler)
74
+ .use(async (ctx, next) => {
75
+ console.log(`[verdant object]`, ctx.req.url);
76
+ await next();
77
+ })
78
+ .use(tokenMiddleware(new TokenVerifier({ secret: verdant.tokenSecret })))
79
+ .use(async (ctx, next) => {
80
+ // only after authorization, migrate the database
81
+ await this.initialize(ctx.get('tokenInfo').libraryId);
82
+ return next();
83
+ })
84
+ .post('/', async (ctx) => {
85
+ const info = ctx.get('tokenInfo');
86
+ const body = (await ctx.req.json()) as
87
+ | { messages: ClientMessage[] }
88
+ | null
89
+ | undefined;
90
+ if (!body) {
91
+ throw new VerdantError(
92
+ VerdantError.Code.BodyRequired,
93
+ undefined,
94
+ 'Invalid request body',
95
+ );
96
+ }
97
+ const finish = this.clientConnections.addFetch(
98
+ ctx.get('key'),
99
+ ctx.req.raw,
100
+ info,
101
+ );
102
+ for (const message of body.messages) {
103
+ await this.library.handleMessage(message, ctx.get('key'), info);
104
+ }
105
+ const res = finish();
106
+ return res;
107
+ })
108
+ .post('/files/:fileId', async (ctx) => {
109
+ const info = ctx.get('tokenInfo');
110
+
111
+ const id = ctx.req.param('fileId');
112
+ if (!id) {
113
+ throw new VerdantError(
114
+ VerdantError.Code.NotFound,
115
+ undefined,
116
+ 'Supply a file ID as a path parameters',
117
+ );
118
+ }
119
+
120
+ const form = await ctx.req.parseBody();
121
+ const file = form.file;
122
+ if (!file || !(file instanceof File)) {
123
+ throw new VerdantError(
124
+ VerdantError.Code.InvalidRequest,
125
+ undefined,
126
+ 'File must be provided as form data',
127
+ );
128
+ }
129
+
130
+ const fileInfo: FileInfo = {
131
+ id,
132
+ libraryId: info.libraryId,
133
+ fileName: file.name,
134
+ type: file.type,
135
+ };
136
+ await verdant.fileStorage.put(file.stream(), fileInfo);
137
+ await this.library.putFileInfo(fileInfo);
138
+
139
+ return ctx.json({ success: true });
140
+ })
141
+ .get('/files/:fileId', async (ctx) => {
142
+ const id = ctx.req.param('fileId');
143
+ if (!id) {
144
+ throw new VerdantError(
145
+ VerdantError.Code.NotFound,
146
+ undefined,
147
+ 'Supply a file ID as a path parameters',
148
+ );
149
+ }
150
+
151
+ const fileInfo = await this.library.getFileInfo(id);
152
+ if (!fileInfo) {
153
+ throw new VerdantError(
154
+ VerdantError.Code.NotFound,
155
+ undefined,
156
+ 'File not found',
157
+ );
158
+ }
159
+
160
+ return ctx.json(fileInfo);
161
+ })
162
+ // add socket handler
163
+ .all('*', async (ctx) => {
164
+ this.log('debug', '[verdant object] socket upgrade request');
165
+ // WebSocket endpoint
166
+ const upgradeHeader = ctx.req.header('Upgrade');
167
+ if (upgradeHeader !== 'websocket') {
168
+ return ctx.json({ error: 'Upgrade to WebSocket required' }, 400);
169
+ }
170
+
171
+ const info = ctx.get('tokenInfo');
172
+
173
+ const websocketPair = new WebSocketPair();
174
+ const [client, server] = Object.values(websocketPair);
175
+
176
+ this.ctx.acceptWebSocket(server);
177
+ this.log('debug', 'WebSocket connection established');
178
+
179
+ const meta: SocketMeta = {
180
+ tokenInfo: info,
181
+ key: ctx.get('key'),
182
+ };
183
+ server.serializeAttachment(meta);
184
+ this.#socketInfoCache.set(client, meta);
185
+
186
+ this.clientConnections.addSocket(meta.key, server, info);
187
+
188
+ return new Response(null, {
189
+ status: 101,
190
+ webSocket: client,
191
+ });
192
+ });
193
+
194
+ // set auto-response for pings
195
+ this.ctx.setWebSocketAutoResponse(
196
+ new WebSocketRequestResponsePair(
197
+ JSON.stringify({ type: 'heartbeat' }),
198
+ JSON.stringify({
199
+ type: 'heartbeat-response',
200
+ }),
201
+ ),
202
+ );
203
+ }
204
+
205
+ #noop = () => {};
206
+ get log() {
207
+ return this.#config.log || this.#noop;
208
+ }
209
+
210
+ initialize = async (libraryId: string) => {
211
+ if (this.#initialized) {
212
+ return;
213
+ }
214
+ const storedLibraryId = await this.ctx.storage.get<string>('libraryId');
215
+ if (storedLibraryId && storedLibraryId !== libraryId) {
216
+ throw new VerdantError(
217
+ VerdantError.Code.InvalidRequest,
218
+ undefined,
219
+ 'Library ID does not match existing storage',
220
+ );
221
+ }
222
+ await this.ctx.storage.put('libraryId', libraryId);
223
+ this.#initialized = true;
224
+ const db = createDurableObjectSqliteExecutor(this.ctx.storage, {
225
+ log: this.log,
226
+ });
227
+ await db.migrate();
228
+ const storage = new SqlShardStorage(db, {
229
+ libraryId,
230
+ fileDeleteExpirationDays:
231
+ this.#config.storageOptions?.fileDeleteExpirationDays ?? 14,
232
+ replicaTruancyMinutes:
233
+ this.#config.storageOptions?.replicaTruancyTimeout ?? 14 * 24 * 60,
234
+ });
235
+ this.library = new Library({
236
+ id: libraryId,
237
+ storage,
238
+ sender: this.clientConnections,
239
+ events: this.events,
240
+ disableRebasing: this.#config.disableRebasing,
241
+ fileStorage: new FileStorageLibraryDelegate(
242
+ libraryId,
243
+ this.#config.fileStorage,
244
+ ),
245
+ log: this.log,
246
+ presence: this.clientConnections.presence,
247
+ });
248
+ };
249
+ #restore = async () => {
250
+ if (this.#initialized) {
251
+ return;
252
+ }
253
+ const storedLibraryId = await this.ctx.storage.get<string>('libraryId');
254
+ if (storedLibraryId) {
255
+ await this.initialize(storedLibraryId);
256
+ }
257
+ };
258
+
259
+ fetch = async (request: Request) => {
260
+ return this.#app.fetch(request);
261
+ };
262
+
263
+ #getSocketInfo = (ws: WebSocket) => {
264
+ if (!this.#socketInfoCache.has(ws)) {
265
+ // check for serialized form
266
+ const info = ws.deserializeAttachment() as SocketMeta | undefined;
267
+ if (info) {
268
+ this.#socketInfoCache.set(ws, info);
269
+ return info;
270
+ }
271
+ throw new Error('No user token info associated with socket');
272
+ }
273
+ return this.#socketInfoCache.get(ws)!;
274
+ };
275
+
276
+ webSocketMessage = (
277
+ ws: WebSocket,
278
+ message: string | ArrayBuffer,
279
+ ): void | Promise<void> => {
280
+ const { tokenInfo, key } = this.#getSocketInfo(ws);
281
+ const msg = JSON.parse(message.toString()) as ClientMessage | null;
282
+ if (!msg) {
283
+ throw new VerdantError(
284
+ VerdantError.Code.InvalidRequest,
285
+ undefined,
286
+ 'Invalid message format',
287
+ );
288
+ }
289
+ this.library.handleMessage(msg, key, tokenInfo);
290
+ };
291
+
292
+ webSocketClose = (
293
+ ws: WebSocket,
294
+ code: number,
295
+ reason: string,
296
+ wasClean: boolean,
297
+ ): void | Promise<void> => {
298
+ const { key } = this.#getSocketInfo(ws);
299
+ this.log('info', 'WebSocket closed', { key, code, reason, wasClean });
300
+ this.clientConnections.remove(key);
301
+ this.#socketInfoCache.delete(ws);
302
+ };
303
+
304
+ webSocketError = (ws: WebSocket, error: unknown): void | Promise<void> => {
305
+ this.log('error', 'WebSocket error', error);
306
+ const { key } = this.#getSocketInfo(ws);
307
+ this.clientConnections.remove(key);
308
+ this.#socketInfoCache.delete(ws);
309
+ };
310
+
311
+ handleMessage = async (
312
+ key: string,
313
+ info: TokenInfo,
314
+ message: ClientMessage,
315
+ ) => {
316
+ await this.#restore();
317
+ return this.library.handleMessage(message, key, info);
318
+ };
319
+
320
+ // public library API
321
+ getDocumentSnapshot = async (collection: string, id: string) => {
322
+ await this.#restore();
323
+ return this.library.getDocumentSnapshot(
324
+ createOid(collection, id),
325
+ ) as Promise<{} | null>;
326
+ };
327
+ getFileInfo = async (fileId: string) => {
328
+ await this.#restore();
329
+ return this.library.getFileInfo(fileId);
330
+ };
331
+ evict = async () => {
332
+ await this.#restore();
333
+ await this.clientConnections.disconnectAll();
334
+ await this.library?.destroy();
335
+ await this.ctx.storage.deleteAll();
336
+ this.#initialized = false;
337
+ };
338
+ forceTruant = async (replicaId: string) => {
339
+ await this.#restore();
340
+ return this.library.forceTruant(replicaId);
341
+ };
342
+ getInfo = async () => {
343
+ await this.#restore();
344
+ return this.library.getInfo();
345
+ };
346
+ }
package/src/files.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { FileInfo, FileStorage } from '@verdant-web/server';
2
+ import { Readable } from 'stream';
3
+
4
+ export class R2FileStorage implements FileStorage {
5
+ private bucket: R2Bucket;
6
+ private host: string;
7
+
8
+ constructor({ host, bucket }: { host: string; bucket: R2Bucket }) {
9
+ this.bucket = bucket;
10
+ this.host = host;
11
+ }
12
+ private getDirectory = (data: FileInfo) => {
13
+ return `${data.libraryId}/${data.id}`;
14
+ };
15
+ private getPath = (data: FileInfo) => {
16
+ return `${this.getDirectory(data)}/${data.fileName}`;
17
+ };
18
+ put = async (
19
+ fileStream: Readable | ReadableStream,
20
+ data: FileInfo,
21
+ ): Promise<void> => {
22
+ const webStream =
23
+ fileStream instanceof ReadableStream
24
+ ? fileStream
25
+ : Readable.toWeb(fileStream);
26
+ await this.bucket.put(this.getPath(data), webStream as any, {
27
+ httpMetadata: {
28
+ contentType: data.type,
29
+ },
30
+ customMetadata: {
31
+ libraryId: data.libraryId,
32
+ fileId: data.id,
33
+ fileName: data.fileName,
34
+ },
35
+ });
36
+ };
37
+ getUrl = async (data: FileInfo): Promise<string> => {
38
+ return this.host + '/' + this.getPath(data);
39
+ };
40
+ delete = async (data: FileInfo): Promise<void> => {
41
+ await this.bucket.delete(this.getPath(data));
42
+ };
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { createVerdantWorkerApp } from './api.js';
2
+ export { DurableObjectLibrary } from './doLibrary.js';
3
+ export { R2FileStorage } from './files.js';
package/src/sql.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { SqliteExecutor } from '@verdant-web/server/internals';
2
+
3
+ export function createDurableObjectSqliteExecutor(
4
+ storage: DurableObjectStorage,
5
+ { log }: { log?: (level: string, ...args: any[]) => void } = {},
6
+ ): SqliteExecutor {
7
+ return new SqliteExecutor(
8
+ {
9
+ close: () => {},
10
+ query: <O extends Record<string, any>>(
11
+ sql: string,
12
+ params: readonly unknown[] = [],
13
+ ) => {
14
+ const cursor = storage.sql.exec<O>(sql, ...params);
15
+ const results: O[] = [];
16
+ let result = cursor.next();
17
+ while (!result.done) {
18
+ results.push(result.value);
19
+ result = cursor.next();
20
+ }
21
+ return results;
22
+ },
23
+ createTransaction: (exec) => (cb) =>
24
+ storage.transactionSync(() => {
25
+ return cb(exec);
26
+ }),
27
+ exec: (sql: string, params: readonly unknown[] = []) => {
28
+ storage.sql.exec(sql, ...params);
29
+ },
30
+ },
31
+ { log },
32
+ );
33
+ }