@arkstack/driver-express 0.5.2 → 0.5.3
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 +2 -0
- package/dist/app.d.ts +25 -0
- package/dist/index.d.ts +61 -5
- package/dist/index.js +114 -25
- package/dist/middlewares/index.d.ts +72 -2
- package/dist/middlewares/index.js +162 -7
- package/dist/types.d.ts +9 -0
- package/dist/types.js +1 -0
- package/package.json +18 -11
- package/dist/index.js.map +0 -1
- package/dist/middlewares/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @arkstack/driver-express
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@arkstack/driver-express)
|
|
4
|
+
|
|
3
5
|
Express driver for Arkstack, providing Express-based runtime integration for the framework.
|
|
4
6
|
|
|
5
7
|
## Auth Middleware
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Request, Response } from 'express'
|
|
2
|
+
|
|
3
|
+
declare module '@arkstack/common' {
|
|
4
|
+
interface HookRegistry {
|
|
5
|
+
'middleware:auth': {
|
|
6
|
+
before: (ctx: { req: Request, res: Response }) => Promise<void>
|
|
7
|
+
after: (ctx: { req: Request, res: Response }) => Promise<void>
|
|
8
|
+
error: (error: unknown, ctx: { req: Request, res: Response }) => Promise<void>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare module '@arkstack/foundry' {
|
|
14
|
+
interface HookRegistry {
|
|
15
|
+
'middleware:auth': {
|
|
16
|
+
before: (ctx: { req: Request, res: Response }) => Promise<void>
|
|
17
|
+
after: (ctx: { req: Request, res: Response }) => Promise<void>
|
|
18
|
+
error: (error: unknown, ctx: { req: Request, res: Response }) => Promise<void>
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare global {
|
|
24
|
+
var tunnelUrl: () => string
|
|
25
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
/// <reference path="./app.d.ts" />
|
|
2
|
+
import { Middleware as Middleware$1, MiddlewareConfig } from "./types.js";
|
|
1
3
|
import { ErrorRequestHandler, Express, Handler, Router as Router$1 } from "express";
|
|
2
|
-
import { ArkstackKitDriver,
|
|
4
|
+
import { ArkstackKitDriver, ArkstackRouteListOptions, PromiseOrValue } from "@arkstack/contract";
|
|
3
5
|
import { Router as Router$2 } from "clear-router/express";
|
|
4
6
|
import { Route } from "clear-router";
|
|
5
7
|
import { Handler as Handler$1, HttpContext, Middleware } from "clear-router/types/express";
|
|
@@ -19,17 +21,71 @@ interface ExpressDriverOptions {
|
|
|
19
21
|
mountPublicAssets?: (app: Express, publicPath: string) => PromiseOrValue<void>;
|
|
20
22
|
errorHandler?: ErrorRequestHandler | Handler;
|
|
21
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The ExpressDriver class implements the ArkstackKitDriver
|
|
26
|
+
* contract for the Express framework.
|
|
27
|
+
*/
|
|
22
28
|
declare class ExpressDriver extends ArkstackKitDriver<Express, Handler> {
|
|
23
29
|
readonly name = "express";
|
|
30
|
+
private tunnel_url?;
|
|
24
31
|
private readonly options;
|
|
32
|
+
/**
|
|
33
|
+
* Creates an instance of ExpressDriver.
|
|
34
|
+
*
|
|
35
|
+
* @param options
|
|
36
|
+
*/
|
|
25
37
|
constructor(options: ExpressDriverOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Creates an Express application instance.
|
|
40
|
+
*
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
26
43
|
createApp(): Express;
|
|
44
|
+
/**
|
|
45
|
+
* Mounts static assets from the specified public path to the Express application.
|
|
46
|
+
*
|
|
47
|
+
* @param app
|
|
48
|
+
* @param publicPath
|
|
49
|
+
*/
|
|
27
50
|
mountPublicAssets(app: Express, publicPath: string): PromiseOrValue<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Binds the router to the Express application using the provided bindRouter function.
|
|
53
|
+
*
|
|
54
|
+
* @param app
|
|
55
|
+
*/
|
|
28
56
|
bindRouter(app: Express): PromiseOrValue<void>;
|
|
29
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Applies middleware to the Express application.
|
|
59
|
+
*
|
|
60
|
+
* @param app
|
|
61
|
+
* @param middleware
|
|
62
|
+
*/
|
|
63
|
+
applyMiddleware(app: Express, middleware: Middleware$1 | MiddlewareConfig): void;
|
|
64
|
+
/**
|
|
65
|
+
* Registers an error handler middleware to the Express
|
|
66
|
+
* application if provided in the options.
|
|
67
|
+
*
|
|
68
|
+
* @param app
|
|
69
|
+
*/
|
|
30
70
|
registerErrorHandler(app: Express): void;
|
|
31
|
-
|
|
71
|
+
/**
|
|
72
|
+
* If trafic has been proxied via ngrok, this will return the tunnel URL.
|
|
73
|
+
*
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
geTunnelUrl(): string | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Starts the Express server on the specified port.
|
|
79
|
+
*
|
|
80
|
+
* The bind host can be overridden with the `APP_HOST` (or `HOST`) env
|
|
81
|
+
* variable. It defaults to `0.0.0.0` so the server is reachable on all
|
|
82
|
+
* network interfaces, which platforms like Railway require for their
|
|
83
|
+
* healthcheck proxy to reach the app.
|
|
84
|
+
*
|
|
85
|
+
* @param app
|
|
86
|
+
* @param port
|
|
87
|
+
*/
|
|
88
|
+
start(app: Express, port: number): Promise<void>;
|
|
32
89
|
}
|
|
33
90
|
//#endregion
|
|
34
|
-
export { ExpressDriver, ExpressDriverOptions, Router, defaultErrorHandler };
|
|
35
|
-
//# sourceMappingURL=index.d.ts.map
|
|
91
|
+
export { ExpressDriver, ExpressDriverOptions, Router, defaultErrorHandler };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
import { ArkstackKitDriver } from "@arkstack/contract";
|
|
3
|
-
import { ErrorHandler, Logger, RequestException,
|
|
3
|
+
import { ErrorHandler, Logger, RequestException, devTlsCredentials, env, localNetworkAddress, renderError, resolveRuntimeModule } from "@arkstack/common";
|
|
4
|
+
import https from "node:https";
|
|
5
|
+
import { resolveMiddleware } from "@arkstack/http";
|
|
6
|
+
import ngrok from "@ngrok/ngrok";
|
|
4
7
|
import { Router as Router$2 } from "clear-router/express";
|
|
5
8
|
import { clearRouterExpressPlugin } from "@resora/plugin-clear-router";
|
|
6
|
-
import { join } from "node:path";
|
|
7
9
|
import { registerPlugin } from "resora";
|
|
8
10
|
//#region src/error-handler.ts
|
|
9
|
-
const
|
|
11
|
+
const webMiddlewareKey = Symbol.for("arkstack:http:web");
|
|
12
|
+
const isRecord = (value) => {
|
|
13
|
+
return typeof value === "object" && value !== null;
|
|
14
|
+
};
|
|
15
|
+
const isWebRequest = (value) => {
|
|
16
|
+
if (!isRecord(value)) return false;
|
|
17
|
+
return value[webMiddlewareKey] === true || value.arkstackWeb === true || isWebRequest(value.context) || isWebRequest(value.req);
|
|
18
|
+
};
|
|
19
|
+
const redirectBackTarget = (req) => {
|
|
20
|
+
const referer = req.headers?.referer ?? req.headers?.referrer;
|
|
21
|
+
const value = Array.isArray(referer) ? referer[0] : referer;
|
|
22
|
+
return typeof value === "string" && value ? value : "/";
|
|
23
|
+
};
|
|
24
|
+
const flashValidationState = async (err, req, errors) => {
|
|
25
|
+
const session = req.httpSession ?? (isRecord(req.session) && typeof req.session.addValidationErrors === "function" ? req.session : void 0);
|
|
26
|
+
if (!session) return;
|
|
27
|
+
if (errors) session.addErrors?.(errors);
|
|
28
|
+
else session.addValidationErrors?.(err);
|
|
29
|
+
await session.save?.();
|
|
30
|
+
};
|
|
31
|
+
const defaultErrorHandler = async (err, req, res, next) => {
|
|
10
32
|
const responseBody = ErrorHandler.createErrorPayload(err);
|
|
11
33
|
if (ErrorHandler.shouldLogError(err)) ErrorHandler.logUnhandledError(err, {
|
|
12
34
|
headers: req.headers,
|
|
@@ -20,6 +42,11 @@ const defaultErrorHandler = (err, req, res, next) => {
|
|
|
20
42
|
}
|
|
21
43
|
const expectsJson = (Array.isArray(req.headers.accept) ? req.headers.accept.join(",") : req.headers.accept ?? "").includes("application/json") || req.originalUrl.startsWith("/api/");
|
|
22
44
|
const code = ErrorHandler.normalizeStatusCode(responseBody.code);
|
|
45
|
+
if (code === 422 && !expectsJson && isWebRequest(req)) {
|
|
46
|
+
await flashValidationState(err, req, responseBody.errors);
|
|
47
|
+
res.redirect(302, redirectBackTarget(req));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
23
50
|
if (expectsJson) {
|
|
24
51
|
res.status(code).json(responseBody);
|
|
25
52
|
return;
|
|
@@ -37,12 +64,16 @@ Router$2.configure({ inferParamName: true });
|
|
|
37
64
|
var Router = class extends Router$2 {
|
|
38
65
|
static async bind() {
|
|
39
66
|
const router = express.Router();
|
|
40
|
-
|
|
41
|
-
await
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
try {
|
|
68
|
+
await Router$2.group("/api", resolveRuntimeModule("src/routes/api.ts"));
|
|
69
|
+
} catch (e) {
|
|
70
|
+
Logger.error("ERROR: Unable to load \"api.ts\" routes: " + e.message, false);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
await Router$2.group("/", resolveRuntimeModule("src/routes/web.ts"));
|
|
74
|
+
} catch (e) {
|
|
75
|
+
Logger.error("ERROR: Unable to load \"web.ts\" routes: " + e.message, false);
|
|
76
|
+
}
|
|
46
77
|
Router$2.apply(router);
|
|
47
78
|
router.all("/*splat", (req, _res, next) => {
|
|
48
79
|
const url = req.originalUrl || req.url;
|
|
@@ -63,6 +94,7 @@ var Router = class extends Router$2 {
|
|
|
63
94
|
*/
|
|
64
95
|
var ExpressDriver = class extends ArkstackKitDriver {
|
|
65
96
|
name = "express";
|
|
97
|
+
tunnel_url;
|
|
66
98
|
options;
|
|
67
99
|
/**
|
|
68
100
|
* Creates an instance of ExpressDriver.
|
|
@@ -114,17 +146,21 @@ var ExpressDriver = class extends ArkstackKitDriver {
|
|
|
114
146
|
* @param middleware
|
|
115
147
|
*/
|
|
116
148
|
applyMiddleware(app, middleware) {
|
|
149
|
+
if (!middleware) return;
|
|
117
150
|
if (typeof middleware === "function") {
|
|
118
|
-
app.use(middleware);
|
|
151
|
+
app.use(resolveMiddleware(middleware));
|
|
119
152
|
return;
|
|
120
153
|
}
|
|
121
|
-
for (const [pos, entries] of Object.entries(middleware)) for (const
|
|
122
|
-
|
|
123
|
-
|
|
154
|
+
for (const [pos, entries] of Object.entries(middleware)) for (const instance of entries) {
|
|
155
|
+
const entry = resolveMiddleware(instance);
|
|
156
|
+
if (pos === "after") app.use(async (req, res, next) => {
|
|
157
|
+
res.once("finish", async () => {
|
|
158
|
+
await entry(req, res, next);
|
|
159
|
+
});
|
|
160
|
+
next();
|
|
124
161
|
});
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
else app.use(entry);
|
|
162
|
+
else app.use(entry);
|
|
163
|
+
}
|
|
128
164
|
}
|
|
129
165
|
/**
|
|
130
166
|
* Registers an error handler middleware to the Express
|
|
@@ -136,18 +172,71 @@ var ExpressDriver = class extends ArkstackKitDriver {
|
|
|
136
172
|
app.use(this.options.errorHandler ?? defaultErrorHandler);
|
|
137
173
|
}
|
|
138
174
|
/**
|
|
139
|
-
*
|
|
175
|
+
* If trafic has been proxied via ngrok, this will return the tunnel URL.
|
|
140
176
|
*
|
|
141
|
-
* @
|
|
142
|
-
* @param port
|
|
177
|
+
* @returns
|
|
143
178
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
179
|
+
geTunnelUrl() {
|
|
180
|
+
return this.tunnel_url;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Starts the Express server on the specified port.
|
|
184
|
+
*
|
|
185
|
+
* The bind host can be overridden with the `APP_HOST` (or `HOST`) env
|
|
186
|
+
* variable. It defaults to `0.0.0.0` so the server is reachable on all
|
|
187
|
+
* network interfaces, which platforms like Railway require for their
|
|
188
|
+
* healthcheck proxy to reach the app.
|
|
189
|
+
*
|
|
190
|
+
* @param app
|
|
191
|
+
* @param port
|
|
192
|
+
*/
|
|
193
|
+
async start(app, port) {
|
|
194
|
+
const host = env("APP_HOST", env("HOST", "0.0.0.0"));
|
|
195
|
+
const secure = env("APP_SECURE", false) === true;
|
|
196
|
+
const tunneled = env("TUNNEL", false);
|
|
197
|
+
const scheme = secure ? "https" : "http";
|
|
198
|
+
const onListen = async () => {
|
|
199
|
+
let log = startupLogLines(scheme, host, port);
|
|
200
|
+
if (tunneled === true) {
|
|
201
|
+
const url = (await ngrok.forward({
|
|
202
|
+
addr: port,
|
|
203
|
+
authtoken: env("NGROK_AUTHTOKEN"),
|
|
204
|
+
domain: env("NGROK_DOMAIN")
|
|
205
|
+
})).url();
|
|
206
|
+
if (url) {
|
|
207
|
+
log = log.concat(Logger.log([["Trafic has been tunnelled to", "white"], [url, "green"]], " ", false));
|
|
208
|
+
process.env.TUNNEL_URL = url;
|
|
209
|
+
this.tunnel_url = url;
|
|
210
|
+
globalThis.tunnelUrl = () => url;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
console.log(log.join("\n"));
|
|
214
|
+
};
|
|
215
|
+
if (secure) {
|
|
216
|
+
const credentials = await devTlsCredentials();
|
|
217
|
+
https.createServer({
|
|
218
|
+
key: credentials.key,
|
|
219
|
+
cert: credentials.cert
|
|
220
|
+
}, app).listen(port, host, onListen);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
app.listen(port, host, onListen);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* Build the "Server is running" startup lines, adding a local-network URL when
|
|
228
|
+
* the server is bound to all interfaces (`0.0.0.0`/`::`) so it is reachable from
|
|
229
|
+
* other devices.
|
|
230
|
+
*/
|
|
231
|
+
const startupLogLines = (scheme, host, port) => {
|
|
232
|
+
const bindsAll = host === "0.0.0.0" || host === "::";
|
|
233
|
+
const localHost = bindsAll ? "localhost" : host;
|
|
234
|
+
const lines = [Logger.log([["Server is running on", "white"], [`${scheme}://${localHost}:${port}`, "cyan"]], " ", false)];
|
|
235
|
+
if (bindsAll) {
|
|
236
|
+
const address = localNetworkAddress();
|
|
237
|
+
if (address) lines.push(Logger.log([["Network access via", "white"], [`${scheme}://${address}:${port}`, "cyan"]], " ", false));
|
|
148
238
|
}
|
|
239
|
+
return lines;
|
|
149
240
|
};
|
|
150
241
|
//#endregion
|
|
151
242
|
export { ExpressDriver, Router, defaultErrorHandler };
|
|
152
|
-
|
|
153
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,18 +1,88 @@
|
|
|
1
1
|
import { Handler, NextFunction, Request, Response } from "express";
|
|
2
2
|
import multer from "multer";
|
|
3
|
+
import * as _$express_rate_limit0 from "express-rate-limit";
|
|
4
|
+
import { ValueDeterminingMiddleware } from "express-rate-limit";
|
|
3
5
|
|
|
4
6
|
//#region src/middlewares/auth.d.ts
|
|
5
7
|
declare const auth: Handler;
|
|
8
|
+
declare class AuthMiddleware {
|
|
9
|
+
handler(req: Request, res: Response, next: NextFunction): unknown;
|
|
10
|
+
}
|
|
6
11
|
//#endregion
|
|
7
12
|
//#region src/middlewares/formdata.d.ts
|
|
8
13
|
declare const formdata: multer.Multer;
|
|
14
|
+
declare class FormDataMiddleware {
|
|
15
|
+
private type;
|
|
16
|
+
private name?;
|
|
17
|
+
private count?;
|
|
18
|
+
private options?;
|
|
19
|
+
constructor(type: 'array', fieldName: string, maxCount?: number | undefined, options?: multer.Options);
|
|
20
|
+
constructor(type: 'fields', fields: multer.Field[], options?: multer.Options);
|
|
21
|
+
constructor(type: 'single', fieldName: string, options?: multer.Options);
|
|
22
|
+
handler(req: Request, res: Response, next: NextFunction): unknown;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/middlewares/inertia.d.ts
|
|
26
|
+
/**
|
|
27
|
+
* Bind the Inertia request context for Express.
|
|
28
|
+
*
|
|
29
|
+
* Normalizes the Express request into the adapter's driver-agnostic shape and
|
|
30
|
+
* runs the downstream handler inside Inertia's async-local context (mirroring the
|
|
31
|
+
* `resora` middleware), so `inertia()` / `Inertia.*` resolve the active request.
|
|
32
|
+
*
|
|
33
|
+
* It also upgrades `302` redirects to `303 See Other` for `PUT`/`PATCH`/`DELETE`
|
|
34
|
+
* Inertia visits, which the Inertia client requires to follow the redirect with
|
|
35
|
+
* a `GET`.
|
|
36
|
+
*
|
|
37
|
+
* `@arkstack/inertia` is imported dynamically so the package stays optional.
|
|
38
|
+
*/
|
|
39
|
+
declare const inertia: () => Handler;
|
|
40
|
+
declare class InertiaMiddleware {
|
|
41
|
+
handler(req: Request, res: Response, next: NextFunction): unknown;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/middlewares/limiter.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* create a rate limiter middleware
|
|
47
|
+
*
|
|
48
|
+
* @param requests number of requests allowed per windowMs
|
|
49
|
+
* @param perMin number of minutes for the window
|
|
50
|
+
* @param message custom message to be returned when rate limit is exceeded
|
|
51
|
+
* @returns
|
|
52
|
+
*/
|
|
53
|
+
declare const limiter: (requests?: number | ValueDeterminingMiddleware<number>, perSec?: number, message?: string | ValueDeterminingMiddleware<string>) => _$express_rate_limit0.RateLimitRequestHandler;
|
|
9
54
|
//#endregion
|
|
10
55
|
//#region src/middlewares/request-logger.d.ts
|
|
56
|
+
/**
|
|
57
|
+
* Middleware to log incoming requests and their response times.
|
|
58
|
+
*
|
|
59
|
+
* @param config Configuration options for the request logger middleware.
|
|
60
|
+
* @param config.allowInProduction If true, the logger will also log requests in production environment. Default is false.
|
|
61
|
+
* @returns
|
|
62
|
+
*/
|
|
11
63
|
declare const requestLogger: ({
|
|
12
64
|
allowInProduction
|
|
13
65
|
}?: {
|
|
14
66
|
allowInProduction?: boolean;
|
|
15
67
|
}) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
68
|
+
declare class RequestLoggerMiddleware {
|
|
69
|
+
private options;
|
|
70
|
+
constructor(options?: {
|
|
71
|
+
allowInProduction?: boolean;
|
|
72
|
+
});
|
|
73
|
+
handler(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/middlewares/resora.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* Apply the application's resora configuration (`src/config/resources.ts`) and
|
|
79
|
+
* bind the per-request `{ req, res }` context so Resources can build URLs and
|
|
80
|
+
* pagination links.
|
|
81
|
+
*
|
|
82
|
+
* Replaces the manual `Resource.setCtx(...)` wiring: resora's runtime config is
|
|
83
|
+
* applied from `config('resources')`, and the request is run within resora's
|
|
84
|
+
* async context so downstream handlers resolve the correct context.
|
|
85
|
+
*/
|
|
86
|
+
declare const resora: () => Handler;
|
|
16
87
|
//#endregion
|
|
17
|
-
export { auth, formdata, requestLogger };
|
|
18
|
-
//# sourceMappingURL=index.d.ts.map
|
|
88
|
+
export { AuthMiddleware, FormDataMiddleware, InertiaMiddleware, RequestLoggerMiddleware, auth, formdata, inertia, limiter, requestLogger, resora };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { Hook, Logger, nodeEnv } from "@arkstack/common";
|
|
2
|
-
import {
|
|
1
|
+
import { Exception, Hook, Logger, config, env, nodeEnv, resolveRuntimeDir } from "@arkstack/common";
|
|
2
|
+
import { applyRuntimeConfig, getDefaultConfig, runWithCtx, setCtx } from "resora";
|
|
3
3
|
import multer from "multer";
|
|
4
|
+
import { rateLimit } from "express-rate-limit";
|
|
4
5
|
//#region src/middlewares/auth.ts
|
|
5
6
|
const auth = async (req, res, next) => {
|
|
7
|
+
const { Auth, AuthenticationException } = await import("@arkstack/auth");
|
|
6
8
|
try {
|
|
7
9
|
if (Hook.has("middleware:auth", "before")) await Promise.resolve(Hook.get("middleware:auth", "before")?.({
|
|
8
10
|
req,
|
|
@@ -14,10 +16,11 @@ const auth = async (req, res, next) => {
|
|
|
14
16
|
status: 401
|
|
15
17
|
});
|
|
16
18
|
const auth = Auth.make().setRequest(req);
|
|
17
|
-
const user = await
|
|
19
|
+
const user = await auth.authorizeToken(token);
|
|
18
20
|
req.user = user;
|
|
19
21
|
req.auth = auth;
|
|
20
22
|
req.authUser = user;
|
|
23
|
+
req.session = auth.session();
|
|
21
24
|
req.authToken = token;
|
|
22
25
|
if (Hook.has("middleware:auth", "after")) await Promise.resolve(Hook.get("middleware:auth", "after")?.({
|
|
23
26
|
req,
|
|
@@ -32,6 +35,11 @@ const auth = async (req, res, next) => {
|
|
|
32
35
|
next(error);
|
|
33
36
|
}
|
|
34
37
|
};
|
|
38
|
+
var AuthMiddleware = class {
|
|
39
|
+
handler(req, res, next) {
|
|
40
|
+
return auth(req, res, next);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
35
43
|
const readBearerToken = (authorization) => {
|
|
36
44
|
const value = Array.isArray(authorization) ? authorization[0] : authorization;
|
|
37
45
|
if (!value?.startsWith("Bearer ")) return null;
|
|
@@ -40,6 +48,110 @@ const readBearerToken = (authorization) => {
|
|
|
40
48
|
//#endregion
|
|
41
49
|
//#region src/middlewares/formdata.ts
|
|
42
50
|
const formdata = multer({ storage: multer.memoryStorage() });
|
|
51
|
+
var FormDataMiddleware = class {
|
|
52
|
+
type;
|
|
53
|
+
name;
|
|
54
|
+
count;
|
|
55
|
+
options;
|
|
56
|
+
constructor(type, name, count, options) {
|
|
57
|
+
this.type = type;
|
|
58
|
+
this.name = name;
|
|
59
|
+
this.count = count;
|
|
60
|
+
this.options = options;
|
|
61
|
+
}
|
|
62
|
+
handler(req, res, next) {
|
|
63
|
+
let inst;
|
|
64
|
+
const options = this.options ?? (typeof this.count === "object" && "storage" in this.count ? this.count : {});
|
|
65
|
+
const formdata = multer({
|
|
66
|
+
storage: multer.memoryStorage(),
|
|
67
|
+
...options
|
|
68
|
+
});
|
|
69
|
+
if (this.type === "any" || this.type === "none") inst = formdata.any();
|
|
70
|
+
else if (this.type === "array") inst = formdata.array(this.name, this.count);
|
|
71
|
+
else if (this.type === "fields") inst = formdata.fields(this.name);
|
|
72
|
+
else inst = formdata.single(this.name);
|
|
73
|
+
return inst.call(inst, req, res, next);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/middlewares/inertia.ts
|
|
78
|
+
/**
|
|
79
|
+
* Bind the Inertia request context for Express.
|
|
80
|
+
*
|
|
81
|
+
* Normalizes the Express request into the adapter's driver-agnostic shape and
|
|
82
|
+
* runs the downstream handler inside Inertia's async-local context (mirroring the
|
|
83
|
+
* `resora` middleware), so `inertia()` / `Inertia.*` resolve the active request.
|
|
84
|
+
*
|
|
85
|
+
* It also upgrades `302` redirects to `303 See Other` for `PUT`/`PATCH`/`DELETE`
|
|
86
|
+
* Inertia visits, which the Inertia client requires to follow the redirect with
|
|
87
|
+
* a `GET`.
|
|
88
|
+
*
|
|
89
|
+
* `@arkstack/inertia` is imported dynamically so the package stays optional.
|
|
90
|
+
*/
|
|
91
|
+
const inertia = () => {
|
|
92
|
+
return async (req, res, next) => {
|
|
93
|
+
try {
|
|
94
|
+
const { runInertia, shouldUpgradeRedirect } = await import("@arkstack/inertia");
|
|
95
|
+
const request = {
|
|
96
|
+
method: String(req.method ?? "GET").toUpperCase(),
|
|
97
|
+
url: req.originalUrl || req.url || "/",
|
|
98
|
+
header: (name) => {
|
|
99
|
+
const value = req.headers[name.toLowerCase()];
|
|
100
|
+
return Array.isArray(value) ? value[0] : value;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
if (request.header("x-inertia") === "true") {
|
|
104
|
+
const original = res.redirect.bind(res);
|
|
105
|
+
res.redirect = ((...args) => {
|
|
106
|
+
let status = typeof args[0] === "number" ? args[0] : 302;
|
|
107
|
+
const url = typeof args[0] === "number" ? args[1] : args[0];
|
|
108
|
+
if (shouldUpgradeRedirect(request.method, status)) status = 303;
|
|
109
|
+
return original(status, url);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
runInertia(request, () => next());
|
|
113
|
+
} catch (error) {
|
|
114
|
+
next(error);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
var InertiaMiddleware = class {
|
|
119
|
+
handler(req, res, next) {
|
|
120
|
+
return inertia()(req, res, next);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/Exceptions/RateLimitExceededException.ts
|
|
125
|
+
var RateLimitExceededException = class extends Exception {
|
|
126
|
+
statusCode = 429;
|
|
127
|
+
name;
|
|
128
|
+
constructor(options) {
|
|
129
|
+
super(options.message);
|
|
130
|
+
this.name = "RateLimitExceededException";
|
|
131
|
+
this.statusCode = options.statusCode ?? 429;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/middlewares/limiter.ts
|
|
136
|
+
/**
|
|
137
|
+
* create a rate limiter middleware
|
|
138
|
+
*
|
|
139
|
+
* @param requests number of requests allowed per windowMs
|
|
140
|
+
* @param perMin number of minutes for the window
|
|
141
|
+
* @param message custom message to be returned when rate limit is exceeded
|
|
142
|
+
* @returns
|
|
143
|
+
*/
|
|
144
|
+
const limiter = (requests = 100, perSec = 900, message) => rateLimit({
|
|
145
|
+
message,
|
|
146
|
+
limit: requests,
|
|
147
|
+
windowMs: (env("NODE_ENV") === "production" ? perSec : 30) * 1e3,
|
|
148
|
+
standardHeaders: true,
|
|
149
|
+
legacyHeaders: false,
|
|
150
|
+
ipv6Subnet: 56,
|
|
151
|
+
handler: (_, __, ___, options) => {
|
|
152
|
+
throw new RateLimitExceededException(options);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
43
155
|
//#endregion
|
|
44
156
|
//#region src/middlewares/request-logger.ts
|
|
45
157
|
const colors = {
|
|
@@ -57,7 +169,8 @@ const colors = {
|
|
|
57
169
|
* @returns
|
|
58
170
|
*/
|
|
59
171
|
const requestLogger = ({ allowInProduction = false } = {}) => async (req, res, next) => {
|
|
60
|
-
|
|
172
|
+
const VERBOSE = process.env.VERBOSITY != "0";
|
|
173
|
+
if (nodeEnv() === "prod" && !allowInProduction || !VERBOSE) return next();
|
|
61
174
|
const start = Date.now();
|
|
62
175
|
const status = res.statusCode || 200;
|
|
63
176
|
const duration = Date.now() - start;
|
|
@@ -69,7 +182,49 @@ const requestLogger = ({ allowInProduction = false } = {}) => async (req, res, n
|
|
|
69
182
|
], " ");
|
|
70
183
|
next();
|
|
71
184
|
};
|
|
185
|
+
var RequestLoggerMiddleware = class {
|
|
186
|
+
options;
|
|
187
|
+
constructor(options = {}) {
|
|
188
|
+
this.options = options;
|
|
189
|
+
}
|
|
190
|
+
handler(req, res, next) {
|
|
191
|
+
const inst = requestLogger(this.options);
|
|
192
|
+
return inst.call(inst, req, res, next);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/middlewares/resora.ts
|
|
197
|
+
/**
|
|
198
|
+
* Apply the application's resora configuration (`src/config/resources.ts`) and
|
|
199
|
+
* bind the per-request `{ req, res }` context so Resources can build URLs and
|
|
200
|
+
* pagination links.
|
|
201
|
+
*
|
|
202
|
+
* Replaces the manual `Resource.setCtx(...)` wiring: resora's runtime config is
|
|
203
|
+
* applied from `config('resources')`, and the request is run within resora's
|
|
204
|
+
* async context so downstream handlers resolve the correct context.
|
|
205
|
+
*/
|
|
206
|
+
const resora = () => {
|
|
207
|
+
let resources;
|
|
208
|
+
return (req, res, next) => {
|
|
209
|
+
try {
|
|
210
|
+
if (!resources) {
|
|
211
|
+
resources = {
|
|
212
|
+
...getDefaultConfig(),
|
|
213
|
+
...config("resources", {})
|
|
214
|
+
};
|
|
215
|
+
if (typeof resources.resourcesDir === "string") resources.resourcesDir = resolveRuntimeDir(resources.resourcesDir);
|
|
216
|
+
}
|
|
217
|
+
applyRuntimeConfig(resources);
|
|
218
|
+
} catch {}
|
|
219
|
+
setCtx({
|
|
220
|
+
req,
|
|
221
|
+
res
|
|
222
|
+
});
|
|
223
|
+
return runWithCtx({
|
|
224
|
+
req,
|
|
225
|
+
res
|
|
226
|
+
}, () => next());
|
|
227
|
+
};
|
|
228
|
+
};
|
|
72
229
|
//#endregion
|
|
73
|
-
export { auth, formdata, requestLogger };
|
|
74
|
-
|
|
75
|
-
//# sourceMappingURL=index.js.map
|
|
230
|
+
export { AuthMiddleware, FormDataMiddleware, InertiaMiddleware, RequestLoggerMiddleware, auth, formdata, inertia, limiter, requestLogger, resora };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Handler } from "express";
|
|
2
|
+
import { ArkstackMiddlewareConfig } from "@arkstack/contract";
|
|
3
|
+
import { ClassMiddleware } from "clear-router/types/basic";
|
|
4
|
+
|
|
5
|
+
//#region src/types.d.ts
|
|
6
|
+
type Middleware = Handler | ClassMiddleware<Handler>;
|
|
7
|
+
type MiddlewareConfig = ArkstackMiddlewareConfig<Middleware>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { Middleware, MiddlewareConfig };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkstack/driver-express",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Express driver for Arkstack, providing Express-based runtime integration for the framework.",
|
|
6
6
|
"homepage": "https://arkstack.toneflix.net",
|
|
@@ -33,32 +33,39 @@
|
|
|
33
33
|
"exports": {
|
|
34
34
|
".": "./dist/index.js",
|
|
35
35
|
"./middlewares": "./dist/middlewares/index.js",
|
|
36
|
+
"./types": "./dist/types.js",
|
|
36
37
|
"./package.json": "./package.json"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"
|
|
40
|
-
"clear-router": "^
|
|
40
|
+
"@ngrok/ngrok": "^1.7.0",
|
|
41
|
+
"@resora/plugin-clear-router": "^1.0.66",
|
|
42
|
+
"clear-router": "^2.9.0",
|
|
41
43
|
"express-rate-limit": "^8.4.1",
|
|
42
|
-
"
|
|
43
|
-
"resora": "^1.
|
|
44
|
-
"@arkstack/contract": "^0.5.
|
|
44
|
+
"multer": "^2.1.1",
|
|
45
|
+
"resora": "^1.3.27",
|
|
46
|
+
"@arkstack/contract": "^0.5.3"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
47
49
|
"express": "^5.2.1",
|
|
48
|
-
"@arkstack/auth": "^0.5.
|
|
49
|
-
"@arkstack/
|
|
50
|
+
"@arkstack/auth": "^0.5.3",
|
|
51
|
+
"@arkstack/foundry": "^0.5.3",
|
|
52
|
+
"@arkstack/inertia": "^0.5.3",
|
|
53
|
+
"@arkstack/common": "^0.5.3"
|
|
50
54
|
},
|
|
51
55
|
"peerDependenciesMeta": {
|
|
52
56
|
"@arkstack/auth": {
|
|
53
57
|
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"@arkstack/inertia": {
|
|
60
|
+
"optional": true
|
|
54
61
|
}
|
|
55
62
|
},
|
|
56
63
|
"devDependencies": {
|
|
57
|
-
"@types/
|
|
58
|
-
"@types/
|
|
64
|
+
"@types/express": "^5.0.6",
|
|
65
|
+
"@types/multer": "^2.1.0"
|
|
59
66
|
},
|
|
60
67
|
"scripts": {
|
|
61
|
-
"build": "tsdown",
|
|
68
|
+
"build": "tsdown --config-loader unrun",
|
|
62
69
|
"version:patch": "pnpm version patch"
|
|
63
70
|
}
|
|
64
71
|
}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["ClearRouter"],"sources":["../src/error-handler.ts","../src/Router.ts","../src/index.ts"],"sourcesContent":["import {\n ErrorHandler,\n renderError,\n} from '@arkstack/common'\n\nimport type { ErrorRequestHandler } from 'express'\n\nexport const defaultErrorHandler: ErrorRequestHandler = (err, req, res, next) => {\n const responseBody = ErrorHandler.createErrorPayload(err)\n\n if (ErrorHandler.shouldLogError(err)) {\n ErrorHandler.logUnhandledError(err, {\n headers: req.headers,\n method: req.method,\n url: req.originalUrl || req.url,\n }, 'Unhandled Express request error')\n }\n\n if (process.env.NODE_ENV === 'development') console.error(responseBody)\n\n if (res.headersSent) {\n next(err)\n\n return\n }\n\n const acceptsHeader = Array.isArray(req.headers.accept) ? req.headers.accept.join(',') : req.headers.accept ?? ''\n const expectsJson = acceptsHeader.includes('application/json') || req.originalUrl.startsWith('/api/')\n const code = ErrorHandler.normalizeStatusCode(responseBody.code)\n\n if (expectsJson) {\n res.status(code).json(responseBody)\n\n return\n }\n\n res.status(code).setHeader('Content-Type', 'text/html').send(renderError({\n message: String(responseBody.message),\n stack: typeof responseBody.stack === 'string' ? responseBody.stack : undefined,\n code,\n }))\n}\n\nexport default defaultErrorHandler\n","import { RequestException, importFile } from '@arkstack/common'\nimport express, { Router as ExpressRouter } from 'express'\n\nimport { ArkstackRouteListOptions } from '@arkstack/contract'\nimport { Router as ClearRouter } from 'clear-router/express'\nimport { type Route } from 'clear-router'\nimport { clearRouterExpressPlugin } from '@resora/plugin-clear-router'\nimport { join } from 'node:path'\nimport { registerPlugin } from 'resora'\nimport type { Handler, HttpContext, Middleware } from 'clear-router/types/express'\n\nregisterPlugin(clearRouterExpressPlugin)\nClearRouter.configure({\n inferParamName: true\n})\n\nexport class Router extends ClearRouter {\n static async bind (): Promise<ExpressRouter> {\n const router = express.Router()\n\n // Register API routes\n await ClearRouter.group('/api', async () => {\n await importFile(join(process.cwd(), 'src/routes/api.ts'))\n })\n\n // Register web routes\n await ClearRouter.group('/', async () => {\n await importFile(join(process.cwd(), 'src/routes/web.ts'))\n })\n\n // Apply the registered routes to the Express application\n ClearRouter.apply(router)\n\n // Handle unmatched routes\n router.all('/*splat', (req, _res, next) => {\n const url = req.originalUrl || req.url\n next(new RequestException(`Cannot find any route matching [${req.method}] ${url}`, 404))\n })\n\n return router\n }\n\n static async list (\n _options: ArkstackRouteListOptions = {}\n ): Promise<Array<Route<HttpContext, Middleware, Handler>>> {\n await this.bind()\n\n return this.allRoutes() as never\n }\n}\n","import express, { type ErrorRequestHandler, type Express, type Handler } from 'express'\n\nimport { ArkstackKitDriver, ArkstackMiddlewareConfig, PromiseOrValue } from '@arkstack/contract'\nimport { Logger } from '@arkstack/common'\nimport { defaultErrorHandler } from './error-handler'\n\nexport interface ExpressDriverOptions {\n bindRouter: (app: Express) => PromiseOrValue<void>;\n mountPublicAssets?: (app: Express, publicPath: string) => PromiseOrValue<void>;\n errorHandler?: ErrorRequestHandler | Handler;\n}\n\n/**\n * The ExpressDriver class implements the ArkstackKitDriver \n * contract for the Express framework.\n */\nexport class ExpressDriver extends ArkstackKitDriver<Express, Handler> {\n readonly name = 'express'\n private readonly options: ExpressDriverOptions\n\n /**\n * Creates an instance of ExpressDriver.\n * \n * @param options \n */\n constructor(options: ExpressDriverOptions) {\n super()\n this.options = options\n }\n\n /**\n * Creates an Express application instance.\n * \n * @returns \n */\n createApp (): Express {\n return express()\n }\n\n /**\n * Mounts static assets from the specified public path to the Express application.\n * \n * @param app \n * @param publicPath \n */\n mountPublicAssets (app: Express, publicPath: string): PromiseOrValue<void> {\n if (this.options.mountPublicAssets) {\n return this.options.mountPublicAssets(app, publicPath)\n }\n\n app.use(express.static(publicPath, {\n maxAge: '1y',\n immutable: true,\n setHeaders: (res) => {\n res.setHeader('Access-Control-Allow-Origin', '*')\n res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')\n },\n }))\n }\n\n /**\n * Binds the router to the Express application using the provided bindRouter function.\n * \n * @param app \n */\n bindRouter (app: Express): PromiseOrValue<void> {\n return this.options.bindRouter(app)\n }\n\n /**\n * Applies middleware to the Express application.\n * \n * @param app \n * @param middleware \n */\n applyMiddleware (\n app: Express,\n middleware: Handler | ArkstackMiddlewareConfig<Handler>,\n ): void {\n if (typeof middleware === 'function') {\n app.use(middleware)\n\n return\n }\n\n for (const [pos, entries] of Object.entries(middleware) as [string, Handler[]][]) {\n for (const entry of entries) {\n if (pos === 'after') {\n app.use(async (req, res, next) => {\n res.once('finish', async () => {\n await entry(req, res, next)\n })\n next()\n })\n } else {\n app.use(entry)\n }\n }\n }\n }\n\n /**\n * Registers an error handler middleware to the Express \n * application if provided in the options.\n * \n * @param app \n */\n registerErrorHandler (app: Express): void {\n app.use((this.options.errorHandler ?? defaultErrorHandler) as ErrorRequestHandler)\n }\n\n /**\n * Starts the Express server on the specified port.\n * \n * @param app \n * @param port \n */\n start (app: Express, port: number): void {\n app.listen(port, () => {\n Logger.log([\n ['Server is running on', 'white'],\n [`http://localhost:${port}`, 'cyan']\n ], ' ')\n })\n }\n}\n\nexport * from './error-handler'\nexport * from './Router'\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/middlewares/auth.ts","../../src/middlewares/formdata.ts","../../src/middlewares/request-logger.ts"],"sourcesContent":["import { Auth, AuthenticationException } from '@arkstack/auth'\n\nimport type { Handler } from 'express'\nimport { Hook } from '@arkstack/common'\n\nexport const auth: Handler = async (req, res, next) => {\n try {\n if (Hook.has('middleware:auth', 'before'))\n await Promise.resolve(Hook.get('middleware:auth', 'before')?.({ req, res }))\n\n const token = readBearerToken(req.headers.authorization)\n\n if (!token) {\n throw new AuthenticationException('Unauthenticated', { req, status: 401 })\n }\n\n const auth = Auth.make().setRequest(req)\n const user = await Auth.make().setRequest(req).authorizeToken(token)\n\n req.user = user\n req.auth = auth\n req.authUser = user\n req.authToken = token\n\n if (Hook.has('middleware:auth', 'after'))\n await Promise.resolve(Hook.get('middleware:auth', 'after')?.({ req, res }))\n\n next()\n } catch (error) {\n if (Hook.has('middleware:auth', 'error'))\n await Promise.resolve(Hook.get('middleware:auth', 'error')?.(error, {\n req,\n res\n }))\n\n next(error)\n }\n}\n\nconst readBearerToken = (authorization: string | string[] | undefined) => {\n const value = Array.isArray(authorization) ? authorization[0] : authorization\n\n if (!value?.startsWith('Bearer ')) {\n return null\n }\n\n return value.substring(7)\n}\n","import multer from 'multer'\n\nexport const formdata = multer({ storage: multer.memoryStorage() })","import { Logger, nodeEnv } from '@arkstack/common'\nimport { NextFunction, Request, Response } from 'express'\n\nconst colors: Record<string, 'green' | 'blue' | 'yellow' | 'red' | 'cyan'> = {\n GET: 'green',\n POST: 'blue',\n PUT: 'yellow',\n DELETE: 'red',\n PATCH: 'cyan',\n}\n\n/**\n * Middleware to log incoming requests and their response times.\n * \n * @param config Configuration options for the request logger middleware.\n * @param config.allowInProduction If true, the logger will also log requests in production environment. Default is false. \n * @returns \n */\nexport const requestLogger = ({\n allowInProduction = false,\n}: {\n allowInProduction?: boolean\n} = {}) => async (req: Request, res: Response, next: NextFunction) => {\n if (nodeEnv() === 'prod' && !allowInProduction) return next()\n\n const start = Date.now()\n\n const status = res.statusCode || 200\n const duration = Date.now() - start\n Logger.log([\n [`[${req.method}]`, colors[req.method] || 'white'],\n [req.url, 'cyan'],\n [status.toString(), status >= 500 ? 'red' : status >= 400 ? 'yellow' : 'green'],\n [`- ${duration}ms`, 'dim']\n ], ' ')\n\n next()\n}"],"mappings":";;;;AAKA,MAAa,OAAgB,OAAO,KAAK,KAAK,SAAS;CACnD,IAAI;EACA,IAAI,KAAK,IAAI,mBAAmB,SAAS,EACrC,MAAM,QAAQ,QAAQ,KAAK,IAAI,mBAAmB,SAAS,GAAG;GAAE;GAAK;GAAK,CAAC,CAAC;EAEhF,MAAM,QAAQ,gBAAgB,IAAI,QAAQ,cAAc;EAExD,IAAI,CAAC,OACD,MAAM,IAAI,wBAAwB,mBAAmB;GAAE;GAAK,QAAQ;GAAK,CAAC;EAG9E,MAAM,OAAO,KAAK,MAAM,CAAC,WAAW,IAAI;EACxC,MAAM,OAAO,MAAM,KAAK,MAAM,CAAC,WAAW,IAAI,CAAC,eAAe,MAAM;EAEpE,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,WAAW;EACf,IAAI,YAAY;EAEhB,IAAI,KAAK,IAAI,mBAAmB,QAAQ,EACpC,MAAM,QAAQ,QAAQ,KAAK,IAAI,mBAAmB,QAAQ,GAAG;GAAE;GAAK;GAAK,CAAC,CAAC;EAE/E,MAAM;UACD,OAAO;EACZ,IAAI,KAAK,IAAI,mBAAmB,QAAQ,EACpC,MAAM,QAAQ,QAAQ,KAAK,IAAI,mBAAmB,QAAQ,GAAG,OAAO;GAChE;GACA;GACH,CAAC,CAAC;EAEP,KAAK,MAAM;;;AAInB,MAAM,mBAAmB,kBAAiD;CACtE,MAAM,QAAQ,MAAM,QAAQ,cAAc,GAAG,cAAc,KAAK;CAEhE,IAAI,CAAC,OAAO,WAAW,UAAU,EAC7B,OAAO;CAGX,OAAO,MAAM,UAAU,EAAE;;;;AC5C7B,MAAa,WAAW,OAAO,EAAE,SAAS,OAAO,eAAe,EAAE,CAAA;;;ACClE,MAAM,SAAuE;CACzE,KAAK;CACL,MAAM;CACN,KAAK;CACL,QAAQ;CACR,OAAO;CACV;;;;;;;;AASD,MAAa,iBAAiB,EAC1B,oBAAoB,UAGpB,EAAE,KAAK,OAAO,KAAc,KAAe,SAAuB;CAClE,IAAI,SAAS,KAAK,UAAU,CAAC,mBAAmB,OAAO,MAAM;CAE7D,MAAM,QAAQ,KAAK,KAAK;CAExB,MAAM,SAAS,IAAI,cAAc;CACjC,MAAM,WAAW,KAAK,KAAK,GAAG;CAC9B,OAAO,IAAI;EACP,CAAC,IAAI,IAAI,OAAO,IAAI,OAAO,IAAI,WAAW,QAAQ;EAClD,CAAC,IAAI,KAAK,OAAO;EACjB,CAAC,OAAO,UAAU,EAAE,UAAU,MAAM,QAAQ,UAAU,MAAM,WAAW,QAAQ;EAC/E,CAAC,KAAK,SAAS,KAAK,MAAM;EAC7B,EAAE,IAAI;CAEP,MAAM"}
|