@c9up/ream 0.2.1 → 0.2.3
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/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +15 -1
- package/dist/Ignitor.js.map +1 -1
- package/dist/container/Container.d.ts +18 -0
- package/dist/container/Container.d.ts.map +1 -1
- package/dist/container/Container.js +29 -0
- package/dist/container/Container.js.map +1 -1
- package/dist/env/Env.d.ts +11 -0
- package/dist/env/Env.d.ts.map +1 -1
- package/dist/env/Env.js +33 -17
- package/dist/env/Env.js.map +1 -1
- package/dist/env/Secret.d.ts +17 -0
- package/dist/env/Secret.d.ts.map +1 -1
- package/dist/env/Secret.js +23 -0
- package/dist/env/Secret.js.map +1 -1
- package/dist/errors/aggregate.d.ts +27 -0
- package/dist/errors/aggregate.d.ts.map +1 -0
- package/dist/errors/aggregate.js +27 -0
- package/dist/errors/aggregate.js.map +1 -0
- package/dist/events/Emitter.d.ts +18 -1
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +25 -6
- package/dist/events/Emitter.js.map +1 -1
- package/dist/events/services/main.d.ts.map +1 -1
- package/dist/events/services/main.js +3 -10
- package/dist/events/services/main.js.map +1 -1
- package/dist/helpers/MessageBuilder.d.ts +29 -0
- package/dist/helpers/MessageBuilder.d.ts.map +1 -0
- package/dist/helpers/MessageBuilder.js +63 -0
- package/dist/helpers/MessageBuilder.js.map +1 -0
- package/dist/helpers/VerificationToken.d.ts +56 -0
- package/dist/helpers/VerificationToken.d.ts.map +1 -0
- package/dist/helpers/VerificationToken.js +70 -0
- package/dist/helpers/VerificationToken.js.map +1 -0
- package/dist/helpers/defineStaticProperty.d.ts +15 -0
- package/dist/helpers/defineStaticProperty.d.ts.map +1 -0
- package/dist/helpers/defineStaticProperty.js +47 -0
- package/dist/helpers/defineStaticProperty.js.map +1 -0
- package/dist/helpers/duration.d.ts +20 -0
- package/dist/helpers/duration.d.ts.map +1 -1
- package/dist/helpers/duration.js +71 -0
- package/dist/helpers/duration.js.map +1 -1
- package/dist/helpers/fs.d.ts +39 -0
- package/dist/helpers/fs.d.ts.map +1 -0
- package/dist/helpers/fs.js +70 -0
- package/dist/helpers/fs.js.map +1 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +7 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/helpers/safeTiming.d.ts +14 -0
- package/dist/helpers/safeTiming.d.ts.map +1 -0
- package/dist/helpers/safeTiming.js +42 -0
- package/dist/helpers/safeTiming.js.map +1 -0
- package/dist/helpers/string.d.ts +85 -0
- package/dist/helpers/string.d.ts.map +1 -1
- package/dist/helpers/string.js +127 -1
- package/dist/helpers/string.js.map +1 -1
- package/dist/http/Exception.d.ts +76 -6
- package/dist/http/Exception.d.ts.map +1 -1
- package/dist/http/Exception.js +178 -85
- package/dist/http/Exception.js.map +1 -1
- package/dist/http/HttpContext.d.ts +14 -1
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/RedirectBuilder.d.ts +16 -0
- package/dist/http/RedirectBuilder.d.ts.map +1 -1
- package/dist/http/RedirectBuilder.js +34 -9
- package/dist/http/RedirectBuilder.js.map +1 -1
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -17
- package/dist/http/Request.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/router/Router.d.ts +54 -0
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +73 -4
- package/dist/router/Router.js.map +1 -1
- package/dist/services/app.d.ts.map +1 -1
- package/dist/services/app.js +3 -21
- package/dist/services/app.js.map +1 -1
- package/dist/services/console.d.ts.map +1 -1
- package/dist/services/console.js +3 -19
- package/dist/services/console.js.map +1 -1
- package/dist/services/createServiceProxy.d.ts +19 -0
- package/dist/services/createServiceProxy.d.ts.map +1 -0
- package/dist/services/createServiceProxy.js +44 -0
- package/dist/services/createServiceProxy.js.map +1 -0
- package/dist/services/router.d.ts.map +1 -1
- package/dist/services/router.js +3 -12
- package/dist/services/router.js.map +1 -1
- package/dist/services/server.d.ts.map +1 -1
- package/dist/services/server.js +3 -22
- package/dist/services/server.js.map +1 -1
- package/dist/session/ReadOnlyValuesStore.d.ts +11 -0
- package/dist/session/ReadOnlyValuesStore.d.ts.map +1 -1
- package/dist/session/ReadOnlyValuesStore.js +17 -0
- package/dist/session/ReadOnlyValuesStore.js.map +1 -1
- package/dist/session/Session.d.ts +11 -3
- package/dist/session/Session.d.ts.map +1 -1
- package/dist/session/Session.js +15 -6
- package/dist/session/Session.js.map +1 -1
- package/dist/session/SessionMiddleware.js +2 -1
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/session/errors.d.ts +25 -0
- package/dist/session/errors.d.ts.map +1 -0
- package/dist/session/errors.js +32 -0
- package/dist/session/errors.js.map +1 -0
- package/dist/types/authenticators.d.ts +24 -0
- package/dist/types/authenticators.d.ts.map +1 -0
- package/dist/types/authenticators.js +2 -0
- package/dist/types/authenticators.js.map +1 -0
- package/dist/types/container.d.ts +64 -0
- package/dist/types/container.d.ts.map +1 -0
- package/dist/types/container.js +23 -0
- package/dist/types/container.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +9 -0
- package/dist/types/index.js.map +1 -0
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +13 -1
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Ignitor.ts +15 -1
- package/src/container/Container.ts +31 -0
- package/src/env/Env.ts +34 -15
- package/src/env/Secret.ts +26 -0
- package/src/errors/aggregate.ts +36 -0
- package/src/events/Emitter.ts +30 -5
- package/src/events/services/main.ts +6 -12
- package/src/helpers/MessageBuilder.ts +69 -0
- package/src/helpers/VerificationToken.ts +93 -0
- package/src/helpers/defineStaticProperty.ts +51 -0
- package/src/helpers/duration.ts +75 -0
- package/src/helpers/fs.ts +104 -0
- package/src/helpers/index.ts +8 -0
- package/src/helpers/safeTiming.ts +45 -0
- package/src/helpers/string.ts +145 -1
- package/src/http/Exception.ts +225 -99
- package/src/http/HttpContext.ts +14 -1
- package/src/http/RedirectBuilder.ts +38 -8
- package/src/http/Request.ts +5 -16
- package/src/index.ts +24 -2
- package/src/router/Router.ts +87 -4
- package/src/services/app.ts +6 -23
- package/src/services/console.ts +6 -21
- package/src/services/createServiceProxy.ts +47 -0
- package/src/services/router.ts +6 -14
- package/src/services/server.ts +6 -24
- package/src/session/ReadOnlyValuesStore.ts +20 -0
- package/src/session/Session.ts +16 -11
- package/src/session/SessionMiddleware.ts +2 -1
- package/src/session/errors.ts +39 -0
- package/src/types/authenticators.ts +24 -0
- package/src/types/container.ts +66 -0
- package/src/types/index.ts +10 -0
package/src/http/Request.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @implements FR21
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { parseQueryString } from '../bodyparser/qsParse.js'
|
|
10
11
|
import type { CookieSigner } from '../security/CookieSigner.js'
|
|
11
12
|
import type { SignedUrl } from '../security/SignedUrl.js'
|
|
12
13
|
import type { Dict } from '../types/helpers.js'
|
|
@@ -701,6 +702,10 @@ export class Request extends Macroable {
|
|
|
701
702
|
/** Get parsed query string as an object. */
|
|
702
703
|
qs(): Dict<unknown> {
|
|
703
704
|
if (!this.#parsedQs) {
|
|
705
|
+
// The same parser the body parser uses. A local one lived here and split
|
|
706
|
+
// on "&" only: `?filter[status]=open` stayed the literal key
|
|
707
|
+
// "filter[status]", and `?tags[]=a&tags[]=b` silently kept only "b" —
|
|
708
|
+
// both are ordinary shapes for a filter or an HTML multi-select.
|
|
704
709
|
this.#parsedQs = parseQueryString(this.#raw.query)
|
|
705
710
|
}
|
|
706
711
|
return { ...this.#parsedQs }
|
|
@@ -1012,22 +1017,6 @@ function safeDecode(value: string): string {
|
|
|
1012
1017
|
}
|
|
1013
1018
|
}
|
|
1014
1019
|
|
|
1015
|
-
function parseQueryString(qs: string): Dict<unknown> {
|
|
1016
|
-
if (!qs) return {}
|
|
1017
|
-
const result: Dict<unknown> = {}
|
|
1018
|
-
for (const pair of qs.split('&')) {
|
|
1019
|
-
const eqIdx = pair.indexOf('=')
|
|
1020
|
-
if (eqIdx === -1) {
|
|
1021
|
-
result[safeDecode(pair)] = ''
|
|
1022
|
-
} else {
|
|
1023
|
-
const key = safeDecode(pair.slice(0, eqIdx))
|
|
1024
|
-
const value = safeDecode(pair.slice(eqIdx + 1))
|
|
1025
|
-
result[key] = value
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
return result
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
1020
|
interface AcceptEntry {
|
|
1032
1021
|
value: string
|
|
1033
1022
|
q: number
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ export {
|
|
|
40
40
|
createError,
|
|
41
41
|
E_FORBIDDEN,
|
|
42
42
|
E_HTTP_EXCEPTION,
|
|
43
|
+
E_HTTP_REQUEST_ABORTED,
|
|
43
44
|
E_ROUTE_NOT_FOUND,
|
|
44
45
|
E_ROW_NOT_FOUND,
|
|
45
46
|
E_UNAUTHORIZED,
|
|
@@ -48,6 +49,7 @@ export {
|
|
|
48
49
|
type ExceptionClass,
|
|
49
50
|
type ExceptionConstructor,
|
|
50
51
|
ExceptionHandler,
|
|
52
|
+
type HttpError,
|
|
51
53
|
InvalidArgumentsException,
|
|
52
54
|
RuntimeException,
|
|
53
55
|
type StatusPageRenderer,
|
|
@@ -58,6 +60,8 @@ export { RedirectBuilder } from './http/RedirectBuilder.js'
|
|
|
58
60
|
export type { RawRequest } from './http/Request.js'
|
|
59
61
|
export { Request } from './http/Request.js'
|
|
60
62
|
export { Response } from './http/Response.js'
|
|
63
|
+
export type { SseStreamOptions } from './http/SseStream.js'
|
|
64
|
+
export { SseStream } from './http/SseStream.js'
|
|
61
65
|
export { type GetterFn, Macroable, type MacroFn } from './utils/Macroable.js'
|
|
62
66
|
|
|
63
67
|
// ─── Session ────────────────────────────────────────────────
|
|
@@ -77,6 +81,12 @@ export type {
|
|
|
77
81
|
SessionRedisClientSource,
|
|
78
82
|
} from './session/drivers/RedisDriver.js'
|
|
79
83
|
export { RedisDriver as SessionRedisDriver } from './session/drivers/RedisDriver.js'
|
|
84
|
+
export {
|
|
85
|
+
E_SESSION_NOT_READY,
|
|
86
|
+
E_SESSION_TAGGING_NOT_SUPPORTED,
|
|
87
|
+
} from './session/errors.js'
|
|
88
|
+
export type { ValuePath } from './session/ReadOnlyValuesStore.js'
|
|
89
|
+
export { ReadOnlyValuesStore } from './session/ReadOnlyValuesStore.js'
|
|
80
90
|
export type {
|
|
81
91
|
SessionConfig,
|
|
82
92
|
SessionDriver,
|
|
@@ -139,7 +149,11 @@ export { StaticProvider } from './storage/StaticProvider.js'
|
|
|
139
149
|
|
|
140
150
|
// Request-filter security (CSRF, XSS, rate-limit, CORS, headers, path/param)
|
|
141
151
|
// lives in @c9up/blackhole. ream core keeps only the crypto primitives.
|
|
142
|
-
export {
|
|
152
|
+
export {
|
|
153
|
+
CookieSigner,
|
|
154
|
+
E_INSECURE_APP_KEY,
|
|
155
|
+
E_MISSING_APP_KEY,
|
|
156
|
+
} from './security/CookieSigner.js'
|
|
143
157
|
export {
|
|
144
158
|
constantTimeEq,
|
|
145
159
|
hasNativeCrypto,
|
|
@@ -179,6 +193,7 @@ export {
|
|
|
179
193
|
matchers,
|
|
180
194
|
OnRouteBuilder,
|
|
181
195
|
RouteBuilder,
|
|
196
|
+
RouteResource,
|
|
182
197
|
Router,
|
|
183
198
|
UrlBuilder,
|
|
184
199
|
} from './router/Router.js'
|
|
@@ -238,8 +253,15 @@ export { ConsoleKernel, defineConfig, Ignitor, prettyPrintError } from './Ignito
|
|
|
238
253
|
|
|
239
254
|
// ─── Providers ──────────────────────────────────────────────
|
|
240
255
|
|
|
256
|
+
export { errors } from './errors/aggregate.js'
|
|
241
257
|
export type { AppContext, ProviderContract } from './Provider.js'
|
|
242
258
|
export { Provider } from './Provider.js'
|
|
259
|
+
export type {
|
|
260
|
+
AuthenticatorName,
|
|
261
|
+
Authenticators,
|
|
262
|
+
ContainerBinding,
|
|
263
|
+
ContainerBindings,
|
|
264
|
+
} from './types/index.js'
|
|
243
265
|
|
|
244
266
|
// ─── Codemods ───────────────────────────────────────────────
|
|
245
267
|
|
|
@@ -349,4 +371,4 @@ export {
|
|
|
349
371
|
StatsTracker,
|
|
350
372
|
} from './scheduler/index.js'
|
|
351
373
|
|
|
352
|
-
export const VERSION = '0.
|
|
374
|
+
export const VERSION = '0.2.3'
|
package/src/router/Router.ts
CHANGED
|
@@ -402,6 +402,54 @@ export class RouteBuilder extends Macroable {
|
|
|
402
402
|
getDefinition(): RouteDefinition {
|
|
403
403
|
return this.#primary()
|
|
404
404
|
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* The handler this route dispatches to (AdonisJS `route.getHandler`).
|
|
408
|
+
*
|
|
409
|
+
* Either the inline function or the controller/method pair — a route always
|
|
410
|
+
* has exactly one of the two.
|
|
411
|
+
*/
|
|
412
|
+
getHandler(): RouteHandlerFunction | { target: AnyConstructor; method: string } | null {
|
|
413
|
+
const route = this.#primary()
|
|
414
|
+
return route.controller ?? route.handler
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Every middleware attached to this route (AdonisJS `route.getMiddleware`).
|
|
419
|
+
*
|
|
420
|
+
* Named entries and inline functions in one list, in the order they run.
|
|
421
|
+
*/
|
|
422
|
+
getMiddleware(): Array<string | MiddlewareFunction> {
|
|
423
|
+
const route = this.#primary()
|
|
424
|
+
return [...route.middleware, ...route.inlineMiddleware]
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* A serialisable description of this route (AdonisJS `route.toJSON`).
|
|
429
|
+
*
|
|
430
|
+
* What a routes-list command or a route-manifest generator reads, and why it
|
|
431
|
+
* carries names rather than the functions themselves.
|
|
432
|
+
*/
|
|
433
|
+
toJSON(): {
|
|
434
|
+
name?: string
|
|
435
|
+
pattern: string
|
|
436
|
+
methods: string[]
|
|
437
|
+
handler: RouteHandlerFunction | { target: AnyConstructor; method: string } | null
|
|
438
|
+
middleware: string[]
|
|
439
|
+
domain?: string
|
|
440
|
+
matchers: Record<string, ParamMatcher>
|
|
441
|
+
} {
|
|
442
|
+
const route = this.#primary()
|
|
443
|
+
return {
|
|
444
|
+
...(route.name === undefined ? {} : { name: route.name }),
|
|
445
|
+
pattern: route.path,
|
|
446
|
+
methods: this.#routes.map((entry) => entry.method),
|
|
447
|
+
handler: this.getHandler(),
|
|
448
|
+
middleware: [...route.middleware],
|
|
449
|
+
...(route.domain === undefined ? {} : { domain: route.domain }),
|
|
450
|
+
matchers: { ...route.matchers },
|
|
451
|
+
}
|
|
452
|
+
}
|
|
405
453
|
}
|
|
406
454
|
|
|
407
455
|
// ─── GroupBuilder ───────────────────────────────────────────
|
|
@@ -415,6 +463,16 @@ export class GroupBuilder extends Macroable {
|
|
|
415
463
|
this.#routes = routes
|
|
416
464
|
}
|
|
417
465
|
|
|
466
|
+
/**
|
|
467
|
+
* The routes this group wraps (AdonisJS `group.routes`).
|
|
468
|
+
*
|
|
469
|
+
* Read-only view: a group applies to its members, it does not hand out the
|
|
470
|
+
* backing array for someone else to splice.
|
|
471
|
+
*/
|
|
472
|
+
get routes(): readonly RouteDefinition[] {
|
|
473
|
+
return [...this.#routes]
|
|
474
|
+
}
|
|
475
|
+
|
|
418
476
|
/** Set URL prefix for all routes in the group. */
|
|
419
477
|
prefix(p: string): this {
|
|
420
478
|
for (const route of this.#routes) {
|
|
@@ -705,6 +763,8 @@ function isViewEngine(value: unknown): value is ViewEngine {
|
|
|
705
763
|
export class OnRouteBuilder {
|
|
706
764
|
#router: Router
|
|
707
765
|
#path: string
|
|
766
|
+
/** The route built by `render`/`redirect`/`redirectToPath`/`setHandler`. */
|
|
767
|
+
#route: RouteBuilder | undefined
|
|
708
768
|
|
|
709
769
|
constructor(router: Router, path: string) {
|
|
710
770
|
this.#router = router
|
|
@@ -721,7 +781,7 @@ export class OnRouteBuilder {
|
|
|
721
781
|
* seeds its own engine there.
|
|
722
782
|
*/
|
|
723
783
|
render(view: string, data?: Record<string, unknown>): RouteBuilder {
|
|
724
|
-
|
|
784
|
+
this.#route = this.#router.get(this.#path, async (ctx) => {
|
|
725
785
|
const candidate = Reflect.get(Object(ctx), 'view') ?? ctx.store.get('view')
|
|
726
786
|
if (!isViewEngine(candidate)) {
|
|
727
787
|
throw new Error(
|
|
@@ -732,6 +792,7 @@ export class OnRouteBuilder {
|
|
|
732
792
|
const html = await candidate.render(view, data)
|
|
733
793
|
ctx.response.type('text/html; charset=utf-8').send(html)
|
|
734
794
|
})
|
|
795
|
+
return this.#route
|
|
735
796
|
}
|
|
736
797
|
|
|
737
798
|
/**
|
|
@@ -740,16 +801,38 @@ export class OnRouteBuilder {
|
|
|
740
801
|
* for a fixed URL.
|
|
741
802
|
*/
|
|
742
803
|
redirect(name: string, params?: Record<string, string>, status = 302): RouteBuilder {
|
|
743
|
-
|
|
804
|
+
this.#route = this.#router.get(this.#path, async (ctx) => {
|
|
744
805
|
ctx.response.redirect().status(status).toRoute(name, params)
|
|
745
806
|
})
|
|
807
|
+
return this.#route
|
|
746
808
|
}
|
|
747
809
|
|
|
748
810
|
/** Redirect to a fixed path/URL (AdonisJS brisk `redirectToPath`). */
|
|
749
811
|
redirectToPath(target: string, status = 302): RouteBuilder {
|
|
750
|
-
|
|
812
|
+
this.#route = this.#router.get(this.#path, async (ctx) => {
|
|
751
813
|
ctx.response.redirect().status(status).toPath(target)
|
|
752
814
|
})
|
|
815
|
+
return this.#route
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Register a handler for this path (AdonisJS brisk `setHandler`).
|
|
820
|
+
*
|
|
821
|
+
* The escape hatch from the canned briskness: `render` and `redirect` cover
|
|
822
|
+
* the two common cases, this covers the third without going back through
|
|
823
|
+
* `router.get()`.
|
|
824
|
+
*/
|
|
825
|
+
setHandler(handler: RouteHandlerFunction): RouteBuilder {
|
|
826
|
+
this.#route = this.#router.get(this.#path, handler)
|
|
827
|
+
return this.#route
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* The route the last `render`/`redirect`/`setHandler` created, or
|
|
832
|
+
* `undefined` while none has been called (AdonisJS brisk `route`).
|
|
833
|
+
*/
|
|
834
|
+
get route(): RouteBuilder | undefined {
|
|
835
|
+
return this.#route
|
|
753
836
|
}
|
|
754
837
|
}
|
|
755
838
|
|
|
@@ -1236,7 +1319,7 @@ export class Router extends Macroable {
|
|
|
1236
1319
|
makeSignedUrl(name: string, params?: Record<string, string>, options?: SignedUrlOptions): string {
|
|
1237
1320
|
if (!this.#signedUrl) {
|
|
1238
1321
|
throw new Error(
|
|
1239
|
-
'[
|
|
1322
|
+
'[E_MISSING_APP_KEY] makeSignedUrl() requires an APP_KEY-backed signer. Set APP_KEY (>= 16 chars) so the encryption service is registered.',
|
|
1240
1323
|
)
|
|
1241
1324
|
}
|
|
1242
1325
|
const path = this.urlFor(name, params)
|
package/src/services/app.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Application } from '../Application.js'
|
|
11
|
+
import { createServiceProxy } from './createServiceProxy.js'
|
|
11
12
|
|
|
12
13
|
let instance: Application | undefined
|
|
13
14
|
|
|
@@ -25,28 +26,10 @@ export function clearApp(app: Application): void {
|
|
|
25
26
|
if (instance === app) instance = undefined
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const app: Application =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// `import { setX } from '.../services/x'` into a crash at import time, far
|
|
34
|
-
// from any real use. They are not members of what this stands in for, so
|
|
35
|
-
// answer undefined and let a genuine access be the one that reports.
|
|
36
|
-
if (typeof prop === 'symbol' || prop === 'then') {
|
|
37
|
-
return undefined
|
|
38
|
-
}
|
|
39
|
-
if (!instance) {
|
|
40
|
-
throw new Error(
|
|
41
|
-
'Application accessed before initialization. ' +
|
|
42
|
-
'Ensure this code runs during or after the boot phase.',
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
// Bind methods so private-field writes inside the class resolve
|
|
46
|
-
// against the real instance brand instead of the Proxy.
|
|
47
|
-
const value = Reflect.get(instance, prop, instance)
|
|
48
|
-
return typeof value === 'function' ? value.bind(instance) : value
|
|
49
|
-
},
|
|
50
|
-
})
|
|
29
|
+
const app: Application = createServiceProxy<Application>(
|
|
30
|
+
() => instance,
|
|
31
|
+
'Application accessed before initialization. ' +
|
|
32
|
+
'Ensure this code runs during or after the boot phase.',
|
|
33
|
+
)
|
|
51
34
|
|
|
52
35
|
export default app
|
package/src/services/console.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { Console } from '../console/Console.js'
|
|
12
|
+
import { createServiceProxy } from './createServiceProxy.js'
|
|
12
13
|
|
|
13
14
|
let instance: Console | undefined
|
|
14
15
|
|
|
@@ -26,26 +27,10 @@ export function clearConsole(consoleApp: Console): void {
|
|
|
26
27
|
if (instance === consoleApp) instance = undefined
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
const consoleApp: Console =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// `import { setX } from '.../services/x'` into a crash at import time, far
|
|
35
|
-
// from any real use. They are not members of what this stands in for, so
|
|
36
|
-
// answer undefined and let a genuine access be the one that reports.
|
|
37
|
-
if (typeof prop === 'symbol' || prop === 'then') {
|
|
38
|
-
return undefined
|
|
39
|
-
}
|
|
40
|
-
if (!instance) {
|
|
41
|
-
throw new Error(
|
|
42
|
-
'Console accessed before initialization. ' +
|
|
43
|
-
'Ensure this code runs during or after the boot phase.',
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
const value = Reflect.get(instance, prop, instance)
|
|
47
|
-
return typeof value === 'function' ? value.bind(instance) : value
|
|
48
|
-
},
|
|
49
|
-
})
|
|
30
|
+
const consoleApp: Console = createServiceProxy<Console>(
|
|
31
|
+
() => instance,
|
|
32
|
+
'Console accessed before initialization. ' +
|
|
33
|
+
'Ensure this code runs during or after the boot phase.',
|
|
34
|
+
)
|
|
50
35
|
|
|
51
36
|
export default consoleApp
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one implementation behind every `@c9up/ream/services/*` accessor.
|
|
3
|
+
*
|
|
4
|
+
* Each accessor stands in for a singleton that only exists once the Ignitor has
|
|
5
|
+
* built it, so the module-level export is a proxy that forwards to whatever the
|
|
6
|
+
* boot phase installed. Written out per service, the proxy was copied five
|
|
7
|
+
* times and the `symbol`/`then` guard below reached only three of them — a fix
|
|
8
|
+
* that has to be applied by hand five times is a fix that will be missed.
|
|
9
|
+
*
|
|
10
|
+
* @see services/app.ts for the accessor shape this backs.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build the proxy an accessor module default-exports.
|
|
15
|
+
*
|
|
16
|
+
* @param read Returns the live singleton, or `undefined` before boot.
|
|
17
|
+
* @param notReady Message thrown when something reads the accessor too early.
|
|
18
|
+
*/
|
|
19
|
+
export function createServiceProxy<T extends object>(
|
|
20
|
+
read: () => T | undefined,
|
|
21
|
+
notReady: string,
|
|
22
|
+
): T {
|
|
23
|
+
// The target is never read through — every trap forwards to `read()`.
|
|
24
|
+
const target: T = Object.create(null)
|
|
25
|
+
return new Proxy(target, {
|
|
26
|
+
get(_target, prop) {
|
|
27
|
+
// A module loader inspects what it imports before anyone uses it: it
|
|
28
|
+
// reads `then` to decide whether the namespace is thenable, and various
|
|
29
|
+
// symbols for interop and formatting. Throwing on those turns a plain
|
|
30
|
+
// `import { setX } from '.../services/x'` into a crash at import time,
|
|
31
|
+
// far from any real use. They are not members of what this stands in
|
|
32
|
+
// for, so answer undefined and let a genuine access be the one that
|
|
33
|
+
// reports.
|
|
34
|
+
if (typeof prop === 'symbol' || prop === 'then') {
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
const instance = read()
|
|
38
|
+
if (!instance) {
|
|
39
|
+
throw new Error(notReady)
|
|
40
|
+
}
|
|
41
|
+
// Bind methods so private-field writes inside the class resolve against
|
|
42
|
+
// the real instance brand instead of the Proxy.
|
|
43
|
+
const value = Reflect.get(instance, prop, instance)
|
|
44
|
+
return typeof value === 'function' ? value.bind(instance) : value
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
}
|
package/src/services/router.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { Router } from '../router/Router.js'
|
|
12
|
+
import { createServiceProxy } from './createServiceProxy.js'
|
|
12
13
|
|
|
13
14
|
let instance: Router | undefined
|
|
14
15
|
|
|
@@ -34,19 +35,10 @@ export function getRouter(): Router | undefined {
|
|
|
34
35
|
* Router proxy — defers all property access to the underlying instance.
|
|
35
36
|
* Throws if accessed before Ignitor initializes it.
|
|
36
37
|
*/
|
|
37
|
-
const router: Router =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
'Ensure your route files are loaded as preloads in reamrc.ts, not at import time.',
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
// Bind methods so private-field writes inside the class resolve
|
|
46
|
-
// against the real instance brand instead of the Proxy.
|
|
47
|
-
const value = Reflect.get(instance, prop, instance)
|
|
48
|
-
return typeof value === 'function' ? value.bind(instance) : value
|
|
49
|
-
},
|
|
50
|
-
})
|
|
38
|
+
const router: Router = createServiceProxy<Router>(
|
|
39
|
+
() => instance,
|
|
40
|
+
'Router accessed before initialization. ' +
|
|
41
|
+
'Ensure your route files are loaded as preloads in reamrc.ts, not at import time.',
|
|
42
|
+
)
|
|
51
43
|
|
|
52
44
|
export default router
|
package/src/services/server.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Server } from '../server/Server.js'
|
|
11
|
+
import { createServiceProxy } from './createServiceProxy.js'
|
|
11
12
|
|
|
12
13
|
let instance: Server | undefined
|
|
13
14
|
|
|
@@ -29,29 +30,10 @@ export function getServer(): Server | undefined {
|
|
|
29
30
|
return instance
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
const server: Server =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// `import { setX } from '.../services/x'` into a crash at import time, far
|
|
38
|
-
// from any real use. They are not members of what this stands in for, so
|
|
39
|
-
// answer undefined and let a genuine access be the one that reports.
|
|
40
|
-
if (typeof prop === 'symbol' || prop === 'then') {
|
|
41
|
-
return undefined
|
|
42
|
-
}
|
|
43
|
-
if (!instance) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
'Server accessed before initialization. ' +
|
|
46
|
-
'Ensure your kernel files are loaded as preloads in reamrc.ts.',
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
// Bind methods to the real instance so private-field access inside
|
|
50
|
-
// them resolves against the underlying class brand — passing the
|
|
51
|
-
// Proxy as `this` breaks `#field` writes (Proxy isn't branded).
|
|
52
|
-
const value = Reflect.get(instance, prop, instance)
|
|
53
|
-
return typeof value === 'function' ? value.bind(instance) : value
|
|
54
|
-
},
|
|
55
|
-
})
|
|
33
|
+
const server: Server = createServiceProxy<Server>(
|
|
34
|
+
() => instance,
|
|
35
|
+
'Server accessed before initialization. ' +
|
|
36
|
+
'Ensure your kernel files are loaded as preloads in reamrc.ts.',
|
|
37
|
+
)
|
|
56
38
|
|
|
57
39
|
export default server
|
|
@@ -49,4 +49,24 @@ export class ReadOnlyValuesStore {
|
|
|
49
49
|
has(path: ValuePath): boolean {
|
|
50
50
|
return this.get(path) !== undefined
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Every value, as a plain object (AdonisJS `toObject`).
|
|
55
|
+
*
|
|
56
|
+
* Same content as {@link all}; the two names exist because AdonisJS ships
|
|
57
|
+
* both and a migrated template may reach for either.
|
|
58
|
+
*/
|
|
59
|
+
toObject(): Record<string, unknown> {
|
|
60
|
+
return this.all()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Every value — what `JSON.stringify(flashMessages)` emits. */
|
|
64
|
+
toJSON(): Record<string, unknown> {
|
|
65
|
+
return this.all()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** JSON text, so `{{ flashMessages }}` in a template renders the contents. */
|
|
69
|
+
toString(): string {
|
|
70
|
+
return JSON.stringify(this.#values)
|
|
71
|
+
}
|
|
52
72
|
}
|
package/src/session/Session.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { SessionRedisClientSource } from './drivers/RedisDriver.js'
|
|
14
|
+
import { E_SESSION_NOT_READY, E_SESSION_TAGGING_NOT_SUPPORTED } from './errors.js'
|
|
15
|
+
import { ReadOnlyValuesStore } from './ReadOnlyValuesStore.js'
|
|
14
16
|
|
|
15
17
|
export interface SessionDriver {
|
|
16
18
|
/**
|
|
@@ -435,9 +437,16 @@ export class Session {
|
|
|
435
437
|
this.forget(INTENDED_URL_KEY)
|
|
436
438
|
}
|
|
437
439
|
|
|
438
|
-
/**
|
|
439
|
-
|
|
440
|
-
|
|
440
|
+
/**
|
|
441
|
+
* What the PREVIOUS request flashed for this one (AdonisJS `flashMessages`).
|
|
442
|
+
*
|
|
443
|
+
* A property holding a store, not a method returning a record: that is the
|
|
444
|
+
* AdonisJS shape, and it is what lets a controller or template read a nested
|
|
445
|
+
* failure with `session.flashMessages.get('errors.email')`. `all()` gives
|
|
446
|
+
* back the plain object.
|
|
447
|
+
*/
|
|
448
|
+
get flashMessages(): ReadOnlyValuesStore {
|
|
449
|
+
return new ReadOnlyValuesStore(this.#previousFlash)
|
|
441
450
|
}
|
|
442
451
|
|
|
443
452
|
/** Get a specific flash message from the previous request. */
|
|
@@ -519,8 +528,8 @@ export class Session {
|
|
|
519
528
|
* live. `flashMessages` is the other half — what the PREVIOUS request left
|
|
520
529
|
* for this one.
|
|
521
530
|
*/
|
|
522
|
-
get responseFlashMessages():
|
|
523
|
-
return
|
|
531
|
+
get responseFlashMessages(): ReadOnlyValuesStore {
|
|
532
|
+
return new ReadOnlyValuesStore(this.#flashData)
|
|
524
533
|
}
|
|
525
534
|
|
|
526
535
|
/**
|
|
@@ -625,14 +634,10 @@ export class Session {
|
|
|
625
634
|
*/
|
|
626
635
|
#taggingDriver(method: string): SessionDriverWithTagging {
|
|
627
636
|
if (this.#driver === undefined) {
|
|
628
|
-
throw new
|
|
629
|
-
`session.${method}() needs a session store — this session was built without one.`,
|
|
630
|
-
)
|
|
637
|
+
throw new E_SESSION_NOT_READY(method)
|
|
631
638
|
}
|
|
632
639
|
if (!supportsTagging(this.#driver)) {
|
|
633
|
-
throw new
|
|
634
|
-
`session.${method}() is not supported by the configured session store. Use the memory, redis, or database store.`,
|
|
635
|
-
)
|
|
640
|
+
throw new E_SESSION_TAGGING_NOT_SUPPORTED(method)
|
|
636
641
|
}
|
|
637
642
|
return this.#driver
|
|
638
643
|
}
|
|
@@ -232,7 +232,8 @@ function shareSessionWithView(ctx: HttpContext, session: Session): void {
|
|
|
232
232
|
const view = Reflect.get(Object(ctx), 'view')
|
|
233
233
|
const share = Reflect.get(Object(view), 'share')
|
|
234
234
|
if (typeof share !== 'function') return
|
|
235
|
-
|
|
235
|
+
// The session hands back a store already — no need to rebuild one here.
|
|
236
|
+
const flashMessages = session.flashMessages
|
|
236
237
|
share.call(view, {
|
|
237
238
|
session: new ReadOnlyValuesStore(session.all()),
|
|
238
239
|
flashMessages,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session exceptions, under the codes AdonisJS raises.
|
|
3
|
+
*
|
|
4
|
+
* The conditions were already guarded — they just threw bare `Error`s, so an
|
|
5
|
+
* app could only match on the message text.
|
|
6
|
+
*
|
|
7
|
+
* `E_SESSION_NOT_MUTABLE` has no counterpart here on purpose: it is what
|
|
8
|
+
* AdonisJS raises for a session initiated in read-only mode, and ream has no
|
|
9
|
+
* read-only mode. Declaring the class with nothing able to throw it would be
|
|
10
|
+
* worse than its absence — an app would guard a branch that never runs.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Exception } from '../http/Exception.js'
|
|
14
|
+
|
|
15
|
+
/** The session was used before a store was attached to it. */
|
|
16
|
+
export class E_SESSION_NOT_READY extends Exception {
|
|
17
|
+
static override status = 500
|
|
18
|
+
static override code = 'E_SESSION_NOT_READY'
|
|
19
|
+
|
|
20
|
+
constructor(method: string) {
|
|
21
|
+
super(`session.${method}() needs a session store — this session was built without one.`, {
|
|
22
|
+
status: 500,
|
|
23
|
+
code: 'E_SESSION_NOT_READY',
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The configured store cannot tag sessions (cookie and file stores cannot). */
|
|
29
|
+
export class E_SESSION_TAGGING_NOT_SUPPORTED extends Exception {
|
|
30
|
+
static override status = 500
|
|
31
|
+
static override code = 'E_SESSION_TAGGING_NOT_SUPPORTED'
|
|
32
|
+
|
|
33
|
+
constructor(method: string) {
|
|
34
|
+
super(
|
|
35
|
+
`session.${method}() is not supported by the configured session store. Use the memory, redis, or database store.`,
|
|
36
|
+
{ status: 500, code: 'E_SESSION_TAGGING_NOT_SUPPORTED' },
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guard name → what `auth.use(name)` hands back.
|
|
3
|
+
*
|
|
4
|
+
* Empty here, and deliberately: ream owns no auth package, so it cannot know
|
|
5
|
+
* the guards an app configured. The auth package fills it in, exactly as
|
|
6
|
+
* AdonisJS does with `Authenticators`:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* declare module '@c9up/ream/types' {
|
|
10
|
+
* interface Authenticators {
|
|
11
|
+
* session: SessionGuard
|
|
12
|
+
* jwt: JwtGuard
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Until something augments it, `keyof Authenticators` is `never` and `use()`
|
|
18
|
+
* falls through to its `unknown` signature — the behaviour ream had before,
|
|
19
|
+
* kept as the floor rather than as the ceiling.
|
|
20
|
+
*/
|
|
21
|
+
export type Authenticators = {}
|
|
22
|
+
|
|
23
|
+
/** A guard name this application knows about. */
|
|
24
|
+
export type AuthenticatorName = keyof Authenticators
|