keypointjs 1.0.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.
@@ -0,0 +1,779 @@
1
+ // Import http module once at top level
2
+ let http;
3
+ try {
4
+ http = await import('http');
5
+ } catch (error) {
6
+ console.error('Failed to import http module:', error);
7
+ }
8
+
9
+ import { Context } from './core/Context.js';
10
+ import { ProtocolEngine, ProtocolError } from './core/ProtocolEngine.js';
11
+ import { Keypoint } from './keypoint/Keypoint.js';
12
+ import { KeypointContext } from './keypoint/KeypointContext.js';
13
+ import { KeypointValidator } from './keypoint/KeypointValidator.js';
14
+ import { MemoryKeypointStorage } from './keypoint/KeypointStorage.js';
15
+ import { ScopeManager } from './keypoint/ScopeManager.js';
16
+ import { PolicyEngine } from './policy/PolicyEngine.js';
17
+ import { BuiltInRules } from './policy/PolicyRule.js';
18
+ import { MinimalRouter } from './router/MinimalRouter.js';
19
+ import { PluginManager, BuiltInHooks } from './plugins/PluginManager.js';
20
+ import { RateLimiter } from './plugins/RateLimiter.js';
21
+ import { AuditLogger } from './plugins/AuditLogger.js';
22
+ import { WebSocketGuard } from './plugins/WebSocketGuard.js';
23
+
24
+ export class KeypointJS {
25
+ constructor(options = {}) {
26
+ this.options = {
27
+ requireKeypoint: true,
28
+ strictMode: true,
29
+ validateOrigin: true,
30
+ validateProtocol: true,
31
+ enableCORS: false,
32
+ corsOrigins: ['*'],
33
+ maxRequestSize: '10mb',
34
+ defaultResponseHeaders: {
35
+ 'X-Powered-By': 'KeypointJS',
36
+ 'X-Content-Type-Options': 'nosniff'
37
+ },
38
+ errorHandler: this.defaultErrorHandler.bind(this),
39
+ ...options
40
+ };
41
+
42
+ // Initialize core components
43
+ this.initializeCore();
44
+
45
+ // Initialize layers
46
+ this.initializeLayers();
47
+
48
+ // Setup built-in policies
49
+ this.setupBuiltInPolicies();
50
+
51
+ // Event emitter
52
+ this.events = new Map();
53
+
54
+ // Statistics
55
+ this.stats = {
56
+ requests: 0,
57
+ successful: 0,
58
+ failed: 0,
59
+ keypointValidations: 0,
60
+ policyChecks: 0,
61
+ startTime: new Date()
62
+ };
63
+ }
64
+
65
+ initializeCore() {
66
+ // Core protocol engine
67
+ this.protocolEngine = new ProtocolEngine({
68
+ maxBodySize: this.options.maxRequestSize,
69
+ parseJSON: true,
70
+ parseForm: true
71
+ });
72
+
73
+ // Keypoint system
74
+ this.keypointStorage = this.options.keypointStorage || new MemoryKeypointStorage();
75
+ this.scopeManager = new ScopeManager();
76
+ this.keypointValidator = new KeypointValidator(this.keypointStorage);
77
+
78
+ // Policy engine
79
+ this.policyEngine = new PolicyEngine();
80
+
81
+ // Router
82
+ this.router = new MinimalRouter();
83
+
84
+ // Plugin manager
85
+ this.pluginManager = new PluginManager();
86
+
87
+ // Middleware chain
88
+ this.middlewareChain = [];
89
+
90
+ // WebSocket support
91
+ this.wsGuard = null;
92
+ }
93
+
94
+ initializeLayers() {
95
+ // Layer 0: Pre-processing (hooks)
96
+ this.use(async (ctx, next) => {
97
+ await this.pluginManager.runHook(BuiltInHooks.BEFORE_KEYPOINT_VALIDATION, ctx);
98
+ return next(ctx);
99
+ });
100
+
101
+ // Layer 1: Protocol Engine
102
+ this.use(async (ctx, next) => {
103
+ try {
104
+ const processed = await this.protocolEngine.process(ctx.request);
105
+
106
+ ctx.id = processed.id;
107
+ ctx.timestamp = processed.timestamp;
108
+ ctx.metadata = processed.metadata;
109
+
110
+ // Update request object
111
+ if (processed.request) {
112
+ ctx.request = {
113
+ ...ctx.request,
114
+ ...processed.request
115
+ };
116
+ }
117
+
118
+ ctx.setState('_protocol', processed.protocol);
119
+ ctx.setState('_ip', processed.request?.ip);
120
+
121
+ } catch (error) {
122
+ throw new KeypointError(`Protocol error: ${error.message}`, 400);
123
+ }
124
+ return next(ctx);
125
+ });
126
+
127
+ // Layer 2: CORS (if enabled)
128
+ if (this.options.enableCORS) {
129
+ this.use(this.corsMiddleware.bind(this));
130
+ }
131
+
132
+ // Layer 3: Keypoint Validation (if required)
133
+ if (this.options.requireKeypoint) {
134
+ this.use(async (ctx, next) => {
135
+ await this.pluginManager.runHook(BuiltInHooks.BEFORE_KEYPOINT_VALIDATION, ctx);
136
+
137
+ try {
138
+ const isValid = await this.keypointValidator.validate(ctx);
139
+ if (!isValid) {
140
+ throw new KeypointError('Invalid or missing keypoint', 401);
141
+ }
142
+
143
+ this.stats.keypointValidations++;
144
+
145
+ // Validate origin if configured
146
+ if (this.options.validateOrigin && !ctx.validateOrigin()) {
147
+ throw new KeypointError('Origin not allowed for this keypoint', 403);
148
+ }
149
+
150
+ // Validate protocol if configured
151
+ if (this.options.validateProtocol && !ctx.validateProtocol()) {
152
+ throw new KeypointError('Protocol not allowed for this keypoint', 403);
153
+ }
154
+
155
+ await this.pluginManager.runHook(BuiltInHooks.AFTER_KEYPOINT_VALIDATION, ctx);
156
+ } catch (error) {
157
+ await this.pluginManager.runHook(BuiltInHooks.ON_ERROR, ctx, error);
158
+ throw error;
159
+ }
160
+
161
+ return next(ctx);
162
+ });
163
+ }
164
+
165
+ // Layer 4: Policy Check
166
+ this.use(async (ctx, next) => {
167
+ await this.pluginManager.runHook(BuiltInHooks.BEFORE_POLICY_CHECK, ctx);
168
+
169
+ try {
170
+ const decision = await this.policyEngine.evaluate(ctx);
171
+ if (!decision.allowed) {
172
+ throw new PolicyError(decision.reason, 403, decision);
173
+ }
174
+
175
+ ctx.policyDecision = decision;
176
+ this.stats.policyChecks++;
177
+
178
+ await this.pluginManager.runHook(BuiltInHooks.AFTER_POLICY_CHECK, ctx, decision);
179
+ } catch (error) {
180
+ await this.pluginManager.runHook(BuiltInHooks.ON_ERROR, ctx, error);
181
+ throw error;
182
+ }
183
+
184
+ return next(ctx);
185
+ });
186
+
187
+ // Layer 5: Plugin Processing
188
+ this.use(async (ctx, next) => {
189
+ return this.pluginManager.process(ctx, next);
190
+ });
191
+
192
+ // Layer 6: Route Execution
193
+ this.use(async (ctx, next) => {
194
+ await this.pluginManager.runHook(BuiltInHooks.BEFORE_ROUTE_EXECUTION, ctx);
195
+
196
+ try {
197
+ await this.router.handle(ctx);
198
+
199
+ await this.pluginManager.runHook(BuiltInHooks.AFTER_ROUTE_EXECUTION, ctx);
200
+ } catch (error) {
201
+ await this.pluginManager.runHook(BuiltInHooks.ON_ERROR, ctx, error);
202
+ throw error;
203
+ }
204
+
205
+ return next(ctx);
206
+ });
207
+
208
+ // Layer 7: Response Processing
209
+ this.use(async (ctx, next) => {
210
+ await this.pluginManager.runHook(BuiltInHooks.BEFORE_RESPONSE, ctx);
211
+
212
+ // Apply default headers
213
+ if (ctx.response) {
214
+ ctx.response.headers = {
215
+ ...this.options.defaultResponseHeaders,
216
+ ...ctx.response.headers
217
+ };
218
+
219
+ // Add security headers
220
+ ctx.response.headers['X-Keypoint-ID'] = ctx.getKeypointId() || 'none';
221
+ ctx.response.headers['X-Policy-Decision'] = ctx.policyDecision?.allowed ? 'allowed' : 'denied';
222
+
223
+ // Add CORS headers if enabled
224
+ if (this.options.enableCORS) {
225
+ this.addCORSHeaders(ctx);
226
+ }
227
+ }
228
+
229
+ await this.pluginManager.runHook(BuiltInHooks.AFTER_RESPONSE, ctx);
230
+
231
+ return next(ctx);
232
+ });
233
+ }
234
+
235
+ setupBuiltInPolicies() {
236
+ // Add rate limiting policy
237
+ const rateLimitRule = BuiltInRules.rateLimitRule(100, 60);
238
+ this.policyEngine.addRule(rateLimitRule);
239
+
240
+ // Add method validation policy
241
+ const methodRule = BuiltInRules.methodRule(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']);
242
+ this.policyEngine.addRule(methodRule);
243
+
244
+ // Add built-in policy templates
245
+ this.policyEngine.addPolicy('public', this.policyEngine.allow({
246
+ scope: 'api:public',
247
+ protocol: 'https'
248
+ }));
249
+
250
+ this.policyEngine.addPolicy('admin', this.policyEngine.allow({
251
+ scope: 'admin',
252
+ protocol: 'https'
253
+ }));
254
+
255
+ this.policyEngine.addPolicy('internal', this.policyEngine.allow({
256
+ scope: 'api:internal',
257
+ protocol: ['https', 'wss']
258
+ }));
259
+ }
260
+
261
+ corsMiddleware(ctx, next) {
262
+ const origin = ctx.getHeader('origin');
263
+
264
+ if (origin) {
265
+ // Check if origin is allowed
266
+ const isAllowed = this.options.corsOrigins.includes('*') ||
267
+ this.options.corsOrigins.includes(origin);
268
+
269
+ if (isAllowed) {
270
+ ctx.response.headers = {
271
+ ...ctx.response.headers,
272
+ 'Access-Control-Allow-Origin': origin,
273
+ 'Access-Control-Allow-Credentials': 'true',
274
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
275
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Keypoint-ID, X-Keypoint-Secret'
276
+ };
277
+ }
278
+ }
279
+
280
+ // Handle preflight requests
281
+ if (ctx.method === 'OPTIONS') {
282
+ ctx.response = {
283
+ status: 204,
284
+ headers: ctx.response.headers
285
+ };
286
+ return;
287
+ }
288
+
289
+ return next(ctx);
290
+ }
291
+
292
+ addCORSHeaders(ctx) {
293
+ if (!this.options.enableCORS) return;
294
+
295
+ const origin = ctx.getHeader('origin');
296
+ if (origin && (this.options.corsOrigins.includes('*') || this.options.corsOrigins.includes(origin))) {
297
+ ctx.response.headers = {
298
+ ...ctx.response.headers,
299
+ 'Access-Control-Allow-Origin': origin,
300
+ 'Access-Control-Expose-Headers': 'X-Keypoint-ID, X-Policy-Decision'
301
+ };
302
+ }
303
+ }
304
+
305
+ // Public API Methods
306
+
307
+ use(middleware) {
308
+ this.middlewareChain.push(middleware);
309
+ return this;
310
+ }
311
+
312
+ route(method, path, handler) {
313
+ this.router.route(method, path, handler);
314
+ return this;
315
+ }
316
+
317
+ get(path, handler) {
318
+ return this.route('GET', path, handler);
319
+ }
320
+
321
+ post(path, handler) {
322
+ return this.route('POST', path, handler);
323
+ }
324
+
325
+ put(path, handler) {
326
+ return this.route('PUT', path, handler);
327
+ }
328
+
329
+ delete(path, handler) {
330
+ return this.route('DELETE', path, handler);
331
+ }
332
+
333
+ patch(path, handler) {
334
+ return this.route('PATCH', path, handler);
335
+ }
336
+
337
+ options(path, handler) {
338
+ return this.route('OPTIONS', path, handler);
339
+ }
340
+
341
+ // Plugin management
342
+
343
+ registerPlugin(plugin, options = {}) {
344
+ this.pluginManager.register(plugin, options);
345
+
346
+ // Special handling for WebSocketGuard
347
+ if (plugin instanceof WebSocketGuard) {
348
+ this.wsGuard = plugin;
349
+ }
350
+
351
+ return this;
352
+ }
353
+
354
+ enableWebSocket(options = {}) {
355
+ if (!this.wsGuard) {
356
+ const wsGuard = new WebSocketGuard(options);
357
+ this.registerPlugin(wsGuard);
358
+ }
359
+ return this.wsGuard;
360
+ }
361
+
362
+ // Keypoint management
363
+
364
+ async createKeypoint(data) {
365
+ const Keypoint = (await import('./keypoint/Keypoint.js')).Keypoint;
366
+ const keypoint = new Keypoint(data);
367
+ await this.keypointStorage.set(keypoint);
368
+
369
+ this.emit('keypoint:created', { keypoint });
370
+ return keypoint;
371
+ }
372
+
373
+ async revokeKeypoint(keyId) {
374
+ const keypoint = await this.keypointStorage.get(keyId);
375
+ if (keypoint) {
376
+ await this.keypointStorage.delete(keyId);
377
+ this.emit('keypoint:revoked', { keyId, keypoint });
378
+ return true;
379
+ }
380
+ return false;
381
+ }
382
+
383
+ async listKeypoints(filter = {}) {
384
+ return await this.keypointStorage.list(filter);
385
+ }
386
+
387
+ async getKeypoint(keyId) {
388
+ return await this.keypointStorage.get(keyId);
389
+ }
390
+
391
+ // Policy management
392
+
393
+ addPolicyRule(rule) {
394
+ this.policyEngine.addRule(rule);
395
+ return this;
396
+ }
397
+
398
+ addPolicy(name, policyFn) {
399
+ this.policyEngine.addPolicy(name, policyFn);
400
+ return this;
401
+ }
402
+
403
+ // Scope management
404
+
405
+ defineScope(name, description, metadata = {}) {
406
+ this.scopeManager.defineScope(name, description, metadata);
407
+ return this;
408
+ }
409
+
410
+ // Request handling
411
+
412
+ async handleRequest(request, response) {
413
+ const ctx = new KeypointContext(request);
414
+ this.stats.requests++;
415
+
416
+ try {
417
+ await this.runMiddlewareChain(ctx);
418
+ this.stats.successful++;
419
+
420
+ this.emit('request:success', {
421
+ ctx,
422
+ timestamp: new Date(),
423
+ duration: ctx.response?.duration || 0
424
+ });
425
+
426
+ return ctx.response;
427
+ } catch (error) {
428
+ this.stats.failed++;
429
+
430
+ this.emit('request:error', {
431
+ ctx,
432
+ error,
433
+ timestamp: new Date()
434
+ });
435
+
436
+ return this.options.errorHandler(error, ctx, response);
437
+ }
438
+ }
439
+
440
+ async runMiddlewareChain(ctx, index = 0) {
441
+ if (index >= this.middlewareChain.length) return;
442
+
443
+ const middleware = this.middlewareChain[index];
444
+ const next = () => this.runMiddlewareChain(ctx, index + 1);
445
+
446
+ return await middleware(ctx, next);
447
+ }
448
+
449
+ // HTTP Server integration
450
+ createServer() {
451
+ if (!http) {
452
+ throw new Error('HTTP module not available');
453
+ }
454
+
455
+ const server = http.createServer(async (req, res) => {
456
+ const response = await this.handleRequest(req, res);
457
+
458
+ res.statusCode = response.status || 200;
459
+ Object.entries(response.headers || {}).forEach(([key, value]) => {
460
+ res.setHeader(key, value);
461
+ });
462
+
463
+ if (response.body !== undefined) {
464
+ const body = typeof response.body === 'string' ?
465
+ response.body :
466
+ JSON.stringify(response.body);
467
+ res.end(body);
468
+ } else {
469
+ res.end();
470
+ }
471
+ });
472
+
473
+ if (this.wsGuard) {
474
+ this.wsGuard.attachToServer(server, this);
475
+ }
476
+
477
+ return server;
478
+ }
479
+
480
+ listen(port, hostname = '0.0.0.0', callback) {
481
+ const server = this.createServer();
482
+
483
+ server.listen(port, hostname, () => {
484
+ const address = server.address();
485
+ console.log(`
486
+ KeypointJS Server Started
487
+ ════════════════════════════════════════
488
+ Address: ${hostname}:${port}
489
+ Mode: ${this.options.requireKeypoint ? 'Strict (Keypoint Required)' : 'Permissive'}
490
+ Protocols: HTTP/HTTPS${this.wsGuard ? ' + WebSocket' : ''}
491
+ Plugins: ${this.pluginManager.getPluginNames().length} loaded
492
+ Keypoints: ${this.keypointStorage.store.size} registered
493
+ ════════════════════════════════════════
494
+ `);
495
+
496
+ if (callback) callback(server);
497
+ });
498
+
499
+ return server;
500
+ }
501
+
502
+ async listen(port, hostname = '0.0.0.0', callback) {
503
+ const server = await this.createServer();
504
+
505
+ server.listen(port, hostname, () => {
506
+ const address = server.address();
507
+ console.log(`
508
+ KeypointJS Server Started
509
+ ════════════════════════════════════════
510
+ Address: ${hostname}:${port}
511
+ Mode: ${this.options.requireKeypoint ? 'Strict (Keypoint Required)' : 'Permissive'}
512
+ Protocols: HTTP/HTTPS${this.wsGuard ? ' + WebSocket' : ''}
513
+ Plugins: ${this.pluginManager.getPluginNames().length} loaded
514
+ Keypoints: ${this.keypointStorage.store.size} registered
515
+ ════════════════════════════════════════
516
+ `);
517
+
518
+ if (callback) callback(server);
519
+ });
520
+
521
+ return server;
522
+ }
523
+
524
+ listen(port, hostname = '0.0.0.0', callback) {
525
+ const server = this.createServer();
526
+
527
+ server.listen(port, hostname, () => {
528
+ const address = server.address();
529
+ console.log(`
530
+ KeypointJS Server Started
531
+ ════════════════════════════════════════
532
+ Address: ${hostname}:${port}
533
+ Mode: ${this.options.requireKeypoint ? 'Strict (Keypoint Required)' : 'Permissive'}
534
+ Protocols: HTTP/HTTPS${this.wsGuard ? ' + WebSocket' : ''}
535
+ Plugins: ${this.pluginManager.getPluginNames().length} loaded
536
+ Keypoints: ${this.keypointStorage.store.size} registered
537
+ ════════════════════════════════════════
538
+ `);
539
+
540
+ if (callback) callback(server);
541
+ });
542
+
543
+ return server;
544
+ }
545
+
546
+ // Error handling
547
+
548
+ defaultErrorHandler(error, ctx, response) {
549
+ const status = error.code || 500;
550
+ const message = error.message || 'Internal Server Error';
551
+
552
+ if (this.options.strictMode) {
553
+ // Hide internal error details in production
554
+ const safeMessage = status >= 500 && process.env.NODE_ENV === 'production'
555
+ ? 'Internal Server Error'
556
+ : message;
557
+
558
+ return {
559
+ status,
560
+ headers: {
561
+ 'Content-Type': 'application/json',
562
+ ...this.options.defaultResponseHeaders
563
+ },
564
+ body: {
565
+ error: safeMessage,
566
+ code: status,
567
+ timestamp: new Date().toISOString(),
568
+ requestId: ctx?.id
569
+ }
570
+ };
571
+ }
572
+
573
+ return {
574
+ status,
575
+ headers: {
576
+ 'Content-Type': 'application/json',
577
+ ...this.options.defaultResponseHeaders
578
+ },
579
+ body: {
580
+ error: message,
581
+ code: status,
582
+ timestamp: new Date().toISOString(),
583
+ requestId: ctx?.id,
584
+ details: error.details || {},
585
+ stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
586
+ }
587
+ };
588
+ }
589
+
590
+ // Event system
591
+
592
+ on(event, handler) {
593
+ if (!this.events.has(event)) {
594
+ this.events.set(event, []);
595
+ }
596
+ this.events.get(event).push(handler);
597
+ return this;
598
+ }
599
+
600
+ off(event, handler) {
601
+ if (!this.events.has(event)) return this;
602
+
603
+ const handlers = this.events.get(event);
604
+ const index = handlers.indexOf(handler);
605
+ if (index !== -1) {
606
+ handlers.splice(index, 1);
607
+ }
608
+ return this;
609
+ }
610
+
611
+ emit(event, data) {
612
+ if (!this.events.has(event)) return;
613
+
614
+ for (const handler of this.events.get(event)) {
615
+ try {
616
+ handler(data);
617
+ } catch (error) {
618
+ console.error(`Error in event handler for ${event}:`, error);
619
+ }
620
+ }
621
+ }
622
+
623
+ // Statistics
624
+
625
+ getStats() {
626
+ const uptime = Date.now() - this.stats.startTime;
627
+
628
+ return {
629
+ ...this.stats,
630
+ uptime,
631
+ uptimeFormatted: this.formatUptime(uptime),
632
+ successRate: this.stats.requests > 0
633
+ ? (this.stats.successful / this.stats.requests * 100).toFixed(2) + '%'
634
+ : '0%',
635
+ plugins: this.pluginManager.getStats(),
636
+ keypoints: {
637
+ total: this.keypointStorage.store.size,
638
+ expired: (async () => {
639
+ const all = await this.keypointStorage.list();
640
+ return all.filter(k => k.isExpired()).length;
641
+ })(),
642
+ active: (async () => {
643
+ const all = await this.keypointStorage.list();
644
+ return all.filter(k => !k.isExpired()).length;
645
+ })()
646
+ },
647
+ routes: this.router.routes.size,
648
+ policies: this.policyEngine.rules.length
649
+ };
650
+ }
651
+
652
+ formatUptime(ms) {
653
+ const seconds = Math.floor(ms / 1000);
654
+ const days = Math.floor(seconds / 86400);
655
+ const hours = Math.floor((seconds % 86400) / 3600);
656
+ const minutes = Math.floor((seconds % 3600) / 60);
657
+ const secs = seconds % 60;
658
+
659
+ const parts = [];
660
+ if (days > 0) parts.push(`${days}d`);
661
+ if (hours > 0) parts.push(`${hours}h`);
662
+ if (minutes > 0) parts.push(`${minutes}m`);
663
+ if (secs > 0 || parts.length === 0) parts.push(`${secs}s`);
664
+
665
+ return parts.join(' ');
666
+ }
667
+
668
+ // Health check
669
+
670
+ async healthCheck() {
671
+ const checks = {
672
+ keypointStorage: this.keypointStorage instanceof MemoryKeypointStorage ? 'memory' : 'connected',
673
+ pluginManager: 'ok',
674
+ policyEngine: 'ok',
675
+ router: 'ok',
676
+ uptime: this.getStats().uptimeFormatted
677
+ };
678
+
679
+ // Check storage connectivity if not memory
680
+ if (!(this.keypointStorage instanceof MemoryKeypointStorage)) {
681
+ try {
682
+ await this.keypointStorage.count();
683
+ checks.keypointStorage = 'connected';
684
+ } catch (error) {
685
+ checks.keypointStorage = 'disconnected';
686
+ checks.error = error.message;
687
+ }
688
+ }
689
+
690
+ const allOk = Object.values(checks).every(v => v !== 'disconnected');
691
+
692
+ return {
693
+ status: allOk ? 'healthy' : 'degraded',
694
+ timestamp: new Date().toISOString(),
695
+ checks
696
+ };
697
+ }
698
+
699
+ // Configuration
700
+
701
+ configure(options) {
702
+ this.options = { ...this.options, ...options };
703
+ return this;
704
+ }
705
+
706
+ // Shutdown
707
+
708
+ async shutdown() {
709
+ console.log('Shutting down KeypointJS...');
710
+
711
+ // Shutdown plugins
712
+ await this.pluginManager.shutdown();
713
+
714
+ // Close WebSocket connections
715
+ if (this.wsGuard) {
716
+ this.wsGuard.cleanup();
717
+ }
718
+
719
+ // Emit shutdown event
720
+ this.emit('shutdown', {
721
+ timestamp: new Date(),
722
+ stats: this.getStats()
723
+ });
724
+
725
+ console.log('KeypointJS shutdown complete');
726
+ }
727
+ }
728
+
729
+ // Custom Error Classes
730
+
731
+ export class KeypointError extends Error {
732
+ constructor(message, code = 401, details = {}) {
733
+ super(message);
734
+ this.name = 'KeypointError';
735
+ this.code = code;
736
+ this.details = details;
737
+ this.timestamp = new Date();
738
+ }
739
+ }
740
+
741
+ export class PolicyError extends Error {
742
+ constructor(message, code = 403, decision = null) {
743
+ super(message);
744
+ this.name = 'PolicyError';
745
+ this.code = code;
746
+ this.decision = decision;
747
+ this.timestamp = new Date();
748
+ }
749
+ }
750
+
751
+ export class ValidationError extends Error {
752
+ constructor(message, code = 400, errors = []) {
753
+ super(message);
754
+ this.name = 'ValidationError';
755
+ this.code = code;
756
+ this.errors = errors;
757
+ this.timestamp = new Date();
758
+ }
759
+ }
760
+
761
+ // Export utilities
762
+
763
+ export {
764
+ Context,
765
+ ProtocolEngine,
766
+ Keypoint,
767
+ KeypointContext,
768
+ KeypointValidator,
769
+ MemoryKeypointStorage,
770
+ ScopeManager,
771
+ PolicyEngine,
772
+ BuiltInRules,
773
+ MinimalRouter,
774
+ PluginManager,
775
+ BuiltInHooks,
776
+ RateLimiter,
777
+ AuditLogger,
778
+ WebSocketGuard
779
+ };