holosphere 1.1.20 → 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.
- package/.env.example +36 -0
- package/.eslintrc.json +16 -0
- package/.prettierrc.json +7 -0
- 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,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema Validation with Ajv and caching
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Ajv from 'ajv';
|
|
6
|
+
|
|
7
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
8
|
+
const schemaCache = new Map();
|
|
9
|
+
const CACHE_TTL = 3600000; // 1 hour in milliseconds
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validation Error class
|
|
13
|
+
*/
|
|
14
|
+
export class ValidationError extends Error {
|
|
15
|
+
constructor(message, errors = []) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'ValidationError';
|
|
18
|
+
this.errors = errors;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compile and cache JSON Schema
|
|
24
|
+
* @param {Object} schema - JSON Schema object
|
|
25
|
+
* @param {string} lensName - Lens identifier for caching
|
|
26
|
+
* @returns {Function} Ajv validator function
|
|
27
|
+
*/
|
|
28
|
+
export function compileSchema(schema, lensName) {
|
|
29
|
+
const cacheKey = lensName;
|
|
30
|
+
const cached = schemaCache.get(cacheKey);
|
|
31
|
+
|
|
32
|
+
// Check if cached and not expired
|
|
33
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
34
|
+
return cached.validator;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Compile new validator
|
|
38
|
+
try {
|
|
39
|
+
const validator = ajv.compile(schema);
|
|
40
|
+
schemaCache.set(cacheKey, {
|
|
41
|
+
validator,
|
|
42
|
+
timestamp: Date.now(),
|
|
43
|
+
});
|
|
44
|
+
return validator;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error(`Schema compilation failed: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validate data against schema
|
|
52
|
+
* @param {Object} data - Data to validate
|
|
53
|
+
* @param {Object} schema - JSON Schema object
|
|
54
|
+
* @param {string} lensName - Lens name for caching
|
|
55
|
+
* @param {boolean} strict - Throw error if invalid (default: false)
|
|
56
|
+
* @returns {boolean} True if valid
|
|
57
|
+
* @throws {ValidationError} If strict=true and validation fails
|
|
58
|
+
*/
|
|
59
|
+
export function validate(data, schema, lensName, strict = false) {
|
|
60
|
+
const validator = compileSchema(schema, lensName);
|
|
61
|
+
const valid = validator(data);
|
|
62
|
+
|
|
63
|
+
if (!valid) {
|
|
64
|
+
const errors = validator.errors || [];
|
|
65
|
+
const errorMessage = errors.map((e) => `${e.instancePath} ${e.message}`).join('; ');
|
|
66
|
+
|
|
67
|
+
if (strict) {
|
|
68
|
+
throw new ValidationError(`ValidationError: Validation failed: ${errorMessage}`, errors);
|
|
69
|
+
} else {
|
|
70
|
+
console.warn(`[Schema Validation Warning] ${lensName}: ${errorMessage}`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Clear schema cache for a lens
|
|
80
|
+
* @param {string} lensName - Lens name
|
|
81
|
+
*/
|
|
82
|
+
export function clearSchemaCache(lensName) {
|
|
83
|
+
schemaCache.delete(lensName);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Clear all cached schemas
|
|
88
|
+
*/
|
|
89
|
+
export function clearAllCaches() {
|
|
90
|
+
schemaCache.clear();
|
|
91
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
latLngToCell,
|
|
3
|
+
cellToParent,
|
|
4
|
+
cellToChildren,
|
|
5
|
+
isValidCell,
|
|
6
|
+
getResolution,
|
|
7
|
+
} from 'h3-js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Convert geographic coordinates to H3 holon ID
|
|
11
|
+
* @param {number} lat - Latitude (-90 to 90)
|
|
12
|
+
* @param {number} lng - Longitude (-180 to 180)
|
|
13
|
+
* @param {number} resolution - H3 resolution (0-15)
|
|
14
|
+
* @returns {string} H3 cell ID
|
|
15
|
+
*/
|
|
16
|
+
export function toHolon(lat, lng, resolution) {
|
|
17
|
+
// Validate inputs
|
|
18
|
+
if (typeof lat !== 'number' || lat < -90 || lat > 90) {
|
|
19
|
+
throw new RangeError(`Invalid latitude: ${lat}. Must be between -90 and 90.`);
|
|
20
|
+
}
|
|
21
|
+
if (typeof lng !== 'number' || lng < -180 || lng > 180) {
|
|
22
|
+
throw new RangeError(`Invalid longitude: ${lng}. Must be between -180 and 180.`);
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isInteger(resolution) || resolution < 0 || resolution > 15) {
|
|
25
|
+
throw new RangeError(`Invalid resolution: ${resolution}. Must be integer between 0 and 15.`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const h3Index = latLngToCell(lat, lng, resolution);
|
|
30
|
+
return h3Index;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`H3 conversion failed: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get all parent holons up the hierarchy
|
|
38
|
+
* @param {string} holonId - H3 cell ID
|
|
39
|
+
* @param {number} maxResolution - Stop at this resolution (default: 0)
|
|
40
|
+
* @returns {string[]} Array of parent H3 IDs (ascending hierarchy)
|
|
41
|
+
*/
|
|
42
|
+
export function getParents(holonId, maxResolution = 0) {
|
|
43
|
+
if (!isValidH3(holonId)) {
|
|
44
|
+
throw new Error(`Invalid H3 holon ID: ${holonId}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const currentResolution = getResolution(holonId);
|
|
48
|
+
const parents = [];
|
|
49
|
+
|
|
50
|
+
let currentCell = holonId;
|
|
51
|
+
for (let res = currentResolution - 1; res >= maxResolution; res--) {
|
|
52
|
+
try {
|
|
53
|
+
currentCell = cellToParent(currentCell, res);
|
|
54
|
+
parents.push(currentCell);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
break; // Reached root
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return parents;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get all child holons at next resolution level
|
|
65
|
+
* @param {string} holonId - H3 cell ID
|
|
66
|
+
* @returns {string[]} Array of child H3 IDs (7 children per hexagon)
|
|
67
|
+
*/
|
|
68
|
+
export function getChildren(holonId) {
|
|
69
|
+
if (!isValidH3(holonId)) {
|
|
70
|
+
throw new Error(`Invalid H3 holon ID: ${holonId}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const resolution = getResolution(holonId);
|
|
75
|
+
// H3 max resolution is 15, can't get children of resolution 15
|
|
76
|
+
if (resolution >= 15) {
|
|
77
|
+
throw new Error('Cannot get children of resolution 15 cell (maximum resolution)');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// cellToChildren requires the child resolution (one level down)
|
|
81
|
+
const children = cellToChildren(holonId, resolution + 1);
|
|
82
|
+
return children;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new Error(`Failed to get children: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate H3 cell ID format
|
|
90
|
+
* @param {string} holonId - H3 cell ID
|
|
91
|
+
* @returns {boolean} True if valid H3 format
|
|
92
|
+
*/
|
|
93
|
+
export function isValidH3(holonId) {
|
|
94
|
+
if (typeof holonId !== 'string') return false;
|
|
95
|
+
if (holonId.length < 15) return false;
|
|
96
|
+
if (!holonId.startsWith('8')) return false;
|
|
97
|
+
return isValidCell(holonId);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get H3 resolution level from cell ID
|
|
102
|
+
* @param {string} holonId - H3 cell ID
|
|
103
|
+
* @returns {number} Resolution (0-15)
|
|
104
|
+
*/
|
|
105
|
+
export function getH3Resolution(holonId) {
|
|
106
|
+
if (!isValidH3(holonId)) {
|
|
107
|
+
throw new Error(`Invalid H3 holon ID: ${holonId}`);
|
|
108
|
+
}
|
|
109
|
+
return getResolution(holonId);
|
|
110
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for creating storage backends
|
|
3
|
+
* Supports lazy loading of backend implementations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BACKEND_MODULES = {
|
|
7
|
+
nostr: './backends/nostr-backend.js',
|
|
8
|
+
gundb: './backends/gundb-backend.js',
|
|
9
|
+
activitypub: './backends/activitypub-backend.js',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Cache for loaded backend classes
|
|
13
|
+
const loadedBackends = new Map();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Factory for creating storage backends
|
|
17
|
+
*/
|
|
18
|
+
export class BackendFactory {
|
|
19
|
+
/**
|
|
20
|
+
* Get list of available backend types
|
|
21
|
+
* @returns {string[]} Available backend type names
|
|
22
|
+
*/
|
|
23
|
+
static getAvailableBackends() {
|
|
24
|
+
return Object.keys(BACKEND_MODULES);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a backend type is available
|
|
29
|
+
* @param {string} type - Backend type
|
|
30
|
+
* @returns {boolean} Whether the backend is available
|
|
31
|
+
*/
|
|
32
|
+
static isAvailable(type) {
|
|
33
|
+
return type in BACKEND_MODULES;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load a backend class dynamically
|
|
38
|
+
* @param {string} type - Backend type
|
|
39
|
+
* @returns {Promise<Class>} Backend class
|
|
40
|
+
*/
|
|
41
|
+
static async loadBackend(type) {
|
|
42
|
+
if (loadedBackends.has(type)) {
|
|
43
|
+
return loadedBackends.get(type);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const modulePath = BACKEND_MODULES[type];
|
|
47
|
+
if (!modulePath) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Unknown backend type: '${type}'. Available backends: ${Object.keys(BACKEND_MODULES).join(', ')}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const module = await import(modulePath);
|
|
55
|
+
const BackendClass = module.default || module[`${capitalize(type)}Backend`];
|
|
56
|
+
|
|
57
|
+
if (!BackendClass) {
|
|
58
|
+
throw new Error(`Backend module '${type}' does not export a valid backend class`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
loadedBackends.set(type, BackendClass);
|
|
62
|
+
return BackendClass;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Backend '${type}' requires additional dependencies. ` +
|
|
67
|
+
`Please install them: ${getDependencyHint(type)}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a storage backend instance
|
|
76
|
+
* @param {string} type - Backend type: 'nostr' | 'gundb' | 'activitypub'
|
|
77
|
+
* @param {Object} config - Backend-specific configuration
|
|
78
|
+
* @returns {Promise<StorageBackend>} Initialized backend
|
|
79
|
+
*/
|
|
80
|
+
static async create(type, config) {
|
|
81
|
+
const BackendClass = await this.loadBackend(type);
|
|
82
|
+
const backend = new BackendClass(config);
|
|
83
|
+
await backend.init();
|
|
84
|
+
return backend;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Register a custom backend
|
|
89
|
+
* @param {string} name - Backend name
|
|
90
|
+
* @param {Class|string} BackendClassOrPath - Backend class or module path
|
|
91
|
+
*/
|
|
92
|
+
static register(name, BackendClassOrPath) {
|
|
93
|
+
if (typeof BackendClassOrPath === 'string') {
|
|
94
|
+
BACKEND_MODULES[name] = BackendClassOrPath;
|
|
95
|
+
} else {
|
|
96
|
+
loadedBackends.set(name, BackendClassOrPath);
|
|
97
|
+
BACKEND_MODULES[name] = null; // Mark as registered but not file-based
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Capitalize first letter
|
|
104
|
+
* @param {string} str - String to capitalize
|
|
105
|
+
* @returns {string} Capitalized string
|
|
106
|
+
*/
|
|
107
|
+
function capitalize(str) {
|
|
108
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get dependency installation hint for a backend
|
|
113
|
+
* @param {string} type - Backend type
|
|
114
|
+
* @returns {string} Installation hint
|
|
115
|
+
*/
|
|
116
|
+
function getDependencyHint(type) {
|
|
117
|
+
const hints = {
|
|
118
|
+
nostr: 'npm install nostr-tools',
|
|
119
|
+
gundb: 'npm install gun',
|
|
120
|
+
activitypub: 'npm install express',
|
|
121
|
+
};
|
|
122
|
+
return hints[type] || 'Check the documentation for required dependencies';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default BackendFactory;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract Storage Backend Interface
|
|
3
|
+
* All storage backends must implement this interface
|
|
4
|
+
*/
|
|
5
|
+
export class StorageBackend {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
if (new.target === StorageBackend) {
|
|
8
|
+
throw new Error('StorageBackend is abstract and cannot be instantiated directly');
|
|
9
|
+
}
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.publicKey = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the backend
|
|
16
|
+
* @returns {Promise<void>}
|
|
17
|
+
*/
|
|
18
|
+
async init() {
|
|
19
|
+
throw new Error('init() must be implemented by subclass');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build path from components
|
|
24
|
+
* @param {string} appName - Application namespace
|
|
25
|
+
* @param {string} holonId - Holon identifier (H3 hex or URI)
|
|
26
|
+
* @param {string} lensName - Lens name
|
|
27
|
+
* @param {string} [key] - Optional data key
|
|
28
|
+
* @returns {string} Constructed path
|
|
29
|
+
*/
|
|
30
|
+
buildPath(appName, holonId, lensName, key = null) {
|
|
31
|
+
throw new Error('buildPath() must be implemented by subclass');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Write data to storage
|
|
36
|
+
* @param {string} path - Storage path
|
|
37
|
+
* @param {Object} data - Data to write
|
|
38
|
+
* @param {Object} [options] - Write options
|
|
39
|
+
* @returns {Promise<boolean>} Success indicator
|
|
40
|
+
*/
|
|
41
|
+
async write(path, data, options = {}) {
|
|
42
|
+
throw new Error('write() must be implemented by subclass');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read data from storage
|
|
47
|
+
* @param {string} path - Storage path
|
|
48
|
+
* @param {Object} [options] - Read options
|
|
49
|
+
* @param {string[]} [options.authors] - Public keys to query
|
|
50
|
+
* @param {boolean} [options.includeAuthor] - Include author in response
|
|
51
|
+
* @returns {Promise<Object|null>} Data or null
|
|
52
|
+
*/
|
|
53
|
+
async read(path, options = {}) {
|
|
54
|
+
throw new Error('read() must be implemented by subclass');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Read all data under a path prefix
|
|
59
|
+
* @param {string} path - Path prefix
|
|
60
|
+
* @param {Object} [options] - Query options
|
|
61
|
+
* @param {boolean} [options.hybrid] - Use hybrid mode (local + remote)
|
|
62
|
+
* @param {string[]} [options.authors] - Public keys to query
|
|
63
|
+
* @returns {Promise<Object[]>} Array of data objects
|
|
64
|
+
*/
|
|
65
|
+
async readAll(path, options = {}) {
|
|
66
|
+
throw new Error('readAll() must be implemented by subclass');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Update data (merge fields)
|
|
71
|
+
* @param {string} path - Storage path
|
|
72
|
+
* @param {Object} updates - Fields to update
|
|
73
|
+
* @returns {Promise<boolean>} Success indicator
|
|
74
|
+
*/
|
|
75
|
+
async update(path, updates) {
|
|
76
|
+
throw new Error('update() must be implemented by subclass');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Delete data at path
|
|
81
|
+
* @param {string} path - Storage path
|
|
82
|
+
* @returns {Promise<boolean>} Success indicator
|
|
83
|
+
*/
|
|
84
|
+
async delete(path) {
|
|
85
|
+
throw new Error('delete() must be implemented by subclass');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Delete all data under path prefix
|
|
90
|
+
* @param {string} path - Path prefix
|
|
91
|
+
* @returns {Promise<Object>} Deletion results { success: boolean, count: number }
|
|
92
|
+
*/
|
|
93
|
+
async deleteAll(path) {
|
|
94
|
+
throw new Error('deleteAll() must be implemented by subclass');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Subscribe to data changes
|
|
99
|
+
* @param {string} path - Path or path prefix
|
|
100
|
+
* @param {Function} callback - Called on changes: (data, key) => void
|
|
101
|
+
* @param {Object} [options] - Subscription options
|
|
102
|
+
* @returns {Promise<Object>} Subscription with unsubscribe() method
|
|
103
|
+
*/
|
|
104
|
+
async subscribe(path, callback, options = {}) {
|
|
105
|
+
throw new Error('subscribe() must be implemented by subclass');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Export all data for migration
|
|
110
|
+
* @param {string} [pathPrefix] - Optional prefix to filter
|
|
111
|
+
* @returns {Promise<Object[]>} Array of exportable records
|
|
112
|
+
*/
|
|
113
|
+
async exportData(pathPrefix = '') {
|
|
114
|
+
throw new Error('exportData() must be implemented by subclass');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Import data from migration
|
|
119
|
+
* @param {Object[]} records - Records to import
|
|
120
|
+
* @param {Object} [options] - Import options
|
|
121
|
+
* @param {boolean} [options.overwrite] - Overwrite existing data
|
|
122
|
+
* @returns {Promise<Object>} Import results { success: number, failed: number, errors: [] }
|
|
123
|
+
*/
|
|
124
|
+
async importData(records, options = {}) {
|
|
125
|
+
throw new Error('importData() must be implemented by subclass');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Close connections and cleanup
|
|
130
|
+
*/
|
|
131
|
+
close() {
|
|
132
|
+
throw new Error('close() must be implemented by subclass');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get backend status/health
|
|
137
|
+
* @returns {Object} Status information { type, publicKey, connected, ... }
|
|
138
|
+
*/
|
|
139
|
+
getStatus() {
|
|
140
|
+
throw new Error('getStatus() must be implemented by subclass');
|
|
141
|
+
}
|
|
142
|
+
}
|