@c9up/ream 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/Application.d.ts +27 -0
- package/dist/Application.d.ts.map +1 -1
- package/dist/Application.js +29 -0
- package/dist/Application.js.map +1 -1
- package/dist/ErrorBoundary.d.ts.map +1 -1
- package/dist/ErrorBoundary.js +13 -1
- package/dist/ErrorBoundary.js.map +1 -1
- package/dist/HttpKernel.d.ts +8 -0
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +35 -12
- 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 +39 -0
- package/dist/Ignitor.js.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.d.ts +11 -4
- package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.js +55 -43
- package/dist/bodyparser/BodyParserMiddleware.js.map +1 -1
- package/dist/bodyparser/qsParse.d.ts +52 -0
- package/dist/bodyparser/qsParse.d.ts.map +1 -0
- package/dist/bodyparser/qsParse.js +161 -0
- package/dist/bodyparser/qsParse.js.map +1 -0
- package/dist/bootstrap/hyper-server.d.ts.map +1 -1
- package/dist/bootstrap/hyper-server.js +2 -8
- package/dist/bootstrap/hyper-server.js.map +1 -1
- package/dist/container/Container.d.ts +41 -0
- package/dist/container/Container.d.ts.map +1 -1
- package/dist/container/Container.js +193 -26
- package/dist/container/Container.js.map +1 -1
- package/dist/container/ContainerResolver.d.ts +46 -0
- package/dist/container/ContainerResolver.d.ts.map +1 -0
- package/dist/container/ContainerResolver.js +60 -0
- package/dist/container/ContainerResolver.js.map +1 -0
- package/dist/container/ContextualBindingsBuilder.d.ts +20 -0
- package/dist/container/ContextualBindingsBuilder.d.ts.map +1 -0
- package/dist/container/ContextualBindingsBuilder.js +31 -0
- package/dist/container/ContextualBindingsBuilder.js.map +1 -0
- package/dist/container/types.d.ts +9 -6
- package/dist/container/types.d.ts.map +1 -1
- package/dist/decorators/Service.d.ts +1 -5
- package/dist/decorators/Service.d.ts.map +1 -1
- package/dist/decorators/Service.js +10 -1
- package/dist/decorators/Service.js.map +1 -1
- package/dist/events/Emitter.d.ts +66 -6
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +165 -28
- package/dist/events/Emitter.js.map +1 -1
- package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
- package/dist/graphql/GraphQLEngine.js +5 -5
- package/dist/graphql/GraphQLEngine.js.map +1 -1
- package/dist/helpers/duration.d.ts +32 -0
- package/dist/helpers/duration.d.ts.map +1 -0
- package/dist/helpers/duration.js +74 -0
- package/dist/helpers/duration.js.map +1 -0
- package/dist/helpers/napi-loader.d.ts +10 -1
- package/dist/helpers/napi-loader.d.ts.map +1 -1
- package/dist/helpers/napi-loader.js +18 -3
- package/dist/helpers/napi-loader.js.map +1 -1
- package/dist/helpers/types.d.ts +1 -1
- package/dist/helpers/types.d.ts.map +1 -1
- package/dist/http/Exception.d.ts.map +1 -1
- package/dist/http/Exception.js +17 -5
- package/dist/http/Exception.js.map +1 -1
- package/dist/http/HttpContext.d.ts +16 -5
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js +4 -2
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/RedirectBuilder.d.ts +48 -2
- package/dist/http/RedirectBuilder.d.ts.map +1 -1
- package/dist/http/RedirectBuilder.js +95 -14
- package/dist/http/RedirectBuilder.js.map +1 -1
- package/dist/http/Request.d.ts +128 -8
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +259 -8
- package/dist/http/Request.js.map +1 -1
- package/dist/http/Response.d.ts +74 -3
- package/dist/http/Response.d.ts.map +1 -1
- package/dist/http/Response.js +177 -4
- package/dist/http/Response.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/router/Router.d.ts +36 -7
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +66 -22
- package/dist/router/Router.js.map +1 -1
- package/dist/security/CookieSigner.d.ts +35 -4
- package/dist/security/CookieSigner.d.ts.map +1 -1
- package/dist/security/CookieSigner.js +99 -10
- package/dist/security/CookieSigner.js.map +1 -1
- package/dist/server/Server.d.ts +2 -3
- package/dist/server/Server.d.ts.map +1 -1
- package/dist/server/Server.js +30 -13
- package/dist/server/Server.js.map +1 -1
- package/dist/session/ReadOnlyValuesStore.d.ts +24 -0
- package/dist/session/ReadOnlyValuesStore.d.ts.map +1 -0
- package/dist/session/ReadOnlyValuesStore.js +45 -0
- package/dist/session/ReadOnlyValuesStore.js.map +1 -0
- package/dist/session/Session.d.ts +181 -9
- package/dist/session/Session.d.ts.map +1 -1
- package/dist/session/Session.js +284 -33
- package/dist/session/Session.js.map +1 -1
- package/dist/session/SessionMiddleware.d.ts.map +1 -1
- package/dist/session/SessionMiddleware.js +87 -33
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/session/drivers/DatabaseDriver.d.ts +66 -0
- package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -0
- package/dist/session/drivers/DatabaseDriver.js +132 -0
- package/dist/session/drivers/DatabaseDriver.js.map +1 -0
- package/dist/session/drivers/FileDriver.d.ts +22 -0
- package/dist/session/drivers/FileDriver.d.ts.map +1 -0
- package/dist/session/drivers/FileDriver.js +66 -0
- package/dist/session/drivers/FileDriver.js.map +1 -0
- package/dist/session/drivers/MemoryDriver.d.ts +10 -2
- package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/session/drivers/MemoryDriver.js +70 -0
- package/dist/session/drivers/MemoryDriver.js.map +1 -1
- package/dist/session/drivers/RedisDriver.d.ts +10 -2
- package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
- package/dist/session/drivers/RedisDriver.js +58 -0
- package/dist/session/drivers/RedisDriver.js.map +1 -1
- package/dist/session/templateTags.d.ts +42 -0
- package/dist/session/templateTags.d.ts.map +1 -0
- package/dist/session/templateTags.js +101 -0
- package/dist/session/templateTags.js.map +1 -0
- package/dist/testing/RequestBuilder.d.ts +38 -1
- package/dist/testing/RequestBuilder.d.ts.map +1 -1
- package/dist/testing/RequestBuilder.js +40 -1
- package/dist/testing/RequestBuilder.js.map +1 -1
- package/dist/testing/TestClient.d.ts +3 -1
- package/dist/testing/TestClient.d.ts.map +1 -1
- package/dist/testing/TestClient.js +3 -1
- package/dist/testing/TestClient.js.map +1 -1
- package/dist/types/helpers.d.ts +1 -1
- package/dist/types/helpers.d.ts.map +1 -1
- package/dist/utils/Macroable.d.ts +14 -5
- package/dist/utils/Macroable.d.ts.map +1 -1
- package/dist/utils/Macroable.js +20 -18
- package/dist/utils/Macroable.js.map +1 -1
- package/events.win32-x64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Application.ts +39 -0
- package/src/ErrorBoundary.ts +13 -1
- package/src/HttpKernel.ts +45 -19
- package/src/Ignitor.ts +39 -0
- package/src/bodyparser/BodyParserMiddleware.ts +66 -44
- package/src/bodyparser/qsParse.ts +179 -0
- package/src/bootstrap/hyper-server.ts +2 -9
- package/src/container/Container.ts +265 -25
- package/src/container/ContainerResolver.ts +84 -0
- package/src/container/ContextualBindingsBuilder.ts +41 -0
- package/src/container/types.ts +11 -7
- package/src/decorators/Service.ts +16 -3
- package/src/events/Emitter.ts +200 -25
- package/src/graphql/GraphQLEngine.ts +7 -8
- package/src/helpers/duration.ts +71 -0
- package/src/helpers/napi-loader.ts +19 -3
- package/src/helpers/types.ts +4 -1
- package/src/http/Exception.ts +17 -5
- package/src/http/HttpContext.ts +20 -5
- package/src/http/RedirectBuilder.ts +126 -11
- package/src/http/Request.ts +283 -13
- package/src/http/Response.ts +213 -6
- package/src/index.ts +17 -3
- package/src/router/Router.ts +98 -33
- package/src/security/CookieSigner.ts +102 -10
- package/src/server/Server.ts +36 -16
- package/src/session/ReadOnlyValuesStore.ts +52 -0
- package/src/session/Session.ts +364 -37
- package/src/session/SessionMiddleware.ts +93 -30
- package/src/session/drivers/DatabaseDriver.ts +184 -0
- package/src/session/drivers/FileDriver.ts +82 -0
- package/src/session/drivers/MemoryDriver.ts +75 -2
- package/src/session/drivers/RedisDriver.ts +77 -2
- package/src/session/templateTags.ts +124 -0
- package/src/testing/RequestBuilder.ts +76 -0
- package/src/testing/TestClient.ts +10 -2
- package/src/types/helpers.ts +4 -1
- package/src/utils/Macroable.ts +23 -8
- package/dist/utils/inflect.d.ts +0 -17
- package/dist/utils/inflect.d.ts.map +0 -1
- package/dist/utils/inflect.js +0 -32
- package/dist/utils/inflect.js.map +0 -1
- package/src/utils/inflect.ts +0 -29
|
@@ -1,43 +1,134 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cookie signing and encryption.
|
|
3
3
|
* Uses Rust NAPI via crypto facade when available.
|
|
4
|
+
*
|
|
5
|
+
* NAMED DEVIATION — the wire format is NOT AdonisJS's.
|
|
6
|
+
*
|
|
7
|
+
* AdonisJS encrypts with `aes-256-cbc` and appends its own HMAC
|
|
8
|
+
* (`base64url(payload).base64url(iv).hmac`). Ream uses `aes-256-gcm`, where
|
|
9
|
+
* authentication is part of the cipher rather than a second primitive bolted on
|
|
10
|
+
* — encrypt-then-MAC assembled by hand is a well-known source of subtle breaks,
|
|
11
|
+
* and there is no reason to reproduce it.
|
|
12
|
+
*
|
|
13
|
+
* The consequence, stated plainly: a cookie encrypted by an AdonisJS app cannot
|
|
14
|
+
* be decrypted here. Migrating an app invalidates the encrypted cookies already
|
|
15
|
+
* in users' browsers — they are signed out once, at deploy. Everything the app
|
|
16
|
+
* CALLS (`encrypt` / `decrypt` / `sign` / `unsign`, with purpose and expiry)
|
|
17
|
+
* behaves the same.
|
|
4
18
|
*/
|
|
5
19
|
|
|
6
20
|
import { createCipheriv, createDecipheriv, createHmac, randomBytes } from 'node:crypto'
|
|
7
21
|
import { hmacSign, hmacVerify } from './crypto.js'
|
|
8
22
|
|
|
23
|
+
/** Raised when no APP_KEY is configured (AdonisJS `E_MISSING_APP_KEY`). */
|
|
24
|
+
export class E_MISSING_APP_KEY extends Error {
|
|
25
|
+
readonly code = 'E_MISSING_APP_KEY' as const
|
|
26
|
+
constructor() {
|
|
27
|
+
super('Missing APP_KEY. Set it in your environment before encrypting anything.')
|
|
28
|
+
this.name = 'E_MISSING_APP_KEY'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Raised when the APP_KEY is too short to be worth having. */
|
|
33
|
+
export class E_INSECURE_APP_KEY extends Error {
|
|
34
|
+
readonly code = 'E_INSECURE_APP_KEY' as const
|
|
35
|
+
constructor() {
|
|
36
|
+
super('APP_KEY is too short — it must be at least 16 characters.')
|
|
37
|
+
this.name = 'E_INSECURE_APP_KEY'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The envelope carried inside an encrypted or signed value. */
|
|
42
|
+
interface Envelope {
|
|
43
|
+
/** The payload. */
|
|
44
|
+
m: string
|
|
45
|
+
/** Epoch-ms expiry, when one was given. */
|
|
46
|
+
e?: number
|
|
47
|
+
/** The purpose it was sealed for, when one was given. */
|
|
48
|
+
p?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
9
51
|
export class CookieSigner {
|
|
10
52
|
#secret: string
|
|
11
53
|
#keyBuffer: Buffer
|
|
12
54
|
|
|
13
55
|
constructor(secret: string) {
|
|
56
|
+
// Validated as AdonisJS does: an absent or trivially short key is a
|
|
57
|
+
// configuration error, not something to discover at decrypt time.
|
|
58
|
+
if (typeof secret !== 'string' || secret.length === 0) throw new E_MISSING_APP_KEY()
|
|
59
|
+
if (secret.length < 16) throw new E_INSECURE_APP_KEY()
|
|
14
60
|
this.#secret = secret
|
|
15
61
|
// Derive a 32-byte key for AES-256-GCM via HKDF-like derivation
|
|
16
62
|
this.#keyBuffer = Buffer.from(createHmac('sha256', secret).update('cookie-key').digest())
|
|
17
63
|
}
|
|
18
64
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Wrap a payload with its expiry and purpose.
|
|
67
|
+
*
|
|
68
|
+
* `purpose` is what stops a value sealed for one use being replayed into
|
|
69
|
+
* another — a password-reset token presented as a session cookie fails here
|
|
70
|
+
* rather than being honoured.
|
|
71
|
+
*/
|
|
72
|
+
#seal(value: string, expiresIn?: number, purpose?: string): string {
|
|
73
|
+
const envelope: Envelope = { m: value }
|
|
74
|
+
if (expiresIn !== undefined) envelope.e = Date.now() + expiresIn
|
|
75
|
+
if (purpose !== undefined) envelope.p = purpose
|
|
76
|
+
return JSON.stringify(envelope)
|
|
22
77
|
}
|
|
23
78
|
|
|
24
|
-
|
|
79
|
+
/** Unwrap a sealed payload, refusing an expired one or the wrong purpose. */
|
|
80
|
+
#open(raw: string, purpose?: string): string | null {
|
|
81
|
+
let envelope: Envelope
|
|
82
|
+
try {
|
|
83
|
+
const parsed: unknown = JSON.parse(raw)
|
|
84
|
+
if (typeof parsed !== 'object' || parsed === null) return null
|
|
85
|
+
envelope = parsed as Envelope
|
|
86
|
+
} catch {
|
|
87
|
+
// Not an envelope — a value sealed before this format existed.
|
|
88
|
+
return raw
|
|
89
|
+
}
|
|
90
|
+
if (typeof envelope.m !== 'string') return raw
|
|
91
|
+
if (envelope.e !== undefined && envelope.e < Date.now()) return null
|
|
92
|
+
if ((envelope.p ?? undefined) !== purpose) return null
|
|
93
|
+
return envelope.m
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Sign a value (AdonisJS `Encryption.verifier.sign`).
|
|
98
|
+
*
|
|
99
|
+
* `expiresIn` is in milliseconds; `purpose` scopes the signature so it cannot
|
|
100
|
+
* be replayed somewhere else.
|
|
101
|
+
*/
|
|
102
|
+
sign(value: string, expiresIn?: number, purpose?: string): string {
|
|
103
|
+
const sealed = Buffer.from(this.#seal(value, expiresIn, purpose)).toString('base64url')
|
|
104
|
+
const sig = hmacSign(sealed, this.#secret)
|
|
105
|
+
return `${sealed}.${sig}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Verify and unwrap, returning null when tampered, expired, or mis-purposed. */
|
|
109
|
+
unsign(signed: string, purpose?: string): string | null {
|
|
25
110
|
const lastDot = signed.lastIndexOf('.')
|
|
26
111
|
if (lastDot === -1) return null
|
|
27
|
-
const
|
|
112
|
+
const sealed = signed.slice(0, lastDot)
|
|
28
113
|
const sig = signed.slice(lastDot + 1)
|
|
29
|
-
|
|
114
|
+
if (!hmacVerify(sealed, sig, this.#secret)) return null
|
|
115
|
+
try {
|
|
116
|
+
return this.#open(Buffer.from(sealed, 'base64url').toString('utf8'), purpose)
|
|
117
|
+
} catch {
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
30
120
|
}
|
|
31
121
|
|
|
32
|
-
encrypt(value: string): string {
|
|
122
|
+
encrypt(value: string, expiresIn?: number, purpose?: string): string {
|
|
33
123
|
const iv = randomBytes(12)
|
|
34
124
|
const cipher = createCipheriv('aes-256-gcm', this.#keyBuffer, iv)
|
|
35
|
-
const
|
|
125
|
+
const sealed = this.#seal(value, expiresIn, purpose)
|
|
126
|
+
const encrypted = Buffer.concat([cipher.update(sealed, 'utf8'), cipher.final()])
|
|
36
127
|
const tag = cipher.getAuthTag()
|
|
37
128
|
return `${iv.toString('base64url')}.${encrypted.toString('base64url')}.${tag.toString('base64url')}`
|
|
38
129
|
}
|
|
39
130
|
|
|
40
|
-
decrypt(encrypted: string): string | null {
|
|
131
|
+
decrypt(encrypted: string, purpose?: string): string | null {
|
|
41
132
|
const parts = encrypted.split('.')
|
|
42
133
|
if (parts.length !== 3) return null
|
|
43
134
|
try {
|
|
@@ -46,7 +137,8 @@ export class CookieSigner {
|
|
|
46
137
|
const tag = Buffer.from(parts[2], 'base64url')
|
|
47
138
|
const decipher = createDecipheriv('aes-256-gcm', this.#keyBuffer, iv)
|
|
48
139
|
decipher.setAuthTag(tag)
|
|
49
|
-
|
|
140
|
+
const raw = Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8')
|
|
141
|
+
return this.#open(raw, purpose)
|
|
50
142
|
} catch {
|
|
51
143
|
return null
|
|
52
144
|
}
|
package/src/server/Server.ts
CHANGED
|
@@ -23,11 +23,11 @@ export interface MiddlewareClass {
|
|
|
23
23
|
/** Lazy import returning a module with a default export. */
|
|
24
24
|
export type LazyImport<T> = () => Promise<{ default: T }>
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
|
|
26
|
+
// `never[]` accepts every constructor shape: parameters are contravariant, so a
|
|
27
|
+
// rest of `never` is assignable from any concrete list. No `any` needed.
|
|
28
|
+
export type ErrorHandlerClass = new (...args: never[]) => ExceptionHandler
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
type MiddlewareClassConstructor = new (...args: any[]) => MiddlewareClass
|
|
30
|
+
export type MiddlewareClassConstructor = new (...args: never[]) => MiddlewareClass
|
|
31
31
|
|
|
32
32
|
/** What server.use() and router.use() accept: lazy imports or direct functions. */
|
|
33
33
|
export type MiddlewareEntry = LazyImport<MiddlewareClassConstructor> | MiddlewareFunction
|
|
@@ -130,6 +130,18 @@ export function resolveMiddlewareEntry(entry: MiddlewareEntry): MiddlewareFuncti
|
|
|
130
130
|
return async (ctx: HttpContext, next: () => Promise<void>) => {
|
|
131
131
|
if (!cachedClass) {
|
|
132
132
|
const mod = await lazyImport()
|
|
133
|
+
if (mod === null || typeof mod !== 'object' || !('default' in mod)) {
|
|
134
|
+
// The zero-arity ambiguity, reported where it can be acted on: a bound
|
|
135
|
+
// or rest-args middleware lands here because it declares no parameters
|
|
136
|
+
// and was read as an import factory.
|
|
137
|
+
throw new Error(
|
|
138
|
+
'[E_MIDDLEWARE_ENTRY] A middleware entry with no declared parameters was treated as a lazy import, ' +
|
|
139
|
+
'but it did not resolve to a module with a default export. ' +
|
|
140
|
+
'If this is an inline middleware (e.g. `handle.bind(this)` or `(...args) => …`), register it on the ' +
|
|
141
|
+
'route or group — `router.get(...).use(fn)` — rather than in `router.use([...])`, which takes ' +
|
|
142
|
+
'imports of middleware classes, as AdonisJS does.',
|
|
143
|
+
)
|
|
144
|
+
}
|
|
133
145
|
cachedClass = mod.default
|
|
134
146
|
}
|
|
135
147
|
const instance = ctx.containerResolver
|
|
@@ -169,18 +181,26 @@ export function resolveParametrizedMiddlewareEntry(
|
|
|
169
181
|
}
|
|
170
182
|
}
|
|
171
183
|
|
|
172
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Whether an entry is a middleware to run, rather than a module to import.
|
|
186
|
+
*
|
|
187
|
+
* The two are separated by WHERE they are registered, exactly as in AdonisJS:
|
|
188
|
+
* `router.use([...])` takes imports of middleware classes (its own `use()`
|
|
189
|
+
* runs `moduleImporter(one, 'handle')` over every entry), while an inline
|
|
190
|
+
* function goes on a route or group. So this only has to cope with the entries
|
|
191
|
+
* that reach it, and a zero-arity function is an import factory.
|
|
192
|
+
*
|
|
193
|
+
* A middleware that lost its parameters — `handle.bind(this)`, `(...args) =>`
|
|
194
|
+
* — still lands here looking like a factory, which is why the resolver below
|
|
195
|
+
* raises a named error instead of failing somewhere deeper.
|
|
196
|
+
*/
|
|
173
197
|
function isMiddlewareFunction(entry: MiddlewareEntry): entry is MiddlewareFunction {
|
|
174
|
-
// A lazy import factory returns a promise (dynamic import). Middleware functions don't.
|
|
175
|
-
// We check: if calling it with no args returns a thenable, it's a lazy import.
|
|
176
|
-
// Better heuristic: lazy imports are () => import(...) which have .length === 0
|
|
177
|
-
// and middleware functions have .length >= 2 (ctx, next).
|
|
178
|
-
// Edge case: arrow with defaults has length 1. But lazy import() always has length 0.
|
|
179
198
|
if (typeof entry !== 'function') return false
|
|
180
|
-
//
|
|
181
|
-
if (entry.length
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
//
|
|
185
|
-
|
|
199
|
+
// A declared parameter means a middleware: `(ctx)`, `(ctx, next)`.
|
|
200
|
+
if (entry.length > 0) return true
|
|
201
|
+
// Zero declared parameters: an import factory. `router.use()` only takes
|
|
202
|
+
// those, so this is not a guess about which kind an entry is — a closure
|
|
203
|
+
// that reaches here was registered in the wrong place, and the resolver
|
|
204
|
+
// says so rather than running it as a module.
|
|
205
|
+
return false
|
|
186
206
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A read-only bag of values addressed by key or key PATH, shared with templates.
|
|
3
|
+
*
|
|
4
|
+
* This is the shape AdonisJS shares as `session` and `flashMessages`, and the
|
|
5
|
+
* shape its `@error` / `@errors` / `@inputError` tags read — `has(['errorsBag',
|
|
6
|
+
* field])` and `get('inputErrorsBag', {})`. A migrated template relies on it, so
|
|
7
|
+
* the path semantics have to match: a dotted string and an array address the
|
|
8
|
+
* same nested value.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** A key, a dotted key path, or the same path already split. */
|
|
12
|
+
export type ValuePath = string | readonly string[]
|
|
13
|
+
|
|
14
|
+
function segments(path: ValuePath): string[] {
|
|
15
|
+
return Array.isArray(path) ? [...path] : String(path).split('.')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ReadOnlyValuesStore {
|
|
19
|
+
readonly #values: Record<string, unknown>
|
|
20
|
+
|
|
21
|
+
constructor(values: Record<string, unknown> | null) {
|
|
22
|
+
this.#values = values ?? {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** True when the bag holds nothing — `@if(flashMessages.isEmpty)`. */
|
|
26
|
+
get isEmpty(): boolean {
|
|
27
|
+
return Object.keys(this.#values).length === 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Every value, as a shallow copy so a template cannot mutate the store. */
|
|
31
|
+
all(): Record<string, unknown> {
|
|
32
|
+
return { ...this.#values }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The value at `path`, or `defaultValue` when any segment is missing. */
|
|
36
|
+
get(path: ValuePath, defaultValue?: unknown): unknown {
|
|
37
|
+
let current: unknown = this.#values
|
|
38
|
+
for (const key of segments(path)) {
|
|
39
|
+
if (current === null || typeof current !== 'object') return defaultValue
|
|
40
|
+
// `Reflect.get` rather than indexing: a flashed key named `__proto__`
|
|
41
|
+
// must read an OWN property, never walk the prototype chain.
|
|
42
|
+
if (!Object.hasOwn(current, key)) return defaultValue
|
|
43
|
+
current = Reflect.get(current, key)
|
|
44
|
+
}
|
|
45
|
+
return current === undefined ? defaultValue : current
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Whether `path` resolves to something other than `undefined`. */
|
|
49
|
+
has(path: ValuePath): boolean {
|
|
50
|
+
return this.get(path) !== undefined
|
|
51
|
+
}
|
|
52
|
+
}
|