@mantiq/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/package.json +65 -0
- package/src/application/Application.ts +241 -0
- package/src/cache/CacheManager.ts +180 -0
- package/src/cache/FileCacheStore.ts +113 -0
- package/src/cache/MemcachedCacheStore.ts +115 -0
- package/src/cache/MemoryCacheStore.ts +62 -0
- package/src/cache/NullCacheStore.ts +39 -0
- package/src/cache/RedisCacheStore.ts +125 -0
- package/src/cache/events.ts +52 -0
- package/src/config/ConfigRepository.ts +115 -0
- package/src/config/env.ts +26 -0
- package/src/container/Container.ts +198 -0
- package/src/container/ContextualBindingBuilder.ts +21 -0
- package/src/contracts/Cache.ts +49 -0
- package/src/contracts/Config.ts +24 -0
- package/src/contracts/Container.ts +68 -0
- package/src/contracts/DriverManager.ts +16 -0
- package/src/contracts/Encrypter.ts +32 -0
- package/src/contracts/EventDispatcher.ts +32 -0
- package/src/contracts/ExceptionHandler.ts +20 -0
- package/src/contracts/Hasher.ts +19 -0
- package/src/contracts/Middleware.ts +23 -0
- package/src/contracts/Request.ts +54 -0
- package/src/contracts/Response.ts +19 -0
- package/src/contracts/Router.ts +62 -0
- package/src/contracts/ServiceProvider.ts +31 -0
- package/src/contracts/Session.ts +47 -0
- package/src/encryption/Encrypter.ts +197 -0
- package/src/encryption/errors.ts +30 -0
- package/src/errors/ConfigKeyNotFoundError.ts +7 -0
- package/src/errors/ContainerResolutionError.ts +13 -0
- package/src/errors/ForbiddenError.ts +7 -0
- package/src/errors/HttpError.ts +16 -0
- package/src/errors/MantiqError.ts +16 -0
- package/src/errors/NotFoundError.ts +7 -0
- package/src/errors/TokenMismatchError.ts +10 -0
- package/src/errors/TooManyRequestsError.ts +10 -0
- package/src/errors/UnauthorizedError.ts +7 -0
- package/src/errors/ValidationError.ts +10 -0
- package/src/exceptions/DevErrorPage.ts +564 -0
- package/src/exceptions/Handler.ts +118 -0
- package/src/hashing/Argon2Hasher.ts +46 -0
- package/src/hashing/BcryptHasher.ts +36 -0
- package/src/hashing/HashManager.ts +80 -0
- package/src/helpers/abort.ts +46 -0
- package/src/helpers/app.ts +17 -0
- package/src/helpers/cache.ts +12 -0
- package/src/helpers/config.ts +15 -0
- package/src/helpers/encrypt.ts +22 -0
- package/src/helpers/env.ts +1 -0
- package/src/helpers/hash.ts +20 -0
- package/src/helpers/response.ts +69 -0
- package/src/helpers/route.ts +24 -0
- package/src/helpers/session.ts +11 -0
- package/src/http/Cookie.ts +26 -0
- package/src/http/Kernel.ts +252 -0
- package/src/http/Request.ts +249 -0
- package/src/http/Response.ts +112 -0
- package/src/http/UploadedFile.ts +56 -0
- package/src/index.ts +97 -0
- package/src/macroable/Macroable.ts +174 -0
- package/src/middleware/Cors.ts +91 -0
- package/src/middleware/EncryptCookies.ts +101 -0
- package/src/middleware/Pipeline.ts +66 -0
- package/src/middleware/StartSession.ts +90 -0
- package/src/middleware/TrimStrings.ts +32 -0
- package/src/middleware/VerifyCsrfToken.ts +130 -0
- package/src/providers/CoreServiceProvider.ts +97 -0
- package/src/routing/ResourceRegistrar.ts +64 -0
- package/src/routing/Route.ts +40 -0
- package/src/routing/RouteCollection.ts +50 -0
- package/src/routing/RouteMatcher.ts +92 -0
- package/src/routing/Router.ts +280 -0
- package/src/routing/events.ts +19 -0
- package/src/session/SessionManager.ts +75 -0
- package/src/session/Store.ts +192 -0
- package/src/session/handlers/CookieSessionHandler.ts +42 -0
- package/src/session/handlers/FileSessionHandler.ts +79 -0
- package/src/session/handlers/MemorySessionHandler.ts +35 -0
- package/src/websocket/WebSocketContext.ts +20 -0
- package/src/websocket/WebSocketKernel.ts +60 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Hasher } from '../contracts/Hasher.ts'
|
|
2
|
+
|
|
3
|
+
export interface Argon2Options {
|
|
4
|
+
memoryCost: number
|
|
5
|
+
timeCost: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const DEFAULTS: Argon2Options = {
|
|
9
|
+
memoryCost: 65536, // 64 MB
|
|
10
|
+
timeCost: 4,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Argon2id hasher using Bun.password (zero external deps).
|
|
15
|
+
*/
|
|
16
|
+
export class Argon2Hasher implements Hasher {
|
|
17
|
+
private readonly memoryCost: number
|
|
18
|
+
private readonly timeCost: number
|
|
19
|
+
|
|
20
|
+
constructor(options?: Partial<Argon2Options>) {
|
|
21
|
+
this.memoryCost = options?.memoryCost ?? DEFAULTS.memoryCost
|
|
22
|
+
this.timeCost = options?.timeCost ?? DEFAULTS.timeCost
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async make(value: string): Promise<string> {
|
|
26
|
+
return Bun.password.hash(value, {
|
|
27
|
+
algorithm: 'argon2id',
|
|
28
|
+
memoryCost: this.memoryCost,
|
|
29
|
+
timeCost: this.timeCost,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async check(value: string, hashedValue: string): Promise<boolean> {
|
|
34
|
+
return Bun.password.verify(value, hashedValue, 'argon2id')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
needsRehash(hashedValue: string): boolean {
|
|
38
|
+
// Argon2 format: $argon2id$v=19$m=<mem>,t=<time>,p=<par>$...
|
|
39
|
+
const match = hashedValue.match(/\$argon2id\$v=\d+\$m=(\d+),t=(\d+),/)
|
|
40
|
+
if (!match) return true
|
|
41
|
+
return (
|
|
42
|
+
parseInt(match[1]!, 10) !== this.memoryCost ||
|
|
43
|
+
parseInt(match[2]!, 10) !== this.timeCost
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Hasher } from '../contracts/Hasher.ts'
|
|
2
|
+
|
|
3
|
+
export interface BcryptOptions {
|
|
4
|
+
rounds: number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const DEFAULT_ROUNDS = 10
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Bcrypt hasher using Bun.password (zero external deps).
|
|
11
|
+
*/
|
|
12
|
+
export class BcryptHasher implements Hasher {
|
|
13
|
+
private readonly rounds: number
|
|
14
|
+
|
|
15
|
+
constructor(options?: Partial<BcryptOptions>) {
|
|
16
|
+
this.rounds = options?.rounds ?? DEFAULT_ROUNDS
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async make(value: string): Promise<string> {
|
|
20
|
+
return Bun.password.hash(value, {
|
|
21
|
+
algorithm: 'bcrypt',
|
|
22
|
+
cost: this.rounds,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async check(value: string, hashedValue: string): Promise<boolean> {
|
|
27
|
+
return Bun.password.verify(value, hashedValue, 'bcrypt')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
needsRehash(hashedValue: string): boolean {
|
|
31
|
+
// Bcrypt format: $2b$<cost>$...
|
|
32
|
+
const match = hashedValue.match(/^\$2[aby]\$(\d+)\$/)
|
|
33
|
+
if (!match) return true
|
|
34
|
+
return parseInt(match[1]!, 10) !== this.rounds
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Hasher } from '../contracts/Hasher.ts'
|
|
2
|
+
import type { DriverManager } from '../contracts/DriverManager.ts'
|
|
3
|
+
import { BcryptHasher } from './BcryptHasher.ts'
|
|
4
|
+
import { Argon2Hasher } from './Argon2Hasher.ts'
|
|
5
|
+
|
|
6
|
+
export interface HashConfig {
|
|
7
|
+
driver: string
|
|
8
|
+
bcrypt?: { rounds?: number }
|
|
9
|
+
argon2?: { memoryCost?: number; timeCost?: number }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Multi-driver hash manager (Laravel-style).
|
|
14
|
+
*
|
|
15
|
+
* Supports bcrypt and argon2id out of the box.
|
|
16
|
+
* Custom drivers can be added via `extend()`.
|
|
17
|
+
*/
|
|
18
|
+
export class HashManager implements DriverManager<Hasher>, Hasher {
|
|
19
|
+
private readonly config: HashConfig
|
|
20
|
+
private readonly drivers = new Map<string, Hasher>()
|
|
21
|
+
private readonly customCreators = new Map<string, () => Hasher>()
|
|
22
|
+
|
|
23
|
+
constructor(config?: Partial<HashConfig>) {
|
|
24
|
+
this.config = {
|
|
25
|
+
driver: config?.driver ?? 'bcrypt',
|
|
26
|
+
bcrypt: config?.bcrypt ?? {},
|
|
27
|
+
argon2: config?.argon2 ?? {},
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── DriverManager ───────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
driver(name?: string): Hasher {
|
|
34
|
+
const driverName = name ?? this.getDefaultDriver()
|
|
35
|
+
|
|
36
|
+
if (!this.drivers.has(driverName)) {
|
|
37
|
+
this.drivers.set(driverName, this.createDriver(driverName))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return this.drivers.get(driverName)!
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
extend(name: string, factory: () => Hasher): void {
|
|
44
|
+
this.customCreators.set(name, factory)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getDefaultDriver(): string {
|
|
48
|
+
return this.config.driver
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Hasher (delegates to default driver) ────────────────────────────────
|
|
52
|
+
|
|
53
|
+
async make(value: string): Promise<string> {
|
|
54
|
+
return this.driver().make(value)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async check(value: string, hashedValue: string): Promise<boolean> {
|
|
58
|
+
return this.driver().check(value, hashedValue)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
needsRehash(hashedValue: string): boolean {
|
|
62
|
+
return this.driver().needsRehash(hashedValue)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Internal ────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
private createDriver(name: string): Hasher {
|
|
68
|
+
const custom = this.customCreators.get(name)
|
|
69
|
+
if (custom) return custom()
|
|
70
|
+
|
|
71
|
+
switch (name) {
|
|
72
|
+
case 'bcrypt':
|
|
73
|
+
return new BcryptHasher(this.config.bcrypt)
|
|
74
|
+
case 'argon2':
|
|
75
|
+
return new Argon2Hasher(this.config.argon2)
|
|
76
|
+
default:
|
|
77
|
+
throw new Error(`Unsupported hash driver: ${name}. Use extend() to register custom drivers.`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { HttpError } from '../errors/HttpError.ts'
|
|
2
|
+
import { NotFoundError } from '../errors/NotFoundError.ts'
|
|
3
|
+
import { UnauthorizedError } from '../errors/UnauthorizedError.ts'
|
|
4
|
+
import { ForbiddenError } from '../errors/ForbiddenError.ts'
|
|
5
|
+
import { TooManyRequestsError } from '../errors/TooManyRequestsError.ts'
|
|
6
|
+
|
|
7
|
+
const STATUS_MESSAGES: Record<number, string> = {
|
|
8
|
+
400: 'Bad Request',
|
|
9
|
+
401: 'Unauthorized',
|
|
10
|
+
403: 'Forbidden',
|
|
11
|
+
404: 'Not Found',
|
|
12
|
+
405: 'Method Not Allowed',
|
|
13
|
+
409: 'Conflict',
|
|
14
|
+
410: 'Gone',
|
|
15
|
+
422: 'Unprocessable Entity',
|
|
16
|
+
429: 'Too Many Requests',
|
|
17
|
+
500: 'Internal Server Error',
|
|
18
|
+
503: 'Service Unavailable',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Throw an HTTP exception from anywhere — controllers, middleware, services.
|
|
23
|
+
*
|
|
24
|
+
* Uses typed error subclasses where available.
|
|
25
|
+
*
|
|
26
|
+
* @throws HttpError (or typed subclass)
|
|
27
|
+
*
|
|
28
|
+
* @example abort(404)
|
|
29
|
+
* @example abort(403, 'You cannot edit this post')
|
|
30
|
+
* @example abort(429, 'Slow down', { 'Retry-After': '60' })
|
|
31
|
+
*/
|
|
32
|
+
export function abort(
|
|
33
|
+
status: number,
|
|
34
|
+
message?: string,
|
|
35
|
+
headers?: Record<string, string>,
|
|
36
|
+
): never {
|
|
37
|
+
const msg = message ?? STATUS_MESSAGES[status] ?? 'Error'
|
|
38
|
+
|
|
39
|
+
switch (status) {
|
|
40
|
+
case 401: throw new UnauthorizedError(msg, headers)
|
|
41
|
+
case 403: throw new ForbiddenError(msg, headers)
|
|
42
|
+
case 404: throw new NotFoundError(msg, headers)
|
|
43
|
+
case 429: throw new TooManyRequestsError(msg)
|
|
44
|
+
default: throw new HttpError(status, msg, headers)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Application } from '../application/Application.ts'
|
|
2
|
+
import type { Bindable } from '../contracts/Container.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Access the application container, or resolve a binding from it.
|
|
6
|
+
*
|
|
7
|
+
* @example app() // Returns the Application instance
|
|
8
|
+
* @example app(Router) // Resolves the Router from the container
|
|
9
|
+
* @example app('cache.store') // Resolves by string alias
|
|
10
|
+
*/
|
|
11
|
+
export function app(): Application
|
|
12
|
+
export function app<T>(abstract: Bindable<T>): T
|
|
13
|
+
export function app<T>(abstract?: Bindable<T>): Application | T {
|
|
14
|
+
const instance = Application.getInstance()
|
|
15
|
+
if (abstract === undefined) return instance
|
|
16
|
+
return instance.make(abstract)
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Application } from '../application/Application.ts'
|
|
2
|
+
import { CacheManager } from '../cache/CacheManager.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Access the cache manager or a specific store.
|
|
6
|
+
*
|
|
7
|
+
* @example cache() // default store (CacheManager, proxies to default)
|
|
8
|
+
* @example cache().store('file') // specific store
|
|
9
|
+
*/
|
|
10
|
+
export function cache(): CacheManager {
|
|
11
|
+
return Application.getInstance().make(CacheManager)
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Application } from '../application/Application.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Access config values from anywhere in the application.
|
|
5
|
+
*
|
|
6
|
+
* Reads from the ConfigRepository loaded at bootstrap.
|
|
7
|
+
* Config is always available after Application.create() — before any provider runs.
|
|
8
|
+
*
|
|
9
|
+
* @example config('app.name')
|
|
10
|
+
* @example config('database.connections.sqlite.path', ':memory:')
|
|
11
|
+
* @example config<boolean>('app.debug', false)
|
|
12
|
+
*/
|
|
13
|
+
export function config<T = any>(key: string, defaultValue?: T): T {
|
|
14
|
+
return Application.getInstance().config().get(key, defaultValue)
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Application } from '../application/Application.ts'
|
|
2
|
+
import type { AesEncrypter } from '../encryption/Encrypter.ts'
|
|
3
|
+
|
|
4
|
+
export const ENCRYPTER = Symbol('Encrypter')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Encrypt a string value using the application encrypter.
|
|
8
|
+
*
|
|
9
|
+
* @example const token = await encrypt('secret-value')
|
|
10
|
+
*/
|
|
11
|
+
export async function encrypt(value: string): Promise<string> {
|
|
12
|
+
return Application.getInstance().make<AesEncrypter>(ENCRYPTER).encrypt(value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Decrypt a string value using the application encrypter.
|
|
17
|
+
*
|
|
18
|
+
* @example const plain = await decrypt(token)
|
|
19
|
+
*/
|
|
20
|
+
export async function decrypt(value: string): Promise<string> {
|
|
21
|
+
return Application.getInstance().make<AesEncrypter>(ENCRYPTER).decrypt(value)
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { env } from '../config/env.ts'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Application } from '../application/Application.ts'
|
|
2
|
+
import { HashManager } from '../hashing/HashManager.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hash a plain-text value using the default hasher.
|
|
6
|
+
*
|
|
7
|
+
* @example const hashed = await hash('password')
|
|
8
|
+
*/
|
|
9
|
+
export async function hash(value: string): Promise<string> {
|
|
10
|
+
return Application.getInstance().make(HashManager).make(value)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check a plain-text value against a hash.
|
|
15
|
+
*
|
|
16
|
+
* @example if (await hashCheck('password', hashed)) { ... }
|
|
17
|
+
*/
|
|
18
|
+
export async function hashCheck(value: string, hashedValue: string): Promise<boolean> {
|
|
19
|
+
return Application.getInstance().make(HashManager).check(value, hashedValue)
|
|
20
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { MantiqResponse, ResponseBuilder } from '../http/Response.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a chainable response builder.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* response().status(201).cookie('token', 'abc').json({ created: true })
|
|
8
|
+
*/
|
|
9
|
+
export function response(): ResponseBuilder {
|
|
10
|
+
return new ResponseBuilder()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Return a JSON response.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* return json({ users }, 200)
|
|
18
|
+
* return json({ error: 'Not found' }, 404)
|
|
19
|
+
*/
|
|
20
|
+
export function json(data: any, status: number = 200, headers?: Record<string, string>): Response {
|
|
21
|
+
return MantiqResponse.json(data, status, headers)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Return an HTML response.
|
|
26
|
+
*
|
|
27
|
+
* @example return html('<h1>Hello</h1>')
|
|
28
|
+
*/
|
|
29
|
+
export function html(content: string, status: number = 200): Response {
|
|
30
|
+
return MantiqResponse.html(content, status)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Return a redirect response.
|
|
35
|
+
*
|
|
36
|
+
* @example return redirect('/dashboard')
|
|
37
|
+
*/
|
|
38
|
+
export function redirect(url: string, status: number = 302): Response {
|
|
39
|
+
return MantiqResponse.redirect(url, status)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Return a 204 No Content response. */
|
|
43
|
+
export function noContent(): Response {
|
|
44
|
+
return MantiqResponse.noContent()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Return a streaming response.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* return stream((controller) => {
|
|
52
|
+
* controller.enqueue('chunk 1')
|
|
53
|
+
* controller.close()
|
|
54
|
+
* })
|
|
55
|
+
*/
|
|
56
|
+
export function stream(
|
|
57
|
+
callback: (controller: ReadableStreamDefaultController) => void | Promise<void>,
|
|
58
|
+
): Response {
|
|
59
|
+
return MantiqResponse.stream(callback)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Return a file download response.
|
|
64
|
+
*
|
|
65
|
+
* @example return download(buffer, 'report.pdf')
|
|
66
|
+
*/
|
|
67
|
+
export function download(content: Uint8Array | string, filename: string, mimeType?: string): Response {
|
|
68
|
+
return MantiqResponse.download(content, filename, mimeType)
|
|
69
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Application } from '../application/Application.ts'
|
|
2
|
+
import type { Router } from '../contracts/Router.ts'
|
|
3
|
+
|
|
4
|
+
// @internal: Router symbol — the Router implementation binds itself under this key
|
|
5
|
+
export const ROUTER = Symbol('Router')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate a URL for a named route.
|
|
9
|
+
*
|
|
10
|
+
* @param name - The route name (e.g., 'users.show')
|
|
11
|
+
* @param params - Route parameters (e.g., { id: 1 })
|
|
12
|
+
* @param absolute - If true, prepends APP_URL to make an absolute URL
|
|
13
|
+
*
|
|
14
|
+
* @example route('users.show', { id: 42 }) // '/users/42'
|
|
15
|
+
* @example route('users.show', { id: 42 }, true) // 'http://localhost:3000/users/42'
|
|
16
|
+
*/
|
|
17
|
+
export function route(
|
|
18
|
+
name: string,
|
|
19
|
+
params?: Record<string, any>,
|
|
20
|
+
absolute: boolean = false,
|
|
21
|
+
): string {
|
|
22
|
+
const router = Application.getInstance().make<Router>(ROUTER)
|
|
23
|
+
return router.url(name, params, absolute)
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Application } from '../application/Application.ts'
|
|
2
|
+
import { SessionManager } from '../session/SessionManager.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Access the session manager.
|
|
6
|
+
*
|
|
7
|
+
* @example session() // SessionManager instance
|
|
8
|
+
*/
|
|
9
|
+
export function session(): SessionManager {
|
|
10
|
+
return Application.getInstance().make(SessionManager)
|
|
11
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CookieOptions } from '../contracts/Response.ts'
|
|
2
|
+
|
|
3
|
+
export function parseCookies(cookieHeader: string | null): Record<string, string> {
|
|
4
|
+
if (!cookieHeader) return {}
|
|
5
|
+
|
|
6
|
+
return Object.fromEntries(
|
|
7
|
+
cookieHeader.split(';').map((pair) => {
|
|
8
|
+
const [key, ...rest] = pair.trim().split('=')
|
|
9
|
+
return [key?.trim() ?? '', decodeURIComponent(rest.join('='))]
|
|
10
|
+
}),
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function serializeCookie(name: string, value: string, options: CookieOptions = {}): string {
|
|
15
|
+
let str = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`
|
|
16
|
+
|
|
17
|
+
if (options.maxAge !== undefined) str += `; Max-Age=${options.maxAge}`
|
|
18
|
+
if (options.expires) str += `; Expires=${options.expires.toUTCString()}`
|
|
19
|
+
if (options.path) str += `; Path=${options.path}`
|
|
20
|
+
if (options.domain) str += `; Domain=${options.domain}`
|
|
21
|
+
if (options.secure) str += '; Secure'
|
|
22
|
+
if (options.httpOnly) str += '; HttpOnly'
|
|
23
|
+
if (options.sameSite) str += `; SameSite=${options.sameSite}`
|
|
24
|
+
|
|
25
|
+
return str
|
|
26
|
+
}
|