@dxos/plugin-wnfs 0.8.4-main.fd6878d → 0.9.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/LICENSE +102 -5
- package/PLUGIN.mdl +371 -0
- package/README.md +2 -2
- package/dist/lib/neutral/WnfsPlugin.mjs +51 -0
- package/dist/lib/neutral/WnfsPlugin.mjs.map +7 -0
- package/dist/lib/neutral/WnfsPlugin.node.mjs +12 -0
- package/dist/lib/neutral/WnfsPlugin.node.mjs.map +7 -0
- package/dist/lib/neutral/WnfsPlugin.workerd.mjs +12 -0
- package/dist/lib/neutral/WnfsPlugin.workerd.mjs.map +7 -0
- package/dist/lib/neutral/backend-4HPTHS33.mjs +47 -0
- package/dist/lib/neutral/backend-4HPTHS33.mjs.map +7 -0
- package/dist/lib/{browser/blockstore-PSYZCC2O.mjs → neutral/blockstore-U6SPNL2C.mjs} +12 -13
- package/dist/lib/neutral/blockstore-U6SPNL2C.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +13 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-KUQHRT52.mjs +39 -0
- package/dist/lib/neutral/chunk-KUQHRT52.mjs.map +7 -0
- package/dist/lib/{browser/chunk-D6VTEQIE.mjs → neutral/chunk-L33MOD23.mjs} +85 -25
- package/dist/lib/neutral/chunk-L33MOD23.mjs.map +7 -0
- package/dist/lib/neutral/chunk-QODCKFTY.mjs +19 -0
- package/dist/lib/neutral/chunk-QODCKFTY.mjs.map +7 -0
- package/dist/lib/neutral/helpers/index.mjs +26 -0
- package/dist/lib/neutral/helpers/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +36 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +8 -0
- package/dist/lib/neutral/meta.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +12 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +17 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +8 -0
- package/dist/lib/neutral/types/index.mjs.map +7 -0
- package/dist/lib/neutral/url-resolver-BIRH7WHL.mjs +47 -0
- package/dist/lib/neutral/url-resolver-BIRH7WHL.mjs.map +7 -0
- package/dist/types/src/WnfsPlugin.d.ts +3 -1
- package/dist/types/src/WnfsPlugin.d.ts.map +1 -1
- package/dist/types/src/WnfsPlugin.node.d.ts +4 -0
- package/dist/types/src/WnfsPlugin.node.d.ts.map +1 -0
- package/dist/types/src/WnfsPlugin.test.d.ts +2 -0
- package/dist/types/src/WnfsPlugin.test.d.ts.map +1 -0
- package/dist/types/src/WnfsPlugin.workerd.d.ts +4 -0
- package/dist/types/src/WnfsPlugin.workerd.d.ts.map +1 -0
- package/dist/types/src/annotations.d.ts +7 -0
- package/dist/types/src/annotations.d.ts.map +1 -0
- package/dist/types/src/blockstore.d.ts.map +1 -1
- package/dist/types/src/capabilities/backend.d.ts +7 -0
- package/dist/types/src/capabilities/backend.d.ts.map +1 -0
- package/dist/types/src/capabilities/blockstore.d.ts +3 -2
- package/dist/types/src/capabilities/blockstore.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +5 -6
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/url-resolver.d.ts +7 -0
- package/dist/types/src/capabilities/url-resolver.d.ts.map +1 -0
- package/dist/types/src/helpers/common.d.ts.map +1 -1
- package/dist/types/src/helpers/load.d.ts +1 -1
- package/dist/types/src/helpers/load.d.ts.map +1 -1
- package/dist/types/src/helpers/upload.d.ts +1 -1
- package/dist/types/src/helpers/upload.d.ts.map +1 -1
- package/dist/types/src/helpers/wnfs-url.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +3 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +2 -3
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/plugin.d.ts +3 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +2 -12
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/WnfsCapabilities.d.ts +11 -0
- package/dist/types/src/types/WnfsCapabilities.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -2
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +86 -51
- package/src/WnfsPlugin.node.ts +11 -0
- package/src/WnfsPlugin.test.ts +20 -0
- package/src/WnfsPlugin.tsx +42 -84
- package/src/WnfsPlugin.workerd.ts +11 -0
- package/src/annotations.ts +16 -0
- package/src/blockstore.ts +1 -1
- package/src/capabilities/backend.ts +43 -0
- package/src/capabilities/blockstore.ts +14 -10
- package/src/capabilities/index.ts +12 -7
- package/src/capabilities/url-resolver.ts +44 -0
- package/src/helpers/load.ts +37 -18
- package/src/helpers/upload.ts +14 -2
- package/src/index.ts +3 -2
- package/src/meta.ts +26 -7
- package/src/plugin.ts +9 -0
- package/src/translations.ts +2 -11
- package/src/types/WnfsCapabilities.ts +18 -0
- package/src/types/index.ts +1 -2
- package/src/vite-env.d.ts +10 -0
- package/dist/lib/browser/FileContainer-LTKYOYSO.mjs +0 -105
- package/dist/lib/browser/FileContainer-LTKYOYSO.mjs.map +0 -7
- package/dist/lib/browser/blockstore-PSYZCC2O.mjs.map +0 -7
- package/dist/lib/browser/chunk-CW4BENRQ.mjs +0 -64
- package/dist/lib/browser/chunk-CW4BENRQ.mjs.map +0 -7
- package/dist/lib/browser/chunk-D6VTEQIE.mjs.map +0 -7
- package/dist/lib/browser/chunk-KZANQHJY.mjs +0 -16
- package/dist/lib/browser/chunk-KZANQHJY.mjs.map +0 -7
- package/dist/lib/browser/chunk-VDDGZWAY.mjs +0 -15
- package/dist/lib/browser/chunk-VDDGZWAY.mjs.map +0 -7
- package/dist/lib/browser/chunk-ZMH2OULB.mjs +0 -16
- package/dist/lib/browser/chunk-ZMH2OULB.mjs.map +0 -7
- package/dist/lib/browser/file-uploader-YRZPRETS.mjs +0 -29
- package/dist/lib/browser/file-uploader-YRZPRETS.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -118
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-CKGJNE6P.mjs +0 -49
- package/dist/lib/browser/intent-resolver-CKGJNE6P.mjs.map +0 -7
- package/dist/lib/browser/markdown-RAGCVYGE.mjs +0 -198
- package/dist/lib/browser/markdown-RAGCVYGE.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/react-surface-HGWZDLP2.mjs +0 -88
- package/dist/lib/browser/react-surface-HGWZDLP2.mjs.map +0 -7
- package/dist/lib/browser/types/index.mjs +0 -10
- package/dist/types/src/capabilities/capabilities.d.ts +0 -12
- package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
- package/dist/types/src/capabilities/file-uploader.d.ts +0 -4
- package/dist/types/src/capabilities/file-uploader.d.ts.map +0 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
- package/dist/types/src/capabilities/markdown.d.ts +0 -4
- package/dist/types/src/capabilities/markdown.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface.d.ts +0 -4
- package/dist/types/src/capabilities/react-surface.d.ts.map +0 -1
- package/dist/types/src/components/FileContainer.d.ts +0 -9
- package/dist/types/src/components/FileContainer.d.ts.map +0 -1
- package/dist/types/src/components/FileInput.d.ts +0 -8
- package/dist/types/src/components/FileInput.d.ts.map +0 -1
- package/dist/types/src/components/FilePreview.d.ts +0 -11
- package/dist/types/src/components/FilePreview.d.ts.map +0 -1
- package/dist/types/src/components/index.d.ts +0 -3
- package/dist/types/src/components/index.d.ts.map +0 -1
- package/dist/types/src/extensions/image.d.ts +0 -14
- package/dist/types/src/extensions/image.d.ts.map +0 -1
- package/dist/types/src/extensions/image.stories.d.ts +0 -10
- package/dist/types/src/extensions/image.stories.d.ts.map +0 -1
- package/dist/types/src/extensions/index.d.ts +0 -2
- package/dist/types/src/extensions/index.d.ts.map +0 -1
- package/dist/types/src/types/file.d.ts +0 -16
- package/dist/types/src/types/file.d.ts.map +0 -1
- package/dist/types/src/types/types.d.ts +0 -51
- package/dist/types/src/types/types.d.ts.map +0 -1
- package/src/capabilities/capabilities.ts +0 -18
- package/src/capabilities/file-uploader.ts +0 -24
- package/src/capabilities/intent-resolver.ts +0 -33
- package/src/capabilities/markdown.ts +0 -26
- package/src/capabilities/react-surface.tsx +0 -45
- package/src/components/FileContainer.tsx +0 -52
- package/src/components/FileInput.tsx +0 -47
- package/src/components/FilePreview.tsx +0 -23
- package/src/components/index.ts +0 -9
- package/src/extensions/image.stories.tsx +0 -101
- package/src/extensions/image.tsx +0 -203
- package/src/extensions/index.ts +0 -5
- package/src/types/file.ts +0 -17
- package/src/types/types.ts +0 -41
- /package/dist/lib/{browser/types/index.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
package/LICENSE
CHANGED
|
@@ -1,8 +1,105 @@
|
|
|
1
|
-
|
|
2
|
-
Copyright (c) 2024 DXOS
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
## Abbreviation
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
FSL-1.1-Apache-2.0
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 DXOS
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
package/PLUGIN.mdl
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: org.dxos.plugin.wnfs
|
|
3
|
+
name: WnfsPlugin
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
A decentralized file storage plugin for `DXOS` Composer built on the Web Native File System (WNFS) protocol.
|
|
8
|
+
|
|
9
|
+
WNFS provides a content-addressed, end-to-end encrypted private file system using a cryptographic "dark forest"
|
|
10
|
+
structure. Files are stored as IPLD blocks referenced by CID, replicated to a DXOS edge blob service, and
|
|
11
|
+
cached locally in IndexedDB. The plugin implements the `FileCapabilities.Backend` and `FileCapabilities.UrlResolver`
|
|
12
|
+
interfaces from `plugin-file`, so other plugins (e.g. `plugin-gallery`) can upload and resolve `wnfs://` URLs
|
|
13
|
+
without knowing the underlying storage layer. Uploads are scoped to a DXOS Space; the private forest root CID
|
|
14
|
+
is persisted on the space's ECHO properties and updated atomically on every write.
|
|
15
|
+
|
|
16
|
+
## Extensions
|
|
17
|
+
|
|
18
|
+
The following extension dialects are used in this document.
|
|
19
|
+
Each extension is defined in the Appendix or resolved via its URI.
|
|
20
|
+
|
|
21
|
+
| Term | URI |
|
|
22
|
+
|-------------|--------------------------------|
|
|
23
|
+
| `type` | `org.dxos.mdl.type@1.0` |
|
|
24
|
+
| `feat` | `org.dxos.mdl.feat@1.0` |
|
|
25
|
+
| `test` | `org.dxos.mdl.test@1.0` |
|
|
26
|
+
| `component` | `org.dxos.mdl.component@1.0` |
|
|
27
|
+
| `op` | `org.dxos.mdl.op@1.0` |
|
|
28
|
+
|
|
29
|
+
## Types
|
|
30
|
+
|
|
31
|
+
```mdl
|
|
32
|
+
type Cid
|
|
33
|
+
desc: A content-addressed identifier encoded as a base-32 multibase string (CIDv1, sha2-256).
|
|
34
|
+
fields:
|
|
35
|
+
value: string
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```mdl
|
|
39
|
+
type WnfsPath
|
|
40
|
+
desc: A WNFS path encoded as a wnfs:// URL; segments are percent-encoded.
|
|
41
|
+
fields:
|
|
42
|
+
url: string # wnfs://spaces/<spaceId>/files/<cid>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```mdl
|
|
46
|
+
type FileInfo
|
|
47
|
+
desc: Metadata returned after a successful upload.
|
|
48
|
+
fields:
|
|
49
|
+
name: string # Original file name
|
|
50
|
+
type: string # MIME type
|
|
51
|
+
url: WnfsPath # wnfs:// URL for later retrieval
|
|
52
|
+
cid: Cid # Raw content CID of the file bytes
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```mdl
|
|
56
|
+
type BlockstoreState
|
|
57
|
+
desc: Runtime state of the MixedBlockstore.
|
|
58
|
+
fields:
|
|
59
|
+
isConnected: boolean # Online / offline status
|
|
60
|
+
queueLength: number # Blocks pending remote flush
|
|
61
|
+
apiHost: string # DXOS edge service base URL
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```mdl
|
|
65
|
+
type WnfsInstances
|
|
66
|
+
desc: In-memory cache mapping forest CID → loaded PrivateDirectory/PrivateForest pair.
|
|
67
|
+
fields:
|
|
68
|
+
entries: Record<string, WnfsInstance>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```mdl
|
|
72
|
+
type WnfsInstance
|
|
73
|
+
desc: A loaded WNFS private file system rooted at a specific forest CID.
|
|
74
|
+
fields:
|
|
75
|
+
directory: PrivateDirectory
|
|
76
|
+
forest: PrivateForest
|
|
77
|
+
```
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Operations
|
|
81
|
+
|
|
82
|
+
Pure operations — no external effects.
|
|
83
|
+
|
|
84
|
+
```mdl
|
|
85
|
+
op wnfsUrl
|
|
86
|
+
desc: Encodes a WNFS path array into a wnfs:// URL with percent-encoded segments.
|
|
87
|
+
input:
|
|
88
|
+
filePath: string[]
|
|
89
|
+
output: string
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```mdl
|
|
93
|
+
op getPathFromUrl
|
|
94
|
+
desc: Decodes a wnfs:// URL back into a path segment array.
|
|
95
|
+
input:
|
|
96
|
+
wnfsUrl: string
|
|
97
|
+
output: string[]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Effectful operations — read or write persistent state.
|
|
101
|
+
|
|
102
|
+
```mdl
|
|
103
|
+
op uploadFile
|
|
104
|
+
desc: |
|
|
105
|
+
Reads a File, writes its bytes into the WNFS private directory at a
|
|
106
|
+
CID-derived path, persists the updated forest pointer to the space's
|
|
107
|
+
ECHO properties, and returns FileInfo.
|
|
108
|
+
input:
|
|
109
|
+
file: File
|
|
110
|
+
space: Space
|
|
111
|
+
output: FileInfo
|
|
112
|
+
effects: [fs:read, echo:write, http:post]
|
|
113
|
+
errors:
|
|
114
|
+
SpaceNotFound: no space matching the given spaceId
|
|
115
|
+
BlockstoreError: local or remote block I/O failure
|
|
116
|
+
note: |
|
|
117
|
+
Uses sha256 for block hashing (not the rs-wnfs default Blake3) for
|
|
118
|
+
broader runtime support. Writes are atomic from ECHO's perspective:
|
|
119
|
+
the forest CID property is only updated after the forest is stored.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```mdl
|
|
123
|
+
op resolveWnfsUrl
|
|
124
|
+
desc: |
|
|
125
|
+
Reads file bytes from the WNFS private directory for a wnfs:// URL,
|
|
126
|
+
wraps them in a Blob, and returns an object URL for in-browser display.
|
|
127
|
+
input:
|
|
128
|
+
wnfsUrl: string
|
|
129
|
+
space: Space
|
|
130
|
+
type?: string # MIME type hint for Blob construction
|
|
131
|
+
output: string # blob: object URL
|
|
132
|
+
effects: [fs:read, http:get]
|
|
133
|
+
errors:
|
|
134
|
+
UrlNotResolvable: space not available or file not found in directory
|
|
135
|
+
note: Errors are swallowed and undefined is returned to avoid breaking image renders.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```mdl
|
|
139
|
+
op flushBlockQueue
|
|
140
|
+
desc: |
|
|
141
|
+
Drains the pending-upload queue by assembling a CAR archive and POSTing
|
|
142
|
+
it to the edge blob service. Called automatically (debounced 5 s) on
|
|
143
|
+
block writes and on the browser `online` event.
|
|
144
|
+
input:
|
|
145
|
+
blockstore: Blockstore
|
|
146
|
+
output: void
|
|
147
|
+
effects: [http:post]
|
|
148
|
+
errors:
|
|
149
|
+
NetworkError: remote POST failed
|
|
150
|
+
note: Queue is persisted to IndexedDB so pending blocks survive page reloads.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Features
|
|
154
|
+
|
|
155
|
+
```mdl
|
|
156
|
+
feat F-1: Upload File
|
|
157
|
+
|
|
158
|
+
req F-1.1:
|
|
159
|
+
when: a FileCapabilities.Backend consumer calls upload(file, db)
|
|
160
|
+
then: the file is read, hashed, written to the WNFS private directory,
|
|
161
|
+
and stored in the blockstore
|
|
162
|
+
|
|
163
|
+
req F-1.2:
|
|
164
|
+
when: the write succeeds
|
|
165
|
+
then: the space's ECHO properties are updated with the new privateForestCid
|
|
166
|
+
|
|
167
|
+
req F-1.3:
|
|
168
|
+
when: the upload completes
|
|
169
|
+
then: a FileInfo object with name, type, size, and wnfs:// URL is returned to the caller
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```mdl
|
|
173
|
+
feat F-2: Resolve wnfs:// URL
|
|
174
|
+
|
|
175
|
+
req F-2.1:
|
|
176
|
+
when: FileCapabilities.UrlResolver.test(url) is called with a wnfs:// URL
|
|
177
|
+
then: returns true
|
|
178
|
+
|
|
179
|
+
req F-2.2:
|
|
180
|
+
when: FileCapabilities.UrlResolver.resolve(url, file, space) is called
|
|
181
|
+
then: loads or reuses a cached WnfsInstance for the space and reads the file bytes
|
|
182
|
+
|
|
183
|
+
req F-2.3:
|
|
184
|
+
when: bytes are successfully read
|
|
185
|
+
then: a blob: object URL is returned for in-browser display
|
|
186
|
+
|
|
187
|
+
req F-2.4:
|
|
188
|
+
when: the space is undefined or reading fails
|
|
189
|
+
then: undefined is returned; no error is propagated
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```mdl
|
|
193
|
+
feat F-3: Blockstore Sync
|
|
194
|
+
|
|
195
|
+
req F-3.1:
|
|
196
|
+
when: a block is written via blockstore.put
|
|
197
|
+
then: the block is saved to IndexedDB and added to the upload queue
|
|
198
|
+
|
|
199
|
+
req F-3.2:
|
|
200
|
+
when: the browser is online and the queue is non-empty
|
|
201
|
+
then: blocks are batched into a CAR archive and flushed to the edge service
|
|
202
|
+
|
|
203
|
+
req F-3.3:
|
|
204
|
+
when: the browser goes offline
|
|
205
|
+
then: the queue is persisted to IndexedDB; no upload is attempted
|
|
206
|
+
|
|
207
|
+
req F-3.4:
|
|
208
|
+
when: the browser comes back online
|
|
209
|
+
then: the flush is triggered automatically
|
|
210
|
+
|
|
211
|
+
req F-3.5:
|
|
212
|
+
when: a block is requested via blockstore.get and is present in IndexedDB
|
|
213
|
+
then: the block is returned from the local cache without a network request
|
|
214
|
+
|
|
215
|
+
req F-3.6:
|
|
216
|
+
when: a block is not in IndexedDB and the browser is online
|
|
217
|
+
then: the block is fetched from the edge service and cached locally
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
```mdl
|
|
221
|
+
feat F-4: Space-Scoped Private File System
|
|
222
|
+
|
|
223
|
+
req F-4.1:
|
|
224
|
+
when: a space has no existing WNFS properties
|
|
225
|
+
then: a new PrivateForest and PrivateDirectory are created and stored
|
|
226
|
+
|
|
227
|
+
req F-4.2:
|
|
228
|
+
when: a space already has a privateForestCid
|
|
229
|
+
then: the existing forest and directory are loaded from the blockstore
|
|
230
|
+
|
|
231
|
+
req F-4.3:
|
|
232
|
+
when: a WnfsInstance is already cached for the current forest CID
|
|
233
|
+
then: the cached instance is reused without reloading from the blockstore
|
|
234
|
+
|
|
235
|
+
req F-4.4:
|
|
236
|
+
when: legacy snake_case WNFS properties are found on a space
|
|
237
|
+
then: they are deleted and replaced with the current camelCase schema
|
|
238
|
+
tags: [migration]
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Acceptance
|
|
242
|
+
|
|
243
|
+
```mdl
|
|
244
|
+
test T-1: Upload creates FileInfo
|
|
245
|
+
given: an active space and a registered WNFS backend
|
|
246
|
+
when: a file is uploaded via the Backend capability
|
|
247
|
+
then:
|
|
248
|
+
- FileInfo.url starts with wnfs://
|
|
249
|
+
- space.properties.wnfs.privateForestCid is set
|
|
250
|
+
- block for the file is present in the local IndexedDB blockstore
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```mdl
|
|
254
|
+
test T-2: Resolve uploaded file
|
|
255
|
+
given: a file was previously uploaded and FileInfo.url is known
|
|
256
|
+
when: UrlResolver.resolve(url, file, space) is called
|
|
257
|
+
then:
|
|
258
|
+
- a blob: object URL is returned
|
|
259
|
+
- URL can be used as an img src
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```mdl
|
|
263
|
+
test T-3: Resolve without space returns undefined
|
|
264
|
+
given: a wnfs:// URL
|
|
265
|
+
when: UrlResolver.resolve is called with space = undefined
|
|
266
|
+
then: undefined is returned; no error is thrown
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```mdl
|
|
270
|
+
test T-4: Blockstore offline queuing
|
|
271
|
+
given: the browser is offline
|
|
272
|
+
when: a block is written to the blockstore
|
|
273
|
+
then:
|
|
274
|
+
- block is saved to IndexedDB
|
|
275
|
+
- queue length increases by 1
|
|
276
|
+
- no HTTP request is made
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
```mdl
|
|
280
|
+
test T-5: Blockstore queue flushes on reconnect
|
|
281
|
+
given: blocks are queued while offline
|
|
282
|
+
when: the browser comes back online
|
|
283
|
+
then:
|
|
284
|
+
- a CAR archive containing all queued blocks is POSTed to the edge service
|
|
285
|
+
- the queue is cleared
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```mdl
|
|
289
|
+
test T-6: New space initializes WNFS directory
|
|
290
|
+
given: a space with no WNFS properties
|
|
291
|
+
when: loadWnfs is called for that space
|
|
292
|
+
then:
|
|
293
|
+
- a new PrivateForest and PrivateDirectory are created
|
|
294
|
+
- space.properties.wnfs.privateForestCid is populated
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```mdl
|
|
298
|
+
test T-7: WnfsInstance cache hit
|
|
299
|
+
given: a WnfsInstance is cached for the current forest CID
|
|
300
|
+
when: loadWnfs is called again for the same space
|
|
301
|
+
then: the cached instance is returned; no blockstore reads occur
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Appendix: Extension Definitions
|
|
307
|
+
|
|
308
|
+
Extension block types used in this document are defined below using
|
|
309
|
+
the core `ext` primitive — the only construct the base language provides.
|
|
310
|
+
|
|
311
|
+
```mdl
|
|
312
|
+
ext type
|
|
313
|
+
uri: org.dxos.mdl.type@1.0
|
|
314
|
+
desc: A named data structure with typed fields and optional literals.
|
|
315
|
+
fields:
|
|
316
|
+
desc?: Prose
|
|
317
|
+
fields?: FieldMap # name[?]: TypeExpr (# inline comment)
|
|
318
|
+
literals?: UnionList # a | b | c
|
|
319
|
+
extends?: TypeRef[]
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
```mdl
|
|
323
|
+
ext feat
|
|
324
|
+
uri: org.dxos.mdl.feat@1.0
|
|
325
|
+
desc: A named feature grouping one or more requirements.
|
|
326
|
+
fields:
|
|
327
|
+
desc?: Prose
|
|
328
|
+
req: RequirementList
|
|
329
|
+
nesting: self # feat blocks may contain feat blocks
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```mdl
|
|
333
|
+
ext test
|
|
334
|
+
uri: org.dxos.mdl.test@1.0
|
|
335
|
+
desc: An acceptance scenario expressed as given / when / then steps.
|
|
336
|
+
fields:
|
|
337
|
+
given?: Step | Step[]
|
|
338
|
+
when?: Step | Step[]
|
|
339
|
+
then: Step | Step[]
|
|
340
|
+
tags?: TagList
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
```mdl
|
|
344
|
+
ext component
|
|
345
|
+
uri: org.dxos.mdl.component@1.0
|
|
346
|
+
desc: A UI component with props, internal state, slots, actions, and events.
|
|
347
|
+
fields:
|
|
348
|
+
desc?: Prose
|
|
349
|
+
props?: FieldMap # external inputs (immutable inside component)
|
|
350
|
+
state?: FieldMap # internal reactive state
|
|
351
|
+
slots?: FieldMap # named ReactNode injection points
|
|
352
|
+
actions?: ActionMap # methods the component exposes or handles
|
|
353
|
+
emits?: EventMap # events the component raises to its parent
|
|
354
|
+
layout?: CodeBlock # ASCII sketch of visual structure (non-normative)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
```mdl
|
|
358
|
+
ext op
|
|
359
|
+
uri: org.dxos.mdl.op@1.0
|
|
360
|
+
desc: |
|
|
361
|
+
A named operation with typed inputs, outputs, and declared errors.
|
|
362
|
+
Pure ops have no effects or requires. Effectful ops declare both.
|
|
363
|
+
fields:
|
|
364
|
+
desc?: Prose
|
|
365
|
+
input?: FieldMap # named input parameters
|
|
366
|
+
output?: TypeExpr # return type
|
|
367
|
+
errors?: ErrorMap # name: Prose (when this error occurs)
|
|
368
|
+
effects?: EffectList # echo:read | echo:write | http | fs | ...
|
|
369
|
+
requires?: ServiceList # injected service dependencies
|
|
370
|
+
note?: Prose # implementation guidance (non-normative)
|
|
371
|
+
```
|
package/README.md
CHANGED
|
@@ -6,10 +6,10 @@ Plugin that allows for data to be stored on WNFS.
|
|
|
6
6
|
|
|
7
7
|
- [Website](https://dxos.org)
|
|
8
8
|
- [Developer Documentation](https://docs.dxos.org)
|
|
9
|
-
- Talk to us on [Discord](https://
|
|
9
|
+
- Talk to us on [Discord](https://dxos.org/discord)
|
|
10
10
|
|
|
11
11
|
## Contributions
|
|
12
12
|
|
|
13
13
|
Your ideas, issues, and code are most welcome. Please take a look at our [community code of conduct](https://github.com/dxos/dxos/blob/main/CODE_OF_CONDUCT.md), the [issue guide](https://github.com/dxos/dxos/blob/main/CONTRIBUTING.md#submitting-issues), and the [PR contribution guide](https://github.com/dxos/dxos/blob/main/CONTRIBUTING.md#submitting-prs).
|
|
14
14
|
|
|
15
|
-
License: [
|
|
15
|
+
License: [FSL-1.1-Apache-2.0](./LICENSE) Copyright 2024 © DXOS
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
+
|
|
3
|
+
// src/WnfsPlugin.tsx
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { Capability, Plugin } from "@dxos/app-framework";
|
|
6
|
+
import { AppPlugin } from "@dxos/app-toolkit";
|
|
7
|
+
import { ClientEvents } from "@dxos/plugin-client";
|
|
8
|
+
import { Backend, Blockstore, UrlResolver } from "#capabilities";
|
|
9
|
+
import { meta } from "#meta";
|
|
10
|
+
import { translations } from "#translations";
|
|
11
|
+
import { WnfsCapabilities } from "#types";
|
|
12
|
+
|
|
13
|
+
// raw-loader:/__w/dxos/dxos/packages/plugins/plugin-wnfs/PLUGIN.mdl?raw
|
|
14
|
+
var PLUGIN_default = "---\nid: org.dxos.plugin.wnfs\nname: WnfsPlugin\nversion: 0.1.0\n---\n\nA decentralized file storage plugin for `DXOS` Composer built on the Web Native File System (WNFS) protocol.\n\nWNFS provides a content-addressed, end-to-end encrypted private file system using a cryptographic \"dark forest\"\nstructure. Files are stored as IPLD blocks referenced by CID, replicated to a DXOS edge blob service, and\ncached locally in IndexedDB. The plugin implements the `FileCapabilities.Backend` and `FileCapabilities.UrlResolver`\ninterfaces from `plugin-file`, so other plugins (e.g. `plugin-gallery`) can upload and resolve `wnfs://` URLs\nwithout knowing the underlying storage layer. Uploads are scoped to a DXOS Space; the private forest root CID\nis persisted on the space's ECHO properties and updated atomically on every write.\n\n## Extensions\n\nThe following extension dialects are used in this document.\nEach extension is defined in the Appendix or resolved via its URI.\n\n| Term | URI |\n|-------------|--------------------------------|\n| `type` | `org.dxos.mdl.type@1.0` |\n| `feat` | `org.dxos.mdl.feat@1.0` |\n| `test` | `org.dxos.mdl.test@1.0` |\n| `component` | `org.dxos.mdl.component@1.0` |\n| `op` | `org.dxos.mdl.op@1.0` |\n\n## Types\n\n```mdl\ntype Cid\n desc: A content-addressed identifier encoded as a base-32 multibase string (CIDv1, sha2-256).\n fields:\n value: string\n```\n\n```mdl\ntype WnfsPath\n desc: A WNFS path encoded as a wnfs:// URL; segments are percent-encoded.\n fields:\n url: string # wnfs://spaces/<spaceId>/files/<cid>\n```\n\n```mdl\ntype FileInfo\n desc: Metadata returned after a successful upload.\n fields:\n name: string # Original file name\n type: string # MIME type\n url: WnfsPath # wnfs:// URL for later retrieval\n cid: Cid # Raw content CID of the file bytes\n```\n\n```mdl\ntype BlockstoreState\n desc: Runtime state of the MixedBlockstore.\n fields:\n isConnected: boolean # Online / offline status\n queueLength: number # Blocks pending remote flush\n apiHost: string # DXOS edge service base URL\n```\n\n```mdl\ntype WnfsInstances\n desc: In-memory cache mapping forest CID \u2192 loaded PrivateDirectory/PrivateForest pair.\n fields:\n entries: Record<string, WnfsInstance>\n```\n\n```mdl\ntype WnfsInstance\n desc: A loaded WNFS private file system rooted at a specific forest CID.\n fields:\n directory: PrivateDirectory\n forest: PrivateForest\n```\n```\n\n## Operations\n\nPure operations \u2014 no external effects.\n\n```mdl\nop wnfsUrl\n desc: Encodes a WNFS path array into a wnfs:// URL with percent-encoded segments.\n input:\n filePath: string[]\n output: string\n```\n\n```mdl\nop getPathFromUrl\n desc: Decodes a wnfs:// URL back into a path segment array.\n input:\n wnfsUrl: string\n output: string[]\n```\n\nEffectful operations \u2014 read or write persistent state.\n\n```mdl\nop uploadFile\n desc: |\n Reads a File, writes its bytes into the WNFS private directory at a\n CID-derived path, persists the updated forest pointer to the space's\n ECHO properties, and returns FileInfo.\n input:\n file: File\n space: Space\n output: FileInfo\n effects: [fs:read, echo:write, http:post]\n errors:\n SpaceNotFound: no space matching the given spaceId\n BlockstoreError: local or remote block I/O failure\n note: |\n Uses sha256 for block hashing (not the rs-wnfs default Blake3) for\n broader runtime support. Writes are atomic from ECHO's perspective:\n the forest CID property is only updated after the forest is stored.\n```\n\n```mdl\nop resolveWnfsUrl\n desc: |\n Reads file bytes from the WNFS private directory for a wnfs:// URL,\n wraps them in a Blob, and returns an object URL for in-browser display.\n input:\n wnfsUrl: string\n space: Space\n type?: string # MIME type hint for Blob construction\n output: string # blob: object URL\n effects: [fs:read, http:get]\n errors:\n UrlNotResolvable: space not available or file not found in directory\n note: Errors are swallowed and undefined is returned to avoid breaking image renders.\n```\n\n```mdl\nop flushBlockQueue\n desc: |\n Drains the pending-upload queue by assembling a CAR archive and POSTing\n it to the edge blob service. Called automatically (debounced 5 s) on\n block writes and on the browser `online` event.\n input:\n blockstore: Blockstore\n output: void\n effects: [http:post]\n errors:\n NetworkError: remote POST failed\n note: Queue is persisted to IndexedDB so pending blocks survive page reloads.\n```\n\n## Features\n\n```mdl\nfeat F-1: Upload File\n\n req F-1.1:\n when: a FileCapabilities.Backend consumer calls upload(file, db)\n then: the file is read, hashed, written to the WNFS private directory,\n and stored in the blockstore\n\n req F-1.2:\n when: the write succeeds\n then: the space's ECHO properties are updated with the new privateForestCid\n\n req F-1.3:\n when: the upload completes\n then: a FileInfo object with name, type, size, and wnfs:// URL is returned to the caller\n```\n\n```mdl\nfeat F-2: Resolve wnfs:// URL\n\n req F-2.1:\n when: FileCapabilities.UrlResolver.test(url) is called with a wnfs:// URL\n then: returns true\n\n req F-2.2:\n when: FileCapabilities.UrlResolver.resolve(url, file, space) is called\n then: loads or reuses a cached WnfsInstance for the space and reads the file bytes\n\n req F-2.3:\n when: bytes are successfully read\n then: a blob: object URL is returned for in-browser display\n\n req F-2.4:\n when: the space is undefined or reading fails\n then: undefined is returned; no error is propagated\n```\n\n```mdl\nfeat F-3: Blockstore Sync\n\n req F-3.1:\n when: a block is written via blockstore.put\n then: the block is saved to IndexedDB and added to the upload queue\n\n req F-3.2:\n when: the browser is online and the queue is non-empty\n then: blocks are batched into a CAR archive and flushed to the edge service\n\n req F-3.3:\n when: the browser goes offline\n then: the queue is persisted to IndexedDB; no upload is attempted\n\n req F-3.4:\n when: the browser comes back online\n then: the flush is triggered automatically\n\n req F-3.5:\n when: a block is requested via blockstore.get and is present in IndexedDB\n then: the block is returned from the local cache without a network request\n\n req F-3.6:\n when: a block is not in IndexedDB and the browser is online\n then: the block is fetched from the edge service and cached locally\n```\n\n```mdl\nfeat F-4: Space-Scoped Private File System\n\n req F-4.1:\n when: a space has no existing WNFS properties\n then: a new PrivateForest and PrivateDirectory are created and stored\n\n req F-4.2:\n when: a space already has a privateForestCid\n then: the existing forest and directory are loaded from the blockstore\n\n req F-4.3:\n when: a WnfsInstance is already cached for the current forest CID\n then: the cached instance is reused without reloading from the blockstore\n\n req F-4.4:\n when: legacy snake_case WNFS properties are found on a space\n then: they are deleted and replaced with the current camelCase schema\n tags: [migration]\n```\n\n## Acceptance\n\n```mdl\ntest T-1: Upload creates FileInfo\n given: an active space and a registered WNFS backend\n when: a file is uploaded via the Backend capability\n then:\n - FileInfo.url starts with wnfs://\n - space.properties.wnfs.privateForestCid is set\n - block for the file is present in the local IndexedDB blockstore\n```\n\n```mdl\ntest T-2: Resolve uploaded file\n given: a file was previously uploaded and FileInfo.url is known\n when: UrlResolver.resolve(url, file, space) is called\n then:\n - a blob: object URL is returned\n - URL can be used as an img src\n```\n\n```mdl\ntest T-3: Resolve without space returns undefined\n given: a wnfs:// URL\n when: UrlResolver.resolve is called with space = undefined\n then: undefined is returned; no error is thrown\n```\n\n```mdl\ntest T-4: Blockstore offline queuing\n given: the browser is offline\n when: a block is written to the blockstore\n then:\n - block is saved to IndexedDB\n - queue length increases by 1\n - no HTTP request is made\n```\n\n```mdl\ntest T-5: Blockstore queue flushes on reconnect\n given: blocks are queued while offline\n when: the browser comes back online\n then:\n - a CAR archive containing all queued blocks is POSTed to the edge service\n - the queue is cleared\n```\n\n```mdl\ntest T-6: New space initializes WNFS directory\n given: a space with no WNFS properties\n when: loadWnfs is called for that space\n then:\n - a new PrivateForest and PrivateDirectory are created\n - space.properties.wnfs.privateForestCid is populated\n```\n\n```mdl\ntest T-7: WnfsInstance cache hit\n given: a WnfsInstance is cached for the current forest CID\n when: loadWnfs is called again for the same space\n then: the cached instance is returned; no blockstore reads occur\n```\n\n---\n\n## Appendix: Extension Definitions\n\nExtension block types used in this document are defined below using\nthe core `ext` primitive \u2014 the only construct the base language provides.\n\n```mdl\next type\n uri: org.dxos.mdl.type@1.0\n desc: A named data structure with typed fields and optional literals.\n fields:\n desc?: Prose\n fields?: FieldMap # name[?]: TypeExpr (# inline comment)\n literals?: UnionList # a | b | c\n extends?: TypeRef[]\n```\n\n```mdl\next feat\n uri: org.dxos.mdl.feat@1.0\n desc: A named feature grouping one or more requirements.\n fields:\n desc?: Prose\n req: RequirementList\n nesting: self # feat blocks may contain feat blocks\n```\n\n```mdl\next test\n uri: org.dxos.mdl.test@1.0\n desc: An acceptance scenario expressed as given / when / then steps.\n fields:\n given?: Step | Step[]\n when?: Step | Step[]\n then: Step | Step[]\n tags?: TagList\n```\n\n```mdl\next component\n uri: org.dxos.mdl.component@1.0\n desc: A UI component with props, internal state, slots, actions, and events.\n fields:\n desc?: Prose\n props?: FieldMap # external inputs (immutable inside component)\n state?: FieldMap # internal reactive state\n slots?: FieldMap # named ReactNode injection points\n actions?: ActionMap # methods the component exposes or handles\n emits?: EventMap # events the component raises to its parent\n layout?: CodeBlock # ASCII sketch of visual structure (non-normative)\n```\n\n```mdl\next op\n uri: org.dxos.mdl.op@1.0\n desc: |\n A named operation with typed inputs, outputs, and declared errors.\n Pure ops have no effects or requires. Effectful ops declare both.\n fields:\n desc?: Prose\n input?: FieldMap # named input parameters\n output?: TypeExpr # return type\n errors?: ErrorMap # name: Prose (when this error occurs)\n effects?: EffectList # echo:read | echo:write | http | fs | ...\n requires?: ServiceList # injected service dependencies\n note?: Prose # implementation guidance (non-normative)\n```\n";
|
|
15
|
+
|
|
16
|
+
// src/WnfsPlugin.tsx
|
|
17
|
+
var WnfsPlugin = Plugin.define(meta).pipe(AppPlugin.addTranslationsModule({
|
|
18
|
+
translations
|
|
19
|
+
}), Plugin.addModule({
|
|
20
|
+
id: "blockstore",
|
|
21
|
+
activatesOn: ClientEvents.ClientReady,
|
|
22
|
+
activate: Blockstore
|
|
23
|
+
}), Plugin.addModule({
|
|
24
|
+
id: "instances",
|
|
25
|
+
activatesOn: ClientEvents.ClientReady,
|
|
26
|
+
activate: () => Effect.sync(() => {
|
|
27
|
+
const instances = {};
|
|
28
|
+
return Capability.contributes(WnfsCapabilities.Instances, instances);
|
|
29
|
+
})
|
|
30
|
+
}), Plugin.addModule({
|
|
31
|
+
id: "backend",
|
|
32
|
+
activatesOn: ClientEvents.ClientReady,
|
|
33
|
+
activate: Backend
|
|
34
|
+
}), Plugin.addModule({
|
|
35
|
+
id: "url-resolver",
|
|
36
|
+
activatesOn: ClientEvents.ClientReady,
|
|
37
|
+
activate: UrlResolver
|
|
38
|
+
}), AppPlugin.addPluginAssetModule({
|
|
39
|
+
asset: {
|
|
40
|
+
pluginId: meta.id,
|
|
41
|
+
path: "PLUGIN.mdl",
|
|
42
|
+
content: PLUGIN_default,
|
|
43
|
+
mimeType: "application/x-mdl"
|
|
44
|
+
}
|
|
45
|
+
}), Plugin.make);
|
|
46
|
+
var WnfsPlugin_default = WnfsPlugin;
|
|
47
|
+
export {
|
|
48
|
+
WnfsPlugin,
|
|
49
|
+
WnfsPlugin_default as default
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=WnfsPlugin.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/WnfsPlugin.tsx", "raw-loader:/__w/dxos/dxos/packages/plugins/plugin-wnfs/PLUGIN.mdl?raw"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capability, Plugin } from '@dxos/app-framework';\nimport { AppPlugin } from '@dxos/app-toolkit';\nimport { ClientEvents } from '@dxos/plugin-client';\n\nimport { Backend, Blockstore, UrlResolver } from '#capabilities';\nimport { meta } from '#meta';\nimport { translations } from '#translations';\nimport { WnfsCapabilities } from '#types';\n\n// eslint-disable-next-line import/no-relative-packages\nimport pluginSpec from '../PLUGIN.mdl?raw';\n\nexport const WnfsPlugin = Plugin.define(meta).pipe(\n AppPlugin.addTranslationsModule({ translations }),\n Plugin.addModule({\n id: 'blockstore',\n activatesOn: ClientEvents.ClientReady,\n activate: Blockstore,\n }),\n Plugin.addModule({\n id: 'instances',\n activatesOn: ClientEvents.ClientReady,\n activate: () =>\n Effect.sync(() => {\n const instances: WnfsCapabilities.Instances = {};\n return Capability.contributes(WnfsCapabilities.Instances, instances);\n }),\n }),\n Plugin.addModule({\n id: 'backend',\n activatesOn: ClientEvents.ClientReady,\n activate: Backend,\n }),\n Plugin.addModule({\n id: 'url-resolver',\n activatesOn: ClientEvents.ClientReady,\n activate: UrlResolver,\n }),\n AppPlugin.addPluginAssetModule({\n asset: { pluginId: meta.id, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },\n }),\n Plugin.make,\n);\n\nexport default WnfsPlugin;\n", "---\nid: org.dxos.plugin.wnfs\nname: WnfsPlugin\nversion: 0.1.0\n---\n\nA decentralized file storage plugin for `DXOS` Composer built on the Web Native File System (WNFS) protocol.\n\nWNFS provides a content-addressed, end-to-end encrypted private file system using a cryptographic \"dark forest\"\nstructure. Files are stored as IPLD blocks referenced by CID, replicated to a DXOS edge blob service, and\ncached locally in IndexedDB. The plugin implements the `FileCapabilities.Backend` and `FileCapabilities.UrlResolver`\ninterfaces from `plugin-file`, so other plugins (e.g. `plugin-gallery`) can upload and resolve `wnfs://` URLs\nwithout knowing the underlying storage layer. Uploads are scoped to a DXOS Space; the private forest root CID\nis persisted on the space's ECHO properties and updated atomically on every write.\n\n## Extensions\n\nThe following extension dialects are used in this document.\nEach extension is defined in the Appendix or resolved via its URI.\n\n| Term | URI |\n|-------------|--------------------------------|\n| `type` | `org.dxos.mdl.type@1.0` |\n| `feat` | `org.dxos.mdl.feat@1.0` |\n| `test` | `org.dxos.mdl.test@1.0` |\n| `component` | `org.dxos.mdl.component@1.0` |\n| `op` | `org.dxos.mdl.op@1.0` |\n\n## Types\n\n```mdl\ntype Cid\n desc: A content-addressed identifier encoded as a base-32 multibase string (CIDv1, sha2-256).\n fields:\n value: string\n```\n\n```mdl\ntype WnfsPath\n desc: A WNFS path encoded as a wnfs:// URL; segments are percent-encoded.\n fields:\n url: string # wnfs://spaces/<spaceId>/files/<cid>\n```\n\n```mdl\ntype FileInfo\n desc: Metadata returned after a successful upload.\n fields:\n name: string # Original file name\n type: string # MIME type\n url: WnfsPath # wnfs:// URL for later retrieval\n cid: Cid # Raw content CID of the file bytes\n```\n\n```mdl\ntype BlockstoreState\n desc: Runtime state of the MixedBlockstore.\n fields:\n isConnected: boolean # Online / offline status\n queueLength: number # Blocks pending remote flush\n apiHost: string # DXOS edge service base URL\n```\n\n```mdl\ntype WnfsInstances\n desc: In-memory cache mapping forest CID \u2192 loaded PrivateDirectory/PrivateForest pair.\n fields:\n entries: Record<string, WnfsInstance>\n```\n\n```mdl\ntype WnfsInstance\n desc: A loaded WNFS private file system rooted at a specific forest CID.\n fields:\n directory: PrivateDirectory\n forest: PrivateForest\n```\n```\n\n## Operations\n\nPure operations \u2014 no external effects.\n\n```mdl\nop wnfsUrl\n desc: Encodes a WNFS path array into a wnfs:// URL with percent-encoded segments.\n input:\n filePath: string[]\n output: string\n```\n\n```mdl\nop getPathFromUrl\n desc: Decodes a wnfs:// URL back into a path segment array.\n input:\n wnfsUrl: string\n output: string[]\n```\n\nEffectful operations \u2014 read or write persistent state.\n\n```mdl\nop uploadFile\n desc: |\n Reads a File, writes its bytes into the WNFS private directory at a\n CID-derived path, persists the updated forest pointer to the space's\n ECHO properties, and returns FileInfo.\n input:\n file: File\n space: Space\n output: FileInfo\n effects: [fs:read, echo:write, http:post]\n errors:\n SpaceNotFound: no space matching the given spaceId\n BlockstoreError: local or remote block I/O failure\n note: |\n Uses sha256 for block hashing (not the rs-wnfs default Blake3) for\n broader runtime support. Writes are atomic from ECHO's perspective:\n the forest CID property is only updated after the forest is stored.\n```\n\n```mdl\nop resolveWnfsUrl\n desc: |\n Reads file bytes from the WNFS private directory for a wnfs:// URL,\n wraps them in a Blob, and returns an object URL for in-browser display.\n input:\n wnfsUrl: string\n space: Space\n type?: string # MIME type hint for Blob construction\n output: string # blob: object URL\n effects: [fs:read, http:get]\n errors:\n UrlNotResolvable: space not available or file not found in directory\n note: Errors are swallowed and undefined is returned to avoid breaking image renders.\n```\n\n```mdl\nop flushBlockQueue\n desc: |\n Drains the pending-upload queue by assembling a CAR archive and POSTing\n it to the edge blob service. Called automatically (debounced 5 s) on\n block writes and on the browser `online` event.\n input:\n blockstore: Blockstore\n output: void\n effects: [http:post]\n errors:\n NetworkError: remote POST failed\n note: Queue is persisted to IndexedDB so pending blocks survive page reloads.\n```\n\n## Features\n\n```mdl\nfeat F-1: Upload File\n\n req F-1.1:\n when: a FileCapabilities.Backend consumer calls upload(file, db)\n then: the file is read, hashed, written to the WNFS private directory,\n and stored in the blockstore\n\n req F-1.2:\n when: the write succeeds\n then: the space's ECHO properties are updated with the new privateForestCid\n\n req F-1.3:\n when: the upload completes\n then: a FileInfo object with name, type, size, and wnfs:// URL is returned to the caller\n```\n\n```mdl\nfeat F-2: Resolve wnfs:// URL\n\n req F-2.1:\n when: FileCapabilities.UrlResolver.test(url) is called with a wnfs:// URL\n then: returns true\n\n req F-2.2:\n when: FileCapabilities.UrlResolver.resolve(url, file, space) is called\n then: loads or reuses a cached WnfsInstance for the space and reads the file bytes\n\n req F-2.3:\n when: bytes are successfully read\n then: a blob: object URL is returned for in-browser display\n\n req F-2.4:\n when: the space is undefined or reading fails\n then: undefined is returned; no error is propagated\n```\n\n```mdl\nfeat F-3: Blockstore Sync\n\n req F-3.1:\n when: a block is written via blockstore.put\n then: the block is saved to IndexedDB and added to the upload queue\n\n req F-3.2:\n when: the browser is online and the queue is non-empty\n then: blocks are batched into a CAR archive and flushed to the edge service\n\n req F-3.3:\n when: the browser goes offline\n then: the queue is persisted to IndexedDB; no upload is attempted\n\n req F-3.4:\n when: the browser comes back online\n then: the flush is triggered automatically\n\n req F-3.5:\n when: a block is requested via blockstore.get and is present in IndexedDB\n then: the block is returned from the local cache without a network request\n\n req F-3.6:\n when: a block is not in IndexedDB and the browser is online\n then: the block is fetched from the edge service and cached locally\n```\n\n```mdl\nfeat F-4: Space-Scoped Private File System\n\n req F-4.1:\n when: a space has no existing WNFS properties\n then: a new PrivateForest and PrivateDirectory are created and stored\n\n req F-4.2:\n when: a space already has a privateForestCid\n then: the existing forest and directory are loaded from the blockstore\n\n req F-4.3:\n when: a WnfsInstance is already cached for the current forest CID\n then: the cached instance is reused without reloading from the blockstore\n\n req F-4.4:\n when: legacy snake_case WNFS properties are found on a space\n then: they are deleted and replaced with the current camelCase schema\n tags: [migration]\n```\n\n## Acceptance\n\n```mdl\ntest T-1: Upload creates FileInfo\n given: an active space and a registered WNFS backend\n when: a file is uploaded via the Backend capability\n then:\n - FileInfo.url starts with wnfs://\n - space.properties.wnfs.privateForestCid is set\n - block for the file is present in the local IndexedDB blockstore\n```\n\n```mdl\ntest T-2: Resolve uploaded file\n given: a file was previously uploaded and FileInfo.url is known\n when: UrlResolver.resolve(url, file, space) is called\n then:\n - a blob: object URL is returned\n - URL can be used as an img src\n```\n\n```mdl\ntest T-3: Resolve without space returns undefined\n given: a wnfs:// URL\n when: UrlResolver.resolve is called with space = undefined\n then: undefined is returned; no error is thrown\n```\n\n```mdl\ntest T-4: Blockstore offline queuing\n given: the browser is offline\n when: a block is written to the blockstore\n then:\n - block is saved to IndexedDB\n - queue length increases by 1\n - no HTTP request is made\n```\n\n```mdl\ntest T-5: Blockstore queue flushes on reconnect\n given: blocks are queued while offline\n when: the browser comes back online\n then:\n - a CAR archive containing all queued blocks is POSTed to the edge service\n - the queue is cleared\n```\n\n```mdl\ntest T-6: New space initializes WNFS directory\n given: a space with no WNFS properties\n when: loadWnfs is called for that space\n then:\n - a new PrivateForest and PrivateDirectory are created\n - space.properties.wnfs.privateForestCid is populated\n```\n\n```mdl\ntest T-7: WnfsInstance cache hit\n given: a WnfsInstance is cached for the current forest CID\n when: loadWnfs is called again for the same space\n then: the cached instance is returned; no blockstore reads occur\n```\n\n---\n\n## Appendix: Extension Definitions\n\nExtension block types used in this document are defined below using\nthe core `ext` primitive \u2014 the only construct the base language provides.\n\n```mdl\next type\n uri: org.dxos.mdl.type@1.0\n desc: A named data structure with typed fields and optional literals.\n fields:\n desc?: Prose\n fields?: FieldMap # name[?]: TypeExpr (# inline comment)\n literals?: UnionList # a | b | c\n extends?: TypeRef[]\n```\n\n```mdl\next feat\n uri: org.dxos.mdl.feat@1.0\n desc: A named feature grouping one or more requirements.\n fields:\n desc?: Prose\n req: RequirementList\n nesting: self # feat blocks may contain feat blocks\n```\n\n```mdl\next test\n uri: org.dxos.mdl.test@1.0\n desc: An acceptance scenario expressed as given / when / then steps.\n fields:\n given?: Step | Step[]\n when?: Step | Step[]\n then: Step | Step[]\n tags?: TagList\n```\n\n```mdl\next component\n uri: org.dxos.mdl.component@1.0\n desc: A UI component with props, internal state, slots, actions, and events.\n fields:\n desc?: Prose\n props?: FieldMap # external inputs (immutable inside component)\n state?: FieldMap # internal reactive state\n slots?: FieldMap # named ReactNode injection points\n actions?: ActionMap # methods the component exposes or handles\n emits?: EventMap # events the component raises to its parent\n layout?: CodeBlock # ASCII sketch of visual structure (non-normative)\n```\n\n```mdl\next op\n uri: org.dxos.mdl.op@1.0\n desc: |\n A named operation with typed inputs, outputs, and declared errors.\n Pure ops have no effects or requires. Effectful ops declare both.\n fields:\n desc?: Prose\n input?: FieldMap # named input parameters\n output?: TypeExpr # return type\n errors?: ErrorMap # name: Prose (when this error occurs)\n effects?: EffectList # echo:read | echo:write | http | fs | ...\n requires?: ServiceList # injected service dependencies\n note?: Prose # implementation guidance (non-normative)\n```\n"],
|
|
5
|
+
"mappings": ";;;AAIA,YAAYA,YAAY;AAExB,SAASC,YAAYC,cAAc;AACnC,SAASC,iBAAiB;AAC1B,SAASC,oBAAoB;AAE7B,SAASC,SAASC,YAAYC,mBAAmB;AACjD,SAASC,YAAY;AACrB,SAASC,oBAAoB;AAC7B,SAASC,wBAAwB;;;ACbjC;;;ADkBO,IAAMC,aAAaC,OAAOC,OAAOC,IAAAA,EAAMC,KAC5CC,UAAUC,sBAAsB;EAAEC;AAAa,CAAA,GAC/CN,OAAOO,UAAU;EACfC,IAAI;EACJC,aAAaC,aAAaC;EAC1BC,UAAUC;AACZ,CAAA,GACAb,OAAOO,UAAU;EACfC,IAAI;EACJC,aAAaC,aAAaC;EAC1BC,UAAU,MACDE,YAAK,MAAA;AACV,UAAMC,YAAwC,CAAC;AAC/C,WAAOC,WAAWC,YAAYC,iBAAiBC,WAAWJ,SAAAA;EAC5D,CAAA;AACJ,CAAA,GACAf,OAAOO,UAAU;EACfC,IAAI;EACJC,aAAaC,aAAaC;EAC1BC,UAAUQ;AACZ,CAAA,GACApB,OAAOO,UAAU;EACfC,IAAI;EACJC,aAAaC,aAAaC;EAC1BC,UAAUS;AACZ,CAAA,GACAjB,UAAUkB,qBAAqB;EAC7BC,OAAO;IAAEC,UAAUtB,KAAKM;IAAIiB,MAAM;IAAcC,SAASC;IAAYC,UAAU;EAAoB;AACrG,CAAA,GACA5B,OAAO6B,IAAI;AAGb,IAAA,qBAAe9B;",
|
|
6
|
+
"names": ["Effect", "Capability", "Plugin", "AppPlugin", "ClientEvents", "Backend", "Blockstore", "UrlResolver", "meta", "translations", "WnfsCapabilities", "WnfsPlugin", "Plugin", "define", "meta", "pipe", "AppPlugin", "addTranslationsModule", "translations", "addModule", "id", "activatesOn", "ClientEvents", "ClientReady", "activate", "Blockstore", "sync", "instances", "Capability", "contributes", "WnfsCapabilities", "Instances", "Backend", "UrlResolver", "addPluginAssetModule", "asset", "pluginId", "path", "content", "pluginSpec", "mimeType", "make"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
+
|
|
3
|
+
// src/WnfsPlugin.node.ts
|
|
4
|
+
import { Plugin } from "@dxos/app-framework";
|
|
5
|
+
import { meta } from "#meta";
|
|
6
|
+
var WnfsPlugin = Plugin.define(meta).pipe(Plugin.make);
|
|
7
|
+
var WnfsPlugin_node_default = WnfsPlugin;
|
|
8
|
+
export {
|
|
9
|
+
WnfsPlugin,
|
|
10
|
+
WnfsPlugin_node_default as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=WnfsPlugin.node.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/WnfsPlugin.node.ts"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\n\nimport { meta } from '#meta';\n\nexport const WnfsPlugin = Plugin.define(meta).pipe(Plugin.make);\n\nexport default WnfsPlugin;\n"],
|
|
5
|
+
"mappings": ";;;AAIA,SAASA,cAAc;AAEvB,SAASC,YAAY;AAEd,IAAMC,aAAaF,OAAOG,OAAOF,IAAAA,EAAMG,KAAKJ,OAAOK,IAAI;AAE9D,IAAA,0BAAeH;",
|
|
6
|
+
"names": ["Plugin", "meta", "WnfsPlugin", "define", "pipe", "make"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
+
|
|
3
|
+
// src/WnfsPlugin.workerd.ts
|
|
4
|
+
import { Plugin } from "@dxos/app-framework";
|
|
5
|
+
import { meta } from "#meta";
|
|
6
|
+
var WnfsPlugin = Plugin.define(meta).pipe(Plugin.make);
|
|
7
|
+
var WnfsPlugin_workerd_default = WnfsPlugin;
|
|
8
|
+
export {
|
|
9
|
+
WnfsPlugin,
|
|
10
|
+
WnfsPlugin_workerd_default as default
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=WnfsPlugin.workerd.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/WnfsPlugin.workerd.ts"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\n\nimport { meta } from '#meta';\n\nexport const WnfsPlugin = Plugin.define(meta).pipe(Plugin.make);\n\nexport default WnfsPlugin;\n"],
|
|
5
|
+
"mappings": ";;;AAIA,SAASA,cAAc;AAEvB,SAASC,YAAY;AAEd,IAAMC,aAAaF,OAAOG,OAAOF,IAAAA,EAAMG,KAAKJ,OAAOK,IAAI;AAE9D,IAAA,6BAAeH;",
|
|
6
|
+
"names": ["Plugin", "meta", "WnfsPlugin", "define", "pipe", "make"]
|
|
7
|
+
}
|