@constructive-io/graphql-server 3.1.1 → 4.0.1
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/errors/404-message.js +1 -1
- package/errors/api-errors.d.ts +200 -0
- package/errors/api-errors.js +276 -0
- package/esm/errors/404-message.js +1 -1
- package/esm/errors/api-errors.js +261 -0
- package/esm/index.js +2 -0
- package/esm/middleware/api.js +355 -277
- package/esm/middleware/auth.js +25 -7
- package/esm/middleware/error-handler.js +86 -0
- package/esm/middleware/favicon.js +12 -0
- package/esm/middleware/graphile.js +149 -64
- package/esm/options.js +232 -0
- package/esm/schema.js +24 -11
- package/esm/server.js +41 -5
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/middleware/api.d.ts +3 -15
- package/middleware/api.js +359 -283
- package/middleware/auth.js +25 -7
- package/middleware/error-handler.d.ts +4 -0
- package/middleware/error-handler.js +94 -0
- package/middleware/favicon.d.ts +2 -0
- package/middleware/favicon.js +16 -0
- package/middleware/graphile.d.ts +14 -0
- package/middleware/graphile.js +149 -64
- package/options.d.ts +131 -0
- package/options.js +244 -0
- package/package.json +23 -24
- package/schema.d.ts +2 -2
- package/schema.js +23 -10
- package/server.d.ts +24 -2
- package/server.js +39 -3
- package/codegen/orm/client.d.ts +0 -55
- package/codegen/orm/client.js +0 -75
- package/codegen/orm/index.d.ts +0 -36
- package/codegen/orm/index.js +0 -59
- package/codegen/orm/input-types.d.ts +0 -20140
- package/codegen/orm/input-types.js +0 -2
- package/codegen/orm/models/api.d.ts +0 -42
- package/codegen/orm/models/api.js +0 -76
- package/codegen/orm/models/domain.d.ts +0 -42
- package/codegen/orm/models/domain.js +0 -76
- package/codegen/orm/models/index.d.ts +0 -7
- package/codegen/orm/models/index.js +0 -12
- package/codegen/orm/mutation/index.d.ts +0 -7
- package/codegen/orm/mutation/index.js +0 -7
- package/codegen/orm/query/index.d.ts +0 -20
- package/codegen/orm/query/index.js +0 -24
- package/codegen/orm/query-builder.d.ts +0 -81
- package/codegen/orm/query-builder.js +0 -496
- package/codegen/orm/select-types.d.ts +0 -83
- package/codegen/orm/select-types.js +0 -7
- package/codegen/orm/types.d.ts +0 -6
- package/codegen/orm/types.js +0 -23
- package/esm/codegen/orm/client.js +0 -70
- package/esm/codegen/orm/index.js +0 -39
- package/esm/codegen/orm/input-types.js +0 -1
- package/esm/codegen/orm/models/api.js +0 -72
- package/esm/codegen/orm/models/domain.js +0 -72
- package/esm/codegen/orm/models/index.js +0 -7
- package/esm/codegen/orm/mutation/index.js +0 -4
- package/esm/codegen/orm/query/index.js +0 -21
- package/esm/codegen/orm/query-builder.js +0 -452
- package/esm/codegen/orm/select-types.js +0 -6
- package/esm/codegen/orm/types.js +0 -7
- package/esm/middleware/gql.js +0 -116
- package/esm/plugins/PublicKeySignature.js +0 -114
- package/esm/scripts/codegen-schema.js +0 -71
- package/esm/scripts/create-bucket.js +0 -40
- package/middleware/gql.d.ts +0 -164
- package/middleware/gql.js +0 -121
- package/plugins/PublicKeySignature.d.ts +0 -11
- package/plugins/PublicKeySignature.js +0 -121
- package/scripts/codegen-schema.d.ts +0 -1
- package/scripts/codegen-schema.js +0 -76
- package/scripts/create-bucket.d.ts +0 -1
- package/scripts/create-bucket.js +0 -42
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed API Error System
|
|
3
|
+
*
|
|
4
|
+
* Provides strongly-typed error hierarchy for the GraphQL server with:
|
|
5
|
+
* - Consistent error classification
|
|
6
|
+
* - HTTP status mapping
|
|
7
|
+
* - Serialization for API responses and logging
|
|
8
|
+
*
|
|
9
|
+
* @module errors/api-errors
|
|
10
|
+
*/
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Error Codes Constant
|
|
13
|
+
// =============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Centralized error code definitions for external use and type safety.
|
|
16
|
+
* Use these constants instead of string literals for error code comparisons.
|
|
17
|
+
*/
|
|
18
|
+
export const ErrorCodes = {
|
|
19
|
+
DOMAIN_NOT_FOUND: 'DOMAIN_NOT_FOUND',
|
|
20
|
+
API_NOT_FOUND: 'API_NOT_FOUND',
|
|
21
|
+
NO_VALID_SCHEMAS: 'NO_VALID_SCHEMAS',
|
|
22
|
+
SCHEMA_INVALID: 'SCHEMA_INVALID',
|
|
23
|
+
SCHEMA_ACCESS_DENIED: 'SCHEMA_ACCESS_DENIED',
|
|
24
|
+
HANDLER_ERROR: 'HANDLER_ERROR',
|
|
25
|
+
DATABASE_CONNECTION_ERROR: 'DATABASE_CONNECTION_ERROR',
|
|
26
|
+
AMBIGUOUS_TENANT: 'AMBIGUOUS_TENANT',
|
|
27
|
+
ADMIN_AUTH_REQUIRED: 'ADMIN_AUTH_REQUIRED',
|
|
28
|
+
};
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Base Error Class
|
|
31
|
+
// =============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* Base class for all API errors.
|
|
34
|
+
*
|
|
35
|
+
* Provides consistent structure for error handling including:
|
|
36
|
+
* - Machine-readable error codes
|
|
37
|
+
* - HTTP status code mapping
|
|
38
|
+
* - Optional debugging context
|
|
39
|
+
* - JSON serialization for API responses
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* throw new ApiError('CUSTOM_ERROR', 400, 'Something went wrong', { detail: 'info' });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export class ApiError extends Error {
|
|
47
|
+
code;
|
|
48
|
+
statusCode;
|
|
49
|
+
context;
|
|
50
|
+
constructor(code, statusCode, message, context) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.code = code;
|
|
53
|
+
this.statusCode = statusCode;
|
|
54
|
+
this.context = context;
|
|
55
|
+
this.name = 'ApiError';
|
|
56
|
+
// Ensure instanceof checks work correctly with ES5 transpilation
|
|
57
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
58
|
+
// Capture stack trace for V8 engines (Node.js)
|
|
59
|
+
// Points to error origin rather than base class constructor
|
|
60
|
+
if (Error.captureStackTrace) {
|
|
61
|
+
Error.captureStackTrace(this, this.constructor);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Serializes the error for API responses and structured logging.
|
|
66
|
+
*
|
|
67
|
+
* @returns Object with error details suitable for JSON serialization
|
|
68
|
+
*/
|
|
69
|
+
toJSON() {
|
|
70
|
+
return {
|
|
71
|
+
name: this.name,
|
|
72
|
+
code: this.code,
|
|
73
|
+
message: this.message,
|
|
74
|
+
statusCode: this.statusCode,
|
|
75
|
+
...(this.context && { context: this.context }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// Error Subclasses
|
|
81
|
+
// =============================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Thrown when a domain lookup fails to find a matching API.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* throw new DomainNotFoundError('example.com', 'api');
|
|
88
|
+
* // Results in: "No API configured for domain: api.example.com"
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export class DomainNotFoundError extends ApiError {
|
|
92
|
+
constructor(domain, subdomain) {
|
|
93
|
+
const fullDomain = subdomain ? `${subdomain}.${domain}` : domain;
|
|
94
|
+
super(ErrorCodes.DOMAIN_NOT_FOUND, 404, `No API configured for domain: ${fullDomain}`, { domain, subdomain, fullDomain });
|
|
95
|
+
this.name = 'DomainNotFoundError';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Thrown when a specific API cannot be found by its ID.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* throw new ApiNotFoundError('api-123');
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export class ApiNotFoundError extends ApiError {
|
|
107
|
+
constructor(apiId) {
|
|
108
|
+
super(ErrorCodes.API_NOT_FOUND, 404, `API not found: ${apiId}`, { apiId });
|
|
109
|
+
this.name = 'ApiNotFoundError';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Thrown when no valid schemas are found for an API.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* throw new NoValidSchemasError('api-123');
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export class NoValidSchemasError extends ApiError {
|
|
121
|
+
constructor(apiId) {
|
|
122
|
+
super(ErrorCodes.NO_VALID_SCHEMAS, 404, `No valid schemas found for API: ${apiId}`, { apiId });
|
|
123
|
+
this.name = 'NoValidSchemasError';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Thrown when schema validation fails.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* throw new SchemaValidationError('Invalid schema structure', { field: 'name' });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export class SchemaValidationError extends ApiError {
|
|
135
|
+
constructor(message, context) {
|
|
136
|
+
super(ErrorCodes.SCHEMA_INVALID, 400, message, context);
|
|
137
|
+
this.name = 'SchemaValidationError';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Thrown when a request handler cannot be created.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* throw new HandlerCreationError('Failed to create PostGraphile handler', { reason: 'timeout' });
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export class HandlerCreationError extends ApiError {
|
|
149
|
+
constructor(message, context) {
|
|
150
|
+
super(ErrorCodes.HANDLER_ERROR, 500, message, context);
|
|
151
|
+
this.name = 'HandlerCreationError';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Thrown when the database connection fails.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* throw new DatabaseConnectionError('Connection timeout', { host: 'db.example.com' });
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export class DatabaseConnectionError extends ApiError {
|
|
163
|
+
constructor(message, context) {
|
|
164
|
+
super(ErrorCodes.DATABASE_CONNECTION_ERROR, 503, message, context);
|
|
165
|
+
this.name = 'DatabaseConnectionError';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Thrown when a tenant attempts to access schemas they do not own.
|
|
170
|
+
* Returns 403 Forbidden to indicate the schemas exist but access is denied.
|
|
171
|
+
*
|
|
172
|
+
* Security Note: This error intentionally does not reveal which specific
|
|
173
|
+
* schemas exist to prevent information disclosure.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* throw new SchemaAccessDeniedError(['schema1', 'schema2'], 'db-123');
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export class SchemaAccessDeniedError extends ApiError {
|
|
181
|
+
constructor(schemas, databaseId) {
|
|
182
|
+
super(ErrorCodes.SCHEMA_ACCESS_DENIED, 403, `Access denied: requested schemas are not associated with tenant`, { schemas, databaseId });
|
|
183
|
+
this.name = 'SchemaAccessDeniedError';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Thrown when domain resolution is ambiguous (multiple APIs match).
|
|
188
|
+
* This is a security concern as it indicates potential misconfiguration
|
|
189
|
+
* that could lead to unpredictable tenant routing.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* throw new AmbiguousTenantError('example.com', 'api', 2);
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export class AmbiguousTenantError extends ApiError {
|
|
197
|
+
constructor(domain, subdomain, matchCount) {
|
|
198
|
+
const fullDomain = subdomain ? `${subdomain}.${domain}` : domain;
|
|
199
|
+
super(ErrorCodes.AMBIGUOUS_TENANT, 500, `Ambiguous tenant resolution: multiple APIs (${matchCount}) match domain ${fullDomain}`, { domain, subdomain, fullDomain, matchCount });
|
|
200
|
+
this.name = 'AmbiguousTenantError';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Thrown when admin authentication is required but not provided or invalid.
|
|
205
|
+
* Used for private API endpoints that require explicit admin credentials.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* throw new AdminAuthRequiredError('Missing authorization header');
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
export class AdminAuthRequiredError extends ApiError {
|
|
213
|
+
constructor(reason) {
|
|
214
|
+
super(ErrorCodes.ADMIN_AUTH_REQUIRED, 401, `Admin authentication required: ${reason}`, { reason });
|
|
215
|
+
this.name = 'AdminAuthRequiredError';
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// =============================================================================
|
|
219
|
+
// Type Guards
|
|
220
|
+
// =============================================================================
|
|
221
|
+
/**
|
|
222
|
+
* Check if an error is an instance of ApiError or any of its subclasses.
|
|
223
|
+
*
|
|
224
|
+
* @param error - The value to check
|
|
225
|
+
* @returns True if error is an ApiError instance
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```typescript
|
|
229
|
+
* try {
|
|
230
|
+
* await resolveApi(req);
|
|
231
|
+
* } catch (error) {
|
|
232
|
+
* if (isApiError(error)) {
|
|
233
|
+
* // TypeScript knows error has code, statusCode, context
|
|
234
|
+
* console.log(error.code);
|
|
235
|
+
* res.status(error.statusCode).json(error.toJSON());
|
|
236
|
+
* }
|
|
237
|
+
* }
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export function isApiError(error) {
|
|
241
|
+
return error instanceof ApiError;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Check if an error has a specific error code.
|
|
245
|
+
* Returns false for non-ApiError values.
|
|
246
|
+
*
|
|
247
|
+
* @param error - The value to check
|
|
248
|
+
* @param code - The error code to match against
|
|
249
|
+
* @returns True if error is an ApiError with the specified code
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```typescript
|
|
253
|
+
* if (hasErrorCode(error, ErrorCodes.DOMAIN_NOT_FOUND)) {
|
|
254
|
+
* // Handle domain not found specifically
|
|
255
|
+
* logDomainMisconfiguration(error.context?.fullDomain);
|
|
256
|
+
* }
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
export function hasErrorCode(error, code) {
|
|
260
|
+
return isApiError(error) && error.code === code;
|
|
261
|
+
}
|
package/esm/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export * from './server';
|
|
2
2
|
export * from './schema';
|
|
3
|
+
// Export options module - types, defaults, type guards, and utility functions
|
|
4
|
+
export * from './options';
|
|
3
5
|
// Export middleware for use in testing packages
|
|
4
6
|
export { createApiMiddleware, getSubdomain, getApiConfig } from './middleware/api';
|
|
5
7
|
export { createAuthenticateMiddleware } from './middleware/auth';
|