holosphere 1.1.19 → 2.0.0-alpha0

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.
Files changed (146) hide show
  1. package/.env.example +36 -0
  2. package/.eslintrc.json +16 -0
  3. package/.prettierrc.json +7 -0
  4. package/README.md +476 -531
  5. package/bin/holosphere-activitypub.js +158 -0
  6. package/cleanup-test-data.js +204 -0
  7. package/examples/demo.html +1333 -0
  8. package/examples/example-bot.js +197 -0
  9. package/package.json +47 -87
  10. package/scripts/check-bundle-size.js +54 -0
  11. package/scripts/check-quest-ids.js +77 -0
  12. package/scripts/import-holons.js +578 -0
  13. package/scripts/publish-to-relay.js +101 -0
  14. package/scripts/read-example.js +186 -0
  15. package/scripts/relay-diagnostic.js +59 -0
  16. package/scripts/relay-example.js +179 -0
  17. package/scripts/resync-to-relay.js +245 -0
  18. package/scripts/revert-import.js +196 -0
  19. package/scripts/test-hybrid-mode.js +108 -0
  20. package/scripts/test-local-storage.js +63 -0
  21. package/scripts/test-nostr-direct.js +55 -0
  22. package/scripts/test-read-data.js +45 -0
  23. package/scripts/test-write-read.js +63 -0
  24. package/scripts/verify-import.js +95 -0
  25. package/scripts/verify-relay-data.js +139 -0
  26. package/src/ai/aggregation.js +319 -0
  27. package/src/ai/breakdown.js +511 -0
  28. package/src/ai/classifier.js +217 -0
  29. package/src/ai/council.js +228 -0
  30. package/src/ai/embeddings.js +279 -0
  31. package/src/ai/federation-ai.js +324 -0
  32. package/src/ai/h3-ai.js +955 -0
  33. package/src/ai/index.js +112 -0
  34. package/src/ai/json-ops.js +225 -0
  35. package/src/ai/llm-service.js +205 -0
  36. package/src/ai/nl-query.js +223 -0
  37. package/src/ai/relationships.js +353 -0
  38. package/src/ai/schema-extractor.js +218 -0
  39. package/src/ai/spatial.js +293 -0
  40. package/src/ai/tts.js +194 -0
  41. package/src/content/social-protocols.js +168 -0
  42. package/src/core/holosphere.js +273 -0
  43. package/src/crypto/secp256k1.js +259 -0
  44. package/src/federation/discovery.js +334 -0
  45. package/src/federation/hologram.js +1042 -0
  46. package/src/federation/registry.js +386 -0
  47. package/src/hierarchical/upcast.js +110 -0
  48. package/src/index.js +2669 -0
  49. package/src/schema/validator.js +91 -0
  50. package/src/spatial/h3-operations.js +110 -0
  51. package/src/storage/backend-factory.js +125 -0
  52. package/src/storage/backend-interface.js +142 -0
  53. package/src/storage/backends/activitypub/server.js +653 -0
  54. package/src/storage/backends/activitypub-backend.js +272 -0
  55. package/src/storage/backends/gundb-backend.js +233 -0
  56. package/src/storage/backends/nostr-backend.js +136 -0
  57. package/src/storage/filesystem-storage-browser.js +41 -0
  58. package/src/storage/filesystem-storage.js +138 -0
  59. package/src/storage/global-tables.js +81 -0
  60. package/src/storage/gun-async.js +281 -0
  61. package/src/storage/gun-wrapper.js +221 -0
  62. package/src/storage/indexeddb-storage.js +122 -0
  63. package/src/storage/key-storage-simple.js +76 -0
  64. package/src/storage/key-storage.js +136 -0
  65. package/src/storage/memory-storage.js +59 -0
  66. package/src/storage/migration.js +338 -0
  67. package/src/storage/nostr-async.js +811 -0
  68. package/src/storage/nostr-client.js +939 -0
  69. package/src/storage/nostr-wrapper.js +211 -0
  70. package/src/storage/outbox-queue.js +208 -0
  71. package/src/storage/persistent-storage.js +109 -0
  72. package/src/storage/sync-service.js +164 -0
  73. package/src/subscriptions/manager.js +142 -0
  74. package/test-ai-real-api.js +202 -0
  75. package/tests/unit/ai/aggregation.test.js +295 -0
  76. package/tests/unit/ai/breakdown.test.js +446 -0
  77. package/tests/unit/ai/classifier.test.js +294 -0
  78. package/tests/unit/ai/council.test.js +262 -0
  79. package/tests/unit/ai/embeddings.test.js +384 -0
  80. package/tests/unit/ai/federation-ai.test.js +344 -0
  81. package/tests/unit/ai/h3-ai.test.js +458 -0
  82. package/tests/unit/ai/index.test.js +304 -0
  83. package/tests/unit/ai/json-ops.test.js +307 -0
  84. package/tests/unit/ai/llm-service.test.js +390 -0
  85. package/tests/unit/ai/nl-query.test.js +383 -0
  86. package/tests/unit/ai/relationships.test.js +311 -0
  87. package/tests/unit/ai/schema-extractor.test.js +384 -0
  88. package/tests/unit/ai/spatial.test.js +279 -0
  89. package/tests/unit/ai/tts.test.js +279 -0
  90. package/tests/unit/content.test.js +332 -0
  91. package/tests/unit/contract/core.test.js +88 -0
  92. package/tests/unit/contract/crypto.test.js +198 -0
  93. package/tests/unit/contract/data.test.js +223 -0
  94. package/tests/unit/contract/federation.test.js +181 -0
  95. package/tests/unit/contract/hierarchical.test.js +113 -0
  96. package/tests/unit/contract/schema.test.js +114 -0
  97. package/tests/unit/contract/social.test.js +217 -0
  98. package/tests/unit/contract/spatial.test.js +110 -0
  99. package/tests/unit/contract/subscriptions.test.js +128 -0
  100. package/tests/unit/contract/utils.test.js +159 -0
  101. package/tests/unit/core.test.js +152 -0
  102. package/tests/unit/crypto.test.js +328 -0
  103. package/tests/unit/federation.test.js +234 -0
  104. package/tests/unit/gun-async.test.js +252 -0
  105. package/tests/unit/hierarchical.test.js +399 -0
  106. package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
  107. package/tests/unit/integration/scenario-02-federation.test.js +76 -0
  108. package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
  109. package/tests/unit/integration/scenario-04-validation.test.js +129 -0
  110. package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
  111. package/tests/unit/integration/scenario-06-social.test.js +135 -0
  112. package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
  113. package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
  114. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
  115. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
  116. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
  117. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
  118. package/tests/unit/performance/benchmark.test.js +85 -0
  119. package/tests/unit/schema.test.js +213 -0
  120. package/tests/unit/spatial.test.js +158 -0
  121. package/tests/unit/storage.test.js +195 -0
  122. package/tests/unit/subscriptions.test.js +328 -0
  123. package/tests/unit/test-data-permanence-debug.js +197 -0
  124. package/tests/unit/test-data-permanence.js +340 -0
  125. package/tests/unit/test-key-persistence-fixed.js +148 -0
  126. package/tests/unit/test-key-persistence.js +172 -0
  127. package/tests/unit/test-relay-permanence.js +376 -0
  128. package/tests/unit/test-second-node.js +95 -0
  129. package/tests/unit/test-simple-write.js +89 -0
  130. package/vite.config.js +49 -0
  131. package/vitest.config.js +20 -0
  132. package/FEDERATION.md +0 -213
  133. package/compute.js +0 -298
  134. package/content.js +0 -1022
  135. package/federation.js +0 -1234
  136. package/global.js +0 -736
  137. package/hexlib.js +0 -335
  138. package/hologram.js +0 -183
  139. package/holosphere-bundle.esm.js +0 -34549
  140. package/holosphere-bundle.js +0 -34580
  141. package/holosphere-bundle.min.js +0 -49
  142. package/holosphere.d.ts +0 -604
  143. package/holosphere.js +0 -739
  144. package/node.js +0 -246
  145. package/schema.js +0 -139
  146. package/utils.js +0 -302
package/node.js DELETED
@@ -1,246 +0,0 @@
1
- // holo_node.js
2
-
3
- /**
4
- * Stores a specific gun node in a given holon and lens.
5
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
6
- * @param {string} holon - The holon identifier.
7
- * @param {string} lens - The lens under which to store the node.
8
- * @param {object} data - The node to store.
9
- */
10
- export async function putNode(holoInstance, holon, lens, data) {
11
- if (!holon || !lens || !data) {
12
- throw new Error('putNode: Missing required parameters');
13
- }
14
-
15
- return new Promise((resolve, reject) => {
16
- try {
17
- // Remove isHologram field before storing - NO LONGER NEEDED
18
- // if (data && data.isHologram !== undefined) {
19
- // delete data.isHologram;
20
- // }
21
-
22
- // Check if the data being stored is a hologram
23
- const isHologram = data.value && holoInstance.isHologram(data.value);
24
-
25
- holoInstance.gun.get(holoInstance.appname)
26
- .get(holon)
27
- .get(lens)
28
- .get('value') // Store at 'value' key
29
- .put(data.value, ack => { // Store the value directly
30
- if (ack.err) {
31
- reject(new Error(ack.err));
32
- } else {
33
- // --- Start: Hologram Tracking Logic (for data *being put*, if it's a hologram) ---
34
- if (isHologram) {
35
- try {
36
- const storedDataSoulInfo = holoInstance.parseSoulPath(data.value.soul);
37
- if (storedDataSoulInfo) {
38
- const targetNodeRef = holoInstance.getNodeRef(data.value.soul); // Target of the data *being put*
39
- // Soul of the hologram that was *actually stored* at holon/lens/value
40
- const storedHologramInstanceSoul = `${holoInstance.appname}/${holon}/${lens}/value`;
41
-
42
- targetNodeRef.get('_holograms').get(storedHologramInstanceSoul).put(true);
43
- } else {
44
- console.warn(`Data (ID: ${data.id}) being put is a hologram, but could not parse its soul ${data.value.soul} for tracking.`);
45
- }
46
- } catch (trackingError) {
47
- console.warn(`Error updating _holograms set for the target of the data being put (data ID: ${data.id}, soul: ${data.value.soul}):`, trackingError);
48
- }
49
- }
50
- // --- End: Hologram Tracking Logic ---
51
-
52
- resolve(true);
53
- }
54
- });
55
- } catch (error) {
56
- reject(error);
57
- }
58
- });
59
- }
60
-
61
- /**
62
- * Retrieves a specific gun node from the specified holon and lens.
63
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
64
- * @param {string} holon - The holon identifier.
65
- * @param {string} lens - The lens identifier.
66
- * @param {string} key - The specific key to retrieve.
67
- * @returns {Promise<any>} - The retrieved node or null if not found.
68
- */
69
- export async function getNode(holoInstance, holon, lens, key) {
70
- if (!holon || !lens || !key) {
71
- throw new Error('getNode: Missing required parameters');
72
- }
73
-
74
- return new Promise((resolve, reject) => {
75
- try {
76
- holoInstance.gun.get(holoInstance.appname)
77
- .get(holon)
78
- .get(lens)
79
- .get(key)
80
- .once((data) => {
81
- if (!data) {
82
- resolve(null);
83
- return;
84
- }
85
- resolve(data); // Return the data directly
86
- });
87
- } catch (error) {
88
- reject(error);
89
- }
90
- });
91
- }
92
-
93
- /**
94
- * Retrieves a Gun node reference using its soul path
95
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
96
- * @param {string} soul - The soul path of the node
97
- * @returns {Gun.ChainReference} - The Gun node reference
98
- */
99
- export function getNodeRef(holoInstance, soul) {
100
- if (typeof soul !== 'string' || !soul) {
101
- throw new Error('getNodeRef: Invalid soul parameter');
102
- }
103
-
104
- const parts = soul.split('/').filter(part => {
105
- if (!part.trim() || /[<>:"/\\|?*]/.test(part)) { // Escaped backslash for regex
106
- throw new Error('getNodeRef: Invalid path segment');
107
- }
108
- return part.trim();
109
- });
110
-
111
- if (parts.length === 0) {
112
- throw new Error('getNodeRef: Invalid soul format');
113
- }
114
-
115
- let ref = holoInstance.gun.get(holoInstance.appname);
116
- parts.forEach(part => {
117
- ref = ref.get(part);
118
- });
119
- return ref;
120
- }
121
-
122
- /**
123
- * Retrieves a node directly using its soul path
124
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
125
- * @param {string} soul - The soul path of the node
126
- * @returns {Promise<any>} - The retrieved node or null if not found.
127
- */
128
- export async function getNodeBySoul(holoInstance, soul) {
129
- if (!soul) {
130
- throw new Error('getNodeBySoul: Missing soul parameter');
131
- }
132
-
133
- console.log(`getNodeBySoul: Accessing soul ${soul}`);
134
-
135
- return new Promise((resolve, reject) => {
136
- try {
137
- const ref = getNodeRef(holoInstance, soul); // Use the exported getNodeRef
138
- ref.once((data) => {
139
- console.log(`getNodeBySoul: Retrieved data:`, data);
140
- if (!data) {
141
- resolve(null);
142
- return;
143
- }
144
- resolve(data); // Return the data directly
145
- });
146
- } catch (error) {
147
- console.error(`getNodeBySoul error:`, error);
148
- reject(error);
149
- }
150
- });
151
- }
152
-
153
- /**
154
- * Deletes a specific gun node from a given holon and lens.
155
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
156
- * @param {string} holon - The holon identifier.
157
- * @param {string} lens - The lens identifier.
158
- * @param {string} key - The key of the node to delete.
159
- * @returns {Promise<boolean>} - Returns true if successful
160
- */
161
- export async function deleteNode(holoInstance, holon, lens, key) {
162
- if (!holon || !lens || !key) {
163
- throw new Error('deleteNode: Missing required parameters');
164
- }
165
-
166
- try {
167
- const dataPath = holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
168
-
169
- // --- Start: Hologram Tracking Removal ---
170
- let trackingRemovalPromise = Promise.resolve(); // Default to resolved promise
171
-
172
- // 1. Get the data first to check if it's a hologram
173
- const rawDataToDelete = await new Promise((resolve) => dataPath.once(resolve));
174
- let dataToDelete = null;
175
- try {
176
- if (typeof rawDataToDelete === 'string') {
177
- dataToDelete = JSON.parse(rawDataToDelete);
178
- } else {
179
- // Handle cases where it might already be an object (though likely string)
180
- dataToDelete = rawDataToDelete;
181
- }
182
- } catch(e) {
183
- console.warn("[deleteNode] Could not JSON parse data for deletion check:", rawDataToDelete, e);
184
- dataToDelete = null; // Ensure it's null if parsing fails
185
- }
186
-
187
- // 2. If it is a hologram, try to remove its reference from the target
188
- const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete);
189
-
190
- if (isDataHologram) {
191
- try {
192
- const targetSoul = dataToDelete.soul;
193
- const targetSoulInfo = holoInstance.parseSoulPath(targetSoul);
194
-
195
- if (targetSoulInfo) {
196
- const targetNodeRef = holoInstance.getNodeRef(targetSoul);
197
- // putNode stores at the 'value' key, not at the data.id key
198
- const deletedHologramSoul = `${holoInstance.appname}/${holon}/${lens}/value`;
199
-
200
- // Create a promise that resolves when the hologram is removed from the list
201
- trackingRemovalPromise = new Promise((resolveTrack) => { // No reject needed, just warn on error
202
- targetNodeRef.get('_holograms').get(deletedHologramSoul).put(null, (ack) => { // Remove the hologram entry completely
203
- if (ack.err) {
204
- console.warn(`[deleteNode] Error removing hologram ${deletedHologramSoul} from target ${targetSoul}:`, ack.err);
205
- }
206
- resolveTrack(); // Resolve regardless of ack error to not block main delete
207
- });
208
- });
209
- } else {
210
- console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal during deleteNode.`);
211
- }
212
- } catch (trackingError) {
213
- console.warn(`Error initiating hologram reference removal from target ${dataToDelete.soul} during deleteNode:`, trackingError);
214
- // Ensure trackingRemovalPromise remains resolved if setup fails
215
- trackingRemovalPromise = Promise.resolve();
216
- }
217
- }
218
- // --- End: Hologram Tracking Removal ---
219
-
220
- // 3. Wait for the tracking removal attempt to be acknowledged
221
- await trackingRemovalPromise;
222
-
223
- // 4. Proceed with the actual deletion of the hologram node itself
224
- return new Promise((resolve, reject) => {
225
- dataPath.put(null, ack => {
226
- if (ack.err) {
227
- reject(new Error(ack.err));
228
- } else {
229
- resolve(true);
230
- }
231
- });
232
- });
233
- } catch (error) {
234
- console.error('Error in deleteNode:', error);
235
- throw error;
236
- }
237
- }
238
-
239
- // Export all node operations as default
240
- export default {
241
- putNode,
242
- getNode,
243
- getNodeRef,
244
- getNodeBySoul,
245
- deleteNode
246
- };
package/schema.js DELETED
@@ -1,139 +0,0 @@
1
- // holo_schema.js
2
-
3
- import Ajv2019 from 'ajv/dist/2019.js';
4
-
5
- /**
6
- * Sets the JSON schema for a specific lens.
7
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
8
- * @param {string} lens - The lens identifier.
9
- * @param {object} schema - The JSON schema to set.
10
- * @returns {Promise<boolean>} - Resolves when the schema is set.
11
- */
12
- export async function setSchema(holoInstance, lens, schema) {
13
- if (!lens || !schema) {
14
- throw new Error('setSchema: Missing required parameters');
15
- }
16
-
17
- // Basic schema validation
18
- if (!schema.type || typeof schema.type !== 'string') {
19
- throw new Error('setSchema: Schema must have a type field');
20
- }
21
-
22
- const metaSchema = {
23
- type: 'object',
24
- required: ['type', 'properties'],
25
- properties: {
26
- type: { type: 'string' },
27
- properties: {
28
- type: 'object',
29
- additionalProperties: {
30
- type: 'object',
31
- required: ['type'],
32
- properties: {
33
- type: { type: 'string' }
34
- }
35
- }
36
- },
37
- required: {
38
- type: 'array',
39
- items: { type: 'string' }
40
- }
41
- }
42
- };
43
-
44
- // Use the validator from the instance
45
- const valid = holoInstance.validator.validate(metaSchema, schema);
46
- if (!valid) {
47
- throw new Error(`Invalid schema structure: ${JSON.stringify(holoInstance.validator.errors)}`);
48
- }
49
-
50
- if (!schema.properties || typeof schema.properties !== 'object') {
51
- throw new Error('Schema must have properties in strict mode');
52
- }
53
-
54
- if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
55
- throw new Error('Schema must have required fields in strict mode');
56
- }
57
-
58
- // Store schema in global table with lens as key using instance's method
59
- await holoInstance.putGlobal('schemas', {
60
- id: lens,
61
- schema: schema,
62
- timestamp: Date.now()
63
- });
64
-
65
- // Update the instance's cache with the new schema
66
- holoInstance.schemaCache.set(lens, {
67
- schema,
68
- timestamp: Date.now()
69
- });
70
-
71
- return true;
72
- }
73
-
74
- /**
75
- * Retrieves the JSON schema for a specific lens.
76
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
77
- * @param {string} lens - The lens identifier.
78
- * @param {object} [options] - Additional options
79
- * @param {boolean} [options.useCache=true] - Whether to use the schema cache
80
- * @param {number} [options.maxCacheAge=3600000] - Maximum cache age in milliseconds (default: 1 hour)
81
- * @returns {Promise<object|null>} - The retrieved schema or null if not found.
82
- */
83
- export async function getSchema(holoInstance, lens, options = {}) {
84
- if (!lens) {
85
- throw new Error('getSchema: Missing lens parameter');
86
- }
87
-
88
- const { useCache = true, maxCacheAge = 3600000 } = options;
89
-
90
- // Check instance's cache first if enabled
91
- if (useCache && holoInstance.schemaCache.has(lens)) {
92
- const cached = holoInstance.schemaCache.get(lens);
93
- const cacheAge = Date.now() - cached.timestamp;
94
-
95
- // Use cache if it's fresh enough
96
- if (cacheAge < maxCacheAge) {
97
- return cached.schema;
98
- }
99
- }
100
-
101
- // Cache miss or expired, fetch from storage using instance's method
102
- const schemaData = await holoInstance.getGlobal('schemas', lens);
103
-
104
- if (!schemaData || !schemaData.schema) {
105
- return null;
106
- }
107
-
108
- // Update instance's cache with fetched schema
109
- holoInstance.schemaCache.set(lens, {
110
- schema: schemaData.schema,
111
- timestamp: Date.now()
112
- });
113
-
114
- return schemaData.schema;
115
- }
116
-
117
- /**
118
- * Clears the schema cache or a specific schema from the cache.
119
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
120
- * @param {string} [lens] - Optional lens to clear from cache. If not provided, clears entire cache.
121
- * @returns {boolean} - Returns true if successful
122
- */
123
- export function clearSchemaCache(holoInstance, lens = null) {
124
- if (lens) {
125
- // Clear specific schema from instance's cache
126
- return holoInstance.schemaCache.delete(lens);
127
- } else {
128
- // Clear entire instance's cache
129
- holoInstance.schemaCache.clear();
130
- return true;
131
- }
132
- }
133
-
134
- // Export all schema operations as default
135
- export default {
136
- setSchema,
137
- getSchema,
138
- clearSchemaCache
139
- };
package/utils.js DELETED
@@ -1,302 +0,0 @@
1
- // holo_utils.js
2
- import * as h3 from 'h3-js';
3
-
4
- /**
5
- * Converts latitude and longitude to a holon identifier.
6
- * @param {number} lat - The latitude.
7
- * @param {number} lng - The longitude.
8
- * @param {number} resolution - The resolution level.
9
- * @returns {Promise<string>} - The resulting holon identifier.
10
- */
11
- export async function getHolon(lat, lng, resolution) { // Doesn't need holoInstance
12
- return h3.latLngToCell(lat, lng, resolution);
13
- }
14
-
15
- /**
16
- * Retrieves all containing holonagons at all scales for given coordinates.
17
- * @param {number} lat - The latitude.
18
- * @param {number} lng - The longitude.
19
- * @returns {Array<string>} - List of holon identifiers.
20
- */
21
- export function getScalespace(lat, lng) { // Doesn't need holoInstance
22
- let list = []
23
- let cell = h3.latLngToCell(lat, lng, 14);
24
- list.push(cell)
25
- for (let i = 13; i >= 0; i--) {
26
- list.push(h3.cellToParent(cell, i))
27
- }
28
- return list
29
- }
30
-
31
- /**
32
- * Retrieves all containing holonagons at all scales for a given holon.
33
- * @param {string} holon - The holon identifier.
34
- * @returns {Array<string>} - List of holon identifiers.
35
- */
36
- export function getHolonScalespace(holon) { // Doesn't need holoInstance
37
- let list = []
38
- let res = h3.getResolution(holon)
39
- for (let i = res; i >= 0; i--) {
40
- list.push(h3.cellToParent(holon, i))
41
- }
42
- return list
43
- }
44
-
45
- /**
46
- * Subscribes to changes in a specific holon and lens.
47
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
48
- * @param {string} holon - The holon identifier.
49
- * @param {string} lens - The lens to subscribe to.
50
- * @param {function} callback - The callback to execute on changes.
51
- * @returns {Promise<object>} - Subscription object with unsubscribe method
52
- */
53
- export async function subscribe(holoInstance, holon, lens, callback) {
54
- if (!holon || !lens) {
55
- throw new Error('subscribe: Missing holon or lens parameters:', holon, lens);
56
- }
57
-
58
- if (!callback || typeof callback !== 'function') {
59
- throw new Error('subscribe: Callback must be a function');
60
- }
61
-
62
- const subscriptionId = holoInstance.generateId(); // Use instance's generateId
63
-
64
- try {
65
- // Get the Gun chain up to the map()
66
- const mapChain = holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).map();
67
-
68
- // Create the subscription by calling .on() on the map chain
69
- const gunListener = mapChain.on(async (data, key) => { // Renamed variable
70
- // Check if subscription ID still exists (might have been unsubscribed)
71
- if (!holoInstance.subscriptions[subscriptionId]) {
72
- return;
73
- }
74
-
75
- if (data) {
76
- try {
77
- let parsed = await holoInstance.parse(data);
78
- if (parsed && holoInstance.isHologram(parsed)) {
79
- const resolved = await holoInstance.resolveHologram(parsed, { followHolograms: true });
80
- if (resolved !== parsed) {
81
- parsed = resolved;
82
- }
83
- }
84
-
85
- // Check again if subscription ID still exists before calling callback
86
- if (holoInstance.subscriptions[subscriptionId]) {
87
- callback(parsed, key);
88
- }
89
- } catch (error) {
90
- console.error('Error processing subscribed data:', error);
91
- }
92
- }
93
- });
94
-
95
- // Store the subscription with its ID on the instance
96
- holoInstance.subscriptions[subscriptionId] = {
97
- id: subscriptionId,
98
- holon,
99
- lens,
100
- callback,
101
- mapChain: mapChain, // Store the map chain
102
- gunListener: gunListener // Store the listener too (optional, maybe needed for close?)
103
- };
104
-
105
- // Return an object with unsubscribe method
106
- return {
107
- unsubscribe: async () => {
108
- const sub = holoInstance.subscriptions[subscriptionId];
109
- if (!sub) {
110
- return;
111
- }
112
-
113
- try {
114
- // Turn off the Gun subscription using the stored mapChain reference
115
- if (sub.mapChain) { // Check if mapChain exists
116
- sub.mapChain.off(); // Call off() on the chain where .on() was attached
117
- // Optional: Add delay back? Let's omit for now.
118
- // await new Promise(res => setTimeout(res, 50));
119
- } // We might not need to call off() on gunListener explicitly
120
-
121
- // Remove from subscriptions object AFTER turning off listener
122
- delete holoInstance.subscriptions[subscriptionId];
123
- } catch (error) {
124
- console.error(`Error during unsubscribe logic for ${subscriptionId}:`, error);
125
- }
126
- }
127
- };
128
- } catch (error) {
129
- console.error('Error creating subscription:', error);
130
- throw error;
131
- }
132
- }
133
-
134
- /**
135
- * Notifies subscribers about data changes
136
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
137
- * @param {object} data - The data to notify about
138
- * @private
139
- */
140
- export function notifySubscribers(holoInstance, data) {
141
- if (!data || !data.holon || !data.lens) {
142
- return;
143
- }
144
-
145
- try {
146
- Object.values(holoInstance.subscriptions).forEach(subscription => {
147
- if (subscription.holon === data.holon &&
148
- subscription.lens === data.lens) {
149
- try {
150
- if (subscription.callback && typeof subscription.callback === 'function') {
151
- subscription.callback(data);
152
- }
153
- } catch (error) {
154
- console.warn('Error in subscription callback:', error);
155
- }
156
- }
157
- });
158
- } catch (error) {
159
- console.warn('Error notifying subscribers:', error);
160
- }
161
- }
162
-
163
- // Add ID generation method
164
- export function generateId() { // Doesn't need holoInstance
165
- return Date.now().toString(10) + Math.random().toString(2);
166
- }
167
-
168
- /**
169
- * Closes the HoloSphere instance and cleans up resources.
170
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
171
- * @returns {Promise<void>}
172
- */
173
- export async function close(holoInstance) {
174
- try {
175
- if (holoInstance.gun) {
176
- // Unsubscribe from all subscriptions
177
- const subscriptionIds = Object.keys(holoInstance.subscriptions);
178
- for (const id of subscriptionIds) {
179
- try {
180
- const subscription = holoInstance.subscriptions[id];
181
- if (subscription) {
182
- // Turn off the Gun subscription using the stored mapChain reference
183
- if (subscription.mapChain) {
184
- subscription.mapChain.off();
185
- } // Also turn off listener directly? Might be redundant.
186
- // if (subscription.gunListener) {
187
- // subscription.gunListener.off();
188
- // }
189
- }
190
- } catch (error) {
191
- console.warn(`Error cleaning up subscription ${id}:`, error);
192
- }
193
- }
194
-
195
- // Clear subscriptions
196
- holoInstance.subscriptions = {};
197
-
198
- // Clear schema cache using instance method
199
- holoInstance.clearSchemaCache();
200
-
201
- // Close Gun connections
202
- if (holoInstance.gun.back) {
203
- try {
204
- // Clean up mesh connections
205
- const mesh = holoInstance.gun.back('opt.mesh');
206
- if (mesh) {
207
- // Clean up mesh.hear
208
- if (mesh.hear) {
209
- try {
210
- // Safely clear mesh.hear without modifying function properties
211
- const hearKeys = Object.keys(mesh.hear);
212
- for (const key of hearKeys) {
213
- // Check if it's an array before trying to clear it
214
- if (Array.isArray(mesh.hear[key])) {
215
- mesh.hear[key] = [];
216
- }
217
- }
218
-
219
- // Create a new empty object for mesh.hear
220
- // Only if mesh.hear is not a function
221
- if (typeof mesh.hear !== 'function') {
222
- mesh.hear = {};
223
- }
224
- } catch (meshError) {
225
- console.warn('Error cleaning up Gun mesh hear:', meshError);
226
- }
227
- }
228
-
229
- // Close any open sockets in the mesh
230
- if (mesh.way) {
231
- try {
232
- Object.values(mesh.way).forEach(connection => {
233
- if (connection && connection.wire && connection.wire.close) {
234
- connection.wire.close();
235
- }
236
- });
237
- } catch (sockError) {
238
- console.warn('Error closing mesh sockets:', sockError);
239
- }
240
- }
241
-
242
- // Clear the peers list
243
- if (mesh.opt && mesh.opt.peers) {
244
- mesh.opt.peers = {};
245
- }
246
- }
247
-
248
- // Attempt to clean up any TCP connections
249
- if (holoInstance.gun.back('opt.web')) {
250
- try {
251
- const server = holoInstance.gun.back('opt.web');
252
- if (server && server.close) {
253
- server.close();
254
- }
255
- } catch (webError) {
256
- console.warn('Error closing web server:', webError);
257
- }
258
- }
259
- } catch (error) {
260
- console.warn('Error accessing Gun mesh:', error);
261
- }
262
- }
263
-
264
- // Clear all Gun instance listeners
265
- try {
266
- holoInstance.gun.off();
267
- } catch (error) {
268
- console.warn('Error turning off Gun listeners:', error);
269
- }
270
-
271
- // Wait a moment for cleanup to complete
272
- await new Promise(resolve => setTimeout(resolve, 100));
273
- }
274
-
275
- console.log('HoloSphere instance closed successfully');
276
- } catch (error) {
277
- console.error('Error closing HoloSphere instance:', error);
278
- }
279
- }
280
-
281
- /**
282
- * Creates a namespaced username for Gun authentication
283
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
284
- * @param {string} holonId - The holon ID
285
- * @returns {string} - Namespaced username
286
- */
287
- export function userName(holoInstance, holonId) {
288
- if (!holonId) return null;
289
- return `${holoInstance.appname}:${holonId}`;
290
- }
291
-
292
- // Export all utility operations as default
293
- export default {
294
- getHolon,
295
- getScalespace,
296
- getHolonScalespace,
297
- subscribe,
298
- notifySubscribers,
299
- generateId,
300
- close,
301
- userName
302
- };