|
9 | 9 |
|
10 | 10 | import type { D1Database } from "@cloudflare/workers-types"; |
11 | 11 | import { WorkerEntrypoint } from "cloudflare:workers"; |
12 | | -import type { ContentCreateOptions, Database, I18nConfig, SandboxEmailSendCallback } from "emdash"; |
| 12 | +import type { |
| 13 | + ConditionalDeleteResult, |
| 14 | + ConditionalWriteResult, |
| 15 | + ContentCreateOptions, |
| 16 | + Database, |
| 17 | + I18nConfig, |
| 18 | + SandboxEmailSendCallback, |
| 19 | + VersionedValue, |
| 20 | +} from "emdash"; |
13 | 21 | import { |
14 | 22 | ContentRepository, |
15 | 23 | createSandboxRouteError, |
@@ -291,12 +299,31 @@ export class PluginBridge extends WorkerEntrypoint<PluginBridgeEnv, PluginBridge |
291 | 299 | async kvSet(key: string, value: unknown): Promise<void> { |
292 | 300 | const { pluginId } = this.ctx.props; |
293 | 301 | await this.env.DB.prepare( |
294 | | - "INSERT OR REPLACE INTO _plugin_storage (plugin_id, collection, id, data, updated_at) VALUES (?, '__kv', ?, ?, datetime('now'))", |
| 302 | + "INSERT OR REPLACE INTO _plugin_storage (plugin_id, collection, id, data, revision, updated_at) VALUES (?, '__kv', ?, ?, ?, datetime('now'))", |
295 | 303 | ) |
296 | | - .bind(pluginId, key, JSON.stringify(value)) |
| 304 | + .bind(pluginId, key, JSON.stringify(value), crypto.randomUUID()) |
297 | 305 | .run(); |
298 | 306 | } |
299 | 307 |
|
| 308 | + async kvGetVersioned(key: string): Promise<VersionedValue | null> { |
| 309 | + return this.getStorageRepo("__kv").getVersioned(key); |
| 310 | + } |
| 311 | + |
| 312 | + async kvCompareAndSet( |
| 313 | + key: string, |
| 314 | + expectedRevision: string | null, |
| 315 | + value: unknown, |
| 316 | + ): Promise<ConditionalWriteResult> { |
| 317 | + return this.getStorageRepo("__kv").compareAndSet(key, expectedRevision, value); |
| 318 | + } |
| 319 | + |
| 320 | + async kvCompareAndDelete( |
| 321 | + key: string, |
| 322 | + expectedRevision: string, |
| 323 | + ): Promise<ConditionalDeleteResult> { |
| 324 | + return this.getStorageRepo("__kv").compareAndDelete(key, expectedRevision); |
| 325 | + } |
| 326 | + |
300 | 327 | async kvDelete(key: string): Promise<boolean> { |
301 | 328 | const { pluginId } = this.ctx.props; |
302 | 329 | const result = await this.env.DB.prepare( |
@@ -345,12 +372,42 @@ export class PluginBridge extends WorkerEntrypoint<PluginBridgeEnv, PluginBridge |
345 | 372 | throw new Error(`Storage collection not declared: ${collection}`); |
346 | 373 | } |
347 | 374 | await this.env.DB.prepare( |
348 | | - "INSERT OR REPLACE INTO _plugin_storage (plugin_id, collection, id, data, updated_at) VALUES (?, ?, ?, ?, datetime('now'))", |
| 375 | + "INSERT OR REPLACE INTO _plugin_storage (plugin_id, collection, id, data, revision, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'))", |
349 | 376 | ) |
350 | | - .bind(pluginId, collection, id, JSON.stringify(data)) |
| 377 | + .bind(pluginId, collection, id, JSON.stringify(data), crypto.randomUUID()) |
351 | 378 | .run(); |
352 | 379 | } |
353 | 380 |
|
| 381 | + async storageGetVersioned(collection: string, id: string): Promise<VersionedValue | null> { |
| 382 | + if (!this.ctx.props.storageCollections.includes(collection)) { |
| 383 | + throw new Error(`Storage collection not declared: ${collection}`); |
| 384 | + } |
| 385 | + return this.getStorageRepo(collection).getVersioned(id); |
| 386 | + } |
| 387 | + |
| 388 | + async storageCompareAndSet( |
| 389 | + collection: string, |
| 390 | + id: string, |
| 391 | + expectedRevision: string | null, |
| 392 | + data: unknown, |
| 393 | + ): Promise<ConditionalWriteResult> { |
| 394 | + if (!this.ctx.props.storageCollections.includes(collection)) { |
| 395 | + throw new Error(`Storage collection not declared: ${collection}`); |
| 396 | + } |
| 397 | + return this.getStorageRepo(collection).compareAndSet(id, expectedRevision, data); |
| 398 | + } |
| 399 | + |
| 400 | + async storageCompareAndDelete( |
| 401 | + collection: string, |
| 402 | + id: string, |
| 403 | + expectedRevision: string, |
| 404 | + ): Promise<ConditionalDeleteResult> { |
| 405 | + if (!this.ctx.props.storageCollections.includes(collection)) { |
| 406 | + throw new Error(`Storage collection not declared: ${collection}`); |
| 407 | + } |
| 408 | + return this.getStorageRepo(collection).compareAndDelete(id, expectedRevision); |
| 409 | + } |
| 410 | + |
354 | 411 | async storageUpdateIf( |
355 | 412 | collection: string, |
356 | 413 | id: string, |
@@ -465,13 +522,11 @@ export class PluginBridge extends WorkerEntrypoint<PluginBridgeEnv, PluginBridge |
465 | 522 | } |
466 | 523 | if (items.length === 0) return; |
467 | 524 |
|
468 | | - // D1 doesn't support batch in prepare, so we do individual inserts |
469 | | - // In future, we could use batch API |
470 | 525 | for (const item of items) { |
471 | 526 | await this.env.DB.prepare( |
472 | | - "INSERT OR REPLACE INTO _plugin_storage (plugin_id, collection, id, data, updated_at) VALUES (?, ?, ?, ?, datetime('now'))", |
| 527 | + "INSERT OR REPLACE INTO _plugin_storage (plugin_id, collection, id, data, revision, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'))", |
473 | 528 | ) |
474 | | - .bind(pluginId, collection, item.id, JSON.stringify(item.data)) |
| 529 | + .bind(pluginId, collection, item.id, JSON.stringify(item.data), crypto.randomUUID()) |
475 | 530 | .run(); |
476 | 531 | } |
477 | 532 | } |
|
0 commit comments