holosphere 1.3.0-alpha5 → 1.3.0-alpha7
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/content.js +98 -17
- package/global.js +44 -2
- package/holosphere-bundle.esm.js +83 -16
- package/holosphere-bundle.js +83 -16
- package/holosphere-bundle.min.js +8 -8
- package/holosphere.d.ts +29 -2
- package/holosphere.js +4 -4
- package/package.json +1 -1
package/content.js
CHANGED
|
@@ -13,6 +13,24 @@
|
|
|
13
13
|
*/
|
|
14
14
|
const READ_TIMEOUT_MS = 8000;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Default deadline (ms) for the put-path's Gun ack callback. Gun fires the
|
|
18
|
+
* ack only after the local commit + at least one peer ack chain — with no
|
|
19
|
+
* reachable peer (cold start, offline, partitioned mesh) it never fires
|
|
20
|
+
* and the consumer's `await holosphere.put(...)` hangs forever. UIs
|
|
21
|
+
* worked around this by racing every `put` against their own timeout;
|
|
22
|
+
* owning the deadline here makes every consumer local-first by default.
|
|
23
|
+
*
|
|
24
|
+
* On timeout the returned promise resolves with a `queued: true` sentinel
|
|
25
|
+
* (see `put` return shape) — Gun keeps the write in its local queue and
|
|
26
|
+
* the ack callback's side effects (subscriber notification, hologram
|
|
27
|
+
* cascade, federation propagation) run later when a peer reappears.
|
|
28
|
+
*
|
|
29
|
+
* Pick `WRITE_TIMEOUT_MS = 0` (or pass `{ timeout: 0 }`) to opt out of
|
|
30
|
+
* the fallback and keep the historical "wait for ack" behaviour.
|
|
31
|
+
*/
|
|
32
|
+
const WRITE_TIMEOUT_MS = 5000;
|
|
33
|
+
|
|
16
34
|
/**
|
|
17
35
|
* `.once()` wrapped in a deadline. Resolves with the value when Gun fires
|
|
18
36
|
* back, or `null` after `timeoutMs` if it hasn't. Pass `timeoutMs <= 0` to
|
|
@@ -32,6 +50,28 @@ function onceWithTimeout(node, timeoutMs = READ_TIMEOUT_MS) {
|
|
|
32
50
|
});
|
|
33
51
|
}
|
|
34
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Race a put-result promise against a deadline. If the underlying Gun
|
|
55
|
+
* ack never arrives, resolve with `queuedResult` so the caller stops
|
|
56
|
+
* waiting; Gun keeps the write in its local queue and replays it when
|
|
57
|
+
* a peer is available.
|
|
58
|
+
*
|
|
59
|
+
* The first responder wins — both branches are idempotent so the late
|
|
60
|
+
* ack-driven resolution after timeout is harmlessly ignored.
|
|
61
|
+
*/
|
|
62
|
+
function withWriteTimeout(promise, timeoutMs, queuedResult) {
|
|
63
|
+
if (!timeoutMs || timeoutMs <= 0) return promise;
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
let done = false;
|
|
66
|
+
const finish = (fn, val) => { if (!done) { done = true; fn(val); } };
|
|
67
|
+
promise.then(
|
|
68
|
+
(v) => finish(resolve, v),
|
|
69
|
+
(e) => finish(reject, e)
|
|
70
|
+
);
|
|
71
|
+
setTimeout(() => finish(resolve, queuedResult), timeoutMs);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
35
75
|
/**
|
|
36
76
|
* Recursively sanitizes a value for storage in GunDB.
|
|
37
77
|
*
|
|
@@ -118,7 +158,11 @@ export async function put(holoInstance, holon, lens, data, password = null, opti
|
|
|
118
158
|
throw new Error('put: Missing required holon or lens parameters:', holon, lens);
|
|
119
159
|
}
|
|
120
160
|
|
|
121
|
-
const {
|
|
161
|
+
const {
|
|
162
|
+
disableHologramRedirection = false,
|
|
163
|
+
timeout: writeTimeoutOverride
|
|
164
|
+
} = options;
|
|
165
|
+
const writeTimeoutMs = writeTimeoutOverride !== undefined ? writeTimeoutOverride : WRITE_TIMEOUT_MS;
|
|
122
166
|
|
|
123
167
|
let targetHolon = holon;
|
|
124
168
|
let targetLens = lens;
|
|
@@ -231,7 +275,7 @@ export async function put(holoInstance, holon, lens, data, password = null, opti
|
|
|
231
275
|
});
|
|
232
276
|
}
|
|
233
277
|
|
|
234
|
-
|
|
278
|
+
const ackPromise = new Promise((resolve, reject) => {
|
|
235
279
|
try {
|
|
236
280
|
// Sanitize before serialization so undefined/NaN/Infinity etc.
|
|
237
281
|
// can never produce a malformed payload like `"initiated":,`
|
|
@@ -290,22 +334,38 @@ export async function put(holoInstance, holon, lens, data, password = null, opti
|
|
|
290
334
|
}
|
|
291
335
|
// --- End: Hologram Tracking Logic ---
|
|
292
336
|
|
|
293
|
-
// --- Start: Active Hologram Update Logic
|
|
337
|
+
// --- Start: Active Hologram Update Logic ---
|
|
338
|
+
//
|
|
339
|
+
// Walks this node's `_holograms` set and stamps every
|
|
340
|
+
// registered hologram with `updated: now` so consumers
|
|
341
|
+
// re-resolve and see the latest source data.
|
|
342
|
+
//
|
|
343
|
+
// Runs for BOTH original-data puts and hologram-update
|
|
344
|
+
// puts so updates cascade through multi-hop forwards
|
|
345
|
+
// (A → B → C → …) even when the second hop's
|
|
346
|
+
// cross-holon registration on the original source
|
|
347
|
+
// can't be relied on to make it across the Gun mesh.
|
|
348
|
+
// Each hop maintains its own local `_holograms` set
|
|
349
|
+
// tracking the next hops, and we walk that set on
|
|
350
|
+
// every put — cycle-protected via
|
|
351
|
+
// `options._cascadeVisited`.
|
|
294
352
|
let updatedHolograms = [];
|
|
295
|
-
|
|
353
|
+
const currentDataSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
|
|
354
|
+
const cascadeVisited = new Set(options._cascadeVisited || []);
|
|
355
|
+
if (!cascadeVisited.has(currentDataSoul)) {
|
|
356
|
+
cascadeVisited.add(currentDataSoul);
|
|
296
357
|
try {
|
|
297
|
-
const currentDataSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
|
|
298
358
|
const currentNodeRef = holoInstance.getNodeRef(currentDataSoul);
|
|
299
|
-
|
|
300
|
-
// Get the _holograms set for this
|
|
359
|
+
|
|
360
|
+
// Get the _holograms set for this node
|
|
301
361
|
await new Promise((resolveHologramUpdate) => {
|
|
302
362
|
currentNodeRef.get('_holograms').once(async (hologramsSet) => {
|
|
303
363
|
if (hologramsSet) {
|
|
304
|
-
const hologramSouls = Object.keys(hologramsSet).filter(k =>
|
|
305
|
-
k !== '_' && hologramsSet[k] === true
|
|
364
|
+
const hologramSouls = Object.keys(hologramsSet).filter(k =>
|
|
365
|
+
k !== '_' && hologramsSet[k] === true && !cascadeVisited.has(k)
|
|
306
366
|
);
|
|
307
|
-
|
|
308
|
-
|
|
367
|
+
|
|
368
|
+
if (hologramSouls.length > 0) {
|
|
309
369
|
// Update each active hologram with an 'updated' timestamp
|
|
310
370
|
const updatePromises = hologramSouls.map(async (hologramSoul) => {
|
|
311
371
|
try {
|
|
@@ -319,26 +379,31 @@ export async function put(holoInstance, holon, lens, data, password = null, opti
|
|
|
319
379
|
null,
|
|
320
380
|
{ resolveHolograms: false }
|
|
321
381
|
);
|
|
322
|
-
|
|
382
|
+
|
|
323
383
|
if (currentHologram) {
|
|
324
384
|
// Update the hologram with an 'updated' timestamp
|
|
325
385
|
const updatedHologram = {
|
|
326
386
|
...currentHologram,
|
|
327
387
|
updated: Date.now()
|
|
328
388
|
};
|
|
329
|
-
|
|
389
|
+
|
|
330
390
|
await holoInstance.put(
|
|
331
391
|
hologramSoulInfo.holon,
|
|
332
392
|
hologramSoulInfo.lens,
|
|
333
393
|
updatedHologram,
|
|
334
394
|
null,
|
|
335
|
-
{
|
|
395
|
+
{
|
|
336
396
|
autoPropagate: false, // Don't auto-propagate hologram updates
|
|
337
397
|
disableHologramRedirection: true, // Prevent redirection when updating holograms
|
|
338
|
-
isHologramUpdate: true
|
|
398
|
+
isHologramUpdate: true,
|
|
399
|
+
// Carry the visited set forward so the
|
|
400
|
+
// recursive put keeps cascading through
|
|
401
|
+
// this hop's `_holograms` set without
|
|
402
|
+
// looping back through us.
|
|
403
|
+
_cascadeVisited: cascadeVisited
|
|
339
404
|
}
|
|
340
405
|
);
|
|
341
|
-
|
|
406
|
+
|
|
342
407
|
// Add to the list of updated holograms
|
|
343
408
|
updatedHolograms.push({
|
|
344
409
|
soul: hologramSoul,
|
|
@@ -354,7 +419,7 @@ export async function put(holoInstance, holon, lens, data, password = null, opti
|
|
|
354
419
|
console.warn(`Error updating hologram ${hologramSoul}:`, hologramUpdateError);
|
|
355
420
|
}
|
|
356
421
|
});
|
|
357
|
-
|
|
422
|
+
|
|
358
423
|
await Promise.all(updatePromises);
|
|
359
424
|
}
|
|
360
425
|
}
|
|
@@ -424,6 +489,22 @@ export async function put(holoInstance, holon, lens, data, password = null, opti
|
|
|
424
489
|
reject(error);
|
|
425
490
|
}
|
|
426
491
|
});
|
|
492
|
+
|
|
493
|
+
// Bound the wait on Gun's put ack so an offline/partitioned mesh
|
|
494
|
+
// doesn't hang the caller forever. Gun keeps the local write and
|
|
495
|
+
// its eventual ack callback (subscriber notify, hologram cascade,
|
|
496
|
+
// propagation) still runs when a peer reappears — we just stop
|
|
497
|
+
// blocking the awaiting consumer in the meantime.
|
|
498
|
+
return withWriteTimeout(ackPromise, writeTimeoutMs, {
|
|
499
|
+
success: true,
|
|
500
|
+
queued: true,
|
|
501
|
+
isHologramAtPath: isHologram,
|
|
502
|
+
pathHolon: targetHolon,
|
|
503
|
+
pathLens: targetLens,
|
|
504
|
+
pathKey: targetKey,
|
|
505
|
+
propagationResult: null,
|
|
506
|
+
updatedHolograms: []
|
|
507
|
+
});
|
|
427
508
|
} catch (error) {
|
|
428
509
|
console.error('Error in put:', error);
|
|
429
510
|
throw error;
|
package/global.js
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
// holo_global.js
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Default deadline (ms) for the put-path's Gun ack callback. See
|
|
5
|
+
* `WRITE_TIMEOUT_MS` in content.js for the rationale — same hang, same
|
|
6
|
+
* fix, kept local here so global.js doesn't depend on content.js's
|
|
7
|
+
* internals.
|
|
8
|
+
*/
|
|
9
|
+
const WRITE_TIMEOUT_MS = 5000;
|
|
10
|
+
|
|
11
|
+
function withWriteTimeout(promise, timeoutMs, queuedResult) {
|
|
12
|
+
if (!timeoutMs || timeoutMs <= 0) return promise;
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
let done = false;
|
|
15
|
+
const finish = (fn, val) => { if (!done) { done = true; fn(val); } };
|
|
16
|
+
promise.then(
|
|
17
|
+
(v) => finish(resolve, v),
|
|
18
|
+
(e) => finish(reject, e)
|
|
19
|
+
);
|
|
20
|
+
setTimeout(() => finish(resolve, queuedResult), timeoutMs);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
3
24
|
/**
|
|
4
25
|
* Stores data in a global (non-holon-specific) table.
|
|
5
26
|
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
6
27
|
* @param {string} tableName - The table name to store data in.
|
|
7
28
|
* @param {object} data - The data to store. If it has an 'id' field, it will be used as the key.
|
|
8
29
|
* @param {string} [password] - Optional password for private holon.
|
|
30
|
+
* @param {object} [options] - Additional options
|
|
31
|
+
* @param {number} [options.timeout=5000] - Ack deadline in ms; resolves anyway after this so an offline mesh can't hang the caller. Pass `0` to disable.
|
|
9
32
|
* @returns {Promise<void>}
|
|
10
33
|
*/
|
|
11
|
-
export async function putGlobal(holoInstance, tableName, data, password = null) {
|
|
34
|
+
export async function putGlobal(holoInstance, tableName, data, password = null, options = {}) {
|
|
12
35
|
try {
|
|
13
36
|
if (!tableName || !data) {
|
|
14
37
|
throw new Error('Table name and data are required');
|
|
@@ -63,7 +86,9 @@ export async function putGlobal(holoInstance, tableName, data, password = null)
|
|
|
63
86
|
});
|
|
64
87
|
}
|
|
65
88
|
|
|
66
|
-
|
|
89
|
+
const writeTimeoutMs = options.timeout !== undefined ? options.timeout : WRITE_TIMEOUT_MS;
|
|
90
|
+
|
|
91
|
+
const ackPromise = new Promise((resolve, reject) => {
|
|
67
92
|
try {
|
|
68
93
|
// Create a copy of data, stripping read-side envelopes that
|
|
69
94
|
// must never be persisted (they're attached at resolution time).
|
|
@@ -143,6 +168,23 @@ export async function putGlobal(holoInstance, tableName, data, password = null)
|
|
|
143
168
|
reject(error);
|
|
144
169
|
}
|
|
145
170
|
});
|
|
171
|
+
|
|
172
|
+
// Bound the wait on Gun's put ack so an offline mesh doesn't hang
|
|
173
|
+
// the caller forever. Gun keeps the write locally and replays it
|
|
174
|
+
// when a peer reappears. We tag the ack-arrived branch with a
|
|
175
|
+
// unique sentinel so we can warn (once) when the timeout fired
|
|
176
|
+
// and still keep the public Promise<void> contract.
|
|
177
|
+
const ACK_OK = Symbol('ackOk');
|
|
178
|
+
return withWriteTimeout(
|
|
179
|
+
ackPromise.then(() => ACK_OK),
|
|
180
|
+
writeTimeoutMs,
|
|
181
|
+
undefined
|
|
182
|
+
).then((result) => {
|
|
183
|
+
if (result !== ACK_OK) {
|
|
184
|
+
console.warn(`putGlobal: no ack within ${writeTimeoutMs}ms for table=${tableName} — write queued locally, will replay on reconnect`);
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
});
|
|
146
188
|
} catch (error) {
|
|
147
189
|
console.error('Error in putGlobal:', error);
|
|
148
190
|
throw error;
|
package/holosphere-bundle.esm.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HoloSphere ESM Bundle v1.3.0-
|
|
2
|
+
* HoloSphere ESM Bundle v1.3.0-alpha7
|
|
3
3
|
* ES6 Module version with all dependencies bundled
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* import HoloSphere from 'https://unpkg.com/holosphere@1.3.0-
|
|
6
|
+
* import HoloSphere from 'https://unpkg.com/holosphere@1.3.0-alpha7/holosphere-bundle.esm.js';
|
|
7
7
|
* const hs = new HoloSphere('myapp');
|
|
8
8
|
*/
|
|
9
9
|
var __create = Object.create;
|
|
@@ -25365,6 +25365,7 @@ function clearSchemaCache(holoInstance, lens = null) {
|
|
|
25365
25365
|
|
|
25366
25366
|
// content.js
|
|
25367
25367
|
var READ_TIMEOUT_MS = 8e3;
|
|
25368
|
+
var WRITE_TIMEOUT_MS = 5e3;
|
|
25368
25369
|
function onceWithTimeout(node, timeoutMs = READ_TIMEOUT_MS) {
|
|
25369
25370
|
return new Promise((resolve) => {
|
|
25370
25371
|
let done = false;
|
|
@@ -25380,6 +25381,23 @@ function onceWithTimeout(node, timeoutMs = READ_TIMEOUT_MS) {
|
|
|
25380
25381
|
}
|
|
25381
25382
|
});
|
|
25382
25383
|
}
|
|
25384
|
+
function withWriteTimeout(promise, timeoutMs, queuedResult) {
|
|
25385
|
+
if (!timeoutMs || timeoutMs <= 0) return promise;
|
|
25386
|
+
return new Promise((resolve, reject) => {
|
|
25387
|
+
let done = false;
|
|
25388
|
+
const finish = (fn, val) => {
|
|
25389
|
+
if (!done) {
|
|
25390
|
+
done = true;
|
|
25391
|
+
fn(val);
|
|
25392
|
+
}
|
|
25393
|
+
};
|
|
25394
|
+
promise.then(
|
|
25395
|
+
(v) => finish(resolve, v),
|
|
25396
|
+
(e) => finish(reject, e)
|
|
25397
|
+
);
|
|
25398
|
+
setTimeout(() => finish(resolve, queuedResult), timeoutMs);
|
|
25399
|
+
});
|
|
25400
|
+
}
|
|
25383
25401
|
function sanitizeForStorage(value, path = "", seen = /* @__PURE__ */ new WeakSet(), warnings = []) {
|
|
25384
25402
|
if (value === null) return null;
|
|
25385
25403
|
const t = typeof value;
|
|
@@ -25426,7 +25444,11 @@ async function put(holoInstance, holon, lens, data, password = null, options = {
|
|
|
25426
25444
|
if (!holon || !lens) {
|
|
25427
25445
|
throw new Error("put: Missing required holon or lens parameters:", holon, lens);
|
|
25428
25446
|
}
|
|
25429
|
-
const {
|
|
25447
|
+
const {
|
|
25448
|
+
disableHologramRedirection = false,
|
|
25449
|
+
timeout: writeTimeoutOverride
|
|
25450
|
+
} = options;
|
|
25451
|
+
const writeTimeoutMs = writeTimeoutOverride !== void 0 ? writeTimeoutOverride : WRITE_TIMEOUT_MS;
|
|
25430
25452
|
let targetHolon = holon;
|
|
25431
25453
|
let targetLens = lens;
|
|
25432
25454
|
let targetKey = data.id;
|
|
@@ -25514,7 +25536,7 @@ async function put(holoInstance, holon, lens, data, password = null, options = {
|
|
|
25514
25536
|
});
|
|
25515
25537
|
});
|
|
25516
25538
|
}
|
|
25517
|
-
|
|
25539
|
+
const ackPromise = new Promise((resolve, reject) => {
|
|
25518
25540
|
try {
|
|
25519
25541
|
const sanitizeWarnings = [];
|
|
25520
25542
|
let dataToStore = sanitizeForStorage(data, "", /* @__PURE__ */ new WeakSet(), sanitizeWarnings) || {};
|
|
@@ -25547,16 +25569,17 @@ async function put(holoInstance, holon, lens, data, password = null, options = {
|
|
|
25547
25569
|
}
|
|
25548
25570
|
}
|
|
25549
25571
|
let updatedHolograms = [];
|
|
25550
|
-
|
|
25572
|
+
const currentDataSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
|
|
25573
|
+
const cascadeVisited = new Set(options._cascadeVisited || []);
|
|
25574
|
+
if (!cascadeVisited.has(currentDataSoul)) {
|
|
25575
|
+
cascadeVisited.add(currentDataSoul);
|
|
25551
25576
|
try {
|
|
25552
|
-
const currentDataSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
|
|
25553
25577
|
const currentNodeRef = holoInstance.getNodeRef(currentDataSoul);
|
|
25554
25578
|
await new Promise((resolveHologramUpdate) => {
|
|
25555
25579
|
currentNodeRef.get("_holograms").once(async (hologramsSet) => {
|
|
25556
25580
|
if (hologramsSet) {
|
|
25557
25581
|
const hologramSouls = Object.keys(hologramsSet).filter(
|
|
25558
|
-
(k) => k !== "_" && hologramsSet[k] === true
|
|
25559
|
-
// Only active holograms (deleted ones are null/removed)
|
|
25582
|
+
(k) => k !== "_" && hologramsSet[k] === true && !cascadeVisited.has(k)
|
|
25560
25583
|
);
|
|
25561
25584
|
if (hologramSouls.length > 0) {
|
|
25562
25585
|
const updatePromises = hologramSouls.map(async (hologramSoul) => {
|
|
@@ -25585,8 +25608,12 @@ async function put(holoInstance, holon, lens, data, password = null, options = {
|
|
|
25585
25608
|
// Don't auto-propagate hologram updates
|
|
25586
25609
|
disableHologramRedirection: true,
|
|
25587
25610
|
// Prevent redirection when updating holograms
|
|
25588
|
-
isHologramUpdate: true
|
|
25589
|
-
//
|
|
25611
|
+
isHologramUpdate: true,
|
|
25612
|
+
// Carry the visited set forward so the
|
|
25613
|
+
// recursive put keeps cascading through
|
|
25614
|
+
// this hop's `_holograms` set without
|
|
25615
|
+
// looping back through us.
|
|
25616
|
+
_cascadeVisited: cascadeVisited
|
|
25590
25617
|
}
|
|
25591
25618
|
);
|
|
25592
25619
|
updatedHolograms.push({
|
|
@@ -25664,6 +25691,16 @@ async function put(holoInstance, holon, lens, data, password = null, options = {
|
|
|
25664
25691
|
reject(error);
|
|
25665
25692
|
}
|
|
25666
25693
|
});
|
|
25694
|
+
return withWriteTimeout(ackPromise, writeTimeoutMs, {
|
|
25695
|
+
success: true,
|
|
25696
|
+
queued: true,
|
|
25697
|
+
isHologramAtPath: isHologram2,
|
|
25698
|
+
pathHolon: targetHolon,
|
|
25699
|
+
pathLens: targetLens,
|
|
25700
|
+
pathKey: targetKey,
|
|
25701
|
+
propagationResult: null,
|
|
25702
|
+
updatedHolograms: []
|
|
25703
|
+
});
|
|
25667
25704
|
} catch (error) {
|
|
25668
25705
|
console.error("Error in put:", error);
|
|
25669
25706
|
throw error;
|
|
@@ -26336,7 +26373,25 @@ async function deleteNode(holoInstance, holon, lens, key) {
|
|
|
26336
26373
|
}
|
|
26337
26374
|
|
|
26338
26375
|
// global.js
|
|
26339
|
-
|
|
26376
|
+
var WRITE_TIMEOUT_MS2 = 5e3;
|
|
26377
|
+
function withWriteTimeout2(promise, timeoutMs, queuedResult) {
|
|
26378
|
+
if (!timeoutMs || timeoutMs <= 0) return promise;
|
|
26379
|
+
return new Promise((resolve, reject) => {
|
|
26380
|
+
let done = false;
|
|
26381
|
+
const finish = (fn, val) => {
|
|
26382
|
+
if (!done) {
|
|
26383
|
+
done = true;
|
|
26384
|
+
fn(val);
|
|
26385
|
+
}
|
|
26386
|
+
};
|
|
26387
|
+
promise.then(
|
|
26388
|
+
(v) => finish(resolve, v),
|
|
26389
|
+
(e) => finish(reject, e)
|
|
26390
|
+
);
|
|
26391
|
+
setTimeout(() => finish(resolve, queuedResult), timeoutMs);
|
|
26392
|
+
});
|
|
26393
|
+
}
|
|
26394
|
+
async function putGlobal(holoInstance, tableName, data, password = null, options = {}) {
|
|
26340
26395
|
try {
|
|
26341
26396
|
if (!tableName || !data) {
|
|
26342
26397
|
throw new Error("Table name and data are required");
|
|
@@ -26383,7 +26438,8 @@ async function putGlobal(holoInstance, tableName, data, password = null) {
|
|
|
26383
26438
|
});
|
|
26384
26439
|
});
|
|
26385
26440
|
}
|
|
26386
|
-
|
|
26441
|
+
const writeTimeoutMs = options.timeout !== void 0 ? options.timeout : WRITE_TIMEOUT_MS2;
|
|
26442
|
+
const ackPromise = new Promise((resolve, reject) => {
|
|
26387
26443
|
try {
|
|
26388
26444
|
let dataToStore = { ...data };
|
|
26389
26445
|
if (dataToStore._meta !== void 0) {
|
|
@@ -26445,6 +26501,17 @@ async function putGlobal(holoInstance, tableName, data, password = null) {
|
|
|
26445
26501
|
reject(error);
|
|
26446
26502
|
}
|
|
26447
26503
|
});
|
|
26504
|
+
const ACK_OK = Symbol("ackOk");
|
|
26505
|
+
return withWriteTimeout2(
|
|
26506
|
+
ackPromise.then(() => ACK_OK),
|
|
26507
|
+
writeTimeoutMs,
|
|
26508
|
+
void 0
|
|
26509
|
+
).then((result) => {
|
|
26510
|
+
if (result !== ACK_OK) {
|
|
26511
|
+
console.warn(`putGlobal: no ack within ${writeTimeoutMs}ms for table=${tableName} \u2014 write queued locally, will replay on reconnect`);
|
|
26512
|
+
}
|
|
26513
|
+
return void 0;
|
|
26514
|
+
});
|
|
26448
26515
|
} catch (error) {
|
|
26449
26516
|
console.error("Error in putGlobal:", error);
|
|
26450
26517
|
throw error;
|
|
@@ -30528,14 +30595,14 @@ var HoloSphere = class {
|
|
|
30528
30595
|
return deleteNode(this, holon, lens, key);
|
|
30529
30596
|
}
|
|
30530
30597
|
// ================================ GLOBAL FUNCTIONS ================================
|
|
30531
|
-
async putGlobal(tableName, data, password = null) {
|
|
30532
|
-
return putGlobal(this, tableName, data, password);
|
|
30598
|
+
async putGlobal(tableName, data, password = null, options = {}) {
|
|
30599
|
+
return putGlobal(this, tableName, data, password, options);
|
|
30533
30600
|
}
|
|
30534
30601
|
/**
|
|
30535
30602
|
* v2-compatible alias for putGlobal (no password param)
|
|
30536
30603
|
*/
|
|
30537
|
-
async writeGlobal(tableName, data) {
|
|
30538
|
-
return putGlobal(this, tableName, data, null);
|
|
30604
|
+
async writeGlobal(tableName, data, options = {}) {
|
|
30605
|
+
return putGlobal(this, tableName, data, null, options);
|
|
30539
30606
|
}
|
|
30540
30607
|
async getGlobal(tableName, key, password = null) {
|
|
30541
30608
|
return getGlobal(this, tableName, key, password);
|
package/holosphere-bundle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HoloSphere Bundle v1.3.0-
|
|
2
|
+
* HoloSphere Bundle v1.3.0-alpha7
|
|
3
3
|
* Holonic Geospatial Communication Infrastructure
|
|
4
4
|
*
|
|
5
5
|
* Includes:
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Ajv (JSON schema validation)
|
|
10
10
|
*
|
|
11
11
|
* Usage:
|
|
12
|
-
* <script src="https://unpkg.com/holosphere@1.3.0-
|
|
12
|
+
* <script src="https://unpkg.com/holosphere@1.3.0-alpha7/holosphere-bundle.js"></script>
|
|
13
13
|
* <script>
|
|
14
14
|
* const hs = new HoloSphere('myapp');
|
|
15
15
|
* </script>
|
|
@@ -25391,6 +25391,7 @@ var HoloSphere = (() => {
|
|
|
25391
25391
|
|
|
25392
25392
|
// content.js
|
|
25393
25393
|
var READ_TIMEOUT_MS = 8e3;
|
|
25394
|
+
var WRITE_TIMEOUT_MS = 5e3;
|
|
25394
25395
|
function onceWithTimeout(node, timeoutMs = READ_TIMEOUT_MS) {
|
|
25395
25396
|
return new Promise((resolve) => {
|
|
25396
25397
|
let done = false;
|
|
@@ -25406,6 +25407,23 @@ var HoloSphere = (() => {
|
|
|
25406
25407
|
}
|
|
25407
25408
|
});
|
|
25408
25409
|
}
|
|
25410
|
+
function withWriteTimeout(promise, timeoutMs, queuedResult) {
|
|
25411
|
+
if (!timeoutMs || timeoutMs <= 0) return promise;
|
|
25412
|
+
return new Promise((resolve, reject) => {
|
|
25413
|
+
let done = false;
|
|
25414
|
+
const finish = (fn, val) => {
|
|
25415
|
+
if (!done) {
|
|
25416
|
+
done = true;
|
|
25417
|
+
fn(val);
|
|
25418
|
+
}
|
|
25419
|
+
};
|
|
25420
|
+
promise.then(
|
|
25421
|
+
(v) => finish(resolve, v),
|
|
25422
|
+
(e) => finish(reject, e)
|
|
25423
|
+
);
|
|
25424
|
+
setTimeout(() => finish(resolve, queuedResult), timeoutMs);
|
|
25425
|
+
});
|
|
25426
|
+
}
|
|
25409
25427
|
function sanitizeForStorage(value, path = "", seen = /* @__PURE__ */ new WeakSet(), warnings = []) {
|
|
25410
25428
|
if (value === null) return null;
|
|
25411
25429
|
const t = typeof value;
|
|
@@ -25452,7 +25470,11 @@ var HoloSphere = (() => {
|
|
|
25452
25470
|
if (!holon || !lens) {
|
|
25453
25471
|
throw new Error("put: Missing required holon or lens parameters:", holon, lens);
|
|
25454
25472
|
}
|
|
25455
|
-
const {
|
|
25473
|
+
const {
|
|
25474
|
+
disableHologramRedirection = false,
|
|
25475
|
+
timeout: writeTimeoutOverride
|
|
25476
|
+
} = options;
|
|
25477
|
+
const writeTimeoutMs = writeTimeoutOverride !== void 0 ? writeTimeoutOverride : WRITE_TIMEOUT_MS;
|
|
25456
25478
|
let targetHolon = holon;
|
|
25457
25479
|
let targetLens = lens;
|
|
25458
25480
|
let targetKey = data.id;
|
|
@@ -25540,7 +25562,7 @@ var HoloSphere = (() => {
|
|
|
25540
25562
|
});
|
|
25541
25563
|
});
|
|
25542
25564
|
}
|
|
25543
|
-
|
|
25565
|
+
const ackPromise = new Promise((resolve, reject) => {
|
|
25544
25566
|
try {
|
|
25545
25567
|
const sanitizeWarnings = [];
|
|
25546
25568
|
let dataToStore = sanitizeForStorage(data, "", /* @__PURE__ */ new WeakSet(), sanitizeWarnings) || {};
|
|
@@ -25573,16 +25595,17 @@ var HoloSphere = (() => {
|
|
|
25573
25595
|
}
|
|
25574
25596
|
}
|
|
25575
25597
|
let updatedHolograms = [];
|
|
25576
|
-
|
|
25598
|
+
const currentDataSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
|
|
25599
|
+
const cascadeVisited = new Set(options._cascadeVisited || []);
|
|
25600
|
+
if (!cascadeVisited.has(currentDataSoul)) {
|
|
25601
|
+
cascadeVisited.add(currentDataSoul);
|
|
25577
25602
|
try {
|
|
25578
|
-
const currentDataSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
|
|
25579
25603
|
const currentNodeRef = holoInstance.getNodeRef(currentDataSoul);
|
|
25580
25604
|
await new Promise((resolveHologramUpdate) => {
|
|
25581
25605
|
currentNodeRef.get("_holograms").once(async (hologramsSet) => {
|
|
25582
25606
|
if (hologramsSet) {
|
|
25583
25607
|
const hologramSouls = Object.keys(hologramsSet).filter(
|
|
25584
|
-
(k) => k !== "_" && hologramsSet[k] === true
|
|
25585
|
-
// Only active holograms (deleted ones are null/removed)
|
|
25608
|
+
(k) => k !== "_" && hologramsSet[k] === true && !cascadeVisited.has(k)
|
|
25586
25609
|
);
|
|
25587
25610
|
if (hologramSouls.length > 0) {
|
|
25588
25611
|
const updatePromises = hologramSouls.map(async (hologramSoul) => {
|
|
@@ -25611,8 +25634,12 @@ var HoloSphere = (() => {
|
|
|
25611
25634
|
// Don't auto-propagate hologram updates
|
|
25612
25635
|
disableHologramRedirection: true,
|
|
25613
25636
|
// Prevent redirection when updating holograms
|
|
25614
|
-
isHologramUpdate: true
|
|
25615
|
-
//
|
|
25637
|
+
isHologramUpdate: true,
|
|
25638
|
+
// Carry the visited set forward so the
|
|
25639
|
+
// recursive put keeps cascading through
|
|
25640
|
+
// this hop's `_holograms` set without
|
|
25641
|
+
// looping back through us.
|
|
25642
|
+
_cascadeVisited: cascadeVisited
|
|
25616
25643
|
}
|
|
25617
25644
|
);
|
|
25618
25645
|
updatedHolograms.push({
|
|
@@ -25690,6 +25717,16 @@ var HoloSphere = (() => {
|
|
|
25690
25717
|
reject(error);
|
|
25691
25718
|
}
|
|
25692
25719
|
});
|
|
25720
|
+
return withWriteTimeout(ackPromise, writeTimeoutMs, {
|
|
25721
|
+
success: true,
|
|
25722
|
+
queued: true,
|
|
25723
|
+
isHologramAtPath: isHologram2,
|
|
25724
|
+
pathHolon: targetHolon,
|
|
25725
|
+
pathLens: targetLens,
|
|
25726
|
+
pathKey: targetKey,
|
|
25727
|
+
propagationResult: null,
|
|
25728
|
+
updatedHolograms: []
|
|
25729
|
+
});
|
|
25693
25730
|
} catch (error) {
|
|
25694
25731
|
console.error("Error in put:", error);
|
|
25695
25732
|
throw error;
|
|
@@ -26362,7 +26399,25 @@ var HoloSphere = (() => {
|
|
|
26362
26399
|
}
|
|
26363
26400
|
|
|
26364
26401
|
// global.js
|
|
26365
|
-
|
|
26402
|
+
var WRITE_TIMEOUT_MS2 = 5e3;
|
|
26403
|
+
function withWriteTimeout2(promise, timeoutMs, queuedResult) {
|
|
26404
|
+
if (!timeoutMs || timeoutMs <= 0) return promise;
|
|
26405
|
+
return new Promise((resolve, reject) => {
|
|
26406
|
+
let done = false;
|
|
26407
|
+
const finish = (fn, val) => {
|
|
26408
|
+
if (!done) {
|
|
26409
|
+
done = true;
|
|
26410
|
+
fn(val);
|
|
26411
|
+
}
|
|
26412
|
+
};
|
|
26413
|
+
promise.then(
|
|
26414
|
+
(v) => finish(resolve, v),
|
|
26415
|
+
(e) => finish(reject, e)
|
|
26416
|
+
);
|
|
26417
|
+
setTimeout(() => finish(resolve, queuedResult), timeoutMs);
|
|
26418
|
+
});
|
|
26419
|
+
}
|
|
26420
|
+
async function putGlobal(holoInstance, tableName, data, password = null, options = {}) {
|
|
26366
26421
|
try {
|
|
26367
26422
|
if (!tableName || !data) {
|
|
26368
26423
|
throw new Error("Table name and data are required");
|
|
@@ -26409,7 +26464,8 @@ var HoloSphere = (() => {
|
|
|
26409
26464
|
});
|
|
26410
26465
|
});
|
|
26411
26466
|
}
|
|
26412
|
-
|
|
26467
|
+
const writeTimeoutMs = options.timeout !== void 0 ? options.timeout : WRITE_TIMEOUT_MS2;
|
|
26468
|
+
const ackPromise = new Promise((resolve, reject) => {
|
|
26413
26469
|
try {
|
|
26414
26470
|
let dataToStore = { ...data };
|
|
26415
26471
|
if (dataToStore._meta !== void 0) {
|
|
@@ -26471,6 +26527,17 @@ var HoloSphere = (() => {
|
|
|
26471
26527
|
reject(error);
|
|
26472
26528
|
}
|
|
26473
26529
|
});
|
|
26530
|
+
const ACK_OK = Symbol("ackOk");
|
|
26531
|
+
return withWriteTimeout2(
|
|
26532
|
+
ackPromise.then(() => ACK_OK),
|
|
26533
|
+
writeTimeoutMs,
|
|
26534
|
+
void 0
|
|
26535
|
+
).then((result) => {
|
|
26536
|
+
if (result !== ACK_OK) {
|
|
26537
|
+
console.warn(`putGlobal: no ack within ${writeTimeoutMs}ms for table=${tableName} \u2014 write queued locally, will replay on reconnect`);
|
|
26538
|
+
}
|
|
26539
|
+
return void 0;
|
|
26540
|
+
});
|
|
26474
26541
|
} catch (error) {
|
|
26475
26542
|
console.error("Error in putGlobal:", error);
|
|
26476
26543
|
throw error;
|
|
@@ -30554,14 +30621,14 @@ var HoloSphere = (() => {
|
|
|
30554
30621
|
return deleteNode(this, holon, lens, key);
|
|
30555
30622
|
}
|
|
30556
30623
|
// ================================ GLOBAL FUNCTIONS ================================
|
|
30557
|
-
async putGlobal(tableName, data, password = null) {
|
|
30558
|
-
return putGlobal(this, tableName, data, password);
|
|
30624
|
+
async putGlobal(tableName, data, password = null, options = {}) {
|
|
30625
|
+
return putGlobal(this, tableName, data, password, options);
|
|
30559
30626
|
}
|
|
30560
30627
|
/**
|
|
30561
30628
|
* v2-compatible alias for putGlobal (no password param)
|
|
30562
30629
|
*/
|
|
30563
|
-
async writeGlobal(tableName, data) {
|
|
30564
|
-
return putGlobal(this, tableName, data, null);
|
|
30630
|
+
async writeGlobal(tableName, data, options = {}) {
|
|
30631
|
+
return putGlobal(this, tableName, data, null, options);
|
|
30565
30632
|
}
|
|
30566
30633
|
async getGlobal(tableName, key, password = null) {
|
|
30567
30634
|
return getGlobal(this, tableName, key, password);
|