@supernova-studio/client 1.96.6 → 1.96.7
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/dist/index.d.mts +101177 -143251
- package/dist/index.d.ts +101177 -143251
- package/dist/index.js +408 -105
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5542 -5239
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -9
- package/src/api/client.ts +62 -0
- package/src/api/conversion/analytics/index.ts +3 -0
- package/src/api/conversion/analytics/page-visits-to-comparison-dto.ts +65 -0
- package/src/api/conversion/analytics/page-visits-to-dto.ts +68 -0
- package/src/api/conversion/analytics/page-visits-to-heatmap-dto.ts +49 -0
- package/src/api/conversion/documentation/documentation-group-v1-to-dto.ts +114 -0
- package/src/api/conversion/documentation/documentation-group-v2-to-dto.ts +91 -0
- package/src/api/conversion/documentation/documentation-item-configuration-v1-to-dto.ts +52 -0
- package/src/api/conversion/documentation/documentation-item-configuration-v2-to-dto.ts +34 -0
- package/src/api/conversion/documentation/documentation-page-to-dto-utils.ts +134 -0
- package/src/api/conversion/documentation/documentation-page-v1-to-dto.ts +69 -0
- package/src/api/conversion/documentation/documentation-page-v2-to-dto.ts +84 -0
- package/src/api/conversion/documentation/documentation-settings-to-dto.ts +14 -0
- package/src/api/conversion/documentation/index.ts +8 -0
- package/src/api/conversion/export/index.ts +1 -0
- package/src/api/conversion/export/pipeline.ts +27 -0
- package/src/api/conversion/index.ts +4 -0
- package/src/api/conversion/integrations/git.ts +37 -0
- package/src/api/conversion/integrations/index.ts +2 -0
- package/src/api/conversion/integrations/integration.ts +37 -0
- package/src/api/dto/access-tokens/access-token.ts +45 -0
- package/src/api/dto/access-tokens/index.ts +1 -0
- package/src/api/dto/aux/color.ts +7 -0
- package/src/api/dto/aux/index.ts +4 -0
- package/src/api/dto/aux/meta.ts +8 -0
- package/src/api/dto/aux/object-metadata.ts +29 -0
- package/src/api/dto/aux/pagination.ts +8 -0
- package/src/api/dto/bff/app-bootstrap-data.ts +20 -0
- package/src/api/dto/bff/index.ts +1 -0
- package/src/api/dto/collections/index.ts +1 -0
- package/src/api/dto/collections/token-collection.ts +32 -0
- package/src/api/dto/design-systems/brand.ts +44 -0
- package/src/api/dto/design-systems/code-component-usage.ts +52 -0
- package/src/api/dto/design-systems/code-component.ts +101 -0
- package/src/api/dto/design-systems/code-history.ts +43 -0
- package/src/api/dto/design-systems/code-scan.ts +29 -0
- package/src/api/dto/design-systems/code-snapshots.ts +46 -0
- package/src/api/dto/design-systems/component.ts +40 -0
- package/src/api/dto/design-systems/contact.ts +11 -0
- package/src/api/dto/design-systems/data-source.ts +216 -0
- package/src/api/dto/design-systems/design-system.ts +75 -0
- package/src/api/dto/design-systems/documentation-search.ts +31 -0
- package/src/api/dto/design-systems/elements-diff.ts +14 -0
- package/src/api/dto/design-systems/figma-variables.ts +122 -0
- package/src/api/dto/design-systems/file.ts +71 -0
- package/src/api/dto/design-systems/import-job.ts +62 -0
- package/src/api/dto/design-systems/index.ts +24 -0
- package/src/api/dto/design-systems/members.ts +52 -0
- package/src/api/dto/design-systems/redirects.ts +33 -0
- package/src/api/dto/design-systems/role.ts +11 -0
- package/src/api/dto/design-systems/stats.ts +20 -0
- package/src/api/dto/design-systems/storybook.ts +45 -0
- package/src/api/dto/design-systems/user-design-systems.ts +10 -0
- package/src/api/dto/design-systems/version-room.ts +12 -0
- package/src/api/dto/design-systems/version.ts +56 -0
- package/src/api/dto/design-systems/view.ts +45 -0
- package/src/api/dto/design-tokens/design-token.ts +78 -0
- package/src/api/dto/design-tokens/index.ts +1 -0
- package/src/api/dto/documentation/analytics-v2.ts +80 -0
- package/src/api/dto/documentation/analytics.ts +32 -0
- package/src/api/dto/documentation/anchor.ts +15 -0
- package/src/api/dto/documentation/approvals.ts +21 -0
- package/src/api/dto/documentation/block-definition.ts +28 -0
- package/src/api/dto/documentation/block.ts +6 -0
- package/src/api/dto/documentation/documentation-page-snapshot.ts +18 -0
- package/src/api/dto/documentation/documentation.ts +34 -0
- package/src/api/dto/documentation/index.ts +11 -0
- package/src/api/dto/documentation/link-preview.ts +23 -0
- package/src/api/dto/documentation/publish.ts +23 -0
- package/src/api/dto/documentation/room.ts +12 -0
- package/src/api/dto/elements/components/figma-component-group.ts +19 -0
- package/src/api/dto/elements/components/figma-component.ts +56 -0
- package/src/api/dto/elements/components/index.ts +2 -0
- package/src/api/dto/elements/documentation/configuration.ts +20 -0
- package/src/api/dto/elements/documentation/draft-state.ts +37 -0
- package/src/api/dto/elements/documentation/group-action.ts +108 -0
- package/src/api/dto/elements/documentation/group-v1.ts +35 -0
- package/src/api/dto/elements/documentation/group-v2.ts +117 -0
- package/src/api/dto/elements/documentation/hierarchy.ts +25 -0
- package/src/api/dto/elements/documentation/index.ts +16 -0
- package/src/api/dto/elements/documentation/item-configuration-v1.ts +24 -0
- package/src/api/dto/elements/documentation/item-configuration-v2.ts +14 -0
- package/src/api/dto/elements/documentation/metadata.ts +8 -0
- package/src/api/dto/elements/documentation/page-actions-v2.ts +138 -0
- package/src/api/dto/elements/documentation/page-content.ts +15 -0
- package/src/api/dto/elements/documentation/page-dependencies.ts +30 -0
- package/src/api/dto/elements/documentation/page-v1.ts +21 -0
- package/src/api/dto/elements/documentation/page-v2.ts +132 -0
- package/src/api/dto/elements/documentation/settings.ts +9 -0
- package/src/api/dto/elements/documentation/structure.ts +37 -0
- package/src/api/dto/elements/elements-action-v2.ts +125 -0
- package/src/api/dto/elements/figma-nodes/figma-node-structure.ts +27 -0
- package/src/api/dto/elements/figma-nodes/figma-node-v1.ts +32 -0
- package/src/api/dto/elements/figma-nodes/figma-node-v2.ts +32 -0
- package/src/api/dto/elements/figma-nodes/figma-node.ts +103 -0
- package/src/api/dto/elements/figma-nodes/index.ts +5 -0
- package/src/api/dto/elements/figma-nodes/node-actions-v2.ts +25 -0
- package/src/api/dto/elements/frame-node-structures/frame-node-structure.ts +17 -0
- package/src/api/dto/elements/frame-node-structures/index.ts +1 -0
- package/src/api/dto/elements/get-elements-v2.ts +27 -0
- package/src/api/dto/elements/index.ts +7 -0
- package/src/api/dto/elements/properties/index.ts +2 -0
- package/src/api/dto/elements/properties/property-definitions.ts +75 -0
- package/src/api/dto/elements/properties/property-values.ts +50 -0
- package/src/api/dto/events/forge-project.ts +51 -0
- package/src/api/dto/events/index.ts +2 -0
- package/src/api/dto/events/workspace.ts +81 -0
- package/src/api/dto/export/exporter-property.ts +114 -0
- package/src/api/dto/export/exporter.ts +115 -0
- package/src/api/dto/export/filter.ts +20 -0
- package/src/api/dto/export/index.ts +5 -0
- package/src/api/dto/export/job.ts +114 -0
- package/src/api/dto/export/pipeline.ts +60 -0
- package/src/api/dto/figma-components/assets/download.ts +30 -0
- package/src/api/dto/figma-components/assets/index.ts +1 -0
- package/src/api/dto/figma-components/index.ts +1 -0
- package/src/api/dto/figma-exporter/figma-node.ts +11 -0
- package/src/api/dto/figma-exporter/index.ts +1 -0
- package/src/api/dto/files/files.ts +94 -0
- package/src/api/dto/files/index.ts +1 -0
- package/src/api/dto/forge/agent.ts +38 -0
- package/src/api/dto/forge/artifact.ts +94 -0
- package/src/api/dto/forge/feature-messages.ts +551 -0
- package/src/api/dto/forge/feature-room.ts +12 -0
- package/src/api/dto/forge/figma-node.ts +117 -0
- package/src/api/dto/forge/index.ts +28 -0
- package/src/api/dto/forge/iteration-message-old.ts +43 -0
- package/src/api/dto/forge/knowledge-item.ts +67 -0
- package/src/api/dto/forge/memory.ts +67 -0
- package/src/api/dto/forge/participant.ts +44 -0
- package/src/api/dto/forge/project-action.ts +123 -0
- package/src/api/dto/forge/project-artifact-room.ts +12 -0
- package/src/api/dto/forge/project-artifact.ts +85 -0
- package/src/api/dto/forge/project-context-feedback.ts +141 -0
- package/src/api/dto/forge/project-context-override.ts +41 -0
- package/src/api/dto/forge/project-context-scope.ts +23 -0
- package/src/api/dto/forge/project-context-v2.ts +228 -0
- package/src/api/dto/forge/project-context.ts +72 -0
- package/src/api/dto/forge/project-feature.ts +86 -0
- package/src/api/dto/forge/project-figma-node.ts +9 -0
- package/src/api/dto/forge/project-file.ts +48 -0
- package/src/api/dto/forge/project-invitation.ts +46 -0
- package/src/api/dto/forge/project-iteration-old.ts +54 -0
- package/src/api/dto/forge/project-member.ts +67 -0
- package/src/api/dto/forge/project-room.ts +12 -0
- package/src/api/dto/forge/project-section.ts +37 -0
- package/src/api/dto/forge/project.ts +105 -0
- package/src/api/dto/forge/relation.ts +34 -0
- package/src/api/dto/forge/threads.ts +153 -0
- package/src/api/dto/index.ts +25 -0
- package/src/api/dto/liveblocks/auth-response.ts +7 -0
- package/src/api/dto/liveblocks/index.ts +1 -0
- package/src/api/dto/mcp/index.ts +1 -0
- package/src/api/dto/mcp/stream.ts +17 -0
- package/src/api/dto/notifications/index.ts +2 -0
- package/src/api/dto/notifications/notifications.ts +65 -0
- package/src/api/dto/notifications/settings.ts +10 -0
- package/src/api/dto/portal/index.ts +1 -0
- package/src/api/dto/portal/portal-settings.ts +45 -0
- package/src/api/dto/sandboxes/index.ts +1 -0
- package/src/api/dto/sandboxes/template.ts +143 -0
- package/src/api/dto/storybook-sites/index.ts +1 -0
- package/src/api/dto/storybook-sites/storybook-sites.ts +60 -0
- package/src/api/dto/themes/index.ts +2 -0
- package/src/api/dto/themes/override.ts +27 -0
- package/src/api/dto/themes/theme.ts +53 -0
- package/src/api/dto/threads/index.ts +1 -0
- package/src/api/dto/threads/threads.ts +280 -0
- package/src/api/dto/trail-events/index.ts +1 -0
- package/src/api/dto/trail-events/trail-events.ts +172 -0
- package/src/api/dto/users/authenticated-user.ts +92 -0
- package/src/api/dto/users/index.ts +2 -0
- package/src/api/dto/users/user.ts +27 -0
- package/src/api/dto/workspaces/billing.ts +156 -0
- package/src/api/dto/workspaces/enums.ts +13 -0
- package/src/api/dto/workspaces/git.ts +32 -0
- package/src/api/dto/workspaces/index.ts +15 -0
- package/src/api/dto/workspaces/integrations.ts +34 -0
- package/src/api/dto/workspaces/invitations.ts +47 -0
- package/src/api/dto/workspaces/ip-whitelist.ts +33 -0
- package/src/api/dto/workspaces/knowledge-items-search.ts +37 -0
- package/src/api/dto/workspaces/login-screen.ts +42 -0
- package/src/api/dto/workspaces/membership.ts +55 -0
- package/src/api/dto/workspaces/npm-registry.ts +62 -0
- package/src/api/dto/workspaces/products.ts +45 -0
- package/src/api/dto/workspaces/subscription.ts +170 -0
- package/src/api/dto/workspaces/transfer-ownership.ts +7 -0
- package/src/api/dto/workspaces/untyped-data.ts +35 -0
- package/src/api/dto/workspaces/workspace.ts +49 -0
- package/src/api/endpoints/auth-tokens.ts +28 -0
- package/src/api/endpoints/codegen/codegen.ts +16 -0
- package/src/api/endpoints/codegen/ds-pipelines.ts +37 -0
- package/src/api/endpoints/codegen/exporters.ts +54 -0
- package/src/api/endpoints/codegen/index.ts +5 -0
- package/src/api/endpoints/codegen/jobs.ts +15 -0
- package/src/api/endpoints/codegen/pipelines.ts +55 -0
- package/src/api/endpoints/design-system/analytics.ts +14 -0
- package/src/api/endpoints/design-system/bff.ts +13 -0
- package/src/api/endpoints/design-system/code-snapshots.ts +47 -0
- package/src/api/endpoints/design-system/contact.ts +12 -0
- package/src/api/endpoints/design-system/design-systems.ts +90 -0
- package/src/api/endpoints/design-system/figma-node-structures.ts +20 -0
- package/src/api/endpoints/design-system/index.ts +11 -0
- package/src/api/endpoints/design-system/members.ts +24 -0
- package/src/api/endpoints/design-system/redirects.ts +46 -0
- package/src/api/endpoints/design-system/sources.ts +80 -0
- package/src/api/endpoints/design-system/storybook.ts +28 -0
- package/src/api/endpoints/design-system/versions/brands.ts +45 -0
- package/src/api/endpoints/design-system/versions/documentation.ts +107 -0
- package/src/api/endpoints/design-system/versions/ds-components.ts +30 -0
- package/src/api/endpoints/design-system/versions/elements-action.ts +65 -0
- package/src/api/endpoints/design-system/versions/elements.ts +27 -0
- package/src/api/endpoints/design-system/versions/figma-component-groups.ts +13 -0
- package/src/api/endpoints/design-system/versions/figma-components.ts +15 -0
- package/src/api/endpoints/design-system/versions/figma-frame-structures.ts +13 -0
- package/src/api/endpoints/design-system/versions/files.ts +43 -0
- package/src/api/endpoints/design-system/versions/import-jobs.ts +13 -0
- package/src/api/endpoints/design-system/versions/index.ts +19 -0
- package/src/api/endpoints/design-system/versions/overrides.ts +17 -0
- package/src/api/endpoints/design-system/versions/property-definitions.ts +43 -0
- package/src/api/endpoints/design-system/versions/property-values.ts +25 -0
- package/src/api/endpoints/design-system/versions/stats.ts +12 -0
- package/src/api/endpoints/design-system/versions/themes.ts +35 -0
- package/src/api/endpoints/design-system/versions/token-collections.ts +13 -0
- package/src/api/endpoints/design-system/versions/token-groups.ts +30 -0
- package/src/api/endpoints/design-system/versions/tokens.ts +29 -0
- package/src/api/endpoints/design-system/versions/versions.ts +90 -0
- package/src/api/endpoints/feedback.ts +60 -0
- package/src/api/endpoints/files.ts +25 -0
- package/src/api/endpoints/forge/agents.ts +54 -0
- package/src/api/endpoints/forge/artifacts.ts +54 -0
- package/src/api/endpoints/forge/documents.ts +18 -0
- package/src/api/endpoints/forge/feature-artifacts.ts +39 -0
- package/src/api/endpoints/forge/feature-iterations.ts +49 -0
- package/src/api/endpoints/forge/feature-messages.ts +68 -0
- package/src/api/endpoints/forge/features.ts +63 -0
- package/src/api/endpoints/forge/forge.ts +38 -0
- package/src/api/endpoints/forge/index.ts +16 -0
- package/src/api/endpoints/forge/iteration-tags.ts +13 -0
- package/src/api/endpoints/forge/memory.ts +41 -0
- package/src/api/endpoints/forge/project-contexts.ts +49 -0
- package/src/api/endpoints/forge/project-files.ts +41 -0
- package/src/api/endpoints/forge/project-invitations.ts +39 -0
- package/src/api/endpoints/forge/project-iterations.ts +65 -0
- package/src/api/endpoints/forge/project-members.ts +55 -0
- package/src/api/endpoints/forge/projects.ts +56 -0
- package/src/api/endpoints/index.ts +13 -0
- package/src/api/endpoints/liveblocks.ts +14 -0
- package/src/api/endpoints/mcp/index.ts +1 -0
- package/src/api/endpoints/mcp/streams.ts +21 -0
- package/src/api/endpoints/sandboxes/builds.ts +30 -0
- package/src/api/endpoints/sandboxes/index.ts +3 -0
- package/src/api/endpoints/sandboxes/sandboxes.ts +13 -0
- package/src/api/endpoints/sandboxes/templates.ts +16 -0
- package/src/api/endpoints/storybook-sites.ts +42 -0
- package/src/api/endpoints/threads.ts +38 -0
- package/src/api/endpoints/users.ts +26 -0
- package/src/api/endpoints/workspaces/billing.ts +24 -0
- package/src/api/endpoints/workspaces/chat-threads.ts +95 -0
- package/src/api/endpoints/workspaces/index.ts +9 -0
- package/src/api/endpoints/workspaces/integrations.ts +15 -0
- package/src/api/endpoints/workspaces/invites.ts +34 -0
- package/src/api/endpoints/workspaces/knowledge-items.ts +59 -0
- package/src/api/endpoints/workspaces/members.ts +31 -0
- package/src/api/endpoints/workspaces/npm-register.ts +20 -0
- package/src/api/endpoints/workspaces/subscription.ts +31 -0
- package/src/api/endpoints/workspaces/workspaces.ts +84 -0
- package/src/api/index.ts +6 -0
- package/src/api/payloads/design-systems/code-snapshots.ts +27 -0
- package/src/api/payloads/design-systems/index.ts +3 -0
- package/src/api/payloads/design-systems/update-design-system.ts +42 -0
- package/src/api/payloads/design-systems/version.ts +29 -0
- package/src/api/payloads/documentation/analytics.ts +36 -0
- package/src/api/payloads/documentation/block-definitions.ts +18 -0
- package/src/api/payloads/documentation/design-data-doc-diff.ts +7 -0
- package/src/api/payloads/documentation/index.ts +3 -0
- package/src/api/payloads/export/index.ts +1 -0
- package/src/api/payloads/export/pipeline.ts +111 -0
- package/src/api/payloads/index.ts +6 -0
- package/src/api/payloads/liveblocks/auth.ts +7 -0
- package/src/api/payloads/liveblocks/index.ts +1 -0
- package/src/api/payloads/users/index.ts +1 -0
- package/src/api/payloads/users/notifications/index.ts +1 -0
- package/src/api/payloads/users/notifications/notification-settings.ts +17 -0
- package/src/api/payloads/workspaces/index.ts +2 -0
- package/src/api/payloads/workspaces/workspace-configuration.ts +62 -0
- package/src/api/payloads/workspaces/workspace-integrations.ts +35 -0
- package/src/api/transport/index.ts +2 -0
- package/src/api/transport/request-executor-error.ts +60 -0
- package/src/api/transport/request-executor.ts +94 -0
- package/src/events/design-system.ts +21 -0
- package/src/events/event.ts +6 -0
- package/src/events/index.ts +2 -0
- package/src/index.ts +5 -0
- package/src/sync/docs-local-action-executor.ts +412 -0
- package/src/sync/docs-structure-repo.ts +283 -0
- package/src/sync/index.ts +5 -0
- package/src/sync/project-content-repo.ts +215 -0
- package/src/sync/project-local-action-executor.ts +415 -0
- package/src/sync/types.ts +37 -0
- package/src/utils/figma.ts +72 -0
- package/src/utils/hash.ts +62 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/query.ts +41 -0
- package/src/utils/redirect-validation.ts +76 -0
- package/src/yjs/design-system-content/documentation-hierarchy.ts +35 -0
- package/src/yjs/design-system-content/index.ts +2 -0
- package/src/yjs/design-system-content/item-configuration.ts +62 -0
- package/src/yjs/docs-editor/blocks-to-prosemirror.ts +764 -0
- package/src/yjs/docs-editor/index.ts +7 -0
- package/src/yjs/docs-editor/list-tree-builder.ts +127 -0
- package/src/yjs/docs-editor/mock.ts +2271 -0
- package/src/yjs/docs-editor/model/block.ts +8 -0
- package/src/yjs/docs-editor/model/index.ts +2 -0
- package/src/yjs/docs-editor/model/page.ts +8 -0
- package/src/yjs/docs-editor/prosemirror/index.ts +3 -0
- package/src/yjs/docs-editor/prosemirror/inner-editor-schema.ts +145 -0
- package/src/yjs/docs-editor/prosemirror/main-editor-schema.ts +577 -0
- package/src/yjs/docs-editor/prosemirror/types.ts +19 -0
- package/src/yjs/docs-editor/prosemirror-to-blocks.ts +986 -0
- package/src/yjs/docs-editor/utils.ts +115 -0
- package/src/yjs/feature-room/backend.ts +36 -0
- package/src/yjs/feature-room/base.ts +109 -0
- package/src/yjs/feature-room/frontend.ts +53 -0
- package/src/yjs/feature-room/index.ts +3 -0
- package/src/yjs/forge-project-room/backend.ts +59 -0
- package/src/yjs/forge-project-room/base.ts +190 -0
- package/src/yjs/forge-project-room/index.ts +2 -0
- package/src/yjs/index.ts +7 -0
- package/src/yjs/thread-room/backend.ts +32 -0
- package/src/yjs/thread-room/base.ts +71 -0
- package/src/yjs/thread-room/frontend.ts +39 -0
- package/src/yjs/thread-room/index.ts +3 -0
- package/src/yjs/utils/index.ts +1 -0
- package/src/yjs/utils/key-value-storage.ts +57 -0
- package/src/yjs/version-room/backend.ts +101 -0
- package/src/yjs/version-room/base.ts +323 -0
- package/src/yjs/version-room/compute-dto.ts +387 -0
- package/src/yjs/version-room/frontend.ts +76 -0
- package/src/yjs/version-room/index.ts +6 -0
- package/src/yjs/version-room/types.ts +31 -0
- package/src/yjs/version-room/utils.ts +109 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { OmitStrict } from "@supernova-studio/model";
|
|
2
|
+
import PQueue from "p-queue";
|
|
3
|
+
import * as Y from "yjs";
|
|
4
|
+
import { DTOForgeProjectAction, DTOForgeProjectActionOfType } from "../api";
|
|
5
|
+
import { ForgeProjectRoomBaseYDoc, ForgeProjectRoomBaseYDocState } from "../yjs";
|
|
6
|
+
import { applyProjectActionsLocally } from "./project-local-action-executor";
|
|
7
|
+
import { ProjectContentState } from "./types";
|
|
8
|
+
|
|
9
|
+
type YObserver = () => void;
|
|
10
|
+
type ProjectContentObserver = (projectContent: ProjectContentState) => void;
|
|
11
|
+
type ErrorObserver<T> = (error: unknown, actionMetadata: T | undefined) => void;
|
|
12
|
+
|
|
13
|
+
type TransactionExecutor = (trx: DTOForgeProjectAction) => Promise<void>;
|
|
14
|
+
type InternalTransactionExecutor<T> = (trx: InternalAction<T>) => Promise<void>;
|
|
15
|
+
type TransactionIdGenerator = () => string;
|
|
16
|
+
|
|
17
|
+
type Action = DTOForgeProjectActionOfType<DTOForgeProjectAction["type"]>;
|
|
18
|
+
type InputAction<T> = OmitStrict<Action, "tId">;
|
|
19
|
+
type InternalAction<T> = { action: Action; metadata?: T };
|
|
20
|
+
|
|
21
|
+
export class ForgeProjectContentRepository<T> {
|
|
22
|
+
private userId: string;
|
|
23
|
+
private projectId: string;
|
|
24
|
+
|
|
25
|
+
private readonly yDoc: Y.Doc;
|
|
26
|
+
private readonly yObserver: YObserver;
|
|
27
|
+
private _yState: ForgeProjectRoomBaseYDocState | undefined;
|
|
28
|
+
|
|
29
|
+
private _currentProjectContent: ProjectContentState | undefined;
|
|
30
|
+
|
|
31
|
+
private localActions: DTOForgeProjectAction[] = [];
|
|
32
|
+
private readonly actionQueue: TransactionQueue<T>;
|
|
33
|
+
private readonly projectContentObservers = new Set<ProjectContentObserver>();
|
|
34
|
+
private readonly errorObservers = new Set<ErrorObserver<T>>();
|
|
35
|
+
private readonly initCallbacks = new Set<() => void>();
|
|
36
|
+
|
|
37
|
+
private readonly transactionIdGenerator: TransactionIdGenerator;
|
|
38
|
+
private readonly transactionExecutor: TransactionExecutor;
|
|
39
|
+
|
|
40
|
+
constructor(config: {
|
|
41
|
+
userId: string;
|
|
42
|
+
projectId: string;
|
|
43
|
+
yDoc: Y.Doc;
|
|
44
|
+
transactionExecutor: TransactionExecutor;
|
|
45
|
+
transactionIdGenerator: TransactionIdGenerator;
|
|
46
|
+
}) {
|
|
47
|
+
this.userId = config.userId;
|
|
48
|
+
this.projectId = config.projectId;
|
|
49
|
+
this.yDoc = config.yDoc;
|
|
50
|
+
|
|
51
|
+
this.yObserver = this.yDoc.on("update", () => this.onYUpdate()) as YObserver;
|
|
52
|
+
this.onYUpdate();
|
|
53
|
+
|
|
54
|
+
this.transactionExecutor = config.transactionExecutor;
|
|
55
|
+
this.transactionIdGenerator = config.transactionIdGenerator;
|
|
56
|
+
|
|
57
|
+
this.actionQueue = new TransactionQueue(action => this.executeInternalAction(action));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//
|
|
61
|
+
// Lifecycle
|
|
62
|
+
//
|
|
63
|
+
|
|
64
|
+
get isInitialized(): boolean {
|
|
65
|
+
return !!this._currentProjectContent;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onInitialized(): Promise<void> {
|
|
69
|
+
if (this.isInitialized) return Promise.resolve();
|
|
70
|
+
return new Promise(resolve => {
|
|
71
|
+
this.initCallbacks.add(resolve);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
addProjectContentObserver(observer: ProjectContentObserver) {
|
|
76
|
+
this.projectContentObservers.add(observer);
|
|
77
|
+
if (this._currentProjectContent) observer(this._currentProjectContent);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
removeProjectContentObserver(observer: ProjectContentObserver) {
|
|
81
|
+
this.projectContentObservers.delete(observer);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
addErrorObserver(observer: ErrorObserver<T>) {
|
|
85
|
+
this.errorObservers.add(observer);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
removeErrorObserver(observer: ErrorObserver<T>) {
|
|
89
|
+
this.errorObservers.delete(observer);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
dispose() {
|
|
93
|
+
this.yDoc.off("update", this.yObserver);
|
|
94
|
+
this.projectContentObservers.clear();
|
|
95
|
+
this.errorObservers.clear();
|
|
96
|
+
this.actionQueue.clear();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//
|
|
100
|
+
// Accessors
|
|
101
|
+
//
|
|
102
|
+
|
|
103
|
+
get currentProjectContent(): ProjectContentState {
|
|
104
|
+
const projectContent = this._currentProjectContent;
|
|
105
|
+
if (!projectContent) throw new Error(`Project content cannot be accessed while it's still loading`);
|
|
106
|
+
return projectContent;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//
|
|
110
|
+
// Actions
|
|
111
|
+
//
|
|
112
|
+
|
|
113
|
+
executeAction(action: InputAction<T>, metadata?: T) {
|
|
114
|
+
void this.executeActionPromise(action, metadata);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
executeActionPromise(action: InputAction<T>, metadata?: T) {
|
|
118
|
+
const fullAction: DTOForgeProjectAction = { ...action, tId: this.transactionIdGenerator() };
|
|
119
|
+
this.localActions.push(fullAction);
|
|
120
|
+
this.refreshProjectContent();
|
|
121
|
+
return this.actionQueue.enqueue({ action: fullAction, metadata });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private async executeInternalAction(action: InternalAction<T>) {
|
|
125
|
+
try {
|
|
126
|
+
return await this.transactionExecutor(action.action);
|
|
127
|
+
} catch (e: unknown) {
|
|
128
|
+
// Remove this action from local actions and reload projectContent
|
|
129
|
+
this.localActions = this.localActions.filter(a => a.tId !== action.action.tId);
|
|
130
|
+
this.refreshProjectContent();
|
|
131
|
+
|
|
132
|
+
this.errorObservers.forEach(o => o(e, action.metadata));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
//
|
|
137
|
+
// Reactions
|
|
138
|
+
//
|
|
139
|
+
|
|
140
|
+
private refreshState() {
|
|
141
|
+
this.refreshProjectContent();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private refreshProjectContent() {
|
|
145
|
+
const yState = this._yState;
|
|
146
|
+
if (!yState) return;
|
|
147
|
+
|
|
148
|
+
// Calculate new project content
|
|
149
|
+
const projectContent = this.calculateProjectState(yState);
|
|
150
|
+
if (!projectContent) return;
|
|
151
|
+
this._currentProjectContent = projectContent;
|
|
152
|
+
|
|
153
|
+
// Invoke observers
|
|
154
|
+
this.projectContentObservers.forEach(o => o(projectContent));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private calculateProjectState(yState: ForgeProjectRoomBaseYDocState): ProjectContentState {
|
|
158
|
+
// Remove local actions that have been applied remotely
|
|
159
|
+
const executedTransactionIds = new Set(yState.executedTransactionIds);
|
|
160
|
+
const localActions = this.localActions.filter(a => a.tId && !executedTransactionIds.has(a.tId));
|
|
161
|
+
this.localActions = localActions;
|
|
162
|
+
|
|
163
|
+
// Apply local actions onto the Yjs state
|
|
164
|
+
const state = applyProjectActionsLocally({
|
|
165
|
+
userId: this.userId,
|
|
166
|
+
projectId: this.projectId,
|
|
167
|
+
remoteState: yState,
|
|
168
|
+
actions: localActions,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
artifacts: state.artifacts,
|
|
173
|
+
features: state.features,
|
|
174
|
+
artifactSections: state.artifactSections,
|
|
175
|
+
featureSections: state.featureSections,
|
|
176
|
+
// we don't apply local actions to relations, so we can just use the Yjs state
|
|
177
|
+
relations: yState.relations,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private onYUpdate() {
|
|
182
|
+
const newState = new ForgeProjectRoomBaseYDoc(this.yDoc).getState();
|
|
183
|
+
if (newState.isLoaded) {
|
|
184
|
+
this._yState = newState;
|
|
185
|
+
this.refreshState();
|
|
186
|
+
|
|
187
|
+
// Flush init callbacks
|
|
188
|
+
this.initCallbacks.forEach(f => f());
|
|
189
|
+
this.initCallbacks.clear();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
class TransactionQueue<T> {
|
|
195
|
+
private readonly executor: InternalTransactionExecutor<T>;
|
|
196
|
+
private readonly queue = new PQueue({
|
|
197
|
+
concurrency: 1,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
constructor(executor: InternalTransactionExecutor<T>) {
|
|
201
|
+
this.executor = executor;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
enqueue(trx: InternalAction<T>) {
|
|
205
|
+
return this.queue.add(() => this.executor(trx));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
onEmpty() {
|
|
209
|
+
return this.queue.onEmpty();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
clear() {
|
|
213
|
+
this.queue.clear();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { mapByUnique } from "@supernova-studio/model";
|
|
2
|
+
import {
|
|
3
|
+
DTOForgeProjectAction,
|
|
4
|
+
DTOForgeProjectActionOfType,
|
|
5
|
+
DTOForgeProjectArtifact,
|
|
6
|
+
DTOForgeProjectFeature,
|
|
7
|
+
DTOForgeSection,
|
|
8
|
+
} from "../api";
|
|
9
|
+
|
|
10
|
+
//
|
|
11
|
+
// Interface
|
|
12
|
+
//
|
|
13
|
+
|
|
14
|
+
type Input = LocalExecutorConfig & {
|
|
15
|
+
actions: DTOForgeProjectAction[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function applyProjectActionsLocally(input: Input) {
|
|
19
|
+
const actionExecutor = new LocalProjectActionExecutor(input);
|
|
20
|
+
actionExecutor.applyActions(input.actions);
|
|
21
|
+
return actionExecutor.localState;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//
|
|
25
|
+
// Executor
|
|
26
|
+
//
|
|
27
|
+
|
|
28
|
+
type LocalExecutorConfig = {
|
|
29
|
+
userId: string;
|
|
30
|
+
projectId: string;
|
|
31
|
+
remoteState: ForgeProjectYDocState;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type ForgeProjectYDocState = {
|
|
35
|
+
artifacts: DTOForgeProjectArtifact[];
|
|
36
|
+
features: DTOForgeProjectFeature[];
|
|
37
|
+
artifactSections: DTOForgeSection[];
|
|
38
|
+
featureSections: DTOForgeSection[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export class LocalProjectActionExecutor {
|
|
42
|
+
private readonly userId: string;
|
|
43
|
+
private readonly projectId: string;
|
|
44
|
+
|
|
45
|
+
private readonly artifacts: Map<string, DTOForgeProjectArtifact>;
|
|
46
|
+
private readonly features: Map<string, DTOForgeProjectFeature>;
|
|
47
|
+
private readonly artifactSections: Map<string, DTOForgeSection>;
|
|
48
|
+
private readonly featureSections: Map<string, DTOForgeSection>;
|
|
49
|
+
|
|
50
|
+
constructor(config: LocalExecutorConfig) {
|
|
51
|
+
const { projectId, remoteState, userId } = config;
|
|
52
|
+
|
|
53
|
+
this.userId = userId;
|
|
54
|
+
this.projectId = projectId;
|
|
55
|
+
|
|
56
|
+
// Seed the state with remote state
|
|
57
|
+
this.artifacts = mapByUnique(remoteState.artifacts, p => p.id);
|
|
58
|
+
this.features = mapByUnique(remoteState.features, p => p.id);
|
|
59
|
+
this.artifactSections = mapByUnique(remoteState.artifactSections, p => p.id);
|
|
60
|
+
this.featureSections = mapByUnique(remoteState.featureSections, p => p.id);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get localState() {
|
|
64
|
+
return {
|
|
65
|
+
artifacts: Array.from(this.artifacts.values()),
|
|
66
|
+
features: Array.from(this.features.values()),
|
|
67
|
+
artifactSections: Array.from(this.artifactSections.values()),
|
|
68
|
+
featureSections: Array.from(this.featureSections.values()),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
applyActions(trx: DTOForgeProjectAction[]) {
|
|
73
|
+
trx.forEach(trx => this.applyTransaction(trx));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
applyTransaction(trx: DTOForgeProjectAction) {
|
|
77
|
+
switch (trx.type) {
|
|
78
|
+
case "ArtifactCreate":
|
|
79
|
+
return this.artifactCreate(trx);
|
|
80
|
+
case "ArtifactUpdate":
|
|
81
|
+
return this.artifactUpdate(trx);
|
|
82
|
+
case "ArtifactDelete":
|
|
83
|
+
return this.artifactDelete(trx);
|
|
84
|
+
case "ArtifactMove":
|
|
85
|
+
return this.artifactMove(trx);
|
|
86
|
+
case "FeatureCreate":
|
|
87
|
+
return this.featureCreate(trx);
|
|
88
|
+
case "FeatureDelete":
|
|
89
|
+
return this.featureDelete(trx);
|
|
90
|
+
case "FeatureUpdate":
|
|
91
|
+
return this.featureUpdate(trx);
|
|
92
|
+
case "FeatureMove":
|
|
93
|
+
return this.featureMove(trx);
|
|
94
|
+
case "SectionCreate":
|
|
95
|
+
return this.sectionCreate(trx);
|
|
96
|
+
case "SectionUpdate":
|
|
97
|
+
return this.sectionUpdate(trx);
|
|
98
|
+
case "SectionDelete":
|
|
99
|
+
return this.sectionDelete(trx);
|
|
100
|
+
case "SectionMove":
|
|
101
|
+
return this.sectionMove(trx);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
//
|
|
106
|
+
// Artifacts
|
|
107
|
+
//
|
|
108
|
+
|
|
109
|
+
public calculateSectionSortOrder(items: Map<string, DTOForgeSection>, afterItemId?: string | null) {
|
|
110
|
+
const sortedItems = Array.from(items.values()).sort((a, b) => a.sortOrder - b.sortOrder);
|
|
111
|
+
const sortOrder = this.calculateSortOrderNoDb(
|
|
112
|
+
pId => sortedItems.find(s => s.projectId === pId),
|
|
113
|
+
pId => sortedItems.findLast(s => s.projectId === pId),
|
|
114
|
+
(pId, id) => sortedItems.find(s => s.projectId === pId && s.id === id),
|
|
115
|
+
(pId, sortOrder) => sortedItems.find(s => s.projectId === pId && s.sortOrder > sortOrder),
|
|
116
|
+
(pId, sortOrder, step) =>
|
|
117
|
+
items.forEach(s => (s.sortOrder > sortOrder && s.projectId === pId ? (s.sortOrder += step) : {})),
|
|
118
|
+
this.projectId,
|
|
119
|
+
afterItemId
|
|
120
|
+
);
|
|
121
|
+
return sortOrder ?? 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public calculateItemSortOrder<T extends DTOForgeProjectArtifact | DTOForgeProjectFeature>(
|
|
125
|
+
items: Map<string, T>,
|
|
126
|
+
afterItemId?: string | null,
|
|
127
|
+
sectionId?: string | null
|
|
128
|
+
) {
|
|
129
|
+
const sortedItems = Array.from(items.values()).sort((a, b) => a.sortOrder - b.sortOrder);
|
|
130
|
+
const matchIds = (i: T, pId: string, sId?: string) => i.sectionId === sId && i.projectId === pId;
|
|
131
|
+
const sortOrder = this.calculateSortOrderNoDb(
|
|
132
|
+
(pId, sId) => sortedItems.find(i => matchIds(i, pId, sId)),
|
|
133
|
+
(pId, sId) => sortedItems.findLast(i => matchIds(i, pId, sId)),
|
|
134
|
+
(pId, id, sId) => sortedItems.find(i => i.id === id && matchIds(i, pId, sId)),
|
|
135
|
+
(pId, sortOrder, sId) => sortedItems.find(i => i.sortOrder > sortOrder && matchIds(i, pId, sId)),
|
|
136
|
+
(pId, sortOrder, step, sId) =>
|
|
137
|
+
items.forEach(i => (i.sortOrder > sortOrder && matchIds(i, pId, sId) ? (i.sortOrder += step) : {})),
|
|
138
|
+
this.projectId,
|
|
139
|
+
afterItemId,
|
|
140
|
+
sectionId ?? undefined
|
|
141
|
+
);
|
|
142
|
+
return sortOrder ?? 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// See also calculateSectionSortOrder and calculateSectionedItemSortOrder
|
|
146
|
+
public calculateSortOrderNoDb(
|
|
147
|
+
findFirst: (projectId: string, sectionId?: string) => { sortOrder: number } | null | undefined,
|
|
148
|
+
findLast: (projectId: string, sectionId?: string) => { sortOrder: number } | null | undefined,
|
|
149
|
+
findUnique: (projectId: string, id: string, sectionId?: string) => { sortOrder: number } | null | undefined,
|
|
150
|
+
findNext: (projectId: string, sortOrder: number, sectionId?: string) => { sortOrder: number } | null | undefined,
|
|
151
|
+
shiftAfter: (projectId: string, sortOrder: number, step: number, sectionId?: string) => void,
|
|
152
|
+
projectId: string,
|
|
153
|
+
afterItemId?: string | null,
|
|
154
|
+
sectionId?: string
|
|
155
|
+
): number | null {
|
|
156
|
+
const SORT_ORDER_STEP = 1000;
|
|
157
|
+
if (afterItemId === undefined) {
|
|
158
|
+
// Move to END: last.sortOrder + step
|
|
159
|
+
const lastSection = findLast(projectId, sectionId);
|
|
160
|
+
return (lastSection?.sortOrder ?? 0) + SORT_ORDER_STEP;
|
|
161
|
+
} else if (afterItemId === null) {
|
|
162
|
+
// Move to BEGINNING: first.sortOrder - step
|
|
163
|
+
const firstSection = findFirst(projectId, sectionId);
|
|
164
|
+
return (firstSection?.sortOrder ?? SORT_ORDER_STEP) - SORT_ORDER_STEP;
|
|
165
|
+
} else {
|
|
166
|
+
// Move AFTER target section: average of neighbors
|
|
167
|
+
const targetSection = findUnique(projectId, afterItemId, sectionId);
|
|
168
|
+
if (!targetSection) return null;
|
|
169
|
+
|
|
170
|
+
// Find the next section after target (if any)
|
|
171
|
+
const nextSection = findNext(projectId, targetSection.sortOrder, sectionId);
|
|
172
|
+
if (nextSection) {
|
|
173
|
+
// Position between target and next: average of neighbors
|
|
174
|
+
let newSortOrder = Math.floor((targetSection.sortOrder + nextSection.sortOrder) / 2);
|
|
175
|
+
|
|
176
|
+
// If average would be same as target (too close), use target + step
|
|
177
|
+
if (newSortOrder <= targetSection.sortOrder) {
|
|
178
|
+
newSortOrder = targetSection.sortOrder + SORT_ORDER_STEP / 2;
|
|
179
|
+
shiftAfter(projectId, targetSection.sortOrder, SORT_ORDER_STEP, sectionId);
|
|
180
|
+
}
|
|
181
|
+
return newSortOrder;
|
|
182
|
+
} else {
|
|
183
|
+
// No next section: position at end (target + step)
|
|
184
|
+
return targetSection.sortOrder + SORT_ORDER_STEP;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private artifactCreate(trx: DTOForgeProjectActionOfType<"ArtifactCreate">) {
|
|
190
|
+
const { input } = trx;
|
|
191
|
+
const { id, afterArtifactId, sectionId } = input;
|
|
192
|
+
const sortOrder = this.calculateItemSortOrder(this.artifacts, afterArtifactId, sectionId);
|
|
193
|
+
|
|
194
|
+
this.artifacts.set(id, {
|
|
195
|
+
id,
|
|
196
|
+
projectId: this.projectId,
|
|
197
|
+
sectionId: sectionId,
|
|
198
|
+
sortOrder: sortOrder,
|
|
199
|
+
title: input.title,
|
|
200
|
+
updatedAt: new Date(),
|
|
201
|
+
createdAt: new Date(),
|
|
202
|
+
createdByUserId: this.userId,
|
|
203
|
+
isArchived: false,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
//
|
|
208
|
+
// Feature
|
|
209
|
+
//
|
|
210
|
+
|
|
211
|
+
private artifactUpdate(trx: DTOForgeProjectActionOfType<"ArtifactUpdate">) {
|
|
212
|
+
const { input } = trx;
|
|
213
|
+
const { id } = input;
|
|
214
|
+
|
|
215
|
+
const existingArtifact = this.artifacts.get(id);
|
|
216
|
+
if (!existingArtifact) {
|
|
217
|
+
throw new Error(`Cannot update artifact: artifact ${id} was not found in local storage`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const mergedArtifact: DTOForgeProjectArtifact = {
|
|
221
|
+
...existingArtifact,
|
|
222
|
+
title: input.title ?? existingArtifact.title,
|
|
223
|
+
isArchived: input.isArchived ?? existingArtifact.isArchived,
|
|
224
|
+
updatedAt: new Date(),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
this.artifacts.set(id, mergedArtifact);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private artifactMove(trx: DTOForgeProjectActionOfType<"ArtifactMove">) {
|
|
231
|
+
const { input } = trx;
|
|
232
|
+
const { id, afterId, sectionId } = input;
|
|
233
|
+
|
|
234
|
+
const existingArtifact = this.artifacts.get(id);
|
|
235
|
+
if (!existingArtifact) {
|
|
236
|
+
throw new Error(`Cannot move artifact: artifact ${id} was not found in local storage`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const sortOrder = this.calculateItemSortOrder(this.artifacts, afterId, sectionId);
|
|
240
|
+
|
|
241
|
+
const mergedArtifact: DTOForgeProjectArtifact = {
|
|
242
|
+
...existingArtifact,
|
|
243
|
+
sortOrder,
|
|
244
|
+
updatedAt: new Date(),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
this.artifacts.set(id, mergedArtifact);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private artifactDelete(trx: DTOForgeProjectActionOfType<"ArtifactDelete">) {
|
|
251
|
+
const { input } = trx;
|
|
252
|
+
const { id } = input;
|
|
253
|
+
|
|
254
|
+
if (!this.artifacts.delete(id)) {
|
|
255
|
+
throw new Error(`Cannot delete artifact: artifact ${id} was not found in local storage`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private featureCreate(trx: DTOForgeProjectActionOfType<"FeatureCreate">) {
|
|
260
|
+
const { input } = trx;
|
|
261
|
+
const { id, afterFeatureId, sectionId } = input;
|
|
262
|
+
const sortOrder = this.calculateItemSortOrder(this.features, afterFeatureId, sectionId);
|
|
263
|
+
|
|
264
|
+
this.features.set(id, {
|
|
265
|
+
id,
|
|
266
|
+
projectId: this.projectId,
|
|
267
|
+
description: input.description,
|
|
268
|
+
isArchived: false,
|
|
269
|
+
sectionId: input.sectionId,
|
|
270
|
+
sortOrder: sortOrder,
|
|
271
|
+
name: input.name ?? "New Feature",
|
|
272
|
+
status: "Draft",
|
|
273
|
+
updatedAt: new Date(),
|
|
274
|
+
createdAt: new Date(),
|
|
275
|
+
createdByUserId: this.userId,
|
|
276
|
+
lastReplyTimestamp: undefined,
|
|
277
|
+
numberOfIterations: 0,
|
|
278
|
+
numberOfBookmarkedIterations: 0,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
//
|
|
283
|
+
// Sections
|
|
284
|
+
//
|
|
285
|
+
|
|
286
|
+
private featureUpdate(trx: DTOForgeProjectActionOfType<"FeatureUpdate">) {
|
|
287
|
+
const { input } = trx;
|
|
288
|
+
const { id } = input;
|
|
289
|
+
|
|
290
|
+
const existingFeature = this.features.get(id);
|
|
291
|
+
if (!existingFeature) {
|
|
292
|
+
throw new Error(`Cannot update feature: feature ${id} was not found in local storage`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let publishedState = existingFeature.publishedState;
|
|
296
|
+
|
|
297
|
+
if (input.publishedState === null) {
|
|
298
|
+
publishedState = undefined;
|
|
299
|
+
} else if (input.publishedState) {
|
|
300
|
+
publishedState = {
|
|
301
|
+
...input.publishedState,
|
|
302
|
+
lastPublishedAt: new Date(),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const mergedFeature: DTOForgeProjectFeature = {
|
|
307
|
+
...existingFeature,
|
|
308
|
+
name: input.name ?? existingFeature.name,
|
|
309
|
+
description: input.description ?? existingFeature.description,
|
|
310
|
+
isArchived: input.isArchived ?? existingFeature.isArchived,
|
|
311
|
+
status: input.status ?? existingFeature.status,
|
|
312
|
+
updatedAt: new Date(),
|
|
313
|
+
publishedState,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
this.features.set(id, mergedFeature);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private featureMove(trx: DTOForgeProjectActionOfType<"FeatureMove">) {
|
|
320
|
+
const { input } = trx;
|
|
321
|
+
const { id, afterId, sectionId } = input;
|
|
322
|
+
|
|
323
|
+
const existingFeature = this.features.get(id);
|
|
324
|
+
if (!existingFeature) {
|
|
325
|
+
throw new Error(`Cannot move feature: feature ${id} was not found in local storage`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const sortOrder = this.calculateItemSortOrder(this.features, afterId, sectionId);
|
|
329
|
+
|
|
330
|
+
const mergedFeature: DTOForgeProjectFeature = {
|
|
331
|
+
...existingFeature,
|
|
332
|
+
sortOrder,
|
|
333
|
+
updatedAt: new Date(),
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
this.features.set(id, mergedFeature);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private featureDelete(trx: DTOForgeProjectActionOfType<"FeatureDelete">) {
|
|
340
|
+
const { input } = trx;
|
|
341
|
+
const { id } = input;
|
|
342
|
+
|
|
343
|
+
if (!this.features.delete(id)) {
|
|
344
|
+
throw new Error(`Cannot delete feature: feature ${id} was not found in local storage`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private sectionCreate(trx: DTOForgeProjectActionOfType<"SectionCreate">) {
|
|
349
|
+
const { input } = trx;
|
|
350
|
+
const { id, afterSectionId } = input;
|
|
351
|
+
const sections = input.childType === "Artifact" ? this.artifactSections : this.featureSections;
|
|
352
|
+
const sortOrder = this.calculateSectionSortOrder(sections, afterSectionId);
|
|
353
|
+
|
|
354
|
+
sections.set(id, {
|
|
355
|
+
id,
|
|
356
|
+
projectId: this.projectId,
|
|
357
|
+
name: input.name,
|
|
358
|
+
childType: input.childType,
|
|
359
|
+
sortOrder: sortOrder,
|
|
360
|
+
updatedAt: new Date(),
|
|
361
|
+
createdAt: new Date(),
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private sectionUpdate(trx: DTOForgeProjectActionOfType<"SectionUpdate">) {
|
|
366
|
+
const { input } = trx;
|
|
367
|
+
const { id } = input;
|
|
368
|
+
|
|
369
|
+
const existingSection = this.artifactSections.get(id) ?? this.featureSections.get(id);
|
|
370
|
+
if (!existingSection) {
|
|
371
|
+
throw new Error(`Cannot update section: section ${id} was not found in local storage`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const mergedSection: DTOForgeSection = {
|
|
375
|
+
...existingSection,
|
|
376
|
+
name: input.name ?? existingSection.name,
|
|
377
|
+
updatedAt: new Date(),
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const sections = existingSection.childType === "Artifact" ? this.artifactSections : this.featureSections;
|
|
381
|
+
sections.set(id, mergedSection);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private sectionMove(trx: DTOForgeProjectActionOfType<"SectionMove">) {
|
|
385
|
+
const { input } = trx;
|
|
386
|
+
const { id, afterSectionId } = input;
|
|
387
|
+
|
|
388
|
+
const existingSection = this.artifactSections.get(id) ?? this.featureSections.get(id);
|
|
389
|
+
if (!existingSection) {
|
|
390
|
+
throw new Error(`Cannot move section: section ${id} was not found in local storage`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const sections = existingSection.childType === "Artifact" ? this.artifactSections : this.featureSections;
|
|
394
|
+
const sortOrder = this.calculateSectionSortOrder(sections, afterSectionId);
|
|
395
|
+
|
|
396
|
+
const mergedSection: DTOForgeSection = {
|
|
397
|
+
...existingSection,
|
|
398
|
+
sortOrder,
|
|
399
|
+
updatedAt: new Date(),
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
sections.set(id, mergedSection);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private sectionDelete(trx: DTOForgeProjectActionOfType<"SectionDelete">) {
|
|
406
|
+
const { input } = trx;
|
|
407
|
+
const { id } = input;
|
|
408
|
+
|
|
409
|
+
const isArtifactSection = this.artifactSections.delete(id);
|
|
410
|
+
const isFeatureSection = this.featureSections.delete(id);
|
|
411
|
+
if (!isArtifactSection && !isFeatureSection) {
|
|
412
|
+
throw new Error(`Cannot delete section: section ${id} was not found in local storage`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { DocumentationPageApproval, DocumentationPageV2, ElementGroup, OmitStrict } from "@supernova-studio/model";
|
|
2
|
+
import {
|
|
3
|
+
DTOElementActionInput,
|
|
4
|
+
DTOForgeProjectArtifact,
|
|
5
|
+
DTOForgeProjectFeature,
|
|
6
|
+
DTOForgeRelation,
|
|
7
|
+
DTOForgeSection,
|
|
8
|
+
} from "../api";
|
|
9
|
+
|
|
10
|
+
export type LocalDocsPage = OmitStrict<DocumentationPageV2, "id" | "userSlug"> & { userSlug?: undefined };
|
|
11
|
+
export type LocalDocsPageGroup = OmitStrict<ElementGroup, "id" | "brandPersistentId" | "childType" | "userSlug"> & {
|
|
12
|
+
userSlug?: undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type LocalApproval = OmitStrict<DocumentationPageApproval, "id" | "pageId" | "persistentId">;
|
|
16
|
+
|
|
17
|
+
type NarrowedUnion<T, U extends T> = U;
|
|
18
|
+
export type SupportedElementActionType = NarrowedUnion<
|
|
19
|
+
DTOElementActionInput["type"],
|
|
20
|
+
| "DocumentationPageCreate"
|
|
21
|
+
| "DocumentationPageUpdate"
|
|
22
|
+
| "DocumentationPageDelete"
|
|
23
|
+
| "DocumentationPageMove"
|
|
24
|
+
| "DocumentationGroupCreate"
|
|
25
|
+
| "DocumentationGroupUpdate"
|
|
26
|
+
| "DocumentationGroupMove"
|
|
27
|
+
| "DocumentationPageApprovalStateChange"
|
|
28
|
+
| "DocumentationTabCreate"
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
export type ProjectContentState = {
|
|
32
|
+
artifacts: DTOForgeProjectArtifact[];
|
|
33
|
+
features: DTOForgeProjectFeature[];
|
|
34
|
+
artifactSections: DTOForgeSection[];
|
|
35
|
+
featureSections: DTOForgeSection[];
|
|
36
|
+
relations: DTOForgeRelation[];
|
|
37
|
+
};
|