@btst/stack 3.0.0-rc.2 → 3.0.0-rc.3
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 +118 -38
- package/dist/api/index.cjs +133 -29
- package/dist/api/index.d.cts +14 -32
- package/dist/api/index.d.mts +14 -32
- package/dist/api/index.d.ts +14 -32
- package/dist/api/index.mjs +133 -28
- package/dist/authorization/client.cjs +154 -0
- package/dist/authorization/client.d.cts +70 -0
- package/dist/authorization/client.d.mts +70 -0
- package/dist/authorization/client.d.ts +70 -0
- package/dist/authorization/client.mjs +152 -0
- package/dist/authorization/index.cjs +371 -0
- package/dist/authorization/index.d.cts +201 -0
- package/dist/authorization/index.d.mts +201 -0
- package/dist/authorization/index.d.ts +201 -0
- package/dist/authorization/index.mjs +365 -0
- package/dist/authorization/remote.cjs +117 -0
- package/dist/authorization/remote.d.cts +79 -0
- package/dist/authorization/remote.d.mts +79 -0
- package/dist/authorization/remote.d.ts +79 -0
- package/dist/authorization/remote.mjs +111 -0
- package/dist/authorization/server.cjs +77 -0
- package/dist/authorization/server.d.cts +51 -0
- package/dist/authorization/server.d.mts +51 -0
- package/dist/authorization/server.d.ts +51 -0
- package/dist/authorization/server.mjs +73 -0
- package/dist/client/components/index.cjs +1 -0
- package/dist/client/components/index.d.cts +23 -6
- package/dist/client/components/index.d.mts +23 -6
- package/dist/client/components/index.d.ts +23 -6
- package/dist/client/components/index.mjs +1 -1
- package/dist/client/hooks/index.cjs +8 -0
- package/dist/client/hooks/index.d.cts +25 -0
- package/dist/client/hooks/index.d.mts +25 -0
- package/dist/client/hooks/index.d.ts +25 -0
- package/dist/client/hooks/index.mjs +2 -0
- package/dist/client/index.cjs +38 -7
- package/dist/client/index.d.cts +30 -100
- package/dist/client/index.d.mts +30 -100
- package/dist/client/index.d.ts +30 -100
- package/dist/client/index.mjs +38 -7
- package/dist/client/server.cjs +96 -0
- package/dist/client/server.d.cts +57 -0
- package/dist/client/server.d.mts +57 -0
- package/dist/client/server.d.ts +57 -0
- package/dist/client/server.mjs +92 -0
- package/dist/components/auto-form/index.d.cts +2 -2
- package/dist/components/auto-form/index.d.mts +2 -2
- package/dist/components/auto-form/index.d.ts +2 -2
- package/dist/components/form-builder/index.d.cts +1 -1
- package/dist/components/form-builder/index.d.mts +1 -1
- package/dist/components/form-builder/index.d.ts +1 -1
- package/dist/components/stepped-auto-form/index.d.cts +1 -1
- package/dist/components/stepped-auto-form/index.d.mts +1 -1
- package/dist/components/stepped-auto-form/index.d.ts +1 -1
- package/dist/context/index.cjs +8 -2
- package/dist/context/index.d.cts +111 -89
- package/dist/context/index.d.mts +111 -89
- package/dist/context/index.d.ts +111 -89
- package/dist/context/index.mjs +3 -1
- package/dist/index.cjs +1 -2
- package/dist/index.d.cts +8 -3
- package/dist/index.d.mts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.mjs +1 -1
- package/dist/next/index.d.cts +19 -7
- package/dist/next/index.d.mts +19 -7
- package/dist/next/index.d.ts +19 -7
- package/dist/next/server.cjs +7 -0
- package/dist/next/server.d.cts +56 -0
- package/dist/next/server.d.mts +56 -0
- package/dist/next/server.d.ts +56 -0
- package/dist/next/server.mjs +1 -0
- package/dist/packages/stack/src/authorization/json.cjs +57 -0
- package/dist/packages/stack/src/authorization/json.mjs +55 -0
- package/dist/packages/stack/src/client/components/compose.cjs +91 -9
- package/dist/packages/stack/src/client/components/compose.mjs +92 -11
- package/dist/packages/stack/src/client/runtime.cjs +295 -0
- package/dist/packages/stack/src/client/runtime.mjs +293 -0
- package/dist/packages/stack/src/context/auth.cjs +141 -73
- package/dist/packages/stack/src/context/auth.mjs +139 -73
- package/dist/packages/stack/src/context/provider.cjs +11 -5
- package/dist/packages/stack/src/context/provider.mjs +11 -5
- package/dist/packages/stack/src/context/router.cjs +9 -0
- package/dist/packages/stack/src/context/router.mjs +7 -0
- package/dist/packages/stack/src/context/site-navigation.cjs +40 -0
- package/dist/packages/stack/src/context/site-navigation.mjs +38 -0
- package/dist/packages/stack/src/next/layout.cjs +27 -0
- package/dist/packages/stack/src/next/layout.mjs +25 -0
- package/dist/packages/stack/src/plugin-registration.cjs +59 -0
- package/dist/packages/stack/src/plugin-registration.mjs +57 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/lifecycle-migrations.cjs +18 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/lifecycle-migrations.mjs +16 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.cjs +1341 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +1333 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +82 -640
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +78 -641
- package/dist/packages/stack/src/plugins/ai-chat/api/serializers.cjs +23 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/serializers.mjs +20 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +183 -111
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +187 -115
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +562 -116
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +567 -121
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +6 -6
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +6 -6
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.cjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.mjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.cjs +45 -29
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.mjs +46 -30
- package/dist/packages/stack/src/plugins/ai-chat/client/components/pages/404-page.cjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/components/pages/404-page.mjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/components/pages/chat-page.cjs +46 -7
- package/dist/packages/stack/src/plugins/ai-chat/client/components/pages/chat-page.internal.cjs +5 -3
- package/dist/packages/stack/src/plugins/ai-chat/client/components/pages/chat-page.internal.mjs +5 -3
- package/dist/packages/stack/src/plugins/ai-chat/client/components/pages/chat-page.mjs +48 -9
- package/dist/packages/stack/src/plugins/ai-chat/client/components/shared/default-error.cjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/components/shared/default-error.mjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/components/tool-call-display.cjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/components/tool-call-display.mjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/hooks/ai-chat-resource.cjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/hooks/ai-chat-resource.mjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/hooks/chat-hooks.cjs +179 -19
- package/dist/packages/stack/src/plugins/ai-chat/client/hooks/chat-hooks.mjs +180 -21
- package/dist/packages/stack/src/plugins/ai-chat/client/navigation.cjs +11 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/navigation.mjs +9 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/overrides.cjs +24 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/overrides.mjs +21 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/plugin.cjs +152 -82
- package/dist/packages/stack/src/plugins/ai-chat/client/plugin.mjs +152 -82
- package/dist/packages/stack/src/plugins/api/create-endpoint.cjs +27 -9
- package/dist/packages/stack/src/plugins/api/create-endpoint.mjs +27 -10
- package/dist/packages/stack/src/plugins/api/endpoint-inventory.cjs +125 -0
- package/dist/packages/stack/src/plugins/api/endpoint-inventory.mjs +123 -0
- package/dist/packages/stack/src/plugins/api/operation.cjs +208 -0
- package/dist/packages/stack/src/plugins/api/operation.mjs +198 -0
- package/dist/packages/stack/src/plugins/blog/api/lifecycle-migrations.cjs +17 -0
- package/dist/packages/stack/src/plugins/blog/api/lifecycle-migrations.mjs +15 -0
- package/dist/packages/stack/src/plugins/blog/api/mutations.cjs +10 -2
- package/dist/packages/stack/src/plugins/blog/api/mutations.mjs +10 -2
- package/dist/packages/stack/src/plugins/blog/api/operations.cjs +455 -0
- package/dist/packages/stack/src/plugins/blog/api/operations.mjs +448 -0
- package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +51 -298
- package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +55 -304
- package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.cjs +82 -67
- package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.mjs +84 -69
- package/dist/packages/stack/src/plugins/blog/client/components/pages/404-page.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/404-page.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.cjs +22 -4
- package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.mjs +23 -5
- package/dist/packages/stack/src/plugins/blog/client/components/pages/home-page.cjs +6 -2
- package/dist/packages/stack/src/plugins/blog/client/components/pages/home-page.internal.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/home-page.internal.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/home-page.mjs +6 -2
- package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.cjs +4 -2
- package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.mjs +4 -2
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.cjs +30 -3
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.mjs +31 -4
- package/dist/packages/stack/src/plugins/blog/client/components/pages/tag-page.cjs +4 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/tag-page.internal.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/tag-page.internal.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/tag-page.mjs +4 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/collapsible-tag-list.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/collapsible-tag-list.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/default-error.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/default-error.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/on-this-page.cjs +3 -2
- package/dist/packages/stack/src/plugins/blog/client/components/shared/on-this-page.mjs +3 -2
- package/dist/packages/stack/src/plugins/blog/client/components/shared/page-wrapper.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/page-wrapper.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/post-card.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/post-card.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/posts-list.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/posts-list.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/recent-posts-carousel.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/recent-posts-carousel.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/components/shared/search-input.cjs +2 -2
- package/dist/packages/stack/src/plugins/blog/client/components/shared/search-input.mjs +1 -1
- package/dist/packages/stack/src/plugins/blog/client/constants.cjs +5 -0
- package/dist/packages/stack/src/plugins/blog/client/constants.mjs +3 -0
- package/dist/packages/stack/src/plugins/blog/client/hooks/blog-resource.cjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/hooks/blog-resource.mjs +2 -1
- package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +169 -163
- package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +169 -163
- package/dist/packages/stack/src/plugins/client/resource/hooks.cjs +31 -16
- package/dist/packages/stack/src/plugins/client/resource/hooks.mjs +31 -16
- package/dist/packages/stack/src/plugins/client/resource/internal.cjs +66 -52
- package/dist/packages/stack/src/plugins/client/resource/internal.mjs +66 -52
- package/dist/packages/stack/src/plugins/client/resource/use-form.cjs +20 -17
- package/dist/packages/stack/src/plugins/client/resource/use-form.mjs +20 -17
- package/dist/packages/stack/src/plugins/client/resource/use-select.cjs +39 -33
- package/dist/packages/stack/src/plugins/client/resource/use-select.mjs +39 -33
- package/dist/packages/stack/src/plugins/cms/api/lifecycle-migrations.cjs +13 -0
- package/dist/packages/stack/src/plugins/cms/api/lifecycle-migrations.mjs +11 -0
- package/dist/packages/stack/src/plugins/cms/api/operations.cjs +1763 -0
- package/dist/packages/stack/src/plugins/cms/api/operations.mjs +1756 -0
- package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +165 -752
- package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +164 -755
- package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.cjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.mjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.cjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.mjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/components/forms/relation-field.cjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/components/forms/relation-field.mjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/components/inverse-relations-panel.cjs +23 -13
- package/dist/packages/stack/src/plugins/cms/client/components/inverse-relations-panel.mjs +24 -14
- package/dist/packages/stack/src/plugins/cms/client/components/pages/404-page.cjs +5 -4
- package/dist/packages/stack/src/plugins/cms/client/components/pages/404-page.mjs +6 -5
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.cjs +40 -7
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.cjs +15 -9
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.mjs +14 -8
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.mjs +41 -8
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.cjs +7 -1
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.internal.cjs +42 -29
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.internal.mjs +40 -27
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.mjs +7 -1
- package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.cjs +4 -1
- package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.internal.cjs +13 -7
- package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.internal.mjs +12 -6
- package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.mjs +4 -1
- package/dist/packages/stack/src/plugins/cms/client/components/shared/page-wrapper.cjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/components/shared/page-wrapper.mjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/constants.cjs +5 -0
- package/dist/packages/stack/src/plugins/cms/client/constants.mjs +3 -0
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-resource.cjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-resource.mjs +2 -1
- package/dist/packages/stack/src/plugins/cms/client/overrides.cjs +26 -0
- package/dist/packages/stack/src/plugins/cms/client/overrides.mjs +24 -0
- package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +102 -81
- package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +103 -82
- package/dist/packages/stack/src/plugins/comments/api/getters.cjs +6 -2
- package/dist/packages/stack/src/plugins/comments/api/getters.mjs +6 -3
- package/dist/packages/stack/src/plugins/comments/api/lifecycle-migrations.cjs +18 -0
- package/dist/packages/stack/src/plugins/comments/api/lifecycle-migrations.mjs +16 -0
- package/dist/packages/stack/src/plugins/comments/api/mutations.cjs +179 -68
- package/dist/packages/stack/src/plugins/comments/api/mutations.mjs +179 -68
- package/dist/packages/stack/src/plugins/comments/api/operations.cjs +453 -0
- package/dist/packages/stack/src/plugins/comments/api/operations.mjs +445 -0
- package/dist/packages/stack/src/plugins/comments/api/plugin.cjs +51 -276
- package/dist/packages/stack/src/plugins/comments/api/plugin.mjs +48 -277
- package/dist/packages/stack/src/plugins/comments/client/components/comment-count.cjs +32 -5
- package/dist/packages/stack/src/plugins/comments/client/components/comment-count.mjs +34 -7
- package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.cjs +160 -104
- package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.mjs +161 -105
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.cjs +14 -2
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.internal.cjs +141 -71
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.internal.mjs +142 -72
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.mjs +14 -2
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-state.cjs +12 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-state.mjs +9 -0
- package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.cjs +2 -1
- package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.internal.cjs +40 -26
- package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.internal.mjs +40 -26
- package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.mjs +2 -1
- package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.cjs +9 -1
- package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.internal.cjs +25 -20
- package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.internal.mjs +26 -21
- package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.mjs +9 -1
- package/dist/packages/stack/src/plugins/comments/client/components/shared/page-wrapper.cjs +2 -1
- package/dist/packages/stack/src/plugins/comments/client/components/shared/page-wrapper.mjs +2 -1
- package/dist/packages/stack/src/plugins/comments/client/components/shared/pagination.cjs +2 -1
- package/dist/packages/stack/src/plugins/comments/client/components/shared/pagination.mjs +2 -1
- package/dist/packages/stack/src/plugins/comments/client/constants.cjs +5 -0
- package/dist/packages/stack/src/plugins/comments/client/constants.mjs +3 -0
- package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.cjs +323 -290
- package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.mjs +325 -292
- package/dist/packages/stack/src/plugins/comments/client/localization/comments-thread.cjs +1 -0
- package/dist/packages/stack/src/plugins/comments/client/localization/comments-thread.mjs +1 -0
- package/dist/packages/stack/src/plugins/comments/client/plugin.cjs +78 -46
- package/dist/packages/stack/src/plugins/comments/client/plugin.mjs +80 -48
- package/dist/packages/stack/src/plugins/form-builder/api/getters.cjs +9 -0
- package/dist/packages/stack/src/plugins/form-builder/api/getters.mjs +9 -1
- package/dist/packages/stack/src/plugins/form-builder/api/lifecycle-migrations.cjs +15 -0
- package/dist/packages/stack/src/plugins/form-builder/api/lifecycle-migrations.mjs +13 -0
- package/dist/packages/stack/src/plugins/form-builder/api/operations.cjs +1093 -0
- package/dist/packages/stack/src/plugins/form-builder/api/operations.mjs +1082 -0
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +97 -466
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +92 -468
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.cjs +5 -0
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.mjs +5 -0
- package/dist/packages/stack/src/plugins/form-builder/client/components/forms/form-renderer.cjs +41 -11
- package/dist/packages/stack/src/plugins/form-builder/client/components/forms/form-renderer.mjs +42 -12
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/404-page.cjs +8 -5
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/404-page.mjs +9 -6
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-builder-page.cjs +36 -3
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-builder-page.internal.cjs +32 -11
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-builder-page.internal.mjs +35 -14
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-builder-page.mjs +37 -4
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.cjs +6 -1
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.cjs +34 -26
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.mjs +34 -26
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.mjs +6 -1
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.cjs +24 -7
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +72 -40
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +76 -44
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.mjs +25 -8
- package/dist/packages/stack/src/plugins/form-builder/client/components/shared/page-wrapper.cjs +2 -1
- package/dist/packages/stack/src/plugins/form-builder/client/components/shared/page-wrapper.mjs +2 -1
- package/dist/packages/stack/src/plugins/form-builder/client/constants.cjs +5 -0
- package/dist/packages/stack/src/plugins/form-builder/client/constants.mjs +3 -0
- package/dist/packages/stack/src/plugins/form-builder/client/hooks/form-builder-hooks.cjs +29 -0
- package/dist/packages/stack/src/plugins/form-builder/client/hooks/form-builder-hooks.mjs +28 -1
- package/dist/packages/stack/src/plugins/form-builder/client/hooks/form-builder-resource.cjs +2 -1
- package/dist/packages/stack/src/plugins/form-builder/client/hooks/form-builder-resource.mjs +2 -1
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +113 -86
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +114 -87
- package/dist/packages/stack/src/plugins/form-builder/schemas.cjs +9 -3
- package/dist/packages/stack/src/plugins/form-builder/schemas.mjs +9 -3
- package/dist/packages/stack/src/plugins/kanban/api/getters.cjs +47 -36
- package/dist/packages/stack/src/plugins/kanban/api/getters.mjs +47 -37
- package/dist/packages/stack/src/plugins/kanban/api/lifecycle-migrations.cjs +23 -0
- package/dist/packages/stack/src/plugins/kanban/api/lifecycle-migrations.mjs +21 -0
- package/dist/packages/stack/src/plugins/kanban/api/operations.cjs +1552 -0
- package/dist/packages/stack/src/plugins/kanban/api/operations.mjs +1543 -0
- package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +64 -631
- package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +62 -634
- package/dist/packages/stack/src/plugins/kanban/api/serializers.cjs +21 -0
- package/dist/packages/stack/src/plugins/kanban/api/serializers.mjs +21 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.cjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.mjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/column-form.cjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/column-form.mjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +72 -54
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +74 -56
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/404-page.cjs +4 -6
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/404-page.mjs +5 -7
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.cjs +22 -6
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.internal.cjs +82 -77
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.internal.mjs +83 -78
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.mjs +23 -7
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/boards-list-page.cjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/boards-list-page.internal.cjs +34 -24
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/boards-list-page.internal.mjs +35 -25
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/boards-list-page.mjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/new-board-page.cjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/new-board-page.internal.cjs +6 -9
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/new-board-page.internal.mjs +7 -10
- package/dist/packages/stack/src/plugins/kanban/client/components/pages/new-board-page.mjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/column-content.cjs +72 -45
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/column-content.mjs +73 -46
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/default-error.cjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/default-error.mjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/kanban-board.cjs +4 -4
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/kanban-board.mjs +4 -4
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/permission-access-any.cjs +23 -0
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/permission-access-any.mjs +21 -0
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/task-card.cjs +54 -17
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/task-card.mjs +55 -18
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/user-avatar.cjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/shared/user-avatar.mjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/constants.cjs +5 -0
- package/dist/packages/stack/src/plugins/kanban/client/constants.mjs +3 -0
- package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.cjs +38 -9
- package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.mjs +39 -10
- package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-resource.cjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-resource.mjs +2 -1
- package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +177 -104
- package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +178 -105
- package/dist/packages/stack/src/plugins/kanban/schemas.cjs +32 -23
- package/dist/packages/stack/src/plugins/kanban/schemas.mjs +33 -21
- package/dist/packages/stack/src/plugins/media/api/lifecycle-migrations.cjs +9 -0
- package/dist/packages/stack/src/plugins/media/api/lifecycle-migrations.mjs +7 -0
- package/dist/packages/stack/src/plugins/media/api/mutations.cjs +7 -2
- package/dist/packages/stack/src/plugins/media/api/mutations.mjs +7 -2
- package/dist/packages/stack/src/plugins/media/api/operations.cjs +1299 -0
- package/dist/packages/stack/src/plugins/media/api/operations.mjs +1290 -0
- package/dist/packages/stack/src/plugins/media/api/plugin.cjs +131 -508
- package/dist/packages/stack/src/plugins/media/api/plugin.mjs +127 -510
- package/dist/packages/stack/src/plugins/media/api/query-key-defs.cjs +26 -2
- package/dist/packages/stack/src/plugins/media/api/query-key-defs.mjs +25 -3
- package/dist/packages/stack/src/plugins/media/api/serializers.cjs +4 -2
- package/dist/packages/stack/src/plugins/media/api/serializers.mjs +4 -2
- package/dist/packages/stack/src/plugins/media/asset-url.cjs +13 -0
- package/dist/packages/stack/src/plugins/media/asset-url.mjs +11 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.cjs +10 -10
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.mjs +12 -12
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.cjs +47 -27
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.mjs +49 -29
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.cjs +225 -180
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.mjs +228 -183
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.cjs +1 -8
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.mjs +2 -9
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.cjs +0 -6
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.mjs +1 -7
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.cjs +4 -2
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.cjs +92 -75
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.mjs +93 -76
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.mjs +4 -2
- package/dist/packages/stack/src/plugins/media/client/components/upload-permission-check.cjs +52 -0
- package/dist/packages/stack/src/plugins/media/client/components/upload-permission-check.mjs +49 -0
- package/dist/packages/stack/src/plugins/media/client/constants.cjs +5 -0
- package/dist/packages/stack/src/plugins/media/client/constants.mjs +3 -0
- package/dist/packages/stack/src/plugins/media/client/hooks/media-resource.cjs +2 -1
- package/dist/packages/stack/src/plugins/media/client/hooks/media-resource.mjs +2 -1
- package/dist/packages/stack/src/plugins/media/client/hooks/use-media.cjs +214 -43
- package/dist/packages/stack/src/plugins/media/client/hooks/use-media.mjs +217 -46
- package/dist/packages/stack/src/plugins/media/client/plugin.cjs +122 -62
- package/dist/packages/stack/src/plugins/media/client/plugin.mjs +123 -63
- package/dist/packages/stack/src/plugins/media/client/upload.cjs +121 -52
- package/dist/packages/stack/src/plugins/media/client/upload.mjs +121 -53
- package/dist/packages/stack/src/plugins/media/db.cjs +8 -0
- package/dist/packages/stack/src/plugins/media/db.mjs +8 -0
- package/dist/packages/stack/src/plugins/open-api/api/generator.cjs +36 -5
- package/dist/packages/stack/src/plugins/open-api/api/generator.mjs +36 -5
- package/dist/packages/stack/src/plugins/open-api/api/plugin.cjs +22 -11
- package/dist/packages/stack/src/plugins/open-api/api/plugin.mjs +22 -11
- package/dist/packages/stack/src/plugins/route-docs/client/components/pages/docs-page.cjs +65 -34
- package/dist/packages/stack/src/plugins/route-docs/client/components/pages/docs-page.mjs +56 -25
- package/dist/packages/stack/src/plugins/route-docs/client/constants.cjs +5 -0
- package/dist/packages/stack/src/plugins/route-docs/client/constants.mjs +3 -0
- package/dist/packages/stack/src/plugins/route-docs/client/hooks.cjs +9 -8
- package/dist/packages/stack/src/plugins/route-docs/client/hooks.mjs +10 -9
- package/dist/packages/stack/src/plugins/route-docs/client/plugin.cjs +143 -75
- package/dist/packages/stack/src/plugins/route-docs/client/plugin.mjs +144 -74
- package/dist/packages/stack/src/plugins/route-docs/client/schema.cjs +26 -0
- package/dist/packages/stack/src/plugins/route-docs/client/schema.mjs +23 -0
- package/dist/packages/stack/src/plugins/route-docs/generator.cjs +7 -4
- package/dist/packages/stack/src/plugins/route-docs/generator.mjs +7 -4
- package/dist/packages/stack/src/plugins/ui-builder/client/components/page-renderer.cjs +41 -12
- package/dist/packages/stack/src/plugins/ui-builder/client/components/page-renderer.mjs +42 -13
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.cjs +37 -3
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.cjs +32 -20
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.mjs +34 -22
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.mjs +38 -4
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-list-page.cjs +8 -2
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-list-page.internal.cjs +37 -19
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-list-page.internal.mjs +38 -20
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-list-page.mjs +8 -2
- package/dist/packages/stack/src/plugins/ui-builder/client/components/shared/default-error.cjs +2 -1
- package/dist/packages/stack/src/plugins/ui-builder/client/components/shared/default-error.mjs +2 -1
- package/dist/packages/stack/src/plugins/ui-builder/client/components/shared/page-wrapper.cjs +2 -1
- package/dist/packages/stack/src/plugins/ui-builder/client/components/shared/page-wrapper.mjs +2 -1
- package/dist/packages/stack/src/plugins/ui-builder/client/constants.cjs +5 -0
- package/dist/packages/stack/src/plugins/ui-builder/client/constants.mjs +3 -0
- package/dist/packages/stack/src/plugins/ui-builder/client/hooks/ui-builder-resource.cjs +2 -1
- package/dist/packages/stack/src/plugins/ui-builder/client/hooks/ui-builder-resource.mjs +2 -1
- package/dist/packages/stack/src/plugins/ui-builder/client/overrides.cjs +8 -0
- package/dist/packages/stack/src/plugins/ui-builder/client/overrides.mjs +6 -0
- package/dist/packages/stack/src/plugins/ui-builder/client/plugin.cjs +93 -53
- package/dist/packages/stack/src/plugins/ui-builder/client/plugin.mjs +94 -54
- package/dist/packages/stack/src/plugins/utils.cjs +4 -12
- package/dist/packages/stack/src/plugins/utils.mjs +5 -12
- package/dist/packages/stack/src/react-router/layout.cjs +14 -0
- package/dist/packages/stack/src/react-router/layout.mjs +12 -0
- package/dist/packages/stack/src/react-router/page.cjs +10 -5
- package/dist/packages/stack/src/react-router/page.mjs +11 -6
- package/dist/packages/stack/src/shared/initial-identity.cjs +27 -0
- package/dist/packages/stack/src/shared/initial-identity.mjs +23 -0
- package/dist/packages/stack/src/tanstack/layout.cjs +10 -0
- package/dist/packages/stack/src/tanstack/layout.mjs +8 -0
- package/dist/packages/stack/src/tanstack/page.cjs +6 -2
- package/dist/packages/stack/src/tanstack/page.mjs +6 -2
- package/dist/plugins/ai-chat/api/index.cjs +10 -0
- package/dist/plugins/ai-chat/api/index.d.cts +31 -5
- package/dist/plugins/ai-chat/api/index.d.mts +31 -5
- package/dist/plugins/ai-chat/api/index.d.ts +31 -5
- package/dist/plugins/ai-chat/api/index.mjs +3 -1
- package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
- package/dist/plugins/ai-chat/client/hooks/index.cjs +1 -0
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +14 -8
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +14 -8
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +14 -8
- package/dist/plugins/ai-chat/client/hooks/index.mjs +1 -1
- package/dist/plugins/ai-chat/client/index.d.cts +21 -25
- package/dist/plugins/ai-chat/client/index.d.mts +21 -25
- package/dist/plugins/ai-chat/client/index.d.ts +21 -25
- package/dist/plugins/ai-chat/permissions.cjs +90 -0
- package/dist/plugins/ai-chat/permissions.d.cts +84 -0
- package/dist/plugins/ai-chat/permissions.d.mts +84 -0
- package/dist/plugins/ai-chat/permissions.d.ts +84 -0
- package/dist/plugins/ai-chat/permissions.mjs +88 -0
- package/dist/plugins/ai-chat/query-keys.cjs +116 -18
- package/dist/plugins/ai-chat/query-keys.d.cts +6 -2
- package/dist/plugins/ai-chat/query-keys.d.mts +6 -2
- package/dist/plugins/ai-chat/query-keys.d.ts +6 -2
- package/dist/plugins/ai-chat/query-keys.mjs +116 -19
- package/dist/plugins/api/index.cjs +4 -0
- package/dist/plugins/api/index.d.cts +13 -18
- package/dist/plugins/api/index.d.mts +13 -18
- package/dist/plugins/api/index.d.ts +13 -18
- package/dist/plugins/api/index.mjs +1 -0
- package/dist/plugins/blog/api/index.cjs +9 -2
- package/dist/plugins/blog/api/index.d.cts +118 -4
- package/dist/plugins/blog/api/index.d.mts +118 -4
- package/dist/plugins/blog/api/index.d.ts +118 -4
- package/dist/plugins/blog/api/index.mjs +4 -1
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +210 -226
- package/dist/plugins/blog/client/index.d.mts +210 -226
- package/dist/plugins/blog/client/index.d.ts +210 -226
- package/dist/plugins/blog/permissions.cjs +47 -0
- package/dist/plugins/blog/permissions.d.cts +49 -0
- package/dist/plugins/blog/permissions.d.mts +49 -0
- package/dist/plugins/blog/permissions.d.ts +49 -0
- package/dist/plugins/blog/permissions.mjs +45 -0
- package/dist/plugins/blog/query-keys.d.cts +7 -3
- package/dist/plugins/blog/query-keys.d.mts +7 -3
- package/dist/plugins/blog/query-keys.d.ts +7 -3
- package/dist/plugins/client/hooks/index.d.cts +2 -2
- package/dist/plugins/client/hooks/index.d.mts +2 -2
- package/dist/plugins/client/hooks/index.d.ts +2 -2
- package/dist/plugins/client/index.cjs +3 -0
- package/dist/plugins/client/index.d.cts +30 -10
- package/dist/plugins/client/index.d.mts +30 -10
- package/dist/plugins/client/index.d.ts +30 -10
- package/dist/plugins/client/index.mjs +3 -0
- package/dist/plugins/cms/api/index.cjs +7 -0
- package/dist/plugins/cms/api/index.d.cts +173 -4
- package/dist/plugins/cms/api/index.d.mts +173 -4
- package/dist/plugins/cms/api/index.d.ts +173 -4
- package/dist/plugins/cms/api/index.mjs +2 -0
- package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
- package/dist/plugins/cms/client/index.d.cts +188 -184
- package/dist/plugins/cms/client/index.d.mts +188 -184
- package/dist/plugins/cms/client/index.d.ts +188 -184
- package/dist/plugins/cms/permissions.cjs +46 -0
- package/dist/plugins/cms/permissions.d.cts +42 -0
- package/dist/plugins/cms/permissions.d.mts +42 -0
- package/dist/plugins/cms/permissions.d.ts +42 -0
- package/dist/plugins/cms/permissions.mjs +44 -0
- package/dist/plugins/cms/query-keys.d.cts +4 -4
- package/dist/plugins/cms/query-keys.d.mts +4 -4
- package/dist/plugins/cms/query-keys.d.ts +4 -4
- package/dist/plugins/comments/api/index.cjs +10 -0
- package/dist/plugins/comments/api/index.d.cts +54 -18
- package/dist/plugins/comments/api/index.d.mts +54 -18
- package/dist/plugins/comments/api/index.d.ts +54 -18
- package/dist/plugins/comments/api/index.mjs +3 -0
- package/dist/plugins/comments/client/components/index.d.cts +2 -2
- package/dist/plugins/comments/client/components/index.d.mts +2 -2
- package/dist/plugins/comments/client/components/index.d.ts +2 -2
- package/dist/plugins/comments/client/hooks/index.d.cts +12 -18
- package/dist/plugins/comments/client/hooks/index.d.mts +12 -18
- package/dist/plugins/comments/client/hooks/index.d.ts +12 -18
- package/dist/plugins/comments/client/index.d.cts +96 -114
- package/dist/plugins/comments/client/index.d.mts +96 -114
- package/dist/plugins/comments/client/index.d.ts +96 -114
- package/dist/plugins/comments/permissions.cjs +70 -0
- package/dist/plugins/comments/permissions.d.cts +76 -0
- package/dist/plugins/comments/permissions.d.mts +76 -0
- package/dist/plugins/comments/permissions.d.ts +76 -0
- package/dist/plugins/comments/permissions.mjs +68 -0
- package/dist/plugins/comments/query-keys.cjs +1 -1
- package/dist/plugins/comments/query-keys.d.cts +16 -19
- package/dist/plugins/comments/query-keys.d.mts +16 -19
- package/dist/plugins/comments/query-keys.d.ts +16 -19
- package/dist/plugins/comments/query-keys.mjs +1 -1
- package/dist/plugins/form-builder/api/index.cjs +8 -0
- package/dist/plugins/form-builder/api/index.d.cts +115 -4
- package/dist/plugins/form-builder/api/index.d.mts +115 -4
- package/dist/plugins/form-builder/api/index.d.ts +115 -4
- package/dist/plugins/form-builder/api/index.mjs +4 -1
- package/dist/plugins/form-builder/client/components/index.d.cts +6 -2
- package/dist/plugins/form-builder/client/components/index.d.mts +6 -2
- package/dist/plugins/form-builder/client/components/index.d.ts +6 -2
- package/dist/plugins/form-builder/client/hooks/index.cjs +2 -0
- package/dist/plugins/form-builder/client/hooks/index.d.cts +34 -8
- package/dist/plugins/form-builder/client/hooks/index.d.mts +34 -8
- package/dist/plugins/form-builder/client/hooks/index.d.ts +34 -8
- package/dist/plugins/form-builder/client/hooks/index.mjs +1 -1
- package/dist/plugins/form-builder/client/index.d.cts +153 -174
- package/dist/plugins/form-builder/client/index.d.mts +153 -174
- package/dist/plugins/form-builder/client/index.d.ts +153 -174
- package/dist/plugins/form-builder/permissions.cjs +95 -0
- package/dist/plugins/form-builder/permissions.d.cts +94 -0
- package/dist/plugins/form-builder/permissions.d.mts +94 -0
- package/dist/plugins/form-builder/permissions.d.ts +94 -0
- package/dist/plugins/form-builder/permissions.mjs +93 -0
- package/dist/plugins/form-builder/query-keys.cjs +11 -4
- package/dist/plugins/form-builder/query-keys.d.cts +38 -12
- package/dist/plugins/form-builder/query-keys.d.mts +38 -12
- package/dist/plugins/form-builder/query-keys.d.ts +38 -12
- package/dist/plugins/form-builder/query-keys.mjs +11 -4
- package/dist/plugins/kanban/api/index.cjs +9 -0
- package/dist/plugins/kanban/api/index.d.cts +137 -5
- package/dist/plugins/kanban/api/index.d.mts +137 -5
- package/dist/plugins/kanban/api/index.d.ts +137 -5
- package/dist/plugins/kanban/api/index.mjs +5 -2
- package/dist/plugins/kanban/client/components/index.d.cts +18 -10
- package/dist/plugins/kanban/client/components/index.d.mts +18 -10
- package/dist/plugins/kanban/client/components/index.d.ts +18 -10
- package/dist/plugins/kanban/client/hooks/index.d.cts +15 -7
- package/dist/plugins/kanban/client/hooks/index.d.mts +15 -7
- package/dist/plugins/kanban/client/hooks/index.d.ts +15 -7
- package/dist/plugins/kanban/client/index.d.cts +32 -28
- package/dist/plugins/kanban/client/index.d.mts +32 -28
- package/dist/plugins/kanban/client/index.d.ts +32 -28
- package/dist/plugins/kanban/permissions.cjs +101 -0
- package/dist/plugins/kanban/permissions.d.cts +109 -0
- package/dist/plugins/kanban/permissions.d.mts +109 -0
- package/dist/plugins/kanban/permissions.d.ts +109 -0
- package/dist/plugins/kanban/permissions.mjs +99 -0
- package/dist/plugins/kanban/query-keys.cjs +14 -15
- package/dist/plugins/kanban/query-keys.d.cts +63 -42
- package/dist/plugins/kanban/query-keys.d.mts +63 -42
- package/dist/plugins/kanban/query-keys.d.ts +63 -42
- package/dist/plugins/kanban/query-keys.mjs +14 -15
- package/dist/plugins/media/api/adapters/local.d.cts +1 -1
- package/dist/plugins/media/api/adapters/local.d.mts +1 -1
- package/dist/plugins/media/api/adapters/local.d.ts +1 -1
- package/dist/plugins/media/api/adapters/s3.d.cts +1 -1
- package/dist/plugins/media/api/adapters/s3.d.mts +1 -1
- package/dist/plugins/media/api/adapters/s3.d.ts +1 -1
- package/dist/plugins/media/api/adapters/vercel-blob.cjs +46 -0
- package/dist/plugins/media/api/adapters/vercel-blob.d.cts +6 -9
- package/dist/plugins/media/api/adapters/vercel-blob.d.mts +6 -9
- package/dist/plugins/media/api/adapters/vercel-blob.d.ts +6 -9
- package/dist/plugins/media/api/adapters/vercel-blob.mjs +46 -0
- package/dist/plugins/media/api/index.cjs +10 -0
- package/dist/plugins/media/api/index.d.cts +93 -6
- package/dist/plugins/media/api/index.d.mts +93 -6
- package/dist/plugins/media/api/index.d.ts +93 -6
- package/dist/plugins/media/api/index.mjs +2 -0
- package/dist/plugins/media/client/components/index.d.cts +1 -1
- package/dist/plugins/media/client/components/index.d.mts +1 -1
- package/dist/plugins/media/client/components/index.d.ts +1 -1
- package/dist/plugins/media/client/hooks/index.d.cts +20 -10
- package/dist/plugins/media/client/hooks/index.d.mts +20 -10
- package/dist/plugins/media/client/hooks/index.d.ts +20 -10
- package/dist/plugins/media/client/index.cjs +1 -0
- package/dist/plugins/media/client/index.d.cts +112 -100
- package/dist/plugins/media/client/index.d.mts +112 -100
- package/dist/plugins/media/client/index.d.ts +112 -100
- package/dist/plugins/media/client/index.mjs +1 -1
- package/dist/plugins/media/permissions.cjs +63 -0
- package/dist/plugins/media/permissions.d.cts +64 -0
- package/dist/plugins/media/permissions.d.mts +64 -0
- package/dist/plugins/media/permissions.d.ts +64 -0
- package/dist/plugins/media/permissions.mjs +61 -0
- package/dist/plugins/media/query-keys.cjs +29 -17
- package/dist/plugins/media/query-keys.d.cts +24 -31
- package/dist/plugins/media/query-keys.d.mts +24 -31
- package/dist/plugins/media/query-keys.d.ts +24 -31
- package/dist/plugins/media/query-keys.mjs +30 -18
- package/dist/plugins/open-api/api/index.d.cts +9 -4
- package/dist/plugins/open-api/api/index.d.mts +9 -4
- package/dist/plugins/open-api/api/index.d.ts +9 -4
- package/dist/plugins/route-docs/client/index.cjs +2 -2
- package/dist/plugins/route-docs/client/index.d.cts +54 -53
- package/dist/plugins/route-docs/client/index.d.mts +54 -53
- package/dist/plugins/route-docs/client/index.d.ts +54 -53
- package/dist/plugins/route-docs/client/index.mjs +2 -1
- package/dist/plugins/ui-builder/client/components/index.d.cts +8 -5
- package/dist/plugins/ui-builder/client/components/index.d.mts +8 -5
- package/dist/plugins/ui-builder/client/components/index.d.ts +8 -5
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +5 -5
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +5 -5
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +5 -5
- package/dist/plugins/ui-builder/client/index.d.cts +151 -199
- package/dist/plugins/ui-builder/client/index.d.mts +151 -199
- package/dist/plugins/ui-builder/client/index.d.ts +151 -199
- package/dist/plugins/ui-builder/index.d.cts +11 -8
- package/dist/plugins/ui-builder/index.d.mts +11 -8
- package/dist/plugins/ui-builder/index.d.ts +11 -8
- package/dist/plugins/ui-builder/query-keys.d.cts +6 -6
- package/dist/plugins/ui-builder/query-keys.d.mts +6 -6
- package/dist/plugins/ui-builder/query-keys.d.ts +6 -6
- package/dist/react-router/index.cjs +2 -0
- package/dist/react-router/index.d.cts +29 -5
- package/dist/react-router/index.d.mts +29 -5
- package/dist/react-router/index.d.ts +29 -5
- package/dist/react-router/index.mjs +1 -0
- package/dist/shared/stack.0O62uXqP.d.ts +613 -0
- package/dist/shared/stack.0y6iTP03.d.ts +533 -0
- package/dist/shared/{stack.CbZenbej.d.cts → stack.1ukVJym7.d.cts} +3 -4
- package/dist/shared/{stack.CbZenbej.d.mts → stack.1ukVJym7.d.mts} +3 -4
- package/dist/shared/{stack.CbZenbej.d.ts → stack.1ukVJym7.d.ts} +3 -4
- package/dist/shared/stack.3Sr6qRtT.d.ts +507 -0
- package/dist/shared/{stack.9BAOaSqE.d.cts → stack.5_zzCejP.d.cts} +16 -14
- package/dist/shared/{stack.9BAOaSqE.d.mts → stack.5_zzCejP.d.mts} +16 -14
- package/dist/shared/{stack.9BAOaSqE.d.ts → stack.5_zzCejP.d.ts} +16 -14
- package/dist/shared/stack.B1UrNoVl.d.mts +533 -0
- package/dist/shared/{stack.B2xi4jj7.d.cts → stack.B1srlBud.d.mts} +2 -2
- package/dist/shared/stack.B3_aDcYL.d.cts +281 -0
- package/dist/shared/stack.B3_aDcYL.d.mts +281 -0
- package/dist/shared/stack.B3_aDcYL.d.ts +281 -0
- package/dist/shared/stack.B5yPgHsp.d.mts +507 -0
- package/dist/shared/stack.B8PG21zX.d.mts +764 -0
- package/dist/shared/stack.BFP_9pRX.d.cts +707 -0
- package/dist/shared/{stack.DzwZw-Ck.d.cts → stack.BGzgFx3R.d.cts} +2 -2
- package/dist/shared/stack.BJx1ZXrN.d.cts +118 -0
- package/dist/shared/{stack.7b3vsM8_.d.ts → stack.BK9Z2dcL.d.ts} +2 -2
- package/dist/shared/stack.BMHoxAn7.d.mts +707 -0
- package/dist/shared/stack.BMmvEBH_.d.ts +118 -0
- package/dist/shared/stack.BOZRaVpu.d.cts +1184 -0
- package/dist/shared/stack.BPLykw4s.d.ts +436 -0
- package/dist/shared/{stack.aqVZ6PEW.d.mts → stack.BRvqQWGt.d.cts} +40 -1
- package/dist/shared/{stack.aqVZ6PEW.d.cts → stack.BRvqQWGt.d.mts} +40 -1
- package/dist/shared/{stack.aqVZ6PEW.d.ts → stack.BRvqQWGt.d.ts} +40 -1
- package/dist/shared/stack.BSACE667.d.cts +23 -0
- package/dist/shared/stack.BYMentwB.d.cts +47 -0
- package/dist/shared/stack.BYMentwB.d.mts +47 -0
- package/dist/shared/stack.BYMentwB.d.ts +47 -0
- package/dist/shared/stack.Bt8hfw9o.d.ts +707 -0
- package/dist/shared/{stack.D7bewlgt.d.mts → stack.Bu2IPOLi.d.cts} +1 -0
- package/dist/shared/{stack.D7bewlgt.d.ts → stack.Bu2IPOLi.d.mts} +1 -0
- package/dist/shared/{stack.D7bewlgt.d.cts → stack.Bu2IPOLi.d.ts} +1 -0
- package/dist/shared/stack.BuxUQqL8.d.ts +408 -0
- package/dist/shared/stack.BzWGB5MG.d.cts +436 -0
- package/dist/shared/stack.C3bU9w6L.d.ts +351 -0
- package/dist/shared/stack.C4oVW2qK.d.cts +507 -0
- package/dist/shared/stack.CKXzdGiJ.d.cts +3 -0
- package/dist/shared/stack.CKXzdGiJ.d.mts +3 -0
- package/dist/shared/stack.CKXzdGiJ.d.ts +3 -0
- package/dist/shared/{stack.C3GBG8in.d.ts → stack.COrJrztD.d.cts} +1 -1
- package/dist/shared/{stack.C3GBG8in.d.cts → stack.COrJrztD.d.mts} +1 -1
- package/dist/shared/{stack.C3GBG8in.d.mts → stack.COrJrztD.d.ts} +1 -1
- package/dist/shared/stack.CX83YNuS.d.cts +408 -0
- package/dist/shared/stack.Cbd7sD5M.d.mts +118 -0
- package/dist/shared/{stack.B9NtGpAR.d.ts → stack.CcjENrr4.d.cts} +1 -5
- package/dist/shared/stack.CfK5NKlK.d.mts +613 -0
- package/dist/shared/{stack.oVIdV7XS.d.mts → stack.CjCSDfWy.d.mts} +2 -2
- package/dist/shared/stack.Cmg7lzzy.d.cts +44 -0
- package/dist/shared/stack.Cmg7lzzy.d.mts +44 -0
- package/dist/shared/stack.Cmg7lzzy.d.ts +44 -0
- package/dist/shared/stack.CpE1mS3p.d.ts +331 -0
- package/dist/shared/stack.CskhFw3z.d.mts +23 -0
- package/dist/shared/stack.CvlC5kqe.d.cts +331 -0
- package/dist/shared/stack.CxKc9evq.d.ts +23 -0
- package/dist/shared/stack.CyalJ0j6.d.ts +764 -0
- package/dist/shared/stack.D6Pg-ufX.d.cts +764 -0
- package/dist/shared/{stack.lkebw2nj.d.mts → stack.DVEjWKCT.d.cts} +19 -5
- package/dist/shared/{stack.lkebw2nj.d.ts → stack.DVEjWKCT.d.mts} +19 -5
- package/dist/shared/{stack.lkebw2nj.d.cts → stack.DVEjWKCT.d.ts} +19 -5
- package/dist/shared/{stack.Cp_6mO5s.d.mts → stack.DbbvHbhn.d.mts} +1 -5
- package/dist/shared/stack.DiuRuzIs.d.cts +613 -0
- package/dist/shared/{stack.BMlLOMED.d.cts → stack.DjGixVx3.d.cts} +6 -2
- package/dist/shared/{stack.BMlLOMED.d.mts → stack.DjGixVx3.d.mts} +6 -2
- package/dist/shared/{stack.BMlLOMED.d.ts → stack.DjGixVx3.d.ts} +6 -2
- package/dist/shared/stack.DlBoNqYu.d.ts +1184 -0
- package/dist/shared/stack.DlCycSM-.d.mts +331 -0
- package/dist/shared/{stack.DycBcTeg.d.mts → stack.DmpPDPxA.d.cts} +2 -2
- package/dist/shared/{stack.DseBJmGF.d.ts → stack.Dpg_JjIo.d.ts} +2 -2
- package/dist/shared/stack.Dvy7FmaA.d.mts +1184 -0
- package/dist/shared/stack.DxHPKr2R.d.mts +436 -0
- package/dist/shared/stack.Egr3RWhA.d.mts +408 -0
- package/dist/shared/stack.Mak7pzIV.d.cts +533 -0
- package/dist/shared/stack.OYT8WU4n.d.mts +351 -0
- package/dist/shared/{stack.CPsYC2-Z.d.cts → stack.fdi94T4S.d.cts} +7 -7
- package/dist/shared/{stack.CPsYC2-Z.d.mts → stack.fdi94T4S.d.mts} +7 -7
- package/dist/shared/{stack.CPsYC2-Z.d.ts → stack.fdi94T4S.d.ts} +7 -7
- package/dist/shared/{stack.Dlgtumru.d.cts → stack.kaCV7vEL.d.ts} +1 -5
- package/dist/shared/stack.v2O65JqF.d.cts +351 -0
- package/dist/tanstack/index.cjs +2 -0
- package/dist/tanstack/index.d.cts +27 -5
- package/dist/tanstack/index.d.mts +27 -5
- package/dist/tanstack/index.d.ts +27 -5
- package/dist/tanstack/index.mjs +1 -0
- package/dist/tanstack/server.cjs +9 -0
- package/dist/tanstack/server.d.cts +15 -0
- package/dist/tanstack/server.d.mts +15 -0
- package/dist/tanstack/server.d.ts +15 -0
- package/dist/tanstack/server.mjs +7 -0
- package/package.json +198 -3
- package/src/__tests__/ai-chat-client-runtime.test.ts +481 -0
- package/src/__tests__/ai-chat-client-runtime.typecheck.tsx +130 -0
- package/src/__tests__/ai-chat-lifecycle-migrations.typecheck.ts +61 -0
- package/src/__tests__/ai-chat-query-keys.test.ts +125 -22
- package/src/__tests__/auth-provider.test.tsx +41 -314
- package/src/__tests__/auth-test-utils.ts +24 -0
- package/src/__tests__/authorization-client.test.tsx +767 -0
- package/src/__tests__/authorization-contraction.test.ts +182 -0
- package/src/__tests__/authorization-remote.test.ts +222 -0
- package/src/__tests__/authorization-server.test.ts +90 -0
- package/src/__tests__/authorization.test.ts +652 -0
- package/src/__tests__/authorization.typecheck.ts +671 -0
- package/src/__tests__/backend-plugin-factories.test.ts +54 -0
- package/src/__tests__/backend-plugin-factories.typecheck.ts +133 -0
- package/src/__tests__/client-plugin-ssr-loaders.test.ts +304 -138
- package/src/__tests__/client-runtime.test.ts +1021 -0
- package/src/__tests__/client-runtime.typecheck.ts +107 -0
- package/src/__tests__/client-stack-test-utils.ts +36 -0
- package/src/__tests__/content-lifecycle-migrations.typecheck.ts +122 -0
- package/src/__tests__/endpoint-inventory.test.ts +768 -0
- package/src/__tests__/form-builder-query-keys.test.ts +32 -0
- package/src/__tests__/i18n-provider.test.tsx +4 -3
- package/src/__tests__/initial-identity-hydration.test.tsx +156 -0
- package/src/__tests__/initial-identity-layouts.test.tsx +204 -0
- package/src/__tests__/kanban-query-keys.test.ts +189 -2
- package/src/__tests__/managed-origin-hydration.browser.test.tsx +163 -0
- package/src/__tests__/media-query-keys.test.ts +91 -17
- package/src/__tests__/notify-provider.test.tsx +3 -2
- package/src/__tests__/package-metadata.test.ts +294 -0
- package/src/__tests__/page-component-overrides.test.tsx +3 -4
- package/src/__tests__/plugin-registration.test.tsx +183 -0
- package/src/__tests__/plugin-registration.typecheck.tsx +139 -0
- package/src/__tests__/plugins.test.tsx +72 -42
- package/src/__tests__/resource-factory.test.tsx +79 -2
- package/src/__tests__/resource-use-form.test.tsx +2 -2
- package/src/__tests__/resource-use-select.test.tsx +2 -4
- package/src/__tests__/router-overrides.test.tsx +20 -8
- package/src/__tests__/sitemap.test.ts +38 -22
- package/src/__tests__/stack-api.test.ts +39 -34
- package/src/__tests__/stack-auth.test.ts +44 -140
- package/src/__tests__/stack-constructors.test.ts +96 -0
- package/src/__tests__/stack-constructors.typecheck.ts +115 -0
- package/src/__tests__/use-list-state.test.tsx +7 -6
- package/src/__tests__/workflow-lifecycle-migrations.typecheck.ts +150 -0
- package/src/api/index.ts +204 -99
- package/src/authorization/client.tsx +310 -0
- package/src/authorization/index.ts +873 -0
- package/src/authorization/json.ts +70 -0
- package/src/authorization/remote.ts +234 -0
- package/src/authorization/server.ts +171 -0
- package/src/client/__tests__/server-origin.test.ts +119 -0
- package/src/client/components/compose.tsx +126 -18
- package/src/client/hooks/index.ts +12 -0
- package/src/client/hooks/use-list-state.ts +7 -13
- package/src/client/index.ts +105 -50
- package/src/client/runtime.ts +414 -0
- package/src/client/server.ts +174 -0
- package/src/context/auth.tsx +205 -154
- package/src/context/index.ts +10 -4
- package/src/context/provider.tsx +96 -57
- package/src/context/router.tsx +7 -0
- package/src/context/site-navigation.ts +52 -0
- package/src/next/layout.tsx +81 -0
- package/src/next/page.tsx +16 -3
- package/src/next/router.tsx +1 -1
- package/src/next/server.ts +5 -0
- package/src/plugin-registration.ts +70 -0
- package/src/plugins/ai-chat/__tests__/authorization.test.ts +2051 -0
- package/src/plugins/ai-chat/__tests__/chat-hooks.test.tsx +147 -0
- package/src/plugins/ai-chat/__tests__/chat-input-authorization.test.tsx +188 -0
- package/src/plugins/ai-chat/__tests__/client-sweep.test.tsx +1506 -34
- package/src/plugins/ai-chat/__tests__/hooks.test.tsx +131 -23
- package/src/plugins/ai-chat/api/getters.ts +2 -2
- package/src/plugins/ai-chat/api/index.ts +6 -1
- package/src/plugins/ai-chat/api/lifecycle-migrations.ts +20 -0
- package/src/plugins/ai-chat/api/operations.ts +1865 -0
- package/src/plugins/ai-chat/api/plugin.ts +78 -1168
- package/src/plugins/ai-chat/api/serializers.ts +30 -0
- package/src/plugins/ai-chat/client/components/chat-input.tsx +228 -129
- package/src/plugins/ai-chat/client/components/chat-interface.tsx +776 -183
- package/src/plugins/ai-chat/client/components/chat-layout.tsx +6 -7
- package/src/plugins/ai-chat/client/components/chat-message.tsx +1 -1
- package/src/plugins/ai-chat/client/components/chat-sidebar.tsx +57 -37
- package/src/plugins/ai-chat/client/components/pages/404-page.tsx +1 -1
- package/src/plugins/ai-chat/client/components/pages/chat-page.internal.tsx +5 -3
- package/src/plugins/ai-chat/client/components/pages/chat-page.tsx +56 -15
- package/src/plugins/ai-chat/client/components/shared/default-error.tsx +1 -1
- package/src/plugins/ai-chat/client/components/tool-call-display.tsx +1 -1
- package/src/plugins/ai-chat/client/hooks/ai-chat-resource.ts +1 -1
- package/src/plugins/ai-chat/client/hooks/chat-hooks.tsx +253 -20
- package/src/plugins/ai-chat/client/index.ts +1 -0
- package/src/plugins/ai-chat/client/navigation.ts +11 -0
- package/src/plugins/ai-chat/client/overrides.ts +47 -11
- package/src/plugins/ai-chat/client/plugin.tsx +208 -111
- package/src/plugins/ai-chat/permissions.ts +89 -0
- package/src/plugins/ai-chat/query-keys.ts +183 -25
- package/src/plugins/ai-chat/types.ts +7 -5
- package/src/plugins/api/create-endpoint.ts +40 -22
- package/src/plugins/api/endpoint-inventory.ts +206 -0
- package/src/plugins/api/index.ts +38 -4
- package/src/plugins/api/operation.ts +600 -0
- package/src/plugins/blog/__tests__/authorization.test.ts +1366 -0
- package/src/plugins/blog/__tests__/client-runtime.browser.test.tsx +257 -0
- package/src/plugins/blog/__tests__/client-runtime.test.ts +283 -0
- package/src/plugins/blog/__tests__/client-runtime.typecheck.tsx +98 -0
- package/src/plugins/blog/__tests__/client-sweep.test.tsx +212 -37
- package/src/plugins/blog/api/getters.ts +3 -3
- package/src/plugins/blog/api/index.ts +2 -0
- package/src/plugins/blog/api/lifecycle-migrations.ts +19 -0
- package/src/plugins/blog/api/mutations.ts +26 -5
- package/src/plugins/blog/api/operations.ts +727 -0
- package/src/plugins/blog/api/plugin.ts +99 -504
- package/src/plugins/blog/client/components/forms/image-field.tsx +2 -1
- package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +2 -1
- package/src/plugins/blog/client/components/forms/post-forms.tsx +61 -33
- package/src/plugins/blog/client/components/pages/404-page.tsx +3 -1
- package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +2 -1
- package/src/plugins/blog/client/components/pages/edit-post-page.tsx +28 -5
- package/src/plugins/blog/client/components/pages/home-page.internal.tsx +2 -1
- package/src/plugins/blog/client/components/pages/home-page.tsx +7 -4
- package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +2 -1
- package/src/plugins/blog/client/components/pages/new-post-page.tsx +5 -2
- package/src/plugins/blog/client/components/pages/post-page.internal.tsx +2 -1
- package/src/plugins/blog/client/components/pages/post-page.tsx +38 -4
- package/src/plugins/blog/client/components/pages/tag-page.internal.tsx +2 -1
- package/src/plugins/blog/client/components/pages/tag-page.tsx +5 -1
- package/src/plugins/blog/client/components/shared/collapsible-tag-list.tsx +2 -1
- package/src/plugins/blog/client/components/shared/default-error.tsx +3 -1
- package/src/plugins/blog/client/components/shared/on-this-page.tsx +5 -2
- package/src/plugins/blog/client/components/shared/page-wrapper.tsx +2 -1
- package/src/plugins/blog/client/components/shared/post-card.tsx +2 -1
- package/src/plugins/blog/client/components/shared/posts-list.tsx +2 -1
- package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +2 -1
- package/src/plugins/blog/client/components/shared/search-input.tsx +1 -1
- package/src/plugins/blog/client/constants.ts +2 -0
- package/src/plugins/blog/client/hooks/blog-resource.ts +2 -1
- package/src/plugins/blog/client/overrides.ts +1 -6
- package/src/plugins/blog/client/plugin.tsx +145 -129
- package/src/plugins/blog/permissions.ts +44 -0
- package/src/plugins/client/index.ts +108 -7
- package/src/plugins/client/resource/hooks.tsx +41 -18
- package/src/plugins/client/resource/internal.ts +77 -61
- package/src/plugins/client/resource/queries.ts +3 -4
- package/src/plugins/client/resource/use-form.ts +23 -20
- package/src/plugins/client/resource/use-select.ts +40 -34
- package/src/plugins/cms/__tests__/authorization.test.ts +2188 -0
- package/src/plugins/cms/__tests__/client-runtime.browser.test.tsx +245 -0
- package/src/plugins/cms/__tests__/client-runtime.test.ts +320 -0
- package/src/plugins/cms/__tests__/client-runtime.typecheck.tsx +208 -0
- package/src/plugins/cms/__tests__/client-sweep.test.tsx +202 -50
- package/src/plugins/cms/api/getters.ts +4 -4
- package/src/plugins/cms/api/index.ts +20 -0
- package/src/plugins/cms/api/lifecycle-migrations.ts +15 -0
- package/src/plugins/cms/api/mutations.ts +2 -2
- package/src/plugins/cms/api/operations.ts +2218 -0
- package/src/plugins/cms/api/plugin.ts +169 -1047
- package/src/plugins/cms/api/relations.ts +3 -1
- package/src/plugins/cms/client/components/forms/content-form.tsx +2 -1
- package/src/plugins/cms/client/components/forms/file-upload.tsx +3 -1
- package/src/plugins/cms/client/components/forms/relation-field.tsx +3 -1
- package/src/plugins/cms/client/components/inverse-relations-panel.tsx +29 -17
- package/src/plugins/cms/client/components/pages/404-page.tsx +7 -5
- package/src/plugins/cms/client/components/pages/content-editor-page.internal.tsx +19 -10
- package/src/plugins/cms/client/components/pages/content-editor-page.tsx +50 -12
- package/src/plugins/cms/client/components/pages/content-list-page.internal.tsx +48 -34
- package/src/plugins/cms/client/components/pages/content-list-page.tsx +8 -1
- package/src/plugins/cms/client/components/pages/dashboard-page.internal.tsx +15 -8
- package/src/plugins/cms/client/components/pages/dashboard-page.tsx +5 -1
- package/src/plugins/cms/client/components/shared/page-wrapper.tsx +2 -1
- package/src/plugins/cms/client/constants.ts +2 -0
- package/src/plugins/cms/client/hooks/cms-resource.ts +2 -1
- package/src/plugins/cms/client/index.ts +6 -1
- package/src/plugins/cms/client/overrides.ts +46 -6
- package/src/plugins/cms/client/plugin.tsx +135 -80
- package/src/plugins/cms/permissions.ts +43 -0
- package/src/plugins/cms/types.ts +174 -46
- package/src/plugins/comments/__tests__/authorization.test.ts +1570 -0
- package/src/plugins/comments/__tests__/client-runtime.browser.test.tsx +207 -0
- package/src/plugins/comments/__tests__/client-runtime.test.ts +224 -0
- package/src/plugins/comments/__tests__/client-runtime.typecheck.tsx +85 -0
- package/src/plugins/comments/__tests__/client-sweep.test.tsx +445 -82
- package/src/plugins/comments/api/getters.ts +19 -3
- package/src/plugins/comments/api/index.ts +2 -6
- package/src/plugins/comments/api/lifecycle-migrations.ts +20 -0
- package/src/plugins/comments/api/mutations.ts +261 -88
- package/src/plugins/comments/api/operations.ts +742 -0
- package/src/plugins/comments/api/plugin.ts +76 -548
- package/src/plugins/comments/client/components/comment-count.tsx +38 -5
- package/src/plugins/comments/client/components/comment-thread.tsx +148 -110
- package/src/plugins/comments/client/components/pages/moderation-page.internal.tsx +144 -78
- package/src/plugins/comments/client/components/pages/moderation-page.tsx +18 -2
- package/src/plugins/comments/client/components/pages/moderation-state.ts +13 -0
- package/src/plugins/comments/client/components/pages/my-comments-page.internal.tsx +38 -30
- package/src/plugins/comments/client/components/pages/my-comments-page.tsx +3 -1
- package/src/plugins/comments/client/components/pages/resource-comments-page.internal.tsx +29 -30
- package/src/plugins/comments/client/components/pages/resource-comments-page.tsx +10 -1
- package/src/plugins/comments/client/components/shared/page-wrapper.tsx +2 -1
- package/src/plugins/comments/client/components/shared/pagination.tsx +2 -1
- package/src/plugins/comments/client/constants.ts +2 -0
- package/src/plugins/comments/client/hooks/use-comments.tsx +430 -421
- package/src/plugins/comments/client/localization/comments-thread.ts +1 -0
- package/src/plugins/comments/client/plugin.tsx +113 -72
- package/src/plugins/comments/permissions.ts +68 -0
- package/src/plugins/comments/query-keys.ts +9 -10
- package/src/plugins/comments/schemas.ts +7 -8
- package/src/plugins/form-builder/__tests__/authorization.test.ts +1600 -0
- package/src/plugins/form-builder/__tests__/client-runtime.browser.test.tsx +255 -0
- package/src/plugins/form-builder/__tests__/client-runtime.test.ts +132 -0
- package/src/plugins/form-builder/__tests__/client-runtime.typecheck.tsx +77 -0
- package/src/plugins/form-builder/__tests__/client-sweep.test.tsx +350 -54
- package/src/plugins/form-builder/__tests__/form-renderer-authorization.test.tsx +162 -0
- package/src/plugins/form-builder/api/getters.ts +21 -5
- package/src/plugins/form-builder/api/index.ts +31 -0
- package/src/plugins/form-builder/api/lifecycle-migrations.ts +17 -0
- package/src/plugins/form-builder/api/operations.ts +1493 -0
- package/src/plugins/form-builder/api/plugin.ts +125 -632
- package/src/plugins/form-builder/api/query-key-defs.ts +7 -0
- package/src/plugins/form-builder/api/serializers.ts +1 -0
- package/src/plugins/form-builder/client/components/forms/form-renderer.tsx +49 -19
- package/src/plugins/form-builder/client/components/pages/404-page.tsx +9 -9
- package/src/plugins/form-builder/client/components/pages/form-builder-page.internal.tsx +54 -30
- package/src/plugins/form-builder/client/components/pages/form-builder-page.tsx +42 -9
- package/src/plugins/form-builder/client/components/pages/form-list-page.internal.tsx +46 -34
- package/src/plugins/form-builder/client/components/pages/form-list-page.tsx +6 -2
- package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +98 -64
- package/src/plugins/form-builder/client/components/pages/submissions-page.tsx +29 -9
- package/src/plugins/form-builder/client/components/shared/page-wrapper.tsx +2 -1
- package/src/plugins/form-builder/client/constants.ts +2 -0
- package/src/plugins/form-builder/client/hooks/form-builder-hooks.tsx +68 -4
- package/src/plugins/form-builder/client/hooks/form-builder-resource.ts +2 -1
- package/src/plugins/form-builder/client/hooks/index.tsx +1 -0
- package/src/plugins/form-builder/client/overrides.ts +1 -6
- package/src/plugins/form-builder/client/plugin.tsx +128 -96
- package/src/plugins/form-builder/permissions.ts +94 -0
- package/src/plugins/form-builder/query-keys.ts +28 -3
- package/src/plugins/form-builder/schemas.ts +9 -1
- package/src/plugins/form-builder/types.ts +173 -41
- package/src/plugins/kanban/__tests__/authorization.test.ts +1382 -0
- package/src/plugins/kanban/__tests__/client-runtime.browser.test.tsx +302 -0
- package/src/plugins/kanban/__tests__/client-runtime.test.ts +130 -0
- package/src/plugins/kanban/__tests__/client-runtime.typecheck.tsx +81 -0
- package/src/plugins/kanban/__tests__/client-sweep.test.tsx +418 -33
- package/src/plugins/kanban/__tests__/identity-partition.test.tsx +306 -0
- package/src/plugins/kanban/api/getters.ts +65 -50
- package/src/plugins/kanban/api/index.ts +55 -4
- package/src/plugins/kanban/api/lifecycle-migrations.ts +25 -0
- package/src/plugins/kanban/api/mutations.ts +3 -3
- package/src/plugins/kanban/api/operations.ts +2065 -0
- package/src/plugins/kanban/api/plugin.ts +83 -1013
- package/src/plugins/kanban/api/serializers.ts +26 -0
- package/src/plugins/kanban/client/components/forms/board-form.tsx +3 -1
- package/src/plugins/kanban/client/components/forms/column-form.tsx +3 -1
- package/src/plugins/kanban/client/components/forms/task-form.tsx +92 -74
- package/src/plugins/kanban/client/components/pages/404-page.tsx +6 -9
- package/src/plugins/kanban/client/components/pages/board-page.internal.tsx +57 -74
- package/src/plugins/kanban/client/components/pages/board-page.tsx +31 -7
- package/src/plugins/kanban/client/components/pages/boards-list-page.internal.tsx +54 -45
- package/src/plugins/kanban/client/components/pages/boards-list-page.tsx +2 -1
- package/src/plugins/kanban/client/components/pages/new-board-page.internal.tsx +8 -12
- package/src/plugins/kanban/client/components/pages/new-board-page.tsx +2 -1
- package/src/plugins/kanban/client/components/shared/column-content.tsx +61 -48
- package/src/plugins/kanban/client/components/shared/default-error.tsx +3 -1
- package/src/plugins/kanban/client/components/shared/kanban-board.tsx +6 -6
- package/src/plugins/kanban/client/components/shared/permission-access-any.tsx +32 -0
- package/src/plugins/kanban/client/components/shared/task-card.tsx +64 -20
- package/src/plugins/kanban/client/components/shared/user-avatar.tsx +3 -1
- package/src/plugins/kanban/client/constants.ts +2 -0
- package/src/plugins/kanban/client/hooks/kanban-hooks.tsx +55 -10
- package/src/plugins/kanban/client/hooks/kanban-resource.ts +2 -1
- package/src/plugins/kanban/client/overrides.ts +0 -5
- package/src/plugins/kanban/client/plugin.tsx +201 -113
- package/src/plugins/kanban/permissions.ts +100 -0
- package/src/plugins/kanban/query-keys.ts +61 -21
- package/src/plugins/kanban/schemas.ts +44 -18
- package/src/plugins/kanban/types.ts +381 -1
- package/src/plugins/media/__tests__/authorization.test.ts +1769 -0
- package/src/plugins/media/__tests__/client-runtime.browser.test.tsx +975 -0
- package/src/plugins/media/__tests__/client-runtime.test.ts +539 -0
- package/src/plugins/media/__tests__/client-runtime.typecheck.tsx +135 -0
- package/src/plugins/media/__tests__/client-sweep.test.tsx +292 -24
- package/src/plugins/media/__tests__/identity-partition.test.tsx +741 -0
- package/src/plugins/media/__tests__/plugin.test.ts +68 -28
- package/src/plugins/media/__tests__/query-key-defs.test.ts +32 -2
- package/src/plugins/media/api/adapters/vercel-blob.ts +84 -8
- package/src/plugins/media/api/getters.ts +6 -6
- package/src/plugins/media/api/index.ts +3 -0
- package/src/plugins/media/api/lifecycle-migrations.ts +11 -0
- package/src/plugins/media/api/mutations.ts +19 -13
- package/src/plugins/media/api/operations.ts +1873 -0
- package/src/plugins/media/api/plugin.ts +211 -899
- package/src/plugins/media/api/query-key-defs.ts +66 -4
- package/src/plugins/media/api/serializers.ts +4 -2
- package/src/plugins/media/api/storage-adapter.ts +22 -4
- package/src/plugins/media/asset-url.ts +14 -0
- package/src/plugins/media/client/components/media-picker/asset-card.tsx +105 -98
- package/src/plugins/media/client/components/media-picker/folder-tree.tsx +82 -60
- package/src/plugins/media/client/components/media-picker/index.tsx +235 -171
- package/src/plugins/media/client/components/media-picker/upload-tab.tsx +2 -10
- package/src/plugins/media/client/components/media-picker/url-tab.tsx +1 -8
- package/src/plugins/media/client/components/pages/library-page.internal.tsx +103 -82
- package/src/plugins/media/client/components/pages/library-page.tsx +4 -2
- package/src/plugins/media/client/components/upload-permission-check.tsx +75 -0
- package/src/plugins/media/client/constants.ts +2 -0
- package/src/plugins/media/client/hooks/media-resource.ts +2 -1
- package/src/plugins/media/client/hooks/use-media.tsx +292 -49
- package/src/plugins/media/client/index.ts +16 -3
- package/src/plugins/media/client/overrides.ts +6 -17
- package/src/plugins/media/client/plugin.tsx +188 -123
- package/src/plugins/media/client/upload.ts +170 -53
- package/src/plugins/media/db.ts +8 -0
- package/src/plugins/media/permissions.ts +61 -0
- package/src/plugins/media/query-keys.ts +63 -19
- package/src/plugins/media/types.ts +8 -2
- package/src/plugins/open-api/__tests__/route-introspection.test.ts +277 -0
- package/src/plugins/open-api/api/generator.ts +49 -8
- package/src/plugins/open-api/api/plugin.ts +36 -14
- package/src/plugins/route-docs/client/components/pages/docs-page.tsx +63 -23
- package/src/plugins/route-docs/client/constants.ts +2 -0
- package/src/plugins/route-docs/client/hooks.ts +23 -13
- package/src/plugins/route-docs/client/index.ts +7 -1
- package/src/plugins/route-docs/client/plugin.tsx +222 -145
- package/src/plugins/route-docs/client/schema.ts +31 -0
- package/src/plugins/route-docs/generator.ts +9 -5
- package/src/plugins/ui-builder/__tests__/client-sweep.test.tsx +427 -48
- package/src/plugins/ui-builder/client/components/page-renderer.tsx +58 -16
- package/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.tsx +47 -26
- package/src/plugins/ui-builder/client/components/pages/page-builder-page.tsx +43 -8
- package/src/plugins/ui-builder/client/components/pages/page-list-page.internal.tsx +38 -19
- package/src/plugins/ui-builder/client/components/pages/page-list-page.tsx +8 -2
- package/src/plugins/ui-builder/client/components/shared/default-error.tsx +2 -1
- package/src/plugins/ui-builder/client/components/shared/page-wrapper.tsx +2 -1
- package/src/plugins/ui-builder/client/constants.ts +2 -0
- package/src/plugins/ui-builder/client/hooks/ui-builder-resource.ts +2 -1
- package/src/plugins/ui-builder/client/index.ts +4 -1
- package/src/plugins/ui-builder/client/overrides.ts +16 -21
- package/src/plugins/ui-builder/client/plugin.tsx +119 -66
- package/src/plugins/ui-builder/client/registry.ts +3 -5
- package/src/plugins/ui-builder/types.ts +7 -4
- package/src/plugins/utils.ts +5 -1
- package/src/react-router/index.tsx +5 -0
- package/src/react-router/layout.ts +43 -0
- package/src/react-router/page.tsx +12 -5
- package/src/react-router/router.tsx +1 -1
- package/src/shared/auth-types.ts +19 -70
- package/src/shared/entry-factories.ts +3 -3
- package/src/shared/initial-identity.ts +73 -0
- package/src/shared/list-state.ts +12 -0
- package/src/shared/types.ts +1 -0
- package/src/tanstack/index.tsx +5 -0
- package/src/tanstack/layout.ts +36 -0
- package/src/tanstack/page.tsx +7 -2
- package/src/tanstack/router.tsx +1 -1
- package/src/tanstack/server.ts +18 -0
- package/src/types.ts +557 -65
- package/dist/shared/stack.B8vdOMnQ.d.cts +0 -329
- package/dist/shared/stack.BISAZ5LX.d.mts +0 -492
- package/dist/shared/stack.BL5PvShn.d.mts +0 -193
- package/dist/shared/stack.Be1QIHEn.d.cts +0 -23
- package/dist/shared/stack.Be1QIHEn.d.mts +0 -23
- package/dist/shared/stack.Be1QIHEn.d.ts +0 -23
- package/dist/shared/stack.BjvOk_Bs.d.cts +0 -492
- package/dist/shared/stack.BozPgbrZ.d.cts +0 -201
- package/dist/shared/stack.BozPgbrZ.d.mts +0 -201
- package/dist/shared/stack.BozPgbrZ.d.ts +0 -201
- package/dist/shared/stack.BqTR6qi2.d.ts +0 -193
- package/dist/shared/stack.BsmDN8r9.d.cts +0 -406
- package/dist/shared/stack.C1ATQ3pd.d.ts +0 -416
- package/dist/shared/stack.C4_gEENS.d.cts +0 -97
- package/dist/shared/stack.C4_gEENS.d.mts +0 -97
- package/dist/shared/stack.C4_gEENS.d.ts +0 -97
- package/dist/shared/stack.C5Y5lViI.d.cts +0 -461
- package/dist/shared/stack.C8SwdUz8.d.ts +0 -461
- package/dist/shared/stack.CBsp9rDM.d.cts +0 -193
- package/dist/shared/stack.CKGjOI3Z.d.cts +0 -840
- package/dist/shared/stack.Cf-DbL1A.d.ts +0 -329
- package/dist/shared/stack.CuI_-z-_.d.cts +0 -560
- package/dist/shared/stack.D2RVkQD8.d.mts +0 -560
- package/dist/shared/stack.DHTvwoNG.d.mts +0 -329
- package/dist/shared/stack.DJaKVY7v.d.cts +0 -76
- package/dist/shared/stack.DJaKVY7v.d.mts +0 -76
- package/dist/shared/stack.DJaKVY7v.d.ts +0 -76
- package/dist/shared/stack.DKZMaVr6.d.cts +0 -416
- package/dist/shared/stack.DOzsgzkY.d.ts +0 -406
- package/dist/shared/stack.DQq8DEIv.d.mts +0 -461
- package/dist/shared/stack.DXrOtuek.d.ts +0 -560
- package/dist/shared/stack.DZHEHd-S.d.mts +0 -840
- package/dist/shared/stack.Dd-8shBE.d.ts +0 -492
- package/dist/shared/stack.EOPVnJrF.d.cts +0 -39
- package/dist/shared/stack.EOPVnJrF.d.mts +0 -39
- package/dist/shared/stack.EOPVnJrF.d.ts +0 -39
- package/dist/shared/stack.NGKtwA6S.d.mts +0 -406
- package/dist/shared/stack.WELCDuMs.d.ts +0 -840
- package/dist/shared/stack.aXdBOJAR.d.cts +0 -173
- package/dist/shared/stack.aXdBOJAR.d.mts +0 -173
- package/dist/shared/stack.aXdBOJAR.d.ts +0 -173
- package/dist/shared/stack.oBokjN_J.d.mts +0 -416
- package/dist/shared/{stack.D18Dq3ve.d.mts → stack.Csi-4Mgz.d.cts} +1 -1
- package/dist/shared/{stack.D18Dq3ve.d.ts → stack.Csi-4Mgz.d.mts} +1 -1
- package/dist/shared/{stack.D18Dq3ve.d.cts → stack.Csi-4Mgz.d.ts} +1 -1
|
@@ -0,0 +1,1873 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type { DBAdapter as Adapter } from "@btst/db";
|
|
3
|
+
import type { PermissionFactsFor } from "@btst/stack/authorization";
|
|
4
|
+
import type { StackIdentity } from "@btst/stack/context";
|
|
5
|
+
import {
|
|
6
|
+
type DeepReadonly,
|
|
7
|
+
defineOperation,
|
|
8
|
+
type Operation,
|
|
9
|
+
OperationHttpError,
|
|
10
|
+
type OperationContext,
|
|
11
|
+
type OperationData,
|
|
12
|
+
} from "@btst/stack/plugins/api";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { mediaPermissions } from "../permissions";
|
|
15
|
+
import {
|
|
16
|
+
AssetListQuerySchema,
|
|
17
|
+
createAssetSchema,
|
|
18
|
+
createFolderSchema,
|
|
19
|
+
updateAssetSchema,
|
|
20
|
+
uploadTokenRequestSchema,
|
|
21
|
+
} from "../schemas";
|
|
22
|
+
import type {
|
|
23
|
+
Asset,
|
|
24
|
+
Folder,
|
|
25
|
+
SerializedAsset,
|
|
26
|
+
SerializedFolder,
|
|
27
|
+
} from "../types";
|
|
28
|
+
import {
|
|
29
|
+
getAssetById,
|
|
30
|
+
getFolderById,
|
|
31
|
+
listAssets,
|
|
32
|
+
listFolders,
|
|
33
|
+
} from "./getters";
|
|
34
|
+
import { createAsset } from "./mutations";
|
|
35
|
+
import { serializeAsset, serializeFolder } from "./serializers";
|
|
36
|
+
import {
|
|
37
|
+
isDirectAdapter,
|
|
38
|
+
isS3Adapter,
|
|
39
|
+
isVercelBlobAdapter,
|
|
40
|
+
type S3UploadToken,
|
|
41
|
+
type StorageAdapter,
|
|
42
|
+
type VercelBlobHandleUploadBody,
|
|
43
|
+
type VercelBlobUploadCompletedBody,
|
|
44
|
+
} from "./storage-adapter";
|
|
45
|
+
|
|
46
|
+
/** Runtime input for one asset operation. */
|
|
47
|
+
export const AssetIdOperationInputSchema = z.object({ id: z.string() });
|
|
48
|
+
/** Runtime input for an asset update operation. */
|
|
49
|
+
export const UpdateAssetOperationInputSchema = z.object({
|
|
50
|
+
id: z.string(),
|
|
51
|
+
data: updateAssetSchema,
|
|
52
|
+
});
|
|
53
|
+
/** Runtime input for one folder operation. */
|
|
54
|
+
export const FolderIdOperationInputSchema = z.object({ id: z.string() });
|
|
55
|
+
/** Runtime input for a folder collection operation. */
|
|
56
|
+
export const FolderListOperationInputSchema = z.object({
|
|
57
|
+
parentId: z.string().nullable().optional(),
|
|
58
|
+
});
|
|
59
|
+
/** Runtime input for a direct file upload operation. */
|
|
60
|
+
export const DirectUploadOperationInputSchema = z.object({
|
|
61
|
+
filename: z.string().min(1),
|
|
62
|
+
mimeType: z.string(),
|
|
63
|
+
size: z.number().int().min(0),
|
|
64
|
+
contentBase64: z.string(),
|
|
65
|
+
folderId: z.string().optional(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const VercelGenerateBodySchema = z.object({
|
|
69
|
+
type: z.literal("blob.generate-client-token"),
|
|
70
|
+
payload: z.object({
|
|
71
|
+
pathname: z.string(),
|
|
72
|
+
multipart: z.boolean(),
|
|
73
|
+
clientPayload: z.string().nullable(),
|
|
74
|
+
callbackUrl: z.string().optional(),
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
const VercelCallbackBodyShape = z.object({
|
|
78
|
+
type: z.literal("blob.upload-completed"),
|
|
79
|
+
payload: z.object({
|
|
80
|
+
blob: z.object({ url: z.string(), pathname: z.string() }),
|
|
81
|
+
tokenPayload: z.string().nullable().optional(),
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
// Provider signatures cover JSON.stringify() of the exact parsed request body.
|
|
85
|
+
// Validate the required shape without stripping or reordering provider fields.
|
|
86
|
+
const VercelCallbackBodySchema = z.custom<
|
|
87
|
+
z.output<typeof VercelCallbackBodyShape>
|
|
88
|
+
>(
|
|
89
|
+
(value) => VercelCallbackBodyShape.safeParse(value).success,
|
|
90
|
+
"Invalid Vercel Blob callback body",
|
|
91
|
+
);
|
|
92
|
+
const LegacyVercelGenerateBodySchema = z.object({
|
|
93
|
+
pathname: z.string(),
|
|
94
|
+
clientPayload: z.string().nullable().optional(),
|
|
95
|
+
});
|
|
96
|
+
const VercelClientPayloadSchema = z.object({
|
|
97
|
+
mimeType: z.string().min(1).optional(),
|
|
98
|
+
size: z.number().int().min(0).optional(),
|
|
99
|
+
folderId: z.string().min(1).optional(),
|
|
100
|
+
});
|
|
101
|
+
/** Runtime input for the Vercel Blob token and callback operation. */
|
|
102
|
+
export const VercelBlobOperationInputSchema = z.object({
|
|
103
|
+
body: z.union([
|
|
104
|
+
VercelGenerateBodySchema,
|
|
105
|
+
VercelCallbackBodySchema,
|
|
106
|
+
LegacyVercelGenerateBodySchema,
|
|
107
|
+
]),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
type LibraryReadFacts = PermissionFactsFor<
|
|
111
|
+
typeof mediaPermissions.library.read
|
|
112
|
+
>;
|
|
113
|
+
type AssetReadFacts = PermissionFactsFor<typeof mediaPermissions.asset.read>;
|
|
114
|
+
type AssetUpdateFacts = PermissionFactsFor<
|
|
115
|
+
typeof mediaPermissions.asset.update
|
|
116
|
+
>;
|
|
117
|
+
type AssetDeleteFacts = PermissionFactsFor<
|
|
118
|
+
typeof mediaPermissions.asset.delete
|
|
119
|
+
>;
|
|
120
|
+
type AssetUploadFacts = PermissionFactsFor<
|
|
121
|
+
typeof mediaPermissions.asset.upload
|
|
122
|
+
>;
|
|
123
|
+
type FolderCreateFacts = PermissionFactsFor<
|
|
124
|
+
typeof mediaPermissions.folder.create
|
|
125
|
+
>;
|
|
126
|
+
type FolderDeleteFacts = PermissionFactsFor<
|
|
127
|
+
typeof mediaPermissions.folder.delete
|
|
128
|
+
>;
|
|
129
|
+
|
|
130
|
+
/** An authorized, row-filtered page of Media assets. */
|
|
131
|
+
export interface MediaAssetListResult {
|
|
132
|
+
readonly items: readonly SerializedAsset[];
|
|
133
|
+
readonly total: number;
|
|
134
|
+
readonly limit?: number;
|
|
135
|
+
readonly offset?: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Complete Media operation inventory shared by every server transport. */
|
|
139
|
+
export type MediaOperations = {
|
|
140
|
+
readonly listAssets: Operation<
|
|
141
|
+
typeof AssetListQuerySchema,
|
|
142
|
+
typeof mediaPermissions.library.read,
|
|
143
|
+
MediaAssetListResult
|
|
144
|
+
>;
|
|
145
|
+
readonly createAsset: Operation<
|
|
146
|
+
typeof createAssetSchema,
|
|
147
|
+
typeof mediaPermissions.asset.upload,
|
|
148
|
+
SerializedAsset
|
|
149
|
+
>;
|
|
150
|
+
readonly updateAsset: Operation<
|
|
151
|
+
typeof UpdateAssetOperationInputSchema,
|
|
152
|
+
typeof mediaPermissions.asset.update,
|
|
153
|
+
SerializedAsset
|
|
154
|
+
>;
|
|
155
|
+
readonly deleteAsset: Operation<
|
|
156
|
+
typeof AssetIdOperationInputSchema,
|
|
157
|
+
typeof mediaPermissions.asset.delete,
|
|
158
|
+
{ readonly success: true }
|
|
159
|
+
>;
|
|
160
|
+
readonly listFolders: Operation<
|
|
161
|
+
typeof FolderListOperationInputSchema,
|
|
162
|
+
typeof mediaPermissions.library.read,
|
|
163
|
+
readonly SerializedFolder[]
|
|
164
|
+
>;
|
|
165
|
+
readonly createFolder: Operation<
|
|
166
|
+
typeof createFolderSchema,
|
|
167
|
+
typeof mediaPermissions.folder.create,
|
|
168
|
+
SerializedFolder
|
|
169
|
+
>;
|
|
170
|
+
readonly deleteFolder: Operation<
|
|
171
|
+
typeof FolderIdOperationInputSchema,
|
|
172
|
+
typeof mediaPermissions.folder.delete,
|
|
173
|
+
{ readonly success: true }
|
|
174
|
+
>;
|
|
175
|
+
readonly uploadDirect: Operation<
|
|
176
|
+
typeof DirectUploadOperationInputSchema,
|
|
177
|
+
typeof mediaPermissions.asset.upload,
|
|
178
|
+
SerializedAsset
|
|
179
|
+
>;
|
|
180
|
+
readonly uploadToken: Operation<
|
|
181
|
+
typeof uploadTokenRequestSchema,
|
|
182
|
+
typeof mediaPermissions.asset.upload,
|
|
183
|
+
S3UploadToken
|
|
184
|
+
>;
|
|
185
|
+
readonly uploadVercelBlob: Operation<
|
|
186
|
+
typeof VercelBlobOperationInputSchema,
|
|
187
|
+
typeof mediaPermissions.asset.upload,
|
|
188
|
+
OperationData
|
|
189
|
+
>;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** A domain/HTTP failure raised after Media authorization succeeds. */
|
|
193
|
+
export class MediaOperationError extends OperationHttpError {
|
|
194
|
+
constructor(statusCode: number, message: string, code: string) {
|
|
195
|
+
super(statusCode, message, code);
|
|
196
|
+
this.name = "MediaOperationError";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Typed context passed to Media lifecycle hooks after authorization succeeds. */
|
|
201
|
+
export interface MediaApiContext<TInput = unknown, TFacts = unknown> {
|
|
202
|
+
readonly input: DeepReadonly<TInput>;
|
|
203
|
+
readonly facts: DeepReadonly<TFacts>;
|
|
204
|
+
readonly identity: DeepReadonly<StackIdentity> | null;
|
|
205
|
+
readonly request?: Request;
|
|
206
|
+
readonly headers?: Headers;
|
|
207
|
+
readonly body?: unknown;
|
|
208
|
+
readonly params?: unknown;
|
|
209
|
+
readonly query?: unknown;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Typed post-operation context passed to Media result lifecycle hooks. */
|
|
213
|
+
export interface MediaApiResultContext<
|
|
214
|
+
TInput = unknown,
|
|
215
|
+
TFacts = unknown,
|
|
216
|
+
TResult = unknown,
|
|
217
|
+
> extends MediaApiContext<TInput, TFacts> {
|
|
218
|
+
readonly result: DeepReadonly<TResult>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Validated input passed to upload lifecycle hooks. */
|
|
222
|
+
export type MediaUploadOperationInput =
|
|
223
|
+
| z.output<typeof createAssetSchema>
|
|
224
|
+
| z.output<typeof DirectUploadOperationInputSchema>
|
|
225
|
+
| z.output<typeof uploadTokenRequestSchema>
|
|
226
|
+
| z.output<typeof VercelBlobOperationInputSchema>;
|
|
227
|
+
|
|
228
|
+
/** Validated input for upload operations that create an asset result. */
|
|
229
|
+
export type MediaUploadResultOperationInput =
|
|
230
|
+
| z.output<typeof createAssetSchema>
|
|
231
|
+
| z.output<typeof DirectUploadOperationInputSchema>;
|
|
232
|
+
|
|
233
|
+
/** Every typed Media lifecycle error context. */
|
|
234
|
+
export type MediaOperationHookContext =
|
|
235
|
+
| MediaApiContext<z.output<typeof AssetListQuerySchema>, LibraryReadFacts>
|
|
236
|
+
| MediaApiContext<z.output<typeof createAssetSchema>, AssetUploadFacts>
|
|
237
|
+
| MediaApiContext<
|
|
238
|
+
z.output<typeof UpdateAssetOperationInputSchema>,
|
|
239
|
+
AssetUpdateFacts
|
|
240
|
+
>
|
|
241
|
+
| MediaApiContext<
|
|
242
|
+
z.output<typeof AssetIdOperationInputSchema>,
|
|
243
|
+
AssetDeleteFacts
|
|
244
|
+
>
|
|
245
|
+
| MediaApiContext<
|
|
246
|
+
z.output<typeof FolderListOperationInputSchema>,
|
|
247
|
+
LibraryReadFacts
|
|
248
|
+
>
|
|
249
|
+
| MediaApiContext<z.output<typeof createFolderSchema>, FolderCreateFacts>
|
|
250
|
+
| MediaApiContext<
|
|
251
|
+
z.output<typeof FolderIdOperationInputSchema>,
|
|
252
|
+
FolderDeleteFacts
|
|
253
|
+
>
|
|
254
|
+
| MediaApiContext<
|
|
255
|
+
z.output<typeof DirectUploadOperationInputSchema>,
|
|
256
|
+
AssetUploadFacts
|
|
257
|
+
>
|
|
258
|
+
| MediaApiContext<z.output<typeof uploadTokenRequestSchema>, AssetUploadFacts>
|
|
259
|
+
| MediaApiContext<
|
|
260
|
+
z.output<typeof VercelBlobOperationInputSchema>,
|
|
261
|
+
AssetUploadFacts
|
|
262
|
+
>;
|
|
263
|
+
|
|
264
|
+
/** Configuration hooks for the Media operation lifecycle. */
|
|
265
|
+
export interface MediaBackendHooks {
|
|
266
|
+
onBeforeUpload?: (
|
|
267
|
+
meta: { filename: string; mimeType: string; size?: number },
|
|
268
|
+
context: MediaApiContext<MediaUploadOperationInput, AssetUploadFacts>,
|
|
269
|
+
) => Promise<void> | void;
|
|
270
|
+
onAfterUpload?: (
|
|
271
|
+
asset: DeepReadonly<SerializedAsset>,
|
|
272
|
+
context: MediaApiResultContext<
|
|
273
|
+
MediaUploadResultOperationInput,
|
|
274
|
+
AssetUploadFacts,
|
|
275
|
+
SerializedAsset
|
|
276
|
+
>,
|
|
277
|
+
) => Promise<void> | void;
|
|
278
|
+
onBeforeDeleteAsset?: (
|
|
279
|
+
asset: DeepReadonly<SerializedAsset>,
|
|
280
|
+
context: MediaApiContext<
|
|
281
|
+
z.output<typeof AssetIdOperationInputSchema>,
|
|
282
|
+
AssetDeleteFacts
|
|
283
|
+
>,
|
|
284
|
+
) => Promise<void> | void;
|
|
285
|
+
onAfterDeleteAsset?: (
|
|
286
|
+
assetId: string,
|
|
287
|
+
context: MediaApiResultContext<
|
|
288
|
+
z.output<typeof AssetIdOperationInputSchema>,
|
|
289
|
+
AssetDeleteFacts,
|
|
290
|
+
{ readonly success: true }
|
|
291
|
+
>,
|
|
292
|
+
) => Promise<void> | void;
|
|
293
|
+
onBeforeListAssets?: (
|
|
294
|
+
filter: z.output<typeof AssetListQuerySchema>,
|
|
295
|
+
context: MediaApiContext<
|
|
296
|
+
z.output<typeof AssetListQuerySchema>,
|
|
297
|
+
LibraryReadFacts
|
|
298
|
+
>,
|
|
299
|
+
) => Promise<void> | void;
|
|
300
|
+
onBeforeUpdateAsset?: (
|
|
301
|
+
asset: DeepReadonly<SerializedAsset>,
|
|
302
|
+
updates: z.output<typeof updateAssetSchema>,
|
|
303
|
+
context: MediaApiContext<
|
|
304
|
+
z.output<typeof UpdateAssetOperationInputSchema>,
|
|
305
|
+
AssetUpdateFacts
|
|
306
|
+
>,
|
|
307
|
+
) => Promise<void> | void;
|
|
308
|
+
onBeforeListFolders?: (
|
|
309
|
+
filter: { parentId?: string | null },
|
|
310
|
+
context: MediaApiContext<
|
|
311
|
+
z.output<typeof FolderListOperationInputSchema>,
|
|
312
|
+
LibraryReadFacts
|
|
313
|
+
>,
|
|
314
|
+
) => Promise<void> | void;
|
|
315
|
+
onBeforeCreateFolder?: (
|
|
316
|
+
input: z.output<typeof createFolderSchema>,
|
|
317
|
+
context: MediaApiContext<
|
|
318
|
+
z.output<typeof createFolderSchema>,
|
|
319
|
+
FolderCreateFacts
|
|
320
|
+
>,
|
|
321
|
+
) => Promise<void> | void;
|
|
322
|
+
onBeforeDeleteFolder?: (
|
|
323
|
+
folder: DeepReadonly<SerializedFolder>,
|
|
324
|
+
context: MediaApiContext<
|
|
325
|
+
z.output<typeof FolderIdOperationInputSchema>,
|
|
326
|
+
FolderDeleteFacts
|
|
327
|
+
>,
|
|
328
|
+
) => Promise<void> | void;
|
|
329
|
+
/** Observes failures after authorization without replacing the original error. */
|
|
330
|
+
onError?: (
|
|
331
|
+
error: Error,
|
|
332
|
+
context: MediaOperationHookContext,
|
|
333
|
+
) => Promise<void> | void;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
interface MediaResolverContext {
|
|
337
|
+
readonly input: unknown;
|
|
338
|
+
readonly request?: Request;
|
|
339
|
+
readonly headers?: Headers;
|
|
340
|
+
readonly body?: unknown;
|
|
341
|
+
readonly params?: unknown;
|
|
342
|
+
readonly query?: unknown;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Server-only dependencies and lifecycle configuration for Media operations. */
|
|
346
|
+
export interface MediaOperationsConfig {
|
|
347
|
+
/** Storage provider used for upload initialization, storage effects, and cleanup. */
|
|
348
|
+
storageAdapter: StorageAdapter;
|
|
349
|
+
/** Maximum accepted upload size in bytes. */
|
|
350
|
+
maxFileSizeBytes?: number;
|
|
351
|
+
/** Optional exact or wildcard MIME allowlist. */
|
|
352
|
+
allowedMimeTypes?: string[];
|
|
353
|
+
/** Trusted public URL prefixes accepted during asset finalization. */
|
|
354
|
+
allowedUrlPrefixes?: string[];
|
|
355
|
+
/** Post-authorization Media domain lifecycle hooks. */
|
|
356
|
+
hooks?: MediaBackendHooks;
|
|
357
|
+
/** Resolve server-only collection scope from validated input/request context. */
|
|
358
|
+
resolveTenantId?: (
|
|
359
|
+
context: MediaResolverContext,
|
|
360
|
+
) => Promise<string | null | undefined> | string | null | undefined;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
interface AssetSnapshot {
|
|
364
|
+
readonly id: string;
|
|
365
|
+
readonly filename: string;
|
|
366
|
+
readonly originalName: string;
|
|
367
|
+
readonly mimeType: string;
|
|
368
|
+
readonly size: number;
|
|
369
|
+
readonly url: string;
|
|
370
|
+
readonly folderId?: string;
|
|
371
|
+
readonly alt?: string;
|
|
372
|
+
readonly tenantId?: string;
|
|
373
|
+
readonly createdAt: Date;
|
|
374
|
+
readonly updatedAt?: Date;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
interface FolderSnapshot {
|
|
378
|
+
readonly id: string;
|
|
379
|
+
readonly name: string;
|
|
380
|
+
readonly parentId?: string;
|
|
381
|
+
readonly tenantId?: string;
|
|
382
|
+
readonly createdAt: Date;
|
|
383
|
+
readonly updatedAt?: Date;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function assetSnapshot(asset: Asset): AssetSnapshot {
|
|
387
|
+
return {
|
|
388
|
+
id: asset.id,
|
|
389
|
+
filename: asset.filename,
|
|
390
|
+
originalName: asset.originalName,
|
|
391
|
+
mimeType: asset.mimeType,
|
|
392
|
+
size: asset.size,
|
|
393
|
+
url: asset.url,
|
|
394
|
+
...(asset.folderId ? { folderId: asset.folderId } : {}),
|
|
395
|
+
...(asset.alt ? { alt: asset.alt } : {}),
|
|
396
|
+
...(asset.tenantId ? { tenantId: asset.tenantId } : {}),
|
|
397
|
+
createdAt: asset.createdAt,
|
|
398
|
+
...(asset.updatedAt ? { updatedAt: asset.updatedAt } : {}),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function folderSnapshot(folder: Folder): FolderSnapshot {
|
|
403
|
+
return {
|
|
404
|
+
id: folder.id,
|
|
405
|
+
name: folder.name,
|
|
406
|
+
...(folder.parentId ? { parentId: folder.parentId } : {}),
|
|
407
|
+
...(folder.tenantId ? { tenantId: folder.tenantId } : {}),
|
|
408
|
+
createdAt: folder.createdAt,
|
|
409
|
+
...(folder.updatedAt ? { updatedAt: folder.updatedAt } : {}),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function sameDate(left: Date | undefined, right: Date | undefined) {
|
|
414
|
+
return left?.getTime() === right?.getTime();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function sameAsset(asset: Asset | null, expected: AssetSnapshot) {
|
|
418
|
+
return (
|
|
419
|
+
asset !== null &&
|
|
420
|
+
asset.id === expected.id &&
|
|
421
|
+
asset.filename === expected.filename &&
|
|
422
|
+
asset.originalName === expected.originalName &&
|
|
423
|
+
asset.mimeType === expected.mimeType &&
|
|
424
|
+
asset.size === expected.size &&
|
|
425
|
+
asset.url === expected.url &&
|
|
426
|
+
(asset.folderId || undefined) === expected.folderId &&
|
|
427
|
+
(asset.alt || undefined) === expected.alt &&
|
|
428
|
+
(asset.tenantId || undefined) === expected.tenantId &&
|
|
429
|
+
sameDate(asset.createdAt, expected.createdAt) &&
|
|
430
|
+
sameDate(asset.updatedAt, expected.updatedAt)
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function sameFolder(folder: Folder | null, expected: FolderSnapshot) {
|
|
435
|
+
return (
|
|
436
|
+
folder !== null &&
|
|
437
|
+
folder.id === expected.id &&
|
|
438
|
+
folder.name === expected.name &&
|
|
439
|
+
(folder.parentId || undefined) === expected.parentId &&
|
|
440
|
+
(folder.tenantId || undefined) === expected.tenantId &&
|
|
441
|
+
sameDate(folder.createdAt, expected.createdAt) &&
|
|
442
|
+
sameDate(folder.updatedAt, expected.updatedAt)
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function nextVersion(previous: Date) {
|
|
447
|
+
return new Date(Math.max(Date.now(), previous.getTime() + 1));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const AFFECTED_ROW_KEYS = [
|
|
451
|
+
"rowCount",
|
|
452
|
+
"affectedRows",
|
|
453
|
+
"rowsAffected",
|
|
454
|
+
"changes",
|
|
455
|
+
"numUpdatedRows",
|
|
456
|
+
] as const;
|
|
457
|
+
|
|
458
|
+
function positive(value: unknown) {
|
|
459
|
+
return (
|
|
460
|
+
(typeof value === "number" && Number.isFinite(value) && value > 0) ||
|
|
461
|
+
(typeof value === "bigint" && value > 0n)
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function didAffectRow(result: unknown, id: string): boolean {
|
|
466
|
+
if (typeof result === "number" || typeof result === "bigint")
|
|
467
|
+
return positive(result);
|
|
468
|
+
if (!result || typeof result !== "object") return false;
|
|
469
|
+
const record = result as Record<string, unknown>;
|
|
470
|
+
if ("count" in record) return positive(record.count);
|
|
471
|
+
if (Array.isArray(result))
|
|
472
|
+
return result.length > 0 && didAffectRow(result[0], id);
|
|
473
|
+
for (const key of AFFECTED_ROW_KEYS) {
|
|
474
|
+
if (key in record) return positive(record[key]);
|
|
475
|
+
}
|
|
476
|
+
if ("meta" in record) {
|
|
477
|
+
const meta = record.meta;
|
|
478
|
+
return Boolean(
|
|
479
|
+
meta &&
|
|
480
|
+
typeof meta === "object" &&
|
|
481
|
+
"changes" in meta &&
|
|
482
|
+
positive((meta as Record<string, unknown>).changes),
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
return record.id === id;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function assetWhere(snapshot: AssetSnapshot) {
|
|
489
|
+
return [
|
|
490
|
+
{ field: "id", value: snapshot.id, operator: "eq" as const },
|
|
491
|
+
{ field: "filename", value: snapshot.filename, operator: "eq" as const },
|
|
492
|
+
{
|
|
493
|
+
field: "originalName",
|
|
494
|
+
value: snapshot.originalName,
|
|
495
|
+
operator: "eq" as const,
|
|
496
|
+
},
|
|
497
|
+
{ field: "mimeType", value: snapshot.mimeType, operator: "eq" as const },
|
|
498
|
+
{ field: "size", value: snapshot.size, operator: "eq" as const },
|
|
499
|
+
{ field: "url", value: snapshot.url, operator: "eq" as const },
|
|
500
|
+
{
|
|
501
|
+
field: "folderId",
|
|
502
|
+
value: snapshot.folderId ?? null,
|
|
503
|
+
operator: "eq" as const,
|
|
504
|
+
},
|
|
505
|
+
{ field: "alt", value: snapshot.alt ?? null, operator: "eq" as const },
|
|
506
|
+
{
|
|
507
|
+
field: "tenantId",
|
|
508
|
+
value: snapshot.tenantId ?? null,
|
|
509
|
+
operator: "eq" as const,
|
|
510
|
+
},
|
|
511
|
+
{ field: "createdAt", value: snapshot.createdAt, operator: "eq" as const },
|
|
512
|
+
...(snapshot.updatedAt
|
|
513
|
+
? [
|
|
514
|
+
{
|
|
515
|
+
field: "updatedAt",
|
|
516
|
+
value: snapshot.updatedAt,
|
|
517
|
+
operator: "eq" as const,
|
|
518
|
+
},
|
|
519
|
+
]
|
|
520
|
+
: []),
|
|
521
|
+
];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function folderWhere(snapshot: FolderSnapshot) {
|
|
525
|
+
return [
|
|
526
|
+
{ field: "id", value: snapshot.id, operator: "eq" as const },
|
|
527
|
+
{ field: "name", value: snapshot.name, operator: "eq" as const },
|
|
528
|
+
{
|
|
529
|
+
field: "parentId",
|
|
530
|
+
value: snapshot.parentId ?? null,
|
|
531
|
+
operator: "eq" as const,
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
field: "tenantId",
|
|
535
|
+
value: snapshot.tenantId ?? null,
|
|
536
|
+
operator: "eq" as const,
|
|
537
|
+
},
|
|
538
|
+
{ field: "createdAt", value: snapshot.createdAt, operator: "eq" as const },
|
|
539
|
+
...(snapshot.updatedAt
|
|
540
|
+
? [
|
|
541
|
+
{
|
|
542
|
+
field: "updatedAt",
|
|
543
|
+
value: snapshot.updatedAt,
|
|
544
|
+
operator: "eq" as const,
|
|
545
|
+
},
|
|
546
|
+
]
|
|
547
|
+
: []),
|
|
548
|
+
];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function staleStateError() {
|
|
552
|
+
return new MediaOperationError(
|
|
553
|
+
409,
|
|
554
|
+
"Media state changed while authorization was being evaluated. Retry the operation.",
|
|
555
|
+
"MEDIA_STATE_CHANGED",
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function notFound(entity: "Asset" | "Folder") {
|
|
560
|
+
return new MediaOperationError(
|
|
561
|
+
404,
|
|
562
|
+
`${entity} not found`,
|
|
563
|
+
`${entity.toUpperCase()}_NOT_FOUND`,
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function normalizeError(error: unknown, fallback: string) {
|
|
568
|
+
return error instanceof Error
|
|
569
|
+
? error
|
|
570
|
+
: new Error(typeof error === "string" ? error : fallback, { cause: error });
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function runDomainHook<T>(
|
|
574
|
+
run: () => Promise<T> | T,
|
|
575
|
+
code: string,
|
|
576
|
+
): Promise<T> {
|
|
577
|
+
try {
|
|
578
|
+
return await run();
|
|
579
|
+
} catch (error) {
|
|
580
|
+
throw new MediaOperationError(
|
|
581
|
+
403,
|
|
582
|
+
normalizeError(error, "Media operation rejected").message,
|
|
583
|
+
code,
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function hookContext<TInput, TFacts, TResult>(
|
|
589
|
+
context: OperationContext<TInput, TFacts> & {
|
|
590
|
+
readonly result: DeepReadonly<TResult>;
|
|
591
|
+
},
|
|
592
|
+
legacy?: { body?: unknown; params?: unknown; query?: unknown },
|
|
593
|
+
): MediaApiResultContext<TInput, TFacts, TResult>;
|
|
594
|
+
function hookContext<TInput, TFacts>(
|
|
595
|
+
context: OperationContext<TInput, TFacts>,
|
|
596
|
+
legacy?: { body?: unknown; params?: unknown; query?: unknown },
|
|
597
|
+
): MediaApiContext<TInput, TFacts>;
|
|
598
|
+
function hookContext<TInput, TFacts, TResult>(
|
|
599
|
+
context:
|
|
600
|
+
| OperationContext<TInput, TFacts>
|
|
601
|
+
| (OperationContext<TInput, TFacts> & {
|
|
602
|
+
readonly result: DeepReadonly<TResult>;
|
|
603
|
+
}),
|
|
604
|
+
legacy: { body?: unknown; params?: unknown; query?: unknown } = {},
|
|
605
|
+
):
|
|
606
|
+
| MediaApiContext<TInput, TFacts>
|
|
607
|
+
| MediaApiResultContext<TInput, TFacts, TResult> {
|
|
608
|
+
return Object.freeze({
|
|
609
|
+
...context,
|
|
610
|
+
...(context.request
|
|
611
|
+
? { request: context.request, headers: context.request.headers }
|
|
612
|
+
: {}),
|
|
613
|
+
...legacy,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async function notifyError(
|
|
618
|
+
hook: MediaBackendHooks["onError"],
|
|
619
|
+
error: unknown,
|
|
620
|
+
context: OperationContext<any, any>,
|
|
621
|
+
legacy: { body?: unknown; params?: unknown; query?: unknown } = {},
|
|
622
|
+
) {
|
|
623
|
+
try {
|
|
624
|
+
await hook?.(
|
|
625
|
+
normalizeError(error, "Media operation failed"),
|
|
626
|
+
hookContext(context, legacy) as MediaOperationHookContext,
|
|
627
|
+
);
|
|
628
|
+
} catch {
|
|
629
|
+
// Error hooks are observational and never replace the operation failure.
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function matchesUrlPrefix(url: string, prefix: string): boolean {
|
|
634
|
+
const trimmed = prefix.trim();
|
|
635
|
+
if (!trimmed) return false;
|
|
636
|
+
if (/^[a-z][a-z\d+.-]*:\/\//i.test(trimmed)) {
|
|
637
|
+
if (trimmed.endsWith("://")) return url.startsWith(trimmed);
|
|
638
|
+
const normalized = trimmed.replace(/\/+$/, "");
|
|
639
|
+
return url === normalized || url.startsWith(`${normalized}/`);
|
|
640
|
+
}
|
|
641
|
+
return url.startsWith(`${trimmed.replace(/\/+$/, "")}/`);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function sanitizeS3KeySegment(value: string): string {
|
|
645
|
+
return value.replace(/[/\\]/g, "-").replace(/\.\./g, "_").trim() || "unknown";
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function validateMimeType(mimeType: string, allowed?: readonly string[]) {
|
|
649
|
+
if (!allowed?.length) return;
|
|
650
|
+
const accepted = allowed.some((pattern) =>
|
|
651
|
+
pattern.endsWith("/*")
|
|
652
|
+
? mimeType.startsWith(pattern.slice(0, -1))
|
|
653
|
+
: mimeType === pattern,
|
|
654
|
+
);
|
|
655
|
+
if (!accepted) {
|
|
656
|
+
throw new MediaOperationError(
|
|
657
|
+
415,
|
|
658
|
+
`MIME type '${mimeType}' is not allowed. Allowed: ${allowed.join(", ")}`,
|
|
659
|
+
"MIME_TYPE_NOT_ALLOWED",
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function validateSize(size: number, maximum: number) {
|
|
665
|
+
if (size > maximum) {
|
|
666
|
+
throw new MediaOperationError(
|
|
667
|
+
413,
|
|
668
|
+
`File size ${size} bytes exceeds the limit of ${maximum} bytes`,
|
|
669
|
+
"FILE_TOO_LARGE",
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function validateAssetUrl(
|
|
675
|
+
url: string,
|
|
676
|
+
storageAdapter: StorageAdapter,
|
|
677
|
+
allowedUrlPrefixes?: readonly string[],
|
|
678
|
+
) {
|
|
679
|
+
let allowed = true;
|
|
680
|
+
let message = "";
|
|
681
|
+
if (allowedUrlPrefixes?.length) {
|
|
682
|
+
allowed = allowedUrlPrefixes.some((prefix) =>
|
|
683
|
+
matchesUrlPrefix(url, prefix),
|
|
684
|
+
);
|
|
685
|
+
message = `URL must start with one of: ${allowedUrlPrefixes.join(", ")}`;
|
|
686
|
+
} else if (isDirectAdapter(storageAdapter)) {
|
|
687
|
+
allowed = false;
|
|
688
|
+
message =
|
|
689
|
+
"Client-supplied asset URLs are not allowed with localAdapter. Use POST /media/upload instead, or configure allowedUrlPrefixes to explicitly allow trusted URL prefixes.";
|
|
690
|
+
} else if (isS3Adapter(storageAdapter)) {
|
|
691
|
+
allowed = matchesUrlPrefix(url, storageAdapter.urlPrefix);
|
|
692
|
+
message = `URL must start with the configured S3 publicBaseUrl: ${storageAdapter.urlPrefix}`;
|
|
693
|
+
} else if (isVercelBlobAdapter(storageAdapter)) {
|
|
694
|
+
try {
|
|
695
|
+
allowed = new URL(url).hostname.endsWith(
|
|
696
|
+
storageAdapter.urlHostnameSuffix,
|
|
697
|
+
);
|
|
698
|
+
} catch {
|
|
699
|
+
allowed = false;
|
|
700
|
+
}
|
|
701
|
+
message = `URL hostname must end with ${storageAdapter.urlHostnameSuffix}`;
|
|
702
|
+
}
|
|
703
|
+
if (!allowed)
|
|
704
|
+
throw new MediaOperationError(400, message, "ASSET_URL_NOT_ALLOWED");
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function assetFacts(snapshot: AssetSnapshot) {
|
|
708
|
+
return {
|
|
709
|
+
assetId: snapshot.id,
|
|
710
|
+
...(snapshot.folderId ? { folderId: snapshot.folderId } : {}),
|
|
711
|
+
mimeType: snapshot.mimeType,
|
|
712
|
+
...(snapshot.tenantId ? { tenantId: snapshot.tenantId } : {}),
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function folderFacts(snapshot: FolderSnapshot) {
|
|
717
|
+
return {
|
|
718
|
+
folderId: snapshot.id,
|
|
719
|
+
...(snapshot.parentId ? { parentId: snapshot.parentId } : {}),
|
|
720
|
+
...(snapshot.tenantId ? { tenantId: snapshot.tenantId } : {}),
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function withoutTenant<T extends { tenantId?: string | null }>(
|
|
725
|
+
value: T,
|
|
726
|
+
): Omit<T, "tenantId"> {
|
|
727
|
+
const { tenantId: _tenantId, ...safe } = value;
|
|
728
|
+
return safe;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function plainStorageResult(value: unknown): OperationData {
|
|
732
|
+
if (value === undefined) return null;
|
|
733
|
+
let serialized: string;
|
|
734
|
+
try {
|
|
735
|
+
serialized = JSON.stringify(value);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
throw new TypeError(
|
|
738
|
+
"Storage adapter returned a non-serializable response.",
|
|
739
|
+
{ cause: error },
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
if (serialized === undefined) return null;
|
|
743
|
+
return JSON.parse(serialized) as OperationData;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
type OperationAsset = SerializedAsset & {
|
|
747
|
+
readonly [key: string]: OperationData;
|
|
748
|
+
};
|
|
749
|
+
type OperationFolder = SerializedFolder & {
|
|
750
|
+
readonly [key: string]: OperationData;
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
function operationAsset(asset: Asset): OperationAsset {
|
|
754
|
+
return serializeAsset(asset) as OperationAsset;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function operationFolder(folder: Folder): OperationFolder {
|
|
758
|
+
return serializeFolder(folder) as OperationFolder;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function operationUploadToken(token: S3UploadToken) {
|
|
762
|
+
return token as S3UploadToken & { readonly [key: string]: OperationData };
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const memoryRollbackMarkers = new WeakMap<Adapter, (error: unknown) => void>();
|
|
766
|
+
|
|
767
|
+
function markMemoryRollback(adapter: Adapter, error: unknown) {
|
|
768
|
+
memoryRollbackMarkers.get(adapter)?.(error);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* The published memory adapter rolls transactions back from a whole-database
|
|
773
|
+
* clone, so overlapping callbacks can otherwise overwrite a later winner.
|
|
774
|
+
* Serialize every access to the shared adapter, including raw helper
|
|
775
|
+
* calls, while preserving the adapter's real transaction behavior.
|
|
776
|
+
*/
|
|
777
|
+
function serializeMemoryOperations(adapter: Adapter): Adapter {
|
|
778
|
+
if (adapter.id !== "memory" || memoryRollbackMarkers.has(adapter)) {
|
|
779
|
+
return adapter;
|
|
780
|
+
}
|
|
781
|
+
const source: Adapter = { ...adapter };
|
|
782
|
+
type TransactionAdapter = Parameters<
|
|
783
|
+
Parameters<Adapter["transaction"]>[0]
|
|
784
|
+
>[0];
|
|
785
|
+
type ActiveAdapter = Omit<Adapter, "transaction"> &
|
|
786
|
+
Partial<Pick<Adapter, "transaction">>;
|
|
787
|
+
type LockContext = {
|
|
788
|
+
owner: object;
|
|
789
|
+
adapter: ActiveAdapter;
|
|
790
|
+
inTransaction?: boolean;
|
|
791
|
+
rollbackError?: unknown;
|
|
792
|
+
rollbackOnly?: boolean;
|
|
793
|
+
};
|
|
794
|
+
const lockContext = new AsyncLocalStorage<LockContext>();
|
|
795
|
+
let tail = Promise.resolve();
|
|
796
|
+
let activeOwner: object | undefined;
|
|
797
|
+
const withLock = async <T>(
|
|
798
|
+
run: (activeAdapter: ActiveAdapter) => Promise<T>,
|
|
799
|
+
): Promise<T> => {
|
|
800
|
+
const inherited = lockContext.getStore();
|
|
801
|
+
if (inherited && inherited.owner === activeOwner) {
|
|
802
|
+
return run(inherited.adapter);
|
|
803
|
+
}
|
|
804
|
+
let release = () => {};
|
|
805
|
+
const previous = tail;
|
|
806
|
+
tail = new Promise<void>((resolve) => {
|
|
807
|
+
release = resolve;
|
|
808
|
+
});
|
|
809
|
+
await previous;
|
|
810
|
+
const owner = {};
|
|
811
|
+
activeOwner = owner;
|
|
812
|
+
try {
|
|
813
|
+
return await lockContext.run({ owner, adapter: source }, () =>
|
|
814
|
+
run(source),
|
|
815
|
+
);
|
|
816
|
+
} finally {
|
|
817
|
+
if (activeOwner === owner) activeOwner = undefined;
|
|
818
|
+
release();
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
const serialized: Adapter = {
|
|
822
|
+
...source,
|
|
823
|
+
create: ((input) =>
|
|
824
|
+
withLock((active) => active.create(input))) as Adapter["create"],
|
|
825
|
+
findOne: ((input) =>
|
|
826
|
+
withLock((active) => active.findOne(input))) as Adapter["findOne"],
|
|
827
|
+
findMany: ((input) =>
|
|
828
|
+
withLock((active) => active.findMany(input))) as Adapter["findMany"],
|
|
829
|
+
count: (input) => withLock((active) => active.count(input)),
|
|
830
|
+
update: ((input) =>
|
|
831
|
+
withLock((active) => active.update(input))) as Adapter["update"],
|
|
832
|
+
updateMany: (input) => withLock((active) => active.updateMany(input)),
|
|
833
|
+
delete: ((input) =>
|
|
834
|
+
withLock((active) => active.delete(input))) as Adapter["delete"],
|
|
835
|
+
deleteMany: (input) => withLock((active) => active.deleteMany(input)),
|
|
836
|
+
consumeOne: ((input) =>
|
|
837
|
+
withLock((active) => active.consumeOne(input))) as Adapter["consumeOne"],
|
|
838
|
+
transaction: ((callback) =>
|
|
839
|
+
withLock((active) => {
|
|
840
|
+
const context = lockContext.getStore();
|
|
841
|
+
if (!context) throw new TypeError("Missing memory lock context.");
|
|
842
|
+
if (context.inTransaction) {
|
|
843
|
+
return callback(active as TransactionAdapter).catch((error) => {
|
|
844
|
+
context.rollbackOnly = true;
|
|
845
|
+
context.rollbackError = error;
|
|
846
|
+
throw error;
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
if (!active.transaction) {
|
|
850
|
+
throw new TypeError("Missing memory transaction adapter.");
|
|
851
|
+
}
|
|
852
|
+
return active.transaction((tx) =>
|
|
853
|
+
lockContext.run(
|
|
854
|
+
{ owner: context.owner, adapter: tx, inTransaction: true },
|
|
855
|
+
async () => {
|
|
856
|
+
const transactionContext = lockContext.getStore();
|
|
857
|
+
const result = await callback(tx);
|
|
858
|
+
if (transactionContext?.rollbackOnly) {
|
|
859
|
+
throw transactionContext.rollbackError;
|
|
860
|
+
}
|
|
861
|
+
return result;
|
|
862
|
+
},
|
|
863
|
+
),
|
|
864
|
+
);
|
|
865
|
+
})) as Adapter["transaction"],
|
|
866
|
+
};
|
|
867
|
+
Object.assign(adapter, serialized);
|
|
868
|
+
memoryRollbackMarkers.set(adapter, (error) => {
|
|
869
|
+
const context = lockContext.getStore();
|
|
870
|
+
if (!context || context.owner !== activeOwner || !context.inTransaction) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
context.rollbackOnly = true;
|
|
874
|
+
context.rollbackError = error;
|
|
875
|
+
});
|
|
876
|
+
return adapter;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function requireAtomicTransactions(adapter: Adapter) {
|
|
880
|
+
if (
|
|
881
|
+
adapter.id !== "memory" &&
|
|
882
|
+
typeof adapter.options?.adapterConfig.transaction !== "function"
|
|
883
|
+
) {
|
|
884
|
+
throw new MediaOperationError(
|
|
885
|
+
500,
|
|
886
|
+
"Media writes require an adapter with isolated transaction support.",
|
|
887
|
+
"ATOMIC_TRANSACTION_REQUIRED",
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
async function claimAsset(
|
|
893
|
+
adapter: Pick<Adapter, "updateMany">,
|
|
894
|
+
snapshot: AssetSnapshot,
|
|
895
|
+
) {
|
|
896
|
+
const claimedAt = nextVersion(snapshot.updatedAt ?? snapshot.createdAt);
|
|
897
|
+
const result = await adapter.updateMany({
|
|
898
|
+
model: "mediaAsset",
|
|
899
|
+
where: assetWhere(snapshot),
|
|
900
|
+
update: { updatedAt: claimedAt },
|
|
901
|
+
});
|
|
902
|
+
if (!didAffectRow(result, snapshot.id)) throw staleStateError();
|
|
903
|
+
return claimedAt;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async function claimFolder(
|
|
907
|
+
adapter: Pick<Adapter, "updateMany">,
|
|
908
|
+
snapshot: FolderSnapshot,
|
|
909
|
+
) {
|
|
910
|
+
const claimedAt = nextVersion(snapshot.updatedAt ?? snapshot.createdAt);
|
|
911
|
+
const result = await adapter.updateMany({
|
|
912
|
+
model: "mediaFolder",
|
|
913
|
+
where: folderWhere(snapshot),
|
|
914
|
+
update: { updatedAt: claimedAt },
|
|
915
|
+
});
|
|
916
|
+
if (!didAffectRow(result, snapshot.id)) throw staleStateError();
|
|
917
|
+
return claimedAt;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
async function restoreFolder(
|
|
921
|
+
adapter: Pick<Adapter, "updateMany">,
|
|
922
|
+
snapshot: FolderSnapshot,
|
|
923
|
+
claimedAt: Date,
|
|
924
|
+
) {
|
|
925
|
+
const result = await adapter.updateMany({
|
|
926
|
+
model: "mediaFolder",
|
|
927
|
+
where: folderWhere({ ...snapshot, updatedAt: claimedAt }),
|
|
928
|
+
update: { updatedAt: snapshot.updatedAt ?? snapshot.createdAt },
|
|
929
|
+
});
|
|
930
|
+
if (!didAffectRow(result, snapshot.id)) throw staleStateError();
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
async function collectFolderSubtree(
|
|
934
|
+
adapter: Pick<Adapter, "findMany">,
|
|
935
|
+
root: FolderSnapshot,
|
|
936
|
+
): Promise<FolderSnapshot[]> {
|
|
937
|
+
const subtree = [root];
|
|
938
|
+
const seen = new Set([root.id]);
|
|
939
|
+
for (let index = 0; index < subtree.length; index++) {
|
|
940
|
+
const children = await adapter.findMany<Folder>({
|
|
941
|
+
model: "mediaFolder",
|
|
942
|
+
where: [{ field: "parentId", value: subtree[index]!.id }],
|
|
943
|
+
});
|
|
944
|
+
for (const child of children) {
|
|
945
|
+
if (seen.has(child.id)) throw staleStateError();
|
|
946
|
+
if ((child.tenantId || undefined) !== root.tenantId) {
|
|
947
|
+
throw staleStateError();
|
|
948
|
+
}
|
|
949
|
+
seen.add(child.id);
|
|
950
|
+
subtree.push(folderSnapshot(child));
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return subtree;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
async function verifyClaimedFolderSubtree(
|
|
957
|
+
adapter: Pick<Adapter, "findMany" | "count">,
|
|
958
|
+
subtree: readonly FolderSnapshot[],
|
|
959
|
+
claims: ReadonlyMap<string, Date>,
|
|
960
|
+
) {
|
|
961
|
+
const root = subtree[0];
|
|
962
|
+
if (!root) throw staleStateError();
|
|
963
|
+
const currentSubtree = await collectFolderSubtree(adapter, {
|
|
964
|
+
...root,
|
|
965
|
+
updatedAt: claims.get(root.id)!,
|
|
966
|
+
});
|
|
967
|
+
if (currentSubtree.length !== subtree.length) throw staleStateError();
|
|
968
|
+
const expectedById = new Map(
|
|
969
|
+
subtree.map((folder) => [folder.id, folder] as const),
|
|
970
|
+
);
|
|
971
|
+
for (const current of currentSubtree) {
|
|
972
|
+
const expected = expectedById.get(current.id);
|
|
973
|
+
if (
|
|
974
|
+
!expected ||
|
|
975
|
+
!sameFolder(current, {
|
|
976
|
+
...expected,
|
|
977
|
+
updatedAt: claims.get(expected.id)!,
|
|
978
|
+
})
|
|
979
|
+
) {
|
|
980
|
+
throw staleStateError();
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
let totalAssets = 0;
|
|
984
|
+
for (const folder of subtree) {
|
|
985
|
+
totalAssets += await adapter.count({
|
|
986
|
+
model: "mediaAsset",
|
|
987
|
+
where: [{ field: "folderId", value: folder.id }],
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
if (totalAssets > 0) {
|
|
991
|
+
throw new MediaOperationError(
|
|
992
|
+
409,
|
|
993
|
+
`Cannot delete folder: it or one of its subfolders contains ${totalAssets} asset(s). Move or delete them first.`,
|
|
994
|
+
"FOLDER_NOT_EMPTY",
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function parseVercelInitialize(
|
|
1000
|
+
body: z.output<typeof VercelBlobOperationInputSchema>["body"],
|
|
1001
|
+
) {
|
|
1002
|
+
const record = body as unknown as Record<string, OperationData>;
|
|
1003
|
+
const payload =
|
|
1004
|
+
record.payload &&
|
|
1005
|
+
typeof record.payload === "object" &&
|
|
1006
|
+
!Array.isArray(record.payload)
|
|
1007
|
+
? (record.payload as Record<string, OperationData>)
|
|
1008
|
+
: record;
|
|
1009
|
+
const pathname =
|
|
1010
|
+
typeof payload.pathname === "string" ? payload.pathname : "asset";
|
|
1011
|
+
const clientPayload =
|
|
1012
|
+
typeof payload.clientPayload === "string" ? payload.clientPayload : null;
|
|
1013
|
+
let parsed: z.output<typeof VercelClientPayloadSchema> = {};
|
|
1014
|
+
try {
|
|
1015
|
+
parsed = VercelClientPayloadSchema.parse(
|
|
1016
|
+
clientPayload ? JSON.parse(clientPayload) : {},
|
|
1017
|
+
);
|
|
1018
|
+
} catch {
|
|
1019
|
+
throw new MediaOperationError(
|
|
1020
|
+
400,
|
|
1021
|
+
"Invalid Vercel Blob client upload metadata.",
|
|
1022
|
+
"INVALID_UPLOAD_BODY",
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
pathname,
|
|
1027
|
+
filename: pathname.split("/").pop() ?? pathname,
|
|
1028
|
+
mimeType: parsed.mimeType ?? "application/octet-stream",
|
|
1029
|
+
size: parsed.size,
|
|
1030
|
+
folderId: parsed.folderId,
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/** Create the complete Media operation inventory used by every server transport. */
|
|
1035
|
+
export function createMediaOperations(
|
|
1036
|
+
sourceAdapter: Adapter,
|
|
1037
|
+
config: MediaOperationsConfig,
|
|
1038
|
+
): MediaOperations {
|
|
1039
|
+
const adapter = serializeMemoryOperations(sourceAdapter);
|
|
1040
|
+
const {
|
|
1041
|
+
storageAdapter,
|
|
1042
|
+
maxFileSizeBytes = 10 * 1024 * 1024,
|
|
1043
|
+
allowedMimeTypes,
|
|
1044
|
+
allowedUrlPrefixes,
|
|
1045
|
+
hooks,
|
|
1046
|
+
resolveTenantId,
|
|
1047
|
+
} = config;
|
|
1048
|
+
const tenants = new WeakMap<object, string | undefined>();
|
|
1049
|
+
const assetLists = new WeakMap<
|
|
1050
|
+
object,
|
|
1051
|
+
Awaited<ReturnType<typeof listAssets>>
|
|
1052
|
+
>();
|
|
1053
|
+
const assets = new WeakMap<object, AssetSnapshot>();
|
|
1054
|
+
const folders = new WeakMap<object, FolderSnapshot | undefined>();
|
|
1055
|
+
const targetFolders = new WeakMap<object, FolderSnapshot | undefined>();
|
|
1056
|
+
const folderSubtrees = new WeakMap<object, readonly FolderSnapshot[]>();
|
|
1057
|
+
const vercelContexts = new WeakMap<
|
|
1058
|
+
object,
|
|
1059
|
+
{
|
|
1060
|
+
phase: "initialize" | "callback";
|
|
1061
|
+
pathname: string;
|
|
1062
|
+
filename: string;
|
|
1063
|
+
mimeType: string;
|
|
1064
|
+
size?: number;
|
|
1065
|
+
folderId?: string;
|
|
1066
|
+
tenantId?: string;
|
|
1067
|
+
}
|
|
1068
|
+
>();
|
|
1069
|
+
|
|
1070
|
+
const resolveTenant = async (
|
|
1071
|
+
input: object,
|
|
1072
|
+
request: Request | undefined,
|
|
1073
|
+
legacy: { body?: unknown; params?: unknown; query?: unknown },
|
|
1074
|
+
) => {
|
|
1075
|
+
const tenantId =
|
|
1076
|
+
request && resolveTenantId
|
|
1077
|
+
? ((await resolveTenantId({
|
|
1078
|
+
input,
|
|
1079
|
+
request,
|
|
1080
|
+
headers: request.headers,
|
|
1081
|
+
...legacy,
|
|
1082
|
+
})) ?? undefined)
|
|
1083
|
+
: undefined;
|
|
1084
|
+
tenants.set(input, tenantId);
|
|
1085
|
+
return tenantId;
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
const loadFolder = async (
|
|
1089
|
+
id: string | undefined,
|
|
1090
|
+
tenantId: string | undefined,
|
|
1091
|
+
) => {
|
|
1092
|
+
if (!id) return undefined;
|
|
1093
|
+
const folder = await getFolderById(adapter, id);
|
|
1094
|
+
if (!folder || (tenantId !== undefined && folder.tenantId !== tenantId)) {
|
|
1095
|
+
throw notFound("Folder");
|
|
1096
|
+
}
|
|
1097
|
+
return folderSnapshot(folder);
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
const bindFolderTenant = (
|
|
1101
|
+
input: object,
|
|
1102
|
+
resolvedTenantId: string | undefined,
|
|
1103
|
+
folder: FolderSnapshot | undefined,
|
|
1104
|
+
) => {
|
|
1105
|
+
const tenantId = resolvedTenantId ?? folder?.tenantId;
|
|
1106
|
+
tenants.set(input, tenantId);
|
|
1107
|
+
return tenantId;
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
const verifyFolder = async (
|
|
1111
|
+
reader: Pick<Adapter, "findOne">,
|
|
1112
|
+
snapshot: FolderSnapshot | undefined,
|
|
1113
|
+
) => {
|
|
1114
|
+
if (!snapshot) return;
|
|
1115
|
+
const current =
|
|
1116
|
+
(await reader.findOne<Folder>({
|
|
1117
|
+
model: "mediaFolder",
|
|
1118
|
+
where: [{ field: "id", value: snapshot.id }],
|
|
1119
|
+
})) ?? null;
|
|
1120
|
+
if (!sameFolder(current, snapshot)) throw staleStateError();
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
const listAssetsOperation = defineOperation({
|
|
1124
|
+
input: AssetListQuerySchema,
|
|
1125
|
+
permission: mediaPermissions.library.read,
|
|
1126
|
+
facts: async ({ input, request }) => {
|
|
1127
|
+
await resolveTenant(input as object, request, { query: input });
|
|
1128
|
+
return undefined;
|
|
1129
|
+
},
|
|
1130
|
+
additionalPermissions: async ({ input }) => {
|
|
1131
|
+
const result = await listAssets(adapter, {
|
|
1132
|
+
...input,
|
|
1133
|
+
tenantId: tenants.get(input as object),
|
|
1134
|
+
});
|
|
1135
|
+
assetLists.set(input as object, result);
|
|
1136
|
+
return result.items.map((asset) =>
|
|
1137
|
+
mediaPermissions.asset.read(assetFacts(assetSnapshot(asset))),
|
|
1138
|
+
);
|
|
1139
|
+
},
|
|
1140
|
+
execute: async (context) => {
|
|
1141
|
+
await runDomainHook(
|
|
1142
|
+
() =>
|
|
1143
|
+
hooks?.onBeforeListAssets?.(
|
|
1144
|
+
{ ...context.input },
|
|
1145
|
+
hookContext(context, { query: context.input }),
|
|
1146
|
+
),
|
|
1147
|
+
"LIST_ASSETS_REJECTED",
|
|
1148
|
+
);
|
|
1149
|
+
const result = assetLists.get(context.input as object);
|
|
1150
|
+
assetLists.delete(context.input as object);
|
|
1151
|
+
if (!result) {
|
|
1152
|
+
throw new MediaOperationError(
|
|
1153
|
+
500,
|
|
1154
|
+
"Asset-list authorization snapshot is unavailable.",
|
|
1155
|
+
"AUTHORIZATION_SNAPSHOT_MISSING",
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
return {
|
|
1159
|
+
items: result.items.map((asset) =>
|
|
1160
|
+
withoutTenant(serializeAsset(asset)),
|
|
1161
|
+
),
|
|
1162
|
+
total: result.total,
|
|
1163
|
+
...(result.limit !== undefined ? { limit: result.limit } : {}),
|
|
1164
|
+
...(result.offset !== undefined ? { offset: result.offset } : {}),
|
|
1165
|
+
};
|
|
1166
|
+
},
|
|
1167
|
+
onError: ({ error, ...context }) =>
|
|
1168
|
+
notifyError(hooks?.onError, error, context, {
|
|
1169
|
+
query: context.input,
|
|
1170
|
+
}),
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
const createAssetOperation = defineOperation({
|
|
1174
|
+
input: createAssetSchema,
|
|
1175
|
+
permission: mediaPermissions.asset.upload,
|
|
1176
|
+
facts: async ({ input, request }) => {
|
|
1177
|
+
const tenantId = await resolveTenant(input as object, request, {
|
|
1178
|
+
body: input,
|
|
1179
|
+
});
|
|
1180
|
+
const folder = await loadFolder(input.folderId, tenantId);
|
|
1181
|
+
const effectiveTenantId = bindFolderTenant(
|
|
1182
|
+
input as object,
|
|
1183
|
+
tenantId,
|
|
1184
|
+
folder,
|
|
1185
|
+
);
|
|
1186
|
+
folders.set(input as object, folder);
|
|
1187
|
+
return {
|
|
1188
|
+
phase: "finalize" as const,
|
|
1189
|
+
...(folder ? { folderId: folder.id } : {}),
|
|
1190
|
+
mimeType: input.mimeType,
|
|
1191
|
+
...(effectiveTenantId ? { tenantId: effectiveTenantId } : {}),
|
|
1192
|
+
} satisfies AssetUploadFacts;
|
|
1193
|
+
},
|
|
1194
|
+
execute: (context) => {
|
|
1195
|
+
requireAtomicTransactions(adapter);
|
|
1196
|
+
return adapter.transaction(async (tx) => {
|
|
1197
|
+
const folder = folders.get(context.input as object);
|
|
1198
|
+
await verifyFolder(tx, folder);
|
|
1199
|
+
const folderClaim = folder ? await claimFolder(tx, folder) : undefined;
|
|
1200
|
+
await runDomainHook(
|
|
1201
|
+
() =>
|
|
1202
|
+
hooks?.onBeforeUpload?.(
|
|
1203
|
+
{
|
|
1204
|
+
filename: context.input.filename,
|
|
1205
|
+
mimeType: context.input.mimeType,
|
|
1206
|
+
size: context.input.size,
|
|
1207
|
+
},
|
|
1208
|
+
hookContext(context, { body: context.input }),
|
|
1209
|
+
),
|
|
1210
|
+
"UPLOAD_REJECTED",
|
|
1211
|
+
);
|
|
1212
|
+
validateMimeType(context.input.mimeType, allowedMimeTypes);
|
|
1213
|
+
validateSize(context.input.size, maxFileSizeBytes);
|
|
1214
|
+
validateAssetUrl(context.input.url, storageAdapter, allowedUrlPrefixes);
|
|
1215
|
+
const asset = await createAsset(tx, {
|
|
1216
|
+
...context.input,
|
|
1217
|
+
tenantId: tenants.get(context.input as object),
|
|
1218
|
+
});
|
|
1219
|
+
if (folder && folderClaim) await restoreFolder(tx, folder, folderClaim);
|
|
1220
|
+
return operationAsset(asset);
|
|
1221
|
+
});
|
|
1222
|
+
},
|
|
1223
|
+
after: ((context: any) =>
|
|
1224
|
+
hooks?.onAfterUpload?.(
|
|
1225
|
+
context.result,
|
|
1226
|
+
hookContext(context, { body: context.input }) as MediaApiResultContext<
|
|
1227
|
+
z.output<typeof createAssetSchema>,
|
|
1228
|
+
AssetUploadFacts,
|
|
1229
|
+
SerializedAsset
|
|
1230
|
+
>,
|
|
1231
|
+
)) as any,
|
|
1232
|
+
onError: ({ error, ...context }) => {
|
|
1233
|
+
markMemoryRollback(adapter, error);
|
|
1234
|
+
return notifyError(hooks?.onError, error, context, {
|
|
1235
|
+
body: context.input,
|
|
1236
|
+
});
|
|
1237
|
+
},
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
const updateAssetOperation = defineOperation({
|
|
1241
|
+
input: UpdateAssetOperationInputSchema,
|
|
1242
|
+
permission: mediaPermissions.asset.update,
|
|
1243
|
+
facts: async ({ input, request }) => {
|
|
1244
|
+
const tenantId = await resolveTenant(input as object, request, {
|
|
1245
|
+
body: input.data,
|
|
1246
|
+
params: { id: input.id },
|
|
1247
|
+
});
|
|
1248
|
+
const asset = await getAssetById(adapter, input.id);
|
|
1249
|
+
if (!asset || (tenantId !== undefined && asset.tenantId !== tenantId)) {
|
|
1250
|
+
throw notFound("Asset");
|
|
1251
|
+
}
|
|
1252
|
+
const snapshot = assetSnapshot(asset);
|
|
1253
|
+
assets.set(input as object, snapshot);
|
|
1254
|
+
const changesFolder = Object.hasOwn(input.data, "folderId");
|
|
1255
|
+
const target =
|
|
1256
|
+
typeof input.data.folderId === "string"
|
|
1257
|
+
? await loadFolder(input.data.folderId, tenantId)
|
|
1258
|
+
: undefined;
|
|
1259
|
+
if (target && target.tenantId !== snapshot.tenantId) {
|
|
1260
|
+
throw notFound("Folder");
|
|
1261
|
+
}
|
|
1262
|
+
targetFolders.set(input as object, target);
|
|
1263
|
+
return {
|
|
1264
|
+
...assetFacts(snapshot),
|
|
1265
|
+
...(changesFolder ? { targetFolderId: target?.id ?? null } : {}),
|
|
1266
|
+
} satisfies AssetUpdateFacts;
|
|
1267
|
+
},
|
|
1268
|
+
execute: (context) => {
|
|
1269
|
+
requireAtomicTransactions(adapter);
|
|
1270
|
+
return adapter.transaction(async (tx) => {
|
|
1271
|
+
const snapshot = assets.get(context.input as object);
|
|
1272
|
+
if (!snapshot) throw staleStateError();
|
|
1273
|
+
const current =
|
|
1274
|
+
(await tx.findOne<Asset>({
|
|
1275
|
+
model: "mediaAsset",
|
|
1276
|
+
where: [{ field: "id", value: snapshot.id }],
|
|
1277
|
+
})) ?? null;
|
|
1278
|
+
if (!sameAsset(current, snapshot)) throw staleStateError();
|
|
1279
|
+
const target = targetFolders.get(context.input as object);
|
|
1280
|
+
await verifyFolder(tx, target);
|
|
1281
|
+
const targetClaim = target ? await claimFolder(tx, target) : undefined;
|
|
1282
|
+
const claimedAt = await claimAsset(tx, snapshot);
|
|
1283
|
+
await runDomainHook(
|
|
1284
|
+
() =>
|
|
1285
|
+
hooks?.onBeforeUpdateAsset?.(
|
|
1286
|
+
operationAsset(current as Asset),
|
|
1287
|
+
{ ...context.input.data },
|
|
1288
|
+
hookContext(context, {
|
|
1289
|
+
body: context.input.data,
|
|
1290
|
+
params: { id: context.input.id },
|
|
1291
|
+
}),
|
|
1292
|
+
),
|
|
1293
|
+
"UPDATE_ASSET_REJECTED",
|
|
1294
|
+
);
|
|
1295
|
+
const update = {
|
|
1296
|
+
...(context.input.data.alt !== undefined
|
|
1297
|
+
? { alt: context.input.data.alt }
|
|
1298
|
+
: {}),
|
|
1299
|
+
...(Object.hasOwn(context.input.data, "folderId")
|
|
1300
|
+
? { folderId: context.input.data.folderId }
|
|
1301
|
+
: {}),
|
|
1302
|
+
updatedAt: nextVersion(claimedAt),
|
|
1303
|
+
};
|
|
1304
|
+
const matched = await tx.updateMany({
|
|
1305
|
+
model: "mediaAsset",
|
|
1306
|
+
where: assetWhere({ ...snapshot, updatedAt: claimedAt }),
|
|
1307
|
+
update,
|
|
1308
|
+
});
|
|
1309
|
+
if (!didAffectRow(matched, snapshot.id)) throw staleStateError();
|
|
1310
|
+
if (target && targetClaim) await restoreFolder(tx, target, targetClaim);
|
|
1311
|
+
const updated = await getAssetById(tx, snapshot.id);
|
|
1312
|
+
if (!updated) throw notFound("Asset");
|
|
1313
|
+
return operationAsset(updated);
|
|
1314
|
+
});
|
|
1315
|
+
},
|
|
1316
|
+
onError: ({ error, ...context }) => {
|
|
1317
|
+
markMemoryRollback(adapter, error);
|
|
1318
|
+
return notifyError(hooks?.onError, error, context, {
|
|
1319
|
+
body: context.input.data,
|
|
1320
|
+
params: { id: context.input.id },
|
|
1321
|
+
});
|
|
1322
|
+
},
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
const deleteAssetOperation = defineOperation({
|
|
1326
|
+
input: AssetIdOperationInputSchema,
|
|
1327
|
+
permission: mediaPermissions.asset.delete,
|
|
1328
|
+
facts: async ({ input, request }) => {
|
|
1329
|
+
const tenantId = await resolveTenant(input as object, request, {
|
|
1330
|
+
params: { id: input.id },
|
|
1331
|
+
});
|
|
1332
|
+
const asset = await getAssetById(adapter, input.id);
|
|
1333
|
+
if (!asset || (tenantId !== undefined && asset.tenantId !== tenantId)) {
|
|
1334
|
+
throw notFound("Asset");
|
|
1335
|
+
}
|
|
1336
|
+
const snapshot = assetSnapshot(asset);
|
|
1337
|
+
assets.set(input as object, snapshot);
|
|
1338
|
+
return assetFacts(snapshot) satisfies AssetDeleteFacts;
|
|
1339
|
+
},
|
|
1340
|
+
execute: (context) => {
|
|
1341
|
+
requireAtomicTransactions(adapter);
|
|
1342
|
+
return adapter.transaction(async (tx) => {
|
|
1343
|
+
const snapshot = assets.get(context.input as object);
|
|
1344
|
+
if (!snapshot) throw staleStateError();
|
|
1345
|
+
const current = await getAssetById(tx, snapshot.id);
|
|
1346
|
+
if (!sameAsset(current, snapshot)) throw staleStateError();
|
|
1347
|
+
const claimedAt = await claimAsset(tx, snapshot);
|
|
1348
|
+
await runDomainHook(
|
|
1349
|
+
() =>
|
|
1350
|
+
hooks?.onBeforeDeleteAsset?.(
|
|
1351
|
+
operationAsset(current as Asset),
|
|
1352
|
+
hookContext(context, { params: { id: context.input.id } }),
|
|
1353
|
+
),
|
|
1354
|
+
"DELETE_ASSET_REJECTED",
|
|
1355
|
+
);
|
|
1356
|
+
try {
|
|
1357
|
+
await storageAdapter.delete(snapshot.url);
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
throw new MediaOperationError(
|
|
1360
|
+
500,
|
|
1361
|
+
"Failed to delete file from storage",
|
|
1362
|
+
"STORAGE_DELETE_FAILED",
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
const deleted = await tx.deleteMany({
|
|
1366
|
+
model: "mediaAsset",
|
|
1367
|
+
where: assetWhere({ ...snapshot, updatedAt: claimedAt }),
|
|
1368
|
+
});
|
|
1369
|
+
if (!didAffectRow(deleted, snapshot.id)) throw staleStateError();
|
|
1370
|
+
return { success: true } as const;
|
|
1371
|
+
});
|
|
1372
|
+
},
|
|
1373
|
+
after: (context) =>
|
|
1374
|
+
hooks?.onAfterDeleteAsset?.(
|
|
1375
|
+
context.input.id,
|
|
1376
|
+
hookContext(context, { params: { id: context.input.id } }),
|
|
1377
|
+
),
|
|
1378
|
+
onError: ({ error, ...context }) => {
|
|
1379
|
+
markMemoryRollback(adapter, error);
|
|
1380
|
+
return notifyError(hooks?.onError, error, context, {
|
|
1381
|
+
params: { id: context.input.id },
|
|
1382
|
+
});
|
|
1383
|
+
},
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
const listFoldersOperation = defineOperation({
|
|
1387
|
+
input: FolderListOperationInputSchema,
|
|
1388
|
+
permission: mediaPermissions.library.read,
|
|
1389
|
+
facts: async ({ input, request }) => {
|
|
1390
|
+
await resolveTenant(input as object, request, { query: input });
|
|
1391
|
+
return undefined;
|
|
1392
|
+
},
|
|
1393
|
+
execute: async (context) => {
|
|
1394
|
+
const filter = { parentId: context.input.parentId };
|
|
1395
|
+
await runDomainHook(
|
|
1396
|
+
() =>
|
|
1397
|
+
hooks?.onBeforeListFolders?.(
|
|
1398
|
+
filter,
|
|
1399
|
+
hookContext(context, { query: context.input }),
|
|
1400
|
+
),
|
|
1401
|
+
"LIST_FOLDERS_REJECTED",
|
|
1402
|
+
);
|
|
1403
|
+
const result = await listFolders(adapter, {
|
|
1404
|
+
...filter,
|
|
1405
|
+
tenantId: tenants.get(context.input as object),
|
|
1406
|
+
});
|
|
1407
|
+
return result.map((folder) => withoutTenant(serializeFolder(folder)));
|
|
1408
|
+
},
|
|
1409
|
+
onError: ({ error, ...context }) =>
|
|
1410
|
+
notifyError(hooks?.onError, error, context, {
|
|
1411
|
+
query: context.input,
|
|
1412
|
+
}),
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
const createFolderOperation = defineOperation({
|
|
1416
|
+
input: createFolderSchema,
|
|
1417
|
+
permission: mediaPermissions.folder.create,
|
|
1418
|
+
facts: async ({ input, request }) => {
|
|
1419
|
+
const tenantId = await resolveTenant(input as object, request, {
|
|
1420
|
+
body: input,
|
|
1421
|
+
});
|
|
1422
|
+
const parent = await loadFolder(input.parentId, tenantId);
|
|
1423
|
+
const effectiveTenantId = bindFolderTenant(
|
|
1424
|
+
input as object,
|
|
1425
|
+
tenantId,
|
|
1426
|
+
parent,
|
|
1427
|
+
);
|
|
1428
|
+
folders.set(input as object, parent);
|
|
1429
|
+
return {
|
|
1430
|
+
...(parent ? { parentId: parent.id } : {}),
|
|
1431
|
+
...(effectiveTenantId ? { tenantId: effectiveTenantId } : {}),
|
|
1432
|
+
} satisfies FolderCreateFacts;
|
|
1433
|
+
},
|
|
1434
|
+
execute: (context) => {
|
|
1435
|
+
requireAtomicTransactions(adapter);
|
|
1436
|
+
return adapter.transaction(async (tx) => {
|
|
1437
|
+
const parent = folders.get(context.input as object);
|
|
1438
|
+
await verifyFolder(tx, parent);
|
|
1439
|
+
const parentClaim = parent ? await claimFolder(tx, parent) : undefined;
|
|
1440
|
+
await runDomainHook(
|
|
1441
|
+
() =>
|
|
1442
|
+
hooks?.onBeforeCreateFolder?.(
|
|
1443
|
+
{ ...context.input },
|
|
1444
|
+
hookContext(context, { body: context.input }),
|
|
1445
|
+
),
|
|
1446
|
+
"CREATE_FOLDER_REJECTED",
|
|
1447
|
+
);
|
|
1448
|
+
const now = new Date();
|
|
1449
|
+
const folder = await tx.create<Folder>({
|
|
1450
|
+
model: "mediaFolder",
|
|
1451
|
+
data: {
|
|
1452
|
+
...context.input,
|
|
1453
|
+
tenantId: tenants.get(context.input as object),
|
|
1454
|
+
createdAt: now,
|
|
1455
|
+
updatedAt: now,
|
|
1456
|
+
},
|
|
1457
|
+
});
|
|
1458
|
+
if (parent && parentClaim) await restoreFolder(tx, parent, parentClaim);
|
|
1459
|
+
return operationFolder(folder);
|
|
1460
|
+
});
|
|
1461
|
+
},
|
|
1462
|
+
onError: ({ error, ...context }) => {
|
|
1463
|
+
markMemoryRollback(adapter, error);
|
|
1464
|
+
return notifyError(hooks?.onError, error, context, {
|
|
1465
|
+
body: context.input,
|
|
1466
|
+
});
|
|
1467
|
+
},
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
const deleteFolderOperation = defineOperation({
|
|
1471
|
+
input: FolderIdOperationInputSchema,
|
|
1472
|
+
permission: mediaPermissions.folder.delete,
|
|
1473
|
+
facts: async ({ input, request }) => {
|
|
1474
|
+
const tenantId = await resolveTenant(input as object, request, {
|
|
1475
|
+
params: { id: input.id },
|
|
1476
|
+
});
|
|
1477
|
+
const folder = await loadFolder(input.id, tenantId);
|
|
1478
|
+
if (!folder) throw notFound("Folder");
|
|
1479
|
+
folders.set(input as object, folder);
|
|
1480
|
+
return folderFacts(folder) satisfies FolderDeleteFacts;
|
|
1481
|
+
},
|
|
1482
|
+
additionalPermissions: async ({ input }) => {
|
|
1483
|
+
const root = folders.get(input as object);
|
|
1484
|
+
if (!root) throw staleStateError();
|
|
1485
|
+
const subtree = await collectFolderSubtree(adapter, root);
|
|
1486
|
+
folderSubtrees.set(input as object, subtree);
|
|
1487
|
+
return subtree
|
|
1488
|
+
.slice(1)
|
|
1489
|
+
.map((folder) => mediaPermissions.folder.delete(folderFacts(folder)));
|
|
1490
|
+
},
|
|
1491
|
+
execute: (context) => {
|
|
1492
|
+
requireAtomicTransactions(adapter);
|
|
1493
|
+
return adapter.transaction(async (tx) => {
|
|
1494
|
+
const subtree = folderSubtrees.get(context.input as object);
|
|
1495
|
+
folderSubtrees.delete(context.input as object);
|
|
1496
|
+
if (!subtree?.length) throw staleStateError();
|
|
1497
|
+
const snapshot = subtree[0]!;
|
|
1498
|
+
const claims = new Map<string, Date>();
|
|
1499
|
+
for (const folder of subtree) {
|
|
1500
|
+
claims.set(folder.id, await claimFolder(tx, folder));
|
|
1501
|
+
}
|
|
1502
|
+
await verifyClaimedFolderSubtree(tx, subtree, claims);
|
|
1503
|
+
await runDomainHook(
|
|
1504
|
+
() =>
|
|
1505
|
+
hooks?.onBeforeDeleteFolder?.(
|
|
1506
|
+
operationFolder({
|
|
1507
|
+
...snapshot,
|
|
1508
|
+
updatedAt: snapshot.updatedAt,
|
|
1509
|
+
}),
|
|
1510
|
+
hookContext(context, { params: { id: context.input.id } }),
|
|
1511
|
+
),
|
|
1512
|
+
"DELETE_FOLDER_REJECTED",
|
|
1513
|
+
);
|
|
1514
|
+
await verifyClaimedFolderSubtree(tx, subtree, claims);
|
|
1515
|
+
for (const folder of [...subtree].reverse()) {
|
|
1516
|
+
const deleted = await tx.deleteMany({
|
|
1517
|
+
model: "mediaFolder",
|
|
1518
|
+
where: folderWhere({
|
|
1519
|
+
...folder,
|
|
1520
|
+
updatedAt: claims.get(folder.id)!,
|
|
1521
|
+
}),
|
|
1522
|
+
});
|
|
1523
|
+
if (!didAffectRow(deleted, folder.id)) throw staleStateError();
|
|
1524
|
+
}
|
|
1525
|
+
return { success: true } as const;
|
|
1526
|
+
});
|
|
1527
|
+
},
|
|
1528
|
+
onError: ({ error, ...context }) => {
|
|
1529
|
+
markMemoryRollback(adapter, error);
|
|
1530
|
+
return notifyError(hooks?.onError, error, context, {
|
|
1531
|
+
params: { id: context.input.id },
|
|
1532
|
+
});
|
|
1533
|
+
},
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
const uploadDirectOperation = defineOperation({
|
|
1537
|
+
input: DirectUploadOperationInputSchema,
|
|
1538
|
+
permission: mediaPermissions.asset.upload,
|
|
1539
|
+
facts: async ({ input, request }) => {
|
|
1540
|
+
const tenantId = await resolveTenant(input as object, request, {
|
|
1541
|
+
body: input,
|
|
1542
|
+
});
|
|
1543
|
+
const folder = await loadFolder(input.folderId, tenantId);
|
|
1544
|
+
const effectiveTenantId = bindFolderTenant(
|
|
1545
|
+
input as object,
|
|
1546
|
+
tenantId,
|
|
1547
|
+
folder,
|
|
1548
|
+
);
|
|
1549
|
+
folders.set(input as object, folder);
|
|
1550
|
+
return {
|
|
1551
|
+
phase: "direct" as const,
|
|
1552
|
+
...(folder ? { folderId: folder.id } : {}),
|
|
1553
|
+
mimeType: input.mimeType,
|
|
1554
|
+
...(effectiveTenantId ? { tenantId: effectiveTenantId } : {}),
|
|
1555
|
+
} satisfies AssetUploadFacts;
|
|
1556
|
+
},
|
|
1557
|
+
execute: async (context) => {
|
|
1558
|
+
requireAtomicTransactions(adapter);
|
|
1559
|
+
if (!isDirectAdapter(storageAdapter)) {
|
|
1560
|
+
throw new MediaOperationError(
|
|
1561
|
+
400,
|
|
1562
|
+
"Direct upload is only supported with the local storage adapter",
|
|
1563
|
+
"DIRECT_UPLOAD_UNSUPPORTED",
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
let uploadedUrl: string | undefined;
|
|
1567
|
+
try {
|
|
1568
|
+
return await adapter.transaction(async (tx) => {
|
|
1569
|
+
const folder = folders.get(context.input as object);
|
|
1570
|
+
await verifyFolder(tx, folder);
|
|
1571
|
+
const folderClaim = folder
|
|
1572
|
+
? await claimFolder(tx, folder)
|
|
1573
|
+
: undefined;
|
|
1574
|
+
await runDomainHook(
|
|
1575
|
+
() =>
|
|
1576
|
+
hooks?.onBeforeUpload?.(
|
|
1577
|
+
{
|
|
1578
|
+
filename: context.input.filename,
|
|
1579
|
+
mimeType: context.input.mimeType,
|
|
1580
|
+
size: context.input.size,
|
|
1581
|
+
},
|
|
1582
|
+
hookContext(context, { body: context.input }),
|
|
1583
|
+
),
|
|
1584
|
+
"UPLOAD_REJECTED",
|
|
1585
|
+
);
|
|
1586
|
+
validateMimeType(context.input.mimeType, allowedMimeTypes);
|
|
1587
|
+
validateSize(context.input.size, maxFileSizeBytes);
|
|
1588
|
+
const buffer = Buffer.from(context.input.contentBase64, "base64");
|
|
1589
|
+
if (buffer.byteLength !== context.input.size) {
|
|
1590
|
+
throw new MediaOperationError(
|
|
1591
|
+
400,
|
|
1592
|
+
"Uploaded file size does not match its bytes",
|
|
1593
|
+
"INVALID_UPLOAD_SIZE",
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
const { url } = await storageAdapter.upload(buffer, {
|
|
1597
|
+
filename: context.input.filename,
|
|
1598
|
+
mimeType: context.input.mimeType,
|
|
1599
|
+
size: context.input.size,
|
|
1600
|
+
...(folder ? { folderId: folder.id } : {}),
|
|
1601
|
+
});
|
|
1602
|
+
uploadedUrl = url;
|
|
1603
|
+
const asset = await createAsset(tx, {
|
|
1604
|
+
filename: url.split("/").pop() ?? context.input.filename,
|
|
1605
|
+
originalName: context.input.filename,
|
|
1606
|
+
mimeType: context.input.mimeType,
|
|
1607
|
+
size: context.input.size,
|
|
1608
|
+
url,
|
|
1609
|
+
...(folder ? { folderId: folder.id } : {}),
|
|
1610
|
+
tenantId: tenants.get(context.input as object),
|
|
1611
|
+
});
|
|
1612
|
+
if (folder && folderClaim)
|
|
1613
|
+
await restoreFolder(tx, folder, folderClaim);
|
|
1614
|
+
return operationAsset(asset);
|
|
1615
|
+
});
|
|
1616
|
+
} catch (error) {
|
|
1617
|
+
if (uploadedUrl) {
|
|
1618
|
+
try {
|
|
1619
|
+
await storageAdapter.delete(uploadedUrl);
|
|
1620
|
+
} catch (cleanupError) {
|
|
1621
|
+
console.error(
|
|
1622
|
+
`[btst/media] Failed to clean up orphaned storage file after operation failure: ${uploadedUrl}`,
|
|
1623
|
+
cleanupError,
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
throw error;
|
|
1628
|
+
}
|
|
1629
|
+
},
|
|
1630
|
+
after: ((context: any) =>
|
|
1631
|
+
hooks?.onAfterUpload?.(
|
|
1632
|
+
context.result,
|
|
1633
|
+
hookContext(context, { body: context.input }) as MediaApiResultContext<
|
|
1634
|
+
z.output<typeof DirectUploadOperationInputSchema>,
|
|
1635
|
+
AssetUploadFacts,
|
|
1636
|
+
SerializedAsset
|
|
1637
|
+
>,
|
|
1638
|
+
)) as any,
|
|
1639
|
+
onError: ({ error, ...context }) => {
|
|
1640
|
+
markMemoryRollback(adapter, error);
|
|
1641
|
+
return notifyError(hooks?.onError, error, context, {
|
|
1642
|
+
body: context.input,
|
|
1643
|
+
});
|
|
1644
|
+
},
|
|
1645
|
+
});
|
|
1646
|
+
|
|
1647
|
+
const uploadTokenOperation = defineOperation({
|
|
1648
|
+
input: uploadTokenRequestSchema,
|
|
1649
|
+
permission: mediaPermissions.asset.upload,
|
|
1650
|
+
facts: async ({ input, request }) => {
|
|
1651
|
+
const tenantId = await resolveTenant(input as object, request, {
|
|
1652
|
+
body: input,
|
|
1653
|
+
});
|
|
1654
|
+
const folder = await loadFolder(input.folderId, tenantId);
|
|
1655
|
+
const effectiveTenantId = bindFolderTenant(
|
|
1656
|
+
input as object,
|
|
1657
|
+
tenantId,
|
|
1658
|
+
folder,
|
|
1659
|
+
);
|
|
1660
|
+
folders.set(input as object, folder);
|
|
1661
|
+
return {
|
|
1662
|
+
phase: "initialize" as const,
|
|
1663
|
+
...(folder ? { folderId: folder.id } : {}),
|
|
1664
|
+
mimeType: input.mimeType,
|
|
1665
|
+
...(effectiveTenantId ? { tenantId: effectiveTenantId } : {}),
|
|
1666
|
+
} satisfies AssetUploadFacts;
|
|
1667
|
+
},
|
|
1668
|
+
execute: async (context) => {
|
|
1669
|
+
if (!isS3Adapter(storageAdapter)) {
|
|
1670
|
+
throw new MediaOperationError(
|
|
1671
|
+
400,
|
|
1672
|
+
"Upload token endpoint is only supported with the S3 storage adapter",
|
|
1673
|
+
"UPLOAD_TOKEN_UNSUPPORTED",
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
const folder = folders.get(context.input as object);
|
|
1677
|
+
const issueToken = async () => {
|
|
1678
|
+
await runDomainHook(
|
|
1679
|
+
() =>
|
|
1680
|
+
hooks?.onBeforeUpload?.(
|
|
1681
|
+
{
|
|
1682
|
+
filename: context.input.filename,
|
|
1683
|
+
mimeType: context.input.mimeType,
|
|
1684
|
+
size: context.input.size,
|
|
1685
|
+
},
|
|
1686
|
+
hookContext(context, { body: context.input }),
|
|
1687
|
+
),
|
|
1688
|
+
"UPLOAD_REJECTED",
|
|
1689
|
+
);
|
|
1690
|
+
validateMimeType(context.input.mimeType, allowedMimeTypes);
|
|
1691
|
+
validateSize(context.input.size, maxFileSizeBytes);
|
|
1692
|
+
return operationUploadToken(
|
|
1693
|
+
await storageAdapter.generateUploadToken({
|
|
1694
|
+
filename: sanitizeS3KeySegment(context.input.filename),
|
|
1695
|
+
mimeType: context.input.mimeType,
|
|
1696
|
+
size: context.input.size,
|
|
1697
|
+
...(folder ? { folderId: folder.id } : {}),
|
|
1698
|
+
}),
|
|
1699
|
+
);
|
|
1700
|
+
};
|
|
1701
|
+
if (!folder) return issueToken();
|
|
1702
|
+
requireAtomicTransactions(adapter);
|
|
1703
|
+
return adapter.transaction(async (tx) => {
|
|
1704
|
+
await verifyFolder(tx, folder);
|
|
1705
|
+
const claimedAt = await claimFolder(tx, folder);
|
|
1706
|
+
const token = await issueToken();
|
|
1707
|
+
await restoreFolder(tx, folder, claimedAt);
|
|
1708
|
+
return token;
|
|
1709
|
+
});
|
|
1710
|
+
},
|
|
1711
|
+
onError: ({ error, ...context }) =>
|
|
1712
|
+
notifyError(hooks?.onError, error, context, {
|
|
1713
|
+
body: context.input,
|
|
1714
|
+
}),
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
const uploadVercelBlobOperation = defineOperation({
|
|
1718
|
+
input: VercelBlobOperationInputSchema,
|
|
1719
|
+
permission: mediaPermissions.asset.upload,
|
|
1720
|
+
facts: async ({ input, request }) => {
|
|
1721
|
+
if (!isVercelBlobAdapter(storageAdapter)) {
|
|
1722
|
+
throw new MediaOperationError(
|
|
1723
|
+
400,
|
|
1724
|
+
"Vercel Blob endpoint is only supported with the vercelBlobAdapter",
|
|
1725
|
+
"VERCEL_BLOB_UNSUPPORTED",
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
if ("type" in input.body && input.body.type === "blob.upload-completed") {
|
|
1729
|
+
if (!request)
|
|
1730
|
+
throw new TypeError("Vercel Blob callbacks require a request.");
|
|
1731
|
+
let verified: Awaited<ReturnType<typeof storageAdapter.verifyCallback>>;
|
|
1732
|
+
try {
|
|
1733
|
+
verified = await storageAdapter.verifyCallback(
|
|
1734
|
+
request,
|
|
1735
|
+
input.body as unknown as VercelBlobUploadCompletedBody,
|
|
1736
|
+
);
|
|
1737
|
+
} catch {
|
|
1738
|
+
throw new MediaOperationError(
|
|
1739
|
+
401,
|
|
1740
|
+
"Invalid Vercel Blob provider callback.",
|
|
1741
|
+
"INVALID_PROVIDER_CALLBACK",
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
const context = {
|
|
1745
|
+
phase: "callback" as const,
|
|
1746
|
+
filename: verified.pathname.split("/").pop() ?? verified.pathname,
|
|
1747
|
+
...verified,
|
|
1748
|
+
};
|
|
1749
|
+
vercelContexts.set(input as object, context);
|
|
1750
|
+
return {
|
|
1751
|
+
phase: "callback" as const,
|
|
1752
|
+
...(verified.folderId ? { folderId: verified.folderId } : {}),
|
|
1753
|
+
mimeType: verified.mimeType,
|
|
1754
|
+
...(verified.tenantId ? { tenantId: verified.tenantId } : {}),
|
|
1755
|
+
} satisfies AssetUploadFacts;
|
|
1756
|
+
}
|
|
1757
|
+
const normalized = parseVercelInitialize(input.body);
|
|
1758
|
+
const tenantId = await resolveTenant(input as object, request, {
|
|
1759
|
+
body: input.body,
|
|
1760
|
+
});
|
|
1761
|
+
const folder = await loadFolder(normalized.folderId, tenantId);
|
|
1762
|
+
const effectiveTenantId = bindFolderTenant(
|
|
1763
|
+
input as object,
|
|
1764
|
+
tenantId,
|
|
1765
|
+
folder,
|
|
1766
|
+
);
|
|
1767
|
+
folders.set(input as object, folder);
|
|
1768
|
+
const context = {
|
|
1769
|
+
phase: "initialize" as const,
|
|
1770
|
+
...normalized,
|
|
1771
|
+
...(folder ? { folderId: folder.id } : {}),
|
|
1772
|
+
...(effectiveTenantId ? { tenantId: effectiveTenantId } : {}),
|
|
1773
|
+
};
|
|
1774
|
+
vercelContexts.set(input as object, context);
|
|
1775
|
+
return {
|
|
1776
|
+
phase: "initialize" as const,
|
|
1777
|
+
...(folder ? { folderId: folder.id } : {}),
|
|
1778
|
+
mimeType: normalized.mimeType,
|
|
1779
|
+
...(effectiveTenantId ? { tenantId: effectiveTenantId } : {}),
|
|
1780
|
+
} satisfies AssetUploadFacts;
|
|
1781
|
+
},
|
|
1782
|
+
execute: async (context) => {
|
|
1783
|
+
if (!isVercelBlobAdapter(storageAdapter) || !context.request) {
|
|
1784
|
+
throw new TypeError(
|
|
1785
|
+
"Vercel Blob execution requires its configured adapter and request.",
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
const request = context.request;
|
|
1789
|
+
const upload = vercelContexts.get(context.input as object);
|
|
1790
|
+
if (!upload) throw staleStateError();
|
|
1791
|
+
const handleProviderRequest = async () => {
|
|
1792
|
+
if (upload.phase === "initialize") {
|
|
1793
|
+
await runDomainHook(
|
|
1794
|
+
() =>
|
|
1795
|
+
hooks?.onBeforeUpload?.(
|
|
1796
|
+
{
|
|
1797
|
+
filename: upload.filename,
|
|
1798
|
+
mimeType: upload.mimeType,
|
|
1799
|
+
size: upload.size,
|
|
1800
|
+
},
|
|
1801
|
+
hookContext(context, { body: context.input.body }),
|
|
1802
|
+
),
|
|
1803
|
+
"UPLOAD_REJECTED",
|
|
1804
|
+
);
|
|
1805
|
+
validateMimeType(upload.mimeType, allowedMimeTypes);
|
|
1806
|
+
if (upload.size !== undefined)
|
|
1807
|
+
validateSize(upload.size, maxFileSizeBytes);
|
|
1808
|
+
}
|
|
1809
|
+
const response = await storageAdapter.handleRequest(
|
|
1810
|
+
request,
|
|
1811
|
+
context.input.body as unknown as VercelBlobHandleUploadBody,
|
|
1812
|
+
{
|
|
1813
|
+
onBeforeGenerateToken: async (pathname) => {
|
|
1814
|
+
if (
|
|
1815
|
+
upload.phase !== "initialize" ||
|
|
1816
|
+
pathname !== upload.pathname
|
|
1817
|
+
) {
|
|
1818
|
+
throw staleStateError();
|
|
1819
|
+
}
|
|
1820
|
+
return {
|
|
1821
|
+
addRandomSuffix: true,
|
|
1822
|
+
allowedContentTypes: allowedMimeTypes?.length
|
|
1823
|
+
? allowedMimeTypes
|
|
1824
|
+
: undefined,
|
|
1825
|
+
maximumSizeInBytes: maxFileSizeBytes,
|
|
1826
|
+
tokenPayload: JSON.stringify({
|
|
1827
|
+
version: 1,
|
|
1828
|
+
pathname: upload.pathname,
|
|
1829
|
+
mimeType: upload.mimeType,
|
|
1830
|
+
...(upload.size !== undefined ? { size: upload.size } : {}),
|
|
1831
|
+
...(upload.folderId ? { folderId: upload.folderId } : {}),
|
|
1832
|
+
...(upload.tenantId ? { tenantId: upload.tenantId } : {}),
|
|
1833
|
+
}),
|
|
1834
|
+
};
|
|
1835
|
+
},
|
|
1836
|
+
},
|
|
1837
|
+
);
|
|
1838
|
+
return plainStorageResult(response);
|
|
1839
|
+
};
|
|
1840
|
+
if (upload.phase === "initialize") {
|
|
1841
|
+
const folder = folders.get(context.input as object);
|
|
1842
|
+
if (folder) {
|
|
1843
|
+
requireAtomicTransactions(adapter);
|
|
1844
|
+
return adapter.transaction(async (tx) => {
|
|
1845
|
+
await verifyFolder(tx, folder);
|
|
1846
|
+
const claimedAt = await claimFolder(tx, folder);
|
|
1847
|
+
const response = await handleProviderRequest();
|
|
1848
|
+
await restoreFolder(tx, folder, claimedAt);
|
|
1849
|
+
return response;
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
return handleProviderRequest();
|
|
1854
|
+
},
|
|
1855
|
+
onError: ({ error, ...context }) =>
|
|
1856
|
+
notifyError(hooks?.onError, error, context, {
|
|
1857
|
+
body: context.input.body,
|
|
1858
|
+
}),
|
|
1859
|
+
});
|
|
1860
|
+
|
|
1861
|
+
return {
|
|
1862
|
+
listAssets: listAssetsOperation,
|
|
1863
|
+
createAsset: createAssetOperation,
|
|
1864
|
+
updateAsset: updateAssetOperation,
|
|
1865
|
+
deleteAsset: deleteAssetOperation,
|
|
1866
|
+
listFolders: listFoldersOperation,
|
|
1867
|
+
createFolder: createFolderOperation,
|
|
1868
|
+
deleteFolder: deleteFolderOperation,
|
|
1869
|
+
uploadDirect: uploadDirectOperation,
|
|
1870
|
+
uploadToken: uploadTokenOperation,
|
|
1871
|
+
uploadVercelBlob: uploadVercelBlobOperation,
|
|
1872
|
+
};
|
|
1873
|
+
}
|