@uvrn/api 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.
Files changed (44) hide show
  1. package/TESTING_GUIDE.md +495 -0
  2. package/dist/config/loader.d.ts +10 -0
  3. package/dist/config/loader.d.ts.map +1 -0
  4. package/dist/config/loader.js +52 -0
  5. package/dist/config/loader.js.map +1 -0
  6. package/dist/config/types.d.ts +17 -0
  7. package/dist/config/types.d.ts.map +1 -0
  8. package/dist/config/types.js +6 -0
  9. package/dist/config/types.js.map +1 -0
  10. package/dist/index.d.ts +8 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +11 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/middleware/errorHandler.d.ts +10 -0
  15. package/dist/middleware/errorHandler.d.ts.map +1 -0
  16. package/dist/middleware/errorHandler.js +67 -0
  17. package/dist/middleware/errorHandler.js.map +1 -0
  18. package/dist/routes/delta.d.ts +10 -0
  19. package/dist/routes/delta.d.ts.map +1 -0
  20. package/dist/routes/delta.js +97 -0
  21. package/dist/routes/delta.js.map +1 -0
  22. package/dist/routes/health.d.ts +10 -0
  23. package/dist/routes/health.d.ts.map +1 -0
  24. package/dist/routes/health.js +56 -0
  25. package/dist/routes/health.js.map +1 -0
  26. package/dist/server.d.ts +15 -0
  27. package/dist/server.d.ts.map +1 -0
  28. package/dist/server.js +130 -0
  29. package/dist/server.js.map +1 -0
  30. package/dist/types/api.d.ts +32 -0
  31. package/dist/types/api.d.ts.map +1 -0
  32. package/dist/types/api.js +6 -0
  33. package/dist/types/api.js.map +1 -0
  34. package/jest.config.js +13 -0
  35. package/package.json +44 -0
  36. package/src/config/loader.ts +60 -0
  37. package/src/config/types.ts +18 -0
  38. package/src/index.ts +13 -0
  39. package/src/middleware/errorHandler.ts +69 -0
  40. package/src/routes/delta.ts +120 -0
  41. package/src/routes/health.ts +65 -0
  42. package/src/server.ts +139 -0
  43. package/src/types/api.ts +35 -0
  44. package/tsconfig.json +9 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Global Error Handler
3
+ * Handles uncaught errors and formats error responses
4
+ */
5
+
6
+ import { FastifyInstance, FastifyError, FastifyRequest, FastifyReply } from 'fastify';
7
+ import { ErrorResponse } from '../types/api';
8
+
9
+ /**
10
+ * Register global error handler
11
+ */
12
+ export function registerErrorHandler(server: FastifyInstance): void {
13
+ server.setErrorHandler((error: FastifyError, request: FastifyRequest, reply: FastifyReply) => {
14
+ // Log the error
15
+ request.log.error({
16
+ err: error,
17
+ url: request.url,
18
+ method: request.method
19
+ }, 'Request error');
20
+
21
+ // Determine status code
22
+ const statusCode = error.statusCode || 500;
23
+
24
+ // Map error to code
25
+ let errorCode = 'INTERNAL_ERROR';
26
+ if (statusCode === 400) errorCode = 'BAD_REQUEST';
27
+ else if (statusCode === 404) errorCode = 'NOT_FOUND';
28
+ else if (statusCode === 415) errorCode = 'UNSUPPORTED_MEDIA_TYPE';
29
+ else if (statusCode === 429) errorCode = 'RATE_LIMIT_EXCEEDED';
30
+ else if (statusCode === 503) errorCode = 'SERVICE_UNAVAILABLE';
31
+
32
+ // Build error response
33
+ const errorResponse: ErrorResponse = {
34
+ error: {
35
+ code: errorCode,
36
+ message: error.message || 'An unexpected error occurred',
37
+ details: {
38
+ statusCode
39
+ }
40
+ }
41
+ };
42
+
43
+ // Don't expose internal error details in production
44
+ if (process.env.NODE_ENV !== 'production' && error.stack) {
45
+ errorResponse.error.details = {
46
+ ...errorResponse.error.details,
47
+ stack: error.stack
48
+ };
49
+ }
50
+
51
+ reply.code(statusCode).send(errorResponse);
52
+ });
53
+
54
+ // Handle 404 errors
55
+ server.setNotFoundHandler((request: FastifyRequest, reply: FastifyReply) => {
56
+ const errorResponse: ErrorResponse = {
57
+ error: {
58
+ code: 'NOT_FOUND',
59
+ message: `Route ${request.method} ${request.url} not found`,
60
+ details: {
61
+ method: request.method,
62
+ url: request.url
63
+ }
64
+ }
65
+ };
66
+
67
+ reply.code(404).send(errorResponse);
68
+ });
69
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Delta Engine API Routes
3
+ * Endpoints for bundle processing, validation, and verification
4
+ */
5
+
6
+ import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
7
+ import {
8
+ DeltaBundle,
9
+ DeltaReceipt,
10
+ runDeltaEngine,
11
+ validateBundle,
12
+ verifyReceipt
13
+ } from '@uvrn/core';
14
+ import { ErrorResponse, ValidationResponse } from '../types/api';
15
+
16
+ /**
17
+ * Register delta engine routes
18
+ */
19
+ export async function registerDeltaRoutes(server: FastifyInstance): Promise<void> {
20
+ // POST /api/v1/delta/run - Execute engine on bundle
21
+ server.post<{
22
+ Body: DeltaBundle;
23
+ Reply: DeltaReceipt | ErrorResponse;
24
+ }>('/api/v1/delta/run', async (request: FastifyRequest<{ Body: DeltaBundle }>, reply: FastifyReply) => {
25
+ try {
26
+ const bundle = request.body;
27
+
28
+ // Validate bundle schema
29
+ const validation = validateBundle(bundle);
30
+ if (!validation.valid) {
31
+ return reply.code(400).send({
32
+ error: {
33
+ code: 'INVALID_BUNDLE',
34
+ message: 'Bundle validation failed',
35
+ details: { validationError: validation.error }
36
+ }
37
+ });
38
+ }
39
+
40
+ // Execute engine
41
+ const receipt = runDeltaEngine(bundle);
42
+
43
+ return reply.code(200).send(receipt);
44
+ } catch (error) {
45
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
46
+ request.log.error({ error: errorMessage }, 'Engine execution failed');
47
+
48
+ return reply.code(500).send({
49
+ error: {
50
+ code: 'ENGINE_ERROR',
51
+ message: 'Failed to process bundle',
52
+ details: { error: errorMessage }
53
+ }
54
+ });
55
+ }
56
+ });
57
+
58
+ // POST /api/v1/delta/validate - Validate bundle schema
59
+ server.post<{
60
+ Body: DeltaBundle;
61
+ Reply: ValidationResponse | ErrorResponse;
62
+ }>('/api/v1/delta/validate', async (request: FastifyRequest<{ Body: DeltaBundle }>, reply: FastifyReply) => {
63
+ try {
64
+ const bundle = request.body;
65
+ const validation = validateBundle(bundle);
66
+
67
+ if (!validation.valid) {
68
+ return reply.code(200).send({
69
+ valid: false,
70
+ errors: [
71
+ {
72
+ field: 'bundle',
73
+ message: validation.error || 'Validation failed'
74
+ }
75
+ ]
76
+ });
77
+ }
78
+
79
+ return reply.code(200).send({ valid: true });
80
+ } catch (error) {
81
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
82
+ request.log.error({ error: errorMessage }, 'Validation failed');
83
+
84
+ return reply.code(500).send({
85
+ error: {
86
+ code: 'VALIDATION_ERROR',
87
+ message: 'Failed to validate bundle',
88
+ details: { error: errorMessage }
89
+ }
90
+ });
91
+ }
92
+ });
93
+
94
+ // POST /api/v1/delta/verify - Verify receipt replay
95
+ server.post<{
96
+ Body: DeltaReceipt;
97
+ Reply: { verified: boolean; recomputedHash?: string } | ErrorResponse;
98
+ }>('/api/v1/delta/verify', async (request: FastifyRequest<{ Body: DeltaReceipt }>, reply: FastifyReply) => {
99
+ try {
100
+ const receipt = request.body;
101
+ const verifyResult = verifyReceipt(receipt);
102
+
103
+ return reply.code(200).send({
104
+ verified: verifyResult.verified,
105
+ recomputedHash: verifyResult.recomputedHash
106
+ });
107
+ } catch (error) {
108
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
109
+ request.log.error({ error: errorMessage }, 'Verification failed');
110
+
111
+ return reply.code(500).send({
112
+ error: {
113
+ code: 'VERIFICATION_ERROR',
114
+ message: 'Failed to verify receipt',
115
+ details: { error: errorMessage }
116
+ }
117
+ });
118
+ }
119
+ });
120
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Health and Version Routes
3
+ * System status and version information endpoints
4
+ */
5
+
6
+ import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
7
+ import { HealthResponse, VersionResponse } from '../types/api';
8
+
9
+ const SERVER_START_TIME = Date.now();
10
+ const API_VERSION = '1.0.0';
11
+ const PROTOCOL_VERSION = '1.0';
12
+
13
+ /**
14
+ * Register health and version routes
15
+ */
16
+ export async function registerHealthRoutes(server: FastifyInstance): Promise<void> {
17
+ // GET /api/v1/health - Health check endpoint
18
+ server.get<{
19
+ Reply: HealthResponse;
20
+ }>('/api/v1/health', async (_request: FastifyRequest, reply: FastifyReply) => {
21
+ const uptime = Date.now() - SERVER_START_TIME;
22
+
23
+ // Check if engine is available by attempting to import it
24
+ let engineAvailable = true;
25
+ try {
26
+ require('@uvrn/core');
27
+ } catch {
28
+ engineAvailable = false;
29
+ }
30
+
31
+ const health: HealthResponse = {
32
+ status: engineAvailable ? 'healthy' : 'unhealthy',
33
+ uptime,
34
+ version: API_VERSION,
35
+ engine: {
36
+ available: engineAvailable
37
+ },
38
+ timestamp: new Date().toISOString()
39
+ };
40
+
41
+ const statusCode = engineAvailable ? 200 : 503;
42
+ return reply.code(statusCode).send(health);
43
+ });
44
+
45
+ // GET /api/v1/version - Version information
46
+ server.get<{
47
+ Reply: VersionResponse;
48
+ }>('/api/v1/version', async (_request: FastifyRequest, reply: FastifyReply) => {
49
+ let engineVersion = 'unknown';
50
+ try {
51
+ const enginePkg = require('@uvrn/core/package.json');
52
+ engineVersion = enginePkg.version;
53
+ } catch {
54
+ // Engine version not available
55
+ }
56
+
57
+ const version: VersionResponse = {
58
+ apiVersion: API_VERSION,
59
+ engineVersion,
60
+ protocolVersion: PROTOCOL_VERSION
61
+ };
62
+
63
+ return reply.code(200).send(version);
64
+ });
65
+ }
package/src/server.ts ADDED
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Delta Engine API Server
3
+ * Fastify-based REST API for bundle processing
4
+ */
5
+
6
+ import Fastify, { FastifyInstance } from 'fastify';
7
+ import cors from '@fastify/cors';
8
+ import helmet from '@fastify/helmet';
9
+ import rateLimit from '@fastify/rate-limit';
10
+ import { loadConfig } from './config/loader';
11
+ import { ServerConfig } from './config/types';
12
+ import { registerDeltaRoutes } from './routes/delta';
13
+ import { registerHealthRoutes } from './routes/health';
14
+ import { registerErrorHandler } from './middleware/errorHandler';
15
+
16
+ /**
17
+ * Create and configure Fastify server instance
18
+ */
19
+ export async function createServer(config?: ServerConfig): Promise<FastifyInstance> {
20
+ // Load configuration
21
+ const serverConfig = config || loadConfig();
22
+
23
+ // Create Fastify instance with logging
24
+ const server = Fastify({
25
+ logger: {
26
+ level: serverConfig.logLevel,
27
+ transport: serverConfig.nodeEnv === 'development' ? {
28
+ target: 'pino-pretty',
29
+ options: {
30
+ translateTime: 'HH:MM:ss Z',
31
+ ignore: 'pid,hostname'
32
+ }
33
+ } : undefined
34
+ }
35
+ });
36
+
37
+ // Register plugins
38
+ await server.register(helmet, {
39
+ contentSecurityPolicy: serverConfig.nodeEnv === 'production' ? undefined : false
40
+ });
41
+
42
+ await server.register(cors, {
43
+ origin: serverConfig.corsOrigins,
44
+ credentials: true
45
+ });
46
+
47
+ await server.register(rateLimit, {
48
+ max: serverConfig.rateLimitMax,
49
+ timeWindow: serverConfig.rateLimitTimeWindow,
50
+ errorResponseBuilder: () => ({
51
+ error: {
52
+ code: 'RATE_LIMIT_EXCEEDED',
53
+ message: 'Too many requests, please try again later',
54
+ details: {
55
+ rateLimitMax: serverConfig.rateLimitMax,
56
+ timeWindow: serverConfig.rateLimitTimeWindow
57
+ }
58
+ }
59
+ })
60
+ });
61
+
62
+ // Add request logging hook
63
+ server.addHook('onRequest', async (request, _reply) => {
64
+ request.log.info({
65
+ url: request.url,
66
+ method: request.method,
67
+ ip: request.ip
68
+ }, 'Incoming request');
69
+ });
70
+
71
+ server.addHook('onResponse', async (request, reply) => {
72
+ request.log.info({
73
+ url: request.url,
74
+ method: request.method,
75
+ statusCode: reply.statusCode,
76
+ responseTime: reply.elapsedTime
77
+ }, 'Request completed');
78
+ });
79
+
80
+ // Validate content-type for POST requests
81
+ server.addHook('preHandler', async (request, reply) => {
82
+ if (request.method === 'POST' && request.url.startsWith('/api/v1/delta/')) {
83
+ const contentType = request.headers['content-type'];
84
+ if (!contentType || !contentType.includes('application/json')) {
85
+ return reply.code(415).send({
86
+ error: {
87
+ code: 'UNSUPPORTED_MEDIA_TYPE',
88
+ message: 'Content-Type must be application/json',
89
+ details: {
90
+ receivedContentType: contentType || 'none'
91
+ }
92
+ }
93
+ });
94
+ }
95
+ }
96
+ });
97
+
98
+ // Register routes
99
+ await registerHealthRoutes(server);
100
+ await registerDeltaRoutes(server);
101
+
102
+ // Register error handler (must be last)
103
+ registerErrorHandler(server);
104
+
105
+ return server;
106
+ }
107
+
108
+ /**
109
+ * Start the server
110
+ */
111
+ export async function startServer(config?: ServerConfig): Promise<FastifyInstance> {
112
+ const serverConfig = config || loadConfig();
113
+ const server = await createServer(serverConfig);
114
+
115
+ try {
116
+ await server.listen({
117
+ port: serverConfig.port,
118
+ host: serverConfig.host
119
+ });
120
+
121
+ server.log.info(`🚀 Delta Engine API server running at http://${serverConfig.host}:${serverConfig.port}`);
122
+ server.log.info(`📊 Health check: http://${serverConfig.host}:${serverConfig.port}/api/v1/health`);
123
+ server.log.info(`📦 Environment: ${serverConfig.nodeEnv}`);
124
+ server.log.info(`🔒 Rate limit: ${serverConfig.rateLimitMax} requests per ${serverConfig.rateLimitTimeWindow}`);
125
+
126
+ return server;
127
+ } catch (error) {
128
+ server.log.error(error);
129
+ throw error;
130
+ }
131
+ }
132
+
133
+ // Start server if run directly
134
+ if (require.main === module) {
135
+ startServer().catch((error) => {
136
+ console.error('Failed to start server:', error);
137
+ process.exit(1);
138
+ });
139
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * API-specific type definitions
3
+ */
4
+
5
+ export interface ErrorResponse {
6
+ error: {
7
+ code: string;
8
+ message: string;
9
+ details?: Record<string, unknown>;
10
+ };
11
+ }
12
+
13
+ export interface HealthResponse {
14
+ status: 'healthy' | 'unhealthy';
15
+ uptime: number;
16
+ version: string;
17
+ engine: {
18
+ available: boolean;
19
+ };
20
+ timestamp: string;
21
+ }
22
+
23
+ export interface VersionResponse {
24
+ apiVersion: string;
25
+ engineVersion: string;
26
+ protocolVersion: string;
27
+ }
28
+
29
+ export interface ValidationResponse {
30
+ valid: boolean;
31
+ errors?: Array<{
32
+ field: string;
33
+ message: string;
34
+ }>;
35
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "tests"]
9
+ }