@arkstack/driver-express 0.12.10 → 0.12.12

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/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /// <reference path="./app.d.ts" />
2
+ import { Middleware as Middleware$1, MiddlewareConfig } from "./types.js";
2
3
  import { ErrorRequestHandler, Express, Handler, Router as Router$1 } from "express";
3
- import { ArkstackKitDriver, ArkstackMiddlewareConfig, ArkstackRouteListOptions, PromiseOrValue } from "@arkstack/contract";
4
- import { Router as Router$2 } from "clear-router/express";
4
+ import { ArkstackKitDriver, ArkstackRouteListOptions, PromiseOrValue } from "@arkstack/contract";
5
5
  import { Route } from "clear-router";
6
+ import { Router as Router$2 } from "clear-router/express";
6
7
  import { Handler as Handler$1, HttpContext, Middleware } from "clear-router/types/express";
7
8
 
8
9
  //#region src/error-handler.d.ts
@@ -58,7 +59,7 @@ declare class ExpressDriver extends ArkstackKitDriver<Express, Handler> {
58
59
  * @param app
59
60
  * @param middleware
60
61
  */
61
- applyMiddleware(app: Express, middleware: Handler | ArkstackMiddlewareConfig<Handler>): void;
62
+ applyMiddleware(app: Express, middleware: Middleware$1 | MiddlewareConfig): void;
62
63
  /**
63
64
  * Registers an error handler middleware to the Express
64
65
  * application if provided in the options.
package/dist/index.js CHANGED
@@ -1,18 +1,23 @@
1
1
  import express from "express";
2
2
  import { Arkstack, ArkstackKitDriver } from "@arkstack/contract";
3
- import { ErrorHandler, Logger, RequestException, importFile, renderError } from "@arkstack/common";
3
+ import { ErrorHandler, Logger, RequestException, isClass, renderError } from "@arkstack/common";
4
+ import { Request as Request$1 } from "clear-router";
5
+ import { definePlugin } from "clear-router/core";
6
+ import { definePlugin as definePlugin$1 } from "kanun";
7
+ import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
8
+ import { DB } from "arkormx";
9
+ import { dirname, join } from "node:path";
10
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
11
  import { Router as Router$2 } from "clear-router/express";
5
12
  import { clearRouterExpressPlugin } from "@resora/plugin-clear-router";
6
- import { join } from "node:path";
7
13
  import { registerPlugin } from "resora";
8
- import { stat } from "node:fs/promises";
9
14
  //#region src/error-handler.ts
10
15
  const webMiddlewareKey = Symbol.for("arkstack:http:web");
11
- const isRecord = (value) => {
16
+ const isRecord$1 = (value) => {
12
17
  return typeof value === "object" && value !== null;
13
18
  };
14
19
  const isWebRequest = (value) => {
15
- if (!isRecord(value)) return false;
20
+ if (!isRecord$1(value)) return false;
16
21
  return value[webMiddlewareKey] === true || value.arkstackWeb === true || isWebRequest(value.context) || isWebRequest(value.req);
17
22
  };
18
23
  const redirectBackTarget = (req) => {
@@ -21,7 +26,7 @@ const redirectBackTarget = (req) => {
21
26
  return typeof value === "string" && value ? value : "/";
22
27
  };
23
28
  const flashValidationState = async (err, req, errors) => {
24
- const session = req.httpSession ?? (isRecord(req.session) && typeof req.session.addValidationErrors === "function" ? req.session : void 0);
29
+ const session = req.httpSession ?? (isRecord$1(req.session) && typeof req.session.addValidationErrors === "function" ? req.session : void 0);
25
30
  if (!session) return;
26
31
  if (errors) session.addErrors?.(errors);
27
32
  else session.addValidationErrors?.(err);
@@ -57,6 +62,1144 @@ const defaultErrorHandler = async (err, req, res, next) => {
57
62
  }));
58
63
  };
59
64
  //#endregion
65
+ //#region ../http/dist/redirect-C1kZMywY.js
66
+ const unwrapRequestSource = (source) => {
67
+ if (source.headers) return source;
68
+ if (source.req) return source.req;
69
+ if (source.request) return source.request;
70
+ return source;
71
+ };
72
+ const normalizeHeaders = (headers) => {
73
+ const normalized = {};
74
+ if (!headers) return normalized;
75
+ if (isHeaders(headers)) {
76
+ headers.forEach((value, key) => {
77
+ normalized[key.toLowerCase()] = value;
78
+ });
79
+ return normalized;
80
+ }
81
+ for (const [key, value] of Object.entries(headers)) {
82
+ const normalizedValue = normalizeHeaderValue(value);
83
+ if (typeof normalizedValue === "string") normalized[key.toLowerCase()] = normalizedValue;
84
+ }
85
+ return normalized;
86
+ };
87
+ const normalizeHeaderValue = (value) => {
88
+ if (Array.isArray(value)) return value.join(", ");
89
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
90
+ return value ?? void 0;
91
+ };
92
+ const isHeaders = (value) => typeof Headers !== "undefined" && value instanceof Headers;
93
+ const isRecord = (value) => {
94
+ return !!value && typeof value === "object" && !Array.isArray(value);
95
+ };
96
+ /**
97
+ * Resolve Middleware
98
+ *
99
+ * @param middleware
100
+ * @returns
101
+ */
102
+ const resolveMiddleware = (middleware) => {
103
+ if (!middleware || typeof middleware === "function" && !isClass(middleware)) return middleware;
104
+ if (middleware && typeof middleware === "object" && !isClass(middleware) && "handle" in middleware) return middleware.handle.bind(middleware);
105
+ const instance = isClass(middleware) ? new middleware() : middleware;
106
+ if (instance && typeof instance.handle === "function") return instance.handle.bind(instance);
107
+ return middleware;
108
+ };
109
+ /**
110
+ * Represents an HTTP request, providing a consistent interface for accessing request data.
111
+ *
112
+ * @author 3m1n3nc3
113
+ */
114
+ var Request$1$1 = class Request$1$2 extends Request$1 {
115
+ headers;
116
+ ip;
117
+ source;
118
+ user;
119
+ authToken;
120
+ constructor(options = {}) {
121
+ super(options);
122
+ this.headers = normalizeHeaders(options.headers);
123
+ if (this.method) this.method = options.method;
124
+ if (this.url) this.url = options.url;
125
+ if (this.path) this.path = options.path;
126
+ this.ip = options.ip ?? null;
127
+ this.user = options.user;
128
+ this.authToken = options.authToken;
129
+ this.source = options.source;
130
+ globalThis.request = (key) => key ? this.input(key) : this;
131
+ }
132
+ static from(source) {
133
+ if (!source) return;
134
+ if (source instanceof Request$1$2) return source;
135
+ const request = unwrapRequestSource(source);
136
+ return new Request$1$2({
137
+ headers: request.headers,
138
+ method: request.method,
139
+ url: request.originalUrl ?? request.url,
140
+ path: request.path,
141
+ ip: request.ip ?? null,
142
+ user: request.user,
143
+ authToken: request.authToken,
144
+ source
145
+ });
146
+ }
147
+ header(name) {
148
+ return this.headers[name.toLowerCase()];
149
+ }
150
+ bearerToken() {
151
+ const authorization = this.header("authorization");
152
+ if (!authorization?.startsWith("Bearer ")) return null;
153
+ return authorization.substring(7);
154
+ }
155
+ setUser(user) {
156
+ this.user = user;
157
+ if (isRecord(this.source)) this.source.user = user;
158
+ return this;
159
+ }
160
+ };
161
+ var FlashBag = class {
162
+ bag = {};
163
+ sweepKeys = /* @__PURE__ */ new Set();
164
+ constructor(items) {
165
+ this.bag = { ...items || {} };
166
+ this.sweepKeys = new Set(Object.keys(this.bag));
167
+ }
168
+ put(key, value) {
169
+ this.bag[key] = value;
170
+ this.sweepKeys.delete(key);
171
+ return this;
172
+ }
173
+ set(key, value) {
174
+ return this.put(key, value);
175
+ }
176
+ get(key, defaultValue) {
177
+ return key in this.bag ? this.bag[key] : defaultValue;
178
+ }
179
+ has(key) {
180
+ if (Array.isArray(key)) return key.every((item) => this.has(item));
181
+ if (key) return key in this.bag;
182
+ return this.any();
183
+ }
184
+ any() {
185
+ return Object.keys(this.bag).length > 0;
186
+ }
187
+ isEmpty() {
188
+ return !this.any();
189
+ }
190
+ isNotEmpty() {
191
+ return this.any();
192
+ }
193
+ keys() {
194
+ return Object.keys(this.bag);
195
+ }
196
+ all() {
197
+ return { ...this.bag };
198
+ }
199
+ clear(key) {
200
+ if (Array.isArray(key)) {
201
+ for (const item of key) {
202
+ delete this.bag[item];
203
+ this.sweepKeys.delete(item);
204
+ }
205
+ return this;
206
+ }
207
+ if (key) {
208
+ delete this.bag[key];
209
+ this.sweepKeys.delete(key);
210
+ return this;
211
+ }
212
+ this.bag = {};
213
+ this.sweepKeys.clear();
214
+ return this;
215
+ }
216
+ forget(key) {
217
+ return this.clear(key);
218
+ }
219
+ markForSweep(keys = this.keys()) {
220
+ this.sweepKeys = new Set(keys);
221
+ return this;
222
+ }
223
+ sweep() {
224
+ for (const key of this.sweepKeys) delete this.bag[key];
225
+ this.sweepKeys = new Set(Object.keys(this.bag));
226
+ return this;
227
+ }
228
+ toJSON() {
229
+ return this.all();
230
+ }
231
+ };
232
+ const sessionKey = Symbol.for("arkstack:http:session");
233
+ const asMessageRecord = (value) => {
234
+ if (!isRecord(value)) return;
235
+ return value;
236
+ };
237
+ const callRecordMethod = (source, method) => {
238
+ if (typeof source[method] !== "function") return;
239
+ return asMessageRecord(source[method]());
240
+ };
241
+ const resolveMessageRecord = (source) => {
242
+ if (!isRecord(source)) return;
243
+ if (typeof source.getMessageBag === "function") {
244
+ const bag = source.getMessageBag();
245
+ if (bag && bag !== source) {
246
+ const messages = resolveMessageRecord(bag);
247
+ if (messages) return messages;
248
+ }
249
+ }
250
+ if (typeof source.errors === "function") {
251
+ const errors = source.errors();
252
+ const messages = resolveMessageRecord(errors) || asMessageRecord(errors);
253
+ if (messages) return messages;
254
+ }
255
+ return callRecordMethod(source, "getMessages") || callRecordMethod(source, "messagesRaw") || callRecordMethod(source, "toArray") || resolveMessageRecord(source.errors) || asMessageRecord(source.errors);
256
+ };
257
+ const getValidationIssueField = (issue) => {
258
+ if (typeof issue.field === "string") return issue.field;
259
+ if (typeof issue.attribute === "string") return issue.attribute;
260
+ if (typeof issue.key === "string") return issue.key;
261
+ if (typeof issue.path === "string") return issue.path;
262
+ if (Array.isArray(issue.path)) return issue.path.join(".") || "_";
263
+ return "_";
264
+ };
265
+ const toMessages = (value) => {
266
+ if (Array.isArray(value)) return value.flatMap((item) => toMessages(item));
267
+ if (value instanceof Error) return [value.message];
268
+ if (isRecord(value) && typeof value.message === "string") return [value.message];
269
+ if (value === null || typeof value === "undefined") return [];
270
+ return [String(value)];
271
+ };
272
+ const getPath = (source, key, defaultValue) => {
273
+ const value = key.split(".").reduce((current, part) => {
274
+ if (!isRecord(current) && !Array.isArray(current)) return;
275
+ return current[part];
276
+ }, source);
277
+ return typeof value === "undefined" ? defaultValue : value;
278
+ };
279
+ var ErrorBag = class ErrorBag extends FlashBag {
280
+ constructor(errors) {
281
+ super();
282
+ if (errors) {
283
+ this.merge(errors);
284
+ this.markForSweep();
285
+ }
286
+ }
287
+ add(field, message) {
288
+ const key = field || "_";
289
+ const messages = toMessages(message);
290
+ if (!messages.length) return this;
291
+ this.put(key, [...this.bag[key] || [], ...messages]);
292
+ return this;
293
+ }
294
+ addIf(condition, field, message) {
295
+ if (condition) this.add(field, message);
296
+ return this;
297
+ }
298
+ merge(errors) {
299
+ const incoming = resolveMessageRecord(errors) || (isRecord(errors) ? errors : void 0);
300
+ if (!incoming) return this.validation(errors);
301
+ for (const [field, messages] of Object.entries(incoming)) this.add(field, messages);
302
+ return this;
303
+ }
304
+ validation(error) {
305
+ if (!error) return this;
306
+ if (error instanceof ErrorBag) return this.merge(error);
307
+ const messages = resolveMessageRecord(error);
308
+ if (messages) return this.merge(messages);
309
+ if (Array.isArray(error)) {
310
+ for (const item of error) if (isRecord(item) && "message" in item) this.add(getValidationIssueField(item), item.message);
311
+ else this.add("_", item);
312
+ return this;
313
+ }
314
+ if (isRecord(error)) {
315
+ if (typeof error.errors === "function") return this.validation(error.errors());
316
+ if (error.errors) return this.validation(error.errors);
317
+ if (Array.isArray(error.issues)) return this.validation(error.issues);
318
+ if ("message" in error) return this.add(getValidationIssueField(error), error.message);
319
+ return this.merge(error);
320
+ }
321
+ if (error instanceof Error) return this.add("_", error.message);
322
+ return this.add("_", error);
323
+ }
324
+ keys() {
325
+ return Object.keys(this.bag);
326
+ }
327
+ get(field = "_") {
328
+ return [...this.bag[field] || []];
329
+ }
330
+ first(field) {
331
+ if (field) return this.bag[field]?.[0] || "";
332
+ return this.all()[0] || "";
333
+ }
334
+ has(field) {
335
+ if (Array.isArray(field)) return field.every((key) => this.has(key));
336
+ if (field) return (this.bag[field]?.length || 0) > 0;
337
+ return this.any();
338
+ }
339
+ hasAny(fields) {
340
+ return (Array.isArray(fields) ? fields : [fields]).some((key) => this.has(key));
341
+ }
342
+ missing(fields) {
343
+ return (Array.isArray(fields) ? fields : [fields]).every((key) => !this.has(key));
344
+ }
345
+ any() {
346
+ return Object.values(this.bag).some((messages) => messages.length > 0);
347
+ }
348
+ isEmpty() {
349
+ return !this.any();
350
+ }
351
+ isNotEmpty() {
352
+ return this.any();
353
+ }
354
+ count() {
355
+ return Object.values(this.bag).reduce((total, messages) => total + messages.length, 0);
356
+ }
357
+ all() {
358
+ return Object.values(this.bag).flat();
359
+ }
360
+ unique() {
361
+ return [...new Set(this.all())];
362
+ }
363
+ clear(field) {
364
+ super.clear(field);
365
+ return this;
366
+ }
367
+ forget(field) {
368
+ return this.clear(field);
369
+ }
370
+ messagesRaw() {
371
+ return this.toJSON();
372
+ }
373
+ getMessages() {
374
+ return this.messagesRaw();
375
+ }
376
+ getMessageBag() {
377
+ return this;
378
+ }
379
+ toArray() {
380
+ return this.toJSON();
381
+ }
382
+ toJSON() {
383
+ return Object.entries(this.bag).reduce((errors, [field, messages]) => {
384
+ errors[field] = [...messages];
385
+ return errors;
386
+ }, {});
387
+ }
388
+ };
389
+ var Session = class Session {
390
+ errors;
391
+ flashBag;
392
+ id;
393
+ data;
394
+ persistent;
395
+ saveQueue = Promise.resolve();
396
+ constructor(initial, persistent) {
397
+ const current = initial instanceof Session ? initial : void 0;
398
+ const state = current ? current.snapshot() : initial && ("data" in initial || "errors" in initial || "flash" in initial) ? initial : { data: initial };
399
+ this.id = persistent?.id ?? current?.id;
400
+ this.persistent = persistent ?? current?.persistent;
401
+ this.saveQueue = current?.saveQueue ?? this.saveQueue;
402
+ this.data = current ? current.data : { ...state.data || {} };
403
+ this.errors = current ? current.errors : state.errors instanceof ErrorBag ? state.errors : new ErrorBag(state.errors);
404
+ this.flashBag = current ? current.flashBag : state.flash instanceof FlashBag ? state.flash : new FlashBag(state.flash);
405
+ const helper = ((key) => key ? this.get(key) : this);
406
+ Object.assign(helper, {
407
+ get: this.get.bind(this),
408
+ put: this.put.bind(this),
409
+ set: this.set.bind(this),
410
+ has: this.has.bind(this),
411
+ forget: this.forget.bind(this),
412
+ clear: this.clear.bind(this),
413
+ all: this.all.bind(this),
414
+ flash: this.flash.bind(this),
415
+ getFlash: this.getFlash.bind(this),
416
+ hasErrors: this.hasErrors.bind(this),
417
+ clearErrors: this.clearErrors.bind(this),
418
+ errors: this.errors,
419
+ flashBag: this.flashBag
420
+ });
421
+ globalThis.session = helper;
422
+ }
423
+ snapshot() {
424
+ return {
425
+ data: this.all(),
426
+ errors: this.errors.toJSON(),
427
+ flash: this.flashBag.toJSON()
428
+ };
429
+ }
430
+ queuePersist() {
431
+ this.save();
432
+ }
433
+ async save() {
434
+ const payload = this.snapshot();
435
+ const previous = this.saveQueue.catch(() => void 0);
436
+ this.saveQueue = previous.then(async () => {
437
+ await this.persistent?.save(payload);
438
+ });
439
+ await this.saveQueue;
440
+ return this;
441
+ }
442
+ async destroy() {
443
+ this.data = {};
444
+ this.errors.clear();
445
+ this.flashBag.clear();
446
+ await this.persistent?.destroy?.();
447
+ return this;
448
+ }
449
+ /**
450
+ * Get an item from the session bag
451
+ *
452
+ * @param key
453
+ * @param defaultValue
454
+ * @returns
455
+ */
456
+ get(key, defaultValue) {
457
+ return key in this.data ? this.data[key] : defaultValue;
458
+ }
459
+ /**
460
+ * Add an item to the session bag
461
+ *
462
+ * @param key
463
+ * @param defaultValue
464
+ * @returns
465
+ */
466
+ put(key, value) {
467
+ this.data[key] = value;
468
+ this.queuePersist();
469
+ return this;
470
+ }
471
+ /**
472
+ * Add an item to the session bag
473
+ *
474
+ * @param key
475
+ * @param defaultValue
476
+ * @returns
477
+ */
478
+ set(key, value) {
479
+ return this.put(key, value);
480
+ }
481
+ /**
482
+ * Check if an item exist in the session bag
483
+ *
484
+ * @param key
485
+ * @returns
486
+ */
487
+ has(key) {
488
+ return key in this.data;
489
+ }
490
+ /**
491
+ * Remove an item from the session bag
492
+ *
493
+ * @param key
494
+ * @returns
495
+ */
496
+ forget(key) {
497
+ delete this.data[key];
498
+ this.queuePersist();
499
+ return this;
500
+ }
501
+ /**
502
+ * Clear the session bag
503
+ *
504
+ * @returns
505
+ */
506
+ clear() {
507
+ this.data = {};
508
+ this.errors.clear();
509
+ this.flashBag.clear();
510
+ this.queuePersist();
511
+ return this;
512
+ }
513
+ /**
514
+ * Get all items in the session bag
515
+ *
516
+ * @returns
517
+ */
518
+ all() {
519
+ return { ...this.data };
520
+ }
521
+ /**
522
+ * Add a flash item for the next request
523
+ *
524
+ * @param key
525
+ * @param value
526
+ * @returns
527
+ */
528
+ flash(key, value) {
529
+ this.flashBag.put(key, value);
530
+ this.queuePersist();
531
+ return this;
532
+ }
533
+ /**
534
+ * Get a flash item
535
+ *
536
+ * @param key
537
+ * @param defaultValue
538
+ * @returns
539
+ */
540
+ getFlash(key, defaultValue) {
541
+ return this.flashBag.get(key, defaultValue);
542
+ }
543
+ /**
544
+ * Sweep flashed data that was loaded for this request
545
+ *
546
+ * @returns
547
+ */
548
+ async sweepFlash() {
549
+ this.errors.sweep();
550
+ this.flashBag.sweep();
551
+ await this.save();
552
+ return this;
553
+ }
554
+ /**
555
+ * Add an error to the session error bag
556
+ *
557
+ * @param field
558
+ * @param message
559
+ * @returns
560
+ */
561
+ addError(field, message) {
562
+ this.errors.add(field, message);
563
+ this.queuePersist();
564
+ return this;
565
+ }
566
+ /**
567
+ * Add multiple errors to the session error bag
568
+ *
569
+ * @param errors
570
+ * @returns
571
+ */
572
+ addErrors(errors) {
573
+ this.errors.merge(errors);
574
+ this.queuePersist();
575
+ return this;
576
+ }
577
+ /**
578
+ * Add a validation error to the session error bag
579
+ *
580
+ * @param error
581
+ * @returns
582
+ */
583
+ addValidationErrors(error) {
584
+ this.errors.validation(error);
585
+ this.queuePersist();
586
+ return this;
587
+ }
588
+ /**
589
+ * Check if the session error bag has any errors
590
+ *
591
+ * @param field
592
+ * @returns
593
+ */
594
+ hasErrors(field) {
595
+ return this.errors.has(field);
596
+ }
597
+ /**
598
+ * Clear all errors in the session error bag
599
+ *
600
+ * @param field
601
+ * @returns
602
+ */
603
+ clearErrors(field) {
604
+ this.errors.clear(field);
605
+ this.queuePersist();
606
+ return this;
607
+ }
608
+ /**
609
+ * Parse session for views
610
+ *
611
+ * @returns
612
+ */
613
+ forView() {
614
+ return {
615
+ ...this.all(),
616
+ errors: this.errors,
617
+ flash: this.flashBag
618
+ };
619
+ }
620
+ /**
621
+ * Return session as json
622
+ *
623
+ * @returns
624
+ */
625
+ toJSON() {
626
+ return {
627
+ ...this.all(),
628
+ errors: this.errors.toJSON(),
629
+ flash: this.flashBag.toJSON()
630
+ };
631
+ }
632
+ };
633
+ const requestInput = () => {
634
+ const request = globalThis.request?.();
635
+ if (request instanceof Request$1$1) {
636
+ if (isRecord(request.body)) return request.body;
637
+ const source = isRecord(request.source) ? request.source : void 0;
638
+ if (source && typeof source.getBody === "function") return source.getBody() ?? {};
639
+ if (isRecord(source?.body)) return source.body;
640
+ }
641
+ if (isRecord(request) && typeof request.getBody === "function") return request.getBody() ?? {};
642
+ return isRecord(request?.body) ? request.body : {};
643
+ };
644
+ const old = (key, defaultValue) => {
645
+ const input = requestInput();
646
+ if (!key) return input;
647
+ return getPath(input, key, defaultValue);
648
+ };
649
+ const sweepRegisteredKey = Symbol.for("arkstack:http:flash-sweep-registered");
650
+ const attachSessionProperty = (target, session) => {
651
+ target.httpSession = session;
652
+ if (!("session" in target) || target.session instanceof Session) target.session = session;
653
+ };
654
+ const responseSource = (target) => {
655
+ return target.res ?? target.ctx?.res ?? target.response?.source ?? target.clearResponse?.source ?? target.context?.res ?? target.context?.response?.source;
656
+ };
657
+ const registerResponseFlashSweep = (target, session) => {
658
+ if (!isRecord(target)) return;
659
+ const current = session ?? getSession(target);
660
+ const res = responseSource(target);
661
+ if (!(current instanceof Session) || !isRecord(res) || typeof res.end !== "function" || res[sweepRegisteredKey]) return;
662
+ res[sweepRegisteredKey] = true;
663
+ const end = res.end.bind(res);
664
+ res.end = (...args) => {
665
+ current.sweepFlash().catch(() => void 0).finally(() => end(...args));
666
+ return res;
667
+ };
668
+ };
669
+ const attachViewState = (target, session) => {
670
+ attachSessionProperty(target, session);
671
+ target.errors = session.errors;
672
+ if (isRecord(target.req)) {
673
+ attachSessionProperty(target.req, session);
674
+ target.req.errors = session.errors;
675
+ target.req.old = old;
676
+ }
677
+ if (isRecord(target.res)) target.res.locals = {
678
+ ...target.res.locals || {},
679
+ session,
680
+ errors: session.errors,
681
+ flash: session.flashBag,
682
+ old
683
+ };
684
+ if (isRecord(target.response?.source)) target.response.source.locals = {
685
+ ...target.response.source.locals || {},
686
+ session,
687
+ errors: session.errors,
688
+ flash: session.flashBag,
689
+ old
690
+ };
691
+ if (isRecord(target.context)) {
692
+ attachSessionProperty(target.context, session);
693
+ target.context.errors = session.errors;
694
+ target.context.flash = session.flashBag;
695
+ target.context.old = old;
696
+ }
697
+ if (isRecord(target.state)) {
698
+ attachSessionProperty(target.state, session);
699
+ target.state.errors = session.errors;
700
+ target.state.flash = session.flashBag;
701
+ target.state.old = old;
702
+ }
703
+ if (typeof target.set === "function") {
704
+ target.set("session", session);
705
+ target.set("errors", session.errors);
706
+ target.set("flash", session.flashBag);
707
+ target.set("old", old);
708
+ }
709
+ };
710
+ /**
711
+ * Ensure a valid session exists
712
+ *
713
+ * @param ctx
714
+ * @param initial
715
+ * @returns
716
+ */
717
+ const ensureSession = (ctx, initial, persistent) => {
718
+ if (!isRecord(ctx)) return new Session(initial, persistent);
719
+ const existing = ctx[sessionKey] ?? (ctx.session instanceof Session ? ctx.session : void 0) ?? (isRecord(ctx.req) && ctx.req.httpSession instanceof Session ? ctx.req.httpSession : void 0);
720
+ const session = existing instanceof Session ? existing : new Session(initial, persistent);
721
+ ctx[sessionKey] = session;
722
+ attachViewState(ctx, session);
723
+ return session;
724
+ };
725
+ /**
726
+ * Get the current session
727
+ *
728
+ * @param ctx
729
+ * @returns
730
+ */
731
+ const getSession = (ctx) => {
732
+ if (!isRecord(ctx)) return;
733
+ const session = ctx[sessionKey] ?? (ctx.httpSession instanceof Session ? ctx.httpSession : void 0) ?? (ctx.session instanceof Session ? ctx.session : void 0) ?? (isRecord(ctx.req) && ctx.req.httpSession instanceof Session ? ctx.req.httpSession : void 0) ?? (isRecord(ctx.context) && ctx.context.httpSession instanceof Session ? ctx.context.httpSession : void 0);
734
+ return session instanceof Session ? session : void 0;
735
+ };
736
+ const generateSessionId = () => randomBytes(32).toString("base64url");
737
+ const signValue = (value, secret) => createHmac("sha256", secret).update(value).digest("base64url");
738
+ const encodeSignedValue = (value, secret) => `${value}.${signValue(value, secret)}`;
739
+ const decodeSignedValue = (value, secret) => {
740
+ if (!value) return void 0;
741
+ const index = value.lastIndexOf(".");
742
+ if (index < 1) return void 0;
743
+ const payload = value.slice(0, index);
744
+ const signature = value.slice(index + 1);
745
+ const expected = signValue(payload, secret);
746
+ const signatureBuffer = Buffer.from(signature);
747
+ const expectedBuffer = Buffer.from(expected);
748
+ if (signatureBuffer.length !== expectedBuffer.length) return void 0;
749
+ return timingSafeEqual(signatureBuffer, expectedBuffer) ? payload : void 0;
750
+ };
751
+ const decodeJson = (value) => {
752
+ if (!value) return void 0;
753
+ try {
754
+ return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
755
+ } catch {
756
+ return;
757
+ }
758
+ };
759
+ const parseCookies = (header) => {
760
+ return (Array.isArray(header) ? header.join("; ") : header || "").split(";").reduce((cookies, part) => {
761
+ const index = part.indexOf("=");
762
+ if (index < 0) return cookies;
763
+ const key = part.slice(0, index).trim();
764
+ const value = part.slice(index + 1).trim();
765
+ if (key) cookies[key] = decodeURIComponent(value);
766
+ return cookies;
767
+ }, {});
768
+ };
769
+ const getCookie = (context, name) => {
770
+ const ctx = isRecord(context.ctx) ? context.ctx : context;
771
+ const headers = (context.request || ctx.clearRequest || ctx.req || ctx.request)?.headers || ctx.headers || ctx.req?.headers || ctx.request?.headers;
772
+ return parseCookies(typeof headers?.get === "function" ? headers.get("cookie") : headers?.cookie)[name];
773
+ };
774
+ const serializeCookie = (name, value, options = {}) => {
775
+ const parts = [`${name}=${encodeURIComponent(value)}`];
776
+ if (typeof options.maxAge === "number") parts.push(`Max-Age=${Math.max(0, Math.floor(options.maxAge))}`);
777
+ if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
778
+ parts.push(`Path=${options.path || "/"}`);
779
+ if (options.domain) parts.push(`Domain=${options.domain}`);
780
+ if (options.httpOnly !== false) parts.push("HttpOnly");
781
+ if (options.secure) parts.push("Secure");
782
+ if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
783
+ return parts.join("; ");
784
+ };
785
+ const splitSetCookieHeader = (value) => {
786
+ return value.split(/,\s*(?=[^;,\s]+=)/).filter(Boolean);
787
+ };
788
+ const withoutCookie = (current, cookieName) => {
789
+ return (Array.isArray(current) ? current.flatMap((item) => splitSetCookieHeader(String(item))) : typeof current === "string" ? splitSetCookieHeader(current) : []).filter((cookie) => !cookie.trim().startsWith(`${cookieName}=`));
790
+ };
791
+ const upsertHeaderValue = (target, headerName, cookieName, value) => {
792
+ if (!target) return false;
793
+ if (typeof target.setHeader === "function") {
794
+ const next = [...withoutCookie(typeof target.getHeader === "function" ? target.getHeader(headerName) : void 0, cookieName), value];
795
+ target.setHeader(headerName, next);
796
+ return true;
797
+ }
798
+ if (target.headers && typeof target.headers.set === "function") {
799
+ const next = [...withoutCookie(target.headers.get(headerName), cookieName), value];
800
+ target.headers.set(headerName, next.join(", "));
801
+ return true;
802
+ }
803
+ if (typeof target.appendHeader === "function") {
804
+ target.appendHeader(headerName, value);
805
+ return true;
806
+ }
807
+ if (typeof target.append === "function") {
808
+ target.append(headerName, value);
809
+ return true;
810
+ }
811
+ return false;
812
+ };
813
+ const setCookie = (context, name, value, options = {}) => {
814
+ const ctx = isRecord(context.ctx) ? context.ctx : context;
815
+ const cookie = serializeCookie(name, value, options);
816
+ const response = context.response || ctx.clearResponse;
817
+ if (response?.headers && typeof response.headers.set === "function") {
818
+ const next = [...withoutCookie(response.headers.get("set-cookie"), name), cookie];
819
+ response.headers.set("set-cookie", next.join(", "));
820
+ }
821
+ upsertHeaderValue(response?.source, "Set-Cookie", name, cookie) || upsertHeaderValue(ctx.res, "Set-Cookie", name, cookie) || upsertHeaderValue(ctx.response, "Set-Cookie", name, cookie) || upsertHeaderValue(ctx.response?.source, "Set-Cookie", name, cookie) || upsertHeaderValue(ctx.event?.res, "Set-Cookie", name, cookie);
822
+ return cookie;
823
+ };
824
+ const byteLength = (value) => Buffer.byteLength(value, "utf8");
825
+ const serializeValue = (value) => {
826
+ if (value === null || typeof value === "undefined") return "N;";
827
+ if (typeof value === "boolean") return `b:${value ? 1 : 0};`;
828
+ if (typeof value === "number") return Number.isInteger(value) ? `i:${value};` : `d:${value};`;
829
+ if (typeof value === "string") return `s:${byteLength(value)}:"${value}";`;
830
+ if (Array.isArray(value)) return serializeEntries(value.map((item, index) => [index, item]));
831
+ if (typeof value === "object") return serializeEntries(Object.entries(value));
832
+ return serializeValue(String(value));
833
+ };
834
+ const serializeEntries = (entries) => {
835
+ return `a:${entries.length}:{${entries.map(([key, value]) => serializeValue(key) + serializeValue(value)).join("")}}`;
836
+ };
837
+ var Parser = class {
838
+ source;
839
+ offset = 0;
840
+ constructor(source) {
841
+ this.source = source;
842
+ }
843
+ parse() {
844
+ const type = this.source[this.offset];
845
+ this.offset += type === "N" ? 1 : 2;
846
+ switch (type) {
847
+ case "N":
848
+ this.expect(";");
849
+ return null;
850
+ case "b": return this.readUntil(";") === "1";
851
+ case "i": return Number.parseInt(this.readUntil(";"), 10);
852
+ case "d": return Number.parseFloat(this.readUntil(";"));
853
+ case "s": return this.parseString();
854
+ case "a": return this.parseArray();
855
+ default: throw new Error(`Unsupported serialized session value: ${type}`);
856
+ }
857
+ }
858
+ parseString() {
859
+ const length = Number.parseInt(this.readUntil(":"), 10);
860
+ this.expect("\"");
861
+ let end = this.offset;
862
+ let bytes = 0;
863
+ while (end < this.source.length && bytes < length) {
864
+ const char = this.source[end];
865
+ bytes += Buffer.byteLength(char, "utf8");
866
+ end += 1;
867
+ }
868
+ const value = this.source.slice(this.offset, end);
869
+ this.offset = end;
870
+ this.expect("\"");
871
+ this.expect(";");
872
+ return value;
873
+ }
874
+ parseArray() {
875
+ const length = Number.parseInt(this.readUntil(":"), 10);
876
+ this.expect("{");
877
+ const entries = [];
878
+ let sequential = true;
879
+ for (let index = 0; index < length; index += 1) {
880
+ const key = this.parse();
881
+ const value = this.parse();
882
+ entries.push([key, value]);
883
+ if (key !== index) sequential = false;
884
+ }
885
+ this.expect("}");
886
+ if (sequential) return entries.map(([, value]) => value);
887
+ return entries.reduce((record, [key, value]) => {
888
+ record[String(key)] = value;
889
+ return record;
890
+ }, {});
891
+ }
892
+ readUntil(token) {
893
+ const index = this.source.indexOf(token, this.offset);
894
+ if (index < 0) throw new Error("Invalid serialized session payload");
895
+ const value = this.source.slice(this.offset, index);
896
+ this.offset = index + token.length;
897
+ return value;
898
+ }
899
+ expect(token) {
900
+ if (this.source.slice(this.offset, this.offset + token.length) !== token) throw new Error("Invalid serialized session payload");
901
+ this.offset += token.length;
902
+ }
903
+ };
904
+ const encodeSessionPayload = (payload) => {
905
+ return serializeValue(payload);
906
+ };
907
+ const normalizeSessionPayload = (payload) => {
908
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return payload;
909
+ const record = payload;
910
+ for (const key of [
911
+ "data",
912
+ "errors",
913
+ "flash"
914
+ ]) if (Array.isArray(record[key]) && record[key].length === 0) record[key] = {};
915
+ return record;
916
+ };
917
+ const decodeSessionPayload = (value) => {
918
+ if (!value) return;
919
+ try {
920
+ return normalizeSessionPayload(new Parser(value).parse());
921
+ } catch {
922
+ return;
923
+ }
924
+ };
925
+ const keyFromSecret = (secret) => {
926
+ if (secret.startsWith("base64:")) {
927
+ const decoded = Buffer.from(secret.slice(7), "base64");
928
+ if (decoded.length === 32) return decoded;
929
+ }
930
+ const raw = Buffer.from(secret, "base64");
931
+ if (raw.length === 32) return raw;
932
+ return createHash("sha256").update(secret).digest();
933
+ };
934
+ const macFor = (iv, value, key) => {
935
+ return createHmac("sha256", key).update(iv + value).digest("hex");
936
+ };
937
+ const encryptSessionValue = (value, secret) => {
938
+ const key = keyFromSecret(secret);
939
+ const iv = randomBytes(16);
940
+ const ivValue = iv.toString("base64");
941
+ const cipher = createCipheriv("aes-256-cbc", key, iv);
942
+ const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]).toString("base64");
943
+ const payload = {
944
+ iv: ivValue,
945
+ value: encrypted,
946
+ mac: macFor(ivValue, encrypted, key),
947
+ tag: ""
948
+ };
949
+ return JSON.stringify(payload);
950
+ };
951
+ const decryptSessionValue = (payload, secret) => {
952
+ if (!payload) return;
953
+ try {
954
+ const decoded = JSON.parse(payload.startsWith("{") ? payload : Buffer.from(payload, "base64").toString("utf8"));
955
+ if (!decoded.iv || !decoded.value || !decoded.mac) return;
956
+ const key = keyFromSecret(secret);
957
+ const expected = macFor(decoded.iv, decoded.value, key);
958
+ const actualBuffer = Buffer.from(decoded.mac);
959
+ const expectedBuffer = Buffer.from(expected);
960
+ if (actualBuffer.length !== expectedBuffer.length || !timingSafeEqual(actualBuffer, expectedBuffer)) return;
961
+ const decipher = createDecipheriv("aes-256-cbc", key, Buffer.from(decoded.iv, "base64"));
962
+ return Buffer.concat([decipher.update(Buffer.from(decoded.value, "base64")), decipher.final()]).toString("utf8");
963
+ } catch {
964
+ return;
965
+ }
966
+ };
967
+ const defaultSecret = () => String(process.env.SESSION_SECRET || process.env.APP_KEY || "arkstack-session-secret");
968
+ const defaultcookie_options = (ttl) => ({
969
+ httpOnly: true,
970
+ sameSite: "Lax",
971
+ secure: process.env.NODE_ENV === "production",
972
+ path: "/",
973
+ maxAge: ttl
974
+ });
975
+ var BaseSessionDriver = class {
976
+ cookie;
977
+ secret;
978
+ ttl;
979
+ cookie_options;
980
+ constructor(options = {}) {
981
+ this.cookie = options.cookie || "arkstack_session";
982
+ this.secret = options.secret || defaultSecret();
983
+ this.ttl = options.ttl;
984
+ this.cookie_options = {
985
+ ...defaultcookie_options(options.ttl),
986
+ ...options.cookie_options || {}
987
+ };
988
+ }
989
+ readSessionId(context) {
990
+ return decodeSignedValue(getCookie(context, this.cookie), this.secret);
991
+ }
992
+ encryptPayload(value) {
993
+ return encryptSessionValue(value, this.secret);
994
+ }
995
+ decryptPayload(value) {
996
+ return decryptSessionValue(value, this.secret);
997
+ }
998
+ writeSessionId(context, id) {
999
+ setCookie(context, this.cookie, encodeSignedValue(id, this.secret), this.cookie_options);
1000
+ }
1001
+ };
1002
+ var CookieSessionDriver = class extends BaseSessionDriver {
1003
+ async start(context) {
1004
+ const cookie = getCookie(context, this.cookie);
1005
+ const decoded = this.decryptPayload(cookie) ?? decodeSignedValue(cookie, this.secret);
1006
+ const payload = decodeSessionPayload(decoded) ?? decodeJson(decoded);
1007
+ const id = payload?.id || generateSessionId();
1008
+ const state = payload ? {
1009
+ data: payload.data,
1010
+ errors: payload.errors,
1011
+ flash: payload.flash
1012
+ } : void 0;
1013
+ const save = async (next) => {
1014
+ setCookie(context, this.cookie, this.encryptPayload(encodeSessionPayload({
1015
+ id,
1016
+ ...next
1017
+ })), this.cookie_options);
1018
+ };
1019
+ const destroy = async () => {
1020
+ setCookie(context, this.cookie, "", {
1021
+ ...this.cookie_options,
1022
+ maxAge: 0,
1023
+ expires: /* @__PURE__ */ new Date(0)
1024
+ });
1025
+ };
1026
+ return {
1027
+ id,
1028
+ state,
1029
+ save,
1030
+ destroy
1031
+ };
1032
+ }
1033
+ };
1034
+ var DatabaseSessionDriver = class extends BaseSessionDriver {
1035
+ tableName;
1036
+ constructor(options = {}) {
1037
+ super(options);
1038
+ this.tableName = options.table || "sessions";
1039
+ }
1040
+ table() {
1041
+ return DB.table(this.tableName);
1042
+ }
1043
+ async start(context) {
1044
+ const id = this.readSessionId(context) || generateSessionId();
1045
+ this.writeSessionId(context, id);
1046
+ const row = await this.table().where({ id }).first();
1047
+ const state = isRecord(row) && typeof row.payload === "string" ? decodeSessionPayload(this.decryptPayload(row.payload) ?? row.payload) ?? decodeJson(this.decryptPayload(row.payload) ?? row.payload) : isRecord(row?.payload) ? row.payload : void 0;
1048
+ const save = async (payload) => {
1049
+ const now = /* @__PURE__ */ new Date();
1050
+ const values = {
1051
+ id,
1052
+ payload: this.encryptPayload(encodeSessionPayload(payload)),
1053
+ updatedAt: now,
1054
+ expiresAt: this.ttl ? new Date(now.getTime() + this.ttl * 1e3) : null
1055
+ };
1056
+ if (await this.table().where({ id }).first()) await this.table().where({ id }).update(values);
1057
+ else await this.table().insert({
1058
+ ...values,
1059
+ createdAt: now
1060
+ });
1061
+ this.writeSessionId(context, id);
1062
+ };
1063
+ const destroy = async () => {
1064
+ await this.table().where({ id }).delete();
1065
+ setCookie(context, this.cookie, "", {
1066
+ ...this.cookie_options,
1067
+ maxAge: 0,
1068
+ expires: /* @__PURE__ */ new Date(0)
1069
+ });
1070
+ };
1071
+ return {
1072
+ id,
1073
+ state,
1074
+ save,
1075
+ destroy
1076
+ };
1077
+ }
1078
+ };
1079
+ var FileSessionDriver = class extends BaseSessionDriver {
1080
+ directory;
1081
+ constructor(options = {}) {
1082
+ super(options);
1083
+ this.directory = options.directory || join(Arkstack.rootDir(), "storage", "framework", "sessions");
1084
+ }
1085
+ path(id) {
1086
+ return join(this.directory, id);
1087
+ }
1088
+ async start(context) {
1089
+ const id = this.readSessionId(context) || generateSessionId();
1090
+ this.writeSessionId(context, id);
1091
+ let state;
1092
+ try {
1093
+ const contents = await readFile(this.path(id), "utf8");
1094
+ const payload = this.decryptPayload(contents) ?? contents;
1095
+ state = decodeSessionPayload(payload) ?? JSON.parse(payload);
1096
+ } catch {
1097
+ state = void 0;
1098
+ }
1099
+ const save = async (payload) => {
1100
+ const path = this.path(id);
1101
+ await mkdir(dirname(path), { recursive: true });
1102
+ await writeFile(path, this.encryptPayload(encodeSessionPayload(payload)), "utf8");
1103
+ this.writeSessionId(context, id);
1104
+ };
1105
+ const destroy = async () => {
1106
+ await rm(this.path(id), { force: true });
1107
+ setCookie(context, this.cookie, "", {
1108
+ ...this.cookie_options,
1109
+ maxAge: 0,
1110
+ expires: /* @__PURE__ */ new Date(0)
1111
+ });
1112
+ };
1113
+ return {
1114
+ id,
1115
+ state,
1116
+ save,
1117
+ destroy
1118
+ };
1119
+ }
1120
+ };
1121
+ let configuredDriver;
1122
+ const readAppSessionConfig = () => {
1123
+ try {
1124
+ if (!globalThis.config) return;
1125
+ return {
1126
+ driver: config("session.driver", "cookie"),
1127
+ cookie: config("session.cookie", "arkstack_session"),
1128
+ secret: config("session.secret"),
1129
+ ttl: config("session.ttl", 3600 * 24 * 7),
1130
+ cookie_options: {
1131
+ path: config("session.path", "/"),
1132
+ httpOnly: config("session.http_only", true),
1133
+ secure: config("session.secure", true),
1134
+ sameSite: config("session.same_site", "Lax")
1135
+ },
1136
+ file: { directory: config("session.directory") },
1137
+ database: { table: config("session.table", "sessions") }
1138
+ };
1139
+ } catch {
1140
+ return;
1141
+ }
1142
+ };
1143
+ const createSessionDriver = (config = {}) => {
1144
+ if (config.driver && typeof config.driver !== "string") return config.driver;
1145
+ const common = {
1146
+ cookie: config.cookie,
1147
+ secret: config.secret,
1148
+ ttl: config.ttl,
1149
+ cookie_options: config.cookie_options
1150
+ };
1151
+ switch (config.driver || "cookie") {
1152
+ case "file": return new FileSessionDriver({
1153
+ ...common,
1154
+ directory: config.file?.directory
1155
+ });
1156
+ case "database": return new DatabaseSessionDriver({
1157
+ ...common,
1158
+ table: config.database?.table
1159
+ });
1160
+ default: return new CookieSessionDriver(common);
1161
+ }
1162
+ };
1163
+ const getSessionDriver = () => {
1164
+ if (!configuredDriver) configuredDriver = createSessionDriver(readAppSessionConfig());
1165
+ return configuredDriver;
1166
+ };
1167
+ definePlugin({
1168
+ name: "arkstack-http",
1169
+ setup: ({ bind, useHttpContext }) => {
1170
+ bind(Session, async ({ ctx }) => {
1171
+ const existing = getSession(ctx);
1172
+ if (existing) return existing;
1173
+ const persistent = await getSessionDriver().start(ctx);
1174
+ const session = ensureSession(ctx, persistent.state, persistent);
1175
+ attachViewState(ctx, session);
1176
+ registerResponseFlashSweep(ctx, session);
1177
+ return session;
1178
+ });
1179
+ useHttpContext((context) => {
1180
+ const session = getSession(context.ctx);
1181
+ if (session) {
1182
+ context.httpSession = session;
1183
+ if (!("session" in context) || context.session instanceof Session) context.session = session;
1184
+ context.errors = session.errors;
1185
+ attachViewState(context.ctx, session);
1186
+ attachViewState(context, session);
1187
+ registerResponseFlashSweep(context, session);
1188
+ } else delete globalThis.session;
1189
+ globalThis.request = (key) => key ? context.request.input(key) : context.request;
1190
+ });
1191
+ }
1192
+ });
1193
+ definePlugin$1({
1194
+ name: "kanun-session-plugin",
1195
+ install({ onValidationError }) {
1196
+ onValidationError((validator) => {
1197
+ const currentSession = globalThis.session?.();
1198
+ if (currentSession instanceof Session) currentSession.addValidationErrors(validator);
1199
+ });
1200
+ }
1201
+ });
1202
+ //#endregion
60
1203
  //#region src/Router.ts
61
1204
  registerPlugin(clearRouterExpressPlugin);
62
1205
  Router$2.configure({ inferParamName: true });
@@ -64,14 +1207,10 @@ var Router = class extends Router$2 {
64
1207
  static async bind() {
65
1208
  const router = express.Router();
66
1209
  try {
67
- if ((await stat(join(Arkstack.rootDir(), "src/routes/api.ts"))).isFile()) await Router$2.group("/api", async () => {
68
- await importFile(join(Arkstack.rootDir(), "src/routes/api.ts"));
69
- });
1210
+ await Router$2.group("/api", join(Arkstack.rootDir(), "src/routes/api.ts"));
70
1211
  } catch {}
71
1212
  try {
72
- if ((await stat(join(Arkstack.rootDir(), "src/routes/web.ts"))).isFile()) await Router$2.group("/", async () => {
73
- await importFile(join(Arkstack.rootDir(), "src/routes/web.ts"));
74
- });
1213
+ await Router$2.group("/", join(Arkstack.rootDir(), "src/routes/web.ts"));
75
1214
  } catch {}
76
1215
  Router$2.apply(router);
77
1216
  router.all("/*splat", (req, _res, next) => {
@@ -146,16 +1285,19 @@ var ExpressDriver = class extends ArkstackKitDriver {
146
1285
  applyMiddleware(app, middleware) {
147
1286
  if (!middleware) return;
148
1287
  if (typeof middleware === "function") {
149
- app.use(middleware);
1288
+ app.use(resolveMiddleware(middleware));
150
1289
  return;
151
1290
  }
152
- for (const [pos, entries] of Object.entries(middleware)) for (const entry of entries) if (pos === "after") app.use(async (req, res, next) => {
153
- res.once("finish", async () => {
154
- await entry(req, res, next);
1291
+ for (const [pos, entries] of Object.entries(middleware)) for (const instance of entries) {
1292
+ const entry = resolveMiddleware(instance);
1293
+ if (pos === "after") app.use(async (req, res, next) => {
1294
+ res.once("finish", async () => {
1295
+ await entry(req, res, next);
1296
+ });
1297
+ next();
155
1298
  });
156
- next();
157
- });
158
- else app.use(entry);
1299
+ else app.use(entry);
1300
+ }
159
1301
  }
160
1302
  /**
161
1303
  * Registers an error handler middleware to the Express
@@ -5,9 +5,22 @@ import { ValueDeterminingMiddleware } from "express-rate-limit";
5
5
 
6
6
  //#region src/middlewares/auth.d.ts
7
7
  declare const auth: Handler;
8
+ declare class AuthMiddleware {
9
+ handler(req: Request, res: Response, next: NextFunction): unknown;
10
+ }
8
11
  //#endregion
9
12
  //#region src/middlewares/formdata.d.ts
10
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
+ }
11
24
  //#endregion
12
25
  //#region src/middlewares/limiter.d.ts
13
26
  /**
@@ -33,5 +46,12 @@ declare const requestLogger: ({
33
46
  }?: {
34
47
  allowInProduction?: boolean;
35
48
  }) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
49
+ declare class RequestLoggerMiddleware {
50
+ private options;
51
+ constructor(options?: {
52
+ allowInProduction?: boolean;
53
+ });
54
+ handler(req: Request, res: Response, next: NextFunction): Promise<void>;
55
+ }
36
56
  //#endregion
37
- export { auth, formdata, limiter, requestLogger };
57
+ export { AuthMiddleware, FormDataMiddleware, RequestLoggerMiddleware, auth, formdata, limiter, requestLogger };
@@ -34,6 +34,11 @@ const auth = async (req, res, next) => {
34
34
  next(error);
35
35
  }
36
36
  };
37
+ var AuthMiddleware = class {
38
+ handler(req, res, next) {
39
+ return auth(req, res, next);
40
+ }
41
+ };
37
42
  const readBearerToken = (authorization) => {
38
43
  const value = Array.isArray(authorization) ? authorization[0] : authorization;
39
44
  if (!value?.startsWith("Bearer ")) return null;
@@ -42,6 +47,31 @@ const readBearerToken = (authorization) => {
42
47
  //#endregion
43
48
  //#region src/middlewares/formdata.ts
44
49
  const formdata = multer({ storage: multer.memoryStorage() });
50
+ var FormDataMiddleware = class {
51
+ type;
52
+ name;
53
+ count;
54
+ options;
55
+ constructor(type, name, count, options) {
56
+ this.type = type;
57
+ this.name = name;
58
+ this.count = count;
59
+ this.options = options;
60
+ }
61
+ handler(req, res, next) {
62
+ let inst;
63
+ const options = this.options ?? (typeof this.count === "object" && "storage" in this.count ? this.count : {});
64
+ const formdata = multer({
65
+ storage: multer.memoryStorage(),
66
+ ...options
67
+ });
68
+ if (this.type === "any" || this.type === "none") inst = formdata.any();
69
+ else if (this.type === "array") inst = formdata.array(this.name, this.count);
70
+ else if (this.type === "fields") inst = formdata.fields(this.name);
71
+ else inst = formdata.single(this.name);
72
+ return inst.call(inst, req, res, next);
73
+ }
74
+ };
45
75
  //#endregion
46
76
  //#region src/Exceptions/RateLimitExceededException.ts
47
77
  var RateLimitExceededException = class extends Exception {
@@ -104,5 +134,15 @@ const requestLogger = ({ allowInProduction = false } = {}) => async (req, res, n
104
134
  ], " ");
105
135
  next();
106
136
  };
137
+ var RequestLoggerMiddleware = class {
138
+ options;
139
+ constructor(options = {}) {
140
+ this.options = options;
141
+ }
142
+ handler(req, res, next) {
143
+ const inst = requestLogger(this.options);
144
+ return inst.call(inst, req, res, next);
145
+ }
146
+ };
107
147
  //#endregion
108
- export { auth, formdata, limiter, requestLogger };
148
+ export { AuthMiddleware, FormDataMiddleware, RequestLoggerMiddleware, auth, formdata, limiter, requestLogger };
package/dist/types.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { Handler } from "express";
2
2
  import { ArkstackMiddlewareConfig } from "@arkstack/contract";
3
+ import { ClassMiddleware } from "clear-router/types/basic";
3
4
 
4
5
  //#region src/types.d.ts
5
- type MiddlewareConfig = ArkstackMiddlewareConfig<Handler>;
6
+ type Middleware = Handler | ClassMiddleware<Handler>;
7
+ type MiddlewareConfig = ArkstackMiddlewareConfig<Middleware>;
6
8
  //#endregion
7
- export { MiddlewareConfig };
9
+ export { Middleware, MiddlewareConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/driver-express",
3
- "version": "0.12.10",
3
+ "version": "0.12.12",
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",
@@ -42,13 +42,13 @@
42
42
  "express-rate-limit": "^8.4.1",
43
43
  "@resora/plugin-clear-router": "^1.0.37",
44
44
  "resora": "^1.3.6",
45
- "@arkstack/contract": "^0.12.10"
45
+ "@arkstack/contract": "^0.12.12"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "express": "^5.2.1",
49
- "@arkstack/common": "^0.12.10",
50
- "@arkstack/foundry": "^0.12.10",
51
- "@arkstack/auth": "^0.12.10"
49
+ "@arkstack/auth": "^0.12.12",
50
+ "@arkstack/common": "^0.12.12",
51
+ "@arkstack/foundry": "^0.12.12"
52
52
  },
53
53
  "peerDependenciesMeta": {
54
54
  "@arkstack/auth": {