@statewalker/cellb 0.0.0-stage → 0.1.1
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 +21 -0
- package/README.md +323 -2
- package/dist/adapters/files-backup.d.ts +16 -0
- package/dist/adapters/files-backup.js +32 -0
- package/dist/adapters/files-backup.js.map +1 -0
- package/dist/adapters/files-changelog.d.ts +33 -0
- package/dist/adapters/files-changelog.js +96 -0
- package/dist/adapters/files-changelog.js.map +1 -0
- package/dist/adapters/files-ltx.d.ts +35 -0
- package/dist/adapters/files-ltx.js +9 -0
- package/dist/adapters/files-ltx.js.map +1 -0
- package/dist/adapters/ltx-vfs.d.ts +21 -0
- package/dist/adapters/ltx-vfs.js +286 -0
- package/dist/adapters/ltx-vfs.js.map +1 -0
- package/dist/adapters/node-sqlite.d.ts +45 -0
- package/dist/adapters/node-sqlite.js +254 -0
- package/dist/adapters/node-sqlite.js.map +1 -0
- package/dist/adapters/wasm-page-hook.d.ts +26 -0
- package/dist/adapters/wasm-page-hook.js +55 -0
- package/dist/adapters/wasm-page-hook.js.map +1 -0
- package/dist/adapters/wasm-session.d.ts +11 -0
- package/dist/adapters/wasm-session.js +76 -0
- package/dist/adapters/wasm-session.js.map +1 -0
- package/dist/adapters/wasm-sqlite.d.ts +89 -0
- package/dist/adapters/wasm-sqlite.js +258 -0
- package/dist/adapters/wasm-sqlite.js.map +1 -0
- package/dist/bindings/assets.d.ts +26 -0
- package/dist/bindings/assets.js +260 -0
- package/dist/bindings/assets.js.map +1 -0
- package/dist/bindings/cache.d.ts +16 -0
- package/dist/bindings/cache.js +34 -0
- package/dist/bindings/cache.js.map +1 -0
- package/dist/bindings/celld-kv.d.ts +26 -0
- package/dist/bindings/celld-kv.js +81 -0
- package/dist/bindings/celld-kv.js.map +1 -0
- package/dist/bindings/celld-queues.d.ts +56 -0
- package/dist/bindings/celld-queues.js +223 -0
- package/dist/bindings/celld-queues.js.map +1 -0
- package/dist/bindings/celld-workflows.d.ts +22 -0
- package/dist/bindings/celld-workflows.js +54 -0
- package/dist/bindings/celld-workflows.js.map +1 -0
- package/dist/bindings/cron.d.ts +27 -0
- package/dist/bindings/cron.js +232 -0
- package/dist/bindings/cron.js.map +1 -0
- package/dist/bindings/d1-migrations.d.ts +22 -0
- package/dist/bindings/d1-migrations.js +32 -0
- package/dist/bindings/d1-migrations.js.map +1 -0
- package/dist/bindings/html-rewriter.d.ts +8 -0
- package/dist/bindings/html-rewriter.js +75 -0
- package/dist/bindings/html-rewriter.js.map +1 -0
- package/dist/bindings/r2-celld.d.ts +3 -0
- package/dist/bindings/r2-celld.js +113 -0
- package/dist/bindings/r2-celld.js.map +1 -0
- package/dist/bindings/r2.d.ts +236 -0
- package/dist/bindings/r2.js +419 -0
- package/dist/bindings/r2.js.map +1 -0
- package/dist/bindings/worker-loader.d.ts +83 -0
- package/dist/bindings/worker-loader.js +183 -0
- package/dist/bindings/worker-loader.js.map +1 -0
- package/dist/bindings/workers-ai.d.ts +39 -0
- package/dist/bindings/workers-ai.js +175 -0
- package/dist/bindings/workers-ai.js.map +1 -0
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +8 -0
- package/dist/browser.js.map +1 -0
- package/dist/bucket/celld-cells.d.ts +86 -0
- package/dist/bucket/celld-cells.js +273 -0
- package/dist/bucket/celld-cells.js.map +1 -0
- package/dist/bucket/celld-format.d.ts +10 -0
- package/dist/bucket/celld-format.js +27 -0
- package/dist/bucket/celld-format.js.map +1 -0
- package/dist/bucket/idb-object-store.d.ts +9 -0
- package/dist/bucket/idb-object-store.js +73 -0
- package/dist/bucket/idb-object-store.js.map +1 -0
- package/dist/bucket/ltx-log.d.ts +34 -0
- package/dist/bucket/ltx-log.js +289 -0
- package/dist/bucket/ltx-log.js.map +1 -0
- package/dist/bucket/object-store.d.ts +51 -0
- package/dist/bucket/object-store.js +114 -0
- package/dist/bucket/object-store.js.map +1 -0
- package/dist/bucket/s3-object-store.d.ts +25 -0
- package/dist/bucket/s3-object-store.js +151 -0
- package/dist/bucket/s3-object-store.js.map +1 -0
- package/dist/bucket/sqlite-object-store.d.ts +10 -0
- package/dist/bucket/sqlite-object-store.js +107 -0
- package/dist/bucket/sqlite-object-store.js.map +1 -0
- package/dist/core/alarms.d.ts +27 -0
- package/dist/core/alarms.js +88 -0
- package/dist/core/alarms.js.map +1 -0
- package/dist/core/async-context.d.ts +34 -0
- package/dist/core/async-context.js +50 -0
- package/dist/core/async-context.js.map +1 -0
- package/dist/core/celld-schema.d.ts +14 -0
- package/dist/core/celld-schema.js +34 -0
- package/dist/core/celld-schema.js.map +1 -0
- package/dist/core/durable-object.d.ts +28 -0
- package/dist/core/durable-object.js +31 -0
- package/dist/core/durable-object.js.map +1 -0
- package/dist/core/gates.d.ts +33 -0
- package/dist/core/gates.js +89 -0
- package/dist/core/gates.js.map +1 -0
- package/dist/core/handles.d.ts +90 -0
- package/dist/core/handles.js +224 -0
- package/dist/core/handles.js.map +1 -0
- package/dist/core/id.d.ts +19 -0
- package/dist/core/id.js +64 -0
- package/dist/core/id.js.map +1 -0
- package/dist/core/namespace.d.ts +38 -0
- package/dist/core/namespace.js +141 -0
- package/dist/core/namespace.js.map +1 -0
- package/dist/core/object-host.d.ts +91 -0
- package/dist/core/object-host.js +616 -0
- package/dist/core/object-host.js.map +1 -0
- package/dist/core/request-body.d.ts +9 -0
- package/dist/core/request-body.js +55 -0
- package/dist/core/request-body.js.map +1 -0
- package/dist/core/runtime.d.ts +121 -0
- package/dist/core/runtime.js +366 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/shard.d.ts +7 -0
- package/dist/core/shard.js +10 -0
- package/dist/core/shard.js.map +1 -0
- package/dist/core/sql-split.d.ts +15 -0
- package/dist/core/sql-split.js +101 -0
- package/dist/core/sql-split.js.map +1 -0
- package/dist/core/sql-storage.d.ts +53 -0
- package/dist/core/sql-storage.js +407 -0
- package/dist/core/sql-storage.js.map +1 -0
- package/dist/core/state.d.ts +44 -0
- package/dist/core/state.js +93 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/storage.d.ts +103 -0
- package/dist/core/storage.js +351 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/worker-rejections.d.ts +21 -0
- package/dist/core/worker-rejections.js +55 -0
- package/dist/core/worker-rejections.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/isolate/browser.d.ts +16 -0
- package/dist/isolate/browser.js +37 -0
- package/dist/isolate/browser.js.map +1 -0
- package/dist/isolate/host.d.ts +24 -0
- package/dist/isolate/host.js +74 -0
- package/dist/isolate/host.js.map +1 -0
- package/dist/isolate/isolate-worker.d.ts +1 -0
- package/dist/isolate/isolate-worker.js +53 -0
- package/dist/isolate/isolate-worker.js.map +1 -0
- package/dist/isolate/isolate.d.ts +10 -0
- package/dist/isolate/isolate.js +169 -0
- package/dist/isolate/isolate.js.map +1 -0
- package/dist/isolate/node-entry.d.ts +1 -0
- package/dist/isolate/node-entry.js +12 -0
- package/dist/isolate/node-entry.js.map +1 -0
- package/dist/isolate/protocol.d.ts +66 -0
- package/dist/isolate/protocol.js +205 -0
- package/dist/isolate/protocol.js.map +1 -0
- package/dist/ltx/crc64.d.ts +6 -0
- package/dist/ltx/crc64.js +46 -0
- package/dist/ltx/crc64.js.map +1 -0
- package/dist/ltx/ltx-page-server.d.ts +65 -0
- package/dist/ltx/ltx-page-server.js +309 -0
- package/dist/ltx/ltx-page-server.js.map +1 -0
- package/dist/ltx/ltx-pages.d.ts +81 -0
- package/dist/ltx/ltx-pages.js +315 -0
- package/dist/ltx/ltx-pages.js.map +1 -0
- package/dist/ltx/ltx-store-worker.d.ts +1 -0
- package/dist/ltx/ltx-store-worker.js +38 -0
- package/dist/ltx/ltx-store-worker.js.map +1 -0
- package/dist/ltx/ltx.d.ts +45 -0
- package/dist/ltx/ltx.js +203 -0
- package/dist/ltx/ltx.js.map +1 -0
- package/dist/ltx/lz4.d.ts +9 -0
- package/dist/ltx/lz4.js +156 -0
- package/dist/ltx/lz4.js.map +1 -0
- package/dist/ltx/opfs-sync-files.d.ts +23 -0
- package/dist/ltx/opfs-sync-files.js +191 -0
- package/dist/ltx/opfs-sync-files.js.map +1 -0
- package/dist/ltx/xxh32.d.ts +1 -0
- package/dist/ltx/xxh32.js +43 -0
- package/dist/ltx/xxh32.js.map +1 -0
- package/dist/miniflare-compat/blob-service.d.ts +13 -0
- package/dist/miniflare-compat/blob-service.js +66 -0
- package/dist/miniflare-compat/blob-service.js.map +1 -0
- package/dist/miniflare-compat/kv-binding.d.ts +44 -0
- package/dist/miniflare-compat/kv-binding.js +99 -0
- package/dist/miniflare-compat/kv-binding.js.map +1 -0
- package/dist/miniflare-compat/shims/assert.d.ts +15 -0
- package/dist/miniflare-compat/shims/assert.js +24 -0
- package/dist/miniflare-compat/shims/assert.js.map +1 -0
- package/dist/miniflare-compat/shims/workerd-cloudflare.d.ts +7 -0
- package/dist/miniflare-compat/shims/workerd-cloudflare.js +6 -0
- package/dist/miniflare-compat/shims/workerd-cloudflare.js.map +1 -0
- package/dist/miniflare-compat/shims/workerd-globals.d.ts +1 -0
- package/dist/miniflare-compat/shims/workerd-globals.js +30 -0
- package/dist/miniflare-compat/shims/workerd-globals.js.map +1 -0
- package/dist/miniflare-compat/shims/workerd-internal.d.ts +20 -0
- package/dist/miniflare-compat/shims/workerd-internal.js +23 -0
- package/dist/miniflare-compat/shims/workerd-internal.js.map +1 -0
- package/dist/miniflare-compat/shims/workerd-performance.d.ts +4 -0
- package/dist/miniflare-compat/shims/workerd-performance.js +17 -0
- package/dist/miniflare-compat/shims/workerd-performance.js.map +1 -0
- package/dist/miniflare-compat/simulator-fetcher.d.ts +9 -0
- package/dist/miniflare-compat/simulator-fetcher.js +14 -0
- package/dist/miniflare-compat/simulator-fetcher.js.map +1 -0
- package/dist/miniflare-compat/vendored.d.ts +15 -0
- package/dist/miniflare-compat/vendored.js +27 -0
- package/dist/miniflare-compat/vendored.js.map +1 -0
- package/dist/node-compat/async_hooks.d.ts +22 -0
- package/dist/node-compat/async_hooks.js +176 -0
- package/dist/node-compat/async_hooks.js.map +1 -0
- package/dist/node-compat/browser-registry.d.ts +3 -0
- package/dist/node-compat/browser-registry.js +39 -0
- package/dist/node-compat/browser-registry.js.map +1 -0
- package/dist/node-compat/crypto.d.ts +60 -0
- package/dist/node-compat/crypto.js +143 -0
- package/dist/node-compat/crypto.js.map +1 -0
- package/dist/node-compat/diagnostics_channel.d.ts +27 -0
- package/dist/node-compat/diagnostics_channel.js +46 -0
- package/dist/node-compat/diagnostics_channel.js.map +1 -0
- package/dist/node-compat/fs.d.ts +35 -0
- package/dist/node-compat/fs.js +40 -0
- package/dist/node-compat/fs.js.map +1 -0
- package/dist/node-compat/globals.d.ts +3 -0
- package/dist/node-compat/globals.js +4 -0
- package/dist/node-compat/globals.js.map +1 -0
- package/dist/node-compat/install.d.ts +5 -0
- package/dist/node-compat/install.js +12 -0
- package/dist/node-compat/install.js.map +1 -0
- package/dist/node-compat/module.d.ts +12 -0
- package/dist/node-compat/module.js +36 -0
- package/dist/node-compat/module.js.map +1 -0
- package/dist/node-compat/modules.d.ts +8 -0
- package/dist/node-compat/modules.js +23 -0
- package/dist/node-compat/modules.js.map +1 -0
- package/dist/node-compat/os.d.ts +36 -0
- package/dist/node-compat/os.js +36 -0
- package/dist/node-compat/os.js.map +1 -0
- package/dist/node-compat/process.d.ts +14 -0
- package/dist/node-compat/process.js +16 -0
- package/dist/node-compat/process.js.map +1 -0
- package/dist/node-compat/shims.d.ts +4 -0
- package/dist/node.d.ts +5 -0
- package/dist/node.js +6 -0
- package/dist/node.js.map +1 -0
- package/dist/peer/celld-peer.d.ts +53 -0
- package/dist/peer/celld-peer.js +555 -0
- package/dist/peer/celld-peer.js.map +1 -0
- package/dist/ports.d.ts +183 -0
- package/dist/ports.js +10 -0
- package/dist/ports.js.map +1 -0
- package/dist/project/browser-bundle.d.ts +17 -0
- package/dist/project/browser-bundle.js +125 -0
- package/dist/project/browser-bundle.js.map +1 -0
- package/dist/project/browser-cookies.d.ts +12 -0
- package/dist/project/browser-cookies.js +90 -0
- package/dist/project/browser-cookies.js.map +1 -0
- package/dist/project/browser-host.d.ts +173 -0
- package/dist/project/browser-host.js +200 -0
- package/dist/project/browser-host.js.map +1 -0
- package/dist/project/browser-modules.d.ts +17 -0
- package/dist/project/browser-modules.js +89 -0
- package/dist/project/browser-modules.js.map +1 -0
- package/dist/project/browser-shards.d.ts +62 -0
- package/dist/project/browser-shards.js +210 -0
- package/dist/project/browser-shards.js.map +1 -0
- package/dist/project/browser-tabs.d.ts +103 -0
- package/dist/project/browser-tabs.js +418 -0
- package/dist/project/browser-tabs.js.map +1 -0
- package/dist/project/cloudflare-email.d.ts +8 -0
- package/dist/project/cloudflare-email.js +11 -0
- package/dist/project/cloudflare-email.js.map +1 -0
- package/dist/project/cloudflare-workers.d.ts +12 -0
- package/dist/project/cloudflare-workers.js +16 -0
- package/dist/project/cloudflare-workers.js.map +1 -0
- package/dist/project/host-modules.d.ts +4 -0
- package/dist/project/host-modules.js +40 -0
- package/dist/project/host-modules.js.map +1 -0
- package/dist/project/jsonc.d.ts +2 -0
- package/dist/project/jsonc.js +27 -0
- package/dist/project/jsonc.js.map +1 -0
- package/dist/project/lower-await.d.ts +2 -0
- package/dist/project/lower-await.js +151 -0
- package/dist/project/lower-await.js.map +1 -0
- package/dist/project/node-project.d.ts +38 -0
- package/dist/project/node-project.js +373 -0
- package/dist/project/node-project.js.map +1 -0
- package/dist/project/own-module.d.ts +6 -0
- package/dist/project/own-module.js +13 -0
- package/dist/project/own-module.js.map +1 -0
- package/dist/project/scheduler.d.ts +2 -0
- package/dist/project/scheduler.js +3 -0
- package/dist/project/scheduler.js.map +1 -0
- package/dist/project/site.d.ts +74 -0
- package/dist/project/site.js +206 -0
- package/dist/project/site.js.map +1 -0
- package/dist/project/virtual-modules.d.ts +3 -0
- package/dist/project/worker-scope.d.ts +27 -0
- package/dist/project/worker-scope.js +46 -0
- package/dist/project/worker-scope.js.map +1 -0
- package/dist/project/workerd-message-channel.d.ts +13 -0
- package/dist/project/workerd-message-channel.js +137 -0
- package/dist/project/workerd-message-channel.js.map +1 -0
- package/dist/project/workers-global-names.d.ts +6 -0
- package/dist/project/workers-global-names.js +15 -0
- package/dist/project/workers-global-names.js.map +1 -0
- package/dist/project/workers-globals-module.d.ts +8 -0
- package/dist/project/workers-globals-module.js +9 -0
- package/dist/project/workers-globals-module.js.map +1 -0
- package/dist/project/workers-globals.d.ts +9 -0
- package/dist/project/workers-globals.js +19 -0
- package/dist/project/workers-globals.js.map +1 -0
- package/dist/project/wrangler.d.ts +116 -0
- package/dist/project/wrangler.js +342 -0
- package/dist/project/wrangler.js.map +1 -0
- package/dist/router/cell-router.d.ts +72 -0
- package/dist/router/cell-router.js +247 -0
- package/dist/router/cell-router.js.map +1 -0
- package/dist/router/shard-worker.d.ts +44 -0
- package/dist/router/shard-worker.js +163 -0
- package/dist/router/shard-worker.js.map +1 -0
- package/dist/transport/cells-http.d.ts +25 -0
- package/dist/transport/cells-http.js +141 -0
- package/dist/transport/cells-http.js.map +1 -0
- package/dist/transport/cells-link.d.ts +27 -0
- package/dist/transport/cells-link.js +71 -0
- package/dist/transport/cells-link.js.map +1 -0
- package/dist/transport/port.d.ts +26 -0
- package/dist/transport/port.js +100 -0
- package/dist/transport/port.js.map +1 -0
- package/dist/v8/codec.d.ts +6 -0
- package/dist/v8/codec.js +9 -0
- package/dist/v8/codec.js.map +1 -0
- package/dist/v8/serialize.d.ts +10 -0
- package/dist/v8/serialize.js +601 -0
- package/dist/v8/serialize.js.map +1 -0
- package/dist/ws/client.d.ts +25 -0
- package/dist/ws/client.js +203 -0
- package/dist/ws/client.js.map +1 -0
- package/dist/ws/control.d.ts +48 -0
- package/dist/ws/control.js +44 -0
- package/dist/ws/control.js.map +1 -0
- package/dist/ws/frames.d.ts +38 -0
- package/dist/ws/frames.js +39 -0
- package/dist/ws/frames.js.map +1 -0
- package/dist/ws/server.d.ts +99 -0
- package/dist/ws/server.js +295 -0
- package/dist/ws/server.js.map +1 -0
- package/dist/ws/sw-connector.d.ts +10 -0
- package/dist/ws/sw-connector.js +25 -0
- package/dist/ws/sw-connector.js.map +1 -0
- package/dist/ws/upgrade.d.ts +28 -0
- package/dist/ws/upgrade.js +75 -0
- package/dist/ws/upgrade.js.map +1 -0
- package/package.json +111 -3
- package/vendor/celld/LICENSE +202 -0
- package/vendor/celld/kv.js +1131 -0
- package/vendor/celld/queues.js +1656 -0
- package/vendor/celld/workflows.js +1610 -0
- package/vendor/html-rewriter-wasm/html_rewriter.js +925 -0
- package/vendor/html-rewriter-wasm/html_rewriter_bg.wasm +0 -0
- package/vendor/miniflare/d1-api.js +738 -0
- package/vendor/miniflare/d1-database.js +6978 -0
- package/vendor/miniflare/kv-namespace.js +3061 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
2
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
3
|
+
import { CELLD_SCHEMA, LOG_SEQ_READ, LOG_SEQ_WRITE, LOG_TABLE, scopeOf } from "./celld-schema.js";
|
|
4
|
+
import { InputGate, OutputGate } from "./gates.js";
|
|
5
|
+
import { asyncStub, isClassHandle, } from "./handles.js";
|
|
6
|
+
import { DurableObjectId as DurableObjectIdClass } from "./id.js";
|
|
7
|
+
import { SqlStorage } from "./sql-storage.js";
|
|
8
|
+
import { DurableObjectState } from "./state.js";
|
|
9
|
+
import { DurableObjectStorage, SyncKvStorage } from "./storage.js";
|
|
10
|
+
export class BrokenObjectError extends Error {
|
|
11
|
+
constructor(cause, reason = "its storage failed to commit durably") {
|
|
12
|
+
const detail = cause instanceof Error ? `: ${cause.message}` : "";
|
|
13
|
+
super(`Durable Object reset because ${reason}${detail}`, { cause });
|
|
14
|
+
this.name = "BrokenObjectError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* One live instance of one Durable Object: its SQLite, gates and dispatcher.
|
|
19
|
+
* Everything that reaches the object (fetch, RPC, later alarms/WebSockets)
|
|
20
|
+
* goes through `deliver()`.
|
|
21
|
+
*/
|
|
22
|
+
export class ObjectHost {
|
|
23
|
+
cellKey;
|
|
24
|
+
#gate = new InputGate();
|
|
25
|
+
#out = new OutputGate();
|
|
26
|
+
/** The open database; `#driver` follows it, so it can be closed while idle and reopened. */
|
|
27
|
+
#db;
|
|
28
|
+
#driver = new Proxy({}, {
|
|
29
|
+
get: (_t, k) => {
|
|
30
|
+
const v = this.#db[k];
|
|
31
|
+
return typeof v === "function" ? v.bind(this.#db) : v;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
#sql;
|
|
35
|
+
#instance;
|
|
36
|
+
#backup;
|
|
37
|
+
#backedUpSeq = 0;
|
|
38
|
+
#saving;
|
|
39
|
+
state;
|
|
40
|
+
#seq = 0;
|
|
41
|
+
broken = false;
|
|
42
|
+
#brokenCause;
|
|
43
|
+
/** blockConcurrencyWhile() rejected (CF: the object is reset; queued events fail). */
|
|
44
|
+
#breakOn(e) {
|
|
45
|
+
this.broken = true;
|
|
46
|
+
this.#brokenCause = e;
|
|
47
|
+
}
|
|
48
|
+
constructor(opts) {
|
|
49
|
+
this.#backup = opts.backup;
|
|
50
|
+
this.cellKey = opts.cellKey;
|
|
51
|
+
this.#epoch = opts.epoch;
|
|
52
|
+
this.#opts = opts;
|
|
53
|
+
this.#open = () => {
|
|
54
|
+
this.#db = opts.openSql(opts.cellKey);
|
|
55
|
+
for (const ext of opts.sqlExtensions ?? [])
|
|
56
|
+
this.#db.loadExtension(ext);
|
|
57
|
+
};
|
|
58
|
+
this.#open();
|
|
59
|
+
this.#sql = new SqlStorage(this.#driver, (changes) => {
|
|
60
|
+
if (changes && this.#epoch !== undefined)
|
|
61
|
+
changes.epoch = this.#epoch;
|
|
62
|
+
// Page-level logs (LTX): the changed pages straight from the page hook when the driver
|
|
63
|
+
// has one; the image only on demand (a generation's first snapshot, or no hook).
|
|
64
|
+
const d = this.#driver;
|
|
65
|
+
const seq = ++this.#seq;
|
|
66
|
+
const pages = opts.durability.capturePages ? d.takeDirtyPages() : undefined;
|
|
67
|
+
const snap = () => d.pageImage?.() ?? d.image?.();
|
|
68
|
+
// The log runs later, after the next event may have opened its transaction, and an
|
|
69
|
+
// image cannot be taken inside one (VACUUM INTO). When the log will surely need it
|
|
70
|
+
// (no page hook) or likely does (this host's first commit), take it now.
|
|
71
|
+
const image = opts.durability.capturePages
|
|
72
|
+
? !pages || seq === 1
|
|
73
|
+
? snap()
|
|
74
|
+
: snap
|
|
75
|
+
: undefined;
|
|
76
|
+
const durable = opts.durability.committed(this.cellKey, {
|
|
77
|
+
seq,
|
|
78
|
+
changes,
|
|
79
|
+
image,
|
|
80
|
+
pages,
|
|
81
|
+
epoch: this.#epoch,
|
|
82
|
+
});
|
|
83
|
+
const wake = this.#wakeAfterCommit();
|
|
84
|
+
return this.#out.track(wake ? durable.then(wake) : durable);
|
|
85
|
+
}, { capture: !!opts.durability.captureChanges });
|
|
86
|
+
// The cell's scope in celld's tables ("Class:idhex"), so its database is celld's format.
|
|
87
|
+
const kv = new SyncKvStorage(this.#sql, opts.codec, scopeOf(opts.cellKey));
|
|
88
|
+
if (!opts.facet)
|
|
89
|
+
kv.actorName = opts.id.name;
|
|
90
|
+
const storage = new DurableObjectStorage(this.#sql, kv, this.#gate, () => this.#barrier());
|
|
91
|
+
if (opts.onAlarmChange)
|
|
92
|
+
storage.onAlarmChange = opts.onAlarmChange;
|
|
93
|
+
storage.deleteAllPreservesAlarm = !!opts.deleteAllPreservesAlarm;
|
|
94
|
+
const state = new DurableObjectState(opts.id, storage, this.#gate, opts.waitUntil, (e) => this.#breakOn(e));
|
|
95
|
+
this.state = state;
|
|
96
|
+
state.attachDispatcher((fn) => this.deliver(fn));
|
|
97
|
+
// ctx.abort(reason) (workerd, celld): the object resets — a fresh instance on the same
|
|
98
|
+
// database serves the next event; the calling event fails with the reason. Writes the event
|
|
99
|
+
// already made still commit (celld: celld's workflow cell aborts after its delete commit).
|
|
100
|
+
state.abort = (reason) => {
|
|
101
|
+
queueMicrotask(() => this.restart());
|
|
102
|
+
throw reason ?? new Error("Durable Object reset because its code calls abort()");
|
|
103
|
+
};
|
|
104
|
+
state.exports = opts.exports ?? {};
|
|
105
|
+
state.props = opts.props;
|
|
106
|
+
state.facets = createFacets({ ...opts, epoch: () => this.#epoch }, (name, cls, env, id, props) => {
|
|
107
|
+
const facet = new ObjectHost({
|
|
108
|
+
...opts,
|
|
109
|
+
// celld's facet stream: <parent>/facets/<SHA-256(name)[0..16] hex> (engine_api.rs)
|
|
110
|
+
cellKey: `${opts.cellKey}/facets/${facetSegment(name)}`,
|
|
111
|
+
id,
|
|
112
|
+
cls,
|
|
113
|
+
env,
|
|
114
|
+
props,
|
|
115
|
+
onAlarmChange: undefined,
|
|
116
|
+
// A facet is part of its root's cell: no lease of its own, the root's epoch.
|
|
117
|
+
acquire: undefined,
|
|
118
|
+
epoch: this.#epoch,
|
|
119
|
+
facet: true,
|
|
120
|
+
});
|
|
121
|
+
this.#facets.add(facet); // closed with its root
|
|
122
|
+
return facet;
|
|
123
|
+
});
|
|
124
|
+
const root = opts.asyncRoot ?? ((fn) => fn());
|
|
125
|
+
this.#root = root;
|
|
126
|
+
const construct = () => root(() => (this.#instance = new opts.cls(state, opts.env)));
|
|
127
|
+
this.#construct = construct;
|
|
128
|
+
const load = opts.backup?.load;
|
|
129
|
+
const replay = opts.backup?.replay;
|
|
130
|
+
const acquire = opts.acquire;
|
|
131
|
+
if (!load && !replay && !acquire) {
|
|
132
|
+
this.#nameFromMetadata(state);
|
|
133
|
+
construct();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Restore-on-open: events queue behind the closed gate until the image is in.
|
|
137
|
+
void this.#gate
|
|
138
|
+
.runClosed(async () => {
|
|
139
|
+
if (acquire) {
|
|
140
|
+
const lease = await acquire(this.cellKey);
|
|
141
|
+
if (!lease)
|
|
142
|
+
throw new CellOwnedElsewhereError(this.cellKey);
|
|
143
|
+
this.#epoch = lease.epoch;
|
|
144
|
+
this.#release = lease.release;
|
|
145
|
+
lease.lost.addEventListener("abort", () => this.#breakOn(new Error(`lease of ${this.cellKey} lost`)), {
|
|
146
|
+
once: true,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// Under a lease (the cell's own, or its root's for a facet) the bucket is the cell's
|
|
150
|
+
// truth: a local file kept from an earlier ownership is older than what other owners
|
|
151
|
+
// wrote since, and may hold commits that never became durable.
|
|
152
|
+
await this.#catchUp(this.#epoch !== undefined);
|
|
153
|
+
if (opts.facet)
|
|
154
|
+
this.#rescope(scopeOf(opts.cellKey));
|
|
155
|
+
else {
|
|
156
|
+
this.#initWake();
|
|
157
|
+
this.#scheduleRestoredAlarm(opts.onAlarmChange);
|
|
158
|
+
}
|
|
159
|
+
this.#nameFromMetadata(state);
|
|
160
|
+
construct();
|
|
161
|
+
})
|
|
162
|
+
.catch((e) => this.#breakOn(e));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Takes an image if the cell changed since the last successful save and hands
|
|
166
|
+
* it to `backup.save`. Taken synchronously between events → a consistent image.
|
|
167
|
+
* Skipped while an async transaction() holds the database or a save is running.
|
|
168
|
+
*/
|
|
169
|
+
backupIfDirty(reason, now) {
|
|
170
|
+
const b = this.#backup;
|
|
171
|
+
if (!b || this.broken || this.#saving || this.#seq === this.#backedUpSeq)
|
|
172
|
+
return this.#saving ?? Promise.resolve();
|
|
173
|
+
if (!this.#driver.image)
|
|
174
|
+
return Promise.reject(new Error("SyncSqlDriver has no image(): cannot back up"));
|
|
175
|
+
if (this.#sql.held)
|
|
176
|
+
return Promise.resolve(); // mid-transaction: next round
|
|
177
|
+
this.#sql.commit();
|
|
178
|
+
const seq = this.#seq;
|
|
179
|
+
let logSeq = 0;
|
|
180
|
+
try {
|
|
181
|
+
logSeq = Number(this.#driver.exec(LOG_SEQ_READ, []).rows[0]?.[0] ?? 0);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// no _cf_LOG yet
|
|
185
|
+
}
|
|
186
|
+
const image = this.#driver.image();
|
|
187
|
+
this.#saving = b
|
|
188
|
+
.save(this.cellKey, image, { seq, takenAt: now, reason, logSeq })
|
|
189
|
+
.then(() => {
|
|
190
|
+
this.#backedUpSeq = Math.max(this.#backedUpSeq, seq);
|
|
191
|
+
}, (e) => b.onError?.(this.cellKey, e))
|
|
192
|
+
.finally(() => (this.#saving = undefined));
|
|
193
|
+
return this.#saving;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Input gate → handler → commit → output gate (durability) → result.
|
|
197
|
+
*
|
|
198
|
+
* Events are queued and STARTED synchronously by a pump only while the gate
|
|
199
|
+
* is open: the first storage op of a handler closes the gate before the
|
|
200
|
+
* next event can start (checking the gate in an `await` continuation is too
|
|
201
|
+
* late — every queued continuation would see it open).
|
|
202
|
+
*/
|
|
203
|
+
deliver(fn) {
|
|
204
|
+
if (this.broken)
|
|
205
|
+
return Promise.reject(new BrokenObjectError(undefined));
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
this.#queue.push(() => {
|
|
208
|
+
if (this.broken)
|
|
209
|
+
return reject(new BrokenObjectError(this.#brokenCause, "its initialization failed"));
|
|
210
|
+
let started;
|
|
211
|
+
this.#active++;
|
|
212
|
+
try {
|
|
213
|
+
started = Promise.resolve(this.#root(() => fn(this.#instance, this.state)));
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
started = Promise.reject(e);
|
|
217
|
+
}
|
|
218
|
+
const done = () => {
|
|
219
|
+
this.#active--;
|
|
220
|
+
this.lastActive = Date.now();
|
|
221
|
+
};
|
|
222
|
+
started.then((r) => {
|
|
223
|
+
this.#pinIfStandardWebSocket(r);
|
|
224
|
+
this.#barrier().then(() => (done(), resolve(r)), (e) => (done(), reject(e)));
|
|
225
|
+
}, (e) => this.#barrier().then(() => (done(), reject(e)), (e2) => (done(), reject(e2))));
|
|
226
|
+
});
|
|
227
|
+
this.#pump();
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
#queue = [];
|
|
231
|
+
#pumpWaiting = false;
|
|
232
|
+
#pump = () => {
|
|
233
|
+
this.#pumpWaiting = false;
|
|
234
|
+
while (this.#queue.length && this.#gate.isOpen) {
|
|
235
|
+
// A database closed while idle: reopen it (and catch up) before anything runs.
|
|
236
|
+
if (this.#dbClosed) {
|
|
237
|
+
this.#dbClosed = false;
|
|
238
|
+
void this.#gate
|
|
239
|
+
.runClosed(async () => {
|
|
240
|
+
this.#open();
|
|
241
|
+
// The lease was held throughout: a kept file is current, an empty one is restored.
|
|
242
|
+
await this.#catchUp(false);
|
|
243
|
+
})
|
|
244
|
+
.catch((e) => this.#breakOn(e));
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
// Waking from hibernation: the constructor runs again before the event (and may
|
|
248
|
+
// close the gate with blockConcurrencyWhile, which the loop then respects).
|
|
249
|
+
if (this.#hibernated) {
|
|
250
|
+
this.#hibernated = false;
|
|
251
|
+
this.#construct?.();
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
this.#queue.shift()();
|
|
255
|
+
}
|
|
256
|
+
if (this.#queue.length && !this.#pumpWaiting) {
|
|
257
|
+
this.#pumpWaiting = true;
|
|
258
|
+
this.#gate.onOpen(this.#pump);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
async #barrier() {
|
|
262
|
+
try {
|
|
263
|
+
this.#sql.commit();
|
|
264
|
+
await this.#out.barrier();
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
this.broken = true;
|
|
268
|
+
try {
|
|
269
|
+
this.#sql.rollback();
|
|
270
|
+
}
|
|
271
|
+
catch { }
|
|
272
|
+
throw new BrokenObjectError(e);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** Settles once the cell lease is released (a store closed earlier would keep it held). */
|
|
276
|
+
close() {
|
|
277
|
+
for (const facet of this.#facets)
|
|
278
|
+
void facet.close();
|
|
279
|
+
this.#facets.clear();
|
|
280
|
+
if (!this.#dbClosed)
|
|
281
|
+
this.#db.close();
|
|
282
|
+
// Hand the cell over at once instead of letting the lease expire.
|
|
283
|
+
const released = this.#release?.().catch(() => { });
|
|
284
|
+
this.#release = undefined;
|
|
285
|
+
return released ?? Promise.resolve();
|
|
286
|
+
}
|
|
287
|
+
#release;
|
|
288
|
+
/** The hosts of this cell's facets: their databases close with the root's. */
|
|
289
|
+
#facets = new Set();
|
|
290
|
+
/**
|
|
291
|
+
* Brings the local database to the storage's state: restores the image, then replays the log.
|
|
292
|
+
* Without `rebuild` only an empty database is restored (the local file is the primary copy);
|
|
293
|
+
* with it, a non-empty one is replaced too — dropped first when the storage has no image.
|
|
294
|
+
*/
|
|
295
|
+
async #catchUp(rebuild) {
|
|
296
|
+
const { load, replay } = this.#backup ?? {};
|
|
297
|
+
if (!load && !replay)
|
|
298
|
+
return;
|
|
299
|
+
const d = this.#driver;
|
|
300
|
+
const empty = Number(d.exec("SELECT count(*) FROM sqlite_master", []).rows[0][0]) === 0;
|
|
301
|
+
if (!empty && !rebuild)
|
|
302
|
+
return;
|
|
303
|
+
const image = await load?.(this.cellKey);
|
|
304
|
+
if (image) {
|
|
305
|
+
if (!d.restore)
|
|
306
|
+
throw new Error("SyncSqlDriver has no restore(): cannot restore a backup");
|
|
307
|
+
d.restore(image);
|
|
308
|
+
}
|
|
309
|
+
else if (!empty) {
|
|
310
|
+
// Indexes and triggers go with their tables; sqlite_* tables cannot be dropped. Foreign
|
|
311
|
+
// keys are off meanwhile, so a parent table can go before its children.
|
|
312
|
+
const objects = d.exec("SELECT type, name FROM sqlite_master WHERE type IN ('view', 'table') AND name NOT LIKE 'sqlite_%'", []).rows;
|
|
313
|
+
const fk = Number(d.exec("PRAGMA foreign_keys", []).rows[0][0]);
|
|
314
|
+
d.exec("PRAGMA foreign_keys = OFF", []);
|
|
315
|
+
try {
|
|
316
|
+
for (const [type, name] of objects)
|
|
317
|
+
d.exec(`DROP ${type} IF EXISTS "${String(name).replace(/"/g, '""')}"`, []);
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
d.exec(`PRAGMA foreign_keys = ${fk}`, []);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Then every logged transaction the image does not contain, in order.
|
|
324
|
+
if (replay)
|
|
325
|
+
await this.#replayLog(replay);
|
|
326
|
+
}
|
|
327
|
+
/** Applies log segments newer than the database's own log sequence (restore path). */
|
|
328
|
+
async #replayLog(replay) {
|
|
329
|
+
const d = this.#driver;
|
|
330
|
+
d.exec(LOG_TABLE, []);
|
|
331
|
+
const row = d.exec(LOG_SEQ_READ, []).rows[0];
|
|
332
|
+
const after = Number(row?.[0] ?? 0);
|
|
333
|
+
for await (const seg of replay(this.cellKey, after)) {
|
|
334
|
+
if (!d.applyChangeset)
|
|
335
|
+
throw new Error("SyncSqlDriver cannot apply changesets: cannot replay a log");
|
|
336
|
+
d.exec("BEGIN", []);
|
|
337
|
+
try {
|
|
338
|
+
for (const ddl of seg.ddl)
|
|
339
|
+
d.exec(ddl, []);
|
|
340
|
+
d.applyChangeset(seg.changeset);
|
|
341
|
+
for (const [sql, params] of seg.statements)
|
|
342
|
+
d.exec(sql, params);
|
|
343
|
+
d.exec(LOG_SEQ_WRITE, [seg.logSeq]);
|
|
344
|
+
d.exec("COMMIT", []);
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
d.exec("ROLLBACK", []);
|
|
348
|
+
throw e;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
#construct;
|
|
353
|
+
#root = (fn) => fn();
|
|
354
|
+
#open;
|
|
355
|
+
#epoch;
|
|
356
|
+
/**
|
|
357
|
+
* celld gives each run of a facet its own scope and moves the facet database's rows to it
|
|
358
|
+
* on open (storage.rs open_embedded); doing the same here reads a facet database celld
|
|
359
|
+
* wrote, whatever scope it used.
|
|
360
|
+
*/
|
|
361
|
+
/**
|
|
362
|
+
* celld's alarm publication identity (storage/wake_record.rs initialize): one `_cf_WAKE`
|
|
363
|
+
* row per cell carrying the writer epoch; triggers bump its sequence on every alarm change.
|
|
364
|
+
*/
|
|
365
|
+
#initWake() {
|
|
366
|
+
if (!this.#epoch || !this.#opts.durability.publishWake)
|
|
367
|
+
return;
|
|
368
|
+
const scope = scopeOf(this.cellKey);
|
|
369
|
+
for (const ddl of CELLD_SCHEMA)
|
|
370
|
+
this.#driver.exec(ddl, []);
|
|
371
|
+
this.#driver.exec(`INSERT INTO _cf_WAKE(scope,epoch,sequence,at_ms)
|
|
372
|
+
VALUES(?1,?2,0,(SELECT at_ms FROM _cf_ALARM WHERE scope=?1))
|
|
373
|
+
ON CONFLICT(scope) DO UPDATE SET epoch=excluded.epoch WHERE excluded.epoch>_cf_WAKE.epoch`, [scope, this.#epoch.toString(16).padStart(16, "0")]);
|
|
374
|
+
this.#wakeSeq = this.#wakeRow()?.sequence;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* A cell restored on open may carry an alarm armed by its previous owner (another node, or
|
|
378
|
+
* celld). This node owns the cell now, so the alarm is its to fire: hand it to the scheduler,
|
|
379
|
+
* which otherwise only hears of alarms set here (or of a later wake-index poll).
|
|
380
|
+
*/
|
|
381
|
+
#scheduleRestoredAlarm(onAlarmChange) {
|
|
382
|
+
let at;
|
|
383
|
+
try {
|
|
384
|
+
at = this.#driver.exec("SELECT at_ms FROM _cf_ALARM WHERE scope = ?", [scopeOf(this.cellKey)])
|
|
385
|
+
.rows[0]?.[0];
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return; // no alarm table: the cell never had an alarm
|
|
389
|
+
}
|
|
390
|
+
if (at != null)
|
|
391
|
+
onAlarmChange?.(Number(at));
|
|
392
|
+
}
|
|
393
|
+
#wakeSeq;
|
|
394
|
+
#wakeRow() {
|
|
395
|
+
try {
|
|
396
|
+
const r = this.#driver.exec("SELECT epoch, sequence, at_ms FROM _cf_WAKE WHERE scope = ?", [
|
|
397
|
+
scopeOf(this.cellKey),
|
|
398
|
+
]).rows[0];
|
|
399
|
+
return r
|
|
400
|
+
? {
|
|
401
|
+
epoch: Number.parseInt(String(r[0]), 16),
|
|
402
|
+
sequence: Number(r[1]),
|
|
403
|
+
at: r[2] == null ? null : Number(r[2]),
|
|
404
|
+
}
|
|
405
|
+
: undefined;
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* After a commit: if it changed the alarm, a publication to run once the commit is durable
|
|
413
|
+
* (celld publishes after replication).
|
|
414
|
+
*/
|
|
415
|
+
#wakeAfterCommit() {
|
|
416
|
+
const publish = this.#opts.durability.publishWake;
|
|
417
|
+
if (!publish || this.#opts.facet || !this.#epoch)
|
|
418
|
+
return undefined;
|
|
419
|
+
const row = this.#wakeRow();
|
|
420
|
+
if (!row || row.sequence === this.#wakeSeq)
|
|
421
|
+
return undefined;
|
|
422
|
+
this.#wakeSeq = row.sequence;
|
|
423
|
+
return () => publish(this.cellKey, row);
|
|
424
|
+
}
|
|
425
|
+
#opts;
|
|
426
|
+
/**
|
|
427
|
+
* celld records the name an object was addressed by (`_cf_METADATA.actor_name`) and gives it
|
|
428
|
+
* back as `ctx.id.name` when the object is woken by id alone (an alarm, a wake entry) —
|
|
429
|
+
* celld's own queue cells rely on it. Both directions here: restore it, and record it.
|
|
430
|
+
*/
|
|
431
|
+
#nameFromMetadata(state) {
|
|
432
|
+
if (this.#opts.facet)
|
|
433
|
+
return;
|
|
434
|
+
const scope = scopeOf(this.cellKey);
|
|
435
|
+
// Reading only: no DDL or write outside a logged transaction (a changeset log replays
|
|
436
|
+
// only what it captured). The name is recorded with the cell's first storage write.
|
|
437
|
+
if (state.id.name !== undefined)
|
|
438
|
+
return;
|
|
439
|
+
try {
|
|
440
|
+
const r = this.#driver.exec("SELECT actor_name FROM _cf_METADATA WHERE scope = ?", [scope])
|
|
441
|
+
.rows[0];
|
|
442
|
+
if (r?.[0] != null)
|
|
443
|
+
state.id = new DurableObjectIdClass(state.id.toString(), String(r[0]));
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
// no _cf_METADATA yet
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
#rescope(scope) {
|
|
450
|
+
for (const table of ["_cf_KV", "_cf_ALARM", "_cf_METADATA"])
|
|
451
|
+
try {
|
|
452
|
+
this.#driver.exec(`UPDATE ${table} SET scope = ? WHERE scope <> ?`, [scope, scope]);
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
// table not created yet
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// ---- hibernation ---------------------------------------------------------------------
|
|
459
|
+
#active = 0;
|
|
460
|
+
#hibernated = false;
|
|
461
|
+
#pinned = 0;
|
|
462
|
+
/** Time of the last event's completion (the runtime's idle sweep reads it). */
|
|
463
|
+
lastActive = Date.now();
|
|
464
|
+
get hibernated() {
|
|
465
|
+
return this.#hibernated;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* A 101 response whose server end was accepted with `accept()` (not acceptWebSocket)
|
|
469
|
+
* holds a standard WebSocket: the object cannot hibernate until that socket closes —
|
|
470
|
+
* as on Cloudflare, only the hibernation API lets an object sleep with open sockets.
|
|
471
|
+
*/
|
|
472
|
+
#pinIfStandardWebSocket(r) {
|
|
473
|
+
const server = r?.webSocket?.peer;
|
|
474
|
+
if (!server || server.hibernatable || !server.accepted)
|
|
475
|
+
return;
|
|
476
|
+
this.#pinned++;
|
|
477
|
+
server.addEventListener("close", () => this.#pinned--, { once: true });
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Drops the instance when nothing runs: no event in flight or queued, no open async
|
|
481
|
+
* transaction, no standard WebSocket. Storage, alarms and hibernatable sockets stay; the
|
|
482
|
+
* next event constructs the class again. Returns whether it hibernated.
|
|
483
|
+
*/
|
|
484
|
+
hibernate() {
|
|
485
|
+
if (this.#hibernated ||
|
|
486
|
+
this.broken ||
|
|
487
|
+
this.#active ||
|
|
488
|
+
this.#queue.length ||
|
|
489
|
+
this.#sql.held ||
|
|
490
|
+
this.#pinned)
|
|
491
|
+
return false;
|
|
492
|
+
if (!this.#gate.isOpen)
|
|
493
|
+
return false;
|
|
494
|
+
this.#sql.commit();
|
|
495
|
+
this.#instance = undefined;
|
|
496
|
+
this.#hibernated = true;
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
#dbClosed = false;
|
|
500
|
+
/**
|
|
501
|
+
* Closes the database of a hibernated cell (the cell lease stays); the next event reopens it.
|
|
502
|
+
* A cell with changes its backup has not taken yet stays open. Returns whether it closed.
|
|
503
|
+
*/
|
|
504
|
+
closeDatabase() {
|
|
505
|
+
if (!this.#hibernated || this.#dbClosed || this.#queue.length || !this.#gate.isOpen)
|
|
506
|
+
return false;
|
|
507
|
+
if (this.#backup && (this.#saving || this.#seq !== this.#backedUpSeq))
|
|
508
|
+
return false;
|
|
509
|
+
this.#db.close();
|
|
510
|
+
this.#dbClosed = true;
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
/** Replaces the instance with a fresh one on the SAME database (facet abort/delete). */
|
|
514
|
+
restart() {
|
|
515
|
+
if (this.#dbClosed)
|
|
516
|
+
return; // the next event reopens it with a fresh instance
|
|
517
|
+
this.#construct?.();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function createFacets(parent, spawn) {
|
|
521
|
+
const running = new Map();
|
|
522
|
+
// abort() stops the stubs handed out so far (their calls throw the reason); a later
|
|
523
|
+
// get() starts the facet again on the same database — as celld does.
|
|
524
|
+
const generation = new Map();
|
|
525
|
+
const local = (h) => ({
|
|
526
|
+
call: (method, args) => h.deliver((obj) => {
|
|
527
|
+
const fn = obj[method];
|
|
528
|
+
if (typeof fn !== "function")
|
|
529
|
+
throw new TypeError(`${method} is not a function`);
|
|
530
|
+
return fn.apply(obj, args);
|
|
531
|
+
}),
|
|
532
|
+
fetch: (req) => h.deliver((obj) => obj.fetch(req)),
|
|
533
|
+
restart: () => h.restart(),
|
|
534
|
+
delete: async () => {
|
|
535
|
+
await h.deliver((_o, state) => state.storage.deleteAll());
|
|
536
|
+
h.restart();
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
/** A class of isolated loaded code: the facet's instance and SQLite live in the isolate. */
|
|
540
|
+
const isolated = (iso, cellKey) => {
|
|
541
|
+
const attached = iso.attach?.(cellKey, parent.epoch?.());
|
|
542
|
+
const ready = async () => attached;
|
|
543
|
+
return {
|
|
544
|
+
call: async (method, args) => (await ready(), iso.call(cellKey, method, args)),
|
|
545
|
+
fetch: async (req) => (await ready(), iso.fetch(cellKey, req)),
|
|
546
|
+
restart: async () => (await ready(), iso.restart(cellKey)),
|
|
547
|
+
delete: async () => (await ready(), iso.delete(cellKey)),
|
|
548
|
+
};
|
|
549
|
+
};
|
|
550
|
+
const targetOf = (name, start) => {
|
|
551
|
+
let p = running.get(name);
|
|
552
|
+
if (!p) {
|
|
553
|
+
if (!start)
|
|
554
|
+
return Promise.reject(new Error(`Facet "${name}" is not running`));
|
|
555
|
+
p = (async () => {
|
|
556
|
+
const info = (await start());
|
|
557
|
+
let cls;
|
|
558
|
+
let env = parent.env;
|
|
559
|
+
let props;
|
|
560
|
+
if (isClassHandle(info.class)) {
|
|
561
|
+
const r = await info.class.resolve();
|
|
562
|
+
if ("isolated" in r)
|
|
563
|
+
return isolated(r.isolated, `${parent.cellKey}/facets/${facetSegment(name)}`);
|
|
564
|
+
({ cls, env, props } = r);
|
|
565
|
+
}
|
|
566
|
+
else
|
|
567
|
+
cls = info.class;
|
|
568
|
+
const id = info.id instanceof DurableObjectIdClass ? info.id : parent.id;
|
|
569
|
+
return local(spawn(name, cls, env, id, props));
|
|
570
|
+
})();
|
|
571
|
+
p.catch(() => running.delete(name));
|
|
572
|
+
running.set(name, p);
|
|
573
|
+
}
|
|
574
|
+
return p;
|
|
575
|
+
};
|
|
576
|
+
return {
|
|
577
|
+
get(name, start) {
|
|
578
|
+
if (new TextEncoder().encode(name).byteLength > 256)
|
|
579
|
+
throw new Error("A facet name has a limit of 256 bytes");
|
|
580
|
+
const gen = generation.get(name)?.n ?? 0;
|
|
581
|
+
const live = () => {
|
|
582
|
+
const g = generation.get(name);
|
|
583
|
+
if (g && g.n !== gen)
|
|
584
|
+
throw g.reason;
|
|
585
|
+
};
|
|
586
|
+
return asyncStub(async (method, args) => (live(), await targetOf(name, start)).call(method, args), async (req) => (live(), await targetOf(name, start)).fetch(req));
|
|
587
|
+
},
|
|
588
|
+
abort(name, reason) {
|
|
589
|
+
const n = (generation.get(name)?.n ?? 0) + 1;
|
|
590
|
+
generation.set(name, { n, reason: reason ?? new Error(`Facet "${name}" was aborted`) });
|
|
591
|
+
// The database stays: the next call on a new stub gets a fresh instance over it.
|
|
592
|
+
void running.get(name)?.then((t) => t.restart());
|
|
593
|
+
},
|
|
594
|
+
async delete(name) {
|
|
595
|
+
const p = running.get(name);
|
|
596
|
+
generation.set(name, {
|
|
597
|
+
n: (generation.get(name)?.n ?? 0) + 1,
|
|
598
|
+
reason: new Error(`Facet "${name}" was deleted`),
|
|
599
|
+
});
|
|
600
|
+
if (p)
|
|
601
|
+
await (await p).delete();
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/** The cell's lease is held by another owner (route the request there, or retry later). */
|
|
606
|
+
export class CellOwnedElsewhereError extends Error {
|
|
607
|
+
constructor(cellKey) {
|
|
608
|
+
super(`Durable Object ${cellKey} is owned by another node`);
|
|
609
|
+
this.name = "CellOwnedElsewhereError";
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
/** celld's path segment for a facet name: the first 16 bytes of SHA-256(name), hex. */
|
|
613
|
+
export function facetSegment(name) {
|
|
614
|
+
return bytesToHex(sha256(new TextEncoder().encode(name)).subarray(0, 16));
|
|
615
|
+
}
|
|
616
|
+
//# sourceMappingURL=object-host.js.map
|