better-supabase 0.5.0 → 0.6.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 (536) hide show
  1. package/README.md +299 -41
  2. package/dist/access-sql-B0dF-SoF.js +181 -0
  3. package/dist/actor-CQTlc1Hh.d.ts +43 -0
  4. package/dist/adapter-Bc1o3zyy.js +1106 -0
  5. package/dist/agents-CVHGd7It.d.ts +94 -0
  6. package/dist/ai-cache-CBWs1W0k.js +80 -0
  7. package/dist/ai-cache-DaDu_G5L.d.ts +64 -0
  8. package/dist/ai-chat-7amvXjmQ.d.ts +590 -0
  9. package/dist/ai-chat-B24UI962.js +743 -0
  10. package/dist/ai-files-CSPNswUY.js +319 -0
  11. package/dist/ai-sdk/agents/index.d.ts +74 -0
  12. package/dist/ai-sdk/agents/index.js +159 -0
  13. package/dist/ai-sdk/batches/index.d.ts +74 -0
  14. package/dist/ai-sdk/batches/index.js +180 -0
  15. package/dist/ai-sdk/cache/index.d.ts +38 -0
  16. package/dist/ai-sdk/cache/index.js +127 -0
  17. package/dist/ai-sdk/chat/index.d.ts +2 -0
  18. package/dist/ai-sdk/chat/index.js +246 -0
  19. package/dist/ai-sdk/embeddings/index.d.ts +3 -0
  20. package/dist/ai-sdk/embeddings/index.js +2 -0
  21. package/dist/ai-sdk/files/index.d.ts +61 -0
  22. package/dist/ai-sdk/files/index.js +111 -0
  23. package/dist/ai-sdk/index.d.ts +145 -0
  24. package/dist/ai-sdk/index.js +174 -0
  25. package/dist/ai-sdk/mcp/index.d.ts +102 -0
  26. package/dist/ai-sdk/mcp/index.js +362 -0
  27. package/dist/ai-sdk/memory/index.d.ts +67 -0
  28. package/dist/ai-sdk/memory/index.js +175 -0
  29. package/dist/ai-sdk/react/index.d.ts +2 -0
  30. package/dist/ai-sdk/react/index.js +3 -0
  31. package/dist/ai-sdk/react/server.d.ts +6 -0
  32. package/dist/ai-sdk/react/server.js +7 -0
  33. package/dist/ai-sdk/workflow/index.d.ts +249 -0
  34. package/dist/ai-sdk/workflow/index.js +575 -0
  35. package/dist/ai-sdk/workflow/react/index.d.ts +20 -0
  36. package/dist/ai-sdk/workflow/react/index.js +66 -0
  37. package/dist/ai-sdk/workflow/react/server.d.ts +10 -0
  38. package/dist/ai-sdk/workflow/react/server.js +12 -0
  39. package/dist/ai-tasks-Cd1HMFvB.d.ts +131 -0
  40. package/dist/announcements-CiyOs-xP.js +107 -0
  41. package/dist/announcements-ZRGnScWI.d.ts +72 -0
  42. package/dist/api-keys-rCGtKaZA.d.ts +156 -0
  43. package/dist/assistant-DwdpDH1q.d.ts +147 -0
  44. package/dist/astro/index.d.ts +86 -0
  45. package/dist/astro/index.js +93 -0
  46. package/dist/attributes-5dYul0u0.js +13 -0
  47. package/dist/auth-link-BjFPqwLz.d.ts +97 -0
  48. package/dist/auth-link-DrDX9KOT.js +114 -0
  49. package/dist/authorization-BFCqamMs.d.ts +172 -0
  50. package/dist/before-route-CnR0C0Jr.js +30 -0
  51. package/dist/bind-Bu_ZNa3W.d.ts +62 -0
  52. package/dist/bind-l4kAvYSv.js +97 -0
  53. package/dist/block-events-CrT8r7Fe.js +76 -0
  54. package/dist/block-fields-GFkxl4dP.js +49 -0
  55. package/dist/block-fields-SJ6fEf3o.d.ts +31 -0
  56. package/dist/block-helpers-CKtbFcRv.d.ts +37 -0
  57. package/dist/block-helpers-DWHxHsEQ.js +190 -0
  58. package/dist/block-hooks-BmjMyXTa.js +114 -0
  59. package/dist/block-hooks-plbZ0AbJ.d.ts +107 -0
  60. package/dist/{kit-transport-DJlHQUBU.js → block-transport-BLr-ZgLc.js} +11 -7
  61. package/dist/block-transport-DvaoPuZ9.d.ts +48 -0
  62. package/dist/blocks/agents/index.d.ts +3 -0
  63. package/dist/blocks/agents/index.js +123 -0
  64. package/dist/blocks/ai-cache/index.d.ts +3 -0
  65. package/dist/blocks/ai-cache/index.js +3 -0
  66. package/dist/blocks/ai-chat/index.d.ts +31 -0
  67. package/dist/blocks/ai-chat/index.js +5 -0
  68. package/dist/blocks/ai-chat/react/index.d.ts +77 -0
  69. package/dist/blocks/ai-chat/react/index.js +387 -0
  70. package/dist/blocks/ai-chat/react/server.d.ts +16 -0
  71. package/dist/blocks/ai-chat/react/server.js +14 -0
  72. package/dist/blocks/ai-files/index.d.ts +14 -0
  73. package/dist/blocks/ai-files/index.js +3 -0
  74. package/dist/blocks/ai-providers/index.d.ts +7 -0
  75. package/dist/blocks/ai-providers/index.js +211 -0
  76. package/dist/blocks/ai-tasks/index.d.ts +3 -0
  77. package/dist/blocks/ai-tasks/index.js +203 -0
  78. package/dist/blocks/announcements/index.d.ts +3 -0
  79. package/dist/blocks/announcements/index.js +3 -0
  80. package/dist/blocks/announcements/react/index.d.ts +31 -0
  81. package/dist/blocks/announcements/react/index.js +86 -0
  82. package/dist/blocks/announcements/react/server.d.ts +6 -0
  83. package/dist/blocks/announcements/react/server.js +7 -0
  84. package/dist/blocks/api-keys/index.d.ts +35 -0
  85. package/dist/blocks/api-keys/index.js +334 -0
  86. package/dist/blocks/attachments/index.d.ts +210 -0
  87. package/dist/blocks/attachments/index.js +222 -0
  88. package/dist/blocks/audit/index.d.ts +366 -0
  89. package/dist/blocks/audit/index.js +714 -0
  90. package/dist/blocks/billing/index.d.ts +265 -0
  91. package/dist/blocks/billing/index.js +400 -0
  92. package/dist/blocks/comments/index.d.ts +200 -0
  93. package/dist/blocks/comments/index.js +207 -0
  94. package/dist/blocks/connectors/index.d.ts +3 -0
  95. package/dist/blocks/connectors/index.js +164 -0
  96. package/dist/blocks/data-lifecycle/index.d.ts +251 -0
  97. package/dist/blocks/data-lifecycle/index.js +353 -0
  98. package/dist/blocks/entitlements/index.d.ts +14 -0
  99. package/dist/blocks/entitlements/index.js +25 -0
  100. package/dist/blocks/flags/index.d.ts +226 -0
  101. package/dist/blocks/flags/index.js +282 -0
  102. package/dist/blocks/inbox/index.d.ts +6 -0
  103. package/dist/blocks/inbox/index.js +499 -0
  104. package/dist/blocks/inbox/react/index.d.ts +2 -0
  105. package/dist/blocks/inbox/react/index.js +225 -0
  106. package/dist/blocks/inbox/react/server.d.ts +10 -0
  107. package/dist/blocks/inbox/react/server.js +12 -0
  108. package/dist/blocks/index.d.ts +65 -0
  109. package/dist/blocks/index.js +77 -0
  110. package/dist/blocks/jobs/index.d.ts +297 -0
  111. package/dist/blocks/jobs/index.js +1051 -0
  112. package/dist/blocks/knowledge/index.d.ts +3 -0
  113. package/dist/blocks/knowledge/index.js +3 -0
  114. package/dist/blocks/memory/index.d.ts +8 -0
  115. package/dist/blocks/memory/index.js +289 -0
  116. package/dist/blocks/notifications/index.d.ts +297 -0
  117. package/dist/blocks/notifications/index.js +425 -0
  118. package/dist/blocks/notifications/react/index.d.ts +44 -0
  119. package/dist/blocks/notifications/react/index.js +67 -0
  120. package/dist/blocks/notifications/react/server.d.ts +6 -0
  121. package/dist/blocks/notifications/react/server.js +7 -0
  122. package/dist/blocks/onboarding/index.d.ts +3 -0
  123. package/dist/blocks/onboarding/index.js +64 -0
  124. package/dist/blocks/onboarding/react/index.d.ts +28 -0
  125. package/dist/blocks/onboarding/react/index.js +78 -0
  126. package/dist/blocks/onboarding/react/server.d.ts +7 -0
  127. package/dist/blocks/onboarding/react/server.js +7 -0
  128. package/dist/blocks/organizations/index.d.ts +241 -0
  129. package/dist/blocks/organizations/index.js +400 -0
  130. package/dist/blocks/outbox/index.d.ts +3 -0
  131. package/dist/blocks/outbox/index.js +266 -0
  132. package/dist/blocks/profiles/index.d.ts +40 -0
  133. package/dist/blocks/profiles/index.js +28 -0
  134. package/dist/blocks/push/index.d.ts +185 -0
  135. package/dist/blocks/push/index.js +258 -0
  136. package/dist/blocks/settings/index.d.ts +89 -0
  137. package/dist/blocks/settings/index.js +104 -0
  138. package/dist/blocks/sso/index.d.ts +198 -0
  139. package/dist/blocks/sso/index.js +1297 -0
  140. package/dist/blocks/usage/index.d.ts +4 -0
  141. package/dist/blocks/usage/index.js +233 -0
  142. package/dist/blocks/waitlist/index.d.ts +117 -0
  143. package/dist/blocks/waitlist/index.js +132 -0
  144. package/dist/blocks/webhooks/index.d.ts +304 -0
  145. package/dist/{webhooks → blocks/webhooks}/index.js +468 -148
  146. package/dist/blocks/workflow-builder/index.d.ts +21 -0
  147. package/dist/blocks/workflow-builder/index.js +5 -0
  148. package/dist/blocks/workflow-builder/react/index.d.ts +47 -0
  149. package/dist/blocks/workflow-builder/react/index.js +119 -0
  150. package/dist/blocks/workflow-builder/react/server.d.ts +8 -0
  151. package/dist/blocks/workflow-builder/react/server.js +11 -0
  152. package/dist/blocks/workflows/index.d.ts +3 -0
  153. package/dist/blocks/workflows/index.js +3 -0
  154. package/dist/blocks/workflows/react/index.d.ts +47 -0
  155. package/dist/blocks/workflows/react/index.js +137 -0
  156. package/dist/blocks/workflows/react/server.d.ts +8 -0
  157. package/dist/blocks/workflows/react/server.js +11 -0
  158. package/dist/{index-SaTsEfGP.d.ts → bucket-Cl46E3jS.d.ts} +134 -91
  159. package/dist/{simplify-_0oUKqCd.js → build-BXIzE6Xv.js} +222 -124
  160. package/dist/{channel-FQW-CnGR.d.ts → channel-_2Mx1Oyu.d.ts} +2 -2
  161. package/dist/channels-CkgQ_X60.js +114 -0
  162. package/dist/chat-sdk/index.d.ts +303 -0
  163. package/dist/chat-sdk/index.js +733 -0
  164. package/dist/chat-sdk/react/index.d.ts +2 -0
  165. package/dist/chat-sdk/react/index.js +3 -0
  166. package/dist/chat-sdk/react/server.d.ts +3 -0
  167. package/dist/chat-sdk/react/server.js +2 -0
  168. package/dist/claims-DDBQT6BK.js +61 -0
  169. package/dist/{claims-BAf6c7is.js → claims-PqzCxaTD.js} +39 -2
  170. package/dist/cli/bin.js +1 -1
  171. package/dist/cli/{codemod-DqV7612m.js → codemod-Bl_tQpHn.js} +105 -2
  172. package/dist/cli/config-B9HkL8nF.js +2 -0
  173. package/dist/cli/config-DU7EJImt.js +49 -0
  174. package/dist/cli/config-NiPxAmUF.js +1077 -0
  175. package/dist/cli/{dist-xn4wlboc.js → dist-DRAVaE6w.js} +111 -1
  176. package/dist/cli/{doctor-L7Z8mYTx.js → doctor-B4JOm_ox.js} +992 -316
  177. package/dist/cli/{env-DmKv_G4A.js → env-CJLbUVPB.js} +1 -1
  178. package/dist/cli/{env-jIFUADLY.js → env-qXgyMV6r.js} +2 -2
  179. package/dist/cli/fresh-import-BeYf9I3x.js +40 -0
  180. package/dist/cli/gen-BGLb_zou.js +3100 -0
  181. package/dist/cli/gen-DAH8FM2W.js +2 -0
  182. package/dist/cli/index.d.ts +44 -5
  183. package/dist/cli/index.js +7 -7
  184. package/dist/cli/{init-Saf5-R9T.js → init-BIIwclPg.js} +242 -27
  185. package/dist/cli/{introspect--H7oGmcd.js → introspect-DNUTuNvb.js} +5 -5
  186. package/dist/cli/{io-k41_z-jT.js → io-DI1mTdSA.js} +12 -3
  187. package/dist/cli/{keys-ByKHnvP9.js → keys-BGCnnb-s.js} +8 -6
  188. package/dist/cli/{ohash-D7bG_EOb.js → ohash-BjQY1cBb.js} +1 -1
  189. package/dist/cli/{openapi-Cp5Fyta-.js → openapi-BlRJ18s3.js} +2 -2
  190. package/dist/cli/{project-B_GeXyHh.js → project-DZRaBaY7.js} +11 -6
  191. package/dist/cli/read-sets-CETOSFx4.js +223 -0
  192. package/dist/cli/{run-BKR2W_79.js → run-DNWxHynX.js} +33 -23
  193. package/dist/cli/{seed-CUlqWh-8.js → seed-DQzqT7LP.js} +8 -6
  194. package/dist/cli/{skills-LJJIhLFR.js → skills-QQs4q68J.js} +3 -2
  195. package/dist/cli/sql-D0GUtUPp.js +536 -0
  196. package/dist/cli/{config-CPVpXP5I.js → supabase-toml-B-bIukQF.js} +72 -942
  197. package/dist/cli/typegen-version-DeFbtzYI.js +8 -0
  198. package/dist/cli/{version-BDJuO0UR.js → version-hr88OZG0.js} +1 -1
  199. package/dist/client/index.d.ts +3 -2
  200. package/dist/client/index.js +11 -71
  201. package/dist/client/native/index.d.ts +228 -0
  202. package/dist/client/native/index.js +361 -0
  203. package/dist/client-CI0Z2zWX.js +277 -0
  204. package/dist/cloud-event-BnmD5fb9.js +46 -0
  205. package/dist/cloud-event-C1JCc1bH.d.ts +50 -0
  206. package/dist/{compiler-BXH_aZ7N.d.ts → compiler-DY_wkj4R.d.ts} +2 -2
  207. package/dist/compose-BIBNgAnq.js +53 -0
  208. package/dist/composite-Bt1YK3Ke.d.ts +146 -0
  209. package/dist/composite-DhE66P5u.js +130 -0
  210. package/dist/config/index.d.ts +6 -4
  211. package/dist/config/index.js +5 -5
  212. package/dist/{config-T_ejwyd3.d.ts → config-D44Y7PAv.d.ts} +406 -205
  213. package/dist/{config-DreERlKo.js → config-Dn7F5k9A.js} +62 -14
  214. package/dist/connector-Dv8kK0MO.d.ts +107 -0
  215. package/dist/connectors-hkLCwyEz.d.ts +120 -0
  216. package/dist/context-BCrWe7ow.d.ts +237 -0
  217. package/dist/credentials/index.d.ts +31 -0
  218. package/dist/credentials/index.js +168 -0
  219. package/dist/cron-DVPUg5k3.js +145 -0
  220. package/dist/csv-CPAzEmB6.js +25 -0
  221. package/dist/db-exception-CnWcYPxL.js +15 -0
  222. package/dist/{define-cNKzqwL_.js → define-B-TVjC25.js} +784 -227
  223. package/dist/{define-Bp_BZa5h.d.ts → define-CJJkkr1-.d.ts} +353 -61
  224. package/dist/edge/index.d.ts +69 -11
  225. package/dist/edge/index.js +191 -75
  226. package/dist/elysia/index.d.ts +59 -0
  227. package/dist/elysia/index.js +62 -0
  228. package/dist/embeddings-4ZvFzjsU.js +127 -0
  229. package/dist/embeddings-C9KGQnXt.d.ts +68 -0
  230. package/dist/{refresh-BTrQ_ZTp.d.ts → ended-jLfvw8h-.d.ts} +20 -2
  231. package/dist/{entitlements-DUxU4oHe.d.ts → entitlements-DHrY2UDZ.d.ts} +16 -7
  232. package/dist/entitlements-DRl6ZJUm.js +24 -0
  233. package/dist/envelope-DqnYGRw7.js +47 -0
  234. package/dist/{errors-DtwXa6b8.js → errors-B11Duf73.js} +82 -13
  235. package/dist/errors-DLjJHPLK.js +44 -0
  236. package/dist/escape-like-DHfx6jRr.js +7 -0
  237. package/dist/escape-like-ImTK0ps_.d.ts +15 -0
  238. package/dist/eve/index.d.ts +220 -0
  239. package/dist/eve/index.js +504 -0
  240. package/dist/eve-gT2Fpjut.d.ts +151 -0
  241. package/dist/events/index.d.ts +4 -3
  242. package/dist/events/index.js +9 -50
  243. package/dist/events-DWDhx77e.d.ts +549 -0
  244. package/dist/executor-Bq28caBo.js +718 -0
  245. package/dist/executor-C-R4pucm.js +83 -0
  246. package/dist/executor-CaWigTyR.d.ts +148 -0
  247. package/dist/executor-D14tGopJ.d.ts +36 -0
  248. package/dist/{executor-Cghdstwu.d.ts → executor-DbvExCFC.d.ts} +7 -1
  249. package/dist/{sql-Coh6n4cC.js → executor-De1Of3m3.js} +210 -11
  250. package/dist/executor-DvsBbNBS.d.ts +261 -0
  251. package/dist/expo/index.d.ts +79 -0
  252. package/dist/expo/index.js +129 -0
  253. package/dist/expo-sqlite/index.d.ts +58 -0
  254. package/dist/expo-sqlite/index.js +104 -0
  255. package/dist/field-errors-BFOa7WhB.d.ts +99 -0
  256. package/dist/field-errors-VF1yb8yI.js +13 -0
  257. package/dist/framework-CONDyPYx.js +51 -0
  258. package/dist/framework-DuVb4Xa8.d.ts +25 -0
  259. package/dist/gateway-C1ssRHnf.d.ts +130 -0
  260. package/dist/graph-BGyLLFLX.js +265 -0
  261. package/dist/h3/index.d.ts +59 -0
  262. package/dist/h3/index.js +55 -0
  263. package/dist/h3/v1/index.d.ts +63 -0
  264. package/dist/h3/v1/index.js +49 -0
  265. package/dist/history-BZih6SFP.js +64 -0
  266. package/dist/hono/index.d.ts +56 -7
  267. package/dist/hono/index.js +97 -45
  268. package/dist/{react-C_s7kiNz.js → hooks-1BTCJsiw.js} +104 -133
  269. package/dist/http-Cv4T29xh.js +40 -0
  270. package/dist/http-DdYadv-o.js +126 -0
  271. package/dist/image-url-pIVlO6R1.js +41 -0
  272. package/dist/impersonation-DcqaTBHx.js +148 -0
  273. package/dist/inbox-i7E1ppo6.d.ts +384 -0
  274. package/dist/index-ACiNPN4Z.d.ts +74 -0
  275. package/dist/{index-DgILJRXj.d.ts → index-Bepi-Hgg.d.ts} +91 -28
  276. package/dist/index-BkWT_uBO.d.ts +51 -0
  277. package/dist/index-BpFYMsi0.d.ts +53 -0
  278. package/dist/{index-C7f8cOBM.d.ts → index-Bxih3l0u.d.ts} +2 -2
  279. package/dist/index-CaxrMD1A.d.ts +1 -0
  280. package/dist/index-ClmjK_o-.d.ts +55 -0
  281. package/dist/index-CtuakVRc.d.ts +267 -0
  282. package/dist/index-D48jVS-N.d.ts +246 -0
  283. package/dist/index-DDrYaUcq.d.ts +138 -0
  284. package/dist/index-DQh4VCc9.d.ts +98 -0
  285. package/dist/{live-D9W2M5aO.d.ts → index-DunNRdnT.d.ts} +136 -38
  286. package/dist/index.d.ts +65 -18
  287. package/dist/index.js +42 -14
  288. package/dist/invalidate-BBqhpxyB.d.ts +19 -0
  289. package/dist/json-CEnpTHlZ.js +16 -0
  290. package/dist/json-schema-CvER4kgT.js +502 -0
  291. package/dist/keys-4MnkWTTh.js +35 -0
  292. package/dist/kit-B5tid-q5.d.ts +60 -0
  293. package/dist/kit-Fm-Fv7q5.js +78 -0
  294. package/dist/knowledge-BXBbT0Tr.d.ts +176 -0
  295. package/dist/knowledge-CDmiMLew.js +255 -0
  296. package/dist/lint/index.d.ts +1 -1
  297. package/dist/lint/index.js +37 -0
  298. package/dist/list/index.d.ts +3 -2
  299. package/dist/list/index.js +1 -444
  300. package/dist/{index-fag8VJZ2.d.ts → list-query-BRt-zDF7.d.ts} +20 -12
  301. package/dist/list-query-gl808ac1.js +459 -0
  302. package/dist/{live-CdJBOc4f.js → live-CTAZQkwl.js} +99 -14
  303. package/dist/logger-DWVYUmwi.d.ts +18 -0
  304. package/dist/mcp/index.d.ts +38 -11
  305. package/dist/mcp/index.js +82 -46
  306. package/dist/mcp/sdk/index.d.ts +105 -0
  307. package/dist/mcp/sdk/index.js +126 -0
  308. package/dist/message-CaKlqdsX.js +219 -0
  309. package/dist/message-CfDoR0Y6.d.ts +94 -0
  310. package/dist/messages-B9-5887d.js +715 -0
  311. package/dist/messages-DYP0sUTe.d.ts +59 -0
  312. package/dist/{mfa-CusDgPaD.d.ts → mfa-Daak8ukG.d.ts} +8 -2
  313. package/dist/modules-DpPwzC7E.d.ts +192 -0
  314. package/dist/modules-dxrVK3ID.js +228 -0
  315. package/dist/nestjs/index.d.ts +68 -0
  316. package/dist/nestjs/index.js +164 -0
  317. package/dist/next/client/index.d.ts +35 -0
  318. package/dist/next/client/index.js +36 -0
  319. package/dist/next/image/index.js +3 -24
  320. package/dist/next/index.d.ts +150 -51
  321. package/dist/next/index.js +261 -107
  322. package/dist/node/index.d.ts +4 -0
  323. package/dist/node/index.js +210 -0
  324. package/dist/node-headers-AO06VhqH.d.ts +24 -0
  325. package/dist/node-headers-C71zNQHe.js +55 -0
  326. package/dist/nuxt/index.d.ts +60 -0
  327. package/dist/nuxt/index.js +64 -0
  328. package/dist/onboarding-BZGHYJks.d.ts +62 -0
  329. package/dist/openapi/index.d.ts +1 -1
  330. package/dist/openapi/index.js +3 -3
  331. package/dist/orpc/index.d.ts +70 -11
  332. package/dist/orpc/index.js +73 -24
  333. package/dist/otel/index.d.ts +7 -7
  334. package/dist/otel/index.js +87 -57
  335. package/dist/outbox-Cq7pPYvl.d.ts +161 -0
  336. package/dist/{plugin-o144f322.d.ts → plugin-FGY8UhYm.d.ts} +30 -10
  337. package/dist/plugin-tfXW1-qO.js +45 -0
  338. package/dist/plugins/actor/index.d.ts +2 -2
  339. package/dist/plugins/actor/index.js +3 -3
  340. package/dist/plugins/rules/index.d.ts +11 -4
  341. package/dist/plugins/rules/index.js +40 -13
  342. package/dist/plugins/soft-delete/index.d.ts +5 -4
  343. package/dist/plugins/soft-delete/index.js +10 -5
  344. package/dist/plugins/tenant/index.d.ts +2 -2
  345. package/dist/plugins/tenant/index.js +18 -11
  346. package/dist/plugins/timestamps/index.d.ts +5 -3
  347. package/dist/plugins/timestamps/index.js +6 -4
  348. package/dist/plugins/validation/index.d.ts +2 -2
  349. package/dist/plugins/validation/index.js +10 -6
  350. package/dist/{pool-C9voM4Kw.d.ts → pool-CAu2nLrL.d.ts} +2 -2
  351. package/dist/postgres/index.d.ts +4 -4
  352. package/dist/postgres/index.js +1 -2
  353. package/dist/{postgrest-CruYKAvV.d.ts → postgrest-BLVdEnfe.d.ts} +13 -3
  354. package/dist/powersync/index.d.ts +43 -0
  355. package/dist/powersync/index.js +199 -0
  356. package/dist/powersync/react/index.d.ts +87 -0
  357. package/dist/powersync/react/index.js +108 -0
  358. package/dist/{problem-BW1KhzSG.js → problem-BreOl8b8.js} +14 -3
  359. package/dist/{problem-Blxe8S31.d.ts → problem-DD8LZFjc.d.ts} +21 -3
  360. package/dist/provider-BH5UWZJ6.d.ts +113 -0
  361. package/dist/provider-Di3sRutW.js +57 -0
  362. package/dist/query/index.d.ts +4 -2
  363. package/dist/query/index.js +4 -3
  364. package/dist/{query-CCbo44lG.js → query-BQ-NaZon.js} +141 -24
  365. package/dist/{queue-BYUCjr59.d.ts → queue-CLJurhdy.d.ts} +258 -83
  366. package/dist/react/index.d.ts +11 -7
  367. package/dist/react/index.js +7 -3
  368. package/dist/react/native/index.d.ts +116 -0
  369. package/dist/react/native/index.js +130 -0
  370. package/dist/react/server.d.ts +28 -10
  371. package/dist/react/server.js +22 -5
  372. package/dist/react/session.d.ts +2 -2
  373. package/dist/react-4ZqkP8da.js +79 -0
  374. package/dist/react-F80mF4-t.js +409 -0
  375. package/dist/react-router/index.d.ts +83 -0
  376. package/dist/react-router/index.js +89 -0
  377. package/dist/react-topic-seAWjL3_.js +38 -0
  378. package/dist/{read-set-DQFt1N_Y.js → read-set-DuKWC9Mj.js} +67 -25
  379. package/dist/realtime/index.d.ts +4 -5
  380. package/dist/realtime/index.js +144 -82
  381. package/dist/refusal-CeJWxmkH.js +61 -0
  382. package/dist/refusal-D1rgbzNq.d.ts +15 -0
  383. package/dist/replicas-CwrYp5Zj.js +94 -0
  384. package/dist/{view-Bq64ki8-.js → resolve-D_IcnvFd.js} +239 -289
  385. package/dist/{resolve-C15TJq77.d.ts → resolve-KW8nCeL6.d.ts} +29 -45
  386. package/dist/{resource-CRxxwkQq.js → resource-BsvzCMlX.js} +4 -4
  387. package/dist/{resource-D8iqWMr-.d.ts → resource-D6Uc7Sk7.d.ts} +5 -5
  388. package/dist/respond-BUgsXNgk.js +91 -0
  389. package/dist/respond-DoBijysA.d.ts +93 -0
  390. package/dist/result-D7y2fT87.d.ts +282 -0
  391. package/dist/{result-BKiliPY7.js → result-DzUbqgdW.js} +27 -2
  392. package/dist/run-state-CPo-1X2B.d.ts +12 -0
  393. package/dist/run-state-CgBKzYMW.js +13 -0
  394. package/dist/search-CUWCRYKL.js +9 -0
  395. package/dist/server/index.d.ts +143 -21
  396. package/dist/server/index.js +46 -14
  397. package/dist/{respond-D2OQl62-.d.ts → server-CeVeeXwz.d.ts} +225 -80
  398. package/dist/session-ChiXDZdZ.d.ts +72 -0
  399. package/dist/shared-CBxosKK3.js +75 -0
  400. package/dist/{shared-D1dRUrAg.js → shared-CxWmcyqB.js} +2 -1
  401. package/dist/shared-DDSjEGDR.d.ts +11 -0
  402. package/dist/sign-in-BCV5S2WZ.d.ts +49 -0
  403. package/dist/sign-in-Cb1mwv6X.js +86 -0
  404. package/dist/{signers-Cc6r98SW.d.ts → signers-4y62_VIi.d.ts} +13 -9
  405. package/dist/simplify-DDO0j0LI.js +83 -0
  406. package/dist/solid/index.d.ts +131 -0
  407. package/dist/solid/index.js +344 -0
  408. package/dist/solid-start/index.d.ts +75 -0
  409. package/dist/solid-start/index.js +58 -0
  410. package/dist/{spec-pins-C3oc-Tdp.js → spec-pins--j6mTok4.js} +8 -1
  411. package/dist/{spec-pins-CqorWDnO.d.ts → spec-pins-C89s6DyW.d.ts} +7 -0
  412. package/dist/sql/index.d.ts +330 -194
  413. package/dist/sql/index.js +34901 -6489
  414. package/dist/ssr/index.d.ts +6 -5
  415. package/dist/ssr/index.js +3 -3
  416. package/dist/{standard-D7r6dMUu.js → standard-BK0cqJ7y.js} +4 -3
  417. package/dist/{standard-J4YgMOLm.d.ts → standard-CYsm7xpT.d.ts} +1 -1
  418. package/dist/stats-D_AD9GGu.js +1123 -0
  419. package/dist/storage/index.d.ts +5 -5
  420. package/dist/storage/index.js +1027 -368
  421. package/dist/store-BdH5ruDx.js +66 -0
  422. package/dist/store-CGjgWu5n.d.ts +68 -0
  423. package/dist/streams/index.d.ts +76 -0
  424. package/dist/streams/index.js +77 -0
  425. package/dist/streams/redis/index.d.ts +52 -0
  426. package/dist/streams/redis/index.js +185 -0
  427. package/dist/stripe-BMj5LtsT.js +38 -0
  428. package/dist/stripe-D04h-QBT.d.ts +136 -0
  429. package/dist/{support-Dk6vdmEw.js → support-BivNqSpK.js} +35 -12
  430. package/dist/{support-view-Da5peKFg.d.ts → support-view-33Oi07TT.d.ts} +1 -1
  431. package/dist/svelte/index.d.ts +114 -0
  432. package/dist/svelte/index.js +344 -0
  433. package/dist/sveltekit/index.d.ts +116 -0
  434. package/dist/sveltekit/index.js +106 -0
  435. package/dist/tags-B99_U9Pp.js +39 -0
  436. package/dist/tags-Cx-LnIQP.d.ts +30 -0
  437. package/dist/tanstack-db/index.d.ts +71 -0
  438. package/dist/tanstack-db/index.js +72 -0
  439. package/dist/tanstack-start/index.d.ts +75 -0
  440. package/dist/tanstack-start/index.js +95 -0
  441. package/dist/tee-CoEppCER.js +101 -0
  442. package/dist/{template-TxrGI5jR.js → template--jDkl3UP.js} +34 -13
  443. package/dist/template-CwO38Do8.d.ts +6 -0
  444. package/dist/temporal-BJGitm-h.js +79 -0
  445. package/dist/{temporal-required-CAt4Dc9Z.js → temporal-required-BVvW7SYJ.js} +3 -2
  446. package/dist/tenant-I1TEZ_BX.js +27 -0
  447. package/dist/tenant-icPeuUBN.d.ts +17 -0
  448. package/dist/testing/index.d.ts +343 -37
  449. package/dist/testing/index.js +1186 -79
  450. package/dist/timing-CyTQPubY.js +92 -0
  451. package/dist/ttl-Hr34gRu8.js +12 -0
  452. package/dist/{types-Dnf_wRDz.d.ts → types-6BdhkmZe.d.ts} +111 -45
  453. package/dist/types-BI-0Sl2v.d.ts +359 -0
  454. package/dist/types-C05bxW-W.d.ts +220 -0
  455. package/dist/types-DGs5L0VN.d.ts +192 -0
  456. package/dist/types-TaiEeBGA.d.ts +156 -0
  457. package/dist/usage-CrvrL2vw.d.ts +177 -0
  458. package/dist/user-change-XzvgafU4.js +130 -0
  459. package/dist/vault-CSO7ss5C.d.ts +50 -0
  460. package/dist/vercel-connect/index.d.ts +47 -0
  461. package/dist/vercel-connect/index.js +119 -0
  462. package/dist/{verify-Ce49ETPz.js → verify-DL41hbvY.js} +85 -13
  463. package/dist/verify-DPuWngD4.d.ts +172 -0
  464. package/dist/{version-DM_Roffb.js → version-Bvv6aLjZ.js} +1 -1
  465. package/dist/view-BeFcRV6G.js +99 -0
  466. package/dist/{view-BEf0diC5.d.ts → view-DbxLNu8L.d.ts} +35 -32
  467. package/dist/vue/index.d.ts +141 -0
  468. package/dist/vue/index.js +362 -0
  469. package/dist/watch-DI34G86j.d.ts +60 -0
  470. package/dist/watch-Xuxrvxq-.js +122 -0
  471. package/dist/{wire-D2dT86vi.js → wire-DtZQ4sdE.js} +15 -5
  472. package/dist/workflow-builder-DJ1oHn8g.js +398 -0
  473. package/dist/workflow-sdk/builder/index.d.ts +85 -0
  474. package/dist/workflow-sdk/builder/index.js +250 -0
  475. package/dist/workflow-sdk/index.d.ts +14 -0
  476. package/dist/workflow-sdk/index.js +3 -0
  477. package/dist/workflow-sdk/world/index.d.ts +107 -0
  478. package/dist/workflow-sdk/world/index.js +702 -0
  479. package/dist/workflow-sdk-B3yLD6Br.d.ts +98 -0
  480. package/dist/workflow-sdk-C2WJ_G2O.js +173 -0
  481. package/dist/workflow-sdk-world-D-prir6O.js +1397 -0
  482. package/dist/workflows-B0j9WKPu.d.ts +185 -0
  483. package/dist/workflows-wu1WWBn7.js +216 -0
  484. package/package.json +470 -29
  485. package/schemas/ai-message-v1.json +258 -0
  486. package/schemas/config-v1.json +1074 -140
  487. package/schemas/doctor-report-v1.json +21 -1
  488. package/schemas/snapshot-v2.json +8 -0
  489. package/skills/better-supabase/SKILL.md +41 -12
  490. package/skills/better-supabase/references/frontend.md +96 -0
  491. package/skills/better-supabase/references/plugins.md +10 -6
  492. package/skills/better-supabase/references/troubleshooting.md +2 -1
  493. package/skills/better-supabase-api/SKILL.md +76 -22
  494. package/skills/better-supabase-api/references/adapters.md +207 -0
  495. package/skills/better-supabase-api/references/ai.md +125 -0
  496. package/skills/better-supabase-api/references/expo.md +65 -0
  497. package/skills/better-supabase-api/references/powersync.md +35 -0
  498. package/skills/better-supabase-api/references/streams-credentials.md +105 -0
  499. package/skills/better-supabase-auth/SKILL.md +62 -18
  500. package/skills/better-supabase-auth/references/authorization-providers.md +51 -0
  501. package/skills/better-supabase-testing/SKILL.md +3 -1
  502. package/dist/cli/config-dtoekM0v.js +0 -2
  503. package/dist/cli/gen-BJc9vdyu.js +0 -2
  504. package/dist/cli/gen-C2osRcr5.js +0 -2204
  505. package/dist/cli/read-sets-BfMQcKy8.js +0 -289
  506. package/dist/cli/sql-DzPbfngx.js +0 -348
  507. package/dist/entitlements-CTEBm7IB.js +0 -20
  508. package/dist/events-aIO7vsIk.d.ts +0 -230
  509. package/dist/executor-BunPOO-k.d.ts +0 -402
  510. package/dist/executor-XycESQPH.js +0 -94
  511. package/dist/impersonation-VfaGc4Q_.js +0 -41
  512. package/dist/index-BGdgBPdf.d.ts +0 -161
  513. package/dist/index-DOESKG27.d.ts +0 -100
  514. package/dist/index-DmXSxbLi.d.ts +0 -62
  515. package/dist/index-Dxn2a5cw.d.ts +0 -50
  516. package/dist/jobs/index.d.ts +0 -226
  517. package/dist/jobs/index.js +0 -1007
  518. package/dist/json-schema-m1fiwKgN.js +0 -208
  519. package/dist/kit-events-CTFDfSwb.js +0 -60
  520. package/dist/kit-transport-B82k1djG.d.ts +0 -33
  521. package/dist/kits-CGLLrNPd.js +0 -157
  522. package/dist/notifications/index.d.ts +0 -162
  523. package/dist/notifications/index.js +0 -264
  524. package/dist/orgs/index.d.ts +0 -132
  525. package/dist/orgs/index.js +0 -245
  526. package/dist/permdock-sql-C2Inc6Xc.d.ts +0 -29
  527. package/dist/permdock-sql-DgOfeixi.js +0 -51
  528. package/dist/plugin-DwARMuQp.js +0 -59
  529. package/dist/server-Csx2sRjg.js +0 -655
  530. package/dist/stats-Bqob3WqT.js +0 -592
  531. package/dist/support-DT-OhSxD.d.ts +0 -88
  532. package/dist/template-mbvlysOo.d.ts +0 -6
  533. package/dist/temporal-CVlIV2DZ.js +0 -40
  534. package/dist/user-change-CHFEUro1.js +0 -49
  535. package/dist/webhooks/index.d.ts +0 -285
  536. package/skills/better-supabase-auth/references/permdock.md +0 -100
@@ -1,31 +1,717 @@
1
- import { n as dbError, o as isDbError, t as DbException } from "../errors-DtwXa6b8.js";
2
- import { r as optionalTemporal } from "../temporal-CVlIV2DZ.js";
3
- import { n as temporalMissing } from "../temporal-required-CAt4Dc9Z.js";
4
- import { i as ok, n as err, o as toDbError, t as AsyncResult } from "../result-BKiliPY7.js";
5
- import { o as spansAllTenants, s as tenantOf } from "../plugin-DwARMuQp.js";
6
- import { i as tenantClaimPaths, n as claimAt } from "../claims-BAf6c7is.js";
7
- import { i as sqlString, n as slug, r as sqlIdent, t as parseTemplate } from "../template-TxrGI5jR.js";
8
- import { n as permdockCheck } from "../permdock-sql-DgOfeixi.js";
1
+ import { t as DbException } from "../db-exception-CnWcYPxL.js";
2
+ import { n as dbError } from "../errors-B11Duf73.js";
3
+ import { c as optionalTemporal } from "../temporal-BJGitm-h.js";
4
+ import { n as temporalMissing } from "../temporal-required-BVvW7SYJ.js";
5
+ import { a as ok, r as err, s as toDbError, t as AsyncResult } from "../result-DzUbqgdW.js";
6
+ import { a as spansAllTenants } from "../plugin-tfXW1-qO.js";
7
+ import { a as tenantClaimPaths, o as tenantFrom, s as tenantPathsFor } from "../claims-PqzCxaTD.js";
8
+ import { n as ttlSeconds, t as TTL } from "../ttl-Hr34gRu8.js";
9
+ import { i as sqlString, n as slug, r as sqlIdent, t as parseTemplate } from "../template--jDkl3UP.js";
10
+ import { t as storageImageUrl } from "../image-url-pIVlO6R1.js";
11
+ import { t as fromStorageError } from "../errors-DLjJHPLK.js";
12
+ import { r as accessCheck } from "../access-sql-B0dF-SoF.js";
13
+ //#region src/storage/versioning.ts
14
+ const MAX_DAYS = 2147483647;
15
+ function wholeIn(value, min, max) {
16
+ return Number.isInteger(value) && value >= min && value <= max;
17
+ }
18
+ /** Checks a lifecycle policy and fills Storage's defaults; throws a `TypeError` for one Storage would refuse. */
19
+ function lifecyclePayload(id, lifecycle) {
20
+ const rules = lifecycle.rules;
21
+ if (rules.length === 0 || rules.length > 1e3) throw new TypeError(`defineBucket("${id}"): lifecycle needs 1 to 1000 rules`);
22
+ const ids = rules.flatMap((rule) => rule.id === void 0 ? [] : [rule.id]);
23
+ if (new Set(ids).size !== ids.length) throw new TypeError(`defineBucket("${id}"): lifecycle rule ids repeat`);
24
+ return { rules: rules.map((rule) => {
25
+ const { noncurrentDays, newerNoncurrentVersions } = rule.noncurrentVersionExpiration;
26
+ if (!wholeIn(noncurrentDays, 1, MAX_DAYS)) throw new TypeError(`defineBucket("${id}"): noncurrentDays must be a whole number of days, at least 1`);
27
+ if (newerNoncurrentVersions !== void 0 && !wholeIn(newerNoncurrentVersions, 1, 100)) throw new TypeError(`defineBucket("${id}"): newerNoncurrentVersions must be 1 to 100`);
28
+ return {
29
+ ...rule.id === void 0 ? {} : { id: rule.id },
30
+ status: rule.status ?? "Enabled",
31
+ filter: {},
32
+ noncurrentVersionExpiration: {
33
+ noncurrentDays,
34
+ ...newerNoncurrentVersions === void 0 ? {} : { newerNoncurrentVersions }
35
+ }
36
+ };
37
+ }) };
38
+ }
39
+ function ruleKey(rule) {
40
+ if (typeof rule !== "object" || rule === null) return JSON.stringify(rule);
41
+ const entry = rule;
42
+ const expiration = entry.noncurrentVersionExpiration ?? {};
43
+ return JSON.stringify([
44
+ entry.status ?? "Enabled",
45
+ expiration.noncurrentDays,
46
+ expiration.newerNoncurrentVersions ?? null
47
+ ]);
48
+ }
49
+ /**
50
+ * Whether a stored `lifecycle_configuration` matches the expected payload.
51
+ * Generated rule ids are ignored; the rules compare as a set.
52
+ */
53
+ function sameLifecycle(expected, actual) {
54
+ const stored = typeof actual === "object" && actual !== null && "rules" in actual && Array.isArray(actual.rules) ? actual.rules : [];
55
+ const want = (expected?.rules ?? []).map(ruleKey).sort();
56
+ const have = stored.map(ruleKey).sort();
57
+ return want.length === have.length && want.every((key, i) => key === have[i]);
58
+ }
59
+ /** Storage codes for a lifecycle that is absent or a feature the project doesn't have. */
60
+ function lifecycleAbsent(error) {
61
+ return error.code === "NoSuchLifecycleConfiguration" || error.kind === "unsupported";
62
+ }
63
+ function toVersion(item) {
64
+ if (!item.version) return void 0;
65
+ const metadata = item.metadata ?? {};
66
+ const size = metadata["size"];
67
+ const type = metadata["mimetype"];
68
+ return {
69
+ versionId: item.version,
70
+ current: !item.archived_at,
71
+ deleteMarker: item.is_delete_marker === true,
72
+ ...typeof size === "number" ? { size } : {},
73
+ ...typeof type === "string" ? { contentType: type } : {},
74
+ createdAt: item.created_at ?? null,
75
+ archivedAt: item.archived_at ?? null
76
+ };
77
+ }
78
+ //#endregion
79
+ //#region src/storage/apply.ts
80
+ /**
81
+ * Creates or updates a bucket through the Storage API, then sets or removes
82
+ * its lifecycle policy. Versioning and lifecycle live behind the API, so
83
+ * `bucket.sql()` can't write them.
84
+ */
85
+ function applyBucket(settings, client) {
86
+ const storage = client.storage;
87
+ const fail = (raw) => err(fromStorageError(raw, settings.id));
88
+ return AsyncResult.from(async () => {
89
+ try {
90
+ const limits = {
91
+ public: settings.public,
92
+ fileSizeLimit: settings.fileSizeLimit ?? null,
93
+ allowedMimeTypes: settings.allowedMimeTypes ? [...settings.allowedMimeTypes] : null
94
+ };
95
+ const found = await storage.getBucket(settings.id);
96
+ const missing = found.error !== null && fromStorageError(found.error).kind === "not_found";
97
+ if (found.error && !missing) return fail(found.error);
98
+ if (missing) {
99
+ const created = await storage.createBucket(settings.id, {
100
+ ...limits,
101
+ ...settings.versioning ? { versioningStatus: "ENABLED" } : {}
102
+ });
103
+ if (created.error) return fail(created.error);
104
+ } else {
105
+ const current = found.data?.versioning_status;
106
+ const versioningStatus = settings.versioning ? "ENABLED" : current === "ENABLED" ? "SUSPENDED" : void 0;
107
+ const updated = await storage.updateBucket(settings.id, {
108
+ ...limits,
109
+ ...versioningStatus ? { versioningStatus } : {}
110
+ });
111
+ if (updated.error) return fail(updated.error);
112
+ }
113
+ if (settings.lifecycle) {
114
+ const set = await storage.updateBucketLifecycle(settings.id, { rules: settings.lifecycle.rules.map((rule) => ({ ...rule })) });
115
+ if (set.error) return fail(set.error);
116
+ } else if (!missing) {
117
+ const removed = await storage.deleteBucketLifecycle(settings.id);
118
+ if (removed.error && !lifecycleAbsent(fromStorageError(removed.error))) return fail(removed.error);
119
+ }
120
+ return ok({ created: missing });
121
+ } catch (cause) {
122
+ return fail(cause);
123
+ }
124
+ });
125
+ }
126
+ //#endregion
127
+ //#region src/storage/layouts.ts
128
+ const SAFE_SEGMENT = /^[\w!\-.*'() &$@=;:+,?]+$/;
129
+ function validateSegment(_name, value) {
130
+ if (value === "." || value === "..") return "is a relative path";
131
+ if (!SAFE_SEGMENT.test(value)) return "contains characters Storage does not allow";
132
+ }
133
+ const keysOf = (values) => Object.keys(values).filter((key) => values[key] !== void 0);
134
+ /** The longest folder, segment by segment, that holds every one of `folders`. */
135
+ function commonFolder(folders) {
136
+ const split = folders.map((folder) => folder ? folder.split("/") : []);
137
+ const shared = [];
138
+ for (const [index, segment] of (split[0] ?? []).entries()) {
139
+ if (split.some((parts) => parts[index] !== segment)) break;
140
+ shared.push(segment);
141
+ }
142
+ return shared.join("/");
143
+ }
144
+ function pathLayouts(id, path, sqlString) {
145
+ const sources = typeof path === "string" ? [path] : path;
146
+ if (sources.length === 0) throw new TypeError(`defineBucket(${id}): path needs a template`);
147
+ const layouts = sources.map((source) => parseTemplate(source, "/", validateSegment, { rest: true }));
148
+ const layoutFor = (values) => {
149
+ if (layouts.length === 1) return layouts[0];
150
+ const keys = keysOf(values);
151
+ const layout = layouts.find((candidate) => candidate.params.length === keys.length && candidate.params.every((name) => keys.includes(name)));
152
+ if (layout) return layout;
153
+ throw new DbException(dbError("invalid_input", `No path template of bucket "${id}" takes exactly {${keys.join(", ")}}`));
154
+ };
155
+ const patterns = layouts.map((layout) => `name ~ ${sqlString(layout.sqlPattern)}`);
156
+ return {
157
+ sources,
158
+ params: [...new Set(layouts.flatMap((layout) => layout.params))],
159
+ build(values) {
160
+ const filled = values;
161
+ return layoutFor(values).build(filled);
162
+ },
163
+ match(value) {
164
+ for (const layout of layouts) {
165
+ const values = layout.match(value);
166
+ if (values) return values;
167
+ }
168
+ return null;
169
+ },
170
+ prefix(values) {
171
+ const given = keysOf(values);
172
+ const fitting = layouts.filter((layout) => given.every((key) => layout.params.includes(key)));
173
+ return commonFolder((fitting.length > 0 ? fitting : layouts).map((layout) => layout.prefix(values, layout.rest === void 0 ? layout.segments - 1 : layout.segments)));
174
+ },
175
+ segmentOf(param, kind) {
176
+ const indexes = layouts.map((layout) => {
177
+ const index = layout.segmentOf(param);
178
+ if (index === void 0) throw new TypeError(`defineBucket: a ${kind} policy needs {${param}} as a whole path segment in "${layout.source}"`);
179
+ return index;
180
+ });
181
+ if (new Set(indexes).size > 1) throw new TypeError(`defineBucket: a ${kind} policy needs {${param}} at the same segment in every path of "${id}"`);
182
+ return indexes[0];
183
+ },
184
+ requireParam(param, label) {
185
+ for (const layout of layouts) if (!layout.params.includes(param)) throw new TypeError(`defineBucket: ${label} {${param}} is not in "${layout.source}"`);
186
+ },
187
+ sqlMatch: patterns.length === 1 ? patterns[0] : `(${patterns.join(" or ")})`
188
+ };
189
+ }
190
+ /** Whether matched path values hold every value of `within`; a rest value may sit under it. */
191
+ function inScope(values, within) {
192
+ if (!values) return false;
193
+ return Object.entries(within).every(([key, expected]) => {
194
+ if (expected === void 0) return true;
195
+ const actual = String(values[key]);
196
+ return actual === String(expected) || actual.startsWith(`${String(expected)}/`);
197
+ });
198
+ }
199
+ //#endregion
200
+ //#region src/storage/progress.ts
201
+ /** The runtime's `XMLHttpRequest`, or `undefined` (Node, workers). */
202
+ function xhrConstructor() {
203
+ const runtime = globalThis;
204
+ return typeof runtime.XMLHttpRequest === "function" ? runtime.XMLHttpRequest : void 0;
205
+ }
206
+ /** Bodies `XMLHttpRequest.send` takes; streams go through storage-js instead. */
207
+ function sendable(body) {
208
+ return !(typeof ReadableStream !== "undefined" && body instanceof ReadableStream);
209
+ }
210
+ /**
211
+ * PUTs `body` to a signed upload URL with upload progress events. Resolves
212
+ * with `undefined` on success or the failure to map.
213
+ */
214
+ function xhrUpload(Request, url, body, options) {
215
+ return new Promise((resolve) => {
216
+ const xhr = new Request();
217
+ const onAbort = () => {
218
+ xhr.abort();
219
+ };
220
+ const done = (failure) => {
221
+ options.signal?.removeEventListener("abort", onAbort);
222
+ resolve(failure);
223
+ };
224
+ xhr.open("PUT", url);
225
+ xhr.setRequestHeader("x-upsert", String(options.upsert));
226
+ xhr.setRequestHeader("cache-control", `max-age=${options.cacheControl ?? "3600"}`);
227
+ const type = options.contentType ?? (typeof Blob !== "undefined" && body instanceof Blob && body.type ? body.type : void 0);
228
+ if (type) xhr.setRequestHeader("content-type", type);
229
+ xhr.upload.onprogress = (event) => {
230
+ if (event.lengthComputable && event.total > 0) options.onProgress(Math.min(1, event.loaded / event.total));
231
+ };
232
+ xhr.onload = () => {
233
+ if (xhr.status >= 200 && xhr.status < 300) {
234
+ options.onProgress(1);
235
+ done(void 0);
236
+ return;
237
+ }
238
+ done(parseFailure(xhr.status, xhr.responseText));
239
+ };
240
+ xhr.onerror = () => {
241
+ done({
242
+ message: "Upload failed",
243
+ statusCode: "0",
244
+ name: "StorageUnknownError"
245
+ });
246
+ };
247
+ xhr.onabort = () => {
248
+ done({
249
+ message: "The upload was aborted",
250
+ statusCode: "0",
251
+ name: "AbortError"
252
+ });
253
+ };
254
+ options.signal?.addEventListener("abort", onAbort, { once: true });
255
+ xhr.send(body);
256
+ });
257
+ }
258
+ function parseFailure(status, text) {
259
+ let message = `Upload failed with status ${String(status)}`;
260
+ let statusCode = String(status);
261
+ try {
262
+ const parsed = JSON.parse(text);
263
+ if (typeof parsed === "object" && parsed !== null) {
264
+ const fields = parsed;
265
+ if (typeof fields.message === "string") message = fields.message;
266
+ if (typeof fields.statusCode === "string") statusCode = fields.statusCode;
267
+ }
268
+ } catch {}
269
+ return {
270
+ message,
271
+ statusCode
272
+ };
273
+ }
274
+ //#endregion
275
+ //#region src/storage/connect.ts
276
+ /** Folder listings in flight at once while `list` walks a tree. */
277
+ const LIST_CONCURRENCY = 4;
278
+ /** Signed URLs kept per connected bucket; the oldest goes first. */
279
+ const SIGNED_CACHE_SIZE = 500;
280
+ /** Runs at most `limit` of the calls passed to the returned function at once. */
281
+ function pool(limit) {
282
+ let active = 0;
283
+ const waiting = [];
284
+ return async (fn) => {
285
+ if (active < limit) active++;
286
+ else await new Promise((resolve) => {
287
+ waiting.push(resolve);
288
+ });
289
+ try {
290
+ return await fn();
291
+ } finally {
292
+ const next = waiting.shift();
293
+ if (next) next();
294
+ else active--;
295
+ }
296
+ };
297
+ }
298
+ function bodyInfo(body, contentType) {
299
+ const info = {};
300
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
301
+ info.size = body.size;
302
+ if (body.type) info.type = body.type;
303
+ } else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) info.size = body.byteLength;
304
+ if (contentType) info.type = contentType;
305
+ return info;
306
+ }
307
+ /** Rejects with the signal's reason when it aborts before `promise` settles. */
308
+ async function abortable(promise, signal) {
309
+ if (!signal) return promise;
310
+ signal.throwIfAborted();
311
+ let abort = () => {};
312
+ const aborted = new Promise((_, reject) => {
313
+ abort = () => {
314
+ const reason = signal.reason;
315
+ reject(reason instanceof Error ? reason : new Error(String(reason)));
316
+ };
317
+ signal.addEventListener("abort", abort, { once: true });
318
+ });
319
+ try {
320
+ return await Promise.race([promise, aborted]);
321
+ } finally {
322
+ signal.removeEventListener("abort", abort);
323
+ }
324
+ }
325
+ function isErrorResult(value) {
326
+ return typeof value === "object" && value !== null && value.ok === false && "error" in value;
327
+ }
328
+ function connectBucket(bucket, client, resolveTarget, connectOptions, guard) {
329
+ const resolve = (target) => {
330
+ const path = resolveTarget(target);
331
+ return guard ? guard.path(path) : path;
332
+ };
333
+ const scopedWithin = (within) => guard ? {
334
+ ...within,
335
+ [guard.param]: guard.within(within)
336
+ } : within ?? {};
337
+ const api = () => client.storage.from(bucket.id);
338
+ const signed = connectOptions.cacheSignedUrls ? /* @__PURE__ */ new Map() : void 0;
339
+ /** Drops cached signed URLs for `paths`; keys start with the JSON-quoted path. */
340
+ const evict = (paths) => {
341
+ if (!signed || signed.size === 0) return;
342
+ const prefixes = paths.map((path) => `[${JSON.stringify(path)},`);
343
+ for (const key of signed.keys()) if (prefixes.some((prefix) => key.startsWith(prefix))) signed.delete(key);
344
+ };
345
+ const run = (fn) => AsyncResult.from(async () => {
346
+ try {
347
+ const { data, error } = await fn();
348
+ return error || data === null ? err(fromStorageError(error, bucket.id)) : ok(data);
349
+ } catch (cause) {
350
+ return err(fromStorageError(cause, bucket.id));
351
+ }
352
+ });
353
+ const fileOptions = (options, contentType) => ({
354
+ ...contentType ? { contentType } : {},
355
+ ...options?.cacheControl ? { cacheControl: options.cacheControl } : {},
356
+ ...options?.metadata ? { metadata: { ...options.metadata } } : {},
357
+ upsert: options?.upsert ?? false
358
+ });
359
+ const download = (value) => value === void 0 ? {} : { download: value };
360
+ const urlExtras = (options) => ({
361
+ ...download(options?.download),
362
+ ...options?.transform ? { transform: { ...options.transform } } : {},
363
+ ...options?.cacheNonce ? { cacheNonce: options.cacheNonce } : {},
364
+ ...options?.versionId ? { versionId: options.versionId } : {}
365
+ });
366
+ const upload = (target, body, options) => AsyncResult.from(async () => {
367
+ options?.signal?.throwIfAborted();
368
+ const path = resolve(target);
369
+ const contentType = options?.contentType;
370
+ const problem = bucket.check(bodyInfo(body, contentType));
371
+ if (problem) return err(problem);
372
+ const Request = options?.onProgress ? xhrConstructor() : void 0;
373
+ if (options?.onProgress && Request && !options.metadata && sendable(body)) {
374
+ const reserved = await run(() => api().createSignedUploadUrl(path, options.upsert ? { upsert: true } : void 0));
375
+ if (!reserved.ok) return reserved;
376
+ const failure = await xhrUpload(Request, reserved.data.signedUrl, body, {
377
+ contentType,
378
+ cacheControl: options.cacheControl,
379
+ upsert: options.upsert ?? false,
380
+ signal: options.signal,
381
+ onProgress: options.onProgress
382
+ });
383
+ if (failure) return err(fromStorageError(failure, bucket.id));
384
+ evict([path]);
385
+ return ok({ path });
386
+ }
387
+ return run(() => abortable(api().upload(path, body, fileOptions(options, contentType)), options?.signal)).map(() => {
388
+ evict([path]);
389
+ options?.onProgress?.(1);
390
+ return { path };
391
+ });
392
+ });
393
+ const remove = (targets) => AsyncResult.from(async () => {
394
+ const paths = targets.map(resolve);
395
+ if (paths.length === 0) return ok([]);
396
+ return run(() => api().remove(paths)).map(() => {
397
+ evict(paths);
398
+ return paths;
399
+ });
400
+ });
401
+ const transfer = (method) => (from, to, options) => AsyncResult.from(async () => {
402
+ const [source, path] = [resolve(from), resolve(to)];
403
+ return run(() => api()[method](source, path, options?.versionId ? { sourceVersionId: options.versionId } : void 0)).map(() => {
404
+ evict(method === "move" ? [source, path] : [path]);
405
+ return { path };
406
+ });
407
+ });
408
+ const versions = (target, options) => AsyncResult.from(async () => {
409
+ const path = resolve(target);
410
+ const out = [];
411
+ for (let cursor;;) {
412
+ options?.signal?.throwIfAborted();
413
+ const { data, error } = await api().listV2({
414
+ prefix: path,
415
+ exactMatch: true,
416
+ noncurrentVersions: "include",
417
+ deleteMarkers: "include",
418
+ sortBy: {
419
+ column: "created_at",
420
+ order: "desc"
421
+ },
422
+ ...cursor ? { cursor } : {}
423
+ }, options?.signal ? { signal: options.signal } : {});
424
+ if (error) return err(fromStorageError(error, bucket.id));
425
+ for (const item of data.objects) {
426
+ const version = toVersion(item);
427
+ if (version) out.push(version);
428
+ }
429
+ if (!data.hasNext || !data.nextCursor) return ok(out);
430
+ cursor = data.nextCursor;
431
+ }
432
+ });
433
+ const toObject = (path, item) => {
434
+ const metadata = item.metadata ?? {};
435
+ return {
436
+ path,
437
+ ...typeof metadata.size === "number" ? { size: metadata.size } : {},
438
+ ...metadata.mimetype ? { contentType: metadata.mimetype } : {},
439
+ createdAt: item.created_at,
440
+ updatedAt: item.updated_at
441
+ };
442
+ };
443
+ const PAGE = 1e3;
444
+ const page = async (folder, offset, signal) => {
445
+ signal?.throwIfAborted();
446
+ const { data, error } = await api().list(folder, {
447
+ limit: PAGE,
448
+ offset,
449
+ sortBy: {
450
+ column: "name",
451
+ order: "asc"
452
+ }
453
+ }, signal ? { signal } : {});
454
+ if (error) throw new DbException(fromStorageError(error, bucket.id));
455
+ return data;
456
+ };
457
+ const walk = async (folder, signal, request) => {
458
+ const parts = [];
459
+ for (let offset = 0;; offset += PAGE) {
460
+ const data = await request(() => page(folder, offset, signal));
461
+ for (const item of data) {
462
+ const path = folder ? `${folder}/${item.name}` : item.name;
463
+ if (item.id === null) {
464
+ const nested = walk(path, signal, request);
465
+ nested.catch(() => void 0);
466
+ parts.push(nested);
467
+ } else parts.push(toObject(path, item));
468
+ }
469
+ if (data.length < PAGE) break;
470
+ }
471
+ const out = [];
472
+ for (const part of parts) if (part instanceof Promise) for (const each of await part) out.push(each);
473
+ else out.push(part);
474
+ return out;
475
+ };
476
+ /** Yields objects one listing page at a time, depth first, in `list` order. */
477
+ async function* stream(folder, signal) {
478
+ for (let offset = 0;; offset += PAGE) {
479
+ const data = await page(folder, offset, signal);
480
+ for (const item of data) {
481
+ const path = folder ? `${folder}/${item.name}` : item.name;
482
+ if (item.id === null) yield* stream(path, signal);
483
+ else yield toObject(path, item);
484
+ }
485
+ if (data.length < PAGE) return;
486
+ }
487
+ }
488
+ const list = (within, options) => AsyncResult.from(async () => ok(await walk(bucket.prefix(scopedWithin(within)), options?.signal, pool(LIST_CONCURRENCY)))).mapError((error) => ({
489
+ ...error,
490
+ table: bucket.id
491
+ }));
492
+ const signedUrl = (target, options) => AsyncResult.from(async () => {
493
+ const path = resolve(target);
494
+ const ttl = ttlSeconds(options?.ttl);
495
+ const key = signed ? JSON.stringify([
496
+ path,
497
+ ttl,
498
+ options?.transform,
499
+ options?.download,
500
+ options?.cacheNonce,
501
+ options?.versionId
502
+ ]) : "";
503
+ const cached = signed?.get(key);
504
+ if (cached) {
505
+ if (cached.until > Date.now()) return ok(cached.url);
506
+ signed?.delete(key);
507
+ }
508
+ const result = await run(() => api().createSignedUrl(path, ttl, urlExtras(options))).map((data) => data.signedUrl);
509
+ if (signed && result.ok) {
510
+ const margin = Math.min(60, ttl / 10);
511
+ if (signed.size >= SIGNED_CACHE_SIZE) {
512
+ const oldest = signed.keys().next();
513
+ if (!oldest.done) signed.delete(oldest.value);
514
+ }
515
+ signed.set(key, {
516
+ url: result.data,
517
+ until: Date.now() + (ttl - margin) * 1e3
518
+ });
519
+ }
520
+ return result;
521
+ });
522
+ return {
523
+ bucket,
524
+ path: (target) => {
525
+ try {
526
+ return ok(resolve(target));
527
+ } catch (cause) {
528
+ return err(toDbError(cause));
529
+ }
530
+ },
531
+ upload,
532
+ download: (target, options) => AsyncResult.from(async () => {
533
+ const path = resolve(target);
534
+ return run(() => api().download(path, options?.versionId ? { versionId: options.versionId } : {}, options?.signal ? { signal: options.signal } : {}));
535
+ }),
536
+ exists: (target) => AsyncResult.from(async () => {
537
+ const path = resolve(target);
538
+ let found;
539
+ try {
540
+ found = await api().exists(path);
541
+ } catch (cause) {
542
+ return err(fromStorageError(cause, bucket.id));
543
+ }
544
+ if (found.data && !found.error) return ok(true);
545
+ if (!found.error) return ok(false);
546
+ const error = fromStorageError(found.error, bucket.id);
547
+ return error.status === 400 || error.status === 404 ? ok(false) : err(error);
548
+ }),
549
+ remove,
550
+ copy: transfer("copy"),
551
+ move: transfer("move"),
552
+ versions,
553
+ removeVersions: (target, versionIds) => AsyncResult.from(async () => {
554
+ const path = resolve(target);
555
+ if (versionIds.length === 0) return ok([]);
556
+ return run(() => api().remove(versionIds.map((versionId) => ({
557
+ path,
558
+ versionId
559
+ })))).map(() => {
560
+ evict([path]);
561
+ return versionIds;
562
+ });
563
+ }),
564
+ purgeCache: (target, options) => AsyncResult.from(async () => {
565
+ const path = resolve(target);
566
+ return run(() => api().purgeCache(path, options?.transformations ? { transformations: true } : {}, options?.signal ? { signal: options.signal } : {})).map(() => void 0);
567
+ }),
568
+ list,
569
+ signedUrl,
570
+ signedUrls: (targets, options) => AsyncResult.from(async () => {
571
+ const paths = targets.map(resolve);
572
+ if (paths.length === 0) return ok([]);
573
+ return run(() => api().createSignedUrls(paths, ttlSeconds(options?.ttl), {
574
+ ...download(options?.download),
575
+ ...options?.cacheNonce ? { cacheNonce: options.cacheNonce } : {}
576
+ })).andThen((data) => {
577
+ const failed = data.find((entry) => entry.error != null || !entry.signedUrl);
578
+ if (failed) return Promise.resolve(err(dbError("not_found", failed.error ?? `No URL for ${String(failed.path)}`, { table: bucket.id })));
579
+ return Promise.resolve(ok(data.map((entry) => entry.signedUrl)));
580
+ });
581
+ }),
582
+ publicUrl(target, options) {
583
+ try {
584
+ return ok(api().getPublicUrl(resolve(target), urlExtras(options)).data.publicUrl);
585
+ } catch (cause) {
586
+ return err(toDbError(cause));
587
+ }
588
+ },
589
+ renderUrl(target, transform, options) {
590
+ if (!bucket.public) return signedUrl(target, {
591
+ ...options,
592
+ transform
593
+ });
594
+ return AsyncResult.from(() => Promise.resolve(ok(api().getPublicUrl(resolve(target), urlExtras({
595
+ ...options,
596
+ transform
597
+ })).data.publicUrl)));
598
+ },
599
+ replace: (target, body, options = {}) => AsyncResult.from(async () => {
600
+ const path = resolve(target);
601
+ const previous = options.previous == null ? null : resolve(options.previous);
602
+ const same = previous === path;
603
+ const uploaded = await upload(path, body, {
604
+ ...options,
605
+ upsert: same || (options.upsert ?? false)
606
+ });
607
+ if (!uploaded.ok) return uploaded;
608
+ const undo = async (error) => {
609
+ if (!same) await remove([path]);
610
+ return err(error);
611
+ };
612
+ try {
613
+ options.signal?.throwIfAborted();
614
+ const outcome = await options.commit?.(path);
615
+ if (isErrorResult(outcome)) return await undo(outcome.error);
616
+ } catch (cause) {
617
+ return undo(fromStorageError(cause, bucket.id));
618
+ }
619
+ if (!previous || same) return ok({
620
+ path,
621
+ removed: null
622
+ });
623
+ const removed = await run(() => api().remove([previous]));
624
+ if (removed.ok) evict([previous]);
625
+ return ok(removed.ok ? {
626
+ path,
627
+ removed: previous
628
+ } : {
629
+ path,
630
+ removed: null,
631
+ cleanup: removed.error
632
+ });
633
+ }),
634
+ reserve: (target, options) => AsyncResult.from(async () => {
635
+ const path = resolve(target);
636
+ return run(() => api().createSignedUploadUrl(path, options?.upsert ? { upsert: true } : void 0)).map((data) => ({
637
+ path: data.path,
638
+ token: data.token,
639
+ signedUrl: data.signedUrl
640
+ }));
641
+ }),
642
+ uploadReserved: (reservation, body, options) => AsyncResult.from(async () => {
643
+ options?.signal?.throwIfAborted();
644
+ const path = resolve(reservation.path);
645
+ const problem = bucket.check(bodyInfo(body, options?.contentType));
646
+ if (problem) return err(problem);
647
+ return run(() => api().uploadToSignedUrl(path, reservation.token, body, fileOptions(options, options?.contentType))).map(() => {
648
+ evict([path]);
649
+ return { path };
650
+ });
651
+ }),
652
+ sweep: (options) => AsyncResult.from(async () => {
653
+ const namespace = optionalTemporal();
654
+ if (namespace === void 0) return err(temporalMissing());
655
+ const now = options.now?.() ?? namespace.Now.instant();
656
+ const cutoff = options.olderThan instanceof namespace.Instant ? options.olderThan.epochMilliseconds : now.epochMilliseconds - options.olderThan.total("milliseconds");
657
+ const scope = scopedWithin(options.within);
658
+ const size = options.batchSize ?? 100;
659
+ const orphans = [];
660
+ const removed = [];
661
+ let scanned = 0;
662
+ let batch = [];
663
+ const check = async () => {
664
+ options.signal?.throwIfAborted();
665
+ const keep = new Set(await options.referenced(batch));
666
+ for (const path of batch) if (!keep.has(path)) orphans.push(path);
667
+ batch = [];
668
+ };
669
+ try {
670
+ for await (const object of stream(bucket.prefix(scope), options.signal)) {
671
+ scanned++;
672
+ if (!inScope(bucket.match(object.path), scope)) continue;
673
+ const created = Date.parse(object.createdAt ?? object.updatedAt ?? "");
674
+ if (!Number.isFinite(created) || created >= cutoff) continue;
675
+ batch.push(object.path);
676
+ if (batch.length >= size) await check();
677
+ }
678
+ if (batch.length > 0) await check();
679
+ } catch (cause) {
680
+ return err({
681
+ ...toDbError(cause),
682
+ table: bucket.id
683
+ });
684
+ }
685
+ for (let index = 0; index < orphans.length && !options.dryRun;) {
686
+ options.signal?.throwIfAborted();
687
+ const chunk = orphans.slice(index, index + size);
688
+ const result = await run(() => api().remove(chunk));
689
+ if (!result.ok) return result;
690
+ evict(chunk);
691
+ removed.push(...chunk);
692
+ index += size;
693
+ }
694
+ return ok({
695
+ scanned,
696
+ orphans,
697
+ removed
698
+ });
699
+ })
700
+ };
701
+ }
702
+ //#endregion
9
703
  //#region src/storage/policy.ts
10
704
  /**
11
- * The checks of a PermDock or access contract policy, or `undefined` for a
12
- * named policy. `segmentOf` returns the tenant segment, or throws when the
13
- * path has none.
705
+ * The checks of an access policy, or `undefined` for a named policy.
706
+ * `segmentOf` returns the tenant segment, or throws when the path has none.
14
707
  */
15
708
  function policyChecks(config, policy, segmentOf) {
16
709
  if (typeof policy === "string") return void 0;
17
- let check;
18
- let keys;
19
- if ("permdock" in policy) {
20
- const where = `defineBucket(${config.id})`;
21
- const id = policy.scope === "global" ? void 0 : `split_part(name, '/', ${String(policy.segment ?? segmentOf("PermDock"))})`;
22
- check = (key) => permdockCheck(where, policy, key, id, config.catalog);
23
- keys = policy.permdock;
24
- } else {
25
- const segment = policy.segment ?? (policy.scope === "platform" ? void 0 : segmentOf("permission"));
26
- check = (key) => segment === void 0 ? `(select better_supabase.is_platform(${sqlString(key)}))` : `split_part(name, '/', ${String(segment)}) in (select t::text from better_supabase.tenant_ids_with(${sqlString(key)}) t)`;
27
- keys = policy.access;
28
- }
710
+ const where = `defineBucket(${config.id})`;
711
+ const segment = policy.segment ?? (policy.scope === "platform" ? void 0 : segmentOf("permission"));
712
+ const id = segment === void 0 || policy.scope === "platform" ? void 0 : `split_part(name, '/', ${String(segment)})`;
713
+ const check = (key) => accessCheck(where, policy, key, id);
714
+ const keys = policy.access;
29
715
  return {
30
716
  read: check(keys.read),
31
717
  ...keys.list === void 0 ? {} : { list: check(keys.list) },
@@ -37,16 +723,8 @@ function policyChecks(config, policy, segmentOf) {
37
723
  //#region src/storage/tenant-scope.ts
38
724
  const forbidden = (bucket, message) => new DbException(dbError("forbidden", message, { table: bucket }));
39
725
  function resolveTenant(options, claim) {
40
- const { context } = options;
41
726
  if (options.tenant !== void 0) return options.tenant;
42
- if (!context) return void 0;
43
- const tenant = tenantOf(context);
44
- if (tenant !== void 0) return tenant;
45
- const paths = typeof claim === "string" ? [claim] : claim ?? tenantClaimPaths();
46
- for (const path of paths) {
47
- const value = claimAt(context.claims, path);
48
- if (value !== void 0) return value;
49
- }
727
+ return options.context && tenantFrom(options.context, tenantPathsFor(claim));
50
728
  }
51
729
  /**
52
730
  * The guard for a client of bucket `bucket` whose template holds the tenant
@@ -84,12 +762,6 @@ const LIST_OPERATIONS = [
84
762
  "object.list_v2",
85
763
  "s3.object.list"
86
764
  ];
87
- const TTL = {
88
- minute: 60,
89
- hour: 3600,
90
- day: 86400,
91
- week: 604800
92
- };
93
765
  const SIZE_UNITS = {
94
766
  b: 1,
95
767
  kb: 1e3,
@@ -103,13 +775,8 @@ function parseSize(value) {
103
775
  if (typeof value === "number") return value;
104
776
  const match = /^\s*(\d+(?:\.\d+)?)\s*([a-z]*)\s*$/i.exec(value);
105
777
  const unit = SIZE_UNITS[(match?.[2] || "b").toLowerCase()];
106
- if (!match || unit === void 0) throw new TypeError(`Invalid size "${value}"`);
107
- return Math.round(Number(match[1]) * unit);
108
- }
109
- const SAFE_SEGMENT = /^[\w!\-.*'() &$@=;:+,?]+$/;
110
- function validateSegment(_name, value) {
111
- if (value === "." || value === "..") return "is a relative path";
112
- if (!SAFE_SEGMENT.test(value)) return "contains characters Storage does not allow";
778
+ if (!match || unit === void 0) throw new TypeError(`Invalid size "${value}"`);
779
+ return Math.round(Number(match[1]) * unit);
113
780
  }
114
781
  function claimSql(claim) {
115
782
  const expressions = (typeof claim === "string" ? [claim] : claim).map((path) => {
@@ -119,6 +786,16 @@ function claimSql(claim) {
119
786
  });
120
787
  return expressions.length === 1 ? expressions[0] : `coalesce(${expressions.join(", ")})`;
121
788
  }
789
+ /**
790
+ * Path segment `index` equals `value`. A first segment also bounds `name` to
791
+ * the `value/` prefix in the "C" collation, the storage name indexes', so
792
+ * Postgres range-scans one tenant's objects instead of the whole bucket;
793
+ * `like` can't, because its pattern only exists at run time.
794
+ */
795
+ function segmentCheck(index, value) {
796
+ const equal = `split_part(name, '/', ${String(index)}) = ${value}`;
797
+ return index === 1 ? `name collate "C" >= (${value} || '/') and name collate "C" < (${value} || '0') and ${equal}` : equal;
798
+ }
122
799
  function mimeAllowed(allowed, type) {
123
800
  const base = type.split(";")[0].trim().toLowerCase();
124
801
  return allowed.some((entry) => {
@@ -134,43 +811,6 @@ function sizeLabel(bytes) {
134
811
  ]) if (bytes >= size && bytes % size === 0) return `${String(bytes / size)}${unit}`;
135
812
  return `${String(bytes)}B`;
136
813
  }
137
- /** Maps a Storage error (`StorageApiError`, fetch failures) to a `DbError`. */
138
- function fromStorageError(raw, table) {
139
- if (isDbError(raw)) return raw;
140
- if (raw instanceof DbException) return raw.error;
141
- if (typeof raw !== "object" || raw === null) return toDbError(raw);
142
- const failure = raw;
143
- if (failure.name === "AbortError") return toDbError(raw);
144
- const message = failure.message ?? "Storage request failed";
145
- const code = failure.code ?? failure.statusCode;
146
- const base = {
147
- ...code ? { code } : {},
148
- ...table ? { table } : {}
149
- };
150
- const statusCode = Number(failure.statusCode);
151
- const status = Number.isFinite(statusCode) && statusCode >= 400 ? statusCode : failure.status ?? 0;
152
- if (/row-level security|unauthorized to|AccessDenied/i.test(`${message} ${code ?? ""}`)) return dbError("forbidden", message, base);
153
- if (/already exists|duplicate/i.test(`${message} ${code ?? ""}`)) return dbError("conflict", message, base);
154
- if (failure.name === "StorageUnknownError" && status === 0) return dbError("network", message, base);
155
- switch (status) {
156
- case 400: return dbError("invalid_request", message, base);
157
- case 401: return dbError("unauthorized", message, base);
158
- case 403: return dbError("forbidden", message, base);
159
- case 404: return dbError("not_found", message, base);
160
- case 409: return dbError("conflict", message, base);
161
- case 413:
162
- case 415: return dbError("invalid_input", message, {
163
- ...base,
164
- status
165
- });
166
- case 408:
167
- case 429: return dbError("network", message, {
168
- ...base,
169
- status
170
- });
171
- default: return status >= 500 ? dbError("network", message, base) : dbError("unexpected", message, base);
172
- }
173
- }
174
814
  /**
175
815
  * A typed Storage bucket: path builders from a template, limits, generated
176
816
  * SQL policies and `config.toml`, and helpers for the multi-step work apps
@@ -179,7 +819,7 @@ function fromStorageError(raw, table) {
179
819
  * ```ts
180
820
  * export const logos = defineBucket({
181
821
  * id: 'customer-logos',
182
- * path: '{orgId}/{customerId}/logo/{version}.webp',
822
+ * path: '{organizationId}/{customerId}/logo/{version}.webp',
183
823
  * policy: 'tenant',
184
824
  * fileSizeLimit: '5MiB',
185
825
  * allowedMimeTypes: ['image/webp'],
@@ -187,41 +827,32 @@ function fromStorageError(raw, table) {
187
827
  * ```
188
828
  */
189
829
  function defineBucket(config) {
190
- const template = parseTemplate(config.path, "/", validateSegment);
830
+ const layouts = pathLayouts(config.id, config.path, sqlString);
191
831
  const policy = config.policy ?? "none";
192
832
  const fileSizeLimit = config.fileSizeLimit === void 0 ? void 0 : parseSize(config.fileSizeLimit);
193
833
  const allowedMimeTypes = config.allowedMimeTypes;
194
- function segmentFor(param, kind) {
195
- const index = template.segmentOf(param);
196
- if (index === void 0) throw new TypeError(`defineBucket: a ${kind} policy needs {${param}} as a whole path segment in "${config.path}"`);
197
- return index;
198
- }
199
- const tenantParam = config.tenant ? config.tenant.param ?? "orgId" : void 0;
200
- if (tenantParam !== void 0 && !template.params.includes(tenantParam)) throw new TypeError(`defineBucket: tenant.param {${tenantParam}} is not in "${config.path}"`);
834
+ const versioning = config.versioning ?? false;
835
+ if (config.lifecycle && !versioning) throw new TypeError(`defineBucket("${config.id}"): lifecycle expires noncurrent versions, so it needs versioning: true`);
836
+ const lifecycle = config.lifecycle ? lifecyclePayload(config.id, config.lifecycle) : void 0;
837
+ const segmentFor = (param, kind) => layouts.segmentOf(param, kind);
838
+ const tenantParam = config.tenant ? config.tenant.param ?? "organizationId" : void 0;
839
+ if (tenantParam !== void 0) layouts.requireParam(tenantParam, "tenant.param");
201
840
  const tenantClaim = config.tenant?.claim;
202
- const mode = typeof policy === "string" ? policy : "permdock" in policy ? "permdock" : "access";
841
+ const mode = typeof policy === "string" ? policy : "access";
203
842
  const accessCheck = (() => {
204
843
  switch (mode) {
205
- case "tenant": {
206
- const index = segmentFor(config.tenant?.param ?? "orgId", "tenant");
207
- const expression = config.tenant?.sql ?? claimSql(config.tenant?.claim ?? tenantClaimPaths());
208
- return `split_part(name, '/', ${String(index)}) = (${expression})`;
209
- }
210
- case "owner": {
211
- const index = segmentFor(config.owner?.param ?? "userId", "owner");
212
- return `split_part(name, '/', ${String(index)}) = (select auth.uid())::text`;
213
- }
844
+ case "tenant": return segmentCheck(segmentFor(config.tenant?.param ?? "organizationId", "tenant"), `(${config.tenant?.sql ?? claimSql(config.tenant?.claim ?? tenantClaimPaths())})`);
845
+ case "owner": return segmentCheck(segmentFor(config.owner?.param ?? "userId", "owner"), "(select auth.uid())::text");
214
846
  case "public":
215
847
  case "none":
216
- case "permdock":
217
848
  case "access": return;
218
849
  default: throw new TypeError(`defineBucket: unknown policy "${String(mode)}"`);
219
850
  }
220
851
  })();
221
- const permdock = policyChecks(config, policy, (kind) => segmentFor(config.tenant?.param ?? "orgId", kind));
852
+ const permission = policyChecks(config, policy, (kind) => segmentFor(config.tenant?.param ?? "organizationId", kind));
222
853
  const resolve = (target) => {
223
- if (typeof target !== "string") return template.build(target);
224
- if (!template.match(target)) throw new DbException(dbError("invalid_input", `Path "${target}" does not match "${config.path}"`));
854
+ if (typeof target !== "string") return layouts.build(target);
855
+ if (!layouts.match(target)) throw new DbException(dbError("invalid_input", `Path "${target}" does not match "${layouts.sources.join("\" or \"")}"`));
225
856
  return target;
226
857
  };
227
858
  const check = (file) => {
@@ -230,23 +861,32 @@ function defineBucket(config) {
230
861
  };
231
862
  const bucket = {
232
863
  id: config.id,
233
- template: config.path,
234
- params: template.params,
235
- owner: policy === "owner" ? config.owner?.param ?? "userId" : template.params.includes("userId") ? "userId" : void 0,
864
+ template: layouts.sources[0],
865
+ templates: layouts.sources,
866
+ params: layouts.params,
867
+ owner: policy === "owner" ? config.owner?.param ?? "userId" : layouts.params.includes("userId") ? "userId" : void 0,
236
868
  tenant: tenantParam,
237
869
  public: config.public ?? false,
238
870
  policy,
239
871
  fileSizeLimit,
240
872
  allowedMimeTypes,
241
- path: (values) => template.build(values),
242
- match: (path) => template.match(path),
243
- prefix: (values = {}) => template.prefix(values, template.segments - 1),
873
+ versioning,
874
+ lifecycle: config.lifecycle,
875
+ path: (values) => {
876
+ try {
877
+ return ok(layouts.build(values));
878
+ } catch (cause) {
879
+ return err(toDbError(cause));
880
+ }
881
+ },
882
+ match: (path) => layouts.match(path),
883
+ prefix: (values = {}) => layouts.prefix(values),
244
884
  check,
245
885
  sql() {
246
886
  const id = sqlString(config.id);
247
887
  const name = slug(config.id);
248
888
  const lines = [
249
- `-- better-supabase: bucket ${config.id} (${config.path})`,
889
+ `-- better-supabase: bucket ${config.id} (${layouts.sources.join(", ")})`,
250
890
  "insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)",
251
891
  `values (${id}, ${id}, ${String(bucket.public)}, ${fileSizeLimit === void 0 ? "null" : String(fileSizeLimit)}, ${allowedMimeTypes ? `array[${allowedMimeTypes.map(sqlString).join(", ")}]` : "null"})`,
252
892
  "on conflict (id) do update set public = excluded.public, file_size_limit = excluded.file_size_limit,",
@@ -254,7 +894,7 @@ function defineBucket(config) {
254
894
  ];
255
895
  const policies = [];
256
896
  const inBucket = `bucket_id = ${id}`;
257
- const pathMatch = `name ~ ${sqlString(template.sqlPattern)}`;
897
+ const pathMatch = layouts.sqlMatch;
258
898
  if (accessCheck) {
259
899
  const using = `${inBucket} and ${accessCheck}`;
260
900
  const write = `${using} and ${pathMatch}`;
@@ -291,29 +931,29 @@ function defineBucket(config) {
291
931
  inBucket,
292
932
  void 0
293
933
  ]);
294
- else if (permdock) {
934
+ else if (permission) {
295
935
  const listing = `storage.allow_any_operation(array[${LIST_OPERATIONS.map(sqlString).join(", ")}])`;
296
- if (permdock.list) policies.push([
936
+ if (permission.list) policies.push([
297
937
  "select",
298
938
  "select",
299
939
  "authenticated",
300
- `${inBucket} and not ${listing} and ${permdock.read}`,
940
+ `${inBucket} and not ${listing} and ${permission.read}`,
301
941
  void 0
302
942
  ], [
303
943
  "list",
304
944
  "select",
305
945
  "authenticated",
306
- `${inBucket} and ${listing} and ${permdock.list}`,
946
+ `${inBucket} and ${listing} and ${permission.list}`,
307
947
  void 0
308
948
  ]);
309
949
  else policies.push([
310
950
  "select",
311
951
  "select",
312
952
  "authenticated",
313
- `${inBucket} and ${permdock.read}`,
953
+ `${inBucket} and ${permission.read}`,
314
954
  void 0
315
955
  ]);
316
- const write = `${inBucket} and ${permdock.write}`;
956
+ const write = `${inBucket} and ${permission.write}`;
317
957
  policies.push([
318
958
  "insert",
319
959
  "insert",
@@ -330,7 +970,7 @@ function defineBucket(config) {
330
970
  "delete",
331
971
  "delete",
332
972
  "authenticated",
333
- `${inBucket} and ${permdock.delete}`,
973
+ `${inBucket} and ${permission.delete}`,
334
974
  void 0
335
975
  ]);
336
976
  }
@@ -338,7 +978,7 @@ function defineBucket(config) {
338
978
  const policyName = sqlIdent(`bs_${name}_${suffix}`);
339
979
  lines.push("", `drop policy if exists ${policyName} on storage.objects;`, `create policy ${policyName} on storage.objects for ${command} to ${roles}${using ? `\n using (${using})` : ""}${withCheck ? `\n with check (${withCheck})` : ""};`);
340
980
  }
341
- if (permdock && !permdock.list) lines.push("", `drop policy if exists ${sqlIdent(`bs_${name}_list`)} on storage.objects;`);
981
+ if (permission && !permission.list) lines.push("", `drop policy if exists ${sqlIdent(`bs_${name}_list`)} on storage.objects;`);
342
982
  return `${lines.join("\n")}\n`;
343
983
  },
344
984
  toml() {
@@ -374,257 +1014,88 @@ function defineBucket(config) {
374
1014
  actual: actual.allowedMimeTypes,
375
1015
  message: `Bucket "${config.id}" allowed MIME types differ`
376
1016
  });
1017
+ if (actual.versioning !== void 0 && actual.versioning !== null && actual.versioning === "ENABLED" !== versioning) issues.push({
1018
+ field: "versioning",
1019
+ expected: versioning ? "ENABLED" : "DISABLED or SUSPENDED",
1020
+ actual: actual.versioning,
1021
+ message: `Bucket "${config.id}" versioning is ${actual.versioning}`
1022
+ });
1023
+ if (actual.lifecycle !== void 0 && !sameLifecycle(lifecycle, actual.lifecycle)) issues.push({
1024
+ field: "lifecycle",
1025
+ expected: lifecycle ?? null,
1026
+ actual: actual.lifecycle,
1027
+ message: `Bucket "${config.id}" lifecycle policy differs`
1028
+ });
377
1029
  return issues;
378
1030
  },
1031
+ apply: (client) => applyBucket({
1032
+ id: config.id,
1033
+ public: bucket.public,
1034
+ fileSizeLimit,
1035
+ allowedMimeTypes,
1036
+ versioning,
1037
+ lifecycle
1038
+ }, client),
379
1039
  connect: (client, options = {}) => connectBucket(bucket, client, resolve, options, tenantGuard(bucket.id, tenantParam, tenantClaim, (path) => bucket.match(path), options))
380
1040
  };
381
1041
  return bucket;
382
1042
  }
383
- function bodyInfo(body, contentType) {
384
- const info = {};
385
- if (typeof Blob !== "undefined" && body instanceof Blob) {
386
- info.size = body.size;
387
- if (body.type) info.type = body.type;
388
- } else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) info.size = body.byteLength;
389
- if (contentType) info.type = contentType;
390
- return info;
391
- }
392
- function ttlSeconds(ttl) {
393
- return typeof ttl === "number" ? ttl : TTL[ttl ?? "hour"];
394
- }
395
- function isErrorResult(value) {
396
- return typeof value === "object" && value !== null && value.ok === false && "error" in value;
397
- }
398
- function connectBucket(bucket, client, resolveTarget, connectOptions, guard) {
399
- const resolve = (target) => {
400
- const path = resolveTarget(target);
401
- return guard ? guard.path(path) : path;
1043
+ //#endregion
1044
+ //#region src/storage/registry.ts
1045
+ /**
1046
+ * A registry of bucket definitions by id, for rows that store a bucket id
1047
+ * next to an object path (files, attachments, sources). An id no bucket
1048
+ * has, and a path outside the bucket's templates, are errors, never a
1049
+ * fallback bucket.
1050
+ *
1051
+ * ```ts
1052
+ * export const buckets = defineBuckets({ files, attachments, logos });
1053
+ *
1054
+ * const ref = buckets.connectStored(supabase, {
1055
+ * bucket: row.bucketId,
1056
+ * path: row.path,
1057
+ * }, { context: db.$context });
1058
+ * if (ref.ok) await ref.data.storage.signedUrl(ref.data.path);
1059
+ * ```
1060
+ */
1061
+ function defineBuckets(buckets) {
1062
+ const byId = /* @__PURE__ */ new Map();
1063
+ for (const [name, bucket] of Object.entries(buckets)) {
1064
+ if (byId.get(bucket.id) !== void 0) throw new TypeError(`defineBuckets: "${name}" reuses the bucket id "${bucket.id}"`);
1065
+ byId.set(bucket.id, bucket);
1066
+ }
1067
+ const lookup = (id) => {
1068
+ const bucket = byId.get(id);
1069
+ return bucket === void 0 ? err(dbError("invalid_input", `Bucket "${id}" is not registered`)) : ok(bucket);
402
1070
  };
403
- const scopedWithin = (within) => guard ? {
404
- ...within,
405
- [guard.param]: guard.within(within)
406
- } : within ?? {};
407
- const api = () => client.storage.from(bucket.id);
408
- const signed = connectOptions.cacheSignedUrls ? /* @__PURE__ */ new Map() : void 0;
409
- const run = (fn) => AsyncResult.from(async () => {
410
- try {
411
- const { data, error } = await fn();
412
- return error || data === null ? err(fromStorageError(error, bucket.id)) : ok(data);
413
- } catch (cause) {
414
- return err(fromStorageError(cause, bucket.id));
415
- }
416
- });
417
- const fileOptions = (options, contentType) => ({
418
- ...contentType ? { contentType } : {},
419
- ...options?.cacheControl ? { cacheControl: options.cacheControl } : {},
420
- ...options?.metadata ? { metadata: { ...options.metadata } } : {},
421
- upsert: options?.upsert ?? false
422
- });
423
- const download = (value) => value === void 0 ? {} : { download: value };
424
- const upload = (target, body, options) => AsyncResult.from(async () => {
425
- options?.signal?.throwIfAborted();
426
- const path = resolve(target);
427
- const contentType = options?.contentType;
428
- const problem = bucket.check(bodyInfo(body, contentType));
429
- if (problem) return err(problem);
430
- return run(() => api().upload(path, body, fileOptions(options, contentType))).map(() => ({ path }));
431
- });
432
- const remove = (targets) => AsyncResult.from(async () => {
433
- const paths = targets.map(resolve);
434
- if (paths.length === 0) return ok([]);
435
- return run(() => api().remove(paths)).map(() => paths);
436
- });
437
- const walk = async (folder, signal, out) => {
438
- const limit = 1e3;
439
- for (let offset = 0;; offset += limit) {
440
- signal?.throwIfAborted();
441
- const { data, error } = await api().list(folder, {
442
- limit,
443
- offset,
444
- sortBy: {
445
- column: "name",
446
- order: "asc"
447
- }
448
- }, signal ? { signal } : {});
449
- if (error) throw new DbException(fromStorageError(error, bucket.id));
450
- for (const item of data) {
451
- const path = folder ? `${folder}/${item.name}` : item.name;
452
- if (item.id === null) await walk(path, signal, out);
453
- else {
454
- const metadata = item.metadata ?? {};
455
- out.push({
456
- path,
457
- ...typeof metadata.size === "number" ? { size: metadata.size } : {},
458
- ...metadata.mimetype ? { contentType: metadata.mimetype } : {},
459
- createdAt: item.created_at,
460
- updatedAt: item.updated_at
461
- });
462
- }
463
- }
464
- if (data.length < limit) return;
465
- }
1071
+ const resolve = (ref) => {
1072
+ const found = lookup(ref.bucket);
1073
+ if (!found.ok) return found;
1074
+ const bucket = found.data;
1075
+ if (bucket.match(ref.path) === null) return err(dbError("invalid_input", `Path "${ref.path}" does not match "${bucket.templates.join("\" or \"")}"`, { table: bucket.id }));
1076
+ return ok({
1077
+ bucket,
1078
+ path: ref.path
1079
+ });
466
1080
  };
467
- const list = (within, options) => AsyncResult.from(async () => {
468
- const out = [];
469
- await walk(bucket.prefix(scopedWithin(within)), options?.signal, out);
470
- return ok(out);
471
- }).mapError((error) => ({
472
- ...error,
473
- table: bucket.id
474
- }));
475
- const signedUrl = (target, options) => AsyncResult.from(async () => {
476
- const path = resolve(target);
477
- const ttl = ttlSeconds(options?.ttl);
478
- const key = signed ? JSON.stringify([
479
- path,
480
- ttl,
481
- options?.transform,
482
- options?.download
483
- ]) : "";
484
- const cached = signed?.get(key);
485
- if (cached && cached.until > Date.now()) return ok(cached.url);
486
- const result = await run(() => api().createSignedUrl(path, ttl, {
487
- ...download(options?.download),
488
- ...options?.transform ? { transform: { ...options.transform } } : {}
489
- })).map((data) => data.signedUrl);
490
- if (signed && result.ok) {
491
- const margin = Math.min(60, ttl / 10);
492
- signed.set(key, {
493
- url: result.data,
494
- until: Date.now() + (ttl - margin) * 1e3
495
- });
496
- }
497
- return result;
498
- });
499
1081
  return {
500
- bucket,
501
- path: (target) => {
502
- try {
503
- return ok(resolve(target));
504
- } catch (cause) {
505
- return err(toDbError(cause));
506
- }
507
- },
508
- upload,
509
- download: (target, options) => AsyncResult.from(async () => {
510
- const path = resolve(target);
511
- return run(() => api().download(path, {}, options?.signal ? { signal: options.signal } : {}));
512
- }),
513
- exists: (target) => AsyncResult.from(async () => {
514
- const path = resolve(target);
515
- const found = await api().exists(path);
516
- if (!found.data) return ok(false);
517
- return found.error ? err(fromStorageError(found.error, bucket.id)) : ok(true);
518
- }),
519
- remove,
520
- list,
521
- signedUrl,
522
- signedUrls: (targets, options) => AsyncResult.from(async () => {
523
- const paths = targets.map(resolve);
524
- if (paths.length === 0) return ok([]);
525
- return run(() => api().createSignedUrls(paths, ttlSeconds(options?.ttl), download(options?.download))).andThen((data) => {
526
- const failed = data.find((entry) => entry.error != null || !entry.signedUrl);
527
- if (failed) return Promise.resolve(err(dbError("not_found", failed.error ?? `No URL for ${String(failed.path)}`, { table: bucket.id })));
528
- return Promise.resolve(ok(data.map((entry) => entry.signedUrl)));
529
- });
530
- }),
531
- publicUrl(target, options) {
532
- const path = resolve(target);
533
- return api().getPublicUrl(path, {
534
- ...download(options?.download),
535
- ...options?.transform ? { transform: { ...options.transform } } : {}
536
- }).data.publicUrl;
537
- },
538
- renderUrl(target, transform, options) {
539
- if (!bucket.public) return signedUrl(target, {
540
- ...options,
541
- transform
542
- });
543
- return AsyncResult.from(() => Promise.resolve(ok(api().getPublicUrl(resolve(target), {
544
- ...download(options?.download),
545
- transform: { ...transform }
546
- }).data.publicUrl)));
547
- },
548
- replace: (target, body, options = {}) => AsyncResult.from(async () => {
549
- const path = resolve(target);
550
- const previous = options.previous ?? null;
551
- const same = previous === path;
552
- const uploaded = await upload(path, body, {
553
- ...options,
554
- upsert: same || (options.upsert ?? false)
555
- });
556
- if (!uploaded.ok) return uploaded;
557
- const undo = async (error) => {
558
- if (!same) await remove([path]);
559
- return err(error);
560
- };
561
- try {
562
- options.signal?.throwIfAborted();
563
- const outcome = await options.commit?.(path);
564
- if (isErrorResult(outcome)) return await undo(outcome.error);
565
- } catch (cause) {
566
- return undo(fromStorageError(cause, bucket.id));
567
- }
568
- if (!previous || same) return ok({
569
- path,
570
- removed: null
571
- });
572
- const removed = await run(() => api().remove([previous]));
573
- return ok(removed.ok ? {
574
- path,
575
- removed: previous
576
- } : {
577
- path,
578
- removed: null,
579
- cleanup: removed.error
580
- });
581
- }),
582
- reserve: (target, options) => AsyncResult.from(async () => {
583
- const path = resolve(target);
584
- return run(() => api().createSignedUploadUrl(path, options?.upsert ? { upsert: true } : void 0)).map((data) => ({
585
- path: data.path,
586
- token: data.token,
587
- signedUrl: data.signedUrl
588
- }));
589
- }),
590
- uploadReserved: (reservation, body, options) => AsyncResult.from(async () => {
591
- options?.signal?.throwIfAborted();
592
- const path = resolve(reservation.path);
593
- const problem = bucket.check(bodyInfo(body, options?.contentType));
594
- if (problem) return err(problem);
595
- return run(() => api().uploadToSignedUrl(path, reservation.token, body, fileOptions(options, options?.contentType))).map(() => ({ path }));
596
- }),
597
- sweep: (options) => AsyncResult.from(async () => {
598
- const namespace = optionalTemporal();
599
- if (namespace === void 0) return err(temporalMissing());
600
- const now = options.now?.() ?? namespace.Now.instant();
601
- const cutoff = options.olderThan instanceof namespace.Instant ? options.olderThan.epochMilliseconds : now.epochMilliseconds - options.olderThan.total("milliseconds");
602
- const found = await list(options.within, options.signal ? { signal: options.signal } : void 0);
603
- if (!found.ok) return found;
604
- const candidates = found.data.filter((object) => bucket.match(object.path) !== null).filter((object) => {
605
- const created = Date.parse(object.createdAt ?? object.updatedAt ?? "");
606
- return Number.isFinite(created) && created < cutoff;
607
- }).map((object) => object.path);
608
- const size = options.batchSize ?? 100;
609
- const orphans = [];
610
- const removed = [];
611
- for (let index = 0; index < candidates.length; index += size) {
612
- options.signal?.throwIfAborted();
613
- const batch = candidates.slice(index, index + size);
614
- const keep = new Set(await options.referenced(batch));
615
- const unreferenced = batch.filter((path) => !keep.has(path));
616
- orphans.push(...unreferenced);
617
- if (options.dryRun || unreferenced.length === 0) continue;
618
- const result = await run(() => api().remove(unreferenced));
619
- if (!result.ok) return result;
620
- removed.push(...unreferenced);
621
- }
622
- return ok({
623
- scanned: found.data.length,
624
- orphans,
625
- removed
626
- });
627
- })
1082
+ buckets,
1083
+ ids: [...byId.keys()],
1084
+ has: (id) => byId.has(id),
1085
+ byId: lookup,
1086
+ resolve,
1087
+ connectStored(client, ref, options) {
1088
+ const resolved = resolve(ref);
1089
+ if (!resolved.ok) return resolved;
1090
+ const { bucket } = resolved.data;
1091
+ const storage = bucket.connect(client, options);
1092
+ const path = storage.path(ref.path);
1093
+ return path.ok ? ok({
1094
+ bucket,
1095
+ storage,
1096
+ path: path.data
1097
+ }) : path;
1098
+ }
628
1099
  };
629
1100
  }
630
1101
  //#endregion
@@ -638,7 +1109,7 @@ const IMAGE_TYPES = [
638
1109
  "image/avif"
639
1110
  ];
640
1111
  const AVATAR_PATH = "{userId}/avatar-{version}.{ext}";
641
- const LOGO_PATH = "{orgId}/logo-{version}.{ext}";
1112
+ const LOGO_PATH = "{organizationId}/logo-{version}.{ext}";
642
1113
  /**
643
1114
  * A bucket for profile pictures: `{userId}/avatar-{version}.{ext}`, public,
644
1115
  * 2 MiB of images, written only by their owner (the `owner` policy).
@@ -654,13 +1125,13 @@ function avatarBucket(options = {}) {
654
1125
  });
655
1126
  }
656
1127
  /**
657
- * A bucket for organization logos: `{orgId}/logo-{version}.{ext}`, public,
1128
+ * A bucket for organization logos: `{organizationId}/logo-{version}.{ext}`, public,
658
1129
  * 2 MiB of images, written by members with the update permission through
659
1130
  * the access contract (`tenant_ids_with`), so it follows the app's roles.
660
1131
  */
661
- function orgLogoBucket(options = {}) {
1132
+ function organizationLogoBucket(options = {}) {
662
1133
  const permission = options.permission ?? "organization.update";
663
- const tenant = options.tenant === false ? void 0 : options.tenant ?? { param: "orgId" };
1134
+ const tenant = options.tenant === false ? void 0 : options.tenant ?? { param: "organizationId" };
664
1135
  return defineBucket({
665
1136
  id: options.id ?? "organization-logos",
666
1137
  path: options.path ?? LOGO_PATH,
@@ -675,4 +1146,192 @@ function orgLogoBucket(options = {}) {
675
1146
  });
676
1147
  }
677
1148
  //#endregion
678
- export { IMAGE_TYPES, TTL, avatarBucket, defineBucket, fromStorageError, orgLogoBucket, parseSize };
1149
+ //#region src/storage/analytics-bucket.ts
1150
+ /**
1151
+ * A Storage analytics bucket: Apache Iceberg tables that query engines read
1152
+ * through the Iceberg REST catalog.
1153
+ *
1154
+ * ```ts
1155
+ * export const events = defineAnalyticsBucket({ id: "events" });
1156
+ * await events.connect(admin).apply().orThrow();
1157
+ * ```
1158
+ */
1159
+ function defineAnalyticsBucket(config) {
1160
+ return {
1161
+ id: config.id,
1162
+ connect: (client) => ({
1163
+ apply: () => AsyncResult.from(async () => {
1164
+ const analytics = client.storage.analytics;
1165
+ try {
1166
+ const listed = await analytics.listBuckets({ search: config.id });
1167
+ if (listed.error) return err(fromStorageError(listed.error, config.id));
1168
+ if (listed.data.some((bucket) => bucket.name === config.id)) return ok({ created: false });
1169
+ const made = await analytics.createBucket(config.id);
1170
+ if (made.error) return err(fromStorageError(made.error, config.id));
1171
+ return ok({ created: true });
1172
+ } catch (cause) {
1173
+ return err(fromStorageError(cause, config.id));
1174
+ }
1175
+ }),
1176
+ catalog: () => client.storage.analytics.from(config.id)
1177
+ })
1178
+ };
1179
+ }
1180
+ //#endregion
1181
+ //#region src/storage/vector-bucket.ts
1182
+ const PUT_BATCH = 500;
1183
+ const GET_BATCH = 100;
1184
+ function chunks(items, size) {
1185
+ const out = [];
1186
+ for (let index = 0; index < items.length; index += size) out.push(items.slice(index, index + size));
1187
+ return out;
1188
+ }
1189
+ /** Batches in flight at once for one `put`, `get` or `remove`. */
1190
+ const CHUNK_CONCURRENCY = 4;
1191
+ /** Runs `run` per item over a few lanes; stops starting items after the first error. */
1192
+ async function inLanes(items, run) {
1193
+ const out = [];
1194
+ let next = 0;
1195
+ let failed;
1196
+ const lane = async () => {
1197
+ while (!failed && next < items.length) {
1198
+ const index = next++;
1199
+ const result = await run(items[index]);
1200
+ if (!result.ok) {
1201
+ failed ??= result;
1202
+ return;
1203
+ }
1204
+ out[index] = result.data;
1205
+ }
1206
+ };
1207
+ await Promise.all(Array.from({ length: Math.min(CHUNK_CONCURRENCY, items.length) }, lane));
1208
+ return failed ?? ok(out);
1209
+ }
1210
+ const metricOf = (config) => config.distanceMetric ?? "cosine";
1211
+ /**
1212
+ * A Storage vector bucket with typed indexes. Storage keeps the vectors
1213
+ * outside Postgres; use the `vector-search` module for pgvector in a table.
1214
+ *
1215
+ * ```ts
1216
+ * export const embeddings = defineVectorBucket({
1217
+ * id: "embeddings",
1218
+ * indexes: { documents: { dimension: 1536 } },
1219
+ * });
1220
+ * const docs = embeddings.connect(admin).index("documents");
1221
+ * await docs.put([{ key: doc.id, vector, metadata: { organizationId } }]);
1222
+ * ```
1223
+ */
1224
+ function defineVectorBucket(config) {
1225
+ for (const [name, index] of Object.entries(config.indexes)) if (!Number.isInteger(index.dimension) || index.dimension < 1) throw new TypeError(`defineVectorBucket("${config.id}"): index "${name}" needs a whole, positive dimension`);
1226
+ return {
1227
+ id: config.id,
1228
+ indexes: config.indexes,
1229
+ connect: (client) => connectVectorBucket(config, client)
1230
+ };
1231
+ }
1232
+ function connectVectorBucket(config, client) {
1233
+ const vectors = () => client.storage.vectors;
1234
+ const scope = () => vectors().from(config.id);
1235
+ const failure = (raw) => fromStorageError(raw, config.id);
1236
+ const attempt = (fn) => AsyncResult.from(async () => {
1237
+ try {
1238
+ return await fn();
1239
+ } catch (cause) {
1240
+ return err(failure(cause));
1241
+ }
1242
+ });
1243
+ return {
1244
+ apply: () => attempt(async () => {
1245
+ const created = [];
1246
+ const found = await vectors().getBucket(config.id);
1247
+ if (found.error) {
1248
+ if (failure(found.error).kind !== "not_found") return err(failure(found.error));
1249
+ const made = await vectors().createBucket(config.id);
1250
+ if (made.error) return err(failure(made.error));
1251
+ }
1252
+ const entries = Object.entries(config.indexes);
1253
+ for (const [name, index] of entries) {
1254
+ const existing = await scope().getIndex(name);
1255
+ if (existing.data) {
1256
+ const actual = existing.data.index;
1257
+ if (actual.dimension !== index.dimension || actual.distanceMetric !== metricOf(index)) return err(dbError("conflict", `Vector index "${name}" has dimension ${String(actual.dimension)} and metric ${actual.distanceMetric}, not ${String(index.dimension)} and ${metricOf(index)}`, { table: config.id }));
1258
+ continue;
1259
+ }
1260
+ if (failure(existing.error).kind !== "not_found") return err(failure(existing.error));
1261
+ const made = await scope().createIndex({
1262
+ indexName: name,
1263
+ dataType: "float32",
1264
+ dimension: index.dimension,
1265
+ distanceMetric: metricOf(index),
1266
+ ...index.nonFilterableMetadataKeys ? { metadataConfiguration: { nonFilterableMetadataKeys: [...index.nonFilterableMetadataKeys] } } : {}
1267
+ });
1268
+ if (made.error) return err(failure(made.error));
1269
+ created.push(name);
1270
+ }
1271
+ return ok({ created });
1272
+ }),
1273
+ index(name) {
1274
+ const settings = config.indexes[name];
1275
+ const api = () => scope().index(name);
1276
+ const checkDimension = (vector, key) => vector.length === settings.dimension ? void 0 : dbError("invalid_input", `${key === void 0 ? "The query vector" : `Vector "${key}"`} has ${String(vector.length)} values; index "${name}" takes ${String(settings.dimension)}`, { table: config.id });
1277
+ const hit = (match) => ({
1278
+ key: match.key,
1279
+ ...match.distance === void 0 ? {} : { distance: match.distance },
1280
+ ...match.metadata === void 0 ? {} : { metadata: match.metadata },
1281
+ ...match.data ? { vector: match.data.float32 } : {}
1282
+ });
1283
+ return {
1284
+ name,
1285
+ dimension: settings.dimension,
1286
+ put: (records) => attempt(async () => {
1287
+ for (const record of records) {
1288
+ const problem = checkDimension(record.vector, record.key);
1289
+ if (problem) return err(problem);
1290
+ }
1291
+ const put = await inLanes(chunks(records, PUT_BATCH), async (batch) => {
1292
+ const { error } = await api().putVectors({ vectors: batch.map((record) => ({
1293
+ key: record.key,
1294
+ data: { float32: [...record.vector] },
1295
+ ...record.metadata ? { metadata: { ...record.metadata } } : {}
1296
+ })) });
1297
+ return error ? err(failure(error)) : ok(batch.length);
1298
+ });
1299
+ return put.ok ? ok(records.length) : put;
1300
+ }),
1301
+ query: (vector, options = {}) => attempt(async () => {
1302
+ const problem = checkDimension(vector);
1303
+ if (problem) return err(problem);
1304
+ const found = await api().queryVectors({
1305
+ queryVector: { float32: [...vector] },
1306
+ topK: options.topK ?? 10,
1307
+ returnDistance: true,
1308
+ returnMetadata: options.metadata ?? true,
1309
+ ...options.filter ? { filter: { ...options.filter } } : {}
1310
+ });
1311
+ if (found.error) return err(failure(found.error));
1312
+ return ok(found.data.vectors.map(hit));
1313
+ }),
1314
+ get: (keys, options = {}) => attempt(async () => {
1315
+ const found = await inLanes(chunks(keys, GET_BATCH), async (batch) => {
1316
+ const { data, error } = await api().getVectors({
1317
+ keys: batch,
1318
+ returnMetadata: true,
1319
+ returnData: options.vector ?? false
1320
+ });
1321
+ return error ? err(failure(error)) : ok(data.vectors.map(hit));
1322
+ });
1323
+ return found.ok ? ok(found.data.flat()) : found;
1324
+ }),
1325
+ remove: (keys) => attempt(async () => {
1326
+ const removed = await inLanes(chunks(keys, PUT_BATCH), async (batch) => {
1327
+ const { error } = await api().deleteVectors({ keys: batch });
1328
+ return error ? err(failure(error)) : ok(batch.length);
1329
+ });
1330
+ return removed.ok ? ok(keys) : removed;
1331
+ })
1332
+ };
1333
+ }
1334
+ };
1335
+ }
1336
+ //#endregion
1337
+ export { IMAGE_TYPES, TTL, avatarBucket, defineAnalyticsBucket, defineBucket, defineBuckets, defineVectorBucket, fromStorageError, organizationLogoBucket, parseSize, storageImageUrl };