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,1035 @@
1
+ /**
2
+ * Artifact Serve Snippet
3
+ *
4
+ * Serves artifacts from R2 Iceberg with SWR caching.
5
+ * Handles path parsing, extension mapping, and cache control.
6
+ *
7
+ * @module snippets/artifacts-serve
8
+ * @see docs/plans/2026-01-10-artifact-storage-design.md
9
+ */
10
+ import { noopMetrics } from './artifacts-ingest';
11
+ // Re-export metrics types for external use
12
+ export { noopMetrics, createDefaultMetrics } from './artifacts-ingest';
13
+ // ============================================================================
14
+ // Constants
15
+ // ============================================================================
16
+ /**
17
+ * Supported file extensions and their compound variants.
18
+ * Order matters: compound extensions must be checked first.
19
+ */
20
+ const COMPOUND_EXTENSIONS = ['mdast.json', 'hast.json', 'estree.json', 'd.ts'];
21
+ const SIMPLE_EXTENSIONS = ['md', 'mdx', 'html', 'js', 'mjs', 'css', 'json'];
22
+ const ALL_EXTENSIONS = [...COMPOUND_EXTENSIONS, ...SIMPLE_EXTENSIONS];
23
+ /**
24
+ * Extension to database column mapping.
25
+ */
26
+ const EXTENSION_TO_COLUMN = {
27
+ 'md': 'markdown',
28
+ 'mdx': 'mdx',
29
+ 'html': 'html',
30
+ 'js': 'esm',
31
+ 'mjs': 'esm',
32
+ 'd.ts': 'dts',
33
+ 'css': 'css',
34
+ 'json': 'frontmatter',
35
+ 'mdast.json': 'mdast',
36
+ 'hast.json': 'hast',
37
+ 'estree.json': 'estree',
38
+ };
39
+ /**
40
+ * Extension to Content-Type mapping.
41
+ */
42
+ const EXTENSION_TO_CONTENT_TYPE = {
43
+ 'md': 'text/markdown; charset=utf-8',
44
+ 'mdx': 'text/mdx; charset=utf-8',
45
+ 'html': 'text/html; charset=utf-8',
46
+ 'js': 'application/javascript; charset=utf-8',
47
+ 'mjs': 'application/javascript; charset=utf-8',
48
+ 'd.ts': 'application/typescript; charset=utf-8',
49
+ 'css': 'text/css; charset=utf-8',
50
+ 'json': 'application/json',
51
+ 'mdast.json': 'application/json',
52
+ 'hast.json': 'application/json',
53
+ 'estree.json': 'application/json',
54
+ };
55
+ /**
56
+ * Default tenant config used when no config is provided.
57
+ */
58
+ const DEFAULT_CONFIG = {
59
+ ns: 'default',
60
+ cache: {
61
+ defaultMaxAge: 300,
62
+ defaultStaleWhileRevalidate: 60,
63
+ minMaxAge: 10,
64
+ allowFreshBypass: true,
65
+ },
66
+ };
67
+ // ============================================================================
68
+ // Path Parsing
69
+ // ============================================================================
70
+ /**
71
+ * Parse URL path into artifact components.
72
+ *
73
+ * URL Pattern: /$.content/{ns}/{type}/{id}.{ext}
74
+ *
75
+ * Supports:
76
+ * - Simple extensions: .md, .html, .js, .css, .json
77
+ * - Compound extensions: .d.ts, .mdast.json, .hast.json, .estree.json
78
+ * - Nested namespaces: myorg.app.do
79
+ * - URL-encoded characters in id
80
+ *
81
+ * @param url - The request URL to parse
82
+ * @returns Parsed path components or null if invalid
83
+ */
84
+ export function parsePath(url) {
85
+ // Remove /$.content/ prefix and get the path
86
+ const pathname = decodeURIComponent(url.pathname);
87
+ const prefix = '/$.content/';
88
+ if (!pathname.startsWith(prefix)) {
89
+ return null;
90
+ }
91
+ const path = pathname.slice(prefix.length);
92
+ const parts = path.split('/');
93
+ // Need at least 3 parts: ns, type, id.ext
94
+ if (parts.length < 3) {
95
+ return null;
96
+ }
97
+ const ns = parts[0];
98
+ const type = parts[1];
99
+ const filenamePart = parts.slice(2).join('/');
100
+ // Try compound extensions first (order matters)
101
+ let ext = null;
102
+ let id = null;
103
+ for (const compoundExt of COMPOUND_EXTENSIONS) {
104
+ const suffix = '.' + compoundExt;
105
+ if (filenamePart.endsWith(suffix)) {
106
+ ext = compoundExt;
107
+ id = filenamePart.slice(0, -suffix.length);
108
+ break;
109
+ }
110
+ }
111
+ // If no compound extension matched, try simple extensions
112
+ if (!ext) {
113
+ const lastDotIdx = filenamePart.lastIndexOf('.');
114
+ if (lastDotIdx === -1 || lastDotIdx === filenamePart.length - 1) {
115
+ return null; // No extension
116
+ }
117
+ const potentialExt = filenamePart.slice(lastDotIdx + 1).toLowerCase();
118
+ if (!SIMPLE_EXTENSIONS.includes(potentialExt)) {
119
+ return null; // Unknown extension
120
+ }
121
+ ext = potentialExt;
122
+ id = filenamePart.slice(0, lastDotIdx);
123
+ }
124
+ // Validate required parts
125
+ if (!ns || !type || !id) {
126
+ return null;
127
+ }
128
+ return { ns, type, id, ext };
129
+ }
130
+ // ============================================================================
131
+ // Extension Mapping
132
+ // ============================================================================
133
+ /**
134
+ * Map file extension to database column name.
135
+ *
136
+ * @param ext - File extension (without leading dot)
137
+ * @returns Database column name
138
+ * @throws Error if extension is unknown
139
+ */
140
+ export function getColumnForExtension(ext) {
141
+ const normalizedExt = ext.toLowerCase();
142
+ const column = EXTENSION_TO_COLUMN[normalizedExt];
143
+ if (!column) {
144
+ throw new Error(`Unsupported extension: ${ext}`);
145
+ }
146
+ return column;
147
+ }
148
+ /**
149
+ * Get Content-Type header for file extension.
150
+ *
151
+ * @param ext - File extension (without leading dot)
152
+ * @returns Content-Type header value
153
+ */
154
+ export function getContentType(ext) {
155
+ const normalizedExt = ext.toLowerCase();
156
+ return EXTENSION_TO_CONTENT_TYPE[normalizedExt] ?? 'application/octet-stream';
157
+ }
158
+ // ============================================================================
159
+ // Cache Control
160
+ // ============================================================================
161
+ /**
162
+ * Build Cache-Control header from config and overrides.
163
+ *
164
+ * @param config - Tenant configuration
165
+ * @param overrides - Optional overrides for max-age, visibility, fresh bypass
166
+ * @returns Cache-Control header value
167
+ */
168
+ export function buildCacheControl(config, overrides) {
169
+ // Fresh bypass returns no-store immediately
170
+ if (overrides?.fresh) {
171
+ return 'no-store';
172
+ }
173
+ // Determine max-age
174
+ let maxAge = overrides?.maxAge ?? config.cache.defaultMaxAge;
175
+ // Enforce minimum
176
+ if (maxAge < config.cache.minMaxAge) {
177
+ maxAge = config.cache.minMaxAge;
178
+ }
179
+ // Build header parts
180
+ const visibility = overrides?.visibility ?? 'public';
181
+ const parts = [`${visibility}`, `max-age=${maxAge}`];
182
+ // Add stale-while-revalidate if > 0
183
+ const swr = config.cache.defaultStaleWhileRevalidate;
184
+ if (swr > 0) {
185
+ parts.push(`stale-while-revalidate=${swr}`);
186
+ }
187
+ return parts.join(', ');
188
+ }
189
+ // ============================================================================
190
+ // Auth Context Extraction
191
+ // ============================================================================
192
+ /**
193
+ * Parse a base64url-encoded JWT payload.
194
+ * Does NOT verify signature - verification should be done by auth middleware.
195
+ *
196
+ * @param token - Bearer token string
197
+ * @returns Decoded payload or null if invalid
198
+ */
199
+ function parseJwtPayload(token) {
200
+ try {
201
+ const parts = token.split('.');
202
+ if (parts.length !== 3)
203
+ return null;
204
+ const payload = parts[1];
205
+ // Base64url to base64 conversion
206
+ const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
207
+ const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
208
+ const decoded = atob(padded);
209
+ return JSON.parse(decoded);
210
+ }
211
+ catch {
212
+ return null;
213
+ }
214
+ }
215
+ /**
216
+ * Check if a JWT token has expired.
217
+ *
218
+ * @param payload - Parsed JWT payload
219
+ * @returns true if expired, false otherwise
220
+ */
221
+ function isTokenExpired(payload) {
222
+ const exp = payload.exp;
223
+ if (typeof exp !== 'number')
224
+ return false;
225
+ // exp is in seconds since epoch
226
+ return Date.now() / 1000 > exp;
227
+ }
228
+ /**
229
+ * Extract auth context from request headers.
230
+ *
231
+ * Extracts:
232
+ * - userId from JWT payload or X-User-Id header
233
+ * - orgId from X-Org-Id header or JWT payload
234
+ * - roles from JWT payload
235
+ *
236
+ * Supports both:
237
+ * - JWT tokens with claims
238
+ * - Non-JWT tokens with X-User-Id/X-Org-Id headers for testing
239
+ *
240
+ * @param request - The HTTP request
241
+ * @returns AuthContext or undefined if no auth present
242
+ */
243
+ export function extractAuthContext(request) {
244
+ const authHeader = request.headers.get('Authorization');
245
+ // Check for X-User-Id and X-Org-Id headers (can work with or without auth)
246
+ const userIdHeader = request.headers.get('X-User-Id');
247
+ const orgIdHeader = request.headers.get('X-Org-Id');
248
+ // If there's no auth header and no X- headers, return undefined
249
+ if (!authHeader && !userIdHeader && !orgIdHeader) {
250
+ return undefined;
251
+ }
252
+ const authContext = {};
253
+ // Try to extract from JWT if present
254
+ if (authHeader?.startsWith('Bearer ')) {
255
+ const token = authHeader.slice(7);
256
+ const payload = parseJwtPayload(token);
257
+ if (payload && !isTokenExpired(payload)) {
258
+ // Extract userId from JWT payload
259
+ if (typeof payload.userId === 'string') {
260
+ authContext.userId = payload.userId;
261
+ }
262
+ // Extract orgId from JWT payload
263
+ if (typeof payload.orgId === 'string') {
264
+ authContext.orgId = payload.orgId;
265
+ }
266
+ // Extract roles from JWT payload
267
+ if (Array.isArray(payload.roles)) {
268
+ authContext.roles = payload.roles.filter((r) => typeof r === 'string');
269
+ }
270
+ }
271
+ }
272
+ // Override/supplement with X- headers (headers take precedence)
273
+ if (userIdHeader) {
274
+ authContext.userId = userIdHeader;
275
+ }
276
+ if (orgIdHeader) {
277
+ authContext.orgId = orgIdHeader;
278
+ }
279
+ // If no properties were extracted, return undefined
280
+ if (!authContext.userId && !authContext.orgId && !authContext.roles) {
281
+ return undefined;
282
+ }
283
+ return authContext;
284
+ }
285
+ /**
286
+ * Determine the visibility level to request based on auth context.
287
+ *
288
+ * Rules:
289
+ * - No auth context: 'public'
290
+ * - Auth with orgId: 'org'
291
+ * - Auth with userId only: 'user'
292
+ * - For unlisted artifacts: return undefined (accessible by direct ID only)
293
+ *
294
+ * @param authContext - The extracted auth context
295
+ * @param isUnlistedAccess - Whether this is accessing an unlisted artifact
296
+ * @returns Visibility level or undefined for unlisted access
297
+ */
298
+ export function determineVisibility(authContext, isUnlistedAccess = false) {
299
+ // Unlisted artifacts don't filter by visibility - they're accessible by direct ID
300
+ if (isUnlistedAccess)
301
+ return undefined;
302
+ if (!authContext)
303
+ return 'public';
304
+ // If orgId is present, use org visibility
305
+ if (authContext.orgId)
306
+ return 'org';
307
+ // If userId is present, use user visibility
308
+ if (authContext.userId)
309
+ return 'user';
310
+ // Default to public
311
+ return 'public';
312
+ }
313
+ /**
314
+ * Get the columns to request based on file extension.
315
+ * Always includes 'id' for record identification.
316
+ *
317
+ * @param ext - File extension
318
+ * @returns Array of column names to request
319
+ */
320
+ export function getColumnsForExtension(ext) {
321
+ const column = getColumnForExtension(ext);
322
+ return ['id', column];
323
+ }
324
+ // ============================================================================
325
+ // Hash Utilities
326
+ // ============================================================================
327
+ /**
328
+ * Generate a simple hash for ETag from content.
329
+ */
330
+ async function generateETag(content) {
331
+ const text = typeof content === 'string' ? content : JSON.stringify(content);
332
+ const encoder = new TextEncoder();
333
+ const data = encoder.encode(text);
334
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
335
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
336
+ const hashHex = hashArray.slice(0, 8).map(b => b.toString(16).padStart(2, '0')).join('');
337
+ return `"${hashHex}"`;
338
+ }
339
+ // ============================================================================
340
+ // Response Helpers
341
+ // ============================================================================
342
+ function jsonError(message, status, cacheControl) {
343
+ const headers = { 'Content-Type': 'application/json' };
344
+ if (cacheControl) {
345
+ headers['Cache-Control'] = cacheControl;
346
+ }
347
+ return new Response(JSON.stringify({ error: message }), {
348
+ status,
349
+ headers,
350
+ });
351
+ }
352
+ function methodNotAllowed() {
353
+ return new Response(JSON.stringify({ error: 'Method not allowed' }), {
354
+ status: 405,
355
+ headers: {
356
+ 'Content-Type': 'application/json',
357
+ 'Allow': 'GET, HEAD',
358
+ },
359
+ });
360
+ }
361
+ function optionsResponse() {
362
+ return new Response(null, {
363
+ status: 204,
364
+ headers: {
365
+ 'Allow': 'GET, HEAD, OPTIONS',
366
+ },
367
+ });
368
+ }
369
+ /**
370
+ * Check if a cached response is stale based on its Date header.
371
+ */
372
+ function getResponseAge(response) {
373
+ const dateHeader = response.headers.get('Date');
374
+ if (!dateHeader)
375
+ return 0;
376
+ const cachedAt = new Date(dateHeader).getTime();
377
+ return Math.floor((Date.now() - cachedAt) / 1000);
378
+ }
379
+ /**
380
+ * Check if response is stale (past max-age but potentially within SWR window).
381
+ */
382
+ function isResponseStale(response) {
383
+ const cacheControl = response.headers.get('Cache-Control');
384
+ if (!cacheControl)
385
+ return false;
386
+ const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
387
+ if (!maxAgeMatch)
388
+ return false;
389
+ const maxAge = parseInt(maxAgeMatch[1]);
390
+ const age = getResponseAge(response);
391
+ return age > maxAge;
392
+ }
393
+ /**
394
+ * Revalidate cache entry in the background.
395
+ */
396
+ async function revalidateCache(cacheKey, parsed, config, reader, overrides) {
397
+ try {
398
+ const record = await reader.getRecord({
399
+ table: 'do_resources',
400
+ partition: { ns: parsed.ns, type: parsed.type },
401
+ id: parsed.id,
402
+ });
403
+ if (!record)
404
+ return;
405
+ const column = getColumnForExtension(parsed.ext);
406
+ const content = record[column];
407
+ if (content === null || content === undefined)
408
+ return;
409
+ const isJson = parsed.ext.endsWith('.json') || parsed.ext === 'json';
410
+ const body = isJson ? JSON.stringify(content) : String(content);
411
+ const etag = await generateETag(body);
412
+ const contentType = getContentType(parsed.ext);
413
+ const cacheControl = buildCacheControl(config, overrides);
414
+ const contentLength = new TextEncoder().encode(body).length;
415
+ const headers = new Headers({
416
+ 'Content-Type': contentType,
417
+ 'Cache-Control': cacheControl,
418
+ 'ETag': etag,
419
+ 'X-Artifact-Source': 'cache',
420
+ 'Vary': 'Accept-Encoding',
421
+ 'Content-Length': String(contentLength),
422
+ 'Date': new Date().toUTCString(),
423
+ });
424
+ const response = new Response(body, { status: 200, headers });
425
+ await caches.default.put(cacheKey, response);
426
+ }
427
+ catch {
428
+ // Ignore revalidation errors
429
+ }
430
+ }
431
+ // ============================================================================
432
+ // Integration Test Handler
433
+ // ============================================================================
434
+ /**
435
+ * Check if options are for integration test mode.
436
+ */
437
+ function isIntegrationOptions(options) {
438
+ return 'cache' in options && typeof options.cache === 'object' && 'match' in options.cache;
439
+ }
440
+ /**
441
+ * Handle artifact serve request - overloaded for integration tests.
442
+ * This version accepts (request, env, ctx, options) signature.
443
+ */
444
+ export async function handleServe(request, envOrCtx, ctxOrOptions, maybeOptions) {
445
+ // Determine which signature was used
446
+ const isIntegrationMode = maybeOptions !== undefined && isIntegrationOptions(maybeOptions);
447
+ if (isIntegrationMode) {
448
+ // Integration test mode: (request, env, ctx, options)
449
+ const ctx = ctxOrOptions;
450
+ const options = maybeOptions;
451
+ return handleServeIntegration(request, ctx, options);
452
+ }
453
+ // Original mode: (request, ctx, options)
454
+ const ctx = envOrCtx;
455
+ const options = ctxOrOptions;
456
+ return handleServeOriginal(request, ctx, options);
457
+ }
458
+ /**
459
+ * Original handleServe implementation returning Response.
460
+ */
461
+ async function handleServeOriginal(request, ctx, options) {
462
+ const method = request.method.toUpperCase();
463
+ const metrics = options.metrics ?? noopMetrics;
464
+ const startTime = Date.now();
465
+ // Handle OPTIONS for CORS preflight
466
+ if (method === 'OPTIONS') {
467
+ return optionsResponse();
468
+ }
469
+ // Only allow GET and HEAD
470
+ if (method !== 'GET' && method !== 'HEAD') {
471
+ return methodNotAllowed();
472
+ }
473
+ const url = new URL(request.url);
474
+ // Parse path
475
+ const parsed = parsePath(url);
476
+ if (!parsed) {
477
+ return jsonError('Invalid path format', 400);
478
+ }
479
+ const { ns, type, id, ext } = parsed;
480
+ // Load config (use provided, load from loader, or default)
481
+ let config;
482
+ if (options.config) {
483
+ config = options.config;
484
+ }
485
+ else if (options.configLoader) {
486
+ try {
487
+ config = await options.configLoader(ns);
488
+ }
489
+ catch {
490
+ // Fall back to default config on loader error
491
+ config = DEFAULT_CONFIG;
492
+ }
493
+ }
494
+ else {
495
+ config = DEFAULT_CONFIG;
496
+ }
497
+ // Parse query params
498
+ const maxAgeParam = url.searchParams.get('max_age');
499
+ const freshParam = url.searchParams.get('fresh');
500
+ // Determine cache overrides
501
+ const overrides = {};
502
+ // Handle fresh bypass
503
+ if (freshParam === 'true' && config.cache.allowFreshBypass) {
504
+ overrides.fresh = true;
505
+ }
506
+ // Handle max_age override
507
+ if (maxAgeParam && !overrides.fresh) {
508
+ const parsedMaxAge = parseInt(maxAgeParam);
509
+ if (!isNaN(parsedMaxAge)) {
510
+ overrides.maxAge = Math.max(parsedMaxAge, 0);
511
+ }
512
+ }
513
+ // Build cache key (without query params)
514
+ const cacheKey = `${url.origin}${url.pathname}`;
515
+ // Try cache first (unless fresh bypass)
516
+ if (!overrides.fresh) {
517
+ try {
518
+ const cachedResponse = await caches.default.match(cacheKey);
519
+ if (cachedResponse) {
520
+ const age = getResponseAge(cachedResponse);
521
+ const isStale = isResponseStale(cachedResponse);
522
+ const responseHeaders = new Headers(cachedResponse.headers);
523
+ responseHeaders.set('X-Artifact-Age', String(age));
524
+ responseHeaders.set('X-Artifact-Source', 'cache');
525
+ const cachedETag = cachedResponse.headers.get('ETag');
526
+ const ifNoneMatch = request.headers.get('If-None-Match');
527
+ if (cachedETag && ifNoneMatch === cachedETag) {
528
+ return new Response(null, {
529
+ status: 304,
530
+ headers: {
531
+ 'ETag': cachedETag,
532
+ 'Cache-Control': cachedResponse.headers.get('Cache-Control') || buildCacheControl(config, overrides),
533
+ },
534
+ });
535
+ }
536
+ if (isStale) {
537
+ ctx.waitUntil(revalidateCache(cacheKey, parsed, config, options.reader, overrides));
538
+ // Record SWR revalidation
539
+ metrics.recordMetric('serve.swr_revalidation', 1, { ns, type, ext });
540
+ metrics.recordMetric('serve.cache_hit', 1, { ns, type, ext, stale: 'true' });
541
+ metrics.recordLatency('serve.latency', Date.now() - startTime, { ns, type, ext, source: 'cache' });
542
+ return new Response(method === 'HEAD' ? null : cachedResponse.body, {
543
+ status: cachedResponse.status,
544
+ headers: responseHeaders,
545
+ });
546
+ }
547
+ // Fresh cache hit
548
+ metrics.recordMetric('serve.cache_hit', 1, { ns, type, ext, stale: 'false' });
549
+ metrics.recordLatency('serve.latency', Date.now() - startTime, { ns, type, ext, source: 'cache' });
550
+ return new Response(method === 'HEAD' ? null : cachedResponse.body, {
551
+ status: cachedResponse.status,
552
+ headers: responseHeaders,
553
+ });
554
+ }
555
+ }
556
+ catch {
557
+ // Cache API error, continue to fetch from origin
558
+ }
559
+ }
560
+ // Fetch from IcebergReader
561
+ let record;
562
+ try {
563
+ record = await options.reader.getRecord({
564
+ table: 'do_resources',
565
+ partition: { ns, type },
566
+ id,
567
+ });
568
+ }
569
+ catch (error) {
570
+ if (error instanceof Error && error.message.includes('connection')) {
571
+ return jsonError('Storage backend unavailable', 502);
572
+ }
573
+ return jsonError('Internal server error', 500);
574
+ }
575
+ // Handle not found
576
+ if (!record) {
577
+ return jsonError('Artifact not found', 404);
578
+ }
579
+ // Get content from the appropriate column
580
+ const column = getColumnForExtension(ext);
581
+ const content = record[column];
582
+ // Handle missing column content
583
+ if (content === null || content === undefined) {
584
+ return jsonError('Artifact format not available', 404);
585
+ }
586
+ // Prepare response body
587
+ const isJson = ext.endsWith('.json') || ext === 'json';
588
+ const body = isJson ? JSON.stringify(content) : String(content);
589
+ // Generate ETag
590
+ const etag = await generateETag(body);
591
+ // Check If-None-Match
592
+ const ifNoneMatch = request.headers.get('If-None-Match');
593
+ if (ifNoneMatch === etag) {
594
+ return new Response(null, {
595
+ status: 304,
596
+ headers: {
597
+ 'ETag': etag,
598
+ 'Cache-Control': buildCacheControl(config, overrides),
599
+ },
600
+ });
601
+ }
602
+ // Build response headers
603
+ const contentType = getContentType(ext);
604
+ const cacheControl = buildCacheControl(config, overrides);
605
+ const contentLength = new TextEncoder().encode(body).length;
606
+ const headers = new Headers({
607
+ 'Content-Type': contentType,
608
+ 'Cache-Control': cacheControl,
609
+ 'ETag': etag,
610
+ 'X-Artifact-Source': 'parquet',
611
+ 'Vary': 'Accept-Encoding',
612
+ 'Content-Length': String(contentLength),
613
+ 'Date': new Date().toUTCString(),
614
+ });
615
+ const response = new Response(method === 'HEAD' ? null : body, {
616
+ status: 200,
617
+ headers,
618
+ });
619
+ // Cache the response
620
+ if (!overrides.fresh) {
621
+ try {
622
+ const putResult = caches.default.put(cacheKey, response.clone());
623
+ if (putResult && typeof putResult.then === 'function') {
624
+ ctx.waitUntil(putResult.catch(() => { }));
625
+ }
626
+ }
627
+ catch {
628
+ // Ignore cache put errors
629
+ }
630
+ }
631
+ // Record cache miss and latency for parquet fetch
632
+ metrics.recordMetric('serve.cache_miss', 1, { ns, type, ext });
633
+ metrics.recordLatency('serve.latency', Date.now() - startTime, { ns, type, ext, source: 'parquet' });
634
+ return response;
635
+ }
636
+ // ============================================================================
637
+ // In-Flight Request Management with TTL
638
+ // ============================================================================
639
+ /**
640
+ * TTL for in-flight request entries in milliseconds.
641
+ * Entries older than this are considered stale and cleaned up.
642
+ */
643
+ const IN_FLIGHT_TTL_MS = 30_000;
644
+ /**
645
+ * Maximum number of entries in the in-flight requests map.
646
+ * Prevents unbounded growth under high load.
647
+ */
648
+ const IN_FLIGHT_MAX_SIZE = 10_000;
649
+ /**
650
+ * In-flight requests map for request coalescing.
651
+ * Uses TTL-based cleanup to prevent memory leaks from hung requests.
652
+ */
653
+ const inFlightRequests = new Map();
654
+ /**
655
+ * Cleans up stale entries from the in-flight requests map.
656
+ * Removes entries older than IN_FLIGHT_TTL_MS.
657
+ */
658
+ function cleanupStaleEntries() {
659
+ const now = Date.now();
660
+ for (const [key, entry] of inFlightRequests) {
661
+ if (now - entry.createdAt > IN_FLIGHT_TTL_MS) {
662
+ inFlightRequests.delete(key);
663
+ }
664
+ }
665
+ }
666
+ /**
667
+ * Gets the current count of in-flight requests.
668
+ * Exported for testing to verify cleanup behavior.
669
+ */
670
+ export function getInFlightRequestCount() {
671
+ return inFlightRequests.size;
672
+ }
673
+ /**
674
+ * Clears all in-flight requests.
675
+ * Exported for testing to reset state between tests.
676
+ */
677
+ export function clearInFlightRequests() {
678
+ inFlightRequests.clear();
679
+ }
680
+ /**
681
+ * Integration test handleServe implementation returning ServeResult.
682
+ * Uses request coalescing to ensure concurrent requests for the same resource
683
+ * share a single reader call.
684
+ *
685
+ * Includes TTL-based cleanup to prevent memory leaks from hung requests.
686
+ */
687
+ async function handleServeIntegration(request, ctx, options) {
688
+ const url = new URL(request.url);
689
+ const cacheKey = url.toString();
690
+ // Lazy cleanup: purge stale entries on each access
691
+ cleanupStaleEntries();
692
+ // Check if there's an in-flight request for the same resource
693
+ const inFlight = inFlightRequests.get(cacheKey);
694
+ if (inFlight) {
695
+ // Check if the entry is still valid (within TTL)
696
+ const now = Date.now();
697
+ if (now - inFlight.createdAt <= IN_FLIGHT_TTL_MS) {
698
+ // Wait for the in-flight request to complete and return its result
699
+ return inFlight.promise;
700
+ }
701
+ // Entry is stale, remove it and proceed with a new request
702
+ inFlightRequests.delete(cacheKey);
703
+ }
704
+ // Enforce max size limit - if at capacity, clean up oldest entries
705
+ if (inFlightRequests.size >= IN_FLIGHT_MAX_SIZE) {
706
+ // Find and remove the oldest entries (LRU-style eviction)
707
+ const entriesToRemove = Math.max(1, Math.floor(IN_FLIGHT_MAX_SIZE * 0.1));
708
+ const sortedEntries = Array.from(inFlightRequests.entries())
709
+ .sort((a, b) => a[1].createdAt - b[1].createdAt);
710
+ for (let i = 0; i < entriesToRemove && i < sortedEntries.length; i++) {
711
+ inFlightRequests.delete(sortedEntries[i][0]);
712
+ }
713
+ }
714
+ // Create a promise for this request and store it with timestamp
715
+ const requestPromise = handleServeIntegrationInner(request, ctx, options);
716
+ const entry = {
717
+ promise: requestPromise,
718
+ createdAt: Date.now(),
719
+ };
720
+ inFlightRequests.set(cacheKey, entry);
721
+ try {
722
+ const result = await requestPromise;
723
+ return result;
724
+ }
725
+ finally {
726
+ // Clean up the in-flight request
727
+ inFlightRequests.delete(cacheKey);
728
+ }
729
+ }
730
+ /**
731
+ * Inner implementation of handleServeIntegration.
732
+ * Uses real IcebergReader interface with visibility, auth, and columns.
733
+ */
734
+ async function handleServeIntegrationInner(request, ctx, options) {
735
+ const url = new URL(request.url);
736
+ const metrics = options.metrics ?? noopMetrics;
737
+ const startTime = Date.now();
738
+ // Parse path
739
+ const parsed = parsePath(url);
740
+ if (!parsed) {
741
+ return {
742
+ status: 400,
743
+ body: JSON.stringify({ error: 'Invalid path format' }),
744
+ contentType: 'application/json',
745
+ headers: {},
746
+ };
747
+ }
748
+ const { ns, type, id, ext } = parsed;
749
+ // Check extension validity and get column
750
+ let column;
751
+ try {
752
+ column = getColumnForExtension(ext);
753
+ }
754
+ catch {
755
+ return {
756
+ status: 400,
757
+ body: JSON.stringify({ error: `Unsupported extension: ${ext}` }),
758
+ contentType: 'application/json',
759
+ headers: {},
760
+ };
761
+ }
762
+ // Use provided config or default
763
+ const config = options.tenantConfig || DEFAULT_CONFIG;
764
+ // Parse query params
765
+ const maxAgeParam = url.searchParams.get('max_age');
766
+ const freshParam = url.searchParams.get('fresh');
767
+ // Determine cache overrides
768
+ const overrides = {};
769
+ if (freshParam === 'true' && config.cache.allowFreshBypass) {
770
+ overrides.fresh = true;
771
+ }
772
+ if (maxAgeParam && !overrides.fresh) {
773
+ const parsedMaxAge = parseInt(maxAgeParam);
774
+ if (!isNaN(parsedMaxAge)) {
775
+ overrides.maxAge = Math.max(parsedMaxAge, 0);
776
+ }
777
+ }
778
+ // Build cache key
779
+ const cacheKey = url.toString();
780
+ // Extract auth context from request headers
781
+ const authContext = extractAuthContext(request);
782
+ // Determine visibility based on auth context
783
+ // For unlisted resources, we don't filter by visibility (undefined)
784
+ // This allows direct ID access while still hiding from listings
785
+ const visibility = determineVisibility(authContext);
786
+ // Get columns to request (id + requested column)
787
+ const columns = getColumnsForExtension(ext);
788
+ // Build partition filter with visibility
789
+ const partition = { ns, type };
790
+ if (visibility !== undefined) {
791
+ partition.visibility = visibility;
792
+ }
793
+ // Check if this is a protected visibility that requires auth
794
+ const requiresAuth = visibility === 'org' || visibility === 'user';
795
+ // Try cache first (unless fresh bypass)
796
+ if (!overrides.fresh) {
797
+ try {
798
+ const cachedResponse = await options.cache.match(cacheKey);
799
+ if (cachedResponse) {
800
+ const age = getResponseAge(cachedResponse);
801
+ const isStale = cachedResponse.headers.get('X-Cache-Stale') === 'true';
802
+ const cacheControl = cachedResponse.headers.get('Cache-Control') || buildCacheControl(config, overrides);
803
+ const headers = {
804
+ 'Cache-Control': cacheControl,
805
+ 'X-Artifact-Source': 'cache',
806
+ 'X-Artifact-Age': String(age),
807
+ };
808
+ if (isStale) {
809
+ headers['X-Cache-Stale'] = 'true';
810
+ // Trigger background revalidation
811
+ const revalidatePromise = (async () => {
812
+ const freshRecord = await options.reader.getRecord({
813
+ table: 'do_resources',
814
+ partition,
815
+ id,
816
+ auth: authContext,
817
+ columns,
818
+ });
819
+ if (freshRecord) {
820
+ const content = freshRecord[column];
821
+ if (content !== null && content !== undefined) {
822
+ const isJson = ext.endsWith('.json') || ext === 'json';
823
+ const body = isJson ? JSON.stringify(content) : String(content);
824
+ const cacheControl = buildCacheControl(config, overrides);
825
+ const response = new Response(body, {
826
+ headers: {
827
+ 'Content-Type': getContentType(ext),
828
+ 'Cache-Control': cacheControl,
829
+ },
830
+ });
831
+ await options.cache.put(cacheKey, response);
832
+ }
833
+ }
834
+ })();
835
+ ctx.waitUntil(revalidatePromise);
836
+ // Record SWR revalidation
837
+ metrics.recordMetric('serve.swr_revalidation', 1, { ns, type, ext });
838
+ }
839
+ const cachedBody = await cachedResponse.text();
840
+ const contentType = cachedResponse.headers.get('Content-Type') || getContentType(ext);
841
+ // Record cache hit metrics
842
+ metrics.recordMetric('serve.cache_hit', 1, { ns, type, ext, stale: String(isStale) });
843
+ metrics.recordLatency('serve.latency', Date.now() - startTime, { ns, type, ext, source: 'cache' });
844
+ return {
845
+ status: cachedResponse.status,
846
+ body: cachedBody,
847
+ contentType: contentType.split(';')[0].trim(),
848
+ headers,
849
+ };
850
+ }
851
+ }
852
+ catch {
853
+ // Cache error, continue to fetch from origin
854
+ }
855
+ }
856
+ // Fetch from IcebergReader using real interface
857
+ let record = null;
858
+ try {
859
+ record = await options.reader.getRecord({
860
+ table: 'do_resources',
861
+ partition,
862
+ id,
863
+ auth: authContext,
864
+ columns,
865
+ });
866
+ }
867
+ catch (error) {
868
+ return {
869
+ status: 500,
870
+ body: JSON.stringify({ error: 'Internal server error' }),
871
+ contentType: 'application/json',
872
+ headers: {},
873
+ };
874
+ }
875
+ // Handle visibility-based auth checks
876
+ // The record might be null because visibility doesn't match
877
+ // or because the record doesn't exist
878
+ // If record not found with visibility filter, try without filter
879
+ // This handles:
880
+ // 1. Unlisted artifacts (accessible by direct ID regardless of visibility)
881
+ // 2. Protected artifacts (need to check auth)
882
+ if (!record) {
883
+ // Check if auth is required but not present
884
+ if (requiresAuth && !authContext) {
885
+ return {
886
+ status: 401,
887
+ body: JSON.stringify({ error: 'Authentication required' }),
888
+ contentType: 'application/json',
889
+ headers: {
890
+ 'WWW-Authenticate': 'Bearer realm="artifacts"',
891
+ },
892
+ };
893
+ }
894
+ // Try fetching with no visibility filter to check if resource exists
895
+ // Include visibility column to check access control
896
+ const unfilteredColumns = [...columns, 'visibility', 'orgId', 'userId'];
897
+ const unfiltered = await options.reader.getRecord({
898
+ table: 'do_resources',
899
+ partition: { ns, type },
900
+ id,
901
+ columns: unfilteredColumns,
902
+ });
903
+ if (unfiltered) {
904
+ const actualVisibility = unfiltered.visibility;
905
+ // Unlisted artifacts are accessible by direct ID lookup
906
+ if (actualVisibility === 'unlisted') {
907
+ // Use the unfiltered record
908
+ record = unfiltered;
909
+ }
910
+ else if (actualVisibility === 'org' || actualVisibility === 'user') {
911
+ // Resource exists but is protected
912
+ if (!authContext) {
913
+ return {
914
+ status: 401,
915
+ body: JSON.stringify({ error: 'Authentication required' }),
916
+ contentType: 'application/json',
917
+ headers: {
918
+ 'WWW-Authenticate': 'Bearer realm="artifacts"',
919
+ },
920
+ };
921
+ }
922
+ // Auth present but doesn't match - access denied
923
+ return {
924
+ status: 403,
925
+ body: JSON.stringify({ error: 'Access denied' }),
926
+ contentType: 'application/json',
927
+ headers: {},
928
+ };
929
+ }
930
+ else {
931
+ // Public artifact should have been found earlier - this is unexpected
932
+ record = unfiltered;
933
+ }
934
+ }
935
+ }
936
+ // Still no record found
937
+ if (!record) {
938
+ return {
939
+ status: 404,
940
+ body: JSON.stringify({ error: 'Artifact not found' }),
941
+ contentType: 'application/json',
942
+ headers: {},
943
+ };
944
+ }
945
+ const recordVisibility = record.visibility;
946
+ // Double-check visibility matches auth
947
+ if (recordVisibility === 'org') {
948
+ const recordOrgId = record.orgId;
949
+ if (!authContext?.orgId || (recordOrgId && authContext.orgId !== recordOrgId)) {
950
+ return {
951
+ status: 403,
952
+ body: JSON.stringify({ error: 'Access denied' }),
953
+ contentType: 'application/json',
954
+ headers: {},
955
+ };
956
+ }
957
+ }
958
+ if (recordVisibility === 'user') {
959
+ const recordUserId = record.userId;
960
+ if (!authContext?.userId || (recordUserId && authContext.userId !== recordUserId)) {
961
+ return {
962
+ status: 403,
963
+ body: JSON.stringify({ error: 'Access denied' }),
964
+ contentType: 'application/json',
965
+ headers: {},
966
+ };
967
+ }
968
+ }
969
+ // Check legacy visibility === 'private' handling (backward compatibility)
970
+ const legacyVisibility = record.visibility;
971
+ if (legacyVisibility === 'private') {
972
+ if (!options.authenticatedNs) {
973
+ return {
974
+ status: 401,
975
+ body: JSON.stringify({ error: 'Authentication required' }),
976
+ contentType: 'application/json',
977
+ headers: {},
978
+ };
979
+ }
980
+ if (options.authenticatedNs !== ns) {
981
+ return {
982
+ status: 403,
983
+ body: JSON.stringify({ error: 'Access denied' }),
984
+ contentType: 'application/json',
985
+ headers: {},
986
+ };
987
+ }
988
+ }
989
+ // Get content from the appropriate column (column already defined above)
990
+ const content = record[column];
991
+ // Handle missing column content
992
+ if (content === null || content === undefined) {
993
+ return {
994
+ status: 404,
995
+ body: JSON.stringify({ error: 'Artifact format not available' }),
996
+ contentType: 'application/json',
997
+ headers: {},
998
+ };
999
+ }
1000
+ // Prepare response body
1001
+ const isJson = ext.endsWith('.json') || ext === 'json';
1002
+ const body = isJson ? JSON.stringify(content) : String(content);
1003
+ // Build response headers
1004
+ const contentType = getContentType(ext);
1005
+ const cacheControl = buildCacheControl(config, overrides);
1006
+ const headers = {
1007
+ 'Cache-Control': cacheControl,
1008
+ 'X-Artifact-Source': 'parquet',
1009
+ };
1010
+ // Cache the response
1011
+ if (!overrides.fresh) {
1012
+ try {
1013
+ const cacheResponse = new Response(body, {
1014
+ headers: {
1015
+ 'Content-Type': contentType,
1016
+ 'Cache-Control': cacheControl,
1017
+ },
1018
+ });
1019
+ await options.cache.put(cacheKey, cacheResponse);
1020
+ }
1021
+ catch {
1022
+ // Ignore cache put errors
1023
+ }
1024
+ }
1025
+ // Record cache miss metrics
1026
+ metrics.recordMetric('serve.cache_miss', 1, { ns, type, ext });
1027
+ metrics.recordLatency('serve.latency', Date.now() - startTime, { ns, type, ext, source: 'parquet' });
1028
+ return {
1029
+ status: 200,
1030
+ body,
1031
+ contentType: contentType.split(';')[0].trim(),
1032
+ headers,
1033
+ };
1034
+ }
1035
+ //# sourceMappingURL=artifacts-serve.js.map