@vasperacapital/vaspera-shared 0.1.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/.turbo/turbo-build.log +34 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/dist/errors/index.d.mts +288 -0
- package/dist/errors/index.d.ts +288 -0
- package/dist/errors/index.js +341 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/index.mjs +310 -0
- package/dist/errors/index.mjs.map +1 -0
- package/dist/index.d.mts +57 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +458 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +421 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types/index.d.mts +122 -0
- package/dist/types/index.d.ts +122 -0
- package/dist/types/index.js +45 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +19 -0
- package/dist/types/index.mjs.map +1 -0
- package/package.json +48 -0
- package/src/__tests__/api-key.test.ts +129 -0
- package/src/__tests__/encryption.test.ts +129 -0
- package/src/__tests__/errors.test.ts +185 -0
- package/src/errors/codes.ts +213 -0
- package/src/errors/factory.ts +164 -0
- package/src/errors/index.ts +10 -0
- package/src/index.ts +8 -0
- package/src/types/index.ts +164 -0
- package/src/utils/api-key.ts +72 -0
- package/src/utils/encryption.ts +79 -0
- package/src/utils/index.ts +15 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors/codes.ts","../../src/errors/factory.ts"],"sourcesContent":["export interface ErrorDefinition {\n code: string;\n message: string;\n httpStatus: number;\n}\n\nexport const ErrorCodes = {\n // Authentication Errors (VPM-AUTH-XXX)\n AUTH: {\n REQUIRED: {\n code: 'VPM-AUTH-001',\n message: 'Authentication required',\n httpStatus: 401,\n },\n INVALID_TOKEN: {\n code: 'VPM-AUTH-002',\n message: 'Invalid or expired authentication token',\n httpStatus: 401,\n },\n INSUFFICIENT_PERMISSIONS: {\n code: 'VPM-AUTH-003',\n message: 'Insufficient permissions for this action',\n httpStatus: 403,\n },\n SESSION_EXPIRED: {\n code: 'VPM-AUTH-004',\n message: 'Session has expired, please login again',\n httpStatus: 401,\n },\n MFA_REQUIRED: {\n code: 'VPM-AUTH-005',\n message: 'Multi-factor authentication required',\n httpStatus: 403,\n },\n },\n\n // API Key Errors (VPM-API-KEY-XXX)\n API_KEY: {\n REQUIRED: {\n code: 'VPM-API-KEY-001',\n message: 'API key is required',\n httpStatus: 401,\n },\n INVALID: {\n code: 'VPM-API-KEY-002',\n message: 'Invalid API key',\n httpStatus: 401,\n },\n REVOKED: {\n code: 'VPM-API-KEY-003',\n message: 'API key has been revoked',\n httpStatus: 401,\n },\n EXPIRED: {\n code: 'VPM-API-KEY-004',\n message: 'API key has expired',\n httpStatus: 401,\n },\n RATE_LIMITED: {\n code: 'VPM-API-KEY-005',\n message: 'API key rate limit exceeded',\n httpStatus: 429,\n },\n QUOTA_EXCEEDED: {\n code: 'VPM-API-KEY-006',\n message: 'Monthly usage quota exceeded',\n httpStatus: 429,\n },\n },\n\n // MCP Tool Errors (VPM-MCP-XXX)\n MCP: {\n TOOL_NOT_FOUND: {\n code: 'VPM-MCP-001',\n message: 'Requested tool not found',\n httpStatus: 404,\n },\n INVALID_ARGUMENTS: {\n code: 'VPM-MCP-002',\n message: 'Invalid tool arguments provided',\n httpStatus: 400,\n },\n EXECUTION_FAILED: {\n code: 'VPM-MCP-003',\n message: 'Tool execution failed',\n httpStatus: 500,\n },\n TIMEOUT: {\n code: 'VPM-MCP-004',\n message: 'Tool execution timed out',\n httpStatus: 504,\n },\n LLM_ERROR: {\n code: 'VPM-MCP-005',\n message: 'AI model returned an error',\n httpStatus: 502,\n },\n CONTEXT_TOO_LARGE: {\n code: 'VPM-MCP-006',\n message: 'Input context exceeds maximum size',\n httpStatus: 413,\n },\n },\n\n // Billing Errors (VPM-BILLING-XXX)\n BILLING: {\n NO_SUBSCRIPTION: {\n code: 'VPM-BILLING-001',\n message: 'No active subscription found',\n httpStatus: 402,\n },\n SUBSCRIPTION_EXPIRED: {\n code: 'VPM-BILLING-002',\n message: 'Subscription has expired',\n httpStatus: 402,\n },\n PAYMENT_FAILED: {\n code: 'VPM-BILLING-003',\n message: 'Payment processing failed',\n httpStatus: 402,\n },\n FEATURE_NOT_INCLUDED: {\n code: 'VPM-BILLING-004',\n message: 'Feature not included in current plan',\n httpStatus: 403,\n },\n UPGRADE_REQUIRED: {\n code: 'VPM-BILLING-005',\n message: 'Plan upgrade required for this action',\n httpStatus: 403,\n },\n },\n\n // Integration Errors (VPM-INT-XXX)\n INTEGRATION: {\n NOT_CONNECTED: {\n code: 'VPM-INT-001',\n message: 'Integration not connected',\n httpStatus: 400,\n },\n TOKEN_EXPIRED: {\n code: 'VPM-INT-002',\n message: 'Integration token expired, reconnection required',\n httpStatus: 401,\n },\n REFRESH_FAILED: {\n code: 'VPM-INT-003',\n message: 'Failed to refresh integration token',\n httpStatus: 502,\n },\n API_ERROR: {\n code: 'VPM-INT-004',\n message: 'External integration API error',\n httpStatus: 502,\n },\n RATE_LIMITED: {\n code: 'VPM-INT-005',\n message: 'Integration rate limit exceeded',\n httpStatus: 429,\n },\n },\n\n // Validation Errors (VPM-VAL-XXX)\n VALIDATION: {\n REQUIRED_FIELD: {\n code: 'VPM-VAL-001',\n message: 'Required field missing',\n httpStatus: 400,\n },\n INVALID_FORMAT: {\n code: 'VPM-VAL-002',\n message: 'Invalid field format',\n httpStatus: 400,\n },\n OUT_OF_RANGE: {\n code: 'VPM-VAL-003',\n message: 'Value out of allowed range',\n httpStatus: 400,\n },\n INVALID_JSON: {\n code: 'VPM-VAL-004',\n message: 'Invalid JSON in request body',\n httpStatus: 400,\n },\n },\n\n // System Errors (VPM-SYS-XXX)\n SYSTEM: {\n INTERNAL_ERROR: {\n code: 'VPM-SYS-001',\n message: 'Internal server error',\n httpStatus: 500,\n },\n DATABASE_ERROR: {\n code: 'VPM-SYS-002',\n message: 'Database operation failed',\n httpStatus: 500,\n },\n SERVICE_UNAVAILABLE: {\n code: 'VPM-SYS-003',\n message: 'Service temporarily unavailable',\n httpStatus: 503,\n },\n MAINTENANCE_MODE: {\n code: 'VPM-SYS-004',\n message: 'System is under maintenance',\n httpStatus: 503,\n },\n },\n} as const;\n\nexport type ErrorCodeType =\n (typeof ErrorCodes)[keyof typeof ErrorCodes][keyof (typeof ErrorCodes)[keyof typeof ErrorCodes]];\n","import type { ErrorDefinition } from './codes.js';\nimport { ErrorCodes } from './codes.js';\n\nexport interface VasperaError {\n code: string;\n message: string;\n details?: Record<string, unknown>;\n timestamp: string;\n requestId: string;\n docUrl?: string;\n}\n\nexport interface ErrorResponse {\n success: false;\n error: VasperaError;\n}\n\nfunction generateRequestId(): string {\n return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;\n}\n\nexport function createError(\n errorDef: ErrorDefinition,\n details?: Record<string, unknown>,\n requestId?: string\n): VasperaError {\n return {\n code: errorDef.code,\n message: errorDef.message,\n details,\n timestamp: new Date().toISOString(),\n requestId: requestId || generateRequestId(),\n docUrl: `https://docs.vaspera.pm/errors/${errorDef.code}`,\n };\n}\n\nexport function createErrorResponse(\n errorDef: ErrorDefinition,\n details?: Record<string, unknown>,\n requestId?: string\n): { response: ErrorResponse; status: number } {\n return {\n response: {\n success: false,\n error: createError(errorDef, details, requestId),\n },\n status: errorDef.httpStatus,\n };\n}\n\nfunction getNextResetDate(): string {\n const now = new Date();\n const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);\n return nextMonth.toISOString();\n}\n\n// Pre-built error creators for common cases\nexport const Errors = {\n authRequired: (requestId?: string) =>\n createErrorResponse(ErrorCodes.AUTH.REQUIRED, undefined, requestId),\n\n invalidApiKey: (requestId?: string) =>\n createErrorResponse(ErrorCodes.API_KEY.INVALID, undefined, requestId),\n\n apiKeyRevoked: (requestId?: string) =>\n createErrorResponse(ErrorCodes.API_KEY.REVOKED, undefined, requestId),\n\n apiKeyExpired: (requestId?: string) =>\n createErrorResponse(ErrorCodes.API_KEY.EXPIRED, undefined, requestId),\n\n rateLimited: (retryAfter: number, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.API_KEY.RATE_LIMITED,\n { retryAfterSeconds: retryAfter },\n requestId\n ),\n\n quotaExceeded: (currentUsage: number, limit: number, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.API_KEY.QUOTA_EXCEEDED,\n { currentUsage, limit, resetDate: getNextResetDate() },\n requestId\n ),\n\n toolNotFound: (toolName: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.MCP.TOOL_NOT_FOUND,\n { tool: toolName },\n requestId\n ),\n\n toolExecutionFailed: (toolName: string, reason: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.MCP.EXECUTION_FAILED,\n { tool: toolName, reason },\n requestId\n ),\n\n toolTimeout: (toolName: string, timeoutMs: number, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.MCP.TIMEOUT,\n { tool: toolName, timeoutMs },\n requestId\n ),\n\n validationFailed: (field: string, reason: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.VALIDATION.REQUIRED_FIELD,\n { field, reason },\n requestId\n ),\n\n invalidFormat: (field: string, expected: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.VALIDATION.INVALID_FORMAT,\n { field, expected },\n requestId\n ),\n\n internalError: (message?: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.SYSTEM.INTERNAL_ERROR,\n message ? { message } : undefined,\n requestId\n ),\n\n databaseError: (operation: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.SYSTEM.DATABASE_ERROR,\n { operation },\n requestId\n ),\n\n integrationNotConnected: (provider: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.INTEGRATION.NOT_CONNECTED,\n { provider },\n requestId\n ),\n\n subscriptionRequired: (feature: string, requiredTier: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.BILLING.FEATURE_NOT_INCLUDED,\n { feature, requiredTier },\n requestId\n ),\n};\n\n// Custom error class for throwing in API routes\nexport class VasperaApiError extends Error {\n public readonly errorDef: ErrorDefinition;\n public readonly details?: Record<string, unknown>;\n\n constructor(errorDef: ErrorDefinition, details?: Record<string, unknown>) {\n super(errorDef.message);\n this.name = 'VasperaApiError';\n this.errorDef = errorDef;\n this.details = details;\n }\n\n toResponse(requestId?: string): { response: ErrorResponse; status: number } {\n return createErrorResponse(this.errorDef, this.details, requestId);\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export { ApiErrorResponse, ApiKey, ApiKeyWithSecret, ApiResponse, ApiSuccessResponse, IntegrationProvider, IntegrationStatus, IntegrationToken, McpToolName, McpToolResult, QUOTA_LIMITS, RATE_LIMITS, SubscriptionStatus, SubscriptionTier, UsageEvent, UsageSummary, UserProfile } from './types/index.mjs';
|
|
2
|
+
export { ErrorCodeType, ErrorCodes, ErrorDefinition, ErrorResponse, Errors, VasperaApiError, VasperaError, createError, createErrorResponse } from './errors/index.mjs';
|
|
3
|
+
|
|
4
|
+
interface GeneratedApiKey {
|
|
5
|
+
key: string;
|
|
6
|
+
keyPrefix: string;
|
|
7
|
+
keyHash: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Generate a new API key
|
|
11
|
+
* @param environment - 'live' or 'test'
|
|
12
|
+
* @returns Generated key with prefix and hash
|
|
13
|
+
*/
|
|
14
|
+
declare function generateApiKey(environment?: 'live' | 'test'): GeneratedApiKey;
|
|
15
|
+
/**
|
|
16
|
+
* Hash an API key for storage
|
|
17
|
+
* Using SHA-256 since we need to look up by hash
|
|
18
|
+
*/
|
|
19
|
+
declare function hashApiKey(key: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Validate API key format
|
|
22
|
+
*/
|
|
23
|
+
declare function isValidApiKeyFormat(key: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Extract prefix from API key
|
|
26
|
+
*/
|
|
27
|
+
declare function extractKeyPrefix(key: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Check if key is a test key
|
|
30
|
+
*/
|
|
31
|
+
declare function isTestKey(key: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Mask an API key for display (show first 16 and last 4 chars)
|
|
34
|
+
*/
|
|
35
|
+
declare function maskApiKey(key: string): string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Encrypt text using AES-256-GCM
|
|
39
|
+
* @param text - Plain text to encrypt
|
|
40
|
+
* @param secret - Encryption secret
|
|
41
|
+
* @returns Encrypted string in format: salt:iv:tag:ciphertext (all base64)
|
|
42
|
+
*/
|
|
43
|
+
declare function encrypt(text: string, secret: string): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Decrypt text that was encrypted with encrypt()
|
|
46
|
+
* @param encryptedText - Encrypted string in format: salt:iv:tag:ciphertext
|
|
47
|
+
* @param secret - Encryption secret (must match encryption)
|
|
48
|
+
* @returns Decrypted plain text
|
|
49
|
+
*/
|
|
50
|
+
declare function decrypt(encryptedText: string, secret: string): Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Generate a random encryption secret
|
|
53
|
+
* @returns 32-byte hex string suitable for use as encryption secret
|
|
54
|
+
*/
|
|
55
|
+
declare function generateEncryptionSecret(): string;
|
|
56
|
+
|
|
57
|
+
export { type GeneratedApiKey, decrypt, encrypt, extractKeyPrefix, generateApiKey, generateEncryptionSecret, hashApiKey, isTestKey, isValidApiKeyFormat, maskApiKey };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export { ApiErrorResponse, ApiKey, ApiKeyWithSecret, ApiResponse, ApiSuccessResponse, IntegrationProvider, IntegrationStatus, IntegrationToken, McpToolName, McpToolResult, QUOTA_LIMITS, RATE_LIMITS, SubscriptionStatus, SubscriptionTier, UsageEvent, UsageSummary, UserProfile } from './types/index.js';
|
|
2
|
+
export { ErrorCodeType, ErrorCodes, ErrorDefinition, ErrorResponse, Errors, VasperaApiError, VasperaError, createError, createErrorResponse } from './errors/index.js';
|
|
3
|
+
|
|
4
|
+
interface GeneratedApiKey {
|
|
5
|
+
key: string;
|
|
6
|
+
keyPrefix: string;
|
|
7
|
+
keyHash: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Generate a new API key
|
|
11
|
+
* @param environment - 'live' or 'test'
|
|
12
|
+
* @returns Generated key with prefix and hash
|
|
13
|
+
*/
|
|
14
|
+
declare function generateApiKey(environment?: 'live' | 'test'): GeneratedApiKey;
|
|
15
|
+
/**
|
|
16
|
+
* Hash an API key for storage
|
|
17
|
+
* Using SHA-256 since we need to look up by hash
|
|
18
|
+
*/
|
|
19
|
+
declare function hashApiKey(key: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Validate API key format
|
|
22
|
+
*/
|
|
23
|
+
declare function isValidApiKeyFormat(key: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Extract prefix from API key
|
|
26
|
+
*/
|
|
27
|
+
declare function extractKeyPrefix(key: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Check if key is a test key
|
|
30
|
+
*/
|
|
31
|
+
declare function isTestKey(key: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Mask an API key for display (show first 16 and last 4 chars)
|
|
34
|
+
*/
|
|
35
|
+
declare function maskApiKey(key: string): string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Encrypt text using AES-256-GCM
|
|
39
|
+
* @param text - Plain text to encrypt
|
|
40
|
+
* @param secret - Encryption secret
|
|
41
|
+
* @returns Encrypted string in format: salt:iv:tag:ciphertext (all base64)
|
|
42
|
+
*/
|
|
43
|
+
declare function encrypt(text: string, secret: string): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Decrypt text that was encrypted with encrypt()
|
|
46
|
+
* @param encryptedText - Encrypted string in format: salt:iv:tag:ciphertext
|
|
47
|
+
* @param secret - Encryption secret (must match encryption)
|
|
48
|
+
* @returns Decrypted plain text
|
|
49
|
+
*/
|
|
50
|
+
declare function decrypt(encryptedText: string, secret: string): Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Generate a random encryption secret
|
|
53
|
+
* @returns 32-byte hex string suitable for use as encryption secret
|
|
54
|
+
*/
|
|
55
|
+
declare function generateEncryptionSecret(): string;
|
|
56
|
+
|
|
57
|
+
export { type GeneratedApiKey, decrypt, encrypt, extractKeyPrefix, generateApiKey, generateEncryptionSecret, hashApiKey, isTestKey, isValidApiKeyFormat, maskApiKey };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ErrorCodes: () => ErrorCodes,
|
|
24
|
+
Errors: () => Errors,
|
|
25
|
+
QUOTA_LIMITS: () => QUOTA_LIMITS,
|
|
26
|
+
RATE_LIMITS: () => RATE_LIMITS,
|
|
27
|
+
VasperaApiError: () => VasperaApiError,
|
|
28
|
+
createError: () => createError,
|
|
29
|
+
createErrorResponse: () => createErrorResponse,
|
|
30
|
+
decrypt: () => decrypt,
|
|
31
|
+
encrypt: () => encrypt,
|
|
32
|
+
extractKeyPrefix: () => extractKeyPrefix,
|
|
33
|
+
generateApiKey: () => generateApiKey,
|
|
34
|
+
generateEncryptionSecret: () => generateEncryptionSecret,
|
|
35
|
+
hashApiKey: () => hashApiKey,
|
|
36
|
+
isTestKey: () => isTestKey,
|
|
37
|
+
isValidApiKeyFormat: () => isValidApiKeyFormat,
|
|
38
|
+
maskApiKey: () => maskApiKey
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/types/index.ts
|
|
43
|
+
var QUOTA_LIMITS = {
|
|
44
|
+
free: 5,
|
|
45
|
+
starter: 100,
|
|
46
|
+
pro: 500,
|
|
47
|
+
enterprise: 999999
|
|
48
|
+
// Effectively unlimited
|
|
49
|
+
};
|
|
50
|
+
var RATE_LIMITS = {
|
|
51
|
+
free: { perMinute: 10, perDay: 100 },
|
|
52
|
+
starter: { perMinute: 30, perDay: 1e3 },
|
|
53
|
+
pro: { perMinute: 60, perDay: 5e3 },
|
|
54
|
+
enterprise: { perMinute: 120, perDay: 999999 }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/errors/codes.ts
|
|
58
|
+
var ErrorCodes = {
|
|
59
|
+
// Authentication Errors (VPM-AUTH-XXX)
|
|
60
|
+
AUTH: {
|
|
61
|
+
REQUIRED: {
|
|
62
|
+
code: "VPM-AUTH-001",
|
|
63
|
+
message: "Authentication required",
|
|
64
|
+
httpStatus: 401
|
|
65
|
+
},
|
|
66
|
+
INVALID_TOKEN: {
|
|
67
|
+
code: "VPM-AUTH-002",
|
|
68
|
+
message: "Invalid or expired authentication token",
|
|
69
|
+
httpStatus: 401
|
|
70
|
+
},
|
|
71
|
+
INSUFFICIENT_PERMISSIONS: {
|
|
72
|
+
code: "VPM-AUTH-003",
|
|
73
|
+
message: "Insufficient permissions for this action",
|
|
74
|
+
httpStatus: 403
|
|
75
|
+
},
|
|
76
|
+
SESSION_EXPIRED: {
|
|
77
|
+
code: "VPM-AUTH-004",
|
|
78
|
+
message: "Session has expired, please login again",
|
|
79
|
+
httpStatus: 401
|
|
80
|
+
},
|
|
81
|
+
MFA_REQUIRED: {
|
|
82
|
+
code: "VPM-AUTH-005",
|
|
83
|
+
message: "Multi-factor authentication required",
|
|
84
|
+
httpStatus: 403
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
// API Key Errors (VPM-API-KEY-XXX)
|
|
88
|
+
API_KEY: {
|
|
89
|
+
REQUIRED: {
|
|
90
|
+
code: "VPM-API-KEY-001",
|
|
91
|
+
message: "API key is required",
|
|
92
|
+
httpStatus: 401
|
|
93
|
+
},
|
|
94
|
+
INVALID: {
|
|
95
|
+
code: "VPM-API-KEY-002",
|
|
96
|
+
message: "Invalid API key",
|
|
97
|
+
httpStatus: 401
|
|
98
|
+
},
|
|
99
|
+
REVOKED: {
|
|
100
|
+
code: "VPM-API-KEY-003",
|
|
101
|
+
message: "API key has been revoked",
|
|
102
|
+
httpStatus: 401
|
|
103
|
+
},
|
|
104
|
+
EXPIRED: {
|
|
105
|
+
code: "VPM-API-KEY-004",
|
|
106
|
+
message: "API key has expired",
|
|
107
|
+
httpStatus: 401
|
|
108
|
+
},
|
|
109
|
+
RATE_LIMITED: {
|
|
110
|
+
code: "VPM-API-KEY-005",
|
|
111
|
+
message: "API key rate limit exceeded",
|
|
112
|
+
httpStatus: 429
|
|
113
|
+
},
|
|
114
|
+
QUOTA_EXCEEDED: {
|
|
115
|
+
code: "VPM-API-KEY-006",
|
|
116
|
+
message: "Monthly usage quota exceeded",
|
|
117
|
+
httpStatus: 429
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
// MCP Tool Errors (VPM-MCP-XXX)
|
|
121
|
+
MCP: {
|
|
122
|
+
TOOL_NOT_FOUND: {
|
|
123
|
+
code: "VPM-MCP-001",
|
|
124
|
+
message: "Requested tool not found",
|
|
125
|
+
httpStatus: 404
|
|
126
|
+
},
|
|
127
|
+
INVALID_ARGUMENTS: {
|
|
128
|
+
code: "VPM-MCP-002",
|
|
129
|
+
message: "Invalid tool arguments provided",
|
|
130
|
+
httpStatus: 400
|
|
131
|
+
},
|
|
132
|
+
EXECUTION_FAILED: {
|
|
133
|
+
code: "VPM-MCP-003",
|
|
134
|
+
message: "Tool execution failed",
|
|
135
|
+
httpStatus: 500
|
|
136
|
+
},
|
|
137
|
+
TIMEOUT: {
|
|
138
|
+
code: "VPM-MCP-004",
|
|
139
|
+
message: "Tool execution timed out",
|
|
140
|
+
httpStatus: 504
|
|
141
|
+
},
|
|
142
|
+
LLM_ERROR: {
|
|
143
|
+
code: "VPM-MCP-005",
|
|
144
|
+
message: "AI model returned an error",
|
|
145
|
+
httpStatus: 502
|
|
146
|
+
},
|
|
147
|
+
CONTEXT_TOO_LARGE: {
|
|
148
|
+
code: "VPM-MCP-006",
|
|
149
|
+
message: "Input context exceeds maximum size",
|
|
150
|
+
httpStatus: 413
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
// Billing Errors (VPM-BILLING-XXX)
|
|
154
|
+
BILLING: {
|
|
155
|
+
NO_SUBSCRIPTION: {
|
|
156
|
+
code: "VPM-BILLING-001",
|
|
157
|
+
message: "No active subscription found",
|
|
158
|
+
httpStatus: 402
|
|
159
|
+
},
|
|
160
|
+
SUBSCRIPTION_EXPIRED: {
|
|
161
|
+
code: "VPM-BILLING-002",
|
|
162
|
+
message: "Subscription has expired",
|
|
163
|
+
httpStatus: 402
|
|
164
|
+
},
|
|
165
|
+
PAYMENT_FAILED: {
|
|
166
|
+
code: "VPM-BILLING-003",
|
|
167
|
+
message: "Payment processing failed",
|
|
168
|
+
httpStatus: 402
|
|
169
|
+
},
|
|
170
|
+
FEATURE_NOT_INCLUDED: {
|
|
171
|
+
code: "VPM-BILLING-004",
|
|
172
|
+
message: "Feature not included in current plan",
|
|
173
|
+
httpStatus: 403
|
|
174
|
+
},
|
|
175
|
+
UPGRADE_REQUIRED: {
|
|
176
|
+
code: "VPM-BILLING-005",
|
|
177
|
+
message: "Plan upgrade required for this action",
|
|
178
|
+
httpStatus: 403
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
// Integration Errors (VPM-INT-XXX)
|
|
182
|
+
INTEGRATION: {
|
|
183
|
+
NOT_CONNECTED: {
|
|
184
|
+
code: "VPM-INT-001",
|
|
185
|
+
message: "Integration not connected",
|
|
186
|
+
httpStatus: 400
|
|
187
|
+
},
|
|
188
|
+
TOKEN_EXPIRED: {
|
|
189
|
+
code: "VPM-INT-002",
|
|
190
|
+
message: "Integration token expired, reconnection required",
|
|
191
|
+
httpStatus: 401
|
|
192
|
+
},
|
|
193
|
+
REFRESH_FAILED: {
|
|
194
|
+
code: "VPM-INT-003",
|
|
195
|
+
message: "Failed to refresh integration token",
|
|
196
|
+
httpStatus: 502
|
|
197
|
+
},
|
|
198
|
+
API_ERROR: {
|
|
199
|
+
code: "VPM-INT-004",
|
|
200
|
+
message: "External integration API error",
|
|
201
|
+
httpStatus: 502
|
|
202
|
+
},
|
|
203
|
+
RATE_LIMITED: {
|
|
204
|
+
code: "VPM-INT-005",
|
|
205
|
+
message: "Integration rate limit exceeded",
|
|
206
|
+
httpStatus: 429
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
// Validation Errors (VPM-VAL-XXX)
|
|
210
|
+
VALIDATION: {
|
|
211
|
+
REQUIRED_FIELD: {
|
|
212
|
+
code: "VPM-VAL-001",
|
|
213
|
+
message: "Required field missing",
|
|
214
|
+
httpStatus: 400
|
|
215
|
+
},
|
|
216
|
+
INVALID_FORMAT: {
|
|
217
|
+
code: "VPM-VAL-002",
|
|
218
|
+
message: "Invalid field format",
|
|
219
|
+
httpStatus: 400
|
|
220
|
+
},
|
|
221
|
+
OUT_OF_RANGE: {
|
|
222
|
+
code: "VPM-VAL-003",
|
|
223
|
+
message: "Value out of allowed range",
|
|
224
|
+
httpStatus: 400
|
|
225
|
+
},
|
|
226
|
+
INVALID_JSON: {
|
|
227
|
+
code: "VPM-VAL-004",
|
|
228
|
+
message: "Invalid JSON in request body",
|
|
229
|
+
httpStatus: 400
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
// System Errors (VPM-SYS-XXX)
|
|
233
|
+
SYSTEM: {
|
|
234
|
+
INTERNAL_ERROR: {
|
|
235
|
+
code: "VPM-SYS-001",
|
|
236
|
+
message: "Internal server error",
|
|
237
|
+
httpStatus: 500
|
|
238
|
+
},
|
|
239
|
+
DATABASE_ERROR: {
|
|
240
|
+
code: "VPM-SYS-002",
|
|
241
|
+
message: "Database operation failed",
|
|
242
|
+
httpStatus: 500
|
|
243
|
+
},
|
|
244
|
+
SERVICE_UNAVAILABLE: {
|
|
245
|
+
code: "VPM-SYS-003",
|
|
246
|
+
message: "Service temporarily unavailable",
|
|
247
|
+
httpStatus: 503
|
|
248
|
+
},
|
|
249
|
+
MAINTENANCE_MODE: {
|
|
250
|
+
code: "VPM-SYS-004",
|
|
251
|
+
message: "System is under maintenance",
|
|
252
|
+
httpStatus: 503
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/errors/factory.ts
|
|
258
|
+
function generateRequestId() {
|
|
259
|
+
return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
|
|
260
|
+
}
|
|
261
|
+
function createError(errorDef, details, requestId) {
|
|
262
|
+
return {
|
|
263
|
+
code: errorDef.code,
|
|
264
|
+
message: errorDef.message,
|
|
265
|
+
details,
|
|
266
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
267
|
+
requestId: requestId || generateRequestId(),
|
|
268
|
+
docUrl: `https://docs.vaspera.pm/errors/${errorDef.code}`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function createErrorResponse(errorDef, details, requestId) {
|
|
272
|
+
return {
|
|
273
|
+
response: {
|
|
274
|
+
success: false,
|
|
275
|
+
error: createError(errorDef, details, requestId)
|
|
276
|
+
},
|
|
277
|
+
status: errorDef.httpStatus
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function getNextResetDate() {
|
|
281
|
+
const now = /* @__PURE__ */ new Date();
|
|
282
|
+
const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
283
|
+
return nextMonth.toISOString();
|
|
284
|
+
}
|
|
285
|
+
var Errors = {
|
|
286
|
+
authRequired: (requestId) => createErrorResponse(ErrorCodes.AUTH.REQUIRED, void 0, requestId),
|
|
287
|
+
invalidApiKey: (requestId) => createErrorResponse(ErrorCodes.API_KEY.INVALID, void 0, requestId),
|
|
288
|
+
apiKeyRevoked: (requestId) => createErrorResponse(ErrorCodes.API_KEY.REVOKED, void 0, requestId),
|
|
289
|
+
apiKeyExpired: (requestId) => createErrorResponse(ErrorCodes.API_KEY.EXPIRED, void 0, requestId),
|
|
290
|
+
rateLimited: (retryAfter, requestId) => createErrorResponse(
|
|
291
|
+
ErrorCodes.API_KEY.RATE_LIMITED,
|
|
292
|
+
{ retryAfterSeconds: retryAfter },
|
|
293
|
+
requestId
|
|
294
|
+
),
|
|
295
|
+
quotaExceeded: (currentUsage, limit, requestId) => createErrorResponse(
|
|
296
|
+
ErrorCodes.API_KEY.QUOTA_EXCEEDED,
|
|
297
|
+
{ currentUsage, limit, resetDate: getNextResetDate() },
|
|
298
|
+
requestId
|
|
299
|
+
),
|
|
300
|
+
toolNotFound: (toolName, requestId) => createErrorResponse(
|
|
301
|
+
ErrorCodes.MCP.TOOL_NOT_FOUND,
|
|
302
|
+
{ tool: toolName },
|
|
303
|
+
requestId
|
|
304
|
+
),
|
|
305
|
+
toolExecutionFailed: (toolName, reason, requestId) => createErrorResponse(
|
|
306
|
+
ErrorCodes.MCP.EXECUTION_FAILED,
|
|
307
|
+
{ tool: toolName, reason },
|
|
308
|
+
requestId
|
|
309
|
+
),
|
|
310
|
+
toolTimeout: (toolName, timeoutMs, requestId) => createErrorResponse(
|
|
311
|
+
ErrorCodes.MCP.TIMEOUT,
|
|
312
|
+
{ tool: toolName, timeoutMs },
|
|
313
|
+
requestId
|
|
314
|
+
),
|
|
315
|
+
validationFailed: (field, reason, requestId) => createErrorResponse(
|
|
316
|
+
ErrorCodes.VALIDATION.REQUIRED_FIELD,
|
|
317
|
+
{ field, reason },
|
|
318
|
+
requestId
|
|
319
|
+
),
|
|
320
|
+
invalidFormat: (field, expected, requestId) => createErrorResponse(
|
|
321
|
+
ErrorCodes.VALIDATION.INVALID_FORMAT,
|
|
322
|
+
{ field, expected },
|
|
323
|
+
requestId
|
|
324
|
+
),
|
|
325
|
+
internalError: (message, requestId) => createErrorResponse(
|
|
326
|
+
ErrorCodes.SYSTEM.INTERNAL_ERROR,
|
|
327
|
+
message ? { message } : void 0,
|
|
328
|
+
requestId
|
|
329
|
+
),
|
|
330
|
+
databaseError: (operation, requestId) => createErrorResponse(
|
|
331
|
+
ErrorCodes.SYSTEM.DATABASE_ERROR,
|
|
332
|
+
{ operation },
|
|
333
|
+
requestId
|
|
334
|
+
),
|
|
335
|
+
integrationNotConnected: (provider, requestId) => createErrorResponse(
|
|
336
|
+
ErrorCodes.INTEGRATION.NOT_CONNECTED,
|
|
337
|
+
{ provider },
|
|
338
|
+
requestId
|
|
339
|
+
),
|
|
340
|
+
subscriptionRequired: (feature, requiredTier, requestId) => createErrorResponse(
|
|
341
|
+
ErrorCodes.BILLING.FEATURE_NOT_INCLUDED,
|
|
342
|
+
{ feature, requiredTier },
|
|
343
|
+
requestId
|
|
344
|
+
)
|
|
345
|
+
};
|
|
346
|
+
var VasperaApiError = class extends Error {
|
|
347
|
+
errorDef;
|
|
348
|
+
details;
|
|
349
|
+
constructor(errorDef, details) {
|
|
350
|
+
super(errorDef.message);
|
|
351
|
+
this.name = "VasperaApiError";
|
|
352
|
+
this.errorDef = errorDef;
|
|
353
|
+
this.details = details;
|
|
354
|
+
}
|
|
355
|
+
toResponse(requestId) {
|
|
356
|
+
return createErrorResponse(this.errorDef, this.details, requestId);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// src/utils/api-key.ts
|
|
361
|
+
var import_crypto = require("crypto");
|
|
362
|
+
var API_KEY_PREFIX_LIVE = "vpm_live_";
|
|
363
|
+
var API_KEY_PREFIX_TEST = "vpm_test_";
|
|
364
|
+
var API_KEY_RANDOM_BYTES = 24;
|
|
365
|
+
function generateApiKey(environment = "live") {
|
|
366
|
+
const prefix = environment === "live" ? API_KEY_PREFIX_LIVE : API_KEY_PREFIX_TEST;
|
|
367
|
+
const randomPart = (0, import_crypto.randomBytes)(API_KEY_RANDOM_BYTES).toString("base64url").slice(0, 32);
|
|
368
|
+
const key = `${prefix}${randomPart}`;
|
|
369
|
+
const keyPrefix = key.slice(0, 16);
|
|
370
|
+
const keyHash = hashApiKey(key);
|
|
371
|
+
return {
|
|
372
|
+
key,
|
|
373
|
+
keyPrefix,
|
|
374
|
+
keyHash
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function hashApiKey(key) {
|
|
378
|
+
return (0, import_crypto.createHash)("sha256").update(key).digest("hex");
|
|
379
|
+
}
|
|
380
|
+
function isValidApiKeyFormat(key) {
|
|
381
|
+
const livePattern = /^vpm_live_[a-zA-Z0-9_-]{32}$/;
|
|
382
|
+
const testPattern = /^vpm_test_[a-zA-Z0-9_-]{32}$/;
|
|
383
|
+
return livePattern.test(key) || testPattern.test(key);
|
|
384
|
+
}
|
|
385
|
+
function extractKeyPrefix(key) {
|
|
386
|
+
return key.slice(0, 16);
|
|
387
|
+
}
|
|
388
|
+
function isTestKey(key) {
|
|
389
|
+
return key.startsWith(API_KEY_PREFIX_TEST);
|
|
390
|
+
}
|
|
391
|
+
function maskApiKey(key) {
|
|
392
|
+
if (key.length < 24) return key;
|
|
393
|
+
return `${key.slice(0, 16)}...${key.slice(-4)}`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/utils/encryption.ts
|
|
397
|
+
var import_crypto2 = require("crypto");
|
|
398
|
+
var import_util = require("util");
|
|
399
|
+
var scryptAsync = (0, import_util.promisify)(import_crypto2.scrypt);
|
|
400
|
+
var ALGORITHM = "aes-256-gcm";
|
|
401
|
+
var IV_LENGTH = 16;
|
|
402
|
+
var SALT_LENGTH = 32;
|
|
403
|
+
var KEY_LENGTH = 32;
|
|
404
|
+
async function encrypt(text, secret) {
|
|
405
|
+
const salt = (0, import_crypto2.randomBytes)(SALT_LENGTH);
|
|
406
|
+
const iv = (0, import_crypto2.randomBytes)(IV_LENGTH);
|
|
407
|
+
const key = await scryptAsync(secret, salt, KEY_LENGTH);
|
|
408
|
+
const cipher = (0, import_crypto2.createCipheriv)(ALGORITHM, key, iv);
|
|
409
|
+
const encrypted = Buffer.concat([
|
|
410
|
+
cipher.update(text, "utf8"),
|
|
411
|
+
cipher.final()
|
|
412
|
+
]);
|
|
413
|
+
const tag = cipher.getAuthTag();
|
|
414
|
+
return [
|
|
415
|
+
salt.toString("base64"),
|
|
416
|
+
iv.toString("base64"),
|
|
417
|
+
tag.toString("base64"),
|
|
418
|
+
encrypted.toString("base64")
|
|
419
|
+
].join(":");
|
|
420
|
+
}
|
|
421
|
+
async function decrypt(encryptedText, secret) {
|
|
422
|
+
const parts = encryptedText.split(":");
|
|
423
|
+
if (parts.length !== 4) {
|
|
424
|
+
throw new Error("Invalid encrypted text format");
|
|
425
|
+
}
|
|
426
|
+
const [saltB64, ivB64, tagB64, encryptedB64] = parts;
|
|
427
|
+
const salt = Buffer.from(saltB64, "base64");
|
|
428
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
429
|
+
const tag = Buffer.from(tagB64, "base64");
|
|
430
|
+
const encrypted = Buffer.from(encryptedB64, "base64");
|
|
431
|
+
const key = await scryptAsync(secret, salt, KEY_LENGTH);
|
|
432
|
+
const decipher = (0, import_crypto2.createDecipheriv)(ALGORITHM, key, iv);
|
|
433
|
+
decipher.setAuthTag(tag);
|
|
434
|
+
return decipher.update(encrypted) + decipher.final("utf8");
|
|
435
|
+
}
|
|
436
|
+
function generateEncryptionSecret() {
|
|
437
|
+
return (0, import_crypto2.randomBytes)(32).toString("hex");
|
|
438
|
+
}
|
|
439
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
440
|
+
0 && (module.exports = {
|
|
441
|
+
ErrorCodes,
|
|
442
|
+
Errors,
|
|
443
|
+
QUOTA_LIMITS,
|
|
444
|
+
RATE_LIMITS,
|
|
445
|
+
VasperaApiError,
|
|
446
|
+
createError,
|
|
447
|
+
createErrorResponse,
|
|
448
|
+
decrypt,
|
|
449
|
+
encrypt,
|
|
450
|
+
extractKeyPrefix,
|
|
451
|
+
generateApiKey,
|
|
452
|
+
generateEncryptionSecret,
|
|
453
|
+
hashApiKey,
|
|
454
|
+
isTestKey,
|
|
455
|
+
isValidApiKeyFormat,
|
|
456
|
+
maskApiKey
|
|
457
|
+
});
|
|
458
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types/index.ts","../src/errors/codes.ts","../src/errors/factory.ts","../src/utils/api-key.ts","../src/utils/encryption.ts"],"sourcesContent":["// Types\nexport * from './types/index.js';\n\n// Errors\nexport * from './errors/index.js';\n\n// Utilities\nexport * from './utils/index.js';\n","// Subscription Types\nexport type SubscriptionTier = 'free' | 'starter' | 'pro' | 'enterprise';\nexport type SubscriptionStatus = 'active' | 'canceled' | 'past_due' | 'trialing' | 'incomplete';\n\n// User Profile\nexport interface UserProfile {\n id: string;\n email: string;\n fullName: string | null;\n avatarUrl: string | null;\n subscriptionTier: SubscriptionTier;\n subscriptionStatus: SubscriptionStatus;\n stripeCustomerId: string | null;\n stripeSubscriptionId: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n// API Key\nexport interface ApiKey {\n id: string;\n userId: string;\n name: string;\n keyPrefix: string;\n lastUsedAt: string | null;\n expiresAt: string | null;\n revokedAt: string | null;\n createdAt: string;\n}\n\nexport interface ApiKeyWithSecret extends Omit<ApiKey, 'revokedAt'> {\n key: string; // Full key (only shown once)\n}\n\n// Usage\nexport interface UsageEvent {\n id: string;\n userId: string;\n apiKeyId: string | null;\n toolName: string;\n tokensUsed: number;\n latencyMs: number | null;\n success: boolean;\n errorCode: string | null;\n requestId: string | null;\n metadata: Record<string, unknown>;\n createdAt: string;\n}\n\nexport interface UsageSummary {\n period: {\n start: string;\n end: string;\n };\n summary: {\n totalToolCalls: number;\n totalTokensUsed: number;\n quotaUsed: number;\n quotaLimit: number;\n quotaPercentage: number;\n };\n byTool: Array<{\n tool: string;\n calls: number;\n tokensUsed: number;\n avgLatencyMs: number;\n }>;\n}\n\n// Quota Limits by Tier\nexport const QUOTA_LIMITS: Record<SubscriptionTier, number> = {\n free: 5,\n starter: 100,\n pro: 500,\n enterprise: 999999, // Effectively unlimited\n};\n\nexport const RATE_LIMITS: Record<SubscriptionTier, { perMinute: number; perDay: number }> = {\n free: { perMinute: 10, perDay: 100 },\n starter: { perMinute: 30, perDay: 1000 },\n pro: { perMinute: 60, perDay: 5000 },\n enterprise: { perMinute: 120, perDay: 999999 },\n};\n\n// Integration Types\nexport type IntegrationProvider = 'jira' | 'linear' | 'github' | 'gitlab' | 'asana';\n\nexport interface IntegrationToken {\n id: string;\n userId: string;\n provider: IntegrationProvider;\n tokenType: string;\n expiresAt: string | null;\n scopes: string[] | null;\n providerUserId: string | null;\n providerEmail: string | null;\n metadata: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface IntegrationStatus {\n provider: IntegrationProvider;\n connected: boolean;\n connectedAt?: string;\n providerEmail?: string;\n scopes?: string[];\n metadata?: Record<string, unknown>;\n}\n\n// MCP Tool Types\nexport type McpToolName =\n | 'synthesize_requirements'\n | 'review_prd'\n | 'explode_backlog'\n | 'generate_architecture'\n | 'sync_to_tracker'\n | 'infer_prd_from_code'\n | 'reverse_engineer_user_flows'\n | 'generate_test_specs'\n | 'explain_codebase'\n | 'validate_implementation'\n | 'suggest_refactors'\n | 'generate_api_docs'\n | 'dependency_audit'\n | 'estimate_migration';\n\nexport interface McpToolResult<T = unknown> {\n content: Array<{\n type: 'text' | 'image' | 'resource';\n text?: string;\n data?: string;\n mimeType?: string;\n }>;\n data?: T;\n tokensUsed?: number;\n isError?: boolean;\n}\n\n// API Response Types\nexport interface ApiSuccessResponse<T> {\n success: true;\n data: T;\n meta?: {\n requestId: string;\n timestamp: string;\n };\n}\n\nexport interface ApiErrorResponse {\n success: false;\n error: {\n code: string;\n message: string;\n details?: Record<string, unknown>;\n docUrl?: string;\n };\n meta?: {\n requestId: string;\n timestamp: string;\n };\n}\n\nexport type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;\n","export interface ErrorDefinition {\n code: string;\n message: string;\n httpStatus: number;\n}\n\nexport const ErrorCodes = {\n // Authentication Errors (VPM-AUTH-XXX)\n AUTH: {\n REQUIRED: {\n code: 'VPM-AUTH-001',\n message: 'Authentication required',\n httpStatus: 401,\n },\n INVALID_TOKEN: {\n code: 'VPM-AUTH-002',\n message: 'Invalid or expired authentication token',\n httpStatus: 401,\n },\n INSUFFICIENT_PERMISSIONS: {\n code: 'VPM-AUTH-003',\n message: 'Insufficient permissions for this action',\n httpStatus: 403,\n },\n SESSION_EXPIRED: {\n code: 'VPM-AUTH-004',\n message: 'Session has expired, please login again',\n httpStatus: 401,\n },\n MFA_REQUIRED: {\n code: 'VPM-AUTH-005',\n message: 'Multi-factor authentication required',\n httpStatus: 403,\n },\n },\n\n // API Key Errors (VPM-API-KEY-XXX)\n API_KEY: {\n REQUIRED: {\n code: 'VPM-API-KEY-001',\n message: 'API key is required',\n httpStatus: 401,\n },\n INVALID: {\n code: 'VPM-API-KEY-002',\n message: 'Invalid API key',\n httpStatus: 401,\n },\n REVOKED: {\n code: 'VPM-API-KEY-003',\n message: 'API key has been revoked',\n httpStatus: 401,\n },\n EXPIRED: {\n code: 'VPM-API-KEY-004',\n message: 'API key has expired',\n httpStatus: 401,\n },\n RATE_LIMITED: {\n code: 'VPM-API-KEY-005',\n message: 'API key rate limit exceeded',\n httpStatus: 429,\n },\n QUOTA_EXCEEDED: {\n code: 'VPM-API-KEY-006',\n message: 'Monthly usage quota exceeded',\n httpStatus: 429,\n },\n },\n\n // MCP Tool Errors (VPM-MCP-XXX)\n MCP: {\n TOOL_NOT_FOUND: {\n code: 'VPM-MCP-001',\n message: 'Requested tool not found',\n httpStatus: 404,\n },\n INVALID_ARGUMENTS: {\n code: 'VPM-MCP-002',\n message: 'Invalid tool arguments provided',\n httpStatus: 400,\n },\n EXECUTION_FAILED: {\n code: 'VPM-MCP-003',\n message: 'Tool execution failed',\n httpStatus: 500,\n },\n TIMEOUT: {\n code: 'VPM-MCP-004',\n message: 'Tool execution timed out',\n httpStatus: 504,\n },\n LLM_ERROR: {\n code: 'VPM-MCP-005',\n message: 'AI model returned an error',\n httpStatus: 502,\n },\n CONTEXT_TOO_LARGE: {\n code: 'VPM-MCP-006',\n message: 'Input context exceeds maximum size',\n httpStatus: 413,\n },\n },\n\n // Billing Errors (VPM-BILLING-XXX)\n BILLING: {\n NO_SUBSCRIPTION: {\n code: 'VPM-BILLING-001',\n message: 'No active subscription found',\n httpStatus: 402,\n },\n SUBSCRIPTION_EXPIRED: {\n code: 'VPM-BILLING-002',\n message: 'Subscription has expired',\n httpStatus: 402,\n },\n PAYMENT_FAILED: {\n code: 'VPM-BILLING-003',\n message: 'Payment processing failed',\n httpStatus: 402,\n },\n FEATURE_NOT_INCLUDED: {\n code: 'VPM-BILLING-004',\n message: 'Feature not included in current plan',\n httpStatus: 403,\n },\n UPGRADE_REQUIRED: {\n code: 'VPM-BILLING-005',\n message: 'Plan upgrade required for this action',\n httpStatus: 403,\n },\n },\n\n // Integration Errors (VPM-INT-XXX)\n INTEGRATION: {\n NOT_CONNECTED: {\n code: 'VPM-INT-001',\n message: 'Integration not connected',\n httpStatus: 400,\n },\n TOKEN_EXPIRED: {\n code: 'VPM-INT-002',\n message: 'Integration token expired, reconnection required',\n httpStatus: 401,\n },\n REFRESH_FAILED: {\n code: 'VPM-INT-003',\n message: 'Failed to refresh integration token',\n httpStatus: 502,\n },\n API_ERROR: {\n code: 'VPM-INT-004',\n message: 'External integration API error',\n httpStatus: 502,\n },\n RATE_LIMITED: {\n code: 'VPM-INT-005',\n message: 'Integration rate limit exceeded',\n httpStatus: 429,\n },\n },\n\n // Validation Errors (VPM-VAL-XXX)\n VALIDATION: {\n REQUIRED_FIELD: {\n code: 'VPM-VAL-001',\n message: 'Required field missing',\n httpStatus: 400,\n },\n INVALID_FORMAT: {\n code: 'VPM-VAL-002',\n message: 'Invalid field format',\n httpStatus: 400,\n },\n OUT_OF_RANGE: {\n code: 'VPM-VAL-003',\n message: 'Value out of allowed range',\n httpStatus: 400,\n },\n INVALID_JSON: {\n code: 'VPM-VAL-004',\n message: 'Invalid JSON in request body',\n httpStatus: 400,\n },\n },\n\n // System Errors (VPM-SYS-XXX)\n SYSTEM: {\n INTERNAL_ERROR: {\n code: 'VPM-SYS-001',\n message: 'Internal server error',\n httpStatus: 500,\n },\n DATABASE_ERROR: {\n code: 'VPM-SYS-002',\n message: 'Database operation failed',\n httpStatus: 500,\n },\n SERVICE_UNAVAILABLE: {\n code: 'VPM-SYS-003',\n message: 'Service temporarily unavailable',\n httpStatus: 503,\n },\n MAINTENANCE_MODE: {\n code: 'VPM-SYS-004',\n message: 'System is under maintenance',\n httpStatus: 503,\n },\n },\n} as const;\n\nexport type ErrorCodeType =\n (typeof ErrorCodes)[keyof typeof ErrorCodes][keyof (typeof ErrorCodes)[keyof typeof ErrorCodes]];\n","import type { ErrorDefinition } from './codes.js';\nimport { ErrorCodes } from './codes.js';\n\nexport interface VasperaError {\n code: string;\n message: string;\n details?: Record<string, unknown>;\n timestamp: string;\n requestId: string;\n docUrl?: string;\n}\n\nexport interface ErrorResponse {\n success: false;\n error: VasperaError;\n}\n\nfunction generateRequestId(): string {\n return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;\n}\n\nexport function createError(\n errorDef: ErrorDefinition,\n details?: Record<string, unknown>,\n requestId?: string\n): VasperaError {\n return {\n code: errorDef.code,\n message: errorDef.message,\n details,\n timestamp: new Date().toISOString(),\n requestId: requestId || generateRequestId(),\n docUrl: `https://docs.vaspera.pm/errors/${errorDef.code}`,\n };\n}\n\nexport function createErrorResponse(\n errorDef: ErrorDefinition,\n details?: Record<string, unknown>,\n requestId?: string\n): { response: ErrorResponse; status: number } {\n return {\n response: {\n success: false,\n error: createError(errorDef, details, requestId),\n },\n status: errorDef.httpStatus,\n };\n}\n\nfunction getNextResetDate(): string {\n const now = new Date();\n const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);\n return nextMonth.toISOString();\n}\n\n// Pre-built error creators for common cases\nexport const Errors = {\n authRequired: (requestId?: string) =>\n createErrorResponse(ErrorCodes.AUTH.REQUIRED, undefined, requestId),\n\n invalidApiKey: (requestId?: string) =>\n createErrorResponse(ErrorCodes.API_KEY.INVALID, undefined, requestId),\n\n apiKeyRevoked: (requestId?: string) =>\n createErrorResponse(ErrorCodes.API_KEY.REVOKED, undefined, requestId),\n\n apiKeyExpired: (requestId?: string) =>\n createErrorResponse(ErrorCodes.API_KEY.EXPIRED, undefined, requestId),\n\n rateLimited: (retryAfter: number, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.API_KEY.RATE_LIMITED,\n { retryAfterSeconds: retryAfter },\n requestId\n ),\n\n quotaExceeded: (currentUsage: number, limit: number, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.API_KEY.QUOTA_EXCEEDED,\n { currentUsage, limit, resetDate: getNextResetDate() },\n requestId\n ),\n\n toolNotFound: (toolName: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.MCP.TOOL_NOT_FOUND,\n { tool: toolName },\n requestId\n ),\n\n toolExecutionFailed: (toolName: string, reason: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.MCP.EXECUTION_FAILED,\n { tool: toolName, reason },\n requestId\n ),\n\n toolTimeout: (toolName: string, timeoutMs: number, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.MCP.TIMEOUT,\n { tool: toolName, timeoutMs },\n requestId\n ),\n\n validationFailed: (field: string, reason: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.VALIDATION.REQUIRED_FIELD,\n { field, reason },\n requestId\n ),\n\n invalidFormat: (field: string, expected: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.VALIDATION.INVALID_FORMAT,\n { field, expected },\n requestId\n ),\n\n internalError: (message?: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.SYSTEM.INTERNAL_ERROR,\n message ? { message } : undefined,\n requestId\n ),\n\n databaseError: (operation: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.SYSTEM.DATABASE_ERROR,\n { operation },\n requestId\n ),\n\n integrationNotConnected: (provider: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.INTEGRATION.NOT_CONNECTED,\n { provider },\n requestId\n ),\n\n subscriptionRequired: (feature: string, requiredTier: string, requestId?: string) =>\n createErrorResponse(\n ErrorCodes.BILLING.FEATURE_NOT_INCLUDED,\n { feature, requiredTier },\n requestId\n ),\n};\n\n// Custom error class for throwing in API routes\nexport class VasperaApiError extends Error {\n public readonly errorDef: ErrorDefinition;\n public readonly details?: Record<string, unknown>;\n\n constructor(errorDef: ErrorDefinition, details?: Record<string, unknown>) {\n super(errorDef.message);\n this.name = 'VasperaApiError';\n this.errorDef = errorDef;\n this.details = details;\n }\n\n toResponse(requestId?: string): { response: ErrorResponse; status: number } {\n return createErrorResponse(this.errorDef, this.details, requestId);\n }\n}\n","import { randomBytes, createHash } from 'crypto';\n\nconst API_KEY_PREFIX_LIVE = 'vpm_live_';\nconst API_KEY_PREFIX_TEST = 'vpm_test_';\nconst API_KEY_RANDOM_BYTES = 24; // 32 chars in base64url\n\nexport interface GeneratedApiKey {\n key: string; // Full key to show user once\n keyPrefix: string; // First 16 chars for identification\n keyHash: string; // Hash for storage and validation\n}\n\n/**\n * Generate a new API key\n * @param environment - 'live' or 'test'\n * @returns Generated key with prefix and hash\n */\nexport function generateApiKey(environment: 'live' | 'test' = 'live'): GeneratedApiKey {\n const prefix = environment === 'live' ? API_KEY_PREFIX_LIVE : API_KEY_PREFIX_TEST;\n const randomPart = randomBytes(API_KEY_RANDOM_BYTES)\n .toString('base64url')\n .slice(0, 32);\n\n const key = `${prefix}${randomPart}`;\n const keyPrefix = key.slice(0, 16);\n const keyHash = hashApiKey(key);\n\n return {\n key,\n keyPrefix,\n keyHash,\n };\n}\n\n/**\n * Hash an API key for storage\n * Using SHA-256 since we need to look up by hash\n */\nexport function hashApiKey(key: string): string {\n return createHash('sha256').update(key).digest('hex');\n}\n\n/**\n * Validate API key format\n */\nexport function isValidApiKeyFormat(key: string): boolean {\n const livePattern = /^vpm_live_[a-zA-Z0-9_-]{32}$/;\n const testPattern = /^vpm_test_[a-zA-Z0-9_-]{32}$/;\n return livePattern.test(key) || testPattern.test(key);\n}\n\n/**\n * Extract prefix from API key\n */\nexport function extractKeyPrefix(key: string): string {\n return key.slice(0, 16);\n}\n\n/**\n * Check if key is a test key\n */\nexport function isTestKey(key: string): boolean {\n return key.startsWith(API_KEY_PREFIX_TEST);\n}\n\n/**\n * Mask an API key for display (show first 16 and last 4 chars)\n */\nexport function maskApiKey(key: string): string {\n if (key.length < 24) return key;\n return `${key.slice(0, 16)}...${key.slice(-4)}`;\n}\n","import {\n createCipheriv,\n createDecipheriv,\n randomBytes,\n scrypt,\n} from 'crypto';\nimport { promisify } from 'util';\n\nconst scryptAsync = promisify(scrypt);\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 16;\nconst SALT_LENGTH = 32;\nconst TAG_LENGTH = 16;\nconst KEY_LENGTH = 32;\n\n/**\n * Encrypt text using AES-256-GCM\n * @param text - Plain text to encrypt\n * @param secret - Encryption secret\n * @returns Encrypted string in format: salt:iv:tag:ciphertext (all base64)\n */\nexport async function encrypt(text: string, secret: string): Promise<string> {\n const salt = randomBytes(SALT_LENGTH);\n const iv = randomBytes(IV_LENGTH);\n const key = (await scryptAsync(secret, salt, KEY_LENGTH)) as Buffer;\n\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([\n cipher.update(text, 'utf8'),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n\n // Format: salt:iv:tag:encrypted (all base64)\n return [\n salt.toString('base64'),\n iv.toString('base64'),\n tag.toString('base64'),\n encrypted.toString('base64'),\n ].join(':');\n}\n\n/**\n * Decrypt text that was encrypted with encrypt()\n * @param encryptedText - Encrypted string in format: salt:iv:tag:ciphertext\n * @param secret - Encryption secret (must match encryption)\n * @returns Decrypted plain text\n */\nexport async function decrypt(\n encryptedText: string,\n secret: string\n): Promise<string> {\n const parts = encryptedText.split(':');\n if (parts.length !== 4) {\n throw new Error('Invalid encrypted text format');\n }\n\n const [saltB64, ivB64, tagB64, encryptedB64] = parts;\n\n const salt = Buffer.from(saltB64!, 'base64');\n const iv = Buffer.from(ivB64!, 'base64');\n const tag = Buffer.from(tagB64!, 'base64');\n const encrypted = Buffer.from(encryptedB64!, 'base64');\n\n const key = (await scryptAsync(secret, salt, KEY_LENGTH)) as Buffer;\n\n const decipher = createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(tag);\n\n return decipher.update(encrypted) + decipher.final('utf8');\n}\n\n/**\n * Generate a random encryption secret\n * @returns 32-byte hex string suitable for use as encryption secret\n */\nexport function generateEncryptionSecret(): string {\n return randomBytes(32).toString('hex');\n}\n"],"mappings":"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","names":["import_crypto"]}
|