holosphere 2.0.0-alpha11 → 2.0.0-alpha13
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/dist/{2019-D2OG2idw.js → 2019-CLMqIAfQ.js} +1722 -1668
- package/dist/{2019-D2OG2idw.js.map → 2019-CLMqIAfQ.js.map} +1 -1
- package/dist/2019-Cp3uYhyY.cjs +8 -0
- package/dist/{2019-EION3wKo.cjs.map → 2019-Cp3uYhyY.cjs.map} +1 -1
- package/dist/browser-D6cNVl0v.cjs +2 -0
- package/dist/{browser-Cq59Ij19.cjs.map → browser-D6cNVl0v.cjs.map} +1 -1
- package/dist/{browser-BSniCNqO.js → browser-nUQt1cnB.js} +2 -2
- package/dist/{browser-BSniCNqO.js.map → browser-nUQt1cnB.js.map} +1 -1
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +67 -50
- package/dist/{index-D-jZhliX.js → index-BN_uoxQK.js} +20324 -735
- package/dist/index-BN_uoxQK.js.map +1 -0
- package/dist/{index-Bl6rM1NW.js → index-CoAjtqsD.js} +2 -2
- package/dist/{index-Bl6rM1NW.js.map → index-CoAjtqsD.js.map} +1 -1
- package/dist/{index-Bwg3OzRM.cjs → index-Cp3tI53z.cjs} +3 -3
- package/dist/{index-Bwg3OzRM.cjs.map → index-Cp3tI53z.cjs.map} +1 -1
- package/dist/index-DJjGSwXG.cjs +13 -0
- package/dist/index-DJjGSwXG.cjs.map +1 -0
- package/dist/index-V8EHMYEY.cjs +29 -0
- package/dist/index-V8EHMYEY.cjs.map +1 -0
- package/dist/index-Z5TstN1e.js +11663 -0
- package/dist/index-Z5TstN1e.js.map +1 -0
- package/dist/indexeddb-storage-CZK5A7XH.cjs +2 -0
- package/dist/indexeddb-storage-CZK5A7XH.cjs.map +1 -0
- package/dist/{indexeddb-storage-5eiUNsHC.js → indexeddb-storage-bpA01pAU.js} +39 -2
- package/dist/indexeddb-storage-bpA01pAU.js.map +1 -0
- package/dist/{memory-storage-DMt36uZO.cjs → memory-storage-B1k8Jszd.cjs} +2 -2
- package/dist/{memory-storage-DMt36uZO.cjs.map → memory-storage-B1k8Jszd.cjs.map} +1 -1
- package/dist/{memory-storage-CI-gfmuG.js → memory-storage-BqhmytP_.js} +2 -2
- package/dist/{memory-storage-CI-gfmuG.js.map → memory-storage-BqhmytP_.js.map} +1 -1
- package/docs/FEDERATION.md +474 -0
- package/package.json +3 -1
- package/src/crypto/nostr-utils.js +7 -0
- package/src/crypto/secp256k1.js +104 -38
- package/src/federation/capabilities.js +162 -0
- package/src/federation/card-storage.js +376 -0
- package/src/federation/handshake.js +561 -9
- package/src/federation/hologram.js +194 -57
- package/src/federation/holon-registry.js +187 -0
- package/src/federation/index.js +68 -0
- package/src/federation/registry.js +164 -6
- package/src/federation/request-card.js +373 -0
- package/src/hierarchical/upcast.js +19 -3
- package/src/index.js +209 -75
- package/src/lib/federation-methods.js +527 -5
- package/src/storage/indexeddb-storage.js +41 -0
- package/src/storage/nostr-async.js +14 -5
- package/src/storage/nostr-client.js +471 -155
- package/src/storage/nostr-wrapper.js +6 -3
- package/dist/2019-EION3wKo.cjs +0 -8
- package/dist/_commonjsHelpers-C37NGDzP.cjs +0 -2
- package/dist/_commonjsHelpers-C37NGDzP.cjs.map +0 -1
- package/dist/_commonjsHelpers-CUmg6egw.js +0 -7
- package/dist/_commonjsHelpers-CUmg6egw.js.map +0 -1
- package/dist/browser-Cq59Ij19.cjs +0 -2
- package/dist/index-D-jZhliX.js.map +0 -1
- package/dist/index-Dc6Z8Aob.cjs +0 -18
- package/dist/index-Dc6Z8Aob.cjs.map +0 -1
- package/dist/indexeddb-storage-5eiUNsHC.js.map +0 -1
- package/dist/indexeddb-storage-FNFUVvTJ.cjs +0 -2
- package/dist/indexeddb-storage-FNFUVvTJ.cjs.map +0 -1
- package/dist/secp256k1-CEwJNcfV.js +0 -1890
- package/dist/secp256k1-CEwJNcfV.js.map +0 -1
- package/dist/secp256k1-CiEONUnj.cjs +0 -12
- package/dist/secp256k1-CiEONUnj.cjs.map +0 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-V8EHMYEY.cjs");class t extends e.PersistentStorage{constructor(){super(),this.db=null,this.dbName="",this.storeName="events"}async init(e){this.dbName=`holosphere_${e}`;try{await this._openDatabase()}catch(t){if("VersionError"!==t.name)throw t;console.warn(`[IndexedDBStorage] Version conflict detected for ${this.dbName}, recreating database...`),await this._deleteDatabase(),await this._openDatabase()}}async _openDatabase(){return new Promise((e,t)=>{const r=indexedDB.open(this.dbName,1);r.onerror=()=>t(r.error),r.onsuccess=()=>{this.db=r.result,e(void 0)},r.onupgradeneeded=e=>{const t=e.target.result;if(!t.objectStoreNames.contains(this.storeName)){t.createObjectStore(this.storeName,{keyPath:"key"}).createIndex("key","key",{unique:!1})}}})}async _deleteDatabase(){return new Promise((e,t)=>{const r=indexedDB.deleteDatabase(this.dbName);r.onsuccess=()=>e(void 0),r.onerror=()=>t(r.error),r.onblocked=()=>{console.warn(`[IndexedDBStorage] Database deletion blocked for ${this.dbName}`),e(void 0)}})}async put(e,t){return new Promise((r,s)=>{if(!this.db)return void s(new Error("Database not initialized"));const o=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).put({key:e,event:t,timestamp:Date.now()});o.onsuccess=()=>r(void 0),o.onerror=()=>s(o.error)})}async get(e){return new Promise((t,r)=>{if(!this.db)return void r(new Error("Database not initialized"));const s=this.db.transaction([this.storeName],"readonly").objectStore(this.storeName).get(e);s.onsuccess=()=>{const e=s.result;t(e?e.event:null)},s.onerror=()=>r(s.error)})}async getAll(e){return new Promise((t,r)=>{if(!this.db)return void r(new Error("Database not initialized"));const s=this.db.transaction([this.storeName],"readonly").objectStore(this.storeName),o=[];let n;if(e){const t=IDBKeyRange.bound(e,e+"",!1,!1);n=s.openCursor(t)}else n=s.openCursor();n.onsuccess=e=>{const r=e.target.result;r?(o.push(r.value.event),r.continue()):t(o)},n.onerror=()=>r(n.error)})}async delete(e){return new Promise((t,r)=>{if(!this.db)return void r(new Error("Database not initialized"));const s=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).delete(e);s.onsuccess=()=>t(void 0),s.onerror=()=>r(s.error)})}async clear(){return new Promise((e,t)=>{if(!this.db)return void t(new Error("Database not initialized"));const r=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).clear();r.onsuccess=()=>e(void 0),r.onerror=()=>t(r.error)})}async close(){this.db&&(this.db.close(),this.db=null)}}exports.IndexedDBStorage=t;
|
|
2
|
+
//# sourceMappingURL=indexeddb-storage-CZK5A7XH.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexeddb-storage-CZK5A7XH.cjs","sources":["../src/storage/indexeddb-storage.js"],"sourcesContent":["/**\n * @fileoverview IndexedDB storage adapter for browsers.\n *\n * Provides persistent storage with good performance using browser IndexedDB.\n * Uses B-tree indexes for efficient prefix queries.\n *\n * @module storage/indexeddb-storage\n */\n\nimport { PersistentStorage } from './persistent-storage.js';\n\n/**\n * IndexedDB storage adapter for browsers.\n *\n * Provides high-performance persistent storage using IndexedDB with efficient prefix queries.\n *\n * @class IndexedDBStorage\n * @extends PersistentStorage\n * @example\n * const storage = new IndexedDBStorage();\n * await storage.init('myapp');\n * await storage.put('key1', { id: 'event1', content: 'test' });\n */\nexport class IndexedDBStorage extends PersistentStorage {\n /**\n * Create a new IndexedDBStorage instance.\n */\n constructor() {\n super();\n /** @type {IDBDatabase|null} */\n this.db = null;\n /** @type {string} */\n this.dbName = '';\n /** @type {string} */\n this.storeName = 'events';\n }\n\n /**\n * Initialize storage with namespace.\n *\n * Creates or opens the IndexedDB database and object store.\n * Handles version conflicts by deleting and recreating the database if needed.\n *\n * @param {string} namespace - Storage namespace\n * @returns {Promise<void>}\n */\n async init(namespace) {\n this.dbName = `holosphere_${namespace}`;\n\n try {\n await this._openDatabase();\n } catch (error) {\n // Handle version conflict by deleting and recreating the database\n if (error.name === 'VersionError') {\n console.warn(`[IndexedDBStorage] Version conflict detected for ${this.dbName}, recreating database...`);\n await this._deleteDatabase();\n await this._openDatabase();\n } else {\n throw error;\n }\n }\n }\n\n /**\n * Open the IndexedDB database.\n *\n * @returns {Promise<void>}\n * @private\n */\n async _openDatabase() {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(this.dbName, 1);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n this.db = request.result;\n resolve(undefined);\n };\n\n request.onupgradeneeded = (event) => {\n const target = /** @type {IDBOpenDBRequest} */ (event.target);\n const db = target.result;\n\n // Create object store if it doesn't exist\n if (!db.objectStoreNames.contains(this.storeName)) {\n const objectStore = db.createObjectStore(this.storeName, { keyPath: 'key' });\n // Create index for prefix queries\n objectStore.createIndex('key', 'key', { unique: false });\n }\n };\n });\n }\n\n /**\n * Delete the IndexedDB database.\n *\n * @returns {Promise<void>}\n * @private\n */\n async _deleteDatabase() {\n return new Promise((resolve, reject) => {\n const request = indexedDB.deleteDatabase(this.dbName);\n request.onsuccess = () => resolve(undefined);\n request.onerror = () => reject(request.error);\n request.onblocked = () => {\n console.warn(`[IndexedDBStorage] Database deletion blocked for ${this.dbName}`);\n // Still resolve - the next open attempt will handle it\n resolve(undefined);\n };\n });\n }\n\n /**\n * Store an event.\n *\n * @param {string} key - Storage key\n * @param {Object} event - Event data\n * @returns {Promise<void>}\n */\n async put(key, event) {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readwrite');\n const objectStore = transaction.objectStore(this.storeName);\n\n const request = objectStore.put({ key, event, timestamp: Date.now() });\n\n request.onsuccess = () => resolve(undefined);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Retrieve an event.\n *\n * @param {string} key - Storage key\n * @returns {Promise<Object|null>} Event data or null\n */\n async get(key) {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readonly');\n const objectStore = transaction.objectStore(this.storeName);\n\n const request = objectStore.get(key);\n\n request.onsuccess = () => {\n const result = request.result;\n resolve(result ? result.event : null);\n };\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Retrieve all events matching a prefix.\n *\n * Uses IDBKeyRange for efficient B-tree index queries.\n *\n * @param {string} prefix - Key prefix to match\n * @returns {Promise<any[]>} Array of matching events\n */\n async getAll(prefix) {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readonly');\n const objectStore = transaction.objectStore(this.storeName);\n\n /** @type {any[]} */\n const results = [];\n\n // Use IDBKeyRange for efficient prefix query instead of full table scan\n // This creates a range from \"prefix\" to \"prefix\\uffff\" (highest unicode char)\n // which efficiently uses the B-tree index\n let request;\n if (prefix) {\n const range = IDBKeyRange.bound(prefix, prefix + '\\uffff', false, false);\n request = objectStore.openCursor(range);\n } else {\n // No prefix = get all\n request = objectStore.openCursor();\n }\n\n request.onsuccess = (event) => {\n const target = /** @type {IDBRequest} */ (event.target);\n const cursor = target.result;\n if (cursor) {\n results.push(cursor.value.event);\n cursor.continue();\n } else {\n resolve(results);\n }\n };\n\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Delete an event.\n *\n * @param {string} key - Storage key\n * @returns {Promise<void>}\n */\n async delete(key) {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readwrite');\n const objectStore = transaction.objectStore(this.storeName);\n\n const request = objectStore.delete(key);\n\n request.onsuccess = () => resolve(undefined);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Clear all stored events.\n *\n * @returns {Promise<void>}\n */\n async clear() {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readwrite');\n const objectStore = transaction.objectStore(this.storeName);\n\n const request = objectStore.clear();\n\n request.onsuccess = () => resolve(undefined);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Close the database connection.\n *\n * @returns {Promise<void>}\n */\n async close() {\n if (this.db) {\n this.db.close();\n this.db = null;\n }\n }\n}\n"],"names":["IndexedDBStorage","PersistentStorage","constructor","super","this","db","dbName","storeName","init","namespace","_openDatabase","error","name","console","warn","_deleteDatabase","Promise","resolve","reject","request","indexedDB","open","onerror","onsuccess","result","onupgradeneeded","event","objectStoreNames","contains","createObjectStore","keyPath","createIndex","unique","deleteDatabase","onblocked","put","key","Error","transaction","objectStore","timestamp","Date","now","get","getAll","prefix","results","range","IDBKeyRange","bound","openCursor","cursor","push","value","continue","delete","clear","close"],"mappings":"wHAuBO,MAAMA,UAAyBC,EAAAA,kBAIpC,WAAAC,GACEC,QAEAC,KAAKC,GAAK,KAEVD,KAAKE,OAAS,GAEdF,KAAKG,UAAY,QACnB,CAWA,UAAMC,CAAKC,GACTL,KAAKE,OAAS,cAAcG,IAE5B,UACQL,KAAKM,eACb,OAASC,GAEP,GAAmB,iBAAfA,EAAMC,KAKR,MAAMD,EAJNE,QAAQC,KAAK,oDAAoDV,KAAKE,wCAChEF,KAAKW,wBACLX,KAAKM,eAIf,CACF,CAQA,mBAAMA,GACJ,OAAO,IAAIM,QAAQ,CAACC,EAASC,KAC3B,MAAMC,EAAUC,UAAUC,KAAKjB,KAAKE,OAAQ,GAE5Ca,EAAQG,QAAU,IAAMJ,EAAOC,EAAQR,OACvCQ,EAAQI,UAAY,KAClBnB,KAAKC,GAAKc,EAAQK,OAClBP,OAAQ,IAGVE,EAAQM,gBAAmBC,IACzB,MACMrB,EAD0CqB,EAAM,OACpCF,OAGlB,IAAKnB,EAAGsB,iBAAiBC,SAASxB,KAAKG,WAAY,CAC7BF,EAAGwB,kBAAkBzB,KAAKG,UAAW,CAAEuB,QAAS,QAExDC,YAAY,MAAO,MAAO,CAAEC,QAAQ,GAClD,IAGN,CAQA,qBAAMjB,GACJ,OAAO,IAAIC,QAAQ,CAACC,EAASC,KAC3B,MAAMC,EAAUC,UAAUa,eAAe7B,KAAKE,QAC9Ca,EAAQI,UAAY,IAAMN,OAAQ,GAClCE,EAAQG,QAAU,IAAMJ,EAAOC,EAAQR,OACvCQ,EAAQe,UAAY,KAClBrB,QAAQC,KAAK,oDAAoDV,KAAKE,UAEtEW,OAAQ,KAGd,CASA,SAAMkB,CAAIC,EAAKV,GACb,OAAO,IAAIV,QAAQ,CAACC,EAASC,KAC3B,IAAKd,KAAKC,GAER,YADAa,EAAO,IAAImB,MAAM,6BAGnB,MAGMlB,EAHcf,KAAKC,GAAGiC,YAAY,CAAClC,KAAKG,WAAY,aAC1BgC,YAAYnC,KAAKG,WAErB4B,IAAI,CAAEC,MAAKV,QAAOc,UAAWC,KAAKC,QAE9DvB,EAAQI,UAAY,IAAMN,OAAQ,GAClCE,EAAQG,QAAU,IAAMJ,EAAOC,EAAQR,QAE3C,CAQA,SAAMgC,CAAIP,GACR,OAAO,IAAIpB,QAAQ,CAACC,EAASC,KAC3B,IAAKd,KAAKC,GAER,YADAa,EAAO,IAAImB,MAAM,6BAGnB,MAGMlB,EAHcf,KAAKC,GAAGiC,YAAY,CAAClC,KAAKG,WAAY,YAC1BgC,YAAYnC,KAAKG,WAErBoC,IAAIP,GAEhCjB,EAAQI,UAAY,KAClB,MAAMC,EAASL,EAAQK,OACvBP,EAAQO,EAASA,EAAOE,MAAQ,OAElCP,EAAQG,QAAU,IAAMJ,EAAOC,EAAQR,QAE3C,CAUA,YAAMiC,CAAOC,GACX,OAAO,IAAI7B,QAAQ,CAACC,EAASC,KAC3B,IAAKd,KAAKC,GAER,YADAa,EAAO,IAAImB,MAAM,6BAGnB,MACME,EADcnC,KAAKC,GAAGiC,YAAY,CAAClC,KAAKG,WAAY,YAC1BgC,YAAYnC,KAAKG,WAG3CuC,EAAU,GAKhB,IAAI3B,EACJ,GAAI0B,EAAQ,CACV,MAAME,EAAQC,YAAYC,MAAMJ,EAAQA,EAAS,KAAU,GAAO,GAClE1B,EAAUoB,EAAYW,WAAWH,EACnC,MAEE5B,EAAUoB,EAAYW,aAGxB/B,EAAQI,UAAaG,IACnB,MACMyB,EADoCzB,EAAM,OAC1BF,OAClB2B,GACFL,EAAQM,KAAKD,EAAOE,MAAM3B,OAC1ByB,EAAOG,YAEPrC,EAAQ6B,IAIZ3B,EAAQG,QAAU,IAAMJ,EAAOC,EAAQR,QAE3C,CAQA,YAAM,CAAOyB,GACX,OAAO,IAAIpB,QAAQ,CAACC,EAASC,KAC3B,IAAKd,KAAKC,GAER,YADAa,EAAO,IAAImB,MAAM,6BAGnB,MAGMlB,EAHcf,KAAKC,GAAGiC,YAAY,CAAClC,KAAKG,WAAY,aAC1BgC,YAAYnC,KAAKG,WAErBgD,OAAOnB,GAEnCjB,EAAQI,UAAY,IAAMN,OAAQ,GAClCE,EAAQG,QAAU,IAAMJ,EAAOC,EAAQR,QAE3C,CAOA,WAAM6C,GACJ,OAAO,IAAIxC,QAAQ,CAACC,EAASC,KAC3B,IAAKd,KAAKC,GAER,YADAa,EAAO,IAAImB,MAAM,6BAGnB,MAGMlB,EAHcf,KAAKC,GAAGiC,YAAY,CAAClC,KAAKG,WAAY,aAC1BgC,YAAYnC,KAAKG,WAErBiD,QAE5BrC,EAAQI,UAAY,IAAMN,OAAQ,GAClCE,EAAQG,QAAU,IAAMJ,EAAOC,EAAQR,QAE3C,CAOA,WAAM8C,GACArD,KAAKC,KACPD,KAAKC,GAAGoD,QACRrD,KAAKC,GAAK,KAEd"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { aV as PersistentStorage } from "./index-BN_uoxQK.js";
|
|
2
2
|
class IndexedDBStorage extends PersistentStorage {
|
|
3
3
|
/**
|
|
4
4
|
* Create a new IndexedDBStorage instance.
|
|
@@ -13,12 +13,32 @@ class IndexedDBStorage extends PersistentStorage {
|
|
|
13
13
|
* Initialize storage with namespace.
|
|
14
14
|
*
|
|
15
15
|
* Creates or opens the IndexedDB database and object store.
|
|
16
|
+
* Handles version conflicts by deleting and recreating the database if needed.
|
|
16
17
|
*
|
|
17
18
|
* @param {string} namespace - Storage namespace
|
|
18
19
|
* @returns {Promise<void>}
|
|
19
20
|
*/
|
|
20
21
|
async init(namespace) {
|
|
21
22
|
this.dbName = `holosphere_${namespace}`;
|
|
23
|
+
try {
|
|
24
|
+
await this._openDatabase();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.name === "VersionError") {
|
|
27
|
+
console.warn(`[IndexedDBStorage] Version conflict detected for ${this.dbName}, recreating database...`);
|
|
28
|
+
await this._deleteDatabase();
|
|
29
|
+
await this._openDatabase();
|
|
30
|
+
} else {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Open the IndexedDB database.
|
|
37
|
+
*
|
|
38
|
+
* @returns {Promise<void>}
|
|
39
|
+
* @private
|
|
40
|
+
*/
|
|
41
|
+
async _openDatabase() {
|
|
22
42
|
return new Promise((resolve, reject) => {
|
|
23
43
|
const request = indexedDB.open(this.dbName, 1);
|
|
24
44
|
request.onerror = () => reject(request.error);
|
|
@@ -39,6 +59,23 @@ class IndexedDBStorage extends PersistentStorage {
|
|
|
39
59
|
};
|
|
40
60
|
});
|
|
41
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Delete the IndexedDB database.
|
|
64
|
+
*
|
|
65
|
+
* @returns {Promise<void>}
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
async _deleteDatabase() {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const request = indexedDB.deleteDatabase(this.dbName);
|
|
71
|
+
request.onsuccess = () => resolve(void 0);
|
|
72
|
+
request.onerror = () => reject(request.error);
|
|
73
|
+
request.onblocked = () => {
|
|
74
|
+
console.warn(`[IndexedDBStorage] Database deletion blocked for ${this.dbName}`);
|
|
75
|
+
resolve(void 0);
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
42
79
|
/**
|
|
43
80
|
* Store an event.
|
|
44
81
|
*
|
|
@@ -173,4 +210,4 @@ class IndexedDBStorage extends PersistentStorage {
|
|
|
173
210
|
export {
|
|
174
211
|
IndexedDBStorage
|
|
175
212
|
};
|
|
176
|
-
//# sourceMappingURL=indexeddb-storage-
|
|
213
|
+
//# sourceMappingURL=indexeddb-storage-bpA01pAU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexeddb-storage-bpA01pAU.js","sources":["../src/storage/indexeddb-storage.js"],"sourcesContent":["/**\n * @fileoverview IndexedDB storage adapter for browsers.\n *\n * Provides persistent storage with good performance using browser IndexedDB.\n * Uses B-tree indexes for efficient prefix queries.\n *\n * @module storage/indexeddb-storage\n */\n\nimport { PersistentStorage } from './persistent-storage.js';\n\n/**\n * IndexedDB storage adapter for browsers.\n *\n * Provides high-performance persistent storage using IndexedDB with efficient prefix queries.\n *\n * @class IndexedDBStorage\n * @extends PersistentStorage\n * @example\n * const storage = new IndexedDBStorage();\n * await storage.init('myapp');\n * await storage.put('key1', { id: 'event1', content: 'test' });\n */\nexport class IndexedDBStorage extends PersistentStorage {\n /**\n * Create a new IndexedDBStorage instance.\n */\n constructor() {\n super();\n /** @type {IDBDatabase|null} */\n this.db = null;\n /** @type {string} */\n this.dbName = '';\n /** @type {string} */\n this.storeName = 'events';\n }\n\n /**\n * Initialize storage with namespace.\n *\n * Creates or opens the IndexedDB database and object store.\n * Handles version conflicts by deleting and recreating the database if needed.\n *\n * @param {string} namespace - Storage namespace\n * @returns {Promise<void>}\n */\n async init(namespace) {\n this.dbName = `holosphere_${namespace}`;\n\n try {\n await this._openDatabase();\n } catch (error) {\n // Handle version conflict by deleting and recreating the database\n if (error.name === 'VersionError') {\n console.warn(`[IndexedDBStorage] Version conflict detected for ${this.dbName}, recreating database...`);\n await this._deleteDatabase();\n await this._openDatabase();\n } else {\n throw error;\n }\n }\n }\n\n /**\n * Open the IndexedDB database.\n *\n * @returns {Promise<void>}\n * @private\n */\n async _openDatabase() {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(this.dbName, 1);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n this.db = request.result;\n resolve(undefined);\n };\n\n request.onupgradeneeded = (event) => {\n const target = /** @type {IDBOpenDBRequest} */ (event.target);\n const db = target.result;\n\n // Create object store if it doesn't exist\n if (!db.objectStoreNames.contains(this.storeName)) {\n const objectStore = db.createObjectStore(this.storeName, { keyPath: 'key' });\n // Create index for prefix queries\n objectStore.createIndex('key', 'key', { unique: false });\n }\n };\n });\n }\n\n /**\n * Delete the IndexedDB database.\n *\n * @returns {Promise<void>}\n * @private\n */\n async _deleteDatabase() {\n return new Promise((resolve, reject) => {\n const request = indexedDB.deleteDatabase(this.dbName);\n request.onsuccess = () => resolve(undefined);\n request.onerror = () => reject(request.error);\n request.onblocked = () => {\n console.warn(`[IndexedDBStorage] Database deletion blocked for ${this.dbName}`);\n // Still resolve - the next open attempt will handle it\n resolve(undefined);\n };\n });\n }\n\n /**\n * Store an event.\n *\n * @param {string} key - Storage key\n * @param {Object} event - Event data\n * @returns {Promise<void>}\n */\n async put(key, event) {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readwrite');\n const objectStore = transaction.objectStore(this.storeName);\n\n const request = objectStore.put({ key, event, timestamp: Date.now() });\n\n request.onsuccess = () => resolve(undefined);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Retrieve an event.\n *\n * @param {string} key - Storage key\n * @returns {Promise<Object|null>} Event data or null\n */\n async get(key) {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readonly');\n const objectStore = transaction.objectStore(this.storeName);\n\n const request = objectStore.get(key);\n\n request.onsuccess = () => {\n const result = request.result;\n resolve(result ? result.event : null);\n };\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Retrieve all events matching a prefix.\n *\n * Uses IDBKeyRange for efficient B-tree index queries.\n *\n * @param {string} prefix - Key prefix to match\n * @returns {Promise<any[]>} Array of matching events\n */\n async getAll(prefix) {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readonly');\n const objectStore = transaction.objectStore(this.storeName);\n\n /** @type {any[]} */\n const results = [];\n\n // Use IDBKeyRange for efficient prefix query instead of full table scan\n // This creates a range from \"prefix\" to \"prefix\\uffff\" (highest unicode char)\n // which efficiently uses the B-tree index\n let request;\n if (prefix) {\n const range = IDBKeyRange.bound(prefix, prefix + '\\uffff', false, false);\n request = objectStore.openCursor(range);\n } else {\n // No prefix = get all\n request = objectStore.openCursor();\n }\n\n request.onsuccess = (event) => {\n const target = /** @type {IDBRequest} */ (event.target);\n const cursor = target.result;\n if (cursor) {\n results.push(cursor.value.event);\n cursor.continue();\n } else {\n resolve(results);\n }\n };\n\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Delete an event.\n *\n * @param {string} key - Storage key\n * @returns {Promise<void>}\n */\n async delete(key) {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readwrite');\n const objectStore = transaction.objectStore(this.storeName);\n\n const request = objectStore.delete(key);\n\n request.onsuccess = () => resolve(undefined);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Clear all stored events.\n *\n * @returns {Promise<void>}\n */\n async clear() {\n return new Promise((resolve, reject) => {\n if (!this.db) {\n reject(new Error('Database not initialized'));\n return;\n }\n const transaction = this.db.transaction([this.storeName], 'readwrite');\n const objectStore = transaction.objectStore(this.storeName);\n\n const request = objectStore.clear();\n\n request.onsuccess = () => resolve(undefined);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Close the database connection.\n *\n * @returns {Promise<void>}\n */\n async close() {\n if (this.db) {\n this.db.close();\n this.db = null;\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./index-
|
|
2
|
-
//# sourceMappingURL=memory-storage-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./index-V8EHMYEY.cjs");class e extends t.PersistentStorage{constructor(){super(),this.data=new Map,this.namespace=null}async init(t){this.namespace=t,e._globalStore||(e._globalStore=new Map),e._globalStore.has(t)||e._globalStore.set(t,new Map),this.data=e._globalStore.get(t)}async put(t,e){this.data.set(t,JSON.parse(JSON.stringify(e)))}async get(t){const e=this.data.get(t);return e?JSON.parse(JSON.stringify(e)):null}async getAll(t){const e=[];for(const[s,a]of this.data.entries())s.startsWith(t)&&e.push(JSON.parse(JSON.stringify(a)));return e}async delete(t){this.data.delete(t)}async clear(){this.data.clear()}async close(){}}e._globalStore=null,exports.MemoryStorage=e;
|
|
2
|
+
//# sourceMappingURL=memory-storage-B1k8Jszd.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-storage-
|
|
1
|
+
{"version":3,"file":"memory-storage-B1k8Jszd.cjs","sources":["../src/storage/memory-storage.js"],"sourcesContent":["/**\n * @fileoverview Memory-only storage adapter (fallback, no actual persistence).\n *\n * Provides in-memory storage for testing or when persistent storage is unavailable.\n * Data is shared across instances with the same namespace but lost on process restart.\n *\n * @module storage/memory-storage\n */\n\nimport { PersistentStorage } from './persistent-storage.js';\n\n/**\n * Memory-only storage adapter.\n *\n * Stores data in memory with no persistence across restarts.\n * Uses a global store to share data across instances with same namespace.\n *\n * @class MemoryStorage\n * @extends PersistentStorage\n * @example\n * const storage = new MemoryStorage();\n * await storage.init('myapp');\n * await storage.put('key1', { id: 'event1' });\n */\nexport class MemoryStorage extends PersistentStorage {\n /**\n * Create a new MemoryStorage instance.\n */\n constructor() {\n super();\n this.data = new Map();\n this.namespace = null;\n }\n\n /**\n * Initialize storage with namespace.\n *\n * @param {string} namespace - Storage namespace\n * @returns {Promise<void>}\n */\n async init(namespace) {\n this.namespace = namespace;\n // Check if there's existing data for this namespace\n if (!MemoryStorage._globalStore) {\n MemoryStorage._globalStore = new Map();\n }\n if (!MemoryStorage._globalStore.has(namespace)) {\n MemoryStorage._globalStore.set(namespace, new Map());\n }\n this.data = MemoryStorage._globalStore.get(namespace);\n }\n\n /**\n * Store an event.\n *\n * @param {string} key - Storage key\n * @param {Object} event - Event data\n * @returns {Promise<void>}\n */\n async put(key, event) {\n this.data.set(key, JSON.parse(JSON.stringify(event))); // Deep clone\n }\n\n /**\n * Retrieve an event.\n *\n * @param {string} key - Storage key\n * @returns {Promise<Object|null>} Event data or null\n */\n async get(key) {\n const data = this.data.get(key);\n return data ? JSON.parse(JSON.stringify(data)) : null; // Deep clone\n }\n\n /**\n * Retrieve all events matching a prefix.\n *\n * @param {string} prefix - Key prefix to match\n * @returns {Promise<any[]>} Array of matching events\n */\n async getAll(prefix) {\n /** @type {any[]} */\n const results = [];\n for (const [key, value] of this.data.entries()) {\n if (key.startsWith(prefix)) {\n results.push(JSON.parse(JSON.stringify(value)));\n }\n }\n return results;\n }\n\n /**\n * Delete an event.\n *\n * @param {string} key - Storage key\n * @returns {Promise<void>}\n */\n async delete(key) {\n this.data.delete(key);\n }\n\n /**\n * Clear all stored events.\n *\n * @returns {Promise<void>}\n */\n async clear() {\n this.data.clear();\n }\n\n /**\n * Close storage (no-op for memory storage).\n *\n * @returns {Promise<void>}\n */\n async close() {\n // Nothing to close for memory storage\n }\n}\n\n/**\n * Global store shared across instances with same namespace.\n * @private\n */\nMemoryStorage._globalStore = null;\n"],"names":["MemoryStorage","PersistentStorage","constructor","super","this","data","Map","namespace","init","_globalStore","has","set","get","put","key","event","JSON","parse","stringify","getAll","prefix","results","value","entries","startsWith","push","delete","clear","close"],"mappings":"wHAwBO,MAAMA,UAAsBC,EAAAA,kBAIjC,WAAAC,GACEC,QACAC,KAAKC,SAAWC,IAChBF,KAAKG,UAAY,IACnB,CAQA,UAAMC,CAAKD,GACTH,KAAKG,UAAYA,EAEZP,EAAcS,eACjBT,EAAcS,iBAAmBH,KAE9BN,EAAcS,aAAaC,IAAIH,IAClCP,EAAcS,aAAaE,IAAIJ,EAAW,IAAID,KAEhDF,KAAKC,KAAOL,EAAcS,aAAaG,IAAIL,EAC7C,CASA,SAAMM,CAAIC,EAAKC,GACbX,KAAKC,KAAKM,IAAIG,EAAKE,KAAKC,MAAMD,KAAKE,UAAUH,IAC/C,CAQA,SAAMH,CAAIE,GACR,MAAMT,EAAOD,KAAKC,KAAKO,IAAIE,GAC3B,OAAOT,EAAOW,KAAKC,MAAMD,KAAKE,UAAUb,IAAS,IACnD,CAQA,YAAMc,CAAOC,GAEX,MAAMC,EAAU,GAChB,IAAA,MAAYP,EAAKQ,KAAUlB,KAAKC,KAAKkB,UAC/BT,EAAIU,WAAWJ,IACjBC,EAAQI,KAAKT,KAAKC,MAAMD,KAAKE,UAAUI,KAG3C,OAAOD,CACT,CAQA,YAAM,CAAOP,GACXV,KAAKC,KAAKqB,OAAOZ,EACnB,CAOA,WAAMa,GACJvB,KAAKC,KAAKsB,OACZ,CAOA,WAAMC,GAEN,EAOF5B,EAAcS,aAAe"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { aV as PersistentStorage } from "./index-BN_uoxQK.js";
|
|
2
2
|
class MemoryStorage extends PersistentStorage {
|
|
3
3
|
/**
|
|
4
4
|
* Create a new MemoryStorage instance.
|
|
@@ -88,4 +88,4 @@ MemoryStorage._globalStore = null;
|
|
|
88
88
|
export {
|
|
89
89
|
MemoryStorage
|
|
90
90
|
};
|
|
91
|
-
//# sourceMappingURL=memory-storage-
|
|
91
|
+
//# sourceMappingURL=memory-storage-BqhmytP_.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-storage-
|
|
1
|
+
{"version":3,"file":"memory-storage-BqhmytP_.js","sources":["../src/storage/memory-storage.js"],"sourcesContent":["/**\n * @fileoverview Memory-only storage adapter (fallback, no actual persistence).\n *\n * Provides in-memory storage for testing or when persistent storage is unavailable.\n * Data is shared across instances with the same namespace but lost on process restart.\n *\n * @module storage/memory-storage\n */\n\nimport { PersistentStorage } from './persistent-storage.js';\n\n/**\n * Memory-only storage adapter.\n *\n * Stores data in memory with no persistence across restarts.\n * Uses a global store to share data across instances with same namespace.\n *\n * @class MemoryStorage\n * @extends PersistentStorage\n * @example\n * const storage = new MemoryStorage();\n * await storage.init('myapp');\n * await storage.put('key1', { id: 'event1' });\n */\nexport class MemoryStorage extends PersistentStorage {\n /**\n * Create a new MemoryStorage instance.\n */\n constructor() {\n super();\n this.data = new Map();\n this.namespace = null;\n }\n\n /**\n * Initialize storage with namespace.\n *\n * @param {string} namespace - Storage namespace\n * @returns {Promise<void>}\n */\n async init(namespace) {\n this.namespace = namespace;\n // Check if there's existing data for this namespace\n if (!MemoryStorage._globalStore) {\n MemoryStorage._globalStore = new Map();\n }\n if (!MemoryStorage._globalStore.has(namespace)) {\n MemoryStorage._globalStore.set(namespace, new Map());\n }\n this.data = MemoryStorage._globalStore.get(namespace);\n }\n\n /**\n * Store an event.\n *\n * @param {string} key - Storage key\n * @param {Object} event - Event data\n * @returns {Promise<void>}\n */\n async put(key, event) {\n this.data.set(key, JSON.parse(JSON.stringify(event))); // Deep clone\n }\n\n /**\n * Retrieve an event.\n *\n * @param {string} key - Storage key\n * @returns {Promise<Object|null>} Event data or null\n */\n async get(key) {\n const data = this.data.get(key);\n return data ? JSON.parse(JSON.stringify(data)) : null; // Deep clone\n }\n\n /**\n * Retrieve all events matching a prefix.\n *\n * @param {string} prefix - Key prefix to match\n * @returns {Promise<any[]>} Array of matching events\n */\n async getAll(prefix) {\n /** @type {any[]} */\n const results = [];\n for (const [key, value] of this.data.entries()) {\n if (key.startsWith(prefix)) {\n results.push(JSON.parse(JSON.stringify(value)));\n }\n }\n return results;\n }\n\n /**\n * Delete an event.\n *\n * @param {string} key - Storage key\n * @returns {Promise<void>}\n */\n async delete(key) {\n this.data.delete(key);\n }\n\n /**\n * Clear all stored events.\n *\n * @returns {Promise<void>}\n */\n async clear() {\n this.data.clear();\n }\n\n /**\n * Close storage (no-op for memory storage).\n *\n * @returns {Promise<void>}\n */\n async close() {\n // Nothing to close for memory storage\n }\n}\n\n/**\n * Global store shared across instances with same namespace.\n * @private\n */\nMemoryStorage._globalStore = null;\n"],"names":[],"mappings":";AAwBO,MAAM,sBAAsB,kBAAkB;AAAA;AAAA;AAAA;AAAA,EAInD,cAAc;AACZ,UAAK;AACL,SAAK,OAAO,oBAAI,IAAG;AACnB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,KAAK,WAAW;AACpB,SAAK,YAAY;AAEjB,QAAI,CAAC,cAAc,cAAc;AAC/B,oBAAc,eAAe,oBAAI,IAAG;AAAA,IACtC;AACA,QAAI,CAAC,cAAc,aAAa,IAAI,SAAS,GAAG;AAC9C,oBAAc,aAAa,IAAI,WAAW,oBAAI,IAAG,CAAE;AAAA,IACrD;AACA,SAAK,OAAO,cAAc,aAAa,IAAI,SAAS;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,IAAI,KAAK,OAAO;AACpB,SAAK,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC,CAAC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,IAAI,KAAK;AACb,UAAM,OAAO,KAAK,KAAK,IAAI,GAAG;AAC9B,WAAO,OAAO,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC,IAAI;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,QAAQ;AAEnB,UAAM,UAAU,CAAA;AAChB,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,WAAW;AAC9C,UAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,gBAAQ,KAAK,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC,CAAC;AAAA,MAChD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,KAAK;AAChB,SAAK,KAAK,OAAO,GAAG;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ;AACZ,SAAK,KAAK,MAAK;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ;AAAA,EAEd;AACF;AAMA,cAAc,eAAe;"}
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# HoloSphere Federation API
|
|
2
|
+
|
|
3
|
+
Federation enables data sharing between holons (agents) through **holograms** - lightweight references that maintain a single source of truth while allowing data to appear in multiple places.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { HoloSphere } from 'holosphere';
|
|
9
|
+
|
|
10
|
+
// Alice's instance
|
|
11
|
+
const alice = new HoloSphere({
|
|
12
|
+
appName: 'myapp',
|
|
13
|
+
privateKey: alicePrivateKey,
|
|
14
|
+
relays: ['wss://relay.example.com']
|
|
15
|
+
});
|
|
16
|
+
await alice.ready();
|
|
17
|
+
|
|
18
|
+
// Alice federates her 'tasks' lens to Bob (outbound)
|
|
19
|
+
await alice.federate(
|
|
20
|
+
alice.myHolon, // source: Alice's holon
|
|
21
|
+
bobPublicKey, // target: Bob's holon
|
|
22
|
+
'tasks', // lens to share
|
|
23
|
+
{ direction: 'outbound' }
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Bob now sees Alice's tasks as holograms in his holon
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Core Concepts
|
|
32
|
+
|
|
33
|
+
### Agent-Holon 1:1 Mapping
|
|
34
|
+
|
|
35
|
+
Each HoloSphere instance represents a single agent. The agent's **holon ID equals their public key**.
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
const hs = new HoloSphere({ privateKey: myPrivateKey });
|
|
39
|
+
await hs.ready();
|
|
40
|
+
|
|
41
|
+
console.log(hs.myHolon); // Your public key (64-char hex)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Ownership rules:**
|
|
45
|
+
- You can only write to your own holon (`hs.myHolon`)
|
|
46
|
+
- Use `asAgent` option to write as a different identity
|
|
47
|
+
- Capability tokens enable cross-holon writes
|
|
48
|
+
|
|
49
|
+
### Holograms
|
|
50
|
+
|
|
51
|
+
A **hologram** is a reference to data that lives in another holon. It contains:
|
|
52
|
+
- Pointer to the source (holon, lens, dataId)
|
|
53
|
+
- Cached copy of the data
|
|
54
|
+
- Capability token for access
|
|
55
|
+
- Local overrides (position, pinned status, etc.)
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
// Get data with hologram metadata visible
|
|
59
|
+
const items = await hs.get(myHolon, 'tasks', null, { resolveHolograms: false });
|
|
60
|
+
|
|
61
|
+
// Hologram structure:
|
|
62
|
+
{
|
|
63
|
+
id: 'task-1',
|
|
64
|
+
hologram: true, // Identifies as hologram
|
|
65
|
+
soul: 'app/sourceHolon/tasks/task-1',
|
|
66
|
+
target: {
|
|
67
|
+
holonId: 'sourcePublicKey',
|
|
68
|
+
lensName: 'tasks',
|
|
69
|
+
dataId: 'task-1'
|
|
70
|
+
},
|
|
71
|
+
capability: 'eyJ...', // JWT capability token
|
|
72
|
+
// Cached data from source:
|
|
73
|
+
title: 'Complete report',
|
|
74
|
+
status: 'pending',
|
|
75
|
+
// Local overrides:
|
|
76
|
+
x: 100,
|
|
77
|
+
y: 200
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Federation Directions
|
|
82
|
+
|
|
83
|
+
| Direction | Data Flow | Use Case |
|
|
84
|
+
|-----------|-----------|----------|
|
|
85
|
+
| `outbound` | Source → Target | Share your data with others |
|
|
86
|
+
| `inbound` | Source ← Target | Receive data from others |
|
|
87
|
+
| `bidirectional` | Source ↔ Target | Two-way sync |
|
|
88
|
+
|
|
89
|
+
### Capability Tokens
|
|
90
|
+
|
|
91
|
+
Capabilities authorize cross-holon operations. They are:
|
|
92
|
+
- **Automatically issued** during federation
|
|
93
|
+
- **Embedded in holograms** for transparent access
|
|
94
|
+
- **Scoped** to specific holons and lenses
|
|
95
|
+
- **Time-limited** (default: 1 year for self, 24h for cross-author)
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## API Reference
|
|
100
|
+
|
|
101
|
+
### `myHolon` Property
|
|
102
|
+
|
|
103
|
+
Returns the current agent's holon ID (public key).
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
const myHolonId = hs.myHolon;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `federate(source, target, lensName, options)`
|
|
110
|
+
|
|
111
|
+
Establishes federation between two holons for a specific lens.
|
|
112
|
+
|
|
113
|
+
**Parameters:**
|
|
114
|
+
|
|
115
|
+
| Parameter | Type | Description |
|
|
116
|
+
|-----------|------|-------------|
|
|
117
|
+
| `source` | `string` or `{holonId, authorPubKey}` | Source holon |
|
|
118
|
+
| `target` | `string` or `{holonId, authorPubKey}` | Target holon |
|
|
119
|
+
| `lensName` | `string` | Lens to federate |
|
|
120
|
+
| `options` | `object` | Federation options |
|
|
121
|
+
|
|
122
|
+
**Options:**
|
|
123
|
+
|
|
124
|
+
| Option | Type | Default | Description |
|
|
125
|
+
|--------|------|---------|-------------|
|
|
126
|
+
| `direction` | `'inbound'` \| `'outbound'` \| `'bidirectional'` | `'outbound'` | Data flow direction |
|
|
127
|
+
| `mode` | `'reference'` \| `'copy'` | `'reference'` | Create holograms or copies |
|
|
128
|
+
| `propagate` | `boolean` | `true` | Propagate existing data |
|
|
129
|
+
| `filter` | `function` | `null` | Filter which data to include |
|
|
130
|
+
| `permissions` | `string[]` | `['read']` | Capability permissions |
|
|
131
|
+
|
|
132
|
+
**Returns:** `Promise<Object>` with federation result
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
// Outbound: share my tasks with partner
|
|
136
|
+
await hs.federate(hs.myHolon, partnerPubKey, 'tasks', {
|
|
137
|
+
direction: 'outbound',
|
|
138
|
+
propagate: true
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Inbound: receive partner's events
|
|
142
|
+
await hs.federate(hs.myHolon, partnerPubKey, 'events', {
|
|
143
|
+
direction: 'inbound'
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Bidirectional: sync notes both ways
|
|
147
|
+
await hs.federate(hs.myHolon, partnerPubKey, 'notes', {
|
|
148
|
+
direction: 'bidirectional'
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// With filter: only share high-priority items
|
|
152
|
+
await hs.federate(hs.myHolon, partnerPubKey, 'tasks', {
|
|
153
|
+
direction: 'outbound',
|
|
154
|
+
filter: (item) => item.priority === 'high'
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### `get(holonId, lensName, dataId?, options?)`
|
|
159
|
+
|
|
160
|
+
Retrieves data with automatic hologram resolution.
|
|
161
|
+
|
|
162
|
+
**Options:**
|
|
163
|
+
|
|
164
|
+
| Option | Type | Default | Description |
|
|
165
|
+
|--------|------|---------|-------------|
|
|
166
|
+
| `resolveHolograms` | `boolean` | `true` | Resolve holograms to source data |
|
|
167
|
+
| `capabilityToken` | `string` | auto | Capability for cross-holon access |
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// Get all tasks (holograms resolved by default)
|
|
171
|
+
const tasks = await hs.get(myHolon, 'tasks');
|
|
172
|
+
// Returns: [{ id: 't1', title: 'My Task' }, { id: 't2', title: 'Partner Task' }]
|
|
173
|
+
|
|
174
|
+
// Get with hologram metadata visible
|
|
175
|
+
const tasks = await hs.get(myHolon, 'tasks', null, { resolveHolograms: false });
|
|
176
|
+
// Returns: [
|
|
177
|
+
// { id: 't1', title: 'My Task' },
|
|
178
|
+
// { id: 't2', hologram: true, target: {...}, title: 'Partner Task' }
|
|
179
|
+
// ]
|
|
180
|
+
|
|
181
|
+
// Get specific item
|
|
182
|
+
const task = await hs.get(myHolon, 'tasks', 'task-1');
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### `put(holonId, lensName, data, options?)`
|
|
186
|
+
|
|
187
|
+
Writes data with automatic propagation and write-through.
|
|
188
|
+
|
|
189
|
+
**Options:**
|
|
190
|
+
|
|
191
|
+
| Option | Type | Default | Description |
|
|
192
|
+
|--------|------|---------|-------------|
|
|
193
|
+
| `asAgent` | `string` | current | Private key to sign as |
|
|
194
|
+
| `autoPropagate` | `boolean` | `true` | Auto-propagate to federated holons |
|
|
195
|
+
| `blocking` | `boolean` | `false` | Wait for relay confirmation |
|
|
196
|
+
| `validate` | `boolean` | `true` | Validate against schema |
|
|
197
|
+
|
|
198
|
+
**Write-through behavior:** When updating a hologram, the write goes to the **source** holon if you have write capability.
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// Write to own holon
|
|
202
|
+
await hs.put(hs.myHolon, 'tasks', { id: 't1', title: 'New Task' });
|
|
203
|
+
|
|
204
|
+
// Update a hologram (writes through to source)
|
|
205
|
+
await hs.put(hs.myHolon, 'tasks', { id: 'partner-task', status: 'done' });
|
|
206
|
+
|
|
207
|
+
// Write as different agent
|
|
208
|
+
await hs.put(partnerHolon, 'tasks', data, { asAgent: partnerPrivateKey });
|
|
209
|
+
|
|
210
|
+
// Wait for relay confirmation
|
|
211
|
+
await hs.put(hs.myHolon, 'tasks', data, { blocking: true });
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `propagate(holonId, lensName, data, options?)`
|
|
215
|
+
|
|
216
|
+
Manually propagate data to all federated partners.
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
// Propagate specific item to all outbound partners
|
|
220
|
+
await hs.propagate(hs.myHolon, 'tasks', taskData);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### `unfederate(source, target, lensName)`
|
|
224
|
+
|
|
225
|
+
Removes federation between two holons.
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
await hs.unfederate(hs.myHolon, partnerPubKey, 'tasks');
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Examples
|
|
234
|
+
|
|
235
|
+
### Two Agents Sharing Tasks
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// === Alice's Code ===
|
|
239
|
+
const alice = new HoloSphere({
|
|
240
|
+
appName: 'taskapp',
|
|
241
|
+
privateKey: aliceKey
|
|
242
|
+
});
|
|
243
|
+
await alice.ready();
|
|
244
|
+
|
|
245
|
+
// Alice creates a task
|
|
246
|
+
await alice.put(alice.myHolon, 'tasks', {
|
|
247
|
+
id: 'task-1',
|
|
248
|
+
title: 'Review proposal',
|
|
249
|
+
status: 'pending'
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Alice shares tasks with Bob
|
|
253
|
+
await alice.federate(alice.myHolon, bobPubKey, 'tasks', {
|
|
254
|
+
direction: 'outbound'
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// === Bob's Code ===
|
|
258
|
+
const bob = new HoloSphere({
|
|
259
|
+
appName: 'taskapp',
|
|
260
|
+
privateKey: bobKey
|
|
261
|
+
});
|
|
262
|
+
await bob.ready();
|
|
263
|
+
|
|
264
|
+
// Bob receives Alice's federation (inbound from his perspective)
|
|
265
|
+
await bob.federate(bob.myHolon, alicePubKey, 'tasks', {
|
|
266
|
+
direction: 'inbound'
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Bob sees Alice's task as a hologram
|
|
270
|
+
const tasks = await bob.get(bob.myHolon, 'tasks');
|
|
271
|
+
console.log(tasks);
|
|
272
|
+
// [{ id: 'task-1', title: 'Review proposal', status: 'pending' }]
|
|
273
|
+
|
|
274
|
+
// Bob can check if it's a hologram
|
|
275
|
+
const rawTasks = await bob.get(bob.myHolon, 'tasks', null, { resolveHolograms: false });
|
|
276
|
+
console.log(rawTasks[0].hologram); // true
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Bidirectional Sync
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
// Both sides set up bidirectional federation
|
|
283
|
+
await alice.federate(alice.myHolon, bobPubKey, 'notes', {
|
|
284
|
+
direction: 'bidirectional'
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await bob.federate(bob.myHolon, alicePubKey, 'notes', {
|
|
288
|
+
direction: 'bidirectional'
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Now both see each other's notes
|
|
292
|
+
// Alice's view: her notes + Bob's notes as holograms
|
|
293
|
+
// Bob's view: his notes + Alice's notes as holograms
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Write-Through to Holograms
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
// Bob has Alice's task as a hologram
|
|
300
|
+
const tasks = await bob.get(bob.myHolon, 'tasks', null, { resolveHolograms: false });
|
|
301
|
+
const aliceTask = tasks.find(t => t.hologram && t.id === 'task-1');
|
|
302
|
+
|
|
303
|
+
// Bob updates the task (write-through to Alice's holon)
|
|
304
|
+
await bob.put(bob.myHolon, 'tasks', {
|
|
305
|
+
id: 'task-1',
|
|
306
|
+
status: 'completed' // This updates Alice's source
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Alice sees the update
|
|
310
|
+
const updated = await alice.get(alice.myHolon, 'tasks', 'task-1');
|
|
311
|
+
console.log(updated.status); // 'completed'
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Detecting Holograms in UI
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
// Get data with hologram info
|
|
318
|
+
const items = await hs.get(myHolon, 'tasks', null, { resolveHolograms: false });
|
|
319
|
+
|
|
320
|
+
items.forEach(item => {
|
|
321
|
+
if (item.hologram) {
|
|
322
|
+
// Show federated indicator
|
|
323
|
+
console.log(`${item.title} (from ${item.target.holonId.slice(0, 8)}...)`);
|
|
324
|
+
} else {
|
|
325
|
+
console.log(item.title);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Best Practices
|
|
333
|
+
|
|
334
|
+
### When to Use `blocking: true`
|
|
335
|
+
|
|
336
|
+
Use blocking mode when:
|
|
337
|
+
- You need to confirm data was saved before proceeding
|
|
338
|
+
- Testing or debugging federation
|
|
339
|
+
- Critical operations that must succeed
|
|
340
|
+
|
|
341
|
+
```javascript
|
|
342
|
+
// For critical writes
|
|
343
|
+
await hs.put(myHolon, 'tasks', importantData, { blocking: true });
|
|
344
|
+
|
|
345
|
+
// For normal writes, non-blocking is fine (faster)
|
|
346
|
+
await hs.put(myHolon, 'tasks', regularData);
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Handling Capability Expiration
|
|
350
|
+
|
|
351
|
+
Capabilities expire after their TTL (default 24h for cross-author). When a capability expires:
|
|
352
|
+
|
|
353
|
+
1. Hologram reads will fail
|
|
354
|
+
2. Write-through will be rejected
|
|
355
|
+
3. Re-establish federation to get new capability
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
try {
|
|
359
|
+
await hs.put(myHolon, 'tasks', update);
|
|
360
|
+
} catch (err) {
|
|
361
|
+
if (err.message.includes('capability')) {
|
|
362
|
+
// Re-federate to refresh capability
|
|
363
|
+
await hs.federate(myHolon, partnerKey, 'tasks', { direction: 'inbound' });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Query Deduplication
|
|
369
|
+
|
|
370
|
+
HoloSphere deduplicates queries within a 2-second window. If you need fresh data immediately after a write:
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
// Write
|
|
374
|
+
await hs.put(myHolon, 'tasks', { id: 't1', status: 'done' }, { blocking: true });
|
|
375
|
+
|
|
376
|
+
// Wait for dedup window if querying same path
|
|
377
|
+
await new Promise(r => setTimeout(r, 2100));
|
|
378
|
+
|
|
379
|
+
// Now query returns fresh data
|
|
380
|
+
const fresh = await hs.get(myHolon, 'tasks');
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Local Overrides on Holograms
|
|
384
|
+
|
|
385
|
+
Store local-only properties (position, pinned status) on holograms:
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
await hs.updateHologramOverrides(myHolon, 'tasks', 'task-1', {
|
|
389
|
+
x: 100,
|
|
390
|
+
y: 200,
|
|
391
|
+
pinned: true
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Error Handling
|
|
398
|
+
|
|
399
|
+
### AuthorizationError
|
|
400
|
+
|
|
401
|
+
Thrown when:
|
|
402
|
+
- Writing to a holon you don't own (without capability)
|
|
403
|
+
- Capability expired or invalid
|
|
404
|
+
- Write-through without write permission
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
try {
|
|
408
|
+
await hs.put(otherHolon, 'tasks', data);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
if (err.name === 'AuthorizationError') {
|
|
411
|
+
console.log('Cannot write to this holon');
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### ValidationError
|
|
417
|
+
|
|
418
|
+
Thrown when data doesn't match schema.
|
|
419
|
+
|
|
420
|
+
```javascript
|
|
421
|
+
try {
|
|
422
|
+
await hs.put(myHolon, 'tasks', invalidData);
|
|
423
|
+
} catch (err) {
|
|
424
|
+
if (err.name === 'ValidationError') {
|
|
425
|
+
console.log('Invalid data:', err.message);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## TypeScript Types
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
interface FederateOptions {
|
|
436
|
+
direction?: 'inbound' | 'outbound' | 'bidirectional';
|
|
437
|
+
mode?: 'reference' | 'copy';
|
|
438
|
+
propagate?: boolean;
|
|
439
|
+
filter?: (item: any) => boolean;
|
|
440
|
+
permissions?: string[];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
interface GetOptions {
|
|
444
|
+
resolveHolograms?: boolean;
|
|
445
|
+
capabilityToken?: string;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
interface PutOptions {
|
|
449
|
+
asAgent?: string;
|
|
450
|
+
autoPropagate?: boolean;
|
|
451
|
+
blocking?: boolean;
|
|
452
|
+
validate?: boolean;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
interface Hologram {
|
|
456
|
+
id: string;
|
|
457
|
+
hologram: true;
|
|
458
|
+
soul: string;
|
|
459
|
+
target: {
|
|
460
|
+
holonId: string;
|
|
461
|
+
lensName: string;
|
|
462
|
+
dataId: string;
|
|
463
|
+
authorPubKey?: string;
|
|
464
|
+
};
|
|
465
|
+
capability: string;
|
|
466
|
+
_meta?: {
|
|
467
|
+
sourceHolon: string;
|
|
468
|
+
sourcePubKey: string;
|
|
469
|
+
grantedAt: number;
|
|
470
|
+
};
|
|
471
|
+
// ... cached data fields
|
|
472
|
+
// ... local overrides
|
|
473
|
+
}
|
|
474
|
+
```
|