@dxos/plugin-wnfs 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.14.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/PLUGIN.mdl +60 -10
- package/dist/lib/capabilities.browser.mjs +31 -0
- package/dist/lib/capabilities.browser.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +1 -3
- package/dist/lib/capabilities.mjs.map +1 -1
- package/dist/lib/capabilities.node.mjs.map +1 -1
- package/dist/lib/capabilities.tauri.mjs +31 -0
- package/dist/lib/capabilities.tauri.mjs.map +1 -0
- package/dist/lib/capabilities.workerd.mjs.map +1 -1
- package/dist/lib/chunk-PLUGIN.mjs +6 -0
- package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
- package/dist/lib/chunk-blob-backend.mjs +1 -1
- package/dist/lib/chunk-blob-backend.mjs.map +1 -1
- package/dist/lib/chunk-dependencies.mjs +1 -1
- package/dist/lib/chunk-dependencies.mjs.map +1 -1
- package/dist/lib/helpers.mjs +12 -2
- package/dist/lib/helpers.mjs.map +1 -1
- package/dist/lib/index.mjs +1 -4
- package/dist/lib/meta.mjs.map +1 -1
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/capabilities/blob-backend.d.ts +1 -1
- package/dist/types/src/capabilities/blob-backend.d.ts.map +1 -1
- package/dist/types/src/capabilities/gen/browser.d.ts +6 -0
- package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/tauri.d.ts +6 -0
- package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
- package/dist/types/src/helpers/index.d.ts +4 -4
- package/dist/types/src/helpers/index.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -4
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +2 -2
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +27 -25
- package/src/capabilities/blob-backend.test.ts +2 -2
- package/src/capabilities/blob-backend.ts +1 -1
- package/src/capabilities/dependencies.ts +1 -1
- package/src/capabilities/gen/browser.ts +50 -0
- package/src/capabilities/gen/node.ts +2 -2
- package/src/capabilities/gen/tauri.ts +50 -0
- package/src/capabilities/gen/workerd.ts +2 -2
- package/src/capabilities/index.ts +2 -2
- package/src/helpers/index.ts +4 -4
- package/src/helpers/load.ts +2 -2
- package/src/helpers/upload.ts +4 -4
- package/src/helpers/wnfs-url.ts +1 -1
- package/src/index.ts +1 -4
- package/src/meta.ts +1 -1
- package/src/plugin.test.ts +2 -2
- package/src/translations.ts +2 -2
- package/src/types/index.ts +2 -2
- package/dist/lib/chunk-annotations.mjs +0 -15
- package/dist/lib/chunk-annotations.mjs.map +0 -1
package/PLUGIN.mdl
CHANGED
|
@@ -24,9 +24,10 @@ Each extension is defined in the Appendix or resolved via its URI.
|
|
|
24
24
|
|-------------|--------------------------------|
|
|
25
25
|
| `type` | `org.dxos.mdl.type@1.0` |
|
|
26
26
|
| `feat` | `org.dxos.mdl.feat@1.0` |
|
|
27
|
-
| `
|
|
27
|
+
| `scenario` | `org.dxos.mdl.scenario@1.0` |
|
|
28
28
|
| `component` | `org.dxos.mdl.component@1.0` |
|
|
29
29
|
| `op` | `org.dxos.mdl.op@1.0` |
|
|
30
|
+
| `test` | `org.dxos.mdl.test@1.0` |
|
|
30
31
|
|
|
31
32
|
## Types
|
|
32
33
|
|
|
@@ -287,7 +288,7 @@ feat F-4: Space-Scoped Private File System
|
|
|
287
288
|
## Acceptance
|
|
288
289
|
|
|
289
290
|
```mdl
|
|
290
|
-
|
|
291
|
+
scenario T-1: Upload creates a wnfs:// blob
|
|
291
292
|
given: an active space and this plugin's BlobBackend registered on the ECHO Hypergraph
|
|
292
293
|
when: Blob.fromBytes is called with storage: 'wnfs'
|
|
293
294
|
then:
|
|
@@ -297,7 +298,7 @@ test T-1: Upload creates a wnfs:// blob
|
|
|
297
298
|
```
|
|
298
299
|
|
|
299
300
|
```mdl
|
|
300
|
-
|
|
301
|
+
scenario T-2: Resolve uploaded blob via getUrl
|
|
301
302
|
given: a blob was previously uploaded and its data.uri (a wnfs:// URI) is known
|
|
302
303
|
when: BlobBackend.getUrl(spaceId, uri, contentType) is called
|
|
303
304
|
then:
|
|
@@ -306,14 +307,14 @@ test T-2: Resolve uploaded blob via getUrl
|
|
|
306
307
|
```
|
|
307
308
|
|
|
308
309
|
```mdl
|
|
309
|
-
|
|
310
|
+
scenario T-3: get/has/getUrl return undefined/false when the space is not open
|
|
310
311
|
given: a wnfs:// URI whose space is not currently open
|
|
311
312
|
when: BlobBackend.get, .has, or .getUrl is called
|
|
312
313
|
then: undefined (or false for .has) is returned; no error is thrown
|
|
313
314
|
```
|
|
314
315
|
|
|
315
316
|
```mdl
|
|
316
|
-
|
|
317
|
+
scenario T-4: Blockstore offline queuing
|
|
317
318
|
given: the browser is offline
|
|
318
319
|
when: a block is written to the blockstore
|
|
319
320
|
then:
|
|
@@ -323,7 +324,7 @@ test T-4: Blockstore offline queuing
|
|
|
323
324
|
```
|
|
324
325
|
|
|
325
326
|
```mdl
|
|
326
|
-
|
|
327
|
+
scenario T-5: Blockstore queue flushes on reconnect
|
|
327
328
|
given: blocks are queued while offline
|
|
328
329
|
when: the browser comes back online
|
|
329
330
|
then:
|
|
@@ -332,7 +333,7 @@ test T-5: Blockstore queue flushes on reconnect
|
|
|
332
333
|
```
|
|
333
334
|
|
|
334
335
|
```mdl
|
|
335
|
-
|
|
336
|
+
scenario T-6: New space initializes WNFS directory
|
|
336
337
|
given: a space with no WNFS properties
|
|
337
338
|
when: loadWnfs is called for that space
|
|
338
339
|
then:
|
|
@@ -341,7 +342,7 @@ test T-6: New space initializes WNFS directory
|
|
|
341
342
|
```
|
|
342
343
|
|
|
343
344
|
```mdl
|
|
344
|
-
|
|
345
|
+
scenario T-7: WnfsInstance cache hit
|
|
345
346
|
given: a WnfsInstance is cached for the current forest CID
|
|
346
347
|
when: loadWnfs is called again for the same space
|
|
347
348
|
then: the cached instance is returned; no blockstore reads occur
|
|
@@ -349,6 +350,55 @@ test T-7: WnfsInstance cache hit
|
|
|
349
350
|
|
|
350
351
|
---
|
|
351
352
|
|
|
353
|
+
## QA
|
|
354
|
+
|
|
355
|
+
Executable tests. Each step carries a human instruction (`do`) and, where the step maps to an
|
|
356
|
+
invocable operation, an agent binding (`invoke`). Report failures by test and step number.
|
|
357
|
+
|
|
358
|
+
```mdl
|
|
359
|
+
test QA-1: Upload a file and resolve it back through the wnfs:// scheme
|
|
360
|
+
desc: |
|
|
361
|
+
plugin-wnfs has no ECHO type or navtree surface of its own — it is a `BlobBackend`
|
|
362
|
+
(`schemes: ['wnfs']`) that `plugin-file` dispatches to transparently through
|
|
363
|
+
`Blob.fromBytes`/`Blob.read`/`Blob.url`. None of its operations (`wnfsBlobPut`,
|
|
364
|
+
`wnfsBlobGet`, `wnfsBlobHas`, `wnfsBlobGetUrl`, `uploadFile`) is an `Operation.make` with a
|
|
365
|
+
`key:` — they are internal backend methods dispatched by core Blob.ts, not entries an
|
|
366
|
+
invoker can look up — so this test is human-only end to end.
|
|
367
|
+
actors: human
|
|
368
|
+
covers: [F-1.1, F-1.2, F-1.3, F-2.1, F-2.3]
|
|
369
|
+
status: unverified
|
|
370
|
+
given:
|
|
371
|
+
- an identity exists
|
|
372
|
+
- space: a space is open
|
|
373
|
+
- plugin-wnfs is enabled — it ships only in the experimental set; it is OFF by default
|
|
374
|
+
- plugin-file is enabled, so drag-and-drop upload and the File article are available
|
|
375
|
+
- the space's file storage backend is set to WNFS (Settings -> Space -> Storage, or the
|
|
376
|
+
space's default when plugin-wnfs is the only storage backend enabled)
|
|
377
|
+
- no file named "qa-upload.png" exists in the space — ABORT rather than deleting one this
|
|
378
|
+
test did not create
|
|
379
|
+
steps:
|
|
380
|
+
- name: Upload a file
|
|
381
|
+
do: Drag "qa-upload.png" onto the space (or a collection) in the navtree.
|
|
382
|
+
expect: a File object named "qa-upload.png" appears in the navtree
|
|
383
|
+
note: |
|
|
384
|
+
NO `invoke` — `wnfsBlobPut` is reached through `Blob.fromBytes`, called by plugin-file's
|
|
385
|
+
upload handler, not through the operation invoker; there is no `key:` to resolve.
|
|
386
|
+
- name: Confirm the blob URI uses the wnfs scheme
|
|
387
|
+
do: Right-click the file and choose "Copy link" (or inspect its properties panel).
|
|
388
|
+
expect: the underlying blob URI starts with `wnfs://spaces/`
|
|
389
|
+
- name: Open the file
|
|
390
|
+
do: Click "qa-upload.png" in the navtree.
|
|
391
|
+
expect: the image renders in the File article — `wnfsBlobGetUrl` resolved the bytes to a
|
|
392
|
+
displayable `blob:` object URL
|
|
393
|
+
after:
|
|
394
|
+
- name: Delete the file
|
|
395
|
+
do: Right-click "qa-upload.png" in the navtree and choose Delete.
|
|
396
|
+
expect: the row leaves the navtree and its plank closes
|
|
397
|
+
note: NO `invoke` — the uploaded File was never captured (no step above invokes anything), so there is no live ref to pass to `space.removeObjects`; deletion is a navtree click.
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
352
402
|
## Appendix: Extension Definitions
|
|
353
403
|
|
|
354
404
|
Extension block types used in this document are defined below using
|
|
@@ -376,8 +426,8 @@ ext feat
|
|
|
376
426
|
```
|
|
377
427
|
|
|
378
428
|
```mdl
|
|
379
|
-
ext
|
|
380
|
-
uri: org.dxos.mdl.
|
|
429
|
+
ext scenario
|
|
430
|
+
uri: org.dxos.mdl.scenario@1.0
|
|
381
431
|
desc: An acceptance scenario expressed as given / when / then steps.
|
|
382
432
|
fields:
|
|
383
433
|
given?: Step | Step[]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { t as PLUGIN_default } from "./chunk-PLUGIN.mjs";
|
|
2
|
+
import * as Capability from "@dxos/app-framework/Capability";
|
|
3
|
+
import * as AppCapability from "@dxos/app-toolkit/AppCapability";
|
|
4
|
+
import * as ClientCapabilities from "@dxos/plugin-client/ClientCapabilities";
|
|
5
|
+
import * as FileCapabilities from "@dxos/plugin-file/FileCapabilities";
|
|
6
|
+
import * as FileEvents from "@dxos/plugin-file/FileEvents";
|
|
7
|
+
import { translations } from "#translations";
|
|
8
|
+
import { WnfsCapabilities } from "#types";
|
|
9
|
+
import { meta } from "#meta";
|
|
10
|
+
//#region src/capabilities/gen/browser.ts
|
|
11
|
+
var BlobBackend = Capability.lazyModule("BlobBackend", {
|
|
12
|
+
requires: [ClientCapabilities.Client],
|
|
13
|
+
provides: [FileCapabilities.Backend],
|
|
14
|
+
activatesOn: FileEvents.Start
|
|
15
|
+
}, () => import("./chunk-blob-backend.mjs"));
|
|
16
|
+
var Dependencies = Capability.lazyModule("Dependencies", {
|
|
17
|
+
requires: [ClientCapabilities.Client],
|
|
18
|
+
provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],
|
|
19
|
+
activatesOn: FileEvents.Start
|
|
20
|
+
}, () => import("./chunk-dependencies.mjs"));
|
|
21
|
+
var Translations = AppCapability.translations(translations);
|
|
22
|
+
var PluginAsset = AppCapability.pluginAsset({
|
|
23
|
+
pluginId: meta.profile.key,
|
|
24
|
+
path: "PLUGIN.mdl",
|
|
25
|
+
content: PLUGIN_default,
|
|
26
|
+
mimeType: "application/x-mdl"
|
|
27
|
+
});
|
|
28
|
+
//#endregion
|
|
29
|
+
export { BlobBackend, Dependencies, PluginAsset, Translations };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=capabilities.browser.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.browser.mjs","names":[],"sources":["../../src/capabilities/gen/browser.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'browser' condition.\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\nimport * as FileCapabilities from '@dxos/plugin-file/FileCapabilities';\nimport * as FileEvents from '@dxos/plugin-file/FileEvents';\n\nimport { meta } from '#meta';\nimport { translations } from '#translations';\nimport { WnfsCapabilities } from '#types';\n\nimport pluginSpec from '../../../PLUGIN.mdl?raw';\n\nexport const BlobBackend = Capability.lazyModule(\n 'BlobBackend',\n {\n // Blockstore/Instances are awaited in the module body, not declared here: they are absent by\n // design when EDGE is unconfigured.\n requires: [ClientCapabilities.Client],\n provides: [FileCapabilities.Backend],\n activatesOn: FileEvents.Start,\n },\n () => import('../blob-backend.ts'),\n);\n\nexport const Dependencies = Capability.lazyModule(\n 'Dependencies',\n {\n requires: [ClientCapabilities.Client],\n // The file plugin's start, not wnfs's own: wnfs contributes no surface, so nothing would\n // ever fire its own start — and these are exactly what the blob backend above waits for.\n provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],\n activatesOn: FileEvents.Start,\n },\n () => import('../dependencies.ts'),\n);\n\nexport const Translations = AppCapability.translations(translations);\n\nexport const PluginAsset = AppCapability.pluginAsset({\n pluginId: meta.profile.key,\n path: 'PLUGIN.mdl',\n content: pluginSpec,\n mimeType: 'application/x-mdl',\n});\n"],"mappings":";;;;;;;;;;AAkBA,IAAa,cAAc,WAAW,WACpC,eACA;CAGE,UAAU,CAAC,mBAAmB,MAAM;CACpC,UAAU,CAAC,iBAAiB,OAAO;CACnC,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,WAAW,WACrC,gBACA;CACE,UAAU,CAAC,mBAAmB,MAAM;CAGpC,UAAU,CAAC,iBAAiB,YAAY,iBAAiB,SAAS;CAClE,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,cAAc,aAAa,YAAY;AAEnE,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as PLUGIN_default } from "./chunk-PLUGIN.mjs";
|
|
1
2
|
import * as Capability from "@dxos/app-framework/Capability";
|
|
2
3
|
import * as AppCapability from "@dxos/app-toolkit/AppCapability";
|
|
3
4
|
import * as ClientCapabilities from "@dxos/plugin-client/ClientCapabilities";
|
|
@@ -6,9 +7,6 @@ import * as FileEvents from "@dxos/plugin-file/FileEvents";
|
|
|
6
7
|
import { translations } from "#translations";
|
|
7
8
|
import { WnfsCapabilities } from "#types";
|
|
8
9
|
import { meta } from "#meta";
|
|
9
|
-
//#region PLUGIN.mdl?raw
|
|
10
|
-
var PLUGIN_default = "---\nid: org.dxos.plugin.wnfs\nname: WnfsPlugin\nversion: 0.2.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 registers a `BlobBackend` (schemes: `['wnfs']`) on the ECHO Hypergraph\nvia `client.graph.registerBlobBackend`, and contributes a matching `FileCapabilities.Backend` descriptor\n(`{ storage: 'wnfs' }`) so `plugin-file` — and any plugin built on it, e.g. `plugin-gallery` — can\nupload files and resolve `wnfs://` URIs uniformly via the core `Blob.fromBytes`/`Blob.url`/`Blob.read` API,\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: |\n Internal return value of the `upload` helper. Only `.url` is surfaced to the outside world,\n as the `uri` field of the `BlobBackend.put` PutResponse.\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 → 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## Operations\n\nPure operations — 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\nBlobBackend implementation — registered on the ECHO Hypergraph, dispatched to by core `Blob.fromBytes` /\n`Blob.read` / `Blob.exists` / `Blob.url` whenever a Blob's storage or URI scheme resolves to `wnfs`.\n\n```mdl\nop wnfsBlobPut\n desc: |\n Implements BlobBackend.put. Resolves the target Space from spaceId, wraps the given bytes in a\n browser File (named `name ?? contentHash`), and delegates to the uploadFile op. Returns\n { uri: info.url } (a wnfs:// URL) as the PutResponse.\n input:\n spaceId: SpaceId\n data: Uint8Array\n contentType?: string\n contentHash: string # sha256 hex digest, computed by the core BlobManager; not re-verified here\n name?: string\n output: { uri: string }\n effects: [fs:read, echo:write, http:post]\n errors:\n SpaceNotFound: no space matching spaceId is currently open (invariant failure)\n```\n\n```mdl\nop wnfsBlobGet\n desc: |\n Implements BlobBackend.get. Resolves the Space from spaceId, loads or reuses the cached\n WnfsInstance, and reads the file bytes at the given wnfs:// URI (via getPathFromUrl + directory.read).\n input:\n spaceId: SpaceId\n uri: string\n output: Uint8Array | undefined\n effects: [fs:read, http:get]\n note: Returns undefined (never throws) if the space is not open or the read fails for any reason.\n```\n\n```mdl\nop wnfsBlobHas\n desc: Implements BlobBackend.has by delegating to the same read path as wnfsBlobGet and checking for a defined result.\n input:\n spaceId: SpaceId\n uri: string\n output: boolean\n```\n\n```mdl\nop wnfsBlobGetUrl\n desc: |\n Implements BlobBackend.getUrl. Resolves the Space, loads or reuses the cached WnfsInstance,\n reads the file bytes, wraps them in a Blob, and returns an object URL for in-browser display.\n input:\n spaceId: SpaceId\n uri: string\n contentType?: string\n output: string | undefined\n effects: [fs:read, http:get]\n note: Returns undefined (never throws) if the space is not open or reading fails.\n```\n\nEffectful operations underlying the BlobBackend implementation above.\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 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: core Blob.fromBytes routes to the 'wnfs' storage (i.e. BlobBackend.put is invoked\n for this backend)\n then: the file is written to the WNFS private directory and stored in the blockstore;\n wnfsBlobPut returns { uri } to the caller\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: the returned uri is a wnfs:// URL, embedded by core Blob.ts as the Blob object's\n `data.uri` (an `external` BlobData variant)\n```\n\n```mdl\nfeat F-2: Resolve wnfs:// blobs\n\n req F-2.1:\n when: a Blob's URI scheme is 'wnfs' (i.e. this plugin's registered schemes include it)\n then: core Blob.read / Blob.exists / Blob.url dispatch to this plugin's BlobBackend\n\n req F-2.2:\n when: BlobBackend.get or .has is called with a wnfs:// uri\n then: loads or reuses a cached WnfsInstance for the space and reads the file bytes\n\n req F-2.3:\n when: BlobBackend.getUrl is called with a wnfs:// uri\n then: bytes are read and wrapped in a blob: object URL for in-browser display\n\n req F-2.4:\n when: the space is not open, or reading/writing fails\n then: get/has/getUrl resolve to undefined/false rather than throwing; put rejects\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 a wnfs:// blob\n given: an active space and this plugin's BlobBackend registered on the ECHO Hypergraph\n when: Blob.fromBytes is called with storage: 'wnfs'\n then:\n - the resulting Blob's data.uri 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 blob via getUrl\n given: a blob was previously uploaded and its data.uri (a wnfs:// URI) is known\n when: BlobBackend.getUrl(spaceId, uri, contentType) is called\n then:\n - a blob: object URL is returned\n - the URL can be used as an img src\n```\n\n```mdl\ntest T-3: get/has/getUrl return undefined/false when the space is not open\n given: a wnfs:// URI whose space is not currently open\n when: BlobBackend.get, .has, or .getUrl is called\n then: undefined (or false for .has) 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 — 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";
|
|
11
|
-
//#endregion
|
|
12
10
|
//#region src/capabilities/index.ts
|
|
13
11
|
var BlobBackend = Capability.lazyModule("BlobBackend", {
|
|
14
12
|
requires: [ClientCapabilities.Client],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.mjs","names":[],"sources":["../../src/capabilities/index.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\nimport * as FileCapabilities from '@dxos/plugin-file/FileCapabilities';\nimport * as FileEvents from '@dxos/plugin-file/FileEvents';\n\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 BlobBackend = Capability.lazyModule(\n 'BlobBackend',\n {\n // Blockstore/Instances are awaited in the module body, not declared here: they are absent by\n // design when EDGE is unconfigured.\n requires: [ClientCapabilities.Client],\n provides: [FileCapabilities.Backend],\n activatesOn: FileEvents.Start,\n },\n () => import('./blob-backend'),\n);\n\nexport const Dependencies = Capability.lazyModule(\n 'Dependencies',\n {\n requires: [ClientCapabilities.Client],\n // The file plugin's start, not wnfs's own: wnfs contributes no surface, so nothing would\n // ever fire its own start — and these are exactly what the blob backend above waits for.\n provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],\n activatesOn: FileEvents.Start,\n },\n () => import('./dependencies'),\n);\n\nexport const Translations = AppCapability.translations(translations);\nexport const PluginAsset = AppCapability.pluginAsset({\n pluginId: meta.profile.key,\n path: 'PLUGIN.mdl',\n content: pluginSpec,\n mimeType: 'application/x-mdl',\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"capabilities.mjs","names":[],"sources":["../../src/capabilities/index.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\nimport * as FileCapabilities from '@dxos/plugin-file/FileCapabilities';\nimport * as FileEvents from '@dxos/plugin-file/FileEvents';\n\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 BlobBackend = Capability.lazyModule(\n 'BlobBackend',\n {\n // Blockstore/Instances are awaited in the module body, not declared here: they are absent by\n // design when EDGE is unconfigured.\n requires: [ClientCapabilities.Client],\n provides: [FileCapabilities.Backend],\n activatesOn: FileEvents.Start,\n },\n () => import('./blob-backend.ts'),\n);\n\nexport const Dependencies = Capability.lazyModule(\n 'Dependencies',\n {\n requires: [ClientCapabilities.Client],\n // The file plugin's start, not wnfs's own: wnfs contributes no surface, so nothing would\n // ever fire its own start — and these are exactly what the blob backend above waits for.\n provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],\n activatesOn: FileEvents.Start,\n },\n () => import('./dependencies.ts'),\n);\n\nexport const Translations = AppCapability.translations(translations);\nexport const PluginAsset = AppCapability.pluginAsset({\n pluginId: meta.profile.key,\n path: 'PLUGIN.mdl',\n content: pluginSpec,\n mimeType: 'application/x-mdl',\n});\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,cAAc,WAAW,WACpC,eACA;CAGE,UAAU,CAAC,mBAAmB,MAAM;CACpC,UAAU,CAAC,iBAAiB,OAAO;CACnC,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,WAAW,WACrC,gBACA;CACE,UAAU,CAAC,mBAAmB,MAAM;CAGpC,UAAU,CAAC,iBAAiB,YAAY,iBAAiB,SAAS;CAClE,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,cAAc,aAAa,YAAY;AACnE,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.node.mjs","names":[],"sources":["../../src/capabilities/gen/node.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'node' condition.\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\nimport * as FileCapabilities from '@dxos/plugin-file/FileCapabilities';\nimport * as FileEvents from '@dxos/plugin-file/FileEvents';\n\nimport { translations } from '#translations';\nimport { WnfsCapabilities } from '#types';\n\nexport const BlobBackend = Capability.lazyModule(\n 'BlobBackend',\n {\n // Blockstore/Instances are awaited in the module body, not declared here: they are absent by\n // design when EDGE is unconfigured.\n requires: [ClientCapabilities.Client],\n provides: [FileCapabilities.Backend],\n activatesOn: FileEvents.Start,\n },\n () => import('../blob-backend'),\n);\n\nexport const Dependencies = Capability.lazyModule(\n 'Dependencies',\n {\n requires: [ClientCapabilities.Client],\n // The file plugin's start, not wnfs's own: wnfs contributes no surface, so nothing would\n // ever fire its own start — and these are exactly what the blob backend above waits for.\n provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],\n activatesOn: FileEvents.Start,\n },\n () => import('../dependencies'),\n);\n\nexport const Translations = AppCapability.translations(translations);\n\nexport const PluginAsset = undefined;\n"],"mappings":";;;;;;;;AAeA,IAAa,cAAc,WAAW,WACpC,eACA;CAGE,UAAU,CAAC,mBAAmB,MAAM;CACpC,UAAU,CAAC,iBAAiB,OAAO;CACnC,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,WAAW,WACrC,gBACA;CACE,UAAU,CAAC,mBAAmB,MAAM;CAGpC,UAAU,CAAC,iBAAiB,YAAY,iBAAiB,SAAS;CAClE,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,cAAc,aAAa,YAAY;AAEnE,IAAa,cAAc,KAAA"}
|
|
1
|
+
{"version":3,"file":"capabilities.node.mjs","names":[],"sources":["../../src/capabilities/gen/node.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'node' condition.\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\nimport * as FileCapabilities from '@dxos/plugin-file/FileCapabilities';\nimport * as FileEvents from '@dxos/plugin-file/FileEvents';\n\nimport { translations } from '#translations';\nimport { WnfsCapabilities } from '#types';\n\nexport const BlobBackend = Capability.lazyModule(\n 'BlobBackend',\n {\n // Blockstore/Instances are awaited in the module body, not declared here: they are absent by\n // design when EDGE is unconfigured.\n requires: [ClientCapabilities.Client],\n provides: [FileCapabilities.Backend],\n activatesOn: FileEvents.Start,\n },\n () => import('../blob-backend.ts'),\n);\n\nexport const Dependencies = Capability.lazyModule(\n 'Dependencies',\n {\n requires: [ClientCapabilities.Client],\n // The file plugin's start, not wnfs's own: wnfs contributes no surface, so nothing would\n // ever fire its own start — and these are exactly what the blob backend above waits for.\n provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],\n activatesOn: FileEvents.Start,\n },\n () => import('../dependencies.ts'),\n);\n\nexport const Translations = AppCapability.translations(translations);\n\nexport const PluginAsset = undefined;\n"],"mappings":";;;;;;;;AAeA,IAAa,cAAc,WAAW,WACpC,eACA;CAGE,UAAU,CAAC,mBAAmB,MAAM;CACpC,UAAU,CAAC,iBAAiB,OAAO;CACnC,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,WAAW,WACrC,gBACA;CACE,UAAU,CAAC,mBAAmB,MAAM;CAGpC,UAAU,CAAC,iBAAiB,YAAY,iBAAiB,SAAS;CAClE,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,cAAc,aAAa,YAAY;AAEnE,IAAa,cAAc,KAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { t as PLUGIN_default } from "./chunk-PLUGIN.mjs";
|
|
2
|
+
import * as Capability from "@dxos/app-framework/Capability";
|
|
3
|
+
import * as AppCapability from "@dxos/app-toolkit/AppCapability";
|
|
4
|
+
import * as ClientCapabilities from "@dxos/plugin-client/ClientCapabilities";
|
|
5
|
+
import * as FileCapabilities from "@dxos/plugin-file/FileCapabilities";
|
|
6
|
+
import * as FileEvents from "@dxos/plugin-file/FileEvents";
|
|
7
|
+
import { translations } from "#translations";
|
|
8
|
+
import { WnfsCapabilities } from "#types";
|
|
9
|
+
import { meta } from "#meta";
|
|
10
|
+
//#region src/capabilities/gen/tauri.ts
|
|
11
|
+
var BlobBackend = Capability.lazyModule("BlobBackend", {
|
|
12
|
+
requires: [ClientCapabilities.Client],
|
|
13
|
+
provides: [FileCapabilities.Backend],
|
|
14
|
+
activatesOn: FileEvents.Start
|
|
15
|
+
}, () => import("./chunk-blob-backend.mjs"));
|
|
16
|
+
var Dependencies = Capability.lazyModule("Dependencies", {
|
|
17
|
+
requires: [ClientCapabilities.Client],
|
|
18
|
+
provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],
|
|
19
|
+
activatesOn: FileEvents.Start
|
|
20
|
+
}, () => import("./chunk-dependencies.mjs"));
|
|
21
|
+
var Translations = AppCapability.translations(translations);
|
|
22
|
+
var PluginAsset = AppCapability.pluginAsset({
|
|
23
|
+
pluginId: meta.profile.key,
|
|
24
|
+
path: "PLUGIN.mdl",
|
|
25
|
+
content: PLUGIN_default,
|
|
26
|
+
mimeType: "application/x-mdl"
|
|
27
|
+
});
|
|
28
|
+
//#endregion
|
|
29
|
+
export { BlobBackend, Dependencies, PluginAsset, Translations };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=capabilities.tauri.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.tauri.mjs","names":[],"sources":["../../src/capabilities/gen/tauri.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'tauri' condition.\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\nimport * as FileCapabilities from '@dxos/plugin-file/FileCapabilities';\nimport * as FileEvents from '@dxos/plugin-file/FileEvents';\n\nimport { meta } from '#meta';\nimport { translations } from '#translations';\nimport { WnfsCapabilities } from '#types';\n\nimport pluginSpec from '../../../PLUGIN.mdl?raw';\n\nexport const BlobBackend = Capability.lazyModule(\n 'BlobBackend',\n {\n // Blockstore/Instances are awaited in the module body, not declared here: they are absent by\n // design when EDGE is unconfigured.\n requires: [ClientCapabilities.Client],\n provides: [FileCapabilities.Backend],\n activatesOn: FileEvents.Start,\n },\n () => import('../blob-backend.ts'),\n);\n\nexport const Dependencies = Capability.lazyModule(\n 'Dependencies',\n {\n requires: [ClientCapabilities.Client],\n // The file plugin's start, not wnfs's own: wnfs contributes no surface, so nothing would\n // ever fire its own start — and these are exactly what the blob backend above waits for.\n provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],\n activatesOn: FileEvents.Start,\n },\n () => import('../dependencies.ts'),\n);\n\nexport const Translations = AppCapability.translations(translations);\n\nexport const PluginAsset = AppCapability.pluginAsset({\n pluginId: meta.profile.key,\n path: 'PLUGIN.mdl',\n content: pluginSpec,\n mimeType: 'application/x-mdl',\n});\n"],"mappings":";;;;;;;;;;AAkBA,IAAa,cAAc,WAAW,WACpC,eACA;CAGE,UAAU,CAAC,mBAAmB,MAAM;CACpC,UAAU,CAAC,iBAAiB,OAAO;CACnC,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,WAAW,WACrC,gBACA;CACE,UAAU,CAAC,mBAAmB,MAAM;CAGpC,UAAU,CAAC,iBAAiB,YAAY,iBAAiB,SAAS;CAClE,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,cAAc,aAAa,YAAY;AAEnE,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.workerd.mjs","names":[],"sources":["../../src/capabilities/gen/workerd.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'workerd' condition.\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\nimport * as FileCapabilities from '@dxos/plugin-file/FileCapabilities';\nimport * as FileEvents from '@dxos/plugin-file/FileEvents';\n\nimport { translations } from '#translations';\nimport { WnfsCapabilities } from '#types';\n\nexport const BlobBackend = Capability.lazyModule(\n 'BlobBackend',\n {\n // Blockstore/Instances are awaited in the module body, not declared here: they are absent by\n // design when EDGE is unconfigured.\n requires: [ClientCapabilities.Client],\n provides: [FileCapabilities.Backend],\n activatesOn: FileEvents.Start,\n },\n () => import('../blob-backend'),\n);\n\nexport const Dependencies = Capability.lazyModule(\n 'Dependencies',\n {\n requires: [ClientCapabilities.Client],\n // The file plugin's start, not wnfs's own: wnfs contributes no surface, so nothing would\n // ever fire its own start — and these are exactly what the blob backend above waits for.\n provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],\n activatesOn: FileEvents.Start,\n },\n () => import('../dependencies'),\n);\n\nexport const Translations = AppCapability.translations(translations);\n\nexport const PluginAsset = undefined;\n"],"mappings":";;;;;;;;AAeA,IAAa,cAAc,WAAW,WACpC,eACA;CAGE,UAAU,CAAC,mBAAmB,MAAM;CACpC,UAAU,CAAC,iBAAiB,OAAO;CACnC,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,WAAW,WACrC,gBACA;CACE,UAAU,CAAC,mBAAmB,MAAM;CAGpC,UAAU,CAAC,iBAAiB,YAAY,iBAAiB,SAAS;CAClE,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,cAAc,aAAa,YAAY;AAEnE,IAAa,cAAc,KAAA"}
|
|
1
|
+
{"version":3,"file":"capabilities.workerd.mjs","names":[],"sources":["../../src/capabilities/gen/workerd.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'workerd' condition.\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\nimport * as FileCapabilities from '@dxos/plugin-file/FileCapabilities';\nimport * as FileEvents from '@dxos/plugin-file/FileEvents';\n\nimport { translations } from '#translations';\nimport { WnfsCapabilities } from '#types';\n\nexport const BlobBackend = Capability.lazyModule(\n 'BlobBackend',\n {\n // Blockstore/Instances are awaited in the module body, not declared here: they are absent by\n // design when EDGE is unconfigured.\n requires: [ClientCapabilities.Client],\n provides: [FileCapabilities.Backend],\n activatesOn: FileEvents.Start,\n },\n () => import('../blob-backend.ts'),\n);\n\nexport const Dependencies = Capability.lazyModule(\n 'Dependencies',\n {\n requires: [ClientCapabilities.Client],\n // The file plugin's start, not wnfs's own: wnfs contributes no surface, so nothing would\n // ever fire its own start — and these are exactly what the blob backend above waits for.\n provides: [WnfsCapabilities.Blockstore, WnfsCapabilities.Instances],\n activatesOn: FileEvents.Start,\n },\n () => import('../dependencies.ts'),\n);\n\nexport const Translations = AppCapability.translations(translations);\n\nexport const PluginAsset = undefined;\n"],"mappings":";;;;;;;;AAeA,IAAa,cAAc,WAAW,WACpC,eACA;CAGE,UAAU,CAAC,mBAAmB,MAAM;CACpC,UAAU,CAAC,iBAAiB,OAAO;CACnC,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,WAAW,WACrC,gBACA;CACE,UAAU,CAAC,mBAAmB,MAAM;CAGpC,UAAU,CAAC,iBAAiB,YAAY,iBAAiB,SAAS;CAClE,aAAa,WAAW;AAC1B,SACM,OAAO,2BACf;AAEA,IAAa,eAAe,cAAc,aAAa,YAAY;AAEnE,IAAa,cAAc,KAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region PLUGIN.mdl?raw
|
|
2
|
+
var PLUGIN_default = "---\nid: org.dxos.plugin.wnfs\nname: WnfsPlugin\nversion: 0.2.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 registers a `BlobBackend` (schemes: `['wnfs']`) on the ECHO Hypergraph\nvia `client.graph.registerBlobBackend`, and contributes a matching `FileCapabilities.Backend` descriptor\n(`{ storage: 'wnfs' }`) so `plugin-file` — and any plugin built on it, e.g. `plugin-gallery` — can\nupload files and resolve `wnfs://` URIs uniformly via the core `Blob.fromBytes`/`Blob.url`/`Blob.read` API,\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| `scenario` | `org.dxos.mdl.scenario@1.0` |\n| `component` | `org.dxos.mdl.component@1.0` |\n| `op` | `org.dxos.mdl.op@1.0` |\n| `test` | `org.dxos.mdl.test@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: |\n Internal return value of the `upload` helper. Only `.url` is surfaced to the outside world,\n as the `uri` field of the `BlobBackend.put` PutResponse.\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 → 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## Operations\n\nPure operations — 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\nBlobBackend implementation — registered on the ECHO Hypergraph, dispatched to by core `Blob.fromBytes` /\n`Blob.read` / `Blob.exists` / `Blob.url` whenever a Blob's storage or URI scheme resolves to `wnfs`.\n\n```mdl\nop wnfsBlobPut\n desc: |\n Implements BlobBackend.put. Resolves the target Space from spaceId, wraps the given bytes in a\n browser File (named `name ?? contentHash`), and delegates to the uploadFile op. Returns\n { uri: info.url } (a wnfs:// URL) as the PutResponse.\n input:\n spaceId: SpaceId\n data: Uint8Array\n contentType?: string\n contentHash: string # sha256 hex digest, computed by the core BlobManager; not re-verified here\n name?: string\n output: { uri: string }\n effects: [fs:read, echo:write, http:post]\n errors:\n SpaceNotFound: no space matching spaceId is currently open (invariant failure)\n```\n\n```mdl\nop wnfsBlobGet\n desc: |\n Implements BlobBackend.get. Resolves the Space from spaceId, loads or reuses the cached\n WnfsInstance, and reads the file bytes at the given wnfs:// URI (via getPathFromUrl + directory.read).\n input:\n spaceId: SpaceId\n uri: string\n output: Uint8Array | undefined\n effects: [fs:read, http:get]\n note: Returns undefined (never throws) if the space is not open or the read fails for any reason.\n```\n\n```mdl\nop wnfsBlobHas\n desc: Implements BlobBackend.has by delegating to the same read path as wnfsBlobGet and checking for a defined result.\n input:\n spaceId: SpaceId\n uri: string\n output: boolean\n```\n\n```mdl\nop wnfsBlobGetUrl\n desc: |\n Implements BlobBackend.getUrl. Resolves the Space, loads or reuses the cached WnfsInstance,\n reads the file bytes, wraps them in a Blob, and returns an object URL for in-browser display.\n input:\n spaceId: SpaceId\n uri: string\n contentType?: string\n output: string | undefined\n effects: [fs:read, http:get]\n note: Returns undefined (never throws) if the space is not open or reading fails.\n```\n\nEffectful operations underlying the BlobBackend implementation above.\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 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: core Blob.fromBytes routes to the 'wnfs' storage (i.e. BlobBackend.put is invoked\n for this backend)\n then: the file is written to the WNFS private directory and stored in the blockstore;\n wnfsBlobPut returns { uri } to the caller\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: the returned uri is a wnfs:// URL, embedded by core Blob.ts as the Blob object's\n `data.uri` (an `external` BlobData variant)\n```\n\n```mdl\nfeat F-2: Resolve wnfs:// blobs\n\n req F-2.1:\n when: a Blob's URI scheme is 'wnfs' (i.e. this plugin's registered schemes include it)\n then: core Blob.read / Blob.exists / Blob.url dispatch to this plugin's BlobBackend\n\n req F-2.2:\n when: BlobBackend.get or .has is called with a wnfs:// uri\n then: loads or reuses a cached WnfsInstance for the space and reads the file bytes\n\n req F-2.3:\n when: BlobBackend.getUrl is called with a wnfs:// uri\n then: bytes are read and wrapped in a blob: object URL for in-browser display\n\n req F-2.4:\n when: the space is not open, or reading/writing fails\n then: get/has/getUrl resolve to undefined/false rather than throwing; put rejects\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\nscenario T-1: Upload creates a wnfs:// blob\n given: an active space and this plugin's BlobBackend registered on the ECHO Hypergraph\n when: Blob.fromBytes is called with storage: 'wnfs'\n then:\n - the resulting Blob's data.uri 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\nscenario T-2: Resolve uploaded blob via getUrl\n given: a blob was previously uploaded and its data.uri (a wnfs:// URI) is known\n when: BlobBackend.getUrl(spaceId, uri, contentType) is called\n then:\n - a blob: object URL is returned\n - the URL can be used as an img src\n```\n\n```mdl\nscenario T-3: get/has/getUrl return undefined/false when the space is not open\n given: a wnfs:// URI whose space is not currently open\n when: BlobBackend.get, .has, or .getUrl is called\n then: undefined (or false for .has) is returned; no error is thrown\n```\n\n```mdl\nscenario 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\nscenario 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\nscenario 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\nscenario 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## QA\n\nExecutable tests. Each step carries a human instruction (`do`) and, where the step maps to an\ninvocable operation, an agent binding (`invoke`). Report failures by test and step number.\n\n```mdl\ntest QA-1: Upload a file and resolve it back through the wnfs:// scheme\n desc: |\n plugin-wnfs has no ECHO type or navtree surface of its own — it is a `BlobBackend`\n (`schemes: ['wnfs']`) that `plugin-file` dispatches to transparently through\n `Blob.fromBytes`/`Blob.read`/`Blob.url`. None of its operations (`wnfsBlobPut`,\n `wnfsBlobGet`, `wnfsBlobHas`, `wnfsBlobGetUrl`, `uploadFile`) is an `Operation.make` with a\n `key:` — they are internal backend methods dispatched by core Blob.ts, not entries an\n invoker can look up — so this test is human-only end to end.\n actors: human\n covers: [F-1.1, F-1.2, F-1.3, F-2.1, F-2.3]\n status: unverified\n given:\n - an identity exists\n - space: a space is open\n - plugin-wnfs is enabled — it ships only in the experimental set; it is OFF by default\n - plugin-file is enabled, so drag-and-drop upload and the File article are available\n - the space's file storage backend is set to WNFS (Settings -> Space -> Storage, or the\n space's default when plugin-wnfs is the only storage backend enabled)\n - no file named \"qa-upload.png\" exists in the space — ABORT rather than deleting one this\n test did not create\n steps:\n - name: Upload a file\n do: Drag \"qa-upload.png\" onto the space (or a collection) in the navtree.\n expect: a File object named \"qa-upload.png\" appears in the navtree\n note: |\n NO `invoke` — `wnfsBlobPut` is reached through `Blob.fromBytes`, called by plugin-file's\n upload handler, not through the operation invoker; there is no `key:` to resolve.\n - name: Confirm the blob URI uses the wnfs scheme\n do: Right-click the file and choose \"Copy link\" (or inspect its properties panel).\n expect: the underlying blob URI starts with `wnfs://spaces/`\n - name: Open the file\n do: Click \"qa-upload.png\" in the navtree.\n expect: the image renders in the File article — `wnfsBlobGetUrl` resolved the bytes to a\n displayable `blob:` object URL\n after:\n - name: Delete the file\n do: Right-click \"qa-upload.png\" in the navtree and choose Delete.\n expect: the row leaves the navtree and its plank closes\n note: NO `invoke` — the uploaded File was never captured (no step above invokes anything), so there is no live ref to pass to `space.removeObjects`; deletion is a navtree click.\n```\n\n---\n\n## Appendix: Extension Definitions\n\nExtension block types used in this document are defined below using\nthe core `ext` primitive — 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 scenario\n uri: org.dxos.mdl.scenario@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";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { PLUGIN_default as t };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=chunk-PLUGIN.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-PLUGIN.mjs","names":[],"sources":[],"mappings":""}
|
|
@@ -2,10 +2,10 @@ import * as Capability from "@dxos/app-framework/Capability";
|
|
|
2
2
|
import * as ClientCapabilities from "@dxos/plugin-client/ClientCapabilities";
|
|
3
3
|
import * as FileCapabilities from "@dxos/plugin-file/FileCapabilities";
|
|
4
4
|
import { WnfsCapabilities } from "#types";
|
|
5
|
-
import { getBlobUrl, loadWnfs, readWnfsFile, upload } from "#helpers";
|
|
6
5
|
import { log } from "@dxos/log";
|
|
7
6
|
import * as Effect from "effect/Effect";
|
|
8
7
|
import { invariant } from "@dxos/invariant";
|
|
8
|
+
import { getBlobUrl, loadWnfs, readWnfsFile, upload } from "#helpers";
|
|
9
9
|
//#region src/capabilities/blob-backend.ts
|
|
10
10
|
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-wnfs/src/capabilities/blob-backend.ts";
|
|
11
11
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-blob-backend.mjs","names":[],"sources":["../../src/capabilities/blob-backend.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport type { Blockstore } from 'interface-blockstore';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport { type
|
|
1
|
+
{"version":3,"file":"chunk-blob-backend.mjs","names":[],"sources":["../../src/capabilities/blob-backend.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport type { Blockstore } from 'interface-blockstore';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport { type BlobBackend } from '@dxos/blob';\nimport { type Client } from '@dxos/client';\nimport { invariant } from '@dxos/invariant';\nimport { type SpaceId } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\nimport * as FileCapabilities from '@dxos/plugin-file/FileCapabilities';\n\nimport { getBlobUrl, loadWnfs, readWnfsFile, upload } from '#helpers';\nimport { WnfsCapabilities } from '#types';\n\ninterface CreateWnfsBlobBackendOptions {\n client: Client;\n blockstore: Blockstore;\n instances?: WnfsCapabilities.Instances;\n}\n\n/**\n * Blob backend that stores bytes in a space's WNFS private forest, addressed by `wnfs://…` URIs.\n * The underlying WASM bindings (`directory.read`) don't expose a distinct not-found error type, so\n * `readBytes`/`getUrl` can't tell a missing path from a genuine transport/blockstore failure —\n * both are logged and reported as a miss (`undefined`) rather than risk rethrowing on the common\n * missing-file path.\n */\nexport const createWnfsBlobBackend = ({ client, blockstore, instances }: CreateWnfsBlobBackendOptions): BlobBackend => {\n const readBytes = async (spaceId: SpaceId, uri: string) => {\n const space = client.spaces.get(spaceId);\n if (!space) {\n return undefined;\n }\n try {\n const { directory, forest } = await loadWnfs({ blockstore, instances, space });\n return await readWnfsFile({ wnfsUrl: uri, blockstore, forest, directory });\n } catch (error) {\n log.warn('failed to read wnfs blob', { spaceId, uri, error });\n return undefined;\n }\n };\n\n return {\n schemes: [WnfsCapabilities.WNFS_SCHEME],\n\n put: async ({ spaceId, data, contentType, contentHash, name }) => {\n const space = client.spaces.get(spaceId);\n invariant(space, 'Space not found');\n // `Uint8Array` is generic over `ArrayBufferLike` (incl. `SharedArrayBuffer`) while DOM's\n // `BlobPart` only covers `ArrayBuffer`-backed views — a gap between the DOM lib types and\n // the TS standard lib, not fixable by typing `data` differently.\n const file = new File([data as BlobPart], name ?? contentHash, { type: contentType });\n const info = await upload({ file, blockstore, instances, space });\n return { uri: info.url };\n },\n\n get: async ({ spaceId, uri }) => readBytes(spaceId, uri),\n\n has: async ({ spaceId, uri }) => (await readBytes(spaceId, uri)) !== undefined,\n\n getUrl: async ({ spaceId, uri, contentType }) => {\n const space = client.spaces.get(spaceId);\n if (!space) {\n return undefined;\n }\n try {\n const { directory, forest } = await loadWnfs({ blockstore, instances, space });\n return await getBlobUrl({ wnfsUrl: uri, blockstore, directory, forest, type: contentType });\n } catch (error) {\n log.warn('failed to resolve wnfs blob url', { spaceId, uri, error });\n return undefined;\n }\n },\n };\n};\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const client = yield* ClientCapabilities.Client;\n // Declared requires resolve before the body runs, so the blockstore is awaited below instead —\n // otherwise this opt-out could never be reached when EDGE is unconfigured.\n yield* Effect.promise(() => client.waitUntilInitialized());\n if (!client.config.values.runtime?.services?.edge?.url) {\n log('wnfs blob backend disabled: EDGE services not configured');\n return [];\n }\n const blockstore = yield* Capability.waitFor(WnfsCapabilities.Blockstore);\n const instances = yield* Capability.waitFor(WnfsCapabilities.Instances);\n\n const cleanup = client.graph.registerBlobBackend(\n WnfsCapabilities.WNFS_BACKEND,\n createWnfsBlobBackend({ client, blockstore, instances }),\n );\n\n yield* Effect.addFinalizer(() => Effect.sync(() => cleanup()));\n return Capability.contribute(FileCapabilities.Backend, {\n name: 'WNFS',\n description: 'Decentralized, end-to-end encrypted storage via Web Native File System.',\n storage: WnfsCapabilities.WNFS_BACKEND,\n });\n }),\n);\n"],"mappings":";;;;;;;;;;;;;;;;;AAgCA,IAAa,yBAAyB,EAAE,QAAQ,YAAY,gBAA2D;CACrH,MAAM,YAAY,OAAO,SAAkB,QAAgB;EACzD,MAAM,QAAQ,OAAO,OAAO,IAAI,OAAO;EACvC,IAAI,CAAC,OACH;EAEF,IAAI;GACF,MAAM,EAAE,WAAW,WAAW,MAAM,SAAS;IAAE;IAAY;IAAW;GAAM,CAAC;GAC7E,OAAO,MAAM,aAAa;IAAE,SAAS;IAAK;IAAY;IAAQ;GAAU,CAAC;EAC3E,SAAS,OAAO;GACd,IAAI,KAAK,4BAA4B;IAAE;IAAS;IAAK;GAAM,GAAA;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA,KAAA;GAAA,CAAC;GAC5D;EACF;CACF;CAEA,OAAO;EACL,SAAS,CAAC,iBAAiB,WAAW;EAEtC,KAAK,OAAO,EAAE,SAAS,MAAM,aAAa,aAAa,WAAW;GAChE,MAAM,QAAQ,OAAO,OAAO,IAAI,OAAO;GACvC,UAAU,OAAO,mBAAgB;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA,KAAA;IAAA,GAAA,CAAA,SAAA,mBAAA;GAAA,CAAC;GAIlC,MAAM,OAAO,IAAI,KAAK,CAAC,IAAgB,GAAG,QAAQ,aAAa,EAAE,MAAM,YAAY,CAAC;GAEpF,OAAO,EAAE,MAAK,MADK,OAAO;IAAE;IAAM;IAAY;IAAW;GAAM,CAAC,EAAA,CAC7C,IAAI;EACzB;EAEA,KAAK,OAAO,EAAE,SAAS,UAAU,UAAU,SAAS,GAAG;EAEvD,KAAK,OAAO,EAAE,SAAS,UAAW,MAAM,UAAU,SAAS,GAAG,MAAO,KAAA;EAErE,QAAQ,OAAO,EAAE,SAAS,KAAK,kBAAkB;GAC/C,MAAM,QAAQ,OAAO,OAAO,IAAI,OAAO;GACvC,IAAI,CAAC,OACH;GAEF,IAAI;IACF,MAAM,EAAE,WAAW,WAAW,MAAM,SAAS;KAAE;KAAY;KAAW;IAAM,CAAC;IAC7E,OAAO,MAAM,WAAW;KAAE,SAAS;KAAK;KAAY;KAAW;KAAQ,MAAM;IAAY,CAAC;GAC5F,SAAS,OAAO;IACd,IAAI,KAAK,mCAAmC;KAAE;KAAS;KAAK;IAAM,GAAA;KAAA,YAAA;KAAA,GAAA;KAAA,GAAA;KAAA,GAAA,KAAA;IAAA,CAAC;IACnE;GACF;EACF;CACF;AACF;AAEA,IAAA,uBAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,MAAM,SAAS,OAAO,mBAAmB;CAGzC,OAAO,OAAO,cAAc,OAAO,qBAAqB,CAAC;CACzD,IAAI,CAAC,OAAO,OAAO,OAAO,SAAS,UAAU,MAAM,KAAK;EACtD,IAAI,4DAAyD,KAAA,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC;EAC9D,OAAO,CAAC;CACV;CACA,MAAM,aAAa,OAAO,WAAW,QAAQ,iBAAiB,UAAU;CACxE,MAAM,YAAY,OAAO,WAAW,QAAQ,iBAAiB,SAAS;CAEtE,MAAM,UAAU,OAAO,MAAM,oBAC3B,iBAAiB,cACjB,sBAAsB;EAAE;EAAQ;EAAY;CAAU,CAAC,CACzD;CAEA,OAAO,OAAO,mBAAmB,OAAO,WAAW,QAAQ,CAAC,CAAC;CAC7D,OAAO,WAAW,WAAW,iBAAiB,SAAS;EACrD,MAAM;EACN,aAAa;EACb,SAAS,iBAAiB;CAC5B,CAAC;AACH,CAAC,CACH"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as Capability from "@dxos/app-framework/Capability";
|
|
2
2
|
import * as ClientCapabilities from "@dxos/plugin-client/ClientCapabilities";
|
|
3
3
|
import { WnfsCapabilities } from "#types";
|
|
4
|
-
import { storeName } from "#helpers";
|
|
5
4
|
import { CID } from "multiformats";
|
|
6
5
|
import * as Uint8Arrays from "uint8arrays";
|
|
7
6
|
import { log } from "@dxos/log";
|
|
8
7
|
import * as Effect from "effect/Effect";
|
|
8
|
+
import { storeName } from "#helpers";
|
|
9
9
|
import { CarWriter } from "@ipld/car";
|
|
10
10
|
import { BaseBlockstore } from "blockstore-core";
|
|
11
11
|
import { IDBBlockstore } from "blockstore-idb";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-dependencies.mjs","names":[],"sources":["../../src/blockstore.ts","../../src/capabilities/dependencies.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { CarWriter } from '@ipld/car';\nimport type { Block } from '@ipld/car/reader';\nimport { BaseBlockstore } from 'blockstore-core';\nimport { IDBBlockstore } from 'blockstore-idb';\nimport debounce from 'debounce';\nimport * as IDB from 'idb-keyval';\nimport all from 'it-all';\nimport { CID } from 'multiformats';\nimport * as Uint8Arrays from 'uint8arrays';\n\nimport { storeName } from '#helpers';\n\nexport const create = (apiHost?: string) => {\n return new MixedBlockstore(apiHost);\n};\n\n/**\n * A blockstore that communicates with the DXOS blob service,\n * and which uses an indexedDB blockstore as a client cache.\n */\nexport class MixedBlockstore extends BaseBlockstore {\n readonly #apiHost: string | undefined;\n readonly #localStore: IDBBlockstore;\n\n #isConnected: boolean;\n #flushQueue: () => void;\n #queue: string[];\n\n constructor(apiHost?: string) {\n super();\n\n this.#apiHost = apiHost ? apiHost.replace(/\\/?$/, '') : undefined;\n this.#localStore = new IDBBlockstore(storeName());\n this.#isConnected = navigator.onLine;\n this.#queue = [];\n\n this.#flushQueue = debounce(this.#flushQueueInt, 5000);\n }\n\n async open(): Promise<void> {\n await this.#localStore.open();\n await this.#restoreQueue();\n\n window.addEventListener('offline', async () => {\n this.#isConnected = false;\n });\n\n window.addEventListener('online', async () => {\n this.#isConnected = true;\n this.#flushQueue();\n });\n }\n\n url(apiHost: string, cid?: CID): string {\n const path = cid ? cid.toString() : '';\n return `${apiHost}/blob/file${path.length ? '/' + path : ''}`;\n }\n\n // BLOCKSTORE IMPLEMENTATION\n\n override async delete(key: CID): Promise<void> {\n await this.#localStore.delete(key);\n\n if (this.#isConnected && this.#apiHost) {\n await fetch(this.url(this.#apiHost, key), {\n method: 'DELETE',\n });\n }\n }\n\n override async get(key: CID): Promise<Uint8Array> {\n if (await this.#localStore.has(key)) {\n return await this.#localStore.get(key);\n }\n\n if (this.#isConnected && this.#apiHost) {\n const block = await fetch(this.url(this.#apiHost, key), {\n method: 'GET',\n })\n .then((r) => r.arrayBuffer())\n .then((r) => new Uint8Array(r));\n\n // Make sure it is cached locally\n await this.#localStore.put(key, block);\n\n return block;\n }\n\n return await this.#localStore.get(key);\n }\n\n override async has(key: CID): Promise<boolean> {\n if (await this.#localStore.has(key)) {\n return true;\n }\n\n if (this.#isConnected && this.#apiHost) {\n return await fetch(this.url(this.#apiHost, key), {\n method: 'HEAD',\n }).then((r) => r.ok);\n }\n\n return false;\n }\n\n override async put(key: CID, val: Uint8Array): Promise<CID> {\n await this.#localStore.put(key, val);\n\n await this.#addToQueue(key);\n this.#flushQueue();\n\n return key;\n }\n\n async destroy(): Promise<void> {\n await this.#localStore.destroy();\n }\n\n // REMOTE\n\n async putRemote(key: CID, val: Uint8Array): Promise<void> {\n if (this.#apiHost) {\n await fetch(this.url(this.#apiHost, key), {\n method: 'POST',\n body: val as Uint8Array<ArrayBuffer>,\n });\n }\n }\n\n async putCarRemote(car: Uint8Array): Promise<void> {\n if (this.#apiHost) {\n await fetch(this.url(this.#apiHost), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/vnd.ipld.car',\n },\n body: car as Uint8Array<ArrayBuffer>,\n });\n }\n }\n\n // QUEUE\n\n readonly queueCacheName = `${storeName()}.state.queue`;\n\n async #addToQueue(key: CID): Promise<void> {\n await this.#saveQueue([...this.#queue, key.toString()]);\n }\n\n async #flushQueueInt(): Promise<void> {\n if (!this.#isConnected || !this.#apiHost) {\n return;\n }\n\n const keys = [...this.#queue];\n if (keys.length === 0) {\n return;\n }\n\n // Collect blocks\n const blocks = await Promise.all(\n keys.map(async (key) => {\n const cid = CID.parse(key);\n const bytes = await this.#localStore.get(cid);\n return { cid, bytes };\n }),\n );\n\n // Create & submit CAR\n const car = await this.#createCar(blocks);\n await this.putCarRemote(car);\n\n // Adjust queue\n const queue = [...this.#queue].filter((k) => {\n if (keys.includes(k)) {\n return false;\n }\n\n return true;\n });\n\n await this.#saveQueue(queue);\n }\n\n async #restoreQueue(): Promise<void> {\n const fromCache = await IDB.get(this.queueCacheName);\n if (fromCache && Array.isArray(fromCache)) {\n this.#queue = fromCache;\n this.#flushQueue();\n }\n }\n\n async #saveQueue(items: string[]): Promise<void> {\n const arr = [...items];\n this.#queue = arr;\n await IDB.set(this.queueCacheName, arr);\n }\n\n // 🛠️\n\n async #createCar(blocks: Block[]): Promise<Uint8Array> {\n const { writer, out } = CarWriter.create();\n const outPromise = all(out);\n\n await Promise.all(\n blocks.map((block) => {\n return writer.put(block);\n }),\n );\n\n await writer.close();\n return Uint8Arrays.concat(await outPromise);\n }\n}\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport { log } from '@dxos/log';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\n\nimport { WnfsCapabilities } from '#types';\n\nimport * as Blockstore from '../blockstore';\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const client = yield* ClientCapabilities.Client;\n // `config` is initialized-only, and this event wave can land before the forked client\n // initialization completes.\n yield* Effect.promise(() => client.waitUntilInitialized());\n const apiHost = client.config.values.runtime?.services?.edge?.url;\n if (!apiHost) {\n // WNFS stores blocks on edge; without an endpoint the module contributes nothing.\n log('wnfs blockstore disabled: EDGE services not configured');\n return [];\n }\n const blockstore = Blockstore.create(apiHost);\n yield* Effect.tryPromise(() => blockstore.open());\n\n const instances: WnfsCapabilities.Instances = {};\n\n return [\n Capability.contribute(WnfsCapabilities.Blockstore, blockstore),\n Capability.contribute(WnfsCapabilities.Instances, instances),\n ];\n }),\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chunk-dependencies.mjs","names":[],"sources":["../../src/blockstore.ts","../../src/capabilities/dependencies.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { CarWriter } from '@ipld/car';\nimport type { Block } from '@ipld/car/reader';\nimport { BaseBlockstore } from 'blockstore-core';\nimport { IDBBlockstore } from 'blockstore-idb';\nimport debounce from 'debounce';\nimport * as IDB from 'idb-keyval';\nimport all from 'it-all';\nimport { CID } from 'multiformats';\nimport * as Uint8Arrays from 'uint8arrays';\n\nimport { storeName } from '#helpers';\n\nexport const create = (apiHost?: string) => {\n return new MixedBlockstore(apiHost);\n};\n\n/**\n * A blockstore that communicates with the DXOS blob service,\n * and which uses an indexedDB blockstore as a client cache.\n */\nexport class MixedBlockstore extends BaseBlockstore {\n readonly #apiHost: string | undefined;\n readonly #localStore: IDBBlockstore;\n\n #isConnected: boolean;\n #flushQueue: () => void;\n #queue: string[];\n\n constructor(apiHost?: string) {\n super();\n\n this.#apiHost = apiHost ? apiHost.replace(/\\/?$/, '') : undefined;\n this.#localStore = new IDBBlockstore(storeName());\n this.#isConnected = navigator.onLine;\n this.#queue = [];\n\n this.#flushQueue = debounce(this.#flushQueueInt, 5000);\n }\n\n async open(): Promise<void> {\n await this.#localStore.open();\n await this.#restoreQueue();\n\n window.addEventListener('offline', async () => {\n this.#isConnected = false;\n });\n\n window.addEventListener('online', async () => {\n this.#isConnected = true;\n this.#flushQueue();\n });\n }\n\n url(apiHost: string, cid?: CID): string {\n const path = cid ? cid.toString() : '';\n return `${apiHost}/blob/file${path.length ? '/' + path : ''}`;\n }\n\n // BLOCKSTORE IMPLEMENTATION\n\n override async delete(key: CID): Promise<void> {\n await this.#localStore.delete(key);\n\n if (this.#isConnected && this.#apiHost) {\n await fetch(this.url(this.#apiHost, key), {\n method: 'DELETE',\n });\n }\n }\n\n override async get(key: CID): Promise<Uint8Array> {\n if (await this.#localStore.has(key)) {\n return await this.#localStore.get(key);\n }\n\n if (this.#isConnected && this.#apiHost) {\n const block = await fetch(this.url(this.#apiHost, key), {\n method: 'GET',\n })\n .then((r) => r.arrayBuffer())\n .then((r) => new Uint8Array(r));\n\n // Make sure it is cached locally\n await this.#localStore.put(key, block);\n\n return block;\n }\n\n return await this.#localStore.get(key);\n }\n\n override async has(key: CID): Promise<boolean> {\n if (await this.#localStore.has(key)) {\n return true;\n }\n\n if (this.#isConnected && this.#apiHost) {\n return await fetch(this.url(this.#apiHost, key), {\n method: 'HEAD',\n }).then((r) => r.ok);\n }\n\n return false;\n }\n\n override async put(key: CID, val: Uint8Array): Promise<CID> {\n await this.#localStore.put(key, val);\n\n await this.#addToQueue(key);\n this.#flushQueue();\n\n return key;\n }\n\n async destroy(): Promise<void> {\n await this.#localStore.destroy();\n }\n\n // REMOTE\n\n async putRemote(key: CID, val: Uint8Array): Promise<void> {\n if (this.#apiHost) {\n await fetch(this.url(this.#apiHost, key), {\n method: 'POST',\n body: val as Uint8Array<ArrayBuffer>,\n });\n }\n }\n\n async putCarRemote(car: Uint8Array): Promise<void> {\n if (this.#apiHost) {\n await fetch(this.url(this.#apiHost), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/vnd.ipld.car',\n },\n body: car as Uint8Array<ArrayBuffer>,\n });\n }\n }\n\n // QUEUE\n\n readonly queueCacheName = `${storeName()}.state.queue`;\n\n async #addToQueue(key: CID): Promise<void> {\n await this.#saveQueue([...this.#queue, key.toString()]);\n }\n\n async #flushQueueInt(): Promise<void> {\n if (!this.#isConnected || !this.#apiHost) {\n return;\n }\n\n const keys = [...this.#queue];\n if (keys.length === 0) {\n return;\n }\n\n // Collect blocks\n const blocks = await Promise.all(\n keys.map(async (key) => {\n const cid = CID.parse(key);\n const bytes = await this.#localStore.get(cid);\n return { cid, bytes };\n }),\n );\n\n // Create & submit CAR\n const car = await this.#createCar(blocks);\n await this.putCarRemote(car);\n\n // Adjust queue\n const queue = [...this.#queue].filter((k) => {\n if (keys.includes(k)) {\n return false;\n }\n\n return true;\n });\n\n await this.#saveQueue(queue);\n }\n\n async #restoreQueue(): Promise<void> {\n const fromCache = await IDB.get(this.queueCacheName);\n if (fromCache && Array.isArray(fromCache)) {\n this.#queue = fromCache;\n this.#flushQueue();\n }\n }\n\n async #saveQueue(items: string[]): Promise<void> {\n const arr = [...items];\n this.#queue = arr;\n await IDB.set(this.queueCacheName, arr);\n }\n\n // 🛠️\n\n async #createCar(blocks: Block[]): Promise<Uint8Array> {\n const { writer, out } = CarWriter.create();\n const outPromise = all(out);\n\n await Promise.all(\n blocks.map((block) => {\n return writer.put(block);\n }),\n );\n\n await writer.close();\n return Uint8Arrays.concat(await outPromise);\n }\n}\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport { log } from '@dxos/log';\nimport * as ClientCapabilities from '@dxos/plugin-client/ClientCapabilities';\n\nimport { WnfsCapabilities } from '#types';\n\nimport * as Blockstore from '../blockstore.ts';\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const client = yield* ClientCapabilities.Client;\n // `config` is initialized-only, and this event wave can land before the forked client\n // initialization completes.\n yield* Effect.promise(() => client.waitUntilInitialized());\n const apiHost = client.config.values.runtime?.services?.edge?.url;\n if (!apiHost) {\n // WNFS stores blocks on edge; without an endpoint the module contributes nothing.\n log('wnfs blockstore disabled: EDGE services not configured');\n return [];\n }\n const blockstore = Blockstore.create(apiHost);\n yield* Effect.tryPromise(() => blockstore.open());\n\n const instances: WnfsCapabilities.Instances = {};\n\n return [\n Capability.contribute(WnfsCapabilities.Blockstore, blockstore),\n Capability.contribute(WnfsCapabilities.Instances, instances),\n ];\n }),\n);\n"],"mappings":"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"}
|
package/dist/lib/helpers.mjs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { t as WnfsStateAnnotation } from "./chunk-annotations.mjs";
|
|
2
|
-
import { Annotation, Obj } from "@dxos/echo";
|
|
3
1
|
import { CID } from "multiformats/cid";
|
|
4
2
|
import { sha256 } from "multiformats/hashes/sha2";
|
|
5
3
|
import * as crypto from "@dxos/crypto";
|
|
@@ -7,6 +5,8 @@ import * as Option from "effect/Option";
|
|
|
7
5
|
import { CID as CID$1 } from "multiformats";
|
|
8
6
|
import * as Uint8Arrays from "uint8arrays";
|
|
9
7
|
import initWnfsWasm, { AccessKey, PrivateDirectory, PrivateForest, PrivateNode } from "wnfs/web";
|
|
8
|
+
import { Annotation, Obj } from "@dxos/echo";
|
|
9
|
+
import * as Schema from "effect/Schema";
|
|
10
10
|
import * as Raw from "multiformats/codecs/raw";
|
|
11
11
|
import { log } from "@dxos/log";
|
|
12
12
|
//#region src/helpers/common.ts
|
|
@@ -68,6 +68,16 @@ var readFile = (file) => {
|
|
|
68
68
|
});
|
|
69
69
|
};
|
|
70
70
|
//#endregion
|
|
71
|
+
//#region src/annotations.ts
|
|
72
|
+
/** WNFS filesystem state stored on space properties meta. */
|
|
73
|
+
var WnfsStateAnnotation = Annotation.make({
|
|
74
|
+
id: "org.dxos.plugin.wnfs.state",
|
|
75
|
+
schema: Schema.Struct({
|
|
76
|
+
accessKey: Schema.String,
|
|
77
|
+
privateForestCid: Schema.String
|
|
78
|
+
})
|
|
79
|
+
});
|
|
80
|
+
//#endregion
|
|
71
81
|
//#region src/helpers/load.ts
|
|
72
82
|
var wasmReady;
|
|
73
83
|
var initWnfsWasmOnce = () => wasmReady ??= initWnfsWasm().catch((error) => {
|
package/dist/lib/helpers.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/helpers/common.ts","../../src/helpers/load.ts","../../src/helpers/wnfs-url.ts","../../src/helpers/upload.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport type { Blockstore } from 'interface-blockstore';\nimport { CID } from 'multiformats/cid';\nimport { sha256 } from 'multiformats/hashes/sha2';\nimport { type BlockStore as WnfsBlockStore } from 'wnfs';\n\nimport * as crypto from '@dxos/crypto';\nimport { type Space } from '@dxos/react-client/echo';\n\n//\n// PATH\n//\n\nexport const filePath = (fileName: string, space: Space) => {\n return ['spaces', space.id, 'files', fileName];\n};\n\n//\n// RNG\n//\n\nexport class Rng {\n randomBytes(count: number): Uint8Array {\n return new Uint8Array(crypto.randomBytes(count));\n }\n}\n\n//\n// STORE\n//\n\nexport const store = (blockstore: Blockstore): WnfsBlockStore => {\n const getBlock = async (cid: Uint8Array): Promise<Uint8Array | undefined> => {\n const decodedCid = CID.decode(cid);\n return await blockstore.get(decodedCid);\n };\n\n const hasBlock = async (cid: Uint8Array): Promise<boolean> => {\n const decodedCid = CID.decode(cid);\n return await blockstore.has(decodedCid);\n };\n\n const putBlockKeyed = async (cid: Uint8Array, bytes: Uint8Array): Promise<void> => {\n const decodedCid = CID.decode(cid);\n await blockstore.put(decodedCid, bytes);\n };\n\n // Don't hash blocks with the rs-wnfs default Blake 3, sha256 has better support.\n const putBlock = async (bytes: Uint8Array, codec: number): Promise<Uint8Array> => {\n const hash = await sha256.digest(bytes);\n const cid = CID.create(1, codec, hash);\n await blockstore.put(cid, bytes);\n return cid.bytes;\n };\n\n return { getBlock, hasBlock, putBlockKeyed, putBlock };\n};\n\nexport const storeName = () => 'dxos/wnfs';\n\n//\n// OTHER\n//\n\nexport const readFile = (file: File): Promise<Uint8Array> => {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = (event) => {\n const result = event.target?.result;\n if (result === null || result === undefined || typeof result === 'string') {\n reject(new Error('Expected an ArrayBuffer'));\n return;\n }\n resolve(new Uint8Array(result));\n };\n reader.onerror = (err) => {\n reject(err);\n };\n reader.readAsArrayBuffer(file);\n });\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport * as Option from 'effect/Option';\nimport type { Blockstore } from 'interface-blockstore';\nimport { CID } from 'multiformats';\nimport * as Uint8Arrays from 'uint8arrays';\nimport initWnfsWasm, { AccessKey, PrivateDirectory, PrivateForest, PrivateNode } from 'wnfs/web';\n\nimport { Annotation, Obj } from '@dxos/echo';\nimport { type Space } from '@dxos/react-client/echo';\n\nimport { WnfsCapabilities } from '#types';\n\nimport { WnfsStateAnnotation } from '../annotations';\nimport { Rng, store } from './common';\n\n//\n// LOAD\n//\n\n// `wnfs/web` does no wasm work at module evaluation — top-level await in a bundle graph trips\n// WebKit's TDZ bug (see composer-app's `slimWasm`) — so this single chokepoint initializes\n// explicitly, clearing the memo on failure so a transient error does not disable wnfs for good.\nlet wasmReady: Promise<unknown> | undefined;\n\nconst initWnfsWasmOnce = (): Promise<unknown> =>\n (wasmReady ??= initWnfsWasm().catch((error: unknown) => {\n wasmReady = undefined;\n throw error;\n }));\n\nexport const loadWnfs = async ({\n space,\n blockstore,\n instances,\n}: {\n space: Space;\n blockstore: Blockstore;\n instances?: WnfsCapabilities.Instances;\n}) => {\n await initWnfsWasmOnce();\n\n // TODO(wittjosiah): Remove.\n // Delete old properties if they exist (pre-DX-971 migration cleanup).\n const propsAny = space.properties as any;\n if (propsAny.wnfs_access_key !== undefined && propsAny.wnfs_private_forest_cid !== undefined) {\n Obj.update(space.properties, (obj) => {\n delete (obj as any)['wnfs_access_key'];\n delete (obj as any)['wnfs_private_forest_cid'];\n });\n }\n // Migrate legacy wnfs data key to annotation.\n if (propsAny.wnfs !== undefined && Option.isNone(Annotation.get(space.properties, WnfsStateAnnotation))) {\n Obj.update(space.properties, (properties) => {\n Annotation.set(properties, WnfsStateAnnotation, propsAny.wnfs);\n delete (properties as any)['wnfs'];\n });\n }\n\n const wnfsState = Annotation.get(space.properties, WnfsStateAnnotation).pipe(Option.getOrUndefined);\n const cacheKey = wnfsState?.privateForestCid as any;\n if (cacheKey && instances?.[cacheKey]) {\n return instances[cacheKey];\n }\n\n const exists = !!wnfsState;\n const instance = exists ? await loadWnfsDir(blockstore, space) : await createWnfsDir(blockstore, space);\n if (instances) {\n // Re-read after createWnfsDir so newly created instances are cached under the real CID.\n const newCacheKey = Annotation.get(space.properties, WnfsStateAnnotation).pipe(Option.getOrUndefined)\n ?.privateForestCid as any;\n if (newCacheKey) {\n instances[newCacheKey] = instance;\n }\n }\n\n return instance;\n};\n\n//\n// PRIVATE\n//\n\nconst createWnfsDir = async (blockstore: Blockstore, space: Space) => {\n const wnfsStore = store(blockstore);\n const rng = new Rng();\n const forest = new PrivateForest(rng);\n\n const dirResult = await PrivateDirectory.newAndStore(forest.emptyName(), new Date(), forest, wnfsStore, rng);\n const dir = dirResult.rootDir as PrivateDirectory;\n\n const [accessKeyRaw, newForest]: [AccessKey, PrivateForest] = await dir\n .asNode()\n .store(dirResult.forest as PrivateForest, wnfsStore, rng);\n\n const cidBytes = await newForest.store(wnfsStore);\n\n Obj.update(space.properties, (properties) => {\n Annotation.set(properties, WnfsStateAnnotation, {\n accessKey: Uint8Arrays.toString(accessKeyRaw.toBytes(), 'base64'),\n privateForestCid: CID.decode(cidBytes).toString(),\n });\n });\n\n return {\n directory: dir,\n forest: newForest,\n };\n};\n\nconst loadWnfsDir = async (blockstore: Blockstore, space: Space) => {\n const wnfsStore = store(blockstore);\n\n const wnfsState = Annotation.get(space.properties, WnfsStateAnnotation).pipe(Option.getOrUndefined)!;\n const accessKey = AccessKey.fromBytes(Uint8Arrays.fromString(wnfsState.accessKey, 'base64'));\n const forest: PrivateForest = await PrivateForest.load(CID.parse(wnfsState.privateForestCid).bytes, wnfsStore);\n const node: PrivateNode = await PrivateNode.load(accessKey, forest, wnfsStore);\n\n return {\n directory: node.asDir(),\n forest,\n };\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport type { Blockstore } from 'interface-blockstore';\nimport { type PrivateDirectory, type PrivateForest } from 'wnfs';\n\nimport { store } from './common';\n\nexport const wnfsUrl = (filePath: string[]) => {\n return 'wnfs://' + filePath.map((f) => encodeURIComponent(f)).join('/');\n};\n\nexport const getPathFromUrl = (wnfsUrl: string) =>\n wnfsUrl\n .replace(/^wnfs:\\/\\//, '')\n .split('/')\n .map((p) => decodeURIComponent(p));\n\nexport const readWnfsFile = async ({\n wnfsUrl,\n blockstore,\n forest,\n directory,\n}: {\n wnfsUrl: string;\n blockstore: Blockstore;\n forest: PrivateForest;\n directory: PrivateDirectory;\n}): Promise<Uint8Array> => {\n const path = getPathFromUrl(wnfsUrl);\n const wnfsStore = store(blockstore);\n const { result } = await directory.read(path, true, forest, wnfsStore);\n return result;\n};\n\nexport const getBlobUrl = async ({\n wnfsUrl,\n blockstore,\n forest,\n directory,\n type,\n}: {\n wnfsUrl: string;\n blockstore: Blockstore;\n forest: PrivateForest;\n directory: PrivateDirectory;\n type?: string;\n}) => {\n const result = await readWnfsFile({ wnfsUrl, blockstore, forest, directory });\n // `Uint8Array` is generic over `ArrayBufferLike` (incl. `SharedArrayBuffer`) while DOM's\n // `BlobPart` only covers `ArrayBuffer`-backed views — a gap between the DOM lib types and the\n // TS standard lib, not fixable by typing `result` differently.\n const blob = new Blob([result as BlobPart], { type });\n return URL.createObjectURL(blob);\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport * as Option from 'effect/Option';\nimport type { Blockstore } from 'interface-blockstore';\nimport { CID } from 'multiformats';\nimport * as Raw from 'multiformats/codecs/raw';\nimport { sha256 } from 'multiformats/hashes/sha2';\n\nimport type { Space } from '@dxos/client/echo';\nimport { Annotation, Obj } from '@dxos/echo';\nimport { log } from '@dxos/log';\n\nimport { WnfsCapabilities } from '#types';\n\nimport { WnfsStateAnnotation } from '../annotations';\nimport { Rng, filePath, store } from './common';\nimport { loadWnfs } from './load';\nimport { wnfsUrl } from './wnfs-url';\n\nexport const upload = async ({\n file,\n blockstore,\n instances,\n space,\n}: {\n file: File;\n blockstore: Blockstore;\n instances?: WnfsCapabilities.Instances;\n space: Space;\n}) => {\n const { directory, forest } = await loadWnfs({ blockstore, instances, space });\n const wnfsStore = store(blockstore);\n\n const bytes = new Uint8Array(await file.arrayBuffer());\n const hash = await sha256.digest(bytes);\n const cid = CID.create(1, Raw.code, hash);\n\n const path = filePath(cid.toString(), space);\n\n // Given the root directory of the private file system,\n // write the file contents to `path` in said directory.\n const result = await directory.write(path, true, bytes, new Date(), forest, wnfsStore, new Rng());\n\n // Given the new immutable instance of the root directory after writing to it,\n // **store** the changes in the \"dark forest\" and the associated (block) store.\n const [_, updatedForest] = await result.rootDir.store(result.forest, wnfsStore, new Rng());\n const cidBytes = await updatedForest.store(wnfsStore);\n\n // Update the forest pointer on the associated space.\n const currentState = Annotation.get(space.properties, WnfsStateAnnotation).pipe(Option.getOrUndefined);\n if (!currentState) {\n throw new Error('WnfsStateAnnotation missing after loadWnfs; cannot persist forest CID.');\n }\n Obj.update(space.properties, (properties) => {\n Annotation.set(properties, WnfsStateAnnotation, {\n ...currentState,\n privateForestCid: CID.decode(cidBytes).toString(),\n });\n });\n\n // Generate `wnfs://` URL & return the info.\n const info = {\n name: file.name,\n type: file.type,\n url: wnfsUrl(path),\n cid: cid.toString(),\n };\n\n log('upload', { file, info });\n return info;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/helpers/common.ts","../../src/annotations.ts","../../src/helpers/load.ts","../../src/helpers/wnfs-url.ts","../../src/helpers/upload.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport type { Blockstore } from 'interface-blockstore';\nimport { CID } from 'multiformats/cid';\nimport { sha256 } from 'multiformats/hashes/sha2';\nimport { type BlockStore as WnfsBlockStore } from 'wnfs';\n\nimport * as crypto from '@dxos/crypto';\nimport { type Space } from '@dxos/react-client/echo';\n\n//\n// PATH\n//\n\nexport const filePath = (fileName: string, space: Space) => {\n return ['spaces', space.id, 'files', fileName];\n};\n\n//\n// RNG\n//\n\nexport class Rng {\n randomBytes(count: number): Uint8Array {\n return new Uint8Array(crypto.randomBytes(count));\n }\n}\n\n//\n// STORE\n//\n\nexport const store = (blockstore: Blockstore): WnfsBlockStore => {\n const getBlock = async (cid: Uint8Array): Promise<Uint8Array | undefined> => {\n const decodedCid = CID.decode(cid);\n return await blockstore.get(decodedCid);\n };\n\n const hasBlock = async (cid: Uint8Array): Promise<boolean> => {\n const decodedCid = CID.decode(cid);\n return await blockstore.has(decodedCid);\n };\n\n const putBlockKeyed = async (cid: Uint8Array, bytes: Uint8Array): Promise<void> => {\n const decodedCid = CID.decode(cid);\n await blockstore.put(decodedCid, bytes);\n };\n\n // Don't hash blocks with the rs-wnfs default Blake 3, sha256 has better support.\n const putBlock = async (bytes: Uint8Array, codec: number): Promise<Uint8Array> => {\n const hash = await sha256.digest(bytes);\n const cid = CID.create(1, codec, hash);\n await blockstore.put(cid, bytes);\n return cid.bytes;\n };\n\n return { getBlock, hasBlock, putBlockKeyed, putBlock };\n};\n\nexport const storeName = () => 'dxos/wnfs';\n\n//\n// OTHER\n//\n\nexport const readFile = (file: File): Promise<Uint8Array> => {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = (event) => {\n const result = event.target?.result;\n if (result === null || result === undefined || typeof result === 'string') {\n reject(new Error('Expected an ArrayBuffer'));\n return;\n }\n resolve(new Uint8Array(result));\n };\n reader.onerror = (err) => {\n reject(err);\n };\n reader.readAsArrayBuffer(file);\n });\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { Annotation } from '@dxos/echo';\n\n/** WNFS filesystem state stored on space properties meta. */\nexport const WnfsStateAnnotation = Annotation.make({\n id: 'org.dxos.plugin.wnfs.state',\n schema: Schema.Struct({\n accessKey: Schema.String,\n privateForestCid: Schema.String,\n }),\n});\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport * as Option from 'effect/Option';\nimport type { Blockstore } from 'interface-blockstore';\nimport { CID } from 'multiformats';\nimport * as Uint8Arrays from 'uint8arrays';\nimport initWnfsWasm, { AccessKey, PrivateDirectory, PrivateForest, PrivateNode } from 'wnfs/web';\n\nimport { Annotation, Obj } from '@dxos/echo';\nimport { type Space } from '@dxos/react-client/echo';\n\nimport { WnfsCapabilities } from '#types';\n\nimport { WnfsStateAnnotation } from '../annotations.ts';\nimport { Rng, store } from './common.ts';\n\n//\n// LOAD\n//\n\n// `wnfs/web` does no wasm work at module evaluation — top-level await in a bundle graph trips\n// WebKit's TDZ bug (see composer-app's `slimWasm`) — so this single chokepoint initializes\n// explicitly, clearing the memo on failure so a transient error does not disable wnfs for good.\nlet wasmReady: Promise<unknown> | undefined;\n\nconst initWnfsWasmOnce = (): Promise<unknown> =>\n (wasmReady ??= initWnfsWasm().catch((error: unknown) => {\n wasmReady = undefined;\n throw error;\n }));\n\nexport const loadWnfs = async ({\n space,\n blockstore,\n instances,\n}: {\n space: Space;\n blockstore: Blockstore;\n instances?: WnfsCapabilities.Instances;\n}) => {\n await initWnfsWasmOnce();\n\n // TODO(wittjosiah): Remove.\n // Delete old properties if they exist (pre-DX-971 migration cleanup).\n const propsAny = space.properties as any;\n if (propsAny.wnfs_access_key !== undefined && propsAny.wnfs_private_forest_cid !== undefined) {\n Obj.update(space.properties, (obj) => {\n delete (obj as any)['wnfs_access_key'];\n delete (obj as any)['wnfs_private_forest_cid'];\n });\n }\n // Migrate legacy wnfs data key to annotation.\n if (propsAny.wnfs !== undefined && Option.isNone(Annotation.get(space.properties, WnfsStateAnnotation))) {\n Obj.update(space.properties, (properties) => {\n Annotation.set(properties, WnfsStateAnnotation, propsAny.wnfs);\n delete (properties as any)['wnfs'];\n });\n }\n\n const wnfsState = Annotation.get(space.properties, WnfsStateAnnotation).pipe(Option.getOrUndefined);\n const cacheKey = wnfsState?.privateForestCid as any;\n if (cacheKey && instances?.[cacheKey]) {\n return instances[cacheKey];\n }\n\n const exists = !!wnfsState;\n const instance = exists ? await loadWnfsDir(blockstore, space) : await createWnfsDir(blockstore, space);\n if (instances) {\n // Re-read after createWnfsDir so newly created instances are cached under the real CID.\n const newCacheKey = Annotation.get(space.properties, WnfsStateAnnotation).pipe(Option.getOrUndefined)\n ?.privateForestCid as any;\n if (newCacheKey) {\n instances[newCacheKey] = instance;\n }\n }\n\n return instance;\n};\n\n//\n// PRIVATE\n//\n\nconst createWnfsDir = async (blockstore: Blockstore, space: Space) => {\n const wnfsStore = store(blockstore);\n const rng = new Rng();\n const forest = new PrivateForest(rng);\n\n const dirResult = await PrivateDirectory.newAndStore(forest.emptyName(), new Date(), forest, wnfsStore, rng);\n const dir = dirResult.rootDir as PrivateDirectory;\n\n const [accessKeyRaw, newForest]: [AccessKey, PrivateForest] = await dir\n .asNode()\n .store(dirResult.forest as PrivateForest, wnfsStore, rng);\n\n const cidBytes = await newForest.store(wnfsStore);\n\n Obj.update(space.properties, (properties) => {\n Annotation.set(properties, WnfsStateAnnotation, {\n accessKey: Uint8Arrays.toString(accessKeyRaw.toBytes(), 'base64'),\n privateForestCid: CID.decode(cidBytes).toString(),\n });\n });\n\n return {\n directory: dir,\n forest: newForest,\n };\n};\n\nconst loadWnfsDir = async (blockstore: Blockstore, space: Space) => {\n const wnfsStore = store(blockstore);\n\n const wnfsState = Annotation.get(space.properties, WnfsStateAnnotation).pipe(Option.getOrUndefined)!;\n const accessKey = AccessKey.fromBytes(Uint8Arrays.fromString(wnfsState.accessKey, 'base64'));\n const forest: PrivateForest = await PrivateForest.load(CID.parse(wnfsState.privateForestCid).bytes, wnfsStore);\n const node: PrivateNode = await PrivateNode.load(accessKey, forest, wnfsStore);\n\n return {\n directory: node.asDir(),\n forest,\n };\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport type { Blockstore } from 'interface-blockstore';\nimport { type PrivateDirectory, type PrivateForest } from 'wnfs';\n\nimport { store } from './common.ts';\n\nexport const wnfsUrl = (filePath: string[]) => {\n return 'wnfs://' + filePath.map((f) => encodeURIComponent(f)).join('/');\n};\n\nexport const getPathFromUrl = (wnfsUrl: string) =>\n wnfsUrl\n .replace(/^wnfs:\\/\\//, '')\n .split('/')\n .map((p) => decodeURIComponent(p));\n\nexport const readWnfsFile = async ({\n wnfsUrl,\n blockstore,\n forest,\n directory,\n}: {\n wnfsUrl: string;\n blockstore: Blockstore;\n forest: PrivateForest;\n directory: PrivateDirectory;\n}): Promise<Uint8Array> => {\n const path = getPathFromUrl(wnfsUrl);\n const wnfsStore = store(blockstore);\n const { result } = await directory.read(path, true, forest, wnfsStore);\n return result;\n};\n\nexport const getBlobUrl = async ({\n wnfsUrl,\n blockstore,\n forest,\n directory,\n type,\n}: {\n wnfsUrl: string;\n blockstore: Blockstore;\n forest: PrivateForest;\n directory: PrivateDirectory;\n type?: string;\n}) => {\n const result = await readWnfsFile({ wnfsUrl, blockstore, forest, directory });\n // `Uint8Array` is generic over `ArrayBufferLike` (incl. `SharedArrayBuffer`) while DOM's\n // `BlobPart` only covers `ArrayBuffer`-backed views — a gap between the DOM lib types and the\n // TS standard lib, not fixable by typing `result` differently.\n const blob = new Blob([result as BlobPart], { type });\n return URL.createObjectURL(blob);\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport * as Option from 'effect/Option';\nimport type { Blockstore } from 'interface-blockstore';\nimport { CID } from 'multiformats';\nimport * as Raw from 'multiformats/codecs/raw';\nimport { sha256 } from 'multiformats/hashes/sha2';\n\nimport type { Space } from '@dxos/client/echo';\nimport { Annotation, Obj } from '@dxos/echo';\nimport { log } from '@dxos/log';\n\nimport { WnfsCapabilities } from '#types';\n\nimport { WnfsStateAnnotation } from '../annotations.ts';\nimport { Rng, filePath, store } from './common.ts';\nimport { loadWnfs } from './load.ts';\nimport { wnfsUrl } from './wnfs-url.ts';\n\nexport const upload = async ({\n file,\n blockstore,\n instances,\n space,\n}: {\n file: File;\n blockstore: Blockstore;\n instances?: WnfsCapabilities.Instances;\n space: Space;\n}) => {\n const { directory, forest } = await loadWnfs({ blockstore, instances, space });\n const wnfsStore = store(blockstore);\n\n const bytes = new Uint8Array(await file.arrayBuffer());\n const hash = await sha256.digest(bytes);\n const cid = CID.create(1, Raw.code, hash);\n\n const path = filePath(cid.toString(), space);\n\n // Given the root directory of the private file system,\n // write the file contents to `path` in said directory.\n const result = await directory.write(path, true, bytes, new Date(), forest, wnfsStore, new Rng());\n\n // Given the new immutable instance of the root directory after writing to it,\n // **store** the changes in the \"dark forest\" and the associated (block) store.\n const [_, updatedForest] = await result.rootDir.store(result.forest, wnfsStore, new Rng());\n const cidBytes = await updatedForest.store(wnfsStore);\n\n // Update the forest pointer on the associated space.\n const currentState = Annotation.get(space.properties, WnfsStateAnnotation).pipe(Option.getOrUndefined);\n if (!currentState) {\n throw new Error('WnfsStateAnnotation missing after loadWnfs; cannot persist forest CID.');\n }\n Obj.update(space.properties, (properties) => {\n Annotation.set(properties, WnfsStateAnnotation, {\n ...currentState,\n privateForestCid: CID.decode(cidBytes).toString(),\n });\n });\n\n // Generate `wnfs://` URL & return the info.\n const info = {\n name: file.name,\n type: file.type,\n url: wnfsUrl(path),\n cid: cid.toString(),\n };\n\n log('upload', { file, info });\n return info;\n};\n"],"mappings":"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"}
|
package/dist/lib/index.mjs
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
1
|
import { t as WnfsPlugin_exports } from "./WnfsPlugin.mjs";
|
|
2
|
-
import { t as WnfsStateAnnotation } from "./chunk-annotations.mjs";
|
|
3
2
|
export * from "#types";
|
|
4
|
-
export
|
|
5
|
-
export * from "#helpers";
|
|
6
|
-
export { WnfsPlugin_exports as WnfsPlugin, WnfsStateAnnotation };
|
|
3
|
+
export { WnfsPlugin_exports as WnfsPlugin };
|
package/dist/lib/meta.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.mjs","names":[],"sources":["../../dx.config.ts","../../src/meta.ts"],"sourcesContent":["//\n// Copyright 2023 DXOS.org\n//\n\nimport { Config2 } from '@dxos/app-framework/config';\nimport { trim } from '@dxos/util';\n\nexport default Config2.make({\n plugin: {\n key: 'org.dxos.plugin.wnfs',\n name: 'WNFS',\n author: 'DXOS',\n description: trim`\n Decentralized, end-to-end encrypted file storage for DXOS Composer built on the Web Native File System (WNFS) protocol.\n\n WNFS organizes files inside a cryptographic \"dark forest\" — a content-addressed private file system\n where each write produces a new immutable root CID. Files are stored as IPLD blocks (CIDv1, sha2-256),\n cached locally in IndexedDB, and asynchronously replicated to the DXOS edge blob service as CAR archives.\n Uploads and reads are scoped to a DXOS Space; the forest root CID is persisted atomically on the\n space's ECHO properties so the file system state survives across sessions and devices.\n\n The plugin registers a \\`BlobBackend\\` on the ECHO Hypergraph (and a matching \\`FileCapabilities.Backend\\`\n descriptor) so any plugin that uses \\`plugin-file\\` (e.g. \\`plugin-studio\\`) can upload files and resolve\n \\`wnfs://\\` URLs without coupling to the storage implementation. The blockstore automatically queues\n blocks while offline and flushes them when connectivity is restored.\n `,\n icon: { key: 'ph--file-cloud--regular', hue: 'teal' },\n source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-wnfs',\n spec: 'PLUGIN.mdl',\n tags: ['labs'],\n dependsOn: ['org.dxos.plugin.file'],\n },\n});\n","//\n// Copyright 2023 DXOS.org\n//\n\nimport * as Plugin from '@dxos/app-framework/Plugin';\n\nimport config from '../dx.config';\n\nexport const meta = Plugin.getMetaFromConfig(config);\n"],"mappings":";;;;AAOA,IAAA,oBAAe,QAAQ,KAAK,EAC1B,QAAQ;CACN,KAAK;CACL,MAAM;CACN,QAAQ;CACR,aAAa,IAAI;;;;;;;;;;;;;;CAcjB,MAAM;EAAE,KAAK;EAA2B,KAAK;CAAO;CACpD,QAAQ;CACR,MAAM;CACN,MAAM,CAAC,MAAM;CACb,WAAW,CAAC,sBAAsB;AACpC,EACF,CAAC;;;ACxBD,IAAa,OAAO,OAAO,kBAAkB,iBAAM"}
|
|
1
|
+
{"version":3,"file":"meta.mjs","names":[],"sources":["../../dx.config.ts","../../src/meta.ts"],"sourcesContent":["//\n// Copyright 2023 DXOS.org\n//\n\nimport { Config2 } from '@dxos/app-framework/config';\nimport { trim } from '@dxos/util';\n\nexport default Config2.make({\n plugin: {\n key: 'org.dxos.plugin.wnfs',\n name: 'WNFS',\n author: 'DXOS',\n description: trim`\n Decentralized, end-to-end encrypted file storage for DXOS Composer built on the Web Native File System (WNFS) protocol.\n\n WNFS organizes files inside a cryptographic \"dark forest\" — a content-addressed private file system\n where each write produces a new immutable root CID. Files are stored as IPLD blocks (CIDv1, sha2-256),\n cached locally in IndexedDB, and asynchronously replicated to the DXOS edge blob service as CAR archives.\n Uploads and reads are scoped to a DXOS Space; the forest root CID is persisted atomically on the\n space's ECHO properties so the file system state survives across sessions and devices.\n\n The plugin registers a \\`BlobBackend\\` on the ECHO Hypergraph (and a matching \\`FileCapabilities.Backend\\`\n descriptor) so any plugin that uses \\`plugin-file\\` (e.g. \\`plugin-studio\\`) can upload files and resolve\n \\`wnfs://\\` URLs without coupling to the storage implementation. The blockstore automatically queues\n blocks while offline and flushes them when connectivity is restored.\n `,\n icon: { key: 'ph--file-cloud--regular', hue: 'teal' },\n source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-wnfs',\n spec: 'PLUGIN.mdl',\n tags: ['labs'],\n dependsOn: ['org.dxos.plugin.file'],\n },\n});\n","//\n// Copyright 2023 DXOS.org\n//\n\nimport * as Plugin from '@dxos/app-framework/Plugin';\n\nimport config from '../dx.config.ts';\n\nexport const meta = Plugin.getMetaFromConfig(config);\n"],"mappings":";;;;AAOA,IAAA,oBAAe,QAAQ,KAAK,EAC1B,QAAQ;CACN,KAAK;CACL,MAAM;CACN,QAAQ;CACR,aAAa,IAAI;;;;;;;;;;;;;;CAcjB,MAAM;EAAE,KAAK;EAA2B,KAAK;CAAO;CACpD,QAAQ;CACR,MAAM;CACN,MAAM,CAAC,MAAM;CACb,WAAW,CAAC,sBAAsB;AACpC,EACF,CAAC;;;ACxBD,IAAa,OAAO,OAAO,kBAAkB,iBAAM"}
|