@volter/sdk 0.0.0-stage → 0.5.204
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/LICENSE +202 -0
- package/NOTICE +20 -0
- package/README.md +38 -3
- package/package.json +510 -4
- package/src/account.ts +210 -0
- package/src/chrome.ts +88 -0
- package/src/client.ts +1646 -0
- package/src/commands.ts +66 -0
- package/src/contributions.ts +619 -0
- package/src/css-numeric-style.ts +97 -0
- package/src/document-probe.ts +282 -0
- package/src/editor-view.ts +225 -0
- package/src/extension.ts +40 -0
- package/src/generations.ts +178 -0
- package/src/host.ts +1157 -0
- package/src/http-transport.browser.ts +14 -0
- package/src/http-transport.node.ts +19 -0
- package/src/index.ts +131 -0
- package/src/kit/CapabilityCoverageSection.tsx +185 -0
- package/src/kit/account-client.ts +333 -0
- package/src/kit/action-registry.ts +317 -0
- package/src/kit/active-product.ts +76 -0
- package/src/kit/active-project.ts +155 -0
- package/src/kit/adapter-editor-config.ts +25 -0
- package/src/kit/adapter-module.ts +7 -0
- package/src/kit/adapter-observation.ts +49 -0
- package/src/kit/animation/animation-clock.ts +479 -0
- package/src/kit/animation/stage-transport.ts +385 -0
- package/src/kit/api/assets.ts +365 -0
- package/src/kit/api/project-open.ts +355 -0
- package/src/kit/api/project-source.ts +180 -0
- package/src/kit/api/project-state.ts +110 -0
- package/src/kit/api/relay.ts +270 -0
- package/src/kit/api/themes.ts +45 -0
- package/src/kit/api-asset-library-wire.ts +45 -0
- package/src/kit/api-base.ts +10 -0
- package/src/kit/api-build.ts +99 -0
- package/src/kit/api-git-wire.ts +56 -0
- package/src/kit/api-logs.ts +92 -0
- package/src/kit/api-project-identity.ts +74 -0
- package/src/kit/api-settings.ts +36 -0
- package/src/kit/api-worktrees.ts +205 -0
- package/src/kit/asset-capabilities.ts +344 -0
- package/src/kit/asset-compare-core.ts +171 -0
- package/src/kit/asset-editor-context.tsx +101 -0
- package/src/kit/asset-events.ts +96 -0
- package/src/kit/asset-inspector-actions.ts +87 -0
- package/src/kit/asset-selection-viewer-registry.ts +113 -0
- package/src/kit/asset-selection.ts +146 -0
- package/src/kit/asset-thumbnails.ts +25 -0
- package/src/kit/asset-viewers.ts +115 -0
- package/src/kit/asset-workflow/asset-import-jobs.ts +106 -0
- package/src/kit/asset-workflow/asset-ledger-backend.ts +126 -0
- package/src/kit/asset-workflow/asset-ledger.ts +156 -0
- package/src/kit/asset-workflow/asset-materialization-report.ts +140 -0
- package/src/kit/asset-workflow/asset-pack-manifest.ts +320 -0
- package/src/kit/asset-workflow/asset-types.ts +142 -0
- package/src/kit/asset-workflow/audio-preview-player.ts +193 -0
- package/src/kit/asset-workflow/audio-waveform.ts +22 -0
- package/src/kit/asset-workflow/cloud-asset-client.ts +263 -0
- package/src/kit/asset-workflow/hosted-asset-materialization.ts +236 -0
- package/src/kit/asset-workflow/image-view-scale.ts +31 -0
- package/src/kit/asset-workflow/import-contract.ts +124 -0
- package/src/kit/asset-workflow/ledger-write-lock.ts +244 -0
- package/src/kit/asset-workflow/pixi-spritesheet.ts +197 -0
- package/src/kit/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/kit/asset-workflow/project-asset-commands.ts +20 -0
- package/src/kit/asset-workflow/project-content.ts +288 -0
- package/src/kit/asset-workflow/project-source-index.ts +545 -0
- package/src/kit/asset-workflow/thumbnail-system.ts +256 -0
- package/src/kit/authoring/active-adapter.ts +200 -0
- package/src/kit/authoring/active-systems.ts +422 -0
- package/src/kit/authoring/adapter-key.ts +18 -0
- package/src/kit/authoring/authoring-asset-url.ts +27 -0
- package/src/kit/authoring/bootstrap-state.ts +49 -0
- package/src/kit/authoring/boundary-authoring-adapter.ts +189 -0
- package/src/kit/authoring/canvas-scene-guides.ts +84 -0
- package/src/kit/authoring/composite-authoring-adapter.ts +2109 -0
- package/src/kit/authoring/consumer-actions.ts +531 -0
- package/src/kit/authoring/design-time-layers.ts +852 -0
- package/src/kit/authoring/design-time-mount-registry.ts +244 -0
- package/src/kit/authoring/edit-mode-authoring.ts +637 -0
- package/src/kit/authoring/empty-project-authoring.ts +22 -0
- package/src/kit/authoring/instance-source-menu.ts +135 -0
- package/src/kit/authoring/layered-pick.ts +185 -0
- package/src/kit/authoring/mounted-root-subjects.ts +146 -0
- package/src/kit/authoring/no-authoring-adapter.ts +59 -0
- package/src/kit/authoring/object3d-document-persistence.ts +122 -0
- package/src/kit/authoring/panel-authoring.ts +121 -0
- package/src/kit/authoring/project-authoring-session.ts +105 -0
- package/src/kit/authoring/provenance.ts +99 -0
- package/src/kit/authoring/react-canvas-navigation.ts +259 -0
- package/src/kit/authoring/react-design-canvas-style.ts +20 -0
- package/src/kit/authoring/react-story-board.ts +917 -0
- package/src/kit/authoring/selection-scope.ts +195 -0
- package/src/kit/authoring/shell-document-ops.ts +169 -0
- package/src/kit/authoring/story-board-chrome-fit.ts +107 -0
- package/src/kit/authoring/story-board-presentation.ts +111 -0
- package/src/kit/authoring/three-root.ts +67 -0
- package/src/kit/authoring/viewport-tool-context.ts +73 -0
- package/src/kit/authoring/viewport-tool-owner.ts +38 -0
- package/src/kit/authoring/world-session-state.ts +101 -0
- package/src/kit/authoring-seam-evidence.ts +300 -0
- package/src/kit/availability-tick.ts +66 -0
- package/src/kit/bitmap-label.ts +120 -0
- package/src/kit/boot-routing.ts +392 -0
- package/src/kit/breakpoint-state.ts +43 -0
- package/src/kit/build-identity.ts +16 -0
- package/src/kit/bytes-codec.ts +62 -0
- package/src/kit/cancellation-reason.ts +58 -0
- package/src/kit/canvas-frames.ts +88 -0
- package/src/kit/capture-camera-pose.ts +77 -0
- package/src/kit/capture-size.ts +88 -0
- package/src/kit/chrome-registry.ts +159 -0
- package/src/kit/chrome-slot-registry.ts +91 -0
- package/src/kit/collaboration-client.ts +264 -0
- package/src/kit/collaboration-presence.ts +41 -0
- package/src/kit/command-dispatch.ts +19 -0
- package/src/kit/command-listener.ts +2182 -0
- package/src/kit/command-registry.ts +71 -0
- package/src/kit/component-board-registry.ts +205 -0
- package/src/kit/component-states-registry.ts +199 -0
- package/src/kit/components/AlignToolbar.tsx +204 -0
- package/src/kit/components/ApplicationMenus.tsx +372 -0
- package/src/kit/components/AssetEditorShell.tsx +216 -0
- package/src/kit/components/AssetInspectorToolSection.tsx +124 -0
- package/src/kit/components/BoardRulers.tsx +354 -0
- package/src/kit/components/CanvasAddNodeDialogs.tsx +529 -0
- package/src/kit/components/CanvasSceneViewport.tsx +1195 -0
- package/src/kit/components/ChromeSlot.tsx +20 -0
- package/src/kit/components/CodeView.tsx +470 -0
- package/src/kit/components/CompactInspectorShell.tsx +39 -0
- package/src/kit/components/ConsolePanel.tsx +273 -0
- package/src/kit/components/GameplaySessionTimeline.tsx +295 -0
- package/src/kit/components/InspectionProjection.tsx +932 -0
- package/src/kit/components/Inspector.tsx +270 -0
- package/src/kit/components/InspectorCanvasPreview.tsx +35 -0
- package/src/kit/components/InspectorFieldsSection.tsx +290 -0
- package/src/kit/components/InspectorStoriesSection.tsx +92 -0
- package/src/kit/components/InspectorToolSection.tsx +96 -0
- package/src/kit/components/InspectorTransformSection.tsx +245 -0
- package/src/kit/components/LightExplorerPanel.tsx +433 -0
- package/src/kit/components/MediaProperties.tsx +145 -0
- package/src/kit/components/ProjectHeader.tsx +328 -0
- package/src/kit/components/ReactCanvasControls.tsx +358 -0
- package/src/kit/components/RootSelectionOverlay.tsx +3688 -0
- package/src/kit/components/RootTextEditor.tsx +79 -0
- package/src/kit/components/SaveStatus.tsx +70 -0
- package/src/kit/components/SurfaceCrashBoundary.tsx +105 -0
- package/src/kit/components/SurfaceStateOverlay.tsx +24 -0
- package/src/kit/components/ToolContributionSurfaces.tsx +49 -0
- package/src/kit/components/ToolHost.tsx +380 -0
- package/src/kit/components/Toolbar.tsx +811 -0
- package/src/kit/components/TransientHint.tsx +44 -0
- package/src/kit/components/VersionControlSection.tsx +470 -0
- package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
- package/src/kit/components/VolterLogo.tsx +18 -0
- package/src/kit/components/WorktreeSwitcher.tsx +712 -0
- package/src/kit/components/account-documents.tsx +1162 -0
- package/src/kit/components/asset-documents.tsx +794 -0
- package/src/kit/components/asset-editor-persistence.ts +216 -0
- package/src/kit/components/asset-selection-section.tsx +545 -0
- package/src/kit/components/asset-thumbnails.tsx +307 -0
- package/src/kit/components/asset-viewers/AudioViewer.tsx +201 -0
- package/src/kit/components/asset-viewers/GenericJsonViewer.tsx +102 -0
- package/src/kit/components/asset-viewers/ImageViewer.tsx +300 -0
- package/src/kit/components/asset-viewers/JsonAssetDocument.tsx +98 -0
- package/src/kit/components/asset-viewers/OnlineAssetDetail.tsx +426 -0
- package/src/kit/components/asset-viewers/SourceAssetViewer.tsx +356 -0
- package/src/kit/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
- package/src/kit/components/asset-viewers/VideoViewer.tsx +101 -0
- package/src/kit/components/asset-viewers/shader-source.ts +144 -0
- package/src/kit/components/board-guides.ts +150 -0
- package/src/kit/components/canvas-scene-hotkeys.ts +37 -0
- package/src/kit/components/canvas-temporary-pivot.ts +34 -0
- package/src/kit/components/core-utilities.tsx +94 -0
- package/src/kit/components/inspector-preview-section.tsx +223 -0
- package/src/kit/components/inspector-revert-label.ts +20 -0
- package/src/kit/components/inspector-selection.ts +42 -0
- package/src/kit/components/inspector-stories-gating.ts +171 -0
- package/src/kit/components/inspector-transform-subject.ts +11 -0
- package/src/kit/components/inspector-transform.ts +88 -0
- package/src/kit/components/kind-documents.tsx +544 -0
- package/src/kit/components/primitives/DraftColorInput.tsx +74 -0
- package/src/kit/components/project-tool-documents.tsx +402 -0
- package/src/kit/components/scene-documents.tsx +221 -0
- package/src/kit/components/status-contributions.tsx +407 -0
- package/src/kit/components/tool-documents.tsx +302 -0
- package/src/kit/components/tool-schema-form.tsx +262 -0
- package/src/kit/components/use-after-paint.ts +41 -0
- package/src/kit/components/use-project-image-assets.ts +86 -0
- package/src/kit/components/workspace-history.ts +32 -0
- package/src/kit/components/world-documents.tsx +570 -0
- package/src/kit/components/world-overlay-gestures.ts +1939 -0
- package/src/kit/composite-screenshot.ts +2238 -0
- package/src/kit/content-entry-source-registry.ts +184 -0
- package/src/kit/contribution-surfaces.ts +48 -0
- package/src/kit/coverage/canvas-reveal.ts +192 -0
- package/src/kit/coverage/design-time-surfaces.ts +101 -0
- package/src/kit/coverage/ontology-invariants.ts +466 -0
- package/src/kit/coverage/session-vitals.ts +503 -0
- package/src/kit/crash-null-boundary.ts +36 -0
- package/src/kit/creation-site-edit.ts +1491 -0
- package/src/kit/creation-site-registry.ts +160 -0
- package/src/kit/delegate-harness-registry.ts +134 -0
- package/src/kit/document-areas.ts +70 -0
- package/src/kit/document-context-registry.ts +193 -0
- package/src/kit/document-open-registry.ts +200 -0
- package/src/kit/document-play-extension.ts +221 -0
- package/src/kit/document-preview-source.ts +20 -0
- package/src/kit/document-renderer-session.ts +138 -0
- package/src/kit/document-stage-sessions.ts +26 -0
- package/src/kit/document-viewports.ts +120 -0
- package/src/kit/editor-api.ts +46 -0
- package/src/kit/editor-chrome-capture.ts +136 -0
- package/src/kit/editor-commands.ts +176 -0
- package/src/kit/editor-console.ts +580 -0
- package/src/kit/editor-current-view.ts +56 -0
- package/src/kit/editor-document-probe.ts +1168 -0
- package/src/kit/editor-git-client.ts +115 -0
- package/src/kit/editor-hotkeys.ts +728 -0
- package/src/kit/editor-lease-view.ts +39 -0
- package/src/kit/editor-lease.ts +415 -0
- package/src/kit/editor-mode.ts +19 -0
- package/src/kit/editor-notifications.ts +140 -0
- package/src/kit/editor-presence.ts +563 -0
- package/src/kit/editor-presentation-activity.ts +58 -0
- package/src/kit/editor-presentation-notice.ts +42 -0
- package/src/kit/editor-runtime.tsx +147 -0
- package/src/kit/editor-server-response.ts +86 -0
- package/src/kit/editor-session-attribution.ts +85 -0
- package/src/kit/editor-session-mode.ts +54 -0
- package/src/kit/editor-state-facets.ts +74 -0
- package/src/kit/editor-view-presentation.ts +777 -0
- package/src/kit/environment-images.ts +58 -0
- package/src/kit/eyedropper-session.ts +60 -0
- package/src/kit/files/file-provider.ts +62 -0
- package/src/kit/files/project-files.ts +270 -0
- package/src/kit/finders/index.ts +137 -0
- package/src/kit/finders/scenes-from-entrypoint-selection.ts +387 -0
- package/src/kit/frame/frame-parts.ts +30 -0
- package/src/kit/framed-document-capture.ts +34 -0
- package/src/kit/game-globals-prelude.ts +143 -0
- package/src/kit/game-surface-defaults.ts +33 -0
- package/src/kit/gameplay-dom-recording.ts +318 -0
- package/src/kit/gameplay-export-state.ts +14 -0
- package/src/kit/gameplay-replay.ts +417 -0
- package/src/kit/gameplay-session-time.ts +9 -0
- package/src/kit/gameplay-sessions.ts +204 -0
- package/src/kit/hierarchy-component-marks.ts +298 -0
- package/src/kit/hierarchy-internals.ts +197 -0
- package/src/kit/hierarchy-kind-icon.ts +217 -0
- package/src/kit/hierarchy-menu-registry.ts +67 -0
- package/src/kit/hierarchy-node-rows.ts +307 -0
- package/src/kit/hierarchy-panel-view.ts +280 -0
- package/src/kit/hierarchy-projection.ts +76 -0
- package/src/kit/hierarchy-row-media.ts +45 -0
- package/src/kit/hierarchy-row-model.ts +308 -0
- package/src/kit/hierarchy-rows.ts +11 -0
- package/src/kit/hierarchy-walk.ts +86 -0
- package/src/kit/history/editor-session.ts +25 -0
- package/src/kit/history/history-commands.ts +147 -0
- package/src/kit/history/history-delegate.ts +187 -0
- package/src/kit/history/history-limit-notices.ts +43 -0
- package/src/kit/history/history-service.ts +1189 -0
- package/src/kit/history/persistence-coordinator.ts +35 -0
- package/src/kit/history/project-file-history.ts +386 -0
- package/src/kit/history/project-root-history-backends.ts +139 -0
- package/src/kit/history/resource-registry.ts +209 -0
- package/src/kit/history/snapshot-store.ts +103 -0
- package/src/kit/history/source-history-backend.ts +546 -0
- package/src/kit/history-types.ts +124 -0
- package/src/kit/hmr-registration-group.ts +67 -0
- package/src/kit/hmr-stable-react-context.ts +23 -0
- package/src/kit/hotkeys.ts +190 -0
- package/src/kit/inference-diagnostics.ts +69 -0
- package/src/kit/initial-project.ts +80 -0
- package/src/kit/inspection/active-subject.ts +571 -0
- package/src/kit/inspection/active-surface.ts +142 -0
- package/src/kit/inspection/compose-subject.ts +1055 -0
- package/src/kit/inspection/compose.ts +7 -0
- package/src/kit/inspection/display.ts +171 -0
- package/src/kit/inspection/document-subject.ts +109 -0
- package/src/kit/inspection/game-subject.ts +85 -0
- package/src/kit/inspection/null-subject.ts +119 -0
- package/src/kit/inspection/serialize.ts +357 -0
- package/src/kit/inspection/use-active-inspection.ts +180 -0
- package/src/kit/inspection-model.ts +542 -0
- package/src/kit/inspection-node-media.ts +58 -0
- package/src/kit/inspector-presentation.ts +203 -0
- package/src/kit/inspector-property-grouping.ts +64 -0
- package/src/kit/inspector-section-registry.ts +221 -0
- package/src/kit/instance-source-actions.ts +163 -0
- package/src/kit/js-heap.ts +71 -0
- package/src/kit/key-actions.ts +91 -0
- package/src/kit/keymap-presets.ts +428 -0
- package/src/kit/layout-policy.ts +31 -0
- package/src/kit/light-explorer-model.ts +134 -0
- package/src/kit/live-canvas-frame.ts +55 -0
- package/src/kit/live-document.ts +296 -0
- package/src/kit/live-gesture-lock.ts +50 -0
- package/src/kit/live-seam-evidence.ts +11 -0
- package/src/kit/live-session-registry.ts +220 -0
- package/src/kit/live-transition.ts +391 -0
- package/src/kit/manifest-project.ts +107 -0
- package/src/kit/module-fetch-diagnosis.ts +192 -0
- package/src/kit/mount-failure-report.ts +154 -0
- package/src/kit/native-selection-style.ts +497 -0
- package/src/kit/object3d-document-write-policy.ts +137 -0
- package/src/kit/packaged-runtime.ts +108 -0
- package/src/kit/palettes/maya.palette.json +57 -0
- package/src/kit/palettes/substance.palette.json +57 -0
- package/src/kit/performance-profiler.ts +367 -0
- package/src/kit/performance-sources.ts +69 -0
- package/src/kit/photograph-notice.ts +141 -0
- package/src/kit/play-boot-phase.ts +166 -0
- package/src/kit/play-camera-flight.ts +35 -0
- package/src/kit/png-encode.worker.ts +26 -0
- package/src/kit/presentation-surface.ts +248 -0
- package/src/kit/product-command.ts +90 -0
- package/src/kit/project-adapter.ts +1140 -0
- package/src/kit/project-asset-refresh.ts +23 -0
- package/src/kit/project-asset-roots.ts +68 -0
- package/src/kit/project-local-state.ts +151 -0
- package/src/kit/project-manager.ts +243 -0
- package/src/kit/project-module-changes.ts +201 -0
- package/src/kit/project-module-split.ts +270 -0
- package/src/kit/project-play-layers.ts +25 -0
- package/src/kit/project-provenance.ts +115 -0
- package/src/kit/project-ready.ts +42 -0
- package/src/kit/project-shape.ts +68 -0
- package/src/kit/project-tools.ts +107 -0
- package/src/kit/projection-types.ts +44 -0
- package/src/kit/readiness.ts +113 -0
- package/src/kit/renderer-resource-counts.ts +27 -0
- package/src/kit/reported-play-state.ts +90 -0
- package/src/kit/resolve-contributed-command.ts +14 -0
- package/src/kit/resolve-relative-specifier.ts +33 -0
- package/src/kit/retained-document-states.ts +91 -0
- package/src/kit/scene-document-plan.ts +320 -0
- package/src/kit/scene-live-open.ts +210 -0
- package/src/kit/scoped-game-css.ts +152 -0
- package/src/kit/served-url.ts +5 -0
- package/src/kit/session-close.ts +17 -0
- package/src/kit/session-tombstone.ts +127 -0
- package/src/kit/settings/settings-provider.ts +82 -0
- package/src/kit/settings-store.ts +348 -0
- package/src/kit/shell-document-state.ts +27 -0
- package/src/kit/shell-store-door.ts +45 -0
- package/src/kit/shell-store.ts +722 -0
- package/src/kit/source-conflict.ts +122 -0
- package/src/kit/stage-context.ts +377 -0
- package/src/kit/stage-invalidation.ts +25 -0
- package/src/kit/stage-store-registry.ts +69 -0
- package/src/kit/startup-failure.ts +80 -0
- package/src/kit/state-report-deferral.ts +73 -0
- package/src/kit/storage/host-files-storage.ts +97 -0
- package/src/kit/storage/http-storage.ts +174 -0
- package/src/kit/storage/index.ts +75 -0
- package/src/kit/storage/mem-storage.ts +158 -0
- package/src/kit/storage/path-lock.ts +44 -0
- package/src/kit/storage/paths.ts +26 -0
- package/src/kit/storage-types.ts +127 -0
- package/src/kit/stories/StoryPreviewMount.tsx +306 -0
- package/src/kit/stories/compose-project-stories.ts +255 -0
- package/src/kit/stories/prefabs-finder.ts +54 -0
- package/src/kit/stories/prefabs-from-stories.ts +182 -0
- package/src/kit/stories/project-story-regions.ts +24 -0
- package/src/kit/stories/story-capture.ts +579 -0
- package/src/kit/stories/story-declared-medium.ts +126 -0
- package/src/kit/stories/story-discovery.ts +176 -0
- package/src/kit/stories/story-dom-runtime.ts +78 -0
- package/src/kit/stories/story-grouping.ts +111 -0
- package/src/kit/stories/story-mount-turn.ts +27 -0
- package/src/kit/stories/story-presentation.ts +215 -0
- package/src/kit/stories/story-preview-component.ts +7 -0
- package/src/kit/stories/story-registry.ts +530 -0
- package/src/kit/stories-scope.ts +35 -0
- package/src/kit/story-document-openers.ts +36 -0
- package/src/kit/story-thumbnails.ts +47 -0
- package/src/kit/surface-keyboard.ts +101 -0
- package/src/kit/surface-state.ts +135 -0
- package/src/kit/system-seam-evidence.ts +72 -0
- package/src/kit/tab-census.ts +202 -0
- package/src/kit/tab-lifecycle-client.ts +227 -0
- package/src/kit/theme-library.ts +897 -0
- package/src/kit/theme-preference.ts +429 -0
- package/src/kit/three-viewport-presentation.ts +23 -0
- package/src/kit/tool-contribution-play.ts +74 -0
- package/src/kit/tool-loader.ts +1918 -0
- package/src/kit/transform-mode-request.ts +66 -0
- package/src/kit/transient-hint.ts +78 -0
- package/src/kit/transport-strip.tsx +174 -0
- package/src/kit/ui-source/adapter-region-includes.ts +238 -0
- package/src/kit/ui-source/file-region-resolver.ts +302 -0
- package/src/kit/ui-source/inspect.ts +775 -0
- package/src/kit/ui-source/source-write-backend.ts +605 -0
- package/src/kit/ui-source/tier-source-write-backend.ts +279 -0
- package/src/kit/user-local-state.ts +105 -0
- package/src/kit/viewport-activation-timings.ts +840 -0
- package/src/kit/viewport-editor-controls.ts +22 -0
- package/src/kit/viewport-presentation.ts +668 -0
- package/src/kit/viewport-surface-status.tsx +55 -0
- package/src/kit/wait-until.ts +37 -0
- package/src/kit/worker-call-metrics.ts +166 -0
- package/src/kit/workspace-areas.ts +191 -0
- package/src/kit/workspace-aux-commands.ts +11 -0
- package/src/kit/workspace-available-documents.ts +142 -0
- package/src/kit/workspace-core-utilities.ts +31 -0
- package/src/kit/workspace-document-ids.ts +59 -0
- package/src/kit/workspace-document-registry.ts +624 -0
- package/src/kit/workspace-document-restore.ts +146 -0
- package/src/kit/workspace-host-commands.ts +141 -0
- package/src/kit/workspace-persistence-gate.ts +40 -0
- package/src/kit/workspace-play-utilities.ts +44 -0
- package/src/kit/workspace-presets.ts +446 -0
- package/src/kit/workspace-regions.ts +276 -0
- package/src/kit/workspace-static-panels.ts +73 -0
- package/src/kit/workspace-status-registry.ts +121 -0
- package/src/kit/workspace-storage.ts +35 -0
- package/src/kit/workspace-style.ts +226 -0
- package/src/kit/workspace-utility-commands.ts +74 -0
- package/src/kit/workspace-utility-registry.ts +263 -0
- package/src/kit/world-adoption-event.ts +23 -0
- package/src/kit/world-adoption.ts +115 -0
- package/src/kit/world-canvas-viewport-state.ts +35 -0
- package/src/kit/world-document-routing.ts +104 -0
- package/src/kit/world-pan-state.ts +198 -0
- package/src/kit/write-pipe.ts +173 -0
- package/src/layout-arrangements.ts +5 -0
- package/src/layouts.tsx +108 -0
- package/src/looks.ts +16 -0
- package/src/project/output-roots.ts +73 -0
- package/src/project/tab-census.ts +155 -0
- package/src/project-tool-catalog.ts +104 -0
- package/src/selection.tsx +107 -0
- package/src/services.ts +18 -0
- package/src/session/build-report.ts +22 -0
- package/src/session/collaboration-types.ts +262 -0
- package/src/session/command-table.ts +327 -0
- package/src/session/discovery.ts +100 -0
- package/src/session/editor-brand.ts +48 -0
- package/src/session/editor-compatibility.ts +329 -0
- package/src/session/editor-control-lifecycle.ts +68 -0
- package/src/session/editor-control-protocol.ts +5 -0
- package/src/session/entrypoint-selection-readers.ts +66 -0
- package/src/session/entrypoint-selection-source.ts +120 -0
- package/src/session/game-css-scope.ts +30 -0
- package/src/session/hosted-attachment.ts +225 -0
- package/src/session/limited-view.ts +82 -0
- package/src/session/product-create.ts +24 -0
- package/src/session/product-locator.ts +478 -0
- package/src/session/project-module-url.ts +242 -0
- package/src/session/project-serving.ts +164 -0
- package/src/session/project-upgrade.ts +669 -0
- package/src/session/registry-format.ts +210 -0
- package/src/session/relative-path-guard.ts +56 -0
- package/src/session/scoped-game-css.ts +461 -0
- package/src/session/source-glob.ts +15 -0
- package/src/session/tool-contribution-convention.ts +123 -0
- package/src/session/workbench-locator.ts +712 -0
- package/src/session.ts +41 -0
- package/src/share.ts +160 -0
- package/src/source-analysis.ts +28 -0
- package/src/source-authoring.ts +439 -0
- package/src/tools/errors.ts +91 -0
- package/src/tools/provider-execution.ts +70 -0
- package/src/tools/registry.ts +341 -0
- package/src/tools/types.ts +159 -0
- package/src/transport.ts +100 -0
- package/src/types.ts +1693 -0
- package/src/views.ts +164 -0
- package/src/widgets/design-system.ts +93 -0
- package/src/widgets/editor-appearance.ts +151 -0
- package/src/widgets/editor-material.ts +83 -0
- package/src/widgets/icon-set-registry.ts +105 -0
- package/src/widgets/index.ts +71 -0
- package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
- package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
- package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
- package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
- package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
- package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
- package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
- package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
- package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
- package/src/widgets/inspector-widgets/color-utils.ts +201 -0
- package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
- package/src/widgets/inspector-widgets/index.ts +25 -0
- package/src/widgets/inspector-widgets/shared.tsx +140 -0
- package/src/widgets/interactive-edit-scope.ts +33 -0
- package/src/widgets/patterns/Dialog.tsx +140 -0
- package/src/widgets/patterns/Fields.tsx +44 -0
- package/src/widgets/patterns/List.tsx +25 -0
- package/src/widgets/patterns/StateSurface.tsx +40 -0
- package/src/widgets/patterns/Surfaces.tsx +122 -0
- package/src/widgets/patterns/Tabs.tsx +80 -0
- package/src/widgets/patterns/Toolbar.tsx +72 -0
- package/src/widgets/patterns/Tree.tsx +72 -0
- package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
- package/src/widgets/primitives/Button.tsx +62 -0
- package/src/widgets/primitives/ColorInput.tsx +78 -0
- package/src/widgets/primitives/DraftTextInput.tsx +63 -0
- package/src/widgets/primitives/EditorIcon.tsx +157 -0
- package/src/widgets/primitives/FormControls.tsx +88 -0
- package/src/widgets/primitives/HoverPreview.tsx +96 -0
- package/src/widgets/primitives/JsonInput.tsx +113 -0
- package/src/widgets/primitives/Layout.tsx +100 -0
- package/src/widgets/primitives/Menu.tsx +161 -0
- package/src/widgets/primitives/NumberInput.tsx +169 -0
- package/src/widgets/primitives/Panel.tsx +80 -0
- package/src/widgets/primitives/SectionHeader.tsx +77 -0
- package/src/widgets/primitives/Text.tsx +54 -0
- package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
- package/src/widgets/primitives/Tooltip.tsx +204 -0
- package/src/widgets/primitives/Vec3Input.tsx +70 -0
- package/src/widgets/primitives/banner-tones.ts +32 -0
- package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
- package/src/widgets/primitives/editor-icons.ts +254 -0
- package/src/widgets/primitives/panel-header-styles.ts +42 -0
- package/src/widgets/theme.ts +2841 -0
- package/src/widgets/z-index.ts +25 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Materialize a project's DECLARED asset packs (`asset-manifest.json`, D-AP1)
|
|
3
|
+
* from the `cloud-asset-library` Worker into a `StorageBackend`, recording
|
|
4
|
+
* the same provenance ledger (`.volter/assets.json`, D-AP3) every other
|
|
5
|
+
* materialization site writes.
|
|
6
|
+
*
|
|
7
|
+
* This is the D-AP4 mechanism: a project's DECLARED packs are materialized
|
|
8
|
+
* from the `cloud-asset-library` worker (existing manifest + object routes),
|
|
9
|
+
* writing the same ledger.
|
|
10
|
+
*
|
|
11
|
+
* Exported for the open path to CALL rather than
|
|
12
|
+
* wired into `seedIfEmpty` here, so any declared pack (in the template or an
|
|
13
|
+
* example) reuses it without change. The template's `asset-manifest.json` is
|
|
14
|
+
* `{"packs": {}}` today, so calling this against the shipped template is a
|
|
15
|
+
* documented, harmless no-op: `declared` reports 0 and nothing is fetched.
|
|
16
|
+
*
|
|
17
|
+
* FAIL CLOSED AND LOUD, never fabricate (the anti-shim rule): a network
|
|
18
|
+
* failure, a 404, or a sha256 mismatch aborts THAT entry only — nothing is
|
|
19
|
+
* written for it — and is reported in the returned result; it never blocks
|
|
20
|
+
* the other declared entries and never substitutes placeholder bytes.
|
|
21
|
+
*
|
|
22
|
+
* ## The expected digest comes from the DECLARATION, never from the response
|
|
23
|
+
*
|
|
24
|
+
* Every entry pins `sha256` in `asset-manifest.json` (see
|
|
25
|
+
* `asset-pack-manifest.ts`), and that pin is the only authority here. Reading
|
|
26
|
+
* the expected digest out of the worker's own `/v1/assets/<id>` record instead
|
|
27
|
+
* would authenticate nothing on the FIRST fetch — a moved or tampered library
|
|
28
|
+
* answers self-consistently (its record and its bytes agree), the bytes get
|
|
29
|
+
* written, and its digest becomes the project's permanent `sourceHash` in the
|
|
30
|
+
* ledger. So: the record's digest must MATCH the pin (a disagreement is a
|
|
31
|
+
* refusal naming the asset), the fetched bytes are verified against the pin,
|
|
32
|
+
* and the pin is what the ledger records.
|
|
33
|
+
*
|
|
34
|
+
* ## "Already materialized" means the FILE is there, not just the record
|
|
35
|
+
*
|
|
36
|
+
* Idempotent and retry-safe: an entry is skipped only when the ledger has it
|
|
37
|
+
* AND its destination exists. Skipping on the ledger record alone made the
|
|
38
|
+
* broken case unrepairable — a project whose bytes were deleted, or whose
|
|
39
|
+
* ledger outlived a failed write, reported success forever, and the reopen
|
|
40
|
+
* retry (`project-manager.ts`) plus the Retry banner both skipped exactly the
|
|
41
|
+
* entry they existed to refetch.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { sha256Hex } from '@volter/sdk/kit/bytes-codec';
|
|
45
|
+
import type { StorageBackend } from '@volter/sdk/kit/storage-types';
|
|
46
|
+
import { normalizeLedgerPath } from './asset-ledger';
|
|
47
|
+
import {
|
|
48
|
+
readAssetLedgerFromBackend,
|
|
49
|
+
recordAssetMaterializationInBackend,
|
|
50
|
+
} from './asset-ledger-backend';
|
|
51
|
+
/** Project-relative path of the pack declaration (D-AP1) — sibling of
|
|
52
|
+
* `volter.project.json` in the template/every example, and (per D-AP4) one of
|
|
53
|
+
* the plain-text files the browser build's seed writes alongside the rest
|
|
54
|
+
* of the template source. */
|
|
55
|
+
import {
|
|
56
|
+
ASSET_MANIFEST_PATH,
|
|
57
|
+
type FlattenedPackEntry,
|
|
58
|
+
flattenAssetPacks,
|
|
59
|
+
parseAssetPackManifest,
|
|
60
|
+
} from './asset-pack-manifest';
|
|
61
|
+
import {
|
|
62
|
+
cloudAssetObjectUrl,
|
|
63
|
+
DEFAULT_CLOUD_ASSET_BASE_URL,
|
|
64
|
+
fetchCloudAssetRecord,
|
|
65
|
+
} from './cloud-asset-client';
|
|
66
|
+
|
|
67
|
+
export { ASSET_MANIFEST_PATH };
|
|
68
|
+
|
|
69
|
+
export interface AssetPackMaterializationFailure {
|
|
70
|
+
readonly entry: FlattenedPackEntry;
|
|
71
|
+
readonly reason: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AssetPackMaterializationResult {
|
|
75
|
+
/** Total entries declared across every pack. */
|
|
76
|
+
readonly declared: number;
|
|
77
|
+
readonly materialized: FlattenedPackEntry[];
|
|
78
|
+
/** Recorded in the ledger AND present on disk — nothing to do, not a failure. */
|
|
79
|
+
readonly alreadyPresent: FlattenedPackEntry[];
|
|
80
|
+
readonly failed: AssetPackMaterializationFailure[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface MaterializeAssetPacksOptions {
|
|
84
|
+
/** Test-only override of the deployed worker's base URL. */
|
|
85
|
+
readonly baseUrl?: string;
|
|
86
|
+
readonly signal?: AbortSignal;
|
|
87
|
+
/** Called once per entry, after it is attempted. */
|
|
88
|
+
readonly onProgress?: (
|
|
89
|
+
entry: FlattenedPackEntry,
|
|
90
|
+
outcome: 'materialized' | 'already-present' | 'failed',
|
|
91
|
+
) => void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Materialize every pack entry `backend` declares in its own
|
|
96
|
+
* `asset-manifest.json` that is not already in the ledger.
|
|
97
|
+
*
|
|
98
|
+
* Reads the manifest from `backend` itself (not the repo), whichever backend
|
|
99
|
+
* the caller is already using.
|
|
100
|
+
*/
|
|
101
|
+
export async function materializeDeclaredAssetPacks(
|
|
102
|
+
backend: StorageBackend,
|
|
103
|
+
options: MaterializeAssetPacksOptions = {},
|
|
104
|
+
): Promise<AssetPackMaterializationResult> {
|
|
105
|
+
const entries = await declaredPackEntries(backend);
|
|
106
|
+
const result: AssetPackMaterializationResult = {
|
|
107
|
+
declared: entries.length,
|
|
108
|
+
materialized: [],
|
|
109
|
+
alreadyPresent: [],
|
|
110
|
+
failed: [],
|
|
111
|
+
};
|
|
112
|
+
if (entries.length === 0) return result;
|
|
113
|
+
|
|
114
|
+
const ledger = await readAssetLedgerFromBackend(backend);
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
if (options.signal?.aborted) break;
|
|
117
|
+
// A ledger record is a record, not the bytes — see the module header.
|
|
118
|
+
if (ledger[normalizeLedgerPath(entry.dest)] && (await backend.exists(entry.dest))) {
|
|
119
|
+
result.alreadyPresent.push(entry);
|
|
120
|
+
options.onProgress?.(entry, 'already-present');
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
await materializeOne(backend, entry, options);
|
|
125
|
+
result.materialized.push(entry);
|
|
126
|
+
options.onProgress?.(entry, 'materialized');
|
|
127
|
+
} catch (error) {
|
|
128
|
+
result.failed.push({ entry, reason: error instanceof Error ? error.message : String(error) });
|
|
129
|
+
options.onProgress?.(entry, 'failed');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function declaredPackEntries(backend: StorageBackend): Promise<FlattenedPackEntry[]> {
|
|
136
|
+
if (!(await backend.exists(ASSET_MANIFEST_PATH))) return [];
|
|
137
|
+
const raw = await backend.read(ASSET_MANIFEST_PATH);
|
|
138
|
+
let parsed: unknown;
|
|
139
|
+
try {
|
|
140
|
+
parsed = JSON.parse(raw);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`${ASSET_MANIFEST_PATH} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return flattenAssetPacks(parseAssetPackManifest(parsed));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function materializeOne(
|
|
150
|
+
backend: StorageBackend,
|
|
151
|
+
entry: FlattenedPackEntry,
|
|
152
|
+
options: MaterializeAssetPacksOptions,
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
const fetched = await fetchVerifiedPackEntry(entry, options);
|
|
155
|
+
await backend.write(entry.dest, fetched.bytes);
|
|
156
|
+
await recordAssetMaterializationInBackend(backend, {
|
|
157
|
+
destPath: entry.dest,
|
|
158
|
+
key: entry.key,
|
|
159
|
+
license: fetched.license,
|
|
160
|
+
// The PIN, not the library's reported digest — identical here by the
|
|
161
|
+
// check inside `fetchVerifiedPackEntry`, and the pin is the fact this
|
|
162
|
+
// project reviewed.
|
|
163
|
+
sourceSha256: entry.sha256,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The bytes of one declared pack entry, plus the library's license string. */
|
|
168
|
+
export interface VerifiedPackEntryBytes {
|
|
169
|
+
readonly bytes: Uint8Array;
|
|
170
|
+
/** The library's own license string, for the ledger. */
|
|
171
|
+
readonly license: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Fetch and VERIFY one declared pack entry, writing nothing.
|
|
176
|
+
*
|
|
177
|
+
* This is the whole network + trust sequence — record lookup, pin/record
|
|
178
|
+
* cross-check, byte fetch, digest verification — with the destination IO left
|
|
179
|
+
* to the caller, because the two materialization sites differ only in where the
|
|
180
|
+
* bytes land: this module writes through a `StorageBackend` (browser/hosted),
|
|
181
|
+
* and `packages/volter-cli/src/asset-packs.ts` writes to the filesystem for an
|
|
182
|
+
* on-disk project the editor's `add` command just declared a pack into. Extracted so neither
|
|
183
|
+
* site can drift from the other on the part that matters — a second copy of
|
|
184
|
+
* "fetch then trust" is exactly how trust-on-first-use gets reintroduced.
|
|
185
|
+
*
|
|
186
|
+
* Throws on every failure and never returns partial bytes.
|
|
187
|
+
*/
|
|
188
|
+
export async function fetchVerifiedPackEntry(
|
|
189
|
+
entry: FlattenedPackEntry,
|
|
190
|
+
options: MaterializeAssetPacksOptions = {},
|
|
191
|
+
): Promise<VerifiedPackEntryBytes> {
|
|
192
|
+
const separator = entry.key.indexOf(':');
|
|
193
|
+
if (separator < 0) throw new Error(`Malformed asset key "${entry.key}" (expected "source:id").`);
|
|
194
|
+
const source = entry.key.slice(0, separator);
|
|
195
|
+
const assetId = entry.key.slice(separator + 1);
|
|
196
|
+
if (source !== 'local') {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Asset key "${entry.key}": only the "local" (cloud-hosted) source can be materialized ` +
|
|
199
|
+
'from the cloud worker.',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const baseUrl = options.baseUrl ?? DEFAULT_CLOUD_ASSET_BASE_URL;
|
|
203
|
+
const fetchOptions = options.signal ? { baseUrl, signal: options.signal } : { baseUrl };
|
|
204
|
+
const record = await fetchCloudAssetRecord(assetId, fetchOptions);
|
|
205
|
+
if (!record) throw new Error(`"${entry.key}" is not hosted by the cloud asset library.`);
|
|
206
|
+
const file = record.files[0];
|
|
207
|
+
if (!file) throw new Error(`"${entry.key}" has no files in the cloud library.`);
|
|
208
|
+
if (file.dependencies.length > 0) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`"${entry.key}" has ${file.dependencies.length} dependency file(s) — a pack entry names ` +
|
|
211
|
+
'exactly one destination file.',
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
// The cloud record must AGREE with the checked-in pin before a byte is
|
|
215
|
+
// fetched. A library that has moved on is a legitimate event; silently
|
|
216
|
+
// adopting its new bytes is not (see the module header).
|
|
217
|
+
if (file.main.sha256 !== entry.sha256) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`"${entry.key}" → ${entry.dest}: the cloud library reports sha256:${file.main.sha256} but ` +
|
|
220
|
+
`${ASSET_MANIFEST_PATH} pins sha256:${entry.sha256}. Nothing was fetched or written; ` +
|
|
221
|
+
'the pin is only re-cut by a reviewed change to the declaration.',
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const url = cloudAssetObjectUrl(baseUrl, assetId, 0);
|
|
225
|
+
const response = await fetch(url, options.signal ? { signal: options.signal } : {});
|
|
226
|
+
if (!response.ok) throw new Error(`${url} returned ${response.status} ${response.statusText}.`);
|
|
227
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
228
|
+
const actual = await sha256Hex(bytes);
|
|
229
|
+
if (actual !== entry.sha256) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`"${entry.key}" → ${entry.dest}: ${url} delivered sha256:${actual} but the declaration ` +
|
|
232
|
+
`pins sha256:${entry.sha256}. Nothing was written.`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return { bytes, license: record.license };
|
|
236
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integer zoom for pixel art: a fractional factor resamples the art, and a
|
|
3
|
+
* resampled sheet is a blur with the correct pixels still in the file.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Largest scale that fits `src` inside `box` without resampling. */
|
|
7
|
+
export function integerFitScale(
|
|
8
|
+
srcWidth: number,
|
|
9
|
+
srcHeight: number,
|
|
10
|
+
boxWidth: number,
|
|
11
|
+
boxHeight: number,
|
|
12
|
+
): number {
|
|
13
|
+
if (srcWidth <= 0 || srcHeight <= 0 || boxWidth <= 0 || boxHeight <= 0) return 1;
|
|
14
|
+
const growX = srcWidth <= boxWidth ? Math.floor(boxWidth / srcWidth) : 0;
|
|
15
|
+
const growY = srcHeight <= boxHeight ? Math.floor(boxHeight / srcHeight) : 0;
|
|
16
|
+
const grow = Math.min(growX, growY);
|
|
17
|
+
if (grow >= 1) return grow;
|
|
18
|
+
return 1 / Math.max(Math.ceil(srcWidth / boxWidth), Math.ceil(srcHeight / boxHeight));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ImageScaleMode = 'fit' | 1 | 4 | 8;
|
|
22
|
+
|
|
23
|
+
export function resolveImageScale(
|
|
24
|
+
mode: ImageScaleMode,
|
|
25
|
+
srcWidth: number,
|
|
26
|
+
srcHeight: number,
|
|
27
|
+
boxWidth: number,
|
|
28
|
+
boxHeight: number,
|
|
29
|
+
): number {
|
|
30
|
+
return mode === 'fit' ? integerFitScale(srcWidth, srcHeight, boxWidth, boxHeight) : mode;
|
|
31
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { isContainedRelativePath } from '@volter/sdk/session/relative-path-guard';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export const assetImportSettingsSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
scale: z.number().positive().default(1),
|
|
7
|
+
sourceUnits: z.enum(['auto', 'mm', 'cm', 'm', 'in', 'ft']).default('auto'),
|
|
8
|
+
upAxis: z.enum(['auto', 'x', 'y', 'z']).default('auto'),
|
|
9
|
+
forwardAxis: z.enum(['auto', 'x', '-x', 'y', '-y', 'z', '-z']).default('auto'),
|
|
10
|
+
normals: z.enum(['preserve', 'generate', 'recalculate']).default('preserve'),
|
|
11
|
+
tangents: z.enum(['preserve', 'generate', 'discard']).default('preserve'),
|
|
12
|
+
materials: z.enum(['import', 'discard']).default('import'),
|
|
13
|
+
textures: z.enum(['copy', 'embed', 'discard']).default('copy'),
|
|
14
|
+
animations: z.array(z.string()).default([]),
|
|
15
|
+
meshOptimization: z.enum(['none', 'safe', 'aggressive']).default('safe'),
|
|
16
|
+
retainSource: z.boolean().default(true),
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
export type AssetImportSettings = z.infer<typeof assetImportSettingsSchema>;
|
|
20
|
+
|
|
21
|
+
const safePathSchema = z
|
|
22
|
+
.string()
|
|
23
|
+
.min(1)
|
|
24
|
+
.refine(isContainedRelativePath, 'Path must stay within the source/project root.');
|
|
25
|
+
|
|
26
|
+
const importFileSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
path: safePathSchema,
|
|
29
|
+
url: z.string().min(1),
|
|
30
|
+
hash: z.string().min(8),
|
|
31
|
+
size: z.number().int().nonnegative(),
|
|
32
|
+
role: z.enum(['primary', 'dependency', 'material', 'texture']),
|
|
33
|
+
})
|
|
34
|
+
.strict();
|
|
35
|
+
|
|
36
|
+
export const assetImportPlanSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
version: z.literal(1),
|
|
39
|
+
id: z.string().min(1),
|
|
40
|
+
identity: z
|
|
41
|
+
.object({
|
|
42
|
+
familyId: z.string().min(1),
|
|
43
|
+
variantId: z.string().min(1),
|
|
44
|
+
deliveryId: z.string().min(1),
|
|
45
|
+
source: z.string().min(1).optional(),
|
|
46
|
+
displayName: z.string().min(1).optional(),
|
|
47
|
+
})
|
|
48
|
+
.strict(),
|
|
49
|
+
sourceFiles: z.array(importFileSchema).min(1),
|
|
50
|
+
generatedFiles: z.array(safePathSchema),
|
|
51
|
+
destination: safePathSchema,
|
|
52
|
+
contentKind: z
|
|
53
|
+
.enum(['model', 'animation-only', 'image', 'audio', 'material', 'data'])
|
|
54
|
+
.default('model'),
|
|
55
|
+
targetSkeleton: z.string().optional(),
|
|
56
|
+
settings: assetImportSettingsSchema,
|
|
57
|
+
conflicts: z.array(
|
|
58
|
+
z
|
|
59
|
+
.object({ path: z.string(), resolution: z.enum(['reuse', 'update', 'rename', 'cancel']) })
|
|
60
|
+
.strict(),
|
|
61
|
+
),
|
|
62
|
+
attribution: z
|
|
63
|
+
.object({
|
|
64
|
+
sourceUrl: z.string(),
|
|
65
|
+
author: z.string(),
|
|
66
|
+
license: z.string(),
|
|
67
|
+
text: z.string(),
|
|
68
|
+
})
|
|
69
|
+
.strict(),
|
|
70
|
+
sourceHash: z.string().min(8),
|
|
71
|
+
runtimeReady: z.boolean(),
|
|
72
|
+
backend: z.enum(['native', 'three', 'blender']),
|
|
73
|
+
createdAt: z.string(),
|
|
74
|
+
})
|
|
75
|
+
.superRefine((plan, context) => {
|
|
76
|
+
if (plan.contentKind === 'animation-only' && plan.runtimeReady && !plan.targetSkeleton) {
|
|
77
|
+
context.addIssue({
|
|
78
|
+
code: 'custom',
|
|
79
|
+
path: ['targetSkeleton'],
|
|
80
|
+
message: 'Runtime-ready animation-only imports require a compatible target skeleton.',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
export type AssetImportPlan = z.infer<typeof assetImportPlanSchema>;
|
|
85
|
+
|
|
86
|
+
export const assetImportResultSchema = z
|
|
87
|
+
.object({
|
|
88
|
+
version: z.literal(1),
|
|
89
|
+
planId: z.string(),
|
|
90
|
+
status: z.enum(['imported', 'skipped', 'failed', 'cancelled']),
|
|
91
|
+
writtenFiles: z.array(
|
|
92
|
+
z
|
|
93
|
+
.object({ path: z.string(), hash: z.string(), size: z.number().int().nonnegative() })
|
|
94
|
+
.strict(),
|
|
95
|
+
),
|
|
96
|
+
skippedFiles: z.array(z.string()),
|
|
97
|
+
issues: z.array(
|
|
98
|
+
z.object({ code: z.string(), stage: z.string(), message: z.string() }).strict(),
|
|
99
|
+
),
|
|
100
|
+
})
|
|
101
|
+
.strict();
|
|
102
|
+
export type AssetImportResult = z.infer<typeof assetImportResultSchema>;
|
|
103
|
+
|
|
104
|
+
export type AssetImportStage = 'fetch' | 'convert' | 'validate' | 'commit';
|
|
105
|
+
export interface AssetImportProgress {
|
|
106
|
+
stage: AssetImportStage;
|
|
107
|
+
item: string;
|
|
108
|
+
loaded: number;
|
|
109
|
+
total: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type AssetConflictKind =
|
|
113
|
+
| 'same-identity-same-hash'
|
|
114
|
+
| 'same-identity-updated'
|
|
115
|
+
| 'different-identity-same-name'
|
|
116
|
+
| 'user-edited-output';
|
|
117
|
+
export function safeConflictResolution(
|
|
118
|
+
kind: AssetConflictKind,
|
|
119
|
+
): 'reuse' | 'update' | 'rename' | 'cancel' {
|
|
120
|
+
if (kind === 'same-identity-same-hash') return 'reuse';
|
|
121
|
+
if (kind === 'same-identity-updated') return 'update';
|
|
122
|
+
if (kind === 'different-identity-same-name') return 'rename';
|
|
123
|
+
return 'cancel';
|
|
124
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two mechanical halves of "a read-modify-write of one file never loses a
|
|
3
|
+
* record", shared by the ledger's two I/O sides (`../../server/
|
|
4
|
+
* asset-ledger-store.ts` over `node:fs`, `./asset-ledger-backend.ts` over a
|
|
5
|
+
* `StorageBackend`).
|
|
6
|
+
*
|
|
7
|
+
* Why the ledger needs this at all: `.volter/assets.json` (D-AP3) is updated by
|
|
8
|
+
* READING it, adding one entry, and writing the whole object back. Two of those
|
|
9
|
+
* in flight interleave — both read the old object, the later write wins, and the
|
|
10
|
+
* earlier record is gone with no error anywhere. Two materializations at once is
|
|
11
|
+
* ordinary, not exotic: two the editor's `edit` command sessions on one project, `npm run
|
|
12
|
+
* asset-packs:sync` while an editor is prewarming, two tabs on one project
|
|
13
|
+
* both retrying their declared packs on reopen.
|
|
14
|
+
*
|
|
15
|
+
* Neither half alone is enough, which is why both are here:
|
|
16
|
+
*
|
|
17
|
+
* - `createSerializer` — an in-process (in-realm) promise chain per key. This
|
|
18
|
+
* is what makes concurrency INSIDE one process correct, and it is the only
|
|
19
|
+
* mechanism that can, since a file lock cannot exclude its own holder.
|
|
20
|
+
* - `withExclusiveLock` — a lock FILE, which is the only thing a second
|
|
21
|
+
* process/realm can see. Injected I/O (`ExclusiveLockIO`) so the same
|
|
22
|
+
* waiting/stale-breaking policy runs over `node:fs` and over a
|
|
23
|
+
* `StorageBackend` without either being reimplemented.
|
|
24
|
+
*
|
|
25
|
+
* This module is deliberately a helper, not a system: no ownership of when a
|
|
26
|
+
* ledger is written, no retries of the caller's work, no state beyond the map a
|
|
27
|
+
* `createSerializer` call hands back.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** File operations `withExclusiveLock` needs. One lock path per instance. */
|
|
31
|
+
export interface ExclusiveLockIO {
|
|
32
|
+
/** Create the lock holding `content` iff absent. `false` when already held. */
|
|
33
|
+
createIfAbsent(content: string): Promise<boolean>;
|
|
34
|
+
/** The held lock's content, or `null` if it has since vanished. */
|
|
35
|
+
read(): Promise<string | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Delete the lock ONLY while it still holds `expected`. Must tolerate an
|
|
38
|
+
* already-absent lock, and must leave a lock holding anything else alone.
|
|
39
|
+
*
|
|
40
|
+
* There is deliberately no unconditional delete: every deletion in this
|
|
41
|
+
* module is either "release the lock I took" or "break the corpse I judged
|
|
42
|
+
* stale", and both name the exact content they mean. A caller that could
|
|
43
|
+
* delete whatever is there deletes ANOTHER writer's live lock whenever its
|
|
44
|
+
* own view is one step out of date — which is how two writers used to end up
|
|
45
|
+
* inside the critical section at once.
|
|
46
|
+
*
|
|
47
|
+
* The compare and the delete must be ATOMIC WITH RESPECT TO EACH OTHER, and
|
|
48
|
+
* that requirement is on the implementer, not on this module. A backend that
|
|
49
|
+
* reads, compares, then deletes as separate steps reintroduces the same
|
|
50
|
+
* defect one level down: two waiters both compare against the corpse, the
|
|
51
|
+
* first deletes it, acquires and enters, and the second's delete — already
|
|
52
|
+
* past its comparison — removes the winner's fresh lock and lets it in behind
|
|
53
|
+
* it. The two shipped backends each pay for this: the browser one runs inside
|
|
54
|
+
* the `navigator.locks` name `writeIfAbsent` takes, and the `node:fs` one has
|
|
55
|
+
* no atomic compare-and-delete at all and uses `removeIfHeldByTakeover`.
|
|
56
|
+
*/
|
|
57
|
+
removeIfHeldBy(expected: string): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The atomic single steps a takeover-based compare-and-delete needs. Each
|
|
62
|
+
* instance belongs to ONE attempt: the aside path is chosen by the caller and
|
|
63
|
+
* unique per attempt, so two concurrent attempts can never collide on it.
|
|
64
|
+
*
|
|
65
|
+
* On `node:fs` these are `rename`, `readFile`, `rm` and `writeFile` with the
|
|
66
|
+
* `wx` flag — every one a single syscall, which is the whole reason the
|
|
67
|
+
* algorithm below can be reasoned about at all.
|
|
68
|
+
*/
|
|
69
|
+
export interface LockTakeoverOps {
|
|
70
|
+
/**
|
|
71
|
+
* Atomically move the lock to this attempt's aside path. `false` when the
|
|
72
|
+
* lock was not there — which, when several attempts race, is how every
|
|
73
|
+
* attempt but one finds out that it lost.
|
|
74
|
+
*/
|
|
75
|
+
moveAside(): Promise<boolean>;
|
|
76
|
+
/** Content of the moved-aside lock; `null` if it is somehow not there. */
|
|
77
|
+
readAside(): Promise<string | null>;
|
|
78
|
+
deleteAside(): Promise<void>;
|
|
79
|
+
/** Put `content` back at the lock path, ONLY while it is still absent. */
|
|
80
|
+
restoreIfAbsent(content: string): Promise<boolean>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* `removeIfHeldBy` for a filesystem that has no atomic compare-and-delete:
|
|
85
|
+
* SEIZE the lock with one atomic move, and only then compare.
|
|
86
|
+
*
|
|
87
|
+
* What this buys, and it is the entire point: for a given lock, AT MOST ONE
|
|
88
|
+
* attempt can move it aside. Every other racer's `moveAside` reports `false`
|
|
89
|
+
* and deletes nothing, so it loops, re-reads, and sees the winner's fresh
|
|
90
|
+
* stamp. Read-then-delete could not offer this — its comparison is a snapshot,
|
|
91
|
+
* and a snapshot is stale by the time the delete runs.
|
|
92
|
+
*
|
|
93
|
+
* If the seized content is NOT the content this attempt judged, this attempt had
|
|
94
|
+
* no business taking it: put it straight back, then drop the aside copy.
|
|
95
|
+
*
|
|
96
|
+
* RESIDUAL — stated precisely, because it is not zero. Between the move and the
|
|
97
|
+
* restore, the lock path is briefly absent. A writer whose `createIfAbsent`
|
|
98
|
+
* lands in exactly that gap acquires while the lock's real holder is still
|
|
99
|
+
* inside its critical section. Reaching it takes two coincidences: the content
|
|
100
|
+
* must change between the caller's read and this move (the holder of a stamp
|
|
101
|
+
* older than `staleAfterMs` releasing, and a successor acquiring, inside that
|
|
102
|
+
* window), AND a third writer's create must land inside the restore window.
|
|
103
|
+
* POSIX offers no primitive that closes it — there is no atomic
|
|
104
|
+
* compare-and-delete — and closing it would take a lease/heartbeat protocol
|
|
105
|
+
* with a "your lock was compromised" callback, which this helper deliberately
|
|
106
|
+
* is not.
|
|
107
|
+
*/
|
|
108
|
+
export async function removeIfHeldByTakeover(
|
|
109
|
+
ops: LockTakeoverOps,
|
|
110
|
+
expected: string,
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
if (!(await ops.moveAside())) return;
|
|
113
|
+
const seized = await ops.readAside();
|
|
114
|
+
if (seized !== null && seized !== expected) await ops.restoreIfAbsent(seized);
|
|
115
|
+
await ops.deleteAside();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface ExclusiveLockOptions {
|
|
119
|
+
/**
|
|
120
|
+
* How long a lock may be held before another writer BREAKS it.
|
|
121
|
+
*
|
|
122
|
+
* Breaking, rather than failing, is deliberate: the holder of a lock file is
|
|
123
|
+
* usually a process that has since died (Ctrl-C in the middle of
|
|
124
|
+
* `asset-packs:sync`, a closed tab), and a provenance record must not be
|
|
125
|
+
* refused forever because of a corpse. A real holder finishes in single-digit
|
|
126
|
+
* milliseconds, so this is orders of magnitude above the honest wait — and it
|
|
127
|
+
* is also the loop bound, so waiting can never deadlock.
|
|
128
|
+
*/
|
|
129
|
+
readonly staleAfterMs?: number;
|
|
130
|
+
/** Poll interval while a live lock is held. */
|
|
131
|
+
readonly retryDelayMs?: number;
|
|
132
|
+
/** Recorded in the lock so a human can see who holds it. */
|
|
133
|
+
readonly owner?: string;
|
|
134
|
+
readonly now?: () => number;
|
|
135
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* The per-ATTEMPT unique part of the stamp. Injectable so a test can name the
|
|
138
|
+
* attempts it schedules; never for production use.
|
|
139
|
+
*
|
|
140
|
+
* It is load-bearing rather than cosmetic: `removeIfHeldBy` can only tell "my
|
|
141
|
+
* lock" from "someone else's lock" if no two attempts can ever write the same
|
|
142
|
+
* stamp, and `{at, owner}` alone repeats whenever two writers with the same
|
|
143
|
+
* owner name land in the same millisecond.
|
|
144
|
+
*/
|
|
145
|
+
readonly token?: () => string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const DEFAULT_STALE_AFTER_MS = 10_000;
|
|
149
|
+
const DEFAULT_RETRY_DELAY_MS = 15;
|
|
150
|
+
|
|
151
|
+
/** Same guarded form as `../storage/index.ts` — `crypto` is absent in a
|
|
152
|
+
* non-secure browser context, and a missing token must not fail a write. */
|
|
153
|
+
function defaultToken(): string {
|
|
154
|
+
return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Run `work` while holding an exclusive on-disk lock, always releasing it. */
|
|
158
|
+
export async function withExclusiveLock<T>(
|
|
159
|
+
io: ExclusiveLockIO,
|
|
160
|
+
work: () => Promise<T>,
|
|
161
|
+
options: ExclusiveLockOptions = {},
|
|
162
|
+
): Promise<T> {
|
|
163
|
+
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
164
|
+
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
165
|
+
const now = options.now ?? Date.now;
|
|
166
|
+
const sleep =
|
|
167
|
+
options.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
|
|
168
|
+
const token = options.token ?? defaultToken;
|
|
169
|
+
|
|
170
|
+
for (;;) {
|
|
171
|
+
const stamp = JSON.stringify({ at: now(), owner: options.owner ?? 'unknown', token: token() });
|
|
172
|
+
if (await io.createIfAbsent(stamp)) {
|
|
173
|
+
try {
|
|
174
|
+
return await work();
|
|
175
|
+
} finally {
|
|
176
|
+
// Release only OUR lock: if a waiter judged it stale and its successor
|
|
177
|
+
// has already taken the lock, `stamp` is no longer the content and this
|
|
178
|
+
// is a no-op. An unconditional delete here would hand the successor's
|
|
179
|
+
// lock to whoever came next.
|
|
180
|
+
await io.removeIfHeldBy(stamp);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const held = await io.read();
|
|
184
|
+
// Gone between the failed create and this read — race the create again.
|
|
185
|
+
if (held === null) continue;
|
|
186
|
+
if (now() - lockHeldSince(held) > staleAfterMs) {
|
|
187
|
+
// Break the exact corpse we judged, not "whatever is there now" — an
|
|
188
|
+
// unconditional delete here removes the lock of whoever broke this same
|
|
189
|
+
// corpse a step ahead of us, and lets us walk in behind them.
|
|
190
|
+
//
|
|
191
|
+
// `held` is a SNAPSHOT: by the time the io acts on it, another waiter may
|
|
192
|
+
// have broken this corpse and acquired. That is why `removeIfHeldBy` is
|
|
193
|
+
// required to compare and delete atomically (see its doc comment) rather
|
|
194
|
+
// than being handed a "delete if this is still there" hint it can act on
|
|
195
|
+
// late — the atomicity is what makes at most one waiter break one corpse.
|
|
196
|
+
// The entrant is then decided only by `createIfAbsent`, and the loser of
|
|
197
|
+
// that race loops, re-reads, and sees the winner's fresh stamp.
|
|
198
|
+
await io.removeIfHeldBy(held);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
await sleep(retryDelayMs);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* When the holder took the lock. An unreadable stamp reports epoch 0, i.e.
|
|
207
|
+
* immediately stale: a lock whose age cannot be established cannot be waited
|
|
208
|
+
* out honestly, and refusing to write provenance because of an unparseable
|
|
209
|
+
* scratch file would be the worse failure.
|
|
210
|
+
*/
|
|
211
|
+
function lockHeldSince(content: string): number {
|
|
212
|
+
try {
|
|
213
|
+
const parsed = JSON.parse(content) as { at?: unknown };
|
|
214
|
+
return typeof parsed.at === 'number' ? parsed.at : 0;
|
|
215
|
+
} catch {
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* A per-key promise chain: `serialize(key, work)` starts `work` only after
|
|
222
|
+
* every earlier `work` for that key has settled (rejections included — one
|
|
223
|
+
* failed write must not wedge the queue).
|
|
224
|
+
*
|
|
225
|
+
* Each call returns its OWN map, so two independent callers can never collide
|
|
226
|
+
* on a key that happens to spell the same.
|
|
227
|
+
*/
|
|
228
|
+
export function createSerializer(): <T>(key: string, work: () => Promise<T>) => Promise<T> {
|
|
229
|
+
const tails = new Map<string, Promise<unknown>>();
|
|
230
|
+
return <T>(key: string, work: () => Promise<T>): Promise<T> => {
|
|
231
|
+
const previous = tails.get(key) ?? Promise.resolve();
|
|
232
|
+
const result = previous.then(work, work);
|
|
233
|
+
// Swallow only on the CHAIN copy — the returned promise still rejects.
|
|
234
|
+
const tail = result.then(
|
|
235
|
+
() => undefined,
|
|
236
|
+
() => undefined,
|
|
237
|
+
);
|
|
238
|
+
tails.set(key, tail);
|
|
239
|
+
void tail.then(() => {
|
|
240
|
+
if (tails.get(key) === tail) tails.delete(key);
|
|
241
|
+
});
|
|
242
|
+
return result;
|
|
243
|
+
};
|
|
244
|
+
}
|