@btst/stack 3.0.0-rc.1 → 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 +122 -46
- 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 +30 -12
- package/dist/next/index.d.mts +30 -12
- package/dist/next/index.d.ts +30 -12
- 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/next/page.cjs +8 -6
- package/dist/packages/stack/src/next/page.mjs +8 -6
- 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 +161 -105
- package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.mjs +162 -106
- 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 +27 -16
- package/dist/packages/stack/src/react-router/page.mjs +28 -17
- 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 +11 -5
- package/dist/packages/stack/src/tanstack/page.mjs +11 -5
- 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 +7 -2
- package/dist/plugins/comments/client/components/index.d.mts +7 -2
- package/dist/plugins/comments/client/components/index.d.ts +7 -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 +41 -7
- package/dist/react-router/index.d.mts +41 -7
- package/dist/react-router/index.d.ts +41 -7
- 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.mts → stack.5_zzCejP.d.cts} +16 -14
- package/dist/shared/{stack.9BAOaSqE.d.ts → stack.5_zzCejP.d.mts} +16 -14
- package/dist/shared/{stack.9BAOaSqE.d.cts → 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.INBa9-TO.d.cts → stack.BGzgFx3R.d.cts} +5 -5
- 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.3di7vQs6.d.mts → stack.CjCSDfWy.d.mts} +5 -5
- 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.mts → stack.DjGixVx3.d.cts} +6 -2
- package/dist/shared/{stack.BMlLOMED.d.ts → stack.DjGixVx3.d.mts} +6 -2
- package/dist/shared/{stack.BMlLOMED.d.cts → 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.CRelKJfH.d.ts → stack.Dpg_JjIo.d.ts} +5 -5
- 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 +44 -14
- package/dist/tanstack/index.d.mts +44 -14
- package/dist/tanstack/index.d.ts +44 -14
- 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 +203 -8
- 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__/entry-factories.test.tsx +104 -4
- 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 +315 -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/index.tsx +2 -0
- package/src/next/layout.tsx +81 -0
- package/src/next/page.tsx +33 -12
- 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 +465 -79
- 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 +154 -112
- 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 +6 -0
- package/src/react-router/layout.ts +43 -0
- package/src/react-router/page.tsx +46 -17
- package/src/react-router/router.tsx +1 -1
- package/src/shared/auth-types.ts +19 -70
- package/src/shared/entry-factories.ts +12 -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 +7 -0
- package/src/tanstack/layout.ts +36 -0
- package/src/tanstack/page.tsx +35 -13
- package/src/tanstack/router.tsx +1 -1
- package/src/tanstack/server.ts +18 -0
- package/src/types.ts +557 -65
- package/dist/shared/stack.80-y2TTR.d.mts +0 -840
- package/dist/shared/stack.B-hEIoUw.d.ts +0 -406
- 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.Bk5UB_BT.d.ts +0 -840
- 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.BvxHSZYT.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.CMbX8Q5C.d.ts +0 -560
- package/dist/shared/stack.Cf-DbL1A.d.ts +0 -329
- 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.DQq8DEIv.d.mts +0 -461
- package/dist/shared/stack.Dd-8shBE.d.ts +0 -492
- package/dist/shared/stack.Dj04W2c3.d.mts +0 -560
- package/dist/shared/stack.DrUBX-QE.d.cts +0 -34
- package/dist/shared/stack.DrUBX-QE.d.mts +0 -34
- package/dist/shared/stack.DrUBX-QE.d.ts +0 -34
- package/dist/shared/stack.H5B8miAV.d.cts +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.eq5eg1yt.d.cts +0 -560
- package/dist/shared/stack.f8Vke-eH.d.mts +0 -406
- package/dist/shared/stack.oBokjN_J.d.mts +0 -416
- package/dist/shared/{stack.B0muOwUF.d.cts → stack.Csi-4Mgz.d.cts} +8 -8
- package/dist/shared/{stack.B0muOwUF.d.mts → stack.Csi-4Mgz.d.mts} +8 -8
- package/dist/shared/{stack.B0muOwUF.d.ts → stack.Csi-4Mgz.d.ts} +8 -8
|
@@ -0,0 +1,2065 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type { DBAdapter as Adapter } from "@btst/db";
|
|
3
|
+
import {
|
|
4
|
+
type DeepReadonly,
|
|
5
|
+
defineOperation,
|
|
6
|
+
type Operation,
|
|
7
|
+
OperationHttpError,
|
|
8
|
+
type OperationContext,
|
|
9
|
+
} from "@btst/stack/plugins/api";
|
|
10
|
+
import type { PermissionFactsFor } from "@btst/stack/authorization";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { kanbanPermissions } from "../permissions";
|
|
13
|
+
import {
|
|
14
|
+
BoardListQuerySchema,
|
|
15
|
+
createBoardSchema,
|
|
16
|
+
createColumnSchema,
|
|
17
|
+
createTaskSchema,
|
|
18
|
+
moveTaskSchema,
|
|
19
|
+
reorderColumnsSchema,
|
|
20
|
+
reorderTasksSchema,
|
|
21
|
+
updateBoardSchema,
|
|
22
|
+
updateColumnSchema,
|
|
23
|
+
updateTaskSchema,
|
|
24
|
+
} from "../schemas";
|
|
25
|
+
import type {
|
|
26
|
+
Board,
|
|
27
|
+
BoardWithColumns,
|
|
28
|
+
Column,
|
|
29
|
+
KanbanApiContext,
|
|
30
|
+
KanbanApiResultContext,
|
|
31
|
+
KanbanBackendHooks,
|
|
32
|
+
SerializedBoard,
|
|
33
|
+
SerializedBoardSummary,
|
|
34
|
+
SerializedBoardWithColumns,
|
|
35
|
+
SerializedColumn,
|
|
36
|
+
SerializedTask,
|
|
37
|
+
Task,
|
|
38
|
+
} from "../types";
|
|
39
|
+
import { slugify } from "../utils";
|
|
40
|
+
import { getBoardById, getBoardSummaries } from "./getters";
|
|
41
|
+
import { serializeBoardSummary, serializeTask } from "./serializers";
|
|
42
|
+
|
|
43
|
+
export const BoardIdOperationInputSchema = z.object({ id: z.string() });
|
|
44
|
+
export const UpdateBoardOperationInputSchema = z.object({
|
|
45
|
+
id: z.string(),
|
|
46
|
+
data: updateBoardSchema.omit({ id: true }),
|
|
47
|
+
});
|
|
48
|
+
export const ColumnIdOperationInputSchema = z.object({ id: z.string() });
|
|
49
|
+
export const UpdateColumnOperationInputSchema = z.object({
|
|
50
|
+
id: z.string(),
|
|
51
|
+
data: updateColumnSchema.omit({ id: true }).superRefine((data, context) => {
|
|
52
|
+
if (Object.hasOwn(data, "order") && data.order === undefined) {
|
|
53
|
+
context.addIssue({
|
|
54
|
+
code: "custom",
|
|
55
|
+
path: ["order"],
|
|
56
|
+
message: "Order must be a number when provided.",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
export const TaskIdOperationInputSchema = z.object({ id: z.string() });
|
|
62
|
+
export const UpdateTaskOperationInputSchema = z.object({
|
|
63
|
+
id: z.string(),
|
|
64
|
+
data: updateTaskSchema.omit({ id: true }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
type BoardReadFacts = PermissionFactsFor<typeof kanbanPermissions.board.read>;
|
|
68
|
+
type BoardUpdateFacts = PermissionFactsFor<
|
|
69
|
+
typeof kanbanPermissions.board.update
|
|
70
|
+
>;
|
|
71
|
+
type BoardDeleteFacts = PermissionFactsFor<
|
|
72
|
+
typeof kanbanPermissions.board.delete
|
|
73
|
+
>;
|
|
74
|
+
type ColumnCreateFacts = PermissionFactsFor<
|
|
75
|
+
typeof kanbanPermissions.column.create
|
|
76
|
+
>;
|
|
77
|
+
type ColumnUpdateFacts = PermissionFactsFor<
|
|
78
|
+
typeof kanbanPermissions.column.update
|
|
79
|
+
>;
|
|
80
|
+
type ColumnDeleteFacts = PermissionFactsFor<
|
|
81
|
+
typeof kanbanPermissions.column.delete
|
|
82
|
+
>;
|
|
83
|
+
type ColumnReorderFacts = PermissionFactsFor<
|
|
84
|
+
typeof kanbanPermissions.column.reorder
|
|
85
|
+
>;
|
|
86
|
+
type TaskCreateFacts = PermissionFactsFor<typeof kanbanPermissions.task.create>;
|
|
87
|
+
type TaskUpdateFacts = PermissionFactsFor<typeof kanbanPermissions.task.update>;
|
|
88
|
+
type TaskDeleteFacts = PermissionFactsFor<typeof kanbanPermissions.task.delete>;
|
|
89
|
+
type TaskMoveFacts = PermissionFactsFor<typeof kanbanPermissions.task.move>;
|
|
90
|
+
type TaskReorderFacts = PermissionFactsFor<
|
|
91
|
+
typeof kanbanPermissions.task.reorder
|
|
92
|
+
>;
|
|
93
|
+
|
|
94
|
+
export interface KanbanBoardListResult {
|
|
95
|
+
readonly items: readonly SerializedBoardSummary[];
|
|
96
|
+
readonly total: number;
|
|
97
|
+
readonly limit?: number;
|
|
98
|
+
readonly offset?: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type KanbanOperations = {
|
|
102
|
+
readonly listBoards: Operation<
|
|
103
|
+
typeof BoardListQuerySchema,
|
|
104
|
+
typeof kanbanPermissions.board.read,
|
|
105
|
+
KanbanBoardListResult
|
|
106
|
+
>;
|
|
107
|
+
readonly getBoard: Operation<
|
|
108
|
+
typeof BoardIdOperationInputSchema,
|
|
109
|
+
typeof kanbanPermissions.board.read,
|
|
110
|
+
SerializedBoardWithColumns
|
|
111
|
+
>;
|
|
112
|
+
readonly createBoard: Operation<
|
|
113
|
+
typeof createBoardSchema,
|
|
114
|
+
typeof kanbanPermissions.board.create,
|
|
115
|
+
SerializedBoardWithColumns
|
|
116
|
+
>;
|
|
117
|
+
readonly updateBoard: Operation<
|
|
118
|
+
typeof UpdateBoardOperationInputSchema,
|
|
119
|
+
typeof kanbanPermissions.board.update,
|
|
120
|
+
SerializedBoard
|
|
121
|
+
>;
|
|
122
|
+
readonly deleteBoard: Operation<
|
|
123
|
+
typeof BoardIdOperationInputSchema,
|
|
124
|
+
typeof kanbanPermissions.board.delete,
|
|
125
|
+
{ readonly success: true }
|
|
126
|
+
>;
|
|
127
|
+
readonly createColumn: Operation<
|
|
128
|
+
typeof createColumnSchema,
|
|
129
|
+
typeof kanbanPermissions.column.create,
|
|
130
|
+
SerializedColumn
|
|
131
|
+
>;
|
|
132
|
+
readonly updateColumn: Operation<
|
|
133
|
+
typeof UpdateColumnOperationInputSchema,
|
|
134
|
+
typeof kanbanPermissions.column.update,
|
|
135
|
+
SerializedColumn
|
|
136
|
+
>;
|
|
137
|
+
readonly deleteColumn: Operation<
|
|
138
|
+
typeof ColumnIdOperationInputSchema,
|
|
139
|
+
typeof kanbanPermissions.column.delete,
|
|
140
|
+
{ readonly success: true }
|
|
141
|
+
>;
|
|
142
|
+
readonly reorderColumns: Operation<
|
|
143
|
+
typeof reorderColumnsSchema,
|
|
144
|
+
typeof kanbanPermissions.column.reorder,
|
|
145
|
+
{ readonly success: true }
|
|
146
|
+
>;
|
|
147
|
+
readonly createTask: Operation<
|
|
148
|
+
typeof createTaskSchema,
|
|
149
|
+
typeof kanbanPermissions.task.create,
|
|
150
|
+
SerializedTask
|
|
151
|
+
>;
|
|
152
|
+
readonly updateTask: Operation<
|
|
153
|
+
typeof UpdateTaskOperationInputSchema,
|
|
154
|
+
typeof kanbanPermissions.task.update,
|
|
155
|
+
SerializedTask
|
|
156
|
+
>;
|
|
157
|
+
readonly deleteTask: Operation<
|
|
158
|
+
typeof TaskIdOperationInputSchema,
|
|
159
|
+
typeof kanbanPermissions.task.delete,
|
|
160
|
+
{ readonly success: true }
|
|
161
|
+
>;
|
|
162
|
+
readonly moveTask: Operation<
|
|
163
|
+
typeof moveTaskSchema,
|
|
164
|
+
typeof kanbanPermissions.task.move,
|
|
165
|
+
SerializedTask
|
|
166
|
+
>;
|
|
167
|
+
readonly reorderTasks: Operation<
|
|
168
|
+
typeof reorderTasksSchema,
|
|
169
|
+
typeof kanbanPermissions.task.reorder,
|
|
170
|
+
{ readonly success: true }
|
|
171
|
+
>;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** A domain/HTTP failure raised after Kanban authorization succeeds. */
|
|
175
|
+
export class KanbanOperationError extends OperationHttpError {
|
|
176
|
+
constructor(statusCode: number, message: string, code: string) {
|
|
177
|
+
super(statusCode, message, code);
|
|
178
|
+
this.name = "KanbanOperationError";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface BoardSnapshot {
|
|
183
|
+
readonly id: string;
|
|
184
|
+
readonly ownerId?: string;
|
|
185
|
+
readonly organizationId?: string;
|
|
186
|
+
readonly updatedAt: Date;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface ColumnSnapshot {
|
|
190
|
+
readonly id: string;
|
|
191
|
+
readonly boardId: string;
|
|
192
|
+
readonly updatedAt: Date;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
interface TaskSnapshot {
|
|
196
|
+
readonly id: string;
|
|
197
|
+
readonly columnId: string;
|
|
198
|
+
readonly assigneeId?: string;
|
|
199
|
+
readonly isArchived: boolean;
|
|
200
|
+
readonly updatedAt: Date;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface ColumnAuthorizationSnapshot {
|
|
204
|
+
readonly board: BoardSnapshot;
|
|
205
|
+
readonly column: ColumnSnapshot;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
interface TaskAuthorizationSnapshot extends ColumnAuthorizationSnapshot {
|
|
209
|
+
readonly task: TaskSnapshot;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
interface ColumnReorderSnapshot {
|
|
213
|
+
readonly board: BoardSnapshot;
|
|
214
|
+
readonly columns: readonly ColumnSnapshot[];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface TaskReorderSnapshot extends ColumnAuthorizationSnapshot {
|
|
218
|
+
readonly tasks: readonly TaskSnapshot[];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
interface TaskMoveSnapshot extends TaskAuthorizationSnapshot {
|
|
222
|
+
readonly targetColumn: ColumnSnapshot;
|
|
223
|
+
readonly sourceTasks: readonly TaskSnapshot[];
|
|
224
|
+
readonly targetTasks: readonly TaskSnapshot[];
|
|
225
|
+
readonly targetOrder: number;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function boardSnapshot(board: Board): BoardSnapshot {
|
|
229
|
+
return {
|
|
230
|
+
id: board.id,
|
|
231
|
+
...(board.ownerId ? { ownerId: board.ownerId } : {}),
|
|
232
|
+
...(board.organizationId ? { organizationId: board.organizationId } : {}),
|
|
233
|
+
updatedAt: board.updatedAt,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function columnSnapshot(column: Column): ColumnSnapshot {
|
|
238
|
+
return {
|
|
239
|
+
id: column.id,
|
|
240
|
+
boardId: column.boardId,
|
|
241
|
+
updatedAt: column.updatedAt,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function taskSnapshot(task: Task): TaskSnapshot {
|
|
246
|
+
return {
|
|
247
|
+
id: task.id,
|
|
248
|
+
columnId: task.columnId,
|
|
249
|
+
...(task.assigneeId ? { assigneeId: task.assigneeId } : {}),
|
|
250
|
+
isArchived: task.isArchived,
|
|
251
|
+
updatedAt: task.updatedAt,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function sameDate(left: Date, right: Date) {
|
|
256
|
+
return left.getTime() === right.getTime();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function sameBoard(board: Board | null, expected: BoardSnapshot) {
|
|
260
|
+
return (
|
|
261
|
+
board !== null &&
|
|
262
|
+
board.id === expected.id &&
|
|
263
|
+
(board.ownerId || undefined) === expected.ownerId &&
|
|
264
|
+
(board.organizationId || undefined) === expected.organizationId &&
|
|
265
|
+
sameDate(board.updatedAt, expected.updatedAt)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function sameColumn(column: Column | null, expected: ColumnSnapshot) {
|
|
270
|
+
return (
|
|
271
|
+
column !== null &&
|
|
272
|
+
column.id === expected.id &&
|
|
273
|
+
column.boardId === expected.boardId &&
|
|
274
|
+
sameDate(column.updatedAt, expected.updatedAt)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function sameTask(task: Task | null, expected: TaskSnapshot) {
|
|
279
|
+
return (
|
|
280
|
+
task !== null &&
|
|
281
|
+
task.id === expected.id &&
|
|
282
|
+
task.columnId === expected.columnId &&
|
|
283
|
+
(task.assigneeId || undefined) === expected.assigneeId &&
|
|
284
|
+
task.isArchived === expected.isArchived &&
|
|
285
|
+
sameDate(task.updatedAt, expected.updatedAt)
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function sameColumnSnapshots(left: ColumnSnapshot, right: ColumnSnapshot) {
|
|
290
|
+
return (
|
|
291
|
+
left.id === right.id &&
|
|
292
|
+
left.boardId === right.boardId &&
|
|
293
|
+
sameDate(left.updatedAt, right.updatedAt)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function sameTaskSnapshots(left: TaskSnapshot, right: TaskSnapshot) {
|
|
298
|
+
return (
|
|
299
|
+
left.id === right.id &&
|
|
300
|
+
left.columnId === right.columnId &&
|
|
301
|
+
left.assigneeId === right.assigneeId &&
|
|
302
|
+
left.isArchived === right.isArchived &&
|
|
303
|
+
sameDate(left.updatedAt, right.updatedAt)
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function sameSnapshotSet<T extends { id: string }>(
|
|
308
|
+
current: readonly T[],
|
|
309
|
+
expected: readonly T[],
|
|
310
|
+
equal: (value: T, snapshot: T) => boolean,
|
|
311
|
+
) {
|
|
312
|
+
if (current.length !== expected.length) return false;
|
|
313
|
+
const byId = new Map(current.map((value) => [value.id, value]));
|
|
314
|
+
return expected.every((snapshot) => {
|
|
315
|
+
const value = byId.get(snapshot.id);
|
|
316
|
+
return value !== undefined && equal(value, snapshot);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function nextSnapshotDate(previous: Date) {
|
|
321
|
+
return new Date(Math.max(Date.now(), previous.getTime() + 1));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const AFFECTED_ROW_KEYS = [
|
|
325
|
+
"rowCount",
|
|
326
|
+
"affectedRows",
|
|
327
|
+
"rowsAffected",
|
|
328
|
+
"changes",
|
|
329
|
+
"numUpdatedRows",
|
|
330
|
+
] as const;
|
|
331
|
+
|
|
332
|
+
function hasPositiveCount(value: unknown): boolean {
|
|
333
|
+
if (typeof value === "number") return Number.isFinite(value) && value > 0;
|
|
334
|
+
if (typeof value === "bigint") return value > 0n;
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function didAffectRow(result: unknown, expectedId: string): boolean {
|
|
339
|
+
if (typeof result === "number" || typeof result === "bigint") {
|
|
340
|
+
return hasPositiveCount(result);
|
|
341
|
+
}
|
|
342
|
+
if (!result || typeof result !== "object") return false;
|
|
343
|
+
const record = result as Record<string, unknown>;
|
|
344
|
+
if ("count" in record) return hasPositiveCount(record.count);
|
|
345
|
+
if (Array.isArray(result)) {
|
|
346
|
+
return result.length > 0 && didAffectRow(result[0], expectedId);
|
|
347
|
+
}
|
|
348
|
+
for (const key of AFFECTED_ROW_KEYS) {
|
|
349
|
+
if (key in record) return hasPositiveCount(record[key]);
|
|
350
|
+
}
|
|
351
|
+
if ("meta" in record) {
|
|
352
|
+
const meta = record.meta;
|
|
353
|
+
return Boolean(
|
|
354
|
+
meta &&
|
|
355
|
+
typeof meta === "object" &&
|
|
356
|
+
"changes" in meta &&
|
|
357
|
+
hasPositiveCount((meta as Record<string, unknown>).changes),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
return record.id === expectedId;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const memoryRollbackMarkers = new WeakMap<Adapter, (error: unknown) => void>();
|
|
364
|
+
|
|
365
|
+
function markMemoryRollback(adapter: Adapter, error: unknown) {
|
|
366
|
+
memoryRollbackMarkers.get(adapter)?.(error);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* The published memory adapter rolls back from a whole-database clone but does
|
|
371
|
+
* not isolate overlapping callbacks. Instrument the shared adapter instance
|
|
372
|
+
* so every stack access queues behind whole-database transactions: temporary
|
|
373
|
+
* CAS claims stay private and rollback cannot overwrite a later winner.
|
|
374
|
+
*/
|
|
375
|
+
function serializeMemoryOperations(adapter: Adapter): Adapter {
|
|
376
|
+
if (adapter.id !== "memory" || memoryRollbackMarkers.has(adapter)) {
|
|
377
|
+
return adapter;
|
|
378
|
+
}
|
|
379
|
+
const source: Adapter = { ...adapter };
|
|
380
|
+
type TransactionAdapter = Parameters<
|
|
381
|
+
Parameters<Adapter["transaction"]>[0]
|
|
382
|
+
>[0];
|
|
383
|
+
type ActiveAdapter = Omit<Adapter, "transaction"> &
|
|
384
|
+
Partial<Pick<Adapter, "transaction">>;
|
|
385
|
+
type LockContext = {
|
|
386
|
+
owner: object;
|
|
387
|
+
adapter: ActiveAdapter;
|
|
388
|
+
inTransaction?: boolean;
|
|
389
|
+
rollbackError?: unknown;
|
|
390
|
+
rollbackOnly?: boolean;
|
|
391
|
+
};
|
|
392
|
+
const lockContext = new AsyncLocalStorage<LockContext>();
|
|
393
|
+
let tail = Promise.resolve();
|
|
394
|
+
let activeOwner: object | undefined;
|
|
395
|
+
const withLock = async <T>(
|
|
396
|
+
run: (activeAdapter: ActiveAdapter) => Promise<T>,
|
|
397
|
+
): Promise<T> => {
|
|
398
|
+
const inherited = lockContext.getStore();
|
|
399
|
+
if (inherited && inherited.owner === activeOwner) {
|
|
400
|
+
return run(inherited.adapter);
|
|
401
|
+
}
|
|
402
|
+
let release = () => {};
|
|
403
|
+
const previous = tail;
|
|
404
|
+
tail = new Promise<void>((resolve) => {
|
|
405
|
+
release = resolve;
|
|
406
|
+
});
|
|
407
|
+
await previous;
|
|
408
|
+
const owner = {};
|
|
409
|
+
activeOwner = owner;
|
|
410
|
+
try {
|
|
411
|
+
return await lockContext.run({ owner, adapter: source }, () =>
|
|
412
|
+
run(source),
|
|
413
|
+
);
|
|
414
|
+
} finally {
|
|
415
|
+
if (activeOwner === owner) activeOwner = undefined;
|
|
416
|
+
release();
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
const serialized: Adapter = {
|
|
420
|
+
...source,
|
|
421
|
+
create: ((input) =>
|
|
422
|
+
withLock((active) => active.create(input))) as Adapter["create"],
|
|
423
|
+
findOne: ((input) =>
|
|
424
|
+
withLock((active) => active.findOne(input))) as Adapter["findOne"],
|
|
425
|
+
findMany: ((input) =>
|
|
426
|
+
withLock((active) => active.findMany(input))) as Adapter["findMany"],
|
|
427
|
+
count: (input) => withLock((active) => active.count(input)),
|
|
428
|
+
update: ((input) =>
|
|
429
|
+
withLock((active) => active.update(input))) as Adapter["update"],
|
|
430
|
+
updateMany: (input) => withLock((active) => active.updateMany(input)),
|
|
431
|
+
delete: ((input) =>
|
|
432
|
+
withLock((active) => active.delete(input))) as Adapter["delete"],
|
|
433
|
+
deleteMany: (input) => withLock((active) => active.deleteMany(input)),
|
|
434
|
+
consumeOne: ((input) =>
|
|
435
|
+
withLock((active) => active.consumeOne(input))) as Adapter["consumeOne"],
|
|
436
|
+
transaction: ((callback) =>
|
|
437
|
+
withLock((active) => {
|
|
438
|
+
const context = lockContext.getStore();
|
|
439
|
+
if (!context) throw new TypeError("Missing memory lock context.");
|
|
440
|
+
if (context.inTransaction) {
|
|
441
|
+
return callback(active as TransactionAdapter).catch((error) => {
|
|
442
|
+
context.rollbackOnly = true;
|
|
443
|
+
context.rollbackError = error;
|
|
444
|
+
throw error;
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
if (!active.transaction) {
|
|
448
|
+
throw new TypeError("Missing memory transaction adapter.");
|
|
449
|
+
}
|
|
450
|
+
return active.transaction((tx) =>
|
|
451
|
+
lockContext.run(
|
|
452
|
+
{ owner: context.owner, adapter: tx, inTransaction: true },
|
|
453
|
+
async () => {
|
|
454
|
+
const transactionContext = lockContext.getStore();
|
|
455
|
+
const result = await callback(tx);
|
|
456
|
+
if (transactionContext?.rollbackOnly) {
|
|
457
|
+
throw transactionContext.rollbackError;
|
|
458
|
+
}
|
|
459
|
+
return result;
|
|
460
|
+
},
|
|
461
|
+
),
|
|
462
|
+
);
|
|
463
|
+
})) as Adapter["transaction"],
|
|
464
|
+
};
|
|
465
|
+
memoryRollbackMarkers.set(adapter, (error) => {
|
|
466
|
+
const context = lockContext.getStore();
|
|
467
|
+
if (!context || context.owner !== activeOwner || !context.inTransaction) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
context.rollbackOnly = true;
|
|
471
|
+
context.rollbackError = error;
|
|
472
|
+
});
|
|
473
|
+
Object.assign(adapter, serialized);
|
|
474
|
+
return adapter;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function requireAtomicTransactions(adapter: Adapter) {
|
|
478
|
+
if (
|
|
479
|
+
adapter.id !== "memory" &&
|
|
480
|
+
typeof adapter.options?.adapterConfig.transaction !== "function"
|
|
481
|
+
) {
|
|
482
|
+
throw new KanbanOperationError(
|
|
483
|
+
500,
|
|
484
|
+
"Kanban writes require an adapter with isolated transaction support.",
|
|
485
|
+
"ATOMIC_TRANSACTION_REQUIRED",
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function boardWhere(snapshot: BoardSnapshot) {
|
|
491
|
+
return [
|
|
492
|
+
{ field: "id", value: snapshot.id, operator: "eq" as const },
|
|
493
|
+
{
|
|
494
|
+
field: "ownerId",
|
|
495
|
+
value: snapshot.ownerId ?? null,
|
|
496
|
+
operator: "eq" as const,
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
field: "organizationId",
|
|
500
|
+
value: snapshot.organizationId ?? null,
|
|
501
|
+
operator: "eq" as const,
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
field: "updatedAt",
|
|
505
|
+
value: snapshot.updatedAt,
|
|
506
|
+
operator: "eq" as const,
|
|
507
|
+
},
|
|
508
|
+
];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function columnWhere(snapshot: ColumnSnapshot) {
|
|
512
|
+
return [
|
|
513
|
+
{ field: "id", value: snapshot.id, operator: "eq" as const },
|
|
514
|
+
{ field: "boardId", value: snapshot.boardId, operator: "eq" as const },
|
|
515
|
+
{
|
|
516
|
+
field: "updatedAt",
|
|
517
|
+
value: snapshot.updatedAt,
|
|
518
|
+
operator: "eq" as const,
|
|
519
|
+
},
|
|
520
|
+
];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function taskWhere(snapshot: TaskSnapshot) {
|
|
524
|
+
return [
|
|
525
|
+
{ field: "id", value: snapshot.id, operator: "eq" as const },
|
|
526
|
+
{ field: "columnId", value: snapshot.columnId, operator: "eq" as const },
|
|
527
|
+
{
|
|
528
|
+
field: "assigneeId",
|
|
529
|
+
value: snapshot.assigneeId ?? null,
|
|
530
|
+
operator: "eq" as const,
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
field: "isArchived",
|
|
534
|
+
value: snapshot.isArchived,
|
|
535
|
+
operator: "eq" as const,
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
field: "updatedAt",
|
|
539
|
+
value: snapshot.updatedAt,
|
|
540
|
+
operator: "eq" as const,
|
|
541
|
+
},
|
|
542
|
+
];
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async function claimSnapshot(
|
|
546
|
+
adapter: Pick<Adapter, "updateMany">,
|
|
547
|
+
model: "kanbanBoard" | "kanbanColumn" | "kanbanTask",
|
|
548
|
+
snapshot: BoardSnapshot | ColumnSnapshot | TaskSnapshot,
|
|
549
|
+
) {
|
|
550
|
+
const claimedAt = nextSnapshotDate(snapshot.updatedAt);
|
|
551
|
+
const where =
|
|
552
|
+
model === "kanbanBoard"
|
|
553
|
+
? boardWhere(snapshot as BoardSnapshot)
|
|
554
|
+
: model === "kanbanColumn"
|
|
555
|
+
? columnWhere(snapshot as ColumnSnapshot)
|
|
556
|
+
: taskWhere(snapshot as TaskSnapshot);
|
|
557
|
+
const claimed = await adapter.updateMany({
|
|
558
|
+
model,
|
|
559
|
+
where,
|
|
560
|
+
update: { updatedAt: claimedAt },
|
|
561
|
+
});
|
|
562
|
+
if (!didAffectRow(claimed, snapshot.id)) throw staleStateError();
|
|
563
|
+
return claimedAt;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function restoreSnapshot(
|
|
567
|
+
adapter: Pick<Adapter, "updateMany">,
|
|
568
|
+
model: "kanbanBoard" | "kanbanColumn",
|
|
569
|
+
snapshot: BoardSnapshot | ColumnSnapshot,
|
|
570
|
+
claimedAt: Date,
|
|
571
|
+
) {
|
|
572
|
+
const where =
|
|
573
|
+
model === "kanbanBoard"
|
|
574
|
+
? boardWhere({ ...(snapshot as BoardSnapshot), updatedAt: claimedAt })
|
|
575
|
+
: columnWhere({ ...(snapshot as ColumnSnapshot), updatedAt: claimedAt });
|
|
576
|
+
const restored = await adapter.updateMany({
|
|
577
|
+
model,
|
|
578
|
+
where,
|
|
579
|
+
update: { updatedAt: snapshot.updatedAt },
|
|
580
|
+
});
|
|
581
|
+
if (!didAffectRow(restored, snapshot.id)) throw staleStateError();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function findBoard(adapter: Pick<Adapter, "findOne">, id: string) {
|
|
585
|
+
return (
|
|
586
|
+
(await adapter.findOne<Board>({
|
|
587
|
+
model: "kanbanBoard",
|
|
588
|
+
where: [{ field: "id", value: id, operator: "eq" as const }],
|
|
589
|
+
})) ?? null
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async function findColumn(adapter: Pick<Adapter, "findOne">, id: string) {
|
|
594
|
+
return (
|
|
595
|
+
(await adapter.findOne<Column>({
|
|
596
|
+
model: "kanbanColumn",
|
|
597
|
+
where: [{ field: "id", value: id, operator: "eq" as const }],
|
|
598
|
+
})) ?? null
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async function findTask(adapter: Pick<Adapter, "findOne">, id: string) {
|
|
603
|
+
return (
|
|
604
|
+
(await adapter.findOne<Task>({
|
|
605
|
+
model: "kanbanTask",
|
|
606
|
+
where: [{ field: "id", value: id, operator: "eq" as const }],
|
|
607
|
+
})) ?? null
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async function listColumns(
|
|
612
|
+
adapter: Pick<Adapter, "findMany">,
|
|
613
|
+
boardId: string,
|
|
614
|
+
) {
|
|
615
|
+
return adapter.findMany<Column>({
|
|
616
|
+
model: "kanbanColumn",
|
|
617
|
+
where: [{ field: "boardId", value: boardId, operator: "eq" as const }],
|
|
618
|
+
sortBy: { field: "order", direction: "asc" },
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async function listTasks(adapter: Pick<Adapter, "findMany">, columnId: string) {
|
|
623
|
+
return adapter.findMany<Task>({
|
|
624
|
+
model: "kanbanTask",
|
|
625
|
+
where: [{ field: "columnId", value: columnId, operator: "eq" as const }],
|
|
626
|
+
sortBy: { field: "order", direction: "asc" },
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function boardNotFoundError() {
|
|
631
|
+
return new KanbanOperationError(404, "Board not found", "BOARD_NOT_FOUND");
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function columnNotFoundError() {
|
|
635
|
+
return new KanbanOperationError(404, "Column not found", "COLUMN_NOT_FOUND");
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function taskNotFoundError() {
|
|
639
|
+
return new KanbanOperationError(404, "Task not found", "TASK_NOT_FOUND");
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function staleStateError() {
|
|
643
|
+
return new KanbanOperationError(
|
|
644
|
+
409,
|
|
645
|
+
"Kanban state changed while authorization was being evaluated. Retry the operation.",
|
|
646
|
+
"KANBAN_STATE_CHANGED",
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function invalidColumnSetError() {
|
|
651
|
+
return new KanbanOperationError(
|
|
652
|
+
400,
|
|
653
|
+
"columnIds must contain every current board column exactly once.",
|
|
654
|
+
"INVALID_COLUMN_SET",
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function invalidTaskSetError() {
|
|
659
|
+
return new KanbanOperationError(
|
|
660
|
+
400,
|
|
661
|
+
"taskIds must contain every current column task exactly once.",
|
|
662
|
+
"INVALID_TASK_SET",
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function invalidTargetColumnError() {
|
|
667
|
+
return new KanbanOperationError(
|
|
668
|
+
400,
|
|
669
|
+
"The target column must belong to the task's board.",
|
|
670
|
+
"INVALID_TARGET_COLUMN",
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function sanitizeSlug(value: string) {
|
|
675
|
+
const slug = slugify(value);
|
|
676
|
+
if (!slug) {
|
|
677
|
+
throw new KanbanOperationError(
|
|
678
|
+
400,
|
|
679
|
+
"Invalid slug: must contain at least one alphanumeric character",
|
|
680
|
+
"INVALID_SLUG",
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
return slug;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function normalizeError(error: unknown, fallback: string) {
|
|
687
|
+
return error instanceof Error
|
|
688
|
+
? error
|
|
689
|
+
: new Error(typeof error === "string" ? error : fallback, { cause: error });
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async function runDomainHook<T>(
|
|
693
|
+
run: () => Promise<T> | T,
|
|
694
|
+
code: string,
|
|
695
|
+
): Promise<T> {
|
|
696
|
+
try {
|
|
697
|
+
return await run();
|
|
698
|
+
} catch (error) {
|
|
699
|
+
throw new KanbanOperationError(
|
|
700
|
+
403,
|
|
701
|
+
normalizeError(error, "Kanban operation rejected").message,
|
|
702
|
+
code,
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function hookContext<TInput, TFacts, TResult>(
|
|
708
|
+
context: OperationContext<TInput, TFacts> & {
|
|
709
|
+
readonly result: DeepReadonly<TResult>;
|
|
710
|
+
},
|
|
711
|
+
legacy?: { body?: unknown; params?: unknown; query?: unknown },
|
|
712
|
+
): KanbanApiResultContext<TInput, TFacts, TResult>;
|
|
713
|
+
function hookContext<TInput, TFacts>(
|
|
714
|
+
context: OperationContext<TInput, TFacts>,
|
|
715
|
+
legacy?: { body?: unknown; params?: unknown; query?: unknown },
|
|
716
|
+
): KanbanApiContext<TInput, TFacts>;
|
|
717
|
+
function hookContext<TInput, TFacts, TResult>(
|
|
718
|
+
context:
|
|
719
|
+
| OperationContext<TInput, TFacts>
|
|
720
|
+
| (OperationContext<TInput, TFacts> & {
|
|
721
|
+
readonly result: DeepReadonly<TResult>;
|
|
722
|
+
}),
|
|
723
|
+
legacy: { body?: unknown; params?: unknown; query?: unknown } = {},
|
|
724
|
+
):
|
|
725
|
+
| KanbanApiContext<TInput, TFacts>
|
|
726
|
+
| KanbanApiResultContext<TInput, TFacts, TResult> {
|
|
727
|
+
return Object.freeze({
|
|
728
|
+
...context,
|
|
729
|
+
...(context.request
|
|
730
|
+
? { request: context.request, headers: context.request.headers }
|
|
731
|
+
: {}),
|
|
732
|
+
...legacy,
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
async function notifyBoardError<TInput, TFacts>(
|
|
737
|
+
hook:
|
|
738
|
+
| ((
|
|
739
|
+
error: Error,
|
|
740
|
+
context: KanbanApiContext<TInput, TFacts>,
|
|
741
|
+
) => Promise<void> | void)
|
|
742
|
+
| undefined,
|
|
743
|
+
error: unknown,
|
|
744
|
+
context: OperationContext<TInput, TFacts>,
|
|
745
|
+
legacy: { body?: unknown; params?: unknown; query?: unknown } = {},
|
|
746
|
+
) {
|
|
747
|
+
const normalized = normalizeError(error, "Kanban board operation failed");
|
|
748
|
+
const lifecycle = hookContext(context, legacy);
|
|
749
|
+
try {
|
|
750
|
+
await hook?.(normalized, lifecycle);
|
|
751
|
+
} catch {
|
|
752
|
+
// Error hooks are observational and must never replace the operation error.
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function boardFacts(snapshot: BoardSnapshot) {
|
|
757
|
+
return {
|
|
758
|
+
boardId: snapshot.id,
|
|
759
|
+
...(snapshot.ownerId ? { ownerId: snapshot.ownerId } : {}),
|
|
760
|
+
...(snapshot.organizationId
|
|
761
|
+
? { organizationId: snapshot.organizationId }
|
|
762
|
+
: {}),
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function taskFacts(snapshot: TaskAuthorizationSnapshot) {
|
|
767
|
+
return {
|
|
768
|
+
...boardFacts(snapshot.board),
|
|
769
|
+
columnId: snapshot.column.id,
|
|
770
|
+
taskId: snapshot.task.id,
|
|
771
|
+
...(snapshot.task.assigneeId
|
|
772
|
+
? { assigneeId: snapshot.task.assigneeId }
|
|
773
|
+
: {}),
|
|
774
|
+
isArchived: snapshot.task.isArchived,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
async function loadColumnAuthorization(
|
|
779
|
+
adapter: Pick<Adapter, "findOne">,
|
|
780
|
+
columnId: string,
|
|
781
|
+
): Promise<ColumnAuthorizationSnapshot> {
|
|
782
|
+
const column = await findColumn(adapter, columnId);
|
|
783
|
+
if (!column) throw columnNotFoundError();
|
|
784
|
+
const board = await findBoard(adapter, column.boardId);
|
|
785
|
+
if (!board) throw boardNotFoundError();
|
|
786
|
+
return { board: boardSnapshot(board), column: columnSnapshot(column) };
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
async function loadTaskAuthorization(
|
|
790
|
+
adapter: Pick<Adapter, "findOne">,
|
|
791
|
+
taskId: string,
|
|
792
|
+
): Promise<TaskAuthorizationSnapshot> {
|
|
793
|
+
const task = await findTask(adapter, taskId);
|
|
794
|
+
if (!task) throw taskNotFoundError();
|
|
795
|
+
const parent = await loadColumnAuthorization(adapter, task.columnId);
|
|
796
|
+
return { ...parent, task: taskSnapshot(task) };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function sameIdSet(left: readonly string[], right: readonly string[]) {
|
|
800
|
+
return (
|
|
801
|
+
left.length === right.length &&
|
|
802
|
+
new Set(left).size === left.length &&
|
|
803
|
+
left.every((id) => right.includes(id))
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function operationTask(task: Task) {
|
|
808
|
+
const serialized = serializeTask(task);
|
|
809
|
+
return { ...serialized };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function operationColumn(column: BoardWithColumns["columns"][number]) {
|
|
813
|
+
return {
|
|
814
|
+
...serializeBareColumn(column),
|
|
815
|
+
tasks: column.tasks.map(operationTask),
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function operationBoard(board: BoardWithColumns) {
|
|
820
|
+
return {
|
|
821
|
+
...serializeBareBoard(board),
|
|
822
|
+
columns: board.columns.map(operationColumn),
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function boardSummary(board: Parameters<typeof serializeBoardSummary>[0]) {
|
|
827
|
+
return serializeBoardSummary(board);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function serializeBareColumn(column: Column) {
|
|
831
|
+
return {
|
|
832
|
+
...column,
|
|
833
|
+
createdAt: column.createdAt.toISOString(),
|
|
834
|
+
updatedAt: column.updatedAt.toISOString(),
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function serializeBareBoard(board: Board) {
|
|
839
|
+
return {
|
|
840
|
+
...board,
|
|
841
|
+
createdAt: board.createdAt.toISOString(),
|
|
842
|
+
updatedAt: board.updatedAt.toISOString(),
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
async function verifyBoard(
|
|
847
|
+
adapter: Pick<Adapter, "findOne">,
|
|
848
|
+
expected: BoardSnapshot,
|
|
849
|
+
) {
|
|
850
|
+
if (!sameBoard(await findBoard(adapter, expected.id), expected)) {
|
|
851
|
+
throw staleStateError();
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function verifyColumnAuthorization(
|
|
856
|
+
adapter: Pick<Adapter, "findOne">,
|
|
857
|
+
expected: ColumnAuthorizationSnapshot,
|
|
858
|
+
) {
|
|
859
|
+
await verifyBoard(adapter, expected.board);
|
|
860
|
+
if (
|
|
861
|
+
!sameColumn(await findColumn(adapter, expected.column.id), expected.column)
|
|
862
|
+
) {
|
|
863
|
+
throw staleStateError();
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
async function verifyTaskAuthorization(
|
|
868
|
+
adapter: Pick<Adapter, "findOne">,
|
|
869
|
+
expected: TaskAuthorizationSnapshot,
|
|
870
|
+
) {
|
|
871
|
+
await verifyColumnAuthorization(adapter, expected);
|
|
872
|
+
if (!sameTask(await findTask(adapter, expected.task.id), expected.task)) {
|
|
873
|
+
throw staleStateError();
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async function loadMoveSnapshot(
|
|
878
|
+
adapter: Pick<Adapter, "findOne" | "findMany">,
|
|
879
|
+
primary: TaskAuthorizationSnapshot,
|
|
880
|
+
targetColumnId: string,
|
|
881
|
+
targetOrder: number,
|
|
882
|
+
): Promise<TaskMoveSnapshot> {
|
|
883
|
+
await verifyTaskAuthorization(adapter, primary);
|
|
884
|
+
const targetColumn = await findColumn(adapter, targetColumnId);
|
|
885
|
+
if (!targetColumn) throw columnNotFoundError();
|
|
886
|
+
if (targetColumn.boardId !== primary.board.id) {
|
|
887
|
+
throw invalidTargetColumnError();
|
|
888
|
+
}
|
|
889
|
+
const [sourceTasks, targetTasks] = await Promise.all([
|
|
890
|
+
listTasks(adapter, primary.column.id),
|
|
891
|
+
targetColumn.id === primary.column.id
|
|
892
|
+
? Promise.resolve<Task[]>([])
|
|
893
|
+
: listTasks(adapter, targetColumn.id),
|
|
894
|
+
]);
|
|
895
|
+
return {
|
|
896
|
+
...primary,
|
|
897
|
+
targetColumn: columnSnapshot(targetColumn),
|
|
898
|
+
sourceTasks: sourceTasks.map(taskSnapshot),
|
|
899
|
+
targetTasks:
|
|
900
|
+
targetColumn.id === primary.column.id
|
|
901
|
+
? sourceTasks.map(taskSnapshot)
|
|
902
|
+
: targetTasks.map(taskSnapshot),
|
|
903
|
+
targetOrder,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
async function verifyMoveSnapshot(
|
|
908
|
+
adapter: Pick<Adapter, "findOne" | "findMany">,
|
|
909
|
+
expected: TaskMoveSnapshot,
|
|
910
|
+
) {
|
|
911
|
+
await verifyTaskAuthorization(adapter, expected);
|
|
912
|
+
if (
|
|
913
|
+
!sameColumn(
|
|
914
|
+
await findColumn(adapter, expected.targetColumn.id),
|
|
915
|
+
expected.targetColumn,
|
|
916
|
+
)
|
|
917
|
+
) {
|
|
918
|
+
throw staleStateError();
|
|
919
|
+
}
|
|
920
|
+
const sourceTasks = (await listTasks(adapter, expected.column.id)).map(
|
|
921
|
+
taskSnapshot,
|
|
922
|
+
);
|
|
923
|
+
const targetTasks =
|
|
924
|
+
expected.targetColumn.id === expected.column.id
|
|
925
|
+
? sourceTasks
|
|
926
|
+
: (await listTasks(adapter, expected.targetColumn.id)).map(taskSnapshot);
|
|
927
|
+
if (
|
|
928
|
+
!sameSnapshotSet(sourceTasks, expected.sourceTasks, sameTaskSnapshots) ||
|
|
929
|
+
!sameSnapshotSet(targetTasks, expected.targetTasks, sameTaskSnapshots)
|
|
930
|
+
) {
|
|
931
|
+
throw staleStateError();
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async function claimColumnAuthorization(
|
|
936
|
+
adapter: Pick<Adapter, "updateMany">,
|
|
937
|
+
snapshot: ColumnAuthorizationSnapshot,
|
|
938
|
+
) {
|
|
939
|
+
const boardClaimedAt = await claimSnapshot(
|
|
940
|
+
adapter,
|
|
941
|
+
"kanbanBoard",
|
|
942
|
+
snapshot.board,
|
|
943
|
+
);
|
|
944
|
+
const columnClaimedAt = await claimSnapshot(
|
|
945
|
+
adapter,
|
|
946
|
+
"kanbanColumn",
|
|
947
|
+
snapshot.column,
|
|
948
|
+
);
|
|
949
|
+
return { boardClaimedAt, columnClaimedAt };
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
async function restoreColumnAuthorization(
|
|
953
|
+
adapter: Pick<Adapter, "updateMany">,
|
|
954
|
+
snapshot: ColumnAuthorizationSnapshot,
|
|
955
|
+
claims: { boardClaimedAt: Date; columnClaimedAt: Date },
|
|
956
|
+
) {
|
|
957
|
+
await restoreSnapshot(
|
|
958
|
+
adapter,
|
|
959
|
+
"kanbanColumn",
|
|
960
|
+
snapshot.column,
|
|
961
|
+
claims.columnClaimedAt,
|
|
962
|
+
);
|
|
963
|
+
await restoreSnapshot(
|
|
964
|
+
adapter,
|
|
965
|
+
"kanbanBoard",
|
|
966
|
+
snapshot.board,
|
|
967
|
+
claims.boardClaimedAt,
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
async function claimMoveSnapshot(
|
|
972
|
+
adapter: Pick<Adapter, "updateMany">,
|
|
973
|
+
snapshot: TaskMoveSnapshot,
|
|
974
|
+
) {
|
|
975
|
+
const boardClaimedAt = await claimSnapshot(
|
|
976
|
+
adapter,
|
|
977
|
+
"kanbanBoard",
|
|
978
|
+
snapshot.board,
|
|
979
|
+
);
|
|
980
|
+
const sourceColumnClaimedAt = await claimSnapshot(
|
|
981
|
+
adapter,
|
|
982
|
+
"kanbanColumn",
|
|
983
|
+
snapshot.column,
|
|
984
|
+
);
|
|
985
|
+
const targetColumnClaimedAt =
|
|
986
|
+
snapshot.targetColumn.id === snapshot.column.id
|
|
987
|
+
? sourceColumnClaimedAt
|
|
988
|
+
: await claimSnapshot(adapter, "kanbanColumn", snapshot.targetColumn);
|
|
989
|
+
const taskClaims = new Map<string, Date>();
|
|
990
|
+
const allTasks = new Map<string, TaskSnapshot>();
|
|
991
|
+
for (const task of [...snapshot.sourceTasks, ...snapshot.targetTasks]) {
|
|
992
|
+
allTasks.set(task.id, task);
|
|
993
|
+
}
|
|
994
|
+
for (const task of allTasks.values()) {
|
|
995
|
+
taskClaims.set(task.id, await claimSnapshot(adapter, "kanbanTask", task));
|
|
996
|
+
}
|
|
997
|
+
return {
|
|
998
|
+
boardClaimedAt,
|
|
999
|
+
sourceColumnClaimedAt,
|
|
1000
|
+
targetColumnClaimedAt,
|
|
1001
|
+
taskClaims,
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
async function applyTaskMove(
|
|
1006
|
+
adapter: Pick<Adapter, "updateMany" | "findOne">,
|
|
1007
|
+
snapshot: TaskMoveSnapshot,
|
|
1008
|
+
claims: Awaited<ReturnType<typeof claimMoveSnapshot>>,
|
|
1009
|
+
extraUpdate: Readonly<Record<string, unknown>>,
|
|
1010
|
+
) {
|
|
1011
|
+
const sourceTasks = snapshot.sourceTasks.filter(
|
|
1012
|
+
(task) => task.id !== snapshot.task.id,
|
|
1013
|
+
);
|
|
1014
|
+
const targetBase =
|
|
1015
|
+
snapshot.targetColumn.id === snapshot.column.id
|
|
1016
|
+
? sourceTasks
|
|
1017
|
+
: snapshot.targetTasks.filter((task) => task.id !== snapshot.task.id);
|
|
1018
|
+
const targetOrder = Math.min(snapshot.targetOrder, targetBase.length);
|
|
1019
|
+
const orderedTarget = [...targetBase];
|
|
1020
|
+
orderedTarget.splice(targetOrder, 0, {
|
|
1021
|
+
...snapshot.task,
|
|
1022
|
+
columnId: snapshot.targetColumn.id,
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
const finalRows = new Map<
|
|
1026
|
+
string,
|
|
1027
|
+
{ snapshot: TaskSnapshot; columnId: string; order: number }
|
|
1028
|
+
>();
|
|
1029
|
+
if (snapshot.targetColumn.id !== snapshot.column.id) {
|
|
1030
|
+
sourceTasks.forEach((task, order) => {
|
|
1031
|
+
finalRows.set(task.id, {
|
|
1032
|
+
snapshot: task,
|
|
1033
|
+
columnId: task.columnId,
|
|
1034
|
+
order,
|
|
1035
|
+
});
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
orderedTarget.forEach((task, order) => {
|
|
1039
|
+
const original =
|
|
1040
|
+
task.id === snapshot.task.id
|
|
1041
|
+
? snapshot.task
|
|
1042
|
+
: (snapshot.targetTasks.find((candidate) => candidate.id === task.id) ??
|
|
1043
|
+
task);
|
|
1044
|
+
finalRows.set(task.id, {
|
|
1045
|
+
snapshot: original,
|
|
1046
|
+
columnId: snapshot.targetColumn.id,
|
|
1047
|
+
order,
|
|
1048
|
+
});
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
let moved: Task | null = null;
|
|
1052
|
+
for (const row of finalRows.values()) {
|
|
1053
|
+
const claimedAt = claims.taskClaims.get(row.snapshot.id);
|
|
1054
|
+
if (!claimedAt) throw staleStateError();
|
|
1055
|
+
const matched = await adapter.updateMany({
|
|
1056
|
+
model: "kanbanTask",
|
|
1057
|
+
where: taskWhere({ ...row.snapshot, updatedAt: claimedAt }),
|
|
1058
|
+
update: {
|
|
1059
|
+
...(row.snapshot.id === snapshot.task.id ? extraUpdate : {}),
|
|
1060
|
+
columnId: row.columnId,
|
|
1061
|
+
order: row.order,
|
|
1062
|
+
updatedAt: nextSnapshotDate(claimedAt),
|
|
1063
|
+
},
|
|
1064
|
+
});
|
|
1065
|
+
if (!didAffectRow(matched, row.snapshot.id)) throw staleStateError();
|
|
1066
|
+
if (row.snapshot.id === snapshot.task.id) {
|
|
1067
|
+
moved = await findTask(adapter, row.snapshot.id);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
if (!moved) throw taskNotFoundError();
|
|
1071
|
+
await restoreSnapshot(
|
|
1072
|
+
adapter,
|
|
1073
|
+
"kanbanColumn",
|
|
1074
|
+
snapshot.column,
|
|
1075
|
+
claims.sourceColumnClaimedAt,
|
|
1076
|
+
);
|
|
1077
|
+
if (snapshot.targetColumn.id !== snapshot.column.id) {
|
|
1078
|
+
await restoreSnapshot(
|
|
1079
|
+
adapter,
|
|
1080
|
+
"kanbanColumn",
|
|
1081
|
+
snapshot.targetColumn,
|
|
1082
|
+
claims.targetColumnClaimedAt,
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
await restoreSnapshot(
|
|
1086
|
+
adapter,
|
|
1087
|
+
"kanbanBoard",
|
|
1088
|
+
snapshot.board,
|
|
1089
|
+
claims.boardClaimedAt,
|
|
1090
|
+
);
|
|
1091
|
+
return moved;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function sameOptionalBoard(
|
|
1095
|
+
board: Board | null,
|
|
1096
|
+
expected: BoardSnapshot | null,
|
|
1097
|
+
) {
|
|
1098
|
+
return expected === null ? board === null : sameBoard(board, expected);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/** Create the complete Kanban operation inventory for every server transport. */
|
|
1102
|
+
export function createKanbanOperations(
|
|
1103
|
+
sourceAdapter: Adapter,
|
|
1104
|
+
hooks?: KanbanBackendHooks,
|
|
1105
|
+
): KanbanOperations {
|
|
1106
|
+
const adapter = serializeMemoryOperations(sourceAdapter);
|
|
1107
|
+
const boardSnapshots = new WeakMap<object, BoardSnapshot | null>();
|
|
1108
|
+
const columnSnapshots = new WeakMap<object, ColumnAuthorizationSnapshot>();
|
|
1109
|
+
const taskSnapshots = new WeakMap<object, TaskAuthorizationSnapshot>();
|
|
1110
|
+
const columnReorderSnapshots = new WeakMap<object, ColumnReorderSnapshot>();
|
|
1111
|
+
const taskReorderSnapshots = new WeakMap<object, TaskReorderSnapshot>();
|
|
1112
|
+
const taskMoveSnapshots = new WeakMap<object, TaskMoveSnapshot>();
|
|
1113
|
+
const updateTaskMoveSnapshots = new WeakMap<object, TaskMoveSnapshot>();
|
|
1114
|
+
const reorderedColumns = new WeakMap<object, readonly SerializedColumn[]>();
|
|
1115
|
+
const reorderedTasks = new WeakMap<object, readonly SerializedTask[]>();
|
|
1116
|
+
|
|
1117
|
+
const listBoards = defineOperation({
|
|
1118
|
+
input: BoardListQuerySchema,
|
|
1119
|
+
permission: kanbanPermissions.board.read,
|
|
1120
|
+
facts: () => ({ scope: "collection" as const }),
|
|
1121
|
+
before: async (context) => {
|
|
1122
|
+
await runDomainHook(
|
|
1123
|
+
() =>
|
|
1124
|
+
hooks?.onBeforeListBoards?.(
|
|
1125
|
+
{ ...context.input },
|
|
1126
|
+
hookContext(context, { query: context.input }),
|
|
1127
|
+
),
|
|
1128
|
+
"LIST_BOARDS_REJECTED",
|
|
1129
|
+
);
|
|
1130
|
+
},
|
|
1131
|
+
execute: async ({ input }) => {
|
|
1132
|
+
const result = await getBoardSummaries(adapter, input);
|
|
1133
|
+
return {
|
|
1134
|
+
items: result.items.map(boardSummary),
|
|
1135
|
+
total: result.total,
|
|
1136
|
+
...(result.limit !== undefined ? { limit: result.limit } : {}),
|
|
1137
|
+
...(result.offset !== undefined ? { offset: result.offset } : {}),
|
|
1138
|
+
};
|
|
1139
|
+
},
|
|
1140
|
+
after: async (context) => {
|
|
1141
|
+
await hooks?.onAfterListBoards?.(
|
|
1142
|
+
context.result.items,
|
|
1143
|
+
{ ...context.input },
|
|
1144
|
+
hookContext(context, { query: context.input }),
|
|
1145
|
+
);
|
|
1146
|
+
},
|
|
1147
|
+
onError: ({ error, ...context }) =>
|
|
1148
|
+
notifyBoardError(hooks?.onErrorListBoards, error, context, {
|
|
1149
|
+
query: context.input,
|
|
1150
|
+
}),
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
const getBoard = defineOperation({
|
|
1154
|
+
input: BoardIdOperationInputSchema,
|
|
1155
|
+
permission: kanbanPermissions.board.read,
|
|
1156
|
+
facts: async ({ input }) => {
|
|
1157
|
+
const board = await findBoard(adapter, input.id);
|
|
1158
|
+
const snapshot = board ? boardSnapshot(board) : null;
|
|
1159
|
+
boardSnapshots.set(input as object, snapshot);
|
|
1160
|
+
return {
|
|
1161
|
+
scope: "record" as const,
|
|
1162
|
+
boardId: input.id,
|
|
1163
|
+
exists: snapshot !== null,
|
|
1164
|
+
...(snapshot ? boardFacts(snapshot) : {}),
|
|
1165
|
+
} satisfies BoardReadFacts;
|
|
1166
|
+
},
|
|
1167
|
+
before: async (context) => {
|
|
1168
|
+
const expected = boardSnapshots.get(context.input as object) ?? null;
|
|
1169
|
+
if (
|
|
1170
|
+
!sameOptionalBoard(await findBoard(adapter, context.input.id), expected)
|
|
1171
|
+
) {
|
|
1172
|
+
throw staleStateError();
|
|
1173
|
+
}
|
|
1174
|
+
await runDomainHook(
|
|
1175
|
+
() =>
|
|
1176
|
+
hooks?.onBeforeGetBoard?.(
|
|
1177
|
+
context.input.id,
|
|
1178
|
+
hookContext(context, { params: { id: context.input.id } }),
|
|
1179
|
+
),
|
|
1180
|
+
"READ_BOARD_REJECTED",
|
|
1181
|
+
);
|
|
1182
|
+
},
|
|
1183
|
+
execute: async (context) => {
|
|
1184
|
+
const expected = boardSnapshots.get(context.input as object) ?? null;
|
|
1185
|
+
const board = await getBoardById(adapter, context.input.id);
|
|
1186
|
+
if (!sameOptionalBoard(board, expected)) {
|
|
1187
|
+
throw staleStateError();
|
|
1188
|
+
}
|
|
1189
|
+
if (!board) throw boardNotFoundError();
|
|
1190
|
+
return operationBoard(board);
|
|
1191
|
+
},
|
|
1192
|
+
after: async (context) => {
|
|
1193
|
+
await hooks?.onAfterGetBoard?.(
|
|
1194
|
+
context.result,
|
|
1195
|
+
hookContext<
|
|
1196
|
+
z.output<typeof BoardIdOperationInputSchema>,
|
|
1197
|
+
BoardReadFacts,
|
|
1198
|
+
SerializedBoardWithColumns
|
|
1199
|
+
>(context, { params: { id: context.input.id } }),
|
|
1200
|
+
);
|
|
1201
|
+
},
|
|
1202
|
+
onError: ({ error, ...context }) =>
|
|
1203
|
+
notifyBoardError(hooks?.onErrorGetBoard, error, context, {
|
|
1204
|
+
params: { id: context.input.id },
|
|
1205
|
+
}),
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
const createBoard = defineOperation({
|
|
1209
|
+
input: createBoardSchema,
|
|
1210
|
+
permission: kanbanPermissions.board.create,
|
|
1211
|
+
facts: () => undefined,
|
|
1212
|
+
execute: async (context) => {
|
|
1213
|
+
const data = { ...context.input };
|
|
1214
|
+
requireAtomicTransactions(adapter);
|
|
1215
|
+
return adapter.transaction(async (tx) => {
|
|
1216
|
+
await runDomainHook(
|
|
1217
|
+
() =>
|
|
1218
|
+
hooks?.onBeforeCreateBoard?.(
|
|
1219
|
+
data,
|
|
1220
|
+
hookContext(context, { body: data }),
|
|
1221
|
+
),
|
|
1222
|
+
"CREATE_BOARD_REJECTED",
|
|
1223
|
+
);
|
|
1224
|
+
const input = {
|
|
1225
|
+
...data,
|
|
1226
|
+
slug: sanitizeSlug(data.slug || data.name),
|
|
1227
|
+
...(context.identity ? { ownerId: context.identity.id } : {}),
|
|
1228
|
+
};
|
|
1229
|
+
const now = new Date();
|
|
1230
|
+
const board = await tx.create<Board>({
|
|
1231
|
+
model: "kanbanBoard",
|
|
1232
|
+
data: {
|
|
1233
|
+
...input,
|
|
1234
|
+
createdAt: now,
|
|
1235
|
+
updatedAt: now,
|
|
1236
|
+
},
|
|
1237
|
+
});
|
|
1238
|
+
const columns: Column[] = [];
|
|
1239
|
+
for (const [order, title] of [
|
|
1240
|
+
"To Do",
|
|
1241
|
+
"In Progress",
|
|
1242
|
+
"Done",
|
|
1243
|
+
].entries()) {
|
|
1244
|
+
columns.push(
|
|
1245
|
+
await tx.create<Column>({
|
|
1246
|
+
model: "kanbanColumn",
|
|
1247
|
+
data: {
|
|
1248
|
+
title,
|
|
1249
|
+
boardId: board.id,
|
|
1250
|
+
order,
|
|
1251
|
+
createdAt: now,
|
|
1252
|
+
updatedAt: now,
|
|
1253
|
+
},
|
|
1254
|
+
}),
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
return operationBoard({
|
|
1258
|
+
...board,
|
|
1259
|
+
columns: columns.map((column) => ({ ...column, tasks: [] })),
|
|
1260
|
+
});
|
|
1261
|
+
});
|
|
1262
|
+
},
|
|
1263
|
+
after: async (context) => {
|
|
1264
|
+
await hooks?.onAfterCreateBoard?.(
|
|
1265
|
+
context.result,
|
|
1266
|
+
hookContext<
|
|
1267
|
+
z.output<typeof createBoardSchema>,
|
|
1268
|
+
undefined,
|
|
1269
|
+
SerializedBoardWithColumns
|
|
1270
|
+
>(context, { body: context.input }),
|
|
1271
|
+
);
|
|
1272
|
+
},
|
|
1273
|
+
onError: ({ error, ...context }) => {
|
|
1274
|
+
markMemoryRollback(adapter, error);
|
|
1275
|
+
return notifyBoardError(hooks?.onErrorCreateBoard, error, context, {
|
|
1276
|
+
body: context.input,
|
|
1277
|
+
});
|
|
1278
|
+
},
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
const updateBoard = defineOperation({
|
|
1282
|
+
input: UpdateBoardOperationInputSchema,
|
|
1283
|
+
permission: kanbanPermissions.board.update,
|
|
1284
|
+
facts: async ({ input }) => {
|
|
1285
|
+
const board = await findBoard(adapter, input.id);
|
|
1286
|
+
if (!board) throw boardNotFoundError();
|
|
1287
|
+
const snapshot = boardSnapshot(board);
|
|
1288
|
+
boardSnapshots.set(input as object, snapshot);
|
|
1289
|
+
return boardFacts(snapshot) satisfies BoardUpdateFacts;
|
|
1290
|
+
},
|
|
1291
|
+
execute: async (context) => {
|
|
1292
|
+
const snapshot = boardSnapshots.get(context.input as object);
|
|
1293
|
+
if (!snapshot) throw staleStateError();
|
|
1294
|
+
requireAtomicTransactions(adapter);
|
|
1295
|
+
const data = {
|
|
1296
|
+
...context.input.data,
|
|
1297
|
+
...(context.input.data.slug
|
|
1298
|
+
? { slug: sanitizeSlug(context.input.data.slug) }
|
|
1299
|
+
: {}),
|
|
1300
|
+
};
|
|
1301
|
+
return adapter.transaction(async (tx) => {
|
|
1302
|
+
await verifyBoard(tx, snapshot);
|
|
1303
|
+
const claimedAt = await claimSnapshot(tx, "kanbanBoard", snapshot);
|
|
1304
|
+
await runDomainHook(
|
|
1305
|
+
() =>
|
|
1306
|
+
hooks?.onBeforeUpdateBoard?.(
|
|
1307
|
+
context.input.id,
|
|
1308
|
+
{ ...data, id: context.input.id },
|
|
1309
|
+
hookContext(context, {
|
|
1310
|
+
body: data,
|
|
1311
|
+
params: { id: context.input.id },
|
|
1312
|
+
}),
|
|
1313
|
+
),
|
|
1314
|
+
"UPDATE_BOARD_REJECTED",
|
|
1315
|
+
);
|
|
1316
|
+
const matched = await tx.updateMany({
|
|
1317
|
+
model: "kanbanBoard",
|
|
1318
|
+
where: boardWhere({ ...snapshot, updatedAt: claimedAt }),
|
|
1319
|
+
update: {
|
|
1320
|
+
...data,
|
|
1321
|
+
updatedAt: nextSnapshotDate(claimedAt),
|
|
1322
|
+
},
|
|
1323
|
+
});
|
|
1324
|
+
if (!didAffectRow(matched, snapshot.id)) throw staleStateError();
|
|
1325
|
+
const updated = await findBoard(tx, snapshot.id);
|
|
1326
|
+
if (!updated) throw boardNotFoundError();
|
|
1327
|
+
return serializeBareBoard(updated);
|
|
1328
|
+
});
|
|
1329
|
+
},
|
|
1330
|
+
after: async (context) => {
|
|
1331
|
+
await hooks?.onAfterUpdateBoard?.(
|
|
1332
|
+
context.result,
|
|
1333
|
+
hookContext(context, {
|
|
1334
|
+
body: context.input.data,
|
|
1335
|
+
params: { id: context.input.id },
|
|
1336
|
+
}),
|
|
1337
|
+
);
|
|
1338
|
+
},
|
|
1339
|
+
onError: ({ error, ...context }) => {
|
|
1340
|
+
markMemoryRollback(adapter, error);
|
|
1341
|
+
return notifyBoardError(hooks?.onErrorUpdateBoard, error, context, {
|
|
1342
|
+
body: context.input.data,
|
|
1343
|
+
params: { id: context.input.id },
|
|
1344
|
+
});
|
|
1345
|
+
},
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
const deleteBoard = defineOperation({
|
|
1349
|
+
input: BoardIdOperationInputSchema,
|
|
1350
|
+
permission: kanbanPermissions.board.delete,
|
|
1351
|
+
facts: async ({ input }) => {
|
|
1352
|
+
const board = await findBoard(adapter, input.id);
|
|
1353
|
+
if (!board) throw boardNotFoundError();
|
|
1354
|
+
const snapshot = boardSnapshot(board);
|
|
1355
|
+
boardSnapshots.set(input as object, snapshot);
|
|
1356
|
+
return boardFacts(snapshot) satisfies BoardDeleteFacts;
|
|
1357
|
+
},
|
|
1358
|
+
execute: async (context) => {
|
|
1359
|
+
const snapshot = boardSnapshots.get(context.input as object);
|
|
1360
|
+
if (!snapshot) throw staleStateError();
|
|
1361
|
+
requireAtomicTransactions(adapter);
|
|
1362
|
+
return adapter.transaction(async (tx) => {
|
|
1363
|
+
await verifyBoard(tx, snapshot);
|
|
1364
|
+
const claimedAt = await claimSnapshot(tx, "kanbanBoard", snapshot);
|
|
1365
|
+
await runDomainHook(
|
|
1366
|
+
() =>
|
|
1367
|
+
hooks?.onBeforeDeleteBoard?.(
|
|
1368
|
+
context.input.id,
|
|
1369
|
+
hookContext(context, {
|
|
1370
|
+
params: { id: context.input.id },
|
|
1371
|
+
}),
|
|
1372
|
+
),
|
|
1373
|
+
"DELETE_BOARD_REJECTED",
|
|
1374
|
+
);
|
|
1375
|
+
const deleted = await tx.deleteMany({
|
|
1376
|
+
model: "kanbanBoard",
|
|
1377
|
+
where: boardWhere({ ...snapshot, updatedAt: claimedAt }),
|
|
1378
|
+
});
|
|
1379
|
+
if (!didAffectRow(deleted, snapshot.id)) throw staleStateError();
|
|
1380
|
+
return { success: true } as const;
|
|
1381
|
+
});
|
|
1382
|
+
},
|
|
1383
|
+
after: async (context) => {
|
|
1384
|
+
await hooks?.onAfterDeleteBoard?.(
|
|
1385
|
+
context.input.id,
|
|
1386
|
+
hookContext(context, { params: { id: context.input.id } }),
|
|
1387
|
+
);
|
|
1388
|
+
},
|
|
1389
|
+
onError: ({ error, ...context }) => {
|
|
1390
|
+
markMemoryRollback(adapter, error);
|
|
1391
|
+
return notifyBoardError(hooks?.onErrorDeleteBoard, error, context, {
|
|
1392
|
+
params: { id: context.input.id },
|
|
1393
|
+
});
|
|
1394
|
+
},
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1397
|
+
const createColumn = defineOperation({
|
|
1398
|
+
input: createColumnSchema,
|
|
1399
|
+
permission: kanbanPermissions.column.create,
|
|
1400
|
+
facts: async ({ input }) => {
|
|
1401
|
+
const board = await findBoard(adapter, input.boardId);
|
|
1402
|
+
if (!board) throw boardNotFoundError();
|
|
1403
|
+
const snapshot = boardSnapshot(board);
|
|
1404
|
+
boardSnapshots.set(input as object, snapshot);
|
|
1405
|
+
return boardFacts(snapshot) satisfies ColumnCreateFacts;
|
|
1406
|
+
},
|
|
1407
|
+
execute: async (context) => {
|
|
1408
|
+
const board = boardSnapshots.get(context.input as object);
|
|
1409
|
+
if (!board) throw staleStateError();
|
|
1410
|
+
requireAtomicTransactions(adapter);
|
|
1411
|
+
return adapter.transaction(async (tx) => {
|
|
1412
|
+
await verifyBoard(tx, board);
|
|
1413
|
+
const boardClaimedAt = await claimSnapshot(tx, "kanbanBoard", board);
|
|
1414
|
+
await runDomainHook(
|
|
1415
|
+
() =>
|
|
1416
|
+
hooks?.onBeforeCreateColumn?.(
|
|
1417
|
+
{ ...context.input },
|
|
1418
|
+
hookContext(context, { body: context.input }),
|
|
1419
|
+
),
|
|
1420
|
+
"CREATE_COLUMN_REJECTED",
|
|
1421
|
+
);
|
|
1422
|
+
const existing = await listColumns(tx, board.id);
|
|
1423
|
+
const nextOrder =
|
|
1424
|
+
existing.length === 0
|
|
1425
|
+
? 0
|
|
1426
|
+
: Math.max(...existing.map((column) => column.order)) + 1;
|
|
1427
|
+
const now = new Date();
|
|
1428
|
+
const column = await tx.create<Column>({
|
|
1429
|
+
model: "kanbanColumn",
|
|
1430
|
+
data: {
|
|
1431
|
+
...context.input,
|
|
1432
|
+
order: context.input.order ?? nextOrder,
|
|
1433
|
+
createdAt: now,
|
|
1434
|
+
updatedAt: now,
|
|
1435
|
+
},
|
|
1436
|
+
});
|
|
1437
|
+
await restoreSnapshot(tx, "kanbanBoard", board, boardClaimedAt);
|
|
1438
|
+
return serializeBareColumn(column);
|
|
1439
|
+
});
|
|
1440
|
+
},
|
|
1441
|
+
after: async (context) => {
|
|
1442
|
+
await hooks?.onAfterCreateColumn?.(
|
|
1443
|
+
context.result,
|
|
1444
|
+
hookContext(context, { body: context.input }),
|
|
1445
|
+
);
|
|
1446
|
+
},
|
|
1447
|
+
onError: ({ error }) => markMemoryRollback(adapter, error),
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
const updateColumn = defineOperation({
|
|
1451
|
+
input: UpdateColumnOperationInputSchema,
|
|
1452
|
+
permission: kanbanPermissions.column.update,
|
|
1453
|
+
facts: async ({ input }) => {
|
|
1454
|
+
const snapshot = await loadColumnAuthorization(adapter, input.id);
|
|
1455
|
+
columnSnapshots.set(input as object, snapshot);
|
|
1456
|
+
return {
|
|
1457
|
+
...boardFacts(snapshot.board),
|
|
1458
|
+
columnId: snapshot.column.id,
|
|
1459
|
+
} satisfies ColumnUpdateFacts;
|
|
1460
|
+
},
|
|
1461
|
+
additionalPermissions: ({ input }) => {
|
|
1462
|
+
if (input.data.order === undefined) return [];
|
|
1463
|
+
const snapshot = columnSnapshots.get(input as object);
|
|
1464
|
+
if (!snapshot) throw staleStateError();
|
|
1465
|
+
return [kanbanPermissions.column.reorder(boardFacts(snapshot.board))];
|
|
1466
|
+
},
|
|
1467
|
+
execute: async (context) => {
|
|
1468
|
+
const snapshot = columnSnapshots.get(context.input as object);
|
|
1469
|
+
if (!snapshot) throw staleStateError();
|
|
1470
|
+
if (
|
|
1471
|
+
context.input.data.boardId &&
|
|
1472
|
+
context.input.data.boardId !== snapshot.board.id
|
|
1473
|
+
) {
|
|
1474
|
+
throw invalidTargetColumnError();
|
|
1475
|
+
}
|
|
1476
|
+
requireAtomicTransactions(adapter);
|
|
1477
|
+
return adapter.transaction(async (tx) => {
|
|
1478
|
+
await verifyColumnAuthorization(tx, snapshot);
|
|
1479
|
+
const claims = await claimColumnAuthorization(tx, snapshot);
|
|
1480
|
+
await runDomainHook(
|
|
1481
|
+
() =>
|
|
1482
|
+
hooks?.onBeforeUpdateColumn?.(
|
|
1483
|
+
context.input.id,
|
|
1484
|
+
{ ...context.input.data, id: context.input.id },
|
|
1485
|
+
hookContext(context, {
|
|
1486
|
+
body: context.input.data,
|
|
1487
|
+
params: { id: context.input.id },
|
|
1488
|
+
}),
|
|
1489
|
+
),
|
|
1490
|
+
"UPDATE_COLUMN_REJECTED",
|
|
1491
|
+
);
|
|
1492
|
+
const matched = await tx.updateMany({
|
|
1493
|
+
model: "kanbanColumn",
|
|
1494
|
+
where: columnWhere({
|
|
1495
|
+
...snapshot.column,
|
|
1496
|
+
updatedAt: claims.columnClaimedAt,
|
|
1497
|
+
}),
|
|
1498
|
+
update: {
|
|
1499
|
+
...context.input.data,
|
|
1500
|
+
boardId: snapshot.board.id,
|
|
1501
|
+
updatedAt: nextSnapshotDate(claims.columnClaimedAt),
|
|
1502
|
+
},
|
|
1503
|
+
});
|
|
1504
|
+
if (!didAffectRow(matched, snapshot.column.id)) {
|
|
1505
|
+
throw staleStateError();
|
|
1506
|
+
}
|
|
1507
|
+
await restoreSnapshot(
|
|
1508
|
+
tx,
|
|
1509
|
+
"kanbanBoard",
|
|
1510
|
+
snapshot.board,
|
|
1511
|
+
claims.boardClaimedAt,
|
|
1512
|
+
);
|
|
1513
|
+
const updated = await findColumn(tx, snapshot.column.id);
|
|
1514
|
+
if (!updated) throw columnNotFoundError();
|
|
1515
|
+
return serializeBareColumn(updated);
|
|
1516
|
+
});
|
|
1517
|
+
},
|
|
1518
|
+
after: async (context) => {
|
|
1519
|
+
await hooks?.onAfterUpdateColumn?.(
|
|
1520
|
+
context.result,
|
|
1521
|
+
hookContext(context, {
|
|
1522
|
+
body: context.input.data,
|
|
1523
|
+
params: { id: context.input.id },
|
|
1524
|
+
}),
|
|
1525
|
+
);
|
|
1526
|
+
},
|
|
1527
|
+
onError: ({ error }) => markMemoryRollback(adapter, error),
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
const deleteColumn = defineOperation({
|
|
1531
|
+
input: ColumnIdOperationInputSchema,
|
|
1532
|
+
permission: kanbanPermissions.column.delete,
|
|
1533
|
+
facts: async ({ input }) => {
|
|
1534
|
+
const snapshot = await loadColumnAuthorization(adapter, input.id);
|
|
1535
|
+
columnSnapshots.set(input as object, snapshot);
|
|
1536
|
+
return {
|
|
1537
|
+
...boardFacts(snapshot.board),
|
|
1538
|
+
columnId: snapshot.column.id,
|
|
1539
|
+
} satisfies ColumnDeleteFacts;
|
|
1540
|
+
},
|
|
1541
|
+
execute: async (context) => {
|
|
1542
|
+
const snapshot = columnSnapshots.get(context.input as object);
|
|
1543
|
+
if (!snapshot) throw staleStateError();
|
|
1544
|
+
requireAtomicTransactions(adapter);
|
|
1545
|
+
return adapter.transaction(async (tx) => {
|
|
1546
|
+
await verifyColumnAuthorization(tx, snapshot);
|
|
1547
|
+
const claims = await claimColumnAuthorization(tx, snapshot);
|
|
1548
|
+
await runDomainHook(
|
|
1549
|
+
() =>
|
|
1550
|
+
hooks?.onBeforeDeleteColumn?.(
|
|
1551
|
+
context.input.id,
|
|
1552
|
+
hookContext(context, {
|
|
1553
|
+
params: { id: context.input.id },
|
|
1554
|
+
}),
|
|
1555
|
+
),
|
|
1556
|
+
"DELETE_COLUMN_REJECTED",
|
|
1557
|
+
);
|
|
1558
|
+
const deleted = await tx.deleteMany({
|
|
1559
|
+
model: "kanbanColumn",
|
|
1560
|
+
where: columnWhere({
|
|
1561
|
+
...snapshot.column,
|
|
1562
|
+
updatedAt: claims.columnClaimedAt,
|
|
1563
|
+
}),
|
|
1564
|
+
});
|
|
1565
|
+
if (!didAffectRow(deleted, snapshot.column.id)) {
|
|
1566
|
+
throw staleStateError();
|
|
1567
|
+
}
|
|
1568
|
+
await restoreSnapshot(
|
|
1569
|
+
tx,
|
|
1570
|
+
"kanbanBoard",
|
|
1571
|
+
snapshot.board,
|
|
1572
|
+
claims.boardClaimedAt,
|
|
1573
|
+
);
|
|
1574
|
+
return { success: true } as const;
|
|
1575
|
+
});
|
|
1576
|
+
},
|
|
1577
|
+
after: async (context) => {
|
|
1578
|
+
await hooks?.onAfterDeleteColumn?.(
|
|
1579
|
+
context.input.id,
|
|
1580
|
+
hookContext(context, { params: { id: context.input.id } }),
|
|
1581
|
+
);
|
|
1582
|
+
},
|
|
1583
|
+
onError: ({ error }) => markMemoryRollback(adapter, error),
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
const reorderColumns = defineOperation({
|
|
1587
|
+
input: reorderColumnsSchema,
|
|
1588
|
+
permission: kanbanPermissions.column.reorder,
|
|
1589
|
+
facts: async ({ input }) => {
|
|
1590
|
+
const board = await findBoard(adapter, input.boardId);
|
|
1591
|
+
if (!board) throw boardNotFoundError();
|
|
1592
|
+
const columns = await listColumns(adapter, board.id);
|
|
1593
|
+
if (
|
|
1594
|
+
!sameIdSet(
|
|
1595
|
+
input.columnIds,
|
|
1596
|
+
columns.map((column) => column.id),
|
|
1597
|
+
)
|
|
1598
|
+
) {
|
|
1599
|
+
throw invalidColumnSetError();
|
|
1600
|
+
}
|
|
1601
|
+
const snapshot = {
|
|
1602
|
+
board: boardSnapshot(board),
|
|
1603
|
+
columns: columns.map(columnSnapshot),
|
|
1604
|
+
};
|
|
1605
|
+
columnReorderSnapshots.set(input as object, snapshot);
|
|
1606
|
+
return boardFacts(snapshot.board) satisfies ColumnReorderFacts;
|
|
1607
|
+
},
|
|
1608
|
+
execute: async (context) => {
|
|
1609
|
+
const snapshot = columnReorderSnapshots.get(context.input as object);
|
|
1610
|
+
if (!snapshot) throw staleStateError();
|
|
1611
|
+
requireAtomicTransactions(adapter);
|
|
1612
|
+
return adapter.transaction(async (tx) => {
|
|
1613
|
+
await verifyBoard(tx, snapshot.board);
|
|
1614
|
+
const current = (await listColumns(tx, snapshot.board.id)).map(
|
|
1615
|
+
columnSnapshot,
|
|
1616
|
+
);
|
|
1617
|
+
if (!sameSnapshotSet(current, snapshot.columns, sameColumnSnapshots)) {
|
|
1618
|
+
throw staleStateError();
|
|
1619
|
+
}
|
|
1620
|
+
const boardClaimedAt = await claimSnapshot(
|
|
1621
|
+
tx,
|
|
1622
|
+
"kanbanBoard",
|
|
1623
|
+
snapshot.board,
|
|
1624
|
+
);
|
|
1625
|
+
const claims = new Map<string, Date>();
|
|
1626
|
+
for (const column of snapshot.columns) {
|
|
1627
|
+
claims.set(
|
|
1628
|
+
column.id,
|
|
1629
|
+
await claimSnapshot(tx, "kanbanColumn", column),
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
for (const [order, columnId] of context.input.columnIds.entries()) {
|
|
1633
|
+
await runDomainHook(
|
|
1634
|
+
() =>
|
|
1635
|
+
hooks?.onBeforeUpdateColumn?.(
|
|
1636
|
+
columnId,
|
|
1637
|
+
{ id: columnId, order },
|
|
1638
|
+
hookContext(context, { body: context.input }),
|
|
1639
|
+
),
|
|
1640
|
+
"REORDER_COLUMNS_REJECTED",
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
const updated: SerializedColumn[] = [];
|
|
1644
|
+
for (const [order, columnId] of context.input.columnIds.entries()) {
|
|
1645
|
+
const original = snapshot.columns.find(
|
|
1646
|
+
(column) => column.id === columnId,
|
|
1647
|
+
);
|
|
1648
|
+
const claimedAt = claims.get(columnId);
|
|
1649
|
+
if (!original || !claimedAt) throw staleStateError();
|
|
1650
|
+
const matched = await tx.updateMany({
|
|
1651
|
+
model: "kanbanColumn",
|
|
1652
|
+
where: columnWhere({ ...original, updatedAt: claimedAt }),
|
|
1653
|
+
update: { order, updatedAt: nextSnapshotDate(claimedAt) },
|
|
1654
|
+
});
|
|
1655
|
+
if (!didAffectRow(matched, columnId)) throw staleStateError();
|
|
1656
|
+
const column = await findColumn(tx, columnId);
|
|
1657
|
+
if (!column) throw columnNotFoundError();
|
|
1658
|
+
updated.push(serializeBareColumn(column));
|
|
1659
|
+
}
|
|
1660
|
+
await restoreSnapshot(
|
|
1661
|
+
tx,
|
|
1662
|
+
"kanbanBoard",
|
|
1663
|
+
snapshot.board,
|
|
1664
|
+
boardClaimedAt,
|
|
1665
|
+
);
|
|
1666
|
+
const result = { success: true } as const;
|
|
1667
|
+
reorderedColumns.set(result, updated);
|
|
1668
|
+
return result;
|
|
1669
|
+
});
|
|
1670
|
+
},
|
|
1671
|
+
after: async (context) => {
|
|
1672
|
+
for (const column of reorderedColumns.get(context.result) ?? []) {
|
|
1673
|
+
await hooks?.onAfterUpdateColumn?.(
|
|
1674
|
+
column,
|
|
1675
|
+
hookContext<
|
|
1676
|
+
z.output<typeof reorderColumnsSchema>,
|
|
1677
|
+
ColumnReorderFacts,
|
|
1678
|
+
{ readonly success: true }
|
|
1679
|
+
>(context, { body: context.input }),
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
},
|
|
1683
|
+
onError: ({ error }) => markMemoryRollback(adapter, error),
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1686
|
+
const createTask = defineOperation({
|
|
1687
|
+
input: createTaskSchema,
|
|
1688
|
+
permission: kanbanPermissions.task.create,
|
|
1689
|
+
facts: async ({ input }) => {
|
|
1690
|
+
const snapshot = await loadColumnAuthorization(adapter, input.columnId);
|
|
1691
|
+
columnSnapshots.set(input as object, snapshot);
|
|
1692
|
+
return {
|
|
1693
|
+
...boardFacts(snapshot.board),
|
|
1694
|
+
columnId: snapshot.column.id,
|
|
1695
|
+
} satisfies TaskCreateFacts;
|
|
1696
|
+
},
|
|
1697
|
+
execute: async (context) => {
|
|
1698
|
+
const snapshot = columnSnapshots.get(context.input as object);
|
|
1699
|
+
if (!snapshot) throw staleStateError();
|
|
1700
|
+
requireAtomicTransactions(adapter);
|
|
1701
|
+
return adapter.transaction(async (tx) => {
|
|
1702
|
+
await verifyColumnAuthorization(tx, snapshot);
|
|
1703
|
+
const claims = await claimColumnAuthorization(tx, snapshot);
|
|
1704
|
+
await runDomainHook(
|
|
1705
|
+
() =>
|
|
1706
|
+
hooks?.onBeforeCreateTask?.(
|
|
1707
|
+
{ ...context.input },
|
|
1708
|
+
hookContext(context, { body: context.input }),
|
|
1709
|
+
),
|
|
1710
|
+
"CREATE_TASK_REJECTED",
|
|
1711
|
+
);
|
|
1712
|
+
const existing = await listTasks(tx, snapshot.column.id);
|
|
1713
|
+
const nextOrder =
|
|
1714
|
+
existing.length === 0
|
|
1715
|
+
? 0
|
|
1716
|
+
: Math.max(...existing.map((task) => task.order)) + 1;
|
|
1717
|
+
const now = new Date();
|
|
1718
|
+
const { completedAt, ...persistenceInput } = context.input;
|
|
1719
|
+
const task = await tx.create<Task>({
|
|
1720
|
+
model: "kanbanTask",
|
|
1721
|
+
data: {
|
|
1722
|
+
...persistenceInput,
|
|
1723
|
+
priority: context.input.priority ?? "MEDIUM",
|
|
1724
|
+
order: context.input.order ?? nextOrder,
|
|
1725
|
+
assigneeId: context.input.assigneeId ?? undefined,
|
|
1726
|
+
isArchived: context.input.isArchived ?? false,
|
|
1727
|
+
...(completedAt ? { completedAt: new Date(completedAt) } : {}),
|
|
1728
|
+
createdAt: now,
|
|
1729
|
+
updatedAt: now,
|
|
1730
|
+
},
|
|
1731
|
+
});
|
|
1732
|
+
await restoreColumnAuthorization(tx, snapshot, claims);
|
|
1733
|
+
return operationTask(task);
|
|
1734
|
+
});
|
|
1735
|
+
},
|
|
1736
|
+
after: async (context) => {
|
|
1737
|
+
await hooks?.onAfterCreateTask?.(
|
|
1738
|
+
context.result,
|
|
1739
|
+
hookContext(context, { body: context.input }),
|
|
1740
|
+
);
|
|
1741
|
+
},
|
|
1742
|
+
onError: ({ error }) => markMemoryRollback(adapter, error),
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
const updateTask = defineOperation({
|
|
1746
|
+
input: UpdateTaskOperationInputSchema,
|
|
1747
|
+
permission: kanbanPermissions.task.update,
|
|
1748
|
+
facts: async ({ input }) => {
|
|
1749
|
+
const snapshot = await loadTaskAuthorization(adapter, input.id);
|
|
1750
|
+
taskSnapshots.set(input as object, snapshot);
|
|
1751
|
+
return taskFacts(snapshot) satisfies TaskUpdateFacts;
|
|
1752
|
+
},
|
|
1753
|
+
additionalPermissions: async ({ input }) => {
|
|
1754
|
+
const snapshot = taskSnapshots.get(input as object);
|
|
1755
|
+
if (!snapshot) throw staleStateError();
|
|
1756
|
+
const targetColumnId = input.data.columnId ?? snapshot.column.id;
|
|
1757
|
+
const changesColumn = targetColumnId !== snapshot.column.id;
|
|
1758
|
+
const changesOrder = input.data.order !== undefined;
|
|
1759
|
+
if (!changesColumn && !changesOrder) return [];
|
|
1760
|
+
const moveSnapshot = await loadMoveSnapshot(
|
|
1761
|
+
adapter,
|
|
1762
|
+
snapshot,
|
|
1763
|
+
targetColumnId,
|
|
1764
|
+
input.data.order ?? Number.MAX_SAFE_INTEGER,
|
|
1765
|
+
);
|
|
1766
|
+
updateTaskMoveSnapshots.set(input as object, moveSnapshot);
|
|
1767
|
+
return [
|
|
1768
|
+
kanbanPermissions.task.move({
|
|
1769
|
+
...taskFacts(moveSnapshot),
|
|
1770
|
+
targetColumnId: moveSnapshot.targetColumn.id,
|
|
1771
|
+
}),
|
|
1772
|
+
];
|
|
1773
|
+
},
|
|
1774
|
+
execute: async (context) => {
|
|
1775
|
+
const snapshot = taskSnapshots.get(context.input as object);
|
|
1776
|
+
if (!snapshot) throw staleStateError();
|
|
1777
|
+
requireAtomicTransactions(adapter);
|
|
1778
|
+
const {
|
|
1779
|
+
columnId: _columnId,
|
|
1780
|
+
order: _order,
|
|
1781
|
+
completedAt,
|
|
1782
|
+
...operationTaskData
|
|
1783
|
+
} = context.input.data;
|
|
1784
|
+
const taskData = {
|
|
1785
|
+
...operationTaskData,
|
|
1786
|
+
...(completedAt ? { completedAt: new Date(completedAt) } : {}),
|
|
1787
|
+
};
|
|
1788
|
+
const moveSnapshot = updateTaskMoveSnapshots.get(context.input as object);
|
|
1789
|
+
if (moveSnapshot) {
|
|
1790
|
+
return adapter.transaction(async (tx) => {
|
|
1791
|
+
await verifyMoveSnapshot(tx, moveSnapshot);
|
|
1792
|
+
const claims = await claimMoveSnapshot(tx, moveSnapshot);
|
|
1793
|
+
await runDomainHook(
|
|
1794
|
+
() =>
|
|
1795
|
+
hooks?.onBeforeUpdateTask?.(
|
|
1796
|
+
context.input.id,
|
|
1797
|
+
{ ...context.input.data, id: context.input.id },
|
|
1798
|
+
hookContext(context, {
|
|
1799
|
+
body: context.input.data,
|
|
1800
|
+
params: { id: context.input.id },
|
|
1801
|
+
}),
|
|
1802
|
+
),
|
|
1803
|
+
"UPDATE_TASK_REJECTED",
|
|
1804
|
+
);
|
|
1805
|
+
return operationTask(
|
|
1806
|
+
await applyTaskMove(tx, moveSnapshot, claims, taskData),
|
|
1807
|
+
);
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
return adapter.transaction(async (tx) => {
|
|
1811
|
+
await verifyTaskAuthorization(tx, snapshot);
|
|
1812
|
+
const claims = await claimColumnAuthorization(tx, snapshot);
|
|
1813
|
+
const taskClaimedAt = await claimSnapshot(
|
|
1814
|
+
tx,
|
|
1815
|
+
"kanbanTask",
|
|
1816
|
+
snapshot.task,
|
|
1817
|
+
);
|
|
1818
|
+
await runDomainHook(
|
|
1819
|
+
() =>
|
|
1820
|
+
hooks?.onBeforeUpdateTask?.(
|
|
1821
|
+
context.input.id,
|
|
1822
|
+
{ ...context.input.data, id: context.input.id },
|
|
1823
|
+
hookContext(context, {
|
|
1824
|
+
body: context.input.data,
|
|
1825
|
+
params: { id: context.input.id },
|
|
1826
|
+
}),
|
|
1827
|
+
),
|
|
1828
|
+
"UPDATE_TASK_REJECTED",
|
|
1829
|
+
);
|
|
1830
|
+
const matched = await tx.updateMany({
|
|
1831
|
+
model: "kanbanTask",
|
|
1832
|
+
where: taskWhere({
|
|
1833
|
+
...snapshot.task,
|
|
1834
|
+
updatedAt: taskClaimedAt,
|
|
1835
|
+
}),
|
|
1836
|
+
update: {
|
|
1837
|
+
...taskData,
|
|
1838
|
+
updatedAt: nextSnapshotDate(taskClaimedAt),
|
|
1839
|
+
},
|
|
1840
|
+
});
|
|
1841
|
+
if (!didAffectRow(matched, snapshot.task.id)) {
|
|
1842
|
+
throw staleStateError();
|
|
1843
|
+
}
|
|
1844
|
+
await restoreColumnAuthorization(tx, snapshot, claims);
|
|
1845
|
+
const updated = await findTask(tx, snapshot.task.id);
|
|
1846
|
+
if (!updated) throw taskNotFoundError();
|
|
1847
|
+
return operationTask(updated);
|
|
1848
|
+
});
|
|
1849
|
+
},
|
|
1850
|
+
after: async (context) => {
|
|
1851
|
+
await hooks?.onAfterUpdateTask?.(
|
|
1852
|
+
context.result,
|
|
1853
|
+
hookContext(context, {
|
|
1854
|
+
body: context.input.data,
|
|
1855
|
+
params: { id: context.input.id },
|
|
1856
|
+
}),
|
|
1857
|
+
);
|
|
1858
|
+
},
|
|
1859
|
+
onError: ({ error }) => markMemoryRollback(adapter, error),
|
|
1860
|
+
});
|
|
1861
|
+
|
|
1862
|
+
const deleteTask = defineOperation({
|
|
1863
|
+
input: TaskIdOperationInputSchema,
|
|
1864
|
+
permission: kanbanPermissions.task.delete,
|
|
1865
|
+
facts: async ({ input }) => {
|
|
1866
|
+
const snapshot = await loadTaskAuthorization(adapter, input.id);
|
|
1867
|
+
taskSnapshots.set(input as object, snapshot);
|
|
1868
|
+
return taskFacts(snapshot) satisfies TaskDeleteFacts;
|
|
1869
|
+
},
|
|
1870
|
+
execute: async (context) => {
|
|
1871
|
+
const snapshot = taskSnapshots.get(context.input as object);
|
|
1872
|
+
if (!snapshot) throw staleStateError();
|
|
1873
|
+
requireAtomicTransactions(adapter);
|
|
1874
|
+
return adapter.transaction(async (tx) => {
|
|
1875
|
+
await verifyTaskAuthorization(tx, snapshot);
|
|
1876
|
+
const claims = await claimColumnAuthorization(tx, snapshot);
|
|
1877
|
+
const taskClaimedAt = await claimSnapshot(
|
|
1878
|
+
tx,
|
|
1879
|
+
"kanbanTask",
|
|
1880
|
+
snapshot.task,
|
|
1881
|
+
);
|
|
1882
|
+
await runDomainHook(
|
|
1883
|
+
() =>
|
|
1884
|
+
hooks?.onBeforeDeleteTask?.(
|
|
1885
|
+
context.input.id,
|
|
1886
|
+
hookContext(context, {
|
|
1887
|
+
params: { id: context.input.id },
|
|
1888
|
+
}),
|
|
1889
|
+
),
|
|
1890
|
+
"DELETE_TASK_REJECTED",
|
|
1891
|
+
);
|
|
1892
|
+
const deleted = await tx.deleteMany({
|
|
1893
|
+
model: "kanbanTask",
|
|
1894
|
+
where: taskWhere({
|
|
1895
|
+
...snapshot.task,
|
|
1896
|
+
updatedAt: taskClaimedAt,
|
|
1897
|
+
}),
|
|
1898
|
+
});
|
|
1899
|
+
if (!didAffectRow(deleted, snapshot.task.id)) {
|
|
1900
|
+
throw staleStateError();
|
|
1901
|
+
}
|
|
1902
|
+
await restoreColumnAuthorization(tx, snapshot, claims);
|
|
1903
|
+
return { success: true } as const;
|
|
1904
|
+
});
|
|
1905
|
+
},
|
|
1906
|
+
after: async (context) => {
|
|
1907
|
+
await hooks?.onAfterDeleteTask?.(
|
|
1908
|
+
context.input.id,
|
|
1909
|
+
hookContext(context, { params: { id: context.input.id } }),
|
|
1910
|
+
);
|
|
1911
|
+
},
|
|
1912
|
+
onError: ({ error }) => markMemoryRollback(adapter, error),
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1915
|
+
const moveTask = defineOperation({
|
|
1916
|
+
input: moveTaskSchema,
|
|
1917
|
+
permission: kanbanPermissions.task.move,
|
|
1918
|
+
facts: async ({ input }) => {
|
|
1919
|
+
const primary = await loadTaskAuthorization(adapter, input.taskId);
|
|
1920
|
+
const snapshot = await loadMoveSnapshot(
|
|
1921
|
+
adapter,
|
|
1922
|
+
primary,
|
|
1923
|
+
input.targetColumnId,
|
|
1924
|
+
input.targetOrder,
|
|
1925
|
+
);
|
|
1926
|
+
taskMoveSnapshots.set(input as object, snapshot);
|
|
1927
|
+
return {
|
|
1928
|
+
...taskFacts(snapshot),
|
|
1929
|
+
targetColumnId: snapshot.targetColumn.id,
|
|
1930
|
+
} satisfies TaskMoveFacts;
|
|
1931
|
+
},
|
|
1932
|
+
execute: async (context) => {
|
|
1933
|
+
const snapshot = taskMoveSnapshots.get(context.input as object);
|
|
1934
|
+
if (!snapshot) throw staleStateError();
|
|
1935
|
+
requireAtomicTransactions(adapter);
|
|
1936
|
+
return adapter.transaction(async (tx) => {
|
|
1937
|
+
await verifyMoveSnapshot(tx, snapshot);
|
|
1938
|
+
const claims = await claimMoveSnapshot(tx, snapshot);
|
|
1939
|
+
await runDomainHook(
|
|
1940
|
+
() =>
|
|
1941
|
+
hooks?.onBeforeUpdateTask?.(
|
|
1942
|
+
context.input.taskId,
|
|
1943
|
+
{
|
|
1944
|
+
id: context.input.taskId,
|
|
1945
|
+
columnId: context.input.targetColumnId,
|
|
1946
|
+
order: context.input.targetOrder,
|
|
1947
|
+
},
|
|
1948
|
+
hookContext(context, { body: context.input }),
|
|
1949
|
+
),
|
|
1950
|
+
"MOVE_TASK_REJECTED",
|
|
1951
|
+
);
|
|
1952
|
+
return operationTask(await applyTaskMove(tx, snapshot, claims, {}));
|
|
1953
|
+
});
|
|
1954
|
+
},
|
|
1955
|
+
after: async (context) => {
|
|
1956
|
+
await hooks?.onAfterUpdateTask?.(
|
|
1957
|
+
context.result,
|
|
1958
|
+
hookContext(context, { body: context.input }),
|
|
1959
|
+
);
|
|
1960
|
+
},
|
|
1961
|
+
onError: ({ error }) => markMemoryRollback(adapter, error),
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
const reorderTasks = defineOperation({
|
|
1965
|
+
input: reorderTasksSchema,
|
|
1966
|
+
permission: kanbanPermissions.task.reorder,
|
|
1967
|
+
facts: async ({ input }) => {
|
|
1968
|
+
const parent = await loadColumnAuthorization(adapter, input.columnId);
|
|
1969
|
+
const tasks = await listTasks(adapter, input.columnId);
|
|
1970
|
+
if (
|
|
1971
|
+
!sameIdSet(
|
|
1972
|
+
input.taskIds,
|
|
1973
|
+
tasks.map((task) => task.id),
|
|
1974
|
+
)
|
|
1975
|
+
) {
|
|
1976
|
+
throw invalidTaskSetError();
|
|
1977
|
+
}
|
|
1978
|
+
const snapshot = { ...parent, tasks: tasks.map(taskSnapshot) };
|
|
1979
|
+
taskReorderSnapshots.set(input as object, snapshot);
|
|
1980
|
+
return {
|
|
1981
|
+
...boardFacts(parent.board),
|
|
1982
|
+
columnId: parent.column.id,
|
|
1983
|
+
} satisfies TaskReorderFacts;
|
|
1984
|
+
},
|
|
1985
|
+
execute: async (context) => {
|
|
1986
|
+
const snapshot = taskReorderSnapshots.get(context.input as object);
|
|
1987
|
+
if (!snapshot) throw staleStateError();
|
|
1988
|
+
requireAtomicTransactions(adapter);
|
|
1989
|
+
return adapter.transaction(async (tx) => {
|
|
1990
|
+
await verifyColumnAuthorization(tx, snapshot);
|
|
1991
|
+
const current = (await listTasks(tx, snapshot.column.id)).map(
|
|
1992
|
+
taskSnapshot,
|
|
1993
|
+
);
|
|
1994
|
+
if (!sameSnapshotSet(current, snapshot.tasks, sameTaskSnapshots)) {
|
|
1995
|
+
throw staleStateError();
|
|
1996
|
+
}
|
|
1997
|
+
const parentClaims = await claimColumnAuthorization(tx, snapshot);
|
|
1998
|
+
const taskClaims = new Map<string, Date>();
|
|
1999
|
+
for (const task of snapshot.tasks) {
|
|
2000
|
+
taskClaims.set(task.id, await claimSnapshot(tx, "kanbanTask", task));
|
|
2001
|
+
}
|
|
2002
|
+
for (const [order, taskId] of context.input.taskIds.entries()) {
|
|
2003
|
+
await runDomainHook(
|
|
2004
|
+
() =>
|
|
2005
|
+
hooks?.onBeforeUpdateTask?.(
|
|
2006
|
+
taskId,
|
|
2007
|
+
{ id: taskId, order },
|
|
2008
|
+
hookContext(context, { body: context.input }),
|
|
2009
|
+
),
|
|
2010
|
+
"REORDER_TASKS_REJECTED",
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
const updated: SerializedTask[] = [];
|
|
2014
|
+
for (const [order, taskId] of context.input.taskIds.entries()) {
|
|
2015
|
+
const original = snapshot.tasks.find((task) => task.id === taskId);
|
|
2016
|
+
const claimedAt = taskClaims.get(taskId);
|
|
2017
|
+
if (!original || !claimedAt) throw staleStateError();
|
|
2018
|
+
const matched = await tx.updateMany({
|
|
2019
|
+
model: "kanbanTask",
|
|
2020
|
+
where: taskWhere({ ...original, updatedAt: claimedAt }),
|
|
2021
|
+
update: { order, updatedAt: nextSnapshotDate(claimedAt) },
|
|
2022
|
+
});
|
|
2023
|
+
if (!didAffectRow(matched, taskId)) throw staleStateError();
|
|
2024
|
+
const task = await findTask(tx, taskId);
|
|
2025
|
+
if (!task) throw taskNotFoundError();
|
|
2026
|
+
updated.push(operationTask(task));
|
|
2027
|
+
}
|
|
2028
|
+
await restoreColumnAuthorization(tx, snapshot, parentClaims);
|
|
2029
|
+
const result = { success: true } as const;
|
|
2030
|
+
reorderedTasks.set(result, updated);
|
|
2031
|
+
return result;
|
|
2032
|
+
});
|
|
2033
|
+
},
|
|
2034
|
+
after: async (context) => {
|
|
2035
|
+
for (const task of reorderedTasks.get(context.result) ?? []) {
|
|
2036
|
+
await hooks?.onAfterUpdateTask?.(
|
|
2037
|
+
task,
|
|
2038
|
+
hookContext<
|
|
2039
|
+
z.output<typeof reorderTasksSchema>,
|
|
2040
|
+
TaskReorderFacts,
|
|
2041
|
+
{ readonly success: true }
|
|
2042
|
+
>(context, { body: context.input }),
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
},
|
|
2046
|
+
onError: ({ error }) => markMemoryRollback(adapter, error),
|
|
2047
|
+
});
|
|
2048
|
+
|
|
2049
|
+
return {
|
|
2050
|
+
listBoards,
|
|
2051
|
+
getBoard,
|
|
2052
|
+
createBoard,
|
|
2053
|
+
updateBoard,
|
|
2054
|
+
deleteBoard,
|
|
2055
|
+
createColumn,
|
|
2056
|
+
updateColumn,
|
|
2057
|
+
deleteColumn,
|
|
2058
|
+
reorderColumns,
|
|
2059
|
+
createTask,
|
|
2060
|
+
updateTask,
|
|
2061
|
+
deleteTask,
|
|
2062
|
+
moveTask,
|
|
2063
|
+
reorderTasks,
|
|
2064
|
+
};
|
|
2065
|
+
}
|