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
|
@@ -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
|
+
});
|