@mantiq/core 0.3.0 → 0.4.0-rc.2
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/package.json +1 -1
- package/src/application/Application.ts +57 -2
- package/src/contracts/Router.ts +2 -2
- package/src/discovery/Discoverer.ts +200 -0
- package/src/http/Kernel.ts +4 -3
- package/src/http/Request.ts +1 -1
- package/src/index.ts +3 -1
- package/src/macroable/Macroable.ts +1 -1
- package/src/providers/CoreServiceProvider.ts +13 -0
- package/src/rateLimit/ThrottleRequests.ts +23 -4
- package/src/types.d.ts +3 -0
- package/src/websocket/WebSocketContext.ts +1 -1
package/package.json
CHANGED
|
@@ -30,7 +30,7 @@ export class Application extends ContainerImpl {
|
|
|
30
30
|
private constructor(private readonly basePath: string = process.cwd()) {
|
|
31
31
|
super()
|
|
32
32
|
// Register the application itself so it can be resolved from the container
|
|
33
|
-
this.instance(Application, this)
|
|
33
|
+
this.instance(Application as any, this)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// ── Singleton access ──────────────────────────────────────────────────────
|
|
@@ -133,6 +133,61 @@ export class Application extends ContainerImpl {
|
|
|
133
133
|
return this.basePath_(path ? `storage/${path}` : 'storage')
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// ── Package provider discovery ──────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Discover service providers from installed @mantiq/* packages.
|
|
140
|
+
* Each package declares its provider in package.json:
|
|
141
|
+
* { "mantiq": { "provider": "AuthServiceProvider" } }
|
|
142
|
+
*
|
|
143
|
+
* Install a package → provider auto-discovered. Uninstall → gone.
|
|
144
|
+
*/
|
|
145
|
+
async discoverPackageProviders(): Promise<Constructor<ServiceProvider>[]> {
|
|
146
|
+
const providers: Constructor<ServiceProvider>[] = []
|
|
147
|
+
const nodeModulesDir = this.basePath_('node_modules/@mantiq')
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const glob = new Bun.Glob('*/package.json')
|
|
151
|
+
for await (const file of glob.scan({ cwd: nodeModulesDir, absolute: false })) {
|
|
152
|
+
try {
|
|
153
|
+
const pkgJson = JSON.parse(
|
|
154
|
+
await Bun.file(`${nodeModulesDir}/${file}`).text()
|
|
155
|
+
)
|
|
156
|
+
const providerName = pkgJson?.mantiq?.provider
|
|
157
|
+
if (!providerName) continue
|
|
158
|
+
|
|
159
|
+
const pkgName = pkgJson.name as string
|
|
160
|
+
const mod = await import(pkgName)
|
|
161
|
+
if (mod[providerName] && typeof mod[providerName] === 'function') {
|
|
162
|
+
providers.push(mod[providerName])
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// Skip packages that can't be loaded
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
// node_modules/@mantiq doesn't exist
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return providers
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* One-call bootstrap: discover package providers, register, boot.
|
|
177
|
+
* Equivalent to:
|
|
178
|
+
* const providers = await app.discoverPackageProviders()
|
|
179
|
+
* await app.registerProviders([CoreServiceProvider, ...providers, ...userProviders])
|
|
180
|
+
* await app.bootProviders()
|
|
181
|
+
*/
|
|
182
|
+
async bootstrap(
|
|
183
|
+
coreProviders: Constructor<ServiceProvider>[] = [],
|
|
184
|
+
userProviders: Constructor<ServiceProvider>[] = [],
|
|
185
|
+
): Promise<void> {
|
|
186
|
+
const packageProviders = await this.discoverPackageProviders()
|
|
187
|
+
await this.registerProviders([...coreProviders, ...packageProviders, ...userProviders])
|
|
188
|
+
await this.bootProviders()
|
|
189
|
+
}
|
|
190
|
+
|
|
136
191
|
// ── Provider lifecycle ────────────────────────────────────────────────────
|
|
137
192
|
|
|
138
193
|
/**
|
|
@@ -191,7 +246,7 @@ export class Application extends ContainerImpl {
|
|
|
191
246
|
* Override make() to handle deferred provider loading.
|
|
192
247
|
* If a binding isn't found in the container, check deferred providers.
|
|
193
248
|
*/
|
|
194
|
-
make<T>(abstract: Bindable<T>): T {
|
|
249
|
+
override make<T>(abstract: Bindable<T>): T {
|
|
195
250
|
try {
|
|
196
251
|
return super.make(abstract)
|
|
197
252
|
} catch (err) {
|
package/src/contracts/Router.ts
CHANGED
|
@@ -19,14 +19,14 @@ export interface RouteMatch {
|
|
|
19
19
|
action: RouteAction
|
|
20
20
|
params: Record<string, any>
|
|
21
21
|
middleware: string[]
|
|
22
|
-
routeName?: string
|
|
22
|
+
routeName?: string | undefined
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface RouteDefinition {
|
|
26
26
|
method: HttpMethod | HttpMethod[]
|
|
27
27
|
path: string
|
|
28
28
|
action: RouteAction
|
|
29
|
-
name?: string
|
|
29
|
+
name?: string | undefined
|
|
30
30
|
middleware: string[]
|
|
31
31
|
wheres: Record<string, RegExp>
|
|
32
32
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, relative } from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Auto-discovers application classes by scanning conventional directories.
|
|
6
|
+
*
|
|
7
|
+
* In development: scans the filesystem and rebuilds the manifest.
|
|
8
|
+
* In production: reads a cached manifest from bootstrap/manifest.json.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const discoverer = new Discoverer(app.basePath)
|
|
12
|
+
* const manifest = await discoverer.build() // scan + cache
|
|
13
|
+
* const manifest = discoverer.cached() // read cache only
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface DiscoveryManifest {
|
|
17
|
+
providers: string[]
|
|
18
|
+
commands: string[]
|
|
19
|
+
routes: string[]
|
|
20
|
+
models: string[]
|
|
21
|
+
policies: string[]
|
|
22
|
+
middleware: string[]
|
|
23
|
+
observers: string[]
|
|
24
|
+
listeners: string[]
|
|
25
|
+
jobs: string[]
|
|
26
|
+
timestamp: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const EMPTY_MANIFEST: DiscoveryManifest = {
|
|
30
|
+
providers: [],
|
|
31
|
+
commands: [],
|
|
32
|
+
routes: [],
|
|
33
|
+
models: [],
|
|
34
|
+
policies: [],
|
|
35
|
+
middleware: [],
|
|
36
|
+
observers: [],
|
|
37
|
+
listeners: [],
|
|
38
|
+
jobs: [],
|
|
39
|
+
timestamp: 0,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Directories to scan, relative to basePath. */
|
|
43
|
+
const DISCOVERY_MAP: Array<{ key: keyof DiscoveryManifest; dir: string; pattern: string }> = [
|
|
44
|
+
{ key: 'providers', dir: 'app/Providers', pattern: '*ServiceProvider.ts' },
|
|
45
|
+
{ key: 'commands', dir: 'app/Console/Commands', pattern: '*Command.ts' },
|
|
46
|
+
{ key: 'routes', dir: 'routes', pattern: '*.ts' },
|
|
47
|
+
{ key: 'models', dir: 'app/Models', pattern: '*.ts' },
|
|
48
|
+
{ key: 'policies', dir: 'app/Policies', pattern: '*Policy.ts' },
|
|
49
|
+
{ key: 'middleware', dir: 'app/Http/Middleware', pattern: '*.ts' },
|
|
50
|
+
{ key: 'observers', dir: 'app/Observers', pattern: '*Observer.ts' },
|
|
51
|
+
{ key: 'listeners', dir: 'app/Listeners', pattern: '*Listener.ts' },
|
|
52
|
+
{ key: 'jobs', dir: 'app/Jobs', pattern: '*.ts' },
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
export class Discoverer {
|
|
56
|
+
private manifestPath: string
|
|
57
|
+
|
|
58
|
+
constructor(private basePath: string) {
|
|
59
|
+
this.manifestPath = join(basePath, 'bootstrap', 'manifest.json')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Scan all directories and build a fresh manifest.
|
|
64
|
+
* Writes to bootstrap/manifest.json for caching.
|
|
65
|
+
*/
|
|
66
|
+
async build(): Promise<DiscoveryManifest> {
|
|
67
|
+
const manifest: DiscoveryManifest = { ...EMPTY_MANIFEST, timestamp: Date.now() }
|
|
68
|
+
|
|
69
|
+
for (const { key, dir, pattern } of DISCOVERY_MAP) {
|
|
70
|
+
if (key === 'timestamp') continue
|
|
71
|
+
const fullDir = join(this.basePath, dir)
|
|
72
|
+
const files = await this.scanDirectory(fullDir, pattern)
|
|
73
|
+
;(manifest[key] as string[]) = files.map((f) => join(dir, f))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Write cache
|
|
77
|
+
this.writeManifest(manifest)
|
|
78
|
+
return manifest
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Read the cached manifest. Returns null if no cache exists.
|
|
83
|
+
*/
|
|
84
|
+
cached(): DiscoveryManifest | null {
|
|
85
|
+
if (!existsSync(this.manifestPath)) return null
|
|
86
|
+
try {
|
|
87
|
+
const raw = readFileSync(this.manifestPath, 'utf-8')
|
|
88
|
+
return JSON.parse(raw) as DiscoveryManifest
|
|
89
|
+
} catch {
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get the manifest — cached in production, fresh in development.
|
|
96
|
+
*/
|
|
97
|
+
async resolve(isDev = true): Promise<DiscoveryManifest> {
|
|
98
|
+
if (!isDev) {
|
|
99
|
+
const cached = this.cached()
|
|
100
|
+
if (cached) return cached
|
|
101
|
+
}
|
|
102
|
+
return this.build()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Load and instantiate all discovered service providers.
|
|
107
|
+
* Returns provider instances ready for registration.
|
|
108
|
+
*/
|
|
109
|
+
async loadProviders(manifest: DiscoveryManifest): Promise<any[]> {
|
|
110
|
+
const providers: any[] = []
|
|
111
|
+
for (const file of manifest.providers) {
|
|
112
|
+
const fullPath = join(this.basePath, file)
|
|
113
|
+
try {
|
|
114
|
+
const mod = await import(fullPath)
|
|
115
|
+
const ProviderClass = this.findExport(mod, (v) =>
|
|
116
|
+
typeof v === 'function' && v.prototype?.register && v.prototype?.boot
|
|
117
|
+
)
|
|
118
|
+
if (ProviderClass) providers.push(ProviderClass)
|
|
119
|
+
} catch {
|
|
120
|
+
// Skip unloadable providers
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return providers
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Load and register all discovered route files.
|
|
128
|
+
* Each route file should export a default function: (router) => void
|
|
129
|
+
*/
|
|
130
|
+
async loadRoutes(manifest: DiscoveryManifest, router: any): Promise<void> {
|
|
131
|
+
for (const file of manifest.routes) {
|
|
132
|
+
const fullPath = join(this.basePath, file)
|
|
133
|
+
try {
|
|
134
|
+
const mod = await import(fullPath)
|
|
135
|
+
if (typeof mod.default === 'function') {
|
|
136
|
+
mod.default(router)
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// Skip unloadable routes
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Load all discovered command classes for CLI kernel.
|
|
146
|
+
*/
|
|
147
|
+
async loadCommands(manifest: DiscoveryManifest): Promise<any[]> {
|
|
148
|
+
const commands: any[] = []
|
|
149
|
+
for (const file of manifest.commands) {
|
|
150
|
+
const fullPath = join(this.basePath, file)
|
|
151
|
+
try {
|
|
152
|
+
const mod = await import(fullPath)
|
|
153
|
+
for (const exported of Object.values(mod)) {
|
|
154
|
+
if (typeof exported !== 'function') continue
|
|
155
|
+
try {
|
|
156
|
+
const instance = new (exported as any)()
|
|
157
|
+
if (instance.name && typeof instance.handle === 'function') {
|
|
158
|
+
commands.push(instance)
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
// Not a command
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// Skip
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return commands
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Internal ──────────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
private async scanDirectory(dir: string, pattern: string): Promise<string[]> {
|
|
174
|
+
const files: string[] = []
|
|
175
|
+
try {
|
|
176
|
+
const glob = new Bun.Glob(pattern)
|
|
177
|
+
for await (const file of glob.scan({ cwd: dir, absolute: false })) {
|
|
178
|
+
// Skip dotfiles and test files
|
|
179
|
+
if (file.startsWith('.') || file.includes('.test.') || file.includes('.spec.')) continue
|
|
180
|
+
files.push(file)
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// Directory doesn't exist
|
|
184
|
+
}
|
|
185
|
+
return files.sort()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private writeManifest(manifest: DiscoveryManifest): void {
|
|
189
|
+
const dir = dirname(this.manifestPath)
|
|
190
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
191
|
+
writeFileSync(this.manifestPath, JSON.stringify(manifest, null, 2))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private findExport(mod: any, predicate: (v: any) => boolean): any {
|
|
195
|
+
for (const exported of Object.values(mod)) {
|
|
196
|
+
if (predicate(exported)) return exported
|
|
197
|
+
}
|
|
198
|
+
return null
|
|
199
|
+
}
|
|
200
|
+
}
|
package/src/http/Kernel.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Container, Constructor } from '../contracts/Container.ts'
|
|
|
2
2
|
import type { ExceptionHandler } from '../contracts/ExceptionHandler.ts'
|
|
3
3
|
import type { Middleware } from '../contracts/Middleware.ts'
|
|
4
4
|
import type { Router, RouteMatch } from '../contracts/Router.ts'
|
|
5
|
+
import type { MantiqRequest as MantiqRequestContract } from '../contracts/Request.ts'
|
|
5
6
|
import { MantiqRequest } from './Request.ts'
|
|
6
7
|
import { MantiqResponse } from './Response.ts'
|
|
7
8
|
import { Pipeline } from '../middleware/Pipeline.ts'
|
|
@@ -90,7 +91,7 @@ export class HttpKernel {
|
|
|
90
91
|
/**
|
|
91
92
|
* Main entry point. Passed to Bun.serve() as the fetch handler.
|
|
92
93
|
*/
|
|
93
|
-
async handle(bunRequest: Request, server: Server): Promise<Response> {
|
|
94
|
+
async handle(bunRequest: Request, server: Bun.Server<any>): Promise<Response> {
|
|
94
95
|
// WebSocket upgrade
|
|
95
96
|
if (bunRequest.headers.get('upgrade')?.toLowerCase() === 'websocket') {
|
|
96
97
|
return this.wsKernel.handleUpgrade(bunRequest, server)
|
|
@@ -145,7 +146,7 @@ export class HttpKernel {
|
|
|
145
146
|
port,
|
|
146
147
|
hostname,
|
|
147
148
|
fetch: (req, server) => this.handle(req, server),
|
|
148
|
-
websocket: this.wsKernel.getBunHandlers()
|
|
149
|
+
websocket: this.wsKernel.getBunHandlers() as Bun.WebSocketHandler<any>,
|
|
149
150
|
})
|
|
150
151
|
|
|
151
152
|
const display = hostname === '0.0.0.0' ? 'localhost' : hostname
|
|
@@ -172,7 +173,7 @@ export class HttpKernel {
|
|
|
172
173
|
* Call the route action (controller method or closure).
|
|
173
174
|
* Converts the return value to a Response.
|
|
174
175
|
*/
|
|
175
|
-
private async callAction(match: RouteMatch, request:
|
|
176
|
+
private async callAction(match: RouteMatch, request: MantiqRequestContract): Promise<Response> {
|
|
176
177
|
const action = match.action
|
|
177
178
|
|
|
178
179
|
let result: any
|
package/src/http/Request.ts
CHANGED
|
@@ -228,7 +228,7 @@ export class MantiqRequest implements MantiqRequestContract {
|
|
|
228
228
|
|
|
229
229
|
try {
|
|
230
230
|
if (contentType.includes('application/json')) {
|
|
231
|
-
this.parsedBody = await this.bunRequest.clone().json()
|
|
231
|
+
this.parsedBody = await this.bunRequest.clone().json() as Record<string, any>
|
|
232
232
|
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
233
233
|
const text = await this.bunRequest.clone().text()
|
|
234
234
|
this.parsedBody = Object.fromEntries(new URLSearchParams(text).entries())
|
package/src/index.ts
CHANGED
|
@@ -55,10 +55,12 @@ export { EncryptCookies } from './middleware/EncryptCookies.ts'
|
|
|
55
55
|
export { VerifyCsrfToken } from './middleware/VerifyCsrfToken.ts'
|
|
56
56
|
export { RateLimiter, MemoryStore } from './rateLimit/RateLimiter.ts'
|
|
57
57
|
export type { RateLimitConfig, RateLimitStore, LimiterResolver } from './rateLimit/RateLimiter.ts'
|
|
58
|
-
export { ThrottleRequests } from './rateLimit/ThrottleRequests.ts'
|
|
58
|
+
export { ThrottleRequests, getDefaultRateLimiter, setDefaultRateLimiter } from './rateLimit/ThrottleRequests.ts'
|
|
59
59
|
export { WebSocketKernel } from './websocket/WebSocketKernel.ts'
|
|
60
60
|
export { DefaultExceptionHandler } from './exceptions/Handler.ts'
|
|
61
61
|
export { CoreServiceProvider } from './providers/CoreServiceProvider.ts'
|
|
62
|
+
export { Discoverer } from './discovery/Discoverer.ts'
|
|
63
|
+
export type { DiscoveryManifest } from './discovery/Discoverer.ts'
|
|
62
64
|
|
|
63
65
|
// ── Encryption ────────────────────────────────────────────────────────────────
|
|
64
66
|
export { AesEncrypter } from './encryption/Encrypter.ts'
|
|
@@ -48,7 +48,7 @@ export interface MacroableInstance {
|
|
|
48
48
|
*/
|
|
49
49
|
export function Macroable<T extends Constructor>(Base: T): T & MacroableStatic {
|
|
50
50
|
class MacroableClass extends (Base as Constructor) {
|
|
51
|
-
|
|
51
|
+
static _macros = new Map<string, Function>()
|
|
52
52
|
|
|
53
53
|
static macro(name: string, fn: Function): void {
|
|
54
54
|
this._ensureOwnMacros()
|
|
@@ -9,6 +9,7 @@ import { TrimStringsMiddleware } from '../middleware/TrimStrings.ts'
|
|
|
9
9
|
import { StartSession } from '../middleware/StartSession.ts'
|
|
10
10
|
import { EncryptCookies } from '../middleware/EncryptCookies.ts'
|
|
11
11
|
import { VerifyCsrfToken } from '../middleware/VerifyCsrfToken.ts'
|
|
12
|
+
import { ThrottleRequests } from '../rateLimit/ThrottleRequests.ts'
|
|
12
13
|
import { ROUTER } from '../helpers/route.ts'
|
|
13
14
|
import { ENCRYPTER } from '../helpers/encrypt.ts'
|
|
14
15
|
import { AesEncrypter } from '../encryption/Encrypter.ts'
|
|
@@ -77,6 +78,9 @@ export class CoreServiceProvider extends ServiceProvider {
|
|
|
77
78
|
this.app.bind(EncryptCookies, (c) => new EncryptCookies(c.make<AesEncrypter>(ENCRYPTER)))
|
|
78
79
|
this.app.bind(VerifyCsrfToken, (c) => new VerifyCsrfToken(c.make<AesEncrypter>(ENCRYPTER)))
|
|
79
80
|
|
|
81
|
+
// Rate limiting — zero-config, uses shared in-memory store
|
|
82
|
+
this.app.singleton(ThrottleRequests, () => new ThrottleRequests())
|
|
83
|
+
|
|
80
84
|
// HTTP kernel — singleton, depends on Router + ExceptionHandler + WsKernel
|
|
81
85
|
this.app.singleton(HttpKernel, (c) => {
|
|
82
86
|
const router = c.make(RouterImpl)
|
|
@@ -93,5 +97,14 @@ export class CoreServiceProvider extends ServiceProvider {
|
|
|
93
97
|
const encrypter = await AesEncrypter.fromAppKey(appKey)
|
|
94
98
|
this.app.instance(ENCRYPTER, encrypter)
|
|
95
99
|
}
|
|
100
|
+
|
|
101
|
+
// ── Auto-register middleware aliases on HttpKernel ─────────────────────
|
|
102
|
+
const kernel = this.app.make(HttpKernel)
|
|
103
|
+
kernel.registerMiddleware('throttle', ThrottleRequests)
|
|
104
|
+
kernel.registerMiddleware('cors', CorsMiddleware)
|
|
105
|
+
kernel.registerMiddleware('trim', TrimStringsMiddleware)
|
|
106
|
+
kernel.registerMiddleware('encrypt.cookies', EncryptCookies)
|
|
107
|
+
kernel.registerMiddleware('session', StartSession)
|
|
108
|
+
kernel.registerMiddleware('csrf', VerifyCsrfToken)
|
|
96
109
|
}
|
|
97
110
|
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import type { MantiqRequest } from '../contracts/Request.ts'
|
|
2
|
+
import type { Middleware } from '../contracts/Middleware.ts'
|
|
2
3
|
import { HttpError } from '../errors/HttpError.ts'
|
|
3
|
-
import
|
|
4
|
+
import { RateLimiter } from './RateLimiter.ts'
|
|
5
|
+
import type { RateLimitConfig } from './RateLimiter.ts'
|
|
6
|
+
|
|
7
|
+
/** Shared default RateLimiter instance. */
|
|
8
|
+
let _defaultLimiter: RateLimiter | null = null
|
|
9
|
+
export function getDefaultRateLimiter(): RateLimiter {
|
|
10
|
+
if (!_defaultLimiter) _defaultLimiter = new RateLimiter()
|
|
11
|
+
return _defaultLimiter
|
|
12
|
+
}
|
|
13
|
+
export function setDefaultRateLimiter(limiter: RateLimiter): void {
|
|
14
|
+
_defaultLimiter = limiter
|
|
15
|
+
}
|
|
4
16
|
|
|
5
17
|
/**
|
|
6
18
|
* Middleware that throttles requests using the RateLimiter.
|
|
@@ -17,12 +29,19 @@ import type { RateLimiter, RateLimitConfig } from './RateLimiter.ts'
|
|
|
17
29
|
* X-RateLimit-Remaining: 45
|
|
18
30
|
* Retry-After: 30 (only when rate limited)
|
|
19
31
|
*/
|
|
20
|
-
export class ThrottleRequests {
|
|
32
|
+
export class ThrottleRequests implements Middleware {
|
|
21
33
|
private params: string[] = []
|
|
34
|
+
private rateLimiter: RateLimiter = getDefaultRateLimiter()
|
|
22
35
|
|
|
23
|
-
constructor(
|
|
36
|
+
constructor() {}
|
|
37
|
+
|
|
38
|
+
/** Use a custom RateLimiter instead of the default shared instance. */
|
|
39
|
+
useRateLimiter(limiter: RateLimiter): this {
|
|
40
|
+
this.rateLimiter = limiter
|
|
41
|
+
return this
|
|
42
|
+
}
|
|
24
43
|
|
|
25
|
-
setParameters(
|
|
44
|
+
setParameters(params: string[]): void {
|
|
26
45
|
this.params = params
|
|
27
46
|
}
|
|
28
47
|
|
package/src/types.d.ts
ADDED