@c9up/ream 0.1.33 → 0.2.0
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/Application.d.ts +68 -0
- package/dist/Application.d.ts.map +1 -1
- package/dist/Application.js +142 -6
- package/dist/Application.js.map +1 -1
- package/dist/ConfigLoader.d.ts +4 -0
- package/dist/ConfigLoader.d.ts.map +1 -1
- package/dist/ConfigLoader.js +0 -8
- package/dist/ConfigLoader.js.map +1 -1
- package/dist/HealthCheck.d.ts +14 -7
- package/dist/HealthCheck.d.ts.map +1 -1
- package/dist/HealthCheck.js +21 -12
- package/dist/HealthCheck.js.map +1 -1
- package/dist/HttpKernel.d.ts +12 -0
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +15 -4
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts +8 -0
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +11 -0
- package/dist/Ignitor.js.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.d.ts +13 -2
- package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.js +50 -30
- package/dist/bodyparser/BodyParserMiddleware.js.map +1 -1
- package/dist/bodyparser/MultipartFile.d.ts +65 -6
- package/dist/bodyparser/MultipartFile.d.ts.map +1 -1
- package/dist/bodyparser/MultipartFile.js +100 -7
- package/dist/bodyparser/MultipartFile.js.map +1 -1
- package/dist/console/BaseCommand.d.ts +8 -1
- package/dist/console/BaseCommand.d.ts.map +1 -1
- package/dist/console/BaseCommand.js +1 -1
- package/dist/console/BaseCommand.js.map +1 -1
- package/dist/console/Kernel.d.ts +15 -0
- package/dist/console/Kernel.d.ts.map +1 -1
- package/dist/console/Kernel.js +17 -0
- package/dist/console/Kernel.js.map +1 -1
- package/dist/directories.d.ts +52 -0
- package/dist/directories.d.ts.map +1 -0
- package/dist/directories.js +38 -0
- package/dist/directories.js.map +1 -0
- package/dist/events/Emitter.d.ts +49 -6
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +117 -7
- package/dist/events/Emitter.js.map +1 -1
- package/dist/events/EventsBuffer.d.ts +47 -0
- package/dist/events/EventsBuffer.d.ts.map +1 -0
- package/dist/events/EventsBuffer.js +107 -0
- package/dist/events/EventsBuffer.js.map +1 -0
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +1 -0
- package/dist/events/index.js.map +1 -1
- package/dist/health/BaseCheck.d.ts +19 -0
- package/dist/health/BaseCheck.d.ts.map +1 -0
- package/dist/health/BaseCheck.js +24 -0
- package/dist/health/BaseCheck.js.map +1 -0
- package/dist/health/HealthChecks.d.ts +15 -0
- package/dist/health/HealthChecks.d.ts.map +1 -0
- package/dist/health/HealthChecks.js +69 -0
- package/dist/health/HealthChecks.js.map +1 -0
- package/dist/health/Result.d.ts +27 -0
- package/dist/health/Result.d.ts.map +1 -0
- package/dist/health/Result.js +55 -0
- package/dist/health/Result.js.map +1 -0
- package/dist/health/checks/DiskSpaceCheck.d.ts +29 -0
- package/dist/health/checks/DiskSpaceCheck.d.ts.map +1 -0
- package/dist/health/checks/DiskSpaceCheck.js +58 -0
- package/dist/health/checks/DiskSpaceCheck.js.map +1 -0
- package/dist/health/checks/MemoryHeapCheck.d.ts +21 -0
- package/dist/health/checks/MemoryHeapCheck.d.ts.map +1 -0
- package/dist/health/checks/MemoryHeapCheck.js +55 -0
- package/dist/health/checks/MemoryHeapCheck.js.map +1 -0
- package/dist/health/checks/MemoryRSSCheck.d.ts +21 -0
- package/dist/health/checks/MemoryRSSCheck.d.ts.map +1 -0
- package/dist/health/checks/MemoryRSSCheck.js +55 -0
- package/dist/health/checks/MemoryRSSCheck.js.map +1 -0
- package/dist/health/checks/memory.d.ts +24 -0
- package/dist/health/checks/memory.d.ts.map +1 -0
- package/dist/health/checks/memory.js +88 -0
- package/dist/health/checks/memory.js.map +1 -0
- package/dist/health/index.d.ts +9 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +8 -0
- package/dist/health/index.js.map +1 -0
- package/dist/health/tracing.d.ts +12 -0
- package/dist/health/tracing.d.ts.map +1 -0
- package/dist/health/tracing.js +11 -0
- package/dist/health/tracing.js.map +1 -0
- package/dist/health/types.d.ts +51 -0
- package/dist/health/types.d.ts.map +1 -0
- package/dist/health/types.js +5 -0
- package/dist/health/types.js.map +1 -0
- package/dist/helpers/bytes.d.ts +28 -0
- package/dist/helpers/bytes.d.ts.map +1 -0
- package/dist/helpers/bytes.js +63 -0
- package/dist/helpers/bytes.js.map +1 -0
- package/dist/helpers/string.d.ts +4 -0
- package/dist/helpers/string.d.ts.map +1 -1
- package/dist/helpers/string.js +2 -0
- package/dist/helpers/string.js.map +1 -1
- package/dist/http/HttpContext.d.ts +38 -2
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js +81 -3
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/Request.d.ts +57 -3
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +51 -38
- package/dist/http/Request.js.map +1 -1
- package/dist/http/Response.d.ts +82 -2
- package/dist/http/Response.d.ts.map +1 -1
- package/dist/http/Response.js +240 -13
- package/dist/http/Response.js.map +1 -1
- package/dist/http/SseStream.d.ts +10 -0
- package/dist/http/SseStream.d.ts.map +1 -1
- package/dist/http/SseStream.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/migrations/MigrationRegistry.d.ts +28 -0
- package/dist/migrations/MigrationRegistry.d.ts.map +1 -0
- package/dist/migrations/MigrationRegistry.js +41 -0
- package/dist/migrations/MigrationRegistry.js.map +1 -0
- package/dist/migrations/types.d.ts +64 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations/types.js +15 -0
- package/dist/migrations/types.js.map +1 -0
- package/dist/router/Router.d.ts +112 -9
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +206 -28
- package/dist/router/Router.js.map +1 -1
- package/dist/security/CookieSigner.d.ts +43 -4
- package/dist/security/CookieSigner.d.ts.map +1 -1
- package/dist/security/CookieSigner.js +68 -10
- package/dist/security/CookieSigner.js.map +1 -1
- package/dist/session/Session.d.ts +46 -1
- package/dist/session/Session.d.ts.map +1 -1
- package/dist/session/Session.js +57 -4
- package/dist/session/Session.js.map +1 -1
- package/dist/session/SessionMiddleware.d.ts.map +1 -1
- package/dist/session/SessionMiddleware.js +19 -9
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/session/drivers/CookieDriver.d.ts +1 -1
- package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
- package/dist/session/drivers/CookieDriver.js +3 -3
- package/dist/session/drivers/CookieDriver.js.map +1 -1
- package/dist/session/drivers/DatabaseDriver.d.ts +1 -1
- package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -1
- package/dist/session/drivers/DatabaseDriver.js +2 -2
- package/dist/session/drivers/DatabaseDriver.js.map +1 -1
- package/dist/session/drivers/FileDriver.d.ts +1 -1
- package/dist/session/drivers/FileDriver.d.ts.map +1 -1
- package/dist/session/drivers/FileDriver.js +7 -5
- package/dist/session/drivers/FileDriver.js.map +1 -1
- package/dist/session/drivers/MemoryDriver.d.ts +1 -1
- package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/session/drivers/MemoryDriver.js +1 -1
- package/dist/session/drivers/MemoryDriver.js.map +1 -1
- package/dist/session/drivers/RedisDriver.d.ts +1 -1
- package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
- package/dist/session/drivers/RedisDriver.js +11 -4
- package/dist/session/drivers/RedisDriver.js.map +1 -1
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +5 -2
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Application.ts +169 -6
- package/src/ConfigLoader.ts +6 -0
- package/src/HealthCheck.ts +27 -14
- package/src/HttpKernel.ts +27 -4
- package/src/Ignitor.ts +17 -0
- package/src/bodyparser/BodyParserMiddleware.ts +70 -36
- package/src/bodyparser/MultipartFile.ts +125 -8
- package/src/console/BaseCommand.ts +8 -1
- package/src/console/Kernel.ts +18 -0
- package/src/directories.ts +81 -0
- package/src/events/Emitter.ts +129 -13
- package/src/events/EventsBuffer.ts +131 -0
- package/src/events/index.ts +7 -0
- package/src/health/BaseCheck.ts +30 -0
- package/src/health/HealthChecks.ts +83 -0
- package/src/health/Result.ts +69 -0
- package/src/health/checks/DiskSpaceCheck.ts +69 -0
- package/src/health/checks/MemoryHeapCheck.ts +68 -0
- package/src/health/checks/MemoryRSSCheck.ts +68 -0
- package/src/health/checks/memory.ts +115 -0
- package/src/health/index.ts +13 -0
- package/src/health/tracing.ts +15 -0
- package/src/health/types.ts +51 -0
- package/src/helpers/bytes.ts +71 -0
- package/src/helpers/string.ts +2 -0
- package/src/http/HttpContext.ts +86 -4
- package/src/http/Request.ts +82 -10
- package/src/http/Response.ts +283 -14
- package/src/http/SseStream.ts +10 -0
- package/src/index.ts +27 -1
- package/src/migrations/MigrationRegistry.ts +49 -0
- package/src/migrations/types.ts +68 -0
- package/src/router/Router.ts +241 -32
- package/src/security/CookieSigner.ts +92 -18
- package/src/session/Session.ts +71 -5
- package/src/session/SessionMiddleware.ts +19 -9
- package/src/session/drivers/CookieDriver.ts +4 -4
- package/src/session/drivers/DatabaseDriver.ts +3 -3
- package/src/session/drivers/FileDriver.ts +8 -6
- package/src/session/drivers/MemoryDriver.ts +2 -2
- package/src/session/drivers/RedisDriver.ts +12 -5
- package/tsconfig.app.json +13 -13
- package/tsconfig.base.json +19 -0
- package/tsconfig.client.json +16 -0
- package/tsconfig.package.json +15 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { RegisteredMigrationSource } from './types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where every store's migration runner registers itself.
|
|
5
|
+
*
|
|
6
|
+
* Bound in the container as `migrations`, always — an app with no data package
|
|
7
|
+
* gets an EMPTY registry, never a missing binding. That distinction is what
|
|
8
|
+
* lets the CLI tell "this app has no migrations" from "this app predates the
|
|
9
|
+
* registry", and say something useful in each case instead of failing the same
|
|
10
|
+
* way twice.
|
|
11
|
+
*/
|
|
12
|
+
export class MigrationRegistry {
|
|
13
|
+
readonly #sources = new Map<string, RegisteredMigrationSource>()
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Register a store's runner. Registering the same name twice is refused:
|
|
17
|
+
* silently replacing it would mean one of two providers migrates nothing,
|
|
18
|
+
* and the run would still report success.
|
|
19
|
+
*/
|
|
20
|
+
register(source: RegisteredMigrationSource): this {
|
|
21
|
+
if (this.#sources.has(source.name)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`A migration source named '${source.name}' is already registered. Two providers cannot claim one name — one of them would migrate nothing while the run still reported success.`,
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
this.#sources.set(source.name, source)
|
|
27
|
+
return this
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Every registered store, in registration order. */
|
|
31
|
+
all(): RegisteredMigrationSource[] {
|
|
32
|
+
return [...this.#sources.values()]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** One store by name, or `undefined`. */
|
|
36
|
+
get(name: string): RegisteredMigrationSource | undefined {
|
|
37
|
+
return this.#sources.get(name)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The registered names, for an error message that lists what IS available. */
|
|
41
|
+
names(): string[] {
|
|
42
|
+
return [...this.#sources.keys()]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Whether anything registered. */
|
|
46
|
+
get isEmpty(): boolean {
|
|
47
|
+
return this.#sources.size === 0
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract a data package implements so `ream migrate` can drive it.
|
|
3
|
+
*
|
|
4
|
+
* REAM PARTICULARITY, deliberate — AdonisJS has no equivalent, because Lucid is
|
|
5
|
+
* its only migration source: `node ace migration:run` can name it directly.
|
|
6
|
+
* Ream expects several stores in one app (a relational one, a time-series one,
|
|
7
|
+
* whatever comes next), so the CLI must not name any of them. A store registers
|
|
8
|
+
* itself and the CLI stays generic for good.
|
|
9
|
+
*
|
|
10
|
+
* The vocabulary is Adonis' where Adonis has one: a status node is
|
|
11
|
+
* `{ name, status, batch? }`, spelled `status` as Lucid's `MigrationListNode`
|
|
12
|
+
* spells it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Whether a migration has run. */
|
|
16
|
+
export type MigrationState = 'applied' | 'pending'
|
|
17
|
+
|
|
18
|
+
/** One migration's position, as `ream migrate:status` prints it. */
|
|
19
|
+
export interface MigrationStatusNode {
|
|
20
|
+
/** The migration's name, without extension. */
|
|
21
|
+
name: string
|
|
22
|
+
status: MigrationState
|
|
23
|
+
/** The batch it was applied in, when it has been. */
|
|
24
|
+
batch?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* What a store's migration runner must provide.
|
|
29
|
+
*
|
|
30
|
+
* Only three methods are REQUIRED — the three `ream migrate`,
|
|
31
|
+
* `migrate:rollback` and `migrate:status` call. Everything else is optional on
|
|
32
|
+
* purpose: a new store must be able to register with a runner that only knows
|
|
33
|
+
* how to go forward, rather than stub methods it cannot honour. A caller that
|
|
34
|
+
* needs an optional one checks for it and says so, instead of crashing.
|
|
35
|
+
*/
|
|
36
|
+
export interface MigrationRunnerContract {
|
|
37
|
+
/** Apply every pending migration. Resolves to the names applied, in order. */
|
|
38
|
+
migrate(): Promise<string[]>
|
|
39
|
+
/** Undo the last batch. Resolves to the names rolled back. */
|
|
40
|
+
rollback(): Promise<string[]>
|
|
41
|
+
/** Every known migration and whether it has run. */
|
|
42
|
+
status(): Promise<MigrationStatusNode[]>
|
|
43
|
+
|
|
44
|
+
/** Undo every migration. */
|
|
45
|
+
reset?(): Promise<string[]>
|
|
46
|
+
/** Reset, then migrate. */
|
|
47
|
+
refresh?(): Promise<unknown>
|
|
48
|
+
/** Drop everything, then migrate. */
|
|
49
|
+
fresh?(): Promise<unknown>
|
|
50
|
+
/** What `migrate()` would do, without doing it. */
|
|
51
|
+
dryRun?(): Promise<unknown>
|
|
52
|
+
/** Release a lock a crashed run left behind. */
|
|
53
|
+
forceUnlock?(): Promise<boolean>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A registered store, as the CLI sees it. */
|
|
57
|
+
export interface RegisteredMigrationSource {
|
|
58
|
+
/**
|
|
59
|
+
* How the store is named on the command line — `atlas`, `eon`, …
|
|
60
|
+
*
|
|
61
|
+
* It is what `ream migrate --only <name>` matches and what prefixes the
|
|
62
|
+
* output, so it belongs to the store, not to the app.
|
|
63
|
+
*/
|
|
64
|
+
name: string
|
|
65
|
+
/** Where its migration files live, for the message when there are none. */
|
|
66
|
+
directory?: string
|
|
67
|
+
runner: MigrationRunnerContract
|
|
68
|
+
}
|
package/src/router/Router.ts
CHANGED
|
@@ -57,8 +57,24 @@ export type LazyControllerAction = [loader: LazyControllerImport, method: string
|
|
|
57
57
|
*/
|
|
58
58
|
export type RouteHandler = RouteHandlerFunction | ControllerAction | LazyControllerAction | string
|
|
59
59
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Param matcher — a bare RegExp, or the AdonisJS `{ match, cast }` shape.
|
|
62
|
+
*
|
|
63
|
+
* `cast` turns the captured string into the value the handler wants, so
|
|
64
|
+
* `where('id', matchers.number())` gives `params.id` as a NUMBER. Ream carried
|
|
65
|
+
* only the pattern, so a matched param stayed a string and every controller
|
|
66
|
+
* re-parsed it. `pattern` is ream's older spelling of `match`, still accepted.
|
|
67
|
+
*/
|
|
68
|
+
export type ParamMatcher =
|
|
69
|
+
| RegExp
|
|
70
|
+
| {
|
|
71
|
+
/** AdonisJS spelling. */
|
|
72
|
+
match?: RegExp
|
|
73
|
+
/** Ream's older spelling of {@link match}. */
|
|
74
|
+
pattern?: RegExp
|
|
75
|
+
/** Casts the captured segment before it reaches `ctx.params`. */
|
|
76
|
+
cast?: (value: string) => unknown
|
|
77
|
+
}
|
|
62
78
|
|
|
63
79
|
export interface RouteDefinition {
|
|
64
80
|
method: string
|
|
@@ -166,17 +182,22 @@ export interface MatchResult {
|
|
|
166
182
|
// ─── Matchers ───────────────────────────────────────────────
|
|
167
183
|
|
|
168
184
|
export const matchers = {
|
|
169
|
-
/** Match numeric params only. */
|
|
185
|
+
/** Match numeric params only, and hand the handler a number. */
|
|
170
186
|
number(): ParamMatcher {
|
|
171
|
-
return {
|
|
187
|
+
return { match: /^\d+$/, cast: (value) => Number(value) }
|
|
172
188
|
},
|
|
173
189
|
/** Match UUID v4 params only. */
|
|
174
190
|
uuid(): ParamMatcher {
|
|
175
|
-
return {
|
|
191
|
+
return {
|
|
192
|
+
match: /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
193
|
+
// AdonisJS lowercases it, so a route keyed on the param is stable
|
|
194
|
+
// whatever case the client sent.
|
|
195
|
+
cast: (value) => value.toLowerCase(),
|
|
196
|
+
}
|
|
176
197
|
},
|
|
177
198
|
/** Match slug params only (lowercase alphanumeric + hyphens). */
|
|
178
199
|
slug(): ParamMatcher {
|
|
179
|
-
return {
|
|
200
|
+
return { match: /^[a-z0-9]+(?:-[a-z0-9]+)*$/ }
|
|
180
201
|
},
|
|
181
202
|
}
|
|
182
203
|
|
|
@@ -190,15 +211,43 @@ export const matchers = {
|
|
|
190
211
|
* anchoring here reproduces AdonisJS's *effective* full-segment semantics.
|
|
191
212
|
*/
|
|
192
213
|
function compileMatcher(matcher: ParamMatcher | RegExp | string): ParamMatcher {
|
|
193
|
-
if (typeof matcher === 'string') return {
|
|
194
|
-
return matcher instanceof RegExp ? {
|
|
214
|
+
if (typeof matcher === 'string') return { match: new RegExp(`^(?:${matcher})$`) }
|
|
215
|
+
return matcher instanceof RegExp ? { match: matcher } : matcher
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Run each matcher's `cast` over the params it matched.
|
|
220
|
+
*
|
|
221
|
+
* The wildcard holds several segments, so the cast applies to each of them.
|
|
222
|
+
* A matcher without a cast leaves its param untouched.
|
|
223
|
+
*/
|
|
224
|
+
function applyCasts(params: MatchedParams, matchers: Record<string, ParamMatcher>): MatchedParams {
|
|
225
|
+
let out: MatchedParams | undefined
|
|
226
|
+
for (const [param, value] of Object.entries(params)) {
|
|
227
|
+
const matcher = matchers[param]
|
|
228
|
+
const cast = matcher instanceof RegExp ? undefined : matcher?.cast
|
|
229
|
+
if (!cast) continue
|
|
230
|
+
out ??= { ...params }
|
|
231
|
+
// The cast's return is the caller's claim about its own param; the router
|
|
232
|
+
// only carries it.
|
|
233
|
+
out[param] = (
|
|
234
|
+
Array.isArray(value) ? value.map((v) => cast(v)) : cast(value)
|
|
235
|
+
) as MatchedParams[string]
|
|
236
|
+
}
|
|
237
|
+
return out ?? params
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** The RegExp a matcher tests with, whichever spelling it used. */
|
|
241
|
+
function matcherPattern(matcher: ParamMatcher): RegExp | undefined {
|
|
242
|
+
if (matcher instanceof RegExp) return matcher
|
|
243
|
+
return matcher.match ?? matcher.pattern
|
|
195
244
|
}
|
|
196
245
|
|
|
197
246
|
// ─── RouteBuilder ───────────────────────────────────────────
|
|
198
247
|
|
|
199
248
|
/**
|
|
200
249
|
* Fluent route builder — mutates the already-registered route definition(s).
|
|
201
|
-
* Backed by an array so a multi-verb `router.route(['GET','POST'], …)` returns
|
|
250
|
+
* Backed by an array so a multi-verb `router.route(pattern, ['GET','POST'], …)` returns
|
|
202
251
|
* ONE builder that fans every mutation across all its verbs; the single-verb
|
|
203
252
|
* helpers (`get`/`post`/…) back it with a one-element array.
|
|
204
253
|
*/
|
|
@@ -808,14 +857,16 @@ export class Router extends Macroable {
|
|
|
808
857
|
// ─── Route registration ───────────────────────────────────
|
|
809
858
|
|
|
810
859
|
/**
|
|
811
|
-
* Register a route for one verb or several
|
|
812
|
-
*
|
|
813
|
-
*
|
|
860
|
+
* Register a route for one verb or several — `route(pattern, methods,
|
|
861
|
+
* handler)`, the AdonisJS argument order. Ream took `(method, path)`, so a
|
|
862
|
+
* call copied from an AdonisJS app registered the pattern as the verb.
|
|
863
|
+
*
|
|
864
|
+
* A multi-verb call returns a single builder that configures every verb.
|
|
814
865
|
*/
|
|
815
|
-
route(
|
|
816
|
-
const
|
|
817
|
-
const defs =
|
|
818
|
-
const def = this.#createDefinition(verb,
|
|
866
|
+
route(pattern: string, methods: string | string[], handler: RouteHandler): RouteBuilder {
|
|
867
|
+
const verbs = Array.isArray(methods) ? methods : [methods]
|
|
868
|
+
const defs = verbs.map((verb) => {
|
|
869
|
+
const def = this.#createDefinition(verb, pattern, handler)
|
|
819
870
|
this.#routes.push(def)
|
|
820
871
|
return def
|
|
821
872
|
})
|
|
@@ -824,36 +875,36 @@ export class Router extends Macroable {
|
|
|
824
875
|
}
|
|
825
876
|
|
|
826
877
|
get(path: string, handler: RouteHandler): RouteBuilder {
|
|
827
|
-
return this.route('GET',
|
|
878
|
+
return this.route(path, 'GET', handler)
|
|
828
879
|
}
|
|
829
880
|
|
|
830
881
|
post(path: string, handler: RouteHandler): RouteBuilder {
|
|
831
|
-
return this.route('POST',
|
|
882
|
+
return this.route(path, 'POST', handler)
|
|
832
883
|
}
|
|
833
884
|
|
|
834
885
|
put(path: string, handler: RouteHandler): RouteBuilder {
|
|
835
|
-
return this.route('PUT',
|
|
886
|
+
return this.route(path, 'PUT', handler)
|
|
836
887
|
}
|
|
837
888
|
|
|
838
889
|
patch(path: string, handler: RouteHandler): RouteBuilder {
|
|
839
|
-
return this.route('PATCH',
|
|
890
|
+
return this.route(path, 'PATCH', handler)
|
|
840
891
|
}
|
|
841
892
|
|
|
842
893
|
delete(path: string, handler: RouteHandler): RouteBuilder {
|
|
843
|
-
return this.route('DELETE',
|
|
894
|
+
return this.route(path, 'DELETE', handler)
|
|
844
895
|
}
|
|
845
896
|
|
|
846
897
|
head(path: string, handler: RouteHandler): RouteBuilder {
|
|
847
|
-
return this.route('HEAD',
|
|
898
|
+
return this.route(path, 'HEAD', handler)
|
|
848
899
|
}
|
|
849
900
|
|
|
850
901
|
options(path: string, handler: RouteHandler): RouteBuilder {
|
|
851
|
-
return this.route('OPTIONS',
|
|
902
|
+
return this.route(path, 'OPTIONS', handler)
|
|
852
903
|
}
|
|
853
904
|
|
|
854
905
|
/** Register a route for all HTTP methods. */
|
|
855
906
|
any(path: string, handler: RouteHandler): RouteBuilder {
|
|
856
|
-
return this.route('*',
|
|
907
|
+
return this.route(path, '*', handler)
|
|
857
908
|
}
|
|
858
909
|
|
|
859
910
|
// ─── Resource routes ──────────────────────────────────────
|
|
@@ -1096,16 +1147,16 @@ export class Router extends Macroable {
|
|
|
1096
1147
|
// separate gates meant the global always had the final say, so a route
|
|
1097
1148
|
// could never loosen it — a global `id: /^[0-9]+$/` plus a route-level
|
|
1098
1149
|
// slug matcher 404'd instead of matching.
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
...this.#globalMatchers,
|
|
1102
|
-
...route.matchers,
|
|
1103
|
-
})
|
|
1104
|
-
) {
|
|
1150
|
+
const effective = { ...this.#globalMatchers, ...route.matchers }
|
|
1151
|
+
if (!this.#validateMatchers(params, effective)) {
|
|
1105
1152
|
continue
|
|
1106
1153
|
}
|
|
1107
1154
|
|
|
1108
|
-
|
|
1155
|
+
// A matcher's `cast` turns the captured segment into the value the
|
|
1156
|
+
// handler wants — `where('id', matchers.number())` hands `params.id`
|
|
1157
|
+
// a NUMBER, as in AdonisJS. Ream carried only the pattern, so every
|
|
1158
|
+
// controller re-parsed a string the router had already validated.
|
|
1159
|
+
return { route, params: applyCasts(params, effective) }
|
|
1109
1160
|
}
|
|
1110
1161
|
}
|
|
1111
1162
|
|
|
@@ -1270,6 +1321,96 @@ export class Router extends Macroable {
|
|
|
1270
1321
|
return this.#routes.filter((route) => !route.deleted)
|
|
1271
1322
|
}
|
|
1272
1323
|
|
|
1324
|
+
/**
|
|
1325
|
+
* A fluent URL builder (AdonisJS `router.builder()` / `urlBuilder`).
|
|
1326
|
+
*
|
|
1327
|
+
* `urlFor` takes everything at once; this is the form upstream gives you
|
|
1328
|
+
* when the pieces arrive separately — params here, query string there, a
|
|
1329
|
+
* prefix from config:
|
|
1330
|
+
*
|
|
1331
|
+
* ```ts
|
|
1332
|
+
* router.builder().params({ id: '1' }).qs({ tab: 'posts' }).make('users.show')
|
|
1333
|
+
* router.builder().prefixUrl('https://acme.test').makeSigned('invite', { expiresIn: '1h' })
|
|
1334
|
+
* ```
|
|
1335
|
+
*/
|
|
1336
|
+
builder(): UrlBuilder {
|
|
1337
|
+
return new UrlBuilder(this)
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/** AdonisJS' property name for {@link builder}. */
|
|
1341
|
+
get urlBuilder(): UrlBuilder {
|
|
1342
|
+
return this.builder()
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* A builder that prefixes every URL with `domain` (AdonisJS
|
|
1347
|
+
* `builderForDomain`).
|
|
1348
|
+
*
|
|
1349
|
+
* The domain is a prefix here, not a lookup key: routes are matched by host
|
|
1350
|
+
* at request time, and a named route is unique across the table.
|
|
1351
|
+
*/
|
|
1352
|
+
builderForDomain(domain: string): UrlBuilder {
|
|
1353
|
+
return this.builder().prefixUrl(domain)
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* The parameter names a route pattern declares, in order (AdonisJS
|
|
1358
|
+
* `parsePattern`).
|
|
1359
|
+
*
|
|
1360
|
+
* `:id` and `:slug?` both count; the optional marker is reported rather than
|
|
1361
|
+
* being part of the name.
|
|
1362
|
+
*/
|
|
1363
|
+
parsePattern(pattern: string): Array<{ name: string; optional: boolean }> {
|
|
1364
|
+
const tokens: Array<{ name: string; optional: boolean }> = []
|
|
1365
|
+
for (const match of pattern.matchAll(/:([A-Za-z_][\w]*)(\?)?/g)) {
|
|
1366
|
+
tokens.push({ name: match[1] ?? '', optional: match[2] === '?' })
|
|
1367
|
+
}
|
|
1368
|
+
return tokens
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/**
|
|
1372
|
+
* Every route, grouped by the domain it answers on (AdonisJS `toJSON`).
|
|
1373
|
+
*
|
|
1374
|
+
* Routes registered without a domain land under `root`, as upstream keys
|
|
1375
|
+
* them. This is what a route list — `ream inspect`, an OpenAPI generator, a
|
|
1376
|
+
* debug screen — reads.
|
|
1377
|
+
*/
|
|
1378
|
+
toJSON(): Record<string, RouteDefinition[]> {
|
|
1379
|
+
const byDomain: Record<string, RouteDefinition[]> = {}
|
|
1380
|
+
for (const route of this.getRoutes()) {
|
|
1381
|
+
const key = route.domain ?? 'root'
|
|
1382
|
+
const bucket = byDomain[key] ?? []
|
|
1383
|
+
bucket.push(route)
|
|
1384
|
+
byDomain[key] = bucket
|
|
1385
|
+
}
|
|
1386
|
+
return byDomain
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* Whether any route is scoped to a domain (AdonisJS `usingDomains`).
|
|
1391
|
+
*
|
|
1392
|
+
* Lets the matcher skip host resolution entirely when nothing asked for it.
|
|
1393
|
+
*/
|
|
1394
|
+
get usingDomains(): boolean {
|
|
1395
|
+
return this.getRoutes().some((route) => route.domain !== undefined)
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* Freeze the route table and build the lookup index (AdonisJS `commit`).
|
|
1400
|
+
*
|
|
1401
|
+
* The index is built lazily on the first `match()`, so this is only needed
|
|
1402
|
+
* to pay that cost up front — at the end of boot rather than inside the
|
|
1403
|
+
* first request.
|
|
1404
|
+
*/
|
|
1405
|
+
commit(): void {
|
|
1406
|
+
this.#buildIndex()
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/** Whether the lookup index has been built (AdonisJS `commited`). */
|
|
1410
|
+
get commited(): boolean {
|
|
1411
|
+
return !this.#indexDirty
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1273
1414
|
/**
|
|
1274
1415
|
* Clear all registered routes AND registries (used by hot-reload). Router
|
|
1275
1416
|
* middleware, named middleware, and global matchers are reset too — the
|
|
@@ -1302,7 +1443,8 @@ export class Router extends Macroable {
|
|
|
1302
1443
|
// The wildcard holds several segments; every one has to satisfy the
|
|
1303
1444
|
// matcher, or a `/assets/*` guard would pass on its first segment alone.
|
|
1304
1445
|
const values = Array.isArray(value) ? value : [value]
|
|
1305
|
-
const regex = matcher
|
|
1446
|
+
const regex = matcherPattern(matcher)
|
|
1447
|
+
if (!regex) continue
|
|
1306
1448
|
// A user-supplied /g or /y regex is stateful (`lastIndex` advances on
|
|
1307
1449
|
// match) — without a reset, the same URL alternates match/no-match
|
|
1308
1450
|
// across requests. Reset before every test.
|
|
@@ -1387,3 +1529,70 @@ function matchDomain(pattern: string, host: string): boolean {
|
|
|
1387
1529
|
}
|
|
1388
1530
|
return false
|
|
1389
1531
|
}
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* Builds a URL for a named route, one piece at a time (AdonisJS `UrlBuilder`).
|
|
1535
|
+
*
|
|
1536
|
+
* Every method returns the builder, and `make` / `makeSigned` are the two ways
|
|
1537
|
+
* out. It defers to {@link Router.urlFor} and {@link Router.makeSignedUrl}, so
|
|
1538
|
+
* a missing param fails the same way whichever spelling you used.
|
|
1539
|
+
*/
|
|
1540
|
+
export class UrlBuilder {
|
|
1541
|
+
readonly #router: Router
|
|
1542
|
+
#params: Record<string, string> = {}
|
|
1543
|
+
#qs: Record<string, string> = {}
|
|
1544
|
+
#prefix = ''
|
|
1545
|
+
#lookup = true
|
|
1546
|
+
|
|
1547
|
+
constructor(router: Router) {
|
|
1548
|
+
this.#router = router
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/** Route params, by name. */
|
|
1552
|
+
params(params: Record<string, string>): this {
|
|
1553
|
+
this.#params = { ...this.#params, ...params }
|
|
1554
|
+
return this
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/** Query-string params appended to the built URL. */
|
|
1558
|
+
qs(qs: Record<string, string>): this {
|
|
1559
|
+
this.#qs = { ...this.#qs, ...qs }
|
|
1560
|
+
return this
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/** A prefix — a domain, a base path — put in front of the result. */
|
|
1564
|
+
prefixUrl(prefix: string): this {
|
|
1565
|
+
this.#prefix = prefix.replace(/\/$/, '')
|
|
1566
|
+
return this
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* Treat what `make()` receives as a literal path instead of a route name
|
|
1571
|
+
* (AdonisJS `disableRouteLookup`).
|
|
1572
|
+
*/
|
|
1573
|
+
disableRouteLookup(): this {
|
|
1574
|
+
this.#lookup = false
|
|
1575
|
+
return this
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/** The URL. */
|
|
1579
|
+
make(identifier: string): string {
|
|
1580
|
+
const path = this.#lookup ? this.#router.urlFor(identifier, this.#params) : identifier
|
|
1581
|
+
const query = new URLSearchParams(this.#qs).toString()
|
|
1582
|
+
return `${this.#prefix}${path}${query ? `?${query}` : ''}`
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/**
|
|
1586
|
+
* The URL with an HMAC signature (AdonisJS `makeSigned`).
|
|
1587
|
+
*
|
|
1588
|
+
* The query string is folded into the signature, so a link cannot be
|
|
1589
|
+
* re-pointed by editing it.
|
|
1590
|
+
*/
|
|
1591
|
+
makeSigned(identifier: string, options?: SignedUrlOptions): string {
|
|
1592
|
+
const signed = this.#router.makeSignedUrl(identifier, this.#params, {
|
|
1593
|
+
...options,
|
|
1594
|
+
qs: { ...this.#qs, ...options?.qs },
|
|
1595
|
+
})
|
|
1596
|
+
return `${this.#prefix}${signed}`
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
@@ -40,8 +40,12 @@ export class E_INSECURE_APP_KEY extends Error {
|
|
|
40
40
|
|
|
41
41
|
/** The envelope carried inside an encrypted or signed value. */
|
|
42
42
|
interface Envelope {
|
|
43
|
-
/**
|
|
44
|
-
|
|
43
|
+
/**
|
|
44
|
+
* The payload. Any JSON value, not just a string: AdonisJS' verifier signs
|
|
45
|
+
* whatever you hand it, and a caller signing `{ id: 1 }` should not have to
|
|
46
|
+
* stringify it first.
|
|
47
|
+
*/
|
|
48
|
+
m: unknown
|
|
45
49
|
/** Epoch-ms expiry, when one was given. */
|
|
46
50
|
e?: number
|
|
47
51
|
/** The purpose it was sealed for, when one was given. */
|
|
@@ -69,25 +73,36 @@ export class CookieSigner {
|
|
|
69
73
|
* another — a password-reset token presented as a session cookie fails here
|
|
70
74
|
* rather than being honoured.
|
|
71
75
|
*/
|
|
72
|
-
#seal(value:
|
|
76
|
+
#seal(value: unknown, expiresIn?: number, purpose?: string): string {
|
|
73
77
|
const envelope: Envelope = { m: value }
|
|
74
78
|
if (expiresIn !== undefined) envelope.e = Date.now() + expiresIn
|
|
75
79
|
if (purpose !== undefined) envelope.p = purpose
|
|
76
80
|
return JSON.stringify(envelope)
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Unwrap a sealed payload, refusing an expired one or the wrong purpose.
|
|
85
|
+
*
|
|
86
|
+
* Anything that is not an envelope is refused rather than handed back.
|
|
87
|
+
* `#seal` only ever produces one, so a non-envelope did not come from this
|
|
88
|
+
* class — and returning it would skip the expiry and purpose checks below,
|
|
89
|
+
* which is precisely the downgrade those checks exist to prevent. Failing
|
|
90
|
+
* closed costs a value nothing here produced; failing open costs the
|
|
91
|
+
* guarantee that a token sealed for one use cannot be replayed into another.
|
|
92
|
+
*/
|
|
93
|
+
#open(raw: string, purpose?: string): unknown {
|
|
94
|
+
let parsed: unknown
|
|
82
95
|
try {
|
|
83
|
-
|
|
84
|
-
if (typeof parsed !== 'object' || parsed === null) return null
|
|
85
|
-
envelope = parsed as Envelope
|
|
96
|
+
parsed = JSON.parse(raw)
|
|
86
97
|
} catch {
|
|
87
|
-
|
|
88
|
-
return raw
|
|
98
|
+
return null
|
|
89
99
|
}
|
|
90
|
-
if (typeof
|
|
100
|
+
if (typeof parsed !== 'object' || parsed === null) return null
|
|
101
|
+
const envelope = parsed as Envelope
|
|
102
|
+
// The KEY must be there, whatever it holds — that is what distinguishes an
|
|
103
|
+
// envelope from an arbitrary JSON document, and a signed `null` or `0` is a
|
|
104
|
+
// legitimate payload.
|
|
105
|
+
if (!('m' in envelope)) return null
|
|
91
106
|
if (envelope.e !== undefined && envelope.e < Date.now()) return null
|
|
92
107
|
if ((envelope.p ?? undefined) !== purpose) return null
|
|
93
108
|
return envelope.m
|
|
@@ -99,27 +114,86 @@ export class CookieSigner {
|
|
|
99
114
|
* `expiresIn` is in milliseconds; `purpose` scopes the signature so it cannot
|
|
100
115
|
* be replayed somewhere else.
|
|
101
116
|
*/
|
|
102
|
-
sign(value:
|
|
117
|
+
sign(value: unknown, expiresIn?: number, purpose?: string): string {
|
|
103
118
|
const sealed = Buffer.from(this.#seal(value, expiresIn, purpose)).toString('base64url')
|
|
104
119
|
const sig = hmacSign(sealed, this.#secret)
|
|
105
120
|
return `${sealed}.${sig}`
|
|
106
121
|
}
|
|
107
122
|
|
|
108
123
|
/** Verify and unwrap, returning null when tampered, expired, or mis-purposed. */
|
|
109
|
-
unsign(signed: string, purpose?: string):
|
|
124
|
+
unsign<T = string>(signed: string, purpose?: string): T | null {
|
|
110
125
|
const lastDot = signed.lastIndexOf('.')
|
|
111
126
|
if (lastDot === -1) return null
|
|
112
127
|
const sealed = signed.slice(0, lastDot)
|
|
113
128
|
const sig = signed.slice(lastDot + 1)
|
|
114
129
|
if (!hmacVerify(sealed, sig, this.#secret)) return null
|
|
115
130
|
try {
|
|
116
|
-
return this.#open(Buffer.from(sealed, 'base64url').toString('utf8'), purpose)
|
|
131
|
+
return this.#open(Buffer.from(sealed, 'base64url').toString('utf8'), purpose) as T | null
|
|
117
132
|
} catch {
|
|
118
133
|
return null
|
|
119
134
|
}
|
|
120
135
|
}
|
|
121
136
|
|
|
122
|
-
|
|
137
|
+
/**
|
|
138
|
+
* The cipher in use (AdonisJS `Encryption.algorithm`).
|
|
139
|
+
*
|
|
140
|
+
* `aes-256-gcm`, not upstream's `aes-256-cbc` — see the deviation at the top
|
|
141
|
+
* of this file.
|
|
142
|
+
*/
|
|
143
|
+
get algorithm(): 'aes-256-gcm' {
|
|
144
|
+
return 'aes-256-gcm'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* base64url helpers (AdonisJS `Encryption.base64`).
|
|
149
|
+
*
|
|
150
|
+
* url-safe and unpadded, which is what every value this class produces uses
|
|
151
|
+
* — a signed or encrypted payload travels in a cookie or a URL, where `+`,
|
|
152
|
+
* `/` and `=` do not survive. `decode` answers `null` on anything that does
|
|
153
|
+
* not round-trip rather than handing back mangled bytes.
|
|
154
|
+
*/
|
|
155
|
+
get base64(): {
|
|
156
|
+
encode(value: string): string
|
|
157
|
+
decode(value: string): string | null
|
|
158
|
+
urlEncode(value: string): string
|
|
159
|
+
urlDecode(value: string): string | null
|
|
160
|
+
} {
|
|
161
|
+
const encode = (value: string) => Buffer.from(value, 'utf8').toString('base64url')
|
|
162
|
+
const decode = (value: string): string | null => {
|
|
163
|
+
const out = Buffer.from(value, 'base64url').toString('utf8')
|
|
164
|
+
return Buffer.from(out, 'utf8').toString('base64url') === value ? out : null
|
|
165
|
+
}
|
|
166
|
+
return { encode, decode, urlEncode: encode, urlDecode: decode }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Sign and verify WITHOUT encrypting (AdonisJS `Encryption.verifier`).
|
|
171
|
+
*
|
|
172
|
+
* The same `sign` / `unsign` this class already exposes, under the name and
|
|
173
|
+
* shape upstream puts them: an app calling `encryption.verifier.sign(...)`
|
|
174
|
+
* found nothing here.
|
|
175
|
+
*/
|
|
176
|
+
get verifier(): {
|
|
177
|
+
sign(payload: unknown, expiresIn?: number, purpose?: string): string
|
|
178
|
+
unsign<T = string>(payload: string, purpose?: string): T | null
|
|
179
|
+
} {
|
|
180
|
+
return {
|
|
181
|
+
sign: (payload, expiresIn, purpose) => this.sign(payload, expiresIn, purpose),
|
|
182
|
+
unsign: <T>(payload: string, purpose?: string) => this.unsign<T>(payload, purpose),
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Another signer on a different secret (AdonisJS `Encryption.child`).
|
|
188
|
+
*
|
|
189
|
+
* What key rotation is built on: keep the old secret's signer around to read
|
|
190
|
+
* values already in the wild while the new one writes.
|
|
191
|
+
*/
|
|
192
|
+
child(secret?: string): CookieSigner {
|
|
193
|
+
return new CookieSigner(secret ?? this.#secret)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
encrypt(value: unknown, expiresIn?: number, purpose?: string): string {
|
|
123
197
|
const iv = randomBytes(12)
|
|
124
198
|
const cipher = createCipheriv('aes-256-gcm', this.#keyBuffer, iv)
|
|
125
199
|
const sealed = this.#seal(value, expiresIn, purpose)
|
|
@@ -128,7 +202,7 @@ export class CookieSigner {
|
|
|
128
202
|
return `${iv.toString('base64url')}.${encrypted.toString('base64url')}.${tag.toString('base64url')}`
|
|
129
203
|
}
|
|
130
204
|
|
|
131
|
-
decrypt(encrypted: string, purpose?: string):
|
|
205
|
+
decrypt<T = string>(encrypted: string, purpose?: string): T | null {
|
|
132
206
|
const parts = encrypted.split('.')
|
|
133
207
|
if (parts.length !== 3) return null
|
|
134
208
|
try {
|
|
@@ -138,7 +212,7 @@ export class CookieSigner {
|
|
|
138
212
|
const decipher = createDecipheriv('aes-256-gcm', this.#keyBuffer, iv)
|
|
139
213
|
decipher.setAuthTag(tag)
|
|
140
214
|
const raw = Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8')
|
|
141
|
-
return this.#open(raw, purpose)
|
|
215
|
+
return this.#open(raw, purpose) as T | null
|
|
142
216
|
} catch {
|
|
143
217
|
return null
|
|
144
218
|
}
|