updates types for orbit and bug fixes, and sanitiza data on document prepare

This commit is contained in:
12inchpenguin 2025-04-07 15:49:13 +03:00
parent d39b3c7003
commit 44d19ad628
6 changed files with 93 additions and 187 deletions

View File

@ -1,126 +0,0 @@
// Example of how to integrate the new @debros/node-core package into an application
import express from 'express';
import { initIpfs, initOrbitDB, createServiceLogger, getConnectedPeers } from '@debros/node-core'; // This would be the import path when installed from npm
// Create service-specific loggers
const apiLogger = createServiceLogger('API');
const networkLogger = createServiceLogger('NETWORK');
// Initialize Express app
const app = express();
app.use(express.json());
// Network state
let ipfsNode: any;
let orbitInstance: any;
let messageDB: any;
// Initialize network components
async function initializeNetwork() {
try {
// Initialize IPFS
networkLogger.info('Initializing IPFS node...');
ipfsNode = await initIpfs();
// Initialize OrbitDB
networkLogger.info('Initializing OrbitDB...');
orbitInstance = await initOrbitDB({
getHelia: () => ipfsNode,
});
// Open message database
messageDB = await orbitInstance.open('messages', {
type: 'feed',
});
networkLogger.info('Network components initialized successfully');
// Log connected peers every minute
setInterval(() => {
const peers = getConnectedPeers();
networkLogger.info(`Connected to ${peers.size} peers`);
}, 60000);
return true;
} catch (error) {
networkLogger.error('Failed to initialize network:', error);
return false;
}
}
// API routes
app.get('/api/peers', (req, res) => {
const peers = getConnectedPeers();
const peerList: any[] = [];
peers.forEach((data, peerId) => {
peerList.push({
id: peerId,
load: data.load,
address: data.publicAddress,
lastSeen: data.lastSeen,
});
});
res.json({ peers: peerList });
});
app.get('/api/messages', async (req, res) => {
try {
const messages = messageDB.iterator({ limit: 100 }).collect();
res.json({ messages });
} catch (error) {
apiLogger.error('Error fetching messages:', error);
res.status(500).json({ error: 'Failed to fetch messages' });
}
});
app.post('/api/messages', async (req, res) => {
try {
const { content } = req.body;
if (!content) {
return res.status(400).json({ error: 'Content is required' });
}
const entry = await messageDB.add({
content,
timestamp: Date.now(),
});
res.status(201).json({ id: entry });
} catch (error) {
apiLogger.error('Error creating message:', error);
res.status(500).json({ error: 'Failed to create message' });
}
});
// Start the application
async function startApp() {
const networkInitialized = await initializeNetwork();
if (networkInitialized) {
const port = config.env.port;
app.listen(port, () => {
apiLogger.info(`Server listening on port ${port}`);
});
} else {
apiLogger.error('Cannot start application: Network initialization failed');
process.exit(1);
}
}
// Shutdown handler
process.on('SIGINT', async () => {
networkLogger.info('Application shutting down...');
if (orbitInstance) {
await orbitInstance.stop();
}
if (ipfsNode) {
await initIpfs.stop();
}
process.exit(0);
});
// Start the application
startApp();

2
orbitdb.d.ts vendored
View File

@ -1,7 +1,7 @@
// custom.d.ts
declare module '@orbitdb/core' {
// Import the types from @constl/orbit-db-types
import { OrbitDBTypes } from '@constl/orbit-db-types';
import { OrbitDBTypes } from '@orbitdb/core-types';
// Assuming @orbitdb/core exports an interface or type you want to override
// Replace 'OrbitDB' with the actual export name from @orbitdb/core you want to type

View File

@ -1,6 +1,6 @@
{
"name": "@debros/network",
"version": "0.0.16-alpha",
"version": "0.0.17-alpha",
"description": "Debros network core functionality for IPFS, libp2p and OrbitDB",
"type": "module",
"main": "dist/index.js",
@ -59,7 +59,6 @@
"typescript": ">=5.0.0"
},
"devDependencies": {
"@constl/orbit-db-types": "^2.0.6",
"@eslint/js": "^9.24.0",
"@orbitdb/core-types": "^1.0.14",
"@types/express": "^5.0.1",
@ -70,12 +69,14 @@
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-prettier": "^5.2.6",
"globals": "^16.0.0",
"husky": "^8.0.3",
"lint-staged": "^15.5.0",
"prettier": "^3.5.3",
"rimraf": "^5.0.5",
"tsc-esm-fix": "^3.1.2",
"typescript": "^5.8.2"
"typescript": "^5.8.2",
"typescript-eslint": "^8.29.0"
},
"compilerOptions": {
"typeRoots": [

37
pnpm-lock.yaml generated
View File

@ -78,9 +78,6 @@ importers:
specifier: ^3.17.0
version: 3.17.0
devDependencies:
'@constl/orbit-db-types':
specifier: ^2.0.6
version: 2.0.6
'@eslint/js':
specifier: ^9.24.0
version: 9.24.0
@ -111,6 +108,9 @@ importers:
eslint-plugin-prettier:
specifier: ^5.2.6
version: 5.2.6(eslint-config-prettier@10.1.1(eslint@9.24.0))(eslint@9.24.0)(prettier@3.5.3)
globals:
specifier: ^16.0.0
version: 16.0.0
husky:
specifier: ^8.0.3
version: 8.0.3
@ -129,6 +129,9 @@ importers:
typescript:
specifier: ^5.8.2
version: 5.8.2
typescript-eslint:
specifier: ^8.29.0
version: 8.29.0(eslint@9.24.0)(typescript@5.8.2)
packages:
@ -827,9 +830,6 @@ packages:
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
engines: {node: '>=0.1.90'}
'@constl/orbit-db-types@2.0.6':
resolution: {integrity: sha512-p23e3tEr9izE9zskwqdN66VhFWYrbhxTvJNalhWqKkXnSV/58gbtc+yFobLPClKEDNKtYAhYhO6spLiI+VpZEg==}
'@dabh/diagnostics@2.0.3':
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
@ -2251,6 +2251,10 @@ packages:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
globals@16.0.0:
resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==}
engines: {node: '>=18'}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@ -3750,6 +3754,13 @@ packages:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
engines: {node: '>= 0.6'}
typescript-eslint@8.29.0:
resolution: {integrity: sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
typescript@5.8.2:
resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
engines: {node: '>=14.17'}
@ -4862,8 +4873,6 @@ snapshots:
'@colors/colors@1.6.0': {}
'@constl/orbit-db-types@2.0.6': {}
'@dabh/diagnostics@2.0.3':
dependencies:
colorspace: 1.1.4
@ -7077,6 +7086,8 @@ snapshots:
globals@14.0.0: {}
globals@16.0.0: {}
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
@ -8846,6 +8857,16 @@ snapshots:
media-typer: 1.1.0
mime-types: 3.0.1
typescript-eslint@8.29.0(eslint@9.24.0)(typescript@5.8.2):
dependencies:
'@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.8.2))(eslint@9.24.0)(typescript@5.8.2)
'@typescript-eslint/parser': 8.29.0(eslint@9.24.0)(typescript@5.8.2)
'@typescript-eslint/utils': 8.29.0(eslint@9.24.0)(typescript@5.8.2)
eslint: 9.24.0
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
typescript@5.8.2: {}
uint8-varint@2.0.4:

View File

@ -1,11 +1,17 @@
import { createServiceLogger } from '../../utils/logger';
import { openDB } from '../../orbit/orbitDBService';
import { getConnection } from '../core/connection';
import * as cache from '../cache/cacheService';
import * as events from '../events/eventService';
import { validateDocument } from '../schema/validator';
import { measurePerformance } from '../metrics/metricsService';
import { ErrorCode, StoreType, StoreOptions, CreateResult, UpdateResult, PaginatedResult, QueryOptions, ListOptions } from '../types';
import {
ErrorCode,
StoreType,
StoreOptions,
CreateResult,
UpdateResult,
PaginatedResult,
QueryOptions,
ListOptions,
} from '../types';
import { DBError } from '../core/error';
const logger = createServiceLogger('DB_STORE');
@ -18,65 +24,57 @@ export interface BaseStore {
* Create a new document
*/
create<T extends Record<string, any>>(
collection: string,
id: string,
data: Omit<T, 'createdAt' | 'updatedAt'>,
options?: StoreOptions
collection: string,
id: string,
data: Omit<T, 'createdAt' | 'updatedAt'>,
options?: StoreOptions,
): Promise<CreateResult>;
/**
* Get a document by ID
*/
get<T extends Record<string, any>>(
collection: string,
id: string,
options?: StoreOptions & { skipCache?: boolean }
collection: string,
id: string,
options?: StoreOptions & { skipCache?: boolean },
): Promise<T | null>;
/**
* Update a document
*/
update<T extends Record<string, any>>(
collection: string,
id: string,
data: Partial<Omit<T, 'createdAt' | 'updatedAt'>>,
options?: StoreOptions & { upsert?: boolean }
collection: string,
id: string,
data: Partial<Omit<T, 'createdAt' | 'updatedAt'>>,
options?: StoreOptions & { upsert?: boolean },
): Promise<UpdateResult>;
/**
* Delete a document
*/
remove(
collection: string,
id: string,
options?: StoreOptions
): Promise<boolean>;
remove(collection: string, id: string, options?: StoreOptions): Promise<boolean>;
/**
* List all documents in a collection with pagination
*/
list<T extends Record<string, any>>(
collection: string,
options?: ListOptions
collection: string,
options?: ListOptions,
): Promise<PaginatedResult<T>>;
/**
* Query documents in a collection with filtering and pagination
*/
query<T extends Record<string, any>>(
collection: string,
collection: string,
filter: (doc: T) => boolean,
options?: QueryOptions
options?: QueryOptions,
): Promise<PaginatedResult<T>>;
/**
* Create an index for a collection to speed up queries
*/
createIndex(
collection: string,
field: string,
options?: StoreOptions
): Promise<boolean>;
createIndex(collection: string, field: string, options?: StoreOptions): Promise<boolean>;
}
/**
@ -85,14 +83,21 @@ export interface BaseStore {
export async function openStore(
collection: string,
storeType: StoreType,
options?: StoreOptions
options?: StoreOptions,
): Promise<any> {
try {
const connection = getConnection(options?.connectionId);
return await openDB(collection, storeType);
logger.info(`Connection for ${collection}:`, connection);
return await openDB(collection, storeType).catch((err) => {
throw new Error(`OrbitDB openDB failed: ${err.message}`);
});
} catch (error) {
logger.error(`Error opening ${storeType} store for collection ${collection}:`, error);
throw new DBError(ErrorCode.OPERATION_FAILED, `Failed to open ${storeType} store for collection ${collection}`, error);
throw new DBError(
ErrorCode.OPERATION_FAILED,
`Failed to open ${storeType} store for collection ${collection}`,
error,
);
}
}
@ -102,31 +107,36 @@ export async function openStore(
export function prepareDocument<T extends Record<string, any>>(
collection: string,
data: Omit<T, 'createdAt' | 'updatedAt'>,
existingDoc?: T | null
existingDoc?: T | null,
): T {
const timestamp = Date.now();
// Sanitize the input data by replacing undefined with null
const sanitizedData = Object.fromEntries(
Object.entries(data).map(([key, value]) => [key, value === undefined ? null : value]),
) as Omit<T, 'createdAt' | 'updatedAt'>;
// If it's an update to an existing document
if (existingDoc) {
const doc = {
...existingDoc,
...data,
updatedAt: timestamp
...sanitizedData,
updatedAt: timestamp,
} as T;
// Validate the document against its schema
validateDocument(collection, doc);
return doc;
}
// Otherwise it's a new document
const doc = {
...data,
...sanitizedData,
createdAt: timestamp,
updatedAt: timestamp
updatedAt: timestamp,
} as unknown as T;
// Validate the document against its schema
validateDocument(collection, doc);
return doc;
}
}

View File

@ -74,7 +74,7 @@ export const openDB = async (name: string, type: string) => {
const dbOptions = {
type,
overwrite: false,
AccessController: IPFSAccessController({ write: ['*'], storage: getHelia() }),
AccessController: IPFSAccessController({ write: ['*'] }),
};
if (existingAddress) {