@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,198 @@
|
|
|
1
|
+
import type { Bindable, Container, Constructor, Resolvable } from '../contracts/Container.ts'
|
|
2
|
+
import { ContainerResolutionError } from '../errors/ContainerResolutionError.ts'
|
|
3
|
+
import { ContextualBindingBuilder } from './ContextualBindingBuilder.ts'
|
|
4
|
+
|
|
5
|
+
type Binding<T> = {
|
|
6
|
+
concrete: Resolvable<T>
|
|
7
|
+
singleton: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ContainerImpl implements Container {
|
|
11
|
+
private bindings = new Map<Bindable<any>, Binding<any>>()
|
|
12
|
+
private instances = new Map<Bindable<any>, any>()
|
|
13
|
+
private aliases = new Map<string | symbol, Bindable<any>>()
|
|
14
|
+
/** contextual[concrete][abstract] = resolvable */
|
|
15
|
+
private contextual = new Map<Constructor<any>, Map<Bindable<any>, Resolvable<any>>>()
|
|
16
|
+
/** Track resolution stack for circular dependency detection */
|
|
17
|
+
private resolving = new Set<Bindable<any>>()
|
|
18
|
+
|
|
19
|
+
// ── Registration ──────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
bind<T>(abstract: Bindable<T>, concrete: Resolvable<T>): void {
|
|
22
|
+
this.bindings.set(abstract, { concrete, singleton: false })
|
|
23
|
+
// Clear any cached singleton instance if re-binding
|
|
24
|
+
this.instances.delete(abstract)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
singleton<T>(abstract: Bindable<T>, concrete: Resolvable<T>): void {
|
|
28
|
+
this.bindings.set(abstract, { concrete, singleton: true })
|
|
29
|
+
this.instances.delete(abstract)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
instance<T>(abstract: Bindable<T>, instance: T): void {
|
|
33
|
+
this.instances.set(abstract, instance)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Resolution ────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
make<T>(abstract: Bindable<T>): T {
|
|
39
|
+
// Resolve aliases
|
|
40
|
+
const resolved = this.resolveAlias(abstract)
|
|
41
|
+
|
|
42
|
+
// Return cached singleton instance
|
|
43
|
+
if (this.instances.has(resolved)) {
|
|
44
|
+
return this.instances.get(resolved) as T
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Detect circular dependencies
|
|
48
|
+
if (this.resolving.has(resolved)) {
|
|
49
|
+
throw new ContainerResolutionError(
|
|
50
|
+
resolved,
|
|
51
|
+
'circular_dependency',
|
|
52
|
+
`Circular dependency detected while resolving ${String(resolved)}`,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.resolving.add(resolved)
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const binding = this.bindings.get(resolved)
|
|
60
|
+
|
|
61
|
+
let instance: T
|
|
62
|
+
|
|
63
|
+
if (binding) {
|
|
64
|
+
instance = this.build<T>(binding.concrete)
|
|
65
|
+
|
|
66
|
+
if (binding.singleton) {
|
|
67
|
+
this.instances.set(resolved, instance)
|
|
68
|
+
}
|
|
69
|
+
} else if (typeof resolved === 'function') {
|
|
70
|
+
// Auto-resolution: try to instantiate the class directly
|
|
71
|
+
instance = this.autoResolve<T>(resolved as Constructor<T>)
|
|
72
|
+
} else {
|
|
73
|
+
throw new ContainerResolutionError(
|
|
74
|
+
resolved,
|
|
75
|
+
'not_bound',
|
|
76
|
+
`No binding found for '${String(resolved)}'`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return instance
|
|
81
|
+
} finally {
|
|
82
|
+
this.resolving.delete(resolved)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
makeOrDefault<T>(abstract: Bindable<T>, defaultValue: T): T {
|
|
87
|
+
try {
|
|
88
|
+
return this.make(abstract)
|
|
89
|
+
} catch {
|
|
90
|
+
return defaultValue
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
has(abstract: Bindable<any>): boolean {
|
|
95
|
+
const resolved = this.resolveAlias(abstract)
|
|
96
|
+
return this.bindings.has(resolved) || this.instances.has(resolved)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Contextual Binding ────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
when(concrete: Constructor<any>): ContextualBindingBuilder {
|
|
102
|
+
return new ContextualBindingBuilder(concrete, (abstract, resolvable) => {
|
|
103
|
+
if (!this.contextual.has(concrete)) {
|
|
104
|
+
this.contextual.set(concrete, new Map())
|
|
105
|
+
}
|
|
106
|
+
this.contextual.get(concrete)!.set(abstract, resolvable)
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Aliases ───────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
alias(abstract: Bindable<any>, alias: string | symbol): void {
|
|
113
|
+
this.aliases.set(alias, abstract)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Flush ─────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
flush(): void {
|
|
119
|
+
this.bindings.clear()
|
|
120
|
+
this.instances.clear()
|
|
121
|
+
this.aliases.clear()
|
|
122
|
+
this.contextual.clear()
|
|
123
|
+
this.resolving.clear()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Method injection ──────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
call<T>(target: object, method: string, extraParams?: Record<string, any>): T {
|
|
129
|
+
const fn = (target as any)[method]
|
|
130
|
+
if (typeof fn !== 'function') {
|
|
131
|
+
throw new ContainerResolutionError(
|
|
132
|
+
method,
|
|
133
|
+
'not_bound',
|
|
134
|
+
`Method '${method}' does not exist on target`,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
// @internal: Basic call without deep reflection; packages can override if needed
|
|
138
|
+
const params = extraParams ? Object.values(extraParams) : []
|
|
139
|
+
return fn.apply(target, params) as T
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Private helpers ───────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
private resolveAlias(abstract: Bindable<any>): Bindable<any> {
|
|
145
|
+
if (typeof abstract === 'string' || typeof abstract === 'symbol') {
|
|
146
|
+
return this.aliases.get(abstract as string | symbol) ?? abstract
|
|
147
|
+
}
|
|
148
|
+
return abstract
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private build<T>(concrete: Resolvable<T>): T {
|
|
152
|
+
if (typeof concrete === 'function' && concrete.prototype === undefined) {
|
|
153
|
+
// Arrow function / factory
|
|
154
|
+
return (concrete as (c: Container) => T)(this)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (typeof concrete === 'function') {
|
|
158
|
+
const ctor = concrete as Constructor<T>
|
|
159
|
+
// Check if it looks like a factory (no prototype.constructor === Function means arrow)
|
|
160
|
+
try {
|
|
161
|
+
return this.autoResolve(ctor)
|
|
162
|
+
} catch {
|
|
163
|
+
// If autoResolve fails, try calling as factory
|
|
164
|
+
return (concrete as (c: Container) => T)(this)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return (concrete as (c: Container) => T)(this)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private autoResolve<T>(ctor: Constructor<T>, parentCtor?: Constructor<any>): T {
|
|
172
|
+
// Try instantiating with no args first (common case)
|
|
173
|
+
try {
|
|
174
|
+
// Check for contextual bindings if we have a parent
|
|
175
|
+
const contextMap = parentCtor ? this.contextual.get(parentCtor) : undefined
|
|
176
|
+
|
|
177
|
+
// Attempt construction. For classes with required deps this will fail
|
|
178
|
+
// but we attempt it for zero-dep classes.
|
|
179
|
+
if (ctor.length === 0) {
|
|
180
|
+
return new ctor()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// If we can't auto-resolve (no metadata), throw clearly
|
|
184
|
+
throw new ContainerResolutionError(
|
|
185
|
+
ctor,
|
|
186
|
+
'unresolvable_parameter',
|
|
187
|
+
`'${ctor.name}' has ${ctor.length} constructor parameter(s) that cannot be auto-resolved. Register an explicit binding.`,
|
|
188
|
+
)
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (err instanceof ContainerResolutionError) throw err
|
|
191
|
+
throw new ContainerResolutionError(
|
|
192
|
+
ctor,
|
|
193
|
+
'unresolvable_parameter',
|
|
194
|
+
String(err),
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Bindable, Resolvable } from '../contracts/Container.ts'
|
|
2
|
+
|
|
3
|
+
type GiveFn = (abstract: Bindable<any>, concrete: Resolvable<any>) => void
|
|
4
|
+
|
|
5
|
+
export class ContextualBindingBuilder {
|
|
6
|
+
private needsAbstract!: Bindable<any>
|
|
7
|
+
|
|
8
|
+
constructor(
|
|
9
|
+
private readonly concrete: new (...args: any[]) => any,
|
|
10
|
+
private readonly giveFn: GiveFn,
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
needs<T>(abstract: Bindable<T>): { give<U extends T>(concrete: Resolvable<U>): void } {
|
|
14
|
+
this.needsAbstract = abstract
|
|
15
|
+
return {
|
|
16
|
+
give: <U>(concrete: Resolvable<U>) => {
|
|
17
|
+
this.giveFn(this.needsAbstract, concrete as Resolvable<any>)
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract for cache store implementations.
|
|
3
|
+
*/
|
|
4
|
+
export interface CacheStore {
|
|
5
|
+
/**
|
|
6
|
+
* Retrieve an item from the cache.
|
|
7
|
+
* Returns `undefined` if not found or expired.
|
|
8
|
+
*/
|
|
9
|
+
get<T = unknown>(key: string): Promise<T | undefined>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Store an item in the cache.
|
|
13
|
+
* @param ttl Time-to-live in seconds. `undefined` = forever.
|
|
14
|
+
*/
|
|
15
|
+
put(key: string, value: unknown, ttl?: number): Promise<void>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Remove an item from the cache.
|
|
19
|
+
*/
|
|
20
|
+
forget(key: string): Promise<boolean>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if an item exists (and is not expired).
|
|
24
|
+
*/
|
|
25
|
+
has(key: string): Promise<boolean>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Remove all items from the cache.
|
|
29
|
+
*/
|
|
30
|
+
flush(): Promise<void>
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Increment a numeric value in the cache.
|
|
34
|
+
* Returns the new value.
|
|
35
|
+
*/
|
|
36
|
+
increment(key: string, value?: number): Promise<number>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Decrement a numeric value in the cache.
|
|
40
|
+
* Returns the new value.
|
|
41
|
+
*/
|
|
42
|
+
decrement(key: string, value?: number): Promise<number>
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Store an item in the cache if the key does not already exist.
|
|
46
|
+
* Returns true if the value was stored, false if key already exists.
|
|
47
|
+
*/
|
|
48
|
+
add(key: string, value: unknown, ttl?: number): Promise<boolean>
|
|
49
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
/**
|
|
3
|
+
* Get a config value using dot-notation.
|
|
4
|
+
* @param key - Dot-notation key: 'database.connections.sqlite.path'
|
|
5
|
+
* @param defaultValue - Returned if key doesn't exist
|
|
6
|
+
* @throws ConfigKeyNotFoundError if key doesn't exist and no default provided
|
|
7
|
+
*/
|
|
8
|
+
get<T = any>(key: string, defaultValue?: T): T
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Set a config value at runtime (not persisted).
|
|
12
|
+
*/
|
|
13
|
+
set(key: string, value: any): void
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a key exists.
|
|
17
|
+
*/
|
|
18
|
+
has(key: string): boolean
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get all config as a flat object.
|
|
22
|
+
*/
|
|
23
|
+
all(): Record<string, any>
|
|
24
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type Constructor<T = any> = new (...args: any[]) => T
|
|
2
|
+
export type Bindable<T = any> = Constructor<T> | symbol | string
|
|
3
|
+
export type Resolvable<T = any> = Constructor<T> | ((container: Container) => T)
|
|
4
|
+
|
|
5
|
+
export interface ContextualBindingBuilder {
|
|
6
|
+
needs<T>(abstract: Bindable<T>): { give<U extends T>(concrete: Resolvable<U>): void }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Container {
|
|
10
|
+
/**
|
|
11
|
+
* Register a transient binding. A new instance is created on each resolve.
|
|
12
|
+
* @param abstract - The interface/class/symbol to bind
|
|
13
|
+
* @param concrete - The implementation class or factory function
|
|
14
|
+
*/
|
|
15
|
+
bind<T>(abstract: Bindable<T>, concrete: Resolvable<T>): void
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register a singleton binding. Created once, cached forever.
|
|
19
|
+
* @param abstract - The interface/class/symbol to bind
|
|
20
|
+
* @param concrete - The implementation class or factory function
|
|
21
|
+
*/
|
|
22
|
+
singleton<T>(abstract: Bindable<T>, concrete: Resolvable<T>): void
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Register an existing instance as a singleton.
|
|
26
|
+
* @param abstract - The interface/class/symbol to bind
|
|
27
|
+
* @param instance - The pre-created instance
|
|
28
|
+
*/
|
|
29
|
+
instance<T>(abstract: Bindable<T>, instance: T): void
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a binding from the container.
|
|
33
|
+
* @param abstract - The interface/class/symbol to resolve
|
|
34
|
+
* @throws ContainerResolutionError if the binding can't be resolved
|
|
35
|
+
*/
|
|
36
|
+
make<T>(abstract: Bindable<T>): T
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a binding, or return the default if not bound.
|
|
40
|
+
*/
|
|
41
|
+
makeOrDefault<T>(abstract: Bindable<T>, defaultValue: T): T
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if a binding exists.
|
|
45
|
+
*/
|
|
46
|
+
has(abstract: Bindable<any>): boolean
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Start building a contextual binding.
|
|
50
|
+
* @example container.when(UserController).needs(Logger).give(UserLogger)
|
|
51
|
+
*/
|
|
52
|
+
when(concrete: Constructor<any>): ContextualBindingBuilder
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register an alias for an abstract.
|
|
56
|
+
*/
|
|
57
|
+
alias(abstract: Bindable<any>, alias: string | symbol): void
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Remove all bindings and cached instances.
|
|
61
|
+
*/
|
|
62
|
+
flush(): void
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Call a method on an object, injecting its dependencies.
|
|
66
|
+
*/
|
|
67
|
+
call<T>(target: object, method: string, extraParams?: Record<string, any>): T
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface DriverManager<T> {
|
|
2
|
+
/**
|
|
3
|
+
* Get a driver instance by name. Returns the default driver if omitted.
|
|
4
|
+
*/
|
|
5
|
+
driver(name?: string): T
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register a custom driver factory.
|
|
9
|
+
*/
|
|
10
|
+
extend(name: string, factory: () => T): void
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns the configured default driver name.
|
|
14
|
+
*/
|
|
15
|
+
getDefaultDriver(): string
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract for encryption services.
|
|
3
|
+
*
|
|
4
|
+
* All implementations must provide symmetric encrypt/decrypt using
|
|
5
|
+
* a consistent serialization format.
|
|
6
|
+
*/
|
|
7
|
+
export interface Encrypter {
|
|
8
|
+
/**
|
|
9
|
+
* Encrypt a string value.
|
|
10
|
+
*/
|
|
11
|
+
encrypt(value: string): Promise<string>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Decrypt a string value.
|
|
15
|
+
*/
|
|
16
|
+
decrypt(encrypted: string): Promise<string>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Encrypt a value (serialized as JSON).
|
|
20
|
+
*/
|
|
21
|
+
encryptObject(value: unknown): Promise<string>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Decrypt and deserialize a JSON value.
|
|
25
|
+
*/
|
|
26
|
+
decryptObject<T = unknown>(encrypted: string): Promise<T>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the encryption key.
|
|
30
|
+
*/
|
|
31
|
+
getKey(): CryptoKey | ArrayBuffer
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Constructor } from './Container.ts'
|
|
2
|
+
|
|
3
|
+
export abstract class Event {
|
|
4
|
+
readonly timestamp: Date = new Date()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export abstract class Listener {
|
|
8
|
+
abstract handle(event: Event): void | Promise<void>
|
|
9
|
+
|
|
10
|
+
shouldQueue: boolean = false
|
|
11
|
+
queue?: string
|
|
12
|
+
connection?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type EventHandler = (event: Event) => void | Promise<void>
|
|
16
|
+
|
|
17
|
+
export interface EventDispatcher {
|
|
18
|
+
/**
|
|
19
|
+
* Dispatch an event to all registered listeners.
|
|
20
|
+
*/
|
|
21
|
+
emit(event: Event): Promise<void>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Register a listener for an event class.
|
|
25
|
+
*/
|
|
26
|
+
on(eventClass: Constructor<Event>, listener: Constructor<Listener> | EventHandler): void
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Remove all listeners for an event class.
|
|
30
|
+
*/
|
|
31
|
+
forget(eventClass: Constructor<Event>): void
|
|
32
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Constructor } from './Container.ts'
|
|
2
|
+
import type { MantiqRequest } from './Request.ts'
|
|
3
|
+
|
|
4
|
+
export interface ExceptionHandler {
|
|
5
|
+
/**
|
|
6
|
+
* Report the exception (log it, send to error tracker, etc.).
|
|
7
|
+
* Called for every exception unless it's in the dontReport list.
|
|
8
|
+
*/
|
|
9
|
+
report(error: Error): Promise<void>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Render the exception as an HTTP response.
|
|
13
|
+
*/
|
|
14
|
+
render(request: MantiqRequest, error: unknown): Response
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Exception classes that should not be reported (e.g., 404s).
|
|
18
|
+
*/
|
|
19
|
+
dontReport: Constructor<Error>[]
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract for hashing services.
|
|
3
|
+
*/
|
|
4
|
+
export interface Hasher {
|
|
5
|
+
/**
|
|
6
|
+
* Hash a plain-text value.
|
|
7
|
+
*/
|
|
8
|
+
make(value: string): Promise<string>
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check a plain-text value against a hash.
|
|
12
|
+
*/
|
|
13
|
+
check(value: string, hashedValue: string): Promise<boolean>
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if the given hash needs to be re-hashed (e.g. cost changed).
|
|
17
|
+
*/
|
|
18
|
+
needsRehash(hashedValue: string): boolean
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { MantiqRequest } from './Request.ts'
|
|
2
|
+
|
|
3
|
+
export type NextFunction = () => Promise<Response>
|
|
4
|
+
|
|
5
|
+
export interface Middleware {
|
|
6
|
+
/**
|
|
7
|
+
* Handle an incoming request.
|
|
8
|
+
* Call next() to pass to the next middleware or route handler.
|
|
9
|
+
* Return a Response without calling next() to short-circuit.
|
|
10
|
+
*/
|
|
11
|
+
handle(request: MantiqRequest, next: NextFunction): Promise<Response>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Optional: runs after the response is sent to the client.
|
|
15
|
+
* Useful for logging, cleanup, analytics.
|
|
16
|
+
*/
|
|
17
|
+
terminate?(request: MantiqRequest, response: Response): Promise<void>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Optional: set parameters parsed from the middleware alias string (e.g., 'throttle:60,1').
|
|
21
|
+
*/
|
|
22
|
+
setParameters?(params: string[]): void
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { UploadedFile } from '../http/UploadedFile.ts'
|
|
2
|
+
import type { SessionStore } from '../session/Store.ts'
|
|
3
|
+
|
|
4
|
+
export interface MantiqRequest {
|
|
5
|
+
// ── HTTP basics ──────────────────────────────────────────────────────────
|
|
6
|
+
method(): string
|
|
7
|
+
path(): string
|
|
8
|
+
url(): string
|
|
9
|
+
fullUrl(): string
|
|
10
|
+
|
|
11
|
+
// ── Input ────────────────────────────────────────────────────────────────
|
|
12
|
+
query(key: string, defaultValue?: string): string
|
|
13
|
+
query(): Record<string, string>
|
|
14
|
+
input(key: string, defaultValue?: any): Promise<any>
|
|
15
|
+
input(): Promise<Record<string, any>>
|
|
16
|
+
only(...keys: string[]): Promise<Record<string, any>>
|
|
17
|
+
except(...keys: string[]): Promise<Record<string, any>>
|
|
18
|
+
has(...keys: string[]): boolean
|
|
19
|
+
filled(...keys: string[]): Promise<boolean>
|
|
20
|
+
|
|
21
|
+
// ── Headers & metadata ───────────────────────────────────────────────────
|
|
22
|
+
header(key: string, defaultValue?: string): string | undefined
|
|
23
|
+
headers(): Record<string, string>
|
|
24
|
+
cookie(key: string, defaultValue?: string): string | undefined
|
|
25
|
+
setCookies(cookies: Record<string, string>): void
|
|
26
|
+
ip(): string
|
|
27
|
+
userAgent(): string
|
|
28
|
+
accepts(...types: string[]): string | false
|
|
29
|
+
expectsJson(): boolean
|
|
30
|
+
isJson(): boolean
|
|
31
|
+
|
|
32
|
+
// ── Files ────────────────────────────────────────────────────────────────
|
|
33
|
+
file(key: string): UploadedFile | null
|
|
34
|
+
files(key: string): UploadedFile[]
|
|
35
|
+
hasFile(key: string): boolean
|
|
36
|
+
|
|
37
|
+
// ── Route params ─────────────────────────────────────────────────────────
|
|
38
|
+
param(key: string, defaultValue?: any): any
|
|
39
|
+
params(): Record<string, any>
|
|
40
|
+
setRouteParams(params: Record<string, any>): void
|
|
41
|
+
|
|
42
|
+
// ── Session ──────────────────────────────────────────────────────────────
|
|
43
|
+
session(): SessionStore
|
|
44
|
+
setSession(session: SessionStore): void
|
|
45
|
+
hasSession(): boolean
|
|
46
|
+
|
|
47
|
+
// ── Auth ─────────────────────────────────────────────────────────────────
|
|
48
|
+
user<T = any>(): T | null
|
|
49
|
+
isAuthenticated(): boolean
|
|
50
|
+
setUser(user: any): void
|
|
51
|
+
|
|
52
|
+
// ── Raw ──────────────────────────────────────────────────────────────────
|
|
53
|
+
raw(): Request
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface CookieOptions {
|
|
2
|
+
maxAge?: number
|
|
3
|
+
expires?: Date
|
|
4
|
+
path?: string
|
|
5
|
+
domain?: string
|
|
6
|
+
secure?: boolean
|
|
7
|
+
httpOnly?: boolean
|
|
8
|
+
sameSite?: 'Strict' | 'Lax' | 'None'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface MantiqResponseBuilder {
|
|
12
|
+
status(code: number): this
|
|
13
|
+
header(key: string, value: string): this
|
|
14
|
+
withHeaders(headers: Record<string, string>): this
|
|
15
|
+
cookie(name: string, value: string, options?: CookieOptions): this
|
|
16
|
+
json(data: any): Response
|
|
17
|
+
html(content: string): Response
|
|
18
|
+
redirect(url: string): Response
|
|
19
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Constructor } from './Container.ts'
|
|
2
|
+
import type { MantiqRequest } from './Request.ts'
|
|
3
|
+
|
|
4
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'
|
|
5
|
+
|
|
6
|
+
export type RouteAction =
|
|
7
|
+
| [Constructor<any>, string]
|
|
8
|
+
| ((request: MantiqRequest) => any)
|
|
9
|
+
| string
|
|
10
|
+
|
|
11
|
+
export interface RouteGroupOptions {
|
|
12
|
+
prefix?: string
|
|
13
|
+
middleware?: string[]
|
|
14
|
+
as?: string
|
|
15
|
+
namespace?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RouteMatch {
|
|
19
|
+
action: RouteAction
|
|
20
|
+
params: Record<string, any>
|
|
21
|
+
middleware: string[]
|
|
22
|
+
routeName?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RouteDefinition {
|
|
26
|
+
method: HttpMethod | HttpMethod[]
|
|
27
|
+
path: string
|
|
28
|
+
action: RouteAction
|
|
29
|
+
name?: string
|
|
30
|
+
middleware: string[]
|
|
31
|
+
wheres: Record<string, RegExp>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Router {
|
|
35
|
+
get(path: string, action: RouteAction): RouterRoute
|
|
36
|
+
post(path: string, action: RouteAction): RouterRoute
|
|
37
|
+
put(path: string, action: RouteAction): RouterRoute
|
|
38
|
+
patch(path: string, action: RouteAction): RouterRoute
|
|
39
|
+
delete(path: string, action: RouteAction): RouterRoute
|
|
40
|
+
options(path: string, action: RouteAction): RouterRoute
|
|
41
|
+
match(methods: HttpMethod[], path: string, action: RouteAction): RouterRoute
|
|
42
|
+
any(path: string, action: RouteAction): RouterRoute
|
|
43
|
+
resource(name: string, controller: Constructor<any>): void
|
|
44
|
+
apiResource(name: string, controller: Constructor<any>): void
|
|
45
|
+
group(options: RouteGroupOptions, callback: (router: Router) => void): void
|
|
46
|
+
url(name: string, params?: Record<string, any>, absolute?: boolean): string
|
|
47
|
+
resolve(request: MantiqRequest): RouteMatch
|
|
48
|
+
routes(): RouteDefinition[]
|
|
49
|
+
model(param: string, model: Constructor<any>): void
|
|
50
|
+
bind(param: string, resolver: (value: string) => Promise<any>): void
|
|
51
|
+
/** Register controller classes for string-based resolution ('AuthController@login') */
|
|
52
|
+
controllers(map: Record<string, Constructor<any>>): void
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RouterRoute {
|
|
56
|
+
name(name: string): this
|
|
57
|
+
middleware(...middleware: string[]): this
|
|
58
|
+
where(param: string, pattern: string | RegExp): this
|
|
59
|
+
whereNumber(param: string): this
|
|
60
|
+
whereAlpha(param: string): this
|
|
61
|
+
whereUuid(param: string): this
|
|
62
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Bindable, Container } from './Container.ts'
|
|
2
|
+
|
|
3
|
+
export abstract class ServiceProvider {
|
|
4
|
+
constructor(protected app: Container) {}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register bindings in the container.
|
|
8
|
+
* Called for ALL providers before any boot() methods are called.
|
|
9
|
+
* Do NOT resolve dependencies here — other providers may not be registered yet.
|
|
10
|
+
*/
|
|
11
|
+
register(): void | Promise<void> {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Boot the service. Called after all providers are registered.
|
|
15
|
+
* Safe to resolve dependencies from the container.
|
|
16
|
+
*/
|
|
17
|
+
boot(): void | Promise<void> {}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* If true, this provider is lazy-loaded.
|
|
21
|
+
* It is registered but not booted until one of its bindings is first resolved.
|
|
22
|
+
*/
|
|
23
|
+
deferred: boolean = false
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The bindings this provider offers (used for deferred loading).
|
|
27
|
+
*/
|
|
28
|
+
provides(): Bindable<any>[] {
|
|
29
|
+
return []
|
|
30
|
+
}
|
|
31
|
+
}
|