holosphere 2.0.0-alpha21 → 2.0.0-alpha23
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 +1 -2
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +61 -58
- package/dist/{index-B6-8KAQm.js → index-BEkCLOwI.js} +2 -2
- package/dist/{index-B6-8KAQm.js.map → index-BEkCLOwI.js.map} +1 -1
- package/dist/{index-D2WstuZJ.js → index-BEvX6DxG.js} +2 -2
- package/dist/{index-D2WstuZJ.js.map → index-BEvX6DxG.js.map} +1 -1
- package/dist/{index--QsHG_gD.cjs → index-BGTOiJ2Y.cjs} +2 -2
- package/dist/{index--QsHG_gD.cjs.map → index-BGTOiJ2Y.cjs.map} +1 -1
- package/dist/{index-COpLk9gL.cjs → index-BH1woZXL.cjs} +2 -2
- package/dist/{index-COpLk9gL.cjs.map → index-BH1woZXL.cjs.map} +1 -1
- package/dist/{index-BHptWysv.js → index-Cvxov2jv.js} +2970 -7753
- package/dist/index-Cvxov2jv.js.map +1 -0
- package/dist/index-vTKI_BAX.cjs +29 -0
- package/dist/index-vTKI_BAX.cjs.map +1 -0
- package/dist/{indexeddb-storage-wKG4mICM.cjs → indexeddb-storage-BmnCNnSg.cjs} +2 -2
- package/dist/{indexeddb-storage-wKG4mICM.cjs.map → indexeddb-storage-BmnCNnSg.cjs.map} +1 -1
- package/dist/{indexeddb-storage-kQ53UHEE.js → indexeddb-storage-MIFisaPy.js} +2 -2
- package/dist/{indexeddb-storage-kQ53UHEE.js.map → indexeddb-storage-MIFisaPy.js.map} +1 -1
- package/dist/{memory-storage-CGC8xM2G.cjs → memory-storage-BJjK3F4r.cjs} +2 -2
- package/dist/{memory-storage-CGC8xM2G.cjs.map → memory-storage-BJjK3F4r.cjs.map} +1 -1
- package/dist/{memory-storage-DnXCSbBl.js → memory-storage-DhHXdKQ-.js} +2 -2
- package/dist/{memory-storage-DnXCSbBl.js.map → memory-storage-DhHXdKQ-.js.map} +1 -1
- package/examples/demo.html +2 -29
- package/package.json +3 -8
- package/src/content/social-protocols.js +3 -59
- package/src/core/holosphere.js +16 -554
- package/src/crypto/nostr-utils.js +98 -1
- package/src/crypto/secp256k1.js +4 -393
- package/src/federation/discovery.js +7 -75
- package/src/federation/handshake.js +69 -202
- package/src/federation/hologram.js +222 -298
- package/src/federation/index.js +2 -9
- package/src/federation/registry.js +67 -1257
- package/src/federation/request-card.js +21 -35
- package/src/hierarchical/upcast.js +4 -9
- package/src/index.js +145 -296
- package/src/lib/federation-methods.js +370 -909
- package/src/storage/global-tables.js +1 -1
- package/src/storage/nostr-wrapper.js +9 -5
- package/src/subscriptions/manager.js +1 -1
- package/types/index.d.ts +145 -37
- package/bin/holosphere-activitypub.js +0 -158
- package/dist/2019-BzVkRcax.js +0 -6680
- package/dist/2019-BzVkRcax.js.map +0 -1
- package/dist/2019-C1hPR_Os.cjs +0 -8
- package/dist/2019-C1hPR_Os.cjs.map +0 -1
- package/dist/browser-BcmACE3G.js +0 -3058
- package/dist/browser-BcmACE3G.js.map +0 -1
- package/dist/browser-DaqYUTcG.cjs +0 -2
- package/dist/browser-DaqYUTcG.cjs.map +0 -1
- package/dist/index-BHptWysv.js.map +0 -1
- package/dist/index-CDlhzxT2.cjs +0 -29
- package/dist/index-CDlhzxT2.cjs.map +0 -1
- package/src/federation/capabilities.js +0 -46
- package/src/storage/backend-factory.js +0 -130
- package/src/storage/backend-interface.js +0 -161
- package/src/storage/backends/activitypub/server.js +0 -675
- package/src/storage/backends/activitypub-backend.js +0 -295
- package/src/storage/backends/gundb-backend.js +0 -875
- package/src/storage/backends/nostr-backend.js +0 -251
- package/src/storage/gun-async.js +0 -341
- package/src/storage/gun-auth.js +0 -373
- package/src/storage/gun-federation.js +0 -785
- package/src/storage/gun-references.js +0 -209
- package/src/storage/gun-schema.js +0 -306
- package/src/storage/gun-wrapper.js +0 -642
- package/src/storage/migration.js +0 -351
- package/src/storage/unified-storage.js +0 -161
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Nostr Storage Backend.
|
|
3
|
-
*
|
|
4
|
-
* Wraps existing nostr-wrapper.js and nostr-client.js as a StorageBackend
|
|
5
|
-
* implementation. Provides Nostr-based distributed storage with relay support.
|
|
6
|
-
*
|
|
7
|
-
* @module storage/backends/nostr-backend
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { StorageBackend } from '../backend-interface.js';
|
|
11
|
-
import { createClient } from '../nostr-client.js';
|
|
12
|
-
import * as wrapper from '../nostr-wrapper.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Nostr storage backend implementation.
|
|
16
|
-
*
|
|
17
|
-
* Provides distributed storage using Nostr relays with event-based data storage.
|
|
18
|
-
*
|
|
19
|
-
* @class NostrBackend
|
|
20
|
-
* @extends StorageBackend
|
|
21
|
-
* @example
|
|
22
|
-
* const backend = new NostrBackend({
|
|
23
|
-
* relays: ['wss://relay.example.com'],
|
|
24
|
-
* appName: 'myapp',
|
|
25
|
-
* persistence: true
|
|
26
|
-
* });
|
|
27
|
-
* await backend.init();
|
|
28
|
-
*/
|
|
29
|
-
export class NostrBackend extends StorageBackend {
|
|
30
|
-
/**
|
|
31
|
-
* Create a new NostrBackend.
|
|
32
|
-
*
|
|
33
|
-
* @param {Object} config - Backend configuration
|
|
34
|
-
* @param {string[]} [config.relays] - Relay URLs
|
|
35
|
-
* @param {string} [config.privateKey] - Private key for signing
|
|
36
|
-
* @param {string} [config.appName] - Application name
|
|
37
|
-
* @param {boolean} [config.persistence=true] - Enable persistent storage
|
|
38
|
-
* @param {boolean} [config.backgroundSync] - Enable background sync
|
|
39
|
-
*/
|
|
40
|
-
constructor(config) {
|
|
41
|
-
super(config);
|
|
42
|
-
this.client = null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Initialize the backend.
|
|
47
|
-
*
|
|
48
|
-
* @returns {Promise<void>}
|
|
49
|
-
*/
|
|
50
|
-
async init() {
|
|
51
|
-
this.client = createClient({
|
|
52
|
-
relays: this.config.relays || ['wss://relay.holons.io'],
|
|
53
|
-
privateKey: this.config.privateKey,
|
|
54
|
-
enableReconnect: this.config.enableReconnect !== false,
|
|
55
|
-
enablePing: this.config.enablePing !== false,
|
|
56
|
-
appName: this.config.appName,
|
|
57
|
-
radisk: this.config.radisk !== false,
|
|
58
|
-
persistence: this.config.persistence !== false,
|
|
59
|
-
dataDir: this.config.dataDir,
|
|
60
|
-
backgroundSync: this.config.backgroundSync,
|
|
61
|
-
syncInterval: this.config.syncInterval,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Wait for client initialization
|
|
65
|
-
await this.client._initReady;
|
|
66
|
-
this.publicKey = this.client.publicKey;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Build path from components.
|
|
71
|
-
*
|
|
72
|
-
* @param {string} appName - Application name
|
|
73
|
-
* @param {string} holonId - Holon ID
|
|
74
|
-
* @param {string} lensName - Lens name
|
|
75
|
-
* @param {string} [key=null] - Optional key
|
|
76
|
-
* @returns {string} Built path
|
|
77
|
-
*/
|
|
78
|
-
buildPath(appName, holonId, lensName, key = null) {
|
|
79
|
-
return wrapper.buildPath(appName, holonId, lensName, key);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Write data to path.
|
|
84
|
-
*
|
|
85
|
-
* @param {string} path - Storage path
|
|
86
|
-
* @param {Object} data - Data to write
|
|
87
|
-
* @param {Object} [options={}] - Write options
|
|
88
|
-
* @returns {Promise<Object>} Write result
|
|
89
|
-
*/
|
|
90
|
-
async write(path, data, options = {}) {
|
|
91
|
-
return wrapper.write(this.client, path, data);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Read data from path.
|
|
96
|
-
*
|
|
97
|
-
* @param {string} path - Storage path
|
|
98
|
-
* @param {Object} [options={}] - Read options
|
|
99
|
-
* @returns {Promise<Object|null>} Data or null
|
|
100
|
-
*/
|
|
101
|
-
async read(path, options = {}) {
|
|
102
|
-
return wrapper.read(this.client, path, options);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Read all data under path.
|
|
107
|
-
*
|
|
108
|
-
* @param {string} path - Storage path prefix
|
|
109
|
-
* @param {Object} [options={}] - Read options
|
|
110
|
-
* @returns {Promise<Object[]>} Array of data objects
|
|
111
|
-
*/
|
|
112
|
-
async readAll(path, options = {}) {
|
|
113
|
-
return wrapper.readAll(this.client, path, options);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Update data at path.
|
|
118
|
-
*
|
|
119
|
-
* @param {string} path - Storage path
|
|
120
|
-
* @param {Object} updates - Fields to update
|
|
121
|
-
* @returns {Promise<Object>} Update result
|
|
122
|
-
*/
|
|
123
|
-
async update(path, updates) {
|
|
124
|
-
return wrapper.update(this.client, path, updates);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Delete data at path.
|
|
129
|
-
*
|
|
130
|
-
* @param {string} path - Storage path
|
|
131
|
-
* @returns {Promise<boolean>} Success indicator
|
|
132
|
-
*/
|
|
133
|
-
async delete(path) {
|
|
134
|
-
return wrapper.deleteData(this.client, path);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Delete all data under path.
|
|
139
|
-
*
|
|
140
|
-
* @param {string} path - Storage path prefix
|
|
141
|
-
* @returns {Promise<Object>} Deletion results
|
|
142
|
-
*/
|
|
143
|
-
async deleteAll(path) {
|
|
144
|
-
return wrapper.deleteAll(this.client, path);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Subscribe to changes at path.
|
|
149
|
-
*
|
|
150
|
-
* @param {string} path - Storage path
|
|
151
|
-
* @param {Function} callback - Callback function
|
|
152
|
-
* @param {Object} [options={}] - Subscribe options
|
|
153
|
-
* @returns {Promise<Object>} Subscription object with unsubscribe method
|
|
154
|
-
*/
|
|
155
|
-
async subscribe(path, callback, options = {}) {
|
|
156
|
-
return wrapper.subscribe(this.client, path, callback, options);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Export all data for this user.
|
|
161
|
-
*
|
|
162
|
-
* @param {string} [pathPrefix=''] - Optional path prefix filter
|
|
163
|
-
* @returns {Promise<Object[]>} Array of exported records
|
|
164
|
-
*/
|
|
165
|
-
async exportData(pathPrefix = '') {
|
|
166
|
-
// Wait for client to be ready
|
|
167
|
-
await this.client._initReady;
|
|
168
|
-
|
|
169
|
-
// Query all events for this author
|
|
170
|
-
const events = await this.client.query({
|
|
171
|
-
kinds: [30000],
|
|
172
|
-
authors: [this.publicKey],
|
|
173
|
-
limit: 10000,
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
return events
|
|
177
|
-
.filter(e => {
|
|
178
|
-
const dTag = e.tags.find(t => t[0] === 'd');
|
|
179
|
-
if (!dTag) return false;
|
|
180
|
-
// Filter by prefix if provided
|
|
181
|
-
if (pathPrefix && !dTag[1].startsWith(pathPrefix)) return false;
|
|
182
|
-
return true;
|
|
183
|
-
})
|
|
184
|
-
.map(e => {
|
|
185
|
-
const dTag = e.tags.find(t => t[0] === 'd');
|
|
186
|
-
let data;
|
|
187
|
-
try {
|
|
188
|
-
data = JSON.parse(e.content);
|
|
189
|
-
} catch {
|
|
190
|
-
data = { content: e.content };
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
path: dTag[1],
|
|
194
|
-
data,
|
|
195
|
-
timestamp: e.created_at * 1000,
|
|
196
|
-
author: e.pubkey,
|
|
197
|
-
};
|
|
198
|
-
})
|
|
199
|
-
.filter(record => !record.data._deleted); // Filter out deleted items
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Import data records.
|
|
204
|
-
*
|
|
205
|
-
* @param {Object[]} records - Array of records to import
|
|
206
|
-
* @param {Object} [options={}] - Import options
|
|
207
|
-
* @returns {Promise<Object>} Import results with success/failed counts
|
|
208
|
-
*/
|
|
209
|
-
async importData(records, options = {}) {
|
|
210
|
-
const results = { success: 0, failed: 0, errors: [] };
|
|
211
|
-
|
|
212
|
-
for (const record of records) {
|
|
213
|
-
try {
|
|
214
|
-
await wrapper.write(this.client, record.path, record.data);
|
|
215
|
-
results.success++;
|
|
216
|
-
} catch (error) {
|
|
217
|
-
results.failed++;
|
|
218
|
-
results.errors.push({ path: record.path, error: error.message });
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return results;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Close the backend and cleanup resources.
|
|
227
|
-
*
|
|
228
|
-
* @returns {void}
|
|
229
|
-
*/
|
|
230
|
-
close() {
|
|
231
|
-
if (this.client) {
|
|
232
|
-
this.client.close();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Get backend status.
|
|
238
|
-
*
|
|
239
|
-
* @returns {Object} Status object with type, publicKey, relays, connected
|
|
240
|
-
*/
|
|
241
|
-
getStatus() {
|
|
242
|
-
return {
|
|
243
|
-
type: 'nostr',
|
|
244
|
-
publicKey: this.publicKey,
|
|
245
|
-
relays: this.config.relays || [],
|
|
246
|
-
connected: !!this.client,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
export default NostrBackend;
|
package/src/storage/gun-async.js
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Gun Async Utilities.
|
|
3
|
-
*
|
|
4
|
-
* Provides Promise-based wrappers and async patterns for Gun operations.
|
|
5
|
-
* Includes utilities for promises, batch operations, retries, streams, and async iteration.
|
|
6
|
-
*
|
|
7
|
-
* @module storage/gun-async
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Get data from Gun using native .then() support.
|
|
12
|
-
*
|
|
13
|
-
* @param {Object} gunChain - Gun chain reference
|
|
14
|
-
* @param {number} [timeout=1000] - Timeout in ms
|
|
15
|
-
* @returns {Promise<any>} Promise resolving to data or null on timeout
|
|
16
|
-
*/
|
|
17
|
-
export function gunPromise(gunChain, timeout = 1000) {
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
let settled = false;
|
|
20
|
-
|
|
21
|
-
const timer = setTimeout(() => {
|
|
22
|
-
if (!settled) {
|
|
23
|
-
settled = true;
|
|
24
|
-
resolve(null);
|
|
25
|
-
}
|
|
26
|
-
}, timeout);
|
|
27
|
-
|
|
28
|
-
gunChain.once((data) => {
|
|
29
|
-
if (!settled) {
|
|
30
|
-
settled = true;
|
|
31
|
-
clearTimeout(timer);
|
|
32
|
-
resolve(data || null);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Wait for Gun write acknowledgement.
|
|
40
|
-
*
|
|
41
|
-
* @param {Object} gunChain - Gun chain reference
|
|
42
|
-
* @param {any} data - Data to write
|
|
43
|
-
* @param {number} [timeout=1000] - Timeout in ms
|
|
44
|
-
* @returns {Promise<Object>} Promise resolving to ack object { ok, timeout? }
|
|
45
|
-
* @throws {Error} If write fails with error
|
|
46
|
-
*/
|
|
47
|
-
export function gunPut(gunChain, data, timeout = 1000) {
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
let settled = false;
|
|
50
|
-
|
|
51
|
-
const timer = setTimeout(() => {
|
|
52
|
-
if (!settled) {
|
|
53
|
-
settled = true;
|
|
54
|
-
resolve({ ok: true, timeout: true });
|
|
55
|
-
}
|
|
56
|
-
}, timeout);
|
|
57
|
-
|
|
58
|
-
gunChain.put(data, (ack) => {
|
|
59
|
-
if (!settled) {
|
|
60
|
-
settled = true;
|
|
61
|
-
clearTimeout(timer);
|
|
62
|
-
if (ack.err) {
|
|
63
|
-
reject(new Error(ack.err));
|
|
64
|
-
} else {
|
|
65
|
-
resolve(ack);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get all items from a Gun map.
|
|
74
|
-
*
|
|
75
|
-
* First gets count of items, then collects until count is reached.
|
|
76
|
-
*
|
|
77
|
-
* @param {Object} gunChain - Gun chain reference
|
|
78
|
-
* @param {number} [timeout=5000] - Timeout in ms (fallback)
|
|
79
|
-
* @returns {Promise<Object>} Promise resolving to map of items
|
|
80
|
-
*/
|
|
81
|
-
export async function gunMap(gunChain, timeout = 5000) {
|
|
82
|
-
return new Promise((resolve) => {
|
|
83
|
-
const items = {};
|
|
84
|
-
let settled = false;
|
|
85
|
-
let expectedCount = 0;
|
|
86
|
-
let receivedCount = 0;
|
|
87
|
-
|
|
88
|
-
const tryResolve = () => {
|
|
89
|
-
if (settled) return;
|
|
90
|
-
if (expectedCount > 0 && receivedCount >= expectedCount) {
|
|
91
|
-
settled = true;
|
|
92
|
-
resolve(items);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Step 1: Get parent to count expected items
|
|
97
|
-
gunChain.once((parentData) => {
|
|
98
|
-
if (settled) return;
|
|
99
|
-
|
|
100
|
-
if (!parentData) {
|
|
101
|
-
settled = true;
|
|
102
|
-
resolve({});
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const keys = Object.keys(parentData).filter(k => k !== '_');
|
|
107
|
-
if (keys.length === 0) {
|
|
108
|
-
settled = true;
|
|
109
|
-
resolve({});
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Pre-collect inline items (not Gun references)
|
|
114
|
-
for (const key of keys) {
|
|
115
|
-
const rawItem = parentData[key];
|
|
116
|
-
if (!rawItem) continue;
|
|
117
|
-
|
|
118
|
-
// Skip Gun references - will be fetched via map().once()
|
|
119
|
-
if (typeof rawItem === 'object' && rawItem['#']) continue;
|
|
120
|
-
|
|
121
|
-
items[key] = rawItem;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Expected count is ALL keys (map().once() fires for all)
|
|
125
|
-
expectedCount = keys.length;
|
|
126
|
-
|
|
127
|
-
// Step 2: Collect items via map().once(), counting as we go
|
|
128
|
-
gunChain.map().once((data, key) => {
|
|
129
|
-
if (settled || !data || key.startsWith('_')) return;
|
|
130
|
-
// Add/update the item (inline items may be updated by map().once())
|
|
131
|
-
items[key] = data;
|
|
132
|
-
receivedCount++;
|
|
133
|
-
tryResolve();
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Fallback timeout
|
|
138
|
-
setTimeout(() => {
|
|
139
|
-
if (!settled) {
|
|
140
|
-
settled = true;
|
|
141
|
-
resolve(items);
|
|
142
|
-
}
|
|
143
|
-
}, timeout);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Load full chain data (follows all references)
|
|
149
|
-
* @param {Object} gunChain - Gun chain reference
|
|
150
|
-
* @param {number} depth - Max depth to traverse (default 3)
|
|
151
|
-
* @returns {Promise<any>} Promise resolving to loaded data
|
|
152
|
-
*/
|
|
153
|
-
export async function gunLoad(gunChain, depth = 3) {
|
|
154
|
-
return new Promise((resolve) => {
|
|
155
|
-
let result = null;
|
|
156
|
-
|
|
157
|
-
gunChain.load((data) => {
|
|
158
|
-
result = data;
|
|
159
|
-
}, { wait: 100 });
|
|
160
|
-
|
|
161
|
-
setTimeout(() => {
|
|
162
|
-
resolve(result);
|
|
163
|
-
}, 100 * depth);
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Async iterator for Gun map
|
|
169
|
-
* @param {Object} gunChain - Gun chain reference
|
|
170
|
-
* @returns {AsyncGenerator} Async generator yielding [key, value] pairs
|
|
171
|
-
*/
|
|
172
|
-
export async function* gunMapIterator(gunChain) {
|
|
173
|
-
const items = await gunMap(gunChain);
|
|
174
|
-
for (const [key, value] of Object.entries(items)) {
|
|
175
|
-
yield [key, value];
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Collect Gun on() stream into array over time
|
|
181
|
-
* @param {Object} gunChain - Gun chain reference
|
|
182
|
-
* @param {number} duration - Collection duration in ms
|
|
183
|
-
* @returns {Promise<Array>} Promise resolving to array of data
|
|
184
|
-
*/
|
|
185
|
-
export async function gunCollect(gunChain, duration = 500) {
|
|
186
|
-
return new Promise((resolve) => {
|
|
187
|
-
const results = [];
|
|
188
|
-
const seen = new Set();
|
|
189
|
-
|
|
190
|
-
const listener = gunChain.on((data, key) => {
|
|
191
|
-
if (data && !seen.has(key)) {
|
|
192
|
-
seen.add(key);
|
|
193
|
-
results.push({ key, data });
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
setTimeout(() => {
|
|
198
|
-
listener.off();
|
|
199
|
-
resolve(results);
|
|
200
|
-
}, duration);
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Wait for specific condition on Gun data
|
|
206
|
-
* @param {Object} gunChain - Gun chain reference
|
|
207
|
-
* @param {Function} predicate - Condition function (data) => boolean
|
|
208
|
-
* @param {number} timeout - Timeout in ms (default 5000ms)
|
|
209
|
-
* @returns {Promise<any>} Promise resolving when condition is met
|
|
210
|
-
*/
|
|
211
|
-
export async function gunWaitFor(gunChain, predicate, timeout = 5000) {
|
|
212
|
-
return new Promise((resolve, reject) => {
|
|
213
|
-
let timeoutId;
|
|
214
|
-
let listener;
|
|
215
|
-
|
|
216
|
-
const cleanup = () => {
|
|
217
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
218
|
-
if (listener) listener.off();
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
listener = gunChain.on((data) => {
|
|
222
|
-
if (predicate(data)) {
|
|
223
|
-
cleanup();
|
|
224
|
-
resolve(data);
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
timeoutId = setTimeout(() => {
|
|
229
|
-
cleanup();
|
|
230
|
-
reject(new Error('Timeout waiting for condition'));
|
|
231
|
-
}, timeout);
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Batch read multiple Gun paths
|
|
237
|
-
* @param {Object} gun - Gun instance
|
|
238
|
-
* @param {string[]} paths - Array of paths to read
|
|
239
|
-
* @returns {Promise<Object>} Object mapping paths to data
|
|
240
|
-
*/
|
|
241
|
-
export async function gunBatchGet(gun, paths) {
|
|
242
|
-
const promises = paths.map(async (path) => {
|
|
243
|
-
const data = await gunPromise(gun.get(path));
|
|
244
|
-
return [path, data];
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const results = await Promise.all(promises);
|
|
248
|
-
return Object.fromEntries(results);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Batch write multiple Gun paths
|
|
253
|
-
* @param {Object} gun - Gun instance
|
|
254
|
-
* @param {Object} pathDataMap - Object mapping paths to data
|
|
255
|
-
* @returns {Promise<Object>} Object mapping paths to ack results
|
|
256
|
-
*/
|
|
257
|
-
export async function gunBatchPut(gun, pathDataMap) {
|
|
258
|
-
const promises = Object.entries(pathDataMap).map(async ([path, data]) => {
|
|
259
|
-
const ack = await gunPut(gun.get(path), data);
|
|
260
|
-
return [path, ack];
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const results = await Promise.all(promises);
|
|
264
|
-
return Object.fromEntries(results);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Retry Gun operation with exponential backoff
|
|
269
|
-
* @param {Function} operation - Async function to retry
|
|
270
|
-
* @param {number} maxRetries - Max retry attempts (default 3)
|
|
271
|
-
* @param {number} baseDelay - Base delay in ms (default 100ms)
|
|
272
|
-
* @returns {Promise<any>} Promise resolving to operation result
|
|
273
|
-
*/
|
|
274
|
-
export async function gunRetry(operation, maxRetries = 3, baseDelay = 100) {
|
|
275
|
-
let lastError;
|
|
276
|
-
|
|
277
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
278
|
-
try {
|
|
279
|
-
return await operation();
|
|
280
|
-
} catch (error) {
|
|
281
|
-
lastError = error;
|
|
282
|
-
if (attempt < maxRetries) {
|
|
283
|
-
const delay = baseDelay * Math.pow(2, attempt);
|
|
284
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
throw lastError;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Create async stream from Gun on() callback
|
|
294
|
-
* @param {Object} gunChain - Gun chain reference
|
|
295
|
-
* @returns {Object} Stream object with async iteration support
|
|
296
|
-
*/
|
|
297
|
-
export function gunStream(gunChain) {
|
|
298
|
-
let listeners = [];
|
|
299
|
-
let buffer = [];
|
|
300
|
-
let ended = false;
|
|
301
|
-
|
|
302
|
-
const stream = {
|
|
303
|
-
[Symbol.asyncIterator]() {
|
|
304
|
-
return {
|
|
305
|
-
async next() {
|
|
306
|
-
if (buffer.length > 0) {
|
|
307
|
-
return { value: buffer.shift(), done: false };
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (ended) {
|
|
311
|
-
return { done: true };
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Wait for next value
|
|
315
|
-
return new Promise((resolve) => {
|
|
316
|
-
listeners.push((data) => {
|
|
317
|
-
resolve({ value: data, done: false });
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
},
|
|
321
|
-
};
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
stop() {
|
|
325
|
-
ended = true;
|
|
326
|
-
if (this.listener) this.listener.off();
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
stream.listener = gunChain.on((data, key) => {
|
|
331
|
-
const item = { key, data };
|
|
332
|
-
if (listeners.length > 0) {
|
|
333
|
-
const listener = listeners.shift();
|
|
334
|
-
listener(item);
|
|
335
|
-
} else {
|
|
336
|
-
buffer.push(item);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
return stream;
|
|
341
|
-
}
|