@lapis-notes/api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE.md +4 -0
- package/README.md +43 -0
- package/dist/__tests__/data-adapter-conformance.js +205 -0
- package/dist/__tests__/lapis-runtime-manifest.js +32 -0
- package/dist/__tests__/svelte-internal-client.d.ts +3 -0
- package/dist/agent-result-views.d.ts +52 -0
- package/dist/agent-result-views.js +89 -0
- package/dist/agent-skills.d.ts +99 -0
- package/dist/agent-skills.js +182 -0
- package/dist/agent-tools.d.ts +92 -0
- package/dist/agent-tools.js +139 -0
- package/dist/app-database-host.d.ts +4 -0
- package/dist/app-database-host.js +4 -0
- package/dist/app-shell-plugin-persistence.d.ts +15 -0
- package/dist/app-shell-plugin-persistence.js +34 -0
- package/dist/app-url.d.ts +54 -0
- package/dist/app-url.js +269 -0
- package/dist/application-compatibility.d.ts +31 -0
- package/dist/application-compatibility.js +64 -0
- package/dist/application-state.svelte.d.ts +14 -0
- package/dist/application-state.svelte.js +30 -0
- package/dist/assets/lapis-logo.svg +471 -0
- package/dist/bases.d.ts +281 -0
- package/dist/bases.js +475 -0
- package/dist/cache.svelte.d.ts +569 -0
- package/dist/cache.svelte.js +1433 -0
- package/dist/command.svelte.d.ts +337 -0
- package/dist/command.svelte.js +571 -0
- package/dist/compat.d.ts +29 -0
- package/dist/compat.js +30 -0
- package/dist/components/configuration/configuration.css +199 -0
- package/dist/components/configuration/date-setting.svelte +71 -0
- package/dist/components/configuration/date-setting.svelte.d.ts +8 -0
- package/dist/components/configuration/list-setting.svelte +128 -0
- package/dist/components/configuration/list-setting.svelte.d.ts +13 -0
- package/dist/components/configuration/object-array-row-grip.svelte +78 -0
- package/dist/components/configuration/object-array-row-grip.svelte.d.ts +9 -0
- package/dist/components/configuration/object-array-row.svelte +163 -0
- package/dist/components/configuration/object-array-row.svelte.d.ts +19 -0
- package/dist/components/configuration/object-array-setting.svelte +196 -0
- package/dist/components/configuration/object-array-setting.svelte.d.ts +15 -0
- package/dist/components/configuration/object-array-table-dnd.d.ts +8 -0
- package/dist/components/configuration/object-array-table-dnd.js +10 -0
- package/dist/components/configuration/object-array-utils.d.ts +11 -0
- package/dist/components/configuration/object-array-utils.js +103 -0
- package/dist/components/configuration/object-grid-setting.svelte +79 -0
- package/dist/components/configuration/object-grid-setting.svelte.d.ts +10 -0
- package/dist/components/configuration/object-map-row.svelte +147 -0
- package/dist/components/configuration/object-map-row.svelte.d.ts +15 -0
- package/dist/components/configuration/object-map-setting.svelte +137 -0
- package/dist/components/configuration/object-map-setting.svelte.d.ts +14 -0
- package/dist/components/configuration/object-map-types.d.ts +5 -0
- package/dist/components/configuration/object-map-types.js +1 -0
- package/dist/components/configuration/options-combobox-setting.svelte +129 -0
- package/dist/components/configuration/options-combobox-setting.svelte.d.ts +12 -0
- package/dist/components/copy-button/button.svelte +93 -0
- package/dist/components/copy-button/button.svelte.d.ts +13 -0
- package/dist/components/copy-button/index.d.ts +2 -0
- package/dist/components/copy-button/index.js +2 -0
- package/dist/components/editor/editor.css +565 -0
- package/dist/components/editor/editor.d.ts +33 -0
- package/dist/components/editor/editor.js +132 -0
- package/dist/components/editor/editor.svelte +176 -0
- package/dist/components/editor/editor.svelte.d.ts +15 -0
- package/dist/components/editor/embedded-editor-surface.d.ts +12 -0
- package/dist/components/editor/embedded-editor-surface.js +22 -0
- package/dist/components/editor/embedded-editor-surface.svelte +115 -0
- package/dist/components/editor/embedded-editor-surface.svelte.d.ts +17 -0
- package/dist/components/editor/extensions/autocomplete/index.d.ts +2 -0
- package/dist/components/editor/extensions/autocomplete/index.js +2 -0
- package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.d.ts +16 -0
- package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.js +34 -0
- package/dist/components/editor/extensions/autocomplete/vault-completion-sources.d.ts +27 -0
- package/dist/components/editor/extensions/autocomplete/vault-completion-sources.js +55 -0
- package/dist/components/editor/extensions/class-highlighter/index.d.ts +1 -0
- package/dist/components/editor/extensions/class-highlighter/index.js +47 -0
- package/dist/components/editor/extensions/fold-gutter/index.d.ts +8 -0
- package/dist/components/editor/extensions/fold-gutter/index.js +40 -0
- package/dist/components/editor/extensions/gutter-line-style-sync/index.d.ts +2 -0
- package/dist/components/editor/extensions/gutter-line-style-sync/index.js +118 -0
- package/dist/components/editor/extensions/lint/index.d.ts +4 -0
- package/dist/components/editor/extensions/lint/index.js +4 -0
- package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.d.ts +26 -0
- package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.js +101 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.d.ts +8 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.js +7 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.d.ts +20 -0
- package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.js +47 -0
- package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.d.ts +9 -0
- package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.js +373 -0
- package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.d.ts +31 -0
- package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.js +191 -0
- package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte +176 -0
- package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte.d.ts +15 -0
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.d.ts +11 -0
- package/dist/components/editor/extensions/lint/lint-tooltip-actions.js +21 -0
- package/dist/components/editor/extensions/lint/mount-lint-tooltip.d.ts +16 -0
- package/dist/components/editor/extensions/lint/mount-lint-tooltip.js +60 -0
- package/dist/components/editor/extensions/search/dom.d.ts +2 -0
- package/dist/components/editor/extensions/search/dom.js +6 -0
- package/dist/components/editor/extensions/search/index.d.ts +35 -0
- package/dist/components/editor/extensions/search/index.js +44 -0
- package/dist/components/editor/extensions/search/search.svelte +335 -0
- package/dist/components/editor/extensions/search/search.svelte.d.ts +8 -0
- package/dist/components/editor/index.d.ts +9 -0
- package/dist/components/editor/index.js +9 -0
- package/dist/components/editor/language-service/dom.d.ts +1 -0
- package/dist/components/editor/language-service/dom.js +6 -0
- package/dist/components/editor/language-service/index.d.ts +28 -0
- package/dist/components/editor/language-service/index.js +342 -0
- package/dist/components/empty-view/empty-view.css +39 -0
- package/dist/components/empty-view/empty-view.svelte +26 -0
- package/dist/components/empty-view/empty-view.svelte.d.ts +9 -0
- package/dist/components/empty-view/index.d.ts +2 -0
- package/dist/components/empty-view/index.js +2 -0
- package/dist/components/icon/icon-list.css +74 -0
- package/dist/components/icon/icon-list.svelte +213 -0
- package/dist/components/icon/icon-list.svelte.d.ts +11 -0
- package/dist/components/icon/icon.svelte +15 -0
- package/dist/components/icon/icon.svelte.d.ts +7 -0
- package/dist/components/icon/index.d.ts +3 -0
- package/dist/components/icon/index.js +3 -0
- package/dist/components/menu/context-menu.svelte +89 -0
- package/dist/components/menu/context-menu.svelte.d.ts +11 -0
- package/dist/components/menu/drawer-menu.svelte +83 -0
- package/dist/components/menu/drawer-menu.svelte.d.ts +8 -0
- package/dist/components/menu/dropdown-menu.svelte +89 -0
- package/dist/components/menu/dropdown-menu.svelte.d.ts +11 -0
- package/dist/components/menu/index.d.ts +6 -0
- package/dist/components/menu/index.js +6 -0
- package/dist/components/menu/menu.css +139 -0
- package/dist/components/menu/menu.svelte +37 -0
- package/dist/components/menu/menu.svelte.d.ts +11 -0
- package/dist/components/menu/mobile-menu-query.svelte.d.ts +3 -0
- package/dist/components/menu/mobile-menu-query.svelte.js +21 -0
- package/dist/components/menu/popover-menu.svelte +90 -0
- package/dist/components/menu/popover-menu.svelte.d.ts +10 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.js +2 -0
- package/dist/components/select/select.svelte +72 -0
- package/dist/components/select/select.svelte.d.ts +25 -0
- package/dist/configuration-option-source-providers.d.ts +17 -0
- package/dist/configuration-option-source-providers.js +59 -0
- package/dist/configuration-option-source-registry.d.ts +59 -0
- package/dist/configuration-option-source-registry.js +123 -0
- package/dist/configuration.svelte.d.ts +258 -0
- package/dist/configuration.svelte.js +887 -0
- package/dist/context-keys.svelte.d.ts +42 -0
- package/dist/context-keys.svelte.js +278 -0
- package/dist/context.svelte.d.ts +247 -0
- package/dist/context.svelte.js +540 -0
- package/dist/daily-document-provider.d.ts +38 -0
- package/dist/daily-document-provider.js +176 -0
- package/dist/design-core-button-types.d.ts +7 -0
- package/dist/design-core-button-types.js +1 -0
- package/dist/diagnostics.d.ts +93 -0
- package/dist/diagnostics.js +84 -0
- package/dist/dialog-portal.d.ts +3 -0
- package/dist/dialog-portal.js +3 -0
- package/dist/docs-api.d.ts +44 -0
- package/dist/docs-api.js +29 -0
- package/dist/editor-view-registry.d.ts +35 -0
- package/dist/editor-view-registry.js +112 -0
- package/dist/editor.svelte.d.ts +126 -0
- package/dist/editor.svelte.js +454 -0
- package/dist/embed-registry.d.ts +15 -0
- package/dist/embed-registry.js +16 -0
- package/dist/enhance.d.ts +111 -0
- package/dist/enhance.js +309 -0
- package/dist/events.d.ts +28 -0
- package/dist/events.js +49 -0
- package/dist/file-manager.d.ts +90 -0
- package/dist/file-manager.js +289 -0
- package/dist/file-tools/app-tools.d.ts +3 -0
- package/dist/file-tools/app-tools.js +242 -0
- package/dist/file-tools/index.d.ts +3 -0
- package/dist/file-tools/index.js +3 -0
- package/dist/file-tools/paths.d.ts +14 -0
- package/dist/file-tools/paths.js +33 -0
- package/dist/file-tools/vault-operations.d.ts +3 -0
- package/dist/file-tools/vault-operations.js +60 -0
- package/dist/generated/plugin-host-modules.schema.generated.d.ts +371 -0
- package/dist/generated/plugin-host-modules.schema.generated.js +374 -0
- package/dist/glob.d.ts +15 -0
- package/dist/glob.js +68 -0
- package/dist/history.svelte.d.ts +24 -0
- package/dist/history.svelte.js +74 -0
- package/dist/hooks/createVirtualizer.svelte.d.ts +7 -0
- package/dist/hooks/createVirtualizer.svelte.js +70 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/is-mobile.svelte.d.ts +5 -0
- package/dist/hooks/is-mobile.svelte.js +23 -0
- package/dist/hooks/mountComponent.svelte.d.ts +7 -0
- package/dist/hooks/mountComponent.svelte.js +16 -0
- package/dist/hooks/use-text-highlight.d.ts +17 -0
- package/dist/hooks/use-text-highlight.js +82 -0
- package/dist/hooks/useBoundingRect.svelte.d.ts +11 -0
- package/dist/hooks/useBoundingRect.svelte.js +46 -0
- package/dist/icons/index.d.ts +57 -0
- package/dist/icons/index.js +288 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +87 -0
- package/dist/language-service/index.d.ts +9 -0
- package/dist/language-service/index.js +7 -0
- package/dist/language-service/manager.d.ts +47 -0
- package/dist/language-service/manager.js +404 -0
- package/dist/language-service/types.d.ts +92 -0
- package/dist/language-service/types.js +1 -0
- package/dist/language-service/virtual-document.d.ts +11 -0
- package/dist/language-service/virtual-document.js +45 -0
- package/dist/language-service/worker-protocol.d.ts +63 -0
- package/dist/language-service/worker-protocol.js +1 -0
- package/dist/language-service/worker-provider.d.ts +9 -0
- package/dist/language-service/worker-provider.js +38 -0
- package/dist/lapis-extension.d.ts +244 -0
- package/dist/lapis-extension.js +591 -0
- package/dist/links.d.ts +34 -0
- package/dist/links.js +191 -0
- package/dist/loaders.d.ts +9 -0
- package/dist/loaders.js +59 -0
- package/dist/localization-manager.svelte.d.ts +56 -0
- package/dist/localization-manager.svelte.js +157 -0
- package/dist/logging.d.ts +172 -0
- package/dist/logging.js +352 -0
- package/dist/markdown-extension-registry.d.ts +31 -0
- package/dist/markdown-extension-registry.js +31 -0
- package/dist/markdown-file-surface-registry.d.ts +25 -0
- package/dist/markdown-file-surface-registry.js +23 -0
- package/dist/markdown.d.ts +205 -0
- package/dist/markdown.js +147 -0
- package/dist/menu-utils.d.ts +1 -0
- package/dist/menu-utils.js +47 -0
- package/dist/menu.svelte.d.ts +121 -0
- package/dist/menu.svelte.js +295 -0
- package/dist/metadata-value.d.ts +10 -0
- package/dist/metadata-value.js +173 -0
- package/dist/metadata.svelte.d.ts +106 -0
- package/dist/metadata.svelte.js +608 -0
- package/dist/notifications.d.ts +209 -0
- package/dist/notifications.js +338 -0
- package/dist/open-file.d.ts +23 -0
- package/dist/open-file.js +102 -0
- package/dist/platform.d.ts +86 -0
- package/dist/platform.js +86 -0
- package/dist/plugin-asset-server.d.ts +60 -0
- package/dist/plugin-asset-server.js +222 -0
- package/dist/plugin-assets.d.ts +4 -0
- package/dist/plugin-assets.js +3 -0
- package/dist/plugin-capability-facade.d.ts +104 -0
- package/dist/plugin-capability-facade.js +61 -0
- package/dist/plugin-dependency-resolver.d.ts +36 -0
- package/dist/plugin-dependency-resolver.js +54 -0
- package/dist/plugin-dependency-scanner.d.ts +9 -0
- package/dist/plugin-dependency-scanner.js +274 -0
- package/dist/plugin-distribution/canonical-json.d.ts +1 -0
- package/dist/plugin-distribution/canonical-json.js +40 -0
- package/dist/plugin-distribution/compatibility.d.ts +5 -0
- package/dist/plugin-distribution/compatibility.js +38 -0
- package/dist/plugin-distribution/errors.d.ts +11 -0
- package/dist/plugin-distribution/errors.js +13 -0
- package/dist/plugin-distribution/hashes.d.ts +2 -0
- package/dist/plugin-distribution/hashes.js +17 -0
- package/dist/plugin-distribution/index.d.ts +18 -0
- package/dist/plugin-distribution/index.js +18 -0
- package/dist/plugin-distribution/installed-plugin-state.d.ts +20 -0
- package/dist/plugin-distribution/installed-plugin-state.js +73 -0
- package/dist/plugin-distribution/installer.d.ts +89 -0
- package/dist/plugin-distribution/installer.js +395 -0
- package/dist/plugin-distribution/manager.d.ts +128 -0
- package/dist/plugin-distribution/manager.js +358 -0
- package/dist/plugin-distribution/path-safety.d.ts +2 -0
- package/dist/plugin-distribution/path-safety.js +20 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.d.ts +28 -0
- package/dist/plugin-distribution/plugin-bundle-verifier.js +96 -0
- package/dist/plugin-distribution/plugin-bundle-worker.d.ts +34 -0
- package/dist/plugin-distribution/plugin-bundle-worker.js +99 -0
- package/dist/plugin-distribution/plugin-bundle.d.ts +16 -0
- package/dist/plugin-distribution/plugin-bundle.js +244 -0
- package/dist/plugin-distribution/plugin-bundle.worker.d.ts +1 -0
- package/dist/plugin-distribution/plugin-bundle.worker.js +38 -0
- package/dist/plugin-distribution/registry-cache.d.ts +26 -0
- package/dist/plugin-distribution/registry-cache.js +85 -0
- package/dist/plugin-distribution/registry-client.d.ts +36 -0
- package/dist/plugin-distribution/registry-client.js +115 -0
- package/dist/plugin-distribution/reserved-ids.d.ts +4 -0
- package/dist/plugin-distribution/reserved-ids.js +10 -0
- package/dist/plugin-distribution/runtime-validation.d.ts +14 -0
- package/dist/plugin-distribution/runtime-validation.js +388 -0
- package/dist/plugin-distribution/schemas.d.ts +4895 -0
- package/dist/plugin-distribution/schemas.js +299 -0
- package/dist/plugin-distribution/signing.d.ts +5 -0
- package/dist/plugin-distribution/signing.js +38 -0
- package/dist/plugin-distribution/types.d.ts +217 -0
- package/dist/plugin-distribution/types.js +1 -0
- package/dist/plugin-install-progress.d.ts +15 -0
- package/dist/plugin-install-progress.js +86 -0
- package/dist/plugin-manager.d.ts +306 -0
- package/dist/plugin-manager.js +2848 -0
- package/dist/plugin-runtime-entry.d.ts +19 -0
- package/dist/plugin-runtime-entry.js +135 -0
- package/dist/plugin.d.ts +350 -0
- package/dist/plugin.js +754 -0
- package/dist/popover.d.ts +20 -0
- package/dist/popover.js +32 -0
- package/dist/promise-worker.d.ts +22 -0
- package/dist/promise-worker.js +65 -0
- package/dist/prompt-confirm.d.ts +9 -0
- package/dist/prompt-confirm.js +33 -0
- package/dist/search-document-provider.d.ts +45 -0
- package/dist/search-document-provider.js +54 -0
- package/dist/search-query/index.d.ts +78 -0
- package/dist/search-query/index.js +426 -0
- package/dist/search-query/parser.d.ts +3 -0
- package/dist/search-query/parser.js +16 -0
- package/dist/search-query/parser.terms.js +26 -0
- package/dist/search-query/search-query.grammar +128 -0
- package/dist/search.d.ts +19 -0
- package/dist/search.js +104 -0
- package/dist/setting-section-layout.d.ts +19 -0
- package/dist/setting-section-layout.js +194 -0
- package/dist/settings.svelte.d.ts +474 -0
- package/dist/settings.svelte.js +1421 -0
- package/dist/source-text-file-view.d.ts +25 -0
- package/dist/source-text-file-view.js +78 -0
- package/dist/status-bar.svelte.d.ts +29 -0
- package/dist/status-bar.svelte.js +72 -0
- package/dist/storage/app-database.d.ts +860 -0
- package/dist/storage/app-database.js +2372 -0
- package/dist/storage/blob-store.d.ts +12 -0
- package/dist/storage/blob-store.js +1 -0
- package/dist/storage/browser-app-database-coordination.d.ts +35 -0
- package/dist/storage/browser-app-database-coordination.js +207 -0
- package/dist/storage/browser-coordinated-app-database.d.ts +109 -0
- package/dist/storage/browser-coordinated-app-database.js +704 -0
- package/dist/storage/browser.d.ts +153 -0
- package/dist/storage/browser.js +794 -0
- package/dist/storage/desktop-native.d.ts +184 -0
- package/dist/storage/desktop-native.js +415 -0
- package/dist/storage/fs.d.ts +188 -0
- package/dist/storage/fs.js +307 -0
- package/dist/storage/index-projection.d.ts +236 -0
- package/dist/storage/index-projection.js +402 -0
- package/dist/storage/index.d.ts +17 -0
- package/dist/storage/index.js +17 -0
- package/dist/storage/keyval.d.ts +94 -0
- package/dist/storage/keyval.js +199 -0
- package/dist/storage/memory-vault-adapter.d.ts +44 -0
- package/dist/storage/memory-vault-adapter.js +351 -0
- package/dist/storage/native-vault-bootstrap-kv.d.ts +22 -0
- package/dist/storage/native-vault-bootstrap-kv.js +96 -0
- package/dist/storage/path.d.ts +22 -0
- package/dist/storage/path.js +110 -0
- package/dist/storage/search-embedding-provider.d.ts +75 -0
- package/dist/storage/search-embedding-provider.js +446 -0
- package/dist/storage/search-embedding-worker.d.ts +1 -0
- package/dist/storage/search-embedding-worker.js +63 -0
- package/dist/storage/search-query-evaluator.d.ts +14 -0
- package/dist/storage/search-query-evaluator.js +678 -0
- package/dist/storage/task-projection.d.ts +152 -0
- package/dist/storage/task-projection.js +154 -0
- package/dist/storage/turso-app-database.d.ts +150 -0
- package/dist/storage/turso-app-database.js +2671 -0
- package/dist/storage/vault-session.d.ts +37 -0
- package/dist/storage/vault-session.js +196 -0
- package/dist/storage/vault-state.d.ts +74 -0
- package/dist/storage/vault-state.js +181 -0
- package/dist/storage/vault.svelte.d.ts +257 -0
- package/dist/storage/vault.svelte.js +661 -0
- package/dist/suggest.d.ts +193 -0
- package/dist/suggest.js +391 -0
- package/dist/tasks.d.ts +7 -0
- package/dist/tasks.js +18 -0
- package/dist/telemetry.d.ts +116 -0
- package/dist/telemetry.js +103 -0
- package/dist/utils.d.ts +55 -0
- package/dist/utils.js +314 -0
- package/dist/vault-api.d.ts +5 -0
- package/dist/vault-api.js +3 -0
- package/dist/vault-maintenance-commands.d.ts +5 -0
- package/dist/vault-maintenance-commands.js +32 -0
- package/dist/view.svelte.d.ts +215 -0
- package/dist/view.svelte.js +381 -0
- package/dist/vite-env.d.ts +2 -0
- package/dist/workspace-host-internal.d.ts +7 -0
- package/dist/workspace-host-internal.js +7 -0
- package/dist/workspace-host.d.ts +10 -0
- package/dist/workspace-host.js +14 -0
- package/dist/workspace-layout-commands.d.ts +3 -0
- package/dist/workspace-layout-commands.js +72 -0
- package/dist/workspace-layout-normalizer.d.ts +92 -0
- package/dist/workspace-layout-normalizer.js +301 -0
- package/dist/workspace-layouts.d.ts +7 -0
- package/dist/workspace-layouts.js +23 -0
- package/dist/workspace-trust.d.ts +32 -0
- package/dist/workspace-trust.js +72 -0
- package/dist/workspace.svelte.d.ts +796 -0
- package/dist/workspace.svelte.js +4380 -0
- package/package.json +240 -0
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { basename, dirname, joinPath, normalizePath } from "./path";
|
|
2
|
+
import { clearVaultScopedState, deleteVaultProfile, generateBrowserVaultId, getDefaultVaultStateStore, getVaultProfile, listVaultProfiles, saveVaultProfile, ScopedVaultStore, } from "./vault-state";
|
|
3
|
+
function createFsError(code, path) {
|
|
4
|
+
const error = new Error(`${code}: ${path}`);
|
|
5
|
+
error.code = code;
|
|
6
|
+
return error;
|
|
7
|
+
}
|
|
8
|
+
function isNotFound(error) {
|
|
9
|
+
const err = error;
|
|
10
|
+
return err?.name === "NotFoundError" || err?.code === "ENOENT";
|
|
11
|
+
}
|
|
12
|
+
const STALE_BROWSER_HANDLE_MESSAGE = /state cached in an interface object was made but the state had changed/i;
|
|
13
|
+
function isRecoverableBrowserHandleError(error) {
|
|
14
|
+
let current = error;
|
|
15
|
+
while (current) {
|
|
16
|
+
const err = current;
|
|
17
|
+
if (err?.name === "NotFoundError" ||
|
|
18
|
+
err?.name === "NotReadableError" ||
|
|
19
|
+
err?.name === "NoModificationAllowedError" ||
|
|
20
|
+
err?.name === "InvalidStateError" ||
|
|
21
|
+
err?.code === "ENOENT" ||
|
|
22
|
+
STALE_BROWSER_HANDLE_MESSAGE.test(err?.message ?? "")) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
current = err?.cause;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
function delayRetry(attempt) {
|
|
30
|
+
const delayMs = 25 * (attempt + 1);
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
setTimeout(resolve, delayMs);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function isArrayBuffer(value) {
|
|
36
|
+
return (value instanceof ArrayBuffer ||
|
|
37
|
+
Object.prototype.toString.call(value) === "[object ArrayBuffer]");
|
|
38
|
+
}
|
|
39
|
+
function toArrayBuffer(data) {
|
|
40
|
+
if (isArrayBuffer(data))
|
|
41
|
+
return data;
|
|
42
|
+
const view = data;
|
|
43
|
+
const copy = new Uint8Array(view.byteLength);
|
|
44
|
+
copy.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
|
45
|
+
return copy.buffer;
|
|
46
|
+
}
|
|
47
|
+
function byteSize(data) {
|
|
48
|
+
return typeof data === "string"
|
|
49
|
+
? new TextEncoder().encode(data).byteLength
|
|
50
|
+
: data.byteLength;
|
|
51
|
+
}
|
|
52
|
+
function normalizeVaultPath(path) {
|
|
53
|
+
path = normalizePath(path);
|
|
54
|
+
if (path === "/" || !path)
|
|
55
|
+
return "/";
|
|
56
|
+
if (path.startsWith("../") || path === "..") {
|
|
57
|
+
throw createFsError("EINVAL", path);
|
|
58
|
+
}
|
|
59
|
+
return path.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
60
|
+
}
|
|
61
|
+
function pathParts(path) {
|
|
62
|
+
path = normalizeVaultPath(path);
|
|
63
|
+
return path === "/" ? [] : path.split("/").filter(Boolean);
|
|
64
|
+
}
|
|
65
|
+
async function listDirectoryEntries(directory) {
|
|
66
|
+
const entries = directory.entries?.() ?? directory[Symbol.asyncIterator]?.() ?? [];
|
|
67
|
+
const items = [];
|
|
68
|
+
for await (const [name, handle] of entries) {
|
|
69
|
+
items.push([
|
|
70
|
+
name,
|
|
71
|
+
handle,
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
items.sort(([left], [right]) => left.localeCompare(right));
|
|
75
|
+
return items;
|
|
76
|
+
}
|
|
77
|
+
async function ensureTargetPathType(target, path, type) {
|
|
78
|
+
const existing = await target.stat(path);
|
|
79
|
+
if (!existing) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (existing.type === "file" && type === "folder") {
|
|
83
|
+
await target.remove(path);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (existing.type === "folder" && type === "file") {
|
|
87
|
+
await target.rmdir(path, true);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function getDirectoryEntry(directory, name) {
|
|
91
|
+
try {
|
|
92
|
+
return await directory.getFileHandle(name);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (!isNotFound(error)) {
|
|
96
|
+
try {
|
|
97
|
+
return await directory.getDirectoryHandle(name);
|
|
98
|
+
}
|
|
99
|
+
catch (dirError) {
|
|
100
|
+
if (isNotFound(dirError))
|
|
101
|
+
return null;
|
|
102
|
+
throw dirError;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return await directory.getDirectoryHandle(name);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (isNotFound(error))
|
|
111
|
+
return null;
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function ensureDirectoryEntryType(directory, name, type) {
|
|
116
|
+
const existing = await getDirectoryEntry(directory, name);
|
|
117
|
+
if (existing?.kind === type) {
|
|
118
|
+
return existing;
|
|
119
|
+
}
|
|
120
|
+
if (existing) {
|
|
121
|
+
await directory.removeEntry(name, {
|
|
122
|
+
recursive: existing.kind === "directory",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return type === "directory"
|
|
126
|
+
? directory.getDirectoryHandle(name, { create: true })
|
|
127
|
+
: directory.getFileHandle(name, { create: true });
|
|
128
|
+
}
|
|
129
|
+
async function getOrCreateDirectoryPath(root, normalizedPath) {
|
|
130
|
+
let current = root;
|
|
131
|
+
for (const part of pathParts(normalizedPath)) {
|
|
132
|
+
current = (await ensureDirectoryEntryType(current, part, "directory"));
|
|
133
|
+
}
|
|
134
|
+
return current;
|
|
135
|
+
}
|
|
136
|
+
async function listAdapterFiles(source, basePath = "/") {
|
|
137
|
+
const normalizedBase = normalizeVaultPath(basePath);
|
|
138
|
+
const listed = await source.list(normalizedBase);
|
|
139
|
+
const files = listed.files.map((name) => normalizedBase === "/" ? name : joinPath(normalizedBase, name));
|
|
140
|
+
for (const folder of listed.folders) {
|
|
141
|
+
const childPath = normalizedBase === "/" ? folder : joinPath(normalizedBase, folder);
|
|
142
|
+
files.push(...(await listAdapterFiles(source, childPath)));
|
|
143
|
+
}
|
|
144
|
+
files.sort((left, right) => left.localeCompare(right));
|
|
145
|
+
return files;
|
|
146
|
+
}
|
|
147
|
+
async function collectDirectoryTree(directory, basePath = "") {
|
|
148
|
+
const folders = [];
|
|
149
|
+
const files = [];
|
|
150
|
+
const entries = await listDirectoryEntries(directory);
|
|
151
|
+
for (const [name, handle] of entries) {
|
|
152
|
+
const nextPath = basePath ? joinPath(basePath, name) : name;
|
|
153
|
+
if (handle.kind === "directory") {
|
|
154
|
+
folders.push(nextPath);
|
|
155
|
+
const nested = await collectDirectoryTree(handle, nextPath);
|
|
156
|
+
folders.push(...nested.folders);
|
|
157
|
+
files.push(...nested.files);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
files.push({ path: nextPath, handle });
|
|
161
|
+
}
|
|
162
|
+
return { folders, files };
|
|
163
|
+
}
|
|
164
|
+
export async function importDirectoryHandleToAdapter(source, target, options = {}) {
|
|
165
|
+
const { folders, files } = await collectDirectoryTree(source);
|
|
166
|
+
let importedFiles = 0;
|
|
167
|
+
await options.onProgress?.({
|
|
168
|
+
totalFiles: files.length,
|
|
169
|
+
importedFiles,
|
|
170
|
+
currentPath: null,
|
|
171
|
+
});
|
|
172
|
+
for (const folder of folders) {
|
|
173
|
+
await ensureTargetPathType(target, folder, "folder");
|
|
174
|
+
await target.mkdir(folder, { recursive: true });
|
|
175
|
+
}
|
|
176
|
+
for (const file of files) {
|
|
177
|
+
const parentPath = dirname(file.path);
|
|
178
|
+
if (parentPath !== "/") {
|
|
179
|
+
await target.mkdir(parentPath, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
await ensureTargetPathType(target, file.path, "file");
|
|
182
|
+
await target.writeBinary(file.path, toArrayBuffer(await (await file.handle.getFile()).arrayBuffer()));
|
|
183
|
+
importedFiles += 1;
|
|
184
|
+
await options.onProgress?.({
|
|
185
|
+
totalFiles: files.length,
|
|
186
|
+
importedFiles,
|
|
187
|
+
currentPath: file.path,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
totalFiles: files.length,
|
|
192
|
+
importedFiles,
|
|
193
|
+
currentPath: files.at(-1)?.path ?? null,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
export async function exportAdapterToDirectoryHandle(source, target, options = {}) {
|
|
197
|
+
const files = await listAdapterFiles(source);
|
|
198
|
+
const progress = {
|
|
199
|
+
totalFiles: files.length,
|
|
200
|
+
exportedFiles: 0,
|
|
201
|
+
currentPath: null,
|
|
202
|
+
};
|
|
203
|
+
await options.onProgress?.({ ...progress });
|
|
204
|
+
for (const path of files) {
|
|
205
|
+
const parentPath = dirname(path);
|
|
206
|
+
const targetDirectory = await getOrCreateDirectoryPath(target, parentPath === "." ? "/" : parentPath);
|
|
207
|
+
const fileHandle = (await ensureDirectoryEntryType(targetDirectory, basename(path), "file"));
|
|
208
|
+
const writable = await fileHandle.createWritable({
|
|
209
|
+
keepExistingData: false,
|
|
210
|
+
});
|
|
211
|
+
const data = new Uint8Array(await source.readBinary(path));
|
|
212
|
+
await writable.write(data);
|
|
213
|
+
await writable.close();
|
|
214
|
+
progress.exportedFiles += 1;
|
|
215
|
+
progress.currentPath = path;
|
|
216
|
+
await options.onProgress?.({ ...progress });
|
|
217
|
+
}
|
|
218
|
+
return progress;
|
|
219
|
+
}
|
|
220
|
+
export class BrowserHandleVaultAdapter {
|
|
221
|
+
kind;
|
|
222
|
+
stats;
|
|
223
|
+
locks = new Map();
|
|
224
|
+
root;
|
|
225
|
+
constructor(root, kind, options = {}) {
|
|
226
|
+
this.kind = kind;
|
|
227
|
+
this.root = root;
|
|
228
|
+
this.vaultId =
|
|
229
|
+
options.vaultId ??
|
|
230
|
+
`${kind}:${root.name || "vault"}`.replace(/[^a-zA-Z0-9:._-]/g, "-");
|
|
231
|
+
this.name = options.name ?? root.name ?? basename(this.vaultId);
|
|
232
|
+
this.stats = new ScopedVaultStore(this.vaultId, "browser-stats", options.stateStore ?? getDefaultVaultStateStore());
|
|
233
|
+
}
|
|
234
|
+
vaultId;
|
|
235
|
+
name;
|
|
236
|
+
getName() {
|
|
237
|
+
return this.name;
|
|
238
|
+
}
|
|
239
|
+
getVaultId() {
|
|
240
|
+
return this.vaultId;
|
|
241
|
+
}
|
|
242
|
+
getCapabilities() {
|
|
243
|
+
return {
|
|
244
|
+
persistent: true,
|
|
245
|
+
userVisibleFiles: this.kind === "file-system-access",
|
|
246
|
+
requiresPermission: this.kind === "file-system-access",
|
|
247
|
+
nativeWatch: false,
|
|
248
|
+
resourceUrls: true,
|
|
249
|
+
systemTrash: false,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async refreshRootHandleIfNeeded() {
|
|
253
|
+
// Subclasses can re-resolve the vault root after stale OPFS handle errors.
|
|
254
|
+
}
|
|
255
|
+
async withPathLock(normalizedPath, operation) {
|
|
256
|
+
const key = normalizeVaultPath(normalizedPath);
|
|
257
|
+
const previous = this.locks.get(key) ?? Promise.resolve();
|
|
258
|
+
const current = previous.catch(() => undefined).then(operation);
|
|
259
|
+
this.locks.set(key, current.then(() => undefined, () => undefined));
|
|
260
|
+
return current;
|
|
261
|
+
}
|
|
262
|
+
async withHandleRetry(operation) {
|
|
263
|
+
let lastError;
|
|
264
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
265
|
+
try {
|
|
266
|
+
return await operation();
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
lastError = error;
|
|
270
|
+
if (!isRecoverableBrowserHandleError(error) || attempt === 4) {
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
await this.refreshRootHandleIfNeeded();
|
|
274
|
+
await delayRetry(attempt);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
throw lastError;
|
|
278
|
+
}
|
|
279
|
+
async getDirectory(normalizedPath, options = {}) {
|
|
280
|
+
let current = this.root;
|
|
281
|
+
for (const part of pathParts(normalizedPath)) {
|
|
282
|
+
current = await current.getDirectoryHandle(part, options);
|
|
283
|
+
}
|
|
284
|
+
return current;
|
|
285
|
+
}
|
|
286
|
+
async getParentDirectory(normalizedPath, options = {}) {
|
|
287
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
288
|
+
if (path === "/")
|
|
289
|
+
throw createFsError("EINVAL", normalizedPath);
|
|
290
|
+
const parentPath = dirname(path);
|
|
291
|
+
const parent = await this.getDirectory(parentPath, options);
|
|
292
|
+
return [parent, basename(path)];
|
|
293
|
+
}
|
|
294
|
+
async getHandle(normalizedPath) {
|
|
295
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
296
|
+
if (path === "/")
|
|
297
|
+
return this.root;
|
|
298
|
+
const [parent, name] = await this.getParentDirectory(path);
|
|
299
|
+
try {
|
|
300
|
+
return await parent.getFileHandle(name);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
if (isRecoverableBrowserHandleError(error) && !isNotFound(error)) {
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
if (!isNotFound(error)) {
|
|
307
|
+
try {
|
|
308
|
+
return await parent.getDirectoryHandle(name);
|
|
309
|
+
}
|
|
310
|
+
catch (dirError) {
|
|
311
|
+
if (isNotFound(dirError))
|
|
312
|
+
return null;
|
|
313
|
+
throw dirError;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
return await parent.getDirectoryHandle(name);
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
if (isNotFound(error))
|
|
322
|
+
return null;
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async readStoredStat(path) {
|
|
327
|
+
return (await this.stats.get(normalizeVaultPath(path))) ?? null;
|
|
328
|
+
}
|
|
329
|
+
async writeStoredStat(path, stat) {
|
|
330
|
+
await this.stats.set(normalizeVaultPath(path), stat);
|
|
331
|
+
}
|
|
332
|
+
async deleteStoredStats(path) {
|
|
333
|
+
path = normalizeVaultPath(path);
|
|
334
|
+
const keys = await this.stats.keys();
|
|
335
|
+
await Promise.all(keys
|
|
336
|
+
.filter((key) => key === path || key.startsWith(`${path}/`))
|
|
337
|
+
.map((key) => this.stats.del(key)));
|
|
338
|
+
}
|
|
339
|
+
async moveStoredStats(from, to) {
|
|
340
|
+
from = normalizeVaultPath(from);
|
|
341
|
+
to = normalizeVaultPath(to);
|
|
342
|
+
const keys = await this.stats.keys();
|
|
343
|
+
await Promise.all(keys
|
|
344
|
+
.filter((key) => key === from || key.startsWith(`${from}/`))
|
|
345
|
+
.map(async (key) => {
|
|
346
|
+
const value = await this.stats.get(key);
|
|
347
|
+
if (!value)
|
|
348
|
+
return;
|
|
349
|
+
const nextKey = key === from ? to : `${to}/${key.slice(from.length + 1)}`;
|
|
350
|
+
await this.stats.set(nextKey, value);
|
|
351
|
+
await this.stats.del(key);
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
async exists(normalizedPath, sensitive) {
|
|
355
|
+
return (await this.stat(normalizedPath)) !== null;
|
|
356
|
+
}
|
|
357
|
+
async stat(normalizedPath) {
|
|
358
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
359
|
+
return this.withHandleRetry(async () => {
|
|
360
|
+
const handle = await this.getHandle(path);
|
|
361
|
+
if (!handle && path !== "/")
|
|
362
|
+
throw createFsError("ENOENT", path);
|
|
363
|
+
if (!handle)
|
|
364
|
+
return null;
|
|
365
|
+
const stored = await this.readStoredStat(path);
|
|
366
|
+
const now = Date.now();
|
|
367
|
+
if (handle.kind === "directory") {
|
|
368
|
+
return {
|
|
369
|
+
type: "folder",
|
|
370
|
+
ctime: stored?.ctime ?? now,
|
|
371
|
+
mtime: stored?.mtime ?? stored?.ctime ?? now,
|
|
372
|
+
size: 0,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const file = await handle.getFile();
|
|
376
|
+
return {
|
|
377
|
+
type: "file",
|
|
378
|
+
ctime: stored?.ctime ?? file.lastModified ?? now,
|
|
379
|
+
mtime: stored?.mtime ?? file.lastModified ?? now,
|
|
380
|
+
size: file.size,
|
|
381
|
+
};
|
|
382
|
+
}).catch((error) => {
|
|
383
|
+
if (isNotFound(error))
|
|
384
|
+
return null;
|
|
385
|
+
throw error;
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
async read(normalizedPath) {
|
|
389
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
390
|
+
return this.withHandleRetry(async () => {
|
|
391
|
+
const handle = await this.getHandle(path);
|
|
392
|
+
if (!handle || handle.kind !== "file") {
|
|
393
|
+
throw createFsError("ENOENT", path);
|
|
394
|
+
}
|
|
395
|
+
return (await handle.getFile()).text();
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
async readBinary(normalizedPath) {
|
|
399
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
400
|
+
return this.withHandleRetry(async () => {
|
|
401
|
+
const handle = await this.getHandle(path);
|
|
402
|
+
if (!handle || handle.kind !== "file") {
|
|
403
|
+
throw createFsError("ENOENT", path);
|
|
404
|
+
}
|
|
405
|
+
return toArrayBuffer(await (await handle.getFile()).arrayBuffer());
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
async write(normalizedPath, data, options) {
|
|
409
|
+
await this.writeFile(normalizedPath, data, options);
|
|
410
|
+
}
|
|
411
|
+
async writeBinary(normalizedPath, data, options) {
|
|
412
|
+
await this.writeFile(normalizedPath, data, options);
|
|
413
|
+
}
|
|
414
|
+
async writeFile(normalizedPath, data, options) {
|
|
415
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
416
|
+
await this.withHandleRetry(async () => {
|
|
417
|
+
const [parent, name] = await this.getParentDirectory(path, {
|
|
418
|
+
create: true,
|
|
419
|
+
});
|
|
420
|
+
const existing = await this.readStoredStat(path);
|
|
421
|
+
const handle = await parent.getFileHandle(name, { create: true });
|
|
422
|
+
const writable = await handle.createWritable({
|
|
423
|
+
keepExistingData: false,
|
|
424
|
+
});
|
|
425
|
+
await writable.write(data);
|
|
426
|
+
await writable.close();
|
|
427
|
+
const now = Date.now();
|
|
428
|
+
await this.writeStoredStat(path, {
|
|
429
|
+
type: "file",
|
|
430
|
+
ctime: options?.ctime ?? existing?.ctime ?? now,
|
|
431
|
+
mtime: options?.mtime ?? now,
|
|
432
|
+
size: byteSize(data),
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
async append(normalizedPath, data, options) {
|
|
437
|
+
await this.withPathLock(normalizedPath, async () => {
|
|
438
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
439
|
+
await this.withHandleRetry(async () => {
|
|
440
|
+
const [parent, name] = await this.getParentDirectory(path, {
|
|
441
|
+
create: true,
|
|
442
|
+
});
|
|
443
|
+
const existing = await this.readStoredStat(path);
|
|
444
|
+
const handle = await parent.getFileHandle(name, { create: true });
|
|
445
|
+
const file = await handle.getFile();
|
|
446
|
+
const writable = await handle.createWritable({ keepExistingData: true });
|
|
447
|
+
await writable.seek(file.size);
|
|
448
|
+
await writable.write(data);
|
|
449
|
+
await writable.close();
|
|
450
|
+
const now = Date.now();
|
|
451
|
+
await this.writeStoredStat(path, {
|
|
452
|
+
type: "file",
|
|
453
|
+
ctime: options?.ctime ?? existing?.ctime ?? now,
|
|
454
|
+
mtime: options?.mtime ?? now,
|
|
455
|
+
size: file.size + byteSize(data),
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
async appendBinary(normalizedPath, data, options) {
|
|
461
|
+
await this.withPathLock(normalizedPath, async () => {
|
|
462
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
463
|
+
await this.withHandleRetry(async () => {
|
|
464
|
+
const [parent, name] = await this.getParentDirectory(path, {
|
|
465
|
+
create: true,
|
|
466
|
+
});
|
|
467
|
+
const existing = await this.readStoredStat(path);
|
|
468
|
+
const handle = await parent.getFileHandle(name, { create: true });
|
|
469
|
+
const file = await handle.getFile();
|
|
470
|
+
const writable = await handle.createWritable({ keepExistingData: true });
|
|
471
|
+
await writable.seek(file.size);
|
|
472
|
+
await writable.write(data);
|
|
473
|
+
await writable.close();
|
|
474
|
+
const now = Date.now();
|
|
475
|
+
await this.writeStoredStat(path, {
|
|
476
|
+
type: "file",
|
|
477
|
+
ctime: options?.ctime ?? existing?.ctime ?? now,
|
|
478
|
+
mtime: options?.mtime ?? now,
|
|
479
|
+
size: file.size + data.byteLength,
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
async process(normalizedPath, fn, options) {
|
|
485
|
+
return this.withPathLock(normalizedPath, async () => {
|
|
486
|
+
const data = fn(await this.read(normalizedPath));
|
|
487
|
+
await this.write(normalizedPath, data, options);
|
|
488
|
+
return data;
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
async list(normalizedPath) {
|
|
492
|
+
return this.withHandleRetry(async () => {
|
|
493
|
+
const dir = await this.getDirectory(normalizedPath);
|
|
494
|
+
const result = { files: [], folders: [] };
|
|
495
|
+
const entries = dir.entries?.() ?? dir[Symbol.asyncIterator]?.() ?? [];
|
|
496
|
+
for await (const [name, handle] of entries) {
|
|
497
|
+
if (handle.kind === "file") {
|
|
498
|
+
result.files.push(name);
|
|
499
|
+
}
|
|
500
|
+
else if (handle.kind === "directory") {
|
|
501
|
+
result.folders.push(name);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
result.files.sort();
|
|
505
|
+
result.folders.sort();
|
|
506
|
+
return result;
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
async mkdir(normalizedPath, options) {
|
|
510
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
511
|
+
if (path === "/")
|
|
512
|
+
return;
|
|
513
|
+
await this.withHandleRetry(async () => {
|
|
514
|
+
const now = Date.now();
|
|
515
|
+
if (options?.recursive) {
|
|
516
|
+
let current = this.root;
|
|
517
|
+
let currentPath = "";
|
|
518
|
+
for (const part of pathParts(path)) {
|
|
519
|
+
currentPath = currentPath ? joinPath(currentPath, part) : part;
|
|
520
|
+
current = await current.getDirectoryHandle(part, { create: true });
|
|
521
|
+
const existing = await this.readStoredStat(currentPath);
|
|
522
|
+
await this.writeStoredStat(currentPath, {
|
|
523
|
+
type: "folder",
|
|
524
|
+
ctime: existing?.ctime ?? now,
|
|
525
|
+
mtime: now,
|
|
526
|
+
size: 0,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const [parent, name] = await this.getParentDirectory(path);
|
|
532
|
+
await parent.getDirectoryHandle(name, { create: true });
|
|
533
|
+
const existing = await this.readStoredStat(path);
|
|
534
|
+
await this.writeStoredStat(path, {
|
|
535
|
+
type: "folder",
|
|
536
|
+
ctime: existing?.ctime ?? now,
|
|
537
|
+
mtime: now,
|
|
538
|
+
size: 0,
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
async rmdir(normalizedPath, recursive) {
|
|
543
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
544
|
+
await this.withHandleRetry(async () => {
|
|
545
|
+
const [parent, name] = await this.getParentDirectory(path);
|
|
546
|
+
await parent.removeEntry(name, { recursive });
|
|
547
|
+
await this.deleteStoredStats(path);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
async remove(normalizedPath) {
|
|
551
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
552
|
+
await this.withHandleRetry(async () => {
|
|
553
|
+
const [parent, name] = await this.getParentDirectory(path);
|
|
554
|
+
await parent.removeEntry(name);
|
|
555
|
+
await this.deleteStoredStats(path);
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
async rename(normalizedPath, normalizedNewPath) {
|
|
559
|
+
const from = normalizeVaultPath(normalizedPath);
|
|
560
|
+
const to = normalizeVaultPath(normalizedNewPath);
|
|
561
|
+
if (from === to)
|
|
562
|
+
return;
|
|
563
|
+
await this.copy(from, to);
|
|
564
|
+
const stat = await this.stat(from);
|
|
565
|
+
if (stat?.type === "folder") {
|
|
566
|
+
await this.rmdir(from, true);
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
await this.remove(from);
|
|
570
|
+
}
|
|
571
|
+
await this.moveStoredStats(from, to);
|
|
572
|
+
}
|
|
573
|
+
async copy(normalizedPath, normalizedNewPath) {
|
|
574
|
+
const sourcePath = normalizeVaultPath(normalizedPath);
|
|
575
|
+
const targetPath = normalizeVaultPath(normalizedNewPath);
|
|
576
|
+
const handle = await this.getHandle(sourcePath);
|
|
577
|
+
if (!handle)
|
|
578
|
+
throw createFsError("ENOENT", sourcePath);
|
|
579
|
+
const existing = await this.getHandle(targetPath);
|
|
580
|
+
if (existing?.kind === "directory") {
|
|
581
|
+
await this.rmdir(targetPath, true);
|
|
582
|
+
}
|
|
583
|
+
else if (existing?.kind === "file") {
|
|
584
|
+
await this.remove(targetPath);
|
|
585
|
+
}
|
|
586
|
+
if (handle.kind === "file") {
|
|
587
|
+
await this.writeBinary(targetPath, await this.readBinary(sourcePath));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
await this.mkdir(targetPath, { recursive: true });
|
|
591
|
+
const { files, folders } = await this.list(sourcePath);
|
|
592
|
+
for (const folder of folders) {
|
|
593
|
+
await this.copy(joinPath(sourcePath, folder), joinPath(targetPath, folder));
|
|
594
|
+
}
|
|
595
|
+
for (const file of files) {
|
|
596
|
+
await this.copy(joinPath(sourcePath, file), joinPath(targetPath, file));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
getResourcePath(normalizedPath) {
|
|
600
|
+
return `browser-vault://${this.vaultId}/${normalizeVaultPath(normalizedPath)}`;
|
|
601
|
+
}
|
|
602
|
+
async getResourceUrl(normalizedPath) {
|
|
603
|
+
const data = await this.readBinary(normalizedPath);
|
|
604
|
+
return URL.createObjectURL(new Blob([data]));
|
|
605
|
+
}
|
|
606
|
+
revokeResourceUrl(url) {
|
|
607
|
+
URL.revokeObjectURL(url);
|
|
608
|
+
}
|
|
609
|
+
trashSystem(normalizedPath) {
|
|
610
|
+
return Promise.resolve(false);
|
|
611
|
+
}
|
|
612
|
+
async trashLocal(normalizedPath) {
|
|
613
|
+
const path = normalizeVaultPath(normalizedPath);
|
|
614
|
+
const trashPath = normalizeVaultPath(joinPath(".trash", path));
|
|
615
|
+
await this.mkdir(dirname(trashPath), { recursive: true });
|
|
616
|
+
await this.rename(path, trashPath);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
export function humanizeOpfsVaultId(vaultId) {
|
|
620
|
+
const withoutPrefix = vaultId.startsWith("opfs-")
|
|
621
|
+
? vaultId.slice("opfs-".length)
|
|
622
|
+
: vaultId;
|
|
623
|
+
const slug = withoutPrefix.replace(/-[a-z0-9]{4}$/, "");
|
|
624
|
+
const words = (slug || withoutPrefix).split("-").filter(Boolean);
|
|
625
|
+
if (words.length === 0) {
|
|
626
|
+
return vaultId;
|
|
627
|
+
}
|
|
628
|
+
return words
|
|
629
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
630
|
+
.join(" ");
|
|
631
|
+
}
|
|
632
|
+
export async function listOpfsVaultIds() {
|
|
633
|
+
const storage = globalThis.navigator?.storage;
|
|
634
|
+
if (!storage?.getDirectory) {
|
|
635
|
+
return [];
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
const root = await storage.getDirectory();
|
|
639
|
+
let vaults;
|
|
640
|
+
try {
|
|
641
|
+
vaults = await root.getDirectoryHandle("vaults");
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
if (isNotFound(error)) {
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
throw error;
|
|
648
|
+
}
|
|
649
|
+
const ids = [];
|
|
650
|
+
const entries = vaults.entries?.() ?? vaults[Symbol.asyncIterator]?.();
|
|
651
|
+
if (!entries) {
|
|
652
|
+
return [];
|
|
653
|
+
}
|
|
654
|
+
for await (const [name, handle] of entries) {
|
|
655
|
+
if (handle.kind === "directory") {
|
|
656
|
+
ids.push(name);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return ids.sort();
|
|
660
|
+
}
|
|
661
|
+
catch {
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
export async function listOrphanOpfsVaultIds(stateStore) {
|
|
666
|
+
const opfsIds = await listOpfsVaultIds();
|
|
667
|
+
if (opfsIds.length === 0) {
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
const store = stateStore ?? getDefaultVaultStateStore();
|
|
671
|
+
const profiles = await listVaultProfiles(store);
|
|
672
|
+
const registeredOpfsIds = new Set(profiles
|
|
673
|
+
.filter((profile) => profile.kind === "opfs")
|
|
674
|
+
.map((profile) => profile.id));
|
|
675
|
+
return opfsIds.filter((id) => !registeredOpfsIds.has(id));
|
|
676
|
+
}
|
|
677
|
+
export async function createOpfsVault(options) {
|
|
678
|
+
const vaultId = options.vaultId ?? generateBrowserVaultId("opfs", options.name);
|
|
679
|
+
return OpfsVaultAdapter.create({
|
|
680
|
+
vaultId,
|
|
681
|
+
name: options.name,
|
|
682
|
+
stateStore: options.stateStore,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
export class OpfsVaultAdapter extends BrowserHandleVaultAdapter {
|
|
686
|
+
static async create(options = {}) {
|
|
687
|
+
const storage = globalThis.navigator?.storage;
|
|
688
|
+
if (!storage?.getDirectory) {
|
|
689
|
+
throw new Error("OPFS is not available in this browser");
|
|
690
|
+
}
|
|
691
|
+
const vaultId = options.vaultId ?? "opfs-default";
|
|
692
|
+
const root = await storage.getDirectory();
|
|
693
|
+
const vaults = await root.getDirectoryHandle("vaults", { create: true });
|
|
694
|
+
const vault = await vaults.getDirectoryHandle(vaultId, { create: true });
|
|
695
|
+
const adapter = new OpfsVaultAdapter(vault, {
|
|
696
|
+
...options,
|
|
697
|
+
vaultId,
|
|
698
|
+
name: options.name ?? "Browser vault",
|
|
699
|
+
});
|
|
700
|
+
const now = Date.now();
|
|
701
|
+
const store = options.stateStore ?? getDefaultVaultStateStore();
|
|
702
|
+
const existingProfile = await getVaultProfile(adapter.getVaultId(), store);
|
|
703
|
+
await saveVaultProfile({
|
|
704
|
+
id: adapter.getVaultId(),
|
|
705
|
+
name: adapter.getName(),
|
|
706
|
+
kind: "opfs",
|
|
707
|
+
createdAt: existingProfile?.createdAt ?? now,
|
|
708
|
+
updatedAt: now,
|
|
709
|
+
demo: existingProfile?.demo,
|
|
710
|
+
}, store);
|
|
711
|
+
return adapter;
|
|
712
|
+
}
|
|
713
|
+
async refreshRootHandleIfNeeded() {
|
|
714
|
+
const storage = globalThis.navigator?.storage;
|
|
715
|
+
if (!storage?.getDirectory) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const opfsRoot = await storage.getDirectory();
|
|
719
|
+
const vaults = await opfsRoot.getDirectoryHandle("vaults");
|
|
720
|
+
this.root = await vaults.getDirectoryHandle(this.vaultId);
|
|
721
|
+
}
|
|
722
|
+
constructor(root, options) {
|
|
723
|
+
super(root, "opfs", options);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
export class FileSystemAccessAdapter extends BrowserHandleVaultAdapter {
|
|
727
|
+
static async fromHandle(handle, options = {}) {
|
|
728
|
+
const adapter = new FileSystemAccessAdapter(handle, options);
|
|
729
|
+
const now = Date.now();
|
|
730
|
+
const store = options.stateStore ?? getDefaultVaultStateStore();
|
|
731
|
+
const existingProfile = await getVaultProfile(adapter.getVaultId(), store);
|
|
732
|
+
await saveVaultProfile({
|
|
733
|
+
id: adapter.getVaultId(),
|
|
734
|
+
name: adapter.getName(),
|
|
735
|
+
kind: "file-system-access",
|
|
736
|
+
handle,
|
|
737
|
+
createdAt: existingProfile?.createdAt ?? now,
|
|
738
|
+
updatedAt: now,
|
|
739
|
+
}, store);
|
|
740
|
+
return adapter;
|
|
741
|
+
}
|
|
742
|
+
constructor(root, options) {
|
|
743
|
+
super(root, "file-system-access", options);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
export async function pickFileSystemAccessDirectoryHandle(options = {}) {
|
|
747
|
+
const picker = globalThis.showDirectoryPicker;
|
|
748
|
+
if (!picker) {
|
|
749
|
+
throw new Error("File System Access API is not available in this browser");
|
|
750
|
+
}
|
|
751
|
+
return picker({
|
|
752
|
+
id: options.id ?? "lapis-notes-vault",
|
|
753
|
+
mode: options.mode ?? "readwrite",
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
export async function pickFileSystemAccessVault(options = {}) {
|
|
757
|
+
const handle = await pickFileSystemAccessDirectoryHandle({
|
|
758
|
+
id: "lapis-notes-vault",
|
|
759
|
+
mode: "readwrite",
|
|
760
|
+
});
|
|
761
|
+
return FileSystemAccessAdapter.fromHandle(handle, options);
|
|
762
|
+
}
|
|
763
|
+
async function deleteOpfsVaultDirectory(vaultId) {
|
|
764
|
+
const storage = globalThis.navigator?.storage;
|
|
765
|
+
if (!storage?.getDirectory) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
const root = await storage.getDirectory();
|
|
770
|
+
const vaults = await root.getDirectoryHandle("vaults");
|
|
771
|
+
await vaults.removeEntry(vaultId, { recursive: true });
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
if (isNotFound(error)) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
throw error;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
export async function deleteBrowserLocalVault(profile, options = {}) {
|
|
781
|
+
if (profile.kind === "opfs") {
|
|
782
|
+
await deleteOpfsVaultDirectory(profile.id);
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
throw new Error(`Cannot delete browser-local vault kind: ${profile.kind}`);
|
|
786
|
+
}
|
|
787
|
+
const store = options.stateStore ?? getDefaultVaultStateStore();
|
|
788
|
+
await clearVaultScopedState(profile.id, store);
|
|
789
|
+
await deleteVaultProfile(profile.id, store);
|
|
790
|
+
}
|
|
791
|
+
export async function deleteOrphanOpfsVault(vaultId, options = {}) {
|
|
792
|
+
await deleteOpfsVaultDirectory(vaultId);
|
|
793
|
+
await clearVaultScopedState(vaultId, options.stateStore ?? getDefaultVaultStateStore());
|
|
794
|
+
}
|