@le-space/orbitdb-storage-bridge 0.10.0 → 0.11.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/README.md CHANGED
@@ -5,8 +5,8 @@
5
5
 
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Node.js](https://img.shields.io/badge/Node.js-22+-green.svg)](https://nodejs.org/)
8
- [![CI/CD Pipeline](https://github.com/NiKrause/@le-space/orbitdb-storage-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/NiKrause/@le-space/orbitdb-storage-bridge/actions/workflows/ci.yml)
9
- [![ESLint](https://img.shields.io/badge/ESLint-passing-brightgreen.svg)](https://github.com/NiKrause/@le-space/orbitdb-storage-bridge/actions/workflows/ci.yml)
8
+ [![CI/CD Pipeline](https://github.com/NiKrause/orbitdb-storage-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/NiKrause/orbitdb-storage-bridge/actions/workflows/ci.yml)
9
+ [![ESLint](https://img.shields.io/badge/ESLint-passing-brightgreen.svg)](https://github.com/NiKrause/orbitdb-storage-bridge/actions/workflows/ci.yml)
10
10
  [![npm version](https://img.shields.io/npm/v/@le-space/orbitdb-storage-bridge.svg)](https://www.npmjs.com/package/@le-space/orbitdb-storage-bridge)
11
11
 
12
12
  > [!NOTE]
@@ -157,7 +157,7 @@ The project includes **Svelte components** for browser-based demos and integrati
157
157
  ## Roadmap
158
158
 
159
159
  > Being re-based on a backend interface instead of a single vendor — the plan is
160
- > [issue 54](https://github.com/NiKrause/@le-space/orbitdb-storage-bridge/issues/54), not here. The WebAuthn/varsig items below survive
160
+ > [issue 54](https://github.com/NiKrause/orbitdb-storage-bridge/issues/54), not here. The WebAuthn/varsig items below survive
161
161
  > unchanged; the Storacha-named ones become backend-agnostic.
162
162
 
163
163
  - [ ] Live parallel persistence: hand an open database a backend-backed OrbitDB `ComposedStorage`, so every block is written to a backend **as it is created** — during sync and after each update — rather than only when a backup runs.
@@ -169,11 +169,11 @@ The project includes **Svelte components** for browser-based demos and integrati
169
169
  - [ ] v0.4.4 (Feb 2026): Latest-backup pointer (single CID) to avoid listing via the Storacha SDK and restore from the IPFS network for initial OrbitDB syncs.
170
170
  - [ ] After each backup, write a small pointer record (JSON) that stores the latest metadata CID, CAR CID, and last heads (block CID).
171
171
  - [ ] Store that pointer in a user-controlled place (local storage, QR/share link, WebAuthN largetBlog extension or file download).
172
- - [ ] v0.5.0 (Feb 2026): OrbitDB CustomStorage (StorachaStorage) ([issue 23](https://github.com/NiKrause/@le-space/orbitdb-storage-bridge/issues/23)).
172
+ - [ ] v0.5.0 (Feb 2026): OrbitDB CustomStorage (StorachaStorage) ([issue 23](https://github.com/NiKrause/orbitdb-storage-bridge/issues/23)).
173
173
  - [ ] v0.6.0 (Mar 2026): WebAuthN + varsig signing/verification (Ed25519 and P-256) for OrbitDB oplog. https://github.com/ChainAgnostic/varsig/blob/main/README.md
174
174
  - [ ] v0.6.1 (Mar 2026): WebAuthN + SimpleEncryption example that uses WebAuthN+PRF key material for encrypted backups and restore.
175
175
  - [ ] v0.7.0 (Apr 2026): WebAuthN + OrbitDB AccessController (store a UCAN instead of only a DID for admin/write access).
176
- - [ ] Alice (authenticated via UCAN or Storacha credentials) can delegate/revoke access for Bob with custom/default capabilities ([issue 16](https://github.com/NiKrause/@le-space/orbitdb-storage-bridge/issues/16)). See [WebAuthN Upload Wall](https://github.com/NiKrause/ucan-upload-wall/tree/browser-only/web) and the [live demo](https://bafybeibdcnp7pr26okzr6kbygcounsz3klyg3vydxwwovmz2ljyzfmprre.ipfs.w3s.link/).
176
+ - [ ] Alice (authenticated via UCAN or Storacha credentials) can delegate/revoke access for Bob with custom/default capabilities ([issue 16](https://github.com/NiKrause/orbitdb-storage-bridge/issues/16)). See [WebAuthN Upload Wall](https://github.com/NiKrause/ucan-upload-wall/tree/browser-only/web) and the [live demo](https://bafybeibdcnp7pr26okzr6kbygcounsz3klyg3vydxwwovmz2ljyzfmprre.ipfs.w3s.link/).
177
177
  - [ ] v0.7.1 (May 2026): Storacha Backup & Restore Svelte widget with WebAuthN-varsig UCAN signing/verification (Ed25519/P-256).
178
178
  - [ ] v0.7.2 (May 2026): Storacha Backup & Restore React widget with WebAuthN-varsig UCAN signing/verification (Ed25519/P-256).
179
179
  - [ ] v0.7.3 (May 2026): Storacha Backup & Restore React widget with WebAuthN-varsig UCAN delegation (Ed25519/P-256).
@@ -224,6 +224,24 @@ The scripts written against Storacha's space and UCAN model are in
224
224
  [`examples/storacha/`](examples/storacha/README.md). None of them runs end to
225
225
  end since the uploads stopped, and the README there says what replaced each.
226
226
 
227
+ ### In a browser: a database back on a device that has nothing
228
+
229
+ [**funkpost's recovery page**](https://nikrause.github.io/funkpost/recovery/) runs the whole
230
+ [recovery procedure](docs/RECOVERY-ON-A-SECOND-DEVICE.md) in a phone's browser, with a
231
+ security key and nothing else ([source](https://github.com/NiKrause/funkpost/tree/main/examples/recovery)):
232
+
233
+ 1. **the key gives the identity** — the DID, and a signing key derived from its PRF output,
234
+ the same on every device;
235
+ 2. **a list** is made and written to;
236
+ 3. **`dehydrate`** backs it up to Aleph as a CAR, and publishes an IPNS pointer under a name
237
+ the key derives;
238
+ 4. **on another device** — or the same one, wiped — the same key finds the pointer, **`hydrate`**
239
+ brings the list back, and the list takes new entries, because the writer is the same.
240
+
241
+ On 21 September 2026 it ran that way on two phones: a Galaxy Fold 5 backed up and was reset,
242
+ and a Galaxy A57 with the same key brought the list back and wrote to it. The page says at
243
+ every step which service it contacts; the technical details sit behind one button.
244
+
227
245
  ### Svelte Components
228
246
 
229
247
  For browser-based integration, this project includes Svelte components for authentication, backup/restore, P2P replication, and WebAuthn biometric authentication. See [**SVELTE-COMPONENTS.md**](SVELTE-COMPONENTS.md) for complete documentation of all available components and demonstrations.
@@ -0,0 +1,145 @@
1
+ /**
2
+ * @fileoverview One backend from a plain choice — a name and, where a service
3
+ * needs one, a key.
4
+ *
5
+ * `resolveBackend` understands a ready backend or Storacha credentials, which is
6
+ * what a Node caller has. A page has something smaller and less trusting: a
7
+ * reader ticked *Lighthouse* and pasted their own key, and nothing else is
8
+ * known. This turns that into a driver.
9
+ *
10
+ * ## Every vendor module is imported lazily, and that is the point
11
+ *
12
+ * A page that chose Aleph must not ship the Pinata and Lighthouse drivers, and
13
+ * a page that chose none of them must ship no vendor code at all. Each branch
14
+ * therefore `import()`s exactly what it builds — the same reason
15
+ * `resolveBackend` loads Storacha only when it builds one, where the module
16
+ * costs about 88 kB gzipped.
17
+ *
18
+ * ## Several choices are a mirror, not a loop in the caller
19
+ *
20
+ * `kind: ["aleph", "lighthouse"]` builds both and returns
21
+ * {@link ../backends/mirror.js createMirrorBackend} over them, so the caller's
22
+ * code is the same whether one service was ticked or three.
23
+ *
24
+ * ## What it refuses
25
+ *
26
+ * A key that is missing is refused here, with the name of the service in the
27
+ * message, rather than at the first upload — a page can then keep the button
28
+ * disabled and say why. Nothing in this module reads an environment variable on
29
+ * a page's behalf: in a browser there is no environment, and a driver that
30
+ * silently finds a key somewhere else is a driver nobody can reason about. The
31
+ * per-driver fallbacks to `process.env` stay where they are, for Node.
32
+ *
33
+ * @author @NiKrause
34
+ * @requires ./types.js - the contract every branch returns
35
+ */
36
+
37
+ import { BackendError } from "./types.js";
38
+
39
+ /** The names a caller may ask for. */
40
+ export const BACKEND_KINDS = Object.freeze([
41
+ "aleph",
42
+ "pinata",
43
+ "lighthouse",
44
+ "storacha",
45
+ "memory",
46
+ ]);
47
+
48
+ /**
49
+ * Build a backend from a choice.
50
+ *
51
+ * @param {object} choice
52
+ * @param {string|string[]} choice.kind - one name, or several for a mirror
53
+ * @param {string} [choice.jwt] - Pinata
54
+ * @param {() => Promise<string>} [choice.getUploadUrl] - Pinata, without a secret in the page
55
+ * @param {string} [choice.apiKey] - Lighthouse
56
+ * @param {"shared"|"user"} [choice.keyOwnership] - Lighthouse: whose key it is,
57
+ * which is what decides `browserSafeAuth`
58
+ * @param {string[]} [choice.gateways] - retrieval gateways, where the driver takes them
59
+ * @param {object} [choice.options] - passed through to the driver, for anything
60
+ * this signature does not name
61
+ * @param {"one"|"all"} [choice.require] - for several kinds: how many must accept a write
62
+ * @returns {Promise<import("./types.js").StorageBackend>}
63
+ */
64
+ export async function createBackendFromChoice(choice = {}) {
65
+ const kinds = Array.isArray(choice.kind) ? choice.kind : [choice.kind];
66
+ const wanted = kinds.filter(Boolean);
67
+
68
+ if (wanted.length === 0) {
69
+ throw new BackendError(
70
+ "INVALID_BACKEND",
71
+ `Pick a backend: ${BACKEND_KINDS.join(", ")}`,
72
+ );
73
+ }
74
+ for (const kind of wanted) {
75
+ if (!BACKEND_KINDS.includes(kind)) {
76
+ throw new BackendError(
77
+ "INVALID_BACKEND",
78
+ `Unknown backend "${kind}" — pick one of ${BACKEND_KINDS.join(", ")}`,
79
+ );
80
+ }
81
+ }
82
+
83
+ if (wanted.length > 1) {
84
+ const backends = [];
85
+ for (const kind of wanted) {
86
+ backends.push(await createBackendFromChoice({ ...choice, kind }));
87
+ }
88
+ const { createMirrorBackend } = await import("./mirror.js");
89
+ return createMirrorBackend(backends, { require: choice.require ?? "one" });
90
+ }
91
+
92
+ const [kind] = wanted;
93
+ const gateways = choice.gateways ? { gateways: choice.gateways } : {};
94
+ const extra = choice.options ?? {};
95
+
96
+ if (kind === "aleph") {
97
+ const { createAlephBackend } = await import("./aleph.js");
98
+ return createAlephBackend({ ...gateways, ...extra });
99
+ }
100
+
101
+ if (kind === "pinata") {
102
+ if (
103
+ !choice.jwt &&
104
+ !choice.getUploadUrl &&
105
+ !extra.jwt &&
106
+ !extra.getUploadUrl
107
+ ) {
108
+ throw new BackendError(
109
+ "INVALID_BACKEND",
110
+ "Pinata needs a JWT, or a getUploadUrl function that mints a presigned URL",
111
+ );
112
+ }
113
+ const { createPinataBackend } = await import("./pinata.js");
114
+ return createPinataBackend({
115
+ ...(choice.jwt ? { jwt: choice.jwt } : {}),
116
+ ...(choice.getUploadUrl ? { getUploadUrl: choice.getUploadUrl } : {}),
117
+ ...gateways,
118
+ ...extra,
119
+ });
120
+ }
121
+
122
+ if (kind === "lighthouse") {
123
+ if (!choice.apiKey && !extra.apiKey) {
124
+ throw new BackendError("INVALID_BACKEND", "Lighthouse needs an apiKey");
125
+ }
126
+ const { createLighthouseBackend } = await import("./lighthouse.js");
127
+ return createLighthouseBackend({
128
+ ...(choice.apiKey ? { apiKey: choice.apiKey } : {}),
129
+ // Whose key it is decides browserSafeAuth, and a page should say which it got.
130
+ keyOwnership: choice.keyOwnership ?? "shared",
131
+ ...gateways,
132
+ ...extra,
133
+ });
134
+ }
135
+
136
+ if (kind === "memory") {
137
+ const { createMemoryBackend } = await import("./memory.js");
138
+ return createMemoryBackend(extra);
139
+ }
140
+
141
+ const { resolveBackend } = await import("./resolve.js");
142
+ return resolveBackend({ ...choice, ...extra, kind: undefined });
143
+ }
144
+
145
+ export default createBackendFromChoice;
@@ -0,0 +1,234 @@
1
+ /**
2
+ * @fileoverview One backup, several services: the mirror backend.
3
+ *
4
+ * A page that wants its backup on Aleph *and* on Lighthouse had to call both
5
+ * and reconcile two handles itself. This does that once, behind the same
6
+ * contract every other driver keeps, so `dehydrate`, `backupDatabaseCAR` and
7
+ * `restoreFromCID` need to know nothing about it.
8
+ *
9
+ * ## What a mirror promises, and what it refuses to
10
+ *
11
+ * **A partial write is reported, never swallowed.** By default a write
12
+ * succeeds when at least one service took it — a backup that fails because the
13
+ * third service was down would be worse than useless — but the handle then
14
+ * names who holds it and who refused, and the caller decides what to say. Pass
15
+ * `require: "all"` when a copy everywhere is the point of the exercise.
16
+ *
17
+ * **A read asks in order and stops at the first answer.** Not a race: a race
18
+ * spends every service's bandwidth on every read, and on a gateway that bills
19
+ * by request that is somebody's money. The order is the order the backends
20
+ * were given, so "the fast one first" is the caller's decision to make.
21
+ *
22
+ * **Capabilities are the honest composition, not the flattering one.**
23
+ * `browserSafeAuth` is true only when *every* service is safe to hand a page,
24
+ * because the weakest one decides what a page leaks. `preservesInnerCids` and
25
+ * `carImport` likewise. `minBlobSize` is the largest of them, because a blob
26
+ * has to clear the strictest door. `pinByCid` is the exception: it is true when
27
+ * *any* service can pin, and `pinCid()` then asks exactly those.
28
+ *
29
+ * **It does not list and it does not delete.** Both questions have no single
30
+ * honest answer across services — a listing would be a union with duplicates,
31
+ * and a deletion that half succeeds leaves a copy behind while reporting
32
+ * success. A caller that wants either can ask the service it means.
33
+ *
34
+ * @author @NiKrause
35
+ * @requires ./types.js - the contract this keeps
36
+ */
37
+
38
+ import {
39
+ defineBackend,
40
+ handleId,
41
+ BackendError,
42
+ DEFAULT_CAPABILITIES,
43
+ } from "./types.js";
44
+ import { logger } from "../logger.js";
45
+
46
+ /** The strictest door decides; an unstated minimum is no minimum. */
47
+ const largestMinimum = (backends) =>
48
+ backends.reduce(
49
+ (largest, backend) =>
50
+ Math.max(largest, backend.capabilities?.minBlobSize ?? 0),
51
+ 0,
52
+ );
53
+
54
+ const everyOne = (backends, flag) =>
55
+ backends.every((backend) => Boolean(backend.capabilities?.[flag]));
56
+ const anyOne = (backends, flag) =>
57
+ backends.some((backend) => Boolean(backend.capabilities?.[flag]));
58
+
59
+ /**
60
+ * Write one backup to several services and read it back from whichever answers.
61
+ *
62
+ * @param {import("./types.js").StorageBackend[]} backends - two or more drivers,
63
+ * in the order reads should try them
64
+ * @param {object} [options]
65
+ * @param {"one"|"all"} [options.require="one"] - how many services must accept a
66
+ * write for it to count as one
67
+ * @param {string} [options.name] - what the mirror calls itself; the default
68
+ * names its members, because a log line saying "mirror" says nothing
69
+ * @returns {import("./types.js").StorageBackend}
70
+ */
71
+ export function createMirrorBackend(backends = [], options = {}) {
72
+ const members = backends.filter(Boolean);
73
+ if (members.length < 2) {
74
+ throw new BackendError(
75
+ "INVALID_BACKEND",
76
+ "createMirrorBackend needs at least two backends; one backend is not a mirror",
77
+ );
78
+ }
79
+
80
+ const require_ = options.require ?? "one";
81
+ if (require_ !== "one" && require_ !== "all") {
82
+ throw new BackendError(
83
+ "INVALID_BACKEND",
84
+ `require must be "one" or "all", not ${require_}`,
85
+ );
86
+ }
87
+
88
+ const name =
89
+ options.name ||
90
+ `mirror(${members.map((backend) => backend.name).join("+")})`;
91
+ const pinners = members.filter(
92
+ (backend) => typeof backend.pinCid === "function",
93
+ );
94
+
95
+ /** Run one operation against every member, keeping which of them said what. */
96
+ const acrossAll = async (operation) => {
97
+ const settled = await Promise.allSettled(
98
+ members.map((backend) => operation(backend)),
99
+ );
100
+ const copies = [];
101
+ const failures = [];
102
+ settled.forEach((result, index) => {
103
+ const backend = members[index];
104
+ if (result.status === "fulfilled") {
105
+ copies.push({ backend: backend.name, ...result.value });
106
+ } else {
107
+ failures.push({
108
+ backend: backend.name,
109
+ code: result.reason?.code ?? "FAILED",
110
+ message: result.reason?.message ?? String(result.reason),
111
+ });
112
+ }
113
+ });
114
+ return { copies, failures };
115
+ };
116
+
117
+ /**
118
+ * One handle for the copies. The id is the CID when every service agreed on
119
+ * one — which is the normal case, since the bytes decide it — and the first
120
+ * success otherwise, so a handle is always usable somewhere.
121
+ */
122
+ const mergeHandles = ({ copies, failures }, what) => {
123
+ if (copies.length === 0) {
124
+ throw new BackendError(
125
+ failures[0]?.code === "TOO_SMALL" ? "TOO_SMALL" : "FAILED",
126
+ `${name}: no service accepted ${what} — ${failures
127
+ .map((failure) => `${failure.backend}: ${failure.message}`)
128
+ .join("; ")}`,
129
+ );
130
+ }
131
+ if (require_ === "all" && failures.length > 0) {
132
+ throw new BackendError(
133
+ "FAILED",
134
+ `${name}: ${failures.length} of ${members.length} services refused ${what} and require is "all" — ${failures
135
+ .map((failure) => `${failure.backend}: ${failure.message}`)
136
+ .join("; ")}`,
137
+ );
138
+ }
139
+ if (failures.length > 0) {
140
+ logger.warn(
141
+ `⚠️ ${name}: ${copies.length} of ${members.length} services hold ${what}; ` +
142
+ failures
143
+ .map((failure) => `${failure.backend} refused (${failure.code})`)
144
+ .join(", "),
145
+ );
146
+ }
147
+
148
+ const cids = new Set(copies.map((copy) => copy.cid).filter(Boolean));
149
+ const agreed = cids.size === 1 ? [...cids][0] : null;
150
+ return {
151
+ id: agreed ?? copies[0].id,
152
+ ...(agreed ? { cid: agreed } : {}),
153
+ backend: name,
154
+ ...(copies[0].size != null ? { size: copies[0].size } : {}),
155
+ ...(copies[0].name ? { name: copies[0].name } : {}),
156
+ copies,
157
+ ...(failures.length > 0 ? { failures } : {}),
158
+ };
159
+ };
160
+
161
+ /** A member's own id for this handle, since only the CID is shared. */
162
+ const idFor = (backend, handle) => {
163
+ const copy =
164
+ typeof handle === "object" && handle?.copies
165
+ ? handle.copies.find((entry) => entry.backend === backend.name)
166
+ : null;
167
+ return copy?.id ?? handleId(handle);
168
+ };
169
+
170
+ const mirror = {
171
+ name,
172
+ /** The members, in read order — a caller that wants one service can reach it. */
173
+ backends: members,
174
+ capabilities: {
175
+ ...DEFAULT_CAPABILITIES,
176
+ pinByCid: pinners.length > 0 && anyOne(members, "pinByCid"),
177
+ carImport: everyOne(members, "carImport"),
178
+ preservesInnerCids: everyOne(members, "preservesInnerCids"),
179
+ browserSafeAuth: everyOne(members, "browserSafeAuth"),
180
+ delegation: everyOne(members, "delegation"),
181
+ listing: false,
182
+ deletion: false,
183
+ minBlobSize: largestMinimum(members),
184
+ },
185
+
186
+ putBlob: async (bytes, meta = {}) =>
187
+ mergeHandles(
188
+ await acrossAll((backend) => backend.putBlob(bytes, meta)),
189
+ "the blob",
190
+ ),
191
+
192
+ getBlob: async (handle) => {
193
+ const reasons = [];
194
+ for (const backend of members) {
195
+ try {
196
+ return await backend.getBlob(idFor(backend, handle));
197
+ } catch (error) {
198
+ reasons.push(`${backend.name}: ${error?.message ?? error}`);
199
+ }
200
+ }
201
+ throw new BackendError(
202
+ "NOT_FOUND",
203
+ `${name}: no service returned ${handleId(handle)} — ${reasons.join("; ")}`,
204
+ );
205
+ },
206
+ };
207
+
208
+ if (mirror.capabilities.pinByCid) {
209
+ /** Only the services that can pin are asked; the others have nothing to say. */
210
+ mirror.pinCid = async (cid, meta = {}) => {
211
+ const settled = await Promise.allSettled(
212
+ pinners.map((backend) => backend.pinCid(cid, meta)),
213
+ );
214
+ const copies = [];
215
+ const failures = [];
216
+ settled.forEach((result, index) => {
217
+ const backend = pinners[index];
218
+ if (result.status === "fulfilled")
219
+ copies.push({ backend: backend.name, ...result.value });
220
+ else
221
+ failures.push({
222
+ backend: backend.name,
223
+ code: result.reason?.code ?? "FAILED",
224
+ message: result.reason?.message ?? String(result.reason),
225
+ });
226
+ });
227
+ return mergeHandles({ copies, failures }, `the pin for ${cid}`);
228
+ };
229
+ }
230
+
231
+ return defineBackend(mirror);
232
+ }
233
+
234
+ export default createMirrorBackend;
@@ -20,6 +20,9 @@ import { logger } from "../logger.js";
20
20
  *
21
21
  * @param {Object} [config] - call options
22
22
  * @param {Object} [config.backend] - any driver implementing the backend contract
23
+ * @param {string|string[]} [config.kind] - a backend by name, or several for a
24
+ * mirror, with the credentials each needs. What a page has, since a reader
25
+ * ticks a service and pastes a key rather than handing over a driver.
23
26
  * @param {Object} [config.ucanClient] - a Storacha client authorised over UCAN
24
27
  * @param {string} [config.spaceDID]
25
28
  * @param {string} [config.storachaKey] - falls back to STORACHA_KEY
@@ -35,6 +38,13 @@ export async function resolveBackend(config = {}) {
35
38
  return config.backend;
36
39
  }
37
40
 
41
+ // A name and a key: loaded here so that one decision stays one decision, and
42
+ // lazily so a caller that never names a vendor never bundles one.
43
+ if (config.kind) {
44
+ const { createBackendFromChoice } = await import("./choose.js");
45
+ return createBackendFromChoice(config);
46
+ }
47
+
38
48
  const gateways = config.gateways;
39
49
 
40
50
  if (config.ucanClient) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/orbitdb-storage-bridge",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Back up, restore and replicate OrbitDB databases through pluggable storage backends, with hash and identity preservation",
5
5
  "main": "lib/orbitdb-storacha-bridge.js",
6
6
  "svelte": "dist/components/",
@@ -23,6 +23,9 @@
23
23
  "./backends/aleph-pin": "./lib/backends/aleph-pin.js",
24
24
  "./backends/pinata": "./lib/backends/pinata.js",
25
25
  "./backends/lighthouse": "./lib/backends/lighthouse.js",
26
+ "./backends/mirror": "./lib/backends/mirror.js",
27
+ "./backends/choose": "./lib/backends/choose.js",
28
+ "./backends/resolve": "./lib/backends/resolve.js",
26
29
  "./memory-courier": "./lib/memory-courier.js",
27
30
  "./StorachaIntegration.svelte": "./dist/components/StorachaIntegration.svelte",
28
31
  "./StorachaAuth.svelte": "./dist/components/StorachaAuth.svelte",