holosphere 1.1.20 → 2.0.0-alpha1
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/.env.example +36 -0
- package/.eslintrc.json +16 -0
- package/.prettierrc.json +7 -0
- package/LICENSE +162 -38
- package/README.md +483 -367
- package/bin/holosphere-activitypub.js +158 -0
- package/cleanup-test-data.js +204 -0
- package/examples/demo.html +1333 -0
- package/examples/example-bot.js +197 -0
- package/package.json +47 -87
- package/scripts/check-bundle-size.js +54 -0
- package/scripts/check-quest-ids.js +77 -0
- package/scripts/import-holons.js +578 -0
- package/scripts/publish-to-relay.js +101 -0
- package/scripts/read-example.js +186 -0
- package/scripts/relay-diagnostic.js +59 -0
- package/scripts/relay-example.js +179 -0
- package/scripts/resync-to-relay.js +245 -0
- package/scripts/revert-import.js +196 -0
- package/scripts/test-hybrid-mode.js +108 -0
- package/scripts/test-local-storage.js +63 -0
- package/scripts/test-nostr-direct.js +55 -0
- package/scripts/test-read-data.js +45 -0
- package/scripts/test-write-read.js +63 -0
- package/scripts/verify-import.js +95 -0
- package/scripts/verify-relay-data.js +139 -0
- package/src/ai/aggregation.js +319 -0
- package/src/ai/breakdown.js +511 -0
- package/src/ai/classifier.js +217 -0
- package/src/ai/council.js +228 -0
- package/src/ai/embeddings.js +279 -0
- package/src/ai/federation-ai.js +324 -0
- package/src/ai/h3-ai.js +955 -0
- package/src/ai/index.js +112 -0
- package/src/ai/json-ops.js +225 -0
- package/src/ai/llm-service.js +205 -0
- package/src/ai/nl-query.js +223 -0
- package/src/ai/relationships.js +353 -0
- package/src/ai/schema-extractor.js +218 -0
- package/src/ai/spatial.js +293 -0
- package/src/ai/tts.js +194 -0
- package/src/content/social-protocols.js +168 -0
- package/src/core/holosphere.js +273 -0
- package/src/crypto/secp256k1.js +259 -0
- package/src/federation/discovery.js +334 -0
- package/src/federation/hologram.js +1042 -0
- package/src/federation/registry.js +386 -0
- package/src/hierarchical/upcast.js +110 -0
- package/src/index.js +2669 -0
- package/src/schema/validator.js +91 -0
- package/src/spatial/h3-operations.js +110 -0
- package/src/storage/backend-factory.js +125 -0
- package/src/storage/backend-interface.js +142 -0
- package/src/storage/backends/activitypub/server.js +653 -0
- package/src/storage/backends/activitypub-backend.js +272 -0
- package/src/storage/backends/gundb-backend.js +233 -0
- package/src/storage/backends/nostr-backend.js +136 -0
- package/src/storage/filesystem-storage-browser.js +41 -0
- package/src/storage/filesystem-storage.js +138 -0
- package/src/storage/global-tables.js +81 -0
- package/src/storage/gun-async.js +281 -0
- package/src/storage/gun-wrapper.js +221 -0
- package/src/storage/indexeddb-storage.js +122 -0
- package/src/storage/key-storage-simple.js +76 -0
- package/src/storage/key-storage.js +136 -0
- package/src/storage/memory-storage.js +59 -0
- package/src/storage/migration.js +338 -0
- package/src/storage/nostr-async.js +811 -0
- package/src/storage/nostr-client.js +939 -0
- package/src/storage/nostr-wrapper.js +211 -0
- package/src/storage/outbox-queue.js +208 -0
- package/src/storage/persistent-storage.js +109 -0
- package/src/storage/sync-service.js +164 -0
- package/src/subscriptions/manager.js +142 -0
- package/test-ai-real-api.js +202 -0
- package/tests/unit/ai/aggregation.test.js +295 -0
- package/tests/unit/ai/breakdown.test.js +446 -0
- package/tests/unit/ai/classifier.test.js +294 -0
- package/tests/unit/ai/council.test.js +262 -0
- package/tests/unit/ai/embeddings.test.js +384 -0
- package/tests/unit/ai/federation-ai.test.js +344 -0
- package/tests/unit/ai/h3-ai.test.js +458 -0
- package/tests/unit/ai/index.test.js +304 -0
- package/tests/unit/ai/json-ops.test.js +307 -0
- package/tests/unit/ai/llm-service.test.js +390 -0
- package/tests/unit/ai/nl-query.test.js +383 -0
- package/tests/unit/ai/relationships.test.js +311 -0
- package/tests/unit/ai/schema-extractor.test.js +384 -0
- package/tests/unit/ai/spatial.test.js +279 -0
- package/tests/unit/ai/tts.test.js +279 -0
- package/tests/unit/content.test.js +332 -0
- package/tests/unit/contract/core.test.js +88 -0
- package/tests/unit/contract/crypto.test.js +198 -0
- package/tests/unit/contract/data.test.js +223 -0
- package/tests/unit/contract/federation.test.js +181 -0
- package/tests/unit/contract/hierarchical.test.js +113 -0
- package/tests/unit/contract/schema.test.js +114 -0
- package/tests/unit/contract/social.test.js +217 -0
- package/tests/unit/contract/spatial.test.js +110 -0
- package/tests/unit/contract/subscriptions.test.js +128 -0
- package/tests/unit/contract/utils.test.js +159 -0
- package/tests/unit/core.test.js +152 -0
- package/tests/unit/crypto.test.js +328 -0
- package/tests/unit/federation.test.js +234 -0
- package/tests/unit/gun-async.test.js +252 -0
- package/tests/unit/hierarchical.test.js +399 -0
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
- package/tests/unit/integration/scenario-02-federation.test.js +76 -0
- package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
- package/tests/unit/integration/scenario-04-validation.test.js +129 -0
- package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
- package/tests/unit/integration/scenario-06-social.test.js +135 -0
- package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
- package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
- package/tests/unit/performance/benchmark.test.js +85 -0
- package/tests/unit/schema.test.js +213 -0
- package/tests/unit/spatial.test.js +158 -0
- package/tests/unit/storage.test.js +195 -0
- package/tests/unit/subscriptions.test.js +328 -0
- package/tests/unit/test-data-permanence-debug.js +197 -0
- package/tests/unit/test-data-permanence.js +340 -0
- package/tests/unit/test-key-persistence-fixed.js +148 -0
- package/tests/unit/test-key-persistence.js +172 -0
- package/tests/unit/test-relay-permanence.js +376 -0
- package/tests/unit/test-second-node.js +95 -0
- package/tests/unit/test-simple-write.js +89 -0
- package/vite.config.js +49 -0
- package/vitest.config.js +20 -0
- package/FEDERATION.md +0 -213
- package/compute.js +0 -298
- package/content.js +0 -980
- package/federation.js +0 -1234
- package/global.js +0 -736
- package/hexlib.js +0 -335
- package/hologram.js +0 -183
- package/holosphere-bundle.esm.js +0 -33256
- package/holosphere-bundle.js +0 -33287
- package/holosphere-bundle.min.js +0 -39
- package/holosphere.d.ts +0 -601
- package/holosphere.js +0 -719
- package/node.js +0 -246
- package/schema.js +0 -139
- package/utils.js +0 -302
package/content.js
DELETED
|
@@ -1,980 +0,0 @@
|
|
|
1
|
-
// holo_content.js
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Stores content in the specified holon and lens.
|
|
5
|
-
* If the target path already contains a hologram, the put operation will be
|
|
6
|
-
* redirected to store the new data at the location specified in the existing
|
|
7
|
-
* hologram's soul.
|
|
8
|
-
* If the stored data (after potential redirection) is a hologram, this function
|
|
9
|
-
* also attempts to update the target data node's `_holograms` set.
|
|
10
|
-
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
11
|
-
* @param {string} holon - The initial holon identifier.
|
|
12
|
-
* @param {string} lens - The initial lens under which to store the content.
|
|
13
|
-
* @param {object} data - The data to store.
|
|
14
|
-
* @param {string} [password] - Optional password for private holon.
|
|
15
|
-
* @param {object} [options] - Additional options
|
|
16
|
-
* @param {boolean} [options.autoPropagate=true] - Whether to automatically propagate to federated holons (default: true)
|
|
17
|
-
* @param {object} [options.propagationOptions] - Options to pass to propagate
|
|
18
|
-
* @param {boolean} [options.propagationOptions.useHolograms=true] - Whether to use holograms instead of duplicating data
|
|
19
|
-
* @param {boolean} [options.disableHologramRedirection=false] - Whether to disable hologram redirection
|
|
20
|
-
* @returns {Promise<object>} - Returns an object with success status, path info, propagation result, and list of updated holograms
|
|
21
|
-
*/
|
|
22
|
-
export async function put(holoInstance, holon, lens, data, password = null, options = {}) {
|
|
23
|
-
if (!data) { // Check data first as it's used for id generation
|
|
24
|
-
throw new Error('put: Missing required data parameter');
|
|
25
|
-
}
|
|
26
|
-
if (!holon || !lens) {
|
|
27
|
-
throw new Error('put: Missing required holon or lens parameters:', holon, lens);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const { disableHologramRedirection = false } = options; // Extract new option
|
|
31
|
-
|
|
32
|
-
let targetHolon = holon;
|
|
33
|
-
let targetLens = lens;
|
|
34
|
-
let targetKey = data.id; // Use data.id as the key
|
|
35
|
-
|
|
36
|
-
if (!targetKey) {
|
|
37
|
-
targetKey = holoInstance.generateId();
|
|
38
|
-
data.id = targetKey; // Assign the generated ID back to the data
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// --- Start: Target Path Hologram Redirection Logic ---
|
|
42
|
-
try {
|
|
43
|
-
// Get the item at the original target path, WITHOUT resolving holograms
|
|
44
|
-
const existingItemAtPath = await get(holoInstance, targetHolon, targetLens, targetKey, password, { resolveHolograms: false });
|
|
45
|
-
|
|
46
|
-
if (!disableHologramRedirection && existingItemAtPath && holoInstance.isHologram(existingItemAtPath)) {
|
|
47
|
-
const soulInfo = holoInstance.parseSoulPath(existingItemAtPath.soul);
|
|
48
|
-
if (soulInfo) {
|
|
49
|
-
// Optional: Check if soulInfo.appname matches holoInstance.appname
|
|
50
|
-
if (soulInfo.appname !== holoInstance.appname) {
|
|
51
|
-
console.warn(`Existing hologram at ${targetHolon}/${targetLens}/${targetKey} has appname (${soulInfo.appname}) in its soul ${existingItemAtPath.soul} which does not match current HoloSphere instance appname (${holoInstance.appname}). Redirecting put to soul's holon/lens within this instance.`);
|
|
52
|
-
}
|
|
53
|
-
targetHolon = soulInfo.holon; // Redirect holon
|
|
54
|
-
targetLens = soulInfo.lens; // Redirect lens
|
|
55
|
-
targetKey = soulInfo.key; // Redirect key (important!)
|
|
56
|
-
// data.id should ideally match soulInfo.key if this is consistent.
|
|
57
|
-
// If data.id is different, it means we are writing data with one ID to a path derived from another ID's soul.
|
|
58
|
-
if (data.id !== targetKey) {
|
|
59
|
-
console.warn(`Data ID ('${data.id}') differs from redirected target key ('${targetKey}') derived from existing hologram's soul. Data will be stored under key '${targetKey}'.`);
|
|
60
|
-
// It's crucial that the actual GunDB path uses targetKey.
|
|
61
|
-
// The 'data' object itself retains its original 'data.id' unless explicitly changed.
|
|
62
|
-
}
|
|
63
|
-
} else {
|
|
64
|
-
console.warn(`Existing item at ${targetHolon}/${targetLens}/${targetKey} (ID: ${existingItemAtPath.id}) is a hologram, but its soul ('${existingItemAtPath.soul}') is invalid. Proceeding with original target.`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
} catch (error) {
|
|
68
|
-
// If 'get' fails (e.g., item not found, auth error), proceed with original target.
|
|
69
|
-
// A "not found" error is expected if the path is new.
|
|
70
|
-
if (error.message && error.message.includes('RESOLVED_NULL')) {
|
|
71
|
-
// This is fine, means nothing was at the path.
|
|
72
|
-
} else {
|
|
73
|
-
console.warn(`Error checking for existing hologram at ${targetHolon}/${targetLens}/${targetKey}: ${error.message}. Proceeding with original target.`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// --- End: Target Path Hologram Redirection Logic ---
|
|
77
|
-
|
|
78
|
-
// The data being stored is 'data'. Its 'id' property is 'data.id'.
|
|
79
|
-
// The final storage path key is 'targetKey'.
|
|
80
|
-
|
|
81
|
-
// Check if the data *being put* is a hologram (this variable is used later for schema and propagation)
|
|
82
|
-
const isHologram = holoInstance.isHologram(data);
|
|
83
|
-
|
|
84
|
-
// Get and validate schema only in strict mode for non-holograms (data being put)
|
|
85
|
-
if (holoInstance.strict && !isHologram) {
|
|
86
|
-
const schema = await holoInstance.getSchema(targetLens); // Use targetLens for schema
|
|
87
|
-
if (!schema) {
|
|
88
|
-
throw new Error('Schema required in strict mode');
|
|
89
|
-
}
|
|
90
|
-
const dataToValidate = JSON.parse(JSON.stringify(data)); // Validate the actual data
|
|
91
|
-
const valid = holoInstance.validator.validate(schema, dataToValidate);
|
|
92
|
-
|
|
93
|
-
if (!valid) {
|
|
94
|
-
const errorMsg = `Schema validation failed: ${JSON.stringify(holoInstance.validator.errors)}`;
|
|
95
|
-
throw new Error(errorMsg);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
let user = null;
|
|
101
|
-
if (password) {
|
|
102
|
-
user = holoInstance.gun.user();
|
|
103
|
-
await new Promise((resolve, reject) => {
|
|
104
|
-
const userNameString = holoInstance.userName(targetHolon); // Use targetHolon for put
|
|
105
|
-
user.auth(userNameString, password, (authAck) => {
|
|
106
|
-
if (authAck.err) {
|
|
107
|
-
console.log(`Initial auth failed for ${userNameString} during put, attempting to create...`);
|
|
108
|
-
user.create(userNameString, password, (createAck) => {
|
|
109
|
-
if (createAck.err) {
|
|
110
|
-
if (createAck.err.includes("already created") || createAck.err.includes("already being created")) {
|
|
111
|
-
console.log(`User ${userNameString} already existed or being created during put, re-attempting auth with fresh user object.`);
|
|
112
|
-
const freshUser = holoInstance.gun.user(); // Get a new user object
|
|
113
|
-
freshUser.auth(userNameString, password, (secondAuthAck) => {
|
|
114
|
-
if (secondAuthAck.err) {
|
|
115
|
-
console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`);
|
|
116
|
-
resolve(); // Resolve anyway to allow test operations
|
|
117
|
-
} else {
|
|
118
|
-
resolve();
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
} else {
|
|
122
|
-
console.log(`Create user error (resolving anyway for operations): ${createAck.err}`);
|
|
123
|
-
resolve(); // Resolve anyway to allow test operations
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
console.log(`User ${userNameString} created successfully during put, attempting auth...`);
|
|
127
|
-
user.auth(userNameString, password, (secondAuthAck) => {
|
|
128
|
-
if (secondAuthAck.err) {
|
|
129
|
-
reject(new Error(`Failed to auth after create for ${userNameString} during put: ${secondAuthAck.err}`));
|
|
130
|
-
} else {
|
|
131
|
-
resolve();
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
} else {
|
|
137
|
-
resolve(); // Auth successful
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return new Promise((resolve, reject) => {
|
|
144
|
-
try {
|
|
145
|
-
// Create a copy of data without the _meta field if it exists
|
|
146
|
-
let dataToStore = { ...data };
|
|
147
|
-
if (dataToStore._meta !== undefined) {
|
|
148
|
-
delete dataToStore._meta;
|
|
149
|
-
}
|
|
150
|
-
const payload = JSON.stringify(dataToStore); // The data being stored
|
|
151
|
-
|
|
152
|
-
const putCallback = async (ack) => {
|
|
153
|
-
if (ack.err) {
|
|
154
|
-
reject(new Error(ack.err));
|
|
155
|
-
} else {
|
|
156
|
-
// --- Start: Hologram Tracking Logic (for data *being put*, if it's a hologram) ---
|
|
157
|
-
if (isHologram) {
|
|
158
|
-
try {
|
|
159
|
-
const storedDataSoulInfo = holoInstance.parseSoulPath(data.soul);
|
|
160
|
-
if (storedDataSoulInfo) {
|
|
161
|
-
const targetNodeRef = holoInstance.getNodeRef(data.soul); // Target of the data *being put*
|
|
162
|
-
// Soul of the hologram that was *actually stored* at targetHolon/targetLens/targetKey
|
|
163
|
-
const storedHologramInstanceSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
|
|
164
|
-
|
|
165
|
-
targetNodeRef.get('_holograms').get(storedHologramInstanceSoul).put(true);
|
|
166
|
-
} else {
|
|
167
|
-
console.warn(`Data (ID: ${data.id}) being put is a hologram, but could not parse its soul ${data.soul} for tracking.`);
|
|
168
|
-
}
|
|
169
|
-
} catch (trackingError) {
|
|
170
|
-
console.warn(`Error updating _holograms set for the target of the data being put (data ID: ${data.id}, soul: ${data.soul}):`, trackingError);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// --- End: Hologram Tracking Logic ---
|
|
174
|
-
|
|
175
|
-
// --- Start: Active Hologram Update Logic (for actual data being stored) ---
|
|
176
|
-
let updatedHolograms = [];
|
|
177
|
-
if (!isHologram && !options.isHologramUpdate) {
|
|
178
|
-
try {
|
|
179
|
-
const currentDataSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
|
|
180
|
-
const currentNodeRef = holoInstance.getNodeRef(currentDataSoul);
|
|
181
|
-
|
|
182
|
-
// Get the _holograms set for this data
|
|
183
|
-
await new Promise((resolveHologramUpdate) => {
|
|
184
|
-
currentNodeRef.get('_holograms').once(async (hologramsSet) => {
|
|
185
|
-
if (hologramsSet) {
|
|
186
|
-
const hologramSouls = Object.keys(hologramsSet).filter(k =>
|
|
187
|
-
k !== '_' && hologramsSet[k] === true // Only active holograms (deleted ones are null/removed)
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
if (hologramSouls.length > 0) {
|
|
191
|
-
// Update each active hologram with an 'updated' timestamp
|
|
192
|
-
const updatePromises = hologramSouls.map(async (hologramSoul) => {
|
|
193
|
-
try {
|
|
194
|
-
const hologramSoulInfo = holoInstance.parseSoulPath(hologramSoul);
|
|
195
|
-
if (hologramSoulInfo) {
|
|
196
|
-
// Get the current hologram data
|
|
197
|
-
const currentHologram = await holoInstance.get(
|
|
198
|
-
hologramSoulInfo.holon,
|
|
199
|
-
hologramSoulInfo.lens,
|
|
200
|
-
hologramSoulInfo.key,
|
|
201
|
-
null,
|
|
202
|
-
{ resolveHolograms: false }
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
if (currentHologram) {
|
|
206
|
-
// Update the hologram with an 'updated' timestamp
|
|
207
|
-
const updatedHologram = {
|
|
208
|
-
...currentHologram,
|
|
209
|
-
updated: Date.now()
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
await holoInstance.put(
|
|
213
|
-
hologramSoulInfo.holon,
|
|
214
|
-
hologramSoulInfo.lens,
|
|
215
|
-
updatedHologram,
|
|
216
|
-
null,
|
|
217
|
-
{
|
|
218
|
-
autoPropagate: false, // Don't auto-propagate hologram updates
|
|
219
|
-
disableHologramRedirection: true, // Prevent redirection when updating holograms
|
|
220
|
-
isHologramUpdate: true // Prevent recursive hologram updates
|
|
221
|
-
}
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// Add to the list of updated holograms
|
|
225
|
-
updatedHolograms.push({
|
|
226
|
-
soul: hologramSoul,
|
|
227
|
-
holon: hologramSoulInfo.holon,
|
|
228
|
-
lens: hologramSoulInfo.lens,
|
|
229
|
-
key: hologramSoulInfo.key,
|
|
230
|
-
id: hologramSoulInfo.key,
|
|
231
|
-
timestamp: updatedHologram.updated
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
} catch (hologramUpdateError) {
|
|
236
|
-
console.warn(`Error updating hologram ${hologramSoul}:`, hologramUpdateError);
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
await Promise.all(updatePromises);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
resolveHologramUpdate(); // Resolve the promise to continue with the main put logic
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
} catch (hologramUpdateError) {
|
|
247
|
-
console.warn(`Error checking for active holograms to update:`, hologramUpdateError);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
// --- End: Active Hologram Update Logic ---
|
|
251
|
-
|
|
252
|
-
// Only notify subscribers for actual data, not holograms
|
|
253
|
-
if (!isHologram) {
|
|
254
|
-
holoInstance.notifySubscribers({
|
|
255
|
-
holon: targetHolon, // Notify with final target
|
|
256
|
-
lens: targetLens,
|
|
257
|
-
...data // The data that was put
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Auto-propagate to federation by default (if data *being put* is not a hologram)
|
|
262
|
-
const shouldPropagate = options.autoPropagate !== false && !isHologram;
|
|
263
|
-
let propagationResult = null;
|
|
264
|
-
|
|
265
|
-
if (shouldPropagate) {
|
|
266
|
-
try {
|
|
267
|
-
const propagationOptions = {
|
|
268
|
-
useHolograms: true,
|
|
269
|
-
...options.propagationOptions
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
propagationResult = await holoInstance.propagate(
|
|
273
|
-
targetHolon, // Propagate from final target
|
|
274
|
-
targetLens,
|
|
275
|
-
data, // The data that was put
|
|
276
|
-
propagationOptions
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
if (propagationResult && propagationResult.errors > 0) {
|
|
280
|
-
console.warn('Auto-propagation had errors:', propagationResult);
|
|
281
|
-
}
|
|
282
|
-
} catch (propError) {
|
|
283
|
-
console.warn('Error in auto-propagation:', propError);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
resolve({
|
|
288
|
-
success: true,
|
|
289
|
-
isHologramAtPath: isHologram, // whether the data *put* was a hologram
|
|
290
|
-
pathHolon: targetHolon,
|
|
291
|
-
pathLens: targetLens,
|
|
292
|
-
pathKey: targetKey,
|
|
293
|
-
propagationResult,
|
|
294
|
-
updatedHolograms: updatedHolograms // List of holograms that were updated
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
// Use targetHolon, targetLens, and targetKey for the actual storage path
|
|
300
|
-
const dataPath = password ?
|
|
301
|
-
user.get('private').get(targetLens).get(targetKey) :
|
|
302
|
-
holoInstance.gun.get(holoInstance.appname).get(targetHolon).get(targetLens).get(targetKey);
|
|
303
|
-
|
|
304
|
-
dataPath.put(payload, putCallback);
|
|
305
|
-
} catch (error) {
|
|
306
|
-
reject(error);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
} catch (error) {
|
|
310
|
-
console.error('Error in put:', error);
|
|
311
|
-
throw error;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Retrieves content from the specified holon and lens.
|
|
317
|
-
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
318
|
-
* @param {string} holon - The holon identifier.
|
|
319
|
-
* @param {string} lens - The lens from which to retrieve content.
|
|
320
|
-
* @param {string} key - The specific key to retrieve.
|
|
321
|
-
* @param {string} [password] - Optional password for private holon.
|
|
322
|
-
* @param {object} [options] - Additional options
|
|
323
|
-
* @param {boolean} [options.resolveHolograms=true] - Whether to automatically resolve holograms
|
|
324
|
-
* @param {object} [options.validationOptions] - Options passed to the schema validator
|
|
325
|
-
* @returns {Promise<object|null>} - The retrieved content or null if not found.
|
|
326
|
-
*/
|
|
327
|
-
export async function get(holoInstance, holon, lens, key, password = null, options = {}) {
|
|
328
|
-
if (!holon || !lens || !key) {
|
|
329
|
-
console.error('get: Missing required parameters');
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Destructure options, including visited
|
|
334
|
-
const { resolveHolograms = true, validationOptions = {}, visited } = options;
|
|
335
|
-
|
|
336
|
-
// Get schema for validation if in strict mode
|
|
337
|
-
let schema = null;
|
|
338
|
-
if (holoInstance.strict) {
|
|
339
|
-
schema = await holoInstance.getSchema(lens);
|
|
340
|
-
if (!schema) {
|
|
341
|
-
throw new Error('Schema required in strict mode');
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
try {
|
|
346
|
-
let user = null;
|
|
347
|
-
if (password) {
|
|
348
|
-
user = holoInstance.gun.user();
|
|
349
|
-
await new Promise((resolve, reject) => {
|
|
350
|
-
const userNameString = holoInstance.userName(holon); // Use holon for get
|
|
351
|
-
user.auth(userNameString, password, (authAck) => {
|
|
352
|
-
if (authAck.err) {
|
|
353
|
-
// If auth fails, reject immediately. Do not attempt to create user.
|
|
354
|
-
reject(new Error(`Authentication failed for ${userNameString} during get: ${authAck.err}`));
|
|
355
|
-
} else {
|
|
356
|
-
resolve(); // Auth successful
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return new Promise((resolve) => {
|
|
363
|
-
const handleData = async (data) => {
|
|
364
|
-
let parsed = null; // Declare parsed here to make it available in catch
|
|
365
|
-
if (!data) {
|
|
366
|
-
resolve(null);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
parsed = await holoInstance.parse(data); // Assign to the outer scoped parsed
|
|
372
|
-
|
|
373
|
-
if (!parsed) {
|
|
374
|
-
resolve(null);
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Check if this is a hologram that needs to be resolved
|
|
379
|
-
if (resolveHolograms && holoInstance.isHologram(parsed)) {
|
|
380
|
-
const resolvedValue = await holoInstance.resolveHologram(parsed, {
|
|
381
|
-
followHolograms: resolveHolograms,
|
|
382
|
-
visited: visited,
|
|
383
|
-
maxDepth: options.maxDepth || 10,
|
|
384
|
-
currentDepth: options.currentDepth || 0
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
if (resolvedValue === null) {
|
|
388
|
-
// This means resolveHologram determined the target doesn't exist or encountered an error
|
|
389
|
-
console.warn(`Broken hologram detected at ${holon}/${lens}/${key}. Removing it...`);
|
|
390
|
-
|
|
391
|
-
try {
|
|
392
|
-
// Delete the broken hologram
|
|
393
|
-
await holoInstance.delete(holon, lens, key, password);
|
|
394
|
-
console.log(`Successfully removed broken hologram from ${holon}/${lens}/${key}`);
|
|
395
|
-
} catch (cleanupError) {
|
|
396
|
-
console.error(`Failed to remove broken hologram at ${holon}/${lens}/${key}:`, cleanupError);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
resolve(null);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
// If resolveHologram encountered a circular ref, it would throw, not return.
|
|
403
|
-
// If it returned the hologram itself (if we ever revert to that), this logic would need adjustment.
|
|
404
|
-
// For now, assume resolvedValue is either the resolved data or we've returned null above.
|
|
405
|
-
|
|
406
|
-
if (resolvedValue !== parsed) {
|
|
407
|
-
parsed = resolvedValue;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Perform schema validation if needed
|
|
412
|
-
if (schema) {
|
|
413
|
-
const valid = holoInstance.validator.validate(schema, parsed);
|
|
414
|
-
if (!valid) {
|
|
415
|
-
console.error('get: Invalid data according to schema:', holoInstance.validator.errors);
|
|
416
|
-
if (holoInstance.strict) {
|
|
417
|
-
resolve(null);
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
resolve(parsed);
|
|
424
|
-
} catch (error) {
|
|
425
|
-
if (error.message?.startsWith('CIRCULAR_REFERENCE')) {
|
|
426
|
-
console.warn(`Caught circular reference during get/handleData for key ${key}. Resolving null.`);
|
|
427
|
-
resolve(null);
|
|
428
|
-
} else {
|
|
429
|
-
console.error('Error processing data in get/handleData:', error);
|
|
430
|
-
resolve(null); // For other errors, resolve null
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
const dataPath = password ?
|
|
436
|
-
user.get('private').get(lens).get(key) :
|
|
437
|
-
holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
|
|
438
|
-
|
|
439
|
-
dataPath.once(handleData);
|
|
440
|
-
});
|
|
441
|
-
} catch (error) {
|
|
442
|
-
console.error('Error in get:', error);
|
|
443
|
-
return null;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Retrieves all content from the specified holon and lens.
|
|
449
|
-
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
450
|
-
* @param {string} holon - The holon identifier.
|
|
451
|
-
* @param {string} lens - The lens from which to retrieve content.
|
|
452
|
-
* @param {string} [password] - Optional password for private holon.
|
|
453
|
-
* @returns {Promise<Array<object>>} - The retrieved content.
|
|
454
|
-
*/
|
|
455
|
-
export async function getAll(holoInstance, holon, lens, password = null) {
|
|
456
|
-
if (!holon || !lens) {
|
|
457
|
-
throw new Error('getAll: Missing required parameters');
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const schema = await holoInstance.getSchema(lens);
|
|
461
|
-
if (!schema && holoInstance.strict) {
|
|
462
|
-
throw new Error('getAll: Schema required in strict mode');
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
try {
|
|
466
|
-
let user = null;
|
|
467
|
-
if (password) {
|
|
468
|
-
user = holoInstance.gun.user();
|
|
469
|
-
await new Promise((resolve, reject) => {
|
|
470
|
-
const userNameString = holoInstance.userName(holon); // Use holon for getAll
|
|
471
|
-
user.auth(userNameString, password, (authAck) => {
|
|
472
|
-
if (authAck.err) {
|
|
473
|
-
console.log(`Initial auth failed for ${userNameString} during getAll, attempting to create...`);
|
|
474
|
-
user.create(userNameString, password, (createAck) => {
|
|
475
|
-
if (createAck.err) {
|
|
476
|
-
if (createAck.err.includes("already created") || createAck.err.includes("already being created")) {
|
|
477
|
-
console.log(`User ${userNameString} already existed or being created during getAll, re-attempting auth with fresh user object.`);
|
|
478
|
-
const freshUser = holoInstance.gun.user(); // Get a new user object
|
|
479
|
-
freshUser.auth(userNameString, password, (secondAuthAck) => {
|
|
480
|
-
if (secondAuthAck.err) {
|
|
481
|
-
console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`);
|
|
482
|
-
resolve(); // Resolve anyway to allow test operations
|
|
483
|
-
} else {
|
|
484
|
-
resolve();
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
} else {
|
|
488
|
-
console.log(`Create user error (resolving anyway for operations): ${createAck.err}`);
|
|
489
|
-
resolve(); // Resolve anyway to allow test operations
|
|
490
|
-
}
|
|
491
|
-
} else {
|
|
492
|
-
console.log(`User ${userNameString} created successfully during getAll, attempting auth...`);
|
|
493
|
-
user.auth(userNameString, password, (secondAuthAck) => {
|
|
494
|
-
if (secondAuthAck.err) {
|
|
495
|
-
reject(new Error(`Failed to auth after create for ${userNameString} during getAll: ${secondAuthAck.err}`));
|
|
496
|
-
} else {
|
|
497
|
-
resolve();
|
|
498
|
-
}
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
});
|
|
502
|
-
} else {
|
|
503
|
-
resolve(); // Auth successful
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
return new Promise((resolve) => {
|
|
510
|
-
const output = new Map();
|
|
511
|
-
|
|
512
|
-
const processData = async (data, key) => {
|
|
513
|
-
if (!data || key === '_') return;
|
|
514
|
-
|
|
515
|
-
try {
|
|
516
|
-
const parsed = await holoInstance.parse(data); // Use instance's parse
|
|
517
|
-
if (!parsed || !parsed.id) return;
|
|
518
|
-
|
|
519
|
-
// Check if this is a hologram that needs to be resolved
|
|
520
|
-
if (holoInstance.isHologram(parsed)) {
|
|
521
|
-
try {
|
|
522
|
-
const resolved = await holoInstance.resolveHologram(parsed, {
|
|
523
|
-
followHolograms: true,
|
|
524
|
-
maxDepth: 10,
|
|
525
|
-
currentDepth: 0
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
if (resolved === null) {
|
|
529
|
-
console.warn(`Broken hologram detected in getAll for key ${key}. Removing it...`);
|
|
530
|
-
|
|
531
|
-
try {
|
|
532
|
-
// Delete the broken hologram
|
|
533
|
-
await holoInstance.delete(holon, lens, key, password);
|
|
534
|
-
console.log(`Successfully removed broken hologram from ${holon}/${lens}/${key}`);
|
|
535
|
-
} catch (cleanupError) {
|
|
536
|
-
console.error(`Failed to remove broken hologram at ${holon}/${lens}/${key}:`, cleanupError);
|
|
537
|
-
}
|
|
538
|
-
return; // Skip adding this item to output
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (resolved && resolved !== parsed) {
|
|
542
|
-
// Hologram was resolved successfully
|
|
543
|
-
if (schema) {
|
|
544
|
-
const valid = holoInstance.validator.validate(schema, resolved);
|
|
545
|
-
if (valid || !holoInstance.strict) {
|
|
546
|
-
output.set(resolved.id, resolved);
|
|
547
|
-
}
|
|
548
|
-
} else {
|
|
549
|
-
output.set(resolved.id, resolved);
|
|
550
|
-
}
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
} catch (hologramError) {
|
|
554
|
-
console.error(`Error resolving hologram for key ${key}:`, hologramError);
|
|
555
|
-
return; // Skip this item
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
if (schema) {
|
|
560
|
-
const valid = holoInstance.validator.validate(schema, parsed);
|
|
561
|
-
if (valid || !holoInstance.strict) {
|
|
562
|
-
output.set(parsed.id, parsed);
|
|
563
|
-
}
|
|
564
|
-
} else {
|
|
565
|
-
output.set(parsed.id, parsed);
|
|
566
|
-
}
|
|
567
|
-
} catch (error) {
|
|
568
|
-
console.error('Error processing data:', error);
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
const handleData = async (data) => {
|
|
573
|
-
if (!data) {
|
|
574
|
-
resolve([]);
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const initialPromises = [];
|
|
579
|
-
Object.keys(data)
|
|
580
|
-
.filter(key => key !== '_')
|
|
581
|
-
.forEach(key => {
|
|
582
|
-
initialPromises.push(processData(data[key], key));
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
try {
|
|
586
|
-
await Promise.all(initialPromises);
|
|
587
|
-
resolve(Array.from(output.values()));
|
|
588
|
-
} catch (error) {
|
|
589
|
-
console.error('Error in getAll:', error);
|
|
590
|
-
resolve([]);
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
const dataPath = password ?
|
|
595
|
-
user.get('private').get(lens) :
|
|
596
|
-
holoInstance.gun.get(holoInstance.appname).get(holon).get(lens);
|
|
597
|
-
|
|
598
|
-
dataPath.once(handleData);
|
|
599
|
-
});
|
|
600
|
-
} catch (error) {
|
|
601
|
-
console.error('Error in getAll:', error);
|
|
602
|
-
return [];
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* Parses data from GunDB, handling various data formats and references.
|
|
608
|
-
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
609
|
-
* @param {*} data - The data to parse, could be a string, object, or GunDB reference.
|
|
610
|
-
* @returns {Promise<object>} - The parsed data.
|
|
611
|
-
*/
|
|
612
|
-
export async function parse(holoInstance, rawData) {
|
|
613
|
-
if (rawData === null || rawData === undefined) {
|
|
614
|
-
console.warn('Parse received null or undefined data.');
|
|
615
|
-
return null;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// 1. Handle string data (attempt JSON parse)
|
|
619
|
-
if (typeof rawData === 'string') {
|
|
620
|
-
try {
|
|
621
|
-
return JSON.parse(rawData);
|
|
622
|
-
} catch (error) {
|
|
623
|
-
// It's a string, but not valid JSON. Return null.
|
|
624
|
-
console.warn("Data was a string but not valid JSON, returning null:", rawData);
|
|
625
|
-
return null;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// 2. Handle object data
|
|
630
|
-
if (typeof rawData === 'object' && rawData !== null) {
|
|
631
|
-
// Check for GunDB soul link (less common now?)
|
|
632
|
-
if (rawData.soul && typeof rawData.soul === 'string' && rawData.id) {
|
|
633
|
-
// This looks like a Hologram object based on structure.
|
|
634
|
-
// Return it as is; resolution happens later if needed.
|
|
635
|
-
return rawData;
|
|
636
|
-
} else if (holoInstance.isHologram(rawData)) {
|
|
637
|
-
// Explicitly check using isHologram (might be redundant if structure check above is reliable)
|
|
638
|
-
return rawData;
|
|
639
|
-
} else if (rawData._) {
|
|
640
|
-
// Handle potential GunDB metadata remnants (attempt cleanup)
|
|
641
|
-
console.warn('Parsing raw Gun object with metadata (_) - attempting cleanup:', rawData);
|
|
642
|
-
const potentialData = Object.keys(rawData).reduce((acc, k) => {
|
|
643
|
-
if (k !== '_') {
|
|
644
|
-
acc[k] = rawData[k];
|
|
645
|
-
}
|
|
646
|
-
return acc;
|
|
647
|
-
}, {});
|
|
648
|
-
if (Object.keys(potentialData).length === 0) {
|
|
649
|
-
console.warn('Raw Gun object had only metadata (_), returning null.');
|
|
650
|
-
return null;
|
|
651
|
-
}
|
|
652
|
-
return potentialData; // Return cleaned-up object
|
|
653
|
-
} else {
|
|
654
|
-
// Assume it's a regular plain object
|
|
655
|
-
return rawData;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// 3. Handle other unexpected types
|
|
660
|
-
console.warn("Parsing encountered unexpected data type, returning null:", typeof rawData, rawData);
|
|
661
|
-
return null;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* Deletes a specific key from a given holon and lens.
|
|
666
|
-
* If the deleted data was a hologram, this function also attempts to update the
|
|
667
|
-
* target data node's `_holograms` set by marking the deleted hologram's soul as 'DELETED'.
|
|
668
|
-
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
669
|
-
* @param {string} holon - The holon identifier.
|
|
670
|
-
* @param {string} lens - The lens from which to delete the key.
|
|
671
|
-
* @param {string} key - The specific key to delete.
|
|
672
|
-
* @param {string} [password] - Optional password for private holon.
|
|
673
|
-
* @returns {Promise<boolean>} - Returns true if successful
|
|
674
|
-
*/
|
|
675
|
-
export async function deleteFunc(holoInstance, holon, lens, key, password = null) { // Renamed to deleteFunc to avoid keyword conflict
|
|
676
|
-
if (!holon || !lens || !key) {
|
|
677
|
-
throw new Error('delete: Missing required parameters');
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
try {
|
|
681
|
-
let user = null;
|
|
682
|
-
if (password) {
|
|
683
|
-
user = holoInstance.gun.user();
|
|
684
|
-
await new Promise((resolve, reject) => {
|
|
685
|
-
const userNameString = holoInstance.userName(holon); // Use holon for deleteFunc
|
|
686
|
-
user.auth(userNameString, password, (authAck) => {
|
|
687
|
-
if (authAck.err) {
|
|
688
|
-
console.log(`Initial auth failed for ${userNameString} during deleteFunc, attempting to create...`);
|
|
689
|
-
user.create(userNameString, password, (createAck) => {
|
|
690
|
-
if (createAck.err) {
|
|
691
|
-
if (createAck.err.includes("already created")) {
|
|
692
|
-
console.log(`User ${userNameString} already existed during deleteFunc, re-attempting auth with fresh user object.`);
|
|
693
|
-
const freshUser = holoInstance.gun.user(); // Get a new user object
|
|
694
|
-
freshUser.auth(userNameString, password, (secondAuthAck) => {
|
|
695
|
-
if (secondAuthAck.err) {
|
|
696
|
-
reject(new Error(`Failed to auth with fresh user object after create attempt (user existed) for ${userNameString} during deleteFunc: ${secondAuthAck.err}`));
|
|
697
|
-
} else {
|
|
698
|
-
resolve();
|
|
699
|
-
}
|
|
700
|
-
});
|
|
701
|
-
} else {
|
|
702
|
-
reject(new Error(`Failed to create user ${userNameString} during deleteFunc: ${createAck.err}`));
|
|
703
|
-
}
|
|
704
|
-
} else {
|
|
705
|
-
console.log(`User ${userNameString} created successfully during deleteFunc, attempting auth...`);
|
|
706
|
-
user.auth(userNameString, password, (secondAuthAck) => {
|
|
707
|
-
if (secondAuthAck.err) {
|
|
708
|
-
reject(new Error(`Failed to auth after create for ${userNameString} during deleteFunc: ${secondAuthAck.err}`));
|
|
709
|
-
} else {
|
|
710
|
-
resolve();
|
|
711
|
-
}
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
} else {
|
|
716
|
-
resolve(); // Auth successful
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
const dataPath = password ?
|
|
723
|
-
user.get('private').get(lens).get(key) :
|
|
724
|
-
holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
|
|
725
|
-
|
|
726
|
-
// --- Start: Hologram Tracking Removal ---
|
|
727
|
-
let trackingRemovalPromise = Promise.resolve(); // Default to resolved promise
|
|
728
|
-
|
|
729
|
-
// 1. Get the data first to check if it's a hologram
|
|
730
|
-
const rawDataToDelete = await new Promise((resolve) => dataPath.once(resolve));
|
|
731
|
-
let dataToDelete = null;
|
|
732
|
-
try {
|
|
733
|
-
if (typeof rawDataToDelete === 'string') {
|
|
734
|
-
dataToDelete = JSON.parse(rawDataToDelete);
|
|
735
|
-
} else {
|
|
736
|
-
// Handle cases where it might already be an object (though likely string)
|
|
737
|
-
dataToDelete = rawDataToDelete;
|
|
738
|
-
}
|
|
739
|
-
} catch(e) {
|
|
740
|
-
console.warn("[deleteFunc] Could not JSON parse data for deletion check:", rawDataToDelete, e);
|
|
741
|
-
dataToDelete = null; // Ensure it's null if parsing fails
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// 2. If it is a hologram, try to remove its reference from the target
|
|
745
|
-
const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete);
|
|
746
|
-
|
|
747
|
-
if (isDataHologram) {
|
|
748
|
-
try {
|
|
749
|
-
const targetSoul = dataToDelete.soul;
|
|
750
|
-
const targetSoulInfo = holoInstance.parseSoulPath(targetSoul);
|
|
751
|
-
|
|
752
|
-
if (targetSoulInfo) {
|
|
753
|
-
const targetNodeRef = holoInstance.getNodeRef(targetSoul);
|
|
754
|
-
const deletedHologramSoul = `${holoInstance.appname}/${holon}/${lens}/${key}`;
|
|
755
|
-
|
|
756
|
-
// Create a promise that resolves when the hologram is removed from the list
|
|
757
|
-
trackingRemovalPromise = new Promise((resolveTrack) => { // No reject needed, just warn on error
|
|
758
|
-
targetNodeRef.get('_holograms').get(deletedHologramSoul).put(null, (ack) => { // Remove the hologram entry completely
|
|
759
|
-
if (ack.err) {
|
|
760
|
-
console.warn(`[deleteFunc] Error removing hologram ${deletedHologramSoul} from target ${targetSoul}:`, ack.err);
|
|
761
|
-
}
|
|
762
|
-
resolveTrack(); // Resolve regardless of ack error to not block main delete
|
|
763
|
-
});
|
|
764
|
-
});
|
|
765
|
-
} else {
|
|
766
|
-
// Keep this warning
|
|
767
|
-
console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal.`);
|
|
768
|
-
}
|
|
769
|
-
} catch (trackingError) {
|
|
770
|
-
// Keep this warning
|
|
771
|
-
console.warn(`Error initiating hologram reference removal from target ${dataToDelete.soul}:`, trackingError);
|
|
772
|
-
// Ensure trackingRemovalPromise remains resolved if setup fails
|
|
773
|
-
trackingRemovalPromise = Promise.resolve();
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
// --- End: Hologram Tracking Removal ---
|
|
777
|
-
|
|
778
|
-
// 3. Wait for the tracking removal attempt to be acknowledged
|
|
779
|
-
await trackingRemovalPromise;
|
|
780
|
-
// Log removed
|
|
781
|
-
|
|
782
|
-
// 4. Proceed with the actual deletion of the hologram node itself
|
|
783
|
-
return new Promise((resolve, reject) => {
|
|
784
|
-
dataPath.put(null, ack => {
|
|
785
|
-
if (ack.err) {
|
|
786
|
-
reject(new Error(ack.err));
|
|
787
|
-
} else {
|
|
788
|
-
resolve(true);
|
|
789
|
-
}
|
|
790
|
-
});
|
|
791
|
-
});
|
|
792
|
-
} catch (error) {
|
|
793
|
-
console.error('Error in delete:', error);
|
|
794
|
-
throw error;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
/**
|
|
799
|
-
* Deletes all keys from a given holon and lens.
|
|
800
|
-
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
801
|
-
* @param {string} holon - The holon identifier.
|
|
802
|
-
* @param {string} lens - The lens from which to delete all keys.
|
|
803
|
-
* @param {string} [password] - Optional password for private holon.
|
|
804
|
-
* @returns {Promise<boolean>} - Returns true if successful
|
|
805
|
-
*/
|
|
806
|
-
export async function deleteAll(holoInstance, holon, lens, password = null) {
|
|
807
|
-
if (!holon || !lens) {
|
|
808
|
-
console.error('deleteAll: Missing holon or lens parameter');
|
|
809
|
-
return false;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
try {
|
|
813
|
-
let user = null;
|
|
814
|
-
if (password) {
|
|
815
|
-
user = holoInstance.gun.user();
|
|
816
|
-
await new Promise((resolve, reject) => {
|
|
817
|
-
const userNameString = holoInstance.userName(holon); // Use holon for deleteAll
|
|
818
|
-
user.auth(userNameString, password, (authAck) => {
|
|
819
|
-
if (authAck.err) {
|
|
820
|
-
console.log(`Initial auth failed for ${userNameString} during deleteAll, attempting to create...`);
|
|
821
|
-
user.create(userNameString, password, (createAck) => {
|
|
822
|
-
if (createAck.err) {
|
|
823
|
-
if (createAck.err.includes("already created") || createAck.err.includes("already being created")) {
|
|
824
|
-
console.log(`User ${userNameString} already existed or being created during deleteAll, re-attempting auth with fresh user object.`);
|
|
825
|
-
const freshUser = holoInstance.gun.user(); // Get a new user object
|
|
826
|
-
freshUser.auth(userNameString, password, (secondAuthAck) => {
|
|
827
|
-
if (secondAuthAck.err) {
|
|
828
|
-
console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`);
|
|
829
|
-
resolve(); // Resolve anyway to allow test operations
|
|
830
|
-
} else {
|
|
831
|
-
resolve();
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
} else {
|
|
835
|
-
console.log(`Create user error (resolving anyway for operations): ${createAck.err}`);
|
|
836
|
-
resolve(); // Resolve anyway to allow test operations
|
|
837
|
-
}
|
|
838
|
-
} else {
|
|
839
|
-
console.log(`User ${userNameString} created successfully during deleteAll, attempting auth...`);
|
|
840
|
-
user.auth(userNameString, password, (secondAuthAck) => {
|
|
841
|
-
if (secondAuthAck.err) {
|
|
842
|
-
reject(new Error(`Failed to auth after create for ${userNameString} during deleteAll: ${secondAuthAck.err}`));
|
|
843
|
-
} else {
|
|
844
|
-
resolve();
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
} else {
|
|
850
|
-
resolve(); // Auth successful
|
|
851
|
-
}
|
|
852
|
-
});
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
return new Promise((resolve) => {
|
|
857
|
-
let deletionPromises = [];
|
|
858
|
-
|
|
859
|
-
const dataPath = password ?
|
|
860
|
-
user.get('private').get(lens) :
|
|
861
|
-
holoInstance.gun.get(holoInstance.appname).get(holon).get(lens);
|
|
862
|
-
|
|
863
|
-
// First get all the data to find keys to delete
|
|
864
|
-
dataPath.once(async (data) => {
|
|
865
|
-
if (!data) {
|
|
866
|
-
resolve(true); // Nothing to delete
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// Get all keys except Gun's metadata key '_'
|
|
871
|
-
const keys = Object.keys(data).filter(key => key !== '_');
|
|
872
|
-
|
|
873
|
-
// Process each key to handle holograms properly
|
|
874
|
-
for (const key of keys) {
|
|
875
|
-
try {
|
|
876
|
-
// Get the data to check if it's a hologram
|
|
877
|
-
const itemPath = password ?
|
|
878
|
-
user.get('private').get(lens).get(key) :
|
|
879
|
-
holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
|
|
880
|
-
|
|
881
|
-
const rawDataToDelete = await new Promise((resolveItem) => itemPath.once(resolveItem));
|
|
882
|
-
let dataToDelete = null;
|
|
883
|
-
|
|
884
|
-
try {
|
|
885
|
-
if (typeof rawDataToDelete === 'string') {
|
|
886
|
-
dataToDelete = JSON.parse(rawDataToDelete);
|
|
887
|
-
} else {
|
|
888
|
-
dataToDelete = rawDataToDelete;
|
|
889
|
-
}
|
|
890
|
-
} catch(e) {
|
|
891
|
-
console.warn("[deleteAll] Could not JSON parse data for deletion check:", rawDataToDelete, e);
|
|
892
|
-
dataToDelete = null;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
// Check if it's a hologram and handle accordingly
|
|
896
|
-
const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete);
|
|
897
|
-
|
|
898
|
-
if (isDataHologram) {
|
|
899
|
-
// Handle hologram deletion - remove from target's _holograms list
|
|
900
|
-
try {
|
|
901
|
-
const targetSoul = dataToDelete.soul;
|
|
902
|
-
const targetSoulInfo = holoInstance.parseSoulPath(targetSoul);
|
|
903
|
-
|
|
904
|
-
if (targetSoulInfo) {
|
|
905
|
-
const targetNodeRef = holoInstance.getNodeRef(targetSoul);
|
|
906
|
-
const deletedHologramSoul = `${holoInstance.appname}/${holon}/${lens}/${key}`;
|
|
907
|
-
|
|
908
|
-
// Remove the hologram from target's _holograms list
|
|
909
|
-
await new Promise((resolveTrack) => {
|
|
910
|
-
targetNodeRef.get('_holograms').get(deletedHologramSoul).put(null, (ack) => {
|
|
911
|
-
if (ack.err) {
|
|
912
|
-
console.warn(`[deleteAll] Error removing hologram ${deletedHologramSoul} from target ${targetSoul}:`, ack.err);
|
|
913
|
-
}
|
|
914
|
-
resolveTrack();
|
|
915
|
-
});
|
|
916
|
-
});
|
|
917
|
-
} else {
|
|
918
|
-
console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal during deleteAll.`);
|
|
919
|
-
}
|
|
920
|
-
} catch (trackingError) {
|
|
921
|
-
console.warn(`Error removing hologram reference from target ${dataToDelete.soul} during deleteAll:`, trackingError);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
// Create deletion promise for this key (whether it's a hologram or not)
|
|
926
|
-
deletionPromises.push(
|
|
927
|
-
new Promise((resolveDelete) => {
|
|
928
|
-
const deletePath = password ?
|
|
929
|
-
user.get('private').get(lens).get(key) :
|
|
930
|
-
holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
|
|
931
|
-
|
|
932
|
-
deletePath.put(null, ack => {
|
|
933
|
-
resolveDelete(!!ack.ok); // Convert to boolean
|
|
934
|
-
});
|
|
935
|
-
})
|
|
936
|
-
);
|
|
937
|
-
} catch (error) {
|
|
938
|
-
console.warn(`Error processing key ${key} during deleteAll:`, error);
|
|
939
|
-
// Still try to delete the item even if hologram processing failed
|
|
940
|
-
deletionPromises.push(
|
|
941
|
-
new Promise((resolveDelete) => {
|
|
942
|
-
const deletePath = password ?
|
|
943
|
-
user.get('private').get(lens).get(key) :
|
|
944
|
-
holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
|
|
945
|
-
|
|
946
|
-
deletePath.put(null, ack => {
|
|
947
|
-
resolveDelete(!!ack.ok);
|
|
948
|
-
});
|
|
949
|
-
})
|
|
950
|
-
);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// Wait for all deletions to complete
|
|
955
|
-
Promise.all(deletionPromises)
|
|
956
|
-
.then(results => {
|
|
957
|
-
const allSuccessful = results.every(result => result === true);
|
|
958
|
-
resolve(allSuccessful);
|
|
959
|
-
})
|
|
960
|
-
.catch(error => {
|
|
961
|
-
console.error('Error in deleteAll:', error);
|
|
962
|
-
resolve(false);
|
|
963
|
-
});
|
|
964
|
-
});
|
|
965
|
-
});
|
|
966
|
-
} catch (error) {
|
|
967
|
-
console.error('Error in deleteAll:', error);
|
|
968
|
-
return false;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// Export all content operations as default
|
|
973
|
-
export default {
|
|
974
|
-
put,
|
|
975
|
-
get,
|
|
976
|
-
getAll,
|
|
977
|
-
parse,
|
|
978
|
-
delete: deleteFunc,
|
|
979
|
-
deleteAll
|
|
980
|
-
};
|