@ruvector/edge-net 0.4.6 → 0.5.0
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/package.json +13 -2
- package/plugins/cli.js +395 -0
- package/plugins/implementations/compression.js +132 -0
- package/plugins/implementations/e2e-encryption.js +160 -0
- package/plugins/implementations/federated-learning.js +249 -0
- package/plugins/implementations/reputation-staking.js +243 -0
- package/plugins/implementations/swarm-intelligence.js +386 -0
- package/plugins/index.js +90 -0
- package/plugins/plugin-loader.js +440 -0
- package/plugins/plugin-manifest.js +702 -0
- package/plugins/plugin-sdk.js +496 -0
- package/tests/plugin-system-test.js +382 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-Net Plugin SDK
|
|
3
|
+
*
|
|
4
|
+
* Create custom plugins for the edge-net ecosystem.
|
|
5
|
+
* Provides base classes, utilities, and validation.
|
|
6
|
+
*
|
|
7
|
+
* @module @ruvector/edge-net/plugins/sdk
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
import { createHash, randomBytes } from 'crypto';
|
|
12
|
+
import { Capability, PluginCategory, PluginTier } from './plugin-manifest.js';
|
|
13
|
+
|
|
14
|
+
// Re-export for plugin authors
|
|
15
|
+
export { Capability, PluginCategory, PluginTier };
|
|
16
|
+
|
|
17
|
+
// ============================================
|
|
18
|
+
// BASE PLUGIN CLASS
|
|
19
|
+
// ============================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Base class for all custom plugins.
|
|
23
|
+
* Extend this to create your own plugins.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```javascript
|
|
27
|
+
* import { BasePlugin, Capability, PluginCategory, PluginTier } from '@ruvector/edge-net/plugins/sdk';
|
|
28
|
+
*
|
|
29
|
+
* export class MyPlugin extends BasePlugin {
|
|
30
|
+
* static manifest = {
|
|
31
|
+
* id: 'my-org.my-plugin',
|
|
32
|
+
* name: 'My Custom Plugin',
|
|
33
|
+
* version: '1.0.0',
|
|
34
|
+
* description: 'Does something awesome',
|
|
35
|
+
* category: PluginCategory.CORE,
|
|
36
|
+
* tier: PluginTier.BETA,
|
|
37
|
+
* capabilities: [Capability.COMPUTE_WASM],
|
|
38
|
+
* configSchema: {
|
|
39
|
+
* type: 'object',
|
|
40
|
+
* properties: {
|
|
41
|
+
* option1: { type: 'string', default: 'default' },
|
|
42
|
+
* },
|
|
43
|
+
* },
|
|
44
|
+
* };
|
|
45
|
+
*
|
|
46
|
+
* async onInit() {
|
|
47
|
+
* console.log('Plugin initialized with config:', this.config);
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* async onDestroy() {
|
|
51
|
+
* console.log('Plugin destroyed');
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* doSomething() {
|
|
55
|
+
* return 'Hello from my plugin!';
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export class BasePlugin extends EventEmitter {
|
|
61
|
+
// Override in subclass
|
|
62
|
+
static manifest = {
|
|
63
|
+
id: 'custom.base-plugin',
|
|
64
|
+
name: 'Base Plugin',
|
|
65
|
+
version: '0.0.0',
|
|
66
|
+
description: 'Base plugin class - extend this',
|
|
67
|
+
category: PluginCategory.CORE,
|
|
68
|
+
tier: PluginTier.EXPERIMENTAL,
|
|
69
|
+
capabilities: [],
|
|
70
|
+
configSchema: { type: 'object', properties: {} },
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
constructor(config = {}, context = {}) {
|
|
74
|
+
super();
|
|
75
|
+
|
|
76
|
+
this.config = this._mergeConfig(config);
|
|
77
|
+
this.context = context;
|
|
78
|
+
this.api = context.api || {};
|
|
79
|
+
this.sandbox = context.sandbox;
|
|
80
|
+
this.initialized = false;
|
|
81
|
+
this.stats = {
|
|
82
|
+
invocations: 0,
|
|
83
|
+
errors: 0,
|
|
84
|
+
lastUsed: null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get plugin manifest
|
|
90
|
+
*/
|
|
91
|
+
static getManifest() {
|
|
92
|
+
return this.manifest;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Merge user config with defaults from schema
|
|
97
|
+
*/
|
|
98
|
+
_mergeConfig(userConfig) {
|
|
99
|
+
const schema = this.constructor.manifest.configSchema;
|
|
100
|
+
const defaults = {};
|
|
101
|
+
|
|
102
|
+
if (schema?.properties) {
|
|
103
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
104
|
+
if (prop.default !== undefined) {
|
|
105
|
+
defaults[key] = prop.default;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { ...defaults, ...userConfig };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Initialize plugin - override in subclass
|
|
115
|
+
*/
|
|
116
|
+
async onInit() {
|
|
117
|
+
// Override in subclass
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Destroy plugin - override in subclass
|
|
122
|
+
*/
|
|
123
|
+
async onDestroy() {
|
|
124
|
+
// Override in subclass
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Called by loader to initialize
|
|
129
|
+
*/
|
|
130
|
+
async init() {
|
|
131
|
+
if (this.initialized) return;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
await this.onInit();
|
|
135
|
+
this.initialized = true;
|
|
136
|
+
this.emit('initialized');
|
|
137
|
+
} catch (error) {
|
|
138
|
+
this.stats.errors++;
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Called by loader to destroy
|
|
145
|
+
*/
|
|
146
|
+
async destroy() {
|
|
147
|
+
if (!this.initialized) return;
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
await this.onDestroy();
|
|
151
|
+
this.initialized = false;
|
|
152
|
+
this.emit('destroyed');
|
|
153
|
+
} catch (error) {
|
|
154
|
+
this.stats.errors++;
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check if plugin has required capability
|
|
161
|
+
*/
|
|
162
|
+
requireCapability(capability) {
|
|
163
|
+
if (!this.sandbox?.hasCapability(capability)) {
|
|
164
|
+
throw new Error(`Plugin ${this.constructor.manifest.id} missing capability: ${capability}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Log message with plugin prefix
|
|
170
|
+
*/
|
|
171
|
+
log(level, message, data = {}) {
|
|
172
|
+
const prefix = `[${this.constructor.manifest.id}]`;
|
|
173
|
+
console.log(`${prefix} [${level.toUpperCase()}]`, message, data);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Record plugin usage
|
|
178
|
+
*/
|
|
179
|
+
_recordUsage() {
|
|
180
|
+
this.stats.invocations++;
|
|
181
|
+
this.stats.lastUsed = Date.now();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get plugin stats
|
|
186
|
+
*/
|
|
187
|
+
getStats() {
|
|
188
|
+
return {
|
|
189
|
+
...this.stats,
|
|
190
|
+
initialized: this.initialized,
|
|
191
|
+
manifest: this.constructor.manifest,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ============================================
|
|
197
|
+
// PLUGIN VALIDATION
|
|
198
|
+
// ============================================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Validate plugin manifest
|
|
202
|
+
*/
|
|
203
|
+
export function validateManifest(manifest) {
|
|
204
|
+
const errors = [];
|
|
205
|
+
|
|
206
|
+
// Required fields
|
|
207
|
+
const required = ['id', 'name', 'version', 'description', 'category', 'tier'];
|
|
208
|
+
for (const field of required) {
|
|
209
|
+
if (!manifest[field]) {
|
|
210
|
+
errors.push(`Missing required field: ${field}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ID format: org.plugin-name or category.plugin-name
|
|
215
|
+
if (manifest.id && !/^[a-z0-9-]+\.[a-z0-9-]+$/.test(manifest.id)) {
|
|
216
|
+
errors.push('Invalid ID format. Use: category.plugin-name or org.plugin-name');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Version semver
|
|
220
|
+
if (manifest.version && !/^\d+\.\d+\.\d+/.test(manifest.version)) {
|
|
221
|
+
errors.push('Invalid version format. Use semantic versioning: X.Y.Z');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Category
|
|
225
|
+
if (manifest.category && !Object.values(PluginCategory).includes(manifest.category)) {
|
|
226
|
+
errors.push(`Invalid category. Use one of: ${Object.values(PluginCategory).join(', ')}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Tier
|
|
230
|
+
if (manifest.tier && !Object.values(PluginTier).includes(manifest.tier)) {
|
|
231
|
+
errors.push(`Invalid tier. Use one of: ${Object.values(PluginTier).join(', ')}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Capabilities
|
|
235
|
+
if (manifest.capabilities) {
|
|
236
|
+
const validCaps = Object.values(Capability);
|
|
237
|
+
for (const cap of manifest.capabilities) {
|
|
238
|
+
if (!validCaps.includes(cap)) {
|
|
239
|
+
errors.push(`Invalid capability: ${cap}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
valid: errors.length === 0,
|
|
246
|
+
errors,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Validate plugin class
|
|
252
|
+
*/
|
|
253
|
+
export function validatePlugin(PluginClass) {
|
|
254
|
+
const errors = [];
|
|
255
|
+
|
|
256
|
+
// Must extend BasePlugin
|
|
257
|
+
if (!(PluginClass.prototype instanceof BasePlugin)) {
|
|
258
|
+
errors.push('Plugin must extend BasePlugin');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Must have manifest
|
|
262
|
+
if (!PluginClass.manifest) {
|
|
263
|
+
errors.push('Plugin must have static manifest property');
|
|
264
|
+
} else {
|
|
265
|
+
const manifestValidation = validateManifest(PluginClass.manifest);
|
|
266
|
+
errors.push(...manifestValidation.errors);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
valid: errors.length === 0,
|
|
271
|
+
errors,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ============================================
|
|
276
|
+
// PLUGIN REGISTRY
|
|
277
|
+
// ============================================
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Registry for custom plugins
|
|
281
|
+
*/
|
|
282
|
+
export class PluginRegistry {
|
|
283
|
+
constructor() {
|
|
284
|
+
this.plugins = new Map(); // id -> PluginClass
|
|
285
|
+
this.metadata = new Map(); // id -> metadata
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Register a custom plugin
|
|
290
|
+
*/
|
|
291
|
+
register(PluginClass) {
|
|
292
|
+
const validation = validatePlugin(PluginClass);
|
|
293
|
+
if (!validation.valid) {
|
|
294
|
+
throw new Error(`Invalid plugin: ${validation.errors.join(', ')}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const manifest = PluginClass.manifest;
|
|
298
|
+
const id = manifest.id;
|
|
299
|
+
|
|
300
|
+
if (this.plugins.has(id)) {
|
|
301
|
+
throw new Error(`Plugin already registered: ${id}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Generate checksum
|
|
305
|
+
const checksum = createHash('sha256')
|
|
306
|
+
.update(PluginClass.toString())
|
|
307
|
+
.digest('hex');
|
|
308
|
+
|
|
309
|
+
this.plugins.set(id, PluginClass);
|
|
310
|
+
this.metadata.set(id, {
|
|
311
|
+
manifest,
|
|
312
|
+
checksum,
|
|
313
|
+
registeredAt: Date.now(),
|
|
314
|
+
source: 'custom',
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
return { id, checksum };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Unregister a plugin
|
|
322
|
+
*/
|
|
323
|
+
unregister(id) {
|
|
324
|
+
if (!this.plugins.has(id)) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
this.plugins.delete(id);
|
|
329
|
+
this.metadata.delete(id);
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Get plugin class
|
|
335
|
+
*/
|
|
336
|
+
get(id) {
|
|
337
|
+
return this.plugins.get(id);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Check if plugin is registered
|
|
342
|
+
*/
|
|
343
|
+
has(id) {
|
|
344
|
+
return this.plugins.has(id);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* List all registered plugins
|
|
349
|
+
*/
|
|
350
|
+
list() {
|
|
351
|
+
return Array.from(this.metadata.entries()).map(([id, meta]) => ({
|
|
352
|
+
id,
|
|
353
|
+
...meta.manifest,
|
|
354
|
+
checksum: meta.checksum,
|
|
355
|
+
registeredAt: meta.registeredAt,
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Export plugin for distribution
|
|
361
|
+
*/
|
|
362
|
+
export(id) {
|
|
363
|
+
const PluginClass = this.plugins.get(id);
|
|
364
|
+
if (!PluginClass) {
|
|
365
|
+
throw new Error(`Plugin not found: ${id}`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const metadata = this.metadata.get(id);
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
manifest: PluginClass.manifest,
|
|
372
|
+
code: PluginClass.toString(),
|
|
373
|
+
checksum: metadata.checksum,
|
|
374
|
+
exportedAt: Date.now(),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Import plugin from exported data
|
|
380
|
+
*/
|
|
381
|
+
import(exportedPlugin) {
|
|
382
|
+
// Security: Only import from trusted sources in production
|
|
383
|
+
// This is a simplified implementation
|
|
384
|
+
const { manifest, code, checksum } = exportedPlugin;
|
|
385
|
+
|
|
386
|
+
// Verify checksum
|
|
387
|
+
const computedChecksum = createHash('sha256').update(code).digest('hex');
|
|
388
|
+
if (computedChecksum !== checksum) {
|
|
389
|
+
throw new Error('Checksum mismatch - plugin may have been tampered');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Parse and register (in production, use proper sandboxing)
|
|
393
|
+
// WARNING: eval is dangerous - use VM2 or similar in production
|
|
394
|
+
console.warn('WARNING: Importing plugins uses eval - only import from trusted sources');
|
|
395
|
+
|
|
396
|
+
return { imported: true, manifest };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ============================================
|
|
401
|
+
// PLUGIN TEMPLATE GENERATOR
|
|
402
|
+
// ============================================
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Generate plugin template
|
|
406
|
+
*/
|
|
407
|
+
export function generatePluginTemplate(options = {}) {
|
|
408
|
+
const {
|
|
409
|
+
id = 'my-org.my-plugin',
|
|
410
|
+
name = 'My Plugin',
|
|
411
|
+
description = 'A custom edge-net plugin',
|
|
412
|
+
category = PluginCategory.CORE,
|
|
413
|
+
tier = PluginTier.EXPERIMENTAL,
|
|
414
|
+
capabilities = [],
|
|
415
|
+
} = options;
|
|
416
|
+
|
|
417
|
+
return `/**
|
|
418
|
+
* ${name}
|
|
419
|
+
*
|
|
420
|
+
* ${description}
|
|
421
|
+
*
|
|
422
|
+
* @module ${id}
|
|
423
|
+
*/
|
|
424
|
+
|
|
425
|
+
import { BasePlugin, Capability, PluginCategory, PluginTier } from '@ruvector/edge-net/plugins/sdk';
|
|
426
|
+
|
|
427
|
+
export class ${toPascalCase(id.split('.').pop())}Plugin extends BasePlugin {
|
|
428
|
+
static manifest = {
|
|
429
|
+
id: '${id}',
|
|
430
|
+
name: '${name}',
|
|
431
|
+
version: '1.0.0',
|
|
432
|
+
description: '${description}',
|
|
433
|
+
category: PluginCategory.${Object.keys(PluginCategory).find(k => PluginCategory[k] === category) || 'CORE'},
|
|
434
|
+
tier: PluginTier.${Object.keys(PluginTier).find(k => PluginTier[k] === tier) || 'EXPERIMENTAL'},
|
|
435
|
+
author: 'Your Name',
|
|
436
|
+
capabilities: [${capabilities.map(c => `Capability.${Object.keys(Capability).find(k => Capability[k] === c)}`).join(', ')}],
|
|
437
|
+
dependencies: [],
|
|
438
|
+
configSchema: {
|
|
439
|
+
type: 'object',
|
|
440
|
+
properties: {
|
|
441
|
+
enabled: { type: 'boolean', default: true },
|
|
442
|
+
// Add your config options here
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
tags: ['custom'],
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
async onInit() {
|
|
449
|
+
this.log('info', 'Initializing...');
|
|
450
|
+
// Your initialization code here
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async onDestroy() {
|
|
454
|
+
this.log('info', 'Destroying...');
|
|
455
|
+
// Your cleanup code here
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Add your plugin methods here
|
|
459
|
+
exampleMethod() {
|
|
460
|
+
this._recordUsage();
|
|
461
|
+
return 'Hello from ${name}!';
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export default ${toPascalCase(id.split('.').pop())}Plugin;
|
|
466
|
+
`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function toPascalCase(str) {
|
|
470
|
+
return str.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ============================================
|
|
474
|
+
// SINGLETON REGISTRY
|
|
475
|
+
// ============================================
|
|
476
|
+
|
|
477
|
+
let globalRegistry = null;
|
|
478
|
+
|
|
479
|
+
export function getRegistry() {
|
|
480
|
+
if (!globalRegistry) {
|
|
481
|
+
globalRegistry = new PluginRegistry();
|
|
482
|
+
}
|
|
483
|
+
return globalRegistry;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export default {
|
|
487
|
+
BasePlugin,
|
|
488
|
+
validateManifest,
|
|
489
|
+
validatePlugin,
|
|
490
|
+
PluginRegistry,
|
|
491
|
+
generatePluginTemplate,
|
|
492
|
+
getRegistry,
|
|
493
|
+
Capability,
|
|
494
|
+
PluginCategory,
|
|
495
|
+
PluginTier,
|
|
496
|
+
};
|