@facetlayer/prism-framework 0.4.0 → 0.4.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/README.md +176 -8
- package/dist/Errors.d.ts +38 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Metrics.d.ts +5 -0
- package/dist/Metrics.d.ts.map +1 -0
- package/dist/RequestContext.d.ts +17 -0
- package/dist/RequestContext.d.ts.map +1 -0
- package/dist/ServiceDefinition.d.ts +16 -0
- package/dist/ServiceDefinition.d.ts.map +1 -0
- package/dist/app/PrismApp.d.ts +31 -0
- package/dist/app/PrismApp.d.ts.map +1 -0
- package/dist/app/callEndpoint.d.ts +13 -0
- package/dist/app/callEndpoint.d.ts.map +1 -0
- package/dist/app/validateApp.d.ts +20 -0
- package/dist/app/validateApp.d.ts.map +1 -0
- package/dist/authorization/AuthSource.d.ts +8 -0
- package/dist/authorization/AuthSource.d.ts.map +1 -0
- package/dist/authorization/Authorization.d.ts +24 -0
- package/dist/authorization/Authorization.d.ts.map +1 -0
- package/dist/authorization/Resource.d.ts +5 -0
- package/dist/authorization/Resource.d.ts.map +1 -0
- package/dist/authorization/index.d.ts +5 -0
- package/dist/authorization/index.d.ts.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/databases/DatabaseInitializationOptions.d.ts +9 -0
- package/dist/databases/DatabaseInitializationOptions.d.ts.map +1 -0
- package/dist/databases/DatabaseSetup.d.ts +3 -0
- package/dist/databases/DatabaseSetup.d.ts.map +1 -0
- package/dist/endpoints/createEndpoint.d.ts +4 -0
- package/dist/endpoints/createEndpoint.d.ts.map +1 -0
- package/dist/endpoints/getEffectiveOperationId.d.ts +19 -0
- package/dist/endpoints/getEffectiveOperationId.d.ts.map +1 -0
- package/dist/env/Env.d.ts +2 -0
- package/dist/env/Env.d.ts.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1364 -0
- package/dist/launch/launchConfig.d.ts +18 -0
- package/dist/launch/launchConfig.d.ts.map +1 -0
- package/dist/logging/index.d.ts +9 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/sse/ConnectionManager.d.ts +23 -0
- package/dist/sse/ConnectionManager.d.ts.map +1 -0
- package/dist/stdin/StdinServer.d.ts +38 -0
- package/dist/stdin/StdinServer.d.ts.map +1 -0
- package/dist/web/EndpointListing.d.ts +3 -0
- package/dist/web/EndpointListing.d.ts.map +1 -0
- package/dist/web/ExpressAppSetup.d.ts +18 -0
- package/dist/web/ExpressAppSetup.d.ts.map +1 -0
- package/dist/web/ExpressEndpointSetup.d.ts +31 -0
- package/dist/web/ExpressEndpointSetup.d.ts.map +1 -0
- package/dist/web/SseResponse.d.ts +15 -0
- package/dist/web/SseResponse.d.ts.map +1 -0
- package/dist/web/ViteIntegration.d.ts +19 -0
- package/dist/web/ViteIntegration.d.ts.map +1 -0
- package/dist/web/corsMiddleware.d.ts +14 -0
- package/dist/web/corsMiddleware.d.ts.map +1 -0
- package/dist/web/localhostOnlyMiddleware.d.ts +3 -0
- package/dist/web/localhostOnlyMiddleware.d.ts.map +1 -0
- package/dist/web/openapi/OpenAPI.d.ts +37 -0
- package/dist/web/openapi/OpenAPI.d.ts.map +1 -0
- package/dist/web/openapi/validateServicesForOpenapi.d.ts +32 -0
- package/dist/web/openapi/validateServicesForOpenapi.d.ts.map +1 -0
- package/dist/web/requestContextMiddleware.d.ts +3 -0
- package/dist/web/requestContextMiddleware.d.ts.map +1 -0
- package/docs/authorization.md +281 -0
- package/docs/cors-setup.md +172 -0
- package/docs/creating-services.md +220 -0
- package/docs/database-setup.md +134 -0
- package/docs/endpoint-tools.md +1 -11
- package/docs/env-files.md +12 -1
- package/docs/error-handling.md +70 -0
- package/docs/getting-started.md +22 -12
- package/docs/launch-configuration.md +223 -0
- package/docs/overview.md +62 -0
- package/docs/server-setup.md +144 -0
- package/docs/source-directory-organization.md +115 -0
- package/docs/stdin-protocol.md +176 -0
- package/package.json +42 -9
- package/src/Errors.ts +120 -0
- package/src/Metrics.ts +53 -0
- package/src/RequestContext.ts +36 -0
- package/src/ServiceDefinition.ts +35 -0
- package/src/__tests__/Authorization.test.ts +350 -0
- package/src/__tests__/Errors.test.ts +378 -0
- package/src/__tests__/ListEndpoints.test.ts +98 -0
- package/src/__tests__/PrismApp.test.ts +274 -0
- package/src/__tests__/RequestContext.test.ts +295 -0
- package/src/__tests__/SseResponse.test.ts +189 -0
- package/src/__tests__/StdinServer.test.ts +304 -0
- package/src/__tests__/corsMiddleware.test.ts +293 -0
- package/src/__tests__/createEndpoint.test.ts +412 -0
- package/src/__tests__/validateApp.test.ts +206 -0
- package/src/app/PrismApp.ts +117 -0
- package/src/app/callEndpoint.ts +55 -0
- package/src/app/validateApp.ts +78 -0
- package/src/authorization/AuthSource.ts +14 -0
- package/src/authorization/Authorization.ts +78 -0
- package/src/authorization/Resource.ts +8 -0
- package/src/authorization/index.ts +4 -0
- package/src/databases/DatabaseInitializationOptions.ts +9 -0
- package/src/databases/DatabaseSetup.ts +19 -0
- package/src/endpoints/createEndpoint.ts +39 -0
- package/src/endpoints/getEffectiveOperationId.ts +90 -0
- package/src/env/Env.ts +23 -0
- package/src/index.ts +78 -0
- package/src/launch/launchConfig.ts +59 -0
- package/src/list-endpoints-command.ts +1 -1
- package/src/logging/index.ts +25 -0
- package/src/sse/ConnectionManager.ts +79 -0
- package/src/stdin/StdinServer.ts +129 -0
- package/src/web/EndpointListing.ts +166 -0
- package/src/web/ExpressAppSetup.ts +125 -0
- package/src/web/ExpressEndpointSetup.ts +178 -0
- package/src/web/SseResponse.ts +78 -0
- package/src/web/ViteIntegration.ts +72 -0
- package/src/web/__tests__/OpenAPI.invalidZodSchemas.test.ts +250 -0
- package/src/web/corsMiddleware.ts +63 -0
- package/src/web/localhostOnlyMiddleware.ts +19 -0
- package/src/web/openapi/OpenAPI.ts +248 -0
- package/src/web/openapi/validateServicesForOpenapi.ts +76 -0
- package/src/web/requestContextMiddleware.ts +25 -0
- package/.claude/settings.local.json +0 -20
- package/CHANGELOG +0 -28
- package/CLAUDE.md +0 -44
- package/build.mts +0 -8
- package/test/call-command.test.ts +0 -96
- package/test/generate-api-clients.test.ts +0 -33
- package/test/generate-api-clients.test.ts.disabled +0 -75
- package/tsconfig.json +0 -21
package/dist/index.js
ADDED
|
@@ -0,0 +1,1364 @@
|
|
|
1
|
+
// src/Errors.ts
|
|
2
|
+
var HttpError = class extends Error {
|
|
3
|
+
constructor(statusCode, message, details) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.statusCode = statusCode;
|
|
6
|
+
this.details = details;
|
|
7
|
+
this.name = "HttpError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var BadRequestError = class extends HttpError {
|
|
11
|
+
constructor(message = "Bad Request", details) {
|
|
12
|
+
super(400, message, details);
|
|
13
|
+
this.name = "BadRequestError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var SchemaValidationError = class extends HttpError {
|
|
17
|
+
constructor(message = "Schema Validation Error", details) {
|
|
18
|
+
super(422, message, details);
|
|
19
|
+
this.name = "SchemaValidationError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var ResponseSchemaValidationError = class extends HttpError {
|
|
23
|
+
constructor(message = "Response Schema Validation Error", details) {
|
|
24
|
+
super(500, message, details);
|
|
25
|
+
this.name = "ResponseSchemaValidationError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var UnauthorizedError = class extends HttpError {
|
|
29
|
+
constructor(message = "Unauthorized", details) {
|
|
30
|
+
super(401, message, details);
|
|
31
|
+
this.name = "UnauthorizedError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var ForbiddenError = class extends HttpError {
|
|
35
|
+
constructor(message = "Forbidden", details) {
|
|
36
|
+
super(403, message, details);
|
|
37
|
+
this.name = "ForbiddenError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var NotFoundError = class extends HttpError {
|
|
41
|
+
constructor(message = "Not Found", details) {
|
|
42
|
+
super(404, message, details);
|
|
43
|
+
this.name = "NotFoundError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var ConflictError = class extends HttpError {
|
|
47
|
+
constructor(message = "Conflict", details) {
|
|
48
|
+
super(409, message, details);
|
|
49
|
+
this.name = "ConflictError";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var ValidationError = class extends HttpError {
|
|
53
|
+
constructor(message = "Validation Error", details) {
|
|
54
|
+
super(422, message, details);
|
|
55
|
+
this.name = "ValidationError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var NotImplementedError = class extends HttpError {
|
|
59
|
+
constructor(message = "Not Implemented", details) {
|
|
60
|
+
super(501, message, details);
|
|
61
|
+
this.name = "NotImplementedError";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var ServiceUnavailableError = class extends HttpError {
|
|
65
|
+
constructor(message = "Service Unavailable", details) {
|
|
66
|
+
super(503, message, details);
|
|
67
|
+
this.name = "ServiceUnavailableError";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function createErrorFromStatus(statusCode, message, details) {
|
|
71
|
+
switch (statusCode) {
|
|
72
|
+
case 400:
|
|
73
|
+
return new BadRequestError(message, details);
|
|
74
|
+
case 401:
|
|
75
|
+
return new UnauthorizedError(message, details);
|
|
76
|
+
case 403:
|
|
77
|
+
return new ForbiddenError(message, details);
|
|
78
|
+
case 404:
|
|
79
|
+
return new NotFoundError(message, details);
|
|
80
|
+
case 409:
|
|
81
|
+
return new ConflictError(message, details);
|
|
82
|
+
case 422:
|
|
83
|
+
return new ValidationError(message, details);
|
|
84
|
+
case 500:
|
|
85
|
+
return new HttpError(500, message || "Internal Server Error", details);
|
|
86
|
+
case 501:
|
|
87
|
+
return new NotImplementedError(message, details);
|
|
88
|
+
case 503:
|
|
89
|
+
return new ServiceUnavailableError(message, details);
|
|
90
|
+
default:
|
|
91
|
+
return new HttpError(statusCode, message || "Unknown Error", details);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isHttpError(error) {
|
|
95
|
+
return error instanceof HttpError;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/web/ExpressAppSetup.ts
|
|
99
|
+
import cookieParser from "cookie-parser";
|
|
100
|
+
import express2 from "express";
|
|
101
|
+
|
|
102
|
+
// src/Metrics.ts
|
|
103
|
+
import PromClient from "prom-client";
|
|
104
|
+
var _hasSetupMetrics = false;
|
|
105
|
+
var httpRequests;
|
|
106
|
+
var httpResponses;
|
|
107
|
+
function setupMetrics() {
|
|
108
|
+
_hasSetupMetrics = true;
|
|
109
|
+
PromClient.collectDefaultMetrics({
|
|
110
|
+
// prefix: ...
|
|
111
|
+
// labels: ...
|
|
112
|
+
});
|
|
113
|
+
httpRequests = new PromClient.Counter({
|
|
114
|
+
name: "http_request_counter",
|
|
115
|
+
help: "HTTP requests",
|
|
116
|
+
labelNames: ["method", "endpoint"]
|
|
117
|
+
});
|
|
118
|
+
httpResponses = new PromClient.Counter({
|
|
119
|
+
name: "http_response_counter",
|
|
120
|
+
help: "HTTP responses",
|
|
121
|
+
labelNames: ["method", "endpoint", "status_code", "duration"]
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function recordHttpRequest(method, endpoint) {
|
|
125
|
+
if (!_hasSetupMetrics) {
|
|
126
|
+
setupMetrics();
|
|
127
|
+
}
|
|
128
|
+
httpRequests.inc({ method, endpoint });
|
|
129
|
+
}
|
|
130
|
+
function recordHttpResponse(method, endpoint, statusCode, duration) {
|
|
131
|
+
if (!_hasSetupMetrics) {
|
|
132
|
+
setupMetrics();
|
|
133
|
+
}
|
|
134
|
+
httpResponses.inc({ method, endpoint, status_code: statusCode.toString(), duration });
|
|
135
|
+
}
|
|
136
|
+
function getMetrics() {
|
|
137
|
+
if (!_hasSetupMetrics) {
|
|
138
|
+
setupMetrics();
|
|
139
|
+
}
|
|
140
|
+
return PromClient.register.metrics();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/web/corsMiddleware.ts
|
|
144
|
+
function setACAOHeader(res, reqOrigin, config) {
|
|
145
|
+
const webBaseUrl = config.webBaseUrl;
|
|
146
|
+
if (!reqOrigin) {
|
|
147
|
+
if (webBaseUrl) {
|
|
148
|
+
res.header("Access-Control-Allow-Origin", webBaseUrl);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (webBaseUrl) {
|
|
153
|
+
const allowedOrigins = [`https://${webBaseUrl}`];
|
|
154
|
+
if (allowedOrigins.includes(reqOrigin)) {
|
|
155
|
+
res.header("Access-Control-Allow-Origin", reqOrigin);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const localhostAllowed = config.allowLocalhost ?? config.enableTestEndpoints;
|
|
160
|
+
if (localhostAllowed && reqOrigin.startsWith("http://localhost:")) {
|
|
161
|
+
res.header("Access-Control-Allow-Origin", reqOrigin);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function corsMiddleware(config = {}) {
|
|
166
|
+
return (req, res, next) => {
|
|
167
|
+
setACAOHeader(res, req.headers.origin, config);
|
|
168
|
+
res.header("Access-Control-Allow-Credentials", "true");
|
|
169
|
+
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH");
|
|
170
|
+
res.header(
|
|
171
|
+
"Access-Control-Allow-Headers",
|
|
172
|
+
"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cookie, Cache-Control"
|
|
173
|
+
);
|
|
174
|
+
res.header("Access-Control-Max-Age", "86400");
|
|
175
|
+
if (req.method === "OPTIONS") {
|
|
176
|
+
res.sendStatus(200);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
next();
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/logging/index.ts
|
|
184
|
+
var useStderr = false;
|
|
185
|
+
function setLogStderr(enabled) {
|
|
186
|
+
useStderr = enabled;
|
|
187
|
+
}
|
|
188
|
+
function logInfo(...args) {
|
|
189
|
+
if (useStderr) {
|
|
190
|
+
console.error(...args);
|
|
191
|
+
} else {
|
|
192
|
+
console.log(...args);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function logWarn(...args) {
|
|
196
|
+
console.warn(...args);
|
|
197
|
+
}
|
|
198
|
+
function logError(...args) {
|
|
199
|
+
console.error(...args);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/web/openapi/OpenAPI.ts
|
|
203
|
+
import {
|
|
204
|
+
OpenAPIRegistry,
|
|
205
|
+
OpenApiGeneratorV31,
|
|
206
|
+
extendZodWithOpenApi
|
|
207
|
+
} from "@asteasolutions/zod-to-openapi";
|
|
208
|
+
import swaggerUi from "swagger-ui-express";
|
|
209
|
+
import z from "zod";
|
|
210
|
+
import { captureError } from "@facetlayer/streams";
|
|
211
|
+
extendZodWithOpenApi(z);
|
|
212
|
+
function parseExpressPathForOpenAPI(expressApiPath) {
|
|
213
|
+
const expressApiPathParts = expressApiPath.split("/");
|
|
214
|
+
const pathParams = [];
|
|
215
|
+
const openApiPathParts = [];
|
|
216
|
+
for (const part of expressApiPathParts) {
|
|
217
|
+
if (part.startsWith(":")) {
|
|
218
|
+
pathParams.push(part.substring(1));
|
|
219
|
+
openApiPathParts.push(`{${part.substring(1)}}`);
|
|
220
|
+
} else {
|
|
221
|
+
openApiPathParts.push(part);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
openApiPath: openApiPathParts.join("/"),
|
|
226
|
+
pathParams
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function generateOpenAPISchema(services, documentInfo) {
|
|
230
|
+
const registry = new OpenAPIRegistry();
|
|
231
|
+
for (const service of services) {
|
|
232
|
+
const endpoints = service.endpoints || [];
|
|
233
|
+
for (const endpoint of endpoints) {
|
|
234
|
+
const { openApiPath, pathParams } = parseExpressPathForOpenAPI(endpoint.path);
|
|
235
|
+
const requestConfig = {};
|
|
236
|
+
if (pathParams.length > 0) {
|
|
237
|
+
const pathParamsSchema = {};
|
|
238
|
+
for (const param of pathParams) {
|
|
239
|
+
pathParamsSchema[param] = z.string();
|
|
240
|
+
}
|
|
241
|
+
requestConfig.params = z.object(pathParamsSchema);
|
|
242
|
+
}
|
|
243
|
+
if (endpoint.requestSchema) {
|
|
244
|
+
requestConfig.body = {
|
|
245
|
+
content: {
|
|
246
|
+
"application/json": {
|
|
247
|
+
schema: endpoint.requestSchema
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
registry.registerPath({
|
|
253
|
+
method: endpoint.method.toLowerCase(),
|
|
254
|
+
path: openApiPath,
|
|
255
|
+
description: endpoint.description || `${endpoint.method} ${endpoint.path}`,
|
|
256
|
+
operationId: getEffectiveOperationId(endpoint),
|
|
257
|
+
request: requestConfig,
|
|
258
|
+
responses: {
|
|
259
|
+
200: {
|
|
260
|
+
description: "Success",
|
|
261
|
+
content: {
|
|
262
|
+
"application/json": {
|
|
263
|
+
schema: endpoint.responseSchema ?? z.any()
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/*
|
|
268
|
+
400: {
|
|
269
|
+
description: 'Bad Request - Schema validation failed',
|
|
270
|
+
content: {
|
|
271
|
+
'application/json': {
|
|
272
|
+
schema: z.object({
|
|
273
|
+
error: z.string(),
|
|
274
|
+
details: z.array(z.any()),
|
|
275
|
+
}),
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
401: {
|
|
280
|
+
description: 'Unauthorized',
|
|
281
|
+
content: {
|
|
282
|
+
'application/json': {
|
|
283
|
+
schema: z.object({
|
|
284
|
+
message: z.string(),
|
|
285
|
+
details: z.any().optional(),
|
|
286
|
+
}),
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
500: {
|
|
291
|
+
description: 'Internal Server Error',
|
|
292
|
+
content: {
|
|
293
|
+
'application/json': {
|
|
294
|
+
schema: z.object({
|
|
295
|
+
message: z.string(),
|
|
296
|
+
}),
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
*/
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const generator = new OpenApiGeneratorV31(
|
|
306
|
+
registry.definitions
|
|
307
|
+
/*
|
|
308
|
+
.concat([
|
|
309
|
+
{
|
|
310
|
+
type: 'component',
|
|
311
|
+
componentType: 'securitySchemes',
|
|
312
|
+
name: 'bearer_auth',
|
|
313
|
+
component: {
|
|
314
|
+
type: 'http',
|
|
315
|
+
scheme: 'bearer',
|
|
316
|
+
bearerFormat: 'JWT',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
])
|
|
320
|
+
*/
|
|
321
|
+
);
|
|
322
|
+
return generator.generateDocument({
|
|
323
|
+
openapi: "3.1.0",
|
|
324
|
+
info: {
|
|
325
|
+
version: documentInfo.version,
|
|
326
|
+
title: documentInfo.title,
|
|
327
|
+
description: documentInfo.description
|
|
328
|
+
},
|
|
329
|
+
servers: [
|
|
330
|
+
{ url: "/api", description: "API server" }
|
|
331
|
+
],
|
|
332
|
+
security: [
|
|
333
|
+
{
|
|
334
|
+
bearer_auth: []
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function setupSwaggerUI(app, openApiJsonPath = "/openapi.json") {
|
|
340
|
+
const router = app;
|
|
341
|
+
router.use(
|
|
342
|
+
"/swagger",
|
|
343
|
+
swaggerUi.serve,
|
|
344
|
+
swaggerUi.setup(null, {
|
|
345
|
+
swaggerOptions: {
|
|
346
|
+
url: openApiJsonPath
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
function mountOpenAPIEndpoints(config, target, prismApp) {
|
|
352
|
+
const router = target;
|
|
353
|
+
router.get("/openapi.json", (req, res) => {
|
|
354
|
+
const services = prismApp.getAllServices();
|
|
355
|
+
try {
|
|
356
|
+
res.json(generateOpenAPISchema(services, {
|
|
357
|
+
version: "1.0.0",
|
|
358
|
+
title: prismApp.name,
|
|
359
|
+
description: prismApp.description
|
|
360
|
+
}));
|
|
361
|
+
} catch (error) {
|
|
362
|
+
const validationResult = validateServicesForOpenapi(services);
|
|
363
|
+
console.error("/openapi.json failed to generate schema", {
|
|
364
|
+
cause: captureError(error),
|
|
365
|
+
problemEndpoints: validationResult.problemEndpoints
|
|
366
|
+
});
|
|
367
|
+
res.status(500).json({ error: "Internal server error" });
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
if (config.enableSwagger) {
|
|
371
|
+
setupSwaggerUI(router, "/api/openapi.json");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/web/openapi/validateServicesForOpenapi.ts
|
|
376
|
+
import { captureError as captureError2 } from "@facetlayer/streams";
|
|
377
|
+
function validateEndpointForOpenapi(endpoint) {
|
|
378
|
+
const testService = {
|
|
379
|
+
name: "test",
|
|
380
|
+
endpoints: [endpoint]
|
|
381
|
+
};
|
|
382
|
+
try {
|
|
383
|
+
generateOpenAPISchema([testService], {
|
|
384
|
+
version: "1.0.0",
|
|
385
|
+
title: "Test",
|
|
386
|
+
description: "Test"
|
|
387
|
+
});
|
|
388
|
+
return null;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
return { error: captureError2(error) };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function validateServicesForOpenapi(services) {
|
|
394
|
+
const problemEndpoints = [];
|
|
395
|
+
for (const service of services) {
|
|
396
|
+
const endpoints = service.endpoints || [];
|
|
397
|
+
for (const endpoint of endpoints) {
|
|
398
|
+
const endpointResult = validateEndpointForOpenapi(endpoint);
|
|
399
|
+
if (endpointResult == null ? void 0 : endpointResult.error) {
|
|
400
|
+
problemEndpoints.push({
|
|
401
|
+
serviceName: service.name,
|
|
402
|
+
path: endpoint.path,
|
|
403
|
+
method: endpoint.method,
|
|
404
|
+
error: endpointResult.error
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return { problemEndpoints };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/endpoints/getEffectiveOperationId.ts
|
|
413
|
+
var INVALID_OPERATION_IDS = /* @__PURE__ */ new Set([
|
|
414
|
+
"handler",
|
|
415
|
+
"anonymous",
|
|
416
|
+
""
|
|
417
|
+
]);
|
|
418
|
+
function isValidOperationId(operationId) {
|
|
419
|
+
if (operationId === void 0) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
return !INVALID_OPERATION_IDS.has(operationId);
|
|
423
|
+
}
|
|
424
|
+
function toCamelCase(str) {
|
|
425
|
+
if (!str.includes("-")) {
|
|
426
|
+
return str;
|
|
427
|
+
}
|
|
428
|
+
return str.split("-").map((part, index) => {
|
|
429
|
+
if (index === 0) {
|
|
430
|
+
return part.toLowerCase();
|
|
431
|
+
}
|
|
432
|
+
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
|
433
|
+
}).join("");
|
|
434
|
+
}
|
|
435
|
+
function generateOperationIdFromPath(method, path) {
|
|
436
|
+
const pathPart = path.split("/").filter(Boolean).map((segment, index) => {
|
|
437
|
+
if (segment.startsWith(":") || segment.startsWith("{")) {
|
|
438
|
+
const paramName = segment.replace(/^[:{}]+|[}]+$/g, "");
|
|
439
|
+
return "_" + toCamelCase(paramName);
|
|
440
|
+
}
|
|
441
|
+
const camelCased = toCamelCase(segment);
|
|
442
|
+
return camelCased.charAt(0).toUpperCase() + camelCased.slice(1);
|
|
443
|
+
}).join("");
|
|
444
|
+
return method.toLowerCase() + pathPart;
|
|
445
|
+
}
|
|
446
|
+
function getEffectiveOperationId(definition) {
|
|
447
|
+
if (definition.operationId && isValidOperationId(definition.operationId)) {
|
|
448
|
+
return definition.operationId;
|
|
449
|
+
}
|
|
450
|
+
const handlerName = definition.handler.name;
|
|
451
|
+
if (handlerName && isValidOperationId(handlerName)) {
|
|
452
|
+
return handlerName;
|
|
453
|
+
}
|
|
454
|
+
return generateOperationIdFromPath(definition.method, definition.path);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/endpoints/createEndpoint.ts
|
|
458
|
+
function createEndpoint(definition) {
|
|
459
|
+
if (definition.operationId !== void 0 && !isValidOperationId(definition.operationId)) {
|
|
460
|
+
logWarn(`Misconfigured endpoint ${definition.path}: operationId "${definition.operationId}" is not allowed`);
|
|
461
|
+
return {
|
|
462
|
+
...definition,
|
|
463
|
+
handler: () => {
|
|
464
|
+
throw new Error(`Misconfigured endpoint ${definition.path}: operationId "${definition.operationId}" is not allowed. Use a descriptive unique identifier.`);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const validationResult = validateEndpointForOpenapi(definition);
|
|
469
|
+
if (validationResult == null ? void 0 : validationResult.error) {
|
|
470
|
+
logWarn(`Misconfigured endpoint ${definition.path}: ${validationResult.error.errorMessage}`);
|
|
471
|
+
return {
|
|
472
|
+
...definition,
|
|
473
|
+
requestSchema: void 0,
|
|
474
|
+
responseSchema: void 0,
|
|
475
|
+
handler: () => {
|
|
476
|
+
throw new Error(`Misconfigured endpoint ${definition.path}: ${validationResult.error.errorMessage}`);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return definition;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/web/ExpressEndpointSetup.ts
|
|
484
|
+
function getRequestDataFromReq(req) {
|
|
485
|
+
let result = {};
|
|
486
|
+
if (req.body) {
|
|
487
|
+
result = { ...result, ...req.body };
|
|
488
|
+
}
|
|
489
|
+
if (req.params) {
|
|
490
|
+
result = { ...result, ...req.params };
|
|
491
|
+
}
|
|
492
|
+
if (req.query) {
|
|
493
|
+
result = { ...result, ...req.query };
|
|
494
|
+
}
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
function getOneHandler(prismApp, endpoint) {
|
|
498
|
+
return async (req, res, next) => {
|
|
499
|
+
const startTime = Date.now();
|
|
500
|
+
const method = req.method;
|
|
501
|
+
const path = req.path;
|
|
502
|
+
try {
|
|
503
|
+
recordHttpRequest(method, endpoint.path);
|
|
504
|
+
const inputData = getRequestDataFromReq(req);
|
|
505
|
+
const result = await prismApp.callEndpoint({ method: endpoint.method, path: endpoint.path, input: inputData });
|
|
506
|
+
res.status(200).json(result);
|
|
507
|
+
recordHttpResponse(endpoint.method, endpoint.path, 200, Date.now() - startTime);
|
|
508
|
+
return;
|
|
509
|
+
} catch (error) {
|
|
510
|
+
const endTime = Date.now();
|
|
511
|
+
const duration = endTime - startTime;
|
|
512
|
+
let statusCode = 500;
|
|
513
|
+
if (isHttpError(error)) {
|
|
514
|
+
statusCode = error.statusCode;
|
|
515
|
+
if (error.statusCode >= 500 || error.statusCode == null) {
|
|
516
|
+
logError(
|
|
517
|
+
`Server error in endpoint ${method} ${path}`,
|
|
518
|
+
{
|
|
519
|
+
path,
|
|
520
|
+
method,
|
|
521
|
+
errorMessage: error.message,
|
|
522
|
+
stack: error.stack
|
|
523
|
+
},
|
|
524
|
+
error
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
logDebug(`response ${error.statusCode}: ${method} ${path}`);
|
|
528
|
+
res.status(error.statusCode).json({
|
|
529
|
+
message: error.message,
|
|
530
|
+
details: error.details
|
|
531
|
+
});
|
|
532
|
+
} else {
|
|
533
|
+
console.error("Unhandled error in endpoint", {
|
|
534
|
+
path,
|
|
535
|
+
method,
|
|
536
|
+
errorMessage: error.message,
|
|
537
|
+
stack: error.stack
|
|
538
|
+
});
|
|
539
|
+
logError(
|
|
540
|
+
`Unhandled error in endpoint handler ${method} ${path}`,
|
|
541
|
+
{
|
|
542
|
+
path,
|
|
543
|
+
method,
|
|
544
|
+
errorMessage: error.message,
|
|
545
|
+
stack: error.stack
|
|
546
|
+
},
|
|
547
|
+
error
|
|
548
|
+
);
|
|
549
|
+
logDebug(`response 500: ${method} ${path}`);
|
|
550
|
+
res.status(500).json({
|
|
551
|
+
message: "Internal Server Error"
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
recordHttpResponse(method, endpoint.path, statusCode, duration);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function mountPrismApp(app, prismApp) {
|
|
559
|
+
const router = app;
|
|
560
|
+
const endpoints = prismApp.listAllEndpoints();
|
|
561
|
+
for (const endpoint of endpoints) {
|
|
562
|
+
const handler = getOneHandler(prismApp, endpoint);
|
|
563
|
+
switch (endpoint.method) {
|
|
564
|
+
case "GET":
|
|
565
|
+
router.get(endpoint.path, handler);
|
|
566
|
+
break;
|
|
567
|
+
case "POST":
|
|
568
|
+
router.post(endpoint.path, handler);
|
|
569
|
+
break;
|
|
570
|
+
case "PUT":
|
|
571
|
+
router.put(endpoint.path, handler);
|
|
572
|
+
break;
|
|
573
|
+
case "DELETE":
|
|
574
|
+
router.delete(endpoint.path, handler);
|
|
575
|
+
break;
|
|
576
|
+
case "PATCH":
|
|
577
|
+
router.patch(endpoint.path, handler);
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function mountMiddleware(app, middleware) {
|
|
583
|
+
app.use(middleware.path, middleware.handler);
|
|
584
|
+
}
|
|
585
|
+
function mountMiddlewares(app, middlewares) {
|
|
586
|
+
middlewares.forEach((middleware) => mountMiddleware(app, middleware));
|
|
587
|
+
}
|
|
588
|
+
function logDebug(_message) {
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/web/localhostOnlyMiddleware.ts
|
|
592
|
+
function localhostOnlyMiddleware(req, res, next) {
|
|
593
|
+
var _a;
|
|
594
|
+
const allowedIPs = ["127.0.0.1", "::1", "::ffff:127.0.0.1"];
|
|
595
|
+
const clientIP = req.ip || ((_a = req.connection) == null ? void 0 : _a.remoteAddress);
|
|
596
|
+
if (!allowedIPs.includes(clientIP)) {
|
|
597
|
+
return res.status(404).json({ error: "Not found" });
|
|
598
|
+
}
|
|
599
|
+
next();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/web/requestContextMiddleware.ts
|
|
603
|
+
import { v4 as uuidv4 } from "uuid";
|
|
604
|
+
|
|
605
|
+
// src/authorization/Authorization.ts
|
|
606
|
+
var Authorization = class {
|
|
607
|
+
constructor(resources = [], authSources = []) {
|
|
608
|
+
this.resources = /* @__PURE__ */ new Map();
|
|
609
|
+
for (const resource of resources) {
|
|
610
|
+
this.resources.set(resource.type, resource);
|
|
611
|
+
}
|
|
612
|
+
this.authSources = [...authSources];
|
|
613
|
+
}
|
|
614
|
+
addResource(resource) {
|
|
615
|
+
this.resources.set(resource.type, resource);
|
|
616
|
+
}
|
|
617
|
+
hasResource(type) {
|
|
618
|
+
return this.resources.has(type);
|
|
619
|
+
}
|
|
620
|
+
getResource(type) {
|
|
621
|
+
return this.resources.get(type);
|
|
622
|
+
}
|
|
623
|
+
getAllResources() {
|
|
624
|
+
return Array.from(this.resources.values());
|
|
625
|
+
}
|
|
626
|
+
addAuthSource(authSource) {
|
|
627
|
+
this.authSources.push(authSource);
|
|
628
|
+
}
|
|
629
|
+
getAuthSources() {
|
|
630
|
+
return [...this.authSources];
|
|
631
|
+
}
|
|
632
|
+
getCookieAuthSource() {
|
|
633
|
+
return this.authSources.find((c) => c.type === "cookie");
|
|
634
|
+
}
|
|
635
|
+
setUserPermissions(userPermissions) {
|
|
636
|
+
this.userPermissions = userPermissions;
|
|
637
|
+
}
|
|
638
|
+
getUserPermissions() {
|
|
639
|
+
return this.userPermissions;
|
|
640
|
+
}
|
|
641
|
+
hasPermission(permission) {
|
|
642
|
+
if (!this.userPermissions) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
return this.userPermissions.permissions.includes(permission);
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// src/RequestContext.ts
|
|
650
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
651
|
+
var requestContextStorage = new AsyncLocalStorage();
|
|
652
|
+
function withRequestContext(context, fn) {
|
|
653
|
+
return requestContextStorage.run(context, fn);
|
|
654
|
+
}
|
|
655
|
+
function getCurrentRequestContext() {
|
|
656
|
+
return requestContextStorage.getStore();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/web/requestContextMiddleware.ts
|
|
660
|
+
function requestContextMiddleware(req, res, next) {
|
|
661
|
+
const requestId = uuidv4();
|
|
662
|
+
const startTime = Date.now();
|
|
663
|
+
const context = {
|
|
664
|
+
requestId,
|
|
665
|
+
startTime,
|
|
666
|
+
req,
|
|
667
|
+
res,
|
|
668
|
+
auth: new Authorization()
|
|
669
|
+
};
|
|
670
|
+
res.setHeader("X-Request-ID", requestId);
|
|
671
|
+
requestContextStorage.run(context, () => {
|
|
672
|
+
next();
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/web/EndpointListing.ts
|
|
677
|
+
import { readFileSync } from "fs";
|
|
678
|
+
import { join } from "path";
|
|
679
|
+
function createListingEndpoints(endpoints) {
|
|
680
|
+
return [
|
|
681
|
+
createEndpoint({
|
|
682
|
+
method: "GET",
|
|
683
|
+
path: "/endpoints",
|
|
684
|
+
description: "Lists all available endpoints in the system",
|
|
685
|
+
handler: () => {
|
|
686
|
+
const htmlContent = generateEndpointsHTML(endpoints);
|
|
687
|
+
return {
|
|
688
|
+
sendHttpResponse: (res) => {
|
|
689
|
+
res.setHeader("Content-Type", "text/html");
|
|
690
|
+
res.send(htmlContent);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
}),
|
|
695
|
+
createEndpoint({
|
|
696
|
+
method: "GET",
|
|
697
|
+
path: "/endpoints.json",
|
|
698
|
+
description: "Lists all available endpoints in the system (JSON format)",
|
|
699
|
+
handler: () => {
|
|
700
|
+
const endpointsData = endpoints.map((endpoint) => ({
|
|
701
|
+
method: endpoint.method,
|
|
702
|
+
path: endpoint.path,
|
|
703
|
+
description: endpoint.description || "No description"
|
|
704
|
+
}));
|
|
705
|
+
return {
|
|
706
|
+
endpoints: endpointsData
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}),
|
|
710
|
+
createEndpoint({
|
|
711
|
+
method: "GET",
|
|
712
|
+
path: "/changelog",
|
|
713
|
+
description: "Serves the CHANGELOG.md file",
|
|
714
|
+
handler: () => {
|
|
715
|
+
const changelogPath = join(process.cwd(), "CHANGELOG.md");
|
|
716
|
+
const changelogContent = readFileSync(changelogPath, "utf8");
|
|
717
|
+
return {
|
|
718
|
+
sendHttpResponse: (res) => {
|
|
719
|
+
res.setHeader("Content-Type", "text/plain");
|
|
720
|
+
res.send(changelogContent);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
}),
|
|
725
|
+
createEndpoint({
|
|
726
|
+
method: "GET",
|
|
727
|
+
path: "/server-info",
|
|
728
|
+
description: "Returns server information including name",
|
|
729
|
+
handler: () => {
|
|
730
|
+
return {
|
|
731
|
+
name: "prism-framework"
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
})
|
|
735
|
+
];
|
|
736
|
+
}
|
|
737
|
+
function generateEndpointsHTML(endpoints) {
|
|
738
|
+
const endpointRows = endpoints.map(
|
|
739
|
+
(endpoint) => `
|
|
740
|
+
<tr>
|
|
741
|
+
<td><code>${endpoint.method}</code></td>
|
|
742
|
+
<td><code>${endpoint.path}</code></td>
|
|
743
|
+
<td>${endpoint.description || "No description"}</td>
|
|
744
|
+
</tr>
|
|
745
|
+
`
|
|
746
|
+
).join("");
|
|
747
|
+
return `
|
|
748
|
+
<!DOCTYPE html>
|
|
749
|
+
<html lang="en">
|
|
750
|
+
<head>
|
|
751
|
+
<meta charset="UTF-8">
|
|
752
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
753
|
+
<title>API Endpoints</title>
|
|
754
|
+
<style>
|
|
755
|
+
body {
|
|
756
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
757
|
+
max-width: 1200px;
|
|
758
|
+
margin: 0 auto;
|
|
759
|
+
padding: 20px;
|
|
760
|
+
background-color: #f5f5f5;
|
|
761
|
+
}
|
|
762
|
+
.container {
|
|
763
|
+
background: white;
|
|
764
|
+
border-radius: 8px;
|
|
765
|
+
padding: 30px;
|
|
766
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
767
|
+
}
|
|
768
|
+
h1 {
|
|
769
|
+
color: #333;
|
|
770
|
+
border-bottom: 3px solid #007acc;
|
|
771
|
+
padding-bottom: 10px;
|
|
772
|
+
}
|
|
773
|
+
table {
|
|
774
|
+
width: 100%;
|
|
775
|
+
border-collapse: collapse;
|
|
776
|
+
margin-top: 20px;
|
|
777
|
+
}
|
|
778
|
+
th, td {
|
|
779
|
+
text-align: left;
|
|
780
|
+
padding: 12px;
|
|
781
|
+
border-bottom: 1px solid #ddd;
|
|
782
|
+
}
|
|
783
|
+
th {
|
|
784
|
+
background-color: #f8f9fa;
|
|
785
|
+
font-weight: 600;
|
|
786
|
+
color: #495057;
|
|
787
|
+
}
|
|
788
|
+
tr:hover {
|
|
789
|
+
background-color: #f8f9fa;
|
|
790
|
+
}
|
|
791
|
+
code {
|
|
792
|
+
background-color: #e9ecef;
|
|
793
|
+
padding: 2px 6px;
|
|
794
|
+
border-radius: 3px;
|
|
795
|
+
font-family: 'Monaco', 'Consolas', monospace;
|
|
796
|
+
}
|
|
797
|
+
.method-get { color: #28a745; }
|
|
798
|
+
.method-post { color: #007bff; }
|
|
799
|
+
.method-put { color: #ffc107; }
|
|
800
|
+
.method-delete { color: #dc3545; }
|
|
801
|
+
.method-patch { color: #6f42c1; }
|
|
802
|
+
.count {
|
|
803
|
+
color: #6c757d;
|
|
804
|
+
font-size: 0.9em;
|
|
805
|
+
}
|
|
806
|
+
</style>
|
|
807
|
+
</head>
|
|
808
|
+
<body>
|
|
809
|
+
<div class="container">
|
|
810
|
+
<h1>API Endpoints</h1>
|
|
811
|
+
<p class="count">Total endpoints: <strong>${endpoints.length}</strong></p>
|
|
812
|
+
|
|
813
|
+
<table>
|
|
814
|
+
<thead>
|
|
815
|
+
<tr>
|
|
816
|
+
<th>Method</th>
|
|
817
|
+
<th>Path</th>
|
|
818
|
+
<th>Description</th>
|
|
819
|
+
</tr>
|
|
820
|
+
</thead>
|
|
821
|
+
<tbody>
|
|
822
|
+
${endpointRows}
|
|
823
|
+
</tbody>
|
|
824
|
+
</table>
|
|
825
|
+
</div>
|
|
826
|
+
</body>
|
|
827
|
+
</html>
|
|
828
|
+
`.trim();
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/web/ExpressAppSetup.ts
|
|
832
|
+
import { captureError as captureError3 } from "@facetlayer/streams";
|
|
833
|
+
|
|
834
|
+
// src/app/validateApp.ts
|
|
835
|
+
function validateApp(app) {
|
|
836
|
+
const errors = [];
|
|
837
|
+
const duplicateError = checkDuplicateOperationIds(app);
|
|
838
|
+
if (duplicateError) {
|
|
839
|
+
errors.push(duplicateError);
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
valid: errors.length === 0,
|
|
843
|
+
errors
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
function checkDuplicateOperationIds(app) {
|
|
847
|
+
const endpoints = app.listAllEndpoints();
|
|
848
|
+
const operationIdToEndpoints = /* @__PURE__ */ new Map();
|
|
849
|
+
for (const endpoint of endpoints) {
|
|
850
|
+
const operationId = getEffectiveOperationId(endpoint);
|
|
851
|
+
const endpointKey2 = `${endpoint.method} ${endpoint.path}`;
|
|
852
|
+
const existing = operationIdToEndpoints.get(operationId) || [];
|
|
853
|
+
existing.push(endpointKey2);
|
|
854
|
+
operationIdToEndpoints.set(operationId, existing);
|
|
855
|
+
}
|
|
856
|
+
const duplicates = [];
|
|
857
|
+
for (const [operationId, endpointKeys] of operationIdToEndpoints) {
|
|
858
|
+
if (endpointKeys.length > 1) {
|
|
859
|
+
duplicates.push(`operationId "${operationId}" is used by: ${endpointKeys.join(", ")}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (duplicates.length > 0) {
|
|
863
|
+
return {
|
|
864
|
+
message: `Duplicate operationIds found. Each endpoint must have a unique operationId.
|
|
865
|
+
${duplicates.join("\n")}`,
|
|
866
|
+
endpoints: duplicates
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
return null;
|
|
870
|
+
}
|
|
871
|
+
function validateAppOrThrow(app) {
|
|
872
|
+
const result = validateApp(app);
|
|
873
|
+
if (!result.valid) {
|
|
874
|
+
const errorMessages = result.errors.map((e) => e.message).join("\n\n");
|
|
875
|
+
throw new Error(`PrismApp validation failed:
|
|
876
|
+
${errorMessages}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/web/ViteIntegration.ts
|
|
881
|
+
import express from "express";
|
|
882
|
+
import { existsSync } from "fs";
|
|
883
|
+
import { join as join2, resolve } from "path";
|
|
884
|
+
async function setupWebMiddleware(expressApp, webConfig) {
|
|
885
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
886
|
+
const webDir = resolve(webConfig.dir);
|
|
887
|
+
if (isDev) {
|
|
888
|
+
try {
|
|
889
|
+
const vite = await Function('return import("vite")')();
|
|
890
|
+
const viteServer = await vite.createServer({
|
|
891
|
+
root: webDir,
|
|
892
|
+
server: { middlewareMode: true },
|
|
893
|
+
appType: "spa"
|
|
894
|
+
});
|
|
895
|
+
expressApp.use(viteServer.middlewares);
|
|
896
|
+
logInfo(`Vite dev server middleware attached for ${webDir}`);
|
|
897
|
+
return;
|
|
898
|
+
} catch {
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const distDir = join2(webDir, "dist");
|
|
902
|
+
const serveDir = !isDev && existsSync(distDir) ? distDir : webDir;
|
|
903
|
+
expressApp.use(express.static(serveDir));
|
|
904
|
+
const indexPath = join2(serveDir, "index.html");
|
|
905
|
+
expressApp.get("*", (req, res) => {
|
|
906
|
+
if (existsSync(indexPath)) {
|
|
907
|
+
res.sendFile(indexPath);
|
|
908
|
+
} else {
|
|
909
|
+
res.status(404).send("Not found");
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
logInfo(`Serving static files from ${serveDir}`);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/web/ExpressAppSetup.ts
|
|
916
|
+
function createExpressApp(config) {
|
|
917
|
+
const app = express2();
|
|
918
|
+
app.use(corsMiddleware(config.corsConfig ?? {}));
|
|
919
|
+
app.use(express2.json());
|
|
920
|
+
app.use(express2.urlencoded({ extended: true }));
|
|
921
|
+
app.use(requestContextMiddleware);
|
|
922
|
+
app.use(cookieParser());
|
|
923
|
+
const apiRouter = express2.Router();
|
|
924
|
+
apiRouter.get("/health", localhostOnlyMiddleware, (req, res) => {
|
|
925
|
+
res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
926
|
+
});
|
|
927
|
+
apiRouter.get("/metrics", localhostOnlyMiddleware, async (req, res) => {
|
|
928
|
+
try {
|
|
929
|
+
const metrics = await getMetrics();
|
|
930
|
+
res.set("Content-Type", "text/plain; version=0.0.4; charset=utf-8");
|
|
931
|
+
res.end(metrics);
|
|
932
|
+
} catch (error) {
|
|
933
|
+
res.status(500).json({ error: "Failed to retrieve metrics" });
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
if (config.openapiConfig) {
|
|
937
|
+
mountOpenAPIEndpoints(config.openapiConfig, apiRouter, config.app);
|
|
938
|
+
}
|
|
939
|
+
const allEndpoints = config.app.listAllEndpoints();
|
|
940
|
+
const listingEndpoints = createListingEndpoints(allEndpoints);
|
|
941
|
+
for (const endpoint of listingEndpoints) {
|
|
942
|
+
const handler = async (req, res) => {
|
|
943
|
+
try {
|
|
944
|
+
const result = await endpoint.handler({});
|
|
945
|
+
if (result.sendHttpResponse) {
|
|
946
|
+
result.sendHttpResponse(res);
|
|
947
|
+
} else {
|
|
948
|
+
res.json(result);
|
|
949
|
+
}
|
|
950
|
+
} catch (error) {
|
|
951
|
+
logError("Unhandled error in endpoint", {
|
|
952
|
+
endpointPath: endpoint.path,
|
|
953
|
+
error: captureError3(error)
|
|
954
|
+
});
|
|
955
|
+
res.status(500).json({ error: "Internal server error" });
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
if (endpoint.method === "GET") {
|
|
959
|
+
apiRouter.get(endpoint.path, handler);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
mountPrismApp(apiRouter, config.app);
|
|
963
|
+
apiRouter.use((req, res) => {
|
|
964
|
+
res.status(404).json({ error: "Not found" });
|
|
965
|
+
});
|
|
966
|
+
app.use("/api", apiRouter);
|
|
967
|
+
return app;
|
|
968
|
+
}
|
|
969
|
+
async function startServer(config) {
|
|
970
|
+
validateAppOrThrow(config.app);
|
|
971
|
+
const port = config.port;
|
|
972
|
+
const app = createExpressApp(config);
|
|
973
|
+
if (config.web) {
|
|
974
|
+
await setupWebMiddleware(app, config.web);
|
|
975
|
+
}
|
|
976
|
+
const server = app.listen(port, () => {
|
|
977
|
+
logInfo(`Server now listening on port ${port}`);
|
|
978
|
+
});
|
|
979
|
+
process.on("SIGTERM", () => {
|
|
980
|
+
logInfo("SIGTERM received, shutting down gracefully");
|
|
981
|
+
server.close(() => {
|
|
982
|
+
logInfo("Server closed");
|
|
983
|
+
process.exit(0);
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
return server;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/web/SseResponse.ts
|
|
990
|
+
var SseResponse = class {
|
|
991
|
+
constructor(response) {
|
|
992
|
+
this.isResponseOpen = true;
|
|
993
|
+
this.response = response;
|
|
994
|
+
this.setupSseHeaders();
|
|
995
|
+
this.setupCloseHandlers();
|
|
996
|
+
}
|
|
997
|
+
setupSseHeaders() {
|
|
998
|
+
this.response.writeHead(200, {
|
|
999
|
+
"Content-Type": "text/event-stream",
|
|
1000
|
+
"Cache-Control": "no-cache",
|
|
1001
|
+
Connection: "keep-alive"
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
setupCloseHandlers() {
|
|
1005
|
+
this.response.on("close", () => {
|
|
1006
|
+
this._triggerOnClose();
|
|
1007
|
+
});
|
|
1008
|
+
this.response.on("finish", () => {
|
|
1009
|
+
this._triggerOnClose();
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
send(data) {
|
|
1013
|
+
if (!this.isResponseOpen) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const jsonData = JSON.stringify(data);
|
|
1017
|
+
this.response.write(`event: item
|
|
1018
|
+
data: ${jsonData}
|
|
1019
|
+
|
|
1020
|
+
`);
|
|
1021
|
+
}
|
|
1022
|
+
isOpen() {
|
|
1023
|
+
return this.isResponseOpen;
|
|
1024
|
+
}
|
|
1025
|
+
// close() - Can be called by the handler to close the response.
|
|
1026
|
+
close() {
|
|
1027
|
+
if (this.isResponseOpen) {
|
|
1028
|
+
this.response.write(`event: done
|
|
1029
|
+
|
|
1030
|
+
`);
|
|
1031
|
+
this.response.end();
|
|
1032
|
+
this._triggerOnClose();
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
// Internal: Called when the response is closed.
|
|
1036
|
+
_triggerOnClose() {
|
|
1037
|
+
this.isResponseOpen = false;
|
|
1038
|
+
if (this._onClose) {
|
|
1039
|
+
this._onClose();
|
|
1040
|
+
this._onClose = null;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
// Adds a callback to be called when the response is closed.
|
|
1044
|
+
onClose(callback) {
|
|
1045
|
+
if (this._onClose) {
|
|
1046
|
+
throw new Error("usage error: alrady have onClose callback");
|
|
1047
|
+
}
|
|
1048
|
+
this._onClose = callback;
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
// src/sse/ConnectionManager.ts
|
|
1053
|
+
var ConnectionManager = class {
|
|
1054
|
+
constructor(options) {
|
|
1055
|
+
this.connections = /* @__PURE__ */ new Map();
|
|
1056
|
+
this.managerName = options.managerName;
|
|
1057
|
+
this.logDebug = options.logDebug || (() => {
|
|
1058
|
+
});
|
|
1059
|
+
this.logError = options.logError || (() => {
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
addConnection(key, sseResponse) {
|
|
1063
|
+
this.logDebug(`${this.managerName} (ConnectionManager) adding connection for: ${key}`);
|
|
1064
|
+
const connectionList = this.connections.get(key) || [];
|
|
1065
|
+
const connection = {
|
|
1066
|
+
key,
|
|
1067
|
+
sseResponse,
|
|
1068
|
+
connectedAt: /* @__PURE__ */ new Date()
|
|
1069
|
+
};
|
|
1070
|
+
connectionList.push(connection);
|
|
1071
|
+
this.connections.set(key, connectionList);
|
|
1072
|
+
sseResponse.onClose(() => {
|
|
1073
|
+
var _a;
|
|
1074
|
+
this.logDebug(`${this.managerName} (ConnectionManager) closed connection for: ${key}`);
|
|
1075
|
+
const updatedList = ((_a = this.connections.get(key)) == null ? void 0 : _a.filter((c) => c !== connection)) || [];
|
|
1076
|
+
if (updatedList.length === 0) {
|
|
1077
|
+
this.connections.delete(key);
|
|
1078
|
+
} else {
|
|
1079
|
+
this.connections.set(key, updatedList);
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
getConnections(key) {
|
|
1084
|
+
return this.connections.get(key) || [];
|
|
1085
|
+
}
|
|
1086
|
+
async postEvent(key, event) {
|
|
1087
|
+
const connections = this.getConnections(key);
|
|
1088
|
+
for (const connection of connections) {
|
|
1089
|
+
if (connection.sseResponse.isOpen()) {
|
|
1090
|
+
try {
|
|
1091
|
+
connection.sseResponse.send(event);
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
this.logError("Error sending SSE event:", error);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
// src/app/callEndpoint.ts
|
|
1101
|
+
async function callEndpoint(app, options) {
|
|
1102
|
+
const match = app.matchEndpoint(options.method, options.path);
|
|
1103
|
+
if (!match) {
|
|
1104
|
+
throw new Error(`Endpoint not found: ${options.method} ${options.path}`);
|
|
1105
|
+
}
|
|
1106
|
+
const { endpoint, params } = match;
|
|
1107
|
+
let input = { ...params, ...options.input || {} };
|
|
1108
|
+
if (endpoint.requestSchema) {
|
|
1109
|
+
const validationResult = endpoint.requestSchema.safeParse(input);
|
|
1110
|
+
if (!validationResult.success) {
|
|
1111
|
+
throw new SchemaValidationError("Schema validation failed", validationResult.error.issues);
|
|
1112
|
+
}
|
|
1113
|
+
input = validationResult.data;
|
|
1114
|
+
}
|
|
1115
|
+
const result = await endpoint.handler(input);
|
|
1116
|
+
if (endpoint.responseSchema) {
|
|
1117
|
+
const validationResult = endpoint.responseSchema.safeParse(result);
|
|
1118
|
+
if (!validationResult.success) {
|
|
1119
|
+
const error = new ResponseSchemaValidationError("Response schema validation failed", validationResult.error.issues);
|
|
1120
|
+
if (options.onResponseSchemaFail) {
|
|
1121
|
+
options.onResponseSchemaFail(error, result);
|
|
1122
|
+
} else {
|
|
1123
|
+
throw error;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return result;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// src/app/PrismApp.ts
|
|
1131
|
+
function endpointKey(method, path) {
|
|
1132
|
+
return `${method} ${path}`;
|
|
1133
|
+
}
|
|
1134
|
+
var PrismApp = class {
|
|
1135
|
+
constructor(config = {}) {
|
|
1136
|
+
this.name = config.name ?? "Prism App";
|
|
1137
|
+
this.description = config.description ?? "";
|
|
1138
|
+
this.services = [];
|
|
1139
|
+
this.endpointMap = /* @__PURE__ */ new Map();
|
|
1140
|
+
if (config.services) {
|
|
1141
|
+
for (const service of config.services) {
|
|
1142
|
+
this.addService(service);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Add a service to the app. Endpoints from the service will be registered
|
|
1148
|
+
* and available for routing.
|
|
1149
|
+
*/
|
|
1150
|
+
addService(service) {
|
|
1151
|
+
this.services.push(service);
|
|
1152
|
+
if (service.endpoints) {
|
|
1153
|
+
for (const endpoint of service.endpoints) {
|
|
1154
|
+
const key = endpointKey(endpoint.method, endpoint.path);
|
|
1155
|
+
this.endpointMap.set(key, endpoint);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
getAllServices() {
|
|
1160
|
+
return this.services;
|
|
1161
|
+
}
|
|
1162
|
+
getEndpoint(method, path) {
|
|
1163
|
+
const key = endpointKey(method, path);
|
|
1164
|
+
return this.endpointMap.get(key);
|
|
1165
|
+
}
|
|
1166
|
+
matchEndpoint(method, path) {
|
|
1167
|
+
const key = endpointKey(method, path);
|
|
1168
|
+
const exactMatch = this.endpointMap.get(key);
|
|
1169
|
+
if (exactMatch) {
|
|
1170
|
+
return { endpoint: exactMatch, params: {} };
|
|
1171
|
+
}
|
|
1172
|
+
for (const [endpointKey2, endpoint] of this.endpointMap.entries()) {
|
|
1173
|
+
if (!endpointKey2.startsWith(method + " ")) {
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
const endpointPath = endpoint.path;
|
|
1177
|
+
const match = this.matchPath(endpointPath, path);
|
|
1178
|
+
if (match) {
|
|
1179
|
+
return { endpoint, params: match };
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return void 0;
|
|
1183
|
+
}
|
|
1184
|
+
matchPath(pattern, path) {
|
|
1185
|
+
const paramNames = [];
|
|
1186
|
+
const regexString = pattern.replace(/:([^/]+)/g, (_, paramName) => {
|
|
1187
|
+
paramNames.push(paramName);
|
|
1188
|
+
return "([^/]+)";
|
|
1189
|
+
}).replace(/\//g, "\\/");
|
|
1190
|
+
const regex = new RegExp(`^${regexString}$`);
|
|
1191
|
+
const match = path.match(regex);
|
|
1192
|
+
if (!match) {
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
const params = {};
|
|
1196
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
1197
|
+
params[paramNames[i]] = match[i + 1];
|
|
1198
|
+
}
|
|
1199
|
+
return params;
|
|
1200
|
+
}
|
|
1201
|
+
listAllEndpoints() {
|
|
1202
|
+
return Array.from(this.endpointMap.values());
|
|
1203
|
+
}
|
|
1204
|
+
callEndpoint(options) {
|
|
1205
|
+
return callEndpoint(this, options);
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
// src/launch/launchConfig.ts
|
|
1210
|
+
var _config;
|
|
1211
|
+
function getLaunchConfig() {
|
|
1212
|
+
if (!_config) {
|
|
1213
|
+
throw new Error("Launch config not initialized");
|
|
1214
|
+
}
|
|
1215
|
+
return _config;
|
|
1216
|
+
}
|
|
1217
|
+
function setLaunchConfig(config) {
|
|
1218
|
+
if (_config) {
|
|
1219
|
+
throw new Error("Launch config already initialized");
|
|
1220
|
+
}
|
|
1221
|
+
_config = config;
|
|
1222
|
+
}
|
|
1223
|
+
function getDatabaseConfig(databaseName) {
|
|
1224
|
+
if (!_config) {
|
|
1225
|
+
throw new Error(
|
|
1226
|
+
"Launch config not initialized (tried to getDatabaseConfig for: " + databaseName + ")"
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
if (!_config.database[databaseName]) {
|
|
1230
|
+
throw new Error("Database config not found (tried to getDatabaseConfig for: " + databaseName + ")");
|
|
1231
|
+
}
|
|
1232
|
+
return _config.database[databaseName];
|
|
1233
|
+
}
|
|
1234
|
+
function getLoggingConfig() {
|
|
1235
|
+
if (!_config) {
|
|
1236
|
+
throw new Error("Launch config not initialized (in getLoggingConfig)");
|
|
1237
|
+
}
|
|
1238
|
+
return _config.logging;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// src/databases/DatabaseSetup.ts
|
|
1242
|
+
function getStatementsForDatabase(databaseName, services) {
|
|
1243
|
+
return (services || []).map((service) => {
|
|
1244
|
+
var _a;
|
|
1245
|
+
const databases = service.databases;
|
|
1246
|
+
return ((_a = databases == null ? void 0 : databases[databaseName]) == null ? void 0 : _a.statements) || [];
|
|
1247
|
+
}).flat();
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// src/stdin/StdinServer.ts
|
|
1251
|
+
import { createInterface } from "readline";
|
|
1252
|
+
function startStdinServer(config) {
|
|
1253
|
+
setLogStderr(true);
|
|
1254
|
+
validateAppOrThrow(config.app);
|
|
1255
|
+
const app = config.app;
|
|
1256
|
+
const rl = createInterface({
|
|
1257
|
+
input: process.stdin,
|
|
1258
|
+
terminal: false
|
|
1259
|
+
});
|
|
1260
|
+
function sendResponse(response) {
|
|
1261
|
+
process.stdout.write(JSON.stringify(response) + "\n");
|
|
1262
|
+
}
|
|
1263
|
+
function sendError(id, status, message, details) {
|
|
1264
|
+
const response = {
|
|
1265
|
+
id: id ?? "unknown",
|
|
1266
|
+
status,
|
|
1267
|
+
body: { message, ...details ? { details } : {} }
|
|
1268
|
+
};
|
|
1269
|
+
sendResponse(response);
|
|
1270
|
+
}
|
|
1271
|
+
rl.on("line", async (line) => {
|
|
1272
|
+
let request;
|
|
1273
|
+
try {
|
|
1274
|
+
request = JSON.parse(line);
|
|
1275
|
+
} catch {
|
|
1276
|
+
sendError(null, 400, "Invalid JSON");
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
if (!request.id || !request.method || !request.path) {
|
|
1280
|
+
sendError((request == null ? void 0 : request.id) ?? null, 400, "Missing required fields: id, method, path");
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
try {
|
|
1284
|
+
const result = await app.callEndpoint({
|
|
1285
|
+
method: request.method.toUpperCase(),
|
|
1286
|
+
path: request.path,
|
|
1287
|
+
input: request.body ?? {}
|
|
1288
|
+
});
|
|
1289
|
+
sendResponse({
|
|
1290
|
+
id: request.id,
|
|
1291
|
+
status: 200,
|
|
1292
|
+
body: result
|
|
1293
|
+
});
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
if (isHttpError(error)) {
|
|
1296
|
+
sendResponse({
|
|
1297
|
+
id: request.id,
|
|
1298
|
+
status: error.statusCode,
|
|
1299
|
+
body: {
|
|
1300
|
+
message: error.message,
|
|
1301
|
+
...error.details ? { details: error.details } : {}
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1304
|
+
} else {
|
|
1305
|
+
sendResponse({
|
|
1306
|
+
id: request.id,
|
|
1307
|
+
status: 500,
|
|
1308
|
+
body: { message: error.message ?? "Internal Server Error" }
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
rl.on("close", () => {
|
|
1314
|
+
process.exit(0);
|
|
1315
|
+
});
|
|
1316
|
+
sendResponse({
|
|
1317
|
+
id: "_ready",
|
|
1318
|
+
status: 200,
|
|
1319
|
+
body: { message: "stdin server ready" }
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
export {
|
|
1323
|
+
PrismApp as App,
|
|
1324
|
+
Authorization,
|
|
1325
|
+
BadRequestError,
|
|
1326
|
+
ConflictError,
|
|
1327
|
+
ConnectionManager,
|
|
1328
|
+
ForbiddenError,
|
|
1329
|
+
HttpError,
|
|
1330
|
+
NotFoundError,
|
|
1331
|
+
NotImplementedError,
|
|
1332
|
+
ServiceUnavailableError,
|
|
1333
|
+
SseResponse,
|
|
1334
|
+
UnauthorizedError,
|
|
1335
|
+
ValidationError,
|
|
1336
|
+
callEndpoint,
|
|
1337
|
+
corsMiddleware,
|
|
1338
|
+
createEndpoint,
|
|
1339
|
+
createErrorFromStatus,
|
|
1340
|
+
createExpressApp,
|
|
1341
|
+
createListingEndpoints,
|
|
1342
|
+
generateOpenAPISchema,
|
|
1343
|
+
getCurrentRequestContext,
|
|
1344
|
+
getDatabaseConfig,
|
|
1345
|
+
getLaunchConfig,
|
|
1346
|
+
getLoggingConfig,
|
|
1347
|
+
getMetrics,
|
|
1348
|
+
getRequestDataFromReq,
|
|
1349
|
+
getStatementsForDatabase,
|
|
1350
|
+
isHttpError,
|
|
1351
|
+
localhostOnlyMiddleware,
|
|
1352
|
+
mountMiddleware,
|
|
1353
|
+
mountMiddlewares,
|
|
1354
|
+
mountPrismApp,
|
|
1355
|
+
parseExpressPathForOpenAPI,
|
|
1356
|
+
recordHttpRequest,
|
|
1357
|
+
recordHttpResponse,
|
|
1358
|
+
requestContextMiddleware,
|
|
1359
|
+
setLaunchConfig,
|
|
1360
|
+
setLogStderr,
|
|
1361
|
+
startServer,
|
|
1362
|
+
startStdinServer,
|
|
1363
|
+
withRequestContext
|
|
1364
|
+
};
|