holosphere 1.3.0-alpha3 → 1.3.0-alpha4
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 +73 -8
- package/federation.js +120 -16
- package/holosphere-bundle.esm.js +122 -16
- package/holosphere-bundle.js +122 -16
- package/holosphere-bundle.min.js +8 -8
- package/package.json +1 -1
package/content.js
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
// holo_content.js
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Recursively sanitizes a value for storage in GunDB.
|
|
5
|
+
*
|
|
6
|
+
* Drops keys whose values would corrupt the graph or round-trip incorrectly:
|
|
7
|
+
* - undefined, NaN, Infinity, -Infinity (JSON.stringify silently turns
|
|
8
|
+
* these into nothing or "null", which is how malformed payloads like
|
|
9
|
+
* `"initiated":,` end up in the graph and surface later as per-character
|
|
10
|
+
* parse warnings).
|
|
11
|
+
* - functions, symbols, bigints (not JSON-representable).
|
|
12
|
+
* Preserves null (legitimate Gun tombstone / explicit empty value).
|
|
13
|
+
* Guards against circular references.
|
|
14
|
+
*
|
|
15
|
+
* Logs one warning per dropped path so the caller can fix the producer.
|
|
16
|
+
*/
|
|
17
|
+
function sanitizeForStorage(value, path = '', seen = new WeakSet(), warnings = []) {
|
|
18
|
+
if (value === null) return null;
|
|
19
|
+
const t = typeof value;
|
|
20
|
+
|
|
21
|
+
if (t === 'number') {
|
|
22
|
+
if (!Number.isFinite(value)) {
|
|
23
|
+
warnings.push(`${path || '<root>'}: ${value} (non-finite number)`);
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
if (t === 'string' || t === 'boolean') return value;
|
|
29
|
+
if (t === 'undefined' || t === 'function' || t === 'symbol' || t === 'bigint') {
|
|
30
|
+
warnings.push(`${path || '<root>'}: ${t}`);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (t === 'object') {
|
|
35
|
+
if (seen.has(value)) {
|
|
36
|
+
warnings.push(`${path || '<root>'}: circular reference`);
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
seen.add(value);
|
|
40
|
+
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
const out = [];
|
|
43
|
+
for (let i = 0; i < value.length; i++) {
|
|
44
|
+
const cleaned = sanitizeForStorage(value[i], `${path}[${i}]`, seen, warnings);
|
|
45
|
+
out.push(cleaned === undefined ? null : cleaned);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const out = {};
|
|
51
|
+
for (const k of Object.keys(value)) {
|
|
52
|
+
const cleaned = sanitizeForStorage(value[k], path ? `${path}.${k}` : k, seen, warnings);
|
|
53
|
+
if (cleaned !== undefined) out[k] = cleaned;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
warnings.push(`${path || '<root>'}: unsupported type ${t}`);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
3
62
|
/**
|
|
4
63
|
* Stores content in the specified holon and lens.
|
|
5
64
|
* If the target path already contains a hologram, the put operation will be
|
|
@@ -142,15 +201,21 @@ export async function put(holoInstance, holon, lens, data, password = null, opti
|
|
|
142
201
|
|
|
143
202
|
return new Promise((resolve, reject) => {
|
|
144
203
|
try {
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
204
|
+
// Sanitize before serialization so undefined/NaN/Infinity etc.
|
|
205
|
+
// can never produce a malformed payload like `"initiated":,`
|
|
206
|
+
// (which is what causes per-character parse warnings on read).
|
|
207
|
+
const sanitizeWarnings = [];
|
|
208
|
+
let dataToStore = sanitizeForStorage(data, '', new WeakSet(), sanitizeWarnings) || {};
|
|
209
|
+
if (sanitizeWarnings.length > 0) {
|
|
210
|
+
console.warn(
|
|
211
|
+
`holosphere.put: sanitized ${sanitizeWarnings.length} field(s) at ${targetHolon}/${targetLens}/${targetKey} (id=${data.id}):`,
|
|
212
|
+
sanitizeWarnings
|
|
213
|
+
);
|
|
153
214
|
}
|
|
215
|
+
// Strip read-side envelopes that must never be persisted
|
|
216
|
+
// (they're attached at resolution time).
|
|
217
|
+
if (dataToStore._meta !== undefined) delete dataToStore._meta;
|
|
218
|
+
if (dataToStore._hologram !== undefined) delete dataToStore._hologram;
|
|
154
219
|
const payload = JSON.stringify(dataToStore); // The data being stored
|
|
155
220
|
|
|
156
221
|
const putCallback = async (ack) => {
|
package/federation.js
CHANGED
|
@@ -6,6 +6,35 @@
|
|
|
6
6
|
import * as h3 from 'h3-js';
|
|
7
7
|
import { attachHologramMeta } from './hologram.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Look up a holon's display name from its `settings` lens.
|
|
11
|
+
*
|
|
12
|
+
* Returns the `name` field of the holon's settings document, or null if
|
|
13
|
+
* settings are missing or unnamed. Callers should fall back to the bare
|
|
14
|
+
* holon id when this returns null.
|
|
15
|
+
*
|
|
16
|
+
* @param {HoloSphere} holosphere
|
|
17
|
+
* @param {string} space - holon id
|
|
18
|
+
* @returns {Promise<string|null>}
|
|
19
|
+
*/
|
|
20
|
+
async function getHolonName(holosphere, space) {
|
|
21
|
+
if (!holosphere || !space) return null;
|
|
22
|
+
try {
|
|
23
|
+
const settings = await holosphere.get(space, 'settings', space);
|
|
24
|
+
if (!settings) return null;
|
|
25
|
+
if (Array.isArray(settings)) {
|
|
26
|
+
const found = settings.find(s => s && typeof s.name === 'string' && s.name.trim() !== '');
|
|
27
|
+
return found ? found.name : null;
|
|
28
|
+
}
|
|
29
|
+
if (typeof settings.name === 'string' && settings.name.trim() !== '') {
|
|
30
|
+
return settings.name;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
9
38
|
/**
|
|
10
39
|
* Creates a directional federation relationship between two spaces.
|
|
11
40
|
*
|
|
@@ -149,7 +178,18 @@ export async function subscribeFederation(holosphere, spaceId, password = null,
|
|
|
149
178
|
const subscriptions = [];
|
|
150
179
|
let lastNotificationTime = {};
|
|
151
180
|
|
|
181
|
+
// Cache partner display names once at setup. Each callback firing for
|
|
182
|
+
// a given partner uses the same name without re-reading settings.
|
|
183
|
+
const partnerNames = new Map();
|
|
184
|
+
|
|
152
185
|
if (fedInfo.inbound && fedInfo.inbound.length > 0) {
|
|
186
|
+
await Promise.all(
|
|
187
|
+
fedInfo.inbound.map(async space => {
|
|
188
|
+
const name = await getHolonName(holosphere, space);
|
|
189
|
+
partnerNames.set(space, name);
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
|
|
153
193
|
for (const federatedSpace of fedInfo.inbound) {
|
|
154
194
|
// For each lens specified (or all if '*')
|
|
155
195
|
for (const lens of lenses) {
|
|
@@ -158,27 +198,34 @@ export async function subscribeFederation(holosphere, spaceId, password = null,
|
|
|
158
198
|
try {
|
|
159
199
|
// Skip if data is missing or not from federated space
|
|
160
200
|
if (!data || !data.id) return;
|
|
161
|
-
|
|
201
|
+
|
|
162
202
|
// Apply throttling if configured
|
|
163
203
|
const now = Date.now();
|
|
164
204
|
const key = `${federatedSpace}_${lens}_${data.id}`;
|
|
165
|
-
|
|
205
|
+
|
|
166
206
|
if (throttle > 0) {
|
|
167
|
-
if (lastNotificationTime[key] &&
|
|
207
|
+
if (lastNotificationTime[key] &&
|
|
168
208
|
(now - lastNotificationTime[key]) < throttle) {
|
|
169
209
|
return; // Skip this notification (throttled)
|
|
170
210
|
}
|
|
171
211
|
lastNotificationTime[key] = now;
|
|
172
212
|
}
|
|
173
|
-
|
|
174
|
-
// Add federation metadata if not present
|
|
175
|
-
|
|
176
|
-
|
|
213
|
+
|
|
214
|
+
// Add federation metadata if not present.
|
|
215
|
+
// Use the canonical `_federation` envelope so it
|
|
216
|
+
// matches what propagate() and getFederated()
|
|
217
|
+
// produce, and what consumers (UI badges, etc.)
|
|
218
|
+
// already check for.
|
|
219
|
+
if (!data._federation) {
|
|
220
|
+
const partnerName = partnerNames.get(federatedSpace);
|
|
221
|
+
data._federation = {
|
|
177
222
|
origin: federatedSpace,
|
|
178
|
-
|
|
223
|
+
sourceLens: lens,
|
|
224
|
+
timestamp: now,
|
|
225
|
+
...(partnerName ? { originName: partnerName } : {})
|
|
179
226
|
};
|
|
180
227
|
}
|
|
181
|
-
|
|
228
|
+
|
|
182
229
|
// Execute callback with the data
|
|
183
230
|
await callback(data, federatedSpace, lens);
|
|
184
231
|
} catch (error) {
|
|
@@ -476,10 +523,37 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
476
523
|
spacesToQuery = spacesToQuery.concat(federatedSpaces);
|
|
477
524
|
}
|
|
478
525
|
|
|
526
|
+
// Resolve display names for federated partner spaces in parallel, so
|
|
527
|
+
// every tagged item can carry the holon's name. Compute once per call.
|
|
528
|
+
const remoteSpaces = spacesToQuery.filter(s => s !== holon);
|
|
529
|
+
const spaceNames = new Map();
|
|
530
|
+
await Promise.all(
|
|
531
|
+
remoteSpaces.map(async space => {
|
|
532
|
+
const name = await getHolonName(holosphere, space);
|
|
533
|
+
spaceNames.set(space, name);
|
|
534
|
+
})
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
// Tag items pulled from a federated partner with the space they came
|
|
538
|
+
// from (and its resolved display name, if any). Local items are left
|
|
539
|
+
// untouched so consumers can distinguish own vs. external by absence
|
|
540
|
+
// or presence of `_federation`.
|
|
541
|
+
const tagWithSource = (item, space) => {
|
|
542
|
+
if (!item || space === holon) return item;
|
|
543
|
+
const originName = spaceNames.get(space);
|
|
544
|
+
const fed = {
|
|
545
|
+
...(item._federation || {}),
|
|
546
|
+
origin: space,
|
|
547
|
+
sourceLens: lens
|
|
548
|
+
};
|
|
549
|
+
if (originName) fed.originName = originName;
|
|
550
|
+
return { ...item, _federation: fed };
|
|
551
|
+
};
|
|
552
|
+
|
|
479
553
|
// Fetch data from all relevant spaces
|
|
480
554
|
for (const currentSpace of spacesToQuery) {
|
|
481
555
|
if (queryIds && Array.isArray(queryIds)) {
|
|
482
|
-
// --- Fetch specific IDs using holosphere.get ---
|
|
556
|
+
// --- Fetch specific IDs using holosphere.get ---
|
|
483
557
|
console.log(`Fetching specific IDs from ${currentSpace}: ${queryIds.join(', ')}`);
|
|
484
558
|
for (const itemId of queryIds) {
|
|
485
559
|
if (fetchedItems.has(itemId)) continue; // Skip if already fetched
|
|
@@ -487,7 +561,7 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
487
561
|
holosphere.get(currentSpace, lens, itemId)
|
|
488
562
|
.then(item => {
|
|
489
563
|
if (item) {
|
|
490
|
-
fetchedItems.set(itemId, item);
|
|
564
|
+
fetchedItems.set(itemId, tagWithSource(item, currentSpace));
|
|
491
565
|
}
|
|
492
566
|
})
|
|
493
567
|
.catch(err => console.warn(`Error fetching item ${itemId} from ${currentSpace}: ${err.message}`))
|
|
@@ -504,7 +578,7 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
504
578
|
.then(items => {
|
|
505
579
|
for (const item of items) {
|
|
506
580
|
if (item && item[idField] && !fetchedItems.has(item[idField])) {
|
|
507
|
-
fetchedItems.set(item[idField], item);
|
|
581
|
+
fetchedItems.set(item[idField], tagWithSource(item, currentSpace));
|
|
508
582
|
}
|
|
509
583
|
}
|
|
510
584
|
})
|
|
@@ -554,7 +628,26 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
554
628
|
if (originalData) {
|
|
555
629
|
// Replace the reference with the resolved data, attaching
|
|
556
630
|
// the canonical _hologram envelope (single source of truth).
|
|
557
|
-
|
|
631
|
+
const withMeta = attachHologramMeta(originalData, item.soul);
|
|
632
|
+
// Stamp the source holon's display name so consumers
|
|
633
|
+
// don't need a second round-trip to render it. Use
|
|
634
|
+
// the per-call cache when possible to avoid duplicate
|
|
635
|
+
// settings reads across many holograms from the same
|
|
636
|
+
// source.
|
|
637
|
+
if (withMeta._hologram?.sourceHolon) {
|
|
638
|
+
let sourceHolonName = spaceNames.get(withMeta._hologram.sourceHolon);
|
|
639
|
+
if (sourceHolonName === undefined) {
|
|
640
|
+
sourceHolonName = await getHolonName(holosphere, withMeta._hologram.sourceHolon);
|
|
641
|
+
spaceNames.set(withMeta._hologram.sourceHolon, sourceHolonName);
|
|
642
|
+
}
|
|
643
|
+
if (sourceHolonName) {
|
|
644
|
+
withMeta._hologram = {
|
|
645
|
+
...withMeta._hologram,
|
|
646
|
+
sourceHolonName
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
result[i] = withMeta;
|
|
558
651
|
} else {
|
|
559
652
|
// Original data not found — keep the id so callers can
|
|
560
653
|
// identify the broken reference, and surface the error.
|
|
@@ -732,6 +825,11 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
732
825
|
// Check if data is already a hologram
|
|
733
826
|
const isAlreadyHologram = holosphere.isHologram(data);
|
|
734
827
|
|
|
828
|
+
// Resolve our own holon's name once so every propagated
|
|
829
|
+
// payload carries it. Falls back to undefined (the field
|
|
830
|
+
// is omitted) so consumers can use the bare holon id.
|
|
831
|
+
const ownName = await getHolonName(holosphere, holon);
|
|
832
|
+
|
|
735
833
|
// For each target space, propagate the data
|
|
736
834
|
const propagatePromises = spaces.map(async (targetSpace) => {
|
|
737
835
|
try {
|
|
@@ -740,7 +838,8 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
740
838
|
origin: holon, // The space from which this data is being propagated
|
|
741
839
|
sourceLens: lens, // The lens from which this data is being propagated
|
|
742
840
|
propagatedAt: Date.now(),
|
|
743
|
-
originalId: data.id
|
|
841
|
+
originalId: data.id,
|
|
842
|
+
...(ownName ? { originName: ownName } : {})
|
|
744
843
|
};
|
|
745
844
|
|
|
746
845
|
if (useHolograms && !isAlreadyHologram) {
|
|
@@ -847,7 +946,11 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
847
946
|
|
|
848
947
|
// Check if data is already a hologram (reuse from federation section)
|
|
849
948
|
const isAlreadyHologram = holosphere.isHologram(data);
|
|
850
|
-
|
|
949
|
+
|
|
950
|
+
// Resolve our own holon's name once for parent propagation
|
|
951
|
+
// (same as the federation block above).
|
|
952
|
+
const ownNameParent = await getHolonName(holosphere, holon);
|
|
953
|
+
|
|
851
954
|
// Propagate to each parent hexagon
|
|
852
955
|
const parentPropagatePromises = parentHexagons.map(async (parentHexagon) => {
|
|
853
956
|
try {
|
|
@@ -858,7 +961,8 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
858
961
|
propagatedAt: Date.now(),
|
|
859
962
|
originalId: data.id,
|
|
860
963
|
propagationType: 'parent', // Indicate this is parent propagation
|
|
861
|
-
parentLevel: holonResolution - h3.getResolution(parentHexagon) // How many levels up
|
|
964
|
+
parentLevel: holonResolution - h3.getResolution(parentHexagon), // How many levels up
|
|
965
|
+
...(ownNameParent ? { originName: ownNameParent } : {})
|
|
862
966
|
};
|
|
863
967
|
|
|
864
968
|
if (useHolograms && !isAlreadyHologram) {
|
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-alpha4
|
|
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-alpha4/holosphere-bundle.esm.js';
|
|
7
7
|
* const hs = new HoloSphere('myapp');
|
|
8
8
|
*/
|
|
9
9
|
var __create = Object.create;
|
|
@@ -24322,6 +24322,23 @@ async function resolveHologram(holoInstance, hologram, options = {}) {
|
|
|
24322
24322
|
}
|
|
24323
24323
|
|
|
24324
24324
|
// federation.js
|
|
24325
|
+
async function getHolonName(holosphere, space) {
|
|
24326
|
+
if (!holosphere || !space) return null;
|
|
24327
|
+
try {
|
|
24328
|
+
const settings = await holosphere.get(space, "settings", space);
|
|
24329
|
+
if (!settings) return null;
|
|
24330
|
+
if (Array.isArray(settings)) {
|
|
24331
|
+
const found = settings.find((s) => s && typeof s.name === "string" && s.name.trim() !== "");
|
|
24332
|
+
return found ? found.name : null;
|
|
24333
|
+
}
|
|
24334
|
+
if (typeof settings.name === "string" && settings.name.trim() !== "") {
|
|
24335
|
+
return settings.name;
|
|
24336
|
+
}
|
|
24337
|
+
return null;
|
|
24338
|
+
} catch {
|
|
24339
|
+
return null;
|
|
24340
|
+
}
|
|
24341
|
+
}
|
|
24325
24342
|
async function federate(holosphere, spaceId1, spaceId2, password1 = null, password2 = null, bidirectional = true, lensConfig = {}) {
|
|
24326
24343
|
if (!spaceId1 || !spaceId2) {
|
|
24327
24344
|
throw new Error("federate: Missing required space IDs");
|
|
@@ -24424,7 +24441,14 @@ async function subscribeFederation(holosphere, spaceId, password = null, callbac
|
|
|
24424
24441
|
}
|
|
24425
24442
|
const subscriptions2 = [];
|
|
24426
24443
|
let lastNotificationTime = {};
|
|
24444
|
+
const partnerNames = /* @__PURE__ */ new Map();
|
|
24427
24445
|
if (fedInfo.inbound && fedInfo.inbound.length > 0) {
|
|
24446
|
+
await Promise.all(
|
|
24447
|
+
fedInfo.inbound.map(async (space) => {
|
|
24448
|
+
const name = await getHolonName(holosphere, space);
|
|
24449
|
+
partnerNames.set(space, name);
|
|
24450
|
+
})
|
|
24451
|
+
);
|
|
24428
24452
|
for (const federatedSpace of fedInfo.inbound) {
|
|
24429
24453
|
for (const lens of lenses) {
|
|
24430
24454
|
try {
|
|
@@ -24439,10 +24463,13 @@ async function subscribeFederation(holosphere, spaceId, password = null, callbac
|
|
|
24439
24463
|
}
|
|
24440
24464
|
lastNotificationTime[key] = now;
|
|
24441
24465
|
}
|
|
24442
|
-
if (!data.
|
|
24443
|
-
|
|
24466
|
+
if (!data._federation) {
|
|
24467
|
+
const partnerName = partnerNames.get(federatedSpace);
|
|
24468
|
+
data._federation = {
|
|
24444
24469
|
origin: federatedSpace,
|
|
24445
|
-
|
|
24470
|
+
sourceLens: lens,
|
|
24471
|
+
timestamp: now,
|
|
24472
|
+
...partnerName ? { originName: partnerName } : {}
|
|
24446
24473
|
};
|
|
24447
24474
|
}
|
|
24448
24475
|
await callback(data, federatedSpace, lens);
|
|
@@ -24635,6 +24662,25 @@ async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
24635
24662
|
const federatedSpaces = maxFederatedSpaces === -1 ? fedInfo.inbound : fedInfo.inbound.slice(0, maxFederatedSpaces);
|
|
24636
24663
|
spacesToQuery = spacesToQuery.concat(federatedSpaces);
|
|
24637
24664
|
}
|
|
24665
|
+
const remoteSpaces = spacesToQuery.filter((s) => s !== holon);
|
|
24666
|
+
const spaceNames = /* @__PURE__ */ new Map();
|
|
24667
|
+
await Promise.all(
|
|
24668
|
+
remoteSpaces.map(async (space) => {
|
|
24669
|
+
const name = await getHolonName(holosphere, space);
|
|
24670
|
+
spaceNames.set(space, name);
|
|
24671
|
+
})
|
|
24672
|
+
);
|
|
24673
|
+
const tagWithSource = (item, space) => {
|
|
24674
|
+
if (!item || space === holon) return item;
|
|
24675
|
+
const originName = spaceNames.get(space);
|
|
24676
|
+
const fed = {
|
|
24677
|
+
...item._federation || {},
|
|
24678
|
+
origin: space,
|
|
24679
|
+
sourceLens: lens
|
|
24680
|
+
};
|
|
24681
|
+
if (originName) fed.originName = originName;
|
|
24682
|
+
return { ...item, _federation: fed };
|
|
24683
|
+
};
|
|
24638
24684
|
for (const currentSpace of spacesToQuery) {
|
|
24639
24685
|
if (queryIds && Array.isArray(queryIds)) {
|
|
24640
24686
|
console.log(`Fetching specific IDs from ${currentSpace}: ${queryIds.join(", ")}`);
|
|
@@ -24643,7 +24689,7 @@ async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
24643
24689
|
fetchPromises.push(
|
|
24644
24690
|
holosphere.get(currentSpace, lens, itemId).then((item) => {
|
|
24645
24691
|
if (item) {
|
|
24646
|
-
fetchedItems.set(itemId, item);
|
|
24692
|
+
fetchedItems.set(itemId, tagWithSource(item, currentSpace));
|
|
24647
24693
|
}
|
|
24648
24694
|
}).catch((err) => console.warn(`Error fetching item ${itemId} from ${currentSpace}: ${err.message}`))
|
|
24649
24695
|
);
|
|
@@ -24657,7 +24703,7 @@ async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
24657
24703
|
holosphere.getAll(currentSpace, lens).then((items) => {
|
|
24658
24704
|
for (const item of items) {
|
|
24659
24705
|
if (item && item[idField] && !fetchedItems.has(item[idField])) {
|
|
24660
|
-
fetchedItems.set(item[idField], item);
|
|
24706
|
+
fetchedItems.set(item[idField], tagWithSource(item, currentSpace));
|
|
24661
24707
|
}
|
|
24662
24708
|
}
|
|
24663
24709
|
}).catch((err) => console.warn(`Error fetching all items from ${currentSpace}: ${err.message}`))
|
|
@@ -24689,7 +24735,21 @@ async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
24689
24735
|
);
|
|
24690
24736
|
console.log(`Original data found via soul path:`, JSON.stringify(originalData));
|
|
24691
24737
|
if (originalData) {
|
|
24692
|
-
|
|
24738
|
+
const withMeta = attachHologramMeta(originalData, item.soul);
|
|
24739
|
+
if (withMeta._hologram?.sourceHolon) {
|
|
24740
|
+
let sourceHolonName = spaceNames.get(withMeta._hologram.sourceHolon);
|
|
24741
|
+
if (sourceHolonName === void 0) {
|
|
24742
|
+
sourceHolonName = await getHolonName(holosphere, withMeta._hologram.sourceHolon);
|
|
24743
|
+
spaceNames.set(withMeta._hologram.sourceHolon, sourceHolonName);
|
|
24744
|
+
}
|
|
24745
|
+
if (sourceHolonName) {
|
|
24746
|
+
withMeta._hologram = {
|
|
24747
|
+
...withMeta._hologram,
|
|
24748
|
+
sourceHolonName
|
|
24749
|
+
};
|
|
24750
|
+
}
|
|
24751
|
+
}
|
|
24752
|
+
result[i] = withMeta;
|
|
24693
24753
|
} else {
|
|
24694
24754
|
result[i] = {
|
|
24695
24755
|
id: item.id,
|
|
@@ -24813,6 +24873,7 @@ async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
24813
24873
|
});
|
|
24814
24874
|
if (spaces.length > 0) {
|
|
24815
24875
|
const isAlreadyHologram = holosphere.isHologram(data);
|
|
24876
|
+
const ownName = await getHolonName(holosphere, holon);
|
|
24816
24877
|
const propagatePromises = spaces.map(async (targetSpace) => {
|
|
24817
24878
|
try {
|
|
24818
24879
|
let payloadToPut;
|
|
@@ -24822,7 +24883,8 @@ async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
24822
24883
|
sourceLens: lens,
|
|
24823
24884
|
// The lens from which this data is being propagated
|
|
24824
24885
|
propagatedAt: Date.now(),
|
|
24825
|
-
originalId: data.id
|
|
24886
|
+
originalId: data.id,
|
|
24887
|
+
...ownName ? { originName: ownName } : {}
|
|
24826
24888
|
};
|
|
24827
24889
|
if (useHolograms && !isAlreadyHologram) {
|
|
24828
24890
|
const newHologram = holosphere.createHologram(holon, lens, data);
|
|
@@ -24907,6 +24969,7 @@ async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
24907
24969
|
if (parentHexagons.length > 0) {
|
|
24908
24970
|
result.parentPropagation.messages.push(`Found ${parentHexagons.length} parent hexagons to propagate to: ${parentHexagons.join(", ")}`);
|
|
24909
24971
|
const isAlreadyHologram = holosphere.isHologram(data);
|
|
24972
|
+
const ownNameParent = await getHolonName(holosphere, holon);
|
|
24910
24973
|
const parentPropagatePromises = parentHexagons.map(async (parentHexagon) => {
|
|
24911
24974
|
try {
|
|
24912
24975
|
let payloadToPut;
|
|
@@ -24919,8 +24982,9 @@ async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
24919
24982
|
originalId: data.id,
|
|
24920
24983
|
propagationType: "parent",
|
|
24921
24984
|
// Indicate this is parent propagation
|
|
24922
|
-
parentLevel: holonResolution - getResolution(parentHexagon)
|
|
24985
|
+
parentLevel: holonResolution - getResolution(parentHexagon),
|
|
24923
24986
|
// How many levels up
|
|
24987
|
+
...ownNameParent ? { originName: ownNameParent } : {}
|
|
24924
24988
|
};
|
|
24925
24989
|
if (useHolograms && !isAlreadyHologram) {
|
|
24926
24990
|
const newHologram = holosphere.createHologram(holon, lens, data);
|
|
@@ -25188,6 +25252,45 @@ function clearSchemaCache(holoInstance, lens = null) {
|
|
|
25188
25252
|
}
|
|
25189
25253
|
|
|
25190
25254
|
// content.js
|
|
25255
|
+
function sanitizeForStorage(value, path = "", seen = /* @__PURE__ */ new WeakSet(), warnings = []) {
|
|
25256
|
+
if (value === null) return null;
|
|
25257
|
+
const t = typeof value;
|
|
25258
|
+
if (t === "number") {
|
|
25259
|
+
if (!Number.isFinite(value)) {
|
|
25260
|
+
warnings.push(`${path || "<root>"}: ${value} (non-finite number)`);
|
|
25261
|
+
return void 0;
|
|
25262
|
+
}
|
|
25263
|
+
return value;
|
|
25264
|
+
}
|
|
25265
|
+
if (t === "string" || t === "boolean") return value;
|
|
25266
|
+
if (t === "undefined" || t === "function" || t === "symbol" || t === "bigint") {
|
|
25267
|
+
warnings.push(`${path || "<root>"}: ${t}`);
|
|
25268
|
+
return void 0;
|
|
25269
|
+
}
|
|
25270
|
+
if (t === "object") {
|
|
25271
|
+
if (seen.has(value)) {
|
|
25272
|
+
warnings.push(`${path || "<root>"}: circular reference`);
|
|
25273
|
+
return void 0;
|
|
25274
|
+
}
|
|
25275
|
+
seen.add(value);
|
|
25276
|
+
if (Array.isArray(value)) {
|
|
25277
|
+
const out2 = [];
|
|
25278
|
+
for (let i = 0; i < value.length; i++) {
|
|
25279
|
+
const cleaned = sanitizeForStorage(value[i], `${path}[${i}]`, seen, warnings);
|
|
25280
|
+
out2.push(cleaned === void 0 ? null : cleaned);
|
|
25281
|
+
}
|
|
25282
|
+
return out2;
|
|
25283
|
+
}
|
|
25284
|
+
const out = {};
|
|
25285
|
+
for (const k of Object.keys(value)) {
|
|
25286
|
+
const cleaned = sanitizeForStorage(value[k], path ? `${path}.${k}` : k, seen, warnings);
|
|
25287
|
+
if (cleaned !== void 0) out[k] = cleaned;
|
|
25288
|
+
}
|
|
25289
|
+
return out;
|
|
25290
|
+
}
|
|
25291
|
+
warnings.push(`${path || "<root>"}: unsupported type ${t}`);
|
|
25292
|
+
return void 0;
|
|
25293
|
+
}
|
|
25191
25294
|
async function put(holoInstance, holon, lens, data, password = null, options = {}) {
|
|
25192
25295
|
if (!data) {
|
|
25193
25296
|
throw new Error("put: Missing required data parameter");
|
|
@@ -25285,13 +25388,16 @@ async function put(holoInstance, holon, lens, data, password = null, options = {
|
|
|
25285
25388
|
}
|
|
25286
25389
|
return new Promise((resolve, reject) => {
|
|
25287
25390
|
try {
|
|
25288
|
-
|
|
25289
|
-
|
|
25290
|
-
|
|
25291
|
-
|
|
25292
|
-
|
|
25293
|
-
|
|
25391
|
+
const sanitizeWarnings = [];
|
|
25392
|
+
let dataToStore = sanitizeForStorage(data, "", /* @__PURE__ */ new WeakSet(), sanitizeWarnings) || {};
|
|
25393
|
+
if (sanitizeWarnings.length > 0) {
|
|
25394
|
+
console.warn(
|
|
25395
|
+
`holosphere.put: sanitized ${sanitizeWarnings.length} field(s) at ${targetHolon}/${targetLens}/${targetKey} (id=${data.id}):`,
|
|
25396
|
+
sanitizeWarnings
|
|
25397
|
+
);
|
|
25294
25398
|
}
|
|
25399
|
+
if (dataToStore._meta !== void 0) delete dataToStore._meta;
|
|
25400
|
+
if (dataToStore._hologram !== void 0) delete dataToStore._hologram;
|
|
25295
25401
|
const payload = JSON.stringify(dataToStore);
|
|
25296
25402
|
const putCallback = async (ack) => {
|
|
25297
25403
|
if (ack.err) {
|