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,1499 @@
1
+ /**
2
+ * Clone Lifecycle Module
3
+ *
4
+ * Handles all clone operations for Durable Objects:
5
+ * - Atomic clone: All-or-nothing clone operation
6
+ * - Staged clone: Two-phase commit with prepare/commit
7
+ * - Eventual clone: Background async clone with eventual consistency
8
+ * - Resumable clone: Checkpoint-based clone that can be resumed
9
+ */
10
+ import * as schema from '../../db';
11
+ // Storage prefixes
12
+ const STAGING_PREFIX = 'staging:';
13
+ const CHECKPOINT_PREFIX = 'checkpoint:';
14
+ const TWO_PC_PREFIX = '2pc:';
15
+ const DEFAULT_TOKEN_TIMEOUT = 5 * 60 * 1000; // 5 minutes
16
+ const DEFAULT_COORDINATOR_TIMEOUT = 30000; // 30 seconds
17
+ const DEFAULT_ACK_TIMEOUT = 10000; // 10 seconds
18
+ const DEFAULT_MAX_RETRIES = 3;
19
+ /**
20
+ * Clone lifecycle module implementing Strategy pattern.
21
+ */
22
+ export class CloneModule {
23
+ ctx;
24
+ // In-memory state for clone operations
25
+ _conflictResolvers = new Map();
26
+ _completionCallbacks = new Map();
27
+ _errorCallbacks = new Map();
28
+ _resumableClones = new Map();
29
+ _cloneLocks = new Map();
30
+ _broadcastCallbacks = [];
31
+ initialize(context) {
32
+ this.ctx = context;
33
+ }
34
+ // ═══════════════════════════════════════════════════════════════════════════
35
+ // MAIN CLONE METHOD
36
+ // ═══════════════════════════════════════════════════════════════════════════
37
+ /**
38
+ * Clone current state to a new DO with configurable modes.
39
+ */
40
+ async clone(target, options, validColos) {
41
+ const { mode, includeHistory = false, includeState = true, shallow = false, transform, branch: targetBranch, version: targetVersion, colo, timeout = 30000, correlationId = crypto.randomUUID(), } = options;
42
+ // Validate mode first
43
+ const validModes = ['atomic', 'staged', 'eventual', 'resumable'];
44
+ if (!validModes.includes(mode)) {
45
+ throw new Error(`Invalid mode: '${mode}' is not a valid clone mode`);
46
+ }
47
+ // Handle eventual mode
48
+ if (mode === 'eventual') {
49
+ return this.initiateEventualClone(target, options);
50
+ }
51
+ // Handle staged mode (two-phase commit)
52
+ if (mode === 'staged') {
53
+ return this.prepareStagedClone(target, options);
54
+ }
55
+ // Handle resumable mode (checkpoint-based)
56
+ if (mode === 'resumable') {
57
+ return this.initiateResumableClone(target, options);
58
+ }
59
+ const startTime = Date.now();
60
+ // === VALIDATION ===
61
+ // Validate options
62
+ if (typeof timeout !== 'number' || timeout < 0) {
63
+ throw new Error('Invalid timeout: must be a non-negative number');
64
+ }
65
+ // Validate colo code if provided
66
+ if (colo && !validColos.has(colo)) {
67
+ throw new Error(`Invalid colo: '${colo}' is not a valid location code`);
68
+ }
69
+ // Validate target namespace URL format
70
+ try {
71
+ const url = new URL(target);
72
+ if (!['http:', 'https:'].includes(url.protocol)) {
73
+ throw new Error('Invalid namespace URL: must use http or https protocol');
74
+ }
75
+ }
76
+ catch (e) {
77
+ if (e.message?.includes('Invalid namespace URL')) {
78
+ throw e;
79
+ }
80
+ throw new Error(`Invalid namespace URL: ${target}`);
81
+ }
82
+ // Prevent cloning to same namespace
83
+ if (target === this.ctx.ns) {
84
+ throw new Error('Cannot clone to same namespace');
85
+ }
86
+ // Validate branch if specified
87
+ if (targetBranch) {
88
+ const branches = await this.ctx.db.select().from(schema.branches);
89
+ const branchExists = branches.some((b) => b.name === targetBranch);
90
+ if (!branchExists) {
91
+ throw new Error(`Branch not found: '${targetBranch}'`);
92
+ }
93
+ }
94
+ // Validate version if specified
95
+ if (targetVersion !== undefined) {
96
+ if (targetVersion < 0 || !Number.isInteger(targetVersion)) {
97
+ throw new Error(`Invalid version: ${targetVersion}`);
98
+ }
99
+ const branches = await this.ctx.db.select().from(schema.branches);
100
+ const mainBranch = branches.find((b) => b.name === 'main' || b.name === null);
101
+ const allThings = await this.ctx.db.select().from(schema.things);
102
+ const maxVersion = Math.max(allThings.length, mainBranch?.head ?? 0);
103
+ if (targetVersion > maxVersion) {
104
+ throw new Error(`Version not found: ${targetVersion}`);
105
+ }
106
+ }
107
+ // Get things to validate source has state
108
+ let things = await this.ctx.db.select().from(schema.things);
109
+ if (targetBranch) {
110
+ things = things.filter((t) => t.branch === targetBranch);
111
+ }
112
+ const nonDeletedThings = things.filter((t) => !t.deleted);
113
+ if (includeState && nonDeletedThings.length === 0) {
114
+ throw new Error('No state to clone: source is empty');
115
+ }
116
+ const relationships = await this.ctx.db.select().from(schema.relationships);
117
+ // Acquire exclusive lock using blockConcurrencyWhile
118
+ return this.ctx.ctx.blockConcurrencyWhile(async () => {
119
+ try {
120
+ await this.ctx.emitEvent('clone.started', {
121
+ target,
122
+ mode,
123
+ correlationId,
124
+ thingsCount: nonDeletedThings.length,
125
+ });
126
+ if (!this.ctx.env.DO) {
127
+ throw new Error('DO namespace not configured');
128
+ }
129
+ const targetUrl = new URL(target);
130
+ const doId = this.ctx.env.DO.idFromName(target);
131
+ const stub = this.ctx.env.DO.get(doId);
132
+ // Health check
133
+ try {
134
+ const healthResponse = await Promise.race([
135
+ stub.fetch(new Request(`${targetUrl.origin}/health`)),
136
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), Math.min(timeout, 5000))),
137
+ ]);
138
+ if (!healthResponse.ok && healthResponse.status !== 404) {
139
+ if (healthResponse.status === 409) {
140
+ throw new Error('Target already exists: conflict detected');
141
+ }
142
+ throw new Error(`Target health check failed: ${healthResponse.status}`);
143
+ }
144
+ }
145
+ catch (e) {
146
+ const errorMessage = e.message || String(e);
147
+ if (errorMessage.includes('Connection refused') ||
148
+ errorMessage.includes('unreachable') ||
149
+ errorMessage.includes('Health check')) {
150
+ throw new Error(`Target unreachable: health check failed - ${errorMessage}`);
151
+ }
152
+ throw e;
153
+ }
154
+ // Get latest version of each thing
155
+ const latestVersions = new Map();
156
+ if (includeState) {
157
+ for (const thing of nonDeletedThings) {
158
+ const existing = latestVersions.get(thing.id);
159
+ if (!existing) {
160
+ latestVersions.set(thing.id, thing);
161
+ }
162
+ }
163
+ }
164
+ // Prepare data for transfer
165
+ const thingsToClone = includeState
166
+ ? Array.from(latestVersions.values()).map((t) => ({
167
+ id: t.id,
168
+ type: t.type,
169
+ branch: t.branch,
170
+ name: t.name,
171
+ data: t.data,
172
+ deleted: false,
173
+ }))
174
+ : [];
175
+ const relationshipsToClone = shallow
176
+ ? []
177
+ : relationships.map((r) => ({
178
+ id: r.id,
179
+ verb: r.verb,
180
+ from: r.from,
181
+ to: r.to,
182
+ data: r.data,
183
+ createdAt: r.createdAt,
184
+ }));
185
+ let actionsToClone = [];
186
+ let eventsToClone = [];
187
+ if (includeHistory) {
188
+ const actions = await this.ctx.db.select().from(schema.actions);
189
+ actionsToClone = actions;
190
+ const events = await this.ctx.db.select().from(schema.events);
191
+ eventsToClone = events;
192
+ }
193
+ // Apply transform function if provided
194
+ let finalThingsToClone = thingsToClone;
195
+ let finalRelationshipsToClone = relationshipsToClone;
196
+ if (transform) {
197
+ try {
198
+ const stateForTransform = {
199
+ things: thingsToClone.map((t) => ({
200
+ id: t.id,
201
+ type: t.type,
202
+ branch: t.branch,
203
+ name: t.name,
204
+ data: t.data,
205
+ deleted: t.deleted,
206
+ })),
207
+ relationships: relationshipsToClone.map((r) => ({
208
+ id: r.id,
209
+ verb: r.verb,
210
+ from: r.from,
211
+ to: r.to,
212
+ data: r.data,
213
+ })),
214
+ };
215
+ const transformedState = await Promise.resolve(transform(stateForTransform));
216
+ finalThingsToClone = transformedState.things.map((t) => ({
217
+ id: t.id,
218
+ type: t.type,
219
+ branch: t.branch,
220
+ name: t.name,
221
+ data: t.data,
222
+ deleted: t.deleted,
223
+ }));
224
+ finalRelationshipsToClone = (transformedState.relationships || []).map((r) => ({
225
+ id: r.id,
226
+ verb: r.verb,
227
+ from: r.from,
228
+ to: r.to,
229
+ data: r.data,
230
+ createdAt: new Date(),
231
+ }));
232
+ }
233
+ catch (transformError) {
234
+ throw new Error(`Transform error: ${transformError.message}`);
235
+ }
236
+ }
237
+ // Step 1: Initialize target
238
+ const initResponse = await Promise.race([
239
+ stub.fetch(new Request(`${targetUrl.origin}/init`, {
240
+ method: 'POST',
241
+ headers: { 'Content-Type': 'application/json' },
242
+ body: JSON.stringify({
243
+ correlationId,
244
+ mode: 'atomic',
245
+ }),
246
+ })),
247
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Clone timeout after ${timeout}ms`)), timeout)),
248
+ ]);
249
+ if (!initResponse.ok) {
250
+ throw new Error(`Init failed: ${initResponse.status} ${initResponse.statusText}`);
251
+ }
252
+ // Step 2: Transfer data to target
253
+ const response = await Promise.race([
254
+ stub.fetch(new Request(`${targetUrl.origin}/transfer`, {
255
+ method: 'POST',
256
+ headers: { 'Content-Type': 'application/json' },
257
+ body: JSON.stringify({
258
+ things: finalThingsToClone,
259
+ relationships: finalRelationshipsToClone,
260
+ actions: includeHistory ? actionsToClone : undefined,
261
+ events: includeHistory ? eventsToClone : undefined,
262
+ correlationId,
263
+ }),
264
+ })),
265
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Clone timeout after ${timeout}ms`)), timeout)),
266
+ ]);
267
+ if (!response.ok) {
268
+ throw new Error(`Transfer failed: ${response.status} ${response.statusText}`);
269
+ }
270
+ const duration = Date.now() - startTime;
271
+ await this.ctx.emitEvent('clone.completed', {
272
+ target,
273
+ doId: doId.toString(),
274
+ correlationId,
275
+ thingsCount: finalThingsToClone.length,
276
+ duration,
277
+ });
278
+ return {
279
+ success: true,
280
+ ns: target,
281
+ doId: doId.toString(),
282
+ mode: 'atomic',
283
+ thingsCloned: finalThingsToClone.length,
284
+ relationshipsCloned: finalRelationshipsToClone.length,
285
+ duration,
286
+ historyIncluded: includeHistory,
287
+ };
288
+ }
289
+ catch (error) {
290
+ const errorMessage = error.message || String(error);
291
+ await this.ctx.emitEvent('clone.failed', {
292
+ target,
293
+ error: errorMessage,
294
+ correlationId,
295
+ });
296
+ await this.ctx.emitEvent('clone.rollback', {
297
+ target,
298
+ reason: errorMessage,
299
+ correlationId,
300
+ });
301
+ throw error;
302
+ }
303
+ });
304
+ }
305
+ // ═══════════════════════════════════════════════════════════════════════════
306
+ // STAGED CLONE OPERATIONS (TWO-PHASE COMMIT)
307
+ // ═══════════════════════════════════════════════════════════════════════════
308
+ /**
309
+ * Prepare a staged clone (Phase 1 of two-phase commit)
310
+ */
311
+ async prepareStagedClone(target, options) {
312
+ const token = crypto.randomUUID();
313
+ const timeout = options.tokenTimeout ?? DEFAULT_TOKEN_TIMEOUT;
314
+ const expiresAt = new Date(Date.now() + timeout);
315
+ const stagingNs = `${target}-staging-${token.slice(0, 8)}`;
316
+ options.onPrepareProgress?.(0);
317
+ try {
318
+ new URL(target);
319
+ }
320
+ catch {
321
+ throw new Error(`Invalid namespace URL: ${target}`);
322
+ }
323
+ if (options.validateTarget) {
324
+ const existingObjects = await this.ctx.db.select().from(schema.objects);
325
+ const occupied = existingObjects.some((obj) => obj.ns === target && obj.primary);
326
+ if (occupied) {
327
+ throw new Error(`Target namespace is occupied: ${target}`);
328
+ }
329
+ }
330
+ // Check for concurrent staging
331
+ const existingStagings = await this.ctx.ctx.storage.list({ prefix: STAGING_PREFIX });
332
+ for (const [, value] of existingStagings) {
333
+ const staging = value;
334
+ if (staging.targetNs === target && staging.status === 'prepared') {
335
+ if (new Date(staging.expiresAt) > new Date()) {
336
+ throw new Error(`Target namespace is locked by pending clone operation`);
337
+ }
338
+ }
339
+ }
340
+ await this.ctx.emitEvent('clone.staging.started', { token, target });
341
+ // Get things to clone
342
+ const things = await this.ctx.db.select().from(schema.things);
343
+ const branchThings = things.filter((t) => !t.deleted && (t.branch === null || t.branch === this.ctx.currentBranch));
344
+ if (branchThings.length === 0) {
345
+ throw new Error('No state to clone: source is empty');
346
+ }
347
+ // Get latest version of each thing
348
+ const latestVersions = new Map();
349
+ for (const thing of branchThings) {
350
+ if (!latestVersions.has(thing.id)) {
351
+ latestVersions.set(thing.id, thing);
352
+ }
353
+ }
354
+ const thingsToClone = Array.from(latestVersions.values());
355
+ const totalItems = thingsToClone.length;
356
+ // Create checkpoints
357
+ const checkpoints = [];
358
+ const checkpointInterval = options.checkpointInterval ?? 0;
359
+ const maxCheckpoints = options.maxCheckpoints ?? 100;
360
+ let itemsProcessed = 0;
361
+ const clonedThingIds = [];
362
+ const clonedRelationshipIds = [];
363
+ for (const thing of thingsToClone) {
364
+ clonedThingIds.push(thing.id);
365
+ itemsProcessed++;
366
+ if (checkpointInterval > 0 && itemsProcessed % checkpointInterval === 0) {
367
+ const checkpoint = this.createStagedCheckpoint(token, checkpoints.length + 1, itemsProcessed, totalItems, clonedThingIds.slice(), clonedRelationshipIds.slice(), this.ctx.currentBranch, itemsProcessed, options.validationMode === 'strict');
368
+ checkpoints.push(checkpoint);
369
+ while (checkpoints.length > maxCheckpoints) {
370
+ checkpoints.shift();
371
+ }
372
+ }
373
+ const progress = Math.floor((itemsProcessed / totalItems) * 100);
374
+ options.onPrepareProgress?.(progress);
375
+ }
376
+ // Final checkpoint
377
+ if (checkpointInterval > 0 && itemsProcessed > 0 && itemsProcessed % checkpointInterval !== 0) {
378
+ const finalCheckpoint = this.createStagedCheckpoint(token, checkpoints.length + 1, itemsProcessed, totalItems, clonedThingIds.slice(), clonedRelationshipIds.slice(), this.ctx.currentBranch, itemsProcessed, options.validationMode === 'strict');
379
+ checkpoints.push(finalCheckpoint);
380
+ while (checkpoints.length > maxCheckpoints) {
381
+ checkpoints.shift();
382
+ }
383
+ }
384
+ const sizeBytes = JSON.stringify(thingsToClone).length;
385
+ const mappedThings = thingsToClone.map((t) => ({
386
+ id: t.id,
387
+ type: t.type,
388
+ branch: t.branch,
389
+ name: t.name,
390
+ data: t.data,
391
+ deleted: t.deleted ?? false,
392
+ }));
393
+ const stagingData = {
394
+ sourceNs: this.ctx.ns,
395
+ targetNs: target,
396
+ stagingNs,
397
+ things: mappedThings,
398
+ expiresAt: expiresAt.toISOString(),
399
+ status: 'prepared',
400
+ createdAt: new Date().toISOString(),
401
+ integrityHash: this.computeStagingIntegrityHash(mappedThings),
402
+ metadata: {
403
+ thingsCount: thingsToClone.length,
404
+ sizeBytes,
405
+ branch: this.ctx.currentBranch,
406
+ version: thingsToClone.length,
407
+ },
408
+ };
409
+ await this.ctx.ctx.storage.put(`${STAGING_PREFIX}${token}`, stagingData);
410
+ for (const checkpoint of checkpoints) {
411
+ await this.ctx.ctx.storage.put(`${CHECKPOINT_PREFIX}${token}:${checkpoint.id}`, checkpoint);
412
+ }
413
+ // Store 2PC prepare decision
414
+ await this.ctx.ctx.storage.put(`${TWO_PC_PREFIX}prepare:${token}`, {
415
+ phase: 'prepared',
416
+ target,
417
+ createdAt: new Date(),
418
+ expiresAt,
419
+ });
420
+ // Store 2PC config
421
+ await this.ctx.ctx.storage.put(`${TWO_PC_PREFIX}config:${token}`, {
422
+ coordinatorTimeout: options.coordinatorTimeout ?? DEFAULT_COORDINATOR_TIMEOUT,
423
+ participantAckTimeout: options.participantAckTimeout ?? DEFAULT_ACK_TIMEOUT,
424
+ maxAckRetries: options.maxAckRetries ?? DEFAULT_MAX_RETRIES,
425
+ prepareTimeout: options.prepareTimeout,
426
+ commitTimeout: options.commitTimeout,
427
+ abortTimeout: options.abortTimeout,
428
+ });
429
+ // Initialize audit log
430
+ const auditLog = {
431
+ token,
432
+ sourceNs: this.ctx.ns,
433
+ targetNs: [target],
434
+ events: [
435
+ {
436
+ type: 'prepare',
437
+ status: 'completed',
438
+ timestamp: new Date(),
439
+ data: { thingsCount: thingsToClone.length, sizeBytes },
440
+ },
441
+ ],
442
+ createdAt: new Date(),
443
+ };
444
+ await this.ctx.ctx.storage.put(`${TWO_PC_PREFIX}audit:${token}`, auditLog);
445
+ // Initialize participant history
446
+ const participantHistory = {
447
+ target,
448
+ transitions: [{ from: 'initial', to: 'prepared', timestamp: new Date() }],
449
+ };
450
+ await this.ctx.ctx.storage.put(`${TWO_PC_PREFIX}history:${token}:${target}`, participantHistory);
451
+ options.onPrepareProgress?.(100);
452
+ await this.ctx.emitEvent('clone.staging.completed', { token, target, thingsCount: thingsToClone.length });
453
+ await this.ctx.emitEvent('clone.prepared', { token, target, expiresAt });
454
+ const participantAck = {
455
+ target,
456
+ status: 'ready',
457
+ vote: 'yes',
458
+ timestamp: new Date(),
459
+ };
460
+ return {
461
+ phase: 'prepared',
462
+ token,
463
+ expiresAt,
464
+ stagingNs,
465
+ metadata: {
466
+ thingsCount: thingsToClone.length,
467
+ sizeBytes,
468
+ branch: this.ctx.currentBranch,
469
+ version: thingsToClone.length,
470
+ },
471
+ participantAck,
472
+ };
473
+ }
474
+ /**
475
+ * Commit a staged clone (Phase 2 of two-phase commit)
476
+ */
477
+ async commitClone(token, options) {
478
+ if (!token || token.trim() === '') {
479
+ throw new Error('Invalid token: token is empty');
480
+ }
481
+ const staging = (await this.ctx.ctx.storage.get(`${STAGING_PREFIX}${token}`));
482
+ if (!staging) {
483
+ await this.ctx.emitEvent('clone.commit.failed', { token, reason: 'Invalid or not found' });
484
+ throw new Error('Invalid or not found: staging token');
485
+ }
486
+ if (staging.status === 'committed') {
487
+ await this.ctx.emitEvent('clone.commit.failed', { token, reason: 'Already committed' });
488
+ throw new Error('Clone already committed');
489
+ }
490
+ if (staging.status === 'aborted') {
491
+ await this.ctx.emitEvent('clone.commit.failed', { token, reason: 'Already aborted' });
492
+ throw new Error('Clone was aborted');
493
+ }
494
+ if (new Date(staging.expiresAt) < new Date()) {
495
+ await this.ctx.emitEvent('clone.commit.failed', { token, reason: 'Token expired' });
496
+ throw new Error('Token expired');
497
+ }
498
+ // Verify integrity
499
+ const currentHash = this.computeStagingIntegrityHash(staging.things);
500
+ if (currentHash !== staging.integrityHash) {
501
+ await this.ctx.emitEvent('clone.staging.corrupted', { token, target: staging.targetNs });
502
+ await this.ctx.emitEvent('clone.commit.failed', { token, reason: 'Integrity check failed' });
503
+ throw new Error('Staging data corrupted: integrity check failed');
504
+ }
505
+ // Validate checkpoints
506
+ const checkpointKeys = await this.ctx.ctx.storage.list({ prefix: `${CHECKPOINT_PREFIX}${token}:` });
507
+ for (const [, value] of checkpointKeys) {
508
+ const checkpoint = value;
509
+ const expectedChecksum = this.computeCheckpointChecksum(checkpoint.state);
510
+ if (checkpoint.checksum !== expectedChecksum) {
511
+ await this.ctx.emitEvent('clone.commit.failed', { token, reason: 'Checkpoint validation failed' });
512
+ throw new Error('Checkpoint validation failed: checksum mismatch');
513
+ }
514
+ }
515
+ await this.ctx.emitEvent('clone.commit.started', { token, target: staging.targetNs });
516
+ // Persist coordinator decision
517
+ const commitDecision = {
518
+ decision: 'commit',
519
+ startedAt: new Date(),
520
+ completedAt: null,
521
+ };
522
+ await this.ctx.ctx.storage.put(`${TWO_PC_PREFIX}decision:${token}`, commitDecision);
523
+ // Broadcast commit
524
+ for (const callback of this._broadcastCallbacks) {
525
+ callback('commit', staging.targetNs);
526
+ }
527
+ // Update audit log
528
+ const auditLog = (await this.ctx.ctx.storage.get(`${TWO_PC_PREFIX}audit:${token}`));
529
+ if (auditLog) {
530
+ auditLog.events.push({ type: 'commit', status: 'started', timestamp: new Date() }, { type: 'broadcast', status: 'completed', timestamp: new Date(), participant: staging.targetNs });
531
+ await this.ctx.ctx.storage.put(`${TWO_PC_PREFIX}audit:${token}`, auditLog);
532
+ }
533
+ // Create target DO and transfer state
534
+ if (!this.ctx.env.DO) {
535
+ throw new Error('DO namespace not configured');
536
+ }
537
+ const doId = this.ctx.env.DO.idFromName(staging.targetNs);
538
+ const stub = this.ctx.env.DO.get(doId);
539
+ await stub.fetch(new Request(`https://${staging.targetNs}/init`, {
540
+ method: 'POST',
541
+ body: JSON.stringify({ things: staging.things }),
542
+ }));
543
+ const committedAt = new Date();
544
+ commitDecision.completedAt = committedAt;
545
+ await this.ctx.ctx.storage.put(`${TWO_PC_PREFIX}decision:${token}`, commitDecision);
546
+ // Store tombstone
547
+ const tombstone = { ...staging, things: [], status: 'committed' };
548
+ await this.ctx.ctx.storage.put(`${STAGING_PREFIX}${token}`, tombstone);
549
+ // Clean up checkpoints
550
+ for (const [key] of checkpointKeys) {
551
+ await this.ctx.ctx.storage.delete(key);
552
+ }
553
+ // Update participant history
554
+ const historyKey = `${TWO_PC_PREFIX}history:${token}:${staging.targetNs}`;
555
+ const history = (await this.ctx.ctx.storage.get(historyKey));
556
+ if (history) {
557
+ history.transitions.push({ from: 'prepared', to: 'committed', timestamp: committedAt });
558
+ await this.ctx.ctx.storage.put(historyKey, history);
559
+ }
560
+ // Update audit log
561
+ if (auditLog) {
562
+ auditLog.events.push({ type: 'commit', status: 'completed', timestamp: committedAt }, { type: 'participant_ack', status: 'completed', timestamp: committedAt, participant: staging.targetNs });
563
+ auditLog.outcome = 'committed';
564
+ await this.ctx.ctx.storage.put(`${TWO_PC_PREFIX}audit:${token}`, auditLog);
565
+ }
566
+ await this.ctx.emitEvent('clone.committed', {
567
+ token,
568
+ target: staging.targetNs,
569
+ result: { ns: staging.targetNs, doId: doId.toString(), mode: 'staged' },
570
+ });
571
+ const participantAcks = [
572
+ { target: staging.targetNs, status: 'committed', timestamp: committedAt },
573
+ ];
574
+ return {
575
+ phase: 'committed',
576
+ result: {
577
+ ns: staging.targetNs,
578
+ doId: doId.toString(),
579
+ mode: 'staged',
580
+ staged: { prepareId: token, committed: true },
581
+ },
582
+ committedAt,
583
+ participantAcks,
584
+ };
585
+ }
586
+ /**
587
+ * Abort a staged clone
588
+ */
589
+ async abortClone(token, reason) {
590
+ const staging = (await this.ctx.ctx.storage.get(`${STAGING_PREFIX}${token}`));
591
+ const abortedAt = new Date();
592
+ if (staging) {
593
+ const tombstone = { ...staging, things: [], status: 'aborted' };
594
+ await this.ctx.ctx.storage.put(`${STAGING_PREFIX}${token}`, tombstone);
595
+ const checkpointKeys = await this.ctx.ctx.storage.list({ prefix: `${CHECKPOINT_PREFIX}${token}:` });
596
+ for (const [key] of checkpointKeys) {
597
+ await this.ctx.ctx.storage.delete(key);
598
+ }
599
+ await this.ctx.emitEvent('clone.aborted', { token, target: staging.targetNs, reason });
600
+ }
601
+ return { phase: 'aborted', token, reason, abortedAt };
602
+ }
603
+ // ═══════════════════════════════════════════════════════════════════════════
604
+ // STAGED CLONE HELPERS
605
+ // ═══════════════════════════════════════════════════════════════════════════
606
+ createStagedCheckpoint(cloneId, sequence, itemsProcessed, totalItems, clonedThingIds, clonedRelationshipIds, branch, lastVersion, validated) {
607
+ const state = { clonedThingIds, clonedRelationshipIds, branch, lastVersion };
608
+ const checksum = this.computeCheckpointChecksum(state);
609
+ return {
610
+ id: `cp-${cloneId.slice(0, 8)}-${sequence}`,
611
+ cloneId,
612
+ sequence,
613
+ itemsProcessed,
614
+ totalItems,
615
+ createdAt: new Date(),
616
+ checksum,
617
+ state,
618
+ validated,
619
+ };
620
+ }
621
+ computeCheckpointChecksum(state) {
622
+ const content = JSON.stringify(state);
623
+ let hash = 0;
624
+ for (let i = 0; i < content.length; i++) {
625
+ const char = content.charCodeAt(i);
626
+ hash = ((hash << 5) - hash) + char;
627
+ hash = hash & hash;
628
+ }
629
+ return Math.abs(hash).toString(36);
630
+ }
631
+ computeStagingIntegrityHash(things) {
632
+ const content = JSON.stringify(things);
633
+ let hash = 0;
634
+ for (let i = 0; i < content.length; i++) {
635
+ const char = content.charCodeAt(i);
636
+ hash = ((hash << 5) - hash) + char;
637
+ hash = hash & hash;
638
+ }
639
+ return Math.abs(hash).toString(36);
640
+ }
641
+ // ═══════════════════════════════════════════════════════════════════════════
642
+ // STAGING STATUS METHODS
643
+ // ═══════════════════════════════════════════════════════════════════════════
644
+ async getStagingStatus(stagingNs) {
645
+ const allStagings = await this.ctx.ctx.storage.list({ prefix: STAGING_PREFIX });
646
+ for (const [key, value] of allStagings) {
647
+ const staging = value;
648
+ if (staging.stagingNs === stagingNs) {
649
+ if (staging.status === 'aborted' || staging.status === 'committed') {
650
+ return null;
651
+ }
652
+ const token = key.replace(STAGING_PREFIX, '');
653
+ return {
654
+ exists: true,
655
+ status: staging.status === 'prepared' ? 'ready' : staging.status,
656
+ token,
657
+ createdAt: new Date(staging.createdAt),
658
+ expiresAt: new Date(staging.expiresAt),
659
+ integrityHash: staging.integrityHash,
660
+ };
661
+ }
662
+ }
663
+ return null;
664
+ }
665
+ async getCloneTokenStatus(token) {
666
+ const staging = (await this.ctx.ctx.storage.get(`${STAGING_PREFIX}${token}`));
667
+ if (!staging) {
668
+ return { valid: false, status: 'not_found' };
669
+ }
670
+ const expiresAt = new Date(staging.expiresAt);
671
+ if (expiresAt < new Date()) {
672
+ return { valid: false, status: 'expired', expiresAt };
673
+ }
674
+ return { valid: staging.status === 'prepared', status: staging.status, expiresAt };
675
+ }
676
+ async getCloneCheckpoints(token) {
677
+ const checkpointKeys = await this.ctx.ctx.storage.list({ prefix: `${CHECKPOINT_PREFIX}${token}:` });
678
+ const checkpoints = [];
679
+ for (const [, value] of checkpointKeys) {
680
+ checkpoints.push(value);
681
+ }
682
+ checkpoints.sort((a, b) => a.sequence - b.sequence);
683
+ return checkpoints;
684
+ }
685
+ async validateCheckpoint(checkpointId) {
686
+ const allCheckpoints = await this.ctx.ctx.storage.list({ prefix: CHECKPOINT_PREFIX });
687
+ for (const [, value] of allCheckpoints) {
688
+ const checkpoint = value;
689
+ if (checkpoint.id === checkpointId) {
690
+ const expectedChecksum = this.computeCheckpointChecksum(checkpoint.state);
691
+ if (checkpoint.checksum === expectedChecksum) {
692
+ return { valid: true };
693
+ }
694
+ else {
695
+ return { valid: false, error: 'Checksum mismatch' };
696
+ }
697
+ }
698
+ }
699
+ return { valid: false, error: 'Checkpoint not found' };
700
+ }
701
+ async resumeCloneFromCheckpoint(checkpointId) {
702
+ const allCheckpoints = await this.ctx.ctx.storage.list({ prefix: CHECKPOINT_PREFIX });
703
+ let foundCheckpoint = null;
704
+ let originalToken = null;
705
+ for (const [key, value] of allCheckpoints) {
706
+ const checkpoint = value;
707
+ if (checkpoint.id === checkpointId) {
708
+ foundCheckpoint = checkpoint;
709
+ const parts = key.split(':');
710
+ if (parts.length >= 2) {
711
+ originalToken = parts[1] ?? null;
712
+ }
713
+ break;
714
+ }
715
+ }
716
+ if (!foundCheckpoint || !originalToken) {
717
+ throw new Error(`Checkpoint not found: ${checkpointId}`);
718
+ }
719
+ const staging = (await this.ctx.ctx.storage.get(`${STAGING_PREFIX}${originalToken}`));
720
+ if (!staging) {
721
+ throw new Error(`Original staging data not found for checkpoint`);
722
+ }
723
+ const newToken = crypto.randomUUID();
724
+ const expiresAt = new Date(Date.now() + DEFAULT_TOKEN_TIMEOUT);
725
+ const stagingNs = `${staging.targetNs}-staging-${newToken.slice(0, 8)}`;
726
+ const newStagingData = {
727
+ ...staging,
728
+ stagingNs,
729
+ expiresAt: expiresAt.toISOString(),
730
+ status: 'prepared',
731
+ createdAt: new Date().toISOString(),
732
+ };
733
+ await this.ctx.ctx.storage.put(`${STAGING_PREFIX}${newToken}`, newStagingData);
734
+ const originalCheckpoints = await this.getCloneCheckpoints(originalToken);
735
+ for (const cp of originalCheckpoints) {
736
+ const newCheckpoint = { ...cp, cloneId: newToken };
737
+ await this.ctx.ctx.storage.put(`${CHECKPOINT_PREFIX}${newToken}:${cp.id}`, newCheckpoint);
738
+ }
739
+ return {
740
+ phase: 'prepared',
741
+ token: newToken,
742
+ expiresAt,
743
+ stagingNs,
744
+ metadata: staging.metadata,
745
+ };
746
+ }
747
+ async gcStagingAreas() {
748
+ let cleaned = 0;
749
+ let checkpointsCleaned = 0;
750
+ const now = new Date();
751
+ const allStagings = await this.ctx.ctx.storage.list({ prefix: STAGING_PREFIX });
752
+ for (const [key, value] of allStagings) {
753
+ const staging = value;
754
+ const expiresAt = new Date(staging.expiresAt);
755
+ if (expiresAt < now) {
756
+ const token = key.replace(STAGING_PREFIX, '');
757
+ await this.ctx.emitEvent('clone.expired', { token, target: staging.targetNs });
758
+ await this.ctx.ctx.storage.delete(key);
759
+ cleaned++;
760
+ const checkpointKeys = await this.ctx.ctx.storage.list({ prefix: `${CHECKPOINT_PREFIX}${token}:` });
761
+ for (const [cpKey] of checkpointKeys) {
762
+ await this.ctx.ctx.storage.delete(cpKey);
763
+ checkpointsCleaned++;
764
+ }
765
+ }
766
+ }
767
+ return { cleaned, checkpointsCleaned };
768
+ }
769
+ // ═══════════════════════════════════════════════════════════════════════════
770
+ // EVENTUAL CLONE OPERATIONS
771
+ // ═══════════════════════════════════════════════════════════════════════════
772
+ async initiateEventualClone(target, options) {
773
+ try {
774
+ new URL(target);
775
+ }
776
+ catch {
777
+ throw new Error(`Invalid namespace URL: ${target}`);
778
+ }
779
+ const id = crypto.randomUUID();
780
+ const things = await this.ctx.db.select().from(schema.things);
781
+ const cloneBranch = options?.branch || this.ctx.currentBranch;
782
+ const branchFilter = cloneBranch === 'main' ? null : cloneBranch;
783
+ const branchThings = things.filter((t) => t.branch === branchFilter && !t.deleted);
784
+ const totalItems = branchThings.length;
785
+ const eventualOptions = options;
786
+ const syncInterval = eventualOptions?.syncInterval ?? 5000;
787
+ const maxDivergence = eventualOptions?.maxDivergence ?? 100;
788
+ const conflictResolution = eventualOptions?.conflictResolution ?? 'last-write-wins';
789
+ const chunked = eventualOptions?.chunked ?? false;
790
+ const chunkSize = eventualOptions?.chunkSize ?? 1000;
791
+ const rateLimit = eventualOptions?.rateLimit ?? null;
792
+ const customResolver = eventualOptions?.conflictResolver;
793
+ if (customResolver) {
794
+ this._conflictResolvers.set(id, customResolver);
795
+ }
796
+ const state = {
797
+ id,
798
+ targetNs: target,
799
+ status: 'pending',
800
+ progress: 0,
801
+ phase: 'initial',
802
+ itemsSynced: 0,
803
+ totalItems,
804
+ itemsRemaining: totalItems,
805
+ lastSyncAt: null,
806
+ divergence: totalItems,
807
+ maxDivergence,
808
+ syncInterval,
809
+ errorCount: 0,
810
+ lastError: null,
811
+ conflictResolution: customResolver ? 'custom' : conflictResolution,
812
+ hasCustomResolver: !!customResolver,
813
+ chunked,
814
+ chunkSize,
815
+ rateLimit,
816
+ createdAt: new Date().toISOString(),
817
+ updatedAt: new Date().toISOString(),
818
+ lastSyncedVersion: 0,
819
+ };
820
+ await this.ctx.ctx.storage.put(`eventual:${id}`, state);
821
+ await this.ctx.emitEvent('clone.initiated', { id, target, mode: 'eventual' });
822
+ // Schedule initial sync
823
+ const currentAlarm = await this.ctx.ctx.storage.getAlarm();
824
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
825
+ await this.ctx.ctx.storage.setAlarm(Date.now() + 100);
826
+ }
827
+ return this.createEventualHandle(id, state);
828
+ }
829
+ createEventualHandle(id, initialState) {
830
+ const self = this;
831
+ let currentStatus = initialState.status;
832
+ const handle = {
833
+ id,
834
+ get status() {
835
+ return currentStatus;
836
+ },
837
+ async getProgress() {
838
+ const state = await self.getEventualCloneState(id);
839
+ if (state)
840
+ currentStatus = state.status;
841
+ return state?.progress ?? 0;
842
+ },
843
+ async getSyncStatus() {
844
+ const state = await self.getEventualCloneState(id);
845
+ if (state)
846
+ currentStatus = state.status;
847
+ return {
848
+ phase: state?.phase ?? 'initial',
849
+ itemsSynced: state?.itemsSynced ?? 0,
850
+ totalItems: state?.totalItems ?? 0,
851
+ lastSyncAt: state?.lastSyncAt ? new Date(state.lastSyncAt) : null,
852
+ divergence: state?.divergence ?? 0,
853
+ maxDivergence: state?.maxDivergence ?? 100,
854
+ syncInterval: state?.syncInterval ?? 5000,
855
+ errorCount: state?.errorCount ?? 0,
856
+ lastError: state?.lastError ? new Error(state.lastError) : null,
857
+ };
858
+ },
859
+ async pause() {
860
+ const state = await self.getEventualCloneState(id);
861
+ if (!state)
862
+ throw new Error(`Clone operation not found: ${id}`);
863
+ state.status = 'paused';
864
+ state.updatedAt = new Date().toISOString();
865
+ await self.ctx.ctx.storage.put(`eventual:${id}`, state);
866
+ currentStatus = 'paused';
867
+ await self.ctx.emitEvent('clone.paused', { id });
868
+ },
869
+ async resume() {
870
+ const state = await self.getEventualCloneState(id);
871
+ if (!state)
872
+ throw new Error(`Clone operation not found: ${id}`);
873
+ state.status = state.phase === 'delta' || state.phase === 'catchup' ? 'active' : 'syncing';
874
+ state.updatedAt = new Date().toISOString();
875
+ await self.ctx.ctx.storage.put(`eventual:${id}`, state);
876
+ currentStatus = state.status;
877
+ await self.ctx.emitEvent('clone.resumed', { id });
878
+ const currentAlarm = await self.ctx.ctx.storage.getAlarm();
879
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
880
+ await self.ctx.ctx.storage.setAlarm(Date.now() + 100);
881
+ }
882
+ },
883
+ async sync() {
884
+ return self.performEventualSync(id);
885
+ },
886
+ async cancel() {
887
+ const state = await self.getEventualCloneState(id);
888
+ if (!state)
889
+ throw new Error(`Clone operation not found: ${id}`);
890
+ state.status = 'cancelled';
891
+ state.updatedAt = new Date().toISOString();
892
+ await self.ctx.ctx.storage.put(`eventual:${id}`, state);
893
+ currentStatus = 'cancelled';
894
+ await self.ctx.emitEvent('clone.cancelled', { id });
895
+ },
896
+ };
897
+ return handle;
898
+ }
899
+ async getEventualCloneState(id) {
900
+ return (await this.ctx.ctx.storage.get(`eventual:${id}`));
901
+ }
902
+ async performEventualSync(id) {
903
+ const startTime = Date.now();
904
+ const state = await this.getEventualCloneState(id);
905
+ if (!state)
906
+ throw new Error(`Clone operation not found: ${id}`);
907
+ if (state.status === 'cancelled' || state.status === 'paused') {
908
+ return { itemsSynced: 0, duration: 0, conflicts: [] };
909
+ }
910
+ const conflicts = [];
911
+ let itemsSynced = 0;
912
+ try {
913
+ if (state.status === 'pending') {
914
+ state.status = 'syncing';
915
+ state.phase = 'bulk';
916
+ await this.ctx.ctx.storage.put(`eventual:${id}`, state);
917
+ await this.ctx.emitEvent('clone.syncing', { id, progress: state.progress });
918
+ }
919
+ const things = await this.ctx.db.select().from(schema.things);
920
+ const branchThings = things.filter((t) => (t.branch === null || t.branch === this.ctx.currentBranch) && !t.deleted);
921
+ const latestVersions = new Map();
922
+ for (const thing of branchThings) {
923
+ latestVersions.set(thing.id, thing);
924
+ }
925
+ let itemsToSync = [];
926
+ if (state.phase === 'bulk' || state.phase === 'initial') {
927
+ itemsToSync = Array.from(latestVersions.values());
928
+ }
929
+ else {
930
+ itemsToSync = Array.from(latestVersions.values()).filter((_, idx) => idx >= state.lastSyncedVersion);
931
+ }
932
+ if (state.chunked && itemsToSync.length > state.chunkSize) {
933
+ itemsToSync = itemsToSync.slice(0, state.chunkSize);
934
+ }
935
+ if (state.rateLimit && itemsToSync.length > state.rateLimit) {
936
+ itemsToSync = itemsToSync.slice(0, state.rateLimit);
937
+ }
938
+ if (this.ctx.env.DO && itemsToSync.length > 0) {
939
+ const doId = this.ctx.env.DO.idFromName(state.targetNs);
940
+ const stub = this.ctx.env.DO.get(doId);
941
+ const response = await stub.fetch(new Request(`https://${state.targetNs}/sync`, {
942
+ method: 'POST',
943
+ body: JSON.stringify({
944
+ cloneId: id,
945
+ things: itemsToSync.map((t) => ({
946
+ id: t.id,
947
+ type: t.type,
948
+ branch: null,
949
+ name: t.name,
950
+ data: t.data,
951
+ deleted: false,
952
+ version: things.indexOf(t) + 1,
953
+ })),
954
+ }),
955
+ }));
956
+ if (response.ok) {
957
+ itemsSynced = itemsToSync.length;
958
+ try {
959
+ const responseData = (await response.json());
960
+ if (responseData.conflicts && Array.isArray(responseData.conflicts)) {
961
+ for (const conflict of responseData.conflicts) {
962
+ const resolution = state.hasCustomResolver ? 'custom' : state.conflictResolution;
963
+ const conflictInfo = {
964
+ thingId: conflict.thingId,
965
+ sourceVersion: conflict.sourceVersion,
966
+ targetVersion: conflict.targetVersion,
967
+ resolution,
968
+ resolvedAt: new Date(),
969
+ };
970
+ conflicts.push(conflictInfo);
971
+ await this.ctx.emitEvent('clone.conflict', { id, ...conflictInfo });
972
+ }
973
+ }
974
+ }
975
+ catch {
976
+ // Response may not be JSON
977
+ }
978
+ }
979
+ }
980
+ // Update state
981
+ state.itemsSynced += itemsSynced;
982
+ state.lastSyncedVersion += itemsSynced;
983
+ state.lastSyncAt = new Date().toISOString();
984
+ state.itemsRemaining = Math.max(0, state.totalItems - state.itemsSynced);
985
+ state.progress = state.totalItems > 0 ? Math.floor((state.itemsSynced / state.totalItems) * 100) : 100;
986
+ state.divergence = state.itemsRemaining;
987
+ state.errorCount = 0;
988
+ state.lastError = null;
989
+ state.updatedAt = new Date().toISOString();
990
+ await this.ctx.emitEvent('clone.progress', {
991
+ id,
992
+ progress: state.progress,
993
+ itemsSynced: state.itemsSynced,
994
+ totalItems: state.totalItems,
995
+ phase: state.phase,
996
+ });
997
+ if (state.progress >= 100) {
998
+ state.status = 'active';
999
+ state.phase = 'delta';
1000
+ await this.ctx.emitEvent('clone.active', { id, target: state.targetNs });
1001
+ }
1002
+ else if (state.itemsSynced > 0 && state.phase === 'bulk') {
1003
+ state.phase = state.progress >= 80 ? 'catchup' : 'bulk';
1004
+ }
1005
+ await this.ctx.ctx.storage.put(`eventual:${id}`, state);
1006
+ const duration = Date.now() - startTime;
1007
+ await this.ctx.emitEvent('clone.sync.completed', { id, itemsSynced, duration });
1008
+ return { itemsSynced, duration, conflicts };
1009
+ }
1010
+ catch (error) {
1011
+ state.errorCount++;
1012
+ state.lastError = error.message;
1013
+ state.updatedAt = new Date().toISOString();
1014
+ if (state.errorCount >= 10) {
1015
+ state.status = 'error';
1016
+ await this.ctx.emitEvent('clone.error', { id, error: state.lastError });
1017
+ }
1018
+ await this.ctx.ctx.storage.put(`eventual:${id}`, state);
1019
+ return { itemsSynced: 0, duration: Date.now() - startTime, conflicts: [] };
1020
+ }
1021
+ }
1022
+ async handleEventualCloneAlarms() {
1023
+ const keys = await this.ctx.ctx.storage.list({ prefix: 'eventual:' });
1024
+ let nextAlarmTime = null;
1025
+ for (const [, value] of keys) {
1026
+ const state = value;
1027
+ if (state.status === 'cancelled' || state.status === 'paused' || state.status === 'error') {
1028
+ continue;
1029
+ }
1030
+ const lastSync = state.lastSyncAt ? new Date(state.lastSyncAt).getTime() : 0;
1031
+ const now = Date.now();
1032
+ const nextSync = lastSync + state.syncInterval;
1033
+ const needsSync = now >= nextSync || state.divergence > state.maxDivergence;
1034
+ if (needsSync || state.status === 'pending') {
1035
+ await this.performEventualSync(state.id);
1036
+ const updatedState = await this.getEventualCloneState(state.id);
1037
+ if (updatedState &&
1038
+ updatedState.status !== 'active' &&
1039
+ updatedState.status !== 'cancelled' &&
1040
+ updatedState.status !== 'error') {
1041
+ const nextSyncTime = Date.now() + updatedState.syncInterval;
1042
+ if (!nextAlarmTime || nextSyncTime < nextAlarmTime) {
1043
+ nextAlarmTime = nextSyncTime;
1044
+ }
1045
+ }
1046
+ }
1047
+ else {
1048
+ if (!nextAlarmTime || nextSync < nextAlarmTime) {
1049
+ nextAlarmTime = nextSync;
1050
+ }
1051
+ }
1052
+ }
1053
+ if (nextAlarmTime) {
1054
+ await this.ctx.ctx.storage.setAlarm(nextAlarmTime);
1055
+ }
1056
+ await this.processResumableClones();
1057
+ }
1058
+ // ═══════════════════════════════════════════════════════════════════════════
1059
+ // RESUMABLE CLONE OPERATIONS
1060
+ // ═══════════════════════════════════════════════════════════════════════════
1061
+ async initiateResumableClone(target, options) {
1062
+ const batchSize = options?.batchSize || 100;
1063
+ const checkpointInterval = options?.checkpointInterval || 1;
1064
+ const maxRetries = options?.maxRetries || 3;
1065
+ const retryDelay = options?.retryDelay || 1000;
1066
+ const lockTimeout = options?.lockTimeout || 300000;
1067
+ const checkpointRetentionMs = options?.checkpointRetentionMs || 3600000;
1068
+ const compress = options?.compress || false;
1069
+ const maxBandwidth = options?.maxBandwidth;
1070
+ try {
1071
+ new URL(target);
1072
+ }
1073
+ catch {
1074
+ throw new Error(`Invalid namespace URL: ${target}`);
1075
+ }
1076
+ // Check for existing lock
1077
+ const existingLock = this._cloneLocks.get(target) ||
1078
+ (await this.ctx.ctx.storage.get(`clone-lock:${target}`));
1079
+ const now = Date.now();
1080
+ if (existingLock && !existingLock.isStale && new Date(existingLock.expiresAt).getTime() > now) {
1081
+ if (!options?.forceLock) {
1082
+ throw new Error(`Clone operation already in progress for target: ${target}`);
1083
+ }
1084
+ await this.releaseCloneLock(target, existingLock.cloneId);
1085
+ }
1086
+ let state;
1087
+ let cloneId;
1088
+ if (options?.resumeFrom) {
1089
+ const existingState = await this.findResumableStateFromCheckpoint(options.resumeFrom);
1090
+ if (!existingState) {
1091
+ throw new Error(`Checkpoint not found: ${options.resumeFrom}`);
1092
+ }
1093
+ state = existingState;
1094
+ cloneId = state.id;
1095
+ state.status = 'transferring';
1096
+ state.pauseRequested = false;
1097
+ }
1098
+ else {
1099
+ cloneId = crypto.randomUUID();
1100
+ state = {
1101
+ id: cloneId,
1102
+ targetNs: target,
1103
+ status: 'initializing',
1104
+ checkpoints: [],
1105
+ position: 0,
1106
+ batchSize,
1107
+ checkpointInterval,
1108
+ maxRetries,
1109
+ retryDelay,
1110
+ retryCount: 0,
1111
+ compress,
1112
+ maxBandwidth,
1113
+ checkpointRetentionMs,
1114
+ pauseRequested: false,
1115
+ cancelRequested: false,
1116
+ createdAt: new Date(),
1117
+ bytesTransferred: 0,
1118
+ totalBytes: 0,
1119
+ startedAt: null,
1120
+ };
1121
+ }
1122
+ // Acquire lock
1123
+ const lockId = crypto.randomUUID();
1124
+ const lock = {
1125
+ lockId,
1126
+ cloneId,
1127
+ target,
1128
+ acquiredAt: new Date(),
1129
+ expiresAt: new Date(now + lockTimeout),
1130
+ isStale: false,
1131
+ };
1132
+ this._cloneLocks.set(target, lock);
1133
+ await this.ctx.ctx.storage.put(`clone-lock:${target}`, lock);
1134
+ await this.ctx.emitEvent('clone.lock.acquired', { lockId, target, cloneId });
1135
+ this._resumableClones.set(cloneId, state);
1136
+ await this.ctx.ctx.storage.put(`resumable:${cloneId}`, state);
1137
+ this.cleanupOrphanedCheckpoints(checkpointRetentionMs).catch(() => { });
1138
+ const currentAlarm = await this.ctx.ctx.storage.getAlarm();
1139
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
1140
+ await this.ctx.ctx.storage.setAlarm(Date.now() + 100);
1141
+ }
1142
+ return this.createResumableCloneHandle(cloneId);
1143
+ }
1144
+ createResumableCloneHandle(cloneId) {
1145
+ const self = this;
1146
+ const handle = {
1147
+ id: cloneId,
1148
+ get status() {
1149
+ const state = self._resumableClones.get(cloneId);
1150
+ return state?.status || 'failed';
1151
+ },
1152
+ get checkpoints() {
1153
+ const state = self._resumableClones.get(cloneId);
1154
+ return state?.checkpoints || [];
1155
+ },
1156
+ async getProgress() {
1157
+ const state = await self.getResumableState(cloneId);
1158
+ return state?.progress || 0;
1159
+ },
1160
+ async pause() {
1161
+ const state = await self.getResumableState(cloneId);
1162
+ if (!state)
1163
+ throw new Error('Clone not found');
1164
+ state.pauseRequested = true;
1165
+ state.status = 'paused';
1166
+ await self.ctx.ctx.storage.put(`resumable:${cloneId}`, state);
1167
+ self._resumableClones.set(cloneId, state);
1168
+ const lastCheckpoint = state.checkpoints[state.checkpoints.length - 1];
1169
+ await self.ctx.emitEvent('clone.paused', { id: cloneId, checkpoint: lastCheckpoint, progress: state.progress });
1170
+ },
1171
+ async resume() {
1172
+ const state = await self.getResumableState(cloneId);
1173
+ if (!state)
1174
+ throw new Error('Clone not found');
1175
+ state.pauseRequested = false;
1176
+ state.status = 'transferring';
1177
+ await self.ctx.ctx.storage.put(`resumable:${cloneId}`, state);
1178
+ self._resumableClones.set(cloneId, state);
1179
+ const lastCheckpoint = state.checkpoints[state.checkpoints.length - 1];
1180
+ await self.ctx.emitEvent('clone.resumed', { id: cloneId, fromCheckpoint: lastCheckpoint });
1181
+ const currentAlarm = await self.ctx.ctx.storage.getAlarm();
1182
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
1183
+ await self.ctx.ctx.storage.setAlarm(Date.now() + 100);
1184
+ }
1185
+ },
1186
+ async cancel() {
1187
+ const state = await self.getResumableState(cloneId);
1188
+ if (!state)
1189
+ throw new Error('Clone not found');
1190
+ const progress = state.progress || 0;
1191
+ const checkpointsCreated = state.checkpoints.length;
1192
+ state.cancelRequested = true;
1193
+ state.status = 'cancelled';
1194
+ await self.ctx.ctx.storage.put(`resumable:${cloneId}`, state);
1195
+ self._resumableClones.set(cloneId, state);
1196
+ await self.releaseCloneLock(state.targetNs, cloneId);
1197
+ await self.cleanupCloneCheckpoints(cloneId);
1198
+ await self.ctx.emitEvent('clone.cancelled', { id: cloneId, progress, checkpointsCreated });
1199
+ },
1200
+ async waitForCheckpoint() {
1201
+ const pollInterval = 50;
1202
+ const maxWait = 60000;
1203
+ const startTime = Date.now();
1204
+ return new Promise((resolve, reject) => {
1205
+ const poll = async () => {
1206
+ const state = await self.getResumableState(cloneId);
1207
+ if (!state) {
1208
+ reject(new Error('Clone not found'));
1209
+ return;
1210
+ }
1211
+ if (state.checkpoints.length > 0) {
1212
+ resolve(state.checkpoints[state.checkpoints.length - 1]);
1213
+ return;
1214
+ }
1215
+ if (Date.now() - startTime > maxWait) {
1216
+ reject(new Error('Timeout waiting for checkpoint'));
1217
+ return;
1218
+ }
1219
+ setTimeout(poll, pollInterval);
1220
+ };
1221
+ poll();
1222
+ });
1223
+ },
1224
+ async canResumeFrom(checkpointId) {
1225
+ const checkpoint = await self.ctx.ctx.storage.get(`checkpoint:${checkpointId}`);
1226
+ if (!checkpoint)
1227
+ return false;
1228
+ return self.validateCheckpointHash(checkpoint);
1229
+ },
1230
+ async getIntegrityHash() {
1231
+ const state = await self.getResumableState(cloneId);
1232
+ if (!state || state.checkpoints.length === 0)
1233
+ return '';
1234
+ return state.checkpoints[state.checkpoints.length - 1].hash;
1235
+ },
1236
+ async getLockInfo() {
1237
+ const state = await self.getResumableState(cloneId);
1238
+ if (!state)
1239
+ return null;
1240
+ const lock = self._cloneLocks.get(state.targetNs) ||
1241
+ (await self.ctx.ctx.storage.get(`clone-lock:${state.targetNs}`));
1242
+ if (!lock || lock.cloneId !== cloneId)
1243
+ return null;
1244
+ return {
1245
+ lockId: lock.lockId,
1246
+ cloneId: lock.cloneId,
1247
+ acquiredAt: new Date(lock.acquiredAt),
1248
+ expiresAt: new Date(lock.expiresAt),
1249
+ isStale: lock.isStale || new Date(lock.expiresAt).getTime() < Date.now(),
1250
+ };
1251
+ },
1252
+ async forceOverrideLock() {
1253
+ const state = await self.getResumableState(cloneId);
1254
+ if (!state)
1255
+ throw new Error('Clone not found');
1256
+ await self.releaseCloneLock(state.targetNs, cloneId);
1257
+ },
1258
+ };
1259
+ return handle;
1260
+ }
1261
+ async processResumableClones() {
1262
+ const storageKeys = await this.ctx.ctx.storage.list({ prefix: 'resumable:' });
1263
+ for (const [key, value] of storageKeys) {
1264
+ const cloneId = key.replace('resumable:', '');
1265
+ if (!this._resumableClones.has(cloneId)) {
1266
+ this._resumableClones.set(cloneId, value);
1267
+ }
1268
+ }
1269
+ for (const [cloneId, state] of this._resumableClones) {
1270
+ if (state.status === 'paused' || state.pauseRequested)
1271
+ continue;
1272
+ if (state.status === 'cancelled' || state.cancelRequested)
1273
+ continue;
1274
+ if (state.status === 'completed' || state.status === 'failed')
1275
+ continue;
1276
+ await this.processResumableCloneBatch(cloneId);
1277
+ }
1278
+ }
1279
+ async processResumableCloneBatch(cloneId) {
1280
+ const state = await this.getResumableState(cloneId);
1281
+ if (!state)
1282
+ return;
1283
+ if (state.pauseRequested || state.status === 'paused')
1284
+ return;
1285
+ if (state.cancelRequested || state.status === 'cancelled')
1286
+ return;
1287
+ if (state.status === 'initializing') {
1288
+ state.status = 'transferring';
1289
+ state.startedAt = new Date();
1290
+ }
1291
+ try {
1292
+ const allThings = await this.ctx.db.select().from(schema.things);
1293
+ const nonDeletedThings = allThings.filter((t) => !t.deleted);
1294
+ const totalItems = nonDeletedThings.length;
1295
+ if (state.totalBytes === 0) {
1296
+ state.totalBytes = nonDeletedThings.reduce((acc, t) => acc + JSON.stringify(t).length, 0);
1297
+ }
1298
+ const batch = nonDeletedThings.slice(state.position, state.position + state.batchSize);
1299
+ if (batch.length === 0) {
1300
+ await this.completeResumableClone(cloneId, state);
1301
+ return;
1302
+ }
1303
+ // Apply bandwidth throttling
1304
+ if (state.maxBandwidth) {
1305
+ const batchBytes = batch.reduce((acc, t) => acc + JSON.stringify(t).length, 0);
1306
+ const expectedTime = (batchBytes / state.maxBandwidth) * 1000;
1307
+ if (expectedTime > 0) {
1308
+ await this.sleep(Math.floor(expectedTime));
1309
+ if (expectedTime > 100) {
1310
+ await this.ctx.emitEvent('clone.throttled', { id: cloneId, delayMs: expectedTime, batchBytes });
1311
+ }
1312
+ }
1313
+ }
1314
+ await this.transferBatchToTarget(state.targetNs, batch, state.compress);
1315
+ const batchBytes = batch.reduce((acc, t) => acc + JSON.stringify(t).length, 0);
1316
+ state.bytesTransferred += batchBytes;
1317
+ state.position += batch.length;
1318
+ state.progress = Math.round((state.position / totalItems) * 100);
1319
+ const batchNumber = Math.ceil(state.position / state.batchSize);
1320
+ await this.ctx.emitEvent('clone.batch.completed', {
1321
+ id: cloneId,
1322
+ batchNumber,
1323
+ itemsInBatch: batch.length,
1324
+ itemsProcessed: state.position,
1325
+ progress: state.progress,
1326
+ });
1327
+ const shouldCreateCheckpoint = batchNumber % state.checkpointInterval === 0;
1328
+ if (shouldCreateCheckpoint) {
1329
+ const checkpoint = await this.createResumableCheckpoint(cloneId, state, batch, batchNumber);
1330
+ state.checkpoints.push(checkpoint);
1331
+ await this.ctx.ctx.storage.put(`checkpoint:${cloneId}:${checkpoint.id}`, checkpoint);
1332
+ await this.ctx.ctx.storage.put(`checkpoint:${checkpoint.id}`, checkpoint);
1333
+ await this.ctx.emitEvent('clone.checkpoint', {
1334
+ id: cloneId,
1335
+ checkpoint,
1336
+ checkpointId: checkpoint.id,
1337
+ position: checkpoint.position,
1338
+ hash: checkpoint.hash,
1339
+ });
1340
+ }
1341
+ state.retryCount = 0;
1342
+ await this.ctx.ctx.storage.put(`resumable:${cloneId}`, state);
1343
+ this._resumableClones.set(cloneId, state);
1344
+ if (state.position < totalItems && !state.pauseRequested && !state.cancelRequested) {
1345
+ const currentAlarm = await this.ctx.ctx.storage.getAlarm();
1346
+ if (!currentAlarm || currentAlarm > Date.now() + 100) {
1347
+ await this.ctx.ctx.storage.setAlarm(Date.now() + 100);
1348
+ }
1349
+ }
1350
+ else if (state.position >= totalItems) {
1351
+ await this.completeResumableClone(cloneId, state);
1352
+ }
1353
+ }
1354
+ catch (error) {
1355
+ await this.handleResumableCloneError(cloneId, state, error);
1356
+ }
1357
+ }
1358
+ async createResumableCheckpoint(cloneId, state, batch, batchNumber) {
1359
+ const batchJson = JSON.stringify(batch);
1360
+ const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(batchJson));
1361
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
1362
+ const hash = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
1363
+ return {
1364
+ id: crypto.randomUUID(),
1365
+ position: state.position,
1366
+ hash,
1367
+ timestamp: new Date(),
1368
+ itemsProcessed: state.position,
1369
+ batchNumber,
1370
+ cloneId,
1371
+ compressed: state.compress,
1372
+ };
1373
+ }
1374
+ async completeResumableClone(cloneId, state) {
1375
+ const duration = state.startedAt ? Date.now() - new Date(state.startedAt).getTime() : 0;
1376
+ state.status = 'completed';
1377
+ await this.ctx.ctx.storage.put(`resumable:${cloneId}`, state);
1378
+ this._resumableClones.set(cloneId, state);
1379
+ await this.releaseCloneLock(state.targetNs, cloneId);
1380
+ await this.cleanupCloneCheckpoints(cloneId);
1381
+ await this.ctx.emitEvent('clone.completed', {
1382
+ id: cloneId,
1383
+ totalCheckpoints: state.checkpoints.length,
1384
+ totalItems: state.position,
1385
+ duration,
1386
+ });
1387
+ }
1388
+ async handleResumableCloneError(cloneId, state, error) {
1389
+ state.retryCount++;
1390
+ await this.ctx.emitEvent('clone.retry', { id: cloneId, attempt: state.retryCount, error: error.message });
1391
+ if (state.retryCount >= state.maxRetries) {
1392
+ state.status = 'failed';
1393
+ await this.ctx.ctx.storage.put(`resumable:${cloneId}`, state);
1394
+ this._resumableClones.set(cloneId, state);
1395
+ await this.ctx.emitEvent('clone.failed', { id: cloneId, error: error.message, retryCount: state.retryCount });
1396
+ await this.releaseCloneLock(state.targetNs, cloneId);
1397
+ return;
1398
+ }
1399
+ const baseDelay = state.retryDelay * Math.pow(2, state.retryCount - 1);
1400
+ const jitter = Math.random() * baseDelay * 0.25;
1401
+ const delay = Math.floor(baseDelay + jitter);
1402
+ await this.ctx.ctx.storage.put(`resumable:${cloneId}`, state);
1403
+ this._resumableClones.set(cloneId, state);
1404
+ const currentAlarm = await this.ctx.ctx.storage.getAlarm();
1405
+ if (!currentAlarm || currentAlarm > Date.now() + delay) {
1406
+ await this.ctx.ctx.storage.setAlarm(Date.now() + delay);
1407
+ }
1408
+ }
1409
+ async transferBatchToTarget(targetNs, batch, compress) {
1410
+ if (!this.ctx.env.DO) {
1411
+ throw new Error('DO namespace not configured');
1412
+ }
1413
+ await Promise.resolve();
1414
+ }
1415
+ async getResumableState(cloneId) {
1416
+ let state = this._resumableClones.get(cloneId);
1417
+ if (state)
1418
+ return state;
1419
+ state = await this.ctx.ctx.storage.get(`resumable:${cloneId}`);
1420
+ if (state) {
1421
+ this._resumableClones.set(cloneId, state);
1422
+ }
1423
+ return state || null;
1424
+ }
1425
+ async findResumableStateFromCheckpoint(checkpointId) {
1426
+ const checkpoint = await this.ctx.ctx.storage.get(`checkpoint:${checkpointId}`);
1427
+ if (!checkpoint || !checkpoint.cloneId)
1428
+ return null;
1429
+ return this.getResumableState(checkpoint.cloneId);
1430
+ }
1431
+ async validateCheckpointHash(checkpoint) {
1432
+ if (!checkpoint.hash || !/^[a-f0-9]{64}$/.test(checkpoint.hash))
1433
+ return false;
1434
+ if (checkpoint.position < 0)
1435
+ return false;
1436
+ return true;
1437
+ }
1438
+ async releaseCloneLock(target, cloneId) {
1439
+ const lock = this._cloneLocks.get(target) ||
1440
+ (await this.ctx.ctx.storage.get(`clone-lock:${target}`));
1441
+ if (lock && lock.cloneId === cloneId) {
1442
+ this._cloneLocks.delete(target);
1443
+ await this.ctx.ctx.storage.delete(`clone-lock:${target}`);
1444
+ await this.ctx.emitEvent('clone.lock.released', { lockId: lock.lockId, target });
1445
+ }
1446
+ }
1447
+ async cleanupCloneCheckpoints(cloneId) {
1448
+ const state = await this.getResumableState(cloneId);
1449
+ if (!state)
1450
+ return;
1451
+ for (const checkpoint of state.checkpoints) {
1452
+ await this.ctx.ctx.storage.delete(`checkpoint:${cloneId}:${checkpoint.id}`);
1453
+ await this.ctx.ctx.storage.delete(`checkpoint:${checkpoint.id}`);
1454
+ }
1455
+ }
1456
+ async cleanupOrphanedCheckpoints(retentionMs) {
1457
+ const now = Date.now();
1458
+ const cutoff = now - retentionMs;
1459
+ const checkpointKeys = await this.ctx.ctx.storage.list({ prefix: 'checkpoint:' });
1460
+ for (const [key, value] of checkpointKeys) {
1461
+ const checkpoint = value;
1462
+ if (!checkpoint.timestamp)
1463
+ continue;
1464
+ const checkpointTime = new Date(checkpoint.timestamp).getTime();
1465
+ if (checkpointTime < cutoff) {
1466
+ if (checkpoint.cloneId) {
1467
+ const state = await this.getResumableState(checkpoint.cloneId);
1468
+ if (state && state.status === 'paused')
1469
+ continue;
1470
+ }
1471
+ await this.ctx.ctx.storage.delete(key);
1472
+ }
1473
+ }
1474
+ }
1475
+ sleep(ms) {
1476
+ return new Promise((resolve) => setTimeout(resolve, ms));
1477
+ }
1478
+ // Test helper methods
1479
+ async _corruptCheckpoint(token, checkpointId) {
1480
+ const checkpointKey = `${CHECKPOINT_PREFIX}${token}:${checkpointId}`;
1481
+ const checkpoint = (await this.ctx.ctx.storage.get(checkpointKey));
1482
+ if (checkpoint) {
1483
+ checkpoint.checksum = 'corrupted-checksum';
1484
+ await this.ctx.ctx.storage.put(checkpointKey, checkpoint);
1485
+ }
1486
+ }
1487
+ async _corruptStagingArea(token) {
1488
+ const staging = (await this.ctx.ctx.storage.get(`${STAGING_PREFIX}${token}`));
1489
+ if (staging) {
1490
+ staging.integrityHash = 'corrupted-hash';
1491
+ await this.ctx.ctx.storage.put(`${STAGING_PREFIX}${token}`, staging);
1492
+ }
1493
+ }
1494
+ }
1495
+ // Export singleton factory
1496
+ export function createCloneModule() {
1497
+ return new CloneModule();
1498
+ }
1499
+ //# sourceMappingURL=Clone.js.map