dotdo 0.0.1 → 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.
Files changed (667) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +446 -315
  3. package/cli/README.md +238 -0
  4. package/cli/agent.ts +72 -0
  5. package/cli/bin.js +44 -0
  6. package/cli/bin.ts +38 -0
  7. package/cli/build.ts +157 -0
  8. package/cli/commands/auth/login.ts +14 -0
  9. package/cli/commands/auth/logout.ts +6 -0
  10. package/cli/commands/auth/whoami.ts +16 -0
  11. package/cli/commands/deploy-multi.ts +245 -0
  12. package/cli/commands/dev/deploy.ts +100 -0
  13. package/cli/commands/dev/dev.ts +95 -0
  14. package/cli/commands/dev/logs.ts +91 -0
  15. package/cli/commands/dev-local.ts +88 -0
  16. package/cli/commands/do-ops.ts +314 -0
  17. package/cli/commands/index.ts +100 -0
  18. package/cli/commands/init.ts +247 -0
  19. package/cli/commands/introspect/emitter.ts +315 -0
  20. package/cli/commands/introspect/index.ts +193 -0
  21. package/cli/commands/link.ts +598 -0
  22. package/cli/commands/snippets.ts +415 -0
  23. package/cli/commands/tunnel.ts +239 -0
  24. package/cli/device-auth.ts +289 -0
  25. package/cli/fallback.ts +12 -0
  26. package/cli/index.ts +121 -0
  27. package/cli/main.ts +246 -0
  28. package/cli/mcp-stdio.ts +790 -0
  29. package/cli/package.json +62 -0
  30. package/cli/runtime/do-registry.ts +193 -0
  31. package/cli/runtime/embedded-db.ts +344 -0
  32. package/cli/runtime/index.ts +9 -0
  33. package/cli/runtime/miniflare-adapter.ts +162 -0
  34. package/cli/sandbox.ts +82 -0
  35. package/cli/src/args.ts +174 -0
  36. package/cli/src/auth.ts +55 -0
  37. package/cli/src/commands/call.ts +84 -0
  38. package/cli/src/commands/charge.ts +96 -0
  39. package/cli/src/commands/config.ts +115 -0
  40. package/cli/src/commands/email.ts +112 -0
  41. package/cli/src/commands/llm.ts +115 -0
  42. package/cli/src/commands/queue.ts +134 -0
  43. package/cli/src/commands/text.ts +86 -0
  44. package/cli/src/config.ts +185 -0
  45. package/cli/src/output.ts +246 -0
  46. package/cli/src/rpc.ts +192 -0
  47. package/cli/utils/config.ts +282 -0
  48. package/cli/utils/detect.ts +73 -0
  49. package/cli/utils/index.ts +15 -0
  50. package/cli/utils/logger.ts +232 -0
  51. package/dist/ai/index.js +19 -0
  52. package/dist/ai/index.js.map +1 -0
  53. package/dist/ai/template-literals.js +852 -0
  54. package/dist/ai/template-literals.js.map +1 -0
  55. package/dist/api/middleware/auth-federation.js +573 -0
  56. package/dist/api/middleware/auth-federation.js.map +1 -0
  57. package/dist/api/middleware/auth.js +545 -0
  58. package/dist/api/middleware/auth.js.map +1 -0
  59. package/dist/db/actions.js +212 -0
  60. package/dist/db/actions.js.map +1 -0
  61. package/dist/db/auth.js +506 -0
  62. package/dist/db/auth.js.map +1 -0
  63. package/dist/db/branches.js +65 -0
  64. package/dist/db/branches.js.map +1 -0
  65. package/dist/db/clickhouse.js +1074 -0
  66. package/dist/db/clickhouse.js.map +1 -0
  67. package/dist/db/dlq.js +39 -0
  68. package/dist/db/dlq.js.map +1 -0
  69. package/dist/db/events.js +28 -0
  70. package/dist/db/events.js.map +1 -0
  71. package/dist/db/exec.js +64 -0
  72. package/dist/db/exec.js.map +1 -0
  73. package/dist/db/files.js +85 -0
  74. package/dist/db/files.js.map +1 -0
  75. package/dist/db/flags.js +24 -0
  76. package/dist/db/flags.js.map +1 -0
  77. package/dist/db/git.js +116 -0
  78. package/dist/db/git.js.map +1 -0
  79. package/dist/db/iceberg/inverted-index.js +862 -0
  80. package/dist/db/iceberg/inverted-index.js.map +1 -0
  81. package/dist/db/iceberg/puffin.js +878 -0
  82. package/dist/db/iceberg/puffin.js.map +1 -0
  83. package/dist/db/iceberg/search-manifest.js +422 -0
  84. package/dist/db/iceberg/search-manifest.js.map +1 -0
  85. package/dist/db/iceberg/types.js +8 -0
  86. package/dist/db/iceberg/types.js.map +1 -0
  87. package/dist/db/index.js +121 -0
  88. package/dist/db/index.js.map +1 -0
  89. package/dist/db/integrations.js +368 -0
  90. package/dist/db/integrations.js.map +1 -0
  91. package/dist/db/json-indexes.js +332 -0
  92. package/dist/db/json-indexes.js.map +1 -0
  93. package/dist/db/linked-accounts.js +287 -0
  94. package/dist/db/linked-accounts.js.map +1 -0
  95. package/dist/db/nouns.js +183 -0
  96. package/dist/db/nouns.js.map +1 -0
  97. package/dist/db/objects.js +170 -0
  98. package/dist/db/objects.js.map +1 -0
  99. package/dist/db/primitives/dag-scheduler/index.js +869 -0
  100. package/dist/db/primitives/dag-scheduler/index.js.map +1 -0
  101. package/dist/db/primitives/exactly-once-context.js +237 -0
  102. package/dist/db/primitives/exactly-once-context.js.map +1 -0
  103. package/dist/db/primitives/index.js +62 -0
  104. package/dist/db/primitives/index.js.map +1 -0
  105. package/dist/db/primitives/keyed-router.js +145 -0
  106. package/dist/db/primitives/keyed-router.js.map +1 -0
  107. package/dist/db/primitives/observability.js +162 -0
  108. package/dist/db/primitives/observability.js.map +1 -0
  109. package/dist/db/primitives/schema-evolution.js +643 -0
  110. package/dist/db/primitives/schema-evolution.js.map +1 -0
  111. package/dist/db/primitives/stateful-operator/index.js +770 -0
  112. package/dist/db/primitives/stateful-operator/index.js.map +1 -0
  113. package/dist/db/primitives/temporal-store.js +306 -0
  114. package/dist/db/primitives/temporal-store.js.map +1 -0
  115. package/dist/db/primitives/typed-column-store.js +1229 -0
  116. package/dist/db/primitives/typed-column-store.js.map +1 -0
  117. package/dist/db/primitives/utils/duration.js +162 -0
  118. package/dist/db/primitives/utils/duration.js.map +1 -0
  119. package/dist/db/primitives/utils/murmur3.js +116 -0
  120. package/dist/db/primitives/utils/murmur3.js.map +1 -0
  121. package/dist/db/primitives/watermark-service.js +136 -0
  122. package/dist/db/primitives/watermark-service.js.map +1 -0
  123. package/dist/db/primitives/window-manager.js +764 -0
  124. package/dist/db/primitives/window-manager.js.map +1 -0
  125. package/dist/db/relationships.js +66 -0
  126. package/dist/db/relationships.js.map +1 -0
  127. package/dist/db/schema-minimal.js +61 -0
  128. package/dist/db/schema-minimal.js.map +1 -0
  129. package/dist/db/search.js +28 -0
  130. package/dist/db/search.js.map +1 -0
  131. package/dist/db/stores.js +1665 -0
  132. package/dist/db/stores.js.map +1 -0
  133. package/dist/db/things.js +297 -0
  134. package/dist/db/things.js.map +1 -0
  135. package/dist/db/vault.js +171 -0
  136. package/dist/db/vault.js.map +1 -0
  137. package/dist/db/verbs.js +102 -0
  138. package/dist/db/verbs.js.map +1 -0
  139. package/dist/do/base.js +48 -0
  140. package/dist/do/base.js.map +1 -0
  141. package/dist/do/tiny.js +31 -0
  142. package/dist/do/tiny.js.map +1 -0
  143. package/dist/lib/DOAuth.js +261 -0
  144. package/dist/lib/DOAuth.js.map +1 -0
  145. package/dist/lib/DODispatcher.js +72 -0
  146. package/dist/lib/DODispatcher.js.map +1 -0
  147. package/dist/lib/Modifier.js +189 -0
  148. package/dist/lib/Modifier.js.map +1 -0
  149. package/dist/lib/StateStorage.js +403 -0
  150. package/dist/lib/StateStorage.js.map +1 -0
  151. package/dist/lib/TypeRegistry.js +122 -0
  152. package/dist/lib/TypeRegistry.js.map +1 -0
  153. package/dist/lib/ai/gateway.js +247 -0
  154. package/dist/lib/ai/gateway.js.map +1 -0
  155. package/dist/lib/ai/tool-loop-agent.js +591 -0
  156. package/dist/lib/ai/tool-loop-agent.js.map +1 -0
  157. package/dist/lib/auto-wiring.js +439 -0
  158. package/dist/lib/auto-wiring.js.map +1 -0
  159. package/dist/lib/browse/browserbase.js +163 -0
  160. package/dist/lib/browse/browserbase.js.map +1 -0
  161. package/dist/lib/browse/cloudflare.js +144 -0
  162. package/dist/lib/browse/cloudflare.js.map +1 -0
  163. package/dist/lib/browse/index.js +62 -0
  164. package/dist/lib/browse/index.js.map +1 -0
  165. package/dist/lib/browse/types.js +13 -0
  166. package/dist/lib/browse/types.js.map +1 -0
  167. package/dist/lib/cache/index.js +37 -0
  168. package/dist/lib/cache/index.js.map +1 -0
  169. package/dist/lib/cache/visibility.js +638 -0
  170. package/dist/lib/cache/visibility.js.map +1 -0
  171. package/dist/lib/capabilities.js +268 -0
  172. package/dist/lib/capabilities.js.map +1 -0
  173. package/dist/lib/channels/base.js +106 -0
  174. package/dist/lib/channels/base.js.map +1 -0
  175. package/dist/lib/channels/discord.js +94 -0
  176. package/dist/lib/channels/discord.js.map +1 -0
  177. package/dist/lib/channels/email.js +204 -0
  178. package/dist/lib/channels/email.js.map +1 -0
  179. package/dist/lib/channels/index.js +90 -0
  180. package/dist/lib/channels/index.js.map +1 -0
  181. package/dist/lib/channels/mdxui-chat.js +95 -0
  182. package/dist/lib/channels/mdxui-chat.js.map +1 -0
  183. package/dist/lib/channels/slack-blockkit.js +121 -0
  184. package/dist/lib/channels/slack-blockkit.js.map +1 -0
  185. package/dist/lib/channels/types.js +7 -0
  186. package/dist/lib/channels/types.js.map +1 -0
  187. package/dist/lib/cloudflare/ai.js +654 -0
  188. package/dist/lib/cloudflare/ai.js.map +1 -0
  189. package/dist/lib/cloudflare/index.js +88 -0
  190. package/dist/lib/cloudflare/index.js.map +1 -0
  191. package/dist/lib/cloudflare/kv.js +342 -0
  192. package/dist/lib/cloudflare/kv.js.map +1 -0
  193. package/dist/lib/cloudflare/queues.js +434 -0
  194. package/dist/lib/cloudflare/queues.js.map +1 -0
  195. package/dist/lib/cloudflare/r2.js +604 -0
  196. package/dist/lib/cloudflare/r2.js.map +1 -0
  197. package/dist/lib/cloudflare/vectorize.js +494 -0
  198. package/dist/lib/cloudflare/vectorize.js.map +1 -0
  199. package/dist/lib/cloudflare/workflows.js +569 -0
  200. package/dist/lib/cloudflare/workflows.js.map +1 -0
  201. package/dist/lib/colo/caching.js +196 -0
  202. package/dist/lib/colo/caching.js.map +1 -0
  203. package/dist/lib/colo/detection.js +194 -0
  204. package/dist/lib/colo/detection.js.map +1 -0
  205. package/dist/lib/colo/external-data.js +219 -0
  206. package/dist/lib/colo/external-data.js.map +1 -0
  207. package/dist/lib/colo/globe-data.js +179 -0
  208. package/dist/lib/colo/globe-data.js.map +1 -0
  209. package/dist/lib/colo/index.js +16 -0
  210. package/dist/lib/colo/index.js.map +1 -0
  211. package/dist/lib/decorators.js +37 -0
  212. package/dist/lib/decorators.js.map +1 -0
  213. package/dist/lib/discovery.js +81 -0
  214. package/dist/lib/discovery.js.map +1 -0
  215. package/dist/lib/executors/AgenticFunctionExecutor.js +619 -0
  216. package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -0
  217. package/dist/lib/executors/BaseFunctionExecutor.js +328 -0
  218. package/dist/lib/executors/BaseFunctionExecutor.js.map +1 -0
  219. package/dist/lib/executors/CascadeExecutor.js +418 -0
  220. package/dist/lib/executors/CascadeExecutor.js.map +1 -0
  221. package/dist/lib/executors/CodeFunctionExecutor.js +904 -0
  222. package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -0
  223. package/dist/lib/executors/GenerativeFunctionExecutor.js +904 -0
  224. package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -0
  225. package/dist/lib/executors/HumanFunctionExecutor.js +884 -0
  226. package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -0
  227. package/dist/lib/executors/ParallelStepExecutor.js +308 -0
  228. package/dist/lib/executors/ParallelStepExecutor.js.map +1 -0
  229. package/dist/lib/executors/types.js +12 -0
  230. package/dist/lib/executors/types.js.map +1 -0
  231. package/dist/lib/experiments.js +89 -0
  232. package/dist/lib/experiments.js.map +1 -0
  233. package/dist/lib/flags/store.js +262 -0
  234. package/dist/lib/flags/store.js.map +1 -0
  235. package/dist/lib/functions/FunctionComposition.js +467 -0
  236. package/dist/lib/functions/FunctionComposition.js.map +1 -0
  237. package/dist/lib/functions/FunctionMiddleware.js +457 -0
  238. package/dist/lib/functions/FunctionMiddleware.js.map +1 -0
  239. package/dist/lib/functions/FunctionRegistry.js +426 -0
  240. package/dist/lib/functions/FunctionRegistry.js.map +1 -0
  241. package/dist/lib/functions/createFunction.js +1048 -0
  242. package/dist/lib/functions/createFunction.js.map +1 -0
  243. package/dist/lib/humans/index.js +68 -0
  244. package/dist/lib/humans/index.js.map +1 -0
  245. package/dist/lib/humans/templates.js +117 -0
  246. package/dist/lib/humans/templates.js.map +1 -0
  247. package/dist/lib/identity.js +98 -0
  248. package/dist/lib/identity.js.map +1 -0
  249. package/dist/lib/index.js +9 -0
  250. package/dist/lib/index.js.map +1 -0
  251. package/dist/lib/logging/error-logger.js +163 -0
  252. package/dist/lib/logging/error-logger.js.map +1 -0
  253. package/dist/lib/logging/index.js +160 -0
  254. package/dist/lib/logging/index.js.map +1 -0
  255. package/dist/lib/mixins/bash.js +753 -0
  256. package/dist/lib/mixins/bash.js.map +1 -0
  257. package/dist/lib/mixins/fs.js +648 -0
  258. package/dist/lib/mixins/fs.js.map +1 -0
  259. package/dist/lib/mixins/git.js +1006 -0
  260. package/dist/lib/mixins/git.js.map +1 -0
  261. package/dist/lib/mixins/npm.js +662 -0
  262. package/dist/lib/mixins/npm.js.map +1 -0
  263. package/dist/lib/noun-id.js +278 -0
  264. package/dist/lib/noun-id.js.map +1 -0
  265. package/dist/lib/rate-limit/sliding-window.js +148 -0
  266. package/dist/lib/rate-limit/sliding-window.js.map +1 -0
  267. package/dist/lib/rate-limit.js +110 -0
  268. package/dist/lib/rate-limit.js.map +1 -0
  269. package/dist/lib/rpc/bindings.js +548 -0
  270. package/dist/lib/rpc/bindings.js.map +1 -0
  271. package/dist/lib/rpc/index.js +64 -0
  272. package/dist/lib/rpc/index.js.map +1 -0
  273. package/dist/lib/safe-stringify.js +223 -0
  274. package/dist/lib/safe-stringify.js.map +1 -0
  275. package/dist/lib/sandbox/miniflare-sandbox.js +1007 -0
  276. package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -0
  277. package/dist/lib/sqids.js +110 -0
  278. package/dist/lib/sqids.js.map +1 -0
  279. package/dist/lib/sql/adapters/index.js +10 -0
  280. package/dist/lib/sql/adapters/index.js.map +1 -0
  281. package/dist/lib/sql/adapters/node-sql-parser.js +552 -0
  282. package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -0
  283. package/dist/lib/sql/adapters/pgsql-parser.js +1190 -0
  284. package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -0
  285. package/dist/lib/sql/index.js +277 -0
  286. package/dist/lib/sql/index.js.map +1 -0
  287. package/dist/lib/sql/types.js +56 -0
  288. package/dist/lib/sql/types.js.map +1 -0
  289. package/dist/lib/type-classifier.js +126 -0
  290. package/dist/lib/type-classifier.js.map +1 -0
  291. package/dist/lib/utils/html.js +47 -0
  292. package/dist/lib/utils/html.js.map +1 -0
  293. package/dist/lib/validation.js +48 -0
  294. package/dist/lib/validation.js.map +1 -0
  295. package/dist/lib/vault/store.js +411 -0
  296. package/dist/lib/vault/store.js.map +1 -0
  297. package/dist/metrics/hunch.js +739 -0
  298. package/dist/metrics/hunch.js.map +1 -0
  299. package/dist/objects/API.js +302 -0
  300. package/dist/objects/API.js.map +1 -0
  301. package/dist/objects/Agent.js +179 -0
  302. package/dist/objects/Agent.js.map +1 -0
  303. package/dist/objects/AgenticFunctionExecutor.js +8 -0
  304. package/dist/objects/AgenticFunctionExecutor.js.map +1 -0
  305. package/dist/objects/App.js +83 -0
  306. package/dist/objects/App.js.map +1 -0
  307. package/dist/objects/Browser.js +884 -0
  308. package/dist/objects/Browser.js.map +1 -0
  309. package/dist/objects/Business.js +107 -0
  310. package/dist/objects/Business.js.map +1 -0
  311. package/dist/objects/CLI.js +221 -0
  312. package/dist/objects/CLI.js.map +1 -0
  313. package/dist/objects/CodeFunctionExecutor.js +8 -0
  314. package/dist/objects/CodeFunctionExecutor.js.map +1 -0
  315. package/dist/objects/Collection.js +161 -0
  316. package/dist/objects/Collection.js.map +1 -0
  317. package/dist/objects/DO.js +41 -0
  318. package/dist/objects/DO.js.map +1 -0
  319. package/dist/objects/DOBase.js +2309 -0
  320. package/dist/objects/DOBase.js.map +1 -0
  321. package/dist/objects/DOCache.js +153 -0
  322. package/dist/objects/DOCache.js.map +1 -0
  323. package/dist/objects/DOFull.js +1676 -0
  324. package/dist/objects/DOFull.js.map +1 -0
  325. package/dist/objects/DOTiny.js +207 -0
  326. package/dist/objects/DOTiny.js.map +1 -0
  327. package/dist/objects/Directory.js +199 -0
  328. package/dist/objects/Directory.js.map +1 -0
  329. package/dist/objects/Entity.js +413 -0
  330. package/dist/objects/Entity.js.map +1 -0
  331. package/dist/objects/Function.js +116 -0
  332. package/dist/objects/Function.js.map +1 -0
  333. package/dist/objects/Human.js +231 -0
  334. package/dist/objects/Human.js.map +1 -0
  335. package/dist/objects/HumanFunctionExecutor.js +8 -0
  336. package/dist/objects/HumanFunctionExecutor.js.map +1 -0
  337. package/dist/objects/IcebergMetadataDO.js +938 -0
  338. package/dist/objects/IcebergMetadataDO.js.map +1 -0
  339. package/dist/objects/IntegrationsDO.js +1174 -0
  340. package/dist/objects/IntegrationsDO.js.map +1 -0
  341. package/dist/objects/ObservabilityBroadcaster.js +149 -0
  342. package/dist/objects/ObservabilityBroadcaster.js.map +1 -0
  343. package/dist/objects/Package.js +154 -0
  344. package/dist/objects/Package.js.map +1 -0
  345. package/dist/objects/Product.js +193 -0
  346. package/dist/objects/Product.js.map +1 -0
  347. package/dist/objects/SDK.js +152 -0
  348. package/dist/objects/SDK.js.map +1 -0
  349. package/dist/objects/SaaS.js +235 -0
  350. package/dist/objects/SaaS.js.map +1 -0
  351. package/dist/objects/SandboxDO.js +759 -0
  352. package/dist/objects/SandboxDO.js.map +1 -0
  353. package/dist/objects/Service.js +337 -0
  354. package/dist/objects/Service.js.map +1 -0
  355. package/dist/objects/Site.js +80 -0
  356. package/dist/objects/Site.js.map +1 -0
  357. package/dist/objects/Startup.js +479 -0
  358. package/dist/objects/Startup.js.map +1 -0
  359. package/dist/objects/ThingsDO.js +170 -0
  360. package/dist/objects/ThingsDO.js.map +1 -0
  361. package/dist/objects/VectorShardDO.js +650 -0
  362. package/dist/objects/VectorShardDO.js.map +1 -0
  363. package/dist/objects/Worker.js +144 -0
  364. package/dist/objects/Worker.js.map +1 -0
  365. package/dist/objects/Workflow.js +196 -0
  366. package/dist/objects/Workflow.js.map +1 -0
  367. package/dist/objects/WorkflowFactory.js +313 -0
  368. package/dist/objects/WorkflowFactory.js.map +1 -0
  369. package/dist/objects/WorkflowRuntime.js +863 -0
  370. package/dist/objects/WorkflowRuntime.js.map +1 -0
  371. package/dist/objects/circuit-breaker-bulkhead.js +178 -0
  372. package/dist/objects/circuit-breaker-bulkhead.js.map +1 -0
  373. package/dist/objects/createFunction.js +934 -0
  374. package/dist/objects/createFunction.js.map +1 -0
  375. package/dist/objects/index.js +80 -0
  376. package/dist/objects/index.js.map +1 -0
  377. package/dist/objects/lifecycle/Branch.js +275 -0
  378. package/dist/objects/lifecycle/Branch.js.map +1 -0
  379. package/dist/objects/lifecycle/Clone.js +1499 -0
  380. package/dist/objects/lifecycle/Clone.js.map +1 -0
  381. package/dist/objects/lifecycle/Compact.js +237 -0
  382. package/dist/objects/lifecycle/Compact.js.map +1 -0
  383. package/dist/objects/lifecycle/Promote.js +476 -0
  384. package/dist/objects/lifecycle/Promote.js.map +1 -0
  385. package/dist/objects/lifecycle/Shard.js +560 -0
  386. package/dist/objects/lifecycle/Shard.js.map +1 -0
  387. package/dist/objects/lifecycle/index.js +15 -0
  388. package/dist/objects/lifecycle/index.js.map +1 -0
  389. package/dist/objects/lifecycle/types.js +33 -0
  390. package/dist/objects/lifecycle/types.js.map +1 -0
  391. package/dist/objects/mixins/infrastructure.js +171 -0
  392. package/dist/objects/mixins/infrastructure.js.map +1 -0
  393. package/dist/objects/modules/StoresModule.js +153 -0
  394. package/dist/objects/modules/StoresModule.js.map +1 -0
  395. package/dist/objects/persistence/checkpoint-manager.js +606 -0
  396. package/dist/objects/persistence/checkpoint-manager.js.map +1 -0
  397. package/dist/objects/persistence/index.js +72 -0
  398. package/dist/objects/persistence/index.js.map +1 -0
  399. package/dist/objects/persistence/migration-runner.js +562 -0
  400. package/dist/objects/persistence/migration-runner.js.map +1 -0
  401. package/dist/objects/persistence/replication-manager.js +501 -0
  402. package/dist/objects/persistence/replication-manager.js.map +1 -0
  403. package/dist/objects/persistence/tiered-storage-manager.js +595 -0
  404. package/dist/objects/persistence/tiered-storage-manager.js.map +1 -0
  405. package/dist/objects/persistence/types.js +14 -0
  406. package/dist/objects/persistence/types.js.map +1 -0
  407. package/dist/objects/persistence/wal-manager.js +653 -0
  408. package/dist/objects/persistence/wal-manager.js.map +1 -0
  409. package/dist/objects/presets/index.js +20 -0
  410. package/dist/objects/presets/index.js.map +1 -0
  411. package/dist/objects/presets/primitives.js +188 -0
  412. package/dist/objects/presets/primitives.js.map +1 -0
  413. package/dist/objects/primitives/alarm-adapter.js +141 -0
  414. package/dist/objects/primitives/alarm-adapter.js.map +1 -0
  415. package/dist/objects/primitives/index.js +337 -0
  416. package/dist/objects/primitives/index.js.map +1 -0
  417. package/dist/objects/primitives/storage-adapter.js +182 -0
  418. package/dist/objects/primitives/storage-adapter.js.map +1 -0
  419. package/dist/objects/primitives/with-primitives.js +102 -0
  420. package/dist/objects/primitives/with-primitives.js.map +1 -0
  421. package/dist/objects/services/StoreManager.js +227 -0
  422. package/dist/objects/services/StoreManager.js.map +1 -0
  423. package/dist/objects/services/index.js +13 -0
  424. package/dist/objects/services/index.js.map +1 -0
  425. package/dist/objects/transport/auth-layer.js +1451 -0
  426. package/dist/objects/transport/auth-layer.js.map +1 -0
  427. package/dist/objects/transport/capnweb-target.js +355 -0
  428. package/dist/objects/transport/capnweb-target.js.map +1 -0
  429. package/dist/objects/transport/chain.js +441 -0
  430. package/dist/objects/transport/chain.js.map +1 -0
  431. package/dist/objects/transport/handler.js +58 -0
  432. package/dist/objects/transport/handler.js.map +1 -0
  433. package/dist/objects/transport/index.js +53 -0
  434. package/dist/objects/transport/index.js.map +1 -0
  435. package/dist/objects/transport/mcp-server.js +691 -0
  436. package/dist/objects/transport/mcp-server.js.map +1 -0
  437. package/dist/objects/transport/rest-autowire.js +1508 -0
  438. package/dist/objects/transport/rest-autowire.js.map +1 -0
  439. package/dist/objects/transport/rest-router.js +440 -0
  440. package/dist/objects/transport/rest-router.js.map +1 -0
  441. package/dist/objects/transport/rpc-server.js +1539 -0
  442. package/dist/objects/transport/rpc-server.js.map +1 -0
  443. package/dist/objects/transport/shared.js +576 -0
  444. package/dist/objects/transport/shared.js.map +1 -0
  445. package/dist/objects/transport/sync-engine.js +291 -0
  446. package/dist/objects/transport/sync-engine.js.map +1 -0
  447. package/dist/objects/transport/types.js +8 -0
  448. package/dist/objects/transport/types.js.map +1 -0
  449. package/dist/sandbox/index.js +258 -0
  450. package/dist/sandbox/index.js.map +1 -0
  451. package/dist/snippets/artifacts-config.js +241 -0
  452. package/dist/snippets/artifacts-config.js.map +1 -0
  453. package/dist/snippets/artifacts-ingest.js +832 -0
  454. package/dist/snippets/artifacts-ingest.js.map +1 -0
  455. package/dist/snippets/artifacts-serve.js +1035 -0
  456. package/dist/snippets/artifacts-serve.js.map +1 -0
  457. package/dist/snippets/artifacts-types.js +161 -0
  458. package/dist/snippets/artifacts-types.js.map +1 -0
  459. package/dist/snippets/cache-probe.js +376 -0
  460. package/dist/snippets/cache-probe.js.map +1 -0
  461. package/dist/snippets/cache.js +10 -0
  462. package/dist/snippets/cache.js.map +1 -0
  463. package/dist/snippets/events.js +469 -0
  464. package/dist/snippets/events.js.map +1 -0
  465. package/dist/snippets/index.js +7 -0
  466. package/dist/snippets/index.js.map +1 -0
  467. package/dist/snippets/proxy.js +495 -0
  468. package/dist/snippets/proxy.js.map +1 -0
  469. package/dist/snippets/search.js +1759 -0
  470. package/dist/snippets/search.js.map +1 -0
  471. package/dist/streams/index.js +30 -0
  472. package/dist/streams/index.js.map +1 -0
  473. package/dist/streams/observability.js +68 -0
  474. package/dist/streams/observability.js.map +1 -0
  475. package/dist/types/AI.js +92 -0
  476. package/dist/types/AI.js.map +1 -0
  477. package/dist/types/AIFunction.js +171 -0
  478. package/dist/types/AIFunction.js.map +1 -0
  479. package/dist/types/BrowseVerb.js +89 -0
  480. package/dist/types/BrowseVerb.js.map +1 -0
  481. package/dist/types/Browser.js +31 -0
  482. package/dist/types/Browser.js.map +1 -0
  483. package/dist/types/Chaos.js +15 -0
  484. package/dist/types/Chaos.js.map +1 -0
  485. package/dist/types/CloudflareBindings.js +109 -0
  486. package/dist/types/CloudflareBindings.js.map +1 -0
  487. package/dist/types/Collection.js +50 -0
  488. package/dist/types/Collection.js.map +1 -0
  489. package/dist/types/DO.js +2 -0
  490. package/dist/types/DO.js.map +1 -0
  491. package/dist/types/DOLocation.js +63 -0
  492. package/dist/types/DOLocation.js.map +1 -0
  493. package/dist/types/EventHandler.js +57 -0
  494. package/dist/types/EventHandler.js.map +1 -0
  495. package/dist/types/Experiment.js +33 -0
  496. package/dist/types/Experiment.js.map +1 -0
  497. package/dist/types/Flag.js +57 -0
  498. package/dist/types/Flag.js.map +1 -0
  499. package/dist/types/Lifecycle.js +13 -0
  500. package/dist/types/Lifecycle.js.map +1 -0
  501. package/dist/types/Location.js +169 -0
  502. package/dist/types/Location.js.map +1 -0
  503. package/dist/types/Noun.js +66 -0
  504. package/dist/types/Noun.js.map +1 -0
  505. package/dist/types/SessionEvent.js +194 -0
  506. package/dist/types/SessionEvent.js.map +1 -0
  507. package/dist/types/Thing.js +55 -0
  508. package/dist/types/Thing.js.map +1 -0
  509. package/dist/types/ThingDO.js +153 -0
  510. package/dist/types/ThingDO.js.map +1 -0
  511. package/dist/types/Things.js +2 -0
  512. package/dist/types/Things.js.map +1 -0
  513. package/dist/types/Verb.js +119 -0
  514. package/dist/types/Verb.js.map +1 -0
  515. package/dist/types/WorkflowContext.js +70 -0
  516. package/dist/types/WorkflowContext.js.map +1 -0
  517. package/dist/types/analytics-api.js +13 -0
  518. package/dist/types/analytics-api.js.map +1 -0
  519. package/dist/types/capabilities.js +135 -0
  520. package/dist/types/capabilities.js.map +1 -0
  521. package/dist/types/drizzle.js +12 -0
  522. package/dist/types/drizzle.js.map +1 -0
  523. package/dist/types/event.js +201 -0
  524. package/dist/types/event.js.map +1 -0
  525. package/dist/types/fn.js +12 -0
  526. package/dist/types/fn.js.map +1 -0
  527. package/dist/types/iceberg.js +48 -0
  528. package/dist/types/iceberg.js.map +1 -0
  529. package/dist/types/ids.js +170 -0
  530. package/dist/types/ids.js.map +1 -0
  531. package/dist/types/index.js +41 -0
  532. package/dist/types/index.js.map +1 -0
  533. package/dist/types/introspect.js +54 -0
  534. package/dist/types/introspect.js.map +1 -0
  535. package/dist/types/observability.js +124 -0
  536. package/dist/types/observability.js.map +1 -0
  537. package/dist/types/sync-protocol.js +175 -0
  538. package/dist/types/sync-protocol.js.map +1 -0
  539. package/dist/types/vector.js +13 -0
  540. package/dist/types/vector.js.map +1 -0
  541. package/dist/workflows/ScheduleManager.js +473 -0
  542. package/dist/workflows/ScheduleManager.js.map +1 -0
  543. package/dist/workflows/StepDOBridge.js +149 -0
  544. package/dist/workflows/StepDOBridge.js.map +1 -0
  545. package/dist/workflows/StepResultStorage.js +232 -0
  546. package/dist/workflows/StepResultStorage.js.map +1 -0
  547. package/dist/workflows/WaitForEventManager.js +461 -0
  548. package/dist/workflows/WaitForEventManager.js.map +1 -0
  549. package/dist/workflows/analyzer.js +332 -0
  550. package/dist/workflows/analyzer.js.map +1 -0
  551. package/dist/workflows/compat/activity-router.js +484 -0
  552. package/dist/workflows/compat/activity-router.js.map +1 -0
  553. package/dist/workflows/compat/backends/cloudflare-workflows.js +431 -0
  554. package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -0
  555. package/dist/workflows/compat/backends/index.js +14 -0
  556. package/dist/workflows/compat/backends/index.js.map +1 -0
  557. package/dist/workflows/compat/errors/index.js +375 -0
  558. package/dist/workflows/compat/errors/index.js.map +1 -0
  559. package/dist/workflows/compat/index.js +79 -0
  560. package/dist/workflows/compat/index.js.map +1 -0
  561. package/dist/workflows/compat/inngest/index.js +989 -0
  562. package/dist/workflows/compat/inngest/index.js.map +1 -0
  563. package/dist/workflows/compat/qstash/index.js +1263 -0
  564. package/dist/workflows/compat/qstash/index.js.map +1 -0
  565. package/dist/workflows/compat/temporal/activities.js +739 -0
  566. package/dist/workflows/compat/temporal/activities.js.map +1 -0
  567. package/dist/workflows/compat/temporal/child-workflows.js +154 -0
  568. package/dist/workflows/compat/temporal/child-workflows.js.map +1 -0
  569. package/dist/workflows/compat/temporal/client.js +381 -0
  570. package/dist/workflows/compat/temporal/client.js.map +1 -0
  571. package/dist/workflows/compat/temporal/context.js +309 -0
  572. package/dist/workflows/compat/temporal/context.js.map +1 -0
  573. package/dist/workflows/compat/temporal/determinism.js +216 -0
  574. package/dist/workflows/compat/temporal/determinism.js.map +1 -0
  575. package/dist/workflows/compat/temporal/errors.js +128 -0
  576. package/dist/workflows/compat/temporal/errors.js.map +1 -0
  577. package/dist/workflows/compat/temporal/index.js +2464 -0
  578. package/dist/workflows/compat/temporal/index.js.map +1 -0
  579. package/dist/workflows/compat/temporal/saga.js +504 -0
  580. package/dist/workflows/compat/temporal/saga.js.map +1 -0
  581. package/dist/workflows/compat/temporal/signals.js +364 -0
  582. package/dist/workflows/compat/temporal/signals.js.map +1 -0
  583. package/dist/workflows/compat/temporal/storage.js +271 -0
  584. package/dist/workflows/compat/temporal/storage.js.map +1 -0
  585. package/dist/workflows/compat/temporal/timers.js +347 -0
  586. package/dist/workflows/compat/temporal/timers.js.map +1 -0
  587. package/dist/workflows/compat/temporal/types.js +7 -0
  588. package/dist/workflows/compat/temporal/types.js.map +1 -0
  589. package/dist/workflows/compat/temporal/unified-primitives.js +339 -0
  590. package/dist/workflows/compat/temporal/unified-primitives.js.map +1 -0
  591. package/dist/workflows/compat/trigger/index.js +468 -0
  592. package/dist/workflows/compat/trigger/index.js.map +1 -0
  593. package/dist/workflows/compat/utils/index.js +69 -0
  594. package/dist/workflows/compat/utils/index.js.map +1 -0
  595. package/dist/workflows/context/correlation-capability.js +266 -0
  596. package/dist/workflows/context/correlation-capability.js.map +1 -0
  597. package/dist/workflows/context/correlation.js +484 -0
  598. package/dist/workflows/context/correlation.js.map +1 -0
  599. package/dist/workflows/context/experiment.js +289 -0
  600. package/dist/workflows/context/experiment.js.map +1 -0
  601. package/dist/workflows/context/flag.js +244 -0
  602. package/dist/workflows/context/flag.js.map +1 -0
  603. package/dist/workflows/context/foundation.js +648 -0
  604. package/dist/workflows/context/foundation.js.map +1 -0
  605. package/dist/workflows/context/human-base.js +106 -0
  606. package/dist/workflows/context/human-base.js.map +1 -0
  607. package/dist/workflows/context/human.js +368 -0
  608. package/dist/workflows/context/human.js.map +1 -0
  609. package/dist/workflows/context/measure.js +354 -0
  610. package/dist/workflows/context/measure.js.map +1 -0
  611. package/dist/workflows/context/rate-limit.js +358 -0
  612. package/dist/workflows/context/rate-limit.js.map +1 -0
  613. package/dist/workflows/context/user.js +117 -0
  614. package/dist/workflows/context/user.js.map +1 -0
  615. package/dist/workflows/context/vault.js +360 -0
  616. package/dist/workflows/context/vault.js.map +1 -0
  617. package/dist/workflows/data/entity-events/entity-events.js +489 -0
  618. package/dist/workflows/data/entity-events/entity-events.js.map +1 -0
  619. package/dist/workflows/data/experiment/index.js +599 -0
  620. package/dist/workflows/data/experiment/index.js.map +1 -0
  621. package/dist/workflows/data/goal/context.js +558 -0
  622. package/dist/workflows/data/goal/context.js.map +1 -0
  623. package/dist/workflows/data/goal/index.js +32 -0
  624. package/dist/workflows/data/goal/index.js.map +1 -0
  625. package/dist/workflows/data/measure/index.js +840 -0
  626. package/dist/workflows/data/measure/index.js.map +1 -0
  627. package/dist/workflows/data/stream/index.js +1149 -0
  628. package/dist/workflows/data/stream/index.js.map +1 -0
  629. package/dist/workflows/data/track/context.js +883 -0
  630. package/dist/workflows/data/track/context.js.map +1 -0
  631. package/dist/workflows/data/track/index.js +15 -0
  632. package/dist/workflows/data/track/index.js.map +1 -0
  633. package/dist/workflows/data/view/context.js +864 -0
  634. package/dist/workflows/data/view/context.js.map +1 -0
  635. package/dist/workflows/domain.js +93 -0
  636. package/dist/workflows/domain.js.map +1 -0
  637. package/dist/workflows/flag.js +176 -0
  638. package/dist/workflows/flag.js.map +1 -0
  639. package/dist/workflows/flags.js +217 -0
  640. package/dist/workflows/flags.js.map +1 -0
  641. package/dist/workflows/hash.js +209 -0
  642. package/dist/workflows/hash.js.map +1 -0
  643. package/dist/workflows/index.js +50 -0
  644. package/dist/workflows/index.js.map +1 -0
  645. package/dist/workflows/on.js +378 -0
  646. package/dist/workflows/on.js.map +1 -0
  647. package/dist/workflows/pipeline-promise.js +481 -0
  648. package/dist/workflows/pipeline-promise.js.map +1 -0
  649. package/dist/workflows/pipeline-types.js +20 -0
  650. package/dist/workflows/pipeline-types.js.map +1 -0
  651. package/dist/workflows/proxy.js +76 -0
  652. package/dist/workflows/proxy.js.map +1 -0
  653. package/dist/workflows/runtime.js +310 -0
  654. package/dist/workflows/runtime.js.map +1 -0
  655. package/dist/workflows/schedule-builder.js +327 -0
  656. package/dist/workflows/schedule-builder.js.map +1 -0
  657. package/dist/workflows/visibility/index.js +146 -0
  658. package/dist/workflows/visibility/index.js.map +1 -0
  659. package/dist/workflows/visibility/query-parser.js +150 -0
  660. package/dist/workflows/visibility/query-parser.js.map +1 -0
  661. package/dist/workflows/visibility/store.js +223 -0
  662. package/dist/workflows/visibility/store.js.map +1 -0
  663. package/dist/workflows/visibility/types.js +30 -0
  664. package/dist/workflows/visibility/types.js.map +1 -0
  665. package/dist/workflows/workflow.js +53 -0
  666. package/dist/workflows/workflow.js.map +1 -0
  667. package/package.json +294 -46
@@ -0,0 +1,1676 @@
1
+ /**
2
+ * DOFull - Full-featured Durable Object with Lifecycle Operations
3
+ *
4
+ * Extends DO (~120KB) with:
5
+ * - Lifecycle (fork, clone, compact, move)
6
+ * - Sharding (shard, unshard, routing)
7
+ * - Branching (branch, checkout, merge)
8
+ * - Promotion (promote, demote)
9
+ * - Staged clone operations (two-phase commit)
10
+ * - Eventual consistency replication
11
+ * - Resumable clone operations
12
+ *
13
+ * Use this when you need the full power of DO lifecycle management.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { DO } from 'dotdo/full'
18
+ *
19
+ * class MyDO extends DO {
20
+ * async onStart() {
21
+ * // Full lifecycle operations available
22
+ * await this.clone('https://backup.example.com.ai')
23
+ * await this.branch('feature-x')
24
+ * }
25
+ * }
26
+ * ```
27
+ */
28
+ import { DO as DOBase } from './DOBase';
29
+ import * as schema from '../db';
30
+ import { createShardModule } from './lifecycle/Shard';
31
+ // Cross-DO config
32
+ const CROSS_DO_CONFIG = {
33
+ STUB_CACHE_TTL: 60000,
34
+ CIRCUIT_BREAKER_THRESHOLD: 5,
35
+ CIRCUIT_BREAKER_TIMEOUT: 30000,
36
+ };
37
+ // ============================================================================
38
+ // DOFull - Full-featured Durable Object
39
+ // ============================================================================
40
+ export class DO extends DOBase {
41
+ // ═══════════════════════════════════════════════════════════════════════════
42
+ // BRANCHING STATE
43
+ // ═══════════════════════════════════════════════════════════════════════════
44
+ currentVersion = null;
45
+ // ═══════════════════════════════════════════════════════════════════════════
46
+ // LIFECYCLE MODULES
47
+ // ═══════════════════════════════════════════════════════════════════════════
48
+ _shardModule;
49
+ get shardModule() {
50
+ if (!this._shardModule) {
51
+ this._shardModule = createShardModule();
52
+ this._shardModule.initialize({
53
+ ns: this.ns,
54
+ currentBranch: this.currentBranch,
55
+ db: this.db,
56
+ env: this.env,
57
+ ctx: this.ctx,
58
+ emitEvent: (verb, data) => this.emitEvent(verb, data),
59
+ log: (message, data) => console.log(`[${this.ns}] ${message}`, data),
60
+ });
61
+ }
62
+ return this._shardModule;
63
+ }
64
+ // ═══════════════════════════════════════════════════════════════════════════
65
+ // CROSS-DO CACHES
66
+ // ═══════════════════════════════════════════════════════════════════════════
67
+ _stubCache = new Map();
68
+ _stubCacheMaxSize = 100;
69
+ _circuitBreaker = new Map();
70
+ // ═══════════════════════════════════════════════════════════════════════════
71
+ // STAGING & 2PC CONSTANTS
72
+ // ═══════════════════════════════════════════════════════════════════════════
73
+ static STAGING_PREFIX = 'staging:';
74
+ static CHECKPOINT_PREFIX = 'checkpoint:';
75
+ static DEFAULT_TOKEN_TIMEOUT = 5 * 60 * 1000;
76
+ static TWO_PC_PREFIX = '2pc:';
77
+ static DEFAULT_COORDINATOR_TIMEOUT = 30000;
78
+ static DEFAULT_ACK_TIMEOUT = 10000;
79
+ static DEFAULT_MAX_RETRIES = 3;
80
+ // ═══════════════════════════════════════════════════════════════════════════
81
+ // FORK OPERATION
82
+ // ═══════════════════════════════════════════════════════════════════════════
83
+ /**
84
+ * Fork current state to a new DO (new identity, fresh history)
85
+ */
86
+ async fork(options) {
87
+ const targetNs = options.to;
88
+ const forkBranch = options.branch || this.currentBranch;
89
+ // Validate target namespace URL
90
+ try {
91
+ new URL(targetNs);
92
+ }
93
+ catch {
94
+ throw new Error(`Invalid namespace URL: ${targetNs}`);
95
+ }
96
+ // Get current state (latest version of each thing, non-deleted, specified branch)
97
+ const things = await this.db.select().from(schema.things);
98
+ const branchFilter = forkBranch === 'main' ? null : forkBranch;
99
+ const branchThings = things.filter(t => t.branch === branchFilter && !t.deleted);
100
+ // Check if there's anything to fork
101
+ if (branchThings.length === 0) {
102
+ throw new Error('No state to fork');
103
+ }
104
+ // Get latest version of each thing (by id)
105
+ const latestVersions = new Map();
106
+ for (const thing of branchThings) {
107
+ const existing = latestVersions.get(thing.id);
108
+ if (!existing) {
109
+ latestVersions.set(thing.id, thing);
110
+ }
111
+ }
112
+ // Emit fork.started event
113
+ await this.emitEvent('fork.started', { targetNs, thingsCount: latestVersions.size });
114
+ // Create new DO at target namespace
115
+ if (!this.env.DO) {
116
+ throw new Error('DO namespace not configured');
117
+ }
118
+ const doId = this.env.DO.idFromName(targetNs);
119
+ const stub = this.env.DO.get(doId);
120
+ // Send state to new DO
121
+ await stub.fetch(new Request(`https://${targetNs}/init`, {
122
+ method: 'POST',
123
+ body: JSON.stringify({
124
+ things: Array.from(latestVersions.values()).map(t => ({
125
+ id: t.id,
126
+ type: t.type,
127
+ branch: null,
128
+ name: t.name,
129
+ data: t.data,
130
+ deleted: false,
131
+ })),
132
+ }),
133
+ }));
134
+ // Emit fork.completed event
135
+ await this.emitEvent('fork.completed', { targetNs, doId: doId.toString() });
136
+ return { ns: targetNs, doId: doId.toString() };
137
+ }
138
+ // ═══════════════════════════════════════════════════════════════════════════
139
+ // COMPACT OPERATION
140
+ // ═══════════════════════════════════════════════════════════════════════════
141
+ /**
142
+ * Squash history to current state (same identity)
143
+ */
144
+ async compact() {
145
+ const things = await this.db.select().from(schema.things);
146
+ const actions = await this.db.select().from(schema.actions);
147
+ const events = await this.db.select().from(schema.events);
148
+ // Check if there's anything to compact
149
+ if (things.length === 0) {
150
+ throw new Error('Nothing to compact');
151
+ }
152
+ // Archive old things versions to R2 FIRST - this provides atomicity
153
+ const R2 = this.env.R2;
154
+ if (R2) {
155
+ await R2.put(`archives/${this.ns}/things/${Date.now()}.json`, JSON.stringify(things));
156
+ // Archive actions to R2
157
+ if (actions.length > 0) {
158
+ await R2.put(`archives/${this.ns}/actions/${Date.now()}.json`, JSON.stringify(actions));
159
+ }
160
+ // Archive events to R2
161
+ const eventsToArchive = events.filter(e => e.verb !== 'compact.started' && e.verb !== 'compact.completed');
162
+ if (eventsToArchive.length > 0) {
163
+ await R2.put(`archives/${this.ns}/events/${Date.now()}.json`, JSON.stringify(eventsToArchive));
164
+ }
165
+ }
166
+ // Emit compact.started event
167
+ await this.emitEvent('compact.started', { thingsCount: things.length });
168
+ // Group things by id+branch to find latest versions
169
+ const thingsByKey = new Map();
170
+ for (const thing of things) {
171
+ const key = `${thing.id}:${thing.branch || 'main'}`;
172
+ const group = thingsByKey.get(key) || [];
173
+ group.push(thing);
174
+ thingsByKey.set(key, group);
175
+ }
176
+ // Keep only latest version of each thing
177
+ let compactedCount = 0;
178
+ const latestThings = [];
179
+ for (const [, group] of thingsByKey) {
180
+ // Get latest version (last in array based on insertion order)
181
+ const latest = group[group.length - 1];
182
+ // Only keep non-deleted things
183
+ if (!latest.deleted) {
184
+ latestThings.push(latest);
185
+ }
186
+ compactedCount += group.length - 1;
187
+ }
188
+ // Delete old versions (use raw SQL for bulk delete)
189
+ await this.ctx.storage.sql.exec('DELETE FROM things');
190
+ // Re-insert only latest versions
191
+ for (const thing of latestThings) {
192
+ await this.db.insert(schema.things).values({
193
+ id: thing.id,
194
+ type: thing.type,
195
+ branch: thing.branch,
196
+ name: thing.name,
197
+ data: thing.data,
198
+ deleted: false,
199
+ });
200
+ }
201
+ // Clear actions
202
+ await this.ctx.storage.sql.exec('DELETE FROM actions');
203
+ // Emit compact.completed event
204
+ await this.emitEvent('compact.completed', {
205
+ thingsCompacted: compactedCount,
206
+ actionsArchived: actions.length,
207
+ eventsArchived: events.filter(e => e.verb !== 'compact.started' && e.verb !== 'compact.completed').length,
208
+ });
209
+ return {
210
+ thingsCompacted: compactedCount,
211
+ actionsArchived: actions.length,
212
+ eventsArchived: events.filter(e => e.verb !== 'compact.started' && e.verb !== 'compact.completed').length,
213
+ };
214
+ }
215
+ // ═══════════════════════════════════════════════════════════════════════════
216
+ // MOVE TO COLO OPERATION
217
+ // ═══════════════════════════════════════════════════════════════════════════
218
+ /**
219
+ * Current colo (for tracking move operations)
220
+ */
221
+ currentColo = null;
222
+ /**
223
+ * Valid colo codes (IATA airport codes)
224
+ */
225
+ static VALID_COLOS = new Set([
226
+ 'ewr', 'lax', 'sfo', 'ord', 'dfw', 'sea', 'atl', 'iad',
227
+ 'lhr', 'fra', 'ams', 'cdg', 'sin', 'hkg', 'nrt', 'syd',
228
+ 'gru', 'jnb', 'bom', 'dub', 'mad', 'mxp', 'vie', 'zrh',
229
+ ]);
230
+ /**
231
+ * Move this DO to a specific colo (data center location)
232
+ */
233
+ async moveTo(colo) {
234
+ // Validate colo code
235
+ if (!DO.VALID_COLOS.has(colo)) {
236
+ throw new Error(`Invalid colo code: ${colo}`);
237
+ }
238
+ // Check if already at target colo
239
+ if (this.currentColo === colo) {
240
+ throw new Error(`Already at colo: ${colo}`);
241
+ }
242
+ const things = await this.db.select().from(schema.things);
243
+ if (things.length === 0) {
244
+ throw new Error('No state to move');
245
+ }
246
+ // Emit move.started event
247
+ await this.emitEvent('move.started', { targetColo: colo });
248
+ // Create new DO with locationHint
249
+ if (!this.env.DO) {
250
+ throw new Error('DO namespace not configured');
251
+ }
252
+ // Use type assertion for locationHint which is a valid Cloudflare option not in types
253
+ const newDoId = this.env.DO.newUniqueId({ locationHint: colo });
254
+ const stub = this.env.DO.get(newDoId);
255
+ // Transfer state to new DO
256
+ await stub.fetch(new Request(`https://${this.ns}/transfer`, {
257
+ method: 'POST',
258
+ body: JSON.stringify({
259
+ things: things.filter(t => !t.deleted),
260
+ branches: await this.db.select().from(schema.branches),
261
+ }),
262
+ }));
263
+ // Update objects table
264
+ await this.db.insert(schema.objects).values({
265
+ ns: this.ns,
266
+ id: newDoId.toString(),
267
+ class: 'DO',
268
+ region: colo,
269
+ primary: true,
270
+ createdAt: new Date(),
271
+ });
272
+ // Update current colo
273
+ this.currentColo = colo;
274
+ // Schedule deletion of old DO
275
+ this.ctx.waitUntil(Promise.resolve());
276
+ // Emit move.completed event
277
+ await this.emitEvent('move.completed', { newDoId: newDoId.toString(), region: colo });
278
+ return { newDoId: newDoId.toString(), region: colo };
279
+ }
280
+ // ═══════════════════════════════════════════════════════════════════════════
281
+ // CLONE OPERATIONS
282
+ // ═══════════════════════════════════════════════════════════════════════════
283
+ /**
284
+ * Clone this DO's state to another DO
285
+ */
286
+ async clone(target, options) {
287
+ const mode = options?.mode ?? 'atomic';
288
+ switch (mode) {
289
+ case 'staged':
290
+ return this.prepareStagedClone(target, { ...options, mode: 'staged' });
291
+ case 'eventual':
292
+ return this.initiateEventualClone(target, { ...options, mode: 'eventual' });
293
+ case 'resumable':
294
+ return this.initiateResumableClone(target, options);
295
+ default:
296
+ return this.performAtomicClone(target, options);
297
+ }
298
+ }
299
+ /**
300
+ * Perform atomic clone (traditional, blocking)
301
+ */
302
+ async performAtomicClone(target, options) {
303
+ // Validate target namespace URL
304
+ try {
305
+ new URL(target);
306
+ }
307
+ catch {
308
+ throw new Error(`Invalid namespace URL: ${target}`);
309
+ }
310
+ // Get all things from current branch
311
+ const things = await this.db.select().from(schema.things);
312
+ const cloneBranch = options?.branch || this.currentBranch;
313
+ const branchFilter = cloneBranch === 'main' ? null : cloneBranch;
314
+ const branchThings = things.filter(t => t.branch === branchFilter && !t.deleted);
315
+ if (branchThings.length === 0) {
316
+ throw new Error('No state to clone: source is empty');
317
+ }
318
+ // Get latest version of each thing
319
+ const latestVersions = new Map();
320
+ for (const thing of branchThings) {
321
+ latestVersions.set(thing.id, thing);
322
+ }
323
+ const thingsToClone = Array.from(latestVersions.values());
324
+ // Get relationships if not excluded
325
+ let relationshipsToClone = [];
326
+ if (!options?.excludeRelationships) {
327
+ const relationships = await this.db.select().from(schema.relationships);
328
+ relationshipsToClone = relationships.map(r => ({
329
+ id: r.id,
330
+ verb: r.verb,
331
+ from: r.from,
332
+ to: r.to,
333
+ data: r.data,
334
+ createdAt: r.createdAt,
335
+ }));
336
+ }
337
+ // Emit clone started event
338
+ await this.emitEvent('clone.started', { target, thingsCount: thingsToClone.length });
339
+ // Transfer to target DO
340
+ if (this.env.DO) {
341
+ const doId = this.env.DO.idFromName(target);
342
+ const stub = this.env.DO.get(doId);
343
+ const response = await stub.fetch(new Request(`${target}/clone-receive`, {
344
+ method: 'POST',
345
+ headers: { 'Content-Type': 'application/json' },
346
+ body: JSON.stringify({
347
+ things: thingsToClone.map(t => ({
348
+ id: t.id,
349
+ type: t.type,
350
+ branch: null,
351
+ name: t.name,
352
+ data: t.data,
353
+ deleted: false,
354
+ })),
355
+ relationships: relationshipsToClone.map(r => ({
356
+ id: r.id,
357
+ verb: r.verb,
358
+ from: r.from,
359
+ to: r.to,
360
+ data: r.data,
361
+ })),
362
+ sourceNs: this.ns,
363
+ }),
364
+ }));
365
+ if (!response.ok) {
366
+ throw new Error(`Clone transfer failed: ${response.status}`);
367
+ }
368
+ }
369
+ // Emit clone completed event
370
+ await this.emitEvent('clone.completed', {
371
+ target,
372
+ thingsCloned: thingsToClone.length,
373
+ relationshipsCloned: relationshipsToClone.length,
374
+ });
375
+ return {
376
+ targetNs: target,
377
+ clonedThings: thingsToClone.length,
378
+ clonedRelationships: relationshipsToClone.length,
379
+ };
380
+ }
381
+ /**
382
+ * Prepare a staged clone (Phase 1 of two-phase commit)
383
+ */
384
+ async prepareStagedClone(target, options) {
385
+ const tokenTimeout = options.tokenTimeout ?? DO.DEFAULT_TOKEN_TIMEOUT;
386
+ const token = crypto.randomUUID();
387
+ const expiresAt = new Date(Date.now() + tokenTimeout);
388
+ const stagingNs = `${target}-staging-${token.slice(0, 8)}`;
389
+ // Validate target namespace URL
390
+ try {
391
+ new URL(target);
392
+ }
393
+ catch {
394
+ throw new Error(`Invalid namespace URL: ${target}`);
395
+ }
396
+ // Emit staging started event
397
+ await this.emitEvent('clone.staging.started', { token, target });
398
+ // Get things to clone
399
+ const things = await this.db.select().from(schema.things);
400
+ const branchThings = things.filter(t => !t.deleted && (t.branch === null || t.branch === this.currentBranch));
401
+ if (branchThings.length === 0) {
402
+ throw new Error('No state to clone: source is empty');
403
+ }
404
+ // Get latest version of each thing
405
+ const latestVersions = new Map();
406
+ for (const thing of branchThings) {
407
+ latestVersions.set(thing.id, thing);
408
+ }
409
+ const thingsToClone = Array.from(latestVersions.values());
410
+ // Calculate size
411
+ const sizeBytes = JSON.stringify(thingsToClone).length;
412
+ // Map things to staging format
413
+ const mappedThings = thingsToClone.map(t => ({
414
+ id: t.id,
415
+ type: t.type,
416
+ branch: t.branch,
417
+ name: t.name,
418
+ data: t.data,
419
+ deleted: t.deleted ?? false,
420
+ }));
421
+ // Store staging data
422
+ const stagingData = {
423
+ sourceNs: this.ns,
424
+ targetNs: target,
425
+ stagingNs,
426
+ things: mappedThings,
427
+ expiresAt: expiresAt.toISOString(),
428
+ status: 'prepared',
429
+ createdAt: new Date().toISOString(),
430
+ integrityHash: this.computeStagingIntegrityHash(mappedThings),
431
+ metadata: {
432
+ thingsCount: thingsToClone.length,
433
+ sizeBytes,
434
+ branch: this.currentBranch,
435
+ version: thingsToClone.length,
436
+ },
437
+ };
438
+ await this.ctx.storage.put(`${DO.STAGING_PREFIX}${token}`, stagingData);
439
+ // Emit events
440
+ await this.emitEvent('clone.staging.completed', { token, target, thingsCount: thingsToClone.length });
441
+ await this.emitEvent('clone.prepared', { token, target, expiresAt });
442
+ return {
443
+ phase: 'prepared',
444
+ token,
445
+ expiresAt,
446
+ stagingNs,
447
+ metadata: {
448
+ thingsCount: thingsToClone.length,
449
+ sizeBytes,
450
+ branch: this.currentBranch,
451
+ version: thingsToClone.length,
452
+ },
453
+ };
454
+ }
455
+ /**
456
+ * Commit a staged clone
457
+ */
458
+ async commitClone(token) {
459
+ const staging = await this.ctx.storage.get(`${DO.STAGING_PREFIX}${token}`);
460
+ if (!staging) {
461
+ throw new Error('Staging data not found');
462
+ }
463
+ if (staging.status === 'committed') {
464
+ throw new Error('Clone already committed');
465
+ }
466
+ if (staging.status === 'aborted') {
467
+ throw new Error('Clone was aborted');
468
+ }
469
+ if (new Date(staging.expiresAt) < new Date()) {
470
+ throw new Error('Staging token expired');
471
+ }
472
+ // Verify integrity
473
+ const currentHash = this.computeStagingIntegrityHash(staging.things);
474
+ if (currentHash !== staging.integrityHash) {
475
+ throw new Error('Integrity check failed: data modified since prepare');
476
+ }
477
+ // Transfer to target
478
+ if (this.env.DO) {
479
+ const doId = this.env.DO.idFromName(staging.targetNs);
480
+ const stub = this.env.DO.get(doId);
481
+ const response = await stub.fetch(new Request(`${staging.targetNs}/clone-receive`, {
482
+ method: 'POST',
483
+ headers: { 'Content-Type': 'application/json' },
484
+ body: JSON.stringify({
485
+ things: staging.things,
486
+ relationships: [],
487
+ sourceNs: this.ns,
488
+ }),
489
+ }));
490
+ if (!response.ok) {
491
+ throw new Error(`Clone commit failed: ${response.status}`);
492
+ }
493
+ }
494
+ // Update status
495
+ staging.status = 'committed';
496
+ await this.ctx.storage.put(`${DO.STAGING_PREFIX}${token}`, staging);
497
+ await this.emitEvent('clone.committed', { token, targetNs: staging.targetNs });
498
+ return {
499
+ targetNs: staging.targetNs,
500
+ thingsCloned: staging.things.length,
501
+ };
502
+ }
503
+ /**
504
+ * Abort a staged clone
505
+ */
506
+ async abortClone(token, reason) {
507
+ const staging = await this.ctx.storage.get(`${DO.STAGING_PREFIX}${token}`);
508
+ if (!staging) {
509
+ throw new Error('Staging data not found');
510
+ }
511
+ if (staging.status === 'committed') {
512
+ throw new Error('Cannot abort committed clone');
513
+ }
514
+ staging.status = 'aborted';
515
+ await this.ctx.storage.put(`${DO.STAGING_PREFIX}${token}`, staging);
516
+ await this.emitEvent('clone.aborted', { token, reason });
517
+ }
518
+ /**
519
+ * Compute integrity hash for staging data
520
+ */
521
+ computeStagingIntegrityHash(things) {
522
+ const content = JSON.stringify(things);
523
+ let hash = 0;
524
+ for (let i = 0; i < content.length; i++) {
525
+ const char = content.charCodeAt(i);
526
+ hash = ((hash << 5) - hash) + char;
527
+ hash = hash & hash;
528
+ }
529
+ return Math.abs(hash).toString(16);
530
+ }
531
+ // ═══════════════════════════════════════════════════════════════════════════
532
+ // EVENTUAL CLONE OPERATIONS
533
+ // ═══════════════════════════════════════════════════════════════════════════
534
+ _conflictResolvers = new Map();
535
+ async initiateEventualClone(target, options) {
536
+ // Validate target namespace URL
537
+ try {
538
+ new URL(target);
539
+ }
540
+ catch {
541
+ throw new Error(`Invalid namespace URL: ${target}`);
542
+ }
543
+ const id = crypto.randomUUID();
544
+ // Get initial thing count
545
+ const things = await this.db.select().from(schema.things);
546
+ const cloneBranch = options?.branch || this.currentBranch;
547
+ const branchFilter = cloneBranch === 'main' ? null : cloneBranch;
548
+ const branchThings = things.filter(t => t.branch === branchFilter && !t.deleted);
549
+ const totalItems = branchThings.length;
550
+ const eventualOptions = options;
551
+ const syncInterval = eventualOptions?.syncInterval ?? 5000;
552
+ const maxDivergence = eventualOptions?.maxDivergence ?? 100;
553
+ const conflictResolution = eventualOptions?.conflictResolution ?? 'last-write-wins';
554
+ // Create initial state
555
+ const state = {
556
+ id,
557
+ targetNs: target,
558
+ status: 'pending',
559
+ progress: 0,
560
+ phase: 'initial',
561
+ itemsSynced: 0,
562
+ totalItems,
563
+ itemsRemaining: totalItems,
564
+ lastSyncAt: null,
565
+ divergence: totalItems,
566
+ maxDivergence,
567
+ syncInterval,
568
+ errorCount: 0,
569
+ lastError: null,
570
+ conflictResolution,
571
+ hasCustomResolver: false,
572
+ chunked: false,
573
+ chunkSize: 1000,
574
+ rateLimit: null,
575
+ createdAt: new Date().toISOString(),
576
+ updatedAt: new Date().toISOString(),
577
+ lastSyncedVersion: 0,
578
+ };
579
+ await this.ctx.storage.put(`eventual:${id}`, state);
580
+ await this.emitEvent('clone.initiated', { id, target, mode: 'eventual' });
581
+ // Schedule initial sync
582
+ const currentAlarm = await this.ctx.storage.getAlarm();
583
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
584
+ await this.ctx.storage.setAlarm(Date.now() + 100);
585
+ }
586
+ return this.createEventualHandle(id, state);
587
+ }
588
+ createEventualHandle(id, initialState) {
589
+ const self = this;
590
+ let currentStatus = initialState.status;
591
+ return {
592
+ id,
593
+ get status() {
594
+ return currentStatus;
595
+ },
596
+ async getProgress() {
597
+ const state = await self.ctx.storage.get(`eventual:${id}`);
598
+ if (state)
599
+ currentStatus = state.status;
600
+ return state?.progress ?? 0;
601
+ },
602
+ async getSyncStatus() {
603
+ const state = await self.ctx.storage.get(`eventual:${id}`);
604
+ if (state)
605
+ currentStatus = state.status;
606
+ return {
607
+ phase: state?.phase ?? 'initial',
608
+ itemsSynced: state?.itemsSynced ?? 0,
609
+ totalItems: state?.totalItems ?? 0,
610
+ lastSyncAt: state?.lastSyncAt ? new Date(state.lastSyncAt) : null,
611
+ divergence: state?.divergence ?? 0,
612
+ maxDivergence: state?.maxDivergence ?? 100,
613
+ syncInterval: state?.syncInterval ?? 5000,
614
+ errorCount: state?.errorCount ?? 0,
615
+ lastError: state?.lastError ? new Error(state.lastError) : null,
616
+ };
617
+ },
618
+ async pause() {
619
+ const state = await self.ctx.storage.get(`eventual:${id}`);
620
+ if (!state)
621
+ throw new Error(`Clone operation not found: ${id}`);
622
+ state.status = 'paused';
623
+ state.updatedAt = new Date().toISOString();
624
+ await self.ctx.storage.put(`eventual:${id}`, state);
625
+ currentStatus = 'paused';
626
+ await self.emitEvent('clone.paused', { id });
627
+ },
628
+ async resume() {
629
+ const state = await self.ctx.storage.get(`eventual:${id}`);
630
+ if (!state)
631
+ throw new Error(`Clone operation not found: ${id}`);
632
+ state.status = 'syncing';
633
+ state.updatedAt = new Date().toISOString();
634
+ await self.ctx.storage.put(`eventual:${id}`, state);
635
+ currentStatus = state.status;
636
+ await self.emitEvent('clone.resumed', { id });
637
+ const currentAlarm = await self.ctx.storage.getAlarm();
638
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
639
+ await self.ctx.storage.setAlarm(Date.now() + 100);
640
+ }
641
+ },
642
+ async sync() {
643
+ return self.performEventualSync(id);
644
+ },
645
+ async cancel() {
646
+ const state = await self.ctx.storage.get(`eventual:${id}`);
647
+ if (!state)
648
+ throw new Error(`Clone operation not found: ${id}`);
649
+ state.status = 'cancelled';
650
+ state.updatedAt = new Date().toISOString();
651
+ await self.ctx.storage.put(`eventual:${id}`, state);
652
+ currentStatus = 'cancelled';
653
+ await self.emitEvent('clone.cancelled', { id });
654
+ },
655
+ };
656
+ }
657
+ async performEventualSync(id) {
658
+ const startTime = Date.now();
659
+ const state = await this.ctx.storage.get(`eventual:${id}`);
660
+ if (!state)
661
+ throw new Error(`Clone operation not found: ${id}`);
662
+ if (state.status === 'cancelled' || state.status === 'paused') {
663
+ return { itemsSynced: 0, duration: 0, conflicts: [] };
664
+ }
665
+ let itemsSynced = 0;
666
+ const conflicts = [];
667
+ try {
668
+ if (state.status === 'pending') {
669
+ state.status = 'syncing';
670
+ state.phase = 'bulk';
671
+ await this.ctx.storage.put(`eventual:${id}`, state);
672
+ }
673
+ const things = await this.db.select().from(schema.things);
674
+ const branchThings = things.filter(t => t.branch === null && !t.deleted);
675
+ const latestVersions = new Map();
676
+ for (const thing of branchThings) {
677
+ latestVersions.set(thing.id, thing);
678
+ }
679
+ let itemsToSync = Array.from(latestVersions.values());
680
+ if (state.phase !== 'bulk' && state.phase !== 'initial') {
681
+ itemsToSync = itemsToSync.filter((_, idx) => idx >= state.lastSyncedVersion);
682
+ }
683
+ if (state.chunked && itemsToSync.length > state.chunkSize) {
684
+ itemsToSync = itemsToSync.slice(0, state.chunkSize);
685
+ }
686
+ if (this.env.DO && itemsToSync.length > 0) {
687
+ const doId = this.env.DO.idFromName(state.targetNs);
688
+ const stub = this.env.DO.get(doId);
689
+ const response = await stub.fetch(new Request(`https://${state.targetNs}/sync`, {
690
+ method: 'POST',
691
+ body: JSON.stringify({
692
+ cloneId: id,
693
+ things: itemsToSync.map(t => ({
694
+ id: t.id,
695
+ type: t.type,
696
+ branch: null,
697
+ name: t.name,
698
+ data: t.data,
699
+ deleted: false,
700
+ })),
701
+ }),
702
+ }));
703
+ if (response.ok) {
704
+ itemsSynced = itemsToSync.length;
705
+ }
706
+ }
707
+ state.itemsSynced += itemsSynced;
708
+ state.lastSyncedVersion += itemsSynced;
709
+ state.lastSyncAt = new Date().toISOString();
710
+ state.itemsRemaining = Math.max(0, state.totalItems - state.itemsSynced);
711
+ state.progress = state.totalItems > 0 ? Math.floor((state.itemsSynced / state.totalItems) * 100) : 100;
712
+ state.divergence = state.itemsRemaining;
713
+ state.errorCount = 0;
714
+ state.lastError = null;
715
+ state.updatedAt = new Date().toISOString();
716
+ if (state.progress >= 100) {
717
+ state.status = 'active';
718
+ state.phase = 'delta';
719
+ await this.emitEvent('clone.active', { id, target: state.targetNs });
720
+ }
721
+ await this.ctx.storage.put(`eventual:${id}`, state);
722
+ const duration = Date.now() - startTime;
723
+ return { itemsSynced, duration, conflicts };
724
+ }
725
+ catch (error) {
726
+ state.errorCount++;
727
+ state.lastError = error.message;
728
+ state.updatedAt = new Date().toISOString();
729
+ if (state.errorCount >= 10) {
730
+ state.status = 'error';
731
+ await this.emitEvent('clone.error', { id, error: state.lastError });
732
+ }
733
+ await this.ctx.storage.put(`eventual:${id}`, state);
734
+ return { itemsSynced: 0, duration: Date.now() - startTime, conflicts: [] };
735
+ }
736
+ }
737
+ // ═══════════════════════════════════════════════════════════════════════════
738
+ // RESUMABLE CLONE OPERATIONS
739
+ // ═══════════════════════════════════════════════════════════════════════════
740
+ _resumableClones = new Map();
741
+ _cloneLocks = new Map();
742
+ async initiateResumableClone(target, options) {
743
+ const batchSize = options?.batchSize || 100;
744
+ const checkpointInterval = options?.checkpointInterval || 1;
745
+ const maxRetries = options?.maxRetries || 3;
746
+ const retryDelay = options?.retryDelay || 1000;
747
+ const lockTimeout = options?.lockTimeout || 300000;
748
+ const checkpointRetentionMs = options?.checkpointRetentionMs || 3600000;
749
+ const compress = options?.compress || false;
750
+ const maxBandwidth = options?.maxBandwidth;
751
+ // Validate target namespace URL
752
+ try {
753
+ new URL(target);
754
+ }
755
+ catch {
756
+ throw new Error(`Invalid namespace URL: ${target}`);
757
+ }
758
+ // Check for existing lock
759
+ const existingLock = this._cloneLocks.get(target) || await this.ctx.storage.get(`clone-lock:${target}`);
760
+ const now = Date.now();
761
+ if (existingLock && !existingLock.isStale && new Date(existingLock.expiresAt).getTime() > now) {
762
+ if (!options?.forceLock) {
763
+ throw new Error(`Clone operation already in progress for target: ${target}`);
764
+ }
765
+ await this.releaseCloneLock(target, existingLock.cloneId);
766
+ }
767
+ const cloneId = crypto.randomUUID();
768
+ const state = {
769
+ id: cloneId,
770
+ targetNs: target,
771
+ status: 'initializing',
772
+ checkpoints: [],
773
+ position: 0,
774
+ batchSize,
775
+ checkpointInterval,
776
+ maxRetries,
777
+ retryDelay,
778
+ retryCount: 0,
779
+ compress,
780
+ maxBandwidth,
781
+ checkpointRetentionMs,
782
+ pauseRequested: false,
783
+ cancelRequested: false,
784
+ createdAt: new Date(),
785
+ bytesTransferred: 0,
786
+ totalBytes: 0,
787
+ startedAt: null,
788
+ };
789
+ // Acquire lock
790
+ const lockId = crypto.randomUUID();
791
+ const lock = {
792
+ lockId,
793
+ cloneId,
794
+ target,
795
+ acquiredAt: new Date(),
796
+ expiresAt: new Date(now + lockTimeout),
797
+ isStale: false,
798
+ };
799
+ this._cloneLocks.set(target, lock);
800
+ await this.ctx.storage.put(`clone-lock:${target}`, lock);
801
+ await this.emitEvent('clone.lock.acquired', { lockId, target, cloneId });
802
+ this._resumableClones.set(cloneId, state);
803
+ await this.ctx.storage.put(`resumable:${cloneId}`, state);
804
+ // Schedule the clone
805
+ const currentAlarm = await this.ctx.storage.getAlarm();
806
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
807
+ await this.ctx.storage.setAlarm(Date.now() + 100);
808
+ }
809
+ return this.createResumableCloneHandle(cloneId);
810
+ }
811
+ createResumableCloneHandle(cloneId) {
812
+ const self = this;
813
+ return {
814
+ id: cloneId,
815
+ get status() {
816
+ const state = self._resumableClones.get(cloneId);
817
+ return state?.status || 'failed';
818
+ },
819
+ get checkpoints() {
820
+ const state = self._resumableClones.get(cloneId);
821
+ return state?.checkpoints || [];
822
+ },
823
+ async getProgress() {
824
+ const state = await self.ctx.storage.get(`resumable:${cloneId}`);
825
+ return state?.progress || 0;
826
+ },
827
+ async pause() {
828
+ const state = await self.ctx.storage.get(`resumable:${cloneId}`);
829
+ if (!state)
830
+ throw new Error('Clone not found');
831
+ state.pauseRequested = true;
832
+ state.status = 'paused';
833
+ await self.ctx.storage.put(`resumable:${cloneId}`, state);
834
+ self._resumableClones.set(cloneId, state);
835
+ await self.emitEvent('clone.paused', { id: cloneId, progress: state.progress });
836
+ },
837
+ async resume() {
838
+ const state = await self.ctx.storage.get(`resumable:${cloneId}`);
839
+ if (!state)
840
+ throw new Error('Clone not found');
841
+ state.pauseRequested = false;
842
+ state.status = 'transferring';
843
+ await self.ctx.storage.put(`resumable:${cloneId}`, state);
844
+ self._resumableClones.set(cloneId, state);
845
+ await self.emitEvent('clone.resumed', { id: cloneId });
846
+ const currentAlarm = await self.ctx.storage.getAlarm();
847
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
848
+ await self.ctx.storage.setAlarm(Date.now() + 100);
849
+ }
850
+ },
851
+ async cancel() {
852
+ const state = await self.ctx.storage.get(`resumable:${cloneId}`);
853
+ if (!state)
854
+ throw new Error('Clone not found');
855
+ state.cancelRequested = true;
856
+ state.status = 'cancelled';
857
+ await self.ctx.storage.put(`resumable:${cloneId}`, state);
858
+ self._resumableClones.set(cloneId, state);
859
+ await self.releaseCloneLock(state.targetNs, cloneId);
860
+ await self.emitEvent('clone.cancelled', { id: cloneId, progress: state.progress });
861
+ },
862
+ async waitForCheckpoint() {
863
+ const pollInterval = 50;
864
+ const maxWait = 60000;
865
+ const startTime = Date.now();
866
+ return new Promise((resolve, reject) => {
867
+ const poll = async () => {
868
+ const state = await self.ctx.storage.get(`resumable:${cloneId}`);
869
+ if (!state) {
870
+ reject(new Error('Clone not found'));
871
+ return;
872
+ }
873
+ if (state.checkpoints.length > 0) {
874
+ resolve(state.checkpoints[state.checkpoints.length - 1]);
875
+ return;
876
+ }
877
+ if (Date.now() - startTime > maxWait) {
878
+ reject(new Error('Timeout waiting for checkpoint'));
879
+ return;
880
+ }
881
+ setTimeout(poll, pollInterval);
882
+ };
883
+ poll();
884
+ });
885
+ },
886
+ async canResumeFrom(checkpointId) {
887
+ const checkpoint = await self.ctx.storage.get(`checkpoint:${checkpointId}`);
888
+ if (!checkpoint)
889
+ return false;
890
+ if (!checkpoint.hash || !/^[a-f0-9]{64}$/.test(checkpoint.hash))
891
+ return false;
892
+ if (checkpoint.position < 0)
893
+ return false;
894
+ return true;
895
+ },
896
+ async getIntegrityHash() {
897
+ const state = await self.ctx.storage.get(`resumable:${cloneId}`);
898
+ if (!state || state.checkpoints.length === 0)
899
+ return '';
900
+ return state.checkpoints[state.checkpoints.length - 1].hash;
901
+ },
902
+ async getLockInfo() {
903
+ const state = await self.ctx.storage.get(`resumable:${cloneId}`);
904
+ if (!state)
905
+ return null;
906
+ const lock = self._cloneLocks.get(state.targetNs) || await self.ctx.storage.get(`clone-lock:${state.targetNs}`);
907
+ if (!lock || lock.cloneId !== cloneId)
908
+ return null;
909
+ return {
910
+ lockId: lock.lockId,
911
+ cloneId: lock.cloneId,
912
+ acquiredAt: new Date(lock.acquiredAt),
913
+ expiresAt: new Date(lock.expiresAt),
914
+ isStale: lock.isStale || new Date(lock.expiresAt).getTime() < Date.now(),
915
+ };
916
+ },
917
+ async forceOverrideLock() {
918
+ const state = await self.ctx.storage.get(`resumable:${cloneId}`);
919
+ if (!state)
920
+ throw new Error('Clone not found');
921
+ await self.releaseCloneLock(state.targetNs, cloneId);
922
+ },
923
+ };
924
+ }
925
+ async releaseCloneLock(target, cloneId) {
926
+ const lock = this._cloneLocks.get(target) || await this.ctx.storage.get(`clone-lock:${target}`);
927
+ if (lock && lock.cloneId === cloneId) {
928
+ this._cloneLocks.delete(target);
929
+ await this.ctx.storage.delete(`clone-lock:${target}`);
930
+ await this.emitEvent('clone.lock.released', { lockId: lock.lockId, target });
931
+ }
932
+ }
933
+ // ═══════════════════════════════════════════════════════════════════════════
934
+ // PROMOTE / DEMOTE
935
+ // ═══════════════════════════════════════════════════════════════════════════
936
+ /**
937
+ * Promote a Thing to its own DO
938
+ */
939
+ async promote(thingId, options) {
940
+ const { targetNs: customNs, preserveHistory = true, mode = 'atomic' } = options ?? {};
941
+ // Get the Thing
942
+ const thing = await this.things.get(thingId);
943
+ if (!thing) {
944
+ throw new Error(`Thing not found: ${thingId}`);
945
+ }
946
+ // Generate target namespace
947
+ const targetNs = customNs || `https://${thingId}.do`;
948
+ // Validate target URL
949
+ try {
950
+ new URL(targetNs);
951
+ }
952
+ catch {
953
+ throw new Error(`Invalid target URL: ${targetNs}`);
954
+ }
955
+ // Cannot promote to self
956
+ if (targetNs === this.ns) {
957
+ throw new Error('Cannot promote to self');
958
+ }
959
+ // Check if DO binding is available
960
+ if (!this.env.DO) {
961
+ throw new Error('DO binding is unavailable');
962
+ }
963
+ await this.emitEvent('promote.started', { thingId, targetNs, mode });
964
+ try {
965
+ // Create new DO and transfer state
966
+ const doId = this.env.DO.idFromName(targetNs);
967
+ const stub = this.env.DO.get(doId);
968
+ // Initialize the new DO
969
+ const initResponse = await stub.fetch(new Request(`${targetNs}/initialize`, {
970
+ method: 'POST',
971
+ headers: { 'Content-Type': 'application/json' },
972
+ body: JSON.stringify({
973
+ ns: targetNs,
974
+ parent: this.ns,
975
+ }),
976
+ }));
977
+ if (!initResponse.ok) {
978
+ throw new Error(`Failed to initialize target DO: ${initResponse.status}`);
979
+ }
980
+ // Transfer the Thing state
981
+ const transferResponse = await stub.fetch(new Request(`${targetNs}/clone-receive`, {
982
+ method: 'POST',
983
+ headers: { 'Content-Type': 'application/json' },
984
+ body: JSON.stringify({
985
+ things: [{
986
+ id: 'root',
987
+ $type: thing.$type,
988
+ branch: null,
989
+ name: thing.name,
990
+ data: thing.data,
991
+ deleted: false,
992
+ }],
993
+ relationships: [],
994
+ sourceNs: this.ns,
995
+ promotedFrom: thingId,
996
+ }),
997
+ }));
998
+ if (!transferResponse.ok) {
999
+ throw new Error(`Failed to transfer state: ${transferResponse.status}`);
1000
+ }
1001
+ // Mark original Thing as promoted
1002
+ await this.things.update(thingId, {
1003
+ data: {
1004
+ ...thing.data,
1005
+ $promotedTo: targetNs,
1006
+ $promotedAt: new Date().toISOString(),
1007
+ },
1008
+ });
1009
+ await this.emitEvent('promote.completed', { thingId, targetNs });
1010
+ return {
1011
+ newNs: targetNs,
1012
+ thingId,
1013
+ originalNs: this.ns,
1014
+ };
1015
+ }
1016
+ catch (error) {
1017
+ await this.emitEvent('promote.failed', { thingId, targetNs, error: error.message });
1018
+ throw error;
1019
+ }
1020
+ }
1021
+ /**
1022
+ * Demote this DO back into a parent DO as a Thing
1023
+ */
1024
+ async demote(targetNs, options) {
1025
+ if (!targetNs || typeof targetNs !== 'string' || targetNs.trim() === '') {
1026
+ throw new Error('targetNs is required for demote');
1027
+ }
1028
+ const { thingId: customThingId, preserveHistory = true, type, force = false, compress = false, mode = 'atomic', preserveId = false, } = options ?? {};
1029
+ // Validate URL format
1030
+ try {
1031
+ new URL(targetNs);
1032
+ }
1033
+ catch {
1034
+ throw new Error(`Invalid target URL: ${targetNs}`);
1035
+ }
1036
+ // Cannot demote to self
1037
+ if (targetNs === this.ns) {
1038
+ throw new Error('Cannot demote to self');
1039
+ }
1040
+ // Check if DO binding is available
1041
+ if (!this.env.DO) {
1042
+ throw new Error('DO binding is unavailable');
1043
+ }
1044
+ // Validate type if provided
1045
+ if (type) {
1046
+ if (!/^[A-Z][a-zA-Z0-9]*$/.test(type) || type.length > 25) {
1047
+ throw new Error(`Invalid type: "${type}"`);
1048
+ }
1049
+ }
1050
+ await this.emitEvent('demote.started', { targetNs, sourceNs: this.ns, mode });
1051
+ try {
1052
+ // Get all state from this DO
1053
+ const things = await this.db.select().from(schema.things);
1054
+ const actions = await this.db.select().from(schema.actions);
1055
+ const events = await this.db.select().from(schema.events);
1056
+ const relationships = await this.db.select().from(schema.relationships);
1057
+ const activeThings = things.filter(t => !t.deleted);
1058
+ // Generate new thing ID
1059
+ const newThingId = customThingId
1060
+ ? customThingId
1061
+ : preserveId
1062
+ ? this.ns.replace(/^https?:\/\//, '').replace(/\.do$/, '')
1063
+ : `demoted-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
1064
+ // Prepare transfer payload
1065
+ const transferPayload = {
1066
+ things: activeThings.length > 0 ? activeThings.map(t => ({
1067
+ ...t,
1068
+ id: t.id === 'root' ? newThingId : `${newThingId}/${t.id}`,
1069
+ })) : [{
1070
+ id: newThingId,
1071
+ type: 0,
1072
+ branch: null,
1073
+ name: `Demoted from ${this.ns}`,
1074
+ data: { $demotedFrom: this.ns },
1075
+ deleted: false,
1076
+ }],
1077
+ actions: compress ? [] : actions,
1078
+ events: compress ? [] : events,
1079
+ relationships: relationships.map(r => ({
1080
+ ...r,
1081
+ from: r.from?.replace(this.ns, `${targetNs}/Thing/${newThingId}`),
1082
+ to: r.to?.replace(this.ns, `${targetNs}/Thing/${newThingId}`),
1083
+ })),
1084
+ demotedFrom: {
1085
+ ns: this.ns,
1086
+ thingsCount: activeThings.length,
1087
+ compress,
1088
+ },
1089
+ };
1090
+ // Transfer to parent DO
1091
+ const parentId = this.env.DO.idFromName(targetNs);
1092
+ const parentStub = this.env.DO.get(parentId);
1093
+ const response = await parentStub.fetch(new Request(`${targetNs}/transfer`, {
1094
+ method: 'POST',
1095
+ headers: { 'Content-Type': 'application/json' },
1096
+ body: JSON.stringify(transferPayload),
1097
+ }));
1098
+ if (!response.ok) {
1099
+ throw new Error(`Transfer to ${targetNs} failed: ${response.status}`);
1100
+ }
1101
+ // Clear local state
1102
+ await this.ctx.storage.sql.exec('DELETE FROM things');
1103
+ await this.ctx.storage.sql.exec('DELETE FROM actions');
1104
+ await this.ctx.storage.sql.exec('DELETE FROM events');
1105
+ await this.ctx.storage.sql.exec('DELETE FROM relationships');
1106
+ await this.emitEvent('demote.completed', {
1107
+ thingId: newThingId,
1108
+ parentNs: targetNs,
1109
+ deletedNs: this.ns,
1110
+ });
1111
+ return {
1112
+ thingId: newThingId,
1113
+ parentNs: targetNs,
1114
+ deletedNs: this.ns,
1115
+ };
1116
+ }
1117
+ catch (error) {
1118
+ await this.emitEvent('demote.failed', { targetNs, error: error.message });
1119
+ throw error;
1120
+ }
1121
+ }
1122
+ // ═══════════════════════════════════════════════════════════════════════════
1123
+ // BRANCHING & VERSION CONTROL
1124
+ // ═══════════════════════════════════════════════════════════════════════════
1125
+ /**
1126
+ * Create a new branch at current HEAD
1127
+ */
1128
+ async branch(name) {
1129
+ if (!name || name.trim() === '') {
1130
+ throw new Error('Branch name cannot be empty');
1131
+ }
1132
+ if (name.includes(' ')) {
1133
+ throw new Error('Branch name cannot contain spaces');
1134
+ }
1135
+ if (name === 'main') {
1136
+ throw new Error('Cannot create branch named "main" - it is reserved');
1137
+ }
1138
+ // Check if branch already exists
1139
+ const branches = await this.db.select().from(schema.branches);
1140
+ if (branches.some(b => b.name === name)) {
1141
+ throw new Error(`Branch "${name}" already exists`);
1142
+ }
1143
+ // Find current HEAD
1144
+ const things = await this.db.select().from(schema.things);
1145
+ const currentBranchThings = things.filter(t => this.currentBranch === 'main' ? t.branch === null : t.branch === this.currentBranch);
1146
+ if (currentBranchThings.length === 0) {
1147
+ throw new Error('No commits on current branch');
1148
+ }
1149
+ const head = currentBranchThings.length;
1150
+ // Create branch record
1151
+ await this.db.insert(schema.branches).values({
1152
+ name,
1153
+ thingId: currentBranchThings[0].id,
1154
+ head,
1155
+ base: head,
1156
+ forkedFrom: this.currentBranch,
1157
+ description: null,
1158
+ createdAt: new Date(),
1159
+ updatedAt: new Date(),
1160
+ });
1161
+ await this.emitEvent('branch.created', { name, head, forkedFrom: this.currentBranch });
1162
+ return { name, head };
1163
+ }
1164
+ /**
1165
+ * Switch to a branch or version
1166
+ */
1167
+ async checkout(ref) {
1168
+ const things = await this.db.select().from(schema.things);
1169
+ let targetRef = ref.startsWith('@') ? ref.slice(1) : ref;
1170
+ // Check for version reference (@v1234)
1171
+ if (targetRef.startsWith('v')) {
1172
+ const version = parseInt(targetRef.slice(1), 10);
1173
+ if (version < 1 || version > things.length) {
1174
+ throw new Error(`Version not found: ${version}`);
1175
+ }
1176
+ this.currentVersion = version;
1177
+ await this.emitEvent('checkout', { version });
1178
+ return { version };
1179
+ }
1180
+ // Check for relative reference (@~N)
1181
+ if (targetRef.startsWith('~')) {
1182
+ const offset = parseInt(targetRef.slice(1), 10);
1183
+ const currentBranchThings = things.filter(t => this.currentBranch === 'main' ? t.branch === null : t.branch === this.currentBranch);
1184
+ if (offset >= currentBranchThings.length) {
1185
+ throw new Error(`Cannot go back ${offset} versions - only ${currentBranchThings.length} versions exist`);
1186
+ }
1187
+ const version = currentBranchThings.length - offset;
1188
+ this.currentVersion = version;
1189
+ await this.emitEvent('checkout', { version, relative: `~${offset}` });
1190
+ return { version };
1191
+ }
1192
+ // Branch reference
1193
+ const branchName = targetRef;
1194
+ if (branchName === 'main') {
1195
+ this.currentBranch = 'main';
1196
+ this.currentVersion = null;
1197
+ await this.emitEvent('checkout', { branch: 'main' });
1198
+ return { branch: 'main' };
1199
+ }
1200
+ const branches = await this.db.select().from(schema.branches);
1201
+ const branchExists = branches.some(b => b.name === branchName);
1202
+ const thingsOnBranch = things.filter(t => t.branch === branchName);
1203
+ if (!branchExists && thingsOnBranch.length === 0) {
1204
+ throw new Error(`Branch not found: ${branchName}`);
1205
+ }
1206
+ this.currentBranch = branchName;
1207
+ this.currentVersion = null;
1208
+ await this.emitEvent('checkout', { branch: branchName });
1209
+ return { branch: branchName };
1210
+ }
1211
+ /**
1212
+ * Merge a branch into current
1213
+ */
1214
+ async merge(branch) {
1215
+ if (this.currentVersion !== null) {
1216
+ throw new Error('Cannot merge into detached HEAD state');
1217
+ }
1218
+ if (branch === this.currentBranch || (branch === 'main' && this.currentBranch === 'main')) {
1219
+ throw new Error('Cannot merge branch into itself');
1220
+ }
1221
+ const things = await this.db.select().from(schema.things);
1222
+ const branches = await this.db.select().from(schema.branches);
1223
+ const sourceBranch = branches.find(b => b.name === branch);
1224
+ const sourceThings = things.filter(t => t.branch === branch);
1225
+ if (!sourceBranch && sourceThings.length === 0) {
1226
+ throw new Error(`Branch not found: ${branch}`);
1227
+ }
1228
+ await this.emitEvent('merge.started', { source: branch, target: this.currentBranch });
1229
+ const targetBranchFilter = this.currentBranch === 'main' ? null : this.currentBranch;
1230
+ const targetThings = things.filter(t => t.branch === targetBranchFilter);
1231
+ // Group by id
1232
+ const sourceById = new Map();
1233
+ for (const t of sourceThings) {
1234
+ const group = sourceById.get(t.id) || [];
1235
+ group.push(t);
1236
+ sourceById.set(t.id, group);
1237
+ }
1238
+ const targetById = new Map();
1239
+ for (const t of targetThings) {
1240
+ const group = targetById.get(t.id) || [];
1241
+ group.push(t);
1242
+ targetById.set(t.id, group);
1243
+ }
1244
+ // Detect conflicts
1245
+ const conflicts = [];
1246
+ const toMerge = [];
1247
+ for (const [id, sourceVersions] of sourceById) {
1248
+ const latestSource = sourceVersions[sourceVersions.length - 1];
1249
+ const targetVersions = targetById.get(id) || [];
1250
+ if (targetVersions.length === 0) {
1251
+ toMerge.push({
1252
+ ...latestSource,
1253
+ branch: targetBranchFilter,
1254
+ });
1255
+ }
1256
+ else {
1257
+ const latestTarget = targetVersions[targetVersions.length - 1];
1258
+ const sourceData = (latestSource.data || {});
1259
+ const targetData = (latestTarget.data || {});
1260
+ const baseVersion = targetVersions[0] || sourceVersions[0];
1261
+ const baseData = (baseVersion?.data || {});
1262
+ const sourceChanges = new Set();
1263
+ const targetChanges = new Set();
1264
+ for (const key of Object.keys(sourceData)) {
1265
+ if (JSON.stringify(sourceData[key]) !== JSON.stringify(baseData[key])) {
1266
+ sourceChanges.add(key);
1267
+ }
1268
+ }
1269
+ for (const key of Object.keys(targetData)) {
1270
+ if (JSON.stringify(targetData[key]) !== JSON.stringify(baseData[key])) {
1271
+ targetChanges.add(key);
1272
+ }
1273
+ }
1274
+ const conflictingFields = [];
1275
+ for (const field of sourceChanges) {
1276
+ if (targetChanges.has(field) &&
1277
+ JSON.stringify(sourceData[field]) !== JSON.stringify(targetData[field])) {
1278
+ conflictingFields.push(field);
1279
+ }
1280
+ }
1281
+ if (conflictingFields.length > 0) {
1282
+ conflicts.push(`${id}:${conflictingFields.join(',')}`);
1283
+ }
1284
+ else {
1285
+ const mergedData = { ...baseData };
1286
+ for (const field of sourceChanges) {
1287
+ mergedData[field] = sourceData[field];
1288
+ }
1289
+ for (const field of targetChanges) {
1290
+ mergedData[field] = targetData[field];
1291
+ }
1292
+ if (latestSource.deleted || latestTarget.deleted || Object.keys(mergedData).length > 0) {
1293
+ toMerge.push({
1294
+ ...latestTarget,
1295
+ data: mergedData,
1296
+ deleted: latestSource.deleted || latestTarget.deleted,
1297
+ });
1298
+ }
1299
+ }
1300
+ }
1301
+ }
1302
+ if (conflicts.length > 0) {
1303
+ await this.emitEvent('merge.conflict', { source: branch, conflicts });
1304
+ return { merged: false, conflicts };
1305
+ }
1306
+ // Apply merge
1307
+ for (const thing of toMerge) {
1308
+ await this.db.insert(schema.things).values({
1309
+ id: thing.id,
1310
+ type: thing.type,
1311
+ branch: thing.branch,
1312
+ name: thing.name,
1313
+ data: thing.data,
1314
+ deleted: thing.deleted,
1315
+ });
1316
+ }
1317
+ await this.emitEvent('merge.completed', { source: branch, target: this.currentBranch, merged: toMerge.length });
1318
+ return { merged: true };
1319
+ }
1320
+ // ═══════════════════════════════════════════════════════════════════════════
1321
+ // ALARM HANDLER (Extended)
1322
+ // ═══════════════════════════════════════════════════════════════════════════
1323
+ async alarm() {
1324
+ // Handle eventual clone syncing
1325
+ await this.handleEventualCloneAlarms();
1326
+ // Handle resumable clones
1327
+ await this.processResumableClones();
1328
+ // Call parent alarm handler for scheduling
1329
+ await super.alarm();
1330
+ }
1331
+ async handleEventualCloneAlarms() {
1332
+ const keys = await this.ctx.storage.list({ prefix: 'eventual:' });
1333
+ let nextAlarmTime = null;
1334
+ for (const [key, value] of keys) {
1335
+ const state = value;
1336
+ if (state.status === 'cancelled' || state.status === 'paused' || state.status === 'error') {
1337
+ continue;
1338
+ }
1339
+ const lastSync = state.lastSyncAt ? new Date(state.lastSyncAt).getTime() : 0;
1340
+ const now = Date.now();
1341
+ const nextSync = lastSync + state.syncInterval;
1342
+ const needsSync = now >= nextSync || state.divergence > state.maxDivergence;
1343
+ if (needsSync || state.status === 'pending') {
1344
+ await this.performEventualSync(state.id);
1345
+ const updatedState = await this.ctx.storage.get(`eventual:${state.id}`);
1346
+ if (updatedState && updatedState.status !== 'active' && updatedState.status !== 'cancelled' && updatedState.status !== 'error') {
1347
+ const nextSyncTime = Date.now() + updatedState.syncInterval;
1348
+ if (!nextAlarmTime || nextSyncTime < nextAlarmTime) {
1349
+ nextAlarmTime = nextSyncTime;
1350
+ }
1351
+ }
1352
+ }
1353
+ else {
1354
+ if (!nextAlarmTime || nextSync < nextAlarmTime) {
1355
+ nextAlarmTime = nextSync;
1356
+ }
1357
+ }
1358
+ }
1359
+ if (nextAlarmTime) {
1360
+ await this.ctx.storage.setAlarm(nextAlarmTime);
1361
+ }
1362
+ }
1363
+ async processResumableClones() {
1364
+ const storageKeys = await this.ctx.storage.list({ prefix: 'resumable:' });
1365
+ for (const [key, value] of storageKeys) {
1366
+ const cloneId = key.replace('resumable:', '');
1367
+ if (!this._resumableClones.has(cloneId)) {
1368
+ this._resumableClones.set(cloneId, value);
1369
+ }
1370
+ }
1371
+ for (const [cloneId, state] of this._resumableClones) {
1372
+ if (state.status === 'paused' || state.pauseRequested)
1373
+ continue;
1374
+ if (state.status === 'cancelled' || state.cancelRequested)
1375
+ continue;
1376
+ if (state.status === 'completed' || state.status === 'failed')
1377
+ continue;
1378
+ await this.processResumableCloneBatch(cloneId);
1379
+ }
1380
+ }
1381
+ async processResumableCloneBatch(cloneId) {
1382
+ const state = this._resumableClones.get(cloneId) || await this.ctx.storage.get(`resumable:${cloneId}`);
1383
+ if (!state)
1384
+ return;
1385
+ if (state.pauseRequested || state.status === 'paused')
1386
+ return;
1387
+ if (state.cancelRequested || state.status === 'cancelled')
1388
+ return;
1389
+ if (state.status === 'initializing') {
1390
+ state.status = 'transferring';
1391
+ state.startedAt = new Date();
1392
+ }
1393
+ try {
1394
+ const allThings = await this.db.select().from(schema.things);
1395
+ const nonDeletedThings = allThings.filter(t => !t.deleted);
1396
+ const totalItems = nonDeletedThings.length;
1397
+ if (state.totalBytes === 0) {
1398
+ state.totalBytes = nonDeletedThings.reduce((acc, t) => acc + JSON.stringify(t).length, 0);
1399
+ }
1400
+ const batch = nonDeletedThings.slice(state.position, state.position + state.batchSize);
1401
+ if (batch.length === 0) {
1402
+ state.status = 'completed';
1403
+ await this.ctx.storage.put(`resumable:${cloneId}`, state);
1404
+ this._resumableClones.set(cloneId, state);
1405
+ await this.releaseCloneLock(state.targetNs, cloneId);
1406
+ await this.emitEvent('clone.completed', { id: cloneId, totalItems: state.position });
1407
+ return;
1408
+ }
1409
+ // Transfer batch (simulated)
1410
+ state.position += batch.length;
1411
+ state.progress = Math.round((state.position / totalItems) * 100);
1412
+ state.bytesTransferred += batch.reduce((acc, t) => acc + JSON.stringify(t).length, 0);
1413
+ // Create checkpoint
1414
+ const batchNumber = Math.ceil(state.position / state.batchSize);
1415
+ if (batchNumber % state.checkpointInterval === 0) {
1416
+ const batchJson = JSON.stringify(batch);
1417
+ const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(batchJson));
1418
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
1419
+ const hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
1420
+ const checkpoint = {
1421
+ id: crypto.randomUUID(),
1422
+ position: state.position,
1423
+ hash,
1424
+ timestamp: new Date(),
1425
+ itemsProcessed: state.position,
1426
+ batchNumber,
1427
+ cloneId,
1428
+ compressed: state.compress,
1429
+ };
1430
+ state.checkpoints.push(checkpoint);
1431
+ await this.ctx.storage.put(`checkpoint:${checkpoint.id}`, checkpoint);
1432
+ await this.emitEvent('clone.checkpoint', { id: cloneId, checkpoint });
1433
+ }
1434
+ state.retryCount = 0;
1435
+ await this.ctx.storage.put(`resumable:${cloneId}`, state);
1436
+ this._resumableClones.set(cloneId, state);
1437
+ if (state.position < totalItems && !state.pauseRequested && !state.cancelRequested) {
1438
+ const currentAlarm = await this.ctx.storage.getAlarm();
1439
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
1440
+ await this.ctx.storage.setAlarm(Date.now() + 100);
1441
+ }
1442
+ }
1443
+ }
1444
+ catch (error) {
1445
+ state.retryCount++;
1446
+ await this.emitEvent('clone.retry', { id: cloneId, attempt: state.retryCount, error: error.message });
1447
+ if (state.retryCount >= state.maxRetries) {
1448
+ state.status = 'failed';
1449
+ await this.ctx.storage.put(`resumable:${cloneId}`, state);
1450
+ this._resumableClones.set(cloneId, state);
1451
+ await this.emitEvent('clone.failed', { id: cloneId, error: error.message });
1452
+ await this.releaseCloneLock(state.targetNs, cloneId);
1453
+ return;
1454
+ }
1455
+ const delay = state.retryDelay * Math.pow(2, state.retryCount - 1);
1456
+ await this.ctx.storage.put(`resumable:${cloneId}`, state);
1457
+ this._resumableClones.set(cloneId, state);
1458
+ const currentAlarm = await this.ctx.storage.getAlarm();
1459
+ if (!currentAlarm || currentAlarm > Date.now() + delay) {
1460
+ await this.ctx.storage.setAlarm(Date.now() + delay);
1461
+ }
1462
+ }
1463
+ }
1464
+ // ═══════════════════════════════════════════════════════════════════════════
1465
+ // CROSS-DO RESOLUTION (with circuit breaker)
1466
+ // ═══════════════════════════════════════════════════════════════════════════
1467
+ async resolveCrossDO(ns, path, ref) {
1468
+ // Check circuit breaker state
1469
+ const circuitState = this._circuitBreaker.get(ns);
1470
+ if (circuitState) {
1471
+ const now = Date.now();
1472
+ if (circuitState.state === 'open') {
1473
+ if (now >= circuitState.openUntil) {
1474
+ circuitState.state = 'half-open';
1475
+ circuitState.halfOpenTestInProgress = false;
1476
+ this._circuitBreaker.set(ns, circuitState);
1477
+ }
1478
+ else {
1479
+ throw new Error(`Circuit breaker open for namespace: ${ns}`);
1480
+ }
1481
+ }
1482
+ if (circuitState.state === 'half-open') {
1483
+ if (circuitState.halfOpenTestInProgress) {
1484
+ throw new Error('Circuit breaker in half-open test');
1485
+ }
1486
+ circuitState.halfOpenTestInProgress = true;
1487
+ this._circuitBreaker.set(ns, circuitState);
1488
+ }
1489
+ }
1490
+ const obj = await this.objects.get(ns);
1491
+ if (!obj) {
1492
+ throw new Error(`Unknown namespace: ${ns}`);
1493
+ }
1494
+ if (!this.env.DO) {
1495
+ throw new Error('DO namespace binding not configured');
1496
+ }
1497
+ const stub = this.getOrCreateStub(ns, obj.id);
1498
+ const resolveUrl = new URL(`${ns}/resolve`);
1499
+ resolveUrl.searchParams.set('path', path);
1500
+ resolveUrl.searchParams.set('ref', ref);
1501
+ try {
1502
+ const response = await stub.fetch(new Request(resolveUrl.toString(), {
1503
+ method: 'GET',
1504
+ headers: { 'Content-Type': 'application/json' },
1505
+ }));
1506
+ if (!response.ok) {
1507
+ this.recordFailure(ns);
1508
+ throw new Error(`Cross-DO resolution failed: ${response.status}`);
1509
+ }
1510
+ this._circuitBreaker.delete(ns);
1511
+ let thing;
1512
+ try {
1513
+ thing = await response.json();
1514
+ }
1515
+ catch {
1516
+ throw new Error('Invalid response from remote DO');
1517
+ }
1518
+ return thing;
1519
+ }
1520
+ catch (error) {
1521
+ if (error instanceof Error &&
1522
+ !error.message.startsWith('Invalid response') &&
1523
+ !error.message.startsWith('Cross-DO resolution failed')) {
1524
+ this.recordFailure(ns);
1525
+ }
1526
+ throw error;
1527
+ }
1528
+ }
1529
+ getOrCreateStub(ns, doId) {
1530
+ const now = Date.now();
1531
+ const cached = this._stubCache.get(ns);
1532
+ if (cached && now - cached.cachedAt < CROSS_DO_CONFIG.STUB_CACHE_TTL) {
1533
+ cached.lastUsed = now;
1534
+ return cached.stub;
1535
+ }
1536
+ const doNamespace = this.env.DO;
1537
+ const id = doNamespace.idFromString(doId);
1538
+ const stub = doNamespace.get(id);
1539
+ this.evictLRUStubs();
1540
+ this._stubCache.set(ns, { stub, cachedAt: now, lastUsed: now });
1541
+ return stub;
1542
+ }
1543
+ evictLRUStubs() {
1544
+ while (this._stubCache.size >= this._stubCacheMaxSize) {
1545
+ let lruNs = null;
1546
+ let lruLastUsed = Infinity;
1547
+ for (const [ns, entry] of this._stubCache) {
1548
+ if (entry.lastUsed < lruLastUsed) {
1549
+ lruLastUsed = entry.lastUsed;
1550
+ lruNs = ns;
1551
+ }
1552
+ }
1553
+ if (lruNs) {
1554
+ this._stubCache.delete(lruNs);
1555
+ }
1556
+ else {
1557
+ break;
1558
+ }
1559
+ }
1560
+ }
1561
+ recordFailure(ns) {
1562
+ const state = this._circuitBreaker.get(ns) || { failures: 0, openUntil: 0, state: 'closed' };
1563
+ state.failures++;
1564
+ if (state.failures >= CROSS_DO_CONFIG.CIRCUIT_BREAKER_THRESHOLD) {
1565
+ state.state = 'open';
1566
+ state.openUntil = Date.now() + CROSS_DO_CONFIG.CIRCUIT_BREAKER_TIMEOUT;
1567
+ state.failures = 0;
1568
+ this._stubCache.delete(ns);
1569
+ }
1570
+ if (state.state === 'half-open') {
1571
+ state.state = 'open';
1572
+ state.openUntil = Date.now() + CROSS_DO_CONFIG.CIRCUIT_BREAKER_TIMEOUT;
1573
+ state.halfOpenTestInProgress = false;
1574
+ }
1575
+ this._circuitBreaker.set(ns, state);
1576
+ }
1577
+ clearCrossDoCache(ns) {
1578
+ if (ns) {
1579
+ this._stubCache.delete(ns);
1580
+ this._circuitBreaker.delete(ns);
1581
+ }
1582
+ else {
1583
+ this._stubCache.clear();
1584
+ this._circuitBreaker.clear();
1585
+ }
1586
+ }
1587
+ // ═══════════════════════════════════════════════════════════════════════════
1588
+ // EVENT EMISSION (protected for this module)
1589
+ // ═══════════════════════════════════════════════════════════════════════════
1590
+ async emitEvent(verb, data) {
1591
+ try {
1592
+ await this.db.insert(schema.events).values({
1593
+ id: crypto.randomUUID(),
1594
+ verb,
1595
+ source: this.ns,
1596
+ data: data,
1597
+ sequence: 0,
1598
+ streamed: false,
1599
+ createdAt: new Date(),
1600
+ });
1601
+ }
1602
+ catch {
1603
+ // Best-effort database insert
1604
+ }
1605
+ if (this.env.PIPELINE) {
1606
+ try {
1607
+ await this.env.PIPELINE.send([{
1608
+ verb,
1609
+ source: this.ns,
1610
+ $context: this.ns,
1611
+ data,
1612
+ timestamp: new Date().toISOString(),
1613
+ }]);
1614
+ }
1615
+ catch {
1616
+ // Best-effort streaming
1617
+ }
1618
+ }
1619
+ }
1620
+ // ═══════════════════════════════════════════════════════════════════════════
1621
+ // SHARD OPERATIONS
1622
+ // ═══════════════════════════════════════════════════════════════════════════
1623
+ /**
1624
+ * Shard this DO into multiple DOs for horizontal scaling
1625
+ *
1626
+ * @param options Sharding configuration
1627
+ * @returns Shard result with shard endpoints and distribution stats
1628
+ */
1629
+ async shard(options) {
1630
+ return this.shardModule.shard(options);
1631
+ }
1632
+ /**
1633
+ * Unshard (merge) sharded DOs back into one
1634
+ *
1635
+ * @param options Unshard configuration
1636
+ */
1637
+ async unshard(options) {
1638
+ return this.shardModule.unshard(options);
1639
+ }
1640
+ /**
1641
+ * Check if this DO is sharded
1642
+ *
1643
+ * @returns True if the DO is sharded
1644
+ */
1645
+ async isSharded() {
1646
+ return this.shardModule.isSharded();
1647
+ }
1648
+ /**
1649
+ * Discover shards in this shard set
1650
+ *
1651
+ * @returns Registry and health status of all shards
1652
+ */
1653
+ async discoverShards() {
1654
+ return this.shardModule.discoverShards();
1655
+ }
1656
+ /**
1657
+ * Query across all shards
1658
+ *
1659
+ * @param options Query configuration
1660
+ * @returns Aggregated results from all shards
1661
+ */
1662
+ async queryShards(options) {
1663
+ return this.shardModule.queryShards(options);
1664
+ }
1665
+ /**
1666
+ * Rebalance shards (add/remove shards or redistribute data)
1667
+ *
1668
+ * @param options Rebalance configuration
1669
+ * @returns Rebalance result with stats
1670
+ */
1671
+ async rebalanceShards(options) {
1672
+ return this.shardModule.rebalanceShards(options);
1673
+ }
1674
+ }
1675
+ export default DO;
1676
+ //# sourceMappingURL=DOFull.js.map