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,2464 @@
1
+ /**
2
+ * Temporal Compat Layer - 100% API Compatible with @temporalio/workflow
3
+ *
4
+ * Drop-in replacement for Temporal that runs on dotdo's
5
+ * durable execution infrastructure.
6
+ *
7
+ * ## Determinism Requirements
8
+ *
9
+ * Workflows MUST be deterministic to support replay. This means that given the
10
+ * same inputs and history, a workflow must produce the same sequence of commands.
11
+ *
12
+ * ### Non-Deterministic Operations to Avoid:
13
+ *
14
+ * | Avoid | Use Instead |
15
+ * |--------------------------|--------------------------------------|
16
+ * | `Date.now()` | `workflowNow()` - deterministic time |
17
+ * | `new Date()` | `workflowNow()` - returns Date |
18
+ * | `Math.random()` | `random()` - deterministic random |
19
+ * | `crypto.randomUUID()` | `uuid4()` - deterministic UUID |
20
+ * | `fetch()` / HTTP calls | Activities (via `proxyActivities`) |
21
+ * | `setTimeout/setInterval` | `sleep()` or `createTimer()` |
22
+ * | File I/O | Activities |
23
+ * | Database queries | Activities |
24
+ *
25
+ * ### Why Determinism Matters:
26
+ *
27
+ * When a workflow fails and restarts, it replays from the beginning using
28
+ * stored history. If your workflow makes different decisions on replay
29
+ * (e.g., because `Math.random()` returns a different value), the replay
30
+ * will diverge from history and fail.
31
+ *
32
+ * ### Development Mode Warnings:
33
+ *
34
+ * Set `warnOnNonDeterministic: true` in configuration to get console warnings
35
+ * when non-deterministic patterns are detected. This is enabled by default
36
+ * in development mode (`NODE_ENV !== 'production'`).
37
+ *
38
+ * ```typescript
39
+ * import { configureDeterminism, enableDeterminismDetection } from '@dotdo/temporal'
40
+ *
41
+ * // Configure warnings
42
+ * configureDeterminism({ warnOnNonDeterministic: true })
43
+ *
44
+ * // Enable detection (patches global Date.now, Math.random, fetch)
45
+ * enableDeterminismDetection()
46
+ * ```
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * import { proxyActivities, defineSignal, setHandler, sleep, condition, workflowNow, random } from '@dotdo/temporal'
51
+ *
52
+ * const { sendEmail, chargeCard } = proxyActivities<typeof activities>({
53
+ * startToCloseTimeout: '10s',
54
+ * retry: { maximumAttempts: 3 },
55
+ * })
56
+ *
57
+ * export async function orderWorkflow(order: Order) {
58
+ * const approved = defineSignal<[boolean]>('approve')
59
+ * let isApproved = false
60
+ *
61
+ * // Use deterministic alternatives
62
+ * const orderTime = workflowNow() // instead of new Date()
63
+ * const shouldDiscount = random() < 0.1 // instead of Math.random()
64
+ *
65
+ * setHandler(approved, (approval) => {
66
+ * isApproved = approval
67
+ * })
68
+ *
69
+ * await condition(() => isApproved, '7d')
70
+ *
71
+ * await chargeCard(order.cardToken, order.amount)
72
+ * await sendEmail(order.email, 'Order confirmed!')
73
+ *
74
+ * return { status: 'completed', orderTime }
75
+ * }
76
+ * ```
77
+ */
78
+ import { AsyncLocalStorage } from 'async_hooks';
79
+ import { WaitForEventManager, WaitCancelledError } from '../../WaitForEventManager';
80
+ import { DurableWorkflowRuntime, InMemoryStepStorage } from '../../runtime';
81
+ import { parseDuration, ensureError } from '../utils';
82
+ import { WorkerActivityRouter, ActivityTimeoutError, TaskQueueNotRegisteredError, } from '../activity-router';
83
+ // Re-export error classes for backward compatibility
84
+ export { ActivityTimeoutError, TaskQueueNotRegisteredError };
85
+ // ============================================================================
86
+ // CFWORKFLOWS STORAGE STRATEGY - Uses native CF Workflows APIs
87
+ // ============================================================================
88
+ /**
89
+ * CFWorkflowsStorageStrategy uses Cloudflare Workflows native APIs for durable execution.
90
+ *
91
+ * Benefits:
92
+ * - sleep() is FREE - you don't pay for wall-clock time
93
+ * - step.do() is DURABLE - survives worker restarts, automatic retries
94
+ * - Replay is automatic - completed steps return cached results
95
+ *
96
+ * This strategy is automatically selected when a WorkflowStep is available
97
+ * in the current workflow context.
98
+ */
99
+ export class CFWorkflowsStorageStrategy {
100
+ step;
101
+ stepResults = new Map();
102
+ constructor(step) {
103
+ this.step = step;
104
+ }
105
+ async executeStep(name, fn, options) {
106
+ // Check for cached result (replay)
107
+ const cached = this.stepResults.get(name);
108
+ if (cached) {
109
+ if (cached.status === 'error') {
110
+ throw cached.error;
111
+ }
112
+ return cached.value;
113
+ }
114
+ // Build step.do() options from execution options
115
+ const stepOptions = this.buildStepDoOptions(options);
116
+ try {
117
+ // Execute via step.do() for durability
118
+ const result = stepOptions
119
+ ? await this.step.do(name, stepOptions, async () => fn())
120
+ : await this.step.do(name, async () => fn());
121
+ // Cache the result with discriminated union
122
+ this.stepResults.set(name, { status: 'success', value: result });
123
+ return result;
124
+ }
125
+ catch (error) {
126
+ // Cache errors for deterministic replay
127
+ const err = ensureError(error);
128
+ this.stepResults.set(name, { status: 'error', error: err });
129
+ throw err;
130
+ }
131
+ }
132
+ async sleep(name, _durationMs, durationStr) {
133
+ // Check for cached completion (replay)
134
+ if (this.stepResults.has(name)) {
135
+ return;
136
+ }
137
+ // Use CF Workflows native step.sleep() - FREE, doesn't consume wall-clock time
138
+ await this.step.sleep(name, durationStr);
139
+ // Cache completion
140
+ this.stepResults.set(name, { status: 'success', value: true });
141
+ }
142
+ async isStepCompleted(name) {
143
+ return this.stepResults.has(name);
144
+ }
145
+ async getStepResult(name) {
146
+ const cached = this.stepResults.get(name);
147
+ if (!cached)
148
+ return undefined;
149
+ if (cached.status === 'error') {
150
+ throw cached.error;
151
+ }
152
+ return cached.value;
153
+ }
154
+ async setStepResult(name, result) {
155
+ if (result instanceof Error) {
156
+ this.stepResults.set(name, { status: 'error', error: result });
157
+ }
158
+ else {
159
+ this.stepResults.set(name, { status: 'success', value: result });
160
+ }
161
+ }
162
+ buildStepDoOptions(options) {
163
+ if (!options)
164
+ return undefined;
165
+ const stepOptions = {};
166
+ if (options.retries) {
167
+ stepOptions.retries = options.retries;
168
+ }
169
+ if (options.timeout) {
170
+ stepOptions.timeout = options.timeout;
171
+ }
172
+ // Only return if we have something to configure
173
+ if (stepOptions.retries || stepOptions.timeout) {
174
+ return stepOptions;
175
+ }
176
+ return undefined;
177
+ }
178
+ }
179
+ // ============================================================================
180
+ // INMEMORY STORAGE STRATEGY - Fallback for testing and non-CF environments
181
+ // ============================================================================
182
+ /**
183
+ * InMemoryStorageStrategy provides fallback behavior when no WorkflowStep is available.
184
+ *
185
+ * This is used in:
186
+ * - Testing environments
187
+ * - Development without CF Workflows runtime
188
+ * - Direct workflow execution outside of CF Workflows
189
+ *
190
+ * Cost: sleep() uses setTimeout which is BILLABLE (consumes DO wall-clock time)
191
+ */
192
+ export class InMemoryStorageStrategy {
193
+ stepResults = new Map();
194
+ durableStorage;
195
+ constructor(storage) {
196
+ this.durableStorage = storage ?? new InMemoryStepStorage();
197
+ }
198
+ async executeStep(name, fn, _options) {
199
+ // Check in-memory cache first
200
+ const cached = this.stepResults.get(name);
201
+ if (cached) {
202
+ if (cached.status === 'error') {
203
+ throw cached.error;
204
+ }
205
+ return cached.value;
206
+ }
207
+ // Check durable storage for completed steps (survives worker restarts)
208
+ const durableResult = await this.durableStorage.get(name);
209
+ if (durableResult?.status === 'completed') {
210
+ this.stepResults.set(name, { status: 'success', value: durableResult.result });
211
+ return durableResult.result;
212
+ }
213
+ // Execute the function
214
+ try {
215
+ const result = await fn();
216
+ // Store in both in-memory and durable storage
217
+ this.stepResults.set(name, { status: 'success', value: result });
218
+ await this.durableStorage.set(name, {
219
+ stepId: name,
220
+ status: 'completed',
221
+ result,
222
+ attempts: 1,
223
+ createdAt: Date.now(),
224
+ completedAt: Date.now(),
225
+ });
226
+ return result;
227
+ }
228
+ catch (error) {
229
+ const err = ensureError(error);
230
+ this.stepResults.set(name, { status: 'error', error: err });
231
+ await this.durableStorage.set(name, {
232
+ stepId: name,
233
+ status: 'failed',
234
+ error: err.message,
235
+ attempts: 1,
236
+ createdAt: Date.now(),
237
+ });
238
+ throw err;
239
+ }
240
+ }
241
+ async sleep(name, durationMs, _durationStr) {
242
+ // Check for cached completion
243
+ if (this.stepResults.has(name)) {
244
+ return;
245
+ }
246
+ // Check durable storage
247
+ const durableResult = await this.durableStorage.get(name);
248
+ if (durableResult?.status === 'completed') {
249
+ this.stepResults.set(name, { status: 'success', value: true });
250
+ return;
251
+ }
252
+ // Persist pending state before sleeping
253
+ await this.durableStorage.set(name, {
254
+ stepId: name,
255
+ status: 'pending',
256
+ attempts: 1,
257
+ createdAt: Date.now(),
258
+ });
259
+ // Fallback to setTimeout - BILLABLE
260
+ await new Promise((resolve) => setTimeout(resolve, durationMs));
261
+ // Persist completed state
262
+ await this.durableStorage.set(name, {
263
+ stepId: name,
264
+ status: 'completed',
265
+ result: true,
266
+ attempts: 1,
267
+ createdAt: Date.now(),
268
+ completedAt: Date.now(),
269
+ });
270
+ this.stepResults.set(name, { status: 'success', value: true });
271
+ }
272
+ async isStepCompleted(name) {
273
+ if (this.stepResults.has(name)) {
274
+ return true;
275
+ }
276
+ const durableResult = await this.durableStorage.get(name);
277
+ return durableResult?.status === 'completed';
278
+ }
279
+ async getStepResult(name) {
280
+ // Check in-memory first
281
+ const cached = this.stepResults.get(name);
282
+ if (cached) {
283
+ if (cached.status === 'error') {
284
+ throw cached.error;
285
+ }
286
+ return cached.value;
287
+ }
288
+ // Check durable storage
289
+ const durableResult = await this.durableStorage.get(name);
290
+ if (durableResult?.status === 'completed') {
291
+ return durableResult.result;
292
+ }
293
+ return undefined;
294
+ }
295
+ async setStepResult(name, result) {
296
+ if (result instanceof Error) {
297
+ this.stepResults.set(name, { status: 'error', error: result });
298
+ await this.durableStorage.set(name, {
299
+ stepId: name,
300
+ status: 'failed',
301
+ error: result.message,
302
+ attempts: 1,
303
+ createdAt: Date.now(),
304
+ });
305
+ }
306
+ else {
307
+ this.stepResults.set(name, { status: 'success', value: result });
308
+ await this.durableStorage.set(name, {
309
+ stepId: name,
310
+ status: 'completed',
311
+ result,
312
+ attempts: 1,
313
+ createdAt: Date.now(),
314
+ completedAt: Date.now(),
315
+ });
316
+ }
317
+ }
318
+ /**
319
+ * Clear all cached results (for testing)
320
+ */
321
+ clear() {
322
+ this.stepResults.clear();
323
+ }
324
+ }
325
+ // ============================================================================
326
+ // STRATEGY FACTORY - Creates the appropriate strategy based on context
327
+ // ============================================================================
328
+ /**
329
+ * Get the appropriate storage strategy for the current workflow execution.
330
+ *
331
+ * Selection logic:
332
+ * 1. If WorkflowStep is available in context, use CFWorkflowsStorageStrategy (FREE sleeping, durable)
333
+ * 2. Otherwise, use InMemoryStorageStrategy (fallback, BILLABLE sleeping)
334
+ *
335
+ * @param workflowStep - Optional WorkflowStep from CF Workflows runtime
336
+ * @param storage - Optional StepStorage for InMemory fallback
337
+ * @returns The appropriate storage strategy
338
+ */
339
+ export function createStorageStrategy(workflowStep, storage) {
340
+ if (workflowStep) {
341
+ return new CFWorkflowsStorageStrategy(workflowStep);
342
+ }
343
+ return new InMemoryStorageStrategy(storage);
344
+ }
345
+ // Default configuration - warn in development, silent in production
346
+ let determinismConfig = {
347
+ warnOnNonDeterministic: typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production',
348
+ };
349
+ /**
350
+ * Configure determinism enforcement settings
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * import { configureDeterminism } from '@dotdo/temporal'
355
+ *
356
+ * // Disable warnings in tests
357
+ * configureDeterminism({ warnOnNonDeterministic: false })
358
+ *
359
+ * // Enable warnings in production for debugging
360
+ * configureDeterminism({ warnOnNonDeterministic: true })
361
+ * ```
362
+ */
363
+ export function configureDeterminism(config) {
364
+ determinismConfig = { ...determinismConfig, ...config };
365
+ }
366
+ /**
367
+ * Warning class for tracking determinism violations.
368
+ * These are logged in development mode to help identify potential replay issues.
369
+ */
370
+ export class WorkflowDeterminismWarning {
371
+ /** Type of non-deterministic operation detected */
372
+ type;
373
+ /** Human-readable message describing the violation */
374
+ message;
375
+ /** Stack trace showing where the violation occurred */
376
+ stack;
377
+ /** Workflow ID where the violation was detected (if available) */
378
+ workflowId;
379
+ /** Suggested alternative to use instead */
380
+ suggestion;
381
+ /** Timestamp when the warning was created */
382
+ timestamp;
383
+ constructor(type, message, suggestion, workflowId) {
384
+ this.type = type;
385
+ this.message = message;
386
+ this.suggestion = suggestion;
387
+ this.workflowId = workflowId;
388
+ this.timestamp = new Date();
389
+ this.stack = new Error().stack;
390
+ }
391
+ /**
392
+ * Format the warning for console output
393
+ */
394
+ toString() {
395
+ const workflowInfo = this.workflowId ? ` in workflow ${this.workflowId}` : '';
396
+ return `[WorkflowDeterminismWarning]${workflowInfo}: ${this.message}\n Suggestion: ${this.suggestion}`;
397
+ }
398
+ }
399
+ /**
400
+ * Track warnings for analysis (limited to prevent memory leaks)
401
+ */
402
+ const MAX_WARNINGS = 100;
403
+ const determinismWarnings = [];
404
+ /**
405
+ * Get all recorded determinism warnings
406
+ */
407
+ export function getDeterminismWarnings() {
408
+ return determinismWarnings;
409
+ }
410
+ /**
411
+ * Clear all recorded determinism warnings
412
+ */
413
+ export function clearDeterminismWarnings() {
414
+ determinismWarnings.length = 0;
415
+ }
416
+ /**
417
+ * Record a determinism violation warning
418
+ */
419
+ function warnNonDeterministic(type, message, suggestion) {
420
+ // Only warn if enabled and we're in a workflow context
421
+ const workflow = getCurrentWorkflow();
422
+ if (!determinismConfig.warnOnNonDeterministic || !workflow) {
423
+ return;
424
+ }
425
+ const warning = new WorkflowDeterminismWarning(type, message, suggestion, workflow.workflowId);
426
+ // Store warning (with limit to prevent memory leaks)
427
+ if (determinismWarnings.length < MAX_WARNINGS) {
428
+ determinismWarnings.push(warning);
429
+ }
430
+ // Log to console in development
431
+ console.warn(warning.toString());
432
+ }
433
+ // AsyncLocalStorage provides execution-scoped context
434
+ // This works in both Node.js and Cloudflare Workers runtime
435
+ const workflowContextStorage = new AsyncLocalStorage();
436
+ /**
437
+ * Get the current workflow context from AsyncLocalStorage.
438
+ * Returns null if not executing within a workflow.
439
+ */
440
+ function getCurrentContext() {
441
+ return workflowContextStorage.getStore() ?? null;
442
+ }
443
+ /**
444
+ * Get the current workflow state for backward compatibility.
445
+ * This function bridges the old global state approach with the new context-based approach.
446
+ */
447
+ function getCurrentWorkflow() {
448
+ const ctx = getCurrentContext();
449
+ return ctx?.workflow ?? null;
450
+ }
451
+ /**
452
+ * Get the current patch state from context.
453
+ */
454
+ function getCurrentPatchState() {
455
+ const ctx = getCurrentContext();
456
+ return ctx?.patchState ?? null;
457
+ }
458
+ /**
459
+ * Set the current patch state within the context.
460
+ */
461
+ function setCurrentPatchState(patchState) {
462
+ const ctx = getCurrentContext();
463
+ if (ctx) {
464
+ ctx.patchState = patchState;
465
+ }
466
+ }
467
+ /**
468
+ * Execute a workflow function within a context.
469
+ * This ensures all workflow operations have access to the correct context.
470
+ */
471
+ function runWithContext(context, fn) {
472
+ return workflowContextStorage.run(context, fn);
473
+ }
474
+ /**
475
+ * Get the storage strategy for the current workflow context.
476
+ *
477
+ * This function creates the appropriate storage strategy based on the current context:
478
+ * - If a WorkflowStep is available, returns CFWorkflowsStorageStrategy (FREE sleeping, durable)
479
+ * - Otherwise, returns InMemoryStorageStrategy (fallback, BILLABLE sleeping)
480
+ *
481
+ * Note: Strategies are created on-demand rather than stored in context to avoid
482
+ * stale strategy references when WorkflowStep is set/cleared dynamically.
483
+ */
484
+ function getCurrentStorageStrategy() {
485
+ const ctx = getCurrentContext();
486
+ const step = ctx?.workflowStep ?? null;
487
+ const storage = getCurrentStorage();
488
+ return createStorageStrategy(step, storage);
489
+ }
490
+ /**
491
+ * Get the current storage from context or fallback to global.
492
+ *
493
+ * This provides a single source of truth for storage access, consolidating
494
+ * the pattern of `ctx?.storage ?? globalStorage` that was previously
495
+ * scattered throughout the codebase.
496
+ */
497
+ function getCurrentStorage() {
498
+ const ctx = getCurrentContext();
499
+ return ctx?.storage ?? globalStorage;
500
+ }
501
+ // ============================================================================
502
+ // CF WORKFLOWS STEP CONTEXT - For native Cloudflare Workflows integration
503
+ // ============================================================================
504
+ /**
505
+ * Set the WorkflowStep context for the current workflow execution.
506
+ * Call this at the beginning of a CF Workflows run() method to enable
507
+ * native step.do() and step.sleep() execution.
508
+ *
509
+ * The step is stored in the workflow's AsyncLocalStorage context, ensuring
510
+ * isolation between concurrent workflow executions.
511
+ *
512
+ * @param step - The WorkflowStep from CF Workflows runtime, or null to clear
513
+ *
514
+ * @example
515
+ * ```typescript
516
+ * async run(event: WorkflowEvent, step: WorkflowStep) {
517
+ * setWorkflowStep(step)
518
+ * try {
519
+ * // Temporal compat layer will use step.sleep() and step.do()
520
+ * await sleep('5s')
521
+ * await activities.processOrder(event.payload.orderId)
522
+ * } finally {
523
+ * clearWorkflowStep()
524
+ * }
525
+ * }
526
+ * ```
527
+ */
528
+ export function setWorkflowStep(step) {
529
+ const ctx = getCurrentContext();
530
+ if (ctx) {
531
+ ctx.workflowStep = step;
532
+ }
533
+ }
534
+ /**
535
+ * Clear the WorkflowStep context for the current workflow.
536
+ * Call this when exiting the CF Workflows context.
537
+ */
538
+ export function clearWorkflowStep() {
539
+ const ctx = getCurrentContext();
540
+ if (ctx) {
541
+ ctx.workflowStep = null;
542
+ }
543
+ }
544
+ /**
545
+ * Get the current WorkflowStep context, or null if not in a CF Workflows context.
546
+ * Used internally by sleep() and proxyActivities() to route to native APIs.
547
+ */
548
+ export function getWorkflowStep() {
549
+ const ctx = getCurrentContext();
550
+ return ctx?.workflowStep ?? null;
551
+ }
552
+ /**
553
+ * Format a duration in milliseconds to CF Workflows format string.
554
+ * CF Workflows accepts durations like '5s', '1m', '1h', etc.
555
+ *
556
+ * @param ms - Duration in milliseconds
557
+ * @returns Formatted duration string
558
+ */
559
+ function formatDurationForCF(ms) {
560
+ if (ms < 1000) {
561
+ return `${ms}ms`;
562
+ }
563
+ if (ms < 60 * 1000) {
564
+ const seconds = Math.round(ms / 1000);
565
+ return `${seconds}s`;
566
+ }
567
+ if (ms < 60 * 60 * 1000) {
568
+ const minutes = Math.round(ms / (60 * 1000));
569
+ return `${minutes}m`;
570
+ }
571
+ const hours = Math.round(ms / (60 * 60 * 1000));
572
+ return `${hours}h`;
573
+ }
574
+ /**
575
+ * Generate a unique step ID for sleep operations.
576
+ * Uses per-workflow counter for deterministic IDs across concurrent workflows.
577
+ *
578
+ * @param ms - Duration in milliseconds
579
+ * @returns Unique step ID
580
+ */
581
+ function generateSleepStepId(ms) {
582
+ const ctx = getCurrentContext();
583
+ if (!ctx?.workflow) {
584
+ // Fallback for non-workflow context (testing)
585
+ return `sleep:${formatDurationForCF(ms)}:${Date.now()}`;
586
+ }
587
+ ctx.workflow.sleepStepCounter++;
588
+ return `sleep:${formatDurationForCF(ms)}:${ctx.workflow.sleepStepCounter}`;
589
+ }
590
+ // ============================================================================
591
+ // DETERMINISTIC TIME - workflowNow() implementation
592
+ // ============================================================================
593
+ /**
594
+ * Per-workflow counters for deterministic workflowNow() step IDs
595
+ */
596
+ const nowCounters = new WeakMap();
597
+ /**
598
+ * Get deterministic current time (for replay).
599
+ *
600
+ * This function returns a deterministic timestamp based on workflow start time
601
+ * plus an offset derived from the step count. On replay, it returns the same
602
+ * timestamp that was recorded during the original execution.
603
+ *
604
+ * Use this instead of `Date.now()` or `new Date()` in workflow code.
605
+ *
606
+ * @returns A Date object representing the current workflow time
607
+ * @throws Error if called outside a workflow context
608
+ *
609
+ * @example
610
+ * ```typescript
611
+ * import { workflowNow } from '@dotdo/temporal'
612
+ *
613
+ * export async function orderWorkflow() {
614
+ * // Use workflowNow() instead of new Date() or Date.now()
615
+ * const orderTime = workflowNow()
616
+ * const expiresAt = new Date(workflowNow().getTime() + 24 * 60 * 60 * 1000)
617
+ *
618
+ * return { orderTime, expiresAt }
619
+ * }
620
+ * ```
621
+ */
622
+ export function workflowNow() {
623
+ const workflow = getCurrentWorkflow();
624
+ if (!workflow) {
625
+ // Outside workflow context - fall back to real time
626
+ // This allows usage in tests or non-workflow code
627
+ return new Date();
628
+ }
629
+ // Get and increment the counter for this workflow
630
+ const counter = nowCounters.get(workflow) ?? 0;
631
+ nowCounters.set(workflow, counter + 1);
632
+ // Create deterministic step ID based on call order
633
+ const stepId = `workflowNow:${counter}`;
634
+ // Check for existing result (replay case)
635
+ if (workflow.stepResults.has(stepId)) {
636
+ return new Date(workflow.stepResults.get(stepId));
637
+ }
638
+ // Calculate deterministic time:
639
+ // Start time + (counter * small increment to show progression)
640
+ // This ensures time appears to progress while remaining deterministic
641
+ // NOTE: We use the workflowNow counter (not historyLength) because historyLength
642
+ // can vary between replays due to conditional paths or optimizations
643
+ const baseTime = workflow.startTime.getTime();
644
+ const stepIncrement = counter + 1; // 1ms per call for minimal progression (counter is 0-indexed)
645
+ const deterministicTime = baseTime + stepIncrement;
646
+ // Store for replay
647
+ workflow.stepResults.set(stepId, deterministicTime);
648
+ workflow.historyLength++;
649
+ return new Date(deterministicTime);
650
+ }
651
+ // ============================================================================
652
+ // NON-DETERMINISTIC PATTERN DETECTION - Interceptors for common violations
653
+ // ============================================================================
654
+ // Store original functions for restoration and proxying
655
+ const originalDateNow = Date.now;
656
+ const originalMathRandom = Math.random;
657
+ const originalFetch = typeof fetch !== 'undefined' ? fetch : undefined;
658
+ const originalSetTimeout = setTimeout;
659
+ const originalSetInterval = setInterval;
660
+ /**
661
+ * Wrapped Date.now that warns about non-deterministic usage in workflows
662
+ */
663
+ function wrappedDateNow() {
664
+ const workflow = getCurrentWorkflow();
665
+ if (workflow && determinismConfig.warnOnNonDeterministic) {
666
+ warnNonDeterministic('Date.now', 'Date.now() is non-deterministic and will return different values on replay', 'Use workflowNow() for deterministic timestamps');
667
+ }
668
+ return originalDateNow.call(Date);
669
+ }
670
+ /**
671
+ * Wrapped Math.random that warns about non-deterministic usage in workflows
672
+ */
673
+ function wrappedMathRandom() {
674
+ const workflow = getCurrentWorkflow();
675
+ if (workflow && determinismConfig.warnOnNonDeterministic) {
676
+ warnNonDeterministic('Math.random', 'Math.random() is non-deterministic and will return different values on replay', 'Use random() for deterministic random numbers');
677
+ }
678
+ return originalMathRandom.call(Math);
679
+ }
680
+ /**
681
+ * Wrapped fetch that warns about non-deterministic usage in workflows
682
+ */
683
+ function wrappedFetch(input, init) {
684
+ const workflow = getCurrentWorkflow();
685
+ if (workflow && determinismConfig.warnOnNonDeterministic) {
686
+ warnNonDeterministic('fetch', 'fetch() is non-deterministic - network responses can vary between executions', 'Use activities (proxyActivities) for network calls');
687
+ }
688
+ return originalFetch(input, init);
689
+ }
690
+ /**
691
+ * Wrapped setTimeout that warns about non-deterministic usage in workflows
692
+ */
693
+ function wrappedSetTimeout(callback, ms, ...args) {
694
+ const workflow = getCurrentWorkflow();
695
+ if (workflow && determinismConfig.warnOnNonDeterministic) {
696
+ warnNonDeterministic('setTimeout', 'setTimeout() is non-deterministic - timing varies between executions', 'Use sleep() or createTimer() for durable delays');
697
+ }
698
+ return originalSetTimeout(callback, ms, ...args);
699
+ }
700
+ /**
701
+ * Wrapped setInterval that warns about non-deterministic usage in workflows
702
+ */
703
+ function wrappedSetInterval(callback, ms, ...args) {
704
+ const workflow = getCurrentWorkflow();
705
+ if (workflow && determinismConfig.warnOnNonDeterministic) {
706
+ warnNonDeterministic('setInterval', 'setInterval() is non-deterministic - timing varies between executions', 'Use sleep() in a loop or schedule recurring activities');
707
+ }
708
+ return originalSetInterval(callback, ms, ...args);
709
+ }
710
+ /**
711
+ * Enable determinism detection by patching global functions.
712
+ * This is automatically called when the module loads in development mode.
713
+ *
714
+ * Note: This patches global objects, which may affect other code.
715
+ * Use with caution in shared environments.
716
+ */
717
+ export function enableDeterminismDetection() {
718
+ // Only patch if we're configured to warn
719
+ if (!determinismConfig.warnOnNonDeterministic) {
720
+ return;
721
+ }
722
+ // Patch Date.now
723
+ Date.now = wrappedDateNow;
724
+ // Patch Math.random
725
+ Math.random = wrappedMathRandom;
726
+ // Patch fetch if available
727
+ if (originalFetch && typeof globalThis !== 'undefined') {
728
+ ;
729
+ globalThis.fetch = wrappedFetch;
730
+ }
731
+ // Patch setTimeout and setInterval
732
+ // Note: These are intentionally not patched by default as they're used internally
733
+ // by the workflow runtime. Only patch if explicitly requested.
734
+ }
735
+ /**
736
+ * Disable determinism detection and restore original functions.
737
+ */
738
+ export function disableDeterminismDetection() {
739
+ Date.now = originalDateNow;
740
+ Math.random = originalMathRandom;
741
+ if (originalFetch && typeof globalThis !== 'undefined') {
742
+ ;
743
+ globalThis.fetch = originalFetch;
744
+ }
745
+ }
746
+ /**
747
+ * Check if code is running within a workflow context.
748
+ * Useful for conditional behavior based on workflow vs non-workflow execution.
749
+ */
750
+ export function inWorkflowContext() {
751
+ return getCurrentWorkflow() !== null;
752
+ }
753
+ // ============================================================================
754
+ // GLOBAL REGISTRIES - These are shared lookup tables, not per-execution state
755
+ // ============================================================================
756
+ let globalStorage = new InMemoryStepStorage();
757
+ let globalState = null;
758
+ let globalWaitManager = null;
759
+ let globalNamespace = 'default';
760
+ // Workflow registry - shared across all executions for handle lookups
761
+ const workflows = new Map();
762
+ const workflowFunctions = new Map();
763
+ /**
764
+ * Shared ActivityRouter instance used for routing activities to workers.
765
+ * This provides the unified routing abstraction used across all compat layers.
766
+ */
767
+ const activityRouter = new WorkerActivityRouter();
768
+ /**
769
+ * Register a worker for a task queue.
770
+ *
771
+ * In real Temporal, workers poll task queues for work. This compat layer
772
+ * validates that a worker is registered before allowing workflow/activity
773
+ * execution on that queue.
774
+ *
775
+ * @param taskQueue - The task queue name to register
776
+ * @param handler - Optional handler configuration for the worker
777
+ * @returns A function to unregister the worker
778
+ *
779
+ * @example
780
+ * ```typescript
781
+ * import { registerWorker } from '@dotdo/temporal'
782
+ *
783
+ * // Simple registration - just validates the queue exists
784
+ * const unregister = registerWorker('my-task-queue')
785
+ *
786
+ * // With workflow types
787
+ * const unregister = registerWorker('my-task-queue', {
788
+ * workflowTypes: new Set(['orderWorkflow', 'paymentWorkflow']),
789
+ * })
790
+ *
791
+ * // Cleanup when done
792
+ * unregister()
793
+ * ```
794
+ */
795
+ export function registerWorker(taskQueue, handler = {}) {
796
+ // Delegate to the shared ActivityRouter instance
797
+ return activityRouter.registerWorker(taskQueue, handler);
798
+ }
799
+ /**
800
+ * Check if a task queue has a registered worker
801
+ */
802
+ export function hasWorker(taskQueue) {
803
+ return activityRouter.hasWorker(taskQueue);
804
+ }
805
+ /**
806
+ * Get the worker handler for a task queue
807
+ */
808
+ export function getWorker(taskQueue) {
809
+ return activityRouter.getWorker(taskQueue);
810
+ }
811
+ /**
812
+ * List all registered task queues
813
+ */
814
+ export function listTaskQueues() {
815
+ return activityRouter.listTaskQueues();
816
+ }
817
+ /**
818
+ * Check if task queue routing is enabled.
819
+ *
820
+ * Task queue validation is enabled when at least one worker has been registered.
821
+ * This maintains backward compatibility - existing code that doesnt use
822
+ * registerWorker() will continue to work without changes.
823
+ */
824
+ function isTaskQueueRoutingEnabled() {
825
+ return activityRouter.isRoutingEnabled();
826
+ }
827
+ /**
828
+ * Validate that a task queue is registered for workflow execution.
829
+ * Throws TaskQueueNotRegisteredError if not registered.
830
+ *
831
+ * NOTE: Validation is only performed when task queue routing is enabled
832
+ * (i.e., when at least one worker has been registered via registerWorker()).
833
+ * This maintains backward compatibility with existing code.
834
+ *
835
+ * @param taskQueue - The task queue to validate
836
+ * @param workflowType - Optional workflow type for more specific validation
837
+ * @throws TaskQueueNotRegisteredError if no worker is registered
838
+ */
839
+ function validateTaskQueueForWorkflow(taskQueue, workflowType) {
840
+ // Skip validation if no workers are registered (backward compatibility)
841
+ if (!isTaskQueueRoutingEnabled()) {
842
+ return;
843
+ }
844
+ const worker = activityRouter.getWorker(taskQueue);
845
+ if (!worker) {
846
+ throw new TaskQueueNotRegisteredError(taskQueue, 'workflow');
847
+ }
848
+ // If worker specifies workflow types, validate this type is registered
849
+ if (workflowType && worker.workflowTypes && worker.workflowTypes.size > 0) {
850
+ if (!worker.workflowTypes.has(workflowType)) {
851
+ throw new Error(`Workflow type "${workflowType}" is not registered on task queue "${taskQueue}". ` +
852
+ `Registered types: ${Array.from(worker.workflowTypes).join(', ')}`);
853
+ }
854
+ }
855
+ }
856
+ /**
857
+ * Validate that a task queue is registered for activity execution.
858
+ * Throws TaskQueueNotRegisteredError if not registered.
859
+ *
860
+ * NOTE: Validation is only performed when task queue routing is enabled
861
+ * (i.e., when at least one worker has been registered via registerWorker()).
862
+ * This maintains backward compatibility with existing code.
863
+ *
864
+ * @param taskQueue - The task queue to validate
865
+ * @param activityName - Optional activity name for more specific validation
866
+ * @throws TaskQueueNotRegisteredError if no worker is registered
867
+ */
868
+ function validateTaskQueueForActivity(taskQueue, activityName) {
869
+ // Skip validation if no workers are registered (backward compatibility)
870
+ if (!isTaskQueueRoutingEnabled()) {
871
+ return;
872
+ }
873
+ const worker = activityRouter.getWorker(taskQueue);
874
+ if (!worker) {
875
+ throw new TaskQueueNotRegisteredError(taskQueue, 'activity');
876
+ }
877
+ // If worker specifies activity types, validate this activity is registered
878
+ if (activityName && worker.activityTypes && worker.activityTypes.size > 0) {
879
+ if (!worker.activityTypes.has(activityName)) {
880
+ throw new Error(`Activity "${activityName}" is not registered on task queue "${taskQueue}". ` +
881
+ `Registered activities: ${Array.from(worker.activityTypes).join(', ')}`);
882
+ }
883
+ }
884
+ }
885
+ // Timer tracking - global for cancel operations by ID
886
+ const activeTimers = new Map();
887
+ // ============================================================================
888
+ // WORKFLOW REGISTRY CLEANUP - Memory leak prevention
889
+ // ============================================================================
890
+ // Completed workflows are kept for a short TTL to allow result retrieval
891
+ const WORKFLOW_COMPLETED_TTL_MS = 60 * 60 * 1000; // 1 hour
892
+ // Maximum number of workflows to keep in registry (LRU eviction)
893
+ const WORKFLOW_MAX_REGISTRY_SIZE = 10000;
894
+ // Cleanup interval for expired workflows
895
+ const WORKFLOW_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
896
+ // Track completion times for TTL-based cleanup
897
+ const workflowCompletionTimes = new Map();
898
+ let workflowCleanupIntervalId = null;
899
+ /**
900
+ * Terminal states that indicate a workflow has finished execution
901
+ */
902
+ const TERMINAL_STATES = new Set([
903
+ 'COMPLETED',
904
+ 'FAILED',
905
+ 'CANCELED',
906
+ 'TERMINATED',
907
+ 'CONTINUED_AS_NEW',
908
+ 'TIMED_OUT',
909
+ ]);
910
+ /**
911
+ * Check if a workflow status is terminal (finished)
912
+ */
913
+ function isTerminalState(status) {
914
+ return TERMINAL_STATES.has(status);
915
+ }
916
+ /**
917
+ * Mark a workflow as completed and schedule cleanup.
918
+ * Called when a workflow transitions to a terminal state.
919
+ */
920
+ function markWorkflowCompleted(workflowId) {
921
+ workflowCompletionTimes.set(workflowId, Date.now());
922
+ }
923
+ /**
924
+ * Remove a workflow from the registry and cleanup tracking
925
+ */
926
+ function removeWorkflow(workflowId) {
927
+ workflows.delete(workflowId);
928
+ workflowCompletionTimes.delete(workflowId);
929
+ }
930
+ /**
931
+ * Clean up expired workflows (past TTL) and enforce LRU eviction.
932
+ * This runs periodically to prevent unbounded memory growth.
933
+ */
934
+ function cleanupExpiredWorkflows() {
935
+ const now = Date.now();
936
+ const expiredIds = [];
937
+ // Find workflows past their TTL
938
+ for (const [workflowId, completionTime] of Array.from(workflowCompletionTimes.entries())) {
939
+ if (now - completionTime >= WORKFLOW_COMPLETED_TTL_MS) {
940
+ expiredIds.push(workflowId);
941
+ }
942
+ }
943
+ // Remove expired workflows
944
+ for (const workflowId of expiredIds) {
945
+ removeWorkflow(workflowId);
946
+ }
947
+ // LRU eviction if still over max size
948
+ if (workflows.size > WORKFLOW_MAX_REGISTRY_SIZE) {
949
+ // Sort completed workflows by completion time (oldest first)
950
+ const completedWorkflows = Array.from(workflowCompletionTimes.entries())
951
+ .sort((a, b) => a[1] - b[1]);
952
+ // Evict oldest completed workflows until under limit
953
+ const excessCount = workflows.size - WORKFLOW_MAX_REGISTRY_SIZE;
954
+ for (let i = 0; i < Math.min(excessCount, completedWorkflows.length); i++) {
955
+ removeWorkflow(completedWorkflows[i][0]);
956
+ }
957
+ }
958
+ }
959
+ /**
960
+ * Start the periodic workflow cleanup (for production use).
961
+ * This should be called once when the module is loaded in a long-running process.
962
+ */
963
+ export function __startWorkflowCleanup() {
964
+ if (workflowCleanupIntervalId === null) {
965
+ workflowCleanupIntervalId = setInterval(cleanupExpiredWorkflows, WORKFLOW_CLEANUP_INTERVAL_MS);
966
+ // Unref the interval so it doesn't prevent process exit
967
+ if (typeof workflowCleanupIntervalId === 'object' && 'unref' in workflowCleanupIntervalId) {
968
+ workflowCleanupIntervalId.unref();
969
+ }
970
+ }
971
+ }
972
+ /**
973
+ * Stop the periodic workflow cleanup.
974
+ */
975
+ export function __stopWorkflowCleanup() {
976
+ if (workflowCleanupIntervalId !== null) {
977
+ clearInterval(workflowCleanupIntervalId);
978
+ workflowCleanupIntervalId = null;
979
+ }
980
+ }
981
+ // ============================================================================
982
+ // LAZY CLEANUP INITIALIZATION - Auto-start cleanup on first usage
983
+ // ============================================================================
984
+ /**
985
+ * Track whether cleanup intervals have been auto-started.
986
+ * This enables lazy initialization - cleanup only starts when actually needed.
987
+ */
988
+ let cleanupStarted = false;
989
+ /**
990
+ * Ensure cleanup intervals are started (lazy initialization).
991
+ *
992
+ * This function is called automatically when:
993
+ * - A workflow is started (WorkflowClient.start, startChild)
994
+ * - A timer is created (createTimer)
995
+ *
996
+ * This eliminates the need to manually call __startWorkflowCleanup() and
997
+ * __startTimerCleanup(), preventing memory leaks from accumulated workflows
998
+ * and timers even when the manual calls are forgotten.
999
+ *
1000
+ * The cleanup intervals are idempotent - calling this multiple times is safe.
1001
+ */
1002
+ export function ensureCleanupStarted() {
1003
+ if (cleanupStarted)
1004
+ return;
1005
+ cleanupStarted = true;
1006
+ __startWorkflowCleanup();
1007
+ __startTimerCleanup();
1008
+ }
1009
+ /**
1010
+ * Reset the cleanup started flag (for testing only).
1011
+ * This allows tests to verify the lazy initialization behavior.
1012
+ */
1013
+ export function __resetCleanupStarted() {
1014
+ cleanupStarted = false;
1015
+ }
1016
+ /**
1017
+ * Check if cleanup has been auto-started (for testing only).
1018
+ */
1019
+ export function __isCleanupStarted() {
1020
+ return cleanupStarted;
1021
+ }
1022
+ /**
1023
+ * Configure the Temporal compat layer globals.
1024
+ *
1025
+ * This is the single entry point for backend configuration. The storage
1026
+ * strategy (CFWorkflows vs InMemory) is auto-detected at runtime based on
1027
+ * whether a WorkflowStep context is available.
1028
+ *
1029
+ * ## Configuration Flow
1030
+ *
1031
+ * 1. Set `storage` for durable step persistence
1032
+ * 2. Set `state` for DurableObject state access (required for waitForEvent)
1033
+ * 3. Set `namespace` for workflow isolation
1034
+ *
1035
+ * ## Storage Strategy Selection (Automatic)
1036
+ *
1037
+ * - If WorkflowStep context is available: Uses CFWorkflowsStorageStrategy
1038
+ * - sleep() uses step.sleep() - FREE (no wall-clock billing)
1039
+ * - Activities use step.do() - DURABLE (survives restarts)
1040
+ *
1041
+ * - Otherwise: Uses InMemoryStorageStrategy (fallback)
1042
+ * - sleep() uses setTimeout - BILLABLE (consumes DO time)
1043
+ * - Activities execute directly - NOT DURABLE
1044
+ *
1045
+ * @example
1046
+ * ```typescript
1047
+ * import { configure } from '@dotdo/temporal'
1048
+ *
1049
+ * // In a Durable Object
1050
+ * configure({
1051
+ * storage: new DOStepStorage(ctx.storage),
1052
+ * state: ctx.state,
1053
+ * namespace: 'production'
1054
+ * })
1055
+ * ```
1056
+ */
1057
+ export function configure(opts) {
1058
+ if (opts.storage)
1059
+ globalStorage = opts.storage;
1060
+ if (opts.state) {
1061
+ globalState = opts.state;
1062
+ globalWaitManager = new WaitForEventManager(opts.state);
1063
+ }
1064
+ if (opts.namespace)
1065
+ globalNamespace = opts.namespace;
1066
+ }
1067
+ // ============================================================================
1068
+ // UTILITIES
1069
+ // Note: parseDuration and ensureError are imported from '../utils'
1070
+ // ============================================================================
1071
+ function generateWorkflowId() {
1072
+ return `wf_${crypto.randomUUID().replace(/-/g, '')}`;
1073
+ }
1074
+ function generateRunId() {
1075
+ return `run_${crypto.randomUUID().replace(/-/g, '')}`;
1076
+ }
1077
+ function generateTimerId() {
1078
+ return `timer_${crypto.randomUUID().replace(/-/g, '')}`;
1079
+ }
1080
+ // ============================================================================
1081
+ // SIGNAL, QUERY, UPDATE DEFINITIONS
1082
+ // ============================================================================
1083
+ /**
1084
+ * Define a signal
1085
+ */
1086
+ export function defineSignal(name) {
1087
+ return { name, type: 'signal' };
1088
+ }
1089
+ /**
1090
+ * Define a query
1091
+ */
1092
+ export function defineQuery(name) {
1093
+ return { name, type: 'query' };
1094
+ }
1095
+ /**
1096
+ * Define an update
1097
+ */
1098
+ export function defineUpdate(name) {
1099
+ return { name, type: 'update' };
1100
+ }
1101
+ export function setHandler(definition, handler) {
1102
+ const workflow = getCurrentWorkflow();
1103
+ if (!workflow) {
1104
+ throw new Error('setHandler can only be called within a workflow');
1105
+ }
1106
+ if (definition.type === 'signal') {
1107
+ workflow.signalHandlers.set(definition.name, handler);
1108
+ }
1109
+ else if (definition.type === 'query') {
1110
+ workflow.queryHandlers.set(definition.name, handler);
1111
+ }
1112
+ else {
1113
+ workflow.updateHandlers.set(definition.name, handler);
1114
+ }
1115
+ }
1116
+ // ============================================================================
1117
+ // WORKFLOW INFO - Enhanced with full context
1118
+ // ============================================================================
1119
+ /**
1120
+ * Get current workflow info
1121
+ */
1122
+ export function workflowInfo() {
1123
+ const workflow = getCurrentWorkflow();
1124
+ if (!workflow) {
1125
+ throw new Error('workflowInfo can only be called within a workflow');
1126
+ }
1127
+ return {
1128
+ workflowId: workflow.workflowId,
1129
+ runId: workflow.runId,
1130
+ workflowType: workflow.workflowType,
1131
+ taskQueue: workflow.taskQueue,
1132
+ namespace: workflow.namespace,
1133
+ firstExecutionRunId: workflow.runId,
1134
+ attempt: workflow.attempt,
1135
+ historyLength: workflow.historyLength,
1136
+ startTime: workflow.startTime,
1137
+ runStartTime: workflow.runStartTime,
1138
+ memo: workflow.memo,
1139
+ searchAttributes: workflow.searchAttributes,
1140
+ parent: workflow.parent,
1141
+ };
1142
+ }
1143
+ // ============================================================================
1144
+ // SEARCH ATTRIBUTES - Full implementation
1145
+ // ============================================================================
1146
+ /**
1147
+ * Set search attributes (replaces all)
1148
+ */
1149
+ export function setSearchAttributes(attrs) {
1150
+ const workflow = getCurrentWorkflow();
1151
+ if (!workflow) {
1152
+ throw new Error('setSearchAttributes can only be called within a workflow');
1153
+ }
1154
+ workflow.searchAttributes = { ...attrs };
1155
+ workflow.historyLength++;
1156
+ }
1157
+ /**
1158
+ * Upsert (merge) search attributes
1159
+ */
1160
+ export function upsertSearchAttributes(attrs) {
1161
+ const workflow = getCurrentWorkflow();
1162
+ if (!workflow) {
1163
+ throw new Error('upsertSearchAttributes can only be called within a workflow');
1164
+ }
1165
+ workflow.searchAttributes = {
1166
+ ...workflow.searchAttributes,
1167
+ ...attrs,
1168
+ };
1169
+ workflow.historyLength++;
1170
+ }
1171
+ // ============================================================================
1172
+ // TIMERS - Full implementation with coalescing optimization
1173
+ // ============================================================================
1174
+ // Timer coalescing: Group timers that fire within the same 10ms window
1175
+ const TIMER_COALESCE_WINDOW_MS = 10;
1176
+ const coalescedTimerBuckets = new Map();
1177
+ // Store bucket timeouts separately to avoid "cancelled leader" bug
1178
+ // When the first timer in a bucket is cancelled, other timers still need the timeout
1179
+ const bucketTimeouts = new Map();
1180
+ // Periodic cleanup for stale timer buckets (memory leak prevention)
1181
+ // Timers are considered stale if they haven't fired 5 minutes past their expected time
1182
+ const TIMER_STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
1183
+ const TIMER_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // Run cleanup every 5 minutes
1184
+ let timerCleanupIntervalId = null;
1185
+ /**
1186
+ * Clean up stale timer buckets that haven't fired.
1187
+ * This handles cases where:
1188
+ * - Workflows terminate without cancelling their timers
1189
+ * - setTimeout fails to fire for some reason
1190
+ * - Timers are orphaned due to errors
1191
+ */
1192
+ function cleanupStaleTimerBuckets() {
1193
+ const now = Date.now();
1194
+ const bucketsToDelete = [];
1195
+ for (const [bucket, timers] of Array.from(coalescedTimerBuckets.entries())) {
1196
+ // Filter out stale timers from this bucket
1197
+ const activeTimersInBucket = timers.filter((timer) => {
1198
+ // Timer is stale if it's past its expected fire time + threshold
1199
+ const isStale = timer.pending && now > timer.expectedFireAt + TIMER_STALE_THRESHOLD_MS;
1200
+ if (isStale) {
1201
+ // Clean up the stale timer
1202
+ timer.pending = false;
1203
+ activeTimers.delete(timer.id);
1204
+ }
1205
+ return !isStale;
1206
+ });
1207
+ if (activeTimersInBucket.length === 0) {
1208
+ bucketsToDelete.push(bucket);
1209
+ }
1210
+ else if (activeTimersInBucket.length !== timers.length) {
1211
+ // Update the bucket with only active timers
1212
+ coalescedTimerBuckets.set(bucket, activeTimersInBucket);
1213
+ }
1214
+ }
1215
+ // Delete empty buckets and their timeouts
1216
+ for (const bucket of bucketsToDelete) {
1217
+ coalescedTimerBuckets.delete(bucket);
1218
+ const timeoutId = bucketTimeouts.get(bucket);
1219
+ if (timeoutId) {
1220
+ clearTimeout(timeoutId);
1221
+ bucketTimeouts.delete(bucket);
1222
+ }
1223
+ }
1224
+ }
1225
+ /**
1226
+ * Start the periodic timer cleanup (for production use).
1227
+ * This should be called once when the module is loaded in a long-running process.
1228
+ */
1229
+ export function __startTimerCleanup() {
1230
+ if (timerCleanupIntervalId === null) {
1231
+ timerCleanupIntervalId = setInterval(cleanupStaleTimerBuckets, TIMER_CLEANUP_INTERVAL_MS);
1232
+ // Unref the interval so it doesn't prevent process exit
1233
+ if (typeof timerCleanupIntervalId === 'object' && 'unref' in timerCleanupIntervalId) {
1234
+ timerCleanupIntervalId.unref();
1235
+ }
1236
+ }
1237
+ }
1238
+ /**
1239
+ * Stop the periodic timer cleanup.
1240
+ */
1241
+ export function __stopTimerCleanup() {
1242
+ if (timerCleanupIntervalId !== null) {
1243
+ clearInterval(timerCleanupIntervalId);
1244
+ timerCleanupIntervalId = null;
1245
+ }
1246
+ }
1247
+ /**
1248
+ * Create a cancellable timer with optional coalescing
1249
+ *
1250
+ * OPTIMIZATION: Timers firing within 10ms of each other are coalesced
1251
+ * into a single setTimeout call, reducing system call overhead.
1252
+ */
1253
+ export function createTimer(duration) {
1254
+ // Auto-start cleanup on first timer creation
1255
+ ensureCleanupStarted();
1256
+ const ms = parseDuration(duration);
1257
+ const id = generateTimerId();
1258
+ const now = Date.now();
1259
+ let resolveTimer;
1260
+ let rejectTimer;
1261
+ const promise = new Promise((resolve, reject) => {
1262
+ resolveTimer = resolve;
1263
+ rejectTimer = reject;
1264
+ });
1265
+ const timerState = {
1266
+ id,
1267
+ pending: true,
1268
+ resolve: resolveTimer,
1269
+ reject: rejectTimer,
1270
+ createdAt: now,
1271
+ expectedFireAt: now + ms,
1272
+ };
1273
+ activeTimers.set(id, timerState);
1274
+ // Calculate coalesce bucket (round to nearest TIMER_COALESCE_WINDOW_MS)
1275
+ const bucket = Math.floor(ms / TIMER_COALESCE_WINDOW_MS) * TIMER_COALESCE_WINDOW_MS;
1276
+ // Check if we can coalesce with an existing timer
1277
+ const existingBucket = coalescedTimerBuckets.get(bucket);
1278
+ if (existingBucket && existingBucket.length > 0) {
1279
+ // Coalesce: add to existing bucket
1280
+ existingBucket.push(timerState);
1281
+ }
1282
+ else {
1283
+ // Create new bucket with single timer
1284
+ const newBucket = [timerState];
1285
+ coalescedTimerBuckets.set(bucket, newBucket);
1286
+ // Set the actual timeout - stored on bucket, not individual timer
1287
+ // This prevents the "cancelled leader" bug where cancelling the first timer
1288
+ // would leave other timers in the bucket without a scheduled callback
1289
+ const timeoutId = setTimeout(() => {
1290
+ bucketTimeouts.delete(bucket);
1291
+ // Fire all timers in this bucket
1292
+ const timersToFire = coalescedTimerBuckets.get(bucket) || [];
1293
+ coalescedTimerBuckets.delete(bucket);
1294
+ for (const timer of timersToFire) {
1295
+ if (timer.pending) {
1296
+ timer.pending = false;
1297
+ timer.resolve();
1298
+ activeTimers.delete(timer.id);
1299
+ const workflow = getCurrentWorkflow();
1300
+ if (workflow) {
1301
+ workflow.historyLength++;
1302
+ }
1303
+ }
1304
+ }
1305
+ }, ms);
1306
+ bucketTimeouts.set(bucket, timeoutId);
1307
+ }
1308
+ // Create a TimerHandle with additional properties
1309
+ const handle = promise;
1310
+ Object.defineProperty(handle, 'id', { value: id, writable: false });
1311
+ Object.defineProperty(handle, 'pending', {
1312
+ get: () => timerState.pending,
1313
+ });
1314
+ return handle;
1315
+ }
1316
+ /**
1317
+ * Cancel a timer
1318
+ *
1319
+ * OPTIMIZATION: Also removes from coalesced bucket to prevent
1320
+ * unnecessary processing of cancelled timers.
1321
+ *
1322
+ * FIX: Timeouts are now stored on the bucket (bucketTimeouts), not on individual
1323
+ * timers. This prevents the "cancelled leader" bug where cancelling the first timer
1324
+ * in a bucket would leave other timers without a scheduled callback.
1325
+ * When cancelling, we only clear the bucket timeout if this was the last timer.
1326
+ */
1327
+ export function cancelTimer(timer) {
1328
+ const timerState = activeTimers.get(timer.id);
1329
+ if (timerState && timerState.pending) {
1330
+ timerState.pending = false;
1331
+ // Remove from coalesced bucket if present
1332
+ for (const [bucket, timers] of Array.from(coalescedTimerBuckets.entries())) {
1333
+ const index = timers.findIndex(t => t.id === timer.id);
1334
+ if (index !== -1) {
1335
+ timers.splice(index, 1);
1336
+ if (timers.length === 0) {
1337
+ // Only clear the bucket timeout when the last timer is cancelled
1338
+ coalescedTimerBuckets.delete(bucket);
1339
+ const timeoutId = bucketTimeouts.get(bucket);
1340
+ if (timeoutId) {
1341
+ clearTimeout(timeoutId);
1342
+ bucketTimeouts.delete(bucket);
1343
+ }
1344
+ }
1345
+ // If other timers remain in the bucket, leave the timeout running
1346
+ // so those timers will fire when the timeout expires
1347
+ break;
1348
+ }
1349
+ }
1350
+ timerState.reject(new WaitCancelledError('Timer cancelled'));
1351
+ activeTimers.delete(timer.id);
1352
+ }
1353
+ }
1354
+ /**
1355
+ * Clear all internal state (useful for testing)
1356
+ * Note: AsyncLocalStorage context is automatically cleaned up when execution ends
1357
+ *
1358
+ * This function properly cleans up all timer resources to prevent memory leaks:
1359
+ * - Cancels all pending setTimeout calls
1360
+ * - Clears all timer state maps
1361
+ * - Stops the periodic cleanup interval
1362
+ */
1363
+ export function __clearTemporalState() {
1364
+ workflows.clear();
1365
+ workflowFunctions.clear();
1366
+ // Clear workflow completion tracking (memory leak fix)
1367
+ workflowCompletionTimes.clear();
1368
+ // Clear task queue registry via activityRouter
1369
+ activityRouter.clear();
1370
+ // Properly cancel all active timers to prevent memory leaks
1371
+ for (const timer of Array.from(activeTimers.values())) {
1372
+ timer.pending = false;
1373
+ }
1374
+ activeTimers.clear();
1375
+ // Clear bucket timeouts (timeouts are stored on buckets, not individual timers)
1376
+ for (const timeoutId of Array.from(bucketTimeouts.values())) {
1377
+ clearTimeout(timeoutId);
1378
+ }
1379
+ bucketTimeouts.clear();
1380
+ // Clear coalesced timer buckets
1381
+ for (const timers of Array.from(coalescedTimerBuckets.values())) {
1382
+ for (const timer of timers) {
1383
+ timer.pending = false;
1384
+ }
1385
+ }
1386
+ coalescedTimerBuckets.clear();
1387
+ // Stop the periodic cleanup intervals
1388
+ __stopTimerCleanup();
1389
+ __stopWorkflowCleanup();
1390
+ // Clear determinism tracking
1391
+ clearDeterminismWarnings();
1392
+ disableDeterminismDetection();
1393
+ // Note: Step counters are now per-workflow (in WorkflowState), not global.
1394
+ // They are automatically reset when workflows are created.
1395
+ // Clear WorkflowStep context
1396
+ clearWorkflowStep();
1397
+ globalNamespace = 'default';
1398
+ }
1399
+ // ============================================================================
1400
+ // VERSIONING / PATCHING - Full implementation
1401
+ // ============================================================================
1402
+ /**
1403
+ * Check if a patch should be applied
1404
+ * For new executions, this always returns true (take the new path)
1405
+ * For replays of old executions, this returns false to maintain compatibility
1406
+ */
1407
+ export function patched(patchId) {
1408
+ let patchState = getCurrentPatchState();
1409
+ if (!patchState) {
1410
+ patchState = {
1411
+ appliedPatches: new Set(),
1412
+ deprecatedPatches: new Set(),
1413
+ };
1414
+ setCurrentPatchState(patchState);
1415
+ }
1416
+ // For new executions, always apply patches
1417
+ // In a full implementation, this would check workflow history
1418
+ patchState.appliedPatches.add(patchId);
1419
+ const workflow = getCurrentWorkflow();
1420
+ if (workflow) {
1421
+ workflow.historyLength++;
1422
+ }
1423
+ return true;
1424
+ }
1425
+ /**
1426
+ * Deprecate an old patch (removes it from consideration in new workflow code)
1427
+ */
1428
+ export function deprecatePatch(patchId) {
1429
+ let patchState = getCurrentPatchState();
1430
+ if (!patchState) {
1431
+ patchState = {
1432
+ appliedPatches: new Set(),
1433
+ deprecatedPatches: new Set(),
1434
+ };
1435
+ setCurrentPatchState(patchState);
1436
+ }
1437
+ patchState.deprecatedPatches.add(patchId);
1438
+ }
1439
+ // ============================================================================
1440
+ // SLEEP AND CONDITION
1441
+ // ============================================================================
1442
+ /**
1443
+ * Sleep for a duration (durable)
1444
+ *
1445
+ * This implementation integrates with both CF Workflows native sleep and
1446
+ * the Temporal compat layer's durable storage:
1447
+ *
1448
+ * 1. If WorkflowStep context is available (CF Workflows), use step.sleep()
1449
+ * - FREE: doesn't use billable DO time
1450
+ * - DURABLE: survives worker restarts
1451
+ *
1452
+ * 2. Otherwise, fall back to setTimeout with durable storage
1453
+ * - Persists sleep state to storage (in case of crash)
1454
+ * - On replay, completed sleeps are skipped immediately
1455
+ */
1456
+ export async function sleep(duration) {
1457
+ const ctx = getCurrentContext();
1458
+ const workflow = ctx?.workflow ?? null;
1459
+ if (!workflow) {
1460
+ throw new Error('sleep can only be called within a workflow');
1461
+ }
1462
+ const ms = parseDuration(duration);
1463
+ const durationStr = typeof duration === 'string' ? duration : formatDurationForCF(ms);
1464
+ const stepId = generateSleepStepId(ms);
1465
+ // Use the unified storage strategy pattern
1466
+ // - CFWorkflowsStorageStrategy: Uses step.sleep() - FREE, doesn't consume wall-clock time
1467
+ // - InMemoryStorageStrategy: Uses setTimeout with durable storage - BILLABLE
1468
+ const strategy = getCurrentStorageStrategy();
1469
+ await strategy.sleep(stepId, ms, durationStr);
1470
+ // Update in-memory cache for replay within same execution
1471
+ workflow.stepResults.set(stepId, true);
1472
+ workflow.historyLength++;
1473
+ }
1474
+ /**
1475
+ * Wait for a condition to be true
1476
+ */
1477
+ export async function condition(fn, timeout) {
1478
+ const workflow = getCurrentWorkflow();
1479
+ if (!workflow) {
1480
+ throw new Error('condition can only be called within a workflow');
1481
+ }
1482
+ const timeoutMs = timeout ? parseDuration(timeout) : undefined;
1483
+ const startTime = Date.now();
1484
+ while (true) {
1485
+ try {
1486
+ if (fn()) {
1487
+ break;
1488
+ }
1489
+ }
1490
+ catch (error) {
1491
+ // Log the error for debugging but let it propagate
1492
+ const err = ensureError(error);
1493
+ console.error(`[condition] Error in condition function: ${err.message}`);
1494
+ throw err;
1495
+ }
1496
+ // Check timeout
1497
+ if (timeoutMs && Date.now() - startTime >= timeoutMs) {
1498
+ return false;
1499
+ }
1500
+ // Poll every 100ms
1501
+ await new Promise((resolve) => setTimeout(resolve, 100));
1502
+ }
1503
+ return true;
1504
+ }
1505
+ // Note: ActivityTimeoutError and TaskQueueNotRegisteredError are imported from '../activity-router'
1506
+ // and re-exported at the top of this file for backward compatibility.
1507
+ //
1508
+ // Activity execution (retry, timeout, error handling) is now handled by the shared
1509
+ // WorkerActivityRouter instance, eliminating duplicate code.
1510
+ /**
1511
+ * Create activity proxies
1512
+ *
1513
+ * Activities integrate with multiple execution backends:
1514
+ *
1515
+ * 1. If a worker with executeActivity handler is registered, route to that handler
1516
+ * - Enables testing workflows with mock activity implementations
1517
+ * - Handles timeouts, retries, and error classification
1518
+ *
1519
+ * 2. If WorkflowStep context is available (CF Workflows), use step.do()
1520
+ * - DURABLE: automatically retries and survives restarts
1521
+ * - REPLAY: completed steps return cached results
1522
+ *
1523
+ * 3. Otherwise, fall back to DurableWorkflowRuntime
1524
+ *
1525
+ * Activities can specify a `taskQueue` option to route execution to a specific
1526
+ * worker. If a task queue is specified, it must have a registered worker.
1527
+ * If no task queue is specified, activities use the workflow's task queue.
1528
+ */
1529
+ export function proxyActivities(options) {
1530
+ // Activity task queue (can be different from workflow's task queue)
1531
+ const activityTaskQueue = options.taskQueue;
1532
+ // Parse timeouts once
1533
+ const startToCloseTimeoutMs = options.startToCloseTimeout
1534
+ ? parseDuration(options.startToCloseTimeout)
1535
+ : undefined;
1536
+ const heartbeatTimeoutMs = options.heartbeatTimeout
1537
+ ? parseDuration(options.heartbeatTimeout)
1538
+ : undefined;
1539
+ // Build CF Workflows step.do() options from Temporal activity options
1540
+ const buildStepDoOptions = () => {
1541
+ const stepOptions = {};
1542
+ // Map retry policy
1543
+ if (options.retry?.maximumAttempts) {
1544
+ stepOptions.retries = {
1545
+ limit: options.retry.maximumAttempts,
1546
+ backoff: options.retry.backoffCoefficient && options.retry.backoffCoefficient > 1 ? 'exponential' : 'constant',
1547
+ delay: options.retry.initialInterval ? String(options.retry.initialInterval) : undefined,
1548
+ };
1549
+ }
1550
+ // Map timeout
1551
+ if (options.startToCloseTimeout) {
1552
+ stepOptions.timeout = typeof options.startToCloseTimeout === 'string'
1553
+ ? options.startToCloseTimeout
1554
+ : formatDurationForCF(parseDuration(options.startToCloseTimeout));
1555
+ }
1556
+ // Only return options if we have something to configure
1557
+ if (stepOptions.retries || stepOptions.timeout) {
1558
+ return stepOptions;
1559
+ }
1560
+ return undefined;
1561
+ };
1562
+ return new Proxy({}, {
1563
+ get(_, name) {
1564
+ return async (...args) => {
1565
+ const workflow = getCurrentWorkflow();
1566
+ if (!workflow) {
1567
+ throw new Error('Activities can only be called within a workflow');
1568
+ }
1569
+ // Determine which task queue to use for this activity
1570
+ const targetTaskQueue = activityTaskQueue ?? workflow.taskQueue;
1571
+ // Validate the task queue has a registered worker
1572
+ validateTaskQueueForActivity(targetTaskQueue, name);
1573
+ // Include task queue in step ID to ensure isolation between queues
1574
+ const stepId = `activity:${targetTaskQueue}:${name}:${JSON.stringify(args)}`;
1575
+ // Check for replay in workflow stepResults (handles both success and error)
1576
+ if (workflow.stepResults.has(stepId)) {
1577
+ const cached = workflow.stepResults.get(stepId);
1578
+ // If cached value is an error, re-throw it
1579
+ if (cached instanceof Error) {
1580
+ throw cached;
1581
+ }
1582
+ return cached;
1583
+ }
1584
+ // Check for CF Workflows step context - use step.do() for durability
1585
+ const step = getWorkflowStep();
1586
+ if (step) {
1587
+ // Use CF Workflows native step.do() - DURABLE
1588
+ // Use per-workflow counter for deterministic IDs across concurrent workflows
1589
+ workflow.activityStepCounter++;
1590
+ const stepName = `activity:${name}:${workflow.activityStepCounter}`;
1591
+ const stepDoOptions = buildStepDoOptions();
1592
+ // The callback is what CF Workflows will execute.
1593
+ // When a worker handler is registered, invoke it for actual activity execution.
1594
+ // Otherwise, return a stub for CF Workflows runtime (production mode).
1595
+ const callback = async () => {
1596
+ // Check if worker has a handler for this activity
1597
+ const worker = activityRouter.getWorker(targetTaskQueue);
1598
+ if (worker?.executeActivity) {
1599
+ // Create activity context with cancellation signal
1600
+ const activityContext = {
1601
+ signal: workflow.abortController?.signal,
1602
+ };
1603
+ return worker.executeActivity(name, args, activityContext);
1604
+ }
1605
+ // Fallback for CF Workflows runtime (no local handler)
1606
+ return { _activity: name, _args: args, _stub: true };
1607
+ };
1608
+ // Call step.do() with or without options
1609
+ const result = stepDoOptions
1610
+ ? await step.do(stepName, stepDoOptions, callback)
1611
+ : await step.do(stepName, callback);
1612
+ workflow.stepResults.set(stepId, result);
1613
+ workflow.historyLength++;
1614
+ return result;
1615
+ }
1616
+ // Fallback when no WorkflowStep is available:
1617
+ // Get the worker for this task queue via activityRouter
1618
+ const worker = activityRouter.getWorker(targetTaskQueue);
1619
+ // If worker has executeActivity handler, route via activityRouter (no step.do() durability)
1620
+ if (worker?.executeActivity) {
1621
+ // Create activity context with cancellation signal
1622
+ const activityContext = {
1623
+ signal: workflow.abortController?.signal,
1624
+ };
1625
+ // Build ActivityRouterOptions from Temporal ActivityOptions
1626
+ // Use heartbeat timeout if specified and shorter than start-to-close timeout
1627
+ // Heartbeat timeout in Temporal means the activity must heartbeat within this interval
1628
+ // In our emulation, we use it as an effective timeout for activities that don't heartbeat
1629
+ let effectiveTimeout = startToCloseTimeoutMs;
1630
+ if (heartbeatTimeoutMs) {
1631
+ if (!effectiveTimeout || heartbeatTimeoutMs < effectiveTimeout) {
1632
+ effectiveTimeout = heartbeatTimeoutMs;
1633
+ }
1634
+ }
1635
+ const routerOptions = {
1636
+ taskQueue: targetTaskQueue,
1637
+ timeout: effectiveTimeout,
1638
+ retries: options.retry ? {
1639
+ maximumAttempts: options.retry.maximumAttempts,
1640
+ initialInterval: options.retry.initialInterval,
1641
+ backoffCoefficient: options.retry.backoffCoefficient,
1642
+ maximumInterval: options.retry.maximumInterval,
1643
+ nonRetryableErrors: options.retry.nonRetryableErrorTypes ? [...options.retry.nonRetryableErrorTypes] : undefined,
1644
+ } : undefined,
1645
+ };
1646
+ try {
1647
+ // Route activity via the shared ActivityRouter - handles timeouts and retries
1648
+ const result = await activityRouter.route(name, args, routerOptions, activityContext);
1649
+ // Cache successful result
1650
+ workflow.stepResults.set(stepId, result);
1651
+ workflow.historyLength++;
1652
+ return result;
1653
+ }
1654
+ catch (error) {
1655
+ // Cache error for replay (determinism)
1656
+ const err = ensureError(error);
1657
+ workflow.stepResults.set(stepId, err);
1658
+ workflow.historyLength++;
1659
+ throw err;
1660
+ }
1661
+ }
1662
+ // Fallback: Execute through DurableWorkflowRuntime
1663
+ // Uses getCurrentStorage() for consistent storage access
1664
+ const runtime = new DurableWorkflowRuntime({
1665
+ storage: getCurrentStorage(),
1666
+ retryPolicy: options.retry
1667
+ ? {
1668
+ maxAttempts: options.retry.maximumAttempts ?? 3,
1669
+ initialDelayMs: options.retry.initialInterval ? parseDuration(options.retry.initialInterval) : 1000,
1670
+ maxDelayMs: options.retry.maximumInterval ? parseDuration(options.retry.maximumInterval) : 30000,
1671
+ backoffMultiplier: options.retry.backoffCoefficient ?? 2,
1672
+ jitter: true,
1673
+ }
1674
+ : undefined,
1675
+ });
1676
+ const result = await runtime.executeStep(stepId, {
1677
+ path: ['Activity', name],
1678
+ context: { args, taskQueue: targetTaskQueue },
1679
+ contextHash: stepId,
1680
+ runtime,
1681
+ }, args, 'do');
1682
+ workflow.stepResults.set(stepId, result);
1683
+ workflow.historyLength++;
1684
+ return result;
1685
+ };
1686
+ },
1687
+ });
1688
+ }
1689
+ /**
1690
+ * Create local activity proxies
1691
+ */
1692
+ export function proxyLocalActivities(options) {
1693
+ // Local activities run in the same process with shorter timeouts
1694
+ return proxyActivities(options);
1695
+ }
1696
+ // ============================================================================
1697
+ // CHILD WORKFLOWS - Enhanced implementation
1698
+ // ============================================================================
1699
+ /**
1700
+ * Start a child workflow
1701
+ *
1702
+ * @throws TaskQueueNotRegisteredError if no worker is registered for the task queue
1703
+ */
1704
+ export async function startChild(workflowType, options) {
1705
+ // Auto-start cleanup on first workflow creation
1706
+ ensureCleanupStarted();
1707
+ const typeName = typeof workflowType === 'string' ? workflowType : workflowType.name;
1708
+ const workflowId = options.workflowId ?? generateWorkflowId();
1709
+ const runId = generateRunId();
1710
+ const now = new Date();
1711
+ const parentWorkflow = getCurrentWorkflow();
1712
+ // Determine the task queue for the child workflow
1713
+ const childTaskQueue = options.taskQueue ?? parentWorkflow?.taskQueue ?? 'default';
1714
+ // Validate task queue has a registered worker (like real Temporal)
1715
+ validateTaskQueueForWorkflow(childTaskQueue, typeName);
1716
+ // Get parent info if executing within a workflow
1717
+ const parentInfo = parentWorkflow
1718
+ ? {
1719
+ workflowId: parentWorkflow.workflowId,
1720
+ runId: parentWorkflow.runId,
1721
+ namespace: parentWorkflow.namespace,
1722
+ }
1723
+ : undefined;
1724
+ // Create child workflow state
1725
+ const childState = {
1726
+ workflowId,
1727
+ runId,
1728
+ workflowType: typeName,
1729
+ taskQueue: childTaskQueue,
1730
+ namespace: parentWorkflow?.namespace ?? globalNamespace,
1731
+ signalHandlers: new Map(),
1732
+ queryHandlers: new Map(),
1733
+ updateHandlers: new Map(),
1734
+ stepResults: new Map(),
1735
+ status: 'RUNNING',
1736
+ searchAttributes: options.searchAttributes ?? {},
1737
+ memo: options.memo,
1738
+ parent: parentInfo,
1739
+ startTime: now,
1740
+ runStartTime: now,
1741
+ historyLength: 1,
1742
+ attempt: 1,
1743
+ children: new Set(),
1744
+ parentClosePolicy: options.parentClosePolicy,
1745
+ abortController: new AbortController(),
1746
+ sleepStepCounter: 0,
1747
+ activityStepCounter: 0,
1748
+ };
1749
+ workflows.set(workflowId, childState);
1750
+ // Track child in parent
1751
+ if (parentWorkflow) {
1752
+ parentWorkflow.children.add(workflowId);
1753
+ parentWorkflow.historyLength++;
1754
+ }
1755
+ // Execute in background with its own context
1756
+ const workflowFn = typeof workflowType === 'function' ? workflowType : workflowFunctions.get(typeName);
1757
+ if (workflowFn) {
1758
+ // Create a new context for the child workflow
1759
+ // Inherits storage from parent context or uses global fallback
1760
+ const childContext = {
1761
+ workflow: childState,
1762
+ patchState: null,
1763
+ storage: getCurrentStorage(),
1764
+ waitManager: globalWaitManager,
1765
+ workflowStep: null,
1766
+ };
1767
+ // Run the child workflow in its own context
1768
+ runWithContext(childContext, () => {
1769
+ workflowFn(...(options.args ?? []))
1770
+ .then((result) => {
1771
+ childState.status = 'COMPLETED';
1772
+ childState.result = result;
1773
+ markWorkflowCompleted(workflowId);
1774
+ })
1775
+ .catch((error) => {
1776
+ childState.status = 'FAILED';
1777
+ childState.error = error;
1778
+ markWorkflowCompleted(workflowId);
1779
+ });
1780
+ });
1781
+ }
1782
+ return {
1783
+ workflowId,
1784
+ firstExecutionRunId: runId,
1785
+ async result() {
1786
+ // Poll until complete
1787
+ while (childState.status === 'RUNNING') {
1788
+ await new Promise((resolve) => setTimeout(resolve, 10));
1789
+ }
1790
+ if (childState.status === 'FAILED' && childState.error) {
1791
+ throw childState.error;
1792
+ }
1793
+ if (childState.status === 'CANCELED') {
1794
+ throw new WaitCancelledError('Child workflow was cancelled');
1795
+ }
1796
+ return childState.result;
1797
+ },
1798
+ async signal(signal, ...args) {
1799
+ const handler = childState.signalHandlers.get(signal.name);
1800
+ if (handler) {
1801
+ await handler(...args);
1802
+ }
1803
+ },
1804
+ async cancel() {
1805
+ childState.status = 'CANCELED';
1806
+ markWorkflowCompleted(workflowId);
1807
+ },
1808
+ };
1809
+ }
1810
+ /**
1811
+ * Execute a child workflow and wait for result
1812
+ */
1813
+ export async function executeChild(workflowType, options) {
1814
+ const handle = await startChild(workflowType, options);
1815
+ return handle.result();
1816
+ }
1817
+ // ============================================================================
1818
+ // CANCELLATION
1819
+ // ============================================================================
1820
+ export class CancellationScope {
1821
+ children = [];
1822
+ cleanupFns = [];
1823
+ _isCancelled = false;
1824
+ get isCancelled() {
1825
+ return this._isCancelled;
1826
+ }
1827
+ cancel() {
1828
+ this._isCancelled = true;
1829
+ for (const child of this.children) {
1830
+ child.cancel();
1831
+ }
1832
+ }
1833
+ /**
1834
+ * Run a function in this cancellation scope
1835
+ */
1836
+ static async run(fn) {
1837
+ const scope = new CancellationScope();
1838
+ try {
1839
+ return await fn();
1840
+ }
1841
+ finally {
1842
+ for (const cleanup of scope.cleanupFns) {
1843
+ await cleanup();
1844
+ }
1845
+ }
1846
+ }
1847
+ /**
1848
+ * Create a non-cancellable scope
1849
+ */
1850
+ static nonCancellable(fn) {
1851
+ return fn();
1852
+ }
1853
+ /**
1854
+ * Create a cancellable scope with timeout
1855
+ */
1856
+ static async cancellable(fn) {
1857
+ return CancellationScope.run(fn);
1858
+ }
1859
+ }
1860
+ /**
1861
+ * Check if cancelled
1862
+ */
1863
+ export function isCancellation(error) {
1864
+ return error instanceof WaitCancelledError;
1865
+ }
1866
+ // ============================================================================
1867
+ // CONTINUE AS NEW
1868
+ // ============================================================================
1869
+ export class ContinueAsNew extends Error {
1870
+ args;
1871
+ options;
1872
+ constructor(args, options = {}) {
1873
+ super('ContinueAsNew');
1874
+ this.name = 'ContinueAsNew';
1875
+ this.args = args;
1876
+ this.options = options;
1877
+ }
1878
+ }
1879
+ /**
1880
+ * Continue as new with fresh history
1881
+ */
1882
+ export function continueAsNew(...args) {
1883
+ throw new ContinueAsNew(args);
1884
+ }
1885
+ /**
1886
+ * Make continue-as-new function for a specific workflow
1887
+ */
1888
+ export function makeContinueAsNewFunc(_workflowType, options) {
1889
+ return (...args) => {
1890
+ throw new ContinueAsNew(args, options);
1891
+ };
1892
+ }
1893
+ // ============================================================================
1894
+ // TEMPORAL ERROR CLASSES - Standard error types for workflow failures
1895
+ // ============================================================================
1896
+ /**
1897
+ * Application-level failure that can be thrown from workflows or activities.
1898
+ * Use this to signal business logic failures vs infrastructure errors.
1899
+ *
1900
+ * @example
1901
+ * ```typescript
1902
+ * import { ApplicationFailure } from '@dotdo/temporal'
1903
+ *
1904
+ * if (!user.hasPermission('admin')) {
1905
+ * throw new ApplicationFailure('User does not have admin permission', true)
1906
+ * }
1907
+ * ```
1908
+ */
1909
+ export class ApplicationFailure extends Error {
1910
+ nonRetryable;
1911
+ details;
1912
+ type = 'ApplicationFailure';
1913
+ constructor(message, nonRetryable = false, details) {
1914
+ super(message);
1915
+ this.nonRetryable = nonRetryable;
1916
+ this.details = details;
1917
+ this.name = 'ApplicationFailure';
1918
+ }
1919
+ /**
1920
+ * Create a non-retryable application failure
1921
+ */
1922
+ static nonRetryable(message, ...details) {
1923
+ return new ApplicationFailure(message, true, details.length > 0 ? details : undefined);
1924
+ }
1925
+ /**
1926
+ * Create a retryable application failure
1927
+ */
1928
+ static retryable(message, ...details) {
1929
+ return new ApplicationFailure(message, false, details.length > 0 ? details : undefined);
1930
+ }
1931
+ }
1932
+ /**
1933
+ * Failure thrown when an activity execution fails.
1934
+ * Contains information about the failed activity and the underlying cause.
1935
+ *
1936
+ * @example
1937
+ * ```typescript
1938
+ * try {
1939
+ * await activities.processPayment(order)
1940
+ * } catch (error) {
1941
+ * if (error instanceof ActivityFailure) {
1942
+ * console.log(`Activity ${error.activityType} failed: ${error.cause?.message}`)
1943
+ * }
1944
+ * }
1945
+ * ```
1946
+ */
1947
+ export class ActivityFailure extends Error {
1948
+ activityType;
1949
+ activityId;
1950
+ cause;
1951
+ type = 'ActivityFailure';
1952
+ constructor(activityType, activityId, cause) {
1953
+ super(`Activity ${activityType} (${activityId}) failed${cause ? `: ${cause.message}` : ''}`);
1954
+ this.activityType = activityType;
1955
+ this.activityId = activityId;
1956
+ this.cause = cause;
1957
+ this.name = 'ActivityFailure';
1958
+ }
1959
+ }
1960
+ /**
1961
+ * Failure thrown when a child workflow execution fails.
1962
+ * Contains information about the failed child workflow and the underlying cause.
1963
+ *
1964
+ * @example
1965
+ * ```typescript
1966
+ * try {
1967
+ * await executeChild(childWorkflow, { workflowId: 'child-1' })
1968
+ * } catch (error) {
1969
+ * if (error instanceof ChildWorkflowFailure) {
1970
+ * console.log(`Child workflow ${error.workflowType} failed: ${error.cause?.message}`)
1971
+ * }
1972
+ * }
1973
+ * ```
1974
+ */
1975
+ export class ChildWorkflowFailure extends Error {
1976
+ workflowType;
1977
+ workflowId;
1978
+ cause;
1979
+ type = 'ChildWorkflowFailure';
1980
+ constructor(workflowType, workflowId, cause) {
1981
+ super(`Child workflow ${workflowType} (${workflowId}) failed${cause ? `: ${cause.message}` : ''}`);
1982
+ this.workflowType = workflowType;
1983
+ this.workflowId = workflowId;
1984
+ this.cause = cause;
1985
+ this.name = 'ChildWorkflowFailure';
1986
+ }
1987
+ }
1988
+ /**
1989
+ * Check if an error is an ApplicationFailure
1990
+ */
1991
+ export function isApplicationFailure(error) {
1992
+ return error instanceof ApplicationFailure;
1993
+ }
1994
+ /**
1995
+ * Check if an error is an ActivityFailure
1996
+ */
1997
+ export function isActivityFailure(error) {
1998
+ return error instanceof ActivityFailure;
1999
+ }
2000
+ /**
2001
+ * Check if an error is a ChildWorkflowFailure
2002
+ */
2003
+ export function isChildWorkflowFailure(error) {
2004
+ return error instanceof ChildWorkflowFailure;
2005
+ }
2006
+ // ============================================================================
2007
+ // WORKFLOW CLIENT - Enhanced with list and search
2008
+ // ============================================================================
2009
+ export class WorkflowClient {
2010
+ namespace;
2011
+ storage;
2012
+ constructor(options = {}) {
2013
+ this.namespace = options.namespace ?? globalNamespace;
2014
+ // Use explicit storage if provided, otherwise use current context storage or global fallback
2015
+ this.storage = options.storage ?? getCurrentStorage();
2016
+ // Update global namespace for workflows started by this client
2017
+ globalNamespace = this.namespace;
2018
+ }
2019
+ /**
2020
+ * Start a workflow
2021
+ *
2022
+ * @throws TaskQueueNotRegisteredError if no worker is registered for the task queue
2023
+ */
2024
+ async start(workflowType, options) {
2025
+ // Auto-start cleanup on first workflow creation
2026
+ ensureCleanupStarted();
2027
+ const typeName = typeof workflowType === 'string' ? workflowType : workflowType.name;
2028
+ const workflowId = options.workflowId ?? generateWorkflowId();
2029
+ const runId = generateRunId();
2030
+ const now = new Date();
2031
+ // Validate task queue has a registered worker (like real Temporal)
2032
+ validateTaskQueueForWorkflow(options.taskQueue, typeName);
2033
+ // Create workflow state with full context
2034
+ const state = {
2035
+ workflowId,
2036
+ runId,
2037
+ workflowType: typeName,
2038
+ taskQueue: options.taskQueue,
2039
+ namespace: this.namespace,
2040
+ signalHandlers: new Map(),
2041
+ queryHandlers: new Map(),
2042
+ updateHandlers: new Map(),
2043
+ stepResults: new Map(),
2044
+ status: 'RUNNING',
2045
+ searchAttributes: options.searchAttributes ?? {},
2046
+ memo: options.memo,
2047
+ startTime: now,
2048
+ runStartTime: now,
2049
+ historyLength: 1,
2050
+ attempt: 1,
2051
+ children: new Set(),
2052
+ abortController: new AbortController(),
2053
+ sleepStepCounter: 0,
2054
+ activityStepCounter: 0,
2055
+ };
2056
+ workflows.set(workflowId, state);
2057
+ // Execute the workflow with its own context
2058
+ const workflowFn = typeof workflowType === 'function' ? workflowType : workflowFunctions.get(typeName);
2059
+ if (workflowFn) {
2060
+ // Create a new context for this workflow execution
2061
+ const context = {
2062
+ workflow: state,
2063
+ patchState: null,
2064
+ storage: this.storage,
2065
+ waitManager: globalWaitManager,
2066
+ workflowStep: null,
2067
+ };
2068
+ // Run the workflow in its own context (enabling concurrent execution)
2069
+ runWithContext(context, () => {
2070
+ workflowFn(...(options.args ?? []))
2071
+ .then((result) => {
2072
+ state.status = 'COMPLETED';
2073
+ state.result = result;
2074
+ markWorkflowCompleted(workflowId);
2075
+ })
2076
+ .catch((error) => {
2077
+ if (error instanceof ContinueAsNew) {
2078
+ state.status = 'CONTINUED_AS_NEW';
2079
+ }
2080
+ else {
2081
+ state.status = 'FAILED';
2082
+ state.error = error;
2083
+ }
2084
+ markWorkflowCompleted(workflowId);
2085
+ });
2086
+ });
2087
+ }
2088
+ return this.createHandle(workflowId, runId, state);
2089
+ }
2090
+ /**
2091
+ * Execute a workflow and wait for result
2092
+ */
2093
+ async execute(workflowType, options) {
2094
+ const handle = await this.start(workflowType, options);
2095
+ return handle.result();
2096
+ }
2097
+ /**
2098
+ * Get a handle to an existing workflow
2099
+ */
2100
+ getHandle(workflowId, runId) {
2101
+ const state = workflows.get(workflowId);
2102
+ if (!state) {
2103
+ throw new Error(`Workflow ${workflowId} not found`);
2104
+ }
2105
+ return this.createHandle(workflowId, runId ?? state.runId, state);
2106
+ }
2107
+ /**
2108
+ * List workflows with optional search query
2109
+ */
2110
+ async list(options = {}) {
2111
+ const results = [];
2112
+ for (const [, state] of Array.from(workflows.entries())) {
2113
+ // If query provided, parse and filter
2114
+ if (options.query) {
2115
+ const match = this.matchesQuery(state, options.query);
2116
+ if (!match)
2117
+ continue;
2118
+ }
2119
+ results.push({
2120
+ status: state.status,
2121
+ workflowId: state.workflowId,
2122
+ runId: state.runId,
2123
+ workflowType: state.workflowType,
2124
+ taskQueue: state.taskQueue,
2125
+ startTime: state.startTime,
2126
+ searchAttributes: state.searchAttributes,
2127
+ memo: state.memo,
2128
+ });
2129
+ if (options.pageSize && results.length >= options.pageSize) {
2130
+ break;
2131
+ }
2132
+ }
2133
+ return results;
2134
+ }
2135
+ /**
2136
+ * Simple query matcher for search attributes
2137
+ *
2138
+ * Security: This method validates queries to prevent injection attacks.
2139
+ * - Empty queries match all (intentional)
2140
+ * - Invalid query format returns false (fail closed)
2141
+ * - Only whitelisted attribute keys are allowed
2142
+ */
2143
+ matchesQuery(state, query) {
2144
+ // Empty query matches all (intentional behavior)
2145
+ if (!query || query.trim() === '')
2146
+ return true;
2147
+ // Simple parser for queries like: Status = "active"
2148
+ const match = query.match(/(\w+)\s*=\s*"([^"]+)"/);
2149
+ if (!match) {
2150
+ // Invalid query format - fail closed (don't match)
2151
+ return false;
2152
+ }
2153
+ const [, key, value] = match;
2154
+ // Validate key is a known search attribute (prevent prototype pollution)
2155
+ const validKeys = ['status', 'workflowType', 'runId', ...Object.keys(state.searchAttributes)];
2156
+ if (!validKeys.includes(key)) {
2157
+ return false;
2158
+ }
2159
+ const attrValue = state.searchAttributes[key];
2160
+ return String(attrValue) === value;
2161
+ }
2162
+ /**
2163
+ * Signal and optionally start a workflow
2164
+ */
2165
+ async signalWithStart(workflowType, options) {
2166
+ const workflowId = options.workflowId ?? generateWorkflowId();
2167
+ // Check if workflow exists
2168
+ let state = workflows.get(workflowId);
2169
+ let handle;
2170
+ if (state) {
2171
+ // Workflow exists, just signal it
2172
+ handle = this.createHandle(workflowId, state.runId, state);
2173
+ }
2174
+ else {
2175
+ // Start the workflow
2176
+ handle = await this.start(workflowType, options);
2177
+ state = workflows.get(workflowId);
2178
+ }
2179
+ // Send the signal
2180
+ await handle.signal(options.signal, ...options.signalArgs);
2181
+ return handle;
2182
+ }
2183
+ /**
2184
+ * Create a workflow handle
2185
+ */
2186
+ createHandle(workflowId, runId, state) {
2187
+ const self = this;
2188
+ return {
2189
+ workflowId,
2190
+ runId,
2191
+ async result() {
2192
+ // Poll until complete
2193
+ while (state.status === 'RUNNING') {
2194
+ await new Promise((resolve) => setTimeout(resolve, 10));
2195
+ }
2196
+ if (state.status === 'FAILED' && state.error) {
2197
+ throw state.error;
2198
+ }
2199
+ if (state.status === 'TERMINATED' && state.error) {
2200
+ throw state.error;
2201
+ }
2202
+ return state.result;
2203
+ },
2204
+ async describe() {
2205
+ return {
2206
+ status: state.status,
2207
+ workflowId,
2208
+ runId,
2209
+ workflowType: state.workflowType,
2210
+ taskQueue: state.taskQueue,
2211
+ startTime: state.startTime,
2212
+ searchAttributes: state.searchAttributes,
2213
+ memo: state.memo,
2214
+ };
2215
+ },
2216
+ async signal(signal, ...args) {
2217
+ const handler = state.signalHandlers.get(signal.name);
2218
+ if (handler) {
2219
+ await handler(...args);
2220
+ }
2221
+ // Also deliver to wait manager if present
2222
+ if (globalWaitManager) {
2223
+ await globalWaitManager.deliverEvent(null, `signal:${signal.name}`, args);
2224
+ }
2225
+ },
2226
+ async query(query, ...args) {
2227
+ const handler = state.queryHandlers.get(query.name);
2228
+ if (!handler) {
2229
+ throw new Error(`Query handler for "${query.name}" not found`);
2230
+ }
2231
+ return handler(...args);
2232
+ },
2233
+ async executeUpdate(update, ...args) {
2234
+ const handler = state.updateHandlers.get(update.name);
2235
+ if (!handler) {
2236
+ throw new Error(`Update handler for "${update.name}" not found`);
2237
+ }
2238
+ return handler(...args);
2239
+ },
2240
+ async cancel() {
2241
+ state.status = 'CANCELED';
2242
+ // Abort any pending activities
2243
+ if (state.abortController) {
2244
+ state.abortController.abort();
2245
+ }
2246
+ // Cancel all children based on parent close policy
2247
+ for (const childId of Array.from(state.children)) {
2248
+ const childState = workflows.get(childId);
2249
+ if (childState && childState.status === 'RUNNING') {
2250
+ childState.status = 'CANCELED';
2251
+ if (childState.abortController) {
2252
+ childState.abortController.abort();
2253
+ }
2254
+ }
2255
+ }
2256
+ },
2257
+ async terminate(reason) {
2258
+ state.status = 'TERMINATED';
2259
+ state.error = new Error(reason ?? 'Workflow terminated');
2260
+ // Terminate all children based on parent close policy
2261
+ for (const childId of Array.from(state.children)) {
2262
+ const childState = workflows.get(childId);
2263
+ if (childState && childState.status === 'RUNNING') {
2264
+ if (childState.parentClosePolicy === 'TERMINATE') {
2265
+ childState.status = 'TERMINATED';
2266
+ }
2267
+ else if (childState.parentClosePolicy === 'REQUEST_CANCEL') {
2268
+ childState.status = 'CANCELED';
2269
+ }
2270
+ // ABANDON: do nothing
2271
+ }
2272
+ }
2273
+ },
2274
+ };
2275
+ }
2276
+ }
2277
+ // ============================================================================
2278
+ // UUID AND RANDOM - Deterministic for Replay
2279
+ // ============================================================================
2280
+ /**
2281
+ * Per-workflow counters for deterministic step IDs
2282
+ * These are tracked per workflow execution to ensure each uuid4()/random() call
2283
+ * gets a unique, deterministic step ID based on call order.
2284
+ */
2285
+ const uuidCounters = new WeakMap();
2286
+ const randomCounters = new WeakMap();
2287
+ /**
2288
+ * Generate deterministic UUID (for replay)
2289
+ *
2290
+ * This function is deterministic within a workflow execution:
2291
+ * - First execution: generates a new UUID and stores it
2292
+ * - Replay: returns the same UUID that was generated before
2293
+ *
2294
+ * IMPORTANT: Must be called within a workflow context.
2295
+ * The step ID is based on call order within the workflow, so
2296
+ * uuid4() calls must happen in the same order on replay.
2297
+ *
2298
+ * @returns A UUID v4 string that is deterministic on replay
2299
+ * @throws Error if called outside a workflow context
2300
+ *
2301
+ * @example
2302
+ * ```typescript
2303
+ * import { uuid4 } from '@dotdo/temporal'
2304
+ *
2305
+ * export async function orderWorkflow() {
2306
+ * // These IDs will be the same on replay
2307
+ * const orderId = uuid4()
2308
+ * const transactionId = uuid4()
2309
+ * return { orderId, transactionId }
2310
+ * }
2311
+ * ```
2312
+ */
2313
+ export function uuid4() {
2314
+ const workflow = getCurrentWorkflow();
2315
+ if (!workflow) {
2316
+ // Outside workflow context - fall back to non-deterministic
2317
+ // This allows usage in tests or non-workflow code
2318
+ return crypto.randomUUID();
2319
+ }
2320
+ // Get and increment the counter for this workflow
2321
+ const counter = uuidCounters.get(workflow) ?? 0;
2322
+ uuidCounters.set(workflow, counter + 1);
2323
+ // Create deterministic step ID based on call order
2324
+ const stepId = `uuid4:${counter}`;
2325
+ // Check for existing result (replay case)
2326
+ if (workflow.stepResults.has(stepId)) {
2327
+ return workflow.stepResults.get(stepId);
2328
+ }
2329
+ // Generate new UUID and store for replay
2330
+ const id = crypto.randomUUID();
2331
+ workflow.stepResults.set(stepId, id);
2332
+ workflow.historyLength++;
2333
+ return id;
2334
+ }
2335
+ /**
2336
+ * Get deterministic random number (for replay)
2337
+ *
2338
+ * This function is deterministic within a workflow execution:
2339
+ * - First execution: generates a new random number and stores it
2340
+ * - Replay: returns the same random number that was generated before
2341
+ *
2342
+ * IMPORTANT: Must be called within a workflow context.
2343
+ * The step ID is based on call order within the workflow, so
2344
+ * random() calls must happen in the same order on replay.
2345
+ *
2346
+ * @returns A number between 0 (inclusive) and 1 (exclusive)
2347
+ * @throws Error if called outside a workflow context
2348
+ *
2349
+ * @example
2350
+ * ```typescript
2351
+ * import { random } from '@dotdo/temporal'
2352
+ *
2353
+ * export async function retryWorkflow() {
2354
+ * // This decision will be the same on replay
2355
+ * const shouldRetry = random() < 0.5
2356
+ * return { shouldRetry }
2357
+ * }
2358
+ * ```
2359
+ */
2360
+ export function random() {
2361
+ const workflow = getCurrentWorkflow();
2362
+ if (!workflow) {
2363
+ // Outside workflow context - fall back to non-deterministic
2364
+ // This allows usage in tests or non-workflow code
2365
+ return Math.random();
2366
+ }
2367
+ // Get and increment the counter for this workflow
2368
+ const counter = randomCounters.get(workflow) ?? 0;
2369
+ randomCounters.set(workflow, counter + 1);
2370
+ // Create deterministic step ID based on call order
2371
+ const stepId = `random:${counter}`;
2372
+ // Check for existing result (replay case)
2373
+ if (workflow.stepResults.has(stepId)) {
2374
+ return workflow.stepResults.get(stepId);
2375
+ }
2376
+ // Generate new random number and store for replay
2377
+ const num = Math.random();
2378
+ workflow.stepResults.set(stepId, num);
2379
+ workflow.historyLength++;
2380
+ return num;
2381
+ }
2382
+ // ============================================================================
2383
+ // EXPORTS
2384
+ // ============================================================================
2385
+ // Re-export activity worker pool types from activities module
2386
+ export { createActivityWorker, ActivityWorker, registerRemoteActivityWorker, getRemoteActivityWorker, hasRemoteActivityWorker, clearRemoteActivityWorkers, } from './activities';
2387
+ // Re-export unified primitives integration
2388
+ export {
2389
+ // Activity deduplication
2390
+ createActivityDeduplicationContext,
2391
+ // Workflow history store
2392
+ createWorkflowHistoryStore,
2393
+ // Timer and deadline management
2394
+ createWorkflowTimerManager, createWorkflowDeadlineManager,
2395
+ // Unified runtime
2396
+ createUnifiedWorkflowRuntime,
2397
+ // Re-exported primitives
2398
+ ExactlyOnceContext, createExactlyOnceContext, createTemporalStore, WindowManager, EventTimeTrigger, CountTrigger, ProcessingTimeTrigger, PurgingTrigger, Trigger, TriggerResult, WatermarkService, createWatermarkService, seconds, minutes, hours, milliseconds, } from './unified-primitives';
2399
+ // Re-export saga / compensation patterns
2400
+ export { Saga, SagaBuilder, runSaga, parallel, withRetry, withTimeout, createDistributedSagaCoordinator, SagaTimeoutError, SagaCompensationError, } from './saga';
2401
+ // Import activity worker functions for use in default export
2402
+ import { createActivityWorker as _createActivityWorker, ActivityWorker as _ActivityWorker, registerRemoteActivityWorker as _registerRemoteActivityWorker, getRemoteActivityWorker as _getRemoteActivityWorker, hasRemoteActivityWorker as _hasRemoteActivityWorker, clearRemoteActivityWorkers as _clearRemoteActivityWorkers, } from './activities';
2403
+ export default {
2404
+ defineSignal,
2405
+ defineQuery,
2406
+ defineUpdate,
2407
+ setHandler,
2408
+ proxyActivities,
2409
+ proxyLocalActivities,
2410
+ startChild,
2411
+ executeChild,
2412
+ sleep,
2413
+ condition,
2414
+ workflowInfo,
2415
+ continueAsNew,
2416
+ makeContinueAsNewFunc,
2417
+ CancellationScope,
2418
+ isCancellation,
2419
+ WorkflowClient,
2420
+ uuid4,
2421
+ random,
2422
+ configure,
2423
+ // New exports for API coverage
2424
+ createTimer,
2425
+ cancelTimer,
2426
+ patched,
2427
+ deprecatePatch,
2428
+ setSearchAttributes,
2429
+ upsertSearchAttributes,
2430
+ // Task queue routing
2431
+ registerWorker,
2432
+ hasWorker,
2433
+ getWorker,
2434
+ listTaskQueues,
2435
+ TaskQueueNotRegisteredError,
2436
+ ActivityTimeoutError,
2437
+ // Determinism enforcement
2438
+ workflowNow,
2439
+ WorkflowDeterminismWarning,
2440
+ configureDeterminism,
2441
+ getDeterminismWarnings,
2442
+ clearDeterminismWarnings,
2443
+ enableDeterminismDetection,
2444
+ disableDeterminismDetection,
2445
+ inWorkflowContext,
2446
+ // CF Workflows integration
2447
+ setWorkflowStep,
2448
+ clearWorkflowStep,
2449
+ getWorkflowStep,
2450
+ // Storage strategy pattern (for advanced use)
2451
+ createStorageStrategy,
2452
+ CFWorkflowsStorageStrategy,
2453
+ InMemoryStorageStrategy,
2454
+ // Activity worker pool (for independent scaling)
2455
+ createActivityWorker: _createActivityWorker,
2456
+ ActivityWorker: _ActivityWorker,
2457
+ registerRemoteActivityWorker: _registerRemoteActivityWorker,
2458
+ getRemoteActivityWorker: _getRemoteActivityWorker,
2459
+ hasRemoteActivityWorker: _hasRemoteActivityWorker,
2460
+ clearRemoteActivityWorkers: _clearRemoteActivityWorkers,
2461
+ // Utility for testing
2462
+ __clearTemporalState,
2463
+ };
2464
+ //# sourceMappingURL=index.js.map