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
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Federation Registry - Manages cross-holosphere partnerships
3
+ * Tracks federated partners and their capability tokens
4
+ */
5
+
6
+ import { writeGlobal, readGlobal } from '../storage/global-tables.js';
7
+ import { matchScope } from '../crypto/secp256k1.js';
8
+
9
+ const FEDERATION_TABLE = 'federations';
10
+
11
+ /**
12
+ * Get the federation registry for this holosphere
13
+ * @param {Object} client - NostrClient instance
14
+ * @param {string} appname - Application namespace
15
+ * @returns {Promise<Object>} Federation registry
16
+ */
17
+ export async function getFederationRegistry(client, appname) {
18
+ const registry = await readGlobal(client, appname, FEDERATION_TABLE, client.publicKey);
19
+
20
+ return registry || {
21
+ id: client.publicKey,
22
+ federatedWith: [],
23
+ discoveryEnabled: false,
24
+ autoAccept: false,
25
+ defaultScope: { holonId: '*', lensName: '*' },
26
+ defaultPermissions: ['read'],
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Save the federation registry
32
+ * @param {Object} client - NostrClient instance
33
+ * @param {string} appname - Application namespace
34
+ * @param {Object} registry - Registry to save
35
+ * @returns {Promise<boolean>} Success indicator
36
+ */
37
+ export async function saveFederationRegistry(client, appname, registry) {
38
+ return writeGlobal(client, appname, FEDERATION_TABLE, registry);
39
+ }
40
+
41
+ /**
42
+ * Add a federated partner
43
+ * @param {Object} client - NostrClient instance
44
+ * @param {string} appname - Application namespace
45
+ * @param {string} partnerPubKey - Partner's public key
46
+ * @param {Object} options - Partner options
47
+ * @param {string} options.alias - Human-readable name
48
+ * @param {string} options.addedVia - How the partner was added ('manual' or 'nostr_discovery')
49
+ * @param {Object[]} options.inboundCapabilities - Capabilities they've granted us
50
+ * @returns {Promise<boolean>} Success indicator
51
+ */
52
+ export async function addFederatedPartner(client, appname, partnerPubKey, options = {}) {
53
+ const registry = await getFederationRegistry(client, appname);
54
+
55
+ // Check if already exists
56
+ const existingIndex = registry.federatedWith.findIndex(p => p.pubKey === partnerPubKey);
57
+
58
+ if (existingIndex >= 0) {
59
+ // Update existing entry
60
+ const existing = registry.federatedWith[existingIndex];
61
+
62
+ if (options.alias) {
63
+ existing.alias = options.alias;
64
+ }
65
+
66
+ if (options.inboundCapabilities) {
67
+ existing.inboundCapabilities = [
68
+ ...(existing.inboundCapabilities || []),
69
+ ...options.inboundCapabilities
70
+ ];
71
+ }
72
+
73
+ existing.updatedAt = Date.now();
74
+ registry.federatedWith[existingIndex] = existing;
75
+ } else {
76
+ // Add new partner
77
+ registry.federatedWith.push({
78
+ pubKey: partnerPubKey,
79
+ alias: options.alias || null,
80
+ addedAt: Date.now(),
81
+ addedVia: options.addedVia || 'manual',
82
+ inboundCapabilities: options.inboundCapabilities || [],
83
+ outboundCapabilities: [],
84
+ });
85
+ }
86
+
87
+ return saveFederationRegistry(client, appname, registry);
88
+ }
89
+
90
+ /**
91
+ * Remove a federated partner
92
+ * @param {Object} client - NostrClient instance
93
+ * @param {string} appname - Application namespace
94
+ * @param {string} partnerPubKey - Partner's public key to remove
95
+ * @returns {Promise<boolean>} Success indicator
96
+ */
97
+ export async function removeFederatedPartner(client, appname, partnerPubKey) {
98
+ const registry = await getFederationRegistry(client, appname);
99
+
100
+ const initialLength = registry.federatedWith.length;
101
+ registry.federatedWith = registry.federatedWith.filter(p => p.pubKey !== partnerPubKey);
102
+
103
+ if (registry.federatedWith.length === initialLength) {
104
+ return false; // Partner not found
105
+ }
106
+
107
+ return saveFederationRegistry(client, appname, registry);
108
+ }
109
+
110
+ /**
111
+ * Get all federated author public keys
112
+ * @param {Object} client - NostrClient instance
113
+ * @param {string} appname - Application namespace
114
+ * @returns {Promise<string[]>} Array of federated public keys
115
+ */
116
+ export async function getFederatedAuthors(client, appname) {
117
+ const registry = await getFederationRegistry(client, appname);
118
+ return registry.federatedWith.map(p => p.pubKey);
119
+ }
120
+
121
+ /**
122
+ * Get a specific federated partner
123
+ * @param {Object} client - NostrClient instance
124
+ * @param {string} appname - Application namespace
125
+ * @param {string} partnerPubKey - Partner's public key
126
+ * @returns {Promise<Object|null>} Partner info or null
127
+ */
128
+ export async function getFederatedPartner(client, appname, partnerPubKey) {
129
+ const registry = await getFederationRegistry(client, appname);
130
+ return registry.federatedWith.find(p => p.pubKey === partnerPubKey) || null;
131
+ }
132
+
133
+ /**
134
+ * Get capability for a specific author and scope
135
+ * Finds a valid, non-expired capability that covers the requested scope
136
+ * @param {Object} client - NostrClient instance
137
+ * @param {string} appname - Application namespace
138
+ * @param {string} authorPubKey - Author's public key
139
+ * @param {Object} scope - Requested scope { holonId, lensName, dataId? }
140
+ * @returns {Promise<Object|null>} Capability entry or null
141
+ */
142
+ export async function getCapabilityForAuthor(client, appname, authorPubKey, scope) {
143
+ const registry = await getFederationRegistry(client, appname);
144
+ const partner = registry.federatedWith.find(p => p.pubKey === authorPubKey);
145
+
146
+ if (!partner || !partner.inboundCapabilities) {
147
+ return null;
148
+ }
149
+
150
+ // Find matching, non-expired capability
151
+ return partner.inboundCapabilities.find(cap => {
152
+ // Check expiration
153
+ if (cap.expires && cap.expires < Date.now()) {
154
+ return false;
155
+ }
156
+
157
+ // Check scope match (supports wildcards)
158
+ return matchScope(cap.scope, scope);
159
+ }) || null;
160
+ }
161
+
162
+ /**
163
+ * Store an inbound capability (one we received from a partner)
164
+ * @param {Object} client - NostrClient instance
165
+ * @param {string} appname - Application namespace
166
+ * @param {string} partnerPubKey - Partner's public key
167
+ * @param {Object} capabilityInfo - Capability information
168
+ * @param {string} capabilityInfo.token - The capability token
169
+ * @param {Object} capabilityInfo.scope - Token scope
170
+ * @param {string[]} capabilityInfo.permissions - Granted permissions
171
+ * @param {number} capabilityInfo.expires - Expiration timestamp
172
+ * @returns {Promise<boolean>} Success indicator
173
+ */
174
+ export async function storeInboundCapability(client, appname, partnerPubKey, capabilityInfo) {
175
+ const registry = await getFederationRegistry(client, appname);
176
+ const partner = registry.federatedWith.find(p => p.pubKey === partnerPubKey);
177
+
178
+ if (!partner) {
179
+ // Auto-add partner if not exists
180
+ return addFederatedPartner(client, appname, partnerPubKey, {
181
+ addedVia: 'capability_received',
182
+ inboundCapabilities: [capabilityInfo]
183
+ });
184
+ }
185
+
186
+ if (!partner.inboundCapabilities) {
187
+ partner.inboundCapabilities = [];
188
+ }
189
+
190
+ // Check if we already have this capability (by scope match)
191
+ const existingIndex = partner.inboundCapabilities.findIndex(cap =>
192
+ JSON.stringify(cap.scope) === JSON.stringify(capabilityInfo.scope)
193
+ );
194
+
195
+ if (existingIndex >= 0) {
196
+ // Update existing capability
197
+ partner.inboundCapabilities[existingIndex] = {
198
+ ...capabilityInfo,
199
+ updatedAt: Date.now()
200
+ };
201
+ } else {
202
+ // Add new capability
203
+ partner.inboundCapabilities.push({
204
+ ...capabilityInfo,
205
+ receivedAt: Date.now()
206
+ });
207
+ }
208
+
209
+ return saveFederationRegistry(client, appname, registry);
210
+ }
211
+
212
+ /**
213
+ * Store an outbound capability (one we issued to a partner)
214
+ * We only store the hash, not the full token
215
+ * @param {Object} client - NostrClient instance
216
+ * @param {string} appname - Application namespace
217
+ * @param {string} partnerPubKey - Partner's public key
218
+ * @param {Object} capabilityInfo - Capability information
219
+ * @param {string} capabilityInfo.tokenHash - SHA-256 hash of the token
220
+ * @param {Object} capabilityInfo.scope - Token scope
221
+ * @param {string[]} capabilityInfo.permissions - Granted permissions
222
+ * @param {number} capabilityInfo.expires - Expiration timestamp
223
+ * @returns {Promise<boolean>} Success indicator
224
+ */
225
+ export async function storeOutboundCapability(client, appname, partnerPubKey, capabilityInfo) {
226
+ const registry = await getFederationRegistry(client, appname);
227
+ const partner = registry.federatedWith.find(p => p.pubKey === partnerPubKey);
228
+
229
+ if (!partner) {
230
+ throw new Error(`Partner ${partnerPubKey} not found in federation registry`);
231
+ }
232
+
233
+ if (!partner.outboundCapabilities) {
234
+ partner.outboundCapabilities = [];
235
+ }
236
+
237
+ partner.outboundCapabilities.push({
238
+ tokenHash: capabilityInfo.tokenHash,
239
+ scope: capabilityInfo.scope,
240
+ permissions: capabilityInfo.permissions,
241
+ issuedAt: Date.now(),
242
+ expires: capabilityInfo.expires,
243
+ });
244
+
245
+ return saveFederationRegistry(client, appname, registry);
246
+ }
247
+
248
+ /**
249
+ * Revoke an outbound capability (by token hash)
250
+ * @param {Object} client - NostrClient instance
251
+ * @param {string} appname - Application namespace
252
+ * @param {string} partnerPubKey - Partner's public key
253
+ * @param {string} tokenHash - Hash of the token to revoke
254
+ * @returns {Promise<boolean>} Success indicator
255
+ */
256
+ export async function revokeOutboundCapability(client, appname, partnerPubKey, tokenHash) {
257
+ const registry = await getFederationRegistry(client, appname);
258
+ const partner = registry.federatedWith.find(p => p.pubKey === partnerPubKey);
259
+
260
+ if (!partner || !partner.outboundCapabilities) {
261
+ return false;
262
+ }
263
+
264
+ const initialLength = partner.outboundCapabilities.length;
265
+ partner.outboundCapabilities = partner.outboundCapabilities.filter(
266
+ cap => cap.tokenHash !== tokenHash
267
+ );
268
+
269
+ if (partner.outboundCapabilities.length === initialLength) {
270
+ return false; // Token not found
271
+ }
272
+
273
+ return saveFederationRegistry(client, appname, registry);
274
+ }
275
+
276
+ /**
277
+ * Update federation discovery settings
278
+ * @param {Object} client - NostrClient instance
279
+ * @param {string} appname - Application namespace
280
+ * @param {Object} settings - Discovery settings
281
+ * @param {boolean} settings.discoveryEnabled - Enable Nostr discovery
282
+ * @param {boolean} settings.autoAccept - Auto-accept federation requests
283
+ * @param {Object} settings.defaultScope - Default scope for auto-accepted federations
284
+ * @param {string[]} settings.defaultPermissions - Default permissions for auto-accepted federations
285
+ * @returns {Promise<boolean>} Success indicator
286
+ */
287
+ export async function updateDiscoverySettings(client, appname, settings) {
288
+ const registry = await getFederationRegistry(client, appname);
289
+
290
+ if (settings.discoveryEnabled !== undefined) {
291
+ registry.discoveryEnabled = settings.discoveryEnabled;
292
+ }
293
+ if (settings.autoAccept !== undefined) {
294
+ registry.autoAccept = settings.autoAccept;
295
+ }
296
+ if (settings.defaultScope !== undefined) {
297
+ registry.defaultScope = settings.defaultScope;
298
+ }
299
+ if (settings.defaultPermissions !== undefined) {
300
+ registry.defaultPermissions = settings.defaultPermissions;
301
+ }
302
+
303
+ return saveFederationRegistry(client, appname, registry);
304
+ }
305
+
306
+ /**
307
+ * Get all federated authors that have granted capabilities for a given scope
308
+ * Returns pubKeys of partners who have given us access to read/write their data
309
+ * @param {Object} client - NostrClient instance
310
+ * @param {string} appname - Application namespace
311
+ * @param {Object} scope - Requested scope { holonId?, lensName?, dataId? }
312
+ * @param {string} permission - Required permission ('read' or 'write')
313
+ * @returns {Promise<Array>} Array of { pubKey, capability } for matching partners
314
+ */
315
+ export async function getFederatedAuthorsForScope(client, appname, scope = {}, permission = 'read') {
316
+ const registry = await getFederationRegistry(client, appname);
317
+ const now = Date.now();
318
+ const results = [];
319
+
320
+ for (const partner of registry.federatedWith) {
321
+ if (!partner.inboundCapabilities || partner.inboundCapabilities.length === 0) {
322
+ continue;
323
+ }
324
+
325
+ // Find a valid capability that covers this scope
326
+ for (const cap of partner.inboundCapabilities) {
327
+ // Check expiration
328
+ if (cap.expires && cap.expires < now) {
329
+ continue;
330
+ }
331
+
332
+ // Check permission
333
+ if (cap.permissions && !cap.permissions.includes(permission)) {
334
+ continue;
335
+ }
336
+
337
+ // Check scope match (supports wildcards)
338
+ if (matchScope(cap.scope, scope)) {
339
+ results.push({
340
+ pubKey: partner.pubKey,
341
+ capability: cap,
342
+ });
343
+ break; // One valid capability per partner is enough
344
+ }
345
+ }
346
+ }
347
+
348
+ return results;
349
+ }
350
+
351
+ /**
352
+ * Clean up expired capabilities from the registry
353
+ * @param {Object} client - NostrClient instance
354
+ * @param {string} appname - Application namespace
355
+ * @returns {Promise<Object>} Cleanup result { inboundRemoved, outboundRemoved }
356
+ */
357
+ export async function cleanupExpiredCapabilities(client, appname) {
358
+ const registry = await getFederationRegistry(client, appname);
359
+ const now = Date.now();
360
+ let inboundRemoved = 0;
361
+ let outboundRemoved = 0;
362
+
363
+ for (const partner of registry.federatedWith) {
364
+ if (partner.inboundCapabilities) {
365
+ const beforeCount = partner.inboundCapabilities.length;
366
+ partner.inboundCapabilities = partner.inboundCapabilities.filter(
367
+ cap => !cap.expires || cap.expires > now
368
+ );
369
+ inboundRemoved += beforeCount - partner.inboundCapabilities.length;
370
+ }
371
+
372
+ if (partner.outboundCapabilities) {
373
+ const beforeCount = partner.outboundCapabilities.length;
374
+ partner.outboundCapabilities = partner.outboundCapabilities.filter(
375
+ cap => !cap.expires || cap.expires > now
376
+ );
377
+ outboundRemoved += beforeCount - partner.outboundCapabilities.length;
378
+ }
379
+ }
380
+
381
+ if (inboundRemoved > 0 || outboundRemoved > 0) {
382
+ await saveFederationRegistry(client, appname, registry);
383
+ }
384
+
385
+ return { inboundRemoved, outboundRemoved };
386
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Hierarchical Aggregation (Upcast Operations)
3
+ * FR-025 to FR-027
4
+ */
5
+
6
+ import { getParents, isValidH3 } from '../spatial/h3-operations.js';
7
+ import { createHologram } from '../federation/hologram.js';
8
+ import { write } from '../storage/nostr-wrapper.js';
9
+
10
+ /**
11
+ * Upcast data to parent holons in hierarchy
12
+ * @param {Object} client - Nostr client instance
13
+ * @param {string} appname - Application namespace
14
+ * @param {string} holonId - Source holon ID (must be H3)
15
+ * @param {string} lensName - Lens name
16
+ * @param {string} dataId - Data ID
17
+ * @param {Object} options - Upcast options
18
+ * @param {number} options.maxLevel - Maximum levels to propagate (default: 3)
19
+ * @param {string} options.operation - 'summarize', 'aggregate', or 'concatenate'
20
+ * @returns {Promise<boolean>} Success indicator
21
+ */
22
+ export async function upcast(client, appname, holonId, lensName, dataId, options = {}) {
23
+ const { maxLevel = 3, operation = 'summarize' } = options;
24
+
25
+ // Validate H3 format
26
+ if (!isValidH3(holonId)) {
27
+ throw new Error('Upcast only supported for geographic (H3) holons, not noospheric holons');
28
+ }
29
+
30
+ // Get parent holons
31
+ const parents = getParents(holonId);
32
+ const targetParents = parents.slice(0, maxLevel);
33
+
34
+ if (targetParents.length === 0) {
35
+ return true; // Already at root
36
+ }
37
+
38
+ // Create holograms in parent holons
39
+ const promises = targetParents.map(async (parentHolon) => {
40
+ return propagateToParent(client, appname, holonId, parentHolon, lensName, dataId, operation);
41
+ });
42
+
43
+ await Promise.all(promises);
44
+ return true;
45
+ }
46
+
47
+ /**
48
+ * Propagate data to parent holon
49
+ * @private
50
+ */
51
+ async function propagateToParent(
52
+ client,
53
+ appname,
54
+ sourceHolon,
55
+ parentHolon,
56
+ lensName,
57
+ dataId,
58
+ operation
59
+ ) {
60
+ // Create hologram reference
61
+ const hologram = createHologram(sourceHolon, parentHolon, lensName, dataId, appname);
62
+
63
+ // Add operation metadata
64
+ hologram._meta.operation = operation;
65
+ hologram._meta.upcast = true;
66
+
67
+ // Write to parent holon
68
+ const path = `${appname}/${parentHolon}/${lensName}/${dataId}`;
69
+ return write(client, path, hologram);
70
+ }
71
+
72
+ /**
73
+ * Summarize operation (count aggregation)
74
+ * @param {Object[]} items - Data items
75
+ * @returns {Object} Summary object
76
+ */
77
+ export function summarize(items) {
78
+ return {
79
+ operation: 'summarize',
80
+ count: items.length,
81
+ summary: true,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Aggregate operation (merge objects)
87
+ * @param {Object[]} items - Data items
88
+ * @returns {Object} Aggregated object
89
+ */
90
+ export function aggregate(items) {
91
+ const result = { operation: 'aggregate' };
92
+
93
+ for (const item of items) {
94
+ Object.assign(result, item);
95
+ }
96
+
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * Concatenate operation (array merge)
102
+ * @param {Array[]} items - Array items
103
+ * @returns {Array} Concatenated array
104
+ */
105
+ export function concatenate(items) {
106
+ return {
107
+ operation: 'concatenate',
108
+ items: items.flat(),
109
+ };
110
+ }