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.
Files changed (147) hide show
  1. package/.env.example +36 -0
  2. package/.eslintrc.json +16 -0
  3. package/.prettierrc.json +7 -0
  4. package/LICENSE +162 -38
  5. package/README.md +483 -367
  6. package/bin/holosphere-activitypub.js +158 -0
  7. package/cleanup-test-data.js +204 -0
  8. package/examples/demo.html +1333 -0
  9. package/examples/example-bot.js +197 -0
  10. package/package.json +47 -87
  11. package/scripts/check-bundle-size.js +54 -0
  12. package/scripts/check-quest-ids.js +77 -0
  13. package/scripts/import-holons.js +578 -0
  14. package/scripts/publish-to-relay.js +101 -0
  15. package/scripts/read-example.js +186 -0
  16. package/scripts/relay-diagnostic.js +59 -0
  17. package/scripts/relay-example.js +179 -0
  18. package/scripts/resync-to-relay.js +245 -0
  19. package/scripts/revert-import.js +196 -0
  20. package/scripts/test-hybrid-mode.js +108 -0
  21. package/scripts/test-local-storage.js +63 -0
  22. package/scripts/test-nostr-direct.js +55 -0
  23. package/scripts/test-read-data.js +45 -0
  24. package/scripts/test-write-read.js +63 -0
  25. package/scripts/verify-import.js +95 -0
  26. package/scripts/verify-relay-data.js +139 -0
  27. package/src/ai/aggregation.js +319 -0
  28. package/src/ai/breakdown.js +511 -0
  29. package/src/ai/classifier.js +217 -0
  30. package/src/ai/council.js +228 -0
  31. package/src/ai/embeddings.js +279 -0
  32. package/src/ai/federation-ai.js +324 -0
  33. package/src/ai/h3-ai.js +955 -0
  34. package/src/ai/index.js +112 -0
  35. package/src/ai/json-ops.js +225 -0
  36. package/src/ai/llm-service.js +205 -0
  37. package/src/ai/nl-query.js +223 -0
  38. package/src/ai/relationships.js +353 -0
  39. package/src/ai/schema-extractor.js +218 -0
  40. package/src/ai/spatial.js +293 -0
  41. package/src/ai/tts.js +194 -0
  42. package/src/content/social-protocols.js +168 -0
  43. package/src/core/holosphere.js +273 -0
  44. package/src/crypto/secp256k1.js +259 -0
  45. package/src/federation/discovery.js +334 -0
  46. package/src/federation/hologram.js +1042 -0
  47. package/src/federation/registry.js +386 -0
  48. package/src/hierarchical/upcast.js +110 -0
  49. package/src/index.js +2669 -0
  50. package/src/schema/validator.js +91 -0
  51. package/src/spatial/h3-operations.js +110 -0
  52. package/src/storage/backend-factory.js +125 -0
  53. package/src/storage/backend-interface.js +142 -0
  54. package/src/storage/backends/activitypub/server.js +653 -0
  55. package/src/storage/backends/activitypub-backend.js +272 -0
  56. package/src/storage/backends/gundb-backend.js +233 -0
  57. package/src/storage/backends/nostr-backend.js +136 -0
  58. package/src/storage/filesystem-storage-browser.js +41 -0
  59. package/src/storage/filesystem-storage.js +138 -0
  60. package/src/storage/global-tables.js +81 -0
  61. package/src/storage/gun-async.js +281 -0
  62. package/src/storage/gun-wrapper.js +221 -0
  63. package/src/storage/indexeddb-storage.js +122 -0
  64. package/src/storage/key-storage-simple.js +76 -0
  65. package/src/storage/key-storage.js +136 -0
  66. package/src/storage/memory-storage.js +59 -0
  67. package/src/storage/migration.js +338 -0
  68. package/src/storage/nostr-async.js +811 -0
  69. package/src/storage/nostr-client.js +939 -0
  70. package/src/storage/nostr-wrapper.js +211 -0
  71. package/src/storage/outbox-queue.js +208 -0
  72. package/src/storage/persistent-storage.js +109 -0
  73. package/src/storage/sync-service.js +164 -0
  74. package/src/subscriptions/manager.js +142 -0
  75. package/test-ai-real-api.js +202 -0
  76. package/tests/unit/ai/aggregation.test.js +295 -0
  77. package/tests/unit/ai/breakdown.test.js +446 -0
  78. package/tests/unit/ai/classifier.test.js +294 -0
  79. package/tests/unit/ai/council.test.js +262 -0
  80. package/tests/unit/ai/embeddings.test.js +384 -0
  81. package/tests/unit/ai/federation-ai.test.js +344 -0
  82. package/tests/unit/ai/h3-ai.test.js +458 -0
  83. package/tests/unit/ai/index.test.js +304 -0
  84. package/tests/unit/ai/json-ops.test.js +307 -0
  85. package/tests/unit/ai/llm-service.test.js +390 -0
  86. package/tests/unit/ai/nl-query.test.js +383 -0
  87. package/tests/unit/ai/relationships.test.js +311 -0
  88. package/tests/unit/ai/schema-extractor.test.js +384 -0
  89. package/tests/unit/ai/spatial.test.js +279 -0
  90. package/tests/unit/ai/tts.test.js +279 -0
  91. package/tests/unit/content.test.js +332 -0
  92. package/tests/unit/contract/core.test.js +88 -0
  93. package/tests/unit/contract/crypto.test.js +198 -0
  94. package/tests/unit/contract/data.test.js +223 -0
  95. package/tests/unit/contract/federation.test.js +181 -0
  96. package/tests/unit/contract/hierarchical.test.js +113 -0
  97. package/tests/unit/contract/schema.test.js +114 -0
  98. package/tests/unit/contract/social.test.js +217 -0
  99. package/tests/unit/contract/spatial.test.js +110 -0
  100. package/tests/unit/contract/subscriptions.test.js +128 -0
  101. package/tests/unit/contract/utils.test.js +159 -0
  102. package/tests/unit/core.test.js +152 -0
  103. package/tests/unit/crypto.test.js +328 -0
  104. package/tests/unit/federation.test.js +234 -0
  105. package/tests/unit/gun-async.test.js +252 -0
  106. package/tests/unit/hierarchical.test.js +399 -0
  107. package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
  108. package/tests/unit/integration/scenario-02-federation.test.js +76 -0
  109. package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
  110. package/tests/unit/integration/scenario-04-validation.test.js +129 -0
  111. package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
  112. package/tests/unit/integration/scenario-06-social.test.js +135 -0
  113. package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
  114. package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
  115. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
  116. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
  117. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
  118. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
  119. package/tests/unit/performance/benchmark.test.js +85 -0
  120. package/tests/unit/schema.test.js +213 -0
  121. package/tests/unit/spatial.test.js +158 -0
  122. package/tests/unit/storage.test.js +195 -0
  123. package/tests/unit/subscriptions.test.js +328 -0
  124. package/tests/unit/test-data-permanence-debug.js +197 -0
  125. package/tests/unit/test-data-permanence.js +340 -0
  126. package/tests/unit/test-key-persistence-fixed.js +148 -0
  127. package/tests/unit/test-key-persistence.js +172 -0
  128. package/tests/unit/test-relay-permanence.js +376 -0
  129. package/tests/unit/test-second-node.js +95 -0
  130. package/tests/unit/test-simple-write.js +89 -0
  131. package/vite.config.js +49 -0
  132. package/vitest.config.js +20 -0
  133. package/FEDERATION.md +0 -213
  134. package/compute.js +0 -298
  135. package/content.js +0 -980
  136. package/federation.js +0 -1234
  137. package/global.js +0 -736
  138. package/hexlib.js +0 -335
  139. package/hologram.js +0 -183
  140. package/holosphere-bundle.esm.js +0 -33256
  141. package/holosphere-bundle.js +0 -33287
  142. package/holosphere-bundle.min.js +0 -39
  143. package/holosphere.d.ts +0 -601
  144. package/holosphere.js +0 -719
  145. package/node.js +0 -246
  146. package/schema.js +0 -139
  147. package/utils.js +0 -302
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Example script to read imported data from holosphere
5
+ *
6
+ * Demonstrates various ways to query and retrieve data
7
+ */
8
+
9
+ import { HoloSphere } from '../src/index.js';
10
+
11
+ async function main() {
12
+ console.log('HoloSphere Read Example');
13
+ console.log('======================\n');
14
+
15
+ // Initialize holosphere with the same app name used during import
16
+ // Using relay.holons.io for distributed access
17
+ const hs = new HoloSphere({
18
+ appName: 'imported',
19
+ relays: ['wss://relay.holons.io'],
20
+ persistence: true,
21
+ logLevel: 'INFO'
22
+ });
23
+
24
+ console.log('Holosphere initialized with relay.holons.io\n');
25
+
26
+ // Wait for storage to be ready and loaded
27
+ console.log('Loading data from persistent storage and relay...');
28
+ await new Promise(resolve => setTimeout(resolve, 2000));
29
+
30
+ // Example 1: Read all data from a specific lens
31
+ console.log('Example 1: Reading all quests from demo app');
32
+ console.log('='.repeat(50));
33
+ try {
34
+ const quests = await hs.readGlobal('quests');
35
+ if (quests && typeof quests === 'object') {
36
+ const questArray = Object.values(quests);
37
+ console.log(`Found ${questArray.length} quests:`);
38
+
39
+ // Show first 3 quests
40
+ questArray.slice(0, 3).forEach((quest, idx) => {
41
+ console.log(`\n${idx + 1}. Quest ID: ${quest.id}`);
42
+ console.log(` Title: ${quest.title || 'N/A'}`);
43
+ console.log(` Status: ${quest.status || 'N/A'}`);
44
+ console.log(` Type: ${quest.type || 'N/A'}`);
45
+ });
46
+
47
+ if (questArray.length > 3) {
48
+ console.log(`\n... and ${questArray.length - 3} more quests`);
49
+ }
50
+ } else {
51
+ console.log('No quests found or data format unexpected');
52
+ }
53
+ } catch (error) {
54
+ console.error('Error reading quests:', error.message);
55
+ }
56
+
57
+ // Example 2: Read a specific item by ID
58
+ console.log('\n\nExample 2: Reading a specific quest by ID');
59
+ console.log('='.repeat(50));
60
+ try {
61
+ const questId = '1734348140557x6irmp8vm';
62
+ const quest = await hs.readGlobal('quests', questId);
63
+
64
+ if (quest) {
65
+ console.log(`Quest found:`);
66
+ console.log(JSON.stringify(quest, null, 2));
67
+ } else {
68
+ console.log(`Quest with ID ${questId} not found`);
69
+ }
70
+ } catch (error) {
71
+ console.error('Error reading specific quest:', error.message);
72
+ }
73
+
74
+ // Example 3: Read all checklists
75
+ console.log('\n\nExample 3: Reading all checklists');
76
+ console.log('='.repeat(50));
77
+ try {
78
+ const checklists = await hs.readGlobal('checklists');
79
+
80
+ if (checklists && typeof checklists === 'object') {
81
+ const checklistArray = Object.values(checklists);
82
+ console.log(`Found ${checklistArray.length} checklists:`);
83
+
84
+ checklistArray.slice(0, 5).forEach((checklist, idx) => {
85
+ console.log(`\n${idx + 1}. ${checklist.id}`);
86
+ if (checklist.value) {
87
+ console.log(` Value: ${JSON.stringify(checklist.value).substring(0, 100)}...`);
88
+ }
89
+ });
90
+ } else {
91
+ console.log('No checklists found');
92
+ }
93
+ } catch (error) {
94
+ console.error('Error reading checklists:', error.message);
95
+ }
96
+
97
+ // Example 4: Read settings data
98
+ console.log('\n\nExample 4: Reading settings');
99
+ console.log('='.repeat(50));
100
+ try {
101
+ const settings = await hs.readGlobal('settings');
102
+
103
+ if (settings && typeof settings === 'object') {
104
+ const settingsArray = Object.values(settings);
105
+ console.log(`Found ${settingsArray.length} settings entries:`);
106
+
107
+ settingsArray.slice(0, 3).forEach((setting, idx) => {
108
+ console.log(`\n${idx + 1}. Setting ID: ${setting.id}`);
109
+ console.log(` Data:`, JSON.stringify(setting, null, 2).substring(0, 200));
110
+ });
111
+ } else {
112
+ console.log('No settings found');
113
+ }
114
+ } catch (error) {
115
+ console.error('Error reading settings:', error.message);
116
+ }
117
+
118
+ // Example 5: Read from H3 holon (if any exist)
119
+ console.log('\n\nExample 5: Reading from H3 holons');
120
+ console.log('='.repeat(50));
121
+ try {
122
+ const h3HolonId = '881e850d53fffff';
123
+ const hubs = await hs.read(h3HolonId, 'hubs');
124
+
125
+ if (hubs && typeof hubs === 'object') {
126
+ const hubsArray = Object.values(hubs);
127
+ console.log(`Found ${hubsArray.length} hubs in holon ${h3HolonId}:`);
128
+
129
+ hubsArray.forEach((hub, idx) => {
130
+ console.log(`\n${idx + 1}. Hub ID: ${hub.id}`);
131
+ console.log(` Data:`, JSON.stringify(hub, null, 2));
132
+ });
133
+ } else {
134
+ console.log(`No hubs found in holon ${h3HolonId}`);
135
+ }
136
+ } catch (error) {
137
+ console.error('Error reading from H3 holon:', error.message);
138
+ }
139
+
140
+ // Example 6: Browse available lenses
141
+ console.log('\n\nExample 6: Available lenses (data categories)');
142
+ console.log('='.repeat(50));
143
+
144
+ const sampleLenses = [
145
+ 'quests', 'checklists', 'settings', 'users', 'shopping',
146
+ 'expenses', 'chats', 'announcements', 'federation', 'hubs'
147
+ ];
148
+
149
+ console.log('Sample lenses available in the imported data:');
150
+ sampleLenses.forEach((lens, idx) => {
151
+ console.log(`${idx + 1}. ${lens}`);
152
+ });
153
+
154
+ console.log('\nTo read from any lens, use:');
155
+ console.log(' await hs.readGlobal(lensName) // Get all items');
156
+ console.log(' await hs.readGlobal(lensName, itemId) // Get specific item');
157
+
158
+ // Example 7: Summary statistics
159
+ console.log('\n\nExample 7: Data summary');
160
+ console.log('='.repeat(50));
161
+
162
+ const lensesToCheck = ['quests', 'checklists', 'settings', 'users', 'shopping'];
163
+
164
+ for (const lens of lensesToCheck) {
165
+ try {
166
+ const data = await hs.readGlobal(lens);
167
+ if (data && typeof data === 'object') {
168
+ const count = Object.keys(data).length;
169
+ console.log(`${lens}: ${count} items`);
170
+ }
171
+ } catch (error) {
172
+ console.log(`${lens}: Error reading (${error.message})`);
173
+ }
174
+ }
175
+
176
+ // Show holosphere metrics
177
+ console.log('\n\nHolosphere Metrics:');
178
+ console.log('='.repeat(50));
179
+ console.log(JSON.stringify(hs.metrics(), null, 2));
180
+ }
181
+
182
+ // Run the example
183
+ main().catch(error => {
184
+ console.error('Fatal error:', error);
185
+ process.exit(1);
186
+ });
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Diagnostic script to test relay connectivity and write operations
5
+ */
6
+
7
+ import { HoloSphere } from '../src/index.js';
8
+ import dotenv from 'dotenv';
9
+
10
+ // Load environment variables
11
+ dotenv.config();
12
+
13
+ async function main() {
14
+ console.log('Relay Diagnostic Test');
15
+ console.log('=====================\n');
16
+
17
+ const hs = new HoloSphere({
18
+ appName: 'test',
19
+ relays: ['wss://relay.holons.io'],
20
+ persistence: true,
21
+ logLevel: 'DEBUG',
22
+ privateKey: process.env.HOLOSPHERE_PRIVATE_KEY
23
+ });
24
+
25
+ console.log('Testing write operation...\n');
26
+
27
+ const testData = {
28
+ id: 'test-1',
29
+ message: 'Hello from HoloSphere!',
30
+ timestamp: Date.now()
31
+ };
32
+
33
+ try {
34
+ console.log('Writing data:', JSON.stringify(testData, null, 2));
35
+ testData.id = 'test-1';
36
+ const result = await hs.writeGlobal('test', testData);
37
+ console.log('\nWrite result:', result);
38
+
39
+ if (result) {
40
+ console.log('✓ Write succeeded');
41
+
42
+ console.log('\nWaiting 3 seconds...');
43
+ await new Promise(resolve => setTimeout(resolve, 3000));
44
+
45
+ console.log('\nReading data back...');
46
+ const retrieved = await hs.readGlobal('test', 'test-1');
47
+ console.log('Retrieved:', JSON.stringify(retrieved, null, 2));
48
+ } else {
49
+ console.log('✗ Write failed');
50
+ }
51
+ } catch (error) {
52
+ console.error('Error:', error);
53
+ console.error('Stack:', error.stack);
54
+ }
55
+
56
+ console.log('\nMetrics:', hs.metrics());
57
+ }
58
+
59
+ main().catch(console.error);
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Example: Write quests to Nostr relays and read them back
5
+ *
6
+ * This demonstrates the full relay round-trip:
7
+ * 1. Write data to distributed Nostr relays
8
+ * 2. Wait for propagation
9
+ * 3. Read data back from relays
10
+ * 4. Display results
11
+ */
12
+
13
+ import { HoloSphere } from '../src/index.js';
14
+ import dotenv from 'dotenv';
15
+
16
+ // Load environment variables
17
+ dotenv.config();
18
+
19
+ async function main() {
20
+ console.log('HoloSphere Relay Example');
21
+ console.log('========================\n');
22
+
23
+ // Initialize holosphere with actual Nostr relays
24
+ const hs = new HoloSphere({
25
+ appName: 'relay-demo',
26
+ relays: [
27
+ 'wss://relay.holons.io',
28
+ 'wss://relay.nostr.band',
29
+ 'wss://nos.lol'
30
+ ],
31
+ persistence: true,
32
+ logLevel: 'INFO',
33
+ privateKey: process.env.HOLOSPHERE_PRIVATE_KEY
34
+ });
35
+
36
+ console.log('✓ HoloSphere initialized with Nostr relays\n');
37
+
38
+ // Sample quests to write
39
+ const quests = [
40
+ {
41
+ id: 'quest-1',
42
+ title: 'Build distributed task manager',
43
+ description: 'Create a P2P task management system using Nostr',
44
+ status: 'in-progress',
45
+ type: 'project',
46
+ priority: 'high',
47
+ created: new Date().toISOString(),
48
+ tags: ['nostr', 'p2p', 'tasks']
49
+ },
50
+ {
51
+ id: 'quest-2',
52
+ title: 'Test relay connectivity',
53
+ description: 'Verify that data can be written and read from Nostr relays',
54
+ status: 'completed',
55
+ type: 'task',
56
+ priority: 'high',
57
+ created: new Date().toISOString(),
58
+ tags: ['testing', 'nostr']
59
+ },
60
+ {
61
+ id: 'quest-3',
62
+ title: 'Implement geospatial queries',
63
+ description: 'Add support for querying tasks by location using H3',
64
+ status: 'planned',
65
+ type: 'feature',
66
+ priority: 'medium',
67
+ created: new Date().toISOString(),
68
+ tags: ['h3', 'spatial', 'queries']
69
+ }
70
+ ];
71
+
72
+ // Step 1: Write quests to relays
73
+ console.log('Step 1: Writing quests to Nostr relays');
74
+ console.log('='.repeat(50));
75
+
76
+ for (const quest of quests) {
77
+ try {
78
+ const success = await hs.writeGlobal('quests', quest);
79
+ if (success) {
80
+ console.log(`✓ Written: "${quest.title}" (${quest.id})`);
81
+ } else {
82
+ console.log(`✗ Failed to write: "${quest.title}"`);
83
+ }
84
+ } catch (error) {
85
+ console.error(`✗ Error writing ${quest.id}:`, error.message);
86
+ }
87
+ }
88
+
89
+ console.log('\n');
90
+
91
+ // Step 2: Wait for relay propagation
92
+ console.log('Step 2: Waiting for relay propagation...');
93
+ console.log('='.repeat(50));
94
+ const waitTime = 3;
95
+ for (let i = waitTime; i > 0; i--) {
96
+ process.stdout.write(`\rWaiting ${i} seconds... `);
97
+ await new Promise(resolve => setTimeout(resolve, 1000));
98
+ }
99
+ console.log('\r✓ Wait complete \n');
100
+
101
+ // Step 3: Read quests back from relays
102
+ console.log('Step 3: Reading quests from Nostr relays');
103
+ console.log('='.repeat(50));
104
+
105
+ // Read individual quests
106
+ console.log('\nReading individual quests by ID:\n');
107
+
108
+ for (const quest of quests) {
109
+ try {
110
+ const retrieved = await hs.readGlobal('quests', quest.id);
111
+
112
+ if (retrieved) {
113
+ console.log(`✓ Quest: ${retrieved.title}`);
114
+ console.log(` ID: ${retrieved.id}`);
115
+ console.log(` Status: ${retrieved.status}`);
116
+ console.log(` Priority: ${retrieved.priority}`);
117
+ console.log(` Tags: ${retrieved.tags.join(', ')}`);
118
+ console.log('');
119
+ } else {
120
+ console.log(`✗ Quest ${quest.id} not found`);
121
+ console.log('');
122
+ }
123
+ } catch (error) {
124
+ console.error(`✗ Error reading ${quest.id}:`, error.message);
125
+ console.log('');
126
+ }
127
+ }
128
+
129
+ // Read all quests from the lens
130
+ console.log('Reading all quests from the lens:\n');
131
+
132
+ try {
133
+ const allQuests = await hs.readGlobal('quests');
134
+
135
+ if (allQuests && typeof allQuests === 'object') {
136
+ const questArray = Object.values(allQuests);
137
+ console.log(`✓ Found ${questArray.length} total quests in relay-demo/global/quests:`);
138
+ console.log('');
139
+
140
+ questArray.forEach((q, idx) => {
141
+ console.log(`${idx + 1}. ${q.title} (${q.status})`);
142
+ if (q.description) {
143
+ console.log(` ${q.description.substring(0, 60)}...`);
144
+ }
145
+ });
146
+ } else {
147
+ console.log('✗ No quests found or unexpected data format');
148
+ }
149
+ } catch (error) {
150
+ console.error('✗ Error reading all quests:', error.message);
151
+ }
152
+
153
+ console.log('\n');
154
+
155
+ // Step 4: Summary and metrics
156
+ console.log('Step 4: Summary');
157
+ console.log('='.repeat(50));
158
+
159
+ const metrics = hs.metrics();
160
+ console.log('Operation Metrics:');
161
+ console.log(` Writes: ${metrics.writes}`);
162
+ console.log(` Reads: ${metrics.reads}`);
163
+ console.log(` Average write time: ${metrics.avgWriteTime || 0}ms`);
164
+ console.log(` Average read time: ${metrics.avgReadTime || 0}ms`);
165
+
166
+ console.log('\n✓ Relay example complete!\n');
167
+
168
+ console.log('Key Takeaways:');
169
+ console.log('- Data is written to multiple Nostr relays for redundancy');
170
+ console.log('- Reads query relays and return the most recent version');
171
+ console.log('- Local cache provides fast access after initial fetch');
172
+ console.log('- All data is cryptographically signed and verifiable');
173
+ }
174
+
175
+ // Run the example
176
+ main().catch(error => {
177
+ console.error('\n✗ Fatal error:', error);
178
+ process.exit(1);
179
+ });
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Re-sync missing data to relay with retry logic and rate limiting
5
+ *
6
+ * This script:
7
+ * 1. Compares local storage vs relay
8
+ * 2. Identifies missing items
9
+ * 3. Re-publishes with retry logic and rate limiting
10
+ * 4. Reports success/failure for each item
11
+ */
12
+
13
+ import { HoloSphere } from '../src/index.js';
14
+ import dotenv from 'dotenv';
15
+
16
+ dotenv.config();
17
+
18
+ // Parse command line arguments for configuration
19
+ const args = process.argv.slice(2);
20
+ let relayUrl = 'wss://relay.holons.io'; // Default relay
21
+ let holonId = '235114395'; // Default holon
22
+
23
+ for (let i = 0; i < args.length; i++) {
24
+ if (args[i] === '--relay' && args[i + 1]) {
25
+ relayUrl = args[i + 1];
26
+ i++;
27
+ } else if (args[i] === '--holon' && args[i + 1]) {
28
+ holonId = args[i + 1];
29
+ i++;
30
+ }
31
+ }
32
+
33
+ // Configuration
34
+ const CONFIG = {
35
+ holonId,
36
+ relays: [relayUrl],
37
+ batchSize: 10, // Process 10 items at a time
38
+ delayBetweenItems: 200, // 200ms between each publish
39
+ delayBetweenBatches: 2000, // 2s between batches
40
+ maxRetries: 3, // Retry failed publishes 3 times
41
+ retryDelay: 1000, // 1s between retries
42
+ };
43
+
44
+ class SyncStats {
45
+ constructor() {
46
+ this.total = 0;
47
+ this.synced = 0;
48
+ this.failed = 0;
49
+ this.skipped = 0;
50
+ this.failures = [];
51
+ }
52
+
53
+ report() {
54
+ console.log('\n' + '='.repeat(60));
55
+ console.log('Sync Complete');
56
+ console.log('='.repeat(60));
57
+ console.log(`Total items: ${this.total}`);
58
+ console.log(`Synced: ${this.synced} ✓`);
59
+ console.log(`Failed: ${this.failed} ✗`);
60
+ console.log(`Skipped: ${this.skipped}`);
61
+ console.log(`Success rate: ${((this.synced / this.total) * 100).toFixed(1)}%`);
62
+
63
+ if (this.failures.length > 0) {
64
+ console.log('\nFailed items:');
65
+ this.failures.forEach(({ lens, id, error }) => {
66
+ console.log(` - ${lens}/${id}: ${error}`);
67
+ });
68
+ }
69
+ }
70
+ }
71
+
72
+ async function sleep(ms) {
73
+ return new Promise(resolve => setTimeout(resolve, ms));
74
+ }
75
+
76
+ async function publishWithRetry(hs, holonId, lens, item, stats, retries = 0) {
77
+ try {
78
+ const itemId = item.id || `unknown-${Date.now()}`;
79
+
80
+ // Attempt to write
81
+ const success = await hs.write(holonId, lens, item);
82
+
83
+ if (success) {
84
+ stats.synced++;
85
+ return true;
86
+ } else {
87
+ throw new Error('Write returned false');
88
+ }
89
+ } catch (error) {
90
+ if (retries < CONFIG.maxRetries) {
91
+ console.log(` Retry ${retries + 1}/${CONFIG.maxRetries} for ${lens}/${item.id}`);
92
+ await sleep(CONFIG.retryDelay);
93
+ return publishWithRetry(hs, holonId, lens, item, stats, retries + 1);
94
+ } else {
95
+ stats.failed++;
96
+ stats.failures.push({
97
+ lens,
98
+ id: item.id || 'unknown',
99
+ error: error.message
100
+ });
101
+ return false;
102
+ }
103
+ }
104
+ }
105
+
106
+ async function syncLens(hsLocal, hsRelay, holonId, lens, stats) {
107
+ console.log(`\nSyncing lens: ${lens}`);
108
+ console.log('-'.repeat(60));
109
+
110
+ // Get data from both sources
111
+ const localData = await hsLocal.read(holonId, lens);
112
+ const relayData = await hsRelay.read(holonId, lens);
113
+
114
+ if (!localData || localData.length === 0) {
115
+ console.log(` No local data for ${lens}`);
116
+ return;
117
+ }
118
+
119
+ // Convert to maps for comparison
120
+ const localMap = new Map();
121
+ localData.forEach(item => {
122
+ if (item && item.id) {
123
+ localMap.set(item.id.toString(), item);
124
+ }
125
+ });
126
+
127
+ const relayMap = new Map();
128
+ if (relayData && relayData.length > 0) {
129
+ relayData.forEach(item => {
130
+ if (item && item.id) {
131
+ relayMap.set(item.id.toString(), item);
132
+ }
133
+ });
134
+ }
135
+
136
+ // Find missing items
137
+ const missing = [];
138
+ for (const [id, item] of localMap) {
139
+ if (!relayMap.has(id)) {
140
+ missing.push(item);
141
+ }
142
+ }
143
+
144
+ console.log(` Local: ${localMap.size} items`);
145
+ console.log(` Relay: ${relayMap.size} items`);
146
+ console.log(` Missing: ${missing.length} items`);
147
+
148
+ if (missing.length === 0) {
149
+ console.log(` ✓ All items synced`);
150
+ return;
151
+ }
152
+
153
+ stats.total += missing.length;
154
+
155
+ // Publish in batches
156
+ const batches = [];
157
+ for (let i = 0; i < missing.length; i += CONFIG.batchSize) {
158
+ batches.push(missing.slice(i, i + CONFIG.batchSize));
159
+ }
160
+
161
+ console.log(` Publishing in ${batches.length} batches...`);
162
+
163
+ for (let batchNum = 0; batchNum < batches.length; batchNum++) {
164
+ const batch = batches[batchNum];
165
+ console.log(` Batch ${batchNum + 1}/${batches.length} (${batch.length} items)`);
166
+
167
+ for (const item of batch) {
168
+ const itemId = item.id || 'unknown';
169
+ process.stdout.write(` ${lens}/${itemId}... `);
170
+
171
+ const success = await publishWithRetry(hsRelay, holonId, lens, item, stats);
172
+
173
+ if (success) {
174
+ console.log('✓');
175
+ } else {
176
+ console.log('✗');
177
+ }
178
+
179
+ // Rate limiting between items
180
+ await sleep(CONFIG.delayBetweenItems);
181
+ }
182
+
183
+ // Longer delay between batches
184
+ if (batchNum < batches.length - 1) {
185
+ console.log(` Waiting ${CONFIG.delayBetweenBatches}ms before next batch...`);
186
+ await sleep(CONFIG.delayBetweenBatches);
187
+ }
188
+ }
189
+ }
190
+
191
+ async function main() {
192
+ console.log('HoloSphere Relay Re-sync Tool');
193
+ console.log('=============================\n');
194
+ console.log(`Holon ID: ${CONFIG.holonId}`);
195
+ console.log(`Relays: ${CONFIG.relays.join(', ')}`);
196
+ console.log(`Batch size: ${CONFIG.batchSize} items`);
197
+ console.log(`Rate limit: ${CONFIG.delayBetweenItems}ms between items\n`);
198
+
199
+ const stats = new SyncStats();
200
+
201
+ // Initialize local client (no relays)
202
+ console.log('Initializing local storage client...');
203
+ const hsLocal = new HoloSphere({
204
+ appName: 'Holons',
205
+ relays: [],
206
+ persistence: true,
207
+ logLevel: 'ERROR',
208
+ privateKey: process.env.HOLOSPHERE_PRIVATE_KEY
209
+ });
210
+
211
+ await sleep(2000);
212
+ console.log('✓ Local storage loaded\n');
213
+
214
+ // Initialize relay client
215
+ console.log('Initializing relay client...');
216
+ const hsRelay = new HoloSphere({
217
+ appName: 'Holons',
218
+ relays: CONFIG.relays,
219
+ persistence: false,
220
+ logLevel: 'ERROR',
221
+ privateKey: process.env.HOLOSPHERE_PRIVATE_KEY
222
+ });
223
+
224
+ await sleep(2000);
225
+ console.log('✓ Connected to relay\n');
226
+
227
+ // Sync each lens
228
+ const lenses = ['3891', 'announcements', 'checklists', 'expenses', 'linked_quests',
229
+ 'profile', 'quests', 'roles', 'settings', 'users'];
230
+
231
+ for (const lens of lenses) {
232
+ await syncLens(hsLocal, hsRelay, CONFIG.holonId, lens, stats);
233
+ }
234
+
235
+ // Report results
236
+ stats.report();
237
+
238
+ console.log('\n✓ Re-sync complete\n');
239
+ process.exit(0);
240
+ }
241
+
242
+ main().catch(error => {
243
+ console.error('\nFatal error:', error);
244
+ process.exit(1);
245
+ });