@le-space/orbitdb-storage-bridge 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +290 -0
- package/dist/components/StorachaAuth.svelte +801 -0
- package/dist/components/StorachaIntegration.svelte +965 -0
- package/dist/components/WebAuthnDIDProvider.js +562 -0
- package/dist/components/storacha-backup.js +325 -0
- package/dist/components/theme.js +79 -0
- package/lib/backends/aleph-pin.js +161 -0
- package/lib/backends/aleph.js +197 -0
- package/lib/backends/lighthouse.js +325 -0
- package/lib/backends/memory.js +140 -0
- package/lib/backends/pinata.js +330 -0
- package/lib/backends/resolve.js +72 -0
- package/lib/backends/storacha.js +178 -0
- package/lib/backends/types.js +187 -0
- package/lib/backup-car.js +1286 -0
- package/lib/backup-helpers.js +87 -0
- package/lib/backup-metadata.js +40 -0
- package/lib/backup.js +249 -0
- package/lib/block-bytes.js +39 -0
- package/lib/car-storage.js +342 -0
- package/lib/courier-sync.js +886 -0
- package/lib/dehydrate.js +146 -0
- package/lib/extract-blocks.js +258 -0
- package/lib/gateway-fetch.js +116 -0
- package/lib/ipns-helpers.js +324 -0
- package/lib/logger.js +128 -0
- package/lib/memory-courier.js +105 -0
- package/lib/orbitdb-storacha-bridge.js +2789 -0
- package/lib/pointer-ipns.js +234 -0
- package/lib/restore-cid.js +302 -0
- package/lib/ucan-bridge.js +921 -0
- package/lib/utils.js +316 -0
- package/package.json +172 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Storage backend contract for OrbitDB Storage Bridge
|
|
3
|
+
*
|
|
4
|
+
* The backend is the volatile part of this library: Storacha switched off writes on
|
|
5
|
+
* 2026-05-15 and its endpoints were gone by September, while every line of block
|
|
6
|
+
* extraction, CID bridging and CAR packing around it kept working. This module is the
|
|
7
|
+
* seam that admission of that fact requires — one contract, several drivers, no single
|
|
8
|
+
* service that can take the library with it.
|
|
9
|
+
*
|
|
10
|
+
* Backends differ in who computes the CID, and the contract has to admit all three shapes:
|
|
11
|
+
*
|
|
12
|
+
* - **pin-by-CID** — we publish through Helia and the service fetches what is already
|
|
13
|
+
* ours, so hashes cannot drift. `pinCid()`, declared by `capabilities.pinByCid`.
|
|
14
|
+
* - **blob push** — we push a CAR and get an opaque handle back. `putBlob()`.
|
|
15
|
+
* - **file push** — the service hashes what we send. Only safe when what we send is a
|
|
16
|
+
* CAR, which turns it back into the case above.
|
|
17
|
+
*
|
|
18
|
+
* Capabilities are declared rather than discovered, so the bridge can pick a strategy and
|
|
19
|
+
* the conformance suite can skip what a driver never claimed to do.
|
|
20
|
+
*
|
|
21
|
+
* @author @NiKrause
|
|
22
|
+
* @see {@link ../../docs/STORAGE-BACKENDS.md} for the evaluation behind these distinctions
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {object} BackendCapabilities
|
|
27
|
+
* @property {boolean} pinByCid - `pinCid()` is implemented: content is fetched from IPFS by CID
|
|
28
|
+
* @property {boolean} carImport - CAR files are unpacked by the service and the inner blocks indexed
|
|
29
|
+
* @property {boolean} preservesInnerCids - stored bytes come back under the CID we computed
|
|
30
|
+
* @property {boolean} browserSafeAuth - no shared secret has to reach the browser
|
|
31
|
+
* @property {boolean} delegation - access can be handed on, scoped and time-bounded
|
|
32
|
+
* @property {boolean} listing - `list()` is implemented
|
|
33
|
+
* @property {boolean} deletion - `remove()` is implemented
|
|
34
|
+
* @property {number} minBlobSize - smallest accepted upload in bytes; 0 when there is no minimum
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} BackendHandle
|
|
39
|
+
* @property {string} id - what this backend needs to get the bytes back
|
|
40
|
+
* @property {string} backend - the `name` of the backend that issued it
|
|
41
|
+
* @property {string} [cid] - set when the id is, or carries, a content identifier
|
|
42
|
+
* @property {number} [size] - stored size in bytes, when the backend reports one
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {object} StorageBackend
|
|
47
|
+
* @property {string} name
|
|
48
|
+
* @property {BackendCapabilities} capabilities
|
|
49
|
+
* @property {(bytes: Uint8Array, meta?: object) => Promise<BackendHandle>} putBlob
|
|
50
|
+
* @property {(handle: BackendHandle|string) => Promise<Uint8Array>} getBlob
|
|
51
|
+
* @property {((cid: string, meta?: object) => Promise<BackendHandle>)} [pinCid]
|
|
52
|
+
* @property {((options?: object) => Promise<BackendHandle[]>)} [list]
|
|
53
|
+
* @property {((handle: BackendHandle|string) => Promise<void>)} [remove]
|
|
54
|
+
* @property {(() => Promise<void>)} [close]
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/** Capability defaults. A driver states what it can do; anything unstated is a no. */
|
|
58
|
+
export const DEFAULT_CAPABILITIES = Object.freeze({
|
|
59
|
+
pinByCid: false,
|
|
60
|
+
carImport: false,
|
|
61
|
+
preservesInnerCids: false,
|
|
62
|
+
browserSafeAuth: false,
|
|
63
|
+
delegation: false,
|
|
64
|
+
listing: false,
|
|
65
|
+
deletion: false,
|
|
66
|
+
minBlobSize: 0,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/** Methods every driver must provide. */
|
|
70
|
+
export const REQUIRED_METHODS = Object.freeze(["putBlob", "getBlob"]);
|
|
71
|
+
|
|
72
|
+
/** Optional methods, and the capability flag that must agree with each. */
|
|
73
|
+
export const OPTIONAL_METHODS = Object.freeze({
|
|
74
|
+
pinCid: "pinByCid",
|
|
75
|
+
list: "listing",
|
|
76
|
+
remove: "deletion",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Error raised by the contract itself, so a caller can tell "this backend cannot"
|
|
81
|
+
* from "this backend failed".
|
|
82
|
+
*/
|
|
83
|
+
export class BackendError extends Error {
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} code - UNSUPPORTED, TOO_SMALL, NOT_FOUND or INVALID_BACKEND
|
|
86
|
+
* @param {string} message
|
|
87
|
+
*/
|
|
88
|
+
constructor(code, message) {
|
|
89
|
+
super(message);
|
|
90
|
+
this.name = "BackendError";
|
|
91
|
+
this.code = code;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Normalise a handle, so drivers can accept either the object they issued or a bare id.
|
|
97
|
+
*
|
|
98
|
+
* @param {BackendHandle|string} handle
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
export function handleId(handle) {
|
|
102
|
+
const id = typeof handle === "string" ? handle : handle?.id;
|
|
103
|
+
if (!id) {
|
|
104
|
+
throw new BackendError("NOT_FOUND", "A backend handle or id is required");
|
|
105
|
+
}
|
|
106
|
+
return id;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate a driver against the contract and return it hardened.
|
|
111
|
+
*
|
|
112
|
+
* The wrapper is thin on purpose — it fills in capability defaults, rejects a driver
|
|
113
|
+
* whose methods and flags disagree, and enforces `minBlobSize` before the bytes leave
|
|
114
|
+
* the process. That last one matters: Filecoin Onchain Cloud rejects anything under
|
|
115
|
+
* 127 bytes, and an OrbitDB block is routinely smaller, so this turns a vendor error
|
|
116
|
+
* arriving mid-backup into a contract error raised on the first block.
|
|
117
|
+
*
|
|
118
|
+
* @param {StorageBackend} backend
|
|
119
|
+
* @returns {StorageBackend}
|
|
120
|
+
*/
|
|
121
|
+
export function defineBackend(backend) {
|
|
122
|
+
if (!backend || typeof backend.name !== "string" || !backend.name) {
|
|
123
|
+
throw new BackendError("INVALID_BACKEND", "A backend needs a name");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const method of REQUIRED_METHODS) {
|
|
127
|
+
if (typeof backend[method] !== "function") {
|
|
128
|
+
throw new BackendError(
|
|
129
|
+
"INVALID_BACKEND",
|
|
130
|
+
`Backend "${backend.name}" is missing ${method}()`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const capabilities = Object.freeze({
|
|
136
|
+
...DEFAULT_CAPABILITIES,
|
|
137
|
+
...(backend.capabilities || {}),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
for (const [method, flag] of Object.entries(OPTIONAL_METHODS)) {
|
|
141
|
+
const implemented = typeof backend[method] === "function";
|
|
142
|
+
if (implemented !== Boolean(capabilities[flag])) {
|
|
143
|
+
throw new BackendError(
|
|
144
|
+
"INVALID_BACKEND",
|
|
145
|
+
`Backend "${backend.name}" declares ${flag}=${capabilities[flag]} but ` +
|
|
146
|
+
`${implemented ? "implements" : "does not implement"} ${method}()`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const putBlob = backend.putBlob.bind(backend);
|
|
152
|
+
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
...backend,
|
|
155
|
+
capabilities,
|
|
156
|
+
putBlob: async (bytes, meta) => {
|
|
157
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
158
|
+
throw new BackendError(
|
|
159
|
+
"INVALID_BACKEND",
|
|
160
|
+
"putBlob expects a Uint8Array",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (bytes.length < capabilities.minBlobSize) {
|
|
164
|
+
throw new BackendError(
|
|
165
|
+
"TOO_SMALL",
|
|
166
|
+
`Backend "${backend.name}" needs at least ${capabilities.minBlobSize} bytes, got ${bytes.length}. ` +
|
|
167
|
+
"Pack blocks into a CAR before uploading.",
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return putBlob(bytes, meta);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Raise a consistent error for something a driver deliberately does not do.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} name - backend name
|
|
179
|
+
* @param {string} operation
|
|
180
|
+
* @returns {never}
|
|
181
|
+
*/
|
|
182
|
+
export function unsupported(name, operation) {
|
|
183
|
+
throw new BackendError(
|
|
184
|
+
"UNSUPPORTED",
|
|
185
|
+
`Backend "${name}" does not support ${operation}`,
|
|
186
|
+
);
|
|
187
|
+
}
|