dotdo 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (667) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +446 -315
  3. package/cli/README.md +238 -0
  4. package/cli/agent.ts +72 -0
  5. package/cli/bin.js +44 -0
  6. package/cli/bin.ts +38 -0
  7. package/cli/build.ts +157 -0
  8. package/cli/commands/auth/login.ts +14 -0
  9. package/cli/commands/auth/logout.ts +6 -0
  10. package/cli/commands/auth/whoami.ts +16 -0
  11. package/cli/commands/deploy-multi.ts +245 -0
  12. package/cli/commands/dev/deploy.ts +100 -0
  13. package/cli/commands/dev/dev.ts +95 -0
  14. package/cli/commands/dev/logs.ts +91 -0
  15. package/cli/commands/dev-local.ts +88 -0
  16. package/cli/commands/do-ops.ts +314 -0
  17. package/cli/commands/index.ts +100 -0
  18. package/cli/commands/init.ts +247 -0
  19. package/cli/commands/introspect/emitter.ts +315 -0
  20. package/cli/commands/introspect/index.ts +193 -0
  21. package/cli/commands/link.ts +598 -0
  22. package/cli/commands/snippets.ts +415 -0
  23. package/cli/commands/tunnel.ts +239 -0
  24. package/cli/device-auth.ts +289 -0
  25. package/cli/fallback.ts +12 -0
  26. package/cli/index.ts +121 -0
  27. package/cli/main.ts +246 -0
  28. package/cli/mcp-stdio.ts +790 -0
  29. package/cli/package.json +62 -0
  30. package/cli/runtime/do-registry.ts +193 -0
  31. package/cli/runtime/embedded-db.ts +344 -0
  32. package/cli/runtime/index.ts +9 -0
  33. package/cli/runtime/miniflare-adapter.ts +162 -0
  34. package/cli/sandbox.ts +82 -0
  35. package/cli/src/args.ts +174 -0
  36. package/cli/src/auth.ts +55 -0
  37. package/cli/src/commands/call.ts +84 -0
  38. package/cli/src/commands/charge.ts +96 -0
  39. package/cli/src/commands/config.ts +115 -0
  40. package/cli/src/commands/email.ts +112 -0
  41. package/cli/src/commands/llm.ts +115 -0
  42. package/cli/src/commands/queue.ts +134 -0
  43. package/cli/src/commands/text.ts +86 -0
  44. package/cli/src/config.ts +185 -0
  45. package/cli/src/output.ts +246 -0
  46. package/cli/src/rpc.ts +192 -0
  47. package/cli/utils/config.ts +282 -0
  48. package/cli/utils/detect.ts +73 -0
  49. package/cli/utils/index.ts +15 -0
  50. package/cli/utils/logger.ts +232 -0
  51. package/dist/ai/index.js +19 -0
  52. package/dist/ai/index.js.map +1 -0
  53. package/dist/ai/template-literals.js +852 -0
  54. package/dist/ai/template-literals.js.map +1 -0
  55. package/dist/api/middleware/auth-federation.js +573 -0
  56. package/dist/api/middleware/auth-federation.js.map +1 -0
  57. package/dist/api/middleware/auth.js +545 -0
  58. package/dist/api/middleware/auth.js.map +1 -0
  59. package/dist/db/actions.js +212 -0
  60. package/dist/db/actions.js.map +1 -0
  61. package/dist/db/auth.js +506 -0
  62. package/dist/db/auth.js.map +1 -0
  63. package/dist/db/branches.js +65 -0
  64. package/dist/db/branches.js.map +1 -0
  65. package/dist/db/clickhouse.js +1074 -0
  66. package/dist/db/clickhouse.js.map +1 -0
  67. package/dist/db/dlq.js +39 -0
  68. package/dist/db/dlq.js.map +1 -0
  69. package/dist/db/events.js +28 -0
  70. package/dist/db/events.js.map +1 -0
  71. package/dist/db/exec.js +64 -0
  72. package/dist/db/exec.js.map +1 -0
  73. package/dist/db/files.js +85 -0
  74. package/dist/db/files.js.map +1 -0
  75. package/dist/db/flags.js +24 -0
  76. package/dist/db/flags.js.map +1 -0
  77. package/dist/db/git.js +116 -0
  78. package/dist/db/git.js.map +1 -0
  79. package/dist/db/iceberg/inverted-index.js +862 -0
  80. package/dist/db/iceberg/inverted-index.js.map +1 -0
  81. package/dist/db/iceberg/puffin.js +878 -0
  82. package/dist/db/iceberg/puffin.js.map +1 -0
  83. package/dist/db/iceberg/search-manifest.js +422 -0
  84. package/dist/db/iceberg/search-manifest.js.map +1 -0
  85. package/dist/db/iceberg/types.js +8 -0
  86. package/dist/db/iceberg/types.js.map +1 -0
  87. package/dist/db/index.js +121 -0
  88. package/dist/db/index.js.map +1 -0
  89. package/dist/db/integrations.js +368 -0
  90. package/dist/db/integrations.js.map +1 -0
  91. package/dist/db/json-indexes.js +332 -0
  92. package/dist/db/json-indexes.js.map +1 -0
  93. package/dist/db/linked-accounts.js +287 -0
  94. package/dist/db/linked-accounts.js.map +1 -0
  95. package/dist/db/nouns.js +183 -0
  96. package/dist/db/nouns.js.map +1 -0
  97. package/dist/db/objects.js +170 -0
  98. package/dist/db/objects.js.map +1 -0
  99. package/dist/db/primitives/dag-scheduler/index.js +869 -0
  100. package/dist/db/primitives/dag-scheduler/index.js.map +1 -0
  101. package/dist/db/primitives/exactly-once-context.js +237 -0
  102. package/dist/db/primitives/exactly-once-context.js.map +1 -0
  103. package/dist/db/primitives/index.js +62 -0
  104. package/dist/db/primitives/index.js.map +1 -0
  105. package/dist/db/primitives/keyed-router.js +145 -0
  106. package/dist/db/primitives/keyed-router.js.map +1 -0
  107. package/dist/db/primitives/observability.js +162 -0
  108. package/dist/db/primitives/observability.js.map +1 -0
  109. package/dist/db/primitives/schema-evolution.js +643 -0
  110. package/dist/db/primitives/schema-evolution.js.map +1 -0
  111. package/dist/db/primitives/stateful-operator/index.js +770 -0
  112. package/dist/db/primitives/stateful-operator/index.js.map +1 -0
  113. package/dist/db/primitives/temporal-store.js +306 -0
  114. package/dist/db/primitives/temporal-store.js.map +1 -0
  115. package/dist/db/primitives/typed-column-store.js +1229 -0
  116. package/dist/db/primitives/typed-column-store.js.map +1 -0
  117. package/dist/db/primitives/utils/duration.js +162 -0
  118. package/dist/db/primitives/utils/duration.js.map +1 -0
  119. package/dist/db/primitives/utils/murmur3.js +116 -0
  120. package/dist/db/primitives/utils/murmur3.js.map +1 -0
  121. package/dist/db/primitives/watermark-service.js +136 -0
  122. package/dist/db/primitives/watermark-service.js.map +1 -0
  123. package/dist/db/primitives/window-manager.js +764 -0
  124. package/dist/db/primitives/window-manager.js.map +1 -0
  125. package/dist/db/relationships.js +66 -0
  126. package/dist/db/relationships.js.map +1 -0
  127. package/dist/db/schema-minimal.js +61 -0
  128. package/dist/db/schema-minimal.js.map +1 -0
  129. package/dist/db/search.js +28 -0
  130. package/dist/db/search.js.map +1 -0
  131. package/dist/db/stores.js +1665 -0
  132. package/dist/db/stores.js.map +1 -0
  133. package/dist/db/things.js +297 -0
  134. package/dist/db/things.js.map +1 -0
  135. package/dist/db/vault.js +171 -0
  136. package/dist/db/vault.js.map +1 -0
  137. package/dist/db/verbs.js +102 -0
  138. package/dist/db/verbs.js.map +1 -0
  139. package/dist/do/base.js +48 -0
  140. package/dist/do/base.js.map +1 -0
  141. package/dist/do/tiny.js +31 -0
  142. package/dist/do/tiny.js.map +1 -0
  143. package/dist/lib/DOAuth.js +261 -0
  144. package/dist/lib/DOAuth.js.map +1 -0
  145. package/dist/lib/DODispatcher.js +72 -0
  146. package/dist/lib/DODispatcher.js.map +1 -0
  147. package/dist/lib/Modifier.js +189 -0
  148. package/dist/lib/Modifier.js.map +1 -0
  149. package/dist/lib/StateStorage.js +403 -0
  150. package/dist/lib/StateStorage.js.map +1 -0
  151. package/dist/lib/TypeRegistry.js +122 -0
  152. package/dist/lib/TypeRegistry.js.map +1 -0
  153. package/dist/lib/ai/gateway.js +247 -0
  154. package/dist/lib/ai/gateway.js.map +1 -0
  155. package/dist/lib/ai/tool-loop-agent.js +591 -0
  156. package/dist/lib/ai/tool-loop-agent.js.map +1 -0
  157. package/dist/lib/auto-wiring.js +439 -0
  158. package/dist/lib/auto-wiring.js.map +1 -0
  159. package/dist/lib/browse/browserbase.js +163 -0
  160. package/dist/lib/browse/browserbase.js.map +1 -0
  161. package/dist/lib/browse/cloudflare.js +144 -0
  162. package/dist/lib/browse/cloudflare.js.map +1 -0
  163. package/dist/lib/browse/index.js +62 -0
  164. package/dist/lib/browse/index.js.map +1 -0
  165. package/dist/lib/browse/types.js +13 -0
  166. package/dist/lib/browse/types.js.map +1 -0
  167. package/dist/lib/cache/index.js +37 -0
  168. package/dist/lib/cache/index.js.map +1 -0
  169. package/dist/lib/cache/visibility.js +638 -0
  170. package/dist/lib/cache/visibility.js.map +1 -0
  171. package/dist/lib/capabilities.js +268 -0
  172. package/dist/lib/capabilities.js.map +1 -0
  173. package/dist/lib/channels/base.js +106 -0
  174. package/dist/lib/channels/base.js.map +1 -0
  175. package/dist/lib/channels/discord.js +94 -0
  176. package/dist/lib/channels/discord.js.map +1 -0
  177. package/dist/lib/channels/email.js +204 -0
  178. package/dist/lib/channels/email.js.map +1 -0
  179. package/dist/lib/channels/index.js +90 -0
  180. package/dist/lib/channels/index.js.map +1 -0
  181. package/dist/lib/channels/mdxui-chat.js +95 -0
  182. package/dist/lib/channels/mdxui-chat.js.map +1 -0
  183. package/dist/lib/channels/slack-blockkit.js +121 -0
  184. package/dist/lib/channels/slack-blockkit.js.map +1 -0
  185. package/dist/lib/channels/types.js +7 -0
  186. package/dist/lib/channels/types.js.map +1 -0
  187. package/dist/lib/cloudflare/ai.js +654 -0
  188. package/dist/lib/cloudflare/ai.js.map +1 -0
  189. package/dist/lib/cloudflare/index.js +88 -0
  190. package/dist/lib/cloudflare/index.js.map +1 -0
  191. package/dist/lib/cloudflare/kv.js +342 -0
  192. package/dist/lib/cloudflare/kv.js.map +1 -0
  193. package/dist/lib/cloudflare/queues.js +434 -0
  194. package/dist/lib/cloudflare/queues.js.map +1 -0
  195. package/dist/lib/cloudflare/r2.js +604 -0
  196. package/dist/lib/cloudflare/r2.js.map +1 -0
  197. package/dist/lib/cloudflare/vectorize.js +494 -0
  198. package/dist/lib/cloudflare/vectorize.js.map +1 -0
  199. package/dist/lib/cloudflare/workflows.js +569 -0
  200. package/dist/lib/cloudflare/workflows.js.map +1 -0
  201. package/dist/lib/colo/caching.js +196 -0
  202. package/dist/lib/colo/caching.js.map +1 -0
  203. package/dist/lib/colo/detection.js +194 -0
  204. package/dist/lib/colo/detection.js.map +1 -0
  205. package/dist/lib/colo/external-data.js +219 -0
  206. package/dist/lib/colo/external-data.js.map +1 -0
  207. package/dist/lib/colo/globe-data.js +179 -0
  208. package/dist/lib/colo/globe-data.js.map +1 -0
  209. package/dist/lib/colo/index.js +16 -0
  210. package/dist/lib/colo/index.js.map +1 -0
  211. package/dist/lib/decorators.js +37 -0
  212. package/dist/lib/decorators.js.map +1 -0
  213. package/dist/lib/discovery.js +81 -0
  214. package/dist/lib/discovery.js.map +1 -0
  215. package/dist/lib/executors/AgenticFunctionExecutor.js +619 -0
  216. package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -0
  217. package/dist/lib/executors/BaseFunctionExecutor.js +328 -0
  218. package/dist/lib/executors/BaseFunctionExecutor.js.map +1 -0
  219. package/dist/lib/executors/CascadeExecutor.js +418 -0
  220. package/dist/lib/executors/CascadeExecutor.js.map +1 -0
  221. package/dist/lib/executors/CodeFunctionExecutor.js +904 -0
  222. package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -0
  223. package/dist/lib/executors/GenerativeFunctionExecutor.js +904 -0
  224. package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -0
  225. package/dist/lib/executors/HumanFunctionExecutor.js +884 -0
  226. package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -0
  227. package/dist/lib/executors/ParallelStepExecutor.js +308 -0
  228. package/dist/lib/executors/ParallelStepExecutor.js.map +1 -0
  229. package/dist/lib/executors/types.js +12 -0
  230. package/dist/lib/executors/types.js.map +1 -0
  231. package/dist/lib/experiments.js +89 -0
  232. package/dist/lib/experiments.js.map +1 -0
  233. package/dist/lib/flags/store.js +262 -0
  234. package/dist/lib/flags/store.js.map +1 -0
  235. package/dist/lib/functions/FunctionComposition.js +467 -0
  236. package/dist/lib/functions/FunctionComposition.js.map +1 -0
  237. package/dist/lib/functions/FunctionMiddleware.js +457 -0
  238. package/dist/lib/functions/FunctionMiddleware.js.map +1 -0
  239. package/dist/lib/functions/FunctionRegistry.js +426 -0
  240. package/dist/lib/functions/FunctionRegistry.js.map +1 -0
  241. package/dist/lib/functions/createFunction.js +1048 -0
  242. package/dist/lib/functions/createFunction.js.map +1 -0
  243. package/dist/lib/humans/index.js +68 -0
  244. package/dist/lib/humans/index.js.map +1 -0
  245. package/dist/lib/humans/templates.js +117 -0
  246. package/dist/lib/humans/templates.js.map +1 -0
  247. package/dist/lib/identity.js +98 -0
  248. package/dist/lib/identity.js.map +1 -0
  249. package/dist/lib/index.js +9 -0
  250. package/dist/lib/index.js.map +1 -0
  251. package/dist/lib/logging/error-logger.js +163 -0
  252. package/dist/lib/logging/error-logger.js.map +1 -0
  253. package/dist/lib/logging/index.js +160 -0
  254. package/dist/lib/logging/index.js.map +1 -0
  255. package/dist/lib/mixins/bash.js +753 -0
  256. package/dist/lib/mixins/bash.js.map +1 -0
  257. package/dist/lib/mixins/fs.js +648 -0
  258. package/dist/lib/mixins/fs.js.map +1 -0
  259. package/dist/lib/mixins/git.js +1006 -0
  260. package/dist/lib/mixins/git.js.map +1 -0
  261. package/dist/lib/mixins/npm.js +662 -0
  262. package/dist/lib/mixins/npm.js.map +1 -0
  263. package/dist/lib/noun-id.js +278 -0
  264. package/dist/lib/noun-id.js.map +1 -0
  265. package/dist/lib/rate-limit/sliding-window.js +148 -0
  266. package/dist/lib/rate-limit/sliding-window.js.map +1 -0
  267. package/dist/lib/rate-limit.js +110 -0
  268. package/dist/lib/rate-limit.js.map +1 -0
  269. package/dist/lib/rpc/bindings.js +548 -0
  270. package/dist/lib/rpc/bindings.js.map +1 -0
  271. package/dist/lib/rpc/index.js +64 -0
  272. package/dist/lib/rpc/index.js.map +1 -0
  273. package/dist/lib/safe-stringify.js +223 -0
  274. package/dist/lib/safe-stringify.js.map +1 -0
  275. package/dist/lib/sandbox/miniflare-sandbox.js +1007 -0
  276. package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -0
  277. package/dist/lib/sqids.js +110 -0
  278. package/dist/lib/sqids.js.map +1 -0
  279. package/dist/lib/sql/adapters/index.js +10 -0
  280. package/dist/lib/sql/adapters/index.js.map +1 -0
  281. package/dist/lib/sql/adapters/node-sql-parser.js +552 -0
  282. package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -0
  283. package/dist/lib/sql/adapters/pgsql-parser.js +1190 -0
  284. package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -0
  285. package/dist/lib/sql/index.js +277 -0
  286. package/dist/lib/sql/index.js.map +1 -0
  287. package/dist/lib/sql/types.js +56 -0
  288. package/dist/lib/sql/types.js.map +1 -0
  289. package/dist/lib/type-classifier.js +126 -0
  290. package/dist/lib/type-classifier.js.map +1 -0
  291. package/dist/lib/utils/html.js +47 -0
  292. package/dist/lib/utils/html.js.map +1 -0
  293. package/dist/lib/validation.js +48 -0
  294. package/dist/lib/validation.js.map +1 -0
  295. package/dist/lib/vault/store.js +411 -0
  296. package/dist/lib/vault/store.js.map +1 -0
  297. package/dist/metrics/hunch.js +739 -0
  298. package/dist/metrics/hunch.js.map +1 -0
  299. package/dist/objects/API.js +302 -0
  300. package/dist/objects/API.js.map +1 -0
  301. package/dist/objects/Agent.js +179 -0
  302. package/dist/objects/Agent.js.map +1 -0
  303. package/dist/objects/AgenticFunctionExecutor.js +8 -0
  304. package/dist/objects/AgenticFunctionExecutor.js.map +1 -0
  305. package/dist/objects/App.js +83 -0
  306. package/dist/objects/App.js.map +1 -0
  307. package/dist/objects/Browser.js +884 -0
  308. package/dist/objects/Browser.js.map +1 -0
  309. package/dist/objects/Business.js +107 -0
  310. package/dist/objects/Business.js.map +1 -0
  311. package/dist/objects/CLI.js +221 -0
  312. package/dist/objects/CLI.js.map +1 -0
  313. package/dist/objects/CodeFunctionExecutor.js +8 -0
  314. package/dist/objects/CodeFunctionExecutor.js.map +1 -0
  315. package/dist/objects/Collection.js +161 -0
  316. package/dist/objects/Collection.js.map +1 -0
  317. package/dist/objects/DO.js +41 -0
  318. package/dist/objects/DO.js.map +1 -0
  319. package/dist/objects/DOBase.js +2309 -0
  320. package/dist/objects/DOBase.js.map +1 -0
  321. package/dist/objects/DOCache.js +153 -0
  322. package/dist/objects/DOCache.js.map +1 -0
  323. package/dist/objects/DOFull.js +1676 -0
  324. package/dist/objects/DOFull.js.map +1 -0
  325. package/dist/objects/DOTiny.js +207 -0
  326. package/dist/objects/DOTiny.js.map +1 -0
  327. package/dist/objects/Directory.js +199 -0
  328. package/dist/objects/Directory.js.map +1 -0
  329. package/dist/objects/Entity.js +413 -0
  330. package/dist/objects/Entity.js.map +1 -0
  331. package/dist/objects/Function.js +116 -0
  332. package/dist/objects/Function.js.map +1 -0
  333. package/dist/objects/Human.js +231 -0
  334. package/dist/objects/Human.js.map +1 -0
  335. package/dist/objects/HumanFunctionExecutor.js +8 -0
  336. package/dist/objects/HumanFunctionExecutor.js.map +1 -0
  337. package/dist/objects/IcebergMetadataDO.js +938 -0
  338. package/dist/objects/IcebergMetadataDO.js.map +1 -0
  339. package/dist/objects/IntegrationsDO.js +1174 -0
  340. package/dist/objects/IntegrationsDO.js.map +1 -0
  341. package/dist/objects/ObservabilityBroadcaster.js +149 -0
  342. package/dist/objects/ObservabilityBroadcaster.js.map +1 -0
  343. package/dist/objects/Package.js +154 -0
  344. package/dist/objects/Package.js.map +1 -0
  345. package/dist/objects/Product.js +193 -0
  346. package/dist/objects/Product.js.map +1 -0
  347. package/dist/objects/SDK.js +152 -0
  348. package/dist/objects/SDK.js.map +1 -0
  349. package/dist/objects/SaaS.js +235 -0
  350. package/dist/objects/SaaS.js.map +1 -0
  351. package/dist/objects/SandboxDO.js +759 -0
  352. package/dist/objects/SandboxDO.js.map +1 -0
  353. package/dist/objects/Service.js +337 -0
  354. package/dist/objects/Service.js.map +1 -0
  355. package/dist/objects/Site.js +80 -0
  356. package/dist/objects/Site.js.map +1 -0
  357. package/dist/objects/Startup.js +479 -0
  358. package/dist/objects/Startup.js.map +1 -0
  359. package/dist/objects/ThingsDO.js +170 -0
  360. package/dist/objects/ThingsDO.js.map +1 -0
  361. package/dist/objects/VectorShardDO.js +650 -0
  362. package/dist/objects/VectorShardDO.js.map +1 -0
  363. package/dist/objects/Worker.js +144 -0
  364. package/dist/objects/Worker.js.map +1 -0
  365. package/dist/objects/Workflow.js +196 -0
  366. package/dist/objects/Workflow.js.map +1 -0
  367. package/dist/objects/WorkflowFactory.js +313 -0
  368. package/dist/objects/WorkflowFactory.js.map +1 -0
  369. package/dist/objects/WorkflowRuntime.js +863 -0
  370. package/dist/objects/WorkflowRuntime.js.map +1 -0
  371. package/dist/objects/circuit-breaker-bulkhead.js +178 -0
  372. package/dist/objects/circuit-breaker-bulkhead.js.map +1 -0
  373. package/dist/objects/createFunction.js +934 -0
  374. package/dist/objects/createFunction.js.map +1 -0
  375. package/dist/objects/index.js +80 -0
  376. package/dist/objects/index.js.map +1 -0
  377. package/dist/objects/lifecycle/Branch.js +275 -0
  378. package/dist/objects/lifecycle/Branch.js.map +1 -0
  379. package/dist/objects/lifecycle/Clone.js +1499 -0
  380. package/dist/objects/lifecycle/Clone.js.map +1 -0
  381. package/dist/objects/lifecycle/Compact.js +237 -0
  382. package/dist/objects/lifecycle/Compact.js.map +1 -0
  383. package/dist/objects/lifecycle/Promote.js +476 -0
  384. package/dist/objects/lifecycle/Promote.js.map +1 -0
  385. package/dist/objects/lifecycle/Shard.js +560 -0
  386. package/dist/objects/lifecycle/Shard.js.map +1 -0
  387. package/dist/objects/lifecycle/index.js +15 -0
  388. package/dist/objects/lifecycle/index.js.map +1 -0
  389. package/dist/objects/lifecycle/types.js +33 -0
  390. package/dist/objects/lifecycle/types.js.map +1 -0
  391. package/dist/objects/mixins/infrastructure.js +171 -0
  392. package/dist/objects/mixins/infrastructure.js.map +1 -0
  393. package/dist/objects/modules/StoresModule.js +153 -0
  394. package/dist/objects/modules/StoresModule.js.map +1 -0
  395. package/dist/objects/persistence/checkpoint-manager.js +606 -0
  396. package/dist/objects/persistence/checkpoint-manager.js.map +1 -0
  397. package/dist/objects/persistence/index.js +72 -0
  398. package/dist/objects/persistence/index.js.map +1 -0
  399. package/dist/objects/persistence/migration-runner.js +562 -0
  400. package/dist/objects/persistence/migration-runner.js.map +1 -0
  401. package/dist/objects/persistence/replication-manager.js +501 -0
  402. package/dist/objects/persistence/replication-manager.js.map +1 -0
  403. package/dist/objects/persistence/tiered-storage-manager.js +595 -0
  404. package/dist/objects/persistence/tiered-storage-manager.js.map +1 -0
  405. package/dist/objects/persistence/types.js +14 -0
  406. package/dist/objects/persistence/types.js.map +1 -0
  407. package/dist/objects/persistence/wal-manager.js +653 -0
  408. package/dist/objects/persistence/wal-manager.js.map +1 -0
  409. package/dist/objects/presets/index.js +20 -0
  410. package/dist/objects/presets/index.js.map +1 -0
  411. package/dist/objects/presets/primitives.js +188 -0
  412. package/dist/objects/presets/primitives.js.map +1 -0
  413. package/dist/objects/primitives/alarm-adapter.js +141 -0
  414. package/dist/objects/primitives/alarm-adapter.js.map +1 -0
  415. package/dist/objects/primitives/index.js +337 -0
  416. package/dist/objects/primitives/index.js.map +1 -0
  417. package/dist/objects/primitives/storage-adapter.js +182 -0
  418. package/dist/objects/primitives/storage-adapter.js.map +1 -0
  419. package/dist/objects/primitives/with-primitives.js +102 -0
  420. package/dist/objects/primitives/with-primitives.js.map +1 -0
  421. package/dist/objects/services/StoreManager.js +227 -0
  422. package/dist/objects/services/StoreManager.js.map +1 -0
  423. package/dist/objects/services/index.js +13 -0
  424. package/dist/objects/services/index.js.map +1 -0
  425. package/dist/objects/transport/auth-layer.js +1451 -0
  426. package/dist/objects/transport/auth-layer.js.map +1 -0
  427. package/dist/objects/transport/capnweb-target.js +355 -0
  428. package/dist/objects/transport/capnweb-target.js.map +1 -0
  429. package/dist/objects/transport/chain.js +441 -0
  430. package/dist/objects/transport/chain.js.map +1 -0
  431. package/dist/objects/transport/handler.js +58 -0
  432. package/dist/objects/transport/handler.js.map +1 -0
  433. package/dist/objects/transport/index.js +53 -0
  434. package/dist/objects/transport/index.js.map +1 -0
  435. package/dist/objects/transport/mcp-server.js +691 -0
  436. package/dist/objects/transport/mcp-server.js.map +1 -0
  437. package/dist/objects/transport/rest-autowire.js +1508 -0
  438. package/dist/objects/transport/rest-autowire.js.map +1 -0
  439. package/dist/objects/transport/rest-router.js +440 -0
  440. package/dist/objects/transport/rest-router.js.map +1 -0
  441. package/dist/objects/transport/rpc-server.js +1539 -0
  442. package/dist/objects/transport/rpc-server.js.map +1 -0
  443. package/dist/objects/transport/shared.js +576 -0
  444. package/dist/objects/transport/shared.js.map +1 -0
  445. package/dist/objects/transport/sync-engine.js +291 -0
  446. package/dist/objects/transport/sync-engine.js.map +1 -0
  447. package/dist/objects/transport/types.js +8 -0
  448. package/dist/objects/transport/types.js.map +1 -0
  449. package/dist/sandbox/index.js +258 -0
  450. package/dist/sandbox/index.js.map +1 -0
  451. package/dist/snippets/artifacts-config.js +241 -0
  452. package/dist/snippets/artifacts-config.js.map +1 -0
  453. package/dist/snippets/artifacts-ingest.js +832 -0
  454. package/dist/snippets/artifacts-ingest.js.map +1 -0
  455. package/dist/snippets/artifacts-serve.js +1035 -0
  456. package/dist/snippets/artifacts-serve.js.map +1 -0
  457. package/dist/snippets/artifacts-types.js +161 -0
  458. package/dist/snippets/artifacts-types.js.map +1 -0
  459. package/dist/snippets/cache-probe.js +376 -0
  460. package/dist/snippets/cache-probe.js.map +1 -0
  461. package/dist/snippets/cache.js +10 -0
  462. package/dist/snippets/cache.js.map +1 -0
  463. package/dist/snippets/events.js +469 -0
  464. package/dist/snippets/events.js.map +1 -0
  465. package/dist/snippets/index.js +7 -0
  466. package/dist/snippets/index.js.map +1 -0
  467. package/dist/snippets/proxy.js +495 -0
  468. package/dist/snippets/proxy.js.map +1 -0
  469. package/dist/snippets/search.js +1759 -0
  470. package/dist/snippets/search.js.map +1 -0
  471. package/dist/streams/index.js +30 -0
  472. package/dist/streams/index.js.map +1 -0
  473. package/dist/streams/observability.js +68 -0
  474. package/dist/streams/observability.js.map +1 -0
  475. package/dist/types/AI.js +92 -0
  476. package/dist/types/AI.js.map +1 -0
  477. package/dist/types/AIFunction.js +171 -0
  478. package/dist/types/AIFunction.js.map +1 -0
  479. package/dist/types/BrowseVerb.js +89 -0
  480. package/dist/types/BrowseVerb.js.map +1 -0
  481. package/dist/types/Browser.js +31 -0
  482. package/dist/types/Browser.js.map +1 -0
  483. package/dist/types/Chaos.js +15 -0
  484. package/dist/types/Chaos.js.map +1 -0
  485. package/dist/types/CloudflareBindings.js +109 -0
  486. package/dist/types/CloudflareBindings.js.map +1 -0
  487. package/dist/types/Collection.js +50 -0
  488. package/dist/types/Collection.js.map +1 -0
  489. package/dist/types/DO.js +2 -0
  490. package/dist/types/DO.js.map +1 -0
  491. package/dist/types/DOLocation.js +63 -0
  492. package/dist/types/DOLocation.js.map +1 -0
  493. package/dist/types/EventHandler.js +57 -0
  494. package/dist/types/EventHandler.js.map +1 -0
  495. package/dist/types/Experiment.js +33 -0
  496. package/dist/types/Experiment.js.map +1 -0
  497. package/dist/types/Flag.js +57 -0
  498. package/dist/types/Flag.js.map +1 -0
  499. package/dist/types/Lifecycle.js +13 -0
  500. package/dist/types/Lifecycle.js.map +1 -0
  501. package/dist/types/Location.js +169 -0
  502. package/dist/types/Location.js.map +1 -0
  503. package/dist/types/Noun.js +66 -0
  504. package/dist/types/Noun.js.map +1 -0
  505. package/dist/types/SessionEvent.js +194 -0
  506. package/dist/types/SessionEvent.js.map +1 -0
  507. package/dist/types/Thing.js +55 -0
  508. package/dist/types/Thing.js.map +1 -0
  509. package/dist/types/ThingDO.js +153 -0
  510. package/dist/types/ThingDO.js.map +1 -0
  511. package/dist/types/Things.js +2 -0
  512. package/dist/types/Things.js.map +1 -0
  513. package/dist/types/Verb.js +119 -0
  514. package/dist/types/Verb.js.map +1 -0
  515. package/dist/types/WorkflowContext.js +70 -0
  516. package/dist/types/WorkflowContext.js.map +1 -0
  517. package/dist/types/analytics-api.js +13 -0
  518. package/dist/types/analytics-api.js.map +1 -0
  519. package/dist/types/capabilities.js +135 -0
  520. package/dist/types/capabilities.js.map +1 -0
  521. package/dist/types/drizzle.js +12 -0
  522. package/dist/types/drizzle.js.map +1 -0
  523. package/dist/types/event.js +201 -0
  524. package/dist/types/event.js.map +1 -0
  525. package/dist/types/fn.js +12 -0
  526. package/dist/types/fn.js.map +1 -0
  527. package/dist/types/iceberg.js +48 -0
  528. package/dist/types/iceberg.js.map +1 -0
  529. package/dist/types/ids.js +170 -0
  530. package/dist/types/ids.js.map +1 -0
  531. package/dist/types/index.js +41 -0
  532. package/dist/types/index.js.map +1 -0
  533. package/dist/types/introspect.js +54 -0
  534. package/dist/types/introspect.js.map +1 -0
  535. package/dist/types/observability.js +124 -0
  536. package/dist/types/observability.js.map +1 -0
  537. package/dist/types/sync-protocol.js +175 -0
  538. package/dist/types/sync-protocol.js.map +1 -0
  539. package/dist/types/vector.js +13 -0
  540. package/dist/types/vector.js.map +1 -0
  541. package/dist/workflows/ScheduleManager.js +473 -0
  542. package/dist/workflows/ScheduleManager.js.map +1 -0
  543. package/dist/workflows/StepDOBridge.js +149 -0
  544. package/dist/workflows/StepDOBridge.js.map +1 -0
  545. package/dist/workflows/StepResultStorage.js +232 -0
  546. package/dist/workflows/StepResultStorage.js.map +1 -0
  547. package/dist/workflows/WaitForEventManager.js +461 -0
  548. package/dist/workflows/WaitForEventManager.js.map +1 -0
  549. package/dist/workflows/analyzer.js +332 -0
  550. package/dist/workflows/analyzer.js.map +1 -0
  551. package/dist/workflows/compat/activity-router.js +484 -0
  552. package/dist/workflows/compat/activity-router.js.map +1 -0
  553. package/dist/workflows/compat/backends/cloudflare-workflows.js +431 -0
  554. package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -0
  555. package/dist/workflows/compat/backends/index.js +14 -0
  556. package/dist/workflows/compat/backends/index.js.map +1 -0
  557. package/dist/workflows/compat/errors/index.js +375 -0
  558. package/dist/workflows/compat/errors/index.js.map +1 -0
  559. package/dist/workflows/compat/index.js +79 -0
  560. package/dist/workflows/compat/index.js.map +1 -0
  561. package/dist/workflows/compat/inngest/index.js +989 -0
  562. package/dist/workflows/compat/inngest/index.js.map +1 -0
  563. package/dist/workflows/compat/qstash/index.js +1263 -0
  564. package/dist/workflows/compat/qstash/index.js.map +1 -0
  565. package/dist/workflows/compat/temporal/activities.js +739 -0
  566. package/dist/workflows/compat/temporal/activities.js.map +1 -0
  567. package/dist/workflows/compat/temporal/child-workflows.js +154 -0
  568. package/dist/workflows/compat/temporal/child-workflows.js.map +1 -0
  569. package/dist/workflows/compat/temporal/client.js +381 -0
  570. package/dist/workflows/compat/temporal/client.js.map +1 -0
  571. package/dist/workflows/compat/temporal/context.js +309 -0
  572. package/dist/workflows/compat/temporal/context.js.map +1 -0
  573. package/dist/workflows/compat/temporal/determinism.js +216 -0
  574. package/dist/workflows/compat/temporal/determinism.js.map +1 -0
  575. package/dist/workflows/compat/temporal/errors.js +128 -0
  576. package/dist/workflows/compat/temporal/errors.js.map +1 -0
  577. package/dist/workflows/compat/temporal/index.js +2464 -0
  578. package/dist/workflows/compat/temporal/index.js.map +1 -0
  579. package/dist/workflows/compat/temporal/saga.js +504 -0
  580. package/dist/workflows/compat/temporal/saga.js.map +1 -0
  581. package/dist/workflows/compat/temporal/signals.js +364 -0
  582. package/dist/workflows/compat/temporal/signals.js.map +1 -0
  583. package/dist/workflows/compat/temporal/storage.js +271 -0
  584. package/dist/workflows/compat/temporal/storage.js.map +1 -0
  585. package/dist/workflows/compat/temporal/timers.js +347 -0
  586. package/dist/workflows/compat/temporal/timers.js.map +1 -0
  587. package/dist/workflows/compat/temporal/types.js +7 -0
  588. package/dist/workflows/compat/temporal/types.js.map +1 -0
  589. package/dist/workflows/compat/temporal/unified-primitives.js +339 -0
  590. package/dist/workflows/compat/temporal/unified-primitives.js.map +1 -0
  591. package/dist/workflows/compat/trigger/index.js +468 -0
  592. package/dist/workflows/compat/trigger/index.js.map +1 -0
  593. package/dist/workflows/compat/utils/index.js +69 -0
  594. package/dist/workflows/compat/utils/index.js.map +1 -0
  595. package/dist/workflows/context/correlation-capability.js +266 -0
  596. package/dist/workflows/context/correlation-capability.js.map +1 -0
  597. package/dist/workflows/context/correlation.js +484 -0
  598. package/dist/workflows/context/correlation.js.map +1 -0
  599. package/dist/workflows/context/experiment.js +289 -0
  600. package/dist/workflows/context/experiment.js.map +1 -0
  601. package/dist/workflows/context/flag.js +244 -0
  602. package/dist/workflows/context/flag.js.map +1 -0
  603. package/dist/workflows/context/foundation.js +648 -0
  604. package/dist/workflows/context/foundation.js.map +1 -0
  605. package/dist/workflows/context/human-base.js +106 -0
  606. package/dist/workflows/context/human-base.js.map +1 -0
  607. package/dist/workflows/context/human.js +368 -0
  608. package/dist/workflows/context/human.js.map +1 -0
  609. package/dist/workflows/context/measure.js +354 -0
  610. package/dist/workflows/context/measure.js.map +1 -0
  611. package/dist/workflows/context/rate-limit.js +358 -0
  612. package/dist/workflows/context/rate-limit.js.map +1 -0
  613. package/dist/workflows/context/user.js +117 -0
  614. package/dist/workflows/context/user.js.map +1 -0
  615. package/dist/workflows/context/vault.js +360 -0
  616. package/dist/workflows/context/vault.js.map +1 -0
  617. package/dist/workflows/data/entity-events/entity-events.js +489 -0
  618. package/dist/workflows/data/entity-events/entity-events.js.map +1 -0
  619. package/dist/workflows/data/experiment/index.js +599 -0
  620. package/dist/workflows/data/experiment/index.js.map +1 -0
  621. package/dist/workflows/data/goal/context.js +558 -0
  622. package/dist/workflows/data/goal/context.js.map +1 -0
  623. package/dist/workflows/data/goal/index.js +32 -0
  624. package/dist/workflows/data/goal/index.js.map +1 -0
  625. package/dist/workflows/data/measure/index.js +840 -0
  626. package/dist/workflows/data/measure/index.js.map +1 -0
  627. package/dist/workflows/data/stream/index.js +1149 -0
  628. package/dist/workflows/data/stream/index.js.map +1 -0
  629. package/dist/workflows/data/track/context.js +883 -0
  630. package/dist/workflows/data/track/context.js.map +1 -0
  631. package/dist/workflows/data/track/index.js +15 -0
  632. package/dist/workflows/data/track/index.js.map +1 -0
  633. package/dist/workflows/data/view/context.js +864 -0
  634. package/dist/workflows/data/view/context.js.map +1 -0
  635. package/dist/workflows/domain.js +93 -0
  636. package/dist/workflows/domain.js.map +1 -0
  637. package/dist/workflows/flag.js +176 -0
  638. package/dist/workflows/flag.js.map +1 -0
  639. package/dist/workflows/flags.js +217 -0
  640. package/dist/workflows/flags.js.map +1 -0
  641. package/dist/workflows/hash.js +209 -0
  642. package/dist/workflows/hash.js.map +1 -0
  643. package/dist/workflows/index.js +50 -0
  644. package/dist/workflows/index.js.map +1 -0
  645. package/dist/workflows/on.js +378 -0
  646. package/dist/workflows/on.js.map +1 -0
  647. package/dist/workflows/pipeline-promise.js +481 -0
  648. package/dist/workflows/pipeline-promise.js.map +1 -0
  649. package/dist/workflows/pipeline-types.js +20 -0
  650. package/dist/workflows/pipeline-types.js.map +1 -0
  651. package/dist/workflows/proxy.js +76 -0
  652. package/dist/workflows/proxy.js.map +1 -0
  653. package/dist/workflows/runtime.js +310 -0
  654. package/dist/workflows/runtime.js.map +1 -0
  655. package/dist/workflows/schedule-builder.js +327 -0
  656. package/dist/workflows/schedule-builder.js.map +1 -0
  657. package/dist/workflows/visibility/index.js +146 -0
  658. package/dist/workflows/visibility/index.js.map +1 -0
  659. package/dist/workflows/visibility/query-parser.js +150 -0
  660. package/dist/workflows/visibility/query-parser.js.map +1 -0
  661. package/dist/workflows/visibility/store.js +223 -0
  662. package/dist/workflows/visibility/store.js.map +1 -0
  663. package/dist/workflows/visibility/types.js +30 -0
  664. package/dist/workflows/visibility/types.js.map +1 -0
  665. package/dist/workflows/workflow.js +53 -0
  666. package/dist/workflows/workflow.js.map +1 -0
  667. package/package.json +294 -46
@@ -0,0 +1,2309 @@
1
+ /**
2
+ * DO - Core Durable Object with WorkflowContext
3
+ *
4
+ * Extends DOTiny (~80KB) with:
5
+ * - WorkflowContext ($)
6
+ * - Event handlers ($.on)
7
+ * - Stores (things, rels, actions, events, search, objects, dlq)
8
+ * - Scheduling ($.every, alarm)
9
+ * - Actor context
10
+ * - Collection accessors
11
+ * - Event emission and dispatch
12
+ *
13
+ * Does NOT include (see DOFull for these):
14
+ * - Lifecycle (fork, clone, compact, move)
15
+ * - Sharding (shard, unshard, routing)
16
+ * - Branching (branch, checkout, merge)
17
+ * - Promotion (promote, demote)
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { DO } from 'dotdo'
22
+ *
23
+ * class MyDO extends DO {
24
+ * async onStart() {
25
+ * // Use workflow context
26
+ * this.$.on.Customer.created(async (event) => {
27
+ * console.log('Customer created:', event)
28
+ * })
29
+ *
30
+ * this.$.every.hour(async () => {
31
+ * // Hourly task
32
+ * })
33
+ * }
34
+ * }
35
+ * ```
36
+ */
37
+ import { DO as DOTiny } from './DOTiny';
38
+ import { eq, sql } from 'drizzle-orm';
39
+ import * as schema from '../db';
40
+ import { isValidNounName } from '../db/nouns';
41
+ import { createMcpHandler, } from './transport/mcp-server';
42
+ import { RPCServer } from './transport/rpc-server';
43
+ import { SyncEngine } from './transport/sync-engine';
44
+ import { handleCapnWebRpc, isCapnWebRequest, } from './transport/capnweb-target';
45
+ import { handleRestRequest, handleGetIndex, } from './transport/rest-router';
46
+ import { createScheduleBuilderProxy } from '../workflows/schedule-builder';
47
+ import { ScheduleManager } from '../workflows/ScheduleManager';
48
+ import { ThingsStore, RelationshipsStore, ActionsStore, EventsStore, SearchStore, ObjectsStore, DLQStore, } from '../db/stores';
49
+ import { logBestEffortError } from '../lib/logging/error-logger';
50
+ import { parseNounId } from '../lib/noun-id';
51
+ import { ai as aiFunc, write as writeFunc, summarize as summarizeFunc, list as listFunc, extract as extractFunc, is as isFunc, decide as decideFunc, } from '../ai';
52
+ import { STORE_VISIBILITY, canAccessVisibility, getHighestRole, } from '../types/introspect';
53
+ import { codeToCity, coloRegion, regionToCF } from '../types/Location';
54
+ import { LOCATION_STORAGE_KEY } from '../lib/colo/caching';
55
+ // ============================================================================
56
+ // CROSS-DO ERROR CLASS
57
+ // ============================================================================
58
+ /**
59
+ * Custom error class for cross-DO call failures with rich context
60
+ */
61
+ export class CrossDOError extends Error {
62
+ code;
63
+ context;
64
+ constructor(code, message, context = {}) {
65
+ super(message);
66
+ this.name = 'CrossDOError';
67
+ this.code = code;
68
+ this.context = context;
69
+ // Preserve stack trace in V8
70
+ if (Error.captureStackTrace) {
71
+ Error.captureStackTrace(this, CrossDOError);
72
+ }
73
+ }
74
+ toJSON() {
75
+ return {
76
+ error: {
77
+ code: this.code,
78
+ message: this.message,
79
+ context: this.context,
80
+ },
81
+ };
82
+ }
83
+ }
84
+ // ============================================================================
85
+ // DO - Core Durable Object with WorkflowContext
86
+ // ============================================================================
87
+ export class DO extends DOTiny {
88
+ // ═══════════════════════════════════════════════════════════════════════════
89
+ // STATIC MCP CONFIGURATION
90
+ // ═══════════════════════════════════════════════════════════════════════════
91
+ /**
92
+ * Static MCP configuration for exposing methods as MCP tools and data as resources.
93
+ * Override in subclasses to expose tools and resources.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * static $mcp = {
98
+ * tools: {
99
+ * search: {
100
+ * description: 'Search items',
101
+ * inputSchema: { query: { type: 'string' } },
102
+ * required: ['query'],
103
+ * },
104
+ * },
105
+ * resources: ['items', 'users'],
106
+ * }
107
+ * ```
108
+ */
109
+ static $mcp;
110
+ // ═══════════════════════════════════════════════════════════════════════════
111
+ // CAPABILITY MIXIN INFRASTRUCTURE
112
+ // ═══════════════════════════════════════════════════════════════════════════
113
+ /**
114
+ * Static array of capability names supported by this class.
115
+ * Populated by capability mixins (e.g., withFS, withGit, withBash).
116
+ * Empty by default in base DO class.
117
+ */
118
+ static capabilities = [];
119
+ /**
120
+ * Check if this DO instance has a specific capability.
121
+ * Capabilities are added via mixins and registered in the static capabilities array.
122
+ *
123
+ * @param name - Capability name to check (e.g., 'fs', 'git', 'bash')
124
+ * @returns true if the capability is registered on this class
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * if (this.hasCapability('fs')) {
129
+ * await this.$.fs.read('/config.json')
130
+ * }
131
+ * ```
132
+ */
133
+ hasCapability(name) {
134
+ return this.constructor.capabilities?.includes(name) ?? false;
135
+ }
136
+ // ═══════════════════════════════════════════════════════════════════════════
137
+ // HONO APP (for HTTP routing)
138
+ // ═══════════════════════════════════════════════════════════════════════════
139
+ /**
140
+ * Optional Hono app for HTTP routing.
141
+ * Subclasses can create and configure this for custom routes.
142
+ */
143
+ app;
144
+ // ═══════════════════════════════════════════════════════════════════════════
145
+ // MCP SESSION STORAGE
146
+ // ═══════════════════════════════════════════════════════════════════════════
147
+ /**
148
+ * MCP session storage for this DO instance.
149
+ */
150
+ _mcpSessions = new Map();
151
+ /**
152
+ * Cached MCP handler for this class.
153
+ */
154
+ _mcpHandler;
155
+ // ═══════════════════════════════════════════════════════════════════════════
156
+ // RPC SERVER
157
+ // ═══════════════════════════════════════════════════════════════════════════
158
+ /**
159
+ * RPC Server instance for Cap'n Web RPC protocol support.
160
+ * Lazy-initialized on first access.
161
+ */
162
+ _rpcServer;
163
+ /**
164
+ * Get the RPC server instance.
165
+ * Creates the server on first access.
166
+ */
167
+ get rpcServer() {
168
+ if (!this._rpcServer) {
169
+ this._rpcServer = new RPCServer(this);
170
+ }
171
+ return this._rpcServer;
172
+ }
173
+ /**
174
+ * Check if a method is exposed via RPC.
175
+ * Note: This method is bound in the constructor to ensure `this` is always correct.
176
+ */
177
+ isRpcExposed = (method) => {
178
+ return this.rpcServer.isRpcExposed(method);
179
+ };
180
+ // ═══════════════════════════════════════════════════════════════════════════
181
+ // SYNC ENGINE
182
+ // ═══════════════════════════════════════════════════════════════════════════
183
+ /**
184
+ * SyncEngine instance for WebSocket sync protocol support.
185
+ * Lazy-initialized on first access.
186
+ */
187
+ _syncEngine;
188
+ /**
189
+ * Get the SyncEngine instance.
190
+ * Creates the engine on first access.
191
+ */
192
+ get syncEngine() {
193
+ if (!this._syncEngine) {
194
+ this._syncEngine = new SyncEngine(this.things);
195
+ }
196
+ return this._syncEngine;
197
+ }
198
+ // ═══════════════════════════════════════════════════════════════════════════
199
+ // ACTOR CONTEXT
200
+ // ═══════════════════════════════════════════════════════════════════════════
201
+ /**
202
+ * Current actor for action logging.
203
+ * Format: 'Type/id' (e.g., 'Human/nathan', 'Agent/support')
204
+ */
205
+ _currentActor = '';
206
+ /**
207
+ * Set the current actor for subsequent action logging.
208
+ */
209
+ setActor(actor) {
210
+ this._currentActor = actor;
211
+ }
212
+ /**
213
+ * Clear the current actor.
214
+ */
215
+ clearActor() {
216
+ this._currentActor = '';
217
+ }
218
+ /**
219
+ * Get the current actor for action logging.
220
+ */
221
+ getCurrentActor() {
222
+ return this._currentActor;
223
+ }
224
+ // ═══════════════════════════════════════════════════════════════════════════
225
+ // STORE ACCESSORS (lazy-loaded)
226
+ // ═══════════════════════════════════════════════════════════════════════════
227
+ _things;
228
+ _rels;
229
+ _actions;
230
+ _events;
231
+ _search;
232
+ _objects;
233
+ _dlq;
234
+ _typeCache = new Map();
235
+ // Event handler registry for $.on.Noun.verb() registration
236
+ _eventHandlers = new Map();
237
+ _handlerCounter = 0;
238
+ // Schedule handler registry for $.every scheduling
239
+ _scheduleHandlers = new Map();
240
+ _scheduleManager;
241
+ /**
242
+ * Get the schedule manager (lazy initialized)
243
+ */
244
+ get scheduleManager() {
245
+ if (!this._scheduleManager) {
246
+ this._scheduleManager = new ScheduleManager(this.ctx);
247
+ this._scheduleManager.onScheduleTrigger(async (schedule) => {
248
+ const handler = this._scheduleHandlers.get(schedule.name);
249
+ if (handler) {
250
+ await handler();
251
+ }
252
+ });
253
+ }
254
+ return this._scheduleManager;
255
+ }
256
+ /**
257
+ * ThingsStore - CRUD operations for Things
258
+ */
259
+ get things() {
260
+ if (!this._things) {
261
+ this._things = new ThingsStore(this.getStoreContext());
262
+ }
263
+ return this._things;
264
+ }
265
+ /**
266
+ * RelationshipsStore - Relationship management
267
+ */
268
+ get rels() {
269
+ if (!this._rels) {
270
+ this._rels = new RelationshipsStore(this.getStoreContext());
271
+ }
272
+ return this._rels;
273
+ }
274
+ /**
275
+ * ActionsStore - Action logging and lifecycle
276
+ */
277
+ get actions() {
278
+ if (!this._actions) {
279
+ this._actions = new ActionsStore(this.getStoreContext());
280
+ }
281
+ return this._actions;
282
+ }
283
+ /**
284
+ * EventsStore - Event emission and streaming
285
+ */
286
+ get events() {
287
+ if (!this._events) {
288
+ this._events = new EventsStore(this.getStoreContext());
289
+ }
290
+ return this._events;
291
+ }
292
+ /**
293
+ * SearchStore - Full-text and semantic search
294
+ */
295
+ get search() {
296
+ if (!this._search) {
297
+ this._search = new SearchStore(this.getStoreContext());
298
+ }
299
+ return this._search;
300
+ }
301
+ /**
302
+ * ObjectsStore - DO registry and resolution
303
+ */
304
+ get objects() {
305
+ if (!this._objects) {
306
+ this._objects = new ObjectsStore(this.getStoreContext());
307
+ }
308
+ return this._objects;
309
+ }
310
+ /**
311
+ * DLQStore - Dead Letter Queue for failed events
312
+ */
313
+ get dlq() {
314
+ if (!this._dlq) {
315
+ const handlerMap = new Map();
316
+ for (const [eventKey, registrations] of this._eventHandlers) {
317
+ if (registrations.length > 0) {
318
+ handlerMap.set(eventKey, async (data) => {
319
+ const event = {
320
+ id: `dlq-replay-${crypto.randomUUID()}`,
321
+ verb: eventKey.split('.')[1] || '',
322
+ source: `https://${this.ns}/${eventKey.split('.')[0]}/replay`,
323
+ data,
324
+ timestamp: new Date(),
325
+ };
326
+ await this.dispatchEventToHandlers(event);
327
+ });
328
+ }
329
+ }
330
+ this._dlq = new DLQStore(this.getStoreContext(), handlerMap);
331
+ }
332
+ return this._dlq;
333
+ }
334
+ /**
335
+ * Get the store context for initializing stores
336
+ */
337
+ getStoreContext() {
338
+ return {
339
+ db: this.db,
340
+ ns: this.ns,
341
+ currentBranch: this.currentBranch,
342
+ env: this.env,
343
+ typeCache: this._typeCache,
344
+ };
345
+ }
346
+ // ═══════════════════════════════════════════════════════════════════════════
347
+ // LOCATION DETECTION & CACHING
348
+ // ═══════════════════════════════════════════════════════════════════════════
349
+ /** Cached location instance (in-memory) */
350
+ _cachedLocation;
351
+ /** Flag to track if location hook was already called */
352
+ _locationHookCalled = false;
353
+ /** Coordinates extracted from CF request headers */
354
+ _extractedCoordinates;
355
+ /**
356
+ * Get the DO's location (with caching).
357
+ *
358
+ * On first call, detects location via Cloudflare's trace endpoint,
359
+ * caches it in storage, and calls the onLocationDetected hook.
360
+ * Subsequent calls return the cached location immediately.
361
+ *
362
+ * @returns Promise resolving to the DO's location
363
+ */
364
+ async getLocation() {
365
+ // Return cached location if available
366
+ if (this._cachedLocation) {
367
+ return this._cachedLocation;
368
+ }
369
+ // Check DO storage for persisted location
370
+ // Note: Storage may have legacy format with latitude/longitude or new format with lat/lng
371
+ const cached = await this.ctx.storage.get(LOCATION_STORAGE_KEY);
372
+ if (cached) {
373
+ // Restore from storage, converting coordinates to canonical format if needed
374
+ const coords = cached.coordinates
375
+ ? {
376
+ lat: cached.coordinates.lat ?? cached.coordinates.latitude ?? 0,
377
+ lng: cached.coordinates.lng ?? cached.coordinates.longitude ?? 0,
378
+ }
379
+ : undefined;
380
+ this._cachedLocation = Object.freeze({
381
+ colo: cached.colo,
382
+ city: cached.city,
383
+ region: cached.region,
384
+ cfHint: cached.cfHint,
385
+ detectedAt: cached.detectedAt instanceof Date
386
+ ? cached.detectedAt
387
+ : new Date(cached.detectedAt),
388
+ coordinates: coords,
389
+ });
390
+ return this._cachedLocation;
391
+ }
392
+ // Detect fresh location
393
+ const location = await this._detectLocation();
394
+ // Cache in memory (frozen for immutability)
395
+ this._cachedLocation = Object.freeze(location);
396
+ // Persist to storage
397
+ await this.ctx.storage.put(LOCATION_STORAGE_KEY, {
398
+ colo: location.colo,
399
+ city: location.city,
400
+ region: location.region,
401
+ cfHint: location.cfHint,
402
+ detectedAt: location.detectedAt.toISOString(),
403
+ coordinates: location.coordinates,
404
+ });
405
+ // Call lifecycle hook (only once)
406
+ if (!this._locationHookCalled) {
407
+ this._locationHookCalled = true;
408
+ try {
409
+ await this.onLocationDetected(this._cachedLocation);
410
+ }
411
+ catch (error) {
412
+ // Log but don't propagate hook errors
413
+ console.error('Error in onLocationDetected hook:', error);
414
+ }
415
+ }
416
+ return this._cachedLocation;
417
+ }
418
+ /**
419
+ * Internal method to detect location from Cloudflare's trace endpoint.
420
+ * Override in tests to provide mock location data.
421
+ *
422
+ * @returns Promise resolving to detected DOLocation
423
+ */
424
+ async _detectLocation() {
425
+ try {
426
+ // Fetch from Cloudflare's trace endpoint
427
+ const response = await fetch('https://cloudflare.com/cdn-cgi/trace');
428
+ if (!response.ok) {
429
+ throw new Error(`Trace endpoint returned ${response.status}`);
430
+ }
431
+ const text = await response.text();
432
+ const lines = text.split('\n');
433
+ const data = {};
434
+ for (const line of lines) {
435
+ const [key, value] = line.split('=');
436
+ if (key && value) {
437
+ data[key.trim()] = value.trim();
438
+ }
439
+ }
440
+ const coloCode = (data.colo || 'lax').toLowerCase();
441
+ const city = (codeToCity[coloCode] || 'LosAngeles');
442
+ const region = (coloRegion[coloCode] || 'us-west');
443
+ const cfHint = (regionToCF[region] || 'wnam');
444
+ const location = {
445
+ colo: coloCode,
446
+ city,
447
+ region,
448
+ cfHint,
449
+ detectedAt: new Date(),
450
+ };
451
+ // Add coordinates if extracted from request
452
+ if (this._extractedCoordinates) {
453
+ location.coordinates = this._extractedCoordinates;
454
+ }
455
+ return location;
456
+ }
457
+ catch (error) {
458
+ // Fallback to default location on error
459
+ console.error('Failed to detect location:', error);
460
+ const location = {
461
+ colo: 'lax',
462
+ city: 'LosAngeles',
463
+ region: 'us-west',
464
+ cfHint: 'wnam',
465
+ detectedAt: new Date(),
466
+ };
467
+ if (this._extractedCoordinates) {
468
+ location.coordinates = this._extractedCoordinates;
469
+ }
470
+ return location;
471
+ }
472
+ }
473
+ /**
474
+ * Lifecycle hook called when location is first detected.
475
+ * Override in subclasses to perform custom actions.
476
+ *
477
+ * @param location - The detected DO location
478
+ */
479
+ async onLocationDetected(location) {
480
+ // Default implementation does nothing
481
+ // Subclasses can override to react to location detection
482
+ }
483
+ // ═══════════════════════════════════════════════════════════════════════════
484
+ // WORKFLOW CONTEXT ($)
485
+ // ═══════════════════════════════════════════════════════════════════════════
486
+ $;
487
+ // ═══════════════════════════════════════════════════════════════════════════
488
+ // CONSTRUCTOR
489
+ // ═══════════════════════════════════════════════════════════════════════════
490
+ constructor(ctx, env) {
491
+ super(ctx, env);
492
+ // Initialize workflow context
493
+ this.$ = this.createWorkflowContext();
494
+ }
495
+ // ═══════════════════════════════════════════════════════════════════════════
496
+ // WORKFLOW CONTEXT FACTORY
497
+ // ═══════════════════════════════════════════════════════════════════════════
498
+ createWorkflowContext() {
499
+ const self = this;
500
+ // List of known properties for hasOwnProperty checks
501
+ const knownProperties = new Set([
502
+ 'send', 'try', 'do', 'on', 'every', 'log', 'state', 'location',
503
+ 'ai', 'write', 'summarize', 'list', 'extract', 'is', 'decide',
504
+ ]);
505
+ return new Proxy({}, {
506
+ get(_, prop) {
507
+ switch (prop) {
508
+ // Execution modes
509
+ case 'send':
510
+ return self.send.bind(self);
511
+ case 'try':
512
+ return self.try.bind(self);
513
+ case 'do':
514
+ return self.do.bind(self);
515
+ // Event subscriptions and scheduling
516
+ case 'on':
517
+ return self.createOnProxy();
518
+ case 'every':
519
+ return self.createScheduleBuilder();
520
+ // Utilities
521
+ case 'log':
522
+ return self.log.bind(self);
523
+ case 'state':
524
+ return {};
525
+ // Location access (lazy, returns Promise)
526
+ case 'location':
527
+ return self.getLocation();
528
+ // AI Functions - Generation
529
+ case 'ai':
530
+ return aiFunc;
531
+ case 'write':
532
+ return writeFunc;
533
+ case 'summarize':
534
+ return summarizeFunc;
535
+ case 'list':
536
+ return listFunc;
537
+ case 'extract':
538
+ return extractFunc;
539
+ // AI Functions - Classification
540
+ case 'is':
541
+ return isFunc;
542
+ case 'decide':
543
+ return decideFunc;
544
+ default:
545
+ // Domain resolution: $.Noun(id)
546
+ return (id) => self.createDomainProxy(prop, id);
547
+ }
548
+ },
549
+ has(_, prop) {
550
+ // Support `in` operator and hasOwnProperty checks for known properties
551
+ return knownProperties.has(String(prop));
552
+ },
553
+ });
554
+ }
555
+ // ═══════════════════════════════════════════════════════════════════════════
556
+ // EXECUTION MODES
557
+ // ═══════════════════════════════════════════════════════════════════════════
558
+ /**
559
+ * Default retry policy for durable execution
560
+ */
561
+ static DEFAULT_RETRY_POLICY = {
562
+ maxAttempts: 3,
563
+ initialDelayMs: 100,
564
+ maxDelayMs: 30000,
565
+ backoffMultiplier: 2,
566
+ jitter: true,
567
+ };
568
+ static DEFAULT_TRY_TIMEOUT = 30000;
569
+ _stepCache = new Map();
570
+ /**
571
+ * Fire-and-forget event emission (non-blocking, non-durable)
572
+ * Errors are logged but don't propagate (by design for fire-and-forget)
573
+ */
574
+ send(event, data) {
575
+ queueMicrotask(() => {
576
+ this.logAction('send', event, data).catch((error) => {
577
+ console.error(`[send] Failed to log action for ${event}:`, error);
578
+ this.emitSystemError('send.logAction.failed', event, error);
579
+ });
580
+ this.emitEvent(event, data).catch((error) => {
581
+ console.error(`[send] Failed to emit event ${event}:`, error);
582
+ this.emitSystemError('send.emitEvent.failed', event, error);
583
+ });
584
+ this.executeAction(event, data).catch((error) => {
585
+ console.error(`[send] Failed to execute action ${event}:`, error);
586
+ this.emitSystemError('send.executeAction.failed', event, error);
587
+ });
588
+ });
589
+ }
590
+ /**
591
+ * Emit a system error event for monitoring/observability
592
+ * This is a best-effort operation that should never throw
593
+ */
594
+ emitSystemError(errorType, originalEvent, error) {
595
+ try {
596
+ const errorMessage = error instanceof Error ? error.message : String(error);
597
+ const errorStack = error instanceof Error ? error.stack : undefined;
598
+ // Log to console for immediate visibility
599
+ console.error(`[system.${errorType}]`, {
600
+ ns: this.ns,
601
+ originalEvent,
602
+ error: errorMessage,
603
+ stack: errorStack,
604
+ });
605
+ // Try to persist to DLQ for later replay
606
+ this.dlq.add({
607
+ eventId: `system-error-${crypto.randomUUID()}`,
608
+ verb: errorType,
609
+ source: this.ns,
610
+ data: { originalEvent, error: errorMessage },
611
+ error: errorMessage,
612
+ errorStack,
613
+ maxRetries: 3,
614
+ }).catch(() => {
615
+ // Absolute last resort - can't even log to DLQ
616
+ console.error(`[CRITICAL] Failed to add system error to DLQ: ${errorType}`);
617
+ });
618
+ }
619
+ catch (catchError) {
620
+ // Never throw from error handler, but log the failure
621
+ logBestEffortError(catchError, {
622
+ operation: 'emitSystemError',
623
+ source: 'DOBase.emitSystemError',
624
+ context: { errorType, originalEvent, ns: this.ns },
625
+ });
626
+ }
627
+ }
628
+ /**
629
+ * Quick attempt without durability (blocking, non-durable)
630
+ */
631
+ async try(action, data, options) {
632
+ const timeout = options?.timeout ?? DO.DEFAULT_TRY_TIMEOUT;
633
+ const startedAt = new Date();
634
+ const actionRecord = await this.logAction('try', action, data);
635
+ await this.updateActionStatus(actionRecord.id, 'running', { startedAt });
636
+ const timeoutPromise = new Promise((_, reject) => {
637
+ setTimeout(() => {
638
+ reject(new Error(`Action '${action}' timed out after ${timeout}ms`));
639
+ }, timeout);
640
+ });
641
+ try {
642
+ const result = await Promise.race([
643
+ this.executeAction(action, data),
644
+ timeoutPromise,
645
+ ]);
646
+ const completedAt = new Date();
647
+ const duration = completedAt.getTime() - startedAt.getTime();
648
+ await this.completeAction(actionRecord.id, result, { completedAt, duration });
649
+ await this.emitEvent(`${action}.completed`, { result });
650
+ return result;
651
+ }
652
+ catch (error) {
653
+ const completedAt = new Date();
654
+ const duration = completedAt.getTime() - startedAt.getTime();
655
+ const actionError = {
656
+ message: error.message,
657
+ name: error.name,
658
+ stack: error.stack,
659
+ };
660
+ await this.failAction(actionRecord.id, actionError, { completedAt, duration });
661
+ await this.emitEvent(`${action}.failed`, { error: actionError }).catch(() => { });
662
+ throw error;
663
+ }
664
+ }
665
+ /**
666
+ * Durable execution with retries (blocking, durable)
667
+ */
668
+ async do(action, data, options) {
669
+ const retryPolicy = {
670
+ ...DO.DEFAULT_RETRY_POLICY,
671
+ ...options?.retry,
672
+ };
673
+ const stepId = options?.stepId ?? this.generateStepId(action, data);
674
+ const cachedResult = this._stepCache.get(stepId);
675
+ if (cachedResult) {
676
+ return cachedResult.result;
677
+ }
678
+ const startedAt = new Date();
679
+ const actionRecord = await this.logAction('do', action, data);
680
+ let lastError;
681
+ let attempts = 0;
682
+ for (let attempt = 1; attempt <= retryPolicy.maxAttempts; attempt++) {
683
+ attempts = attempt;
684
+ const status = attempt === 1 ? 'running' : 'retrying';
685
+ await this.updateActionStatus(actionRecord.id, status, { startedAt, attempts });
686
+ try {
687
+ const result = await this.executeAction(action, data);
688
+ const completedAt = new Date();
689
+ const duration = completedAt.getTime() - startedAt.getTime();
690
+ await this.completeAction(actionRecord.id, result, { completedAt, duration, attempts });
691
+ this._stepCache.set(stepId, { result, completedAt: completedAt.getTime() });
692
+ await this.persistStepResult(stepId, result);
693
+ await this.emitEvent(`${action}.completed`, { result });
694
+ return result;
695
+ }
696
+ catch (error) {
697
+ lastError = error;
698
+ await this.updateActionAttempts(actionRecord.id, attempts);
699
+ if (attempt < retryPolicy.maxAttempts) {
700
+ const delay = this.calculateBackoffDelay(attempt, retryPolicy);
701
+ await this.sleep(delay);
702
+ }
703
+ }
704
+ }
705
+ const completedAt = new Date();
706
+ const duration = completedAt.getTime() - startedAt.getTime();
707
+ const actionError = {
708
+ message: lastError.message,
709
+ name: lastError.name,
710
+ stack: lastError.stack,
711
+ };
712
+ await this.failAction(actionRecord.id, actionError, { completedAt, duration, attempts });
713
+ await this.emitEvent(`${action}.failed`, { error: actionError });
714
+ throw lastError;
715
+ }
716
+ calculateBackoffDelay(attempt, policy) {
717
+ let delay = policy.initialDelayMs * Math.pow(policy.backoffMultiplier, attempt - 1);
718
+ delay = Math.min(delay, policy.maxDelayMs);
719
+ if (policy.jitter) {
720
+ const jitterRange = delay * 0.25;
721
+ delay += Math.random() * jitterRange;
722
+ }
723
+ return Math.floor(delay);
724
+ }
725
+ generateStepId(action, data) {
726
+ const content = JSON.stringify({ action, data });
727
+ let hash = 0;
728
+ for (let i = 0; i < content.length; i++) {
729
+ const char = content.charCodeAt(i);
730
+ hash = ((hash << 5) - hash) + char;
731
+ hash = hash & hash;
732
+ }
733
+ return `${action}:${Math.abs(hash).toString(36)}`;
734
+ }
735
+ async persistStepResult(stepId, result) {
736
+ try {
737
+ await this.ctx.storage.put(`step:${stepId}`, { result, completedAt: Date.now() });
738
+ }
739
+ catch (error) {
740
+ logBestEffortError(error, {
741
+ operation: 'persistStepResult',
742
+ source: 'DOBase.persistStepResult',
743
+ context: { stepId, ns: this.ns },
744
+ });
745
+ }
746
+ }
747
+ async loadPersistedSteps() {
748
+ try {
749
+ const steps = await this.ctx.storage.list({ prefix: 'step:' });
750
+ for (const [key, value] of steps) {
751
+ const stepId = key.replace('step:', '');
752
+ const data = value;
753
+ this._stepCache.set(stepId, data);
754
+ }
755
+ }
756
+ catch (error) {
757
+ logBestEffortError(error, {
758
+ operation: 'loadPersistedSteps',
759
+ source: 'DOBase.loadPersistedSteps',
760
+ context: { ns: this.ns },
761
+ });
762
+ }
763
+ }
764
+ // ═══════════════════════════════════════════════════════════════════════════
765
+ // ACTION LOGGING (append-only)
766
+ // ═══════════════════════════════════════════════════════════════════════════
767
+ async logAction(durability, verb, input) {
768
+ const id = crypto.randomUUID();
769
+ await this.db
770
+ .insert(schema.actions)
771
+ // @ts-expect-error - Schema field names may differ
772
+ .values({
773
+ id,
774
+ verb,
775
+ target: this.ns,
776
+ actor: this._currentActor,
777
+ input: input,
778
+ durability,
779
+ status: 'pending',
780
+ createdAt: new Date(),
781
+ });
782
+ return { id, rowid: 0 };
783
+ }
784
+ async updateActionStatus(actionId, status, fields) {
785
+ try {
786
+ const updateData = { status };
787
+ if (fields?.startedAt) {
788
+ updateData.startedAt = fields.startedAt;
789
+ }
790
+ if (fields?.attempts !== undefined) {
791
+ updateData.options = JSON.stringify({ attempts: fields.attempts });
792
+ }
793
+ await this.db
794
+ .update(schema.actions)
795
+ .set(updateData)
796
+ .where(eq(schema.actions.id, actionId));
797
+ }
798
+ catch (error) {
799
+ logBestEffortError(error, {
800
+ operation: 'updateActionStatus',
801
+ source: 'DOBase.updateActionStatus',
802
+ context: { actionId, status, ns: this.ns },
803
+ });
804
+ }
805
+ }
806
+ async updateActionAttempts(actionId, attempts) {
807
+ try {
808
+ await this.db
809
+ .update(schema.actions)
810
+ .set({ options: JSON.stringify({ attempts }) })
811
+ .where(eq(schema.actions.id, actionId));
812
+ }
813
+ catch (error) {
814
+ logBestEffortError(error, {
815
+ operation: 'updateActionAttempts',
816
+ source: 'DOBase.updateActionAttempts',
817
+ context: { actionId, attempts, ns: this.ns },
818
+ });
819
+ }
820
+ }
821
+ async completeAction(actionId, output, fields) {
822
+ try {
823
+ const updateData = {
824
+ status: 'completed',
825
+ output: output,
826
+ };
827
+ if (fields?.completedAt) {
828
+ updateData.completedAt = fields.completedAt;
829
+ }
830
+ if (fields?.duration !== undefined) {
831
+ updateData.duration = fields.duration;
832
+ }
833
+ if (fields?.attempts !== undefined) {
834
+ updateData.options = JSON.stringify({ attempts: fields.attempts });
835
+ }
836
+ await this.db
837
+ .update(schema.actions)
838
+ .set(updateData)
839
+ .where(eq(schema.actions.id, actionId));
840
+ }
841
+ catch (error) {
842
+ logBestEffortError(error, {
843
+ operation: 'completeAction',
844
+ source: 'DOBase.completeAction',
845
+ context: { actionId, ns: this.ns },
846
+ });
847
+ }
848
+ }
849
+ async failAction(actionId, error, fields) {
850
+ try {
851
+ const updateData = {
852
+ status: 'failed',
853
+ error: error,
854
+ };
855
+ if (fields?.completedAt) {
856
+ updateData.completedAt = fields.completedAt;
857
+ }
858
+ if (fields?.duration !== undefined) {
859
+ updateData.duration = fields.duration;
860
+ }
861
+ if (fields?.attempts !== undefined) {
862
+ updateData.options = JSON.stringify({ attempts: fields.attempts });
863
+ }
864
+ await this.db
865
+ .update(schema.actions)
866
+ .set(updateData)
867
+ .where(eq(schema.actions.id, actionId));
868
+ }
869
+ catch (catchError) {
870
+ logBestEffortError(catchError, {
871
+ operation: 'failAction',
872
+ source: 'DOBase.failAction',
873
+ context: { actionId, errorMessage: error.message, ns: this.ns },
874
+ });
875
+ }
876
+ }
877
+ /**
878
+ * Execute an action - override in subclasses to handle specific actions
879
+ */
880
+ async executeAction(action, data) {
881
+ throw new Error(`Unknown action: ${action}`);
882
+ }
883
+ // ═══════════════════════════════════════════════════════════════════════════
884
+ // EVENT EMISSION
885
+ // ═══════════════════════════════════════════════════════════════════════════
886
+ async emitEvent(verb, data) {
887
+ const eventId = crypto.randomUUID();
888
+ let dbError = null;
889
+ let pipelineError = null;
890
+ // Attempt database insert with error capture
891
+ try {
892
+ await this.db.insert(schema.events).values({
893
+ id: eventId,
894
+ verb,
895
+ source: this.ns,
896
+ data: data,
897
+ sequence: 0,
898
+ streamed: false,
899
+ createdAt: new Date(),
900
+ });
901
+ }
902
+ catch (error) {
903
+ dbError = error instanceof Error ? error : new Error(String(error));
904
+ console.error(`[emitEvent] Database insert failed for ${verb}:`, dbError.message);
905
+ // Add to DLQ for retry
906
+ try {
907
+ await this.dlq.add({
908
+ eventId,
909
+ verb,
910
+ source: this.ns,
911
+ data: data,
912
+ error: dbError.message,
913
+ errorStack: dbError.stack,
914
+ maxRetries: 3,
915
+ });
916
+ }
917
+ catch (dlqError) {
918
+ console.error(`[emitEvent] Failed to add to DLQ:`, dlqError);
919
+ }
920
+ }
921
+ // Attempt pipeline send with error capture and retry
922
+ if (this.env.PIPELINE) {
923
+ const maxPipelineRetries = 3;
924
+ const baseDelay = 100;
925
+ for (let attempt = 1; attempt <= maxPipelineRetries; attempt++) {
926
+ try {
927
+ await this.env.PIPELINE.send([{
928
+ verb,
929
+ source: this.ns,
930
+ $context: this.ns,
931
+ data,
932
+ timestamp: new Date().toISOString(),
933
+ }]);
934
+ pipelineError = null; // Success - clear any previous error
935
+ break;
936
+ }
937
+ catch (error) {
938
+ pipelineError = error instanceof Error ? error : new Error(String(error));
939
+ console.error(`[emitEvent] Pipeline send attempt ${attempt}/${maxPipelineRetries} failed for ${verb}:`, pipelineError.message);
940
+ if (attempt < maxPipelineRetries) {
941
+ // Exponential backoff
942
+ const delay = baseDelay * Math.pow(2, attempt - 1);
943
+ await this.sleep(delay);
944
+ }
945
+ }
946
+ }
947
+ // If all pipeline retries failed, log for metrics
948
+ if (pipelineError) {
949
+ console.error(`[emitEvent] Pipeline send failed after ${maxPipelineRetries} attempts for ${verb}`);
950
+ }
951
+ }
952
+ // Emit system error event if either operation failed (for observability)
953
+ if (dbError || pipelineError) {
954
+ try {
955
+ // Use console for metrics visibility (can be scraped by log aggregators)
956
+ console.error('[metrics.event.emission.failure]', {
957
+ ns: this.ns,
958
+ verb,
959
+ dbError: dbError?.message ?? null,
960
+ pipelineError: pipelineError?.message ?? null,
961
+ });
962
+ }
963
+ catch {
964
+ // Never throw from error reporting
965
+ }
966
+ }
967
+ }
968
+ /**
969
+ * Emit an event (public wrapper for emitEvent)
970
+ */
971
+ async emit(verb, data) {
972
+ return this.emitEvent(verb, data);
973
+ }
974
+ // ═══════════════════════════════════════════════════════════════════════════
975
+ // NOUN FK RESOLUTION
976
+ // ═══════════════════════════════════════════════════════════════════════════
977
+ async resolveNounToFK(noun) {
978
+ if (!noun || noun.trim() === '') {
979
+ throw new Error('Noun name cannot be empty');
980
+ }
981
+ if (!isValidNounName(noun)) {
982
+ throw new Error(`Invalid noun '${noun}': must be PascalCase`);
983
+ }
984
+ const cached = this._typeCache.get(noun);
985
+ if (cached !== undefined) {
986
+ return cached;
987
+ }
988
+ const results = await this.db
989
+ .select({
990
+ noun: schema.nouns.noun,
991
+ rowid: sql `rowid`,
992
+ })
993
+ .from(schema.nouns)
994
+ .where(eq(schema.nouns.noun, noun));
995
+ if (results.length === 0) {
996
+ throw new Error(`Noun '${noun}' not found in nouns table. Register it first with registerNoun().`);
997
+ }
998
+ const fk = results[0].rowid;
999
+ this._typeCache.set(noun, fk);
1000
+ return fk;
1001
+ }
1002
+ async registerNoun(noun, config) {
1003
+ if (!noun || noun.trim() === '') {
1004
+ throw new Error('Noun name cannot be empty');
1005
+ }
1006
+ if (!isValidNounName(noun)) {
1007
+ throw new Error(`Invalid noun '${noun}': must be PascalCase`);
1008
+ }
1009
+ const cached = this._typeCache.get(noun);
1010
+ if (cached !== undefined) {
1011
+ return cached;
1012
+ }
1013
+ const existing = await this.db
1014
+ .select({
1015
+ noun: schema.nouns.noun,
1016
+ rowid: sql `rowid`,
1017
+ })
1018
+ .from(schema.nouns)
1019
+ .where(eq(schema.nouns.noun, noun));
1020
+ if (existing.length > 0) {
1021
+ const fk = existing[0].rowid;
1022
+ this._typeCache.set(noun, fk);
1023
+ return fk;
1024
+ }
1025
+ await this.db.insert(schema.nouns).values({
1026
+ noun,
1027
+ plural: config?.plural ?? `${noun}s`,
1028
+ description: config?.description ?? null,
1029
+ schema: config?.schema ? JSON.stringify(config.schema) : null,
1030
+ doClass: config?.doClass ?? null,
1031
+ });
1032
+ const inserted = await this.db
1033
+ .select({
1034
+ noun: schema.nouns.noun,
1035
+ rowid: sql `rowid`,
1036
+ })
1037
+ .from(schema.nouns)
1038
+ .where(eq(schema.nouns.noun, noun));
1039
+ if (inserted.length === 0) {
1040
+ throw new Error(`Failed to register noun '${noun}'`);
1041
+ }
1042
+ const fk = inserted[0].rowid;
1043
+ this._typeCache.set(noun, fk);
1044
+ return fk;
1045
+ }
1046
+ // ═══════════════════════════════════════════════════════════════════════════
1047
+ // TYPED COLLECTION ACCESSORS
1048
+ // ═══════════════════════════════════════════════════════════════════════════
1049
+ collection(noun) {
1050
+ if (!noun || noun.trim() === '') {
1051
+ throw new Error('Noun name cannot be empty');
1052
+ }
1053
+ if (!isValidNounName(noun)) {
1054
+ throw new Error(`Invalid noun '${noun}': must be PascalCase`);
1055
+ }
1056
+ const self = this;
1057
+ return {
1058
+ get: async (id) => {
1059
+ const typeFK = await self.resolveNounToFK(noun);
1060
+ const results = await self.db.select().from(schema.things);
1061
+ const result = results.find((r) => r.id === id && r.type === typeFK && !r.deleted);
1062
+ if (!result)
1063
+ return null;
1064
+ const data = result.data;
1065
+ return { $id: result.id, $type: noun, ...data };
1066
+ },
1067
+ list: async () => {
1068
+ const typeFK = await self.resolveNounToFK(noun);
1069
+ const results = await self.db.select().from(schema.things);
1070
+ return results
1071
+ .filter((r) => r.type === typeFK && !r.deleted)
1072
+ .map((r) => {
1073
+ const data = r.data;
1074
+ return { $id: r.id, $type: noun, ...data };
1075
+ });
1076
+ },
1077
+ find: async (query) => {
1078
+ const typeFK = await self.resolveNounToFK(noun);
1079
+ const results = await self.db.select().from(schema.things);
1080
+ return results
1081
+ .filter((r) => {
1082
+ if (r.type !== typeFK || r.deleted)
1083
+ return false;
1084
+ const data = r.data;
1085
+ if (!data)
1086
+ return false;
1087
+ return Object.entries(query).every(([key, value]) => data[key] === value);
1088
+ })
1089
+ .map((r) => {
1090
+ const data = r.data;
1091
+ return { $id: r.id, $type: noun, ...data };
1092
+ });
1093
+ },
1094
+ create: async (data) => {
1095
+ const typeFK = await self.resolveNounToFK(noun);
1096
+ const id = data.$id || crypto.randomUUID();
1097
+ await self.db.insert(schema.things).values({
1098
+ id,
1099
+ type: typeFK,
1100
+ branch: self.currentBranch,
1101
+ data: data,
1102
+ deleted: false,
1103
+ });
1104
+ self._typeCache.set(noun, typeFK);
1105
+ return { ...data, $id: id, $type: noun };
1106
+ },
1107
+ update: async (id, data) => {
1108
+ const result = await self.things.update(id, {
1109
+ data: data,
1110
+ });
1111
+ return {
1112
+ $id: result.$id,
1113
+ $type: noun,
1114
+ ...result.data,
1115
+ $rowid: result.version ?? 0,
1116
+ };
1117
+ },
1118
+ delete: async (id) => {
1119
+ const result = await self.things.delete(id);
1120
+ return {
1121
+ $id: result.$id,
1122
+ $type: noun,
1123
+ ...result.data,
1124
+ $rowid: result.version ?? 0,
1125
+ };
1126
+ },
1127
+ };
1128
+ }
1129
+ /**
1130
+ * Relationships table accessor
1131
+ */
1132
+ get relationships() {
1133
+ return {
1134
+ create: async (data) => {
1135
+ const id = crypto.randomUUID();
1136
+ await this.db.insert(schema.relationships).values({
1137
+ id,
1138
+ verb: data.verb,
1139
+ from: data.from,
1140
+ to: data.to,
1141
+ data: data.data,
1142
+ createdAt: new Date(),
1143
+ });
1144
+ return { id };
1145
+ },
1146
+ list: async (query) => {
1147
+ const results = await this.db.select().from(schema.relationships);
1148
+ return results
1149
+ .filter((r) => {
1150
+ if (query?.from && r.from !== query.from)
1151
+ return false;
1152
+ if (query?.to && r.to !== query.to)
1153
+ return false;
1154
+ if (query?.verb && r.verb !== query.verb)
1155
+ return false;
1156
+ return true;
1157
+ })
1158
+ .map((r) => ({
1159
+ id: r.id,
1160
+ verb: r.verb,
1161
+ from: r.from,
1162
+ to: r.to,
1163
+ data: r.data,
1164
+ createdAt: r.createdAt,
1165
+ }));
1166
+ },
1167
+ };
1168
+ }
1169
+ // ═══════════════════════════════════════════════════════════════════════════
1170
+ // PROXY FACTORIES
1171
+ // ═══════════════════════════════════════════════════════════════════════════
1172
+ createOnProxy() {
1173
+ const self = this;
1174
+ return new Proxy({}, {
1175
+ get: (_, noun) => {
1176
+ return new Proxy({}, {
1177
+ get: (_, verb) => {
1178
+ return (handler, options) => {
1179
+ const eventKey = `${noun}.${verb}`;
1180
+ const registrations = self._eventHandlers.get(eventKey) ?? [];
1181
+ const handlerName = options?.name
1182
+ || handler.name
1183
+ || `handler_${++self._handlerCounter}`;
1184
+ const registration = {
1185
+ name: handlerName,
1186
+ priority: options?.priority ?? 0,
1187
+ registeredAt: Date.now(),
1188
+ sourceNs: self.ns,
1189
+ handler,
1190
+ filter: options?.filter,
1191
+ maxRetries: options?.maxRetries ?? 3,
1192
+ executionCount: 0,
1193
+ successCount: 0,
1194
+ failureCount: 0,
1195
+ };
1196
+ registrations.push(registration);
1197
+ registrations.sort((a, b) => {
1198
+ if (b.priority !== a.priority) {
1199
+ return b.priority - a.priority;
1200
+ }
1201
+ return a.registeredAt - b.registeredAt;
1202
+ });
1203
+ self._eventHandlers.set(eventKey, registrations);
1204
+ };
1205
+ },
1206
+ });
1207
+ },
1208
+ });
1209
+ }
1210
+ createScheduleBuilder() {
1211
+ const self = this;
1212
+ const config = {
1213
+ state: this.ctx,
1214
+ onScheduleRegistered: (cron, name, handler) => {
1215
+ self._scheduleHandlers.set(name, handler);
1216
+ self.scheduleManager.schedule(cron, name).catch((error) => {
1217
+ console.error(`Failed to register schedule ${name}:`, error);
1218
+ });
1219
+ },
1220
+ };
1221
+ return createScheduleBuilderProxy(config);
1222
+ }
1223
+ createDomainProxy(noun, id) {
1224
+ const self = this;
1225
+ return new Proxy({}, {
1226
+ get(_, method) {
1227
+ if (method === 'then' || method === 'catch' || method === 'finally') {
1228
+ return undefined;
1229
+ }
1230
+ return (...args) => {
1231
+ return self.invokeDomainMethod(noun, id, method, args);
1232
+ };
1233
+ },
1234
+ });
1235
+ }
1236
+ async invokeDomainMethod(noun, id, method, args) {
1237
+ const localMethod = this[method];
1238
+ if (typeof localMethod === 'function') {
1239
+ try {
1240
+ return await localMethod.apply(this, args);
1241
+ }
1242
+ catch (error) {
1243
+ throw error;
1244
+ }
1245
+ }
1246
+ return this.invokeCrossDOMethod(noun, id, method, args);
1247
+ }
1248
+ // Circuit breaker state for cross-DO calls (per target DO)
1249
+ static _circuitBreakers = new Map();
1250
+ // Circuit breaker configuration
1251
+ static CIRCUIT_BREAKER_CONFIG = {
1252
+ failureThreshold: 5,
1253
+ resetTimeoutMs: 30000,
1254
+ halfOpenRequests: 1,
1255
+ };
1256
+ // Cross-DO retry configuration
1257
+ static CROSS_DO_RETRY_CONFIG = {
1258
+ maxAttempts: 3,
1259
+ initialDelayMs: 100,
1260
+ maxDelayMs: 5000,
1261
+ backoffMultiplier: 2,
1262
+ retryableStatuses: [500, 502, 503, 504],
1263
+ retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED'],
1264
+ };
1265
+ // Default timeout for cross-DO calls
1266
+ static CROSS_DO_TIMEOUT_MS = 30000;
1267
+ async invokeCrossDOMethod(noun, id, method, args, options) {
1268
+ if (!this.env.DO) {
1269
+ throw new Error(`Method '${method}' not found and DO namespace not configured for cross-DO calls`);
1270
+ }
1271
+ const targetNs = `${noun}/${id}`;
1272
+ const timeout = options?.timeout ?? DO.CROSS_DO_TIMEOUT_MS;
1273
+ // Check circuit breaker
1274
+ const circuitState = this.checkCircuitBreaker(targetNs);
1275
+ if (circuitState === 'open') {
1276
+ throw new CrossDOError('CIRCUIT_BREAKER_OPEN', `Circuit breaker open for ${targetNs}`, { targetDO: targetNs, source: this.ns });
1277
+ }
1278
+ const doNamespace = this.env.DO;
1279
+ const doId = doNamespace.idFromName(targetNs);
1280
+ const stub = doNamespace.get(doId);
1281
+ const { maxAttempts, initialDelayMs, maxDelayMs, backoffMultiplier, retryableStatuses } = DO.CROSS_DO_RETRY_CONFIG;
1282
+ let lastError;
1283
+ let attempts = 0;
1284
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1285
+ attempts = attempt;
1286
+ try {
1287
+ const response = await this.fetchWithCrossDOTimeout(stub, `https://${targetNs}/rpc/${method}`, { args }, timeout);
1288
+ // Check for rate limiting
1289
+ if (response.status === 429) {
1290
+ const retryAfter = response.headers.get('Retry-After');
1291
+ const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : initialDelayMs * Math.pow(backoffMultiplier, attempt - 1);
1292
+ if (attempt < maxAttempts) {
1293
+ await this.sleep(Math.min(delay, maxDelayMs));
1294
+ continue;
1295
+ }
1296
+ }
1297
+ // Non-retryable client errors
1298
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
1299
+ const errorText = await response.text();
1300
+ this.recordCircuitBreakerSuccess(targetNs);
1301
+ throw new CrossDOError('CROSS_DO_CLIENT_ERROR', `Cross-DO RPC failed: ${response.status} - ${errorText}`, { targetDO: targetNs, method, source: this.ns });
1302
+ }
1303
+ // Retryable server errors
1304
+ if (!response.ok && retryableStatuses.includes(response.status)) {
1305
+ const errorText = await response.text();
1306
+ lastError = new Error(`Cross-DO RPC failed: ${response.status} - ${errorText}`);
1307
+ if (attempt < maxAttempts) {
1308
+ const delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, attempt - 1), maxDelayMs);
1309
+ await this.sleep(delay);
1310
+ continue;
1311
+ }
1312
+ }
1313
+ if (!response.ok) {
1314
+ const errorText = await response.text();
1315
+ this.recordCircuitBreakerFailure(targetNs);
1316
+ throw new CrossDOError('CROSS_DO_ERROR', `Cross-DO RPC failed: ${response.status} - ${errorText}`, { targetDO: targetNs, method, attempts, source: this.ns });
1317
+ }
1318
+ const result = await response.json();
1319
+ if (result.error) {
1320
+ this.recordCircuitBreakerFailure(targetNs);
1321
+ throw new CrossDOError('CROSS_DO_ERROR', result.error, { targetDO: targetNs, method, source: this.ns });
1322
+ }
1323
+ // Success - record and return
1324
+ this.recordCircuitBreakerSuccess(targetNs);
1325
+ return result.result;
1326
+ }
1327
+ catch (error) {
1328
+ lastError = error instanceof Error ? error : new Error(String(error));
1329
+ // Check for timeout
1330
+ if (lastError.name === 'AbortError' || lastError.message.includes('timeout')) {
1331
+ this.recordCircuitBreakerFailure(targetNs);
1332
+ throw new CrossDOError('CROSS_DO_TIMEOUT', `Cross-DO call to ${targetNs}.${method}() timed out after ${timeout}ms`, { targetDO: targetNs, method, source: this.ns });
1333
+ }
1334
+ // Check if error is retryable
1335
+ const isRetryable = DO.CROSS_DO_RETRY_CONFIG.retryableErrors.some(errType => lastError.message.includes(errType));
1336
+ if (isRetryable && attempt < maxAttempts) {
1337
+ const delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, attempt - 1), maxDelayMs);
1338
+ await this.sleep(delay);
1339
+ continue;
1340
+ }
1341
+ // Not retryable or exhausted retries
1342
+ this.recordCircuitBreakerFailure(targetNs);
1343
+ throw lastError;
1344
+ }
1345
+ }
1346
+ // Exhausted all retries
1347
+ this.recordCircuitBreakerFailure(targetNs);
1348
+ throw new CrossDOError('CROSS_DO_ERROR', `Cross-DO call failed after ${attempts} attempts`, {
1349
+ targetDO: targetNs,
1350
+ method,
1351
+ attempts,
1352
+ source: this.ns,
1353
+ originalError: lastError?.message,
1354
+ });
1355
+ }
1356
+ /**
1357
+ * Fetch with timeout for cross-DO calls
1358
+ */
1359
+ async fetchWithCrossDOTimeout(stub, url, body, timeoutMs) {
1360
+ const controller = new AbortController();
1361
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1362
+ try {
1363
+ return await stub.fetch(new Request(url, {
1364
+ method: 'POST',
1365
+ headers: { 'Content-Type': 'application/json' },
1366
+ body: JSON.stringify(body),
1367
+ signal: controller.signal,
1368
+ }));
1369
+ }
1370
+ finally {
1371
+ clearTimeout(timeoutId);
1372
+ }
1373
+ }
1374
+ /**
1375
+ * Check circuit breaker state for a target DO
1376
+ */
1377
+ checkCircuitBreaker(targetNs) {
1378
+ const breaker = DO._circuitBreakers.get(targetNs);
1379
+ if (!breaker)
1380
+ return 'closed';
1381
+ const { failureThreshold, resetTimeoutMs } = DO.CIRCUIT_BREAKER_CONFIG;
1382
+ if (breaker.state === 'open') {
1383
+ // Check if enough time has passed to try half-open
1384
+ if (Date.now() - breaker.lastFailure >= resetTimeoutMs) {
1385
+ breaker.state = 'half-open';
1386
+ return 'half-open';
1387
+ }
1388
+ return 'open';
1389
+ }
1390
+ if (breaker.failures >= failureThreshold) {
1391
+ breaker.state = 'open';
1392
+ return 'open';
1393
+ }
1394
+ return breaker.state;
1395
+ }
1396
+ /**
1397
+ * Record a successful cross-DO call (reset circuit breaker)
1398
+ */
1399
+ recordCircuitBreakerSuccess(targetNs) {
1400
+ DO._circuitBreakers.set(targetNs, {
1401
+ failures: 0,
1402
+ lastFailure: 0,
1403
+ state: 'closed',
1404
+ });
1405
+ }
1406
+ /**
1407
+ * Record a failed cross-DO call
1408
+ */
1409
+ recordCircuitBreakerFailure(targetNs) {
1410
+ const breaker = DO._circuitBreakers.get(targetNs) ?? {
1411
+ failures: 0,
1412
+ lastFailure: 0,
1413
+ state: 'closed',
1414
+ };
1415
+ breaker.failures++;
1416
+ breaker.lastFailure = Date.now();
1417
+ if (breaker.failures >= DO.CIRCUIT_BREAKER_CONFIG.failureThreshold) {
1418
+ breaker.state = 'open';
1419
+ }
1420
+ DO._circuitBreakers.set(targetNs, breaker);
1421
+ }
1422
+ /**
1423
+ * Reset all static state - ONLY for testing.
1424
+ * This clears accumulated static Maps that persist across test runs.
1425
+ */
1426
+ static _resetTestState() {
1427
+ DO._circuitBreakers.clear();
1428
+ }
1429
+ // ═══════════════════════════════════════════════════════════════════════════
1430
+ // EVENT HANDLER MANAGEMENT
1431
+ // ═══════════════════════════════════════════════════════════════════════════
1432
+ getEventHandlers(eventKey) {
1433
+ const registrations = this._eventHandlers.get(eventKey) ?? [];
1434
+ return registrations.map((r) => r.handler);
1435
+ }
1436
+ getHandlersByPriority(eventKey) {
1437
+ const registrations = this._eventHandlers.get(eventKey) ?? [];
1438
+ return registrations.map((r) => ({ handler: r.handler, priority: r.priority }));
1439
+ }
1440
+ getHandlerMetadata(eventKey, handlerName) {
1441
+ const registrations = this._eventHandlers.get(eventKey) ?? [];
1442
+ return registrations.find((r) => r.name === handlerName);
1443
+ }
1444
+ getHandlerRegistrations(eventKey) {
1445
+ return this._eventHandlers.get(eventKey) ?? [];
1446
+ }
1447
+ listAllHandlers() {
1448
+ return new Map(this._eventHandlers);
1449
+ }
1450
+ collectMatchingHandlers(noun, verb) {
1451
+ const matchingHandlers = [];
1452
+ const exactKey = `${noun}.${verb}`;
1453
+ for (const reg of this._eventHandlers.get(exactKey) ?? []) {
1454
+ matchingHandlers.push({ registration: reg, isWildcard: false });
1455
+ }
1456
+ for (const reg of this._eventHandlers.get(`*.${verb}`) ?? []) {
1457
+ matchingHandlers.push({ registration: reg, isWildcard: true });
1458
+ }
1459
+ for (const reg of this._eventHandlers.get(`${noun}.*`) ?? []) {
1460
+ matchingHandlers.push({ registration: reg, isWildcard: true });
1461
+ }
1462
+ for (const reg of this._eventHandlers.get('*.*') ?? []) {
1463
+ matchingHandlers.push({ registration: reg, isWildcard: true });
1464
+ }
1465
+ matchingHandlers.sort((a, b) => {
1466
+ if (b.registration.priority !== a.registration.priority) {
1467
+ return b.registration.priority - a.registration.priority;
1468
+ }
1469
+ if (a.isWildcard !== b.isWildcard) {
1470
+ return a.isWildcard ? 1 : -1;
1471
+ }
1472
+ return a.registration.registeredAt - b.registration.registeredAt;
1473
+ });
1474
+ return matchingHandlers.map((h) => h.registration);
1475
+ }
1476
+ async dispatchEventToHandlers(event) {
1477
+ const sourceParts = event.source.split('/');
1478
+ const noun = sourceParts[sourceParts.length - 2] || '';
1479
+ const registrations = this.collectMatchingHandlers(noun, event.verb);
1480
+ let handled = 0;
1481
+ let filtered = 0;
1482
+ let wildcardMatches = 0;
1483
+ const errors = [];
1484
+ const dlqEntries = [];
1485
+ const exactKey = `${noun}.${event.verb}`;
1486
+ const exactRegistrations = new Set((this._eventHandlers.get(exactKey) ?? []).map((r) => r.name));
1487
+ for (const registration of registrations) {
1488
+ if (!exactRegistrations.has(registration.name)) {
1489
+ wildcardMatches++;
1490
+ }
1491
+ if (registration.filter) {
1492
+ try {
1493
+ const shouldExecute = await registration.filter(event);
1494
+ if (!shouldExecute) {
1495
+ filtered++;
1496
+ continue;
1497
+ }
1498
+ }
1499
+ catch {
1500
+ filtered++;
1501
+ continue;
1502
+ }
1503
+ }
1504
+ registration.executionCount++;
1505
+ registration.lastExecutedAt = Date.now();
1506
+ try {
1507
+ await registration.handler(event);
1508
+ registration.successCount++;
1509
+ handled++;
1510
+ }
1511
+ catch (e) {
1512
+ registration.failureCount++;
1513
+ const error = e instanceof Error ? e : new Error(String(e));
1514
+ errors.push(error);
1515
+ try {
1516
+ const dlqEntry = await this.dlq.add({
1517
+ eventId: event.id,
1518
+ verb: `${noun}.${event.verb}`,
1519
+ source: event.source,
1520
+ data: event.data,
1521
+ error: error.message,
1522
+ errorStack: error.stack,
1523
+ maxRetries: registration.maxRetries,
1524
+ });
1525
+ dlqEntries.push(dlqEntry.id);
1526
+ }
1527
+ catch {
1528
+ console.error('Failed to add event to DLQ');
1529
+ }
1530
+ }
1531
+ }
1532
+ return { handled, errors, dlqEntries, filtered, wildcardMatches };
1533
+ }
1534
+ unregisterEventHandler(eventKey, handler) {
1535
+ const registrations = this._eventHandlers.get(eventKey);
1536
+ if (!registrations) {
1537
+ return false;
1538
+ }
1539
+ const index = registrations.findIndex((r) => r.handler === handler);
1540
+ if (index > -1) {
1541
+ registrations.splice(index, 1);
1542
+ return true;
1543
+ }
1544
+ return false;
1545
+ }
1546
+ // ═══════════════════════════════════════════════════════════════════════════
1547
+ // RESOLUTION
1548
+ // ═══════════════════════════════════════════════════════════════════════════
1549
+ /**
1550
+ * Resolve any URL to a Thing (local, cross-DO, or external)
1551
+ */
1552
+ async resolve(url) {
1553
+ const parsed = new URL(url);
1554
+ const ns = `${parsed.protocol}//${parsed.host}`;
1555
+ const path = parsed.pathname.slice(1);
1556
+ const ref = parsed.hash.slice(1) || 'main';
1557
+ if (ns === this.ns) {
1558
+ return this.resolveLocal(path, ref);
1559
+ }
1560
+ else {
1561
+ return this.resolveCrossDO(ns, path, ref);
1562
+ }
1563
+ }
1564
+ async resolveLocal(path, ref) {
1565
+ const parsed = parseNounId(path);
1566
+ const branch = parsed.branch ?? (ref || this.currentBranch);
1567
+ const thingId = parsed.id;
1568
+ if (parsed.version !== undefined || parsed.relativeVersion !== undefined) {
1569
+ const versions = await this.things.versions(thingId);
1570
+ if (versions.length === 0) {
1571
+ throw new Error(`Thing not found: ${path}`);
1572
+ }
1573
+ let targetVersion;
1574
+ if (parsed.version !== undefined) {
1575
+ const versionIndex = parsed.version - 1;
1576
+ if (versionIndex < 0 || versionIndex >= versions.length) {
1577
+ throw new Error(`Thing not found: ${path}`);
1578
+ }
1579
+ targetVersion = versions[versionIndex];
1580
+ }
1581
+ else if (parsed.relativeVersion !== undefined) {
1582
+ const targetIndex = versions.length - 1 - parsed.relativeVersion;
1583
+ if (targetIndex < 0) {
1584
+ throw new Error(`Relative version @~${parsed.relativeVersion} exceeds available versions (${versions.length} total)`);
1585
+ }
1586
+ targetVersion = versions[targetIndex];
1587
+ }
1588
+ if (!targetVersion) {
1589
+ throw new Error(`Version not found for path: ${path}`);
1590
+ }
1591
+ const fullId = this.ns ? `${this.ns}/${parsed.noun}/${parsed.id}` : `${parsed.noun}/${parsed.id}`;
1592
+ return {
1593
+ $id: fullId,
1594
+ $type: parsed.noun,
1595
+ name: targetVersion.name ?? undefined,
1596
+ data: targetVersion.data ?? undefined,
1597
+ };
1598
+ }
1599
+ const options = {};
1600
+ if (branch && branch !== 'main') {
1601
+ options.branch = branch;
1602
+ }
1603
+ const thing = await this.things.get(thingId, options);
1604
+ if (!thing) {
1605
+ throw new Error(`Thing not found: ${path}`);
1606
+ }
1607
+ const fullId = this.ns ? `${this.ns}/${parsed.noun}/${parsed.id}` : `${parsed.noun}/${parsed.id}`;
1608
+ return {
1609
+ $id: fullId,
1610
+ $type: parsed.noun,
1611
+ name: thing.name ?? undefined,
1612
+ data: thing.data ?? undefined,
1613
+ };
1614
+ }
1615
+ async resolveCrossDO(ns, path, ref) {
1616
+ const obj = await this.objects.get(ns);
1617
+ if (!obj) {
1618
+ throw new Error(`Unknown namespace: ${ns}`);
1619
+ }
1620
+ if (!this.env.DO) {
1621
+ throw new Error('DO namespace binding not configured');
1622
+ }
1623
+ const doNamespace = this.env.DO;
1624
+ const id = doNamespace.idFromString(obj.id);
1625
+ const stub = doNamespace.get(id);
1626
+ const resolveUrl = new URL(`${ns}/resolve`);
1627
+ resolveUrl.searchParams.set('path', path);
1628
+ resolveUrl.searchParams.set('ref', ref);
1629
+ const response = await stub.fetch(new Request(resolveUrl.toString(), {
1630
+ method: 'GET',
1631
+ headers: { 'Content-Type': 'application/json' },
1632
+ }));
1633
+ if (!response.ok) {
1634
+ throw new Error(`Cross-DO resolution failed: ${response.status}`);
1635
+ }
1636
+ let thing;
1637
+ try {
1638
+ thing = await response.json();
1639
+ }
1640
+ catch {
1641
+ throw new Error('Invalid response from remote DO');
1642
+ }
1643
+ return thing;
1644
+ }
1645
+ // ═══════════════════════════════════════════════════════════════════════════
1646
+ // RELATIONSHIPS
1647
+ // ═══════════════════════════════════════════════════════════════════════════
1648
+ parent;
1649
+ async link(target, relationType = 'related') {
1650
+ const targetNs = typeof target === 'string' ? target : target.doId;
1651
+ const metadata = typeof target === 'string' ? undefined : target;
1652
+ await this.db.insert(schema.relationships).values({
1653
+ id: crypto.randomUUID(),
1654
+ verb: typeof target === 'string' ? relationType : target.role || relationType,
1655
+ from: this.ns,
1656
+ to: targetNs,
1657
+ data: metadata,
1658
+ createdAt: new Date(),
1659
+ });
1660
+ }
1661
+ async getLinkedObjects(relationType) {
1662
+ const results = await this.db.select().from(schema.relationships);
1663
+ return results
1664
+ .filter((r) => r.from === this.ns && (!relationType || r.verb === relationType))
1665
+ .map((r) => ({
1666
+ ns: r.to,
1667
+ relationType: r.verb,
1668
+ doId: r.to,
1669
+ doClass: r.data?.doClass,
1670
+ data: r.data,
1671
+ }));
1672
+ }
1673
+ async createThing(data) {
1674
+ const id = crypto.randomUUID();
1675
+ // @ts-expect-error - Drizzle schema types may differ slightly
1676
+ await this.db.insert(schema.things).values({
1677
+ id,
1678
+ ns: this.ns,
1679
+ type: data.type,
1680
+ data: { name: data.name, ...data.data },
1681
+ version: 1,
1682
+ branch: this.currentBranch,
1683
+ createdAt: new Date(),
1684
+ updatedAt: new Date(),
1685
+ });
1686
+ return { id };
1687
+ }
1688
+ async createAction(data) {
1689
+ const id = crypto.randomUUID();
1690
+ // @ts-expect-error - Schema field names may differ
1691
+ await this.db.insert(schema.actions).values({
1692
+ id,
1693
+ verb: data.type,
1694
+ target: data.target,
1695
+ actor: data.actor,
1696
+ input: data.data,
1697
+ status: 'pending',
1698
+ createdAt: new Date(),
1699
+ });
1700
+ return { id };
1701
+ }
1702
+ // ═══════════════════════════════════════════════════════════════════════════
1703
+ // VISIBILITY HELPERS
1704
+ // ═══════════════════════════════════════════════════════════════════════════
1705
+ _currentActorContext = {};
1706
+ setActorContext(actor) {
1707
+ this._currentActorContext = actor;
1708
+ }
1709
+ getActorContext() {
1710
+ return this._currentActorContext;
1711
+ }
1712
+ clearActorContext() {
1713
+ this._currentActorContext = {};
1714
+ }
1715
+ canViewThing(thing) {
1716
+ if (!thing) {
1717
+ return false;
1718
+ }
1719
+ const visibility = thing.data?.visibility ?? 'user';
1720
+ const actor = this._currentActorContext;
1721
+ if (visibility === 'public' || visibility === 'unlisted') {
1722
+ return true;
1723
+ }
1724
+ if (visibility === 'org') {
1725
+ const dataObj = thing.data;
1726
+ const metaObj = dataObj?.meta;
1727
+ const thingOrgId = metaObj?.orgId ?? dataObj?.orgId;
1728
+ return !!actor.orgId && actor.orgId === thingOrgId;
1729
+ }
1730
+ const dataObj = thing.data;
1731
+ const metaObj = dataObj?.meta;
1732
+ const thingOwnerId = metaObj?.ownerId ?? dataObj?.ownerId;
1733
+ return !!actor.userId && actor.userId === thingOwnerId;
1734
+ }
1735
+ assertCanView(thing, message) {
1736
+ if (!thing) {
1737
+ throw new Error(message ?? 'Thing not found');
1738
+ }
1739
+ if (!this.canViewThing(thing)) {
1740
+ const visibility = thing.data?.visibility ?? 'user';
1741
+ let reason;
1742
+ switch (visibility) {
1743
+ case 'org':
1744
+ reason = 'Organization membership required';
1745
+ break;
1746
+ case 'user':
1747
+ reason = 'Owner access required';
1748
+ break;
1749
+ default:
1750
+ reason = 'Access denied';
1751
+ }
1752
+ throw new Error(message ?? reason);
1753
+ }
1754
+ }
1755
+ filterVisibleThings(things) {
1756
+ return things.filter((thing) => this.canViewThing(thing));
1757
+ }
1758
+ async getVisibleThing(id) {
1759
+ const thing = await this.things.get(id);
1760
+ if (!thing) {
1761
+ return null;
1762
+ }
1763
+ return this.canViewThing(thing) ? thing : null;
1764
+ }
1765
+ getVisibility(thing) {
1766
+ if (!thing) {
1767
+ return 'user';
1768
+ }
1769
+ return thing.data?.visibility ?? 'user';
1770
+ }
1771
+ isOwner(thing) {
1772
+ if (!thing) {
1773
+ return false;
1774
+ }
1775
+ const dataObj = thing.data;
1776
+ const metaObj = dataObj?.meta;
1777
+ const thingOwnerId = metaObj?.ownerId ?? dataObj?.ownerId;
1778
+ const actor = this._currentActorContext;
1779
+ return !!actor.userId && actor.userId === thingOwnerId;
1780
+ }
1781
+ isInThingOrg(thing) {
1782
+ if (!thing) {
1783
+ return false;
1784
+ }
1785
+ const dataObj = thing.data;
1786
+ const metaObj = dataObj?.meta;
1787
+ const thingOrgId = metaObj?.orgId ?? dataObj?.orgId;
1788
+ const actor = this._currentActorContext;
1789
+ return !!actor.orgId && actor.orgId === thingOrgId;
1790
+ }
1791
+ // ═══════════════════════════════════════════════════════════════════════════
1792
+ // REST ROUTER CONTEXT
1793
+ // ═══════════════════════════════════════════════════════════════════════════
1794
+ /**
1795
+ * Get REST router context for handling REST API requests.
1796
+ * Provides the things store and namespace for CRUD operations.
1797
+ */
1798
+ getRestRouterContext() {
1799
+ // Get the DO class type from static $type or constructor name
1800
+ const DOClass = this.constructor;
1801
+ const doType = DOClass.$type || this.constructor.name;
1802
+ return {
1803
+ things: this.things,
1804
+ ns: this.ns,
1805
+ contextUrl: 'https://dotdo.dev/context',
1806
+ nouns: this.getRegisteredNouns(),
1807
+ doType,
1808
+ };
1809
+ }
1810
+ /**
1811
+ * Get list of registered nouns for the index.
1812
+ * Override in subclasses to provide custom noun list.
1813
+ */
1814
+ getRegisteredNouns() {
1815
+ // Return cached nouns from typeCache
1816
+ // This is populated as types are used
1817
+ const nouns = [];
1818
+ for (const [noun] of this._typeCache) {
1819
+ // Simple pluralization (subclasses can override for complex cases)
1820
+ const plural = noun.endsWith('y')
1821
+ ? noun.slice(0, -1) + 'ies'
1822
+ : noun.endsWith('s') || noun.endsWith('x') || noun.endsWith('ch') || noun.endsWith('sh')
1823
+ ? noun + 'es'
1824
+ : noun + 's';
1825
+ nouns.push({ noun, plural });
1826
+ }
1827
+ return nouns;
1828
+ }
1829
+ // ═══════════════════════════════════════════════════════════════════════════
1830
+ // MCP HANDLER
1831
+ // ═══════════════════════════════════════════════════════════════════════════
1832
+ /**
1833
+ * Handle MCP (Model Context Protocol) requests.
1834
+ * This method is exposed for direct MCP access and is also routed from /mcp path.
1835
+ *
1836
+ * @param request - The incoming HTTP request
1837
+ * @returns Response with JSON-RPC 2.0 formatted result
1838
+ */
1839
+ async handleMcp(request) {
1840
+ const DOClass = this.constructor;
1841
+ // Initialize MCP handler if not already done
1842
+ if (!this._mcpHandler) {
1843
+ this._mcpHandler = createMcpHandler(DOClass);
1844
+ }
1845
+ return this._mcpHandler(this, request, this._mcpSessions);
1846
+ }
1847
+ // ═══════════════════════════════════════════════════════════════════════════
1848
+ // SYNC WEBSOCKET HANDLER
1849
+ // ═══════════════════════════════════════════════════════════════════════════
1850
+ /**
1851
+ * Handle WebSocket sync requests for TanStack DB integration.
1852
+ * Returns 426 Upgrade Required for non-WebSocket requests.
1853
+ *
1854
+ * @param request - The incoming HTTP request
1855
+ * @returns Response (101 for WebSocket upgrade, 426 for non-WebSocket)
1856
+ */
1857
+ handleSyncWebSocket(request) {
1858
+ // Check for WebSocket upgrade
1859
+ const upgradeHeader = request.headers.get('upgrade');
1860
+ if (upgradeHeader?.toLowerCase() !== 'websocket') {
1861
+ return Response.json({ error: 'WebSocket upgrade required for /sync endpoint' }, {
1862
+ status: 426,
1863
+ headers: { 'Upgrade': 'websocket' },
1864
+ });
1865
+ }
1866
+ // Create WebSocket pair
1867
+ const pair = new WebSocketPair();
1868
+ const [client, server] = Object.values(pair);
1869
+ // Accept the server side
1870
+ server.accept();
1871
+ // Register with sync engine
1872
+ this.syncEngine.accept(server);
1873
+ // Return the client side to the caller
1874
+ return new Response(null, {
1875
+ status: 101,
1876
+ webSocket: client,
1877
+ });
1878
+ }
1879
+ // ═══════════════════════════════════════════════════════════════════════════
1880
+ // HTTP HANDLER (Extended from DOTiny)
1881
+ // ═══════════════════════════════════════════════════════════════════════════
1882
+ async handleFetch(request) {
1883
+ const url = new URL(request.url);
1884
+ // Extract coordinates from CF request headers if available
1885
+ const cf = request.cf;
1886
+ if (cf?.latitude && cf?.longitude && !this._extractedCoordinates) {
1887
+ const lat = parseFloat(cf.latitude);
1888
+ const lng = parseFloat(cf.longitude);
1889
+ if (!isNaN(lat) && !isNaN(lng)) {
1890
+ this._extractedCoordinates = { lat, lng };
1891
+ }
1892
+ }
1893
+ // Built-in routes
1894
+ if (url.pathname === '/health') {
1895
+ return Response.json({ status: 'ok', ns: this.ns });
1896
+ }
1897
+ // ═══════════════════════════════════════════════════════════════════════════
1898
+ // ROOT ENDPOINT (/) - Cap'n Web RPC
1899
+ // ═══════════════════════════════════════════════════════════════════════════
1900
+ // POST / → capnweb RPC (HTTP batch)
1901
+ // WebSocket / → capnweb RPC (persistent connection)
1902
+ // GET / → info/discovery
1903
+ if (url.pathname === '/') {
1904
+ // Handle Cap'n Web RPC (POST or WebSocket upgrade)
1905
+ if (isCapnWebRequest(request)) {
1906
+ return this.#handleCapnWebRpc(request);
1907
+ }
1908
+ // GET request - return JSON-LD index with collections
1909
+ if (request.method === 'GET') {
1910
+ // Build REST router context for index generation
1911
+ const restCtx = this.getRestRouterContext();
1912
+ return handleGetIndex(restCtx, request);
1913
+ }
1914
+ // Other methods not supported at root
1915
+ return new Response('Method Not Allowed', {
1916
+ status: 405,
1917
+ headers: { 'Allow': 'GET, POST' },
1918
+ });
1919
+ }
1920
+ // Handle /$introspect endpoint for schema discovery
1921
+ if (url.pathname === '/$introspect') {
1922
+ return this.handleIntrospectRoute(request);
1923
+ }
1924
+ // Handle /mcp endpoint for MCP transport
1925
+ if (url.pathname === '/mcp') {
1926
+ return this.handleMcp(request);
1927
+ }
1928
+ // Handle /rpc endpoint for RPC protocol (JSON-RPC 2.0 + Chain RPC)
1929
+ if (url.pathname === '/rpc') {
1930
+ // Check for WebSocket upgrade
1931
+ const upgradeHeader = request.headers.get('upgrade');
1932
+ const connectionHeader = request.headers.get('connection')?.toLowerCase() || '';
1933
+ const hasConnectionUpgrade = connectionHeader.includes('upgrade');
1934
+ if (upgradeHeader?.toLowerCase() === 'websocket' && hasConnectionUpgrade) {
1935
+ return this.rpcServer.handleWebSocketRpc();
1936
+ }
1937
+ // HTTP RPC request
1938
+ if (request.method === 'POST') {
1939
+ return this.rpcServer.handleRpcRequest(request);
1940
+ }
1941
+ // GET request - return RPC info
1942
+ return Response.json({
1943
+ message: 'RPC endpoint - use POST for HTTP batch mode or WebSocket for streaming',
1944
+ methods: this.rpcServer.methods,
1945
+ }, { headers: { 'Content-Type': 'application/json' } });
1946
+ }
1947
+ // Handle /sync endpoint for WebSocket sync protocol (TanStack DB)
1948
+ if (url.pathname === '/sync') {
1949
+ return this.handleSyncWebSocket(request);
1950
+ }
1951
+ // Handle /resolve endpoint for cross-DO resolution
1952
+ if (url.pathname === '/resolve') {
1953
+ const path = url.searchParams.get('path');
1954
+ const ref = url.searchParams.get('ref') || 'main';
1955
+ if (!path) {
1956
+ return Response.json({ error: 'Missing path parameter' }, { status: 400 });
1957
+ }
1958
+ try {
1959
+ const thing = await this.resolveLocal(path, ref);
1960
+ return Response.json(thing);
1961
+ }
1962
+ catch (error) {
1963
+ const message = error instanceof Error ? error.message : 'Resolution failed';
1964
+ return Response.json({ error: message }, { status: 404 });
1965
+ }
1966
+ }
1967
+ // Delegate to Hono app if configured
1968
+ if (this.app) {
1969
+ const response = await this.app.fetch(request, this.env);
1970
+ return response;
1971
+ }
1972
+ // ═══════════════════════════════════════════════════════════════════════════
1973
+ // REST API ROUTES (/:type and /:type/:id)
1974
+ // ═══════════════════════════════════════════════════════════════════════════
1975
+ // Handle REST routes for Things CRUD operations
1976
+ // GET /customers → list customers
1977
+ // GET /customers/cust-1 → get customer by id
1978
+ // POST /customers → create customer
1979
+ // PUT /customers/cust-1 → replace customer
1980
+ // PATCH /customers/cust-1 → update customer (merge)
1981
+ // DELETE /customers/cust-1 → delete customer
1982
+ const restCtx = this.getRestRouterContext();
1983
+ const restResponse = await handleRestRequest(request, restCtx);
1984
+ if (restResponse) {
1985
+ return restResponse;
1986
+ }
1987
+ // Default: 404 Not Found
1988
+ return new Response('Not Found', { status: 404 });
1989
+ }
1990
+ // ═══════════════════════════════════════════════════════════════════════════
1991
+ // CAP'N WEB RPC HANDLER
1992
+ // ═══════════════════════════════════════════════════════════════════════════
1993
+ /**
1994
+ * Cap'n Web RPC options. Override in subclasses to customize.
1995
+ */
1996
+ get capnWebOptions() {
1997
+ return {
1998
+ includeStackTraces: this.env.ENVIRONMENT !== 'production',
1999
+ };
2000
+ }
2001
+ /**
2002
+ * Handle Cap'n Web RPC requests (POST or WebSocket upgrade at root endpoint).
2003
+ *
2004
+ * This is marked with # prefix to indicate it's internal and should not
2005
+ * be exposed via RPC itself.
2006
+ */
2007
+ async #handleCapnWebRpc(request) {
2008
+ return handleCapnWebRpc(request, this, this.capnWebOptions);
2009
+ }
2010
+ // ═══════════════════════════════════════════════════════════════════════════
2011
+ // ALARM HANDLER (for scheduled tasks)
2012
+ // ═══════════════════════════════════════════════════════════════════════════
2013
+ async alarm() {
2014
+ // Delegate to schedule manager to handle scheduled tasks
2015
+ if (this._scheduleManager) {
2016
+ await this._scheduleManager.handleAlarm();
2017
+ }
2018
+ }
2019
+ // ═══════════════════════════════════════════════════════════════════════════
2020
+ // INTROSPECTION HTTP HANDLER
2021
+ // ═══════════════════════════════════════════════════════════════════════════
2022
+ /**
2023
+ * Handle the /$introspect HTTP route.
2024
+ * Requires authentication and returns the DOSchema.
2025
+ */
2026
+ async handleIntrospectRoute(request) {
2027
+ // Only allow GET requests
2028
+ if (request.method !== 'GET') {
2029
+ return Response.json({ error: 'Method not allowed' }, { status: 405 });
2030
+ }
2031
+ // Check for Authorization header
2032
+ const authHeader = request.headers.get('Authorization');
2033
+ if (!authHeader) {
2034
+ return Response.json({ error: 'Authentication required' }, { status: 401 });
2035
+ }
2036
+ // Parse JWT from Bearer token
2037
+ if (!authHeader.startsWith('Bearer ')) {
2038
+ return Response.json({ error: 'Invalid authorization header' }, { status: 401 });
2039
+ }
2040
+ const token = authHeader.slice(7);
2041
+ // Parse and verify the JWT
2042
+ try {
2043
+ const parts = token.split('.');
2044
+ if (parts.length !== 3) {
2045
+ return Response.json({ error: 'Invalid token format' }, { status: 401 });
2046
+ }
2047
+ // Verify JWT signature if JWT_SECRET is configured
2048
+ const jwtSecret = this.env.JWT_SECRET;
2049
+ if (jwtSecret) {
2050
+ const isValid = await this.verifyJwtSignature(token, jwtSecret);
2051
+ if (!isValid) {
2052
+ return Response.json({ error: 'Invalid token signature' }, { status: 401 });
2053
+ }
2054
+ }
2055
+ else {
2056
+ // In development without JWT_SECRET, log warning but allow request
2057
+ console.warn('JWT_SECRET not configured - skipping signature verification');
2058
+ }
2059
+ const payload = JSON.parse(atob(parts[1]));
2060
+ // Check expiration
2061
+ const now = Math.floor(Date.now() / 1000);
2062
+ if (payload.exp && payload.exp < now) {
2063
+ return Response.json({ error: 'Token expired' }, { status: 401 });
2064
+ }
2065
+ // Build auth context from JWT claims
2066
+ const authContext = {
2067
+ authenticated: true,
2068
+ user: {
2069
+ id: payload.sub || 'anonymous',
2070
+ email: payload.email,
2071
+ name: payload.name,
2072
+ roles: payload.roles || [],
2073
+ permissions: payload.permissions || [],
2074
+ },
2075
+ token: {
2076
+ type: 'jwt',
2077
+ expiresAt: payload.exp ? new Date(payload.exp * 1000) : new Date(Date.now() + 3600000),
2078
+ },
2079
+ };
2080
+ // Call $introspect with auth context
2081
+ const schema = await this.$introspect(authContext);
2082
+ return Response.json(schema);
2083
+ }
2084
+ catch (error) {
2085
+ return Response.json({ error: 'Invalid token' }, { status: 401 });
2086
+ }
2087
+ }
2088
+ // ═══════════════════════════════════════════════════════════════════════════
2089
+ // INTROSPECTION ($introspect)
2090
+ // ═══════════════════════════════════════════════════════════════════════════
2091
+ /**
2092
+ * Introspect the DO schema, filtered by user role.
2093
+ *
2094
+ * Returns information about:
2095
+ * - Available classes and their methods
2096
+ * - MCP tools from static $mcp config
2097
+ * - REST endpoints from static $rest config
2098
+ * - Available stores (filtered by role)
2099
+ * - Storage capabilities (filtered by role)
2100
+ * - Registered nouns and verbs
2101
+ *
2102
+ * @param authContext - Optional auth context for role-based filtering
2103
+ * @returns DOSchema object with introspection data
2104
+ */
2105
+ async $introspect(authContext) {
2106
+ // Determine role from auth context
2107
+ const role = this.determineRole(authContext);
2108
+ const scopes = authContext?.user?.permissions || [];
2109
+ // Build the schema response
2110
+ const schema = {
2111
+ ns: this.ns,
2112
+ permissions: {
2113
+ role,
2114
+ scopes,
2115
+ },
2116
+ classes: this.introspectClasses(role),
2117
+ nouns: await this.introspectNouns(),
2118
+ verbs: await this.introspectVerbs(),
2119
+ stores: this.introspectStores(role),
2120
+ storage: this.introspectStorage(role),
2121
+ };
2122
+ return schema;
2123
+ }
2124
+ /**
2125
+ * Determine the effective role from auth context
2126
+ */
2127
+ determineRole(authContext) {
2128
+ if (!authContext || !authContext.authenticated) {
2129
+ return 'public';
2130
+ }
2131
+ const roles = authContext.user?.roles || [];
2132
+ if (roles.length === 0) {
2133
+ // Authenticated but no specific roles - default to 'user'
2134
+ return 'user';
2135
+ }
2136
+ return getHighestRole(roles);
2137
+ }
2138
+ /**
2139
+ * Introspect available classes
2140
+ */
2141
+ introspectClasses(role) {
2142
+ const classes = [];
2143
+ // Get class info from constructor
2144
+ const DOClass = this.constructor;
2145
+ const className = DOClass.$type || this.constructor.name;
2146
+ // Determine class visibility based on config
2147
+ // Default to 'user' visibility for the class itself
2148
+ const classVisibility = 'user';
2149
+ // Only include class if caller can access it
2150
+ if (!canAccessVisibility(role, classVisibility)) {
2151
+ return classes;
2152
+ }
2153
+ // Build tools list from $mcp config, filtered by role
2154
+ const tools = [];
2155
+ if (DOClass.$mcp?.tools) {
2156
+ for (const [name, config] of Object.entries(DOClass.$mcp.tools)) {
2157
+ const toolVisibility = config.visibility || 'user';
2158
+ if (canAccessVisibility(role, toolVisibility)) {
2159
+ tools.push({
2160
+ name,
2161
+ description: config.description,
2162
+ inputSchema: config.inputSchema,
2163
+ });
2164
+ }
2165
+ }
2166
+ }
2167
+ // Build endpoints list from $rest config, filtered by role
2168
+ const endpoints = [];
2169
+ if (DOClass.$rest?.endpoints) {
2170
+ for (const endpoint of DOClass.$rest.endpoints) {
2171
+ const endpointVisibility = endpoint.visibility || 'user';
2172
+ if (canAccessVisibility(role, endpointVisibility)) {
2173
+ endpoints.push({
2174
+ method: endpoint.method,
2175
+ path: endpoint.path,
2176
+ description: endpoint.description,
2177
+ });
2178
+ }
2179
+ }
2180
+ }
2181
+ // Build class schema
2182
+ classes.push({
2183
+ name: className,
2184
+ type: 'thing', // Default to 'thing', could be 'collection' for collection DOs
2185
+ pattern: `/:type/:id`,
2186
+ visibility: classVisibility,
2187
+ tools,
2188
+ endpoints,
2189
+ properties: [], // Could be populated from static schema
2190
+ actions: [], // Could be populated from method decorators
2191
+ });
2192
+ return classes;
2193
+ }
2194
+ /**
2195
+ * Introspect available stores, filtered by role
2196
+ */
2197
+ introspectStores(role) {
2198
+ const stores = [];
2199
+ const storeDefinitions = [
2200
+ { name: 'things', type: 'things' },
2201
+ { name: 'relationships', type: 'relationships' },
2202
+ { name: 'actions', type: 'actions' },
2203
+ { name: 'events', type: 'events' },
2204
+ { name: 'search', type: 'search' },
2205
+ { name: 'objects', type: 'objects' },
2206
+ { name: 'dlq', type: 'dlq' },
2207
+ ];
2208
+ for (const store of storeDefinitions) {
2209
+ const storeVisibility = STORE_VISIBILITY[store.type];
2210
+ if (canAccessVisibility(role, storeVisibility)) {
2211
+ stores.push({
2212
+ name: store.name,
2213
+ type: store.type,
2214
+ visibility: storeVisibility,
2215
+ });
2216
+ }
2217
+ }
2218
+ return stores;
2219
+ }
2220
+ /**
2221
+ * Introspect storage capabilities, filtered by role
2222
+ */
2223
+ introspectStorage(role) {
2224
+ // Capability visibility levels:
2225
+ // - fsx, gitx: user and above
2226
+ // - bashx, r2, sql: admin and above
2227
+ // - iceberg, edgevec: system only
2228
+ const isUser = canAccessVisibility(role, 'user');
2229
+ const isAdmin = canAccessVisibility(role, 'admin');
2230
+ const isSystem = canAccessVisibility(role, 'system');
2231
+ return {
2232
+ fsx: isUser,
2233
+ gitx: isUser,
2234
+ bashx: isAdmin,
2235
+ r2: {
2236
+ enabled: isAdmin,
2237
+ buckets: isAdmin ? [] : undefined, // Would list actual buckets from env
2238
+ },
2239
+ sql: {
2240
+ enabled: isAdmin,
2241
+ tables: isAdmin ? [] : undefined, // Would list actual tables from schema
2242
+ },
2243
+ iceberg: isSystem,
2244
+ edgevec: isSystem,
2245
+ };
2246
+ }
2247
+ /**
2248
+ * Introspect registered nouns
2249
+ */
2250
+ async introspectNouns() {
2251
+ // Query nouns from the database (simplified for now)
2252
+ // In full implementation, would query from db.nouns table
2253
+ return [];
2254
+ }
2255
+ /**
2256
+ * Introspect registered verbs
2257
+ */
2258
+ async introspectVerbs() {
2259
+ // TODO: Query verbs from the database
2260
+ // In full implementation, would query from db.verbs table
2261
+ return [];
2262
+ }
2263
+ // ═══════════════════════════════════════════════════════════════════════════
2264
+ // JWT VERIFICATION HELPERS
2265
+ // ═══════════════════════════════════════════════════════════════════════════
2266
+ /**
2267
+ * Verify JWT signature using HMAC-SHA256.
2268
+ *
2269
+ * @param token - The full JWT token string
2270
+ * @param secret - The secret key for verification
2271
+ * @returns true if signature is valid, false otherwise
2272
+ */
2273
+ async verifyJwtSignature(token, secret) {
2274
+ try {
2275
+ const parts = token.split('.');
2276
+ if (parts.length !== 3)
2277
+ return false;
2278
+ const signatureInput = `${parts[0]}.${parts[1]}`;
2279
+ const signature = parts[2];
2280
+ const encoder = new TextEncoder();
2281
+ const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
2282
+ // Base64url decode the signature
2283
+ const signatureBytes = this.base64UrlDecode(signature);
2284
+ return await crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(signatureInput));
2285
+ }
2286
+ catch {
2287
+ return false;
2288
+ }
2289
+ }
2290
+ /**
2291
+ * Decode a base64url-encoded string to Uint8Array.
2292
+ */
2293
+ base64UrlDecode(str) {
2294
+ // Replace base64url chars with base64 chars
2295
+ let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
2296
+ // Pad with '=' to make it valid base64
2297
+ while (base64.length % 4) {
2298
+ base64 += '=';
2299
+ }
2300
+ const binary = atob(base64);
2301
+ const bytes = new Uint8Array(binary.length);
2302
+ for (let i = 0; i < binary.length; i++) {
2303
+ bytes[i] = binary.charCodeAt(i);
2304
+ }
2305
+ return bytes;
2306
+ }
2307
+ }
2308
+ export default DO;
2309
+ //# sourceMappingURL=DOBase.js.map