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