holosphere 2.0.0-alpha4 → 2.0.0-alpha6
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/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +1 -1
- package/dist/index-BtKHqqet.cjs +5 -0
- package/dist/index-BtKHqqet.cjs.map +1 -0
- package/dist/{index-CBitK71M.cjs → index-CmzkI7SI.cjs} +2 -2
- package/dist/{index-CBitK71M.cjs.map → index-CmzkI7SI.cjs.map} +1 -1
- package/dist/{index-Cz-PLCUR.js → index-JFz-dW43.js} +2 -2
- package/dist/{index-Cz-PLCUR.js.map → index-JFz-dW43.js.map} +1 -1
- package/dist/{index-CV0eOogK.js → index-NOravBLu.js} +733 -164
- package/dist/index-NOravBLu.js.map +1 -0
- package/dist/{indexeddb-storage-CRsZyB2f.cjs → indexeddb-storage-C4HsulhA.cjs} +2 -2
- package/dist/{indexeddb-storage-CRsZyB2f.cjs.map → indexeddb-storage-C4HsulhA.cjs.map} +1 -1
- package/dist/{indexeddb-storage-DZaGlY_a.js → indexeddb-storage-OtSAVDZY.js} +2 -2
- package/dist/{indexeddb-storage-DZaGlY_a.js.map → indexeddb-storage-OtSAVDZY.js.map} +1 -1
- package/dist/{memory-storage-BkUi6sZG.js → memory-storage-ChpcYvxA.js} +2 -2
- package/dist/{memory-storage-BkUi6sZG.js.map → memory-storage-ChpcYvxA.js.map} +1 -1
- package/dist/{memory-storage-C0DuUsdY.cjs → memory-storage-MD6ED00P.cjs} +2 -2
- package/dist/{memory-storage-C0DuUsdY.cjs.map → memory-storage-MD6ED00P.cjs.map} +1 -1
- package/dist/{secp256k1-0kPdAVkK.cjs → secp256k1-DcTYQrqC.cjs} +2 -2
- package/dist/{secp256k1-0kPdAVkK.cjs.map → secp256k1-DcTYQrqC.cjs.map} +1 -1
- package/dist/{secp256k1-DN4FVXcv.js → secp256k1-PfNOEI7a.js} +2 -2
- package/dist/{secp256k1-DN4FVXcv.js.map → secp256k1-PfNOEI7a.js.map} +1 -1
- package/package.json +1 -1
- package/src/contracts/abis/Bundle.json +1438 -1435
- package/src/contracts/deployer.js +32 -3
- package/src/federation/handshake.js +13 -5
- package/src/index.js +9 -1
- package/src/storage/gun-async.js +55 -6
- package/src/storage/gun-auth.js +81 -30
- package/src/storage/gun-wrapper.js +56 -48
- package/src/storage/nostr-async.js +149 -14
- package/src/storage/nostr-client.js +574 -48
- package/dist/index-BB_vVJgv.cjs +0 -5
- package/dist/index-BB_vVJgv.cjs.map +0 -1
- package/dist/index-CV0eOogK.js.map +0 -1
|
@@ -304,6 +304,7 @@ export class ContractDeployer {
|
|
|
304
304
|
|
|
305
305
|
/**
|
|
306
306
|
* Deploy a Bundle contract (2-way split with steepness)
|
|
307
|
+
* This is the simplest way to deploy a holon - no registry needed!
|
|
307
308
|
* @param {string} name - Contract name
|
|
308
309
|
* @param {number} [steepness=500000000000000000n] - Steepness factor (0.5e18 = 50% decay)
|
|
309
310
|
* @param {number} [nZones=6] - Number of zones
|
|
@@ -319,9 +320,17 @@ export class ContractDeployer {
|
|
|
319
320
|
// Default steepness: 0.5e18 (50% decay per zone)
|
|
320
321
|
const steepnessValue = steepness || ethers.parseEther('0.5');
|
|
321
322
|
|
|
323
|
+
// BundleABI now has { abi, bytecode } structure
|
|
324
|
+
const abi = BundleABI.abi || BundleABI;
|
|
325
|
+
const bytecode = BundleABI.bytecode;
|
|
326
|
+
|
|
327
|
+
if (!bytecode) {
|
|
328
|
+
throw new Error('Bundle bytecode not available. Please rebuild contracts.');
|
|
329
|
+
}
|
|
330
|
+
|
|
322
331
|
const result = await this._deployContract(
|
|
323
|
-
|
|
324
|
-
|
|
332
|
+
abi,
|
|
333
|
+
bytecode,
|
|
325
334
|
[deployerAddress, creatorUserId, name, steepnessValue, nZones]
|
|
326
335
|
);
|
|
327
336
|
|
|
@@ -329,10 +338,30 @@ export class ContractDeployer {
|
|
|
329
338
|
...result,
|
|
330
339
|
type: 'Bundle',
|
|
331
340
|
steepness: steepnessValue,
|
|
332
|
-
nZones
|
|
341
|
+
nZones,
|
|
342
|
+
creatorUserId,
|
|
343
|
+
name
|
|
333
344
|
};
|
|
334
345
|
}
|
|
335
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Deploy a Bundle contract directly (simplified 1-click deployment)
|
|
349
|
+
* No registry needed - just deploys and returns the address
|
|
350
|
+
* @param {string} holonId - Unique identifier for the holon
|
|
351
|
+
* @param {string} name - Display name for the holon
|
|
352
|
+
* @param {Object} [options] - Options { steepness, nZones }
|
|
353
|
+
* @returns {Promise<{address, txHash, contract}>}
|
|
354
|
+
*/
|
|
355
|
+
async deployBundleDirect(holonId, name, options = {}) {
|
|
356
|
+
const steepness = options.steepness;
|
|
357
|
+
const nZones = options.nZones || 6;
|
|
358
|
+
|
|
359
|
+
return this.deployBundle(name, steepness, nZones, {
|
|
360
|
+
creatorUserId: holonId,
|
|
361
|
+
...options
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
336
365
|
/**
|
|
337
366
|
* Deploy a TestToken (ERC20 for testing)
|
|
338
367
|
* @param {string} [initialSupply='1000000'] - Initial supply in whole tokens
|
|
@@ -301,8 +301,8 @@ export async function initiateFederationHandshake(holosphere, privateKey, params
|
|
|
301
301
|
|
|
302
302
|
try {
|
|
303
303
|
// Get sender's public key
|
|
304
|
-
const {
|
|
305
|
-
const senderPubKey =
|
|
304
|
+
const { getPublicKey } = await import('../crypto/nostr-utils.js');
|
|
305
|
+
const senderPubKey = getPublicKey(privateKey);
|
|
306
306
|
|
|
307
307
|
// Create federation request
|
|
308
308
|
const request = createFederationRequest({
|
|
@@ -361,10 +361,10 @@ export async function acceptFederationRequest(holosphere, privateKey, params) {
|
|
|
361
361
|
|
|
362
362
|
try {
|
|
363
363
|
// Get responder's public key
|
|
364
|
-
const {
|
|
365
|
-
const responderPubKey =
|
|
364
|
+
const { getPublicKey } = await import('../crypto/nostr-utils.js');
|
|
365
|
+
const responderPubKey = getPublicKey(privateKey);
|
|
366
366
|
|
|
367
|
-
// Add sender as federated partner
|
|
367
|
+
// Add sender as federated partner in Nostr registry
|
|
368
368
|
if (holosphere.client) {
|
|
369
369
|
await addFederatedPartner(holosphere.client, holosphere.config.appName, senderPubKey, {
|
|
370
370
|
alias: request.senderHolonName,
|
|
@@ -379,6 +379,14 @@ export async function acceptFederationRequest(holosphere, privateKey, params) {
|
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
// Create the actual federation record in GunDB storage
|
|
383
|
+
// This is what makes the partner appear in getFederation() / loadFederationData()
|
|
384
|
+
// Store the sender's name so we can display it without reading their settings
|
|
385
|
+
await holosphere.federateHolon(holonId, senderPubKey, {
|
|
386
|
+
lensConfig,
|
|
387
|
+
partnerName: request.senderHolonName
|
|
388
|
+
});
|
|
389
|
+
|
|
382
390
|
// Create and send response
|
|
383
391
|
const response = createFederationResponse({
|
|
384
392
|
requestId: request.requestId,
|
package/src/index.js
CHANGED
|
@@ -798,6 +798,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
798
798
|
if (!Array.isArray(data.inbound)) data.inbound = [];
|
|
799
799
|
if (!Array.isArray(data.outbound)) data.outbound = [];
|
|
800
800
|
if (!data.lensConfig || typeof data.lensConfig !== 'object') data.lensConfig = {};
|
|
801
|
+
if (!data.partnerNames || typeof data.partnerNames !== 'object') data.partnerNames = {};
|
|
801
802
|
|
|
802
803
|
if (!Array.isArray(data.federated)) {
|
|
803
804
|
const allFederated = new Set([...data.inbound, ...data.outbound]);
|
|
@@ -808,7 +809,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
808
809
|
}
|
|
809
810
|
|
|
810
811
|
async federateHolon(sourceHolon, targetHolon, options = {}) {
|
|
811
|
-
const { lensConfig = { inbound: [], outbound: [] } } = options;
|
|
812
|
+
const { lensConfig = { inbound: [], outbound: [] }, partnerName = null } = options;
|
|
812
813
|
|
|
813
814
|
if (sourceHolon === targetHolon) {
|
|
814
815
|
throw new Error('Cannot federate a holon with itself');
|
|
@@ -821,6 +822,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
821
822
|
inbound: [],
|
|
822
823
|
outbound: [],
|
|
823
824
|
lensConfig: {},
|
|
825
|
+
partnerNames: {},
|
|
824
826
|
timestamp: Date.now()
|
|
825
827
|
};
|
|
826
828
|
|
|
@@ -828,11 +830,17 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
828
830
|
if (!Array.isArray(federationData.inbound)) federationData.inbound = [];
|
|
829
831
|
if (!Array.isArray(federationData.outbound)) federationData.outbound = [];
|
|
830
832
|
if (!federationData.lensConfig || typeof federationData.lensConfig !== 'object') federationData.lensConfig = {};
|
|
833
|
+
if (!federationData.partnerNames || typeof federationData.partnerNames !== 'object') federationData.partnerNames = {};
|
|
831
834
|
|
|
832
835
|
if (!federationData.federated.includes(targetHolon)) {
|
|
833
836
|
federationData.federated.push(targetHolon);
|
|
834
837
|
}
|
|
835
838
|
|
|
839
|
+
// Store the partner's name if provided
|
|
840
|
+
if (partnerName) {
|
|
841
|
+
federationData.partnerNames[targetHolon] = partnerName;
|
|
842
|
+
}
|
|
843
|
+
|
|
836
844
|
if (lensConfig.outbound && lensConfig.outbound.length > 0) {
|
|
837
845
|
if (!federationData.outbound.includes(targetHolon)) {
|
|
838
846
|
federationData.outbound.push(targetHolon);
|
package/src/storage/gun-async.js
CHANGED
|
@@ -64,22 +64,71 @@ export function gunPut(gunChain, data, timeout = 1000) {
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Get all items from a Gun map
|
|
67
|
+
* First gets count of items, then collects until count is reached
|
|
67
68
|
* @param {Object} gunChain - Gun chain reference
|
|
68
|
-
* @param {number} timeout - Timeout in ms (default
|
|
69
|
+
* @param {number} timeout - Timeout in ms (fallback, default 5000ms)
|
|
69
70
|
* @returns {Promise<Object>} Promise resolving to map of items
|
|
70
71
|
*/
|
|
71
|
-
export async function gunMap(gunChain, timeout =
|
|
72
|
+
export async function gunMap(gunChain, timeout = 5000) {
|
|
72
73
|
return new Promise((resolve) => {
|
|
73
74
|
const items = {};
|
|
75
|
+
let settled = false;
|
|
76
|
+
let expectedCount = 0;
|
|
77
|
+
let receivedCount = 0;
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
79
|
+
const tryResolve = () => {
|
|
80
|
+
if (settled) return;
|
|
81
|
+
if (expectedCount > 0 && receivedCount >= expectedCount) {
|
|
82
|
+
settled = true;
|
|
83
|
+
resolve(items);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Step 1: Get parent to count expected items
|
|
88
|
+
gunChain.once((parentData) => {
|
|
89
|
+
if (settled) return;
|
|
90
|
+
|
|
91
|
+
if (!parentData) {
|
|
92
|
+
settled = true;
|
|
93
|
+
resolve({});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const keys = Object.keys(parentData).filter(k => k !== '_');
|
|
98
|
+
if (keys.length === 0) {
|
|
99
|
+
settled = true;
|
|
100
|
+
resolve({});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Count references that need fetching
|
|
105
|
+
expectedCount = keys.filter(k => {
|
|
106
|
+
const item = parentData[k];
|
|
107
|
+
return item && typeof item === 'object' && item['#'];
|
|
108
|
+
}).length;
|
|
109
|
+
|
|
110
|
+
// If no references, we're done
|
|
111
|
+
if (expectedCount === 0) {
|
|
112
|
+
settled = true;
|
|
113
|
+
resolve({});
|
|
114
|
+
return;
|
|
78
115
|
}
|
|
116
|
+
|
|
117
|
+
// Step 2: Collect items, counting as we go
|
|
118
|
+
gunChain.map().once((data, key) => {
|
|
119
|
+
if (settled || !data || key.startsWith('_')) return;
|
|
120
|
+
items[key] = data;
|
|
121
|
+
receivedCount++;
|
|
122
|
+
tryResolve();
|
|
123
|
+
});
|
|
79
124
|
});
|
|
80
125
|
|
|
126
|
+
// Fallback timeout
|
|
81
127
|
setTimeout(() => {
|
|
82
|
-
|
|
128
|
+
if (!settled) {
|
|
129
|
+
settled = true;
|
|
130
|
+
resolve(items);
|
|
131
|
+
}
|
|
83
132
|
}, timeout);
|
|
84
133
|
});
|
|
85
134
|
}
|
package/src/storage/gun-auth.js
CHANGED
|
@@ -208,11 +208,12 @@ export class GunAuth {
|
|
|
208
208
|
|
|
209
209
|
/**
|
|
210
210
|
* Read all data from a private lens
|
|
211
|
+
* First gets count of items, then collects until count is reached
|
|
211
212
|
* @param {string} lens - Lens name
|
|
212
|
-
* @param {number} timeout - Timeout in ms
|
|
213
|
+
* @param {number} timeout - Timeout in ms (fallback)
|
|
213
214
|
* @returns {Promise<Object[]>} Array of data objects
|
|
214
215
|
*/
|
|
215
|
-
async readAllPrivate(lens, timeout =
|
|
216
|
+
async readAllPrivate(lens, timeout = 5000) {
|
|
216
217
|
if (!this.isAuthenticated()) {
|
|
217
218
|
throw new Error('Not authenticated');
|
|
218
219
|
}
|
|
@@ -221,42 +222,92 @@ export class GunAuth {
|
|
|
221
222
|
const results = [];
|
|
222
223
|
const seen = new Set();
|
|
223
224
|
const ref = this.getPrivatePath(lens);
|
|
225
|
+
let settled = false;
|
|
226
|
+
let expectedCount = 0;
|
|
227
|
+
let receivedCount = 0;
|
|
228
|
+
|
|
229
|
+
const tryResolve = () => {
|
|
230
|
+
if (settled) return;
|
|
231
|
+
if (expectedCount > 0 && receivedCount >= expectedCount) {
|
|
232
|
+
settled = true;
|
|
233
|
+
resolve(results);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
224
236
|
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
237
|
+
const parseItem = (data) => {
|
|
238
|
+
if (!data) return null;
|
|
239
|
+
let parsed = null;
|
|
240
|
+
// Primary: JSON string format (like old holosphere)
|
|
241
|
+
if (typeof data === 'string') {
|
|
242
|
+
try {
|
|
243
|
+
parsed = JSON.parse(data);
|
|
244
|
+
} catch (e) {
|
|
245
|
+
parsed = data;
|
|
246
|
+
}
|
|
247
|
+
// Legacy: _json wrapper format
|
|
248
|
+
} else if (data._json && typeof data._json === 'string') {
|
|
249
|
+
try {
|
|
250
|
+
parsed = JSON.parse(data._json);
|
|
251
|
+
} catch (e) {}
|
|
252
|
+
// Plain object
|
|
253
|
+
} else if (typeof data === 'object') {
|
|
254
|
+
parsed = { ...data };
|
|
255
|
+
delete parsed['_'];
|
|
256
|
+
}
|
|
257
|
+
return parsed;
|
|
258
|
+
};
|
|
231
259
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
260
|
+
// Step 1: Get parent to count expected items
|
|
261
|
+
ref.once((parentData) => {
|
|
262
|
+
if (settled) return;
|
|
235
263
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
} catch (e) {
|
|
242
|
-
parsed = data;
|
|
243
|
-
}
|
|
244
|
-
// Legacy: _json wrapper format
|
|
245
|
-
} else if (data._json && typeof data._json === 'string') {
|
|
246
|
-
try {
|
|
247
|
-
parsed = JSON.parse(data._json);
|
|
248
|
-
} catch (e) {}
|
|
249
|
-
// Plain object
|
|
250
|
-
} else if (typeof data === 'object') {
|
|
251
|
-
parsed = { ...data };
|
|
252
|
-
delete parsed['_'];
|
|
253
|
-
}
|
|
264
|
+
if (!parentData) {
|
|
265
|
+
settled = true;
|
|
266
|
+
resolve([]);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
254
269
|
|
|
270
|
+
const keys = Object.keys(parentData).filter(k => k !== '_');
|
|
271
|
+
if (keys.length === 0) {
|
|
272
|
+
settled = true;
|
|
273
|
+
resolve([]);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Count references that need fetching
|
|
278
|
+
expectedCount = keys.filter(k => {
|
|
279
|
+
const item = parentData[k];
|
|
280
|
+
return item && typeof item === 'object' && item['#'];
|
|
281
|
+
}).length;
|
|
282
|
+
|
|
283
|
+
// If no references, we're done
|
|
284
|
+
if (expectedCount === 0) {
|
|
285
|
+
settled = true;
|
|
286
|
+
resolve([]);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Step 2: Collect items, counting as we go
|
|
291
|
+
ref.map().once((data, key) => {
|
|
292
|
+
if (settled || !data || key.startsWith('_') || seen.has(key)) return;
|
|
293
|
+
seen.add(key);
|
|
294
|
+
|
|
295
|
+
const parsed = parseItem(data);
|
|
255
296
|
if (parsed && !parsed._deleted) {
|
|
256
297
|
results.push(parsed);
|
|
257
298
|
}
|
|
258
|
-
|
|
299
|
+
receivedCount++;
|
|
300
|
+
tryResolve();
|
|
301
|
+
});
|
|
259
302
|
});
|
|
303
|
+
|
|
304
|
+
// Fallback timeout
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
if (!settled) {
|
|
307
|
+
settled = true;
|
|
308
|
+
resolve(results);
|
|
309
|
+
}
|
|
310
|
+
}, timeout);
|
|
260
311
|
});
|
|
261
312
|
}
|
|
262
313
|
|
|
@@ -186,20 +186,46 @@ export async function read(gun, path) {
|
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
188
|
* Read all data under a path (lens query)
|
|
189
|
-
*
|
|
190
|
-
* Falls back to .once() with iteration
|
|
189
|
+
* First gets the count of expected items, then collects until count is reached
|
|
191
190
|
* @param {Object} gun - Gun instance
|
|
192
191
|
* @param {string} path - Gun path
|
|
192
|
+
* @param {number} timeout - Maximum timeout in ms (default 5000)
|
|
193
193
|
* @returns {Promise<Object[]>} Array of data objects
|
|
194
194
|
*/
|
|
195
|
-
export async function readAll(gun, path) {
|
|
195
|
+
export async function readAll(gun, path, timeout = 5000) {
|
|
196
196
|
return new Promise((resolve) => {
|
|
197
197
|
const output = new Map();
|
|
198
198
|
let settled = false;
|
|
199
|
+
let expectedCount = 0;
|
|
200
|
+
let receivedCount = 0;
|
|
199
201
|
|
|
200
202
|
const ref = gun.get(path);
|
|
201
203
|
|
|
202
|
-
|
|
204
|
+
const tryResolve = () => {
|
|
205
|
+
if (settled) return;
|
|
206
|
+
if (expectedCount > 0 && receivedCount >= expectedCount) {
|
|
207
|
+
settled = true;
|
|
208
|
+
resolve(Array.from(output.values()));
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const parseItem = (data) => {
|
|
213
|
+
if (!data) return null;
|
|
214
|
+
|
|
215
|
+
let item = null;
|
|
216
|
+
if (data._json && typeof data._json === 'string') {
|
|
217
|
+
try {
|
|
218
|
+
item = JSON.parse(data._json);
|
|
219
|
+
} catch (e) {}
|
|
220
|
+
} else if (typeof data === 'string') {
|
|
221
|
+
try {
|
|
222
|
+
item = JSON.parse(data);
|
|
223
|
+
} catch (e) {}
|
|
224
|
+
}
|
|
225
|
+
return item;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Step 1: Get the parent data to count expected items
|
|
203
229
|
ref.once((parentData) => {
|
|
204
230
|
if (settled) return;
|
|
205
231
|
|
|
@@ -218,81 +244,63 @@ export async function readAll(gun, path) {
|
|
|
218
244
|
return;
|
|
219
245
|
}
|
|
220
246
|
|
|
221
|
-
//
|
|
222
|
-
|
|
247
|
+
// Count expected items and check for inline data
|
|
248
|
+
const referenceKeys = [];
|
|
249
|
+
|
|
223
250
|
for (const key of keys) {
|
|
224
251
|
const rawItem = parentData[key];
|
|
225
252
|
if (!rawItem) continue;
|
|
226
253
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Check if it's a Gun reference (soul)
|
|
254
|
+
// Check if it's a Gun reference (soul) - need to fetch separately
|
|
230
255
|
if (typeof rawItem === 'object' && rawItem['#']) {
|
|
231
|
-
|
|
256
|
+
referenceKeys.push(key);
|
|
232
257
|
continue;
|
|
233
258
|
}
|
|
234
259
|
|
|
235
|
-
// Try to parse
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
item = JSON.parse(rawItem._json);
|
|
239
|
-
} catch (e) {}
|
|
240
|
-
}
|
|
241
|
-
// Try as direct JSON string
|
|
242
|
-
else if (typeof rawItem === 'string') {
|
|
243
|
-
try {
|
|
244
|
-
item = JSON.parse(rawItem);
|
|
245
|
-
} catch (e) {}
|
|
246
|
-
}
|
|
247
|
-
|
|
260
|
+
// Try to parse inline data
|
|
261
|
+
const item = parseItem(rawItem);
|
|
248
262
|
if (item && item.id && !item._deleted) {
|
|
249
263
|
output.set(item.id, item);
|
|
264
|
+
receivedCount++;
|
|
250
265
|
}
|
|
251
266
|
}
|
|
252
267
|
|
|
253
|
-
//
|
|
254
|
-
|
|
268
|
+
// Set expected count: references that need fetching
|
|
269
|
+
expectedCount = referenceKeys.length;
|
|
270
|
+
|
|
271
|
+
// If no references to resolve, we're done
|
|
272
|
+
if (expectedCount === 0) {
|
|
255
273
|
settled = true;
|
|
256
274
|
resolve(Array.from(output.values()));
|
|
257
275
|
return;
|
|
258
276
|
}
|
|
259
277
|
|
|
260
|
-
//
|
|
278
|
+
// Step 2: Use map().once() to resolve references, counting as we go
|
|
261
279
|
ref.map().once((data, key) => {
|
|
262
280
|
if (settled || !data || key === '_') return;
|
|
263
281
|
|
|
264
|
-
|
|
265
|
-
if (data._json && typeof data._json === 'string') {
|
|
266
|
-
try {
|
|
267
|
-
item = JSON.parse(data._json);
|
|
268
|
-
} catch (e) {}
|
|
269
|
-
} else if (typeof data === 'string') {
|
|
270
|
-
try {
|
|
271
|
-
item = JSON.parse(data);
|
|
272
|
-
} catch (e) {}
|
|
273
|
-
}
|
|
274
|
-
|
|
282
|
+
const item = parseItem(data);
|
|
275
283
|
if (item && item.id && !item._deleted) {
|
|
276
|
-
output.
|
|
284
|
+
if (!output.has(item.id)) {
|
|
285
|
+
output.set(item.id, item);
|
|
286
|
+
receivedCount++;
|
|
287
|
+
tryResolve();
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
// Item was null/deleted, still count it as received
|
|
291
|
+
receivedCount++;
|
|
292
|
+
tryResolve();
|
|
277
293
|
}
|
|
278
294
|
});
|
|
279
|
-
|
|
280
|
-
// Give map().once() time to complete
|
|
281
|
-
setTimeout(() => {
|
|
282
|
-
if (!settled) {
|
|
283
|
-
settled = true;
|
|
284
|
-
resolve(Array.from(output.values()));
|
|
285
|
-
}
|
|
286
|
-
}, 500);
|
|
287
295
|
});
|
|
288
296
|
|
|
289
|
-
//
|
|
297
|
+
// Fallback timeout in case count-based resolution fails
|
|
290
298
|
setTimeout(() => {
|
|
291
299
|
if (!settled) {
|
|
292
300
|
settled = true;
|
|
293
301
|
resolve(Array.from(output.values()));
|
|
294
302
|
}
|
|
295
|
-
},
|
|
303
|
+
}, timeout);
|
|
296
304
|
});
|
|
297
305
|
}
|
|
298
306
|
|