better-supabase 0.7.1 → 0.9.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.
- package/README.md +146 -81
- package/bin/better-supabase.js +14 -2
- package/dist/access-sql-DUSSMaC3.js +201 -0
- package/dist/actor-BAhb-d1S.d.ts +104 -0
- package/dist/actor-KHDOKGb1.js +151 -0
- package/dist/adapter-CEjmtazZ.js +1984 -0
- package/dist/agents-BCNhIxVZ.d.ts +111 -0
- package/dist/ai-cache-Dps7aRu1.d.ts +70 -0
- package/dist/ai-cache-zeUL94pB.js +84 -0
- package/dist/ai-chat-3GBSlFR4.d.ts +608 -0
- package/dist/ai-files-D2_9YLjF.js +331 -0
- package/dist/ai-message-DfaP8sis.js +14 -0
- package/dist/ai-sdk/agents/index.d.ts +154 -10
- package/dist/ai-sdk/agents/index.js +172 -17
- package/dist/ai-sdk/batches/index.d.ts +4 -4
- package/dist/ai-sdk/batches/index.js +5 -5
- package/dist/ai-sdk/cache/index.d.ts +2 -2
- package/dist/ai-sdk/cache/index.js +2 -2
- package/dist/ai-sdk/chat/index.d.ts +1 -1
- package/dist/ai-sdk/chat/index.js +10 -21
- package/dist/ai-sdk/embeddings/index.d.ts +3 -3
- package/dist/ai-sdk/embeddings/index.js +1 -2
- package/dist/ai-sdk/files/index.d.ts +3 -3
- package/dist/ai-sdk/files/index.js +4 -4
- package/dist/ai-sdk/index.d.ts +38 -9
- package/dist/ai-sdk/index.js +5 -3
- package/dist/ai-sdk/mcp/index.d.ts +43 -6
- package/dist/ai-sdk/mcp/index.js +37 -8
- package/dist/ai-sdk/memory/index.d.ts +5 -2
- package/dist/ai-sdk/memory/index.js +2 -2
- package/dist/ai-sdk/react/index.js +1 -2
- package/dist/ai-sdk/workflow/index.d.ts +7 -6
- package/dist/ai-sdk/workflow/index.js +15 -26
- package/dist/ai-tasks-Ih5oHfyj.d.ts +157 -0
- package/dist/announcements-BHCzraR6.js +128 -0
- package/dist/api-CFjuYKFY.js +680 -0
- package/dist/api-TG3jE5aO.d.ts +411 -0
- package/dist/api-keys-BEQatEUW.js +318 -0
- package/dist/api-keys-BYoGCM9p.d.ts +205 -0
- package/dist/app-tables-BGl5_U2x.js +584 -0
- package/dist/approvals-a2KmioBh.d.ts +124 -0
- package/dist/arazzo/index.d.ts +255 -0
- package/dist/arazzo/index.js +622 -0
- package/dist/assistant-Bf2cA6RO.d.ts +148 -0
- package/dist/astro/index.d.ts +9 -8
- package/dist/astro/index.js +12 -9
- package/dist/asyncapi/index.d.ts +103 -0
- package/dist/asyncapi/index.js +569 -0
- package/dist/attachments-DAj2Yp95.js +264 -0
- package/dist/attributes-D4mA7WhI.js +38 -0
- package/dist/audit-log-BMMuGPdP.js +786 -0
- package/dist/auth-CdBKt0CS.js +20 -0
- package/dist/auth-reply-DUCwb8YO.js +31 -0
- package/dist/auth-reply-Dj124sbX.d.ts +11 -0
- package/dist/authorization-DpRfs6Wx.d.ts +1412 -0
- package/dist/authorization-hooks-DkmrnDX7.d.ts +214 -0
- package/dist/authorizer-DH7DjkL3.js +126 -0
- package/dist/authorizer-eiOjo-3l.d.ts +73 -0
- package/dist/base64-BmeCBgGo.js +42 -0
- package/dist/batch-limit-DKVQDz4o.js +5 -0
- package/dist/before-route-B98js9m_.js +30 -0
- package/dist/better-result/index.d.ts +118 -0
- package/dist/better-result/index.js +64 -0
- package/dist/bind-a7sUgvf4.d.ts +70 -0
- package/dist/block-env-CBd_SoL5.js +29 -0
- package/dist/block-env-Dxl7rsI0.d.ts +10 -0
- package/dist/block-events-DD9Q_NUz.js +96 -0
- package/dist/block-fields-5UA84ZUg.d.ts +55 -0
- package/dist/block-fields-ffD6Gxe4.js +139 -0
- package/dist/block-helpers-C86hPEjI.d.ts +37 -0
- package/dist/block-helpers-CfG_MvDR.js +264 -0
- package/dist/block-hooks-BLPWD3Jt.d.ts +107 -0
- package/dist/block-hooks-BsXncUlZ.js +114 -0
- package/dist/block-transport-DaGGXjIw.d.ts +70 -0
- package/dist/block-transport-G4t1umrR.js +85 -0
- package/dist/blocks/access-requests/index.d.ts +98 -0
- package/dist/blocks/access-requests/index.js +99 -0
- package/dist/blocks/agents/index.d.ts +21 -3
- package/dist/blocks/agents/index.js +65 -18
- package/dist/blocks/ai-cache/index.d.ts +8 -3
- package/dist/blocks/ai-cache/index.js +15 -3
- package/dist/blocks/ai-chat/index.d.ts +28 -4
- package/dist/blocks/ai-chat/index.js +31 -5
- package/dist/blocks/ai-chat/react/index.d.ts +12 -46
- package/dist/blocks/ai-chat/react/index.js +43 -335
- package/dist/blocks/ai-chat/solid/index.d.ts +34 -0
- package/dist/blocks/ai-chat/solid/index.js +41 -0
- package/dist/blocks/ai-chat/svelte/index.d.ts +34 -0
- package/dist/blocks/ai-chat/svelte/index.js +41 -0
- package/dist/blocks/ai-chat/vue/index.d.ts +34 -0
- package/dist/blocks/ai-chat/vue/index.js +41 -0
- package/dist/blocks/ai-files/index.d.ts +11 -4
- package/dist/blocks/ai-files/index.js +16 -3
- package/dist/blocks/ai-providers/index.d.ts +18 -3
- package/dist/blocks/ai-providers/index.js +41 -15
- package/dist/blocks/ai-tasks/index.d.ts +10 -3
- package/dist/blocks/ai-tasks/index.js +71 -21
- package/dist/blocks/announcements/index.d.ts +18 -3
- package/dist/blocks/announcements/index.js +27 -3
- package/dist/blocks/announcements/react/index.d.ts +4 -14
- package/dist/blocks/announcements/react/index.js +22 -70
- package/dist/blocks/announcements/solid/index.d.ts +13 -0
- package/dist/blocks/announcements/solid/index.js +14 -0
- package/dist/blocks/announcements/svelte/index.d.ts +13 -0
- package/dist/blocks/announcements/svelte/index.js +14 -0
- package/dist/blocks/announcements/vue/index.d.ts +13 -0
- package/dist/blocks/announcements/vue/index.js +14 -0
- package/dist/blocks/api-keys/index.d.ts +24 -7
- package/dist/blocks/api-keys/index.js +30 -295
- package/dist/blocks/api-keys/react/index.d.ts +29 -0
- package/dist/blocks/api-keys/react/index.js +43 -0
- package/dist/blocks/api-keys/react/server.d.ts +7 -0
- package/dist/blocks/api-keys/react/server.js +7 -0
- package/dist/blocks/api-keys/solid/index.d.ts +13 -0
- package/dist/blocks/api-keys/solid/index.js +14 -0
- package/dist/blocks/api-keys/svelte/index.d.ts +13 -0
- package/dist/blocks/api-keys/svelte/index.js +14 -0
- package/dist/blocks/api-keys/vue/index.d.ts +13 -0
- package/dist/blocks/api-keys/vue/index.js +14 -0
- package/dist/blocks/approvals/index.d.ts +22 -0
- package/dist/blocks/approvals/index.js +189 -0
- package/dist/blocks/attachments/index.d.ts +18 -223
- package/dist/blocks/attachments/index.js +26 -233
- package/dist/blocks/audit/index.d.ts +72 -354
- package/dist/blocks/audit/index.js +91 -695
- package/dist/blocks/audit/react/index.d.ts +26 -0
- package/dist/blocks/audit/react/index.js +44 -0
- package/dist/blocks/audit/react/server.d.ts +7 -0
- package/dist/blocks/audit/react/server.js +7 -0
- package/dist/blocks/audit/solid/index.d.ts +13 -0
- package/dist/blocks/audit/solid/index.js +14 -0
- package/dist/blocks/audit/svelte/index.d.ts +13 -0
- package/dist/blocks/audit/svelte/index.js +14 -0
- package/dist/blocks/audit/vue/index.d.ts +13 -0
- package/dist/blocks/audit/vue/index.js +14 -0
- package/dist/blocks/billing/index.d.ts +40 -13
- package/dist/blocks/billing/index.js +49 -18
- package/dist/blocks/billing/react/index.d.ts +34 -0
- package/dist/blocks/billing/react/index.js +36 -0
- package/dist/blocks/billing/react/server.d.ts +7 -0
- package/dist/blocks/billing/react/server.js +7 -0
- package/dist/blocks/billing/solid/index.d.ts +15 -0
- package/dist/blocks/billing/solid/index.js +14 -0
- package/dist/blocks/billing/svelte/index.d.ts +15 -0
- package/dist/blocks/billing/svelte/index.js +14 -0
- package/dist/blocks/billing/vue/index.d.ts +15 -0
- package/dist/blocks/billing/vue/index.js +14 -0
- package/dist/blocks/bookings/index.d.ts +36 -0
- package/dist/blocks/bookings/index.js +28 -0
- package/dist/blocks/bookings/react/index.d.ts +31 -0
- package/dist/blocks/bookings/react/index.js +59 -0
- package/dist/blocks/bookings/react/server.d.ts +6 -0
- package/dist/blocks/bookings/react/server.js +7 -0
- package/dist/blocks/bookings/solid/index.d.ts +13 -0
- package/dist/blocks/bookings/solid/index.js +14 -0
- package/dist/blocks/bookings/svelte/index.d.ts +13 -0
- package/dist/blocks/bookings/svelte/index.js +14 -0
- package/dist/blocks/bookings/vue/index.d.ts +13 -0
- package/dist/blocks/bookings/vue/index.js +14 -0
- package/dist/blocks/bulk/index.d.ts +142 -0
- package/dist/blocks/bulk/index.js +154 -0
- package/dist/blocks/calendar/index.d.ts +20 -0
- package/dist/blocks/calendar/index.js +33 -0
- package/dist/blocks/calendar/react/index.d.ts +26 -0
- package/dist/blocks/calendar/react/index.js +58 -0
- package/dist/blocks/calendar/react/server.d.ts +6 -0
- package/dist/blocks/calendar/react/server.js +7 -0
- package/dist/blocks/calendar/solid/index.d.ts +13 -0
- package/dist/blocks/calendar/solid/index.js +14 -0
- package/dist/blocks/calendar/svelte/index.d.ts +13 -0
- package/dist/blocks/calendar/svelte/index.js +14 -0
- package/dist/blocks/calendar/vue/index.d.ts +13 -0
- package/dist/blocks/calendar/vue/index.js +14 -0
- package/dist/blocks/catalog/index.d.ts +224 -0
- package/dist/blocks/catalog/index.js +292 -0
- package/dist/blocks/collections/index.d.ts +184 -0
- package/dist/blocks/collections/index.js +208 -0
- package/dist/blocks/comments/index.d.ts +19 -166
- package/dist/blocks/comments/index.js +26 -206
- package/dist/blocks/comments/react/index.d.ts +27 -0
- package/dist/blocks/comments/react/index.js +45 -0
- package/dist/blocks/comments/react/server.d.ts +7 -0
- package/dist/blocks/comments/react/server.js +7 -0
- package/dist/blocks/comments/solid/index.d.ts +13 -0
- package/dist/blocks/comments/solid/index.js +14 -0
- package/dist/blocks/comments/svelte/index.d.ts +13 -0
- package/dist/blocks/comments/svelte/index.js +14 -0
- package/dist/blocks/comments/vue/index.d.ts +13 -0
- package/dist/blocks/comments/vue/index.js +14 -0
- package/dist/blocks/connectors/index.d.ts +21 -3
- package/dist/blocks/connectors/index.js +54 -18
- package/dist/blocks/custom-fields/index.d.ts +62 -0
- package/dist/blocks/custom-fields/index.js +85 -0
- package/dist/blocks/data-lifecycle/index.d.ts +119 -10
- package/dist/blocks/data-lifecycle/index.js +103 -12
- package/dist/blocks/deals/index.d.ts +262 -0
- package/dist/blocks/deals/index.js +380 -0
- package/dist/blocks/documents/index.d.ts +17 -0
- package/dist/blocks/documents/index.js +118 -0
- package/dist/blocks/domains/index.d.ts +117 -0
- package/dist/blocks/domains/index.js +118 -0
- package/dist/blocks/drive/index.d.ts +296 -0
- package/dist/blocks/drive/index.js +483 -0
- package/dist/blocks/employees/index.d.ts +205 -0
- package/dist/blocks/employees/index.js +289 -0
- package/dist/blocks/engagements/index.d.ts +125 -0
- package/dist/blocks/engagements/index.js +143 -0
- package/dist/blocks/entitlements/index.d.ts +40 -4
- package/dist/blocks/entitlements/index.js +14 -24
- package/dist/blocks/entitlements/react/index.d.ts +24 -0
- package/dist/blocks/entitlements/react/index.js +35 -0
- package/dist/blocks/entitlements/react/server.d.ts +6 -0
- package/dist/blocks/entitlements/react/server.js +7 -0
- package/dist/blocks/entitlements/solid/index.d.ts +13 -0
- package/dist/blocks/entitlements/solid/index.js +14 -0
- package/dist/blocks/entitlements/svelte/index.d.ts +13 -0
- package/dist/blocks/entitlements/svelte/index.js +14 -0
- package/dist/blocks/entitlements/vue/index.d.ts +13 -0
- package/dist/blocks/entitlements/vue/index.js +14 -0
- package/dist/blocks/flags/index.d.ts +61 -187
- package/dist/blocks/flags/index.js +65 -252
- package/dist/blocks/forms/index.d.ts +302 -0
- package/dist/blocks/forms/index.js +341 -0
- package/dist/blocks/forms/react/index.d.ts +16 -0
- package/dist/blocks/forms/react/index.js +25 -0
- package/dist/blocks/forms/react/server.d.ts +6 -0
- package/dist/blocks/forms/react/server.js +7 -0
- package/dist/blocks/forms/solid/index.d.ts +13 -0
- package/dist/blocks/forms/solid/index.js +14 -0
- package/dist/blocks/forms/svelte/index.d.ts +13 -0
- package/dist/blocks/forms/svelte/index.js +14 -0
- package/dist/blocks/forms/vue/index.d.ts +13 -0
- package/dist/blocks/forms/vue/index.js +14 -0
- package/dist/blocks/hiring/index.d.ts +154 -0
- package/dist/blocks/hiring/index.js +180 -0
- package/dist/blocks/identities/index.d.ts +83 -0
- package/dist/blocks/identities/index.js +103 -0
- package/dist/blocks/imports/index.d.ts +91 -0
- package/dist/blocks/imports/index.js +123 -0
- package/dist/blocks/inbox/index.d.ts +22 -3
- package/dist/blocks/inbox/index.js +78 -6
- package/dist/blocks/inbox/react/index.d.ts +3 -2
- package/dist/blocks/inbox/react/index.js +55 -199
- package/dist/blocks/inbox/react/server.d.ts +2 -1
- package/dist/blocks/inbox/solid/index.d.ts +27 -0
- package/dist/blocks/inbox/solid/index.js +32 -0
- package/dist/blocks/inbox/svelte/index.d.ts +27 -0
- package/dist/blocks/inbox/svelte/index.js +32 -0
- package/dist/blocks/inbox/vue/index.d.ts +27 -0
- package/dist/blocks/inbox/vue/index.js +32 -0
- package/dist/blocks/index.d.ts +9 -64
- package/dist/blocks/index.js +193 -57
- package/dist/blocks/inventory/index.d.ts +181 -0
- package/dist/blocks/inventory/index.js +219 -0
- package/dist/blocks/invoices/index.d.ts +154 -0
- package/dist/blocks/invoices/index.js +216 -0
- package/dist/blocks/jobs/index.d.ts +188 -49
- package/dist/blocks/jobs/index.js +698 -527
- package/dist/blocks/knowledge/index.d.ts +10 -3
- package/dist/blocks/knowledge/index.js +17 -3
- package/dist/blocks/leave/index.d.ts +241 -0
- package/dist/blocks/leave/index.js +209 -0
- package/dist/blocks/memory/index.d.ts +9 -4
- package/dist/blocks/memory/index.js +49 -12
- package/dist/blocks/metrics/index.d.ts +174 -0
- package/dist/blocks/metrics/index.js +217 -0
- package/dist/blocks/mfa/index.d.ts +7 -0
- package/dist/blocks/mfa/index.js +12 -0
- package/dist/blocks/mfa/react/index.d.ts +30 -0
- package/dist/blocks/mfa/react/index.js +45 -0
- package/dist/blocks/mfa/react/server.d.ts +9 -0
- package/dist/blocks/mfa/react/server.js +12 -0
- package/dist/blocks/mfa/solid/index.d.ts +20 -0
- package/dist/blocks/mfa/solid/index.js +23 -0
- package/dist/blocks/mfa/svelte/index.d.ts +20 -0
- package/dist/blocks/mfa/svelte/index.js +23 -0
- package/dist/blocks/mfa/vue/index.d.ts +20 -0
- package/dist/blocks/mfa/vue/index.js +23 -0
- package/dist/blocks/notifications/index.d.ts +345 -26
- package/dist/blocks/notifications/index.js +744 -120
- package/dist/blocks/notifications/react/index.d.ts +7 -32
- package/dist/blocks/notifications/react/index.js +23 -49
- package/dist/blocks/notifications/react/server.d.ts +2 -1
- package/dist/blocks/notifications/solid/index.d.ts +13 -0
- package/dist/blocks/notifications/solid/index.js +14 -0
- package/dist/blocks/notifications/svelte/index.d.ts +13 -0
- package/dist/blocks/notifications/svelte/index.js +14 -0
- package/dist/blocks/notifications/vue/index.d.ts +13 -0
- package/dist/blocks/notifications/vue/index.js +14 -0
- package/dist/blocks/number-sequences/index.d.ts +39 -0
- package/dist/blocks/number-sequences/index.js +38 -0
- package/dist/blocks/oauth-server/index.d.ts +228 -0
- package/dist/blocks/oauth-server/index.js +309 -0
- package/dist/blocks/onboarding/index.d.ts +8 -3
- package/dist/blocks/onboarding/index.js +17 -63
- package/dist/blocks/onboarding/react/index.d.ts +3 -12
- package/dist/blocks/onboarding/react/index.js +18 -60
- package/dist/blocks/onboarding/react/server.d.ts +2 -2
- package/dist/blocks/onboarding/react/server.js +2 -2
- package/dist/blocks/onboarding/solid/index.d.ts +13 -0
- package/dist/blocks/onboarding/solid/index.js +14 -0
- package/dist/blocks/onboarding/svelte/index.d.ts +13 -0
- package/dist/blocks/onboarding/svelte/index.js +14 -0
- package/dist/blocks/onboarding/vue/index.d.ts +13 -0
- package/dist/blocks/onboarding/vue/index.js +14 -0
- package/dist/blocks/orders/index.d.ts +264 -0
- package/dist/blocks/orders/index.js +589 -0
- package/dist/blocks/organizations/index.d.ts +31 -238
- package/dist/blocks/organizations/index.js +49 -399
- package/dist/blocks/organizations/react/index.d.ts +108 -0
- package/dist/blocks/organizations/react/index.js +113 -0
- package/dist/blocks/organizations/react/server.d.ts +11 -0
- package/dist/blocks/organizations/react/server.js +15 -0
- package/dist/blocks/organizations/solid/index.d.ts +34 -0
- package/dist/blocks/organizations/solid/index.js +32 -0
- package/dist/blocks/organizations/svelte/index.d.ts +34 -0
- package/dist/blocks/organizations/svelte/index.js +32 -0
- package/dist/blocks/organizations/vue/index.d.ts +34 -0
- package/dist/blocks/organizations/vue/index.js +32 -0
- package/dist/blocks/outbox/index.d.ts +12 -3
- package/dist/blocks/outbox/index.js +97 -67
- package/dist/blocks/parties/index.d.ts +181 -0
- package/dist/blocks/parties/index.js +258 -0
- package/dist/blocks/pipelines/index.d.ts +189 -0
- package/dist/blocks/pipelines/index.js +204 -0
- package/dist/blocks/profiles/index.d.ts +14 -9
- package/dist/blocks/profiles/index.js +20 -6
- package/dist/blocks/promotions/index.d.ts +125 -0
- package/dist/blocks/promotions/index.js +123 -0
- package/dist/blocks/push/index.d.ts +48 -28
- package/dist/blocks/push/index.js +45 -16
- package/dist/blocks/quotes/index.d.ts +152 -0
- package/dist/blocks/quotes/index.js +209 -0
- package/dist/blocks/rank/index.d.ts +71 -0
- package/dist/blocks/rank/index.js +53 -0
- package/dist/blocks/reactions/index.d.ts +18 -0
- package/dist/blocks/reactions/index.js +26 -0
- package/dist/blocks/reactions/react/index.d.ts +23 -0
- package/dist/blocks/reactions/react/index.js +46 -0
- package/dist/blocks/reactions/react/server.d.ts +6 -0
- package/dist/blocks/reactions/react/server.js +7 -0
- package/dist/blocks/reactions/solid/index.d.ts +13 -0
- package/dist/blocks/reactions/solid/index.js +14 -0
- package/dist/blocks/reactions/svelte/index.d.ts +13 -0
- package/dist/blocks/reactions/svelte/index.js +14 -0
- package/dist/blocks/reactions/vue/index.d.ts +13 -0
- package/dist/blocks/reactions/vue/index.js +14 -0
- package/dist/blocks/relations/index.d.ts +180 -0
- package/dist/blocks/relations/index.js +254 -0
- package/dist/blocks/reminders/index.d.ts +177 -0
- package/dist/blocks/reminders/index.js +171 -0
- package/dist/blocks/reviews/index.d.ts +148 -0
- package/dist/blocks/reviews/index.js +139 -0
- package/dist/blocks/rules/index.d.ts +103 -0
- package/dist/blocks/rules/index.js +54 -0
- package/dist/blocks/rules/react/index.d.ts +34 -0
- package/dist/blocks/rules/react/index.js +55 -0
- package/dist/blocks/rules/react/server.d.ts +6 -0
- package/dist/blocks/rules/react/server.js +7 -0
- package/dist/blocks/rules/solid/index.d.ts +13 -0
- package/dist/blocks/rules/solid/index.js +14 -0
- package/dist/blocks/rules/svelte/index.d.ts +13 -0
- package/dist/blocks/rules/svelte/index.js +14 -0
- package/dist/blocks/rules/vue/index.d.ts +13 -0
- package/dist/blocks/rules/vue/index.js +14 -0
- package/dist/blocks/saved-views/index.d.ts +17 -0
- package/dist/blocks/saved-views/index.js +29 -0
- package/dist/blocks/saved-views/react/index.d.ts +34 -0
- package/dist/blocks/saved-views/react/index.js +57 -0
- package/dist/blocks/saved-views/react/server.d.ts +7 -0
- package/dist/blocks/saved-views/react/server.js +7 -0
- package/dist/blocks/saved-views/solid/index.d.ts +13 -0
- package/dist/blocks/saved-views/solid/index.js +14 -0
- package/dist/blocks/saved-views/svelte/index.d.ts +13 -0
- package/dist/blocks/saved-views/svelte/index.js +14 -0
- package/dist/blocks/saved-views/vue/index.d.ts +13 -0
- package/dist/blocks/saved-views/vue/index.js +14 -0
- package/dist/blocks/search/index.d.ts +31 -0
- package/dist/blocks/search/index.js +61 -0
- package/dist/blocks/search/react/index.d.ts +36 -0
- package/dist/blocks/search/react/index.js +49 -0
- package/dist/blocks/search/react/server.d.ts +7 -0
- package/dist/blocks/search/react/server.js +8 -0
- package/dist/blocks/search/solid/index.d.ts +13 -0
- package/dist/blocks/search/solid/index.js +14 -0
- package/dist/blocks/search/svelte/index.d.ts +13 -0
- package/dist/blocks/search/svelte/index.js +14 -0
- package/dist/blocks/search/vue/index.d.ts +13 -0
- package/dist/blocks/search/vue/index.js +14 -0
- package/dist/blocks/service-principals/index.d.ts +126 -0
- package/dist/blocks/service-principals/index.js +107 -0
- package/dist/blocks/sessions/index.d.ts +7 -0
- package/dist/blocks/sessions/index.js +14 -0
- package/dist/blocks/sessions/react/index.d.ts +31 -0
- package/dist/blocks/sessions/react/index.js +51 -0
- package/dist/blocks/sessions/react/server.d.ts +9 -0
- package/dist/blocks/sessions/react/server.js +11 -0
- package/dist/blocks/sessions/solid/index.d.ts +20 -0
- package/dist/blocks/sessions/solid/index.js +23 -0
- package/dist/blocks/sessions/svelte/index.d.ts +20 -0
- package/dist/blocks/sessions/svelte/index.js +23 -0
- package/dist/blocks/sessions/vue/index.d.ts +20 -0
- package/dist/blocks/sessions/vue/index.js +23 -0
- package/dist/blocks/settings/index.d.ts +20 -86
- package/dist/blocks/settings/index.js +28 -101
- package/dist/blocks/settings/react/index.d.ts +25 -0
- package/dist/blocks/settings/react/index.js +37 -0
- package/dist/blocks/settings/react/server.d.ts +7 -0
- package/dist/blocks/settings/react/server.js +7 -0
- package/dist/blocks/settings/solid/index.d.ts +13 -0
- package/dist/blocks/settings/solid/index.js +14 -0
- package/dist/blocks/settings/svelte/index.d.ts +13 -0
- package/dist/blocks/settings/svelte/index.js +14 -0
- package/dist/blocks/settings/vue/index.d.ts +13 -0
- package/dist/blocks/settings/vue/index.js +14 -0
- package/dist/blocks/share-links/index.d.ts +99 -0
- package/dist/blocks/share-links/index.js +99 -0
- package/dist/blocks/shared-signals/index.d.ts +395 -0
- package/dist/blocks/shared-signals/index.js +880 -0
- package/dist/blocks/signing-keys/index.d.ts +29 -0
- package/dist/blocks/signing-keys/index.js +161 -0
- package/dist/blocks/sla/index.d.ts +114 -0
- package/dist/blocks/sla/index.js +122 -0
- package/dist/blocks/sso/index.d.ts +169 -147
- package/dist/blocks/sso/index.js +259 -321
- package/dist/blocks/state-machine/index.d.ts +105 -0
- package/dist/blocks/state-machine/index.js +116 -0
- package/dist/blocks/tags/index.d.ts +20 -0
- package/dist/blocks/tags/index.js +28 -0
- package/dist/blocks/tags/react/index.d.ts +28 -0
- package/dist/blocks/tags/react/index.js +47 -0
- package/dist/blocks/tags/react/server.d.ts +7 -0
- package/dist/blocks/tags/react/server.js +7 -0
- package/dist/blocks/tags/solid/index.d.ts +13 -0
- package/dist/blocks/tags/solid/index.js +14 -0
- package/dist/blocks/tags/svelte/index.d.ts +13 -0
- package/dist/blocks/tags/svelte/index.js +14 -0
- package/dist/blocks/tags/vue/index.d.ts +13 -0
- package/dist/blocks/tags/vue/index.js +14 -0
- package/dist/blocks/targets/index.d.ts +82 -0
- package/dist/blocks/targets/index.js +73 -0
- package/dist/blocks/teams/index.d.ts +61 -0
- package/dist/blocks/teams/index.js +83 -0
- package/dist/blocks/templates/index.d.ts +362 -0
- package/dist/blocks/templates/index.js +483 -0
- package/dist/blocks/time-tracking/index.d.ts +250 -0
- package/dist/blocks/time-tracking/index.js +317 -0
- package/dist/blocks/trash/index.d.ts +78 -0
- package/dist/blocks/trash/index.js +79 -0
- package/dist/blocks/undo/index.d.ts +9 -0
- package/dist/blocks/undo/index.js +14 -0
- package/dist/blocks/undo/react/index.d.ts +17 -0
- package/dist/blocks/undo/react/index.js +24 -0
- package/dist/blocks/undo/react/server.d.ts +7 -0
- package/dist/blocks/undo/react/server.js +7 -0
- package/dist/blocks/undo/solid/index.d.ts +13 -0
- package/dist/blocks/undo/solid/index.js +14 -0
- package/dist/blocks/undo/svelte/index.d.ts +13 -0
- package/dist/blocks/undo/svelte/index.js +14 -0
- package/dist/blocks/undo/vue/index.d.ts +13 -0
- package/dist/blocks/undo/vue/index.js +14 -0
- package/dist/blocks/usage/index.d.ts +10 -4
- package/dist/blocks/usage/index.js +16 -232
- package/dist/blocks/usage/react/index.d.ts +23 -0
- package/dist/blocks/usage/react/index.js +38 -0
- package/dist/blocks/usage/react/server.d.ts +7 -0
- package/dist/blocks/usage/react/server.js +7 -0
- package/dist/blocks/usage/solid/index.d.ts +13 -0
- package/dist/blocks/usage/solid/index.js +14 -0
- package/dist/blocks/usage/svelte/index.d.ts +13 -0
- package/dist/blocks/usage/svelte/index.js +14 -0
- package/dist/blocks/usage/vue/index.d.ts +13 -0
- package/dist/blocks/usage/vue/index.js +14 -0
- package/dist/blocks/versions/index.d.ts +165 -0
- package/dist/blocks/versions/index.js +283 -0
- package/dist/blocks/waitlist/index.d.ts +57 -19
- package/dist/blocks/waitlist/index.js +47 -12
- package/dist/blocks/webhooks/index.d.ts +275 -134
- package/dist/blocks/webhooks/index.js +775 -231
- package/dist/blocks/work-items/index.d.ts +208 -0
- package/dist/blocks/work-items/index.js +305 -0
- package/dist/blocks/workflow-builder/index.d.ts +26 -6
- package/dist/blocks/workflow-builder/index.js +30 -5
- package/dist/blocks/workflow-builder/react/index.d.ts +4 -19
- package/dist/blocks/workflow-builder/react/index.js +45 -89
- package/dist/blocks/workflow-builder/solid/index.d.ts +20 -0
- package/dist/blocks/workflow-builder/solid/index.js +23 -0
- package/dist/blocks/workflow-builder/svelte/index.d.ts +20 -0
- package/dist/blocks/workflow-builder/svelte/index.js +23 -0
- package/dist/blocks/workflow-builder/vue/index.d.ts +20 -0
- package/dist/blocks/workflow-builder/vue/index.js +23 -0
- package/dist/blocks/workflows/index.d.ts +20 -3
- package/dist/blocks/workflows/index.js +27 -3
- package/dist/blocks/workflows/react/index.d.ts +5 -20
- package/dist/blocks/workflows/react/index.js +41 -113
- package/dist/blocks/workflows/solid/index.d.ts +20 -0
- package/dist/blocks/workflows/solid/index.js +23 -0
- package/dist/blocks/workflows/svelte/index.d.ts +20 -0
- package/dist/blocks/workflows/svelte/index.js +23 -0
- package/dist/blocks/workflows/vue/index.d.ts +20 -0
- package/dist/blocks/workflows/vue/index.js +23 -0
- package/dist/blocks-D2WQ63PI.js +84 -0
- package/dist/bookings-0XnhHNLM.js +181 -0
- package/dist/build-Cm0T5MVG.js +1815 -0
- package/dist/build-Cx08LOT7.js +689 -0
- package/dist/calendar-DcFrpSOy.js +801 -0
- package/dist/casing-DdFWEtqr.js +16 -0
- package/dist/channel-D-LHCY11.d.ts +78 -0
- package/dist/channels-C-GCoNLd.js +114 -0
- package/dist/chat-sdk/index.d.ts +131 -7
- package/dist/chat-sdk/index.js +155 -6
- package/dist/chat-sdk/react/index.d.ts +2 -1
- package/dist/chat-sdk/react/index.js +1 -2
- package/dist/chat-sdk/react/server.d.ts +2 -1
- package/dist/chat-sdk/react/server.js +1 -2
- package/dist/claims-0KZVT804.js +61 -0
- package/dist/cli/add-B0zoJUYX.js +651 -0
- package/dist/cli/add-Bd5M6uEZ.js +75 -0
- package/dist/cli/add-CT6p5psO.js +1 -0
- package/dist/cli/adopt-C9jl8z0h.js +1 -0
- package/dist/cli/adopt-CebmlHL-.js +393 -0
- package/dist/cli/agent-CCt_aXpJ.js +176 -0
- package/dist/cli/apply-Dxk8AUNU.js +302 -0
- package/dist/cli/arazzo-1.0-BalU61-_.js +532 -0
- package/dist/cli/arazzo-1.1-Bnfasbun.js +705 -0
- package/dist/cli/asyncapi-3.0.0-C78dBjR6.js +5593 -0
- package/dist/cli/asyncapi-3.1.0-g-7aIfV1.js +5833 -0
- package/dist/cli/bin.js +41 -21
- package/dist/cli/blocks-Bzz326nn.js +1 -0
- package/dist/cli/blocks-Dq25D80b.js +124 -0
- package/dist/cli/cache-Cx6qlRDR.js +502 -0
- package/dist/cli/catalog-Di15TkM2.js +187 -0
- package/dist/cli/codemod-woL4cVe0.js +985 -0
- package/dist/cli/command-CK9Cn8HV.js +836 -0
- package/dist/cli/completion-BO5UK6bG.js +1 -0
- package/dist/cli/completion-CWoDmFZr.js +205 -0
- package/dist/cli/completion-J3dqnNoW.js +30 -0
- package/dist/cli/config-C1d-mbUA.js +1 -0
- package/dist/cli/config-CLHpTk9Y.js +1421 -0
- package/dist/cli/config-MuKY9kUj.js +39 -0
- package/dist/cli/config-edit-Ct1lXe8a.js +603 -0
- package/dist/cli/db-DT1bGB02.js +78 -0
- package/dist/cli/design-edit-B-WsnhUF.js +175 -0
- package/dist/cli/detect-DYDUBLU4.js +84 -0
- package/dist/cli/dist-D_VbPAMR.js +129 -0
- package/dist/cli/dist-DuiMMRbJ.js +1112 -0
- package/dist/cli/doctor-SGw36h1D.js +3806 -0
- package/dist/cli/emit-ZAYeh4Yn.js +1042 -0
- package/dist/cli/env-CVXvJoLP.js +142 -0
- package/dist/cli/env-D3gZ-OHj.js +1 -0
- package/dist/cli/env-DJ4ZaJpe.js +1 -0
- package/dist/cli/env-DMw_MBe5.js +996 -0
- package/dist/cli/errors-Caiz_0so.js +98 -0
- package/dist/cli/fastest-levenshtein-dhRRvrM2.js +120 -0
- package/dist/cli/files-DDvAwTko.js +963 -0
- package/dist/cli/gen-B344S1Oy.js +304 -0
- package/dist/cli/guards-CUlDNYZs.js +8 -0
- package/dist/cli/index.d.ts +297 -140
- package/dist/cli/index.js +13 -8
- package/dist/cli/init-B1dL-RZL.js +393 -0
- package/dist/cli/introspect-BPcFi8ow.js +51 -0
- package/dist/cli/io-BiPX-_J1.js +37 -0
- package/dist/cli/jsonc-C8M0EhHB.js +1 -0
- package/dist/cli/keys-jsxCNJdz.js +106 -0
- package/dist/cli/metadata-CxBZo4KM.js +138 -0
- package/dist/cli/module-migration-YFFdWC5X.js +592 -0
- package/dist/cli/moved-D9OT_Uyy.js +152 -0
- package/dist/cli/names-D6LrQnOv.js +6 -0
- package/dist/cli/openapi-3.0-t7xwkuRH.js +928 -0
- package/dist/cli/openapi-3.1-CVze7tBS.js +870 -0
- package/dist/cli/openapi-3.2-CFoMYM01.js +1031 -0
- package/dist/cli/parse-DE59XyWt.js +170 -0
- package/dist/cli/preset-DmcC4nWE.js +129 -0
- package/dist/cli/prompts-Bvg_DBpe.js +1137 -0
- package/dist/cli/redact-PjNswZOs.js +35 -0
- package/dist/cli/rolldown-runtime-kJ_i4yPo.js +26 -0
- package/dist/cli/run-CM_2g6KQ.js +684 -0
- package/dist/cli/run-DaHfUUO9.js +1539 -0
- package/dist/cli/scaffold-CFdDq36-.js +898 -0
- package/dist/cli/scaffold-Dsjr7sOT.js +1 -0
- package/dist/cli/seed-DldDdXWO.js +113 -0
- package/dist/cli/seed-DtRFysBe.js +1 -0
- package/dist/cli/shell-Btv_4Yhe.js +10 -0
- package/dist/cli/skills-v34NDb2D.js +121 -0
- package/dist/cli/snapshot-DAxLi4hO.js +347 -0
- package/dist/cli/source-args-0I9zylYy.js +47 -0
- package/dist/cli/spec-CaaSOYZS.js +7501 -0
- package/dist/cli/spec-De0cfceR.js +1 -0
- package/dist/cli/sql-0I1EQ3JG.js +2 -0
- package/dist/cli/sql-DDLWwzh9.js +326 -0
- package/dist/cli/stack-status-CRanl-CN.js +456 -0
- package/dist/cli/style-2JfIPVMQ.js +29 -0
- package/dist/cli/supabase-toml-DjLgBYEo.js +1153 -0
- package/dist/cli/sync-DFx-6t5n.js +88 -0
- package/dist/cli/topics-GJuIDwlY.js +30 -0
- package/dist/cli/trace-VbdO6bcN.js +7 -0
- package/dist/cli/typegen--TfvOPLh.js +494 -0
- package/dist/cli/typegen-version-BNBPI4Eu.js +9 -0
- package/dist/cli/validate-BfWNKvxy.js +7522 -0
- package/dist/cli/version-DNRW4DAP.js +4 -0
- package/dist/cli/write-BS6F3Yvz.js +180 -0
- package/dist/client/index.d.ts +7 -3
- package/dist/client/index.js +207 -4
- package/dist/client/native/index.d.ts +71 -4
- package/dist/client/native/index.js +65 -6
- package/dist/client-01MAuqzW.js +137 -0
- package/dist/client-B7QVIrJS.d.ts +263 -0
- package/dist/client-B9CqzgPO.js +75 -0
- package/dist/client-BAcILZOf.d.ts +122 -0
- package/dist/client-BOT2KVIp.js +128 -0
- package/dist/client-BUGIS__1.js +1051 -0
- package/dist/client-BVZtq_rQ.d.ts +34 -0
- package/dist/client-BYaOGCOG.js +84 -0
- package/dist/client-BfgwocyT.js +155 -0
- package/dist/client-BpS8a6Fm.js +51 -0
- package/dist/client-BxnLb9bz.d.ts +58 -0
- package/dist/client-BzCv4kKE.js +65 -0
- package/dist/client-C-2ajnhM.js +89 -0
- package/dist/client-C5mUEb2n.js +55 -0
- package/dist/client-C9QrWrnt.js +209 -0
- package/dist/client-CFdMJ1h9.d.ts +277 -0
- package/dist/client-CHmjaI0j.d.ts +103 -0
- package/dist/client-CWs3X4g1.js +120 -0
- package/dist/client-Cdedx_lw.js +438 -0
- package/dist/client-CiGjS6us.js +169 -0
- package/dist/client-CilSKXNu.js +288 -0
- package/dist/client-ClZ0D-if.d.ts +162 -0
- package/dist/client-Cmensu7l.d.ts +419 -0
- package/dist/client-Co6BPmvr.d.ts +108 -0
- package/dist/client-CoVGPi0c.js +133 -0
- package/dist/client-CvITjt-9.js +310 -0
- package/dist/client-CzI4QpG3.d.ts +131 -0
- package/dist/client-D5CZ2a34.d.ts +455 -0
- package/dist/client-DAHsEOVV.d.ts +155 -0
- package/dist/client-DG7FwgZD.js +104 -0
- package/dist/client-DIrCmOXz.d.ts +157 -0
- package/dist/client-DNq8xYb-.js +52 -0
- package/dist/client-DdfSIZ3M.d.ts +107 -0
- package/dist/client-DjTRlN3a.d.ts +68 -0
- package/dist/client-Ezlbskjq.js +60 -0
- package/dist/client-LLmgiCwJ.d.ts +135 -0
- package/dist/client-Lx3mlvKO.d.ts +100 -0
- package/dist/client-PGRk3DGz.js +98 -0
- package/dist/client-Tc47rlSe.d.ts +350 -0
- package/dist/client-VvY0K3wr.d.ts +59 -0
- package/dist/client-WAdEBBrH.d.ts +65 -0
- package/dist/client-WHeWTNVL.d.ts +130 -0
- package/dist/client-WgrPIHH-.js +213 -0
- package/dist/client-ZTtSV3l8.js +40 -0
- package/dist/client-b2_GjzzD.js +438 -0
- package/dist/client-ckXfdy1k.js +38 -0
- package/dist/client-e-hFkLUf.d.ts +34 -0
- package/dist/client-hyP_nS5R.d.ts +218 -0
- package/dist/client-i8glKhxh.js +67 -0
- package/dist/client-iDLLvGR4.d.ts +52 -0
- package/dist/client-pxjbgOz6.js +376 -0
- package/dist/client-xkSCp9hN.d.ts +101 -0
- package/dist/client-ysNShuJZ.d.ts +39 -0
- package/dist/clone-dN9XOL1K.js +29 -0
- package/dist/cloud-event-5rCEzIgK.d.ts +64 -0
- package/dist/cloud-event-CTh9Sr5w.js +60 -0
- package/dist/codecs-Bm46sVxr.d.ts +215 -0
- package/dist/codecs-YAekuQ08.js +358 -0
- package/dist/codegen-DeoSt1mi.d.ts +1571 -0
- package/dist/comments-D7sbd0xG.js +230 -0
- package/dist/commerce/index.d.ts +211 -0
- package/dist/commerce/index.js +517 -0
- package/dist/compiler-BVxepAY0.d.ts +14 -0
- package/dist/compose-Djntdacs.js +53 -0
- package/dist/composite-M6zPs1Xm.js +130 -0
- package/dist/composite-djtGy6bz.d.ts +67 -0
- package/dist/config/index.d.ts +7 -6
- package/dist/config/index.js +7 -4
- package/dist/config-BA1K4OPk.js +557 -0
- package/dist/config-C4MozTD7.js +3892 -0
- package/dist/connector-Cp8CqssJ.d.ts +107 -0
- package/dist/connectors-CjD-R2ks.d.ts +141 -0
- package/dist/context-1sSWcd7_.d.ts +365 -0
- package/dist/context-B8_p3MXo.js +11 -0
- package/dist/context-BDDgjoxz.js +41 -0
- package/dist/context-C-bHOtoD.js +5 -0
- package/dist/context-D6ZMKc2L.js +19 -0
- package/dist/context-wDKxSl9x.js +9 -0
- package/dist/controller-D6NyNrlZ.js +79 -0
- package/dist/controller-D845qKh2.d.ts +46 -0
- package/dist/credentials/index.d.ts +15 -5
- package/dist/credentials/index.js +17 -17
- package/dist/cron-CUaKTjWC.js +145 -0
- package/dist/cron-route-CoL-By8Z.js +24 -0
- package/dist/csv-CVfXqaAu.js +89 -0
- package/dist/csv-D3eKxV1T.d.ts +28 -0
- package/dist/define-B2ln373W.js +1896 -0
- package/dist/define-DxHHbNEb.d.ts +1005 -0
- package/dist/define-block-B6Gjs57G.js +52 -0
- package/dist/define-block-CLZMEFeK.d.ts +256 -0
- package/dist/delegation-Dt8hMJWu.js +140 -0
- package/dist/delivery-B1mKaOen.js +30 -0
- package/dist/delivery-BeHgwS3k.d.ts +69 -0
- package/dist/documents-CWn2mtYf.d.ts +96 -0
- package/dist/edge/index.d.ts +68 -13
- package/dist/edge/index.js +75 -43
- package/dist/elysia/index.d.ts +1 -1
- package/dist/elysia/index.js +2 -2
- package/dist/email-BcmbIWOX.d.ts +42 -0
- package/dist/embeddings-C2S14foD.d.ts +78 -0
- package/dist/embeddings-CVeOoufZ.js +128 -0
- package/dist/ended-nIdGCHxk.d.ts +70 -0
- package/dist/entitlements-BvU5dLpW.js +45 -0
- package/dist/entitlements-CbzCKalR.d.ts +53 -0
- package/dist/env/index.d.ts +1 -1
- package/dist/env/index.js +12 -5
- package/dist/envelope-CHYk5MXU.js +90 -0
- package/dist/errors-8i6tSICm.js +301 -0
- package/dist/errors-PIpcoe4z.js +44 -0
- package/dist/escape-like-AUEPn-C5.d.ts +15 -0
- package/dist/eve/index.d.ts +5 -5
- package/dist/eve/index.js +11 -17
- package/dist/eve-DUF70Vgi.d.ts +151 -0
- package/dist/event-calendar-DJXXMJ3x.d.ts +217 -0
- package/dist/event-map-Dy2vKHai.js +19 -0
- package/dist/events/index.d.ts +4 -4
- package/dist/events/index.js +16 -8
- package/dist/events-CTreRrgP.d.ts +800 -0
- package/dist/events-xbpHkcK9.js +82 -0
- package/dist/executor-BBHgjUJu.d.ts +39 -0
- package/dist/executor-BINiCFNp.d.ts +95 -0
- package/dist/executor-BXSHYyW2.js +735 -0
- package/dist/executor-D1kYY5BA.js +206 -0
- package/dist/executor-D3K3tycZ.js +147 -0
- package/dist/executor-DKrZr3AC.d.ts +174 -0
- package/dist/executor-DoE-5iM8.d.ts +272 -0
- package/dist/expo/index.d.ts +4 -4
- package/dist/expo/index.js +7 -7
- package/dist/expo-sqlite/index.d.ts +3 -3
- package/dist/expo-sqlite/index.js +2 -2
- package/dist/extra-CGQ2_q3S.js +10 -0
- package/dist/field-errors-CWXN_h_E.d.ts +113 -0
- package/dist/field-errors-DZVRWRkf.js +13 -0
- package/dist/field-formats-Cif5gr7c.js +102 -0
- package/dist/field-formats-DCvOQlOy.d.ts +39 -0
- package/dist/filter-evaluate-C0ULNfS7.js +214 -0
- package/dist/filter-evaluate-CjcFq0RT.d.ts +34 -0
- package/dist/filter-query-B3lhckKX.js +518 -0
- package/dist/filter-query-BKmRYrTE.d.ts +121 -0
- package/dist/flags-D_D1jAHB.d.ts +230 -0
- package/dist/flags-XAOsdNAc.js +279 -0
- package/dist/flags-sdk/index.d.ts +143 -0
- package/dist/flags-sdk/index.js +175 -0
- package/dist/form-definition-sagoefQr.d.ts +95 -0
- package/dist/format-CNwSjXUU.d.ts +70 -0
- package/dist/framework-DG5Bv39u.d.ts +26 -0
- package/dist/framework-INbjMZX1.js +51 -0
- package/dist/function-contract-B4c0XuTU.d.ts +21 -0
- package/dist/function-contract-DmkBg19r.js +5 -0
- package/dist/gateway-Cup00l0w.d.ts +109 -0
- package/dist/gateway-options-ClvvfuuU.d.ts +29 -0
- package/dist/gateway-options-DcLcWL5_.js +27 -0
- package/dist/graph-Cs28bOlK.js +360 -0
- package/dist/groups-CxonH3u5.js +16 -0
- package/dist/guards-6F2bCtQR.js +12 -0
- package/dist/h3/index.d.ts +7 -3
- package/dist/h3/index.js +3 -3
- package/dist/h3/v1/index.d.ts +1 -1
- package/dist/h3/v1/index.js +2 -2
- package/dist/history-GH1HJWQV.js +271 -0
- package/dist/hmac-DCud0myt.js +22 -0
- package/dist/hono/index.d.ts +33 -14
- package/dist/hono/index.js +58 -25
- package/dist/http-BF-V0CUF.js +25 -0
- package/dist/impersonation-CDeCzZP6.js +62 -0
- package/dist/impersonation-D5chOkrO.d.ts +25 -0
- package/dist/inbox-3ql4phyr.d.ts +400 -0
- package/dist/index-BIi8i6BO.d.ts +97 -0
- package/dist/index-BkzNUPId.d.ts +131 -0
- package/dist/index-BlqrGvb3.d.ts +227 -0
- package/dist/index-Brhm22eL.d.ts +138 -0
- package/dist/index-Bvw9r94Q.d.ts +245 -0
- package/dist/index-CETRk4YO.d.ts +82 -0
- package/dist/index-CrZitvSC.d.ts +258 -0
- package/dist/index-DfVXx4Ia.d.ts +202 -0
- package/dist/index-DygICr8c.d.ts +206 -0
- package/dist/index-Ej8XQ9w9.d.ts +58 -0
- package/dist/index-jhqhynmh.d.ts +58 -0
- package/dist/index.d.ts +90 -15
- package/dist/index.js +16 -14
- package/dist/iso4217-A8ev-CNW.js +176 -0
- package/dist/json-schema-CpYINXG8.js +548 -0
- package/dist/jws-35a8VhFV.d.ts +60 -0
- package/dist/jws-milwSg7E.js +191 -0
- package/dist/kit-D5S7KNFe.d.ts +66 -0
- package/dist/kit-MUK34-ZG.js +104 -0
- package/dist/knowledge-BXQ2-V0a.d.ts +187 -0
- package/dist/knowledge-CJ_Mm3ic.js +272 -0
- package/dist/lint/index.js +9 -9
- package/dist/list/index.d.ts +4 -3
- package/dist/list/index.js +4 -2
- package/dist/list-query-CBXNfJt4.js +549 -0
- package/dist/list-query-PvP3pPGQ.d.ts +194 -0
- package/dist/live-DwDreq1n.js +239 -0
- package/dist/mcp/index.d.ts +67 -20
- package/dist/mcp/index.js +220 -135
- package/dist/mcp/sdk/index.d.ts +38 -6
- package/dist/mcp/sdk/index.js +89 -7
- package/dist/message-B0fNJgq8.d.ts +93 -0
- package/dist/messages-Cx4ttLJJ.js +493 -0
- package/dist/metric-definition-95HFoW1V.d.ts +70 -0
- package/dist/metric-definition-BjxVn4R5.js +117 -0
- package/dist/mfa-DtflCXEt.d.ts +30 -0
- package/dist/model-lUafmeBQ.d.ts +311 -0
- package/dist/model-messages-CTu-6kBu.js +206 -0
- package/dist/model-messages-D4GZllOD.d.ts +34 -0
- package/dist/money-95Rff2vT.js +185 -0
- package/dist/money-DFpWzQvk.d.ts +76 -0
- package/dist/money-codec-C_-K5ZUU.js +41 -0
- package/dist/nestjs/index.d.ts +2 -2
- package/dist/nestjs/index.js +2 -2
- package/dist/next/index.d.ts +115 -55
- package/dist/next/index.js +208 -173
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.js +3 -3
- package/dist/nuxt/index.d.ts +2 -2
- package/dist/nuxt/index.js +3 -2
- package/dist/onboarding-BZvLLPaY.js +68 -0
- package/dist/openapi/index.d.ts +4 -2
- package/dist/openapi/index.js +21 -343
- package/dist/operations-C9-UcReN.d.ts +6 -0
- package/dist/operations-CsIfMED7.js +29 -0
- package/dist/optional-peer-Dx8TaYXw.js +29 -0
- package/dist/options-B0FcEKtH.js +5 -0
- package/dist/options-B5BU2Yg7.js +11 -0
- package/dist/options-B5woz53h.js +5 -0
- package/dist/options-B7EJaOX-.js +5 -0
- package/dist/options-B9q8CROT.js +9 -0
- package/dist/options-BA6jV6py.js +10 -0
- package/dist/options-BAW2ZlZ8.js +8 -0
- package/dist/options-BMjcVuaI.js +82 -0
- package/dist/options-BOOEzor9.js +5 -0
- package/dist/options-BQ07tTYr.js +11 -0
- package/dist/options-BZsoPJGX.js +5 -0
- package/dist/options-BesyRBnM.js +16 -0
- package/dist/options-Bf96aVk5.js +10 -0
- package/dist/options-BlOPivzq.js +8 -0
- package/dist/options-Blg8lAkz.js +9 -0
- package/dist/options-Bn4cu0gt.js +5 -0
- package/dist/options-BnoJOwXK.js +8 -0
- package/dist/options-Bp7sgKvl.js +30 -0
- package/dist/options-BqO9GYig.js +311 -0
- package/dist/options-BxcFLL1G.js +8 -0
- package/dist/options-C1HR4UZd.js +13 -0
- package/dist/options-C239TLRF.js +14 -0
- package/dist/options-C2HesT4A.js +9 -0
- package/dist/options-C3EzJATk.js +10 -0
- package/dist/options-C3hZ1fbC.js +10 -0
- package/dist/options-C6rEL6U5.js +8 -0
- package/dist/options-CAzVf-1T.js +8 -0
- package/dist/options-CGKSUFUq.js +5 -0
- package/dist/options-CGjGE8xS.js +12 -0
- package/dist/options-CK5WiFwG.js +11 -0
- package/dist/options-CN7PHkhU.js +8 -0
- package/dist/options-CTZNpb2n.js +5 -0
- package/dist/options-CUqQ9pm7.js +9 -0
- package/dist/options-CVSWgZpY.js +8 -0
- package/dist/options-CXFz0xxS.js +8 -0
- package/dist/options-CZk_fbbg.js +9 -0
- package/dist/options-C_pb1Nd3.js +5 -0
- package/dist/options-CbSOj53r.d.ts +52 -0
- package/dist/options-ChD15QxJ.js +9 -0
- package/dist/options-CkScQRJR.js +12 -0
- package/dist/options-CljDnQt2.js +25 -0
- package/dist/options-CmraJhd9.js +5 -0
- package/dist/options-CoX6VKGb.js +8 -0
- package/dist/options-CrC78Tee.js +5 -0
- package/dist/options-Ctac2R0Q.js +11 -0
- package/dist/options-CyD7OObW.js +17 -0
- package/dist/options-D1blDihM.js +9 -0
- package/dist/options-D2Gd6VfS.js +8 -0
- package/dist/options-D2d1fFuw.js +11 -0
- package/dist/options-D40CQzji.js +8 -0
- package/dist/options-D7f2VKzj.js +25 -0
- package/dist/options-DARkpk2P.js +8 -0
- package/dist/options-DCduTMST.js +17 -0
- package/dist/options-DDzggjN7.js +8 -0
- package/dist/options-DMR68VAC.js +5 -0
- package/dist/options-DNKyN5uq.js +20 -0
- package/dist/options-DTDxFT6J.js +13 -0
- package/dist/options-DWpz3V3F.js +8 -0
- package/dist/options-DXqcJyOP.js +10 -0
- package/dist/options-DYBQ973I.js +8 -0
- package/dist/options-Dh4wu9Ks.js +15 -0
- package/dist/options-Dmg1Da9P.js +25 -0
- package/dist/options-DsUgamiy.js +5 -0
- package/dist/options-EsUh744F.js +13 -0
- package/dist/options-Fz6xqu-b.js +5 -0
- package/dist/options-HSv8R6h5.js +13 -0
- package/dist/options-I8zVkB_y.js +8 -0
- package/dist/options-MHhjOoWz.js +5 -0
- package/dist/options-MLxMkd5B.js +11 -0
- package/dist/options-OJ5zmgr8.js +10 -0
- package/dist/options-QKP9FFcK.js +8 -0
- package/dist/options-SViK_2x2.js +12 -0
- package/dist/options-XgfTctbI.js +5 -0
- package/dist/options-_9arpsX9.js +11 -0
- package/dist/options-eBPsk4WI.js +11 -0
- package/dist/options-kLKILdIu.js +10 -0
- package/dist/options-n22NK3PL.js +8 -0
- package/dist/options-oeAq4Ho5.js +12 -0
- package/dist/options-qU8vgxhO.js +18 -0
- package/dist/options-vXMBEWSH.js +5 -0
- package/dist/options-wFnK3qWl.js +9 -0
- package/dist/options-xVAy3UsV.js +9 -0
- package/dist/organizations-Cw7om8uk.js +398 -0
- package/dist/orpc/index.d.ts +26 -10
- package/dist/orpc/index.js +66 -44
- package/dist/otel/index.d.ts +3 -3
- package/dist/otel/index.js +42 -7
- package/dist/outbox-BTeqjbum.d.ts +181 -0
- package/dist/overlay/index.d.ts +233 -0
- package/dist/overlay/index.js +554 -0
- package/dist/payments-h45bswA-.js +16 -0
- package/dist/pg-error-BV_X8Isq.js +26 -0
- package/dist/plugin-CDLJRFFg.d.ts +190 -0
- package/dist/plugins/actor/index.d.ts +1 -1
- package/dist/plugins/actor/index.js +1 -1
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +2 -2
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +11 -4
- package/dist/plugins/tenant/index.d.ts +9 -3
- package/dist/plugins/tenant/index.js +17 -3
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +8 -1
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +4 -4
- package/dist/pool-CwOCsX9e.d.ts +114 -0
- package/dist/pool-errors-GMjlCN0d.js +27 -0
- package/dist/ports-Ca63W4cW.d.ts +285 -0
- package/dist/ports-DAdQyjZz.d.ts +86 -0
- package/dist/ports-DGK3KTDH.js +133 -0
- package/dist/postgres/index.d.ts +28 -6
- package/dist/postgres/index.js +54 -22
- package/dist/postgrest-Cw9Bs5gm.d.ts +53 -0
- package/dist/powersync/index.d.ts +5 -5
- package/dist/powersync/index.js +4 -4
- package/dist/powersync/react/index.d.ts +8 -3
- package/dist/powersync/react/index.js +11 -3
- package/dist/preset/index.d.ts +352 -0
- package/dist/preset/index.js +564 -0
- package/dist/problem-CEjyhV5T.d.ts +76 -0
- package/dist/problem-pIiL3WbE.js +159 -0
- package/dist/provider-9gBq9W6k.d.ts +113 -0
- package/dist/provider-DKh2xFZb.js +57 -0
- package/dist/push-token-D6REHMMw.js +16 -0
- package/dist/push-token-asrmi7-6.d.ts +24 -0
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +1 -1
- package/dist/query-Ctz-qKRx.js +303 -0
- package/dist/range-DwpWe8e7.js +7 -0
- package/dist/range-p5V4rBhZ.d.ts +8 -0
- package/dist/rank-keys-DcPMt_Jk.js +60 -0
- package/dist/rank-result-DS8IoLup.js +18 -0
- package/dist/rank-result-Dngpt2n5.d.ts +14 -0
- package/dist/rate-limit--QSiFA6D.js +325 -0
- package/dist/rate-limit-BtbddDnE.js +49 -0
- package/dist/rate-limit-C88MEAOm.d.ts +41 -0
- package/dist/ratelimit-headers-Bsxx0Tj2.js +62 -0
- package/dist/react/index.d.ts +9 -9
- package/dist/react/index.js +4 -5
- package/dist/react/native/index.d.ts +1 -1
- package/dist/react/native/index.js +2 -2
- package/dist/react/server.d.ts +10 -10
- package/dist/react/server.js +4 -4
- package/dist/react/session.d.ts +2 -2
- package/dist/react-CKXUkwPx.js +410 -0
- package/dist/react-controller-Xn-IU44p.js +40 -0
- package/dist/react-env-Cc7Vf6eb.js +13 -0
- package/dist/react-router/index.d.ts +6 -6
- package/dist/react-router/index.js +10 -12
- package/dist/read-set-GEb-i0On.js +314 -0
- package/dist/realtime/index.d.ts +8 -4
- package/dist/realtime/index.js +5 -333
- package/dist/realtime-status-D2dw1hOE.js +12 -0
- package/dist/realtime-status-DcsO00Gw.d.ts +8 -0
- package/dist/refusal-br4Ma9Cf.js +61 -0
- package/dist/refusal-oD3ISM9h.d.ts +18 -0
- package/dist/replicas-BvIL4_jL.js +94 -0
- package/dist/resolve-CAR9oaKW.js +869 -0
- package/dist/resolve-w9EpHzuM.d.ts +266 -0
- package/dist/resource-B4ka8dDH.d.ts +106 -0
- package/dist/resource-C8r5xGqY.js +463 -0
- package/dist/resource-DO4gbVi5.js +91 -0
- package/dist/resource-execute-D0yQ-8WR.d.ts +12 -0
- package/dist/resource-schemas-BJ518zFH.js +79 -0
- package/dist/resource-types-D2vEQVXI.d.ts +141 -0
- package/dist/resources-BklF-rjz.d.ts +7 -0
- package/dist/resources-BlZdLsWQ.js +64 -0
- package/dist/respond-C7I8kLK_.d.ts +93 -0
- package/dist/respond-DdhZ9Wbn.js +95 -0
- package/dist/result-BlgnMB0H.js +128 -0
- package/dist/result-Cj3yWhjH.d.ts +298 -0
- package/dist/rrule-zBh3CA1r.js +290 -0
- package/dist/rules-B0-3Iosu.js +261 -0
- package/dist/run-watch-CQRdpy-f.js +53 -0
- package/dist/sales-lines-BAk8In-Z.js +41 -0
- package/dist/sales-lines-C6gafAwg.d.ts +28 -0
- package/dist/sales-shared-DAAGwQwN.d.ts +8 -0
- package/dist/sales-shared-SblIgCgI.js +22 -0
- package/dist/scope-DgWjuXUw.js +90 -0
- package/dist/search-B6h03cAx.js +106 -0
- package/dist/search-BtoWY6n_.js +9 -0
- package/dist/server/index.d.ts +211 -43
- package/dist/server/index.js +296 -19
- package/dist/server-types-OohhIQb2.d.ts +737 -0
- package/dist/session-DiWHtYrO.d.ts +95 -0
- package/dist/settings-BXFn9zpS.js +90 -0
- package/dist/shared-BmMcMoW9.js +89 -0
- package/dist/shared-DbO3Dfms.js +14 -0
- package/dist/shared-as0EG51f.js +60 -0
- package/dist/sign-in-D0xN6E_l.js +360 -0
- package/dist/signatures-B41ApWXA.js +145 -0
- package/dist/signatures-BMdJ7neI.d.ts +43 -0
- package/dist/signers-CXt_wA9S.d.ts +385 -0
- package/dist/signing-keys-Bq9mMCTh.d.ts +90 -0
- package/dist/solid/index.d.ts +9 -18
- package/dist/solid/index.js +4 -9
- package/dist/solid-controller-DLsmrczT.js +33 -0
- package/dist/solid-controller-DqlvYFZa.d.ts +11 -0
- package/dist/solid-start/index.d.ts +8 -12
- package/dist/solid-start/index.js +10 -9
- package/dist/spec/index.d.ts +154 -0
- package/dist/spec/index.js +386 -0
- package/dist/spec-pins-Dqpml2dZ.d.ts +84 -0
- package/dist/spec-pins-S4PymRxp.js +60 -0
- package/dist/sql/index.d.ts +327 -140
- package/dist/sql/index.js +71245 -23282
- package/dist/sql-DJrXG-3Z.js +422 -0
- package/dist/sso-BDva_-4y.js +217 -0
- package/dist/sso-BN0LnMqE.d.ts +131 -0
- package/dist/ssr/index.d.ts +5 -6
- package/dist/ssr/index.js +4 -3
- package/dist/standard-B5fe2gnW.js +25 -0
- package/dist/standard-C3_AAF3g.d.ts +7 -0
- package/dist/stats-D6vkb89f.js +1128 -0
- package/dist/storage/index.d.ts +4 -6
- package/dist/storage/index.js +23 -20
- package/dist/storage-api-Cx-1iWBd.d.ts +582 -0
- package/dist/store-B5qYKsTh.d.ts +68 -0
- package/dist/streams/index.d.ts +5 -5
- package/dist/streams/index.js +6 -5
- package/dist/streams/redis/index.d.ts +1 -1
- package/dist/streams/redis/index.js +6 -12
- package/dist/stripe/index.d.ts +16 -0
- package/dist/stripe/index.js +14 -0
- package/dist/stripe-NVcNOhHy.d.ts +140 -0
- package/dist/stripe-XZrSmDTf.js +32 -0
- package/dist/subject-readers-DiePmaYP.js +46 -0
- package/dist/subject-readers-fxP5wu0s.d.ts +21 -0
- package/dist/subjects-z8VWA_lF.d.ts +69 -0
- package/dist/subscribers-Cq--DL1E.js +34 -0
- package/dist/supabase-mcp-C6tMoEjn.d.ts +43 -0
- package/dist/supabase-mcp-DomZ-amK.js +28 -0
- package/dist/support-BsKkvA92.js +522 -0
- package/dist/support-view-BbcZR7Io.d.ts +17 -0
- package/dist/svelte/index.d.ts +8 -17
- package/dist/svelte/index.js +3 -3
- package/dist/svelte-controller-CDAujo-_.js +50 -0
- package/dist/svelte-controller-DxxHEHxD.d.ts +10 -0
- package/dist/sveltekit/index.d.ts +11 -13
- package/dist/sveltekit/index.js +12 -14
- package/dist/table-schema-pQlER3jj.js +283 -0
- package/dist/tags-Dw8vKkcI.d.ts +30 -0
- package/dist/tags-PaG0Ns_9.js +84 -0
- package/dist/tanstack-db/index.d.ts +2 -2
- package/dist/tanstack-db/index.js +1 -1
- package/dist/tanstack-db/supabase/index.d.ts +51 -0
- package/dist/tanstack-db/supabase/index.js +268 -0
- package/dist/tanstack-start/index.d.ts +7 -7
- package/dist/tanstack-start/index.js +9 -13
- package/dist/tax-DDZUB_oc.d.ts +273 -0
- package/dist/tax-D_kSCdWU.js +140 -0
- package/dist/tee-Bsisfd7t.js +101 -0
- package/dist/template-DdgYjIZ5.js +123 -0
- package/dist/temporal-CB5Fpo5d.js +79 -0
- package/dist/temporal-required-DqRzr7i0.js +20 -0
- package/dist/tenant-B-fiItlJ.js +27 -0
- package/dist/tenant-DdmVhsff.d.ts +17 -0
- package/dist/tenant-naming-Bsj3Uvap.js +27 -0
- package/dist/testing/index.d.ts +459 -125
- package/dist/testing/index.js +2368 -720
- package/dist/timing-BMCWgpdZ.js +92 -0
- package/dist/topic-ByDz9edV.js +542 -0
- package/dist/topic-cMS7UtM1.d.ts +359 -0
- package/dist/ttl-cache-XzZ-Q2eX.js +29 -0
- package/dist/types-6oiy6rIa.d.ts +205 -0
- package/dist/types-BRTSpOIh.js +5 -0
- package/dist/types-CdP0z5tS.d.ts +564 -0
- package/dist/types-D0W4fnsc.d.ts +162 -0
- package/dist/types-R6syp1el.js +29 -0
- package/dist/types-YfwpIE-d.d.ts +357 -0
- package/dist/types-pNY_UpDs.d.ts +240 -0
- package/dist/types-vaoP9S9_.d.ts +635 -0
- package/dist/undo-CEGIRkPA.js +44 -0
- package/dist/undo-DGo0skyO.d.ts +43 -0
- package/dist/usage-CdlfuS3i.js +250 -0
- package/dist/usage-DM2rzczj.d.ts +202 -0
- package/dist/vault-tOkPLnMB.d.ts +50 -0
- package/dist/vercel-connect/index.d.ts +1 -1
- package/dist/vercel-connect/index.js +6 -12
- package/dist/verify-D63A2ALE.d.ts +142 -0
- package/dist/verify-EQcC3Khp.js +126 -0
- package/dist/version-D87ZteKy.js +4 -0
- package/dist/view-DZ63nqwt.js +105 -0
- package/dist/view-Dv0A949a.d.ts +90 -0
- package/dist/vue/index.d.ts +9 -18
- package/dist/vue/index.js +4 -17
- package/dist/vue-controller-C9tFzqAc.js +32 -0
- package/dist/vue-controller-HPfMTDa2.d.ts +11 -0
- package/dist/watch-DDuWYv0Y.d.ts +84 -0
- package/dist/watch-DT_B3CnJ.js +139 -0
- package/dist/watch-topic-EFpOIIDE.d.ts +28 -0
- package/dist/watch-topic-miuymrli.js +57 -0
- package/dist/wire-DEY0ajW9.js +45 -0
- package/dist/workflow-builder-OrglbXUh.js +681 -0
- package/dist/workflow-sdk/builder/index.d.ts +46 -15
- package/dist/workflow-sdk/builder/index.js +69 -21
- package/dist/workflow-sdk/index.d.ts +75 -2
- package/dist/workflow-sdk/index.js +83 -3
- package/dist/workflow-sdk/world/index.d.ts +51 -20
- package/dist/workflow-sdk/world/index.js +189 -128
- package/dist/workflow-sdk-BZ6qsRlY.js +243 -0
- package/dist/workflow-sdk-q8cLwW1K.d.ts +161 -0
- package/dist/workflow-sdk-world-DPMdBfzn.js +2103 -0
- package/dist/workflows-C-OvZpeg.js +282 -0
- package/dist/workflows-DFQeXDAI.d.ts +243 -0
- package/dist/yjs/index.d.ts +67 -0
- package/dist/yjs/index.js +212 -0
- package/package.json +837 -44
- package/schemas/cli-commands-v1.json +220 -0
- package/schemas/cli-result-v1.json +138 -0
- package/schemas/config-v1.json +4317 -653
- package/schemas/doctor-report-v1.json +27 -1
- package/schemas/snapshot-v2.json +271 -0
- package/schemas/spec-manifest-v1.json +95 -0
- package/skills/better-supabase/SKILL.md +19 -8
- package/skills/better-supabase/references/plugins.md +2 -2
- package/skills/better-supabase/references/troubleshooting.md +20 -20
- package/skills/better-supabase/references/ui-recipes.md +18652 -0
- package/skills/better-supabase-api/SKILL.md +14 -14
- package/skills/better-supabase-api/references/adapters.md +12 -2
- package/skills/better-supabase-api/references/ai.md +4 -4
- package/skills/better-supabase-api/references/expo.md +7 -4
- package/skills/better-supabase-api/references/powersync.md +2 -1
- package/skills/better-supabase-api/references/streams-credentials.md +8 -5
- package/skills/better-supabase-auth/SKILL.md +18 -11
- package/skills/better-supabase-auth/references/authorization-providers.md +5 -5
- package/skills/better-supabase-testing/SKILL.md +2 -2
- package/testing/act-cases.json +370 -0
- package/dist/access-sql-B0dF-SoF.js +0 -181
- package/dist/actor-CQTlc1Hh.d.ts +0 -43
- package/dist/adapter-iKgjHVPx.js +0 -1190
- package/dist/agents-CVHGd7It.d.ts +0 -94
- package/dist/ai-cache-CBWs1W0k.js +0 -80
- package/dist/ai-cache-DaDu_G5L.d.ts +0 -64
- package/dist/ai-chat-7amvXjmQ.d.ts +0 -590
- package/dist/ai-chat-B24UI962.js +0 -743
- package/dist/ai-files-CSPNswUY.js +0 -319
- package/dist/ai-tasks-Cd1HMFvB.d.ts +0 -131
- package/dist/announcements-CiyOs-xP.js +0 -107
- package/dist/announcements-ZRGnScWI.d.ts +0 -72
- package/dist/api-keys-CIjghYjV.d.ts +0 -156
- package/dist/assistant-DwdpDH1q.d.ts +0 -147
- package/dist/attributes-5dYul0u0.js +0 -13
- package/dist/authorization-BFCqamMs.d.ts +0 -172
- package/dist/base64-D2gsioCY.js +0 -42
- package/dist/before-route-CnR0C0Jr.js +0 -30
- package/dist/bind-Bu_ZNa3W.d.ts +0 -62
- package/dist/bind-l4kAvYSv.js +0 -97
- package/dist/block-events-CrT8r7Fe.js +0 -76
- package/dist/block-fields-GFkxl4dP.js +0 -49
- package/dist/block-fields-SJ6fEf3o.d.ts +0 -31
- package/dist/block-helpers-CKtbFcRv.d.ts +0 -37
- package/dist/block-helpers-DWHxHsEQ.js +0 -190
- package/dist/block-hooks-BmjMyXTa.js +0 -114
- package/dist/block-hooks-plbZ0AbJ.d.ts +0 -107
- package/dist/block-transport-BLr-ZgLc.js +0 -59
- package/dist/block-transport-DvaoPuZ9.d.ts +0 -48
- package/dist/bucket-Cl46E3jS.d.ts +0 -347
- package/dist/build-BXIzE6Xv.js +0 -689
- package/dist/channel-_2Mx1Oyu.d.ts +0 -70
- package/dist/channels-CkgQ_X60.js +0 -114
- package/dist/claims-DDBQT6BK.js +0 -61
- package/dist/cli/bin.d.ts +0 -1
- package/dist/cli/codemod-Bl_tQpHn.js +0 -491
- package/dist/cli/command-CK14fP91.js +0 -502
- package/dist/cli/config-B9HkL8nF.js +0 -2
- package/dist/cli/config-DU7EJImt.js +0 -49
- package/dist/cli/config-NiPxAmUF.js +0 -1077
- package/dist/cli/db-DOBPt660.js +0 -52
- package/dist/cli/diff-SjkO5rNr.js +0 -752
- package/dist/cli/dist-DBPdlPYK.js +0 -1112
- package/dist/cli/dist-DRAVaE6w.js +0 -842
- package/dist/cli/dist-DreZ24Sz.js +0 -129
- package/dist/cli/doctor-DJk0aeBi.js +0 -3591
- package/dist/cli/env-CJLbUVPB.js +0 -108
- package/dist/cli/env-qXgyMV6r.js +0 -511
- package/dist/cli/errors-4v4DTtda.js +0 -56
- package/dist/cli/fastest-levenshtein-CFN-lNDv.js +0 -120
- package/dist/cli/gen-CCMc39Dx.js +0 -3100
- package/dist/cli/gen-DRmzPI75.js +0 -2
- package/dist/cli/init-BHuEQrQK.js +0 -765
- package/dist/cli/introspect-DzR2nyQb.js +0 -81
- package/dist/cli/io-DI1mTdSA.js +0 -33
- package/dist/cli/jsonc-BUniUpOk.js +0 -2
- package/dist/cli/keys-BGCnnb-s.js +0 -85
- package/dist/cli/openapi-BlRJ18s3.js +0 -66
- package/dist/cli/project-DZRaBaY7.js +0 -75
- package/dist/cli/prompts-BNcNfAve.js +0 -1142
- package/dist/cli/prompts-BTYQGunc.js +0 -2
- package/dist/cli/read-sets-CETOSFx4.js +0 -223
- package/dist/cli/rolldown-runtime-BPOCksWG.js +0 -24
- package/dist/cli/run-BqDPh9UH.js +0 -361
- package/dist/cli/seed-DQzqT7LP.js +0 -100
- package/dist/cli/skills-QQs4q68J.js +0 -138
- package/dist/cli/sql-B8csQPyn.js +0 -495
- package/dist/cli/sql-layout-D6cphHT3.js +0 -69
- package/dist/cli/style-C0NFdJRL.js +0 -29
- package/dist/cli/supabase-toml-B-bIukQF.js +0 -1095
- package/dist/cli/typegen-version-DeFbtzYI.js +0 -8
- package/dist/cli/version-59gcepIG.js +0 -4
- package/dist/client-CI0Z2zWX.js +0 -277
- package/dist/clone-BfgmLU90.js +0 -21
- package/dist/cloud-event-BnmD5fb9.js +0 -46
- package/dist/cloud-event-C1JCc1bH.d.ts +0 -50
- package/dist/compiler-DY_wkj4R.d.ts +0 -14
- package/dist/compose-BIBNgAnq.js +0 -53
- package/dist/composite--yqry7uW.d.ts +0 -146
- package/dist/composite-DXVUibdi.js +0 -130
- package/dist/config-D44Y7PAv.d.ts +0 -984
- package/dist/config-Dn7F5k9A.js +0 -202
- package/dist/connector-Dv8kK0MO.d.ts +0 -107
- package/dist/connectors-CrpknU0P.d.ts +0 -120
- package/dist/context-BCrWe7ow.d.ts +0 -237
- package/dist/cron-DVPUg5k3.js +0 -145
- package/dist/csv-DCK-PT1n.js +0 -25
- package/dist/define-B-TVjC25.js +0 -1915
- package/dist/define-CJJkkr1-.d.ts +0 -980
- package/dist/embeddings-4ZvFzjsU.js +0 -127
- package/dist/embeddings-C9KGQnXt.d.ts +0 -68
- package/dist/ended-XjeGQ5XW.d.ts +0 -63
- package/dist/entitlements-BKSVGtrG.d.ts +0 -51
- package/dist/entitlements-DRl6ZJUm.js +0 -24
- package/dist/envelope-DqnYGRw7.js +0 -47
- package/dist/errors-B11Duf73.js +0 -266
- package/dist/errors-DLjJHPLK.js +0 -44
- package/dist/escape-like-ImTK0ps_.d.ts +0 -15
- package/dist/eve-gT2Fpjut.d.ts +0 -151
- package/dist/events-DWDhx77e.d.ts +0 -549
- package/dist/executor-BVBFM86G.d.ts +0 -36
- package/dist/executor-Bq28caBo.js +0 -718
- package/dist/executor-C-R4pucm.js +0 -83
- package/dist/executor-CaWigTyR.d.ts +0 -148
- package/dist/executor-DbvExCFC.d.ts +0 -30
- package/dist/executor-De1Of3m3.js +0 -523
- package/dist/executor-DvsBbNBS.d.ts +0 -261
- package/dist/field-errors-BFOa7WhB.d.ts +0 -99
- package/dist/field-errors-VF1yb8yI.js +0 -13
- package/dist/framework-BmzgK32X.d.ts +0 -25
- package/dist/framework-a7aRCIqv.js +0 -51
- package/dist/gateway-C1ssRHnf.d.ts +0 -130
- package/dist/graph-BGyLLFLX.js +0 -265
- package/dist/guards-BnzDvFrz.js +0 -10
- package/dist/history-BZih6SFP.js +0 -64
- package/dist/hooks-1BTCJsiw.js +0 -309
- package/dist/impersonation-DcqaTBHx.js +0 -148
- package/dist/inbox-i7E1ppo6.d.ts +0 -384
- package/dist/index-ACiNPN4Z.d.ts +0 -74
- package/dist/index-Bepi-Hgg.d.ts +0 -206
- package/dist/index-BkWT_uBO.d.ts +0 -51
- package/dist/index-Bxih3l0u.d.ts +0 -81
- package/dist/index-CLg87OBW.d.ts +0 -76
- package/dist/index-CaxrMD1A.d.ts +0 -1
- package/dist/index-CdZKQF98.d.ts +0 -138
- package/dist/index-CewnYSpv.d.ts +0 -267
- package/dist/index-ClmjK_o-.d.ts +0 -55
- package/dist/index-D48jVS-N.d.ts +0 -246
- package/dist/index-DQh4VCc9.d.ts +0 -98
- package/dist/index-DunNRdnT.d.ts +0 -300
- package/dist/json-schema-CvER4kgT.js +0 -502
- package/dist/keys-4MnkWTTh.js +0 -35
- package/dist/kit-CkNzZUqv.d.ts +0 -60
- package/dist/kit-Fm-Fv7q5.js +0 -78
- package/dist/knowledge-BXBbT0Tr.d.ts +0 -176
- package/dist/knowledge-CDmiMLew.js +0 -255
- package/dist/list-query-BRt-zDF7.d.ts +0 -164
- package/dist/list-query-gl808ac1.js +0 -459
- package/dist/live-CTAZQkwl.js +0 -257
- package/dist/message-CaKlqdsX.js +0 -219
- package/dist/message-CfDoR0Y6.d.ts +0 -94
- package/dist/messages-B9-5887d.js +0 -715
- package/dist/messages-DYP0sUTe.d.ts +0 -59
- package/dist/mfa-Daak8ukG.d.ts +0 -54
- package/dist/modules-DpPwzC7E.d.ts +0 -192
- package/dist/modules-dxrVK3ID.js +0 -228
- package/dist/onboarding-BZGHYJks.d.ts +0 -62
- package/dist/outbox-Cq7pPYvl.d.ts +0 -161
- package/dist/path-Cv__-OTj.d.ts +0 -17
- package/dist/plugin-FGY8UhYm.d.ts +0 -151
- package/dist/pool-CAu2nLrL.d.ts +0 -89
- package/dist/postgrest-BLVdEnfe.d.ts +0 -53
- package/dist/problem-BreOl8b8.js +0 -115
- package/dist/problem-DD8LZFjc.d.ts +0 -67
- package/dist/provider-D-UEMvSD.d.ts +0 -113
- package/dist/provider-Di3sRutW.js +0 -57
- package/dist/query-BQ-NaZon.js +0 -303
- package/dist/queue-CLJurhdy.d.ts +0 -427
- package/dist/react-F80mF4-t.js +0 -409
- package/dist/react-topic-seAWjL3_.js +0 -38
- package/dist/read-set-DuKWC9Mj.js +0 -314
- package/dist/refusal-CeJWxmkH.js +0 -61
- package/dist/refusal-DhSU2Vvr.d.ts +0 -15
- package/dist/replicas-CwrYp5Zj.js +0 -94
- package/dist/resolve-ChTYeTEl.d.ts +0 -169
- package/dist/resolve-tJZY3FEa.js +0 -697
- package/dist/resource-BsvzCMlX.js +0 -212
- package/dist/resource-IPS5xi9q.d.ts +0 -74
- package/dist/respond-BUgsXNgk.js +0 -91
- package/dist/respond-DprrgyXX.d.ts +0 -93
- package/dist/result-D7y2fT87.d.ts +0 -282
- package/dist/result-DzUbqgdW.js +0 -128
- package/dist/scope-CDHcS7dE.js +0 -90
- package/dist/search-CUWCRYKL.js +0 -9
- package/dist/server-Bh0EVSwU.d.ts +0 -558
- package/dist/session-ChiXDZdZ.d.ts +0 -72
- package/dist/shared-CBxosKK3.js +0 -75
- package/dist/shared-CxWmcyqB.js +0 -73
- package/dist/sign-in-Cb1mwv6X.js +0 -86
- package/dist/signers-4y62_VIi.d.ts +0 -166
- package/dist/spec-pins--j6mTok4.js +0 -27
- package/dist/spec-pins-C89s6DyW.d.ts +0 -27
- package/dist/standard-BK0cqJ7y.js +0 -25
- package/dist/standard-CYsm7xpT.d.ts +0 -7
- package/dist/stats-D_AD9GGu.js +0 -1123
- package/dist/store-CGjgWu5n.d.ts +0 -68
- package/dist/stripe-BMj5LtsT.js +0 -38
- package/dist/stripe-D04h-QBT.d.ts +0 -136
- package/dist/support-ig_K4PvS.js +0 -314
- package/dist/support-view-CVOJJLpc.d.ts +0 -17
- package/dist/tags-Cx-LnIQP.d.ts +0 -30
- package/dist/tee-CoEppCER.js +0 -101
- package/dist/template--jDkl3UP.js +0 -114
- package/dist/template-CwO38Do8.d.ts +0 -6
- package/dist/temporal-BJGitm-h.js +0 -79
- package/dist/temporal-required-BVvW7SYJ.js +0 -20
- package/dist/tenant-BxNFk9bx.d.ts +0 -17
- package/dist/tenant-I1TEZ_BX.js +0 -27
- package/dist/timing-CyTQPubY.js +0 -92
- package/dist/types-6BdhkmZe.d.ts +0 -340
- package/dist/types-6cYK9pbJ.js +0 -29
- package/dist/types-6rocye-y.d.ts +0 -156
- package/dist/types-AYOvcyep.d.ts +0 -359
- package/dist/types-C05bxW-W.d.ts +0 -220
- package/dist/types-DGs5L0VN.d.ts +0 -192
- package/dist/usage-CrvrL2vw.d.ts +0 -177
- package/dist/vault-wlbBhqm7.d.ts +0 -50
- package/dist/verify-DL41hbvY.js +0 -232
- package/dist/verify-DPuWngD4.d.ts +0 -172
- package/dist/version-C6OfRAIn.js +0 -4
- package/dist/view-BeFcRV6G.js +0 -99
- package/dist/view-CF7ihnkg.d.ts +0 -87
- package/dist/watch-DI34G86j.d.ts +0 -60
- package/dist/watch-Xuxrvxq-.js +0 -122
- package/dist/wire-DtZQ4sdE.js +0 -45
- package/dist/workflow-builder-DJ1oHn8g.js +0 -398
- package/dist/workflow-sdk-B3yLD6Br.d.ts +0 -98
- package/dist/workflow-sdk-C2WJ_G2O.js +0 -173
- package/dist/workflow-sdk-world-D-prir6O.js +0 -1397
- package/dist/workflows-B0j9WKPu.d.ts +0 -185
- package/dist/workflows-wu1WWBn7.js +0 -216
package/dist/testing/index.js
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { t as DbException } from "../db-exception-CnWcYPxL.js";
|
|
2
|
-
import { n as dbError } from "../errors-
|
|
3
|
-
import { d as temporalText } from "../temporal-
|
|
4
|
-
import { t as temporal } from "../temporal-required-
|
|
5
|
-
import { a as ok } from "../result-
|
|
6
|
-
import { t as BetterSupabase } from "../define-
|
|
7
|
-
import {
|
|
8
|
-
import { t as
|
|
9
|
-
import {
|
|
2
|
+
import { n as dbError } from "../errors-8i6tSICm.js";
|
|
3
|
+
import { d as temporalText } from "../temporal-CB5Fpo5d.js";
|
|
4
|
+
import { t as temporal } from "../temporal-required-DqRzr7i0.js";
|
|
5
|
+
import { a as ok } from "../result-BlgnMB0H.js";
|
|
6
|
+
import { t as BetterSupabase } from "../define-B2ln373W.js";
|
|
7
|
+
import { t as isList } from "../guards-6F2bCtQR.js";
|
|
8
|
+
import { a as toBase64Url, r as fromBase64, t as base64ToText } from "../base64-BmeCBgGo.js";
|
|
9
|
+
import { o as resolveConfig } from "../config-BA1K4OPk.js";
|
|
10
10
|
import { ENV_VARIABLES } from "../env/index.js";
|
|
11
|
-
import {
|
|
12
|
-
import "../replicas-
|
|
13
|
-
import { a as tenantCredentialRef } from "../provider-
|
|
14
|
-
import { n as
|
|
15
|
-
import { t as
|
|
16
|
-
import {
|
|
11
|
+
import { t as defineController } from "../controller-D6NyNrlZ.js";
|
|
12
|
+
import "../replicas-BvIL4_jL.js";
|
|
13
|
+
import { a as tenantCredentialRef } from "../provider-DKh2xFZb.js";
|
|
14
|
+
import { n as rawError } from "../block-transport-G4t1umrR.js";
|
|
15
|
+
import { t as postgresExecutor } from "../executor-D1kYY5BA.js";
|
|
16
|
+
import { i as requestOf, n as checkPermission, r as defineAuthorizer } from "../authorizer-DH7DjkL3.js";
|
|
17
|
+
import { n as parseDbStats } from "../timing-BMCWgpdZ.js";
|
|
18
|
+
import { t as mailHeaders } from "../delivery-B1mKaOen.js";
|
|
17
19
|
import { toCloudEvents } from "../events/index.js";
|
|
20
|
+
import { d as money, l as isRate } from "../money-95Rff2vT.js";
|
|
21
|
+
import { t as SHIPPING_LINE } from "../tax-D_kSCdWU.js";
|
|
22
|
+
import { a as isScopeName, c as templateFunctions, l as templatePlaceholders, n as TEMPLATE_PLACEHOLDERS, s as scopeProblems, u as templateProblems } from "../access-sql-DUSSMaC3.js";
|
|
23
|
+
import { t as buildApiModel } from "../build-Cm0T5MVG.js";
|
|
18
24
|
import { timestamps } from "../plugins/timestamps/index.js";
|
|
19
25
|
import { softDelete } from "../plugins/soft-delete/index.js";
|
|
20
26
|
import { tenant } from "../plugins/tenant/index.js";
|
|
@@ -149,11 +155,11 @@ async function findSigningKeys(from) {
|
|
|
149
155
|
const supabase = join(dir, "supabase");
|
|
150
156
|
const toml = join(supabase, "config.toml");
|
|
151
157
|
if (existsSync(toml)) {
|
|
152
|
-
const configured = SIGNING_KEYS_PATH.exec(await readFile(toml, "
|
|
158
|
+
const configured = SIGNING_KEYS_PATH.exec(await readFile(toml, "utf-8"))?.[1];
|
|
153
159
|
const path = resolve(supabase, configured ?? "signing_keys.json");
|
|
154
160
|
if (existsSync(path)) return path;
|
|
155
161
|
}
|
|
156
|
-
if (dirname(dir) === dir) return
|
|
162
|
+
if (dirname(dir) === dir) return;
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
165
|
const isSigningKey = (value) => typeof value === "object" && value !== null && "kid" in value && typeof value.kid === "string" && "d" in value && typeof value.d === "string";
|
|
@@ -165,7 +171,7 @@ const isSigningKey = (value) => typeof value === "object" && value !== null && "
|
|
|
165
171
|
async function localSigningKey(path) {
|
|
166
172
|
const file = path ?? process.env["SUPABASE_SIGNING_KEYS_PATH"] ?? await findSigningKeys(process.cwd());
|
|
167
173
|
if (!file || !existsSync(file)) throw new TypeError("No local signing key. Run `better-supabase keys`, set `signing_keys_path` in supabase/config.toml and restart the stack, or pass { alg: \"HS256\" } to sign with the local JWT secret.");
|
|
168
|
-
const keys = JSON.parse(await readFile(file, "
|
|
174
|
+
const keys = JSON.parse(await readFile(file, "utf-8"));
|
|
169
175
|
const first = Array.isArray(keys) ? keys[0] : void 0;
|
|
170
176
|
if (!isSigningKey(first)) throw new TypeError(`${file} has no private ES256 key first.`);
|
|
171
177
|
return first;
|
|
@@ -179,7 +185,7 @@ async function signLocalJwt(claims, path) {
|
|
|
179
185
|
/** The local CLI stack's default JWT secret. */
|
|
180
186
|
const LOCAL_JWT_SECRET = "super-secret-jwt-token-with-at-least-32-characters-long";
|
|
181
187
|
function env$1(...names) {
|
|
182
|
-
if (typeof process === "undefined") return
|
|
188
|
+
if (typeof process === "undefined") return;
|
|
183
189
|
for (const name of names) {
|
|
184
190
|
const value = process.env[name];
|
|
185
191
|
if (value) return value;
|
|
@@ -230,7 +236,10 @@ async function asUser(betterSupabase, claims, stack = {}) {
|
|
|
230
236
|
},
|
|
231
237
|
claims: full
|
|
232
238
|
};
|
|
233
|
-
const supabase = createClient(url, publishableKey, {
|
|
239
|
+
const supabase = createClient(url, publishableKey, {
|
|
240
|
+
accessToken: () => Promise.resolve(token),
|
|
241
|
+
...stack.fetch ? { global: { fetch: stack.fetch } } : {}
|
|
242
|
+
});
|
|
234
243
|
return {
|
|
235
244
|
token,
|
|
236
245
|
claims: full,
|
|
@@ -240,6 +249,141 @@ async function asUser(betterSupabase, claims, stack = {}) {
|
|
|
240
249
|
};
|
|
241
250
|
}
|
|
242
251
|
//#endregion
|
|
252
|
+
//#region src/lite/runtime.ts
|
|
253
|
+
const LITE_INSTALL = "Supabase Lite is an optional peer: install it with `pnpm add -D @supabase/lite`";
|
|
254
|
+
/**
|
|
255
|
+
* `@supabase/lite` is an optional peer: variable specifiers keep bundlers
|
|
256
|
+
* from resolving it, and a missing install returns `undefined` so callers
|
|
257
|
+
* can say how to add it.
|
|
258
|
+
*/
|
|
259
|
+
async function loadLite() {
|
|
260
|
+
const root = "@supabase/lite";
|
|
261
|
+
const pglite = "@supabase/lite/pglite";
|
|
262
|
+
const sqlite = "@supabase/lite/sqlite";
|
|
263
|
+
try {
|
|
264
|
+
const [{ App }, pg, sq] = await Promise.all([
|
|
265
|
+
import(root),
|
|
266
|
+
import(pglite),
|
|
267
|
+
import(sqlite)
|
|
268
|
+
]);
|
|
269
|
+
return {
|
|
270
|
+
App,
|
|
271
|
+
createPgliteConnection: () => pg.createPgliteConnection(),
|
|
272
|
+
createSqliteConnection: (url) => sq.createConnection({ url })
|
|
273
|
+
};
|
|
274
|
+
} catch {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** Applies migrations, then declarative schemas, with Lite's migrator. */
|
|
279
|
+
async function migrateLite(connection, sql) {
|
|
280
|
+
const ddl = sql.filter((part) => part.trim() !== "").join("\n;\n");
|
|
281
|
+
if (ddl !== "") await connection.createMigrator(ddl).migrate();
|
|
282
|
+
}
|
|
283
|
+
/** Runs seed SQL: as is on PGlite, translated to SQLite on `sqlite-postgres`. */
|
|
284
|
+
async function seedLite(target, sql) {
|
|
285
|
+
if (sql.trim() === "") return;
|
|
286
|
+
if (target.driver === "pglite") {
|
|
287
|
+
await target.connection.driver.exec(sql);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const { connection } = target;
|
|
291
|
+
const translated = await connection.translateDdl(sql);
|
|
292
|
+
const statements = typeof translated === "string" ? translated : typeof translated === "object" && translated !== null && "ddl" in translated && typeof translated.ddl === "string" ? translated.ddl : "";
|
|
293
|
+
if (statements !== "") await connection.transaction([statements]);
|
|
294
|
+
}
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/testing/lite.ts
|
|
297
|
+
const LITE_URL = "http://localhost:54321";
|
|
298
|
+
function randomToken(bytes) {
|
|
299
|
+
return toBase64Url(crypto.getRandomValues(new Uint8Array(bytes)));
|
|
300
|
+
}
|
|
301
|
+
function list(value) {
|
|
302
|
+
if (value === void 0) return [];
|
|
303
|
+
return typeof value === "string" ? [value] : [...value];
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Supabase Lite in this process, for tests without Docker: an in-memory
|
|
307
|
+
* database with your schema, Auth with fresh API keys, and supabase-js
|
|
308
|
+
* clients whose requests go straight to Lite's `fetch` handler (no port).
|
|
309
|
+
*
|
|
310
|
+
* ```ts
|
|
311
|
+
* await using stack = await liteStack({ schemas: [await readFile("supabase/schemas/notes.sql", "utf8")] });
|
|
312
|
+
* const alice = await stack.asUser(betterSupabase, { sub: aliceId });
|
|
313
|
+
* expect(await alice.db.notes.count().orThrow()).toBe(1);
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
async function liteStack(options = {}) {
|
|
317
|
+
const lite = await loadLite();
|
|
318
|
+
if (!lite) throw new Error(`liteStack() needs @supabase/lite. ${LITE_INSTALL}`);
|
|
319
|
+
const driver = options.driver ?? "sqlite-postgres";
|
|
320
|
+
const jwtSecret = options.jwtSecret ?? randomToken(32);
|
|
321
|
+
const publishableKey = `sb_publishable_${randomToken(16)}`;
|
|
322
|
+
const secretKey = `sb_secret_${randomToken(16)}`;
|
|
323
|
+
let target;
|
|
324
|
+
if (driver === "pglite") target = {
|
|
325
|
+
driver,
|
|
326
|
+
connection: await lite.createPgliteConnection()
|
|
327
|
+
};
|
|
328
|
+
else target = {
|
|
329
|
+
driver,
|
|
330
|
+
connection: await lite.createSqliteConnection(":memory:")
|
|
331
|
+
};
|
|
332
|
+
const { connection } = target;
|
|
333
|
+
try {
|
|
334
|
+
const app = new lite.App({
|
|
335
|
+
connection,
|
|
336
|
+
auth: {
|
|
337
|
+
enabled: true,
|
|
338
|
+
jwt_secret: jwtSecret,
|
|
339
|
+
publishable_key: publishableKey,
|
|
340
|
+
secret_key: secretKey
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
await app.init();
|
|
344
|
+
await migrateLite(connection, [...options.migrations ?? [], ...options.schemas ?? []]);
|
|
345
|
+
for (const seed of list(options.seed)) await seedLite(target, seed);
|
|
346
|
+
const fetch = (input, init) => app.fetch(new Request(input, init));
|
|
347
|
+
const stateless = {
|
|
348
|
+
auth: {
|
|
349
|
+
persistSession: false,
|
|
350
|
+
autoRefreshToken: false
|
|
351
|
+
},
|
|
352
|
+
global: { fetch }
|
|
353
|
+
};
|
|
354
|
+
const close = () => connection.close();
|
|
355
|
+
return {
|
|
356
|
+
url: LITE_URL,
|
|
357
|
+
publishableKey,
|
|
358
|
+
secretKey,
|
|
359
|
+
jwtSecret,
|
|
360
|
+
fetch,
|
|
361
|
+
env: {
|
|
362
|
+
url: LITE_URL,
|
|
363
|
+
publishableKey,
|
|
364
|
+
secretKey,
|
|
365
|
+
secretKeys: { default: secretKey },
|
|
366
|
+
jwksUrl: new URL(`${LITE_URL}/auth/v1/.well-known/jwks.json`),
|
|
367
|
+
jwtSecret
|
|
368
|
+
},
|
|
369
|
+
supabase: createClient(LITE_URL, publishableKey, stateless),
|
|
370
|
+
admin: createClient(LITE_URL, secretKey, stateless),
|
|
371
|
+
asUser: (betterSupabase, claims) => asUser(betterSupabase, claims, {
|
|
372
|
+
url: LITE_URL,
|
|
373
|
+
publishableKey,
|
|
374
|
+
alg: "HS256",
|
|
375
|
+
jwtSecret,
|
|
376
|
+
fetch
|
|
377
|
+
}),
|
|
378
|
+
close,
|
|
379
|
+
[Symbol.asyncDispose]: close
|
|
380
|
+
};
|
|
381
|
+
} catch (error) {
|
|
382
|
+
await connection.close();
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
//#endregion
|
|
243
387
|
//#region src/testing/budget.ts
|
|
244
388
|
function isPrefetch(response) {
|
|
245
389
|
const headers = response.request().headers();
|
|
@@ -449,395 +593,684 @@ function deepFreeze(value) {
|
|
|
449
593
|
}
|
|
450
594
|
const frozenCopy = (value) => deepFreeze(structuredClone(value));
|
|
451
595
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
rows: [],
|
|
458
|
-
count: 0
|
|
459
|
-
}));
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
/** The operation `run` sends to the executor, built by `betterSupabase` without touching a database. */
|
|
463
|
-
async function captureOp(betterSupabase, run) {
|
|
464
|
-
const ops = [];
|
|
465
|
-
await run(betterSupabase.connect(recorder(ops)));
|
|
466
|
-
const [op] = ops;
|
|
467
|
-
if (!op) throw new Error("the repository call did not reach the executor");
|
|
468
|
-
return op;
|
|
596
|
+
//#endregion
|
|
597
|
+
//#region src/testing/isolation.ts
|
|
598
|
+
const scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
599
|
+
function keyOf(table, row) {
|
|
600
|
+
return Object.fromEntries(table.primaryKey.map((column) => [column, row[column]]));
|
|
469
601
|
}
|
|
470
|
-
function
|
|
471
|
-
const
|
|
472
|
-
if (!
|
|
473
|
-
return
|
|
602
|
+
async function rowsOf(outcome) {
|
|
603
|
+
const result = await outcome;
|
|
604
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
605
|
+
return Array.isArray(result.data) ? result.data : [result.data];
|
|
606
|
+
}
|
|
607
|
+
function env(names) {
|
|
608
|
+
if (typeof process === "undefined") return;
|
|
609
|
+
for (const name of names) {
|
|
610
|
+
const value = process.env[name];
|
|
611
|
+
if (value) return value;
|
|
612
|
+
}
|
|
474
613
|
}
|
|
475
|
-
const hasName = (subject) => ["has a name", () => {
|
|
476
|
-
expect(typeof subject.name === "string" && subject.name.length > 0, "name must be a non-empty string");
|
|
477
|
-
}];
|
|
478
614
|
/**
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
615
|
+
* Checks row-level security between two tenants against a running stack: for
|
|
616
|
+
* each table, a user of one tenant must not select, insert, update or delete
|
|
617
|
+
* the other tenant's rows. Runs without `betterSupabase`'s plugins, so the `tenant()`
|
|
618
|
+
* plugin can't hide a missing policy. Throws a `ConformanceError` naming each
|
|
619
|
+
* table and command that leaks; removes every row it created.
|
|
483
620
|
*
|
|
484
621
|
* ```ts
|
|
485
|
-
*
|
|
622
|
+
* await expectTenantIsolation(betterSupabase, {
|
|
623
|
+
* tenants: [
|
|
624
|
+
* { id: ACME, claims: { sub: alice, tenant_id: ACME } },
|
|
625
|
+
* { id: GLOBEX, claims: { sub: bob, tenant_id: GLOBEX } },
|
|
626
|
+
* ],
|
|
627
|
+
* tables: {
|
|
628
|
+
* tags: {
|
|
629
|
+
* row: (tenant, n) => ({ organizationId: tenant.id, name: `iso-${n}` }),
|
|
630
|
+
* update: { name: 'changed' },
|
|
631
|
+
* },
|
|
632
|
+
* },
|
|
633
|
+
* });
|
|
486
634
|
* ```
|
|
487
635
|
*/
|
|
488
|
-
function
|
|
489
|
-
const
|
|
490
|
-
const
|
|
491
|
-
const
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}],
|
|
516
|
-
["returns an aborted error for an aborted signal", async () => {
|
|
517
|
-
const controller = new AbortController();
|
|
518
|
-
controller.abort();
|
|
519
|
-
const result = await executor.execute(await read(), {
|
|
520
|
-
...context,
|
|
521
|
-
signal: controller.signal
|
|
522
|
-
});
|
|
523
|
-
expect(!result.ok, "an aborted request must fail");
|
|
524
|
-
expect(result.error.kind === "aborted", `expected kind "aborted", got "${result.error.kind}"`);
|
|
525
|
-
}],
|
|
526
|
-
["returns failures as results instead of throwing", async () => {
|
|
527
|
-
const op = await read();
|
|
528
|
-
const missing = {
|
|
529
|
-
...op,
|
|
530
|
-
table: {
|
|
531
|
-
...op.table,
|
|
532
|
-
name: "__better_supabase_missing__"
|
|
533
|
-
}
|
|
534
|
-
};
|
|
535
|
-
const result = await executor.execute(missing, context);
|
|
536
|
-
expect(!result.ok, "a missing table must fail");
|
|
537
|
-
expect(typeof result.error.kind === "string" && typeof result.error.message === "string", "errors must be DbErrors");
|
|
538
|
-
}],
|
|
539
|
-
executor.rpc && ["returns rpc failures as results", async () => {
|
|
540
|
-
expect(!(await executor.rpc("__better_supabase_missing__", {}, {
|
|
541
|
-
...context,
|
|
542
|
-
schema: "public"
|
|
543
|
-
})).ok, "calling a missing function must fail");
|
|
544
|
-
}],
|
|
545
|
-
executor.functionSources && ["reads from SelectOp.source instead of the table", async () => {
|
|
546
|
-
const op = await read();
|
|
547
|
-
if (op.kind !== "select") return;
|
|
548
|
-
expect(!(await executor.execute({
|
|
549
|
-
...op,
|
|
550
|
-
source: {
|
|
551
|
-
schema: op.table.schema,
|
|
552
|
-
name: "__better_supabase_missing__",
|
|
553
|
-
args: {}
|
|
554
|
-
}
|
|
555
|
-
}, context)).ok, "a missing source function must fail; reading the table ignores source");
|
|
556
|
-
}],
|
|
557
|
-
executor.batch && ["batch returns one result per operation, in order", async () => {
|
|
558
|
-
const rows = await read();
|
|
559
|
-
const count = await captureOp(betterSupabase, (db) => db[table]["count"]());
|
|
560
|
-
const missing = {
|
|
561
|
-
...rows,
|
|
562
|
-
table: {
|
|
563
|
-
...rows.table,
|
|
564
|
-
name: "__better_supabase_missing__"
|
|
565
|
-
}
|
|
566
|
-
};
|
|
567
|
-
const results = await executor.batch([
|
|
568
|
-
rows,
|
|
569
|
-
count,
|
|
570
|
-
missing
|
|
571
|
-
], context);
|
|
572
|
-
expect(results.length === 3, `expected 3 results, got ${results.length}`);
|
|
573
|
-
const [first, second, third] = results;
|
|
574
|
-
expect(first?.ok === true && Array.isArray(first.data.rows), "the first operation must return rows");
|
|
575
|
-
expect(second?.ok === true && typeof second.data.count === "number", "the second operation must return a count");
|
|
576
|
-
expect(third?.ok === false, "a failing operation must fail on its own, as a result");
|
|
577
|
-
}],
|
|
578
|
-
create && ["round-trips a write", async () => {
|
|
579
|
-
const repository = betterSupabase.connect(executor)[table];
|
|
580
|
-
const created = await repository["create"](create);
|
|
581
|
-
expect(created.ok, `create failed: ${JSON.stringify(created.error)}`);
|
|
582
|
-
const row = created.data;
|
|
583
|
-
for (const [key, value] of Object.entries(create)) expect(same(row[key], value), `created row has ${key} = ${JSON.stringify(row[key])}`);
|
|
584
|
-
const [primary] = betterSupabase.meta.tables[table].primaryKey;
|
|
585
|
-
expect(primary, "write checks need a table with a primary key");
|
|
586
|
-
const removed = await repository["delete"](row[primary]);
|
|
587
|
-
expect(removed.ok, `delete failed: ${JSON.stringify(removed.error)}`);
|
|
588
|
-
const again = await repository["findFirst"]({ where: { [primary]: row[primary] } });
|
|
589
|
-
expect(again.ok && again.data === null, "the deleted row is still readable");
|
|
590
|
-
}],
|
|
591
|
-
create && ["refuses a write past maxAffected and writes nothing", async () => {
|
|
592
|
-
const repository = betterSupabase.connect(executor)[table];
|
|
593
|
-
const [primary] = betterSupabase.meta.tables[table].primaryKey;
|
|
594
|
-
expect(primary, "write checks need a table with a primary key");
|
|
595
|
-
const created = await repository["create"](create);
|
|
596
|
-
expect(created.ok, `create failed: ${JSON.stringify(created.error)}`);
|
|
597
|
-
const id = created.data[primary];
|
|
598
|
-
try {
|
|
599
|
-
const refused = await repository["deleteMany"]({
|
|
600
|
-
where: { [primary]: id },
|
|
601
|
-
maxAffected: 0
|
|
602
|
-
});
|
|
603
|
-
expect(!refused.ok, "deleteMany past maxAffected must fail");
|
|
604
|
-
const kind = refused.error?.kind;
|
|
605
|
-
expect(kind === "max_affected", `expected kind "max_affected", got "${String(kind)}"`);
|
|
606
|
-
const kept = await repository["findFirst"]({ where: { [primary]: id } });
|
|
607
|
-
expect(kept.ok && kept.data !== null, "a refused deleteMany must not delete the row");
|
|
608
|
-
const allowed = await repository["deleteMany"]({
|
|
609
|
-
where: { [primary]: id },
|
|
610
|
-
maxAffected: 1
|
|
611
|
-
});
|
|
612
|
-
expect(allowed.ok, `deleteMany within maxAffected failed: ${JSON.stringify(allowed.error)}`);
|
|
613
|
-
} finally {
|
|
614
|
-
await repository["delete"](id);
|
|
636
|
+
async function expectTenantIsolation(betterSupabase, options) {
|
|
637
|
+
const stack = options.stack ?? {};
|
|
638
|
+
const bare = new BetterSupabase(betterSupabase.schema);
|
|
639
|
+
const url = stack.url ?? env(ENV_VARIABLES.url) ?? "http://127.0.0.1:54321";
|
|
640
|
+
const secretKey = stack.secretKey ?? env(ENV_VARIABLES.secretKey);
|
|
641
|
+
if (!secretKey) throw new TypeError("expectTenantIsolation needs stack.secretKey or $SUPABASE_SECRET_KEY to seed rows.");
|
|
642
|
+
const admin = bare.connect(createClient(url, secretKey, { auth: {
|
|
643
|
+
persistSession: false,
|
|
644
|
+
autoRefreshToken: false
|
|
645
|
+
} }));
|
|
646
|
+
await options.seed?.();
|
|
647
|
+
const tenants = options.tenants;
|
|
648
|
+
const users = await Promise.all(tenants.map(async (tenant) => (await asUser(bare, tenant.claims, stack)).db));
|
|
649
|
+
const label = (tenant) => tenant.name ?? tenant.id;
|
|
650
|
+
const cleanup = [];
|
|
651
|
+
const reads = [];
|
|
652
|
+
const deletes = [];
|
|
653
|
+
try {
|
|
654
|
+
for (const [key, spec] of Object.entries(options.tables)) {
|
|
655
|
+
const table = bare.meta.tables[key];
|
|
656
|
+
if (!table || table.primaryKey.length === 0) throw new TypeError(`expectTenantIsolation: "${key}" is not a table with a primary key`);
|
|
657
|
+
const seeded = [];
|
|
658
|
+
for (const tenant of tenants) {
|
|
659
|
+
const [row] = await rowsOf(admin[key].create(spec.row(tenant, 0)));
|
|
660
|
+
const where = keyOf(table, row);
|
|
661
|
+
cleanup.push(() => admin[key].deleteMany({ where }));
|
|
662
|
+
seeded.push(row);
|
|
615
663
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
tables: [table, `${table}_children`],
|
|
659
|
-
ids: ["1"]
|
|
660
|
-
})]
|
|
661
|
-
]);
|
|
664
|
+
for (const [self, other] of [[0, 1], [1, 0]]) {
|
|
665
|
+
const user = users[self][key];
|
|
666
|
+
const who = label(tenants[self]);
|
|
667
|
+
const whose = `${label(tenants[other])}'s`;
|
|
668
|
+
const own = keyOf(table, seeded[self]);
|
|
669
|
+
const target = keyOf(table, seeded[other]);
|
|
670
|
+
const current = async () => (await rowsOf(admin[key].findMany({ where: target })))[0];
|
|
671
|
+
reads.push([`${key}: ${who} reads its own row`, async () => {
|
|
672
|
+
expect((await rowsOf(user.findMany({ where: own }))).length === 1, `${who} can't read its own row, so the other checks prove nothing; check its claims and memberships`);
|
|
673
|
+
}], [`${key}: ${who} can't select ${whose} rows`, async () => {
|
|
674
|
+
expect((await rowsOf(user.findMany({ where: target }))).length === 0, `select returned ${whose} row`);
|
|
675
|
+
}], [`${key}: ${who} can't insert ${whose} rows`, async () => {
|
|
676
|
+
const row = spec.row(tenants[other], 1);
|
|
677
|
+
const result = await user.create(row, { returning: false });
|
|
678
|
+
if (result.ok) {
|
|
679
|
+
const where = Object.fromEntries(Object.entries(row).filter(([, value]) => scalar(value)));
|
|
680
|
+
cleanup.push(() => admin[key].deleteMany({ where }));
|
|
681
|
+
}
|
|
682
|
+
expect(!result.ok, `insert of a row for ${whose} tenant succeeded`);
|
|
683
|
+
expect(result.error.kind === "forbidden", `insert failed with ${result.error.kind} (${result.error.message}), not an RLS error; check the row factory`);
|
|
684
|
+
}], [`${key}: ${who} can't update ${whose} rows`, async () => {
|
|
685
|
+
const before = await current();
|
|
686
|
+
const patch = spec.update;
|
|
687
|
+
expect(Object.entries(patch).some(([column, value]) => before?.[column] !== value), "update must change the row to detect a leak");
|
|
688
|
+
await user.updateMany({
|
|
689
|
+
where: target,
|
|
690
|
+
data: patch,
|
|
691
|
+
returning: false
|
|
692
|
+
});
|
|
693
|
+
const after = await current();
|
|
694
|
+
expect(Object.entries(patch).every(([column]) => after?.[column] === before?.[column]), `update changed ${whose} row`);
|
|
695
|
+
}]);
|
|
696
|
+
deletes.push([`${key}: ${who} can't delete ${whose} rows`, async () => {
|
|
697
|
+
await user.deleteMany({ where: target });
|
|
698
|
+
expect(await current() !== void 0, `delete removed ${whose} row`);
|
|
699
|
+
}]);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return await conform("Tenant isolation", [...reads, ...deletes]);
|
|
703
|
+
} finally {
|
|
704
|
+
for (const remove of cleanup.reverse()) await remove();
|
|
705
|
+
}
|
|
662
706
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
707
|
+
//#endregion
|
|
708
|
+
//#region src/testing/claims-fixtures.ts
|
|
709
|
+
const base = {
|
|
710
|
+
sub: "6f1c2c1e-5d0a-4d9e-9a51-6b1f0e7c2a10",
|
|
711
|
+
aud: "authenticated",
|
|
712
|
+
role: "authenticated",
|
|
713
|
+
iss: "https://project.supabase.co/auth/v1",
|
|
714
|
+
aal: "aal1",
|
|
715
|
+
session_id: "b7a8f9c0-1111-4222-8333-944455556666",
|
|
716
|
+
is_anonymous: false
|
|
717
|
+
};
|
|
718
|
+
const admin = "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d";
|
|
719
|
+
const supportSessionId = "4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b";
|
|
720
|
+
const client = "5f0e4d3c-2b1a-4098-8776-655443322110";
|
|
721
|
+
const chain = {
|
|
722
|
+
sub: "agent-runner",
|
|
723
|
+
act: { sub: "mcp-client-42" }
|
|
724
|
+
};
|
|
725
|
+
const agentAct = {
|
|
726
|
+
kind: "agent",
|
|
727
|
+
sub: "agent-7",
|
|
728
|
+
tools: ["search_knowledge"],
|
|
729
|
+
act: chain
|
|
730
|
+
};
|
|
731
|
+
const agentActReadOnly = {
|
|
732
|
+
kind: "agent",
|
|
733
|
+
sub: "agent-7",
|
|
734
|
+
tools: ["search_knowledge"],
|
|
735
|
+
read_only: true,
|
|
736
|
+
act: chain
|
|
737
|
+
};
|
|
738
|
+
/**
|
|
739
|
+
* `supportSession` and `supportSessionReadOnly` are what `supportClaims`
|
|
740
|
+
* mints (`act.kind: "support"`), `impersonation` what `actingAs` mints
|
|
741
|
+
* (`act.kind: "impersonation"`), `oauthClient` a Supabase OAuth server
|
|
742
|
+
* token (its identity scopes `openid` and `email` are left out of the
|
|
743
|
+
* delegation), `agentChain` an RFC 8693 chain without `kind`, and
|
|
744
|
+
* `agentRun` and `agentRunReadOnly` what `agentClaims` mints over that
|
|
745
|
+
* chain (`act.kind: "agent"`, with `readOnly: true` for the second).
|
|
746
|
+
*/
|
|
747
|
+
const supabaseClaimFixtures = {
|
|
748
|
+
supportSession: {
|
|
749
|
+
claims: {
|
|
750
|
+
...base,
|
|
751
|
+
act: {
|
|
752
|
+
kind: "support",
|
|
753
|
+
sub: admin,
|
|
754
|
+
reason: "ticket 42",
|
|
755
|
+
session_id: supportSessionId,
|
|
756
|
+
read_only: false
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
expect: {
|
|
760
|
+
actor: {
|
|
761
|
+
kind: "support",
|
|
762
|
+
id: admin,
|
|
763
|
+
sessionId: supportSessionId,
|
|
764
|
+
readOnly: false,
|
|
765
|
+
reason: "ticket 42"
|
|
766
|
+
},
|
|
767
|
+
impersonator: {
|
|
768
|
+
kind: "support",
|
|
769
|
+
id: admin,
|
|
770
|
+
sessionId: supportSessionId,
|
|
771
|
+
readOnly: false,
|
|
772
|
+
reason: "ticket 42"
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
supportSessionReadOnly: {
|
|
777
|
+
claims: {
|
|
778
|
+
...base,
|
|
779
|
+
act: {
|
|
780
|
+
kind: "support",
|
|
781
|
+
sub: admin,
|
|
782
|
+
reason: "ticket 42",
|
|
783
|
+
session_id: supportSessionId,
|
|
784
|
+
read_only: true
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
expect: {
|
|
788
|
+
actor: {
|
|
789
|
+
kind: "support",
|
|
790
|
+
id: admin,
|
|
791
|
+
sessionId: supportSessionId,
|
|
792
|
+
readOnly: true,
|
|
793
|
+
reason: "ticket 42"
|
|
794
|
+
},
|
|
795
|
+
impersonator: {
|
|
796
|
+
kind: "support",
|
|
797
|
+
id: admin,
|
|
798
|
+
sessionId: supportSessionId,
|
|
799
|
+
readOnly: true,
|
|
800
|
+
reason: "ticket 42"
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
impersonation: {
|
|
805
|
+
claims: {
|
|
806
|
+
...base,
|
|
807
|
+
act: {
|
|
808
|
+
kind: "impersonation",
|
|
809
|
+
sub: admin,
|
|
810
|
+
reason: "ticket 42"
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
expect: {
|
|
814
|
+
actor: {
|
|
815
|
+
kind: "impersonation",
|
|
816
|
+
id: admin,
|
|
817
|
+
reason: "ticket 42"
|
|
818
|
+
},
|
|
819
|
+
impersonator: {
|
|
820
|
+
kind: "impersonation",
|
|
821
|
+
id: admin,
|
|
822
|
+
reason: "ticket 42"
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
},
|
|
826
|
+
oauthClient: {
|
|
827
|
+
claims: {
|
|
828
|
+
...base,
|
|
829
|
+
client_id: client,
|
|
830
|
+
scope: "openid email posts:read"
|
|
831
|
+
},
|
|
832
|
+
expect: {
|
|
833
|
+
actor: {
|
|
834
|
+
kind: "oauth-client",
|
|
835
|
+
id: client
|
|
836
|
+
},
|
|
837
|
+
delegation: { scopes: ["posts:read"] }
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
agentChain: {
|
|
841
|
+
claims: {
|
|
842
|
+
...base,
|
|
843
|
+
scope: "posts:read",
|
|
844
|
+
act: chain
|
|
845
|
+
},
|
|
846
|
+
expect: {
|
|
847
|
+
actor: {
|
|
848
|
+
kind: "oauth-client",
|
|
849
|
+
id: "agent-runner",
|
|
850
|
+
chain
|
|
851
|
+
},
|
|
852
|
+
delegation: {
|
|
853
|
+
scopes: ["posts:read"],
|
|
854
|
+
chain
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
agentRun: {
|
|
859
|
+
claims: {
|
|
860
|
+
...base,
|
|
861
|
+
scope: "posts:read",
|
|
862
|
+
act: agentAct
|
|
863
|
+
},
|
|
864
|
+
expect: {
|
|
865
|
+
actor: {
|
|
866
|
+
kind: "agent",
|
|
867
|
+
id: "agent-7",
|
|
868
|
+
tools: ["search_knowledge"],
|
|
869
|
+
readOnly: false,
|
|
870
|
+
chain: agentAct
|
|
871
|
+
},
|
|
872
|
+
delegation: {
|
|
873
|
+
scopes: ["posts:read"],
|
|
874
|
+
chain: agentAct
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
agentRunReadOnly: {
|
|
879
|
+
claims: {
|
|
880
|
+
...base,
|
|
881
|
+
scope: "posts:read",
|
|
882
|
+
act: agentActReadOnly
|
|
883
|
+
},
|
|
884
|
+
expect: {
|
|
885
|
+
actor: {
|
|
886
|
+
kind: "agent",
|
|
887
|
+
id: "agent-7",
|
|
888
|
+
tools: ["search_knowledge"],
|
|
889
|
+
readOnly: true,
|
|
890
|
+
chain: agentActReadOnly
|
|
891
|
+
},
|
|
892
|
+
delegation: {
|
|
893
|
+
scopes: ["posts:read"],
|
|
894
|
+
chain: agentActReadOnly
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
const refused = {
|
|
900
|
+
kind: "invalid",
|
|
901
|
+
reason: "actor",
|
|
902
|
+
code: "ACTOR_INVALID"
|
|
903
|
+
};
|
|
904
|
+
/**
|
|
905
|
+
* `legacySupportSession` is a 0.5.0 support token: `session_id` without
|
|
906
|
+
* `kind`. better-supabase refuses it instead of reading it as a support
|
|
907
|
+
* session; mint a new one with `supportClaims`. The others are malformed
|
|
908
|
+
* levels: a support or agent `read_only` that is not a boolean, an agent
|
|
909
|
+
* with a `session_id`, an empty tool name or `tools` that is not a list, a
|
|
910
|
+
* `kind` better-supabase does not know, and a nested level without `sub`.
|
|
911
|
+
*/
|
|
912
|
+
const supabaseInvalidClaimFixtures = {
|
|
913
|
+
legacySupportSession: {
|
|
914
|
+
claims: {
|
|
915
|
+
...base,
|
|
916
|
+
act: {
|
|
917
|
+
sub: admin,
|
|
918
|
+
session_id: supportSessionId,
|
|
919
|
+
reason: "ticket 42"
|
|
920
|
+
}
|
|
921
|
+
},
|
|
922
|
+
expect: refused
|
|
923
|
+
},
|
|
924
|
+
supportReadOnlyNotBoolean: {
|
|
925
|
+
claims: {
|
|
926
|
+
...base,
|
|
927
|
+
act: {
|
|
928
|
+
kind: "support",
|
|
929
|
+
sub: admin,
|
|
930
|
+
session_id: supportSessionId,
|
|
931
|
+
read_only: "false"
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
expect: refused
|
|
935
|
+
},
|
|
936
|
+
agentWithSessionId: {
|
|
937
|
+
claims: {
|
|
938
|
+
...base,
|
|
939
|
+
act: {
|
|
940
|
+
kind: "agent",
|
|
941
|
+
sub: "agent-7",
|
|
942
|
+
session_id: supportSessionId
|
|
943
|
+
}
|
|
944
|
+
},
|
|
945
|
+
expect: refused
|
|
946
|
+
},
|
|
947
|
+
agentEmptyToolName: {
|
|
948
|
+
claims: {
|
|
949
|
+
...base,
|
|
950
|
+
act: {
|
|
951
|
+
kind: "agent",
|
|
952
|
+
sub: "agent-7",
|
|
953
|
+
tools: ["search_knowledge", ""]
|
|
954
|
+
}
|
|
955
|
+
},
|
|
956
|
+
expect: refused
|
|
957
|
+
},
|
|
958
|
+
agentToolsNotArray: {
|
|
959
|
+
claims: {
|
|
960
|
+
...base,
|
|
961
|
+
act: {
|
|
962
|
+
kind: "agent",
|
|
963
|
+
sub: "agent-7",
|
|
964
|
+
tools: "search_knowledge"
|
|
965
|
+
}
|
|
966
|
+
},
|
|
967
|
+
expect: refused
|
|
968
|
+
},
|
|
969
|
+
agentReadOnlyNotBoolean: {
|
|
970
|
+
claims: {
|
|
971
|
+
...base,
|
|
972
|
+
act: {
|
|
973
|
+
kind: "agent",
|
|
974
|
+
sub: "agent-7",
|
|
975
|
+
read_only: "true"
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
expect: refused
|
|
979
|
+
},
|
|
980
|
+
unknownKind: {
|
|
981
|
+
claims: {
|
|
982
|
+
...base,
|
|
983
|
+
act: {
|
|
984
|
+
kind: "delegate",
|
|
985
|
+
sub: "agent-7"
|
|
986
|
+
}
|
|
987
|
+
},
|
|
988
|
+
expect: refused
|
|
989
|
+
},
|
|
990
|
+
levelWithoutSub: {
|
|
991
|
+
claims: {
|
|
992
|
+
...base,
|
|
993
|
+
act: {
|
|
994
|
+
kind: "agent",
|
|
995
|
+
sub: "agent-7",
|
|
996
|
+
act: { client_id: client }
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
expect: refused
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
//#endregion
|
|
1003
|
+
//#region src/testing/seed.ts
|
|
1004
|
+
const SEED = Symbol.for("better-supabase.seed");
|
|
1005
|
+
function isSeed(value) {
|
|
1006
|
+
return typeof value === "object" && value !== null && SEED in value;
|
|
686
1007
|
}
|
|
687
|
-
|
|
688
|
-
|
|
1008
|
+
const quote = (text) => `'${text.replaceAll("'", "''")}'`;
|
|
1009
|
+
function arrayLiteral(values) {
|
|
1010
|
+
return `{${values.map((value) => {
|
|
1011
|
+
if (value === null) return "NULL";
|
|
1012
|
+
if (Array.isArray(value)) return arrayLiteral(value);
|
|
1013
|
+
return `"${(temporalText(value) ?? String(value)).replaceAll(/[\\"]/g, "\\$&")}"`;
|
|
1014
|
+
}).join(",")}}`;
|
|
1015
|
+
}
|
|
1016
|
+
function literal(value, column, where) {
|
|
1017
|
+
if (value === null) return "null";
|
|
1018
|
+
if (column.json) return quote(JSON.stringify(value));
|
|
1019
|
+
if (Array.isArray(value)) return quote(arrayLiteral(value));
|
|
1020
|
+
const temporalValue = temporalText(value);
|
|
1021
|
+
if (temporalValue !== void 0) return quote(temporalValue);
|
|
1022
|
+
const kind = typeof value;
|
|
1023
|
+
switch (kind) {
|
|
1024
|
+
case "string": return quote(value);
|
|
1025
|
+
case "boolean":
|
|
1026
|
+
case "bigint": return String(value);
|
|
1027
|
+
case "number":
|
|
1028
|
+
if (!Number.isFinite(value)) throw new TypeError(`${where}: ${String(value)} is not a finite number`);
|
|
1029
|
+
return String(value);
|
|
1030
|
+
case "object":
|
|
1031
|
+
case "function":
|
|
1032
|
+
case "symbol":
|
|
1033
|
+
case "undefined": throw new TypeError(`${where}: unsupported value of type ${kind}`);
|
|
1034
|
+
default: throw new TypeError(`${where}: unsupported value of type ${String(kind)}`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const ident = (name) => `"${name.replaceAll("\"", "\"\"")}"`;
|
|
1038
|
+
/** Parents first (by forward relations), otherwise in the order given. */
|
|
1039
|
+
function order(meta, keys) {
|
|
1040
|
+
const sorted = [];
|
|
1041
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1042
|
+
const visit = (key) => {
|
|
1043
|
+
if (sorted.includes(key) || visiting.has(key)) return;
|
|
1044
|
+
visiting.add(key);
|
|
1045
|
+
for (const relation of Object.values(meta.tables[key]?.relations ?? {})) if (relation.direction === "forward" && relation.table !== key && keys.includes(relation.table)) visit(relation.table);
|
|
1046
|
+
visiting.delete(key);
|
|
1047
|
+
sorted.push(key);
|
|
1048
|
+
};
|
|
1049
|
+
for (const key of keys) visit(key);
|
|
1050
|
+
return sorted;
|
|
1051
|
+
}
|
|
1052
|
+
function statement(table, rows) {
|
|
1053
|
+
const entries = Object.entries(rows);
|
|
1054
|
+
if (entries.length === 0) return;
|
|
1055
|
+
const columns = [];
|
|
1056
|
+
for (const [, row] of entries) for (const [key, value] of Object.entries(row)) {
|
|
1057
|
+
if (value === void 0 || columns.includes(key)) continue;
|
|
1058
|
+
const column = table.columns[key];
|
|
1059
|
+
if (!column) throw new TypeError(`seed ${table.key}: unknown column "${key}"`);
|
|
1060
|
+
if (column.generated) throw new TypeError(`seed ${table.key}: "${key}" is a generated column`);
|
|
1061
|
+
columns.push(key);
|
|
1062
|
+
}
|
|
1063
|
+
const values = entries.map(([name, row]) => {
|
|
1064
|
+
return ` (${columns.map((key) => row[key] === void 0 ? "default" : literal(row[key], table.columns[key], `seed ${table.key}.${name}.${key}`)).join(", ")})`;
|
|
1065
|
+
});
|
|
1066
|
+
return `insert into ${`${ident(table.schema)}.${ident(table.name)}`} (${columns.map((key) => ident(table.columns[key].db)).join(", ")}) values\n${values.join(",\n")}\non conflict do nothing;`;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Typed fixtures shared by `supabase/seeds` and your tests. Rows use app
|
|
1070
|
+
* casing and Insert types; `better-supabase seed` renders them to SQL.
|
|
1071
|
+
*
|
|
1072
|
+
* ```ts
|
|
1073
|
+
* export const seed = defineSeed(betterSupabase, {
|
|
1074
|
+
* organizations: { acme: { id: ACME, name: 'Acme' } },
|
|
1075
|
+
* customers: { first: { organizationId: ACME, name: 'First customer' } },
|
|
1076
|
+
* });
|
|
1077
|
+
* ```
|
|
1078
|
+
*/
|
|
1079
|
+
function defineSeed(betterSupabase, fixtures) {
|
|
1080
|
+
const meta = betterSupabase.meta;
|
|
1081
|
+
const statements = () => {
|
|
1082
|
+
const keys = Object.keys(fixtures).filter((key) => fixtures[key] !== void 0);
|
|
1083
|
+
for (const key of keys) {
|
|
1084
|
+
const table = meta.tables[key];
|
|
1085
|
+
if (!table) throw new TypeError(`seed: unknown table "${key}"`);
|
|
1086
|
+
if (table.kind === "view") throw new TypeError(`seed: "${key}" is a view`);
|
|
1087
|
+
}
|
|
1088
|
+
return order(meta, keys).flatMap((key) => {
|
|
1089
|
+
const sql = statement(meta.tables[key], fixtures[key]);
|
|
1090
|
+
return sql ? [sql] : [];
|
|
1091
|
+
});
|
|
1092
|
+
};
|
|
689
1093
|
return {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
lastError: row.message?.last_error ?? null,
|
|
698
|
-
context: {}
|
|
1094
|
+
[SEED]: true,
|
|
1095
|
+
rows: fixtures,
|
|
1096
|
+
statements,
|
|
1097
|
+
sql: () => statements().join("\n\n"),
|
|
1098
|
+
async insert(sql) {
|
|
1099
|
+
for (const text of statements()) await sql.queryRaw(text);
|
|
1100
|
+
}
|
|
699
1101
|
};
|
|
700
1102
|
}
|
|
1103
|
+
//#endregion
|
|
1104
|
+
//#region src/testing/kits/executor.ts
|
|
1105
|
+
const recorder = (ops) => ({
|
|
1106
|
+
name: "recorder",
|
|
1107
|
+
execute: (op) => {
|
|
1108
|
+
ops.push(op);
|
|
1109
|
+
return Promise.resolve(ok({
|
|
1110
|
+
rows: [],
|
|
1111
|
+
count: 0
|
|
1112
|
+
}));
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
/** The operation `run` sends to the executor, built by `betterSupabase` without touching a database. */
|
|
1116
|
+
async function captureOp(betterSupabase, run) {
|
|
1117
|
+
const ops = [];
|
|
1118
|
+
await run(betterSupabase.connect(recorder(ops)));
|
|
1119
|
+
const [op] = ops;
|
|
1120
|
+
if (!op) throw new Error("the repository call did not reach the executor");
|
|
1121
|
+
return op;
|
|
1122
|
+
}
|
|
1123
|
+
function tableKey(betterSupabase, table) {
|
|
1124
|
+
const key = table ?? Object.keys(betterSupabase.meta.tables)[0];
|
|
1125
|
+
if (!key || !(key in betterSupabase.meta.tables)) throw new TypeError(`better-supabase: unknown table "${String(key)}"`);
|
|
1126
|
+
return key;
|
|
1127
|
+
}
|
|
1128
|
+
const hasName = (subject) => ["has a name", () => {
|
|
1129
|
+
expect(typeof subject.name === "string" && subject.name.length > 0, "name must be a non-empty string");
|
|
1130
|
+
}];
|
|
701
1131
|
/**
|
|
702
|
-
* Proves
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
1132
|
+
* Proves an `Executor` meets the contract: rows keyed by the selection's
|
|
1133
|
+
* aliases, counts, `aborted` errors, failures returned as `Result`s rather
|
|
1134
|
+
* than thrown, and (with `create`) a write round trip and a `deleteMany`
|
|
1135
|
+
* that `maxAffected` refuses without deleting anything.
|
|
1136
|
+
*
|
|
1137
|
+
* ```ts
|
|
1138
|
+
* it('conforms', () => testExecutor(myExecutor, { betterSupabase, table: 'tags', create: { name: 'x' } }));
|
|
1139
|
+
* ```
|
|
706
1140
|
*/
|
|
707
|
-
function
|
|
708
|
-
const {
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
return conform(`
|
|
714
|
-
hasName(
|
|
715
|
-
["
|
|
716
|
-
const
|
|
717
|
-
|
|
1141
|
+
function testExecutor(executor, options) {
|
|
1142
|
+
const { betterSupabase } = options;
|
|
1143
|
+
const table = tableKey(betterSupabase, options.table);
|
|
1144
|
+
const context = { errorMappers: [] };
|
|
1145
|
+
const read = () => captureOp(betterSupabase, (db) => db[table]["findMany"]({ limit: 2 }));
|
|
1146
|
+
const create = options.create;
|
|
1147
|
+
return conform(`Executor "${executor.name}"`, [
|
|
1148
|
+
hasName(executor),
|
|
1149
|
+
["reads rows keyed by the selection aliases", async () => {
|
|
1150
|
+
const op = await read();
|
|
1151
|
+
const result = await executor.execute(op, context);
|
|
1152
|
+
expect(result.ok, `select failed: ${result.ok ? "" : result.error.message}`);
|
|
1153
|
+
expect(isList(result.data.rows), "rows must be an array");
|
|
1154
|
+
expect(result.data.rows.length <= 2, `limit 2 returned ${result.data.rows.length} rows`);
|
|
1155
|
+
expect(result.data.count === null || typeof result.data.count === "number", "count must be a number or null");
|
|
1156
|
+
if (op.kind !== "select") return;
|
|
1157
|
+
const aliases = new Set(op.selection.columns.map((column) => column.alias));
|
|
1158
|
+
for (const row of result.data.rows) {
|
|
1159
|
+
const extra = Object.keys(row).filter((key) => !aliases.has(key));
|
|
1160
|
+
expect(extra.length === 0, `rows must use selection aliases; got ${extra.join(", ")}`);
|
|
1161
|
+
}
|
|
718
1162
|
}],
|
|
719
|
-
["
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
expect(
|
|
724
|
-
expect(same(job.payload, { n: 1 }), "the payload must round-trip");
|
|
725
|
-
expect(job.attempts === 1, "the first claim must be attempt 1");
|
|
726
|
-
expect(job.maxAttempts === 3, "max_attempts must round-trip");
|
|
727
|
-
expect(await claimOne() === void 0, "a leased message must stay hidden from other reads");
|
|
728
|
-
expect(await backend.complete(job), "complete must return true");
|
|
1163
|
+
["counts rows", async () => {
|
|
1164
|
+
const op = await captureOp(betterSupabase, (db) => db[table]["count"]());
|
|
1165
|
+
const result = await executor.execute(op, context);
|
|
1166
|
+
expect(result.ok, `count failed: ${result.ok ? "" : result.error.message}`);
|
|
1167
|
+
expect(typeof result.data.count === "number" && result.data.count >= 0, "count must be a number");
|
|
729
1168
|
}],
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
expect(
|
|
739
|
-
expect(await backend.complete(job), "complete must return true");
|
|
1169
|
+
["returns an aborted error for an aborted signal", async () => {
|
|
1170
|
+
const controller = new AbortController();
|
|
1171
|
+
controller.abort();
|
|
1172
|
+
const result = await executor.execute(await read(), {
|
|
1173
|
+
...context,
|
|
1174
|
+
signal: controller.signal
|
|
1175
|
+
});
|
|
1176
|
+
expect(!result.ok, "an aborted request must fail");
|
|
1177
|
+
expect(result.error.kind === "aborted", `expected kind "aborted", got "${result.error.kind}"`);
|
|
740
1178
|
}],
|
|
741
|
-
["
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1179
|
+
["returns failures as results instead of throwing", async () => {
|
|
1180
|
+
const op = await read();
|
|
1181
|
+
const missing = {
|
|
1182
|
+
...op,
|
|
1183
|
+
table: {
|
|
1184
|
+
...op.table,
|
|
1185
|
+
name: "__better_supabase_missing__"
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
const result = await executor.execute(missing, context);
|
|
1189
|
+
expect(!result.ok, "a missing table must fail");
|
|
1190
|
+
expect(typeof result.error.kind === "string" && typeof result.error.message === "string", "errors must be DbErrors");
|
|
750
1191
|
}],
|
|
751
|
-
|
|
752
|
-
expect((
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
if (backend.retryDead) {
|
|
757
|
-
expect(await backend.retryDead(queue, [Number(dead.id)], 10) === 1, "retryDead must enqueue the dead letter again");
|
|
758
|
-
const job = await claimOne();
|
|
759
|
-
expect(job, "the retried job must be claimable");
|
|
760
|
-
expect(same(job.payload, { n: 3 }), "the retried job must keep its payload");
|
|
761
|
-
await backend.complete(job);
|
|
762
|
-
}
|
|
1192
|
+
executor.rpc && ["returns rpc failures as results", async () => {
|
|
1193
|
+
expect(!(await executor.rpc("__better_supabase_missing__", {}, {
|
|
1194
|
+
...context,
|
|
1195
|
+
schema: "public"
|
|
1196
|
+
})).ok, "calling a missing function must fail");
|
|
763
1197
|
}],
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
expect(
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
* previous session on start, ends a session once, and lists sessions.
|
|
776
|
-
* It writes sessions to the store; they are all ended when it returns.
|
|
777
|
-
*/
|
|
778
|
-
function testSupportSessionStore(store, options) {
|
|
779
|
-
const { admin } = options;
|
|
780
|
-
const [first, second] = options.targets;
|
|
781
|
-
const start = (targetUserId) => store.start({
|
|
782
|
-
adminId: admin.id,
|
|
783
|
-
adminClaims: admin.claims,
|
|
784
|
-
targetUserId,
|
|
785
|
-
reason: "conformance",
|
|
786
|
-
ttlSeconds: 600,
|
|
787
|
-
readOnly: true,
|
|
788
|
-
metadata: { ticket: "T-1" }
|
|
789
|
-
});
|
|
790
|
-
return conform(`SupportSessionStore "${store.name}"`, [
|
|
791
|
-
hasName(store),
|
|
792
|
-
["has apiVersion 1", () => {
|
|
793
|
-
const version = store.apiVersion;
|
|
794
|
-
expect(version === 1, "apiVersion must be 1");
|
|
1198
|
+
executor.functionSources && ["reads from SelectOp.source instead of the table", async () => {
|
|
1199
|
+
const op = await read();
|
|
1200
|
+
if (op.kind !== "select") return;
|
|
1201
|
+
expect(!(await executor.execute({
|
|
1202
|
+
...op,
|
|
1203
|
+
source: {
|
|
1204
|
+
schema: op.table.schema,
|
|
1205
|
+
name: "__better_supabase_missing__",
|
|
1206
|
+
args: {}
|
|
1207
|
+
}
|
|
1208
|
+
}, context)).ok, "a missing source function must fail; reading the table ignores source");
|
|
795
1209
|
}],
|
|
796
|
-
["
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
}
|
|
1210
|
+
executor.batch && ["batch returns one result per operation, in order", async () => {
|
|
1211
|
+
const rows = await read();
|
|
1212
|
+
const count = await captureOp(betterSupabase, (db) => db[table]["count"]());
|
|
1213
|
+
const missing = {
|
|
1214
|
+
...rows,
|
|
1215
|
+
table: {
|
|
1216
|
+
...rows.table,
|
|
1217
|
+
name: "__better_supabase_missing__"
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
const results = await executor.batch([
|
|
1221
|
+
rows,
|
|
1222
|
+
count,
|
|
1223
|
+
missing
|
|
1224
|
+
], context);
|
|
1225
|
+
expect(results.length === 3, `expected 3 results, got ${results.length}`);
|
|
1226
|
+
const [first, second, third] = results;
|
|
1227
|
+
expect(first?.ok === true && Array.isArray(first.data.rows), "the first operation must return rows");
|
|
1228
|
+
expect(second?.ok === true && typeof second.data.count === "number", "the second operation must return a count");
|
|
1229
|
+
expect(third?.ok === false, "a failing operation must fail on its own, as a result");
|
|
812
1230
|
}],
|
|
813
|
-
["
|
|
814
|
-
const
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1231
|
+
create && ["round-trips a write", async () => {
|
|
1232
|
+
const repository = betterSupabase.connect(executor)[table];
|
|
1233
|
+
const created = await repository["create"](create);
|
|
1234
|
+
expect(created.ok, `create failed: ${JSON.stringify(created.error)}`);
|
|
1235
|
+
const row = created.data;
|
|
1236
|
+
for (const [key, value] of Object.entries(create)) expect(same(row[key], value), `created row has ${key} = ${JSON.stringify(row[key])}`);
|
|
1237
|
+
const [primary] = betterSupabase.meta.tables[table].primaryKey;
|
|
1238
|
+
expect(primary, "write checks need a table with a primary key");
|
|
1239
|
+
const removed = await repository["delete"](row[primary]);
|
|
1240
|
+
expect(removed.ok, `delete failed: ${JSON.stringify(removed.error)}`);
|
|
1241
|
+
const again = await repository["findFirst"]({ where: { [primary]: row[primary] } });
|
|
1242
|
+
expect(again.ok && again.data === null, "the deleted row is still readable");
|
|
822
1243
|
}],
|
|
823
|
-
["
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
expect(
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1244
|
+
create && ["refuses a write past maxAffected and writes nothing", async () => {
|
|
1245
|
+
const repository = betterSupabase.connect(executor)[table];
|
|
1246
|
+
const [primary] = betterSupabase.meta.tables[table].primaryKey;
|
|
1247
|
+
expect(primary, "write checks need a table with a primary key");
|
|
1248
|
+
const created = await repository["create"](create);
|
|
1249
|
+
expect(created.ok, `create failed: ${JSON.stringify(created.error)}`);
|
|
1250
|
+
const id = created.data[primary];
|
|
1251
|
+
try {
|
|
1252
|
+
const refused = await repository["deleteMany"]({
|
|
1253
|
+
where: { [primary]: id },
|
|
1254
|
+
maxAffected: 0
|
|
1255
|
+
});
|
|
1256
|
+
expect(!refused.ok, "deleteMany past maxAffected must fail");
|
|
1257
|
+
const kind = refused.error?.kind;
|
|
1258
|
+
expect(kind === "max_affected", `expected kind "max_affected", got "${String(kind)}"`);
|
|
1259
|
+
const kept = await repository["findFirst"]({ where: { [primary]: id } });
|
|
1260
|
+
expect(kept.ok && kept.data !== null, "a refused deleteMany must not delete the row");
|
|
1261
|
+
const allowed = await repository["deleteMany"]({
|
|
1262
|
+
where: { [primary]: id },
|
|
1263
|
+
maxAffected: 1
|
|
1264
|
+
});
|
|
1265
|
+
expect(allowed.ok, `deleteMany within maxAffected failed: ${JSON.stringify(allowed.error)}`);
|
|
1266
|
+
} finally {
|
|
1267
|
+
await repository["delete"](id);
|
|
1268
|
+
}
|
|
838
1269
|
}]
|
|
839
1270
|
]);
|
|
840
1271
|
}
|
|
1272
|
+
//#endregion
|
|
1273
|
+
//#region src/testing/kits/auth-resolver.ts
|
|
841
1274
|
async function resolveSafely(resolver, request) {
|
|
842
1275
|
try {
|
|
843
1276
|
return await resolver.resolve(request);
|
|
@@ -873,6 +1306,393 @@ function testAuthResolver(resolver, options) {
|
|
|
873
1306
|
}]
|
|
874
1307
|
]);
|
|
875
1308
|
}
|
|
1309
|
+
//#endregion
|
|
1310
|
+
//#region src/testing/kits/cache-adapter.ts
|
|
1311
|
+
/**
|
|
1312
|
+
* Proves a `CacheAdapter` accepts table and row targets, repeated and
|
|
1313
|
+
* concurrent calls, and never mutates its input.
|
|
1314
|
+
*/
|
|
1315
|
+
function testCacheAdapter(adapter, options = {}) {
|
|
1316
|
+
const table = options.table ?? "customers";
|
|
1317
|
+
const invalidate = (target) => Promise.resolve(adapter.invalidate(deepFreeze(target)));
|
|
1318
|
+
return conform(`CacheAdapter "${adapter.name}"`, [
|
|
1319
|
+
hasName(adapter),
|
|
1320
|
+
["invalidates a table", () => invalidate({
|
|
1321
|
+
table,
|
|
1322
|
+
tables: [table],
|
|
1323
|
+
ids: []
|
|
1324
|
+
})],
|
|
1325
|
+
["invalidates rows of a tenant", () => invalidate({
|
|
1326
|
+
table,
|
|
1327
|
+
tables: [table],
|
|
1328
|
+
ids: ["1", "a,b"],
|
|
1329
|
+
tenant: "organization_1"
|
|
1330
|
+
})],
|
|
1331
|
+
["accepts repeated and concurrent calls", async () => {
|
|
1332
|
+
await invalidate({
|
|
1333
|
+
table,
|
|
1334
|
+
tables: [table],
|
|
1335
|
+
ids: ["1"]
|
|
1336
|
+
});
|
|
1337
|
+
await Promise.all(Array.from({ length: 10 }, (_, index) => invalidate({
|
|
1338
|
+
table,
|
|
1339
|
+
tables: [table],
|
|
1340
|
+
ids: [String(index)]
|
|
1341
|
+
})));
|
|
1342
|
+
}],
|
|
1343
|
+
["accepts tables it has never seen", () => invalidate({
|
|
1344
|
+
table: "__better_supabase_unknown__",
|
|
1345
|
+
tables: ["__better_supabase_unknown__"],
|
|
1346
|
+
ids: []
|
|
1347
|
+
})],
|
|
1348
|
+
["invalidates tables reached through foreign keys", () => invalidate({
|
|
1349
|
+
table,
|
|
1350
|
+
tables: [table, `${table}_children`],
|
|
1351
|
+
ids: ["1"]
|
|
1352
|
+
})]
|
|
1353
|
+
]);
|
|
1354
|
+
}
|
|
1355
|
+
//#endregion
|
|
1356
|
+
//#region src/testing/kits/event-sink.ts
|
|
1357
|
+
/** Proves an `EventSink` accepts empty and full batches without mutating them, and delivers them when readable. */
|
|
1358
|
+
function testEventSink(sink, options = {}) {
|
|
1359
|
+
const events = toCloudEvents({
|
|
1360
|
+
table: "customers",
|
|
1361
|
+
kind: "insert",
|
|
1362
|
+
rows: [{
|
|
1363
|
+
id: "1",
|
|
1364
|
+
name: "Acme"
|
|
1365
|
+
}, {
|
|
1366
|
+
id: "2",
|
|
1367
|
+
name: "Globex"
|
|
1368
|
+
}],
|
|
1369
|
+
context: { tenant: "organization_1" }
|
|
1370
|
+
}, { source: "/better-supabase/conformance" });
|
|
1371
|
+
return conform("EventSink", [
|
|
1372
|
+
["accepts an empty batch", () => sink.send(deepFreeze([]))],
|
|
1373
|
+
["accepts a batch without mutating it", () => sink.send(frozenCopy(events))],
|
|
1374
|
+
options.received && ["delivers every event", async () => {
|
|
1375
|
+
const ids = new Set((await options.received()).map((event) => event.id));
|
|
1376
|
+
const missing = events.filter((event) => !ids.has(event.id));
|
|
1377
|
+
expect(missing.length === 0, `${missing.length} of ${events.length} events were not delivered`);
|
|
1378
|
+
}]
|
|
1379
|
+
]);
|
|
1380
|
+
}
|
|
1381
|
+
//#endregion
|
|
1382
|
+
//#region src/testing/kits/payment-provider.ts
|
|
1383
|
+
const kindOf$2 = (result) => result.ok ? "ok" : result.error.kind;
|
|
1384
|
+
const half = (value) => ({
|
|
1385
|
+
amount: value.amount / 2n,
|
|
1386
|
+
currency: value.currency
|
|
1387
|
+
});
|
|
1388
|
+
/**
|
|
1389
|
+
* Runs the `PaymentProvider` contract against `provider`: the API version,
|
|
1390
|
+
* refused amounts, a created payment and its fields, an idempotency key
|
|
1391
|
+
* that pays once, `not_found` for an unknown payment, and, with the
|
|
1392
|
+
* options that drive them, capture and refunds of an authorized payment
|
|
1393
|
+
* and webhook verification. Every call carries a fresh reference, so the
|
|
1394
|
+
* kit runs against a test account without cleanup.
|
|
1395
|
+
*/
|
|
1396
|
+
function testPaymentProvider(provider, options = {}) {
|
|
1397
|
+
const amount = options.amount ?? money(1000n, "EUR");
|
|
1398
|
+
const reference = () => `conformance-${crypto.randomUUID()}`;
|
|
1399
|
+
const { authorize, webhook } = options;
|
|
1400
|
+
const authorized = async () => {
|
|
1401
|
+
const created = await provider.createPayment({
|
|
1402
|
+
amount,
|
|
1403
|
+
reference: reference(),
|
|
1404
|
+
capture: "manual"
|
|
1405
|
+
}).orThrow();
|
|
1406
|
+
await authorize?.(created);
|
|
1407
|
+
return created;
|
|
1408
|
+
};
|
|
1409
|
+
return conform(`PaymentProvider "${provider.name}"`, [
|
|
1410
|
+
["has apiVersion 1 and a name", () => {
|
|
1411
|
+
const version = provider.apiVersion;
|
|
1412
|
+
expect(version === 1, `apiVersion is ${String(version)}`);
|
|
1413
|
+
expect(provider.name.length > 0, "name is empty");
|
|
1414
|
+
}],
|
|
1415
|
+
["refuses a zero amount and a number instead of Money", async () => {
|
|
1416
|
+
const zero = await provider.createPayment({
|
|
1417
|
+
amount: {
|
|
1418
|
+
amount: 0n,
|
|
1419
|
+
currency: amount.currency
|
|
1420
|
+
},
|
|
1421
|
+
reference: reference()
|
|
1422
|
+
});
|
|
1423
|
+
expect(kindOf$2(zero) === "invalid_input", `zero gave ${kindOf$2(zero)}`);
|
|
1424
|
+
const plain = await provider.createPayment({
|
|
1425
|
+
amount: 1e3,
|
|
1426
|
+
reference: reference()
|
|
1427
|
+
});
|
|
1428
|
+
expect(kindOf$2(plain) === "invalid_input", `a number gave ${kindOf$2(plain)}`);
|
|
1429
|
+
}],
|
|
1430
|
+
["creates a payment with its amount, reference and provider", async () => {
|
|
1431
|
+
const ref = reference();
|
|
1432
|
+
const payment = await provider.createPayment({
|
|
1433
|
+
amount,
|
|
1434
|
+
reference: ref
|
|
1435
|
+
}).orThrow();
|
|
1436
|
+
expect(payment.id.length > 0, "the payment has no id");
|
|
1437
|
+
expect(payment.provider === provider.name, `provider is ${payment.provider}`);
|
|
1438
|
+
expect(payment.reference === ref, `reference is ${payment.reference}`);
|
|
1439
|
+
expect(payment.amount.amount === amount.amount && payment.amount.currency === amount.currency, `amount is ${String(payment.amount.amount)} ${payment.amount.currency}`);
|
|
1440
|
+
expect(payment.refunded.amount === 0n, "a new payment has refunds");
|
|
1441
|
+
expect(typeof payment.amount.amount === "bigint", "the amount is not a bigint");
|
|
1442
|
+
}],
|
|
1443
|
+
["pays once for one idempotency key", async () => {
|
|
1444
|
+
const key = crypto.randomUUID();
|
|
1445
|
+
const input = {
|
|
1446
|
+
amount,
|
|
1447
|
+
reference: reference()
|
|
1448
|
+
};
|
|
1449
|
+
const first = await provider.createPayment(input, { idempotencyKey: key }).orThrow();
|
|
1450
|
+
const again = await provider.createPayment(input, { idempotencyKey: key }).orThrow();
|
|
1451
|
+
expect(first.id === again.id, "the same key created a second payment");
|
|
1452
|
+
}],
|
|
1453
|
+
["returns not_found for an unknown payment", async () => {
|
|
1454
|
+
const id = `conformance_missing_${crypto.randomUUID()}`;
|
|
1455
|
+
const captured = await provider.capture(id);
|
|
1456
|
+
expect(kindOf$2(captured) === "not_found", `capture gave ${kindOf$2(captured)}`);
|
|
1457
|
+
const refunded = await provider.refund(id);
|
|
1458
|
+
expect(kindOf$2(refunded) === "not_found", `refund gave ${kindOf$2(refunded)}`);
|
|
1459
|
+
}],
|
|
1460
|
+
provider.get !== void 0 && ["reads a payment back", async () => {
|
|
1461
|
+
const created = await provider.createPayment({
|
|
1462
|
+
amount,
|
|
1463
|
+
reference: reference()
|
|
1464
|
+
}).orThrow();
|
|
1465
|
+
const read = await provider.get?.(created.id).orThrow();
|
|
1466
|
+
expect(read?.id === created.id, "get returned another payment");
|
|
1467
|
+
expect(read.reference === created.reference, "get lost the reference");
|
|
1468
|
+
}],
|
|
1469
|
+
authorize !== void 0 && ["captures an authorized payment", async () => {
|
|
1470
|
+
const payment = await authorized();
|
|
1471
|
+
const captured = await provider.capture(payment.id).orThrow();
|
|
1472
|
+
expect(captured.status === "succeeded", `status is ${captured.status}`);
|
|
1473
|
+
expect(captured.captured.amount === amount.amount, `captured ${String(captured.captured.amount)}`);
|
|
1474
|
+
}],
|
|
1475
|
+
authorize !== void 0 && ["refunds part of a payment and refuses more than is left", async () => {
|
|
1476
|
+
const payment = await authorized();
|
|
1477
|
+
await provider.capture(payment.id).orThrow();
|
|
1478
|
+
const part = half(amount);
|
|
1479
|
+
const refund = await provider.refund(payment.id, { amount: part }).orThrow();
|
|
1480
|
+
expect(refund.paymentId === payment.id, "the refund names another payment");
|
|
1481
|
+
expect(refund.amount.amount === part.amount, `refunded ${String(refund.amount.amount)}`);
|
|
1482
|
+
expect(refund.status === "succeeded" || refund.status === "pending", `refund status is ${refund.status}`);
|
|
1483
|
+
const tooMuch = await provider.refund(payment.id, { amount });
|
|
1484
|
+
expect(kindOf$2(tooMuch) === "invalid_input", `refunding more than is left gave ${kindOf$2(tooMuch)}`);
|
|
1485
|
+
}],
|
|
1486
|
+
provider.cancel !== void 0 && ["cancels a payment that was not captured", async () => {
|
|
1487
|
+
const created = await provider.createPayment({
|
|
1488
|
+
amount,
|
|
1489
|
+
reference: reference(),
|
|
1490
|
+
capture: "manual"
|
|
1491
|
+
}).orThrow();
|
|
1492
|
+
const canceled = await provider.cancel?.(created.id).orThrow();
|
|
1493
|
+
expect(canceled?.status === "canceled", `status is ${String(canceled?.status)}`);
|
|
1494
|
+
}],
|
|
1495
|
+
webhook !== void 0 && ["parses a signed webhook and refuses a tampered one", async () => {
|
|
1496
|
+
const payment = await provider.createPayment({
|
|
1497
|
+
amount,
|
|
1498
|
+
reference: reference()
|
|
1499
|
+
}).orThrow();
|
|
1500
|
+
const { valid, tampered } = await webhook(payment);
|
|
1501
|
+
const events = await provider.parseWebhook(valid).orThrow();
|
|
1502
|
+
expect(events.length > 0, "a signed delivery gave no events");
|
|
1503
|
+
for (const event of events) {
|
|
1504
|
+
expect(event.id.length > 0, "an event has no id");
|
|
1505
|
+
expect(event.paymentId === payment.id, `an event names payment ${event.paymentId}`);
|
|
1506
|
+
}
|
|
1507
|
+
const refused = await provider.parseWebhook(tampered);
|
|
1508
|
+
expect(kindOf$2(refused) === "unauthorized", `a tampered delivery gave ${kindOf$2(refused)}`);
|
|
1509
|
+
}]
|
|
1510
|
+
]);
|
|
1511
|
+
}
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region src/testing/kits/tax-provider.ts
|
|
1514
|
+
const kindOf$1 = (result) => result.ok ? "ok" : result.error.kind;
|
|
1515
|
+
const sum = (values) => values.reduce((total, value) => total + value.amount, 0n);
|
|
1516
|
+
/**
|
|
1517
|
+
* Runs the `TaxProvider` contract against `provider`: the API version,
|
|
1518
|
+
* refused carts, a quote with one line per cart line in order and a total
|
|
1519
|
+
* that adds up, inclusive prices, shipping, exempt customers, commit, and
|
|
1520
|
+
* full and partial reversals with their limits. Every commit carries a
|
|
1521
|
+
* fresh reference, so the kit runs against a test account without cleanup.
|
|
1522
|
+
*/
|
|
1523
|
+
function testTaxProvider(provider, options) {
|
|
1524
|
+
const currency = options.currency ?? "EUR";
|
|
1525
|
+
const { address, taxCategory } = options;
|
|
1526
|
+
const expectTax = options.expectTax ?? true;
|
|
1527
|
+
const line = (id, minor, inclusive = false) => ({
|
|
1528
|
+
id,
|
|
1529
|
+
amount: money(minor, currency),
|
|
1530
|
+
inclusive,
|
|
1531
|
+
...taxCategory === void 0 ? {} : { taxCategory }
|
|
1532
|
+
});
|
|
1533
|
+
const cart = (overrides = {}) => ({
|
|
1534
|
+
currency,
|
|
1535
|
+
address,
|
|
1536
|
+
lines: [line("a", 10000n), line("b", 2500n)],
|
|
1537
|
+
...overrides
|
|
1538
|
+
});
|
|
1539
|
+
const reference = () => `conformance-${crypto.randomUUID()}`;
|
|
1540
|
+
const committed = async () => {
|
|
1541
|
+
const quote = await provider.quote(cart()).orThrow();
|
|
1542
|
+
return provider.commit({
|
|
1543
|
+
quote,
|
|
1544
|
+
reference: reference()
|
|
1545
|
+
}).orThrow();
|
|
1546
|
+
};
|
|
1547
|
+
const addsUp = (quote) => {
|
|
1548
|
+
expect(quote.totalTax.amount === sum(quote.lines.map((each) => each.tax)), "totalTax is not the sum of the lines' tax");
|
|
1549
|
+
for (const each of quote.lines) {
|
|
1550
|
+
expect(isRate(each.rate), `line ${each.lineId} has rate "${each.rate}"`);
|
|
1551
|
+
expect(each.tax.currency === currency && each.taxable.currency === currency, `line ${each.lineId} is not in ${currency}`);
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
return conform(`TaxProvider "${provider.name}"`, [
|
|
1555
|
+
["has apiVersion 1 and a name", () => {
|
|
1556
|
+
const version = provider.apiVersion;
|
|
1557
|
+
expect(version === 1, `apiVersion is ${String(version)}`);
|
|
1558
|
+
expect(provider.name.length > 0, "name is empty");
|
|
1559
|
+
}],
|
|
1560
|
+
["refuses a cart that mixes currencies or has no country", async () => {
|
|
1561
|
+
const other = currency === "USD" ? "EUR" : "USD";
|
|
1562
|
+
const mixed = await provider.quote(cart({ lines: [{
|
|
1563
|
+
id: "a",
|
|
1564
|
+
amount: money(100n, other)
|
|
1565
|
+
}] }));
|
|
1566
|
+
expect(kindOf$1(mixed) === "invalid_input", `mixed currencies gave ${kindOf$1(mixed)}`);
|
|
1567
|
+
const nowhere = await provider.quote(cart({ address: {
|
|
1568
|
+
...address,
|
|
1569
|
+
country: ""
|
|
1570
|
+
} }));
|
|
1571
|
+
expect(kindOf$1(nowhere) === "invalid_input", `no country gave ${kindOf$1(nowhere)}`);
|
|
1572
|
+
}],
|
|
1573
|
+
["quotes one line per cart line, in order, with a total that adds up", async () => {
|
|
1574
|
+
const quote = await provider.quote(cart()).orThrow();
|
|
1575
|
+
expect(quote.provider === provider.name, `provider is ${quote.provider}`);
|
|
1576
|
+
expect(quote.lines.map((each) => each.lineId).join(",") === "a,b", `lines are ${quote.lines.map((each) => each.lineId).join(",")}`);
|
|
1577
|
+
expect(quote.lines[0]?.taxable.amount === 10000n, "line a lost its amount");
|
|
1578
|
+
addsUp(quote);
|
|
1579
|
+
expect(expectTax ? quote.totalTax.amount > 0n : quote.totalTax.amount === 0n, `totalTax is ${String(quote.totalTax.amount)}`);
|
|
1580
|
+
}],
|
|
1581
|
+
["splits an inclusive price into taxable and tax", async () => {
|
|
1582
|
+
const only = (await provider.quote(cart({ lines: [line("a", 12100n, true)] })).orThrow()).lines[0];
|
|
1583
|
+
expect(only?.inclusive === true, "the line is not inclusive");
|
|
1584
|
+
expect(only.taxable.amount + only.tax.amount === 12100n, "taxable and tax don't add up to the price");
|
|
1585
|
+
}],
|
|
1586
|
+
["quotes shipping as the last line", async () => {
|
|
1587
|
+
const quote = await provider.quote(cart({ shipping: { amount: money(500n, currency) } })).orThrow();
|
|
1588
|
+
expect(quote.lines.at(-1)?.lineId === SHIPPING_LINE, "the last line is not shipping");
|
|
1589
|
+
addsUp(quote);
|
|
1590
|
+
}],
|
|
1591
|
+
["charges an exempt customer no tax", async () => {
|
|
1592
|
+
const quote = await provider.quote(cart({ customer: { exempt: true } })).orThrow();
|
|
1593
|
+
expect(quote.totalTax.amount === 0n, `totalTax is ${String(quote.totalTax.amount)}`);
|
|
1594
|
+
}],
|
|
1595
|
+
["commits a quote as a transaction", async () => {
|
|
1596
|
+
const quote = await provider.quote(cart()).orThrow();
|
|
1597
|
+
const ref = reference();
|
|
1598
|
+
const transaction = await provider.commit({
|
|
1599
|
+
quote,
|
|
1600
|
+
reference: ref
|
|
1601
|
+
}).orThrow();
|
|
1602
|
+
expect(transaction.id.length > 0, "the transaction has no id");
|
|
1603
|
+
expect(transaction.reference === ref, "the reference changed");
|
|
1604
|
+
expect(transaction.totalTax.amount === quote.totalTax.amount, "the transaction's tax differs from the quote");
|
|
1605
|
+
}],
|
|
1606
|
+
["reverses a whole sale", async () => {
|
|
1607
|
+
const sale = await committed();
|
|
1608
|
+
const reversal = await provider.reverse({
|
|
1609
|
+
transaction: sale,
|
|
1610
|
+
reference: reference()
|
|
1611
|
+
}).orThrow();
|
|
1612
|
+
expect(reversal.totalTax.amount === -sale.totalTax.amount, `reversed ${String(reversal.totalTax.amount)} of ${String(sale.totalTax.amount)}`);
|
|
1613
|
+
}],
|
|
1614
|
+
["reverses part of a line and refuses more than was sold", async () => {
|
|
1615
|
+
const sale = await committed();
|
|
1616
|
+
const sold = sale.lines[0];
|
|
1617
|
+
expect(sold !== void 0, "the sale has no lines");
|
|
1618
|
+
const part = {
|
|
1619
|
+
lineId: sold.lineId,
|
|
1620
|
+
amount: money(sold.taxable.amount / 2n, currency),
|
|
1621
|
+
tax: money(sold.tax.amount / 2n, currency)
|
|
1622
|
+
};
|
|
1623
|
+
const reversal = await provider.reverse({
|
|
1624
|
+
transaction: sale,
|
|
1625
|
+
reference: reference(),
|
|
1626
|
+
lines: [part]
|
|
1627
|
+
}).orThrow();
|
|
1628
|
+
expect(reversal.totalTax.amount === -part.tax.amount, `reversed ${String(reversal.totalTax.amount)}`);
|
|
1629
|
+
const tooMuch = await provider.reverse({
|
|
1630
|
+
transaction: sale,
|
|
1631
|
+
reference: reference(),
|
|
1632
|
+
lines: [{
|
|
1633
|
+
...part,
|
|
1634
|
+
amount: money(sold.taxable.amount + 1n, currency)
|
|
1635
|
+
}]
|
|
1636
|
+
});
|
|
1637
|
+
expect(kindOf$1(tooMuch) === "invalid_input", `reversing more than was sold gave ${kindOf$1(tooMuch)}`);
|
|
1638
|
+
const unknown = await provider.reverse({
|
|
1639
|
+
transaction: sale,
|
|
1640
|
+
reference: reference(),
|
|
1641
|
+
lines: [{
|
|
1642
|
+
...part,
|
|
1643
|
+
lineId: "conformance-unknown"
|
|
1644
|
+
}]
|
|
1645
|
+
});
|
|
1646
|
+
expect(kindOf$1(unknown) === "invalid_input", `an unknown line gave ${kindOf$1(unknown)}`);
|
|
1647
|
+
}]
|
|
1648
|
+
]);
|
|
1649
|
+
}
|
|
1650
|
+
//#endregion
|
|
1651
|
+
//#region src/testing/kits/incoming-destination.ts
|
|
1652
|
+
/**
|
|
1653
|
+
* Proves an `IncomingDestination` accepts a delivery once per endpoint and
|
|
1654
|
+
* message id: a repeat reports the first id as a duplicate, while another
|
|
1655
|
+
* message id or another endpoint is new.
|
|
1656
|
+
* It delivers four messages with random endpoint ids; the destination keeps them.
|
|
1657
|
+
*/
|
|
1658
|
+
function testIncomingDestination(destination, options) {
|
|
1659
|
+
const endpoint = crypto.randomUUID();
|
|
1660
|
+
const delivery = (messageId, endpointId = endpoint) => ({
|
|
1661
|
+
endpoint: {
|
|
1662
|
+
id: endpointId,
|
|
1663
|
+
tenant: options.tenant,
|
|
1664
|
+
metadata: {}
|
|
1665
|
+
},
|
|
1666
|
+
source: options.source ?? "conformance",
|
|
1667
|
+
messageId,
|
|
1668
|
+
type: "conformance.test",
|
|
1669
|
+
payload: {
|
|
1670
|
+
type: "conformance.test",
|
|
1671
|
+
messageId
|
|
1672
|
+
},
|
|
1673
|
+
headers: {}
|
|
1674
|
+
});
|
|
1675
|
+
return conform(`IncomingDestination "${destination.name}"`, [
|
|
1676
|
+
hasName(destination),
|
|
1677
|
+
["has apiVersion 1", () => {
|
|
1678
|
+
const version = destination.apiVersion;
|
|
1679
|
+
expect(version === 1, "apiVersion must be 1");
|
|
1680
|
+
}],
|
|
1681
|
+
["reports a repeated endpoint and message id as a duplicate of the first", async () => {
|
|
1682
|
+
const first = await destination.deliver(delivery("m1"));
|
|
1683
|
+
expect(!first.duplicate, "the first delivery must not be a duplicate");
|
|
1684
|
+
const again = await destination.deliver(delivery("m1"));
|
|
1685
|
+
expect(again.duplicate, "a repeated delivery must be a duplicate");
|
|
1686
|
+
expect(String(again.id) === String(first.id), "a duplicate must return the first delivery's id");
|
|
1687
|
+
}],
|
|
1688
|
+
["treats another message id or another endpoint as a new delivery", async () => {
|
|
1689
|
+
expect(!(await destination.deliver(delivery("m2"))).duplicate, "another message id must be new");
|
|
1690
|
+
expect(!(await destination.deliver(delivery("m1", crypto.randomUUID()))).duplicate, "the same id on another endpoint must be new");
|
|
1691
|
+
}]
|
|
1692
|
+
]);
|
|
1693
|
+
}
|
|
1694
|
+
//#endregion
|
|
1695
|
+
//#region src/testing/kits/plugin.ts
|
|
876
1696
|
/**
|
|
877
1697
|
* Proves a `Plugin` targets API v1, installs cleanly on its own and next to
|
|
878
1698
|
* the first-party plugins in either order, adds repository methods without
|
|
@@ -998,356 +1818,292 @@ function testPlugin(plugin, options) {
|
|
|
998
1818
|
]);
|
|
999
1819
|
}
|
|
1000
1820
|
//#endregion
|
|
1001
|
-
//#region src/testing/
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
return
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
if (value) return value;
|
|
1016
|
-
}
|
|
1821
|
+
//#region src/testing/kits/queue-backend.ts
|
|
1822
|
+
function claimedJob(queue, row) {
|
|
1823
|
+
const instant = temporal().Instant.fromEpochMilliseconds(0);
|
|
1824
|
+
return {
|
|
1825
|
+
id: Number(row.id),
|
|
1826
|
+
queue,
|
|
1827
|
+
payload: row.message?.payload,
|
|
1828
|
+
attempts: row.attempts,
|
|
1829
|
+
maxAttempts: row.message?.max_attempts ?? 5,
|
|
1830
|
+
enqueuedAt: instant,
|
|
1831
|
+
visibleUntil: instant,
|
|
1832
|
+
lastError: row.message?.last_error ?? null,
|
|
1833
|
+
context: {}
|
|
1834
|
+
};
|
|
1017
1835
|
}
|
|
1018
1836
|
/**
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1023
|
-
* table and command that leaks; removes every row it created.
|
|
1024
|
-
*
|
|
1025
|
-
* ```ts
|
|
1026
|
-
* await expectTenantIsolation(betterSupabase, {
|
|
1027
|
-
* tenants: [
|
|
1028
|
-
* { id: ACME, claims: { sub: alice, tenant_id: ACME } },
|
|
1029
|
-
* { id: GLOBEX, claims: { sub: bob, tenant_id: GLOBEX } },
|
|
1030
|
-
* ],
|
|
1031
|
-
* tables: {
|
|
1032
|
-
* tags: {
|
|
1033
|
-
* row: (tenant, n) => ({ organizationId: tenant.id, name: `iso-${n}` }),
|
|
1034
|
-
* update: { name: 'changed' },
|
|
1035
|
-
* },
|
|
1036
|
-
* },
|
|
1037
|
-
* });
|
|
1038
|
-
* ```
|
|
1837
|
+
* Proves a `QueueBackend` round-trips payloads, hides claimed messages for
|
|
1838
|
+
* their lease, counts attempts, retries and dead-letters on `fail`, and
|
|
1839
|
+
* (when it has `leases`) rejects a stale attempt and extends a lease. It
|
|
1840
|
+
* writes to `options.queue` on the backend's real store.
|
|
1039
1841
|
*/
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
returning: false
|
|
1096
|
-
});
|
|
1097
|
-
const after = await current();
|
|
1098
|
-
expect(Object.entries(patch).every(([column]) => after?.[column] === before?.[column]), `update changed ${whose} row`);
|
|
1099
|
-
}]);
|
|
1100
|
-
deletes.push([`${key}: ${who} can't delete ${whose} rows`, async () => {
|
|
1101
|
-
await user.deleteMany({ where: target });
|
|
1102
|
-
expect(await current() !== void 0, `delete removed ${whose} row`);
|
|
1103
|
-
}]);
|
|
1842
|
+
function testQueueBackend(backend, options) {
|
|
1843
|
+
const { queue } = options;
|
|
1844
|
+
const claimOne = async () => {
|
|
1845
|
+
const [row] = await backend.read(queue, 30, 1);
|
|
1846
|
+
return row && claimedJob(queue, row);
|
|
1847
|
+
};
|
|
1848
|
+
return conform(`QueueBackend "${backend.name}"`, [
|
|
1849
|
+
hasName(backend),
|
|
1850
|
+
["has apiVersion 1", () => {
|
|
1851
|
+
const version = backend.apiVersion;
|
|
1852
|
+
expect(version === 1, "apiVersion must be 1");
|
|
1853
|
+
}],
|
|
1854
|
+
["round-trips a payload and leases it", async () => {
|
|
1855
|
+
const id = await backend.send(queue, { n: 1 }, 0, 3, void 0);
|
|
1856
|
+
expect(Number.isFinite(id), "send must return a numeric id");
|
|
1857
|
+
const job = await claimOne();
|
|
1858
|
+
expect(job?.id === id, "read must return the sent message");
|
|
1859
|
+
expect(same(job.payload, { n: 1 }), "the payload must round-trip");
|
|
1860
|
+
expect(job.attempts === 1, "the first claim must be attempt 1");
|
|
1861
|
+
expect(job.maxAttempts === 3, "max_attempts must round-trip");
|
|
1862
|
+
expect(await claimOne() === void 0, "a leased message must stay hidden from other reads");
|
|
1863
|
+
expect(await backend.complete(job), "complete must return true");
|
|
1864
|
+
}],
|
|
1865
|
+
backend.leases && ["rejects a stale attempt and extends a lease", async () => {
|
|
1866
|
+
await backend.send(queue, { n: 2 }, 0, 3, void 0);
|
|
1867
|
+
const job = await claimOne();
|
|
1868
|
+
expect(job, "read must return the sent message");
|
|
1869
|
+
expect(!await backend.complete({
|
|
1870
|
+
...job,
|
|
1871
|
+
attempts: job.attempts + 1
|
|
1872
|
+
}), "complete with another attempt must return false");
|
|
1873
|
+
expect(await backend.extend(job, 60), "extend must return true");
|
|
1874
|
+
expect(await backend.complete(job), "complete must return true");
|
|
1875
|
+
}],
|
|
1876
|
+
["retries, then dead-letters at max attempts", async () => {
|
|
1877
|
+
await backend.send(queue, { n: 3 }, 0, 2, void 0);
|
|
1878
|
+
const first = await claimOne();
|
|
1879
|
+
expect(first, "read must return the sent message");
|
|
1880
|
+
expect(await backend.fail(first, "boom", 0) === "queued", "fail before max_attempts must return queued");
|
|
1881
|
+
const second = await claimOne();
|
|
1882
|
+
expect(second?.attempts === 2, "a retried message must be attempt 2");
|
|
1883
|
+
expect(await backend.fail(second, "boom", 0) === "dead", "fail at max_attempts must return dead");
|
|
1884
|
+
expect(await claimOne() === void 0, "a dead message must not be claimed again");
|
|
1885
|
+
}],
|
|
1886
|
+
backend.stats && backend.listDead && ["counts and lists dead letters", async () => {
|
|
1887
|
+
expect(((await backend.stats?.(queue))?.dead ?? 0) >= 1, "stats must count the dead letter");
|
|
1888
|
+
const [dead] = await backend.listDead?.(queue, 10, void 0) ?? [];
|
|
1889
|
+
expect(dead, "listDead must return the dead letter");
|
|
1890
|
+
expect(same(dead.message?.payload, { n: 3 }), "listDead must return the newest dead letter first");
|
|
1891
|
+
if (backend.retryDead) {
|
|
1892
|
+
expect(await backend.retryDead(queue, [Number(dead.id)], 10) === 1, "retryDead must enqueue the dead letter again");
|
|
1893
|
+
const job = await claimOne();
|
|
1894
|
+
expect(job, "the retried job must be claimable");
|
|
1895
|
+
expect(same(job.payload, { n: 3 }), "the retried job must keep its payload");
|
|
1896
|
+
await backend.complete(job);
|
|
1104
1897
|
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1898
|
+
}],
|
|
1899
|
+
(options.dedupe ?? backend.leases) && ["returns the first id for a repeated dedupe key", async () => {
|
|
1900
|
+
expect(await backend.send(queue, { n: 4 }, 0, 3, "same") === await backend.send(queue, { n: 4 }, 0, 3, "same"), "a waiting dedupe key must return its id");
|
|
1901
|
+
const job = await claimOne();
|
|
1902
|
+
expect(job, "read must return the sent message");
|
|
1903
|
+
await backend.complete(job);
|
|
1904
|
+
}],
|
|
1905
|
+
backend.sendOnce && ["sends once per key, counting completed messages, when it has sendOnce", async () => {
|
|
1906
|
+
const key = `once-${Date.now()}`;
|
|
1907
|
+
const first = await backend.sendOnce?.(queue, { n: 5 }, 0, 3, key);
|
|
1908
|
+
expect(first && !first.duplicate, "the first sendOnce must send");
|
|
1909
|
+
const job = await claimOne();
|
|
1910
|
+
expect(job?.id === first.id, "read must return the sent message");
|
|
1911
|
+
await backend.complete(job);
|
|
1912
|
+
const again = await backend.sendOnce?.(queue, { n: 5 }, 0, 3, key);
|
|
1913
|
+
expect(again?.duplicate === true && again.id === first.id, "sendOnce after completion must return the first id as a duplicate");
|
|
1914
|
+
}]
|
|
1915
|
+
]);
|
|
1110
1916
|
}
|
|
1111
1917
|
//#endregion
|
|
1112
|
-
//#region src/testing/
|
|
1113
|
-
const base = {
|
|
1114
|
-
sub: "6f1c2c1e-5d0a-4d9e-9a51-6b1f0e7c2a10",
|
|
1115
|
-
aud: "authenticated",
|
|
1116
|
-
role: "authenticated",
|
|
1117
|
-
iss: "https://project.supabase.co/auth/v1",
|
|
1118
|
-
aal: "aal1",
|
|
1119
|
-
session_id: "b7a8f9c0-1111-4222-8333-944455556666",
|
|
1120
|
-
is_anonymous: false
|
|
1121
|
-
};
|
|
1122
|
-
const admin = "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d";
|
|
1123
|
-
const supportSessionId = "4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b";
|
|
1124
|
-
const client = "5f0e4d3c-2b1a-4098-8776-655443322110";
|
|
1125
|
-
const chain = {
|
|
1126
|
-
sub: "agent-runner",
|
|
1127
|
-
act: { sub: "mcp-client-42" }
|
|
1128
|
-
};
|
|
1918
|
+
//#region src/testing/kits/support-session-store.ts
|
|
1129
1919
|
/**
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1920
|
+
* Proves a `SupportSessionStore` starts sessions with the input it was
|
|
1921
|
+
* given, shows a running session only to its admin, ends the admin's
|
|
1922
|
+
* previous session on start, ends a session once, lists sessions and, with
|
|
1923
|
+
* `expire`, leaves running sessions alone.
|
|
1924
|
+
* It writes sessions to the store; they are all ended when it returns.
|
|
1134
1925
|
*/
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
kind: "support",
|
|
1169
|
-
sub: admin,
|
|
1170
|
-
reason: "ticket 42",
|
|
1171
|
-
session_id: supportSessionId,
|
|
1172
|
-
read_only: true
|
|
1173
|
-
}
|
|
1174
|
-
},
|
|
1175
|
-
expect: {
|
|
1176
|
-
actor: {
|
|
1177
|
-
kind: "support",
|
|
1178
|
-
id: admin,
|
|
1179
|
-
sessionId: supportSessionId,
|
|
1180
|
-
readOnly: true,
|
|
1181
|
-
reason: "ticket 42"
|
|
1182
|
-
},
|
|
1183
|
-
impersonator: {
|
|
1184
|
-
kind: "support",
|
|
1185
|
-
id: admin,
|
|
1186
|
-
sessionId: supportSessionId,
|
|
1187
|
-
readOnly: true,
|
|
1188
|
-
reason: "ticket 42"
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
},
|
|
1192
|
-
impersonation: {
|
|
1193
|
-
claims: {
|
|
1194
|
-
...base,
|
|
1195
|
-
act: {
|
|
1196
|
-
kind: "impersonation",
|
|
1197
|
-
sub: admin,
|
|
1198
|
-
reason: "ticket 42"
|
|
1926
|
+
function testSupportSessionStore(store, options) {
|
|
1927
|
+
const { admin } = options;
|
|
1928
|
+
const [first, second] = options.targets;
|
|
1929
|
+
const start = (targetUserId) => store.start({
|
|
1930
|
+
adminId: admin.id,
|
|
1931
|
+
adminClaims: admin.claims,
|
|
1932
|
+
targetUserId,
|
|
1933
|
+
reason: "conformance",
|
|
1934
|
+
ttlSeconds: 600,
|
|
1935
|
+
readOnly: true,
|
|
1936
|
+
metadata: { ticket: "T-1" }
|
|
1937
|
+
});
|
|
1938
|
+
return conform(`SupportSessionStore "${store.name}"`, [
|
|
1939
|
+
hasName(store),
|
|
1940
|
+
["has apiVersion 1", () => {
|
|
1941
|
+
const version = store.apiVersion;
|
|
1942
|
+
expect(version === 1, "apiVersion must be 1");
|
|
1943
|
+
}],
|
|
1944
|
+
["starts a session with its input and shows it to its admin only", async () => {
|
|
1945
|
+
const session = await start(first);
|
|
1946
|
+
try {
|
|
1947
|
+
expect(session.id.length > 0, "start must return an id");
|
|
1948
|
+
expect(session.adminId === admin.id, "adminId must round-trip");
|
|
1949
|
+
expect(session.targetUserId === first, "targetUserId must round-trip");
|
|
1950
|
+
expect(session.reason === "conformance", "reason must round-trip");
|
|
1951
|
+
expect(session.readOnly, "readOnly must round-trip");
|
|
1952
|
+
expect(same(session.metadata, { ticket: "T-1" }), "metadata must round-trip");
|
|
1953
|
+
const seconds = (session.expiresAt.epochMilliseconds - session.startedAt.epochMilliseconds) / 1e3;
|
|
1954
|
+
expect(Math.abs(seconds - 600) < 5, "expiresAt must be ttlSeconds after startedAt");
|
|
1955
|
+
expect((await store.get(session.id, admin.id))?.id === session.id, "get must return the running session");
|
|
1956
|
+
expect(await store.get(session.id, first) === void 0, "get must hide a session from anyone but its admin");
|
|
1957
|
+
} finally {
|
|
1958
|
+
await store.end(session.id, "admin");
|
|
1199
1959
|
}
|
|
1200
|
-
},
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
id: admin,
|
|
1210
|
-
reason: "ticket 42"
|
|
1960
|
+
}],
|
|
1961
|
+
["ends the admin's previous session on start", async () => {
|
|
1962
|
+
const older = await start(first);
|
|
1963
|
+
const newer = await start(second);
|
|
1964
|
+
try {
|
|
1965
|
+
expect(await store.get(older.id, admin.id) === void 0, "starting a session must end the admin's previous one");
|
|
1966
|
+
expect((await store.get(newer.id, admin.id))?.id === newer.id, "the new session must be running");
|
|
1967
|
+
} finally {
|
|
1968
|
+
await store.end(newer.id, "admin");
|
|
1211
1969
|
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
actor: {
|
|
1240
|
-
kind: "oauth-client",
|
|
1241
|
-
id: "agent-runner",
|
|
1242
|
-
chain
|
|
1243
|
-
},
|
|
1244
|
-
delegation: {
|
|
1245
|
-
scopes: ["posts:read"],
|
|
1246
|
-
chain
|
|
1970
|
+
}],
|
|
1971
|
+
["ends a session once and lists it", async () => {
|
|
1972
|
+
const session = await start(first);
|
|
1973
|
+
expect(await store.end(session.id, "admin"), "end must return true");
|
|
1974
|
+
expect(!await store.end(session.id, "admin"), "ending an ended session must return false");
|
|
1975
|
+
expect(await store.get(session.id, admin.id) === void 0, "get must not return an ended session");
|
|
1976
|
+
const entry = (await store.list({
|
|
1977
|
+
adminId: admin.id,
|
|
1978
|
+
active: false
|
|
1979
|
+
})).find((item) => item.id === session.id);
|
|
1980
|
+
expect(entry, "list must include the ended session");
|
|
1981
|
+
expect(entry.endedAt !== void 0, "an ended session has endedAt");
|
|
1982
|
+
expect((await store.list({
|
|
1983
|
+
adminId: admin.id,
|
|
1984
|
+
active: true
|
|
1985
|
+
})).every((item) => item.id !== session.id), "list({ active: true }) must leave out ended sessions");
|
|
1986
|
+
}],
|
|
1987
|
+
["expires only sessions past their expiry, when it has expire", async () => {
|
|
1988
|
+
if (!store.expire) return;
|
|
1989
|
+
const session = await start(first);
|
|
1990
|
+
try {
|
|
1991
|
+
const expired = await store.expire();
|
|
1992
|
+
expect(expired.every((item) => item.id !== session.id), "expire must leave a running session alone");
|
|
1993
|
+
expect(expired.every((item) => item.endedBy === "expired"), "expire must end sessions with endedBy expired");
|
|
1994
|
+
expect((await store.get(session.id, admin.id))?.id === session.id, "the running session must still be running after expire");
|
|
1995
|
+
} finally {
|
|
1996
|
+
await store.end(session.id, "admin");
|
|
1247
1997
|
}
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
};
|
|
1251
|
-
//#endregion
|
|
1252
|
-
//#region src/testing/seed.ts
|
|
1253
|
-
const SEED = Symbol.for("better-supabase.seed");
|
|
1254
|
-
function isSeed(value) {
|
|
1255
|
-
return typeof value === "object" && value !== null && SEED in value;
|
|
1256
|
-
}
|
|
1257
|
-
const quote = (text) => `'${text.replaceAll("'", "''")}'`;
|
|
1258
|
-
function arrayLiteral(values) {
|
|
1259
|
-
return `{${values.map((value) => {
|
|
1260
|
-
if (value === null) return "NULL";
|
|
1261
|
-
if (Array.isArray(value)) return arrayLiteral(value);
|
|
1262
|
-
return `"${(temporalText(value) ?? String(value)).replaceAll(/[\\"]/g, "\\$&")}"`;
|
|
1263
|
-
}).join(",")}}`;
|
|
1264
|
-
}
|
|
1265
|
-
function literal(value, column, where) {
|
|
1266
|
-
if (value === null) return "null";
|
|
1267
|
-
if (column.json) return quote(JSON.stringify(value));
|
|
1268
|
-
if (Array.isArray(value)) return quote(arrayLiteral(value));
|
|
1269
|
-
const temporalValue = temporalText(value);
|
|
1270
|
-
if (temporalValue !== void 0) return quote(temporalValue);
|
|
1271
|
-
const kind = typeof value;
|
|
1272
|
-
switch (kind) {
|
|
1273
|
-
case "string": return quote(value);
|
|
1274
|
-
case "boolean":
|
|
1275
|
-
case "bigint": return String(value);
|
|
1276
|
-
case "number":
|
|
1277
|
-
if (!Number.isFinite(value)) throw new TypeError(`${where}: ${String(value)} is not a finite number`);
|
|
1278
|
-
return String(value);
|
|
1279
|
-
case "object":
|
|
1280
|
-
case "function":
|
|
1281
|
-
case "symbol":
|
|
1282
|
-
case "undefined": throw new TypeError(`${where}: unsupported value of type ${kind}`);
|
|
1283
|
-
default: throw new TypeError(`${where}: unsupported value of type ${String(kind)}`);
|
|
1284
|
-
}
|
|
1998
|
+
}]
|
|
1999
|
+
]);
|
|
1285
2000
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
2001
|
+
//#endregion
|
|
2002
|
+
//#region src/testing/kits/controller-binding.ts
|
|
2003
|
+
const PROBE_INITIAL = {
|
|
2004
|
+
count: 0,
|
|
2005
|
+
label: "initial"
|
|
2006
|
+
};
|
|
2007
|
+
function probe() {
|
|
2008
|
+
let starts = 0;
|
|
2009
|
+
let stops = 0;
|
|
2010
|
+
let disposed = false;
|
|
2011
|
+
let made;
|
|
2012
|
+
const idle = () => void 0;
|
|
2013
|
+
let emit = idle;
|
|
2014
|
+
return {
|
|
2015
|
+
create: () => {
|
|
2016
|
+
const controller = defineController(PROBE_INITIAL, (state) => ({
|
|
2017
|
+
actions: {
|
|
2018
|
+
increment: () => {
|
|
2019
|
+
state.set((current) => ({ count: current.count + 1 }));
|
|
2020
|
+
},
|
|
2021
|
+
rename: (label) => {
|
|
2022
|
+
state.set({ label });
|
|
2023
|
+
}
|
|
2024
|
+
},
|
|
2025
|
+
start: () => {
|
|
2026
|
+
starts += 1;
|
|
2027
|
+
emit = (patch) => {
|
|
2028
|
+
state.set(patch);
|
|
2029
|
+
};
|
|
2030
|
+
return () => {
|
|
2031
|
+
stops += 1;
|
|
2032
|
+
emit = idle;
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
}));
|
|
2036
|
+
made = {
|
|
2037
|
+
...controller,
|
|
2038
|
+
dispose: () => {
|
|
2039
|
+
disposed = true;
|
|
2040
|
+
controller.dispose();
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
return made;
|
|
2044
|
+
},
|
|
2045
|
+
emit: (patch) => {
|
|
2046
|
+
emit(patch);
|
|
2047
|
+
},
|
|
2048
|
+
starts: () => starts,
|
|
2049
|
+
stops: () => stops,
|
|
2050
|
+
disposed: () => disposed,
|
|
2051
|
+
controller: () => made
|
|
1297
2052
|
};
|
|
1298
|
-
for (const key of keys) visit(key);
|
|
1299
|
-
return sorted;
|
|
1300
|
-
}
|
|
1301
|
-
function statement(table, rows) {
|
|
1302
|
-
const entries = Object.entries(rows);
|
|
1303
|
-
if (entries.length === 0) return void 0;
|
|
1304
|
-
const columns = [];
|
|
1305
|
-
for (const [, row] of entries) for (const [key, value] of Object.entries(row)) {
|
|
1306
|
-
if (value === void 0 || columns.includes(key)) continue;
|
|
1307
|
-
const column = table.columns[key];
|
|
1308
|
-
if (!column) throw new TypeError(`seed ${table.key}: unknown column "${key}"`);
|
|
1309
|
-
if (column.generated) throw new TypeError(`seed ${table.key}: "${key}" is a generated column`);
|
|
1310
|
-
columns.push(key);
|
|
1311
|
-
}
|
|
1312
|
-
const values = entries.map(([name, row]) => {
|
|
1313
|
-
return ` (${columns.map((key) => row[key] === void 0 ? "default" : literal(row[key], table.columns[key], `seed ${table.key}.${name}.${key}`)).join(", ")})`;
|
|
1314
|
-
});
|
|
1315
|
-
return `insert into ${`${ident(table.schema)}.${ident(table.name)}`} (${columns.map((key) => ident(table.columns[key].db)).join(", ")}) values\n${values.join(",\n")}\non conflict do nothing;`;
|
|
1316
2053
|
}
|
|
1317
2054
|
/**
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
* ```ts
|
|
1322
|
-
* export const seed = defineSeed(betterSupabase, {
|
|
1323
|
-
* organizations: { acme: { id: ACME, name: 'Acme' } },
|
|
1324
|
-
* customers: { first: { organizationId: ACME, name: 'First customer' } },
|
|
1325
|
-
* });
|
|
1326
|
-
* ```
|
|
2055
|
+
* Proves a framework binding over a block controller renders the initial
|
|
2056
|
+
* snapshot, follows each change, passes the actions through and stops the
|
|
2057
|
+
* controller's work (and disposes it) on unmount.
|
|
1327
2058
|
*/
|
|
1328
|
-
function
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
const keys = Object.keys(fixtures).filter((key) => fixtures[key] !== void 0);
|
|
1332
|
-
for (const key of keys) {
|
|
1333
|
-
const table = meta.tables[key];
|
|
1334
|
-
if (!table) throw new TypeError(`seed: unknown table "${key}"`);
|
|
1335
|
-
if (table.kind === "view") throw new TypeError(`seed: "${key}" is a view`);
|
|
1336
|
-
}
|
|
1337
|
-
return order(meta, keys).flatMap((key) => {
|
|
1338
|
-
const sql = statement(meta.tables[key], fixtures[key]);
|
|
1339
|
-
return sql ? [sql] : [];
|
|
1340
|
-
});
|
|
2059
|
+
function testControllerBinding(name, harness) {
|
|
2060
|
+
const settle = async () => {
|
|
2061
|
+
await harness.settle?.();
|
|
1341
2062
|
};
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
2063
|
+
const mounted = async (run) => {
|
|
2064
|
+
const current = probe();
|
|
2065
|
+
const binding = await harness.mount(current.create);
|
|
2066
|
+
try {
|
|
2067
|
+
await settle();
|
|
2068
|
+
await run(binding, current);
|
|
2069
|
+
} finally {
|
|
2070
|
+
await binding.unmount();
|
|
1349
2071
|
}
|
|
1350
2072
|
};
|
|
2073
|
+
return conform(`ControllerBinding "${name}"`, [
|
|
2074
|
+
["renders the initial snapshot", () => mounted((binding, current) => {
|
|
2075
|
+
const state = binding.read();
|
|
2076
|
+
expect(state.count === PROBE_INITIAL.count && state.label === PROBE_INITIAL.label, `rendered ${JSON.stringify(state)} instead of the initial snapshot`);
|
|
2077
|
+
expect(current.starts() === 1, `the controller started ${current.starts()} times on mount, not once`);
|
|
2078
|
+
})],
|
|
2079
|
+
["renders a change from the controller", () => mounted(async (binding, current) => {
|
|
2080
|
+
current.emit({ label: "changed" });
|
|
2081
|
+
await settle();
|
|
2082
|
+
expect(binding.read().label === "changed", `rendered label ${JSON.stringify(binding.read().label)} after the controller changed it`);
|
|
2083
|
+
current.emit({ count: 5 });
|
|
2084
|
+
await settle();
|
|
2085
|
+
expect(binding.read().count === 5, `rendered count ${binding.read().count} after a second change`);
|
|
2086
|
+
})],
|
|
2087
|
+
["passes the controller's actions through", () => mounted(async (binding, current) => {
|
|
2088
|
+
expect(binding.actions.increment === current.controller()?.actions.increment, "the binding's actions are not the controller's");
|
|
2089
|
+
binding.actions.increment();
|
|
2090
|
+
binding.actions.rename("renamed");
|
|
2091
|
+
await settle();
|
|
2092
|
+
const state = binding.read();
|
|
2093
|
+
expect(state.count === 1 && state.label === "renamed", `rendered ${JSON.stringify(state)} after the actions ran`);
|
|
2094
|
+
})],
|
|
2095
|
+
["stops the controller on unmount", async () => {
|
|
2096
|
+
const current = probe();
|
|
2097
|
+
const binding = await harness.mount(current.create);
|
|
2098
|
+
await settle();
|
|
2099
|
+
await binding.unmount();
|
|
2100
|
+
await settle();
|
|
2101
|
+
expect(current.stops() === current.starts() && current.starts() > 0, `the controller started ${current.starts()} times and stopped ${current.stops()} times`);
|
|
2102
|
+
if (harness.disposes ?? true) expect(current.disposed(), "unmount didn't dispose the controller");
|
|
2103
|
+
current.emit({ count: 9 });
|
|
2104
|
+
await settle();
|
|
2105
|
+
}]
|
|
2106
|
+
]);
|
|
1351
2107
|
}
|
|
1352
2108
|
//#endregion
|
|
1353
2109
|
//#region src/testing/adapter.ts
|
|
@@ -1484,13 +2240,13 @@ function testBlockTransportMiddleware(middleware) {
|
|
|
1484
2240
|
}
|
|
1485
2241
|
//#endregion
|
|
1486
2242
|
//#region src/testing/authorization-provider.ts
|
|
1487
|
-
function isPlainJson(value) {
|
|
2243
|
+
function isPlainJson$1(value) {
|
|
1488
2244
|
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1489
2245
|
if (typeof value === "number") return Number.isFinite(value);
|
|
1490
|
-
if (Array.isArray(value)) return value.every(isPlainJson);
|
|
2246
|
+
if (Array.isArray(value)) return value.every(isPlainJson$1);
|
|
1491
2247
|
if (typeof value !== "object") return false;
|
|
1492
2248
|
const prototype = Object.getPrototypeOf(value);
|
|
1493
|
-
return (prototype === Object.prototype || prototype === null) && Object.values(value).every((entry) => entry === void 0 || isPlainJson(entry));
|
|
2249
|
+
return (prototype === Object.prototype || prototype === null) && Object.values(value).every((entry) => entry === void 0 || isPlainJson$1(entry));
|
|
1494
2250
|
}
|
|
1495
2251
|
const QUALIFIED = /^[a-z_][\w$]*\.[a-z_][\w${}]*$/i;
|
|
1496
2252
|
const TABLE = /^[a-z_][\w$]*\.[a-z_][\w$]*$/i;
|
|
@@ -1520,7 +2276,7 @@ function testAuthorizationProvider(provider) {
|
|
|
1520
2276
|
expect(provider.apiVersion === 1, `apiVersion is ${String(provider.apiVersion)}`);
|
|
1521
2277
|
}],
|
|
1522
2278
|
["is plain JSON", () => {
|
|
1523
|
-
expect(isPlainJson(provider), "it doesn't survive a JSON round trip");
|
|
2279
|
+
expect(isPlainJson$1(provider), "it doesn't survive a JSON round trip");
|
|
1524
2280
|
}],
|
|
1525
2281
|
["names valid, unique scopes", () => {
|
|
1526
2282
|
expect(scopes.size > 0, "scopes is empty");
|
|
@@ -1559,6 +2315,14 @@ function testAuthorizationProvider(provider) {
|
|
|
1559
2315
|
for (const scope of permission.scopes ?? []) expect(scopes.has(scope), `permission "${permission.key}" names unknown scope "${scope}"`);
|
|
1560
2316
|
}
|
|
1561
2317
|
}],
|
|
2318
|
+
provider.operations && ["operations name listed permissions", () => {
|
|
2319
|
+
const keys = provider.permissions === void 0 ? void 0 : new Set(provider.permissions.map((entry) => entry.key));
|
|
2320
|
+
for (const [name, operation] of Object.entries(provider.operations)) {
|
|
2321
|
+
expect(name !== "", "an operation name is empty");
|
|
2322
|
+
expect(operation.permission !== "", `operation "${name}" names no permission`);
|
|
2323
|
+
if (keys) expect(keys.has(operation.permission), `operation "${name}" needs "${operation.permission}", which permissions doesn't list`);
|
|
2324
|
+
}
|
|
2325
|
+
}],
|
|
1562
2326
|
provider.memberships && ["memberships name known scopes", () => {
|
|
1563
2327
|
for (const membership of provider.memberships) {
|
|
1564
2328
|
expect(TABLE.test(membership.table), `membership table "${membership.table}" is not schema.table`);
|
|
@@ -1603,6 +2367,566 @@ function testAuthorizationProvider(provider) {
|
|
|
1603
2367
|
]);
|
|
1604
2368
|
}
|
|
1605
2369
|
//#endregion
|
|
2370
|
+
//#region src/testing/authorization-hooks.ts
|
|
2371
|
+
/**
|
|
2372
|
+
* Proves `AuthorizationHooks` target API v1 and fail closed for strangers:
|
|
2373
|
+
* fan-out returns only subscribers it was given, an identity nobody linked
|
|
2374
|
+
* and a forged ticket resolve to `null`, a requester never approves their
|
|
2375
|
+
* own request, an unknown publisher can neither publish nor run, an
|
|
2376
|
+
* unlinked chat author gets nothing, an admin with no claims cannot start
|
|
2377
|
+
* a support session and never approves their own, and a delegated token
|
|
2378
|
+
* with no scopes maps to no permissions.
|
|
2379
|
+
*/
|
|
2380
|
+
function testAuthorizationHooks(hooks) {
|
|
2381
|
+
const stranger = crypto.randomUUID();
|
|
2382
|
+
const event = {
|
|
2383
|
+
type: "conformance.checked",
|
|
2384
|
+
tenant: crypto.randomUUID(),
|
|
2385
|
+
actorId: stranger,
|
|
2386
|
+
payload: {}
|
|
2387
|
+
};
|
|
2388
|
+
const subscribers = [{
|
|
2389
|
+
principal: crypto.randomUUID(),
|
|
2390
|
+
kind: "user"
|
|
2391
|
+
}, {
|
|
2392
|
+
principal: crypto.randomUUID(),
|
|
2393
|
+
kind: "service"
|
|
2394
|
+
}];
|
|
2395
|
+
const subset = async (policy, where) => {
|
|
2396
|
+
const kept = await policy(event, subscribers);
|
|
2397
|
+
for (const entry of kept) expect(subscribers.includes(entry), `${where} returned a subscriber it was not given`);
|
|
2398
|
+
expect((await policy(event, [])).length === 0, `${where} returned subscribers from an empty list`);
|
|
2399
|
+
};
|
|
2400
|
+
const supportStart = (extra) => {
|
|
2401
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2402
|
+
const claims = {
|
|
2403
|
+
sub: stranger,
|
|
2404
|
+
role: "authenticated",
|
|
2405
|
+
aud: "authenticated",
|
|
2406
|
+
iss: "conformance",
|
|
2407
|
+
exp: now + 60,
|
|
2408
|
+
iat: now,
|
|
2409
|
+
...extra
|
|
2410
|
+
};
|
|
2411
|
+
return {
|
|
2412
|
+
admin: {
|
|
2413
|
+
kind: "user",
|
|
2414
|
+
token: "",
|
|
2415
|
+
claims,
|
|
2416
|
+
user: {
|
|
2417
|
+
id: stranger,
|
|
2418
|
+
role: "authenticated"
|
|
2419
|
+
},
|
|
2420
|
+
source: "conformance",
|
|
2421
|
+
expiresAt: claims.exp
|
|
2422
|
+
},
|
|
2423
|
+
targetUserId: crypto.randomUUID(),
|
|
2424
|
+
reason: "conformance",
|
|
2425
|
+
readOnly: true,
|
|
2426
|
+
metadata: {}
|
|
2427
|
+
};
|
|
2428
|
+
};
|
|
2429
|
+
const { delivery, identities, approverOf, tickets, automation, chat, delegation } = hooks;
|
|
2430
|
+
return conform(`AuthorizationHooks "${hooks.name}"`, [
|
|
2431
|
+
["targets API v1 and has a name", () => {
|
|
2432
|
+
expect(hooks.apiVersion === 1, `apiVersion is ${String(hooks.apiVersion)}`);
|
|
2433
|
+
expect(hooks.name !== "", "name is empty");
|
|
2434
|
+
}],
|
|
2435
|
+
delivery?.subscribers && ["subscribers returns a subset", () => subset(delivery.subscribers, "delivery.subscribers")],
|
|
2436
|
+
delivery?.audience && ["audience returns a subset", () => subset(delivery.audience, "delivery.audience")],
|
|
2437
|
+
identities && ["an unlinked identity is null", async () => {
|
|
2438
|
+
expect(await identities.principalByIdentity({
|
|
2439
|
+
kind: "email",
|
|
2440
|
+
value: `${stranger}@conformance.invalid`
|
|
2441
|
+
}) === null, "an address nobody linked resolved to someone");
|
|
2442
|
+
}],
|
|
2443
|
+
approverOf && ["a requester never approves their own request", async () => {
|
|
2444
|
+
expect(!await approverOf({
|
|
2445
|
+
userId: stranger,
|
|
2446
|
+
claims: { sub: stranger }
|
|
2447
|
+
}, {
|
|
2448
|
+
kind: "tool",
|
|
2449
|
+
subject: crypto.randomUUID(),
|
|
2450
|
+
tenant: event.tenant,
|
|
2451
|
+
requester: stranger
|
|
2452
|
+
}), "approverOf allowed the requester");
|
|
2453
|
+
}],
|
|
2454
|
+
tickets && ["a forged ticket is null", async () => {
|
|
2455
|
+
expect(await tickets.subject(`forged.${stranger}.ticket`) === null, "a forged ticket resolved to a context");
|
|
2456
|
+
}],
|
|
2457
|
+
automation?.mayPublish && ["an unknown publisher may not publish", async () => {
|
|
2458
|
+
expect(!await automation.mayPublish({
|
|
2459
|
+
publisher: stranger,
|
|
2460
|
+
tenant: event.tenant,
|
|
2461
|
+
permissions: ["conformance.write"],
|
|
2462
|
+
claims: {
|
|
2463
|
+
sub: stranger,
|
|
2464
|
+
role: "authenticated"
|
|
2465
|
+
}
|
|
2466
|
+
}), "mayPublish allowed a publisher nobody knows");
|
|
2467
|
+
}],
|
|
2468
|
+
automation?.runAs && ["an unknown publisher runs as no one", async () => {
|
|
2469
|
+
expect(await automation.runAs({
|
|
2470
|
+
publisher: stranger,
|
|
2471
|
+
tenant: event.tenant,
|
|
2472
|
+
permissions: ["conformance.read"]
|
|
2473
|
+
}) === null, "runAs returned claims for an unknown publisher");
|
|
2474
|
+
}],
|
|
2475
|
+
chat?.onAction && ["an unlinked chat author is refused", async () => {
|
|
2476
|
+
const decided = await chat.onAction({
|
|
2477
|
+
id: "approve",
|
|
2478
|
+
author: {
|
|
2479
|
+
kind: "chat",
|
|
2480
|
+
provider: "conformance",
|
|
2481
|
+
value: stranger
|
|
2482
|
+
}
|
|
2483
|
+
}, null);
|
|
2484
|
+
expect(decided === void 0 || !decided.allowed, "onAction allowed an author with no identity link");
|
|
2485
|
+
}],
|
|
2486
|
+
hooks.support && ["an admin with no claims cannot start a support session", async () => {
|
|
2487
|
+
expect(await hooks.support.authorize(supportStart({})) === false, "support.authorize allowed an admin with no claims");
|
|
2488
|
+
}],
|
|
2489
|
+
hooks.support && ["an admin never approves their own support session", async () => {
|
|
2490
|
+
const decision = await hooks.support.authorize(supportStart({
|
|
2491
|
+
user_role: "admin",
|
|
2492
|
+
app_role: "admin"
|
|
2493
|
+
}));
|
|
2494
|
+
expect(typeof decision !== "object" || decision.approvedBy !== stranger, "support.authorize named the admin as their own approver");
|
|
2495
|
+
}],
|
|
2496
|
+
delegation && ["a delegation with no scopes grants no permissions", async () => {
|
|
2497
|
+
expect((await delegation.permissions({
|
|
2498
|
+
userId: stranger,
|
|
2499
|
+
tenant: event.tenant,
|
|
2500
|
+
clientId: "conformance",
|
|
2501
|
+
scopes: []
|
|
2502
|
+
})).length === 0, "delegation.permissions granted keys to a token with no scopes");
|
|
2503
|
+
}]
|
|
2504
|
+
]);
|
|
2505
|
+
}
|
|
2506
|
+
//#endregion
|
|
2507
|
+
//#region src/testing/delivery-transport.ts
|
|
2508
|
+
const KEY = "00000000-0000-4000-8000-00000000d0f0";
|
|
2509
|
+
/**
|
|
2510
|
+
* Runs the `DeliveryTransport` contract against `transport`: API version 1,
|
|
2511
|
+
* the `email` channel, a provider name, and one frozen sample message it
|
|
2512
|
+
* sends without mutating, with the delivery id as its idempotency key.
|
|
2513
|
+
* Throws a `ConformanceError` listing every failed check.
|
|
2514
|
+
*/
|
|
2515
|
+
function testDeliveryTransport(transport, options = {}) {
|
|
2516
|
+
const message = deepFreeze({
|
|
2517
|
+
from: "Conformance <conformance@example.test>",
|
|
2518
|
+
to: options.to ?? "delivered@example.test",
|
|
2519
|
+
subject: "Conformance check",
|
|
2520
|
+
text: "Conformance check",
|
|
2521
|
+
category: "notification",
|
|
2522
|
+
headers: mailHeaders("notification", { url: "https://example.test/unsubscribe" })
|
|
2523
|
+
});
|
|
2524
|
+
return conform(`DeliveryTransport "${transport.provider}"`, [
|
|
2525
|
+
["has apiVersion 1", () => {
|
|
2526
|
+
const version = transport.apiVersion;
|
|
2527
|
+
expect(version === 1, "apiVersion must be 1");
|
|
2528
|
+
}],
|
|
2529
|
+
["sends on the email channel", () => {
|
|
2530
|
+
const channel = transport.channel;
|
|
2531
|
+
expect(channel === "email", `channel must be "email", got ${String(channel)}`);
|
|
2532
|
+
}],
|
|
2533
|
+
["has a provider name", () => {
|
|
2534
|
+
expect(typeof transport.provider === "string" && transport.provider !== "", "provider must be a non-empty string");
|
|
2535
|
+
}],
|
|
2536
|
+
["sends a message without mutating it and returns a valid receipt", async () => {
|
|
2537
|
+
const receipt = await transport.send(message, { idempotencyKey: KEY });
|
|
2538
|
+
if (receipt === void 0) return;
|
|
2539
|
+
expect(receipt.providerMessageId === void 0 || typeof receipt.providerMessageId === "string", "providerMessageId must be a string");
|
|
2540
|
+
}],
|
|
2541
|
+
options.received && ["passes the idempotency key and the headers to the provider", async () => {
|
|
2542
|
+
const found = (await options.received()).find((entry) => entry.idempotencyKey === KEY);
|
|
2543
|
+
expect(found, `no message arrived with idempotency key ${KEY}`);
|
|
2544
|
+
for (const [name, value] of Object.entries(message.headers)) expect(found.headers[name] === value, `header ${name} must be "${value}", got ${String(found.headers[name])}`);
|
|
2545
|
+
}]
|
|
2546
|
+
]);
|
|
2547
|
+
}
|
|
2548
|
+
//#endregion
|
|
2549
|
+
//#region src/testing/authorizer.ts
|
|
2550
|
+
const OUTCOMES = /* @__PURE__ */ new Set([
|
|
2551
|
+
"granted",
|
|
2552
|
+
"denied",
|
|
2553
|
+
"approval-required"
|
|
2554
|
+
]);
|
|
2555
|
+
const isOutcome = (value) => typeof value === "object" && value !== null && "outcome" in value && typeof value.outcome === "string" && OUTCOMES.has(value.outcome);
|
|
2556
|
+
/** Input that claims a privileged caller; the subject must decide, never this. */
|
|
2557
|
+
const FORGED = {
|
|
2558
|
+
subject: {
|
|
2559
|
+
type: "service",
|
|
2560
|
+
id: "service"
|
|
2561
|
+
},
|
|
2562
|
+
actor: {
|
|
2563
|
+
id: "service",
|
|
2564
|
+
kind: "service",
|
|
2565
|
+
role: "service_role"
|
|
2566
|
+
},
|
|
2567
|
+
role: "service_role",
|
|
2568
|
+
user_id: "00000000-0000-4000-8000-000000000000",
|
|
2569
|
+
memberships: [{
|
|
2570
|
+
tenant: "conformance",
|
|
2571
|
+
role: "owner"
|
|
2572
|
+
}],
|
|
2573
|
+
claims: {
|
|
2574
|
+
role: "service_role",
|
|
2575
|
+
user_role: "admin",
|
|
2576
|
+
roles: ["admin"],
|
|
2577
|
+
memberships: [{
|
|
2578
|
+
tenant: "conformance",
|
|
2579
|
+
role: "owner"
|
|
2580
|
+
}]
|
|
2581
|
+
}
|
|
2582
|
+
};
|
|
2583
|
+
/**
|
|
2584
|
+
* Proves an `Authorizer` targets API v1, keys every permission to the same
|
|
2585
|
+
* string each time (and after a JSON round trip, so references are plain
|
|
2586
|
+
* JSON), answers every request with a known outcome without mutating it,
|
|
2587
|
+
* keeps the request order in `evaluations`, decides from the subject rather
|
|
2588
|
+
* than the input (forged roles, claims and memberships in the resource
|
|
2589
|
+
* properties or the context never turn a denial into a grant), never grants an unknown permission, and grants
|
|
2590
|
+
* `options.granted` through `checkPermission`.
|
|
2591
|
+
*/
|
|
2592
|
+
function testAuthorizer(authorizer, options) {
|
|
2593
|
+
const { subject, context } = requestOf(void 0);
|
|
2594
|
+
const requestFor = (action, id, properties) => ({
|
|
2595
|
+
subject,
|
|
2596
|
+
action,
|
|
2597
|
+
resource: {
|
|
2598
|
+
type: "conformance",
|
|
2599
|
+
id,
|
|
2600
|
+
...properties ? { properties } : {}
|
|
2601
|
+
},
|
|
2602
|
+
context
|
|
2603
|
+
});
|
|
2604
|
+
const requests = options.permissions.flatMap((permission) => [
|
|
2605
|
+
"a",
|
|
2606
|
+
"b",
|
|
2607
|
+
"c"
|
|
2608
|
+
].map((id) => requestFor(permission, id)));
|
|
2609
|
+
return conform(`Authorizer "${authorizer.name}"`, [
|
|
2610
|
+
hasName(authorizer),
|
|
2611
|
+
["targets API v1", () => {
|
|
2612
|
+
const version = authorizer.apiVersion;
|
|
2613
|
+
expect(version === 1, `apiVersion is ${String(version)}`);
|
|
2614
|
+
expect(options.permissions.length > 0, "options.permissions lists no permission to check");
|
|
2615
|
+
}],
|
|
2616
|
+
["keys every permission stably, as plain JSON", () => {
|
|
2617
|
+
for (const permission of options.permissions) {
|
|
2618
|
+
const key = authorizer.key(permission);
|
|
2619
|
+
expect(typeof key === "string" && key.length > 0, "key() must return a non-empty string");
|
|
2620
|
+
expect(authorizer.key(permission) === key, `key() gave two keys for ${key}`);
|
|
2621
|
+
const copy = JSON.parse(JSON.stringify(permission));
|
|
2622
|
+
expect(same(copy, permission) && authorizer.key(copy) === key, `the reference for ${key} is not plain JSON`);
|
|
2623
|
+
}
|
|
2624
|
+
}],
|
|
2625
|
+
["answers every request with a known outcome, without mutating it", async () => {
|
|
2626
|
+
for (const request of requests) {
|
|
2627
|
+
const outcome = await authorizer.evaluate(frozenCopy(request));
|
|
2628
|
+
expect(isOutcome(outcome), `evaluate() gave no known outcome for ${authorizer.key(request.action)}`);
|
|
2629
|
+
}
|
|
2630
|
+
}],
|
|
2631
|
+
authorizer.evaluations !== void 0 && ["keeps the request order in evaluations", async () => {
|
|
2632
|
+
const evaluations = authorizer.evaluations.bind(authorizer);
|
|
2633
|
+
const single = await Promise.all(requests.map((request) => Promise.resolve(authorizer.evaluate(request))));
|
|
2634
|
+
const batch = await evaluations(frozenCopy(requests));
|
|
2635
|
+
expect(batch.length === requests.length, `evaluations() gave ${batch.length} outcomes for ${requests.length} requests`);
|
|
2636
|
+
expect(same(batch.map((outcome) => outcome.outcome), single.map((outcome) => outcome.outcome)), "evaluations() disagrees with evaluate() for the same requests");
|
|
2637
|
+
const reversed = await evaluations(frozenCopy(requests.toReversed()));
|
|
2638
|
+
expect(same(reversed.toReversed().map((outcome) => outcome.outcome), batch.map((outcome) => outcome.outcome)), "evaluations() does not keep the request order");
|
|
2639
|
+
}],
|
|
2640
|
+
["decides from the subject, never from the input", async () => {
|
|
2641
|
+
for (const permission of options.permissions) {
|
|
2642
|
+
const plain = await authorizer.evaluate(requestFor(permission, "a"));
|
|
2643
|
+
const forgedResource = await authorizer.evaluate(requestFor(permission, "a", FORGED));
|
|
2644
|
+
const forgedContext = await authorizer.evaluate({
|
|
2645
|
+
...requestFor(permission, "a"),
|
|
2646
|
+
context: {
|
|
2647
|
+
...context,
|
|
2648
|
+
...FORGED
|
|
2649
|
+
}
|
|
2650
|
+
});
|
|
2651
|
+
for (const forged of [forgedResource, forgedContext]) expect(forged.outcome !== "granted" || plain.outcome === "granted", `input claiming a service caller was granted ${authorizer.key(permission)}`);
|
|
2652
|
+
}
|
|
2653
|
+
}],
|
|
2654
|
+
options.unknown !== void 0 && ["never grants an unknown permission", async () => {
|
|
2655
|
+
expect(!(await checkPermission({
|
|
2656
|
+
authorizer,
|
|
2657
|
+
permission: options.unknown,
|
|
2658
|
+
resource: { type: "conformance" },
|
|
2659
|
+
context: options.granted?.context
|
|
2660
|
+
})).ok, "an unknown permission was granted");
|
|
2661
|
+
}],
|
|
2662
|
+
options.granted !== void 0 && ["grants the granted permission through checkPermission", async () => {
|
|
2663
|
+
const checked = await checkPermission({
|
|
2664
|
+
authorizer,
|
|
2665
|
+
permission: options.granted.permission,
|
|
2666
|
+
resource: { type: "conformance" },
|
|
2667
|
+
context: options.granted.context
|
|
2668
|
+
});
|
|
2669
|
+
expect(checked.ok, checked.ok ? "" : `the permission was refused: ${checked.error.message}`);
|
|
2670
|
+
}]
|
|
2671
|
+
]);
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* An in-memory `Authorizer` for tests and examples: permissions come from
|
|
2675
|
+
* the subject's `role` and id only, never from the request's resource or
|
|
2676
|
+
* context, and it ignores `subject.properties.claims`.
|
|
2677
|
+
*/
|
|
2678
|
+
function staticAuthorizer(options) {
|
|
2679
|
+
const approvals = new Set(options.approvals);
|
|
2680
|
+
const evaluate = (request) => {
|
|
2681
|
+
const { subject, action } = request;
|
|
2682
|
+
if (approvals.has(action)) return {
|
|
2683
|
+
outcome: "approval-required",
|
|
2684
|
+
approval: { id: `approval:${action}` }
|
|
2685
|
+
};
|
|
2686
|
+
const role = subject.properties?.["role"];
|
|
2687
|
+
return [...options.roles?.[typeof role === "string" ? role : subject.type] ?? [], ...options.grants?.[subject.id] ?? []].includes(action) ? { outcome: "granted" } : {
|
|
2688
|
+
outcome: "denied",
|
|
2689
|
+
reason: `Permission ${action} is required`
|
|
2690
|
+
};
|
|
2691
|
+
};
|
|
2692
|
+
return defineAuthorizer({
|
|
2693
|
+
name: "static",
|
|
2694
|
+
key: (ref) => ref,
|
|
2695
|
+
evaluate,
|
|
2696
|
+
evaluations: (requests) => Promise.resolve(requests.map(evaluate))
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
//#endregion
|
|
2700
|
+
//#region src/testing/document-format.ts
|
|
2701
|
+
const STATUSES$1 = /* @__PURE__ */ new Set([
|
|
2702
|
+
"stable",
|
|
2703
|
+
"legacy",
|
|
2704
|
+
"preview"
|
|
2705
|
+
]);
|
|
2706
|
+
const SEVERITIES = /* @__PURE__ */ new Set([
|
|
2707
|
+
"error",
|
|
2708
|
+
"warning",
|
|
2709
|
+
"info"
|
|
2710
|
+
]);
|
|
2711
|
+
const CODE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
2712
|
+
const jsonSchema = (schema) => ({ "~standard": {
|
|
2713
|
+
version: 1,
|
|
2714
|
+
vendor: "better-supabase-conformance",
|
|
2715
|
+
validate: (value) => ({ value }),
|
|
2716
|
+
jsonSchema: {
|
|
2717
|
+
input: () => schema,
|
|
2718
|
+
output: () => schema
|
|
2719
|
+
}
|
|
2720
|
+
} });
|
|
2721
|
+
function sampleModel() {
|
|
2722
|
+
const customer = {
|
|
2723
|
+
type: "object",
|
|
2724
|
+
required: ["id", "name"],
|
|
2725
|
+
properties: {
|
|
2726
|
+
id: {
|
|
2727
|
+
type: "string",
|
|
2728
|
+
format: "uuid"
|
|
2729
|
+
},
|
|
2730
|
+
name: { type: "string" },
|
|
2731
|
+
note: { type: ["string", "null"] }
|
|
2732
|
+
}
|
|
2733
|
+
};
|
|
2734
|
+
return buildApiModel({ meta: {
|
|
2735
|
+
version: 1,
|
|
2736
|
+
casing: "camel",
|
|
2737
|
+
tables: {},
|
|
2738
|
+
enums: {},
|
|
2739
|
+
functions: {}
|
|
2740
|
+
} }, {
|
|
2741
|
+
info: {
|
|
2742
|
+
title: "Conformance",
|
|
2743
|
+
version: "1.0.0"
|
|
2744
|
+
},
|
|
2745
|
+
servers: [{
|
|
2746
|
+
url: "https://api.test",
|
|
2747
|
+
name: "test"
|
|
2748
|
+
}],
|
|
2749
|
+
tags: [{
|
|
2750
|
+
name: "customers",
|
|
2751
|
+
summary: "Customers"
|
|
2752
|
+
}],
|
|
2753
|
+
basePath: "/api",
|
|
2754
|
+
routes: [
|
|
2755
|
+
{
|
|
2756
|
+
method: "get",
|
|
2757
|
+
path: "/customers/{id}",
|
|
2758
|
+
operationId: "getCustomer",
|
|
2759
|
+
tags: ["customers"],
|
|
2760
|
+
output: jsonSchema(customer)
|
|
2761
|
+
},
|
|
2762
|
+
{
|
|
2763
|
+
method: "post",
|
|
2764
|
+
path: "/customers",
|
|
2765
|
+
operationId: "createCustomer",
|
|
2766
|
+
tags: ["customers"],
|
|
2767
|
+
input: jsonSchema(customer),
|
|
2768
|
+
output: jsonSchema(customer),
|
|
2769
|
+
status: 201
|
|
2770
|
+
},
|
|
2771
|
+
{
|
|
2772
|
+
method: "get",
|
|
2773
|
+
path: "/customers/changes",
|
|
2774
|
+
operationId: "streamCustomerChanges",
|
|
2775
|
+
stream: { item: jsonSchema(customer) }
|
|
2776
|
+
}
|
|
2777
|
+
],
|
|
2778
|
+
webhooks: [{
|
|
2779
|
+
id: "customer.created",
|
|
2780
|
+
payload: customer,
|
|
2781
|
+
standardWebhooks: true
|
|
2782
|
+
}],
|
|
2783
|
+
realtimeUrl: "wss://abc.supabase.co/realtime/v1/websocket",
|
|
2784
|
+
messages: { customerChanged: {
|
|
2785
|
+
id: "customerChanged",
|
|
2786
|
+
payload: customer
|
|
2787
|
+
} },
|
|
2788
|
+
channels: [{
|
|
2789
|
+
id: "customers",
|
|
2790
|
+
address: "bs:t:public.customers",
|
|
2791
|
+
protocol: "ws",
|
|
2792
|
+
messages: ["customerChanged"],
|
|
2793
|
+
operations: [{
|
|
2794
|
+
id: "onCustomerChanged",
|
|
2795
|
+
action: "receive"
|
|
2796
|
+
}]
|
|
2797
|
+
}],
|
|
2798
|
+
workflows: [{
|
|
2799
|
+
id: "createThenGet",
|
|
2800
|
+
steps: [{
|
|
2801
|
+
id: "create",
|
|
2802
|
+
operationId: "createCustomer",
|
|
2803
|
+
outputs: { id: "$response.body#/id" }
|
|
2804
|
+
}, {
|
|
2805
|
+
id: "get",
|
|
2806
|
+
operationId: "getCustomer",
|
|
2807
|
+
parameters: [{
|
|
2808
|
+
name: "id",
|
|
2809
|
+
in: "path",
|
|
2810
|
+
value: "$steps.create.outputs.id"
|
|
2811
|
+
}]
|
|
2812
|
+
}]
|
|
2813
|
+
}],
|
|
2814
|
+
security: ["bearer"]
|
|
2815
|
+
}).model;
|
|
2816
|
+
}
|
|
2817
|
+
function isPlainJson(value) {
|
|
2818
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
2819
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
2820
|
+
if (Array.isArray(value)) return value.every(isPlainJson);
|
|
2821
|
+
if (typeof value !== "object") return false;
|
|
2822
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2823
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
2824
|
+
return Object.values(value).every((entry) => entry !== void 0 && isPlainJson(entry));
|
|
2825
|
+
}
|
|
2826
|
+
function diagnosticProblem(diagnostic) {
|
|
2827
|
+
if (typeof diagnostic.code !== "string" || !CODE.test(diagnostic.code)) return `the code ${JSON.stringify(diagnostic.code)} is not kebab-case`;
|
|
2828
|
+
const severity = diagnostic.severity;
|
|
2829
|
+
if (typeof severity !== "string" || !SEVERITIES.has(severity)) return `${diagnostic.code} has the severity ${String(severity)}`;
|
|
2830
|
+
if (typeof diagnostic.message !== "string" || diagnostic.message === "") return `${diagnostic.code} has no message`;
|
|
2831
|
+
if (diagnostic.pointer !== void 0 && !/^(?:\/|$)/.test(diagnostic.pointer)) return `${diagnostic.code} has the pointer ${diagnostic.pointer}, which is not a JSON Pointer`;
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Proves a `DocumentFormat` targets API v1, lists versions with pins and a
|
|
2835
|
+
* known status, defaults to a version it lists that is not a preview, and,
|
|
2836
|
+
* for every version: lowers the model without mutating it (also when the
|
|
2837
|
+
* model is frozen), renders the same document twice, renders plain JSON or
|
|
2838
|
+
* text, reports diagnostics with a kebab-case code, a severity and a
|
|
2839
|
+
* message, finds no error in its own output with `lint`, and names a bare,
|
|
2840
|
+
* stable file.
|
|
2841
|
+
*/
|
|
2842
|
+
function testDocumentFormat(format, options = {}) {
|
|
2843
|
+
const subject = format;
|
|
2844
|
+
const id = subject.id;
|
|
2845
|
+
const defaultVersion = subject.defaultVersion;
|
|
2846
|
+
const versions = options.versions ?? Object.keys(subject.versions);
|
|
2847
|
+
let model;
|
|
2848
|
+
const modelOf = () => model ??= options.model ?? sampleModel();
|
|
2849
|
+
const lower = (version, input) => {
|
|
2850
|
+
const diagnostics = [];
|
|
2851
|
+
return {
|
|
2852
|
+
document: subject.lower(input, version, { report: (diagnostic) => {
|
|
2853
|
+
diagnostics.push(diagnostic);
|
|
2854
|
+
} }),
|
|
2855
|
+
diagnostics
|
|
2856
|
+
};
|
|
2857
|
+
};
|
|
2858
|
+
return conform(`DocumentFormat "${String(id)}"`, [
|
|
2859
|
+
["targets API v1 with an id", () => {
|
|
2860
|
+
const version = subject.apiVersion;
|
|
2861
|
+
expect(version === 1, `apiVersion is ${String(version)}`);
|
|
2862
|
+
expect(typeof id === "string" && id.length > 0, "id must be a non-empty string");
|
|
2863
|
+
}],
|
|
2864
|
+
["lists versions with a pin and a known status", () => {
|
|
2865
|
+
const listed = Object.entries(subject.versions);
|
|
2866
|
+
expect(listed.length > 0, "versions lists no version");
|
|
2867
|
+
for (const [version, entry] of listed) {
|
|
2868
|
+
const status = entry.status;
|
|
2869
|
+
expect(typeof entry.pin === "string" && entry.pin.length > 0, `version ${version} has no pin`);
|
|
2870
|
+
expect(typeof status === "string" && STATUSES$1.has(status), `version ${version} has the status ${String(status)}`);
|
|
2871
|
+
}
|
|
2872
|
+
for (const version of versions) expect(Object.hasOwn(subject.versions, version), `options.versions names ${version}, which the format does not list`);
|
|
2873
|
+
}],
|
|
2874
|
+
["defaults to a listed version that is not a preview", () => {
|
|
2875
|
+
const entry = Object.hasOwn(subject.versions, subject.defaultVersion) ? subject.versions[subject.defaultVersion] : void 0;
|
|
2876
|
+
expect(entry !== void 0, `the default version ${String(defaultVersion)} is not in versions`);
|
|
2877
|
+
expect(entry.status !== "preview", `the default version ${String(defaultVersion)} is a preview`);
|
|
2878
|
+
}],
|
|
2879
|
+
["lowers every version without mutating the model", () => {
|
|
2880
|
+
const original = modelOf();
|
|
2881
|
+
const before = JSON.stringify(original);
|
|
2882
|
+
for (const version of versions) {
|
|
2883
|
+
lower(version, original);
|
|
2884
|
+
expect(JSON.stringify(original) === before, `lower(model, "${version}") changed the model`);
|
|
2885
|
+
lower(version, frozenCopy(original));
|
|
2886
|
+
}
|
|
2887
|
+
}],
|
|
2888
|
+
["renders the same document twice", () => {
|
|
2889
|
+
for (const version of versions) {
|
|
2890
|
+
const first = lower(version, structuredClone(modelOf()));
|
|
2891
|
+
const second = lower(version, structuredClone(modelOf()));
|
|
2892
|
+
expect(same(first.document, second.document), `lower(model, "${version}") is not deterministic`);
|
|
2893
|
+
expect(same(first.diagnostics, second.diagnostics), `lower(model, "${version}") reports different diagnostics each time`);
|
|
2894
|
+
}
|
|
2895
|
+
}],
|
|
2896
|
+
["renders plain JSON or text", () => {
|
|
2897
|
+
for (const version of versions) {
|
|
2898
|
+
const { document } = lower(version, modelOf());
|
|
2899
|
+
expect(typeof document === "string" || typeof document === "object" && document !== null && isPlainJson(document), `lower(model, "${version}") returned something other than a JSON object or a string`);
|
|
2900
|
+
}
|
|
2901
|
+
}],
|
|
2902
|
+
["reports diagnostics with a code, a severity and a message", () => {
|
|
2903
|
+
for (const version of versions) {
|
|
2904
|
+
const { document, diagnostics } = lower(version, modelOf());
|
|
2905
|
+
const linted = subject.lint?.(document, version) ?? [];
|
|
2906
|
+
for (const diagnostic of [...diagnostics, ...linted]) {
|
|
2907
|
+
const problem = diagnosticProblem(diagnostic);
|
|
2908
|
+
expect(problem === void 0, `${version}: ${problem ?? ""}`);
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
}],
|
|
2912
|
+
subject.lint !== void 0 && ["finds no error in its own output", () => {
|
|
2913
|
+
for (const version of versions) {
|
|
2914
|
+
const { document, diagnostics } = lower(version, modelOf());
|
|
2915
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) continue;
|
|
2916
|
+
const errors = (subject.lint?.(document, version) ?? []).filter((diagnostic) => diagnostic.severity === "error");
|
|
2917
|
+
expect(errors.length === 0, `lint(lower(model, "${version}")) reports ${errors.map((error) => error.code).join(", ")}`);
|
|
2918
|
+
}
|
|
2919
|
+
}],
|
|
2920
|
+
["names a bare, stable file per version", () => {
|
|
2921
|
+
for (const version of versions) {
|
|
2922
|
+
const name = subject.fileName(version);
|
|
2923
|
+
expect(typeof name === "string" && /^[^/\\]+\.[A-Za-z0-9]+$/.test(name), `fileName("${version}") is ${JSON.stringify(name)}, not a file name with an extension`);
|
|
2924
|
+
expect(subject.fileName(version) === name, `fileName("${version}") changes between calls`);
|
|
2925
|
+
}
|
|
2926
|
+
}]
|
|
2927
|
+
]);
|
|
2928
|
+
}
|
|
2929
|
+
//#endregion
|
|
1606
2930
|
//#region src/testing/generator.ts
|
|
1607
2931
|
function relativeImport(from, to) {
|
|
1608
2932
|
const fromParts = from.split("/").slice(0, -1);
|
|
@@ -1842,7 +3166,8 @@ function sample(channel, email) {
|
|
|
1842
3166
|
priority: null,
|
|
1843
3167
|
createdAt,
|
|
1844
3168
|
readAt: null,
|
|
1845
|
-
resolvedAt: null
|
|
3169
|
+
resolvedAt: null,
|
|
3170
|
+
fields: Object.freeze({})
|
|
1846
3171
|
}),
|
|
1847
3172
|
text: Object.freeze({ title: "Conformance check" })
|
|
1848
3173
|
});
|
|
@@ -2078,7 +3403,7 @@ function testChatState(state, options) {
|
|
|
2078
3403
|
"d"
|
|
2079
3404
|
]) await state.appendToList(key, value, { maxLength: 3 });
|
|
2080
3405
|
const list = await state.getList(key);
|
|
2081
|
-
expect(list.join() === "b,c,d", `list is ${list.join()}`);
|
|
3406
|
+
expect(list.join(",") === "b,c,d", `list is ${list.join(",")}`);
|
|
2082
3407
|
expect((await state.getList(fresh())).length === 0, "missing list is not empty");
|
|
2083
3408
|
}],
|
|
2084
3409
|
["queues per thread, keeps the newest maxSize and reports the depth", async () => {
|
|
@@ -2350,4 +3675,327 @@ function testCredentialProvider(provider, options) {
|
|
|
2350
3675
|
]);
|
|
2351
3676
|
}
|
|
2352
3677
|
//#endregion
|
|
2353
|
-
|
|
3678
|
+
//#region src/testing/act-cases.ts
|
|
3679
|
+
/**
|
|
3680
|
+
* The RFC 8693 `act` chains better-supabase accepts and refuses, as plain
|
|
3681
|
+
* JSON (`better-supabase/testing/act-cases.json`), so another library that
|
|
3682
|
+
* reads the same claims can run the same cases.
|
|
3683
|
+
*/
|
|
3684
|
+
const actCases = {
|
|
3685
|
+
version: 1,
|
|
3686
|
+
cases: [
|
|
3687
|
+
{
|
|
3688
|
+
"name": "no actor",
|
|
3689
|
+
"claims": {},
|
|
3690
|
+
"valid": true,
|
|
3691
|
+
"kind": null,
|
|
3692
|
+
"readOnly": null,
|
|
3693
|
+
"tools": null,
|
|
3694
|
+
"mayWrite": true
|
|
3695
|
+
},
|
|
3696
|
+
{
|
|
3697
|
+
"name": "oauth client_id",
|
|
3698
|
+
"claims": { "client_id": "client-1" },
|
|
3699
|
+
"valid": true,
|
|
3700
|
+
"kind": "oauth-client",
|
|
3701
|
+
"readOnly": null,
|
|
3702
|
+
"tools": null,
|
|
3703
|
+
"mayWrite": true
|
|
3704
|
+
},
|
|
3705
|
+
{
|
|
3706
|
+
"name": "chain without kind",
|
|
3707
|
+
"claims": { "act": {
|
|
3708
|
+
"sub": "agent-runner",
|
|
3709
|
+
"act": { "sub": "mcp-client-42" }
|
|
3710
|
+
} },
|
|
3711
|
+
"valid": true,
|
|
3712
|
+
"kind": "oauth-client",
|
|
3713
|
+
"readOnly": null,
|
|
3714
|
+
"tools": null,
|
|
3715
|
+
"mayWrite": true
|
|
3716
|
+
},
|
|
3717
|
+
{
|
|
3718
|
+
"name": "support, read_only missing",
|
|
3719
|
+
"claims": { "act": {
|
|
3720
|
+
"kind": "support",
|
|
3721
|
+
"sub": "admin-1",
|
|
3722
|
+
"session_id": "session-1"
|
|
3723
|
+
} },
|
|
3724
|
+
"valid": true,
|
|
3725
|
+
"kind": "support",
|
|
3726
|
+
"readOnly": true,
|
|
3727
|
+
"tools": null,
|
|
3728
|
+
"mayWrite": false
|
|
3729
|
+
},
|
|
3730
|
+
{
|
|
3731
|
+
"name": "support, read_only true",
|
|
3732
|
+
"claims": { "act": {
|
|
3733
|
+
"kind": "support",
|
|
3734
|
+
"sub": "admin-1",
|
|
3735
|
+
"session_id": "session-1",
|
|
3736
|
+
"read_only": true
|
|
3737
|
+
} },
|
|
3738
|
+
"valid": true,
|
|
3739
|
+
"kind": "support",
|
|
3740
|
+
"readOnly": true,
|
|
3741
|
+
"tools": null,
|
|
3742
|
+
"mayWrite": false
|
|
3743
|
+
},
|
|
3744
|
+
{
|
|
3745
|
+
"name": "support, read_only false",
|
|
3746
|
+
"claims": { "act": {
|
|
3747
|
+
"kind": "support",
|
|
3748
|
+
"sub": "admin-1",
|
|
3749
|
+
"session_id": "session-1",
|
|
3750
|
+
"read_only": false
|
|
3751
|
+
} },
|
|
3752
|
+
"valid": true,
|
|
3753
|
+
"kind": "support",
|
|
3754
|
+
"readOnly": false,
|
|
3755
|
+
"tools": null,
|
|
3756
|
+
"mayWrite": true
|
|
3757
|
+
},
|
|
3758
|
+
{
|
|
3759
|
+
"name": "support, read_only as a string",
|
|
3760
|
+
"claims": { "act": {
|
|
3761
|
+
"kind": "support",
|
|
3762
|
+
"sub": "admin-1",
|
|
3763
|
+
"session_id": "session-1",
|
|
3764
|
+
"read_only": "false"
|
|
3765
|
+
} },
|
|
3766
|
+
"valid": false,
|
|
3767
|
+
"kind": null,
|
|
3768
|
+
"readOnly": null,
|
|
3769
|
+
"tools": null,
|
|
3770
|
+
"mayWrite": false
|
|
3771
|
+
},
|
|
3772
|
+
{
|
|
3773
|
+
"name": "support without session_id",
|
|
3774
|
+
"claims": { "act": {
|
|
3775
|
+
"kind": "support",
|
|
3776
|
+
"sub": "admin-1",
|
|
3777
|
+
"read_only": false
|
|
3778
|
+
} },
|
|
3779
|
+
"valid": false,
|
|
3780
|
+
"kind": null,
|
|
3781
|
+
"readOnly": null,
|
|
3782
|
+
"tools": null,
|
|
3783
|
+
"mayWrite": true
|
|
3784
|
+
},
|
|
3785
|
+
{
|
|
3786
|
+
"name": "session_id without kind (0.5.0 support)",
|
|
3787
|
+
"claims": { "act": {
|
|
3788
|
+
"sub": "admin-1",
|
|
3789
|
+
"session_id": "session-1"
|
|
3790
|
+
} },
|
|
3791
|
+
"valid": false,
|
|
3792
|
+
"kind": null,
|
|
3793
|
+
"readOnly": null,
|
|
3794
|
+
"tools": null,
|
|
3795
|
+
"mayWrite": false
|
|
3796
|
+
},
|
|
3797
|
+
{
|
|
3798
|
+
"name": "impersonation",
|
|
3799
|
+
"claims": { "act": {
|
|
3800
|
+
"kind": "impersonation",
|
|
3801
|
+
"sub": "admin-1",
|
|
3802
|
+
"reason": "ticket 42"
|
|
3803
|
+
} },
|
|
3804
|
+
"valid": true,
|
|
3805
|
+
"kind": "impersonation",
|
|
3806
|
+
"readOnly": null,
|
|
3807
|
+
"tools": null,
|
|
3808
|
+
"mayWrite": false
|
|
3809
|
+
},
|
|
3810
|
+
{
|
|
3811
|
+
"name": "impersonation, read_only false",
|
|
3812
|
+
"claims": { "act": {
|
|
3813
|
+
"kind": "impersonation",
|
|
3814
|
+
"sub": "admin-1",
|
|
3815
|
+
"read_only": false
|
|
3816
|
+
} },
|
|
3817
|
+
"valid": true,
|
|
3818
|
+
"kind": "impersonation",
|
|
3819
|
+
"readOnly": null,
|
|
3820
|
+
"tools": null,
|
|
3821
|
+
"mayWrite": false
|
|
3822
|
+
},
|
|
3823
|
+
{
|
|
3824
|
+
"name": "agent, every tool",
|
|
3825
|
+
"claims": { "act": {
|
|
3826
|
+
"kind": "agent",
|
|
3827
|
+
"sub": "agent-7"
|
|
3828
|
+
} },
|
|
3829
|
+
"valid": true,
|
|
3830
|
+
"kind": "agent",
|
|
3831
|
+
"readOnly": false,
|
|
3832
|
+
"tools": null,
|
|
3833
|
+
"mayWrite": true
|
|
3834
|
+
},
|
|
3835
|
+
{
|
|
3836
|
+
"name": "agent, listed tools",
|
|
3837
|
+
"claims": { "act": {
|
|
3838
|
+
"kind": "agent",
|
|
3839
|
+
"sub": "agent-7",
|
|
3840
|
+
"tools": ["search_knowledge"]
|
|
3841
|
+
} },
|
|
3842
|
+
"valid": true,
|
|
3843
|
+
"kind": "agent",
|
|
3844
|
+
"readOnly": false,
|
|
3845
|
+
"tools": ["search_knowledge"],
|
|
3846
|
+
"mayWrite": true
|
|
3847
|
+
},
|
|
3848
|
+
{
|
|
3849
|
+
"name": "agent, no tools",
|
|
3850
|
+
"claims": { "act": {
|
|
3851
|
+
"kind": "agent",
|
|
3852
|
+
"sub": "agent-7",
|
|
3853
|
+
"tools": []
|
|
3854
|
+
} },
|
|
3855
|
+
"valid": true,
|
|
3856
|
+
"kind": "agent",
|
|
3857
|
+
"readOnly": false,
|
|
3858
|
+
"tools": [],
|
|
3859
|
+
"mayWrite": true
|
|
3860
|
+
},
|
|
3861
|
+
{
|
|
3862
|
+
"name": "agent, read_only true",
|
|
3863
|
+
"claims": { "act": {
|
|
3864
|
+
"kind": "agent",
|
|
3865
|
+
"sub": "agent-7",
|
|
3866
|
+
"read_only": true
|
|
3867
|
+
} },
|
|
3868
|
+
"valid": true,
|
|
3869
|
+
"kind": "agent",
|
|
3870
|
+
"readOnly": true,
|
|
3871
|
+
"tools": null,
|
|
3872
|
+
"mayWrite": false
|
|
3873
|
+
},
|
|
3874
|
+
{
|
|
3875
|
+
"name": "agent, read_only false",
|
|
3876
|
+
"claims": { "act": {
|
|
3877
|
+
"kind": "agent",
|
|
3878
|
+
"sub": "agent-7",
|
|
3879
|
+
"read_only": false
|
|
3880
|
+
} },
|
|
3881
|
+
"valid": true,
|
|
3882
|
+
"kind": "agent",
|
|
3883
|
+
"readOnly": false,
|
|
3884
|
+
"tools": null,
|
|
3885
|
+
"mayWrite": true
|
|
3886
|
+
},
|
|
3887
|
+
{
|
|
3888
|
+
"name": "agent, read_only as a string",
|
|
3889
|
+
"claims": { "act": {
|
|
3890
|
+
"kind": "agent",
|
|
3891
|
+
"sub": "agent-7",
|
|
3892
|
+
"read_only": "true"
|
|
3893
|
+
} },
|
|
3894
|
+
"valid": false,
|
|
3895
|
+
"kind": null,
|
|
3896
|
+
"readOnly": null,
|
|
3897
|
+
"tools": null,
|
|
3898
|
+
"mayWrite": false
|
|
3899
|
+
},
|
|
3900
|
+
{
|
|
3901
|
+
"name": "agent over an OAuth client",
|
|
3902
|
+
"claims": {
|
|
3903
|
+
"scope": "posts:read",
|
|
3904
|
+
"act": {
|
|
3905
|
+
"kind": "agent",
|
|
3906
|
+
"sub": "agent-7",
|
|
3907
|
+
"tools": ["search_knowledge"],
|
|
3908
|
+
"act": { "sub": "mcp-client-42" }
|
|
3909
|
+
}
|
|
3910
|
+
},
|
|
3911
|
+
"valid": true,
|
|
3912
|
+
"kind": "agent",
|
|
3913
|
+
"readOnly": false,
|
|
3914
|
+
"tools": ["search_knowledge"],
|
|
3915
|
+
"mayWrite": true
|
|
3916
|
+
},
|
|
3917
|
+
{
|
|
3918
|
+
"name": "agent, tools not a list",
|
|
3919
|
+
"claims": { "act": {
|
|
3920
|
+
"kind": "agent",
|
|
3921
|
+
"sub": "agent-7",
|
|
3922
|
+
"tools": "search_knowledge"
|
|
3923
|
+
} },
|
|
3924
|
+
"valid": false,
|
|
3925
|
+
"kind": null,
|
|
3926
|
+
"readOnly": null,
|
|
3927
|
+
"tools": null,
|
|
3928
|
+
"mayWrite": true
|
|
3929
|
+
},
|
|
3930
|
+
{
|
|
3931
|
+
"name": "agent, empty tool name",
|
|
3932
|
+
"claims": { "act": {
|
|
3933
|
+
"kind": "agent",
|
|
3934
|
+
"sub": "agent-7",
|
|
3935
|
+
"tools": [""]
|
|
3936
|
+
} },
|
|
3937
|
+
"valid": false,
|
|
3938
|
+
"kind": null,
|
|
3939
|
+
"readOnly": null,
|
|
3940
|
+
"tools": null,
|
|
3941
|
+
"mayWrite": true
|
|
3942
|
+
},
|
|
3943
|
+
{
|
|
3944
|
+
"name": "agent with session_id",
|
|
3945
|
+
"claims": { "act": {
|
|
3946
|
+
"kind": "agent",
|
|
3947
|
+
"sub": "agent-7",
|
|
3948
|
+
"session_id": "session-1"
|
|
3949
|
+
} },
|
|
3950
|
+
"valid": false,
|
|
3951
|
+
"kind": null,
|
|
3952
|
+
"readOnly": null,
|
|
3953
|
+
"tools": null,
|
|
3954
|
+
"mayWrite": true
|
|
3955
|
+
},
|
|
3956
|
+
{
|
|
3957
|
+
"name": "unknown kind",
|
|
3958
|
+
"claims": { "act": {
|
|
3959
|
+
"kind": "robot",
|
|
3960
|
+
"sub": "robot-1"
|
|
3961
|
+
} },
|
|
3962
|
+
"valid": false,
|
|
3963
|
+
"kind": null,
|
|
3964
|
+
"readOnly": null,
|
|
3965
|
+
"tools": null,
|
|
3966
|
+
"mayWrite": false
|
|
3967
|
+
},
|
|
3968
|
+
{
|
|
3969
|
+
"name": "act not an object",
|
|
3970
|
+
"claims": { "act": "agent-7" },
|
|
3971
|
+
"valid": false,
|
|
3972
|
+
"kind": null,
|
|
3973
|
+
"readOnly": null,
|
|
3974
|
+
"tools": null,
|
|
3975
|
+
"mayWrite": true
|
|
3976
|
+
},
|
|
3977
|
+
{
|
|
3978
|
+
"name": "act without sub",
|
|
3979
|
+
"claims": { "act": { "kind": "agent" } },
|
|
3980
|
+
"valid": false,
|
|
3981
|
+
"kind": null,
|
|
3982
|
+
"readOnly": null,
|
|
3983
|
+
"tools": null,
|
|
3984
|
+
"mayWrite": true
|
|
3985
|
+
},
|
|
3986
|
+
{
|
|
3987
|
+
"name": "nested level without sub",
|
|
3988
|
+
"claims": { "act": {
|
|
3989
|
+
"sub": "agent-runner",
|
|
3990
|
+
"act": { "sub": "" }
|
|
3991
|
+
} },
|
|
3992
|
+
"valid": false,
|
|
3993
|
+
"kind": null,
|
|
3994
|
+
"readOnly": null,
|
|
3995
|
+
"tools": null,
|
|
3996
|
+
"mayWrite": true
|
|
3997
|
+
}
|
|
3998
|
+
]
|
|
3999
|
+
};
|
|
4000
|
+
//#endregion
|
|
4001
|
+
export { ConformanceError, LOCAL_JWT_SECRET, PROBE_INITIAL, actCases, asUser, createTestSigner, defineSeed, expectDbBudget, expectInstant, expectTenantIsolation, isSeed, liteStack, localAuth, localSigningKey, signLocalJwt, signTestJwt, signTestJwtWithKey, staticAuthorizer, supabaseClaimFixtures, supabaseInvalidClaimFixtures, testAdapter, testAiTaskRunner, testAuthResolver, testAuthorizationHooks, testAuthorizationProvider, testAuthorizer, testBlockTransportMiddleware, testBuilderStarter, testCacheAdapter, testChatState, testControllerBinding, testCredentialProvider, testDeliveryTransport, testDocumentFormat, testEmbedder, testEveDocumentBackend, testEventSink, testExecutor, testGenerator, testGraphCompiler, testIncomingDestination, testNotificationChannel, testPaymentProvider, testPlugin, testQueueBackend, testStreamStore, testSupportSessionStore, testTaxProvider, testWebhookSecretStore, testWebhookSigner, testWebhookTransport };
|