@arkstack/driver-express 0.12.9 → 0.12.11
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 +4 -3
- package/dist/index.js +1151 -19
- package/dist/middlewares/index.d.ts +21 -1
- package/dist/middlewares/index.js +41 -1
- package/dist/types.d.ts +4 -2
- package/package.json +7 -7
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,
|
|
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:
|
|
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,
|
|
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,1134 @@ const defaultErrorHandler = async (err, req, res, next) => {
|
|
|
57
62
|
}));
|
|
58
63
|
};
|
|
59
64
|
//#endregion
|
|
65
|
+
//#region ../http/dist/redirect-Bwrh6IHq.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.session;
|
|
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, ({ ctx }) => ensureSession(ctx));
|
|
1171
|
+
useHttpContext(async (context) => {
|
|
1172
|
+
globalThis.request = (key) => key ? context.request.input(key) : context.request;
|
|
1173
|
+
const persistent = await getSessionDriver().start(context);
|
|
1174
|
+
const session = ensureSession(context.ctx, persistent.state, persistent);
|
|
1175
|
+
context.httpSession = session;
|
|
1176
|
+
if (!("session" in context) || context.session instanceof Session) context.session = session;
|
|
1177
|
+
context.errors = session.errors;
|
|
1178
|
+
attachViewState(context, session);
|
|
1179
|
+
registerResponseFlashSweep(context, session);
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
definePlugin$1({
|
|
1184
|
+
name: "kanun-session-plugin",
|
|
1185
|
+
install({ onValidationError }) {
|
|
1186
|
+
onValidationError((validator) => {
|
|
1187
|
+
const currentSession = globalThis.session?.();
|
|
1188
|
+
if (currentSession instanceof Session) currentSession.addValidationErrors(validator);
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
//#endregion
|
|
60
1193
|
//#region src/Router.ts
|
|
61
1194
|
registerPlugin(clearRouterExpressPlugin);
|
|
62
1195
|
Router$2.configure({ inferParamName: true });
|
|
@@ -64,14 +1197,10 @@ var Router = class extends Router$2 {
|
|
|
64
1197
|
static async bind() {
|
|
65
1198
|
const router = express.Router();
|
|
66
1199
|
try {
|
|
67
|
-
|
|
68
|
-
await importFile(join(Arkstack.rootDir(), "src/routes/api.ts"));
|
|
69
|
-
});
|
|
1200
|
+
await Router$2.group("/api", join(Arkstack.rootDir(), "src/routes/api.ts"));
|
|
70
1201
|
} catch {}
|
|
71
1202
|
try {
|
|
72
|
-
|
|
73
|
-
await importFile(join(Arkstack.rootDir(), "src/routes/web.ts"));
|
|
74
|
-
});
|
|
1203
|
+
await Router$2.group("/", join(Arkstack.rootDir(), "src/routes/web.ts"));
|
|
75
1204
|
} catch {}
|
|
76
1205
|
Router$2.apply(router);
|
|
77
1206
|
router.all("/*splat", (req, _res, next) => {
|
|
@@ -146,16 +1275,19 @@ var ExpressDriver = class extends ArkstackKitDriver {
|
|
|
146
1275
|
applyMiddleware(app, middleware) {
|
|
147
1276
|
if (!middleware) return;
|
|
148
1277
|
if (typeof middleware === "function") {
|
|
149
|
-
app.use(middleware);
|
|
1278
|
+
app.use(resolveMiddleware(middleware));
|
|
150
1279
|
return;
|
|
151
1280
|
}
|
|
152
|
-
for (const [pos, entries] of Object.entries(middleware)) for (const
|
|
153
|
-
|
|
154
|
-
|
|
1281
|
+
for (const [pos, entries] of Object.entries(middleware)) for (const instance of entries) {
|
|
1282
|
+
const entry = resolveMiddleware(instance);
|
|
1283
|
+
if (pos === "after") app.use(async (req, res, next) => {
|
|
1284
|
+
res.once("finish", async () => {
|
|
1285
|
+
await entry(req, res, next);
|
|
1286
|
+
});
|
|
1287
|
+
next();
|
|
155
1288
|
});
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
else app.use(entry);
|
|
1289
|
+
else app.use(entry);
|
|
1290
|
+
}
|
|
159
1291
|
}
|
|
160
1292
|
/**
|
|
161
1293
|
* 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
|
|
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.
|
|
3
|
+
"version": "0.12.11",
|
|
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",
|
|
@@ -38,17 +38,17 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"multer": "^2.1.1",
|
|
41
|
-
"clear-router": "^2.8.
|
|
41
|
+
"clear-router": "^2.8.6",
|
|
42
42
|
"express-rate-limit": "^8.4.1",
|
|
43
|
-
"@resora/plugin-clear-router": "^1.0.
|
|
43
|
+
"@resora/plugin-clear-router": "^1.0.37",
|
|
44
44
|
"resora": "^1.3.6",
|
|
45
|
-
"@arkstack/contract": "^0.12.
|
|
45
|
+
"@arkstack/contract": "^0.12.11"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"express": "^5.2.1",
|
|
49
|
-
"@arkstack/auth": "^0.12.
|
|
50
|
-
"@arkstack/common": "^0.12.
|
|
51
|
-
"@arkstack/foundry": "^0.12.
|
|
49
|
+
"@arkstack/auth": "^0.12.11",
|
|
50
|
+
"@arkstack/common": "^0.12.11",
|
|
51
|
+
"@arkstack/foundry": "^0.12.11"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@arkstack/auth": {
|