@dxos/plugin-wnfs 0.8.4-main.c85a9c8dae → 0.8.4-main.d9fc60f731

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.
Files changed (180) hide show
  1. package/LICENSE +102 -5
  2. package/PLUGIN.mdl +371 -0
  3. package/README.md +2 -2
  4. package/dist/lib/neutral/WnfsPlugin.mjs +51 -0
  5. package/dist/lib/neutral/WnfsPlugin.mjs.map +7 -0
  6. package/dist/lib/neutral/WnfsPlugin.node.mjs +12 -0
  7. package/dist/lib/neutral/WnfsPlugin.node.mjs.map +7 -0
  8. package/dist/lib/neutral/WnfsPlugin.workerd.mjs +12 -0
  9. package/dist/lib/neutral/WnfsPlugin.workerd.mjs.map +7 -0
  10. package/dist/lib/neutral/backend-4HPTHS33.mjs +47 -0
  11. package/dist/lib/neutral/backend-4HPTHS33.mjs.map +7 -0
  12. package/dist/lib/{browser/blockstore-Q66HQJ4Q.mjs → neutral/blockstore-U6SPNL2C.mjs} +7 -8
  13. package/dist/lib/neutral/blockstore-U6SPNL2C.mjs.map +7 -0
  14. package/dist/lib/neutral/capabilities/index.mjs +13 -0
  15. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  16. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  17. package/dist/lib/neutral/chunk-KUQHRT52.mjs +39 -0
  18. package/dist/lib/neutral/chunk-KUQHRT52.mjs.map +7 -0
  19. package/dist/lib/{browser/chunk-UC4HMNZZ.mjs → neutral/chunk-L33MOD23.mjs} +80 -30
  20. package/dist/lib/neutral/chunk-L33MOD23.mjs.map +7 -0
  21. package/dist/lib/neutral/chunk-QODCKFTY.mjs +19 -0
  22. package/dist/lib/neutral/chunk-QODCKFTY.mjs.map +7 -0
  23. package/dist/lib/neutral/helpers/index.mjs +26 -0
  24. package/dist/lib/neutral/helpers/index.mjs.map +7 -0
  25. package/dist/lib/neutral/index.mjs +36 -0
  26. package/dist/lib/neutral/index.mjs.map +7 -0
  27. package/dist/lib/neutral/meta.json +1 -0
  28. package/dist/lib/neutral/meta.mjs +8 -0
  29. package/dist/lib/neutral/meta.mjs.map +7 -0
  30. package/dist/lib/neutral/plugin.mjs +12 -0
  31. package/dist/lib/neutral/plugin.mjs.map +7 -0
  32. package/dist/lib/neutral/translations.mjs +17 -0
  33. package/dist/lib/neutral/translations.mjs.map +7 -0
  34. package/dist/lib/neutral/types/index.mjs +8 -0
  35. package/dist/lib/neutral/types/index.mjs.map +7 -0
  36. package/dist/lib/neutral/url-resolver-BIRH7WHL.mjs +47 -0
  37. package/dist/lib/neutral/url-resolver-BIRH7WHL.mjs.map +7 -0
  38. package/dist/types/src/WnfsPlugin.d.ts +1 -0
  39. package/dist/types/src/WnfsPlugin.d.ts.map +1 -1
  40. package/dist/types/src/WnfsPlugin.node.d.ts +4 -0
  41. package/dist/types/src/WnfsPlugin.node.d.ts.map +1 -0
  42. package/dist/types/src/WnfsPlugin.test.d.ts +2 -0
  43. package/dist/types/src/WnfsPlugin.test.d.ts.map +1 -0
  44. package/dist/types/src/WnfsPlugin.workerd.d.ts +4 -0
  45. package/dist/types/src/WnfsPlugin.workerd.d.ts.map +1 -0
  46. package/dist/types/src/annotations.d.ts +7 -0
  47. package/dist/types/src/annotations.d.ts.map +1 -0
  48. package/dist/types/src/blockstore.d.ts.map +1 -1
  49. package/dist/types/src/capabilities/backend.d.ts +7 -0
  50. package/dist/types/src/capabilities/backend.d.ts.map +1 -0
  51. package/dist/types/src/capabilities/blockstore.d.ts.map +1 -0
  52. package/dist/types/src/capabilities/index.d.ts +5 -5
  53. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  54. package/dist/types/src/capabilities/url-resolver.d.ts +7 -0
  55. package/dist/types/src/capabilities/url-resolver.d.ts.map +1 -0
  56. package/dist/types/src/helpers/common.d.ts.map +1 -1
  57. package/dist/types/src/helpers/load.d.ts +1 -1
  58. package/dist/types/src/helpers/load.d.ts.map +1 -1
  59. package/dist/types/src/helpers/upload.d.ts +1 -1
  60. package/dist/types/src/helpers/upload.d.ts.map +1 -1
  61. package/dist/types/src/helpers/wnfs-url.d.ts.map +1 -1
  62. package/dist/types/src/index.d.ts +3 -2
  63. package/dist/types/src/index.d.ts.map +1 -1
  64. package/dist/types/src/meta.d.ts +1 -1
  65. package/dist/types/src/meta.d.ts.map +1 -1
  66. package/dist/types/src/plugin.d.ts +3 -0
  67. package/dist/types/src/plugin.d.ts.map +1 -0
  68. package/dist/types/src/translations.d.ts +2 -15
  69. package/dist/types/src/translations.d.ts.map +1 -1
  70. package/dist/types/src/types/WnfsCapabilities.d.ts +11 -0
  71. package/dist/types/src/types/WnfsCapabilities.d.ts.map +1 -0
  72. package/dist/types/src/types/index.d.ts +1 -3
  73. package/dist/types/src/types/index.d.ts.map +1 -1
  74. package/dist/types/tsconfig.tsbuildinfo +1 -1
  75. package/package.json +77 -48
  76. package/src/WnfsPlugin.node.ts +11 -0
  77. package/src/WnfsPlugin.test.ts +20 -0
  78. package/src/WnfsPlugin.tsx +16 -41
  79. package/src/WnfsPlugin.workerd.ts +11 -0
  80. package/src/annotations.ts +16 -0
  81. package/src/blockstore.ts +1 -1
  82. package/src/capabilities/backend.ts +43 -0
  83. package/src/capabilities/{blockstore/blockstore.ts → blockstore.ts} +3 -2
  84. package/src/capabilities/index.ts +14 -5
  85. package/src/capabilities/url-resolver.ts +44 -0
  86. package/src/helpers/load.ts +33 -19
  87. package/src/helpers/upload.ts +13 -4
  88. package/src/index.ts +3 -2
  89. package/src/meta.ts +22 -6
  90. package/src/plugin.ts +9 -0
  91. package/src/translations.ts +2 -14
  92. package/src/types/WnfsCapabilities.ts +18 -0
  93. package/src/types/index.ts +1 -3
  94. package/src/vite-env.d.ts +10 -0
  95. package/dist/lib/browser/FileContainer-EILNV5LV.mjs +0 -74
  96. package/dist/lib/browser/FileContainer-EILNV5LV.mjs.map +0 -7
  97. package/dist/lib/browser/blockstore-Q66HQJ4Q.mjs.map +0 -7
  98. package/dist/lib/browser/chunk-BXGCJRL2.mjs +0 -155
  99. package/dist/lib/browser/chunk-BXGCJRL2.mjs.map +0 -7
  100. package/dist/lib/browser/chunk-HXVPBJ3A.mjs +0 -56
  101. package/dist/lib/browser/chunk-HXVPBJ3A.mjs.map +0 -7
  102. package/dist/lib/browser/chunk-UC4HMNZZ.mjs.map +0 -7
  103. package/dist/lib/browser/file-uploader-QL4DTOMM.mjs +0 -35
  104. package/dist/lib/browser/file-uploader-QL4DTOMM.mjs.map +0 -7
  105. package/dist/lib/browser/index.mjs +0 -117
  106. package/dist/lib/browser/index.mjs.map +0 -7
  107. package/dist/lib/browser/markdown-AYCVG6TJ.mjs +0 -201
  108. package/dist/lib/browser/markdown-AYCVG6TJ.mjs.map +0 -7
  109. package/dist/lib/browser/meta.json +0 -1
  110. package/dist/lib/browser/operation-resolver-YYWUF6EY.mjs +0 -99
  111. package/dist/lib/browser/operation-resolver-YYWUF6EY.mjs.map +0 -7
  112. package/dist/lib/browser/react-surface-3LKVDPH7.mjs +0 -60
  113. package/dist/lib/browser/react-surface-3LKVDPH7.mjs.map +0 -7
  114. package/dist/lib/browser/types/index.mjs +0 -13
  115. package/dist/types/src/capabilities/blockstore/blockstore.d.ts.map +0 -1
  116. package/dist/types/src/capabilities/blockstore/index.d.ts +0 -3
  117. package/dist/types/src/capabilities/blockstore/index.d.ts.map +0 -1
  118. package/dist/types/src/capabilities/file-uploader/file-uploader.d.ts +0 -6
  119. package/dist/types/src/capabilities/file-uploader/file-uploader.d.ts.map +0 -1
  120. package/dist/types/src/capabilities/file-uploader/index.d.ts +0 -3
  121. package/dist/types/src/capabilities/file-uploader/index.d.ts.map +0 -1
  122. package/dist/types/src/capabilities/markdown/index.d.ts +0 -3
  123. package/dist/types/src/capabilities/markdown/index.d.ts.map +0 -1
  124. package/dist/types/src/capabilities/markdown/markdown.d.ts +0 -5
  125. package/dist/types/src/capabilities/markdown/markdown.d.ts.map +0 -1
  126. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  127. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  128. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  129. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  130. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  131. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  132. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +0 -5
  133. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  134. package/dist/types/src/components/FileInput.d.ts +0 -8
  135. package/dist/types/src/components/FileInput.d.ts.map +0 -1
  136. package/dist/types/src/components/FilePreview.d.ts +0 -11
  137. package/dist/types/src/components/FilePreview.d.ts.map +0 -1
  138. package/dist/types/src/components/index.d.ts +0 -3
  139. package/dist/types/src/components/index.d.ts.map +0 -1
  140. package/dist/types/src/containers/FileContainer/FileContainer.d.ts +0 -7
  141. package/dist/types/src/containers/FileContainer/FileContainer.d.ts.map +0 -1
  142. package/dist/types/src/containers/FileContainer/index.d.ts +0 -3
  143. package/dist/types/src/containers/FileContainer/index.d.ts.map +0 -1
  144. package/dist/types/src/containers/index.d.ts +0 -3
  145. package/dist/types/src/containers/index.d.ts.map +0 -1
  146. package/dist/types/src/extensions/image.d.ts +0 -14
  147. package/dist/types/src/extensions/image.d.ts.map +0 -1
  148. package/dist/types/src/extensions/image.stories.d.ts +0 -11
  149. package/dist/types/src/extensions/image.stories.d.ts.map +0 -1
  150. package/dist/types/src/extensions/index.d.ts +0 -2
  151. package/dist/types/src/extensions/index.d.ts.map +0 -1
  152. package/dist/types/src/types/File.d.ts +0 -16
  153. package/dist/types/src/types/File.d.ts.map +0 -1
  154. package/dist/types/src/types/capabilities.d.ts +0 -13
  155. package/dist/types/src/types/capabilities.d.ts.map +0 -1
  156. package/dist/types/src/types/types.d.ts +0 -112
  157. package/dist/types/src/types/types.d.ts.map +0 -1
  158. package/src/capabilities/blockstore/index.ts +0 -7
  159. package/src/capabilities/file-uploader/file-uploader.ts +0 -34
  160. package/src/capabilities/file-uploader/index.ts +0 -7
  161. package/src/capabilities/markdown/index.ts +0 -7
  162. package/src/capabilities/markdown/markdown.ts +0 -33
  163. package/src/capabilities/operation-resolver/index.ts +0 -7
  164. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -62
  165. package/src/capabilities/react-surface/index.ts +0 -7
  166. package/src/capabilities/react-surface/react-surface.tsx +0 -51
  167. package/src/components/FileInput.tsx +0 -47
  168. package/src/components/FilePreview.tsx +0 -23
  169. package/src/components/index.ts +0 -6
  170. package/src/containers/FileContainer/FileContainer.tsx +0 -58
  171. package/src/containers/FileContainer/index.ts +0 -7
  172. package/src/containers/index.ts +0 -7
  173. package/src/extensions/image.stories.tsx +0 -104
  174. package/src/extensions/image.tsx +0 -203
  175. package/src/extensions/index.ts +0 -5
  176. package/src/types/File.ts +0 -24
  177. package/src/types/capabilities.ts +0 -18
  178. package/src/types/types.ts +0 -81
  179. /package/dist/lib/{browser/types/index.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
  180. /package/dist/types/src/capabilities/{blockstore/blockstore.d.ts → blockstore.d.ts} +0 -0
package/LICENSE CHANGED
@@ -1,8 +1,105 @@
1
- MIT License
2
- Copyright (c) 2024 DXOS
1
+ # Functional Source License, Version 1.1, ALv2 Future License
3
2
 
4
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3
+ ## Abbreviation
5
4
 
6
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5
+ FSL-1.1-Apache-2.0
7
6
 
8
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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://discord.gg/eXVfryv3sW)
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: [MIT](./LICENSE) Copyright 2024 © DXOS
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
+ }