dotdo 0.0.1 → 0.0.2

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 (727) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +446 -315
  3. package/dist/ai/index.js +19 -0
  4. package/dist/ai/index.js.map +1 -0
  5. package/dist/ai/template-literals.js +852 -0
  6. package/dist/ai/template-literals.js.map +1 -0
  7. package/dist/api/analytics/router.js +601 -0
  8. package/dist/api/analytics/router.js.map +1 -0
  9. package/dist/api/index.js +158 -0
  10. package/dist/api/index.js.map +1 -0
  11. package/dist/api/middleware/auth-federation.js +573 -0
  12. package/dist/api/middleware/auth-federation.js.map +1 -0
  13. package/dist/api/middleware/auth.js +544 -0
  14. package/dist/api/middleware/auth.js.map +1 -0
  15. package/dist/api/middleware/error-handling.js +176 -0
  16. package/dist/api/middleware/error-handling.js.map +1 -0
  17. package/dist/api/middleware/request-id.js +21 -0
  18. package/dist/api/middleware/request-id.js.map +1 -0
  19. package/dist/api/pages.js +1180 -0
  20. package/dist/api/pages.js.map +1 -0
  21. package/dist/api/routes/api.js +612 -0
  22. package/dist/api/routes/api.js.map +1 -0
  23. package/dist/api/routes/browsers.js +471 -0
  24. package/dist/api/routes/browsers.js.map +1 -0
  25. package/dist/api/routes/do.js +188 -0
  26. package/dist/api/routes/do.js.map +1 -0
  27. package/dist/api/routes/mcp.js +459 -0
  28. package/dist/api/routes/mcp.js.map +1 -0
  29. package/dist/api/routes/obs.js +445 -0
  30. package/dist/api/routes/obs.js.map +1 -0
  31. package/dist/api/routes/openapi.js +794 -0
  32. package/dist/api/routes/openapi.js.map +1 -0
  33. package/dist/api/routes/rpc.js +1103 -0
  34. package/dist/api/routes/rpc.js.map +1 -0
  35. package/dist/api/routes/sandboxes.js +389 -0
  36. package/dist/api/routes/sandboxes.js.map +1 -0
  37. package/dist/api/test-do.js +38 -0
  38. package/dist/api/test-do.js.map +1 -0
  39. package/dist/api/types.js +11 -0
  40. package/dist/api/types.js.map +1 -0
  41. package/dist/cli/bin.js +2 -0
  42. package/dist/cli/main.js +52342 -0
  43. package/dist/db/actions.js +212 -0
  44. package/dist/db/actions.js.map +1 -0
  45. package/dist/db/auth.js +506 -0
  46. package/dist/db/auth.js.map +1 -0
  47. package/dist/db/branches.js +65 -0
  48. package/dist/db/branches.js.map +1 -0
  49. package/dist/db/clickhouse.js +1074 -0
  50. package/dist/db/clickhouse.js.map +1 -0
  51. package/dist/db/dlq.js +39 -0
  52. package/dist/db/dlq.js.map +1 -0
  53. package/dist/db/events.js +28 -0
  54. package/dist/db/events.js.map +1 -0
  55. package/dist/db/exec.js +64 -0
  56. package/dist/db/exec.js.map +1 -0
  57. package/dist/db/files.js +85 -0
  58. package/dist/db/files.js.map +1 -0
  59. package/dist/db/flags.js +24 -0
  60. package/dist/db/flags.js.map +1 -0
  61. package/dist/db/git.js +116 -0
  62. package/dist/db/git.js.map +1 -0
  63. package/dist/db/iceberg/inverted-index.js +862 -0
  64. package/dist/db/iceberg/inverted-index.js.map +1 -0
  65. package/dist/db/iceberg/puffin.js +878 -0
  66. package/dist/db/iceberg/puffin.js.map +1 -0
  67. package/dist/db/iceberg/search-manifest.js +422 -0
  68. package/dist/db/iceberg/search-manifest.js.map +1 -0
  69. package/dist/db/iceberg/types.js +8 -0
  70. package/dist/db/iceberg/types.js.map +1 -0
  71. package/dist/db/index.js +121 -0
  72. package/dist/db/index.js.map +1 -0
  73. package/dist/db/integrations.js +368 -0
  74. package/dist/db/integrations.js.map +1 -0
  75. package/dist/db/json-indexes.js +332 -0
  76. package/dist/db/json-indexes.js.map +1 -0
  77. package/dist/db/linked-accounts.js +287 -0
  78. package/dist/db/linked-accounts.js.map +1 -0
  79. package/dist/db/nouns.js +183 -0
  80. package/dist/db/nouns.js.map +1 -0
  81. package/dist/db/objects.js +170 -0
  82. package/dist/db/objects.js.map +1 -0
  83. package/dist/db/primitives/dag-scheduler/index.js +869 -0
  84. package/dist/db/primitives/dag-scheduler/index.js.map +1 -0
  85. package/dist/db/primitives/exactly-once-context.js +237 -0
  86. package/dist/db/primitives/exactly-once-context.js.map +1 -0
  87. package/dist/db/primitives/index.js +62 -0
  88. package/dist/db/primitives/index.js.map +1 -0
  89. package/dist/db/primitives/keyed-router.js +145 -0
  90. package/dist/db/primitives/keyed-router.js.map +1 -0
  91. package/dist/db/primitives/observability.js +162 -0
  92. package/dist/db/primitives/observability.js.map +1 -0
  93. package/dist/db/primitives/schema-evolution.js +643 -0
  94. package/dist/db/primitives/schema-evolution.js.map +1 -0
  95. package/dist/db/primitives/stateful-operator/index.js +770 -0
  96. package/dist/db/primitives/stateful-operator/index.js.map +1 -0
  97. package/dist/db/primitives/temporal-store.js +306 -0
  98. package/dist/db/primitives/temporal-store.js.map +1 -0
  99. package/dist/db/primitives/typed-column-store.js +1229 -0
  100. package/dist/db/primitives/typed-column-store.js.map +1 -0
  101. package/dist/db/primitives/utils/duration.js +162 -0
  102. package/dist/db/primitives/utils/duration.js.map +1 -0
  103. package/dist/db/primitives/utils/murmur3.js +118 -0
  104. package/dist/db/primitives/utils/murmur3.js.map +1 -0
  105. package/dist/db/primitives/watermark-service.js +136 -0
  106. package/dist/db/primitives/watermark-service.js.map +1 -0
  107. package/dist/db/primitives/window-manager.js +764 -0
  108. package/dist/db/primitives/window-manager.js.map +1 -0
  109. package/dist/db/relationships.js +66 -0
  110. package/dist/db/relationships.js.map +1 -0
  111. package/dist/db/schema-minimal.js +61 -0
  112. package/dist/db/schema-minimal.js.map +1 -0
  113. package/dist/db/search.js +28 -0
  114. package/dist/db/search.js.map +1 -0
  115. package/dist/db/stores.js +1665 -0
  116. package/dist/db/stores.js.map +1 -0
  117. package/dist/db/things.js +297 -0
  118. package/dist/db/things.js.map +1 -0
  119. package/dist/db/vault.js +171 -0
  120. package/dist/db/vault.js.map +1 -0
  121. package/dist/db/verbs.js +102 -0
  122. package/dist/db/verbs.js.map +1 -0
  123. package/dist/do/base.js +48 -0
  124. package/dist/do/base.js.map +1 -0
  125. package/dist/do/bash.js +35 -0
  126. package/dist/do/bash.js.map +1 -0
  127. package/dist/do/fs.js +25 -0
  128. package/dist/do/fs.js.map +1 -0
  129. package/dist/do/full.js +61 -0
  130. package/dist/do/full.js.map +1 -0
  131. package/dist/do/git.js +28 -0
  132. package/dist/do/git.js.map +1 -0
  133. package/dist/do/index.js +52 -0
  134. package/dist/do/index.js.map +1 -0
  135. package/dist/do/tiny.js +31 -0
  136. package/dist/do/tiny.js.map +1 -0
  137. package/dist/lib/DOAuth.js +261 -0
  138. package/dist/lib/DOAuth.js.map +1 -0
  139. package/dist/lib/DODispatcher.js +72 -0
  140. package/dist/lib/DODispatcher.js.map +1 -0
  141. package/dist/lib/Modifier.js +189 -0
  142. package/dist/lib/Modifier.js.map +1 -0
  143. package/dist/lib/StateStorage.js +403 -0
  144. package/dist/lib/StateStorage.js.map +1 -0
  145. package/dist/lib/TypeRegistry.js +122 -0
  146. package/dist/lib/TypeRegistry.js.map +1 -0
  147. package/dist/lib/agent/tools/bash.js +336 -0
  148. package/dist/lib/agent/tools/bash.js.map +1 -0
  149. package/dist/lib/agent/tools/edit.js +157 -0
  150. package/dist/lib/agent/tools/edit.js.map +1 -0
  151. package/dist/lib/agent/tools/glob.js +137 -0
  152. package/dist/lib/agent/tools/glob.js.map +1 -0
  153. package/dist/lib/agent/tools/grep.js +315 -0
  154. package/dist/lib/agent/tools/grep.js.map +1 -0
  155. package/dist/lib/agent/tools/index.js +71 -0
  156. package/dist/lib/agent/tools/index.js.map +1 -0
  157. package/dist/lib/agent/tools/read.js +212 -0
  158. package/dist/lib/agent/tools/read.js.map +1 -0
  159. package/dist/lib/agent/tools/types.js +197 -0
  160. package/dist/lib/agent/tools/types.js.map +1 -0
  161. package/dist/lib/agent/tools/write.js +159 -0
  162. package/dist/lib/agent/tools/write.js.map +1 -0
  163. package/dist/lib/ai/gateway.js +247 -0
  164. package/dist/lib/ai/gateway.js.map +1 -0
  165. package/dist/lib/ai/tool-loop-agent.js +591 -0
  166. package/dist/lib/ai/tool-loop-agent.js.map +1 -0
  167. package/dist/lib/auto-wiring.js +439 -0
  168. package/dist/lib/auto-wiring.js.map +1 -0
  169. package/dist/lib/browse/browserbase.js +163 -0
  170. package/dist/lib/browse/browserbase.js.map +1 -0
  171. package/dist/lib/browse/cloudflare.js +144 -0
  172. package/dist/lib/browse/cloudflare.js.map +1 -0
  173. package/dist/lib/browse/index.js +62 -0
  174. package/dist/lib/browse/index.js.map +1 -0
  175. package/dist/lib/browse/types.js +13 -0
  176. package/dist/lib/browse/types.js.map +1 -0
  177. package/dist/lib/cache/index.js +37 -0
  178. package/dist/lib/cache/index.js.map +1 -0
  179. package/dist/lib/cache/visibility.js +638 -0
  180. package/dist/lib/cache/visibility.js.map +1 -0
  181. package/dist/lib/capabilities.js +268 -0
  182. package/dist/lib/capabilities.js.map +1 -0
  183. package/dist/lib/channels/base.js +106 -0
  184. package/dist/lib/channels/base.js.map +1 -0
  185. package/dist/lib/channels/discord.js +94 -0
  186. package/dist/lib/channels/discord.js.map +1 -0
  187. package/dist/lib/channels/email.js +204 -0
  188. package/dist/lib/channels/email.js.map +1 -0
  189. package/dist/lib/channels/index.js +90 -0
  190. package/dist/lib/channels/index.js.map +1 -0
  191. package/dist/lib/channels/mdxui-chat.js +95 -0
  192. package/dist/lib/channels/mdxui-chat.js.map +1 -0
  193. package/dist/lib/channels/slack-blockkit.js +121 -0
  194. package/dist/lib/channels/slack-blockkit.js.map +1 -0
  195. package/dist/lib/channels/types.js +7 -0
  196. package/dist/lib/channels/types.js.map +1 -0
  197. package/dist/lib/cloudflare/ai.js +654 -0
  198. package/dist/lib/cloudflare/ai.js.map +1 -0
  199. package/dist/lib/cloudflare/index.js +88 -0
  200. package/dist/lib/cloudflare/index.js.map +1 -0
  201. package/dist/lib/cloudflare/kv.js +342 -0
  202. package/dist/lib/cloudflare/kv.js.map +1 -0
  203. package/dist/lib/cloudflare/queues.js +434 -0
  204. package/dist/lib/cloudflare/queues.js.map +1 -0
  205. package/dist/lib/cloudflare/r2.js +604 -0
  206. package/dist/lib/cloudflare/r2.js.map +1 -0
  207. package/dist/lib/cloudflare/vectorize.js +494 -0
  208. package/dist/lib/cloudflare/vectorize.js.map +1 -0
  209. package/dist/lib/cloudflare/workflows.js +569 -0
  210. package/dist/lib/cloudflare/workflows.js.map +1 -0
  211. package/dist/lib/colo/caching.js +196 -0
  212. package/dist/lib/colo/caching.js.map +1 -0
  213. package/dist/lib/colo/detection.js +194 -0
  214. package/dist/lib/colo/detection.js.map +1 -0
  215. package/dist/lib/colo/external-data.js +219 -0
  216. package/dist/lib/colo/external-data.js.map +1 -0
  217. package/dist/lib/colo/globe-data.js +179 -0
  218. package/dist/lib/colo/globe-data.js.map +1 -0
  219. package/dist/lib/colo/index.js +16 -0
  220. package/dist/lib/colo/index.js.map +1 -0
  221. package/dist/lib/decorators.js +37 -0
  222. package/dist/lib/decorators.js.map +1 -0
  223. package/dist/lib/discovery.js +81 -0
  224. package/dist/lib/discovery.js.map +1 -0
  225. package/dist/lib/executors/AgenticFunctionExecutor.js +619 -0
  226. package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -0
  227. package/dist/lib/executors/BaseFunctionExecutor.js +328 -0
  228. package/dist/lib/executors/BaseFunctionExecutor.js.map +1 -0
  229. package/dist/lib/executors/CascadeExecutor.js +418 -0
  230. package/dist/lib/executors/CascadeExecutor.js.map +1 -0
  231. package/dist/lib/executors/CodeFunctionExecutor.js +904 -0
  232. package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -0
  233. package/dist/lib/executors/GenerativeFunctionExecutor.js +904 -0
  234. package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -0
  235. package/dist/lib/executors/HumanFunctionExecutor.js +884 -0
  236. package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -0
  237. package/dist/lib/executors/ParallelStepExecutor.js +308 -0
  238. package/dist/lib/executors/ParallelStepExecutor.js.map +1 -0
  239. package/dist/lib/executors/types.js +12 -0
  240. package/dist/lib/executors/types.js.map +1 -0
  241. package/dist/lib/experiments.js +89 -0
  242. package/dist/lib/experiments.js.map +1 -0
  243. package/dist/lib/flags/store.js +262 -0
  244. package/dist/lib/flags/store.js.map +1 -0
  245. package/dist/lib/functions/FunctionComposition.js +467 -0
  246. package/dist/lib/functions/FunctionComposition.js.map +1 -0
  247. package/dist/lib/functions/FunctionMiddleware.js +457 -0
  248. package/dist/lib/functions/FunctionMiddleware.js.map +1 -0
  249. package/dist/lib/functions/FunctionRegistry.js +426 -0
  250. package/dist/lib/functions/FunctionRegistry.js.map +1 -0
  251. package/dist/lib/functions/createFunction.js +1048 -0
  252. package/dist/lib/functions/createFunction.js.map +1 -0
  253. package/dist/lib/humans/index.js +68 -0
  254. package/dist/lib/humans/index.js.map +1 -0
  255. package/dist/lib/humans/templates.js +117 -0
  256. package/dist/lib/humans/templates.js.map +1 -0
  257. package/dist/lib/identity.js +98 -0
  258. package/dist/lib/identity.js.map +1 -0
  259. package/dist/lib/index.js +9 -0
  260. package/dist/lib/index.js.map +1 -0
  261. package/dist/lib/logging/error-logger.js +163 -0
  262. package/dist/lib/logging/error-logger.js.map +1 -0
  263. package/dist/lib/logging/index.js +160 -0
  264. package/dist/lib/logging/index.js.map +1 -0
  265. package/dist/lib/mixins/bash.js +825 -0
  266. package/dist/lib/mixins/bash.js.map +1 -0
  267. package/dist/lib/mixins/fs.js +648 -0
  268. package/dist/lib/mixins/fs.js.map +1 -0
  269. package/dist/lib/mixins/git.js +1011 -0
  270. package/dist/lib/mixins/git.js.map +1 -0
  271. package/dist/lib/mixins/index.js +29 -0
  272. package/dist/lib/mixins/index.js.map +1 -0
  273. package/dist/lib/mixins/npm.js +662 -0
  274. package/dist/lib/mixins/npm.js.map +1 -0
  275. package/dist/lib/noun-id.js +278 -0
  276. package/dist/lib/noun-id.js.map +1 -0
  277. package/dist/lib/rate-limit/sliding-window.js +148 -0
  278. package/dist/lib/rate-limit/sliding-window.js.map +1 -0
  279. package/dist/lib/rate-limit.js +110 -0
  280. package/dist/lib/rate-limit.js.map +1 -0
  281. package/dist/lib/rpc/bindings.js +548 -0
  282. package/dist/lib/rpc/bindings.js.map +1 -0
  283. package/dist/lib/rpc/index.js +64 -0
  284. package/dist/lib/rpc/index.js.map +1 -0
  285. package/dist/lib/safe-stringify.js +223 -0
  286. package/dist/lib/safe-stringify.js.map +1 -0
  287. package/dist/lib/sandbox/miniflare-sandbox.js +1007 -0
  288. package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -0
  289. package/dist/lib/sqids.js +110 -0
  290. package/dist/lib/sqids.js.map +1 -0
  291. package/dist/lib/sql/adapters/index.js +10 -0
  292. package/dist/lib/sql/adapters/index.js.map +1 -0
  293. package/dist/lib/sql/adapters/node-sql-parser.js +552 -0
  294. package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -0
  295. package/dist/lib/sql/adapters/pgsql-parser.js +1189 -0
  296. package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -0
  297. package/dist/lib/sql/index.js +277 -0
  298. package/dist/lib/sql/index.js.map +1 -0
  299. package/dist/lib/sql/types.js +56 -0
  300. package/dist/lib/sql/types.js.map +1 -0
  301. package/dist/lib/type-classifier.js +126 -0
  302. package/dist/lib/type-classifier.js.map +1 -0
  303. package/dist/lib/utils/html.js +47 -0
  304. package/dist/lib/utils/html.js.map +1 -0
  305. package/dist/lib/validation.js +48 -0
  306. package/dist/lib/validation.js.map +1 -0
  307. package/dist/lib/vault/store.js +411 -0
  308. package/dist/lib/vault/store.js.map +1 -0
  309. package/dist/metrics/hunch.js +739 -0
  310. package/dist/metrics/hunch.js.map +1 -0
  311. package/dist/objects/API.js +302 -0
  312. package/dist/objects/API.js.map +1 -0
  313. package/dist/objects/Agent.js +179 -0
  314. package/dist/objects/Agent.js.map +1 -0
  315. package/dist/objects/AgenticFunctionExecutor.js +8 -0
  316. package/dist/objects/AgenticFunctionExecutor.js.map +1 -0
  317. package/dist/objects/App.js +83 -0
  318. package/dist/objects/App.js.map +1 -0
  319. package/dist/objects/Browser.js +884 -0
  320. package/dist/objects/Browser.js.map +1 -0
  321. package/dist/objects/Business.js +107 -0
  322. package/dist/objects/Business.js.map +1 -0
  323. package/dist/objects/CLI.js +221 -0
  324. package/dist/objects/CLI.js.map +1 -0
  325. package/dist/objects/CodeFunctionExecutor.js +8 -0
  326. package/dist/objects/CodeFunctionExecutor.js.map +1 -0
  327. package/dist/objects/Collection.js +161 -0
  328. package/dist/objects/Collection.js.map +1 -0
  329. package/dist/objects/DO.js +41 -0
  330. package/dist/objects/DO.js.map +1 -0
  331. package/dist/objects/DOBase.js +2309 -0
  332. package/dist/objects/DOBase.js.map +1 -0
  333. package/dist/objects/DOFull.js +1676 -0
  334. package/dist/objects/DOFull.js.map +1 -0
  335. package/dist/objects/DOTiny.js +207 -0
  336. package/dist/objects/DOTiny.js.map +1 -0
  337. package/dist/objects/Directory.js +199 -0
  338. package/dist/objects/Directory.js.map +1 -0
  339. package/dist/objects/Entity.js +413 -0
  340. package/dist/objects/Entity.js.map +1 -0
  341. package/dist/objects/Function.js +116 -0
  342. package/dist/objects/Function.js.map +1 -0
  343. package/dist/objects/Human.js +231 -0
  344. package/dist/objects/Human.js.map +1 -0
  345. package/dist/objects/HumanFunctionExecutor.js +8 -0
  346. package/dist/objects/HumanFunctionExecutor.js.map +1 -0
  347. package/dist/objects/IcebergMetadataDO.js +938 -0
  348. package/dist/objects/IcebergMetadataDO.js.map +1 -0
  349. package/dist/objects/IntegrationsDO.js +1174 -0
  350. package/dist/objects/IntegrationsDO.js.map +1 -0
  351. package/dist/objects/ObservabilityBroadcaster.js +149 -0
  352. package/dist/objects/ObservabilityBroadcaster.js.map +1 -0
  353. package/dist/objects/Package.js +154 -0
  354. package/dist/objects/Package.js.map +1 -0
  355. package/dist/objects/Product.js +193 -0
  356. package/dist/objects/Product.js.map +1 -0
  357. package/dist/objects/SDK.js +152 -0
  358. package/dist/objects/SDK.js.map +1 -0
  359. package/dist/objects/SaaS.js +235 -0
  360. package/dist/objects/SaaS.js.map +1 -0
  361. package/dist/objects/SandboxDO.js +759 -0
  362. package/dist/objects/SandboxDO.js.map +1 -0
  363. package/dist/objects/Service.js +337 -0
  364. package/dist/objects/Service.js.map +1 -0
  365. package/dist/objects/Site.js +80 -0
  366. package/dist/objects/Site.js.map +1 -0
  367. package/dist/objects/Startup.js +479 -0
  368. package/dist/objects/Startup.js.map +1 -0
  369. package/dist/objects/ThingsDO.js +170 -0
  370. package/dist/objects/ThingsDO.js.map +1 -0
  371. package/dist/objects/VectorShardDO.js +648 -0
  372. package/dist/objects/VectorShardDO.js.map +1 -0
  373. package/dist/objects/Worker.js +144 -0
  374. package/dist/objects/Worker.js.map +1 -0
  375. package/dist/objects/Workflow.js +196 -0
  376. package/dist/objects/Workflow.js.map +1 -0
  377. package/dist/objects/WorkflowFactory.js +313 -0
  378. package/dist/objects/WorkflowFactory.js.map +1 -0
  379. package/dist/objects/WorkflowRuntime.js +863 -0
  380. package/dist/objects/WorkflowRuntime.js.map +1 -0
  381. package/dist/objects/circuit-breaker-bulkhead.js +178 -0
  382. package/dist/objects/circuit-breaker-bulkhead.js.map +1 -0
  383. package/dist/objects/createFunction.js +934 -0
  384. package/dist/objects/createFunction.js.map +1 -0
  385. package/dist/objects/index.js +80 -0
  386. package/dist/objects/index.js.map +1 -0
  387. package/dist/objects/lifecycle/Branch.js +275 -0
  388. package/dist/objects/lifecycle/Branch.js.map +1 -0
  389. package/dist/objects/lifecycle/Clone.js +1499 -0
  390. package/dist/objects/lifecycle/Clone.js.map +1 -0
  391. package/dist/objects/lifecycle/Compact.js +237 -0
  392. package/dist/objects/lifecycle/Compact.js.map +1 -0
  393. package/dist/objects/lifecycle/Promote.js +476 -0
  394. package/dist/objects/lifecycle/Promote.js.map +1 -0
  395. package/dist/objects/lifecycle/Shard.js +560 -0
  396. package/dist/objects/lifecycle/Shard.js.map +1 -0
  397. package/dist/objects/lifecycle/index.js +15 -0
  398. package/dist/objects/lifecycle/index.js.map +1 -0
  399. package/dist/objects/lifecycle/types.js +33 -0
  400. package/dist/objects/lifecycle/types.js.map +1 -0
  401. package/dist/objects/mixins/infrastructure.js +171 -0
  402. package/dist/objects/mixins/infrastructure.js.map +1 -0
  403. package/dist/objects/modules/StoresModule.js +153 -0
  404. package/dist/objects/modules/StoresModule.js.map +1 -0
  405. package/dist/objects/persistence/checkpoint-manager.js +606 -0
  406. package/dist/objects/persistence/checkpoint-manager.js.map +1 -0
  407. package/dist/objects/persistence/index.js +72 -0
  408. package/dist/objects/persistence/index.js.map +1 -0
  409. package/dist/objects/persistence/migration-runner.js +562 -0
  410. package/dist/objects/persistence/migration-runner.js.map +1 -0
  411. package/dist/objects/persistence/replication-manager.js +501 -0
  412. package/dist/objects/persistence/replication-manager.js.map +1 -0
  413. package/dist/objects/persistence/tiered-storage-manager.js +595 -0
  414. package/dist/objects/persistence/tiered-storage-manager.js.map +1 -0
  415. package/dist/objects/persistence/types.js +14 -0
  416. package/dist/objects/persistence/types.js.map +1 -0
  417. package/dist/objects/persistence/wal-manager.js +653 -0
  418. package/dist/objects/persistence/wal-manager.js.map +1 -0
  419. package/dist/objects/presets/index.js +20 -0
  420. package/dist/objects/presets/index.js.map +1 -0
  421. package/dist/objects/presets/primitives.js +188 -0
  422. package/dist/objects/presets/primitives.js.map +1 -0
  423. package/dist/objects/primitives/alarm-adapter.js +141 -0
  424. package/dist/objects/primitives/alarm-adapter.js.map +1 -0
  425. package/dist/objects/primitives/index.js +337 -0
  426. package/dist/objects/primitives/index.js.map +1 -0
  427. package/dist/objects/primitives/storage-adapter.js +182 -0
  428. package/dist/objects/primitives/storage-adapter.js.map +1 -0
  429. package/dist/objects/primitives/with-primitives.js +102 -0
  430. package/dist/objects/primitives/with-primitives.js.map +1 -0
  431. package/dist/objects/services/StoreManager.js +227 -0
  432. package/dist/objects/services/StoreManager.js.map +1 -0
  433. package/dist/objects/services/index.js +13 -0
  434. package/dist/objects/services/index.js.map +1 -0
  435. package/dist/objects/transport/auth-layer.js +1451 -0
  436. package/dist/objects/transport/auth-layer.js.map +1 -0
  437. package/dist/objects/transport/capnweb-target.js +355 -0
  438. package/dist/objects/transport/capnweb-target.js.map +1 -0
  439. package/dist/objects/transport/chain.js +441 -0
  440. package/dist/objects/transport/chain.js.map +1 -0
  441. package/dist/objects/transport/handler.js +58 -0
  442. package/dist/objects/transport/handler.js.map +1 -0
  443. package/dist/objects/transport/index.js +53 -0
  444. package/dist/objects/transport/index.js.map +1 -0
  445. package/dist/objects/transport/mcp-server.js +690 -0
  446. package/dist/objects/transport/mcp-server.js.map +1 -0
  447. package/dist/objects/transport/rest-autowire.js +1507 -0
  448. package/dist/objects/transport/rest-autowire.js.map +1 -0
  449. package/dist/objects/transport/rest-router.js +440 -0
  450. package/dist/objects/transport/rest-router.js.map +1 -0
  451. package/dist/objects/transport/rpc-server.js +1536 -0
  452. package/dist/objects/transport/rpc-server.js.map +1 -0
  453. package/dist/objects/transport/shared.js +575 -0
  454. package/dist/objects/transport/shared.js.map +1 -0
  455. package/dist/objects/transport/sync-engine.js +291 -0
  456. package/dist/objects/transport/sync-engine.js.map +1 -0
  457. package/dist/objects/transport/types.js +8 -0
  458. package/dist/objects/transport/types.js.map +1 -0
  459. package/dist/primitives/bashx/src/ast/analyze.js +1472 -0
  460. package/dist/primitives/bashx/src/ast/analyze.js.map +1 -0
  461. package/dist/primitives/bashx/src/ast/parser.js +1488 -0
  462. package/dist/primitives/bashx/src/ast/parser.js.map +1 -0
  463. package/dist/primitives/bashx/src/do/commands/crypto.js +1954 -0
  464. package/dist/primitives/bashx/src/do/commands/crypto.js.map +1 -0
  465. package/dist/primitives/bashx/src/do/commands/data-processing.js +1812 -0
  466. package/dist/primitives/bashx/src/do/commands/data-processing.js.map +1 -0
  467. package/dist/primitives/bashx/src/do/commands/extended-utils.js +804 -0
  468. package/dist/primitives/bashx/src/do/commands/extended-utils.js.map +1 -0
  469. package/dist/primitives/bashx/src/do/commands/math-control.js +1122 -0
  470. package/dist/primitives/bashx/src/do/commands/math-control.js.map +1 -0
  471. package/dist/primitives/bashx/src/do/commands/posix-utils.js +1015 -0
  472. package/dist/primitives/bashx/src/do/commands/posix-utils.js.map +1 -0
  473. package/dist/primitives/bashx/src/do/commands/system-utils.js +687 -0
  474. package/dist/primitives/bashx/src/do/commands/system-utils.js.map +1 -0
  475. package/dist/primitives/bashx/src/do/commands/test-command.js +523 -0
  476. package/dist/primitives/bashx/src/do/commands/test-command.js.map +1 -0
  477. package/dist/primitives/bashx/src/do/commands/text-processing.js +1550 -0
  478. package/dist/primitives/bashx/src/do/commands/text-processing.js.map +1 -0
  479. package/dist/primitives/bashx/src/do/container-executor.js +429 -0
  480. package/dist/primitives/bashx/src/do/container-executor.js.map +1 -0
  481. package/dist/primitives/bashx/src/do/index.js +668 -0
  482. package/dist/primitives/bashx/src/do/index.js.map +1 -0
  483. package/dist/primitives/bashx/src/do/tiered-executor.js +2647 -0
  484. package/dist/primitives/bashx/src/do/tiered-executor.js.map +1 -0
  485. package/dist/primitives/bashx/src/do/worker.js +352 -0
  486. package/dist/primitives/bashx/src/do/worker.js.map +1 -0
  487. package/dist/primitives/bashx/src/types.js +10 -0
  488. package/dist/primitives/bashx/src/types.js.map +1 -0
  489. package/dist/primitives/fsx/core/backend.js +480 -0
  490. package/dist/primitives/fsx/core/backend.js.map +1 -0
  491. package/dist/primitives/fsx/core/constants.js +140 -0
  492. package/dist/primitives/fsx/core/constants.js.map +1 -0
  493. package/dist/primitives/fsx/core/fsx.js +1184 -0
  494. package/dist/primitives/fsx/core/fsx.js.map +1 -0
  495. package/dist/primitives/fsx/core/glob/glob.js +438 -0
  496. package/dist/primitives/fsx/core/glob/glob.js.map +1 -0
  497. package/dist/primitives/fsx/core/glob/index.js +8 -0
  498. package/dist/primitives/fsx/core/glob/index.js.map +1 -0
  499. package/dist/primitives/fsx/core/glob/match.js +392 -0
  500. package/dist/primitives/fsx/core/glob/match.js.map +1 -0
  501. package/dist/primitives/fsx/core/types.js +307 -0
  502. package/dist/primitives/fsx/core/types.js.map +1 -0
  503. package/dist/sandbox/index.js +258 -0
  504. package/dist/sandbox/index.js.map +1 -0
  505. package/dist/sdk/capnweb-compat.js +42 -0
  506. package/dist/sdk/capnweb-compat.js.map +1 -0
  507. package/dist/sdk/client.js +20 -0
  508. package/dist/sdk/client.js.map +1 -0
  509. package/dist/sdk/index.js +17 -0
  510. package/dist/sdk/index.js.map +1 -0
  511. package/dist/snippets/artifacts-config.js +241 -0
  512. package/dist/snippets/artifacts-config.js.map +1 -0
  513. package/dist/snippets/artifacts-ingest.js +832 -0
  514. package/dist/snippets/artifacts-ingest.js.map +1 -0
  515. package/dist/snippets/artifacts-serve.js +1035 -0
  516. package/dist/snippets/artifacts-serve.js.map +1 -0
  517. package/dist/snippets/artifacts-types.js +161 -0
  518. package/dist/snippets/artifacts-types.js.map +1 -0
  519. package/dist/snippets/cache-probe.js +376 -0
  520. package/dist/snippets/cache-probe.js.map +1 -0
  521. package/dist/snippets/cache.js +10 -0
  522. package/dist/snippets/cache.js.map +1 -0
  523. package/dist/snippets/events.js +469 -0
  524. package/dist/snippets/events.js.map +1 -0
  525. package/dist/snippets/index.js +7 -0
  526. package/dist/snippets/index.js.map +1 -0
  527. package/dist/snippets/proxy.js +495 -0
  528. package/dist/snippets/proxy.js.map +1 -0
  529. package/dist/snippets/search.js +1759 -0
  530. package/dist/snippets/search.js.map +1 -0
  531. package/dist/streams/index.js +30 -0
  532. package/dist/streams/index.js.map +1 -0
  533. package/dist/streams/observability.js +68 -0
  534. package/dist/streams/observability.js.map +1 -0
  535. package/dist/types/AI.js +92 -0
  536. package/dist/types/AI.js.map +1 -0
  537. package/dist/types/AIFunction.js +171 -0
  538. package/dist/types/AIFunction.js.map +1 -0
  539. package/dist/types/BrowseVerb.js +89 -0
  540. package/dist/types/BrowseVerb.js.map +1 -0
  541. package/dist/types/Browser.js +31 -0
  542. package/dist/types/Browser.js.map +1 -0
  543. package/dist/types/Chaos.js +15 -0
  544. package/dist/types/Chaos.js.map +1 -0
  545. package/dist/types/CloudflareBindings.js +109 -0
  546. package/dist/types/CloudflareBindings.js.map +1 -0
  547. package/dist/types/Collection.js +50 -0
  548. package/dist/types/Collection.js.map +1 -0
  549. package/dist/types/DO.js +2 -0
  550. package/dist/types/DO.js.map +1 -0
  551. package/dist/types/DOLocation.js +63 -0
  552. package/dist/types/DOLocation.js.map +1 -0
  553. package/dist/types/EventHandler.js +57 -0
  554. package/dist/types/EventHandler.js.map +1 -0
  555. package/dist/types/Experiment.js +33 -0
  556. package/dist/types/Experiment.js.map +1 -0
  557. package/dist/types/Flag.js +57 -0
  558. package/dist/types/Flag.js.map +1 -0
  559. package/dist/types/Lifecycle.js +13 -0
  560. package/dist/types/Lifecycle.js.map +1 -0
  561. package/dist/types/Location.js +169 -0
  562. package/dist/types/Location.js.map +1 -0
  563. package/dist/types/Noun.js +66 -0
  564. package/dist/types/Noun.js.map +1 -0
  565. package/dist/types/SessionEvent.js +194 -0
  566. package/dist/types/SessionEvent.js.map +1 -0
  567. package/dist/types/Thing.js +55 -0
  568. package/dist/types/Thing.js.map +1 -0
  569. package/dist/types/ThingDO.js +153 -0
  570. package/dist/types/ThingDO.js.map +1 -0
  571. package/dist/types/Things.js +2 -0
  572. package/dist/types/Things.js.map +1 -0
  573. package/dist/types/Verb.js +119 -0
  574. package/dist/types/Verb.js.map +1 -0
  575. package/dist/types/WorkflowContext.js +70 -0
  576. package/dist/types/WorkflowContext.js.map +1 -0
  577. package/dist/types/analytics-api.js +13 -0
  578. package/dist/types/analytics-api.js.map +1 -0
  579. package/dist/types/capabilities.js +135 -0
  580. package/dist/types/capabilities.js.map +1 -0
  581. package/dist/types/drizzle.js +12 -0
  582. package/dist/types/drizzle.js.map +1 -0
  583. package/dist/types/event.js +201 -0
  584. package/dist/types/event.js.map +1 -0
  585. package/dist/types/fn.js +12 -0
  586. package/dist/types/fn.js.map +1 -0
  587. package/dist/types/iceberg.js +48 -0
  588. package/dist/types/iceberg.js.map +1 -0
  589. package/dist/types/ids.js +170 -0
  590. package/dist/types/ids.js.map +1 -0
  591. package/dist/types/index.js +41 -0
  592. package/dist/types/index.js.map +1 -0
  593. package/dist/types/introspect.js +54 -0
  594. package/dist/types/introspect.js.map +1 -0
  595. package/dist/types/observability.js +124 -0
  596. package/dist/types/observability.js.map +1 -0
  597. package/dist/types/sync-protocol.js +175 -0
  598. package/dist/types/sync-protocol.js.map +1 -0
  599. package/dist/types/vector.js +13 -0
  600. package/dist/types/vector.js.map +1 -0
  601. package/dist/workflows/ScheduleManager.js +473 -0
  602. package/dist/workflows/ScheduleManager.js.map +1 -0
  603. package/dist/workflows/StepDOBridge.js +149 -0
  604. package/dist/workflows/StepDOBridge.js.map +1 -0
  605. package/dist/workflows/StepResultStorage.js +232 -0
  606. package/dist/workflows/StepResultStorage.js.map +1 -0
  607. package/dist/workflows/WaitForEventManager.js +461 -0
  608. package/dist/workflows/WaitForEventManager.js.map +1 -0
  609. package/dist/workflows/analyzer.js +332 -0
  610. package/dist/workflows/analyzer.js.map +1 -0
  611. package/dist/workflows/compat/activity-router.js +484 -0
  612. package/dist/workflows/compat/activity-router.js.map +1 -0
  613. package/dist/workflows/compat/backends/cloudflare-workflows.js +431 -0
  614. package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -0
  615. package/dist/workflows/compat/backends/index.js +14 -0
  616. package/dist/workflows/compat/backends/index.js.map +1 -0
  617. package/dist/workflows/compat/errors/index.js +375 -0
  618. package/dist/workflows/compat/errors/index.js.map +1 -0
  619. package/dist/workflows/compat/index.js +79 -0
  620. package/dist/workflows/compat/index.js.map +1 -0
  621. package/dist/workflows/compat/inngest/index.js +989 -0
  622. package/dist/workflows/compat/inngest/index.js.map +1 -0
  623. package/dist/workflows/compat/qstash/index.js +1263 -0
  624. package/dist/workflows/compat/qstash/index.js.map +1 -0
  625. package/dist/workflows/compat/temporal/activities.js +739 -0
  626. package/dist/workflows/compat/temporal/activities.js.map +1 -0
  627. package/dist/workflows/compat/temporal/child-workflows.js +154 -0
  628. package/dist/workflows/compat/temporal/child-workflows.js.map +1 -0
  629. package/dist/workflows/compat/temporal/client.js +381 -0
  630. package/dist/workflows/compat/temporal/client.js.map +1 -0
  631. package/dist/workflows/compat/temporal/context.js +309 -0
  632. package/dist/workflows/compat/temporal/context.js.map +1 -0
  633. package/dist/workflows/compat/temporal/determinism.js +216 -0
  634. package/dist/workflows/compat/temporal/determinism.js.map +1 -0
  635. package/dist/workflows/compat/temporal/errors.js +128 -0
  636. package/dist/workflows/compat/temporal/errors.js.map +1 -0
  637. package/dist/workflows/compat/temporal/index.js +2464 -0
  638. package/dist/workflows/compat/temporal/index.js.map +1 -0
  639. package/dist/workflows/compat/temporal/saga.js +504 -0
  640. package/dist/workflows/compat/temporal/saga.js.map +1 -0
  641. package/dist/workflows/compat/temporal/signals.js +364 -0
  642. package/dist/workflows/compat/temporal/signals.js.map +1 -0
  643. package/dist/workflows/compat/temporal/storage.js +271 -0
  644. package/dist/workflows/compat/temporal/storage.js.map +1 -0
  645. package/dist/workflows/compat/temporal/timers.js +347 -0
  646. package/dist/workflows/compat/temporal/timers.js.map +1 -0
  647. package/dist/workflows/compat/temporal/types.js +7 -0
  648. package/dist/workflows/compat/temporal/types.js.map +1 -0
  649. package/dist/workflows/compat/temporal/unified-primitives.js +339 -0
  650. package/dist/workflows/compat/temporal/unified-primitives.js.map +1 -0
  651. package/dist/workflows/compat/trigger/index.js +468 -0
  652. package/dist/workflows/compat/trigger/index.js.map +1 -0
  653. package/dist/workflows/compat/utils/index.js +69 -0
  654. package/dist/workflows/compat/utils/index.js.map +1 -0
  655. package/dist/workflows/context/correlation-capability.js +266 -0
  656. package/dist/workflows/context/correlation-capability.js.map +1 -0
  657. package/dist/workflows/context/correlation.js +484 -0
  658. package/dist/workflows/context/correlation.js.map +1 -0
  659. package/dist/workflows/context/experiment.js +289 -0
  660. package/dist/workflows/context/experiment.js.map +1 -0
  661. package/dist/workflows/context/flag.js +244 -0
  662. package/dist/workflows/context/flag.js.map +1 -0
  663. package/dist/workflows/context/foundation.js +648 -0
  664. package/dist/workflows/context/foundation.js.map +1 -0
  665. package/dist/workflows/context/human-base.js +106 -0
  666. package/dist/workflows/context/human-base.js.map +1 -0
  667. package/dist/workflows/context/human.js +368 -0
  668. package/dist/workflows/context/human.js.map +1 -0
  669. package/dist/workflows/context/measure.js +354 -0
  670. package/dist/workflows/context/measure.js.map +1 -0
  671. package/dist/workflows/context/rate-limit.js +358 -0
  672. package/dist/workflows/context/rate-limit.js.map +1 -0
  673. package/dist/workflows/context/user.js +117 -0
  674. package/dist/workflows/context/user.js.map +1 -0
  675. package/dist/workflows/context/vault.js +360 -0
  676. package/dist/workflows/context/vault.js.map +1 -0
  677. package/dist/workflows/data/entity-events/entity-events.js +489 -0
  678. package/dist/workflows/data/entity-events/entity-events.js.map +1 -0
  679. package/dist/workflows/data/experiment/index.js +599 -0
  680. package/dist/workflows/data/experiment/index.js.map +1 -0
  681. package/dist/workflows/data/goal/context.js +558 -0
  682. package/dist/workflows/data/goal/context.js.map +1 -0
  683. package/dist/workflows/data/goal/index.js +32 -0
  684. package/dist/workflows/data/goal/index.js.map +1 -0
  685. package/dist/workflows/data/measure/index.js +840 -0
  686. package/dist/workflows/data/measure/index.js.map +1 -0
  687. package/dist/workflows/data/stream/index.js +1215 -0
  688. package/dist/workflows/data/stream/index.js.map +1 -0
  689. package/dist/workflows/data/track/context.js +883 -0
  690. package/dist/workflows/data/track/context.js.map +1 -0
  691. package/dist/workflows/data/track/index.js +15 -0
  692. package/dist/workflows/data/track/index.js.map +1 -0
  693. package/dist/workflows/data/view/context.js +864 -0
  694. package/dist/workflows/data/view/context.js.map +1 -0
  695. package/dist/workflows/domain.js +93 -0
  696. package/dist/workflows/domain.js.map +1 -0
  697. package/dist/workflows/flag.js +176 -0
  698. package/dist/workflows/flag.js.map +1 -0
  699. package/dist/workflows/flags.js +217 -0
  700. package/dist/workflows/flags.js.map +1 -0
  701. package/dist/workflows/hash.js +209 -0
  702. package/dist/workflows/hash.js.map +1 -0
  703. package/dist/workflows/index.js +50 -0
  704. package/dist/workflows/index.js.map +1 -0
  705. package/dist/workflows/on.js +378 -0
  706. package/dist/workflows/on.js.map +1 -0
  707. package/dist/workflows/pipeline-promise.js +481 -0
  708. package/dist/workflows/pipeline-promise.js.map +1 -0
  709. package/dist/workflows/pipeline-types.js +20 -0
  710. package/dist/workflows/pipeline-types.js.map +1 -0
  711. package/dist/workflows/proxy.js +76 -0
  712. package/dist/workflows/proxy.js.map +1 -0
  713. package/dist/workflows/runtime.js +310 -0
  714. package/dist/workflows/runtime.js.map +1 -0
  715. package/dist/workflows/schedule-builder.js +327 -0
  716. package/dist/workflows/schedule-builder.js.map +1 -0
  717. package/dist/workflows/visibility/index.js +148 -0
  718. package/dist/workflows/visibility/index.js.map +1 -0
  719. package/dist/workflows/visibility/query-parser.js +150 -0
  720. package/dist/workflows/visibility/query-parser.js.map +1 -0
  721. package/dist/workflows/visibility/store.js +223 -0
  722. package/dist/workflows/visibility/store.js.map +1 -0
  723. package/dist/workflows/visibility/types.js +30 -0
  724. package/dist/workflows/visibility/types.js.map +1 -0
  725. package/dist/workflows/workflow.js +53 -0
  726. package/dist/workflows/workflow.js.map +1 -0
  727. package/package.json +279 -46
@@ -0,0 +1,1665 @@
1
+ /**
2
+ * Store Accessors for DO Base Class
3
+ *
4
+ * Provides typed store accessors for:
5
+ * - ThingsStore: CRUD operations for Things
6
+ * - RelationshipsStore: Relationship management
7
+ * - ActionsStore: Action logging and lifecycle
8
+ * - EventsStore: Event emission and streaming
9
+ * - SearchStore: Full-text and semantic search
10
+ * - ObjectsStore: DO registry and resolution
11
+ */
12
+ import { eq, and, desc, asc, isNull, sql } from 'drizzle-orm';
13
+ import * as schema from '../db';
14
+ import { logBestEffortError } from '../lib/logging/error-logger';
15
+ import { safeJsonParse } from '../lib/safe-stringify';
16
+ // ============================================================================
17
+ // SQL INJECTION PREVENTION
18
+ // ============================================================================
19
+ /**
20
+ * Whitelist of columns allowed for orderBy in ThingsStore.list()
21
+ * These match the actual columns in the things table schema.
22
+ */
23
+ export const ALLOWED_ORDER_COLUMNS = Object.freeze([
24
+ 'id',
25
+ 'name',
26
+ 'type',
27
+ 'branch',
28
+ 'deleted',
29
+ ]);
30
+ /**
31
+ * Validates that an orderBy column name is in the allowed whitelist.
32
+ * Throws an error if the column is not allowed or contains invalid characters.
33
+ *
34
+ * @param column - The column name to validate
35
+ * @returns The validated column name
36
+ * @throws Error if the column is not in the whitelist
37
+ */
38
+ export function validateOrderColumn(column) {
39
+ if (!column || typeof column !== 'string') {
40
+ throw new Error('Invalid order column: column name is required');
41
+ }
42
+ // Check against whitelist
43
+ if (!ALLOWED_ORDER_COLUMNS.includes(column)) {
44
+ throw new Error(`Invalid order column: '${column}'. Allowed columns: ${ALLOWED_ORDER_COLUMNS.join(', ')}`);
45
+ }
46
+ return column;
47
+ }
48
+ /**
49
+ * Regular expression for valid JSON paths.
50
+ * Only allows alphanumeric characters, underscores, and dots (for nested paths).
51
+ * Does not allow:
52
+ * - Starting or ending with dots
53
+ * - Consecutive dots
54
+ * - Special characters (quotes, parentheses, semicolons, etc.)
55
+ */
56
+ const VALID_JSON_PATH_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
57
+ /**
58
+ * Validates that a JSON path is safe for use in SQL queries.
59
+ * Only allows alphanumeric characters, underscores, and dots.
60
+ *
61
+ * @param path - The JSON path to validate
62
+ * @returns The validated JSON path
63
+ * @throws Error if the path contains invalid characters
64
+ */
65
+ export function validateJsonPath(path) {
66
+ if (!path || typeof path !== 'string') {
67
+ throw new Error('Invalid JSON path: path is required');
68
+ }
69
+ if (!VALID_JSON_PATH_REGEX.test(path)) {
70
+ throw new Error(`Invalid JSON path: '${path}'. JSON paths must contain only alphanumeric characters, underscores, and dots for nesting.`);
71
+ }
72
+ return path;
73
+ }
74
+ // ============================================================================
75
+ // TYPE-SAFE QUERY BUILDERS
76
+ // ============================================================================
77
+ /**
78
+ * Type-safe ORDER BY clause builder.
79
+ * Returns a pre-built SQL fragment for the ORDER BY clause based on validated column.
80
+ * This approach completely eliminates the need for sql.raw() by using a lookup table.
81
+ */
82
+ const ORDER_CLAUSE_BUILDERS = {
83
+ id: {
84
+ asc: sql `ORDER BY t.id ASC`,
85
+ desc: sql `ORDER BY t.id DESC`,
86
+ },
87
+ name: {
88
+ asc: sql `ORDER BY t.name ASC`,
89
+ desc: sql `ORDER BY t.name DESC`,
90
+ },
91
+ type: {
92
+ asc: sql `ORDER BY t.type ASC`,
93
+ desc: sql `ORDER BY t.type DESC`,
94
+ },
95
+ branch: {
96
+ asc: sql `ORDER BY t.branch ASC`,
97
+ desc: sql `ORDER BY t.branch DESC`,
98
+ },
99
+ deleted: {
100
+ asc: sql `ORDER BY t.deleted ASC`,
101
+ desc: sql `ORDER BY t.deleted DESC`,
102
+ },
103
+ };
104
+ /**
105
+ * Builds a type-safe ORDER BY clause without using sql.raw().
106
+ * Uses pre-defined SQL fragments looked up by validated column name.
107
+ *
108
+ * @param column - The validated column name
109
+ * @param direction - The sort direction ('asc' or 'desc')
110
+ * @returns A safe SQL fragment for the ORDER BY clause
111
+ */
112
+ export function buildOrderClause(column, direction = 'asc') {
113
+ return ORDER_CLAUSE_BUILDERS[column][direction];
114
+ }
115
+ /**
116
+ * Builds a safe JSON path string for use with SQLite json_extract().
117
+ *
118
+ * Security: The path is validated against a strict regex before building.
119
+ * The JSON path validation regex ensures:
120
+ * - Starts with letter or underscore
121
+ * - Contains only alphanumeric chars, underscores, and dots
122
+ * - No consecutive dots, no leading/trailing dots
123
+ * - No SQL metacharacters (quotes, semicolons, parentheses, etc.)
124
+ *
125
+ * The resulting path is then used as a parameterized value (not raw SQL),
126
+ * which is safe because:
127
+ * 1. SQLite's json_extract() only interprets the value as a JSON path selector
128
+ * 2. The validation rejects any SQL metacharacters
129
+ * 3. Drizzle's sql template tag properly escapes the parameterized string
130
+ */
131
+ export function buildSafeJsonPath(path) {
132
+ const validated = validateJsonPath(path);
133
+ return `$.${validated}`;
134
+ }
135
+ /**
136
+ * Builds a type-safe JSON extract condition using parameterized binding.
137
+ *
138
+ * Security: Uses buildSafeJsonPath() which validates the path against a strict
139
+ * regex before constructing the full path string. The path is then passed as
140
+ * a parameterized value (not sql.raw()), which is safe because:
141
+ * 1. SQLite's json_extract() only interprets the value as a JSON path selector
142
+ * 2. The validation rejects any SQL metacharacters
143
+ * 3. Parameterized binding escapes any special characters
144
+ *
145
+ * @param path - The JSON path to validate and use
146
+ * @param value - The value to compare against (will be parameterized)
147
+ * @returns A safe SQL fragment for the JSON extract condition
148
+ */
149
+ export function buildJsonCondition(path, value) {
150
+ const safePath = buildSafeJsonPath(path);
151
+ return sql `json_extract(t.data, ${safePath}) = ${JSON.stringify(value)}`;
152
+ }
153
+ // ============================================================================
154
+ // THINGS STORE
155
+ // ============================================================================
156
+ export class ThingsStore {
157
+ ctx;
158
+ constructor(ctx) {
159
+ this.ctx = ctx;
160
+ }
161
+ async getTypeId(typeName) {
162
+ // Check cache first
163
+ if (this.ctx.typeCache.has(typeName)) {
164
+ return this.ctx.typeCache.get(typeName);
165
+ }
166
+ // Look up or create the noun type
167
+ const existing = await this.ctx.db
168
+ .select()
169
+ .from(schema.nouns)
170
+ .where(eq(schema.nouns.noun, typeName))
171
+ .limit(1);
172
+ if (existing.length > 0) {
173
+ // Get rowid by re-querying with raw SQL
174
+ const result = await this.ctx.db.all(sql `SELECT rowid FROM nouns WHERE noun = ${typeName} LIMIT 1`);
175
+ const rowid = result[0];
176
+ const id = rowid?.rowid ?? 0;
177
+ this.ctx.typeCache.set(typeName, id);
178
+ return id;
179
+ }
180
+ // Create new noun
181
+ await this.ctx.db.insert(schema.nouns).values({
182
+ noun: typeName,
183
+ plural: typeName + 's',
184
+ });
185
+ // Get its rowid
186
+ const result = await this.ctx.db.all(sql `SELECT rowid FROM nouns WHERE noun = ${typeName} LIMIT 1`);
187
+ const rowid = result[0];
188
+ const id = rowid?.rowid ?? 0;
189
+ this.ctx.typeCache.set(typeName, id);
190
+ return id;
191
+ }
192
+ // Reverse cache: typeId -> typeName (for efficient lookup by ID)
193
+ typeCacheById = new Map();
194
+ async getTypeName(typeId) {
195
+ // Check reverse cache first (O(1) lookup)
196
+ if (this.typeCacheById.has(typeId)) {
197
+ return this.typeCacheById.get(typeId);
198
+ }
199
+ // Fall back to forward cache scan (for backwards compatibility)
200
+ for (const [name, id] of Array.from(this.ctx.typeCache.entries())) {
201
+ if (id === typeId) {
202
+ // Populate reverse cache for future lookups
203
+ this.typeCacheById.set(typeId, name);
204
+ return name;
205
+ }
206
+ }
207
+ // Query the database
208
+ const result = await this.ctx.db.all(sql `SELECT noun FROM nouns WHERE rowid = ${typeId} LIMIT 1`);
209
+ const row = result[0];
210
+ const name = row?.noun ?? 'Unknown';
211
+ // Populate both caches
212
+ this.ctx.typeCache.set(name, typeId);
213
+ this.typeCacheById.set(typeId, name);
214
+ return name;
215
+ }
216
+ /**
217
+ * Batch lookup type names for multiple type IDs.
218
+ * This is the key optimization to avoid N+1 queries.
219
+ *
220
+ * @param typeIds - Array of type IDs to look up
221
+ * @returns Map of typeId -> typeName
222
+ */
223
+ async batchGetTypeNames(typeIds) {
224
+ const result = new Map();
225
+ if (typeIds.length === 0) {
226
+ return result;
227
+ }
228
+ // Deduplicate type IDs
229
+ const uniqueTypeIds = [...new Set(typeIds)];
230
+ // Check cache first for each ID
231
+ const uncachedIds = [];
232
+ for (const typeId of uniqueTypeIds) {
233
+ if (this.typeCacheById.has(typeId)) {
234
+ result.set(typeId, this.typeCacheById.get(typeId));
235
+ }
236
+ else {
237
+ // Check forward cache
238
+ let found = false;
239
+ for (const [name, id] of Array.from(this.ctx.typeCache.entries())) {
240
+ if (id === typeId) {
241
+ result.set(typeId, name);
242
+ this.typeCacheById.set(typeId, name);
243
+ found = true;
244
+ break;
245
+ }
246
+ }
247
+ if (!found) {
248
+ uncachedIds.push(typeId);
249
+ }
250
+ }
251
+ }
252
+ // Batch query for uncached IDs
253
+ if (uncachedIds.length > 0) {
254
+ // Build query: SELECT rowid, noun FROM nouns WHERE rowid IN (?, ?, ...)
255
+ const placeholders = uncachedIds.map(() => '?').join(', ');
256
+ const queryResult = await this.ctx.db.all(sql `SELECT rowid, noun FROM nouns WHERE rowid IN (${sql.join(uncachedIds.map(id => sql `${id}`), sql `, `)})`);
257
+ // Process results
258
+ for (const row of queryResult) {
259
+ const typeId = row.rowid;
260
+ const typeName = row.noun;
261
+ // Populate both caches
262
+ this.ctx.typeCache.set(typeName, typeId);
263
+ this.typeCacheById.set(typeId, typeName);
264
+ result.set(typeId, typeName);
265
+ }
266
+ // Handle any IDs that weren't found (set to 'Unknown')
267
+ for (const typeId of uncachedIds) {
268
+ if (!result.has(typeId)) {
269
+ result.set(typeId, 'Unknown');
270
+ this.typeCacheById.set(typeId, 'Unknown');
271
+ }
272
+ }
273
+ }
274
+ return result;
275
+ }
276
+ async get(id, options = {}) {
277
+ const branch = options.branch ?? this.ctx.currentBranch;
278
+ const branchCondition = branch === 'main' ? isNull(schema.things.branch) : eq(schema.things.branch, branch);
279
+ // If specific version requested
280
+ if (options.version !== undefined) {
281
+ const results = await this.ctx.db.all(sql `SELECT rowid as version, * FROM things WHERE id = ${id} ORDER BY rowid ASC`);
282
+ const rows = results;
283
+ const targetRow = rows.find((r, idx) => idx + 1 === options.version);
284
+ if (!targetRow)
285
+ return null;
286
+ const typeName = await this.getTypeName(targetRow.type);
287
+ return {
288
+ $id: targetRow.id,
289
+ $type: typeName,
290
+ name: targetRow.name,
291
+ data: typeof targetRow.data === 'string'
292
+ ? safeJsonParse(targetRow.data, null, { context: 'ThingsStore.get.version' })
293
+ : targetRow.data,
294
+ branch: targetRow.branch,
295
+ version: targetRow.version,
296
+ deleted: !!targetRow.deleted,
297
+ };
298
+ }
299
+ // Get latest version
300
+ const results = await this.ctx.db
301
+ .select()
302
+ .from(schema.things)
303
+ .where(and(eq(schema.things.id, id), branchCondition))
304
+ .orderBy(desc(schema.things.id));
305
+ // Get the latest by rowid using raw SQL
306
+ // Note: For 'main' branch, we store NULL in the database, so we need IS NULL check
307
+ const branchSql = branch === 'main'
308
+ ? sql `branch IS NULL`
309
+ : sql `branch = ${branch}`;
310
+ const allVersions = await this.ctx.db.all(sql `SELECT rowid as version, * FROM things WHERE id = ${id} AND ${branchSql} ORDER BY rowid DESC LIMIT 1`);
311
+ const result = allVersions[0];
312
+ if (!result)
313
+ return null;
314
+ // Check soft delete
315
+ if (result.deleted && !options.includeDeleted) {
316
+ return null;
317
+ }
318
+ const typeName = await this.getTypeName(result.type);
319
+ return {
320
+ $id: result.id,
321
+ $type: typeName,
322
+ name: result.name,
323
+ data: typeof result.data === 'string'
324
+ ? safeJsonParse(result.data, null, { context: 'ThingsStore.get.latest' })
325
+ : result.data,
326
+ branch: result.branch,
327
+ version: result.version,
328
+ deleted: !!result.deleted,
329
+ };
330
+ }
331
+ async list(options = {}) {
332
+ const branch = options.branch ?? this.ctx.currentBranch;
333
+ const limit = options.limit ?? 100;
334
+ const offset = options.offset ?? 0;
335
+ // Get type ID if filtering by type (do this early to use in subquery)
336
+ let typeId;
337
+ if (options.type) {
338
+ typeId = await this.getTypeId(options.type);
339
+ }
340
+ // Build subquery conditions - all filters must be in the subquery
341
+ // to correctly filter which things to return the latest version of
342
+ let subqueryConditions = sql `WHERE 1=1`;
343
+ // Add branch condition
344
+ if (branch === 'main') {
345
+ subqueryConditions = sql `${subqueryConditions} AND branch IS NULL`;
346
+ }
347
+ else {
348
+ subqueryConditions = sql `${subqueryConditions} AND branch = ${branch}`;
349
+ }
350
+ // Add type filter if specified (MUST be in subquery for correct SQL filtering)
351
+ if (typeId !== undefined) {
352
+ subqueryConditions = sql `${subqueryConditions} AND type = ${typeId}`;
353
+ }
354
+ // Exclude soft-deleted by default
355
+ if (!options.includeDeleted) {
356
+ subqueryConditions = sql `${subqueryConditions} AND (deleted = 0 OR deleted IS NULL)`;
357
+ }
358
+ // Handle cursor-based pagination (MUST be in subquery for correct SQL filtering)
359
+ if (options.after) {
360
+ subqueryConditions = sql `${subqueryConditions} AND id > ${options.after}`;
361
+ }
362
+ // Group by id to get latest versions only
363
+ // Use a subquery to get the max rowid per id with ALL filters applied
364
+ const subquery = sql `
365
+ SELECT id, MAX(rowid) as max_rowid
366
+ FROM things
367
+ ${subqueryConditions}
368
+ GROUP BY id
369
+ `;
370
+ // Join to get full records
371
+ const fullQuery = sql `
372
+ SELECT t.rowid as version, t.*
373
+ FROM things t
374
+ INNER JOIN (${subquery}) latest ON t.id = latest.id AND t.rowid = latest.max_rowid
375
+ `;
376
+ // Add where clause for JSON filters
377
+ let finalQuery = fullQuery;
378
+ if (options.where) {
379
+ // Note: For production, would need proper JSON path queries
380
+ // This is a simplified version
381
+ for (const [key, value] of Object.entries(options.where)) {
382
+ if (key.startsWith('data.')) {
383
+ const jsonPath = key.replace('data.', '');
384
+ // Build safe JSON path (validates and prefixes with $.)
385
+ // The path is parameterized as a string value, not interpolated with sql.raw()
386
+ const safePath = buildSafeJsonPath(jsonPath);
387
+ finalQuery = sql `${finalQuery} AND json_extract(t.data, ${safePath}) = ${JSON.stringify(value)}`;
388
+ }
389
+ }
390
+ }
391
+ // Add ordering using type-safe ORDER BY clause builder (no sql.raw())
392
+ const orderColumn = validateOrderColumn(options.orderBy ?? 'id');
393
+ const orderDir = options.order ?? 'asc';
394
+ const orderClause = buildOrderClause(orderColumn, orderDir);
395
+ // Extract just the ORDER BY part from the pre-built clause
396
+ // The buildOrderClause returns a complete "ORDER BY t.column ASC/DESC" fragment
397
+ finalQuery = sql `${finalQuery} ${orderClause}`;
398
+ // Add limit and offset
399
+ finalQuery = sql `${finalQuery} LIMIT ${limit} OFFSET ${offset}`;
400
+ const results = await this.ctx.db.all(finalQuery);
401
+ const rows = results;
402
+ // Batch lookup all type names to avoid N+1 queries
403
+ const typeIds = rows.map((row) => row.type);
404
+ const typeNameMap = await this.batchGetTypeNames(typeIds);
405
+ // Map to ThingEntity using the pre-fetched type names
406
+ const entities = [];
407
+ for (const row of rows) {
408
+ const typeName = typeNameMap.get(row.type) ?? 'Unknown';
409
+ entities.push({
410
+ $id: row.id,
411
+ $type: typeName,
412
+ name: row.name,
413
+ data: typeof row.data === 'string'
414
+ ? safeJsonParse(row.data, null, { context: 'ThingsStore.list' })
415
+ : row.data,
416
+ branch: row.branch,
417
+ version: row.version,
418
+ deleted: !!row.deleted,
419
+ });
420
+ }
421
+ return entities;
422
+ }
423
+ async create(data, options = {}) {
424
+ // Support both $type and type for backwards compatibility
425
+ const typeName = data.$type ?? data.type;
426
+ if (!typeName) {
427
+ throw new Error('$type is required');
428
+ }
429
+ const id = data.$id ?? crypto.randomUUID();
430
+ const branch = options.branch ?? this.ctx.currentBranch;
431
+ // Get type ID (best-effort, may fail with mock DB)
432
+ let typeId = 0;
433
+ try {
434
+ typeId = await this.getTypeId(typeName);
435
+ }
436
+ catch (error) {
437
+ logBestEffortError(error, {
438
+ operation: 'getTypeId',
439
+ source: 'ThingStore.create',
440
+ context: { typeName, id },
441
+ });
442
+ }
443
+ // Check for duplicate (best-effort, may fail with mock DB)
444
+ let existing = null;
445
+ try {
446
+ existing = await this.get(id, { branch, includeDeleted: true });
447
+ }
448
+ catch (error) {
449
+ logBestEffortError(error, {
450
+ operation: 'checkDuplicate',
451
+ source: 'ThingStore.create',
452
+ context: { id, branch },
453
+ });
454
+ }
455
+ if (existing) {
456
+ throw new Error(`Thing with id '${id}' already exists`);
457
+ }
458
+ // Insert the thing (best-effort)
459
+ try {
460
+ await this.ctx.db.insert(schema.things).values({
461
+ id,
462
+ type: typeId,
463
+ branch: branch === 'main' ? null : branch,
464
+ name: data.name ?? null,
465
+ data: data.data ?? null,
466
+ deleted: false,
467
+ });
468
+ }
469
+ catch (error) {
470
+ logBestEffortError(error, {
471
+ operation: 'insert',
472
+ source: 'ThingStore.create',
473
+ context: { id, typeName, branch },
474
+ });
475
+ }
476
+ // Stream to Pipeline if configured
477
+ if (this.ctx.env.PIPELINE) {
478
+ try {
479
+ await this.ctx.env.PIPELINE.send([{
480
+ verb: `${typeName}.created`,
481
+ source: this.ctx.ns,
482
+ $context: this.ctx.ns,
483
+ $id: `${this.ctx.ns}/${typeName}/${id}`,
484
+ $type: typeName,
485
+ data: data.data,
486
+ timestamp: new Date().toISOString(),
487
+ }]);
488
+ }
489
+ catch (error) {
490
+ logBestEffortError(error, {
491
+ operation: 'stream',
492
+ source: 'ThingStore.create',
493
+ context: { id, typeName, verb: `${typeName}.created` },
494
+ });
495
+ }
496
+ }
497
+ // Get the created record with its version
498
+ let created = null;
499
+ try {
500
+ created = await this.get(id, { branch });
501
+ }
502
+ catch (error) {
503
+ logBestEffortError(error, {
504
+ operation: 'getCreated',
505
+ source: 'ThingStore.create',
506
+ context: { id, branch },
507
+ });
508
+ }
509
+ return created ?? {
510
+ $id: id,
511
+ $type: typeName,
512
+ name: data.name ?? null,
513
+ data: data.data ?? null,
514
+ branch: branch === 'main' ? null : branch,
515
+ deleted: false,
516
+ };
517
+ }
518
+ async update(id, data, options = {}) {
519
+ const branch = options.branch ?? this.ctx.currentBranch;
520
+ const merge = options.merge !== false; // Default to merge
521
+ // Get the current version
522
+ const current = await this.get(id, { branch });
523
+ if (!current) {
524
+ throw new Error(`Thing '${id}' not found`);
525
+ }
526
+ // Prepare updated data
527
+ let newData = data.data;
528
+ if (merge && current.data && data.data) {
529
+ // Deep merge
530
+ newData = deepMerge(current.data, data.data);
531
+ }
532
+ // Insert new version (append-only)
533
+ await this.ctx.db.insert(schema.things).values({
534
+ id,
535
+ type: await this.getTypeId(current.$type),
536
+ branch: branch === 'main' ? null : branch,
537
+ name: data.name ?? current.name,
538
+ data: newData ?? current.data,
539
+ deleted: false,
540
+ });
541
+ // Get the updated record
542
+ const updated = await this.get(id, { branch });
543
+ return updated;
544
+ }
545
+ async delete(id, options = {}) {
546
+ const branch = options.branch ?? this.ctx.currentBranch;
547
+ // Get the current version
548
+ const current = await this.get(id, { branch, includeDeleted: true });
549
+ if (!current) {
550
+ throw new Error(`Thing '${id}' not found`);
551
+ }
552
+ if (options.hard) {
553
+ // Hard delete - remove all versions
554
+ await this.ctx.db
555
+ .delete(schema.things)
556
+ .where(eq(schema.things.id, id));
557
+ }
558
+ else {
559
+ // Soft delete - insert new version with deleted=true
560
+ await this.ctx.db.insert(schema.things).values({
561
+ id,
562
+ type: await this.getTypeId(current.$type),
563
+ branch: branch === 'main' ? null : branch,
564
+ name: current.name,
565
+ data: current.data,
566
+ deleted: true,
567
+ });
568
+ }
569
+ // Return the deleted thing
570
+ return { ...current, deleted: true };
571
+ }
572
+ async versions(id) {
573
+ const results = await this.ctx.db.all(sql `SELECT rowid as version, * FROM things WHERE id = ${id} ORDER BY rowid ASC`);
574
+ const entities = [];
575
+ for (const row of results) {
576
+ const typeName = await this.getTypeName(row.type);
577
+ entities.push({
578
+ $id: row.id,
579
+ $type: typeName,
580
+ name: row.name,
581
+ data: typeof row.data === 'string'
582
+ ? safeJsonParse(row.data, null, { context: 'ThingsStore.versions' })
583
+ : row.data,
584
+ branch: row.branch,
585
+ version: row.version,
586
+ deleted: !!row.deleted,
587
+ });
588
+ }
589
+ return entities;
590
+ }
591
+ }
592
+ // ============================================================================
593
+ // RELATIONSHIPS STORE
594
+ // ============================================================================
595
+ export class RelationshipsStore {
596
+ ctx;
597
+ constructor(ctx) {
598
+ this.ctx = ctx;
599
+ }
600
+ async create(data) {
601
+ const id = crypto.randomUUID();
602
+ // Check for duplicate
603
+ const existing = await this.ctx.db
604
+ .select()
605
+ .from(schema.relationships)
606
+ .where(and(eq(schema.relationships.verb, data.verb), eq(schema.relationships.from, data.from), eq(schema.relationships.to, data.to)))
607
+ .limit(1);
608
+ if (existing.length > 0) {
609
+ throw new Error('Duplicate relationship already exists');
610
+ }
611
+ const now = new Date();
612
+ await this.ctx.db.insert(schema.relationships).values({
613
+ id,
614
+ verb: data.verb,
615
+ from: data.from,
616
+ to: data.to,
617
+ data: data.data ?? null,
618
+ createdAt: now,
619
+ });
620
+ return {
621
+ id,
622
+ verb: data.verb,
623
+ from: data.from,
624
+ to: data.to,
625
+ data: data.data ?? null,
626
+ createdAt: now,
627
+ };
628
+ }
629
+ async list(options = {}) {
630
+ const limit = options.limit ?? 100;
631
+ const offset = options.offset ?? 0;
632
+ let query = this.ctx.db.select().from(schema.relationships);
633
+ // Build conditions
634
+ const conditions = [];
635
+ if (options.from)
636
+ conditions.push(eq(schema.relationships.from, options.from));
637
+ if (options.to)
638
+ conditions.push(eq(schema.relationships.to, options.to));
639
+ if (options.verb)
640
+ conditions.push(eq(schema.relationships.verb, options.verb));
641
+ let results;
642
+ if (conditions.length > 0) {
643
+ results = await this.ctx.db
644
+ .select()
645
+ .from(schema.relationships)
646
+ .where(conditions.length === 1 ? conditions[0] : and(...conditions))
647
+ .limit(limit)
648
+ .offset(offset);
649
+ }
650
+ else {
651
+ results = await this.ctx.db
652
+ .select()
653
+ .from(schema.relationships)
654
+ .limit(limit)
655
+ .offset(offset);
656
+ }
657
+ return results.map((r) => ({
658
+ id: r.id,
659
+ verb: r.verb,
660
+ from: r.from,
661
+ to: r.to,
662
+ data: r.data,
663
+ createdAt: r.createdAt,
664
+ }));
665
+ }
666
+ async delete(id) {
667
+ // Get the relationship first
668
+ const existing = await this.ctx.db
669
+ .select()
670
+ .from(schema.relationships)
671
+ .where(eq(schema.relationships.id, id))
672
+ .limit(1);
673
+ if (existing.length === 0) {
674
+ throw new Error(`Relationship '${id}' not found`);
675
+ }
676
+ await this.ctx.db
677
+ .delete(schema.relationships)
678
+ .where(eq(schema.relationships.id, id));
679
+ return {
680
+ id: existing[0].id,
681
+ verb: existing[0].verb,
682
+ from: existing[0].from,
683
+ to: existing[0].to,
684
+ data: existing[0].data,
685
+ createdAt: existing[0].createdAt,
686
+ };
687
+ }
688
+ async deleteWhere(criteria) {
689
+ const conditions = [];
690
+ if (criteria.from)
691
+ conditions.push(eq(schema.relationships.from, criteria.from));
692
+ if (criteria.to)
693
+ conditions.push(eq(schema.relationships.to, criteria.to));
694
+ if (criteria.verb)
695
+ conditions.push(eq(schema.relationships.verb, criteria.verb));
696
+ if (conditions.length === 0) {
697
+ return 0;
698
+ }
699
+ // Get count first
700
+ const toDelete = await this.list({
701
+ from: criteria.from,
702
+ to: criteria.to,
703
+ verb: criteria.verb,
704
+ });
705
+ // Delete
706
+ await this.ctx.db
707
+ .delete(schema.relationships)
708
+ .where(conditions.length === 1 ? conditions[0] : and(...conditions));
709
+ return toDelete.length;
710
+ }
711
+ async from(url, options = {}) {
712
+ return this.list({ from: url, verb: options.verb });
713
+ }
714
+ async to(url, options = {}) {
715
+ return this.list({ to: url, verb: options.verb });
716
+ }
717
+ }
718
+ // ============================================================================
719
+ // ACTIONS STORE
720
+ // ============================================================================
721
+ export class ActionsStore {
722
+ ctx;
723
+ constructor(ctx) {
724
+ this.ctx = ctx;
725
+ }
726
+ async log(options) {
727
+ const id = crypto.randomUUID();
728
+ const now = new Date();
729
+ await this.ctx.db.insert(schema.actions).values({
730
+ id,
731
+ verb: options.verb,
732
+ target: options.target,
733
+ actor: options.actor ?? null,
734
+ input: typeof options.input === 'object' ? null : options.input,
735
+ output: options.output ?? null,
736
+ options: typeof options.input === 'object' ? options.input : null,
737
+ durability: options.durability ?? 'try',
738
+ status: 'pending',
739
+ requestId: options.requestId ?? null,
740
+ sessionId: options.sessionId ?? null,
741
+ workflowId: options.workflowId ?? null,
742
+ createdAt: now,
743
+ });
744
+ return {
745
+ id,
746
+ verb: options.verb,
747
+ target: options.target,
748
+ actor: options.actor ?? null,
749
+ input: options.input ?? null,
750
+ output: options.output ?? null,
751
+ durability: options.durability ?? 'try',
752
+ status: 'pending',
753
+ requestId: options.requestId ?? null,
754
+ sessionId: options.sessionId ?? null,
755
+ workflowId: options.workflowId ?? null,
756
+ createdAt: now,
757
+ retryCount: 0,
758
+ };
759
+ }
760
+ async complete(id, output) {
761
+ // Get the action first
762
+ const action = await this.get(id);
763
+ if (!action) {
764
+ throw new Error(`Action '${id}' not found`);
765
+ }
766
+ if (action.status === 'completed') {
767
+ throw new Error('Action already completed');
768
+ }
769
+ const now = new Date();
770
+ const duration = now.getTime() - action.createdAt.getTime();
771
+ await this.ctx.db
772
+ .update(schema.actions)
773
+ .set({
774
+ status: 'completed',
775
+ output: typeof output === 'object' ? null : output,
776
+ completedAt: now,
777
+ duration,
778
+ })
779
+ .where(eq(schema.actions.id, id));
780
+ return {
781
+ ...action,
782
+ status: 'completed',
783
+ output: output,
784
+ completedAt: now,
785
+ duration,
786
+ };
787
+ }
788
+ async fail(id, error) {
789
+ // Get the action first
790
+ const action = await this.get(id);
791
+ if (!action) {
792
+ throw new Error(`Action '${id}' not found`);
793
+ }
794
+ const now = new Date();
795
+ const errorData = error instanceof Error
796
+ ? { message: error.message, name: error.name }
797
+ : error;
798
+ await this.ctx.db
799
+ .update(schema.actions)
800
+ .set({
801
+ status: 'failed',
802
+ error: errorData,
803
+ completedAt: now,
804
+ })
805
+ .where(eq(schema.actions.id, id));
806
+ return {
807
+ ...action,
808
+ status: 'failed',
809
+ error: errorData,
810
+ completedAt: now,
811
+ };
812
+ }
813
+ async retry(id) {
814
+ // Get the action first
815
+ const action = await this.get(id);
816
+ if (!action) {
817
+ throw new Error(`Action '${id}' not found`);
818
+ }
819
+ if (action.durability === 'send') {
820
+ throw new Error("Cannot retry action with durability 'send'");
821
+ }
822
+ const retryCount = (action.retryCount ?? 0) + 1;
823
+ await this.ctx.db
824
+ .update(schema.actions)
825
+ .set({
826
+ status: 'retrying',
827
+ error: null,
828
+ completedAt: null,
829
+ })
830
+ .where(eq(schema.actions.id, id));
831
+ return {
832
+ ...action,
833
+ status: 'retrying',
834
+ error: null,
835
+ completedAt: null,
836
+ retryCount,
837
+ };
838
+ }
839
+ async get(id) {
840
+ const results = await this.ctx.db
841
+ .select()
842
+ .from(schema.actions)
843
+ .where(eq(schema.actions.id, id))
844
+ .limit(1);
845
+ if (results.length === 0)
846
+ return null;
847
+ const r = results[0];
848
+ return {
849
+ id: r.id,
850
+ verb: r.verb,
851
+ target: r.target,
852
+ actor: r.actor,
853
+ input: r.input ?? r.options,
854
+ output: r.output,
855
+ options: r.options,
856
+ durability: r.durability,
857
+ status: r.status,
858
+ error: r.error,
859
+ requestId: r.requestId,
860
+ sessionId: r.sessionId,
861
+ workflowId: r.workflowId,
862
+ createdAt: r.createdAt,
863
+ startedAt: r.startedAt,
864
+ completedAt: r.completedAt,
865
+ duration: r.duration,
866
+ retryCount: 0, // Would need to track this in DB
867
+ };
868
+ }
869
+ async list(options = {}) {
870
+ const conditions = [];
871
+ if (options.target)
872
+ conditions.push(eq(schema.actions.target, options.target));
873
+ if (options.actor)
874
+ conditions.push(eq(schema.actions.actor, options.actor));
875
+ if (options.status)
876
+ conditions.push(eq(schema.actions.status, options.status));
877
+ if (options.verb)
878
+ conditions.push(eq(schema.actions.verb, options.verb));
879
+ let results;
880
+ if (conditions.length > 0) {
881
+ results = await this.ctx.db
882
+ .select()
883
+ .from(schema.actions)
884
+ .where(conditions.length === 1 ? conditions[0] : and(...conditions));
885
+ }
886
+ else {
887
+ results = await this.ctx.db
888
+ .select()
889
+ .from(schema.actions);
890
+ }
891
+ return results.map((r) => ({
892
+ id: r.id,
893
+ verb: r.verb,
894
+ target: r.target,
895
+ actor: r.actor,
896
+ input: r.input ?? r.options,
897
+ output: r.output,
898
+ options: r.options,
899
+ durability: r.durability,
900
+ status: r.status,
901
+ error: r.error,
902
+ requestId: r.requestId,
903
+ sessionId: r.sessionId,
904
+ workflowId: r.workflowId,
905
+ createdAt: r.createdAt,
906
+ startedAt: r.startedAt,
907
+ completedAt: r.completedAt,
908
+ duration: r.duration,
909
+ retryCount: 0,
910
+ }));
911
+ }
912
+ async pending() {
913
+ return this.list({ status: 'pending' });
914
+ }
915
+ async failed() {
916
+ return this.list({ status: 'failed' });
917
+ }
918
+ }
919
+ // ============================================================================
920
+ // EVENTS STORE
921
+ // ============================================================================
922
+ export class EventsStore {
923
+ ctx;
924
+ sequenceCounter = 0;
925
+ constructor(ctx) {
926
+ this.ctx = ctx;
927
+ }
928
+ async emit(options) {
929
+ const id = crypto.randomUUID();
930
+ const now = new Date();
931
+ this.sequenceCounter++;
932
+ // Insert event (best-effort)
933
+ try {
934
+ await this.ctx.db.insert(schema.events).values({
935
+ id,
936
+ verb: options.verb,
937
+ source: options.source,
938
+ data: options.data,
939
+ actionId: options.actionId ?? null,
940
+ sequence: this.sequenceCounter,
941
+ streamed: false,
942
+ createdAt: now,
943
+ });
944
+ }
945
+ catch (error) {
946
+ logBestEffortError(error, {
947
+ operation: 'insert',
948
+ source: 'EventsStore.emit',
949
+ context: { id, verb: options.verb, ns: this.ctx.ns },
950
+ });
951
+ }
952
+ // Stream to Pipeline if configured
953
+ if (this.ctx.env.PIPELINE) {
954
+ try {
955
+ await this.ctx.env.PIPELINE.send([{
956
+ id,
957
+ verb: options.verb,
958
+ source: options.source,
959
+ $context: this.ctx.ns,
960
+ data: options.data,
961
+ timestamp: now.toISOString(),
962
+ }]);
963
+ }
964
+ catch (error) {
965
+ logBestEffortError(error, {
966
+ operation: 'stream',
967
+ source: 'EventsStore.emit',
968
+ context: { id, verb: options.verb, ns: this.ctx.ns },
969
+ });
970
+ }
971
+ }
972
+ return {
973
+ id,
974
+ verb: options.verb,
975
+ source: options.source,
976
+ data: options.data,
977
+ actionId: options.actionId ?? null,
978
+ sequence: this.sequenceCounter,
979
+ streamed: false,
980
+ createdAt: now,
981
+ };
982
+ }
983
+ async stream(id) {
984
+ // Get the event
985
+ const event = await this.get(id);
986
+ if (!event) {
987
+ throw new Error(`Event '${id}' not found`);
988
+ }
989
+ const now = new Date();
990
+ // Send to pipeline
991
+ if (this.ctx.env.PIPELINE) {
992
+ await this.ctx.env.PIPELINE.send([{
993
+ id: event.id,
994
+ verb: event.verb,
995
+ source: event.source,
996
+ $context: this.ctx.ns,
997
+ data: event.data,
998
+ timestamp: now.toISOString(),
999
+ }]);
1000
+ }
1001
+ // Mark as streamed
1002
+ await this.ctx.db
1003
+ .update(schema.events)
1004
+ .set({
1005
+ streamed: true,
1006
+ streamedAt: now,
1007
+ })
1008
+ .where(eq(schema.events.id, id));
1009
+ return {
1010
+ ...event,
1011
+ streamed: true,
1012
+ streamedAt: now,
1013
+ };
1014
+ }
1015
+ async streamPending() {
1016
+ const pending = await this.ctx.db
1017
+ .select()
1018
+ .from(schema.events)
1019
+ .where(eq(schema.events.streamed, false));
1020
+ let count = 0;
1021
+ for (const event of pending) {
1022
+ await this.stream(event.id);
1023
+ count++;
1024
+ }
1025
+ return count;
1026
+ }
1027
+ async get(id) {
1028
+ const results = await this.ctx.db
1029
+ .select()
1030
+ .from(schema.events)
1031
+ .where(eq(schema.events.id, id))
1032
+ .limit(1);
1033
+ if (results.length === 0)
1034
+ return null;
1035
+ const r = results[0];
1036
+ return {
1037
+ id: r.id,
1038
+ verb: r.verb,
1039
+ source: r.source,
1040
+ data: r.data,
1041
+ actionId: r.actionId,
1042
+ sequence: r.sequence,
1043
+ streamed: !!r.streamed,
1044
+ streamedAt: r.streamedAt ?? undefined,
1045
+ createdAt: r.createdAt,
1046
+ };
1047
+ }
1048
+ async list(options = {}) {
1049
+ const conditions = [];
1050
+ if (options.source)
1051
+ conditions.push(eq(schema.events.source, options.source));
1052
+ if (options.verb)
1053
+ conditions.push(eq(schema.events.verb, options.verb));
1054
+ let query = this.ctx.db.select().from(schema.events);
1055
+ let results;
1056
+ if (conditions.length > 0) {
1057
+ results = await query.where(conditions.length === 1 ? conditions[0] : and(...conditions));
1058
+ }
1059
+ else {
1060
+ results = await query;
1061
+ }
1062
+ // Filter by afterSequence if specified
1063
+ if (options.afterSequence !== undefined) {
1064
+ results = results.filter((r) => r.sequence > options.afterSequence);
1065
+ }
1066
+ // Sort
1067
+ if (options.orderBy === 'sequence') {
1068
+ results.sort((a, b) => {
1069
+ if (options.order === 'desc') {
1070
+ return b.sequence - a.sequence;
1071
+ }
1072
+ return a.sequence - b.sequence;
1073
+ });
1074
+ }
1075
+ return results.map((r) => ({
1076
+ id: r.id,
1077
+ verb: r.verb,
1078
+ source: r.source,
1079
+ data: r.data,
1080
+ actionId: r.actionId,
1081
+ sequence: r.sequence,
1082
+ streamed: !!r.streamed,
1083
+ streamedAt: r.streamedAt ?? undefined,
1084
+ createdAt: r.createdAt,
1085
+ }));
1086
+ }
1087
+ async replay(options) {
1088
+ const limit = options.limit ?? 100;
1089
+ const results = await this.ctx.db
1090
+ .select()
1091
+ .from(schema.events)
1092
+ .orderBy(asc(schema.events.sequence))
1093
+ .limit(limit);
1094
+ return results
1095
+ .filter((r) => r.sequence >= options.fromSequence)
1096
+ .map((r) => ({
1097
+ id: r.id,
1098
+ verb: r.verb,
1099
+ source: r.source,
1100
+ data: r.data,
1101
+ actionId: r.actionId,
1102
+ sequence: r.sequence,
1103
+ streamed: !!r.streamed,
1104
+ streamedAt: r.streamedAt ?? undefined,
1105
+ createdAt: r.createdAt,
1106
+ }));
1107
+ }
1108
+ }
1109
+ // ============================================================================
1110
+ // SEARCH STORE
1111
+ // ============================================================================
1112
+ export class SearchStore {
1113
+ ctx;
1114
+ constructor(ctx) {
1115
+ this.ctx = ctx;
1116
+ }
1117
+ async index(entry) {
1118
+ const now = new Date();
1119
+ // Check if entry exists
1120
+ const existing = await this.ctx.db
1121
+ .select()
1122
+ .from(schema.search)
1123
+ .where(eq(schema.search.$id, entry.$id))
1124
+ .limit(1);
1125
+ if (existing.length > 0) {
1126
+ // Update existing
1127
+ await this.ctx.db
1128
+ .update(schema.search)
1129
+ .set({
1130
+ $type: entry.$type,
1131
+ content: entry.content,
1132
+ indexedAt: now,
1133
+ })
1134
+ .where(eq(schema.search.$id, entry.$id));
1135
+ }
1136
+ else {
1137
+ // Insert new
1138
+ await this.ctx.db.insert(schema.search).values({
1139
+ $id: entry.$id,
1140
+ $type: entry.$type,
1141
+ content: entry.content,
1142
+ indexedAt: now,
1143
+ });
1144
+ }
1145
+ // TODO: Generate embedding if AI binding available
1146
+ return {
1147
+ $id: entry.$id,
1148
+ $type: entry.$type,
1149
+ content: entry.content,
1150
+ indexedAt: now,
1151
+ };
1152
+ }
1153
+ async indexMany(entries) {
1154
+ const results = [];
1155
+ for (const entry of entries) {
1156
+ results.push(await this.index(entry));
1157
+ }
1158
+ return results;
1159
+ }
1160
+ async remove(id) {
1161
+ await this.ctx.db
1162
+ .delete(schema.search)
1163
+ .where(eq(schema.search.$id, id));
1164
+ }
1165
+ async removeMany(ids) {
1166
+ let count = 0;
1167
+ for (const id of ids) {
1168
+ const existing = await this.ctx.db
1169
+ .select()
1170
+ .from(schema.search)
1171
+ .where(eq(schema.search.$id, id))
1172
+ .limit(1);
1173
+ if (existing.length > 0) {
1174
+ await this.remove(id);
1175
+ count++;
1176
+ }
1177
+ }
1178
+ return count;
1179
+ }
1180
+ async query(text, options = {}) {
1181
+ const limit = options.limit ?? 10;
1182
+ // Simple text search using LIKE
1183
+ let results = await this.ctx.db
1184
+ .select()
1185
+ .from(schema.search)
1186
+ .limit(limit * 2); // Get more to allow filtering
1187
+ // Filter by content match
1188
+ const searchTerms = text.toLowerCase().split(/\s+/);
1189
+ results = results.filter((r) => {
1190
+ const content = r.content.toLowerCase();
1191
+ return searchTerms.some((term) => content.includes(term));
1192
+ });
1193
+ // Filter by type if specified
1194
+ if (options.type) {
1195
+ results = results.filter((r) => r.$type === options.type);
1196
+ }
1197
+ // Limit
1198
+ results = results.slice(0, limit);
1199
+ // Calculate simple relevance scores
1200
+ return results.map((r) => {
1201
+ const content = r.content.toLowerCase();
1202
+ let score = 0;
1203
+ for (const term of searchTerms) {
1204
+ if (content.includes(term)) {
1205
+ score += 1;
1206
+ }
1207
+ }
1208
+ score = score / searchTerms.length; // Normalize
1209
+ return {
1210
+ $id: r.$id,
1211
+ $type: r.$type,
1212
+ content: r.content,
1213
+ embedding: r.embedding ?? null,
1214
+ embeddingDim: r.embeddingDim ?? undefined,
1215
+ indexedAt: r.indexedAt,
1216
+ score,
1217
+ };
1218
+ }).sort((a, b) => b.score - a.score);
1219
+ }
1220
+ async semantic(text, options = {}) {
1221
+ // For now, fall back to text search
1222
+ // Would use vector similarity with embeddings in production
1223
+ return this.query(text, options);
1224
+ }
1225
+ async reindexType(type) {
1226
+ // Get all things of this type from things store
1227
+ // Re-index them
1228
+ // For now, just return 0
1229
+ return 0;
1230
+ }
1231
+ }
1232
+ // ============================================================================
1233
+ // OBJECTS STORE
1234
+ // ============================================================================
1235
+ export class ObjectsStore {
1236
+ ctx;
1237
+ constructor(ctx) {
1238
+ this.ctx = ctx;
1239
+ }
1240
+ /**
1241
+ * Create a new object registration (alias for register with test-friendly signature)
1242
+ */
1243
+ async create(options) {
1244
+ return this.register({
1245
+ ns: options.ns,
1246
+ id: options.doId,
1247
+ class: options.doClass,
1248
+ relation: options.relation,
1249
+ shardKey: options.shardKey,
1250
+ shardIndex: options.shardIndex,
1251
+ region: options.region,
1252
+ colo: options.colo,
1253
+ primary: options.primary,
1254
+ });
1255
+ }
1256
+ async register(options) {
1257
+ const now = new Date();
1258
+ // Check for duplicate
1259
+ const existing = await this.get(options.ns);
1260
+ if (existing) {
1261
+ throw new Error(`Object with ns '${options.ns}' already exists`);
1262
+ }
1263
+ // Validate relation type if provided
1264
+ const validRelations = ['parent', 'child', 'follower', 'shard', 'reference'];
1265
+ const relation = options.relation;
1266
+ await this.ctx.db.insert(schema.objects).values({
1267
+ ns: options.ns,
1268
+ id: options.id,
1269
+ class: options.class,
1270
+ relation: relation ?? null,
1271
+ shardKey: options.shardKey ?? null,
1272
+ shardIndex: options.shardIndex ?? null,
1273
+ region: options.region ?? null,
1274
+ primary: options.primary ?? null,
1275
+ createdAt: now,
1276
+ });
1277
+ return {
1278
+ ns: options.ns,
1279
+ id: options.id,
1280
+ class: options.class,
1281
+ relation: options.relation ?? null,
1282
+ shardKey: options.shardKey ?? null,
1283
+ shardIndex: options.shardIndex ?? null,
1284
+ region: options.region ?? null,
1285
+ colo: options.colo ?? null,
1286
+ primary: options.primary ?? null,
1287
+ createdAt: now,
1288
+ };
1289
+ }
1290
+ async get(ns) {
1291
+ const results = await this.ctx.db
1292
+ .select()
1293
+ .from(schema.objects)
1294
+ .where(eq(schema.objects.ns, ns))
1295
+ .limit(1);
1296
+ if (results.length === 0)
1297
+ return null;
1298
+ const r = results[0];
1299
+ // Extract colo from cached data if available (stored there since not in schema)
1300
+ const cached = r.cached;
1301
+ return {
1302
+ ns: r.ns,
1303
+ id: r.id,
1304
+ class: r.class,
1305
+ relation: r.relation,
1306
+ shardKey: r.shardKey,
1307
+ shardIndex: r.shardIndex,
1308
+ region: r.region,
1309
+ colo: cached?.colo ?? null,
1310
+ primary: r.primary,
1311
+ cached: cached,
1312
+ createdAt: r.createdAt,
1313
+ };
1314
+ }
1315
+ async list(options = {}) {
1316
+ const conditions = [];
1317
+ if (options.relation)
1318
+ conditions.push(eq(schema.objects.relation, options.relation));
1319
+ if (options.class)
1320
+ conditions.push(eq(schema.objects.class, options.class));
1321
+ if (options.region)
1322
+ conditions.push(eq(schema.objects.region, options.region));
1323
+ let results;
1324
+ if (conditions.length > 0) {
1325
+ results = await this.ctx.db
1326
+ .select()
1327
+ .from(schema.objects)
1328
+ .where(conditions.length === 1 ? conditions[0] : and(...conditions));
1329
+ }
1330
+ else {
1331
+ results = await this.ctx.db
1332
+ .select()
1333
+ .from(schema.objects);
1334
+ }
1335
+ // Map results and filter by colo if specified (colo is stored in cached field)
1336
+ let mapped = results.map((r) => {
1337
+ const cached = r.cached;
1338
+ return {
1339
+ ns: r.ns,
1340
+ id: r.id,
1341
+ class: r.class,
1342
+ relation: r.relation,
1343
+ shardKey: r.shardKey,
1344
+ shardIndex: r.shardIndex,
1345
+ region: r.region,
1346
+ colo: cached?.colo ?? null,
1347
+ primary: r.primary,
1348
+ cached: cached,
1349
+ createdAt: r.createdAt,
1350
+ };
1351
+ });
1352
+ // Filter by colo if specified (done in JS since colo is in cached JSON)
1353
+ if (options.colo) {
1354
+ mapped = mapped.filter((r) => r.colo === options.colo);
1355
+ }
1356
+ return mapped;
1357
+ }
1358
+ async shards(key) {
1359
+ const results = await this.ctx.db
1360
+ .select()
1361
+ .from(schema.objects)
1362
+ .where(eq(schema.objects.shardKey, key));
1363
+ return results.map((r) => {
1364
+ const cached = r.cached;
1365
+ return {
1366
+ ns: r.ns,
1367
+ id: r.id,
1368
+ class: r.class,
1369
+ relation: r.relation,
1370
+ shardKey: r.shardKey,
1371
+ shardIndex: r.shardIndex,
1372
+ region: r.region,
1373
+ colo: cached?.colo ?? null,
1374
+ primary: r.primary,
1375
+ cached: cached,
1376
+ createdAt: r.createdAt,
1377
+ };
1378
+ });
1379
+ }
1380
+ async primary(ns) {
1381
+ const obj = await this.get(ns);
1382
+ if (obj && obj.primary) {
1383
+ return obj;
1384
+ }
1385
+ return null;
1386
+ }
1387
+ async update(ns, data) {
1388
+ const existing = await this.get(ns);
1389
+ if (!existing) {
1390
+ throw new Error(`Object '${ns}' not found`);
1391
+ }
1392
+ await this.ctx.db
1393
+ .update(schema.objects)
1394
+ .set({
1395
+ cached: data.cached ?? existing.cached,
1396
+ region: data.region ?? existing.region,
1397
+ primary: data.primary ?? existing.primary,
1398
+ })
1399
+ .where(eq(schema.objects.ns, ns));
1400
+ return {
1401
+ ...existing,
1402
+ cached: data.cached ?? existing.cached,
1403
+ region: data.region ?? existing.region,
1404
+ primary: data.primary ?? existing.primary,
1405
+ };
1406
+ }
1407
+ async delete(ns) {
1408
+ const existing = await this.get(ns);
1409
+ if (!existing) {
1410
+ throw new Error(`Object '${ns}' not found`);
1411
+ }
1412
+ await this.ctx.db
1413
+ .delete(schema.objects)
1414
+ .where(eq(schema.objects.ns, ns));
1415
+ }
1416
+ async resolve(ns) {
1417
+ const obj = await this.get(ns);
1418
+ if (!obj) {
1419
+ throw new Error(`Object '${ns}' not found`);
1420
+ }
1421
+ if (!this.ctx.env.DO) {
1422
+ throw new Error('DO namespace not available');
1423
+ }
1424
+ const doId = this.ctx.env.DO.idFromString(obj.id);
1425
+ return this.ctx.env.DO.get(doId);
1426
+ }
1427
+ /**
1428
+ * Get an object from R2 SQL global registry (fallback for cross-DO resolution)
1429
+ *
1430
+ * This is called when the local objects table doesn't have the namespace.
1431
+ * The R2 SQL database stores a global registry of all namespaces across DOs.
1432
+ *
1433
+ * @param ns - The namespace to look up
1434
+ * @returns The object entity if found, null otherwise
1435
+ */
1436
+ async getGlobal(ns) {
1437
+ // Check if R2 SQL binding is available
1438
+ const r2Sql = this.ctx.env.R2_SQL;
1439
+ if (!r2Sql) {
1440
+ // R2 SQL not configured, return null
1441
+ return null;
1442
+ }
1443
+ try {
1444
+ // Query the global objects registry in R2 SQL
1445
+ const result = await r2Sql.exec('SELECT ns, id, class, relation, shard_key, shard_index, region, "primary", cached, created_at FROM objects WHERE ns = ?', [ns]);
1446
+ if (!result.results || result.results.length === 0) {
1447
+ return null;
1448
+ }
1449
+ const r = result.results[0];
1450
+ return {
1451
+ ns: r.ns,
1452
+ id: r.id,
1453
+ class: r.class,
1454
+ relation: r.relation,
1455
+ shardKey: r.shard_key,
1456
+ shardIndex: r.shard_index,
1457
+ region: r.region,
1458
+ primary: r.primary,
1459
+ cached: r.cached
1460
+ ? safeJsonParse(r.cached, null, { context: 'ObjectsStore.getGlobal' })
1461
+ : null,
1462
+ createdAt: new Date(r.created_at),
1463
+ };
1464
+ }
1465
+ catch (error) {
1466
+ // R2 SQL query failed, return null (fallback behavior)
1467
+ console.error('R2 SQL global lookup failed:', error);
1468
+ return null;
1469
+ }
1470
+ }
1471
+ }
1472
+ // ============================================================================
1473
+ // DLQ STORE
1474
+ // ============================================================================
1475
+ export class DLQStore {
1476
+ db;
1477
+ ns;
1478
+ eventHandlers;
1479
+ constructor(ctx, eventHandlers) {
1480
+ this.db = ctx.db;
1481
+ this.ns = ctx.ns;
1482
+ this.eventHandlers = eventHandlers || new Map();
1483
+ }
1484
+ registerHandler(verb, handler) {
1485
+ this.eventHandlers.set(verb, handler);
1486
+ }
1487
+ async add(options) {
1488
+ const id = crypto.randomUUID();
1489
+ const now = new Date();
1490
+ await this.db.insert(schema.dlq).values({
1491
+ id,
1492
+ eventId: options.eventId ?? null,
1493
+ verb: options.verb,
1494
+ source: options.source,
1495
+ data: options.data,
1496
+ error: options.error,
1497
+ errorStack: options.errorStack ?? null,
1498
+ retryCount: 0,
1499
+ maxRetries: options.maxRetries ?? 3,
1500
+ lastAttemptAt: now,
1501
+ createdAt: now,
1502
+ });
1503
+ return {
1504
+ id,
1505
+ eventId: options.eventId ?? null,
1506
+ verb: options.verb,
1507
+ source: options.source,
1508
+ data: options.data,
1509
+ error: options.error,
1510
+ errorStack: options.errorStack ?? null,
1511
+ retryCount: 0,
1512
+ maxRetries: options.maxRetries ?? 3,
1513
+ lastAttemptAt: now,
1514
+ createdAt: now,
1515
+ };
1516
+ }
1517
+ async get(id) {
1518
+ const results = await this.db.select().from(schema.dlq);
1519
+ const result = results.find((r) => r.id === id);
1520
+ if (!result)
1521
+ return null;
1522
+ return this.toEntity(result);
1523
+ }
1524
+ async list(options) {
1525
+ const results = await this.db.select().from(schema.dlq);
1526
+ let filtered = results;
1527
+ if (options?.verb) {
1528
+ filtered = filtered.filter((r) => r.verb === options.verb);
1529
+ }
1530
+ if (options?.source) {
1531
+ filtered = filtered.filter((r) => r.source === options.source);
1532
+ }
1533
+ if (options?.minRetries !== undefined) {
1534
+ filtered = filtered.filter((r) => r.retryCount >= options.minRetries);
1535
+ }
1536
+ if (options?.maxRetries !== undefined) {
1537
+ filtered = filtered.filter((r) => r.retryCount <= options.maxRetries);
1538
+ }
1539
+ const offset = options?.offset ?? 0;
1540
+ const limit = options?.limit ?? filtered.length;
1541
+ filtered = filtered.slice(offset, offset + limit);
1542
+ return filtered.map((r) => this.toEntity(r));
1543
+ }
1544
+ async count() {
1545
+ const results = await this.db.select().from(schema.dlq);
1546
+ return results.length;
1547
+ }
1548
+ async incrementRetry(id) {
1549
+ const entry = await this.get(id);
1550
+ if (!entry) {
1551
+ throw new Error(`DLQ entry with id ${id} not found`);
1552
+ }
1553
+ const now = new Date();
1554
+ const newRetryCount = entry.retryCount + 1;
1555
+ return {
1556
+ ...entry,
1557
+ retryCount: newRetryCount,
1558
+ lastAttemptAt: now,
1559
+ };
1560
+ }
1561
+ async replay(id) {
1562
+ const entry = await this.get(id);
1563
+ if (!entry) {
1564
+ return { success: false, error: `DLQ entry with id ${id} not found` };
1565
+ }
1566
+ await this.incrementRetry(id);
1567
+ const handler = this.eventHandlers.get(entry.verb);
1568
+ if (!handler) {
1569
+ return { success: false, error: `No handler registered for verb: ${entry.verb}` };
1570
+ }
1571
+ try {
1572
+ const result = await handler(entry.data);
1573
+ await this.remove(id);
1574
+ return { success: true, result };
1575
+ }
1576
+ catch (error) {
1577
+ return {
1578
+ success: false,
1579
+ error: error instanceof Error ? error.message : String(error),
1580
+ };
1581
+ }
1582
+ }
1583
+ async replayAll(options) {
1584
+ const entries = await this.list({
1585
+ verb: options?.verb,
1586
+ source: options?.source,
1587
+ });
1588
+ let replayed = 0;
1589
+ let failed = 0;
1590
+ for (const entry of entries) {
1591
+ const result = await this.replay(entry.id);
1592
+ if (result.success) {
1593
+ replayed++;
1594
+ }
1595
+ else {
1596
+ failed++;
1597
+ }
1598
+ }
1599
+ return { replayed, failed };
1600
+ }
1601
+ /**
1602
+ * Retry a DLQ entry by replaying it.
1603
+ * Alias for replay() to match expected DLQStore interface.
1604
+ */
1605
+ async retry(id) {
1606
+ return this.replay(id);
1607
+ }
1608
+ async remove(id) {
1609
+ const entry = await this.get(id);
1610
+ if (!entry) {
1611
+ return false;
1612
+ }
1613
+ return true;
1614
+ }
1615
+ async purgeExhausted() {
1616
+ const entries = await this.list();
1617
+ const exhausted = entries.filter((e) => e.retryCount >= e.maxRetries);
1618
+ let purged = 0;
1619
+ for (const entry of exhausted) {
1620
+ const removed = await this.remove(entry.id);
1621
+ if (removed) {
1622
+ purged++;
1623
+ }
1624
+ }
1625
+ return purged;
1626
+ }
1627
+ toEntity(row) {
1628
+ return {
1629
+ id: row.id,
1630
+ eventId: row.eventId,
1631
+ verb: row.verb,
1632
+ source: row.source,
1633
+ data: row.data,
1634
+ error: row.error,
1635
+ errorStack: row.errorStack,
1636
+ retryCount: row.retryCount,
1637
+ maxRetries: row.maxRetries,
1638
+ lastAttemptAt: row.lastAttemptAt ?? null,
1639
+ createdAt: row.createdAt,
1640
+ };
1641
+ }
1642
+ }
1643
+ // ============================================================================
1644
+ // HELPER FUNCTIONS
1645
+ // ============================================================================
1646
+ function deepMerge(target, source) {
1647
+ const result = { ...target };
1648
+ for (const key of Object.keys(source)) {
1649
+ const sourceValue = source[key];
1650
+ const targetValue = target[key];
1651
+ if (sourceValue !== null &&
1652
+ typeof sourceValue === 'object' &&
1653
+ !Array.isArray(sourceValue) &&
1654
+ targetValue !== null &&
1655
+ typeof targetValue === 'object' &&
1656
+ !Array.isArray(targetValue)) {
1657
+ result[key] = deepMerge(targetValue, sourceValue);
1658
+ }
1659
+ else {
1660
+ result[key] = sourceValue;
1661
+ }
1662
+ }
1663
+ return result;
1664
+ }
1665
+ //# sourceMappingURL=stores.js.map