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,1263 @@
1
+ /**
2
+ * QStash Compat Layer - 100% API Compatible with @upstash/qstash
3
+ *
4
+ * Drop-in replacement for Upstash QStash that runs on dotdo's
5
+ * durable execution infrastructure.
6
+ *
7
+ * Features:
8
+ * - Real HTTP delivery with fetch()
9
+ * - Retry logic with exponential backoff (5xx only, not 4xx)
10
+ * - Upstash-Retried header on retry attempts
11
+ * - Dead letter queue support (both internal and URL-based)
12
+ * - URL Groups for fan-out delivery (concurrent with Promise.all)
13
+ * - Callback URLs on success/failure
14
+ * - HMAC-SHA256 signature generation and verification
15
+ * - Request timeout handling with AbortController
16
+ * - Content-based deduplication (1 hour window)
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { Client } from '@dotdo/qstash'
21
+ *
22
+ * const client = new Client({ token: 'xxx' })
23
+ * await client.publishJSON({
24
+ * url: 'https://example.com.ai/api/webhook',
25
+ * body: { hello: 'world' },
26
+ * delay: '5m',
27
+ * })
28
+ * ```
29
+ *
30
+ * @see https://upstash.com/docs/qstash for the original QStash documentation
31
+ *
32
+ * Note: For production use with Cloudflare Workers, consider integrating
33
+ * with CF Workflows for durable retry scheduling (free waits vs setTimeout).
34
+ */
35
+ import { ScheduleManager, parseCronExpression, getNextRunTime } from '../../ScheduleManager';
36
+ import { DurableWorkflowRuntime, InMemoryStepStorage } from '../../runtime';
37
+ import { parseDuration, ensureError } from '../utils';
38
+ // ============================================================================
39
+ // UTILITIES
40
+ // Note: parseDuration and ensureError are imported from '../utils'
41
+ // ============================================================================
42
+ function generateMessageId() {
43
+ return `msg_${crypto.randomUUID().replace(/-/g, '')}`;
44
+ }
45
+ function generateScheduleId() {
46
+ return `sched_${crypto.randomUUID().replace(/-/g, '')}`;
47
+ }
48
+ function generateTopicId() {
49
+ return `topic_${crypto.randomUUID().replace(/-/g, '')}`;
50
+ }
51
+ function generateSubscriptionId() {
52
+ return `sub_${crypto.randomUUID().replace(/-/g, '')}`;
53
+ }
54
+ function generateEventId() {
55
+ return `evt_${crypto.randomUUID().replace(/-/g, '')}`;
56
+ }
57
+ /**
58
+ * Valid HTTP methods for QStash requests (matching PublishRequest.method)
59
+ */
60
+ const validHttpMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
61
+ /**
62
+ * Check if a string is a valid HTTP method for publishing
63
+ */
64
+ function isValidHttpMethod(method) {
65
+ return validHttpMethods.includes(method);
66
+ }
67
+ async function hashBody(body) {
68
+ const encoder = new TextEncoder();
69
+ const data = encoder.encode(body);
70
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
71
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
72
+ return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
73
+ }
74
+ /**
75
+ * Calculate exponential backoff delay with jitter
76
+ */
77
+ function calculateBackoffDelay(attempt, initialDelayMs = 1000, maxDelayMs = 30000) {
78
+ // Exponential backoff: initialDelay * 2^(attempt-1)
79
+ let delay = initialDelayMs * Math.pow(2, attempt - 1);
80
+ // Cap at max delay
81
+ delay = Math.min(delay, maxDelayMs);
82
+ // Add jitter (0-25% of delay)
83
+ const jitter = delay * 0.25 * Math.random();
84
+ delay += jitter;
85
+ return Math.floor(delay);
86
+ }
87
+ /**
88
+ * Check if HTTP status code is retryable (5xx errors only)
89
+ */
90
+ function isRetryableStatus(status) {
91
+ return status >= 500 && status < 600;
92
+ }
93
+ /**
94
+ * Generate HMAC-SHA256 signature for a payload
95
+ */
96
+ async function generateSignature(signingKey, body) {
97
+ const timestamp = Math.floor(Date.now() / 1000);
98
+ const payload = `${timestamp}.${body}`;
99
+ const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(signingKey), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
100
+ const signatureBuffer = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(payload));
101
+ const signature = Array.from(new Uint8Array(signatureBuffer))
102
+ .map((b) => b.toString(16).padStart(2, '0'))
103
+ .join('');
104
+ return `t=${timestamp},v1=${signature}`;
105
+ }
106
+ // ============================================================================
107
+ // SCHEDULES API
108
+ // ============================================================================
109
+ class Schedules {
110
+ manager;
111
+ inMemorySchedules = new Map();
112
+ constructor(state) {
113
+ this.manager = state ? new ScheduleManager(state) : null;
114
+ }
115
+ /**
116
+ * Create a new schedule
117
+ */
118
+ async create(request) {
119
+ const scheduleId = request.scheduleId || generateScheduleId();
120
+ const schedule = {
121
+ scheduleId,
122
+ cron: request.cron,
123
+ destination: request.destination,
124
+ body: typeof request.body === 'object' ? JSON.stringify(request.body) : request.body,
125
+ method: request.method || 'POST',
126
+ headers: request.headers,
127
+ contentType: request.contentType,
128
+ retries: request.retries ?? 3,
129
+ callback: request.callback,
130
+ failureCallback: request.failureCallback,
131
+ timeout: request.timeout,
132
+ createdAt: Date.now(),
133
+ isPaused: false,
134
+ };
135
+ if (this.manager) {
136
+ // Use DO-based schedule manager
137
+ await this.manager.schedule(request.cron, scheduleId, {
138
+ metadata: {
139
+ destination: request.destination,
140
+ body: schedule.body,
141
+ method: schedule.method,
142
+ headers: schedule.headers,
143
+ contentType: schedule.contentType,
144
+ retries: schedule.retries,
145
+ callback: schedule.callback,
146
+ failureCallback: schedule.failureCallback,
147
+ timeout: schedule.timeout,
148
+ },
149
+ });
150
+ }
151
+ // Always store in memory for compatibility
152
+ this.inMemorySchedules.set(scheduleId, schedule);
153
+ return { scheduleId };
154
+ }
155
+ /**
156
+ * Get a schedule by ID
157
+ */
158
+ async get(scheduleId) {
159
+ // Check in-memory first
160
+ const schedule = this.inMemorySchedules.get(scheduleId);
161
+ if (schedule)
162
+ return schedule;
163
+ // Check DO storage
164
+ if (this.manager) {
165
+ const stored = await this.manager.getSchedule(scheduleId);
166
+ if (stored) {
167
+ const meta = stored.metadata;
168
+ return {
169
+ scheduleId,
170
+ cron: stored.cronExpression,
171
+ destination: meta?.destination || '',
172
+ body: meta?.body,
173
+ method: meta?.method || 'POST',
174
+ headers: meta?.headers,
175
+ contentType: meta?.contentType,
176
+ retries: meta?.retries || 3,
177
+ callback: meta?.callback,
178
+ failureCallback: meta?.failureCallback,
179
+ timeout: meta?.timeout,
180
+ createdAt: stored.createdAt.getTime(),
181
+ isPaused: stored.status === 'paused',
182
+ };
183
+ }
184
+ }
185
+ return null;
186
+ }
187
+ /**
188
+ * List all schedules
189
+ */
190
+ async list() {
191
+ const schedules = [];
192
+ // Add in-memory schedules
193
+ for (const schedule of Array.from(this.inMemorySchedules.values())) {
194
+ schedules.push(schedule);
195
+ }
196
+ // Add DO schedules (avoiding duplicates)
197
+ if (this.manager) {
198
+ const stored = await this.manager.listSchedules();
199
+ for (const s of stored) {
200
+ if (!this.inMemorySchedules.has(s.name)) {
201
+ const meta = s.metadata;
202
+ schedules.push({
203
+ scheduleId: s.name,
204
+ cron: s.cronExpression,
205
+ destination: meta?.destination || '',
206
+ body: meta?.body,
207
+ method: meta?.method || 'POST',
208
+ headers: meta?.headers,
209
+ contentType: meta?.contentType,
210
+ retries: meta?.retries || 3,
211
+ callback: meta?.callback,
212
+ failureCallback: meta?.failureCallback,
213
+ timeout: meta?.timeout,
214
+ createdAt: s.createdAt.getTime(),
215
+ isPaused: s.status === 'paused',
216
+ });
217
+ }
218
+ }
219
+ }
220
+ return schedules;
221
+ }
222
+ /**
223
+ * Delete a schedule
224
+ */
225
+ async delete(scheduleId) {
226
+ this.inMemorySchedules.delete(scheduleId);
227
+ if (this.manager) {
228
+ try {
229
+ await this.manager.deleteSchedule(scheduleId);
230
+ }
231
+ catch {
232
+ // Schedule may not exist in DO storage
233
+ }
234
+ }
235
+ }
236
+ /**
237
+ * Pause a schedule
238
+ */
239
+ async pause(scheduleId) {
240
+ const schedule = this.inMemorySchedules.get(scheduleId);
241
+ if (schedule) {
242
+ schedule.isPaused = true;
243
+ }
244
+ if (this.manager) {
245
+ await this.manager.updateSchedule(scheduleId, { enabled: false });
246
+ }
247
+ }
248
+ /**
249
+ * Resume a schedule
250
+ */
251
+ async resume(scheduleId) {
252
+ const schedule = this.inMemorySchedules.get(scheduleId);
253
+ if (schedule) {
254
+ schedule.isPaused = false;
255
+ }
256
+ if (this.manager) {
257
+ await this.manager.updateSchedule(scheduleId, { enabled: true });
258
+ }
259
+ }
260
+ }
261
+ // ============================================================================
262
+ // URL GROUPS API
263
+ // ============================================================================
264
+ export class URLGroups {
265
+ groups = new Map();
266
+ /**
267
+ * Create a new URL group
268
+ * @param nameOrRequest - Either a group name (string) or a CreateURLGroupRequest object
269
+ * @param endpoints - Array of endpoint URLs (only used when first arg is a string)
270
+ */
271
+ async create(nameOrRequest, endpoints) {
272
+ let name;
273
+ let endpointList;
274
+ if (typeof nameOrRequest === 'string') {
275
+ // Simple signature: create('name', ['url1', 'url2'])
276
+ name = nameOrRequest;
277
+ endpointList = (endpoints || []).map((url) => ({ url }));
278
+ }
279
+ else {
280
+ // Object signature: create({ name: 'name', endpoints: [...] })
281
+ name = nameOrRequest.name;
282
+ endpointList = [...nameOrRequest.endpoints];
283
+ }
284
+ const group = {
285
+ name,
286
+ endpoints: endpointList,
287
+ createdAt: Date.now(),
288
+ };
289
+ this.groups.set(name, group);
290
+ return group;
291
+ }
292
+ /**
293
+ * Get a URL group by name
294
+ */
295
+ async get(name) {
296
+ return this.groups.get(name) ?? null;
297
+ }
298
+ /**
299
+ * List all URL groups
300
+ */
301
+ async list() {
302
+ return Array.from(this.groups.values());
303
+ }
304
+ /**
305
+ * Delete a URL group
306
+ */
307
+ async delete(name) {
308
+ this.groups.delete(name);
309
+ }
310
+ /**
311
+ * Add endpoints to an existing group
312
+ */
313
+ async addEndpoints(name, endpoints) {
314
+ const group = this.groups.get(name);
315
+ if (!group) {
316
+ throw new Error(`URL group '${name}' not found`);
317
+ }
318
+ group.endpoints.push(...endpoints);
319
+ }
320
+ /**
321
+ * Remove endpoints from an existing group
322
+ */
323
+ async removeEndpoints(name, endpoints) {
324
+ const group = this.groups.get(name);
325
+ if (!group) {
326
+ throw new Error(`URL group '${name}' not found`);
327
+ }
328
+ const urlsToRemove = new Set(endpoints.map((e) => e.url));
329
+ group.endpoints = group.endpoints.filter((e) => !urlsToRemove.has(e.url));
330
+ }
331
+ }
332
+ // ============================================================================
333
+ // DEAD LETTER QUEUE API
334
+ // ============================================================================
335
+ export class DLQ {
336
+ messages = new Map();
337
+ messageOrder = [];
338
+ /**
339
+ * Add a message to the DLQ (internal use)
340
+ */
341
+ _addMessage(message) {
342
+ this.messages.set(message.messageId, message);
343
+ this.messageOrder.push(message.messageId);
344
+ }
345
+ /**
346
+ * List DLQ messages
347
+ */
348
+ async list(options) {
349
+ const limit = options?.limit ?? 100;
350
+ let startIndex = 0;
351
+ if (options?.cursor) {
352
+ const cursorIndex = this.messageOrder.indexOf(options.cursor);
353
+ if (cursorIndex !== -1) {
354
+ startIndex = cursorIndex + 1;
355
+ }
356
+ }
357
+ const result = [];
358
+ for (let i = startIndex; i < Math.min(startIndex + limit, this.messageOrder.length); i++) {
359
+ const id = this.messageOrder[i];
360
+ const msg = this.messages.get(id);
361
+ if (msg) {
362
+ result.push({
363
+ ...msg,
364
+ cursor: id,
365
+ });
366
+ }
367
+ }
368
+ return result;
369
+ }
370
+ /**
371
+ * Get a specific DLQ message
372
+ */
373
+ async get(messageId) {
374
+ return this.messages.get(messageId) ?? null;
375
+ }
376
+ /**
377
+ * Replay a DLQ message
378
+ */
379
+ async replay(messageId, options, publishFn) {
380
+ const message = this.messages.get(messageId);
381
+ if (!message) {
382
+ return { success: false };
383
+ }
384
+ const url = options?.url ?? message.url;
385
+ if (publishFn) {
386
+ try {
387
+ // Validate HTTP method before casting
388
+ if (!isValidHttpMethod(message.method)) {
389
+ throw new Error(`Invalid HTTP method in DLQ message: ${message.method}`);
390
+ }
391
+ const result = await publishFn({
392
+ url,
393
+ body: message.body,
394
+ method: message.method,
395
+ headers: message.headers,
396
+ });
397
+ // Remove from DLQ on successful replay
398
+ this.messages.delete(messageId);
399
+ this.messageOrder = this.messageOrder.filter((id) => id !== messageId);
400
+ return {
401
+ success: true,
402
+ newMessageId: result.messageId,
403
+ url,
404
+ };
405
+ }
406
+ catch {
407
+ return { success: false };
408
+ }
409
+ }
410
+ // If no publish function provided, just remove from DLQ
411
+ this.messages.delete(messageId);
412
+ this.messageOrder = this.messageOrder.filter((id) => id !== messageId);
413
+ return {
414
+ success: true,
415
+ newMessageId: generateMessageId(),
416
+ url,
417
+ };
418
+ }
419
+ /**
420
+ * Delete a DLQ message
421
+ */
422
+ async delete(messageId) {
423
+ this.messages.delete(messageId);
424
+ this.messageOrder = this.messageOrder.filter((id) => id !== messageId);
425
+ }
426
+ /**
427
+ * Delete multiple DLQ messages
428
+ */
429
+ async deleteMany(messageIds) {
430
+ for (const id of messageIds) {
431
+ this.messages.delete(id);
432
+ }
433
+ const idsSet = new Set(messageIds);
434
+ this.messageOrder = this.messageOrder.filter((id) => !idsSet.has(id));
435
+ }
436
+ /**
437
+ * Purge all DLQ messages
438
+ */
439
+ async purge() {
440
+ this.messages.clear();
441
+ this.messageOrder = [];
442
+ }
443
+ }
444
+ // ============================================================================
445
+ // TOPICS API
446
+ // ============================================================================
447
+ export class Topics {
448
+ topics = new Map();
449
+ subscriptions = new Map();
450
+ topicSubscriptions = new Map(); // topicName -> subscriptionIds
451
+ /**
452
+ * Create a new topic
453
+ */
454
+ async create(request) {
455
+ const topic = {
456
+ topicId: generateTopicId(),
457
+ name: request.name,
458
+ createdAt: Date.now(),
459
+ };
460
+ this.topics.set(request.name, topic);
461
+ this.topicSubscriptions.set(request.name, new Set());
462
+ return topic;
463
+ }
464
+ /**
465
+ * Get a topic by name
466
+ */
467
+ async get(name) {
468
+ return this.topics.get(name) ?? null;
469
+ }
470
+ /**
471
+ * List all topics
472
+ */
473
+ async list() {
474
+ return Array.from(this.topics.values());
475
+ }
476
+ /**
477
+ * Delete a topic
478
+ */
479
+ async delete(name) {
480
+ this.topics.delete(name);
481
+ // Remove all subscriptions for this topic
482
+ const subIds = this.topicSubscriptions.get(name);
483
+ if (subIds) {
484
+ for (const subId of Array.from(subIds)) {
485
+ this.subscriptions.delete(subId);
486
+ }
487
+ }
488
+ this.topicSubscriptions.delete(name);
489
+ }
490
+ /**
491
+ * Subscribe to a topic
492
+ */
493
+ async subscribe(topicName, request) {
494
+ const subscription = {
495
+ subscriptionId: generateSubscriptionId(),
496
+ topicName,
497
+ url: request.url,
498
+ urlGroup: request.urlGroup,
499
+ createdAt: Date.now(),
500
+ };
501
+ this.subscriptions.set(subscription.subscriptionId, subscription);
502
+ let topicSubs = this.topicSubscriptions.get(topicName);
503
+ if (!topicSubs) {
504
+ topicSubs = new Set();
505
+ this.topicSubscriptions.set(topicName, topicSubs);
506
+ }
507
+ topicSubs.add(subscription.subscriptionId);
508
+ return subscription;
509
+ }
510
+ /**
511
+ * Unsubscribe from a topic
512
+ */
513
+ async unsubscribe(subscriptionId) {
514
+ const sub = this.subscriptions.get(subscriptionId);
515
+ if (sub) {
516
+ this.subscriptions.delete(subscriptionId);
517
+ const topicSubs = this.topicSubscriptions.get(sub.topicName);
518
+ if (topicSubs) {
519
+ topicSubs.delete(subscriptionId);
520
+ }
521
+ }
522
+ }
523
+ /**
524
+ * List subscriptions for a topic
525
+ */
526
+ async listSubscriptions(topicName) {
527
+ const subIds = this.topicSubscriptions.get(topicName);
528
+ if (!subIds)
529
+ return [];
530
+ const result = [];
531
+ for (const id of Array.from(subIds)) {
532
+ const sub = this.subscriptions.get(id);
533
+ if (sub) {
534
+ result.push(sub);
535
+ }
536
+ }
537
+ return result;
538
+ }
539
+ /**
540
+ * Get all subscribers for a topic (internal use)
541
+ */
542
+ _getSubscribers(topicName) {
543
+ return this.listSubscriptions(topicName);
544
+ }
545
+ }
546
+ export class Events {
547
+ events = [];
548
+ eventMap = new Map();
549
+ /**
550
+ * Record an event (internal use)
551
+ */
552
+ _recordEvent(event) {
553
+ const eventId = generateEventId();
554
+ const fullEvent = {
555
+ ...event,
556
+ eventId,
557
+ cursor: eventId,
558
+ };
559
+ this.events.push(fullEvent);
560
+ this.eventMap.set(eventId, fullEvent);
561
+ }
562
+ /**
563
+ * List events with optional filters
564
+ */
565
+ async list(options) {
566
+ let result = [...this.events];
567
+ // Filter by type
568
+ if (options?.type) {
569
+ result = result.filter((e) => e.type === options.type);
570
+ }
571
+ // Filter by messageId
572
+ if (options?.messageId) {
573
+ result = result.filter((e) => e.messageId === options.messageId);
574
+ }
575
+ // Handle pagination
576
+ if (options?.cursor) {
577
+ const cursorIndex = result.findIndex((e) => e.eventId === options.cursor);
578
+ if (cursorIndex !== -1) {
579
+ result = result.slice(cursorIndex + 1);
580
+ }
581
+ }
582
+ // Apply limit
583
+ if (options?.limit) {
584
+ result = result.slice(0, options.limit);
585
+ }
586
+ return result;
587
+ }
588
+ /**
589
+ * Get a specific event by ID
590
+ */
591
+ async get(eventId) {
592
+ return this.eventMap.get(eventId) ?? null;
593
+ }
594
+ }
595
+ // ============================================================================
596
+ // CLIENT
597
+ // ============================================================================
598
+ export class Client {
599
+ schedules;
600
+ urlGroups;
601
+ dlq;
602
+ topics;
603
+ events;
604
+ runtime;
605
+ deduplicationCache = new Map();
606
+ config;
607
+ cleanupIntervalId = null;
608
+ // Deduplication window is 1 hour, evict entries older than 2 hours
609
+ static DEDUP_EVICTION_AGE_MS = 2 * 60 * 60 * 1000; // 2 hours
610
+ static DEDUP_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
611
+ constructor(config = {}) {
612
+ this.config = config;
613
+ this.runtime = new DurableWorkflowRuntime({
614
+ storage: config.storage ?? new InMemoryStepStorage(),
615
+ retryPolicy: config.retry
616
+ ? {
617
+ maxAttempts: config.retry.retries ?? 3,
618
+ initialDelayMs: 1000,
619
+ maxDelayMs: 30000,
620
+ backoffMultiplier: 2,
621
+ jitter: true,
622
+ }
623
+ : undefined,
624
+ });
625
+ this.schedules = new Schedules(config.state);
626
+ this.urlGroups = new URLGroups();
627
+ this.dlq = new DLQ();
628
+ this.topics = new Topics();
629
+ this.events = new Events();
630
+ // Start periodic cleanup of deduplication cache
631
+ this._startDeduplicationCleanup();
632
+ }
633
+ /**
634
+ * Start periodic cleanup of the deduplication cache to prevent memory leaks
635
+ */
636
+ _startDeduplicationCleanup() {
637
+ this.cleanupIntervalId = setInterval(() => {
638
+ this._cleanupDeduplicationCache();
639
+ }, Client.DEDUP_CLEANUP_INTERVAL_MS);
640
+ // Ensure cleanup doesn't prevent process from exiting (Node.js)
641
+ if (typeof this.cleanupIntervalId === 'object' && 'unref' in this.cleanupIntervalId) {
642
+ this.cleanupIntervalId.unref();
643
+ }
644
+ }
645
+ /**
646
+ * Clean up expired entries from the deduplication cache
647
+ */
648
+ _cleanupDeduplicationCache() {
649
+ const now = Date.now();
650
+ this.deduplicationCache.forEach((entry, key) => {
651
+ if (now - entry.timestamp > Client.DEDUP_EVICTION_AGE_MS) {
652
+ this.deduplicationCache.delete(key);
653
+ }
654
+ });
655
+ }
656
+ /**
657
+ * Stop the cleanup interval (useful for testing or shutdown)
658
+ */
659
+ destroy() {
660
+ if (this.cleanupIntervalId !== null) {
661
+ clearInterval(this.cleanupIntervalId);
662
+ this.cleanupIntervalId = null;
663
+ }
664
+ }
665
+ /**
666
+ * Handle callback failure - logs the error and emits a callback_failed event
667
+ */
668
+ _handleCallbackFailure(messageId, callbackUrl, error) {
669
+ const errorMessage = error instanceof Error ? error.message : String(error);
670
+ console.error(`[QStash] Callback failed for ${callbackUrl}:`, error);
671
+ this.events._recordEvent({
672
+ type: 'callback_failed',
673
+ messageId,
674
+ url: callbackUrl,
675
+ error: errorMessage,
676
+ timestamp: Date.now(),
677
+ });
678
+ }
679
+ /**
680
+ * Publish a message to a URL, URL group, or topic
681
+ */
682
+ async publish(request) {
683
+ const messageId = generateMessageId();
684
+ const bodyStr = typeof request.body === 'object' ? JSON.stringify(request.body) : (request.body ?? '');
685
+ const maxRetries = request.retries ?? 3;
686
+ // Check for deduplication
687
+ if (request.deduplicationId || request.contentBasedDeduplication) {
688
+ const dedupKey = request.deduplicationId ?? (await hashBody(bodyStr));
689
+ const cached = this.deduplicationCache.get(dedupKey);
690
+ // QStash deduplication window is 1 hour
691
+ if (cached && Date.now() - cached.timestamp < 60 * 60 * 1000) {
692
+ return {
693
+ messageId: cached.messageId,
694
+ url: request.url,
695
+ deduplicated: true,
696
+ };
697
+ }
698
+ this.deduplicationCache.set(dedupKey, { messageId, timestamp: Date.now() });
699
+ }
700
+ // Record created event
701
+ this.events._recordEvent({
702
+ type: 'created',
703
+ messageId,
704
+ timestamp: Date.now(),
705
+ url: request.url,
706
+ });
707
+ // Calculate delay
708
+ const delayMs = request.delay ? parseDuration(request.delay) : 0;
709
+ // Handle URL group fan-out
710
+ if (request.urlGroup) {
711
+ return this._publishToUrlGroup(request, messageId, bodyStr, delayMs);
712
+ }
713
+ // Handle topic pub/sub (also check if it's a URL group name for simpler fan-out)
714
+ if (request.topic) {
715
+ const group = await this.urlGroups.get(request.topic);
716
+ if (group) {
717
+ // Topic name matches a URL group - do fan-out to all group endpoints
718
+ return this._publishToUrlGroupByName(request, messageId, bodyStr, delayMs, request.topic);
719
+ }
720
+ return this._publishToTopic(request, messageId, bodyStr, delayMs);
721
+ }
722
+ // Get the destination URL
723
+ const url = request.url ?? (request.api ? `https://qstash.upstash.io/v2/api/${request.api.name}` : '');
724
+ if (!url) {
725
+ throw new Error('Either url, urlGroup, topic, or api must be provided');
726
+ }
727
+ // Execute the delivery
728
+ const attemptHistory = [];
729
+ const executeDelivery = async () => {
730
+ let lastError;
731
+ let lastStatus;
732
+ let attempt = 0;
733
+ let shouldRetry = true;
734
+ for (attempt = 1; attempt <= maxRetries + 1 && shouldRetry; attempt++) {
735
+ try {
736
+ const response = await this._deliverMessage(url, bodyStr, request, messageId, attempt);
737
+ // Record delivered event
738
+ this.events._recordEvent({
739
+ type: 'delivered',
740
+ messageId,
741
+ timestamp: Date.now(),
742
+ url,
743
+ statusCode: response.status,
744
+ });
745
+ // Call success callback
746
+ if (request.callback) {
747
+ const responseBody = await response.clone().text();
748
+ const responseHeaders = {};
749
+ response.headers.forEach((value, key) => {
750
+ responseHeaders[key] = value;
751
+ });
752
+ await fetch(request.callback, {
753
+ method: 'POST',
754
+ headers: { 'Content-Type': 'application/json' },
755
+ body: JSON.stringify({
756
+ messageId,
757
+ url,
758
+ status: 'success',
759
+ statusCode: response.status,
760
+ body: responseBody,
761
+ response: {
762
+ body: responseBody,
763
+ headers: responseHeaders,
764
+ },
765
+ }),
766
+ }).catch((error) => {
767
+ this._handleCallbackFailure(messageId, request.callback, error);
768
+ });
769
+ }
770
+ return; // Success - exit the function
771
+ }
772
+ catch (error) {
773
+ lastError = error;
774
+ // Track attempt history
775
+ attemptHistory.push({
776
+ attempt,
777
+ timestamp: Date.now(),
778
+ error: lastError.message,
779
+ });
780
+ // Extract status code from error message if present
781
+ const statusMatch = lastError.message.match(/HTTP (\d+):/);
782
+ if (statusMatch) {
783
+ lastStatus = parseInt(statusMatch[1], 10);
784
+ // Don't retry on 4xx errors (client errors)
785
+ if (!isRetryableStatus(lastStatus)) {
786
+ shouldRetry = false;
787
+ }
788
+ }
789
+ // Record retry event (if not the last attempt and should retry)
790
+ if (attempt < maxRetries + 1 && shouldRetry) {
791
+ this.events._recordEvent({
792
+ type: 'retry',
793
+ messageId,
794
+ timestamp: Date.now(),
795
+ url,
796
+ error: lastError.message,
797
+ attempt,
798
+ });
799
+ // Wait before next retry with exponential backoff
800
+ const backoffDelay = calculateBackoffDelay(attempt);
801
+ await new Promise((resolve) => setTimeout(resolve, backoffDelay));
802
+ }
803
+ }
804
+ }
805
+ // All retries exhausted or non-retryable error
806
+ this.events._recordEvent({
807
+ type: 'failed',
808
+ messageId,
809
+ timestamp: Date.now(),
810
+ url,
811
+ error: lastError?.message,
812
+ });
813
+ // Send to DLQ URL if provided in request
814
+ if (request.deadLetterQueue) {
815
+ await fetch(request.deadLetterQueue, {
816
+ method: 'POST',
817
+ headers: { 'Content-Type': 'application/json' },
818
+ body: JSON.stringify({
819
+ messageId,
820
+ originalUrl: url,
821
+ body: bodyStr,
822
+ method: request.method ?? 'POST',
823
+ headers: request.headers,
824
+ error: lastError?.message ?? 'Unknown error',
825
+ attempts: attempt - 1,
826
+ timestamp: Date.now(),
827
+ }),
828
+ }).catch((error) => {
829
+ // Log DLQ delivery failure
830
+ console.error(`[QStash] DLQ delivery failed for ${request.deadLetterQueue}:`, error);
831
+ });
832
+ }
833
+ // Add to internal DLQ storage
834
+ this.dlq._addMessage({
835
+ messageId,
836
+ url,
837
+ body: bodyStr,
838
+ method: request.method ?? 'POST',
839
+ headers: request.headers,
840
+ failureReason: lastError?.message ?? 'Unknown error',
841
+ attempts: attempt - 1,
842
+ dlqTimestamp: Date.now(),
843
+ createdAt: Date.now(),
844
+ });
845
+ // Record DLQ event
846
+ this.events._recordEvent({
847
+ type: 'dlq',
848
+ messageId,
849
+ timestamp: Date.now(),
850
+ url,
851
+ error: lastError?.message,
852
+ });
853
+ // Call failure callback
854
+ if (request.failureCallback) {
855
+ await fetch(request.failureCallback, {
856
+ method: 'POST',
857
+ headers: { 'Content-Type': 'application/json' },
858
+ body: JSON.stringify({
859
+ messageId,
860
+ url,
861
+ status: 'failed',
862
+ error: lastError?.message,
863
+ attempts: attempt - 1,
864
+ attemptHistory,
865
+ }),
866
+ }).catch((error) => {
867
+ this._handleCallbackFailure(messageId, request.failureCallback, error);
868
+ });
869
+ }
870
+ };
871
+ // Execute with delay if needed
872
+ if (delayMs > 0) {
873
+ setTimeout(executeDelivery, delayMs);
874
+ }
875
+ else {
876
+ // Execute immediately (non-blocking)
877
+ executeDelivery().catch(() => {
878
+ // Errors handled within executeDelivery
879
+ });
880
+ }
881
+ return {
882
+ messageId,
883
+ url,
884
+ deduplicated: false,
885
+ };
886
+ }
887
+ /**
888
+ * Deliver a message to a single URL
889
+ */
890
+ async _deliverMessage(url, bodyStr, request, messageId, attempt = 1) {
891
+ const headers = {
892
+ 'Content-Type': request.contentType ?? 'application/json',
893
+ ...request.headers,
894
+ 'Upstash-Message-Id': messageId,
895
+ };
896
+ // Add Upstash-Retried header on retries (attempt > 1)
897
+ if (attempt > 1) {
898
+ headers['Upstash-Retried'] = String(attempt - 1);
899
+ }
900
+ // Add signature if signing key is configured
901
+ if (this.config.signingKey) {
902
+ headers['Upstash-Signature'] = await generateSignature(this.config.signingKey, bodyStr);
903
+ }
904
+ // Setup timeout with AbortController
905
+ let controller;
906
+ let timeoutId;
907
+ if (request.timeout) {
908
+ controller = new AbortController();
909
+ const timeoutMs = typeof request.timeout === 'number' ? request.timeout * 1000 : parseDuration(request.timeout);
910
+ timeoutId = setTimeout(() => controller.abort(), timeoutMs);
911
+ }
912
+ try {
913
+ const response = await fetch(url, {
914
+ method: request.method ?? 'POST',
915
+ headers,
916
+ body: bodyStr || undefined,
917
+ signal: controller?.signal,
918
+ });
919
+ if (timeoutId)
920
+ clearTimeout(timeoutId);
921
+ if (!response.ok) {
922
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
923
+ }
924
+ return response;
925
+ }
926
+ catch (error) {
927
+ if (timeoutId)
928
+ clearTimeout(timeoutId);
929
+ throw error;
930
+ }
931
+ }
932
+ /**
933
+ * Publish to a URL group by name (for topic-based fan-out)
934
+ */
935
+ async _publishToUrlGroupByName(request, messageId, bodyStr, delayMs, groupName) {
936
+ const group = await this.urlGroups.get(groupName);
937
+ if (!group) {
938
+ throw new Error(`URL group '${groupName}' not found`);
939
+ }
940
+ const deliveries = group.endpoints.map((endpoint) => ({
941
+ url: endpoint.url,
942
+ status: 'pending',
943
+ timestamp: Date.now(),
944
+ }));
945
+ const executeDelivery = async () => {
946
+ const deliveryPromises = group.endpoints.map(async (endpoint, index) => {
947
+ try {
948
+ const response = await this._deliverMessage(endpoint.url, bodyStr, request, messageId, 1);
949
+ deliveries[index] = {
950
+ url: endpoint.url,
951
+ status: 'success',
952
+ statusCode: response.status,
953
+ timestamp: Date.now(),
954
+ };
955
+ }
956
+ catch (error) {
957
+ deliveries[index] = {
958
+ url: endpoint.url,
959
+ status: 'failed',
960
+ error: ensureError(error).message,
961
+ timestamp: Date.now(),
962
+ };
963
+ }
964
+ });
965
+ await Promise.all(deliveryPromises);
966
+ };
967
+ if (delayMs > 0) {
968
+ setTimeout(executeDelivery, delayMs);
969
+ }
970
+ else {
971
+ executeDelivery().catch(() => { });
972
+ }
973
+ return {
974
+ messageId,
975
+ topic: groupName,
976
+ deliveries,
977
+ deduplicated: false,
978
+ };
979
+ }
980
+ /**
981
+ * Publish to a URL group (fan-out)
982
+ */
983
+ async _publishToUrlGroup(request, messageId, bodyStr, delayMs) {
984
+ const group = await this.urlGroups.get(request.urlGroup);
985
+ if (!group) {
986
+ throw new Error(`URL group '${request.urlGroup}' not found`);
987
+ }
988
+ const deliveries = group.endpoints.map((endpoint) => ({
989
+ url: endpoint.url,
990
+ status: 'pending',
991
+ timestamp: Date.now(),
992
+ }));
993
+ const executeDelivery = async () => {
994
+ const deliveryPromises = group.endpoints.map(async (endpoint, index) => {
995
+ try {
996
+ const response = await this._deliverMessage(endpoint.url, bodyStr, request, messageId);
997
+ deliveries[index] = {
998
+ url: endpoint.url,
999
+ status: 'success',
1000
+ statusCode: response.status,
1001
+ timestamp: Date.now(),
1002
+ };
1003
+ // Call callback for each successful delivery
1004
+ if (request.callback) {
1005
+ await fetch(request.callback, {
1006
+ method: 'POST',
1007
+ headers: { 'Content-Type': 'application/json' },
1008
+ body: JSON.stringify({
1009
+ messageId,
1010
+ destinationUrl: endpoint.url,
1011
+ status: 'success',
1012
+ statusCode: response.status,
1013
+ }),
1014
+ }).catch((err) => {
1015
+ this._handleCallbackFailure(messageId, request.callback, err);
1016
+ });
1017
+ }
1018
+ }
1019
+ catch (error) {
1020
+ const errorMessage = ensureError(error).message;
1021
+ deliveries[index] = {
1022
+ url: endpoint.url,
1023
+ status: 'failed',
1024
+ error: errorMessage,
1025
+ timestamp: Date.now(),
1026
+ };
1027
+ // Call callback for each failed delivery
1028
+ if (request.callback) {
1029
+ await fetch(request.callback, {
1030
+ method: 'POST',
1031
+ headers: { 'Content-Type': 'application/json' },
1032
+ body: JSON.stringify({
1033
+ messageId,
1034
+ destinationUrl: endpoint.url,
1035
+ status: 'failed',
1036
+ error: errorMessage,
1037
+ }),
1038
+ }).catch((err) => {
1039
+ this._handleCallbackFailure(messageId, request.callback, err);
1040
+ });
1041
+ }
1042
+ }
1043
+ });
1044
+ await Promise.all(deliveryPromises);
1045
+ };
1046
+ if (delayMs > 0) {
1047
+ setTimeout(executeDelivery, delayMs);
1048
+ }
1049
+ else {
1050
+ executeDelivery().catch(() => { });
1051
+ }
1052
+ return {
1053
+ messageId,
1054
+ urlGroup: request.urlGroup,
1055
+ deliveries,
1056
+ deduplicated: false,
1057
+ };
1058
+ }
1059
+ /**
1060
+ * Publish to a topic (pub/sub)
1061
+ */
1062
+ async _publishToTopic(request, messageId, bodyStr, delayMs) {
1063
+ const subscriptions = await this.topics.listSubscriptions(request.topic);
1064
+ const deliveries = [];
1065
+ const executeDelivery = async () => {
1066
+ for (const sub of subscriptions) {
1067
+ if (sub.url) {
1068
+ // Direct URL subscription
1069
+ try {
1070
+ await this._deliverMessage(sub.url, bodyStr, request, messageId);
1071
+ deliveries.push({
1072
+ url: sub.url,
1073
+ status: 'success',
1074
+ timestamp: Date.now(),
1075
+ });
1076
+ }
1077
+ catch (error) {
1078
+ deliveries.push({
1079
+ url: sub.url,
1080
+ status: 'failed',
1081
+ error: ensureError(error).message,
1082
+ timestamp: Date.now(),
1083
+ });
1084
+ }
1085
+ }
1086
+ else if (sub.urlGroup) {
1087
+ // URL group subscription
1088
+ const group = await this.urlGroups.get(sub.urlGroup);
1089
+ if (group) {
1090
+ for (const endpoint of group.endpoints) {
1091
+ try {
1092
+ await this._deliverMessage(endpoint.url, bodyStr, request, messageId);
1093
+ deliveries.push({
1094
+ url: endpoint.url,
1095
+ status: 'success',
1096
+ timestamp: Date.now(),
1097
+ });
1098
+ }
1099
+ catch (error) {
1100
+ deliveries.push({
1101
+ url: endpoint.url,
1102
+ status: 'failed',
1103
+ error: ensureError(error).message,
1104
+ timestamp: Date.now(),
1105
+ });
1106
+ }
1107
+ }
1108
+ }
1109
+ }
1110
+ }
1111
+ };
1112
+ if (delayMs > 0) {
1113
+ setTimeout(executeDelivery, delayMs);
1114
+ }
1115
+ else {
1116
+ executeDelivery().catch(() => { });
1117
+ }
1118
+ return {
1119
+ messageId,
1120
+ topic: request.topic,
1121
+ deliveries,
1122
+ deduplicated: false,
1123
+ };
1124
+ }
1125
+ /**
1126
+ * Publish JSON data to a URL (convenience method)
1127
+ */
1128
+ async publishJSON(request) {
1129
+ return this.publish({
1130
+ ...request,
1131
+ body: request.body,
1132
+ contentType: 'application/json',
1133
+ });
1134
+ }
1135
+ /**
1136
+ * Publish multiple messages in a batch
1137
+ * Uses Promise.allSettled to return partial results even if some messages fail
1138
+ */
1139
+ async batch(messages) {
1140
+ const results = await Promise.allSettled(messages.map((msg) => this.publish(msg)));
1141
+ const responses = results.map((result, index) => {
1142
+ if (result.status === 'fulfilled') {
1143
+ return result.value;
1144
+ }
1145
+ // For rejected promises, return a minimal response with error info
1146
+ return {
1147
+ messageId: `failed_${index}`,
1148
+ url: messages[index].url,
1149
+ deduplicated: false,
1150
+ error: result.reason?.message || 'Unknown error',
1151
+ };
1152
+ });
1153
+ return { responses };
1154
+ }
1155
+ /**
1156
+ * Enqueue a message (alias for publish)
1157
+ */
1158
+ async enqueue(request) {
1159
+ return this.publish(request);
1160
+ }
1161
+ /**
1162
+ * Get a message by ID (stub - QStash doesn't actually support this well)
1163
+ */
1164
+ async getMessage(_messageId) {
1165
+ // QStash doesn't have a direct getMessage API
1166
+ // This would need to be implemented via DLQ or logs
1167
+ return null;
1168
+ }
1169
+ /**
1170
+ * Publish to a URL group (fan-out) by topic name
1171
+ */
1172
+ async publishToGroup(request) {
1173
+ const group = await this.urlGroups.get(request.topic);
1174
+ if (!group) {
1175
+ throw new Error(`URL group '${request.topic}' not found`);
1176
+ }
1177
+ const messageId = generateMessageId();
1178
+ const bodyStr = typeof request.body === 'object' ? JSON.stringify(request.body) : (request.body ?? '');
1179
+ const responses = group.endpoints.map((endpoint) => ({
1180
+ messageId,
1181
+ url: endpoint.url,
1182
+ deduplicated: false,
1183
+ }));
1184
+ // Calculate delay
1185
+ const delayMs = request.delay ? parseDuration(request.delay) : 0;
1186
+ const executeDelivery = async () => {
1187
+ await Promise.all(group.endpoints.map(async (endpoint) => {
1188
+ try {
1189
+ await this._deliverMessage(endpoint.url, bodyStr, request, messageId, 1);
1190
+ }
1191
+ catch {
1192
+ // Errors logged elsewhere
1193
+ }
1194
+ }));
1195
+ };
1196
+ if (delayMs > 0) {
1197
+ setTimeout(executeDelivery, delayMs);
1198
+ }
1199
+ else {
1200
+ executeDelivery().catch(() => { });
1201
+ }
1202
+ return { responses };
1203
+ }
1204
+ }
1205
+ // ============================================================================
1206
+ // RECEIVER
1207
+ // ============================================================================
1208
+ export class Receiver {
1209
+ currentSigningKey;
1210
+ nextSigningKey;
1211
+ constructor(config) {
1212
+ this.currentSigningKey = config.currentSigningKey;
1213
+ this.nextSigningKey = config.nextSigningKey;
1214
+ }
1215
+ /**
1216
+ * Verify a QStash webhook signature
1217
+ */
1218
+ async verify(request) {
1219
+ const { signature, body, clockTolerance = 0 } = request;
1220
+ // QStash signature format: t=<timestamp>,v1=<signature>
1221
+ const parts = signature.split(',');
1222
+ const timestampPart = parts.find((p) => p.startsWith('t='));
1223
+ const signaturePart = parts.find((p) => p.startsWith('v1='));
1224
+ if (!timestampPart || !signaturePart) {
1225
+ return false;
1226
+ }
1227
+ const timestamp = parseInt(timestampPart.slice(2), 10);
1228
+ const expectedSignature = signaturePart.slice(3);
1229
+ // Check timestamp tolerance
1230
+ if (clockTolerance > 0) {
1231
+ const now = Math.floor(Date.now() / 1000);
1232
+ if (Math.abs(now - timestamp) > clockTolerance) {
1233
+ return false;
1234
+ }
1235
+ }
1236
+ // Verify signature with both keys
1237
+ const payload = `${timestamp}.${body}`;
1238
+ for (const key of [this.currentSigningKey, this.nextSigningKey]) {
1239
+ if (!key)
1240
+ continue;
1241
+ try {
1242
+ const cryptoKey = await crypto.subtle.importKey('raw', new TextEncoder().encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
1243
+ const signatureBuffer = await crypto.subtle.sign('HMAC', cryptoKey, new TextEncoder().encode(payload));
1244
+ const computedSignature = Array.from(new Uint8Array(signatureBuffer))
1245
+ .map((b) => b.toString(16).padStart(2, '0'))
1246
+ .join('');
1247
+ if (computedSignature === expectedSignature) {
1248
+ return true;
1249
+ }
1250
+ }
1251
+ catch {
1252
+ continue;
1253
+ }
1254
+ }
1255
+ return false;
1256
+ }
1257
+ }
1258
+ // ============================================================================
1259
+ // EXPORTS
1260
+ // ============================================================================
1261
+ export default Client;
1262
+ export { parseCronExpression, getNextRunTime };
1263
+ //# sourceMappingURL=index.js.map