@resq-sw/decorators 0.1.0
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 +277 -0
- package/lib/_utils.d.ts +46 -0
- package/lib/_utils.d.ts.map +1 -0
- package/lib/_utils.js +91 -0
- package/lib/_utils.js.map +1 -0
- package/lib/after/after.d.ts +60 -0
- package/lib/after/after.d.ts.map +1 -0
- package/lib/after/after.fn.d.ts +39 -0
- package/lib/after/after.fn.d.ts.map +1 -0
- package/lib/after/after.fn.js +59 -0
- package/lib/after/after.fn.js.map +1 -0
- package/lib/after/after.js +41 -0
- package/lib/after/after.js.map +1 -0
- package/lib/after/after.types.d.ts +86 -0
- package/lib/after/after.types.d.ts.map +1 -0
- package/lib/after/after.types.js +0 -0
- package/lib/after/index.d.ts +3 -0
- package/lib/after/index.js +2 -0
- package/lib/before/before.d.ts +61 -0
- package/lib/before/before.d.ts.map +1 -0
- package/lib/before/before.fn.d.ts +39 -0
- package/lib/before/before.fn.d.ts.map +1 -0
- package/lib/before/before.fn.js +51 -0
- package/lib/before/before.fn.js.map +1 -0
- package/lib/before/before.js +40 -0
- package/lib/before/before.js.map +1 -0
- package/lib/before/before.types.d.ts +48 -0
- package/lib/before/before.types.d.ts.map +1 -0
- package/lib/before/before.types.js +0 -0
- package/lib/before/index.d.ts +3 -0
- package/lib/before/index.js +2 -0
- package/lib/bind/bind.d.ts +75 -0
- package/lib/bind/bind.d.ts.map +1 -0
- package/lib/bind/bind.fn.d.ts +46 -0
- package/lib/bind/bind.fn.d.ts.map +1 -0
- package/lib/bind/bind.fn.js +39 -0
- package/lib/bind/bind.fn.js.map +1 -0
- package/lib/bind/bind.js +64 -0
- package/lib/bind/bind.js.map +1 -0
- package/lib/bind/bind.types.d.ts +36 -0
- package/lib/bind/bind.types.d.ts.map +1 -0
- package/lib/bind/bind.types.js +0 -0
- package/lib/bind/index.d.ts +3 -0
- package/lib/bind/index.js +2 -0
- package/lib/debounce/debounce.d.ts +34 -0
- package/lib/debounce/debounce.d.ts.map +1 -0
- package/lib/debounce/debounce.fn.d.ts +40 -0
- package/lib/debounce/debounce.fn.d.ts.map +1 -0
- package/lib/debounce/debounce.fn.js +47 -0
- package/lib/debounce/debounce.fn.js.map +1 -0
- package/lib/debounce/debounce.js +48 -0
- package/lib/debounce/debounce.js.map +1 -0
- package/lib/debounce/index.d.ts +2 -0
- package/lib/debounce/index.js +2 -0
- package/lib/delay/delay.d.ts +35 -0
- package/lib/delay/delay.d.ts.map +1 -0
- package/lib/delay/delay.fn.d.ts +33 -0
- package/lib/delay/delay.fn.d.ts.map +1 -0
- package/lib/delay/delay.fn.js +38 -0
- package/lib/delay/delay.fn.js.map +1 -0
- package/lib/delay/delay.js +43 -0
- package/lib/delay/delay.js.map +1 -0
- package/lib/delay/index.d.ts +2 -0
- package/lib/delay/index.js +2 -0
- package/lib/delegate/delegate.d.ts +48 -0
- package/lib/delegate/delegate.d.ts.map +1 -0
- package/lib/delegate/delegate.fn.d.ts +57 -0
- package/lib/delegate/delegate.fn.d.ts.map +1 -0
- package/lib/delegate/delegate.fn.js +55 -0
- package/lib/delegate/delegate.fn.js.map +1 -0
- package/lib/delegate/delegate.js +56 -0
- package/lib/delegate/delegate.js.map +1 -0
- package/lib/delegate/delegate.types.d.ts +45 -0
- package/lib/delegate/delegate.types.d.ts.map +1 -0
- package/lib/delegate/delegate.types.js +0 -0
- package/lib/delegate/index.d.ts +3 -0
- package/lib/delegate/index.js +2 -0
- package/lib/exec-time/exec-time.d.ts +42 -0
- package/lib/exec-time/exec-time.d.ts.map +1 -0
- package/lib/exec-time/exec-time.fn.d.ts +50 -0
- package/lib/exec-time/exec-time.fn.d.ts.map +1 -0
- package/lib/exec-time/exec-time.fn.js +91 -0
- package/lib/exec-time/exec-time.fn.js.map +1 -0
- package/lib/exec-time/exec-time.js +55 -0
- package/lib/exec-time/exec-time.js.map +1 -0
- package/lib/exec-time/exec-time.types.d.ts +70 -0
- package/lib/exec-time/exec-time.types.d.ts.map +1 -0
- package/lib/exec-time/exec-time.types.js +0 -0
- package/lib/exec-time/index.d.ts +4 -0
- package/lib/exec-time/index.js +3 -0
- package/lib/execute/execute.d.ts +78 -0
- package/lib/execute/execute.d.ts.map +1 -0
- package/lib/execute/execute.js +82 -0
- package/lib/execute/execute.js.map +1 -0
- package/lib/execute/index.d.ts +2 -0
- package/lib/execute/index.js +2 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +19 -0
- package/lib/memoize/index.d.ts +3 -0
- package/lib/memoize/index.js +2 -0
- package/lib/memoize/memoize.d.ts +67 -0
- package/lib/memoize/memoize.d.ts.map +1 -0
- package/lib/memoize/memoize.fn.d.ts +69 -0
- package/lib/memoize/memoize.fn.d.ts.map +1 -0
- package/lib/memoize/memoize.fn.js +43 -0
- package/lib/memoize/memoize.fn.js.map +1 -0
- package/lib/memoize/memoize.js +40 -0
- package/lib/memoize/memoize.js.map +1 -0
- package/lib/memoize/memoize.types.d.ts +107 -0
- package/lib/memoize/memoize.types.d.ts.map +1 -0
- package/lib/memoize/memoize.types.js +0 -0
- package/lib/memoize-async/index.d.ts +4 -0
- package/lib/memoize-async/index.js +3 -0
- package/lib/memoize-async/memoize-async.d.ts +68 -0
- package/lib/memoize-async/memoize-async.d.ts.map +1 -0
- package/lib/memoize-async/memoize-async.fn.d.ts +69 -0
- package/lib/memoize-async/memoize-async.fn.d.ts.map +1 -0
- package/lib/memoize-async/memoize-async.fn.js +52 -0
- package/lib/memoize-async/memoize-async.fn.js.map +1 -0
- package/lib/memoize-async/memoize-async.js +15 -0
- package/lib/memoize-async/memoize-async.js.map +1 -0
- package/lib/memoize-async/memoize-async.types.d.ts +74 -0
- package/lib/memoize-async/memoize-async.types.d.ts.map +1 -0
- package/lib/memoize-async/memoize-async.types.js +0 -0
- package/lib/observer/index.d.ts +3 -0
- package/lib/observer/index.js +2 -0
- package/lib/observer/observer.d.ts +54 -0
- package/lib/observer/observer.d.ts.map +1 -0
- package/lib/observer/observer.js +85 -0
- package/lib/observer/observer.js.map +1 -0
- package/lib/observer/observer.types.d.ts +41 -0
- package/lib/observer/observer.types.d.ts.map +1 -0
- package/lib/observer/observer.types.js +0 -0
- package/lib/rate-limit/index.d.ts +4 -0
- package/lib/rate-limit/index.js +3 -0
- package/lib/rate-limit/rate-limit.d.ts +58 -0
- package/lib/rate-limit/rate-limit.d.ts.map +1 -0
- package/lib/rate-limit/rate-limit.fn.d.ts +43 -0
- package/lib/rate-limit/rate-limit.fn.d.ts.map +1 -0
- package/lib/rate-limit/rate-limit.fn.js +56 -0
- package/lib/rate-limit/rate-limit.fn.js.map +1 -0
- package/lib/rate-limit/rate-limit.js +65 -0
- package/lib/rate-limit/rate-limit.js.map +1 -0
- package/lib/rate-limit/rate-limit.types.d.ts +148 -0
- package/lib/rate-limit/rate-limit.types.d.ts.map +1 -0
- package/lib/rate-limit/rate-limit.types.js +0 -0
- package/lib/rate-limit/simple-rate-limit-counter.d.ts +89 -0
- package/lib/rate-limit/simple-rate-limit-counter.d.ts.map +1 -0
- package/lib/rate-limit/simple-rate-limit-counter.js +98 -0
- package/lib/rate-limit/simple-rate-limit-counter.js.map +1 -0
- package/lib/readonly/index.d.ts +3 -0
- package/lib/readonly/index.js +2 -0
- package/lib/readonly/readonly.d.ts +39 -0
- package/lib/readonly/readonly.d.ts.map +1 -0
- package/lib/readonly/readonly.js +43 -0
- package/lib/readonly/readonly.js.map +1 -0
- package/lib/readonly/readonly.types.d.ts +40 -0
- package/lib/readonly/readonly.types.d.ts.map +1 -0
- package/lib/readonly/readonly.types.js +0 -0
- package/lib/throttle/index.d.ts +2 -0
- package/lib/throttle/index.js +2 -0
- package/lib/throttle/throttle.d.ts +35 -0
- package/lib/throttle/throttle.d.ts.map +1 -0
- package/lib/throttle/throttle.fn.d.ts +42 -0
- package/lib/throttle/throttle.fn.d.ts.map +1 -0
- package/lib/throttle/throttle.fn.js +52 -0
- package/lib/throttle/throttle.fn.js.map +1 -0
- package/lib/throttle/throttle.js +43 -0
- package/lib/throttle/throttle.js.map +1 -0
- package/lib/throttle-async/index.d.ts +2 -0
- package/lib/throttle-async/index.js +2 -0
- package/lib/throttle-async/throttle-async-executor.d.ts +79 -0
- package/lib/throttle-async/throttle-async-executor.d.ts.map +1 -0
- package/lib/throttle-async/throttle-async-executor.js +122 -0
- package/lib/throttle-async/throttle-async-executor.js.map +1 -0
- package/lib/throttle-async/throttle-async.d.ts +68 -0
- package/lib/throttle-async/throttle-async.d.ts.map +1 -0
- package/lib/throttle-async/throttle-async.fn.d.ts +41 -0
- package/lib/throttle-async/throttle-async.fn.d.ts.map +1 -0
- package/lib/throttle-async/throttle-async.fn.js +46 -0
- package/lib/throttle-async/throttle-async.fn.js.map +1 -0
- package/lib/throttle-async/throttle-async.js +45 -0
- package/lib/throttle-async/throttle-async.js.map +1 -0
- package/lib/types.d.ts +81 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +0 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# @resq-sw/decorators
|
|
2
|
+
|
|
3
|
+
> TypeScript method and class decorators for caching, rate limiting, control flow, and observability.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @resq-sw/decorators
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Zero runtime dependencies.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { memoize, throttle, bind, debounce } from "@resq-sw/decorators";
|
|
17
|
+
|
|
18
|
+
class SearchService {
|
|
19
|
+
@memoize()
|
|
20
|
+
computeExpensive(n: number): number { return n * n; }
|
|
21
|
+
|
|
22
|
+
@throttle(200)
|
|
23
|
+
handleScroll(): void { /* at most once per 200ms */ }
|
|
24
|
+
|
|
25
|
+
@debounce(300)
|
|
26
|
+
onInput(query: string): void { /* fires after 300ms idle */ }
|
|
27
|
+
|
|
28
|
+
@bind
|
|
29
|
+
handleClick(): void { /* `this` always bound to instance */ }
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## API Reference
|
|
34
|
+
|
|
35
|
+
### `@after(config)`
|
|
36
|
+
|
|
37
|
+
Executes a function after the decorated method completes.
|
|
38
|
+
|
|
39
|
+
| Option | Type | Default | Description |
|
|
40
|
+
|--------|------|---------|-------------|
|
|
41
|
+
| `func` | `((data: { args, response }) => void) \| string` | required | Callback or method name on `this` |
|
|
42
|
+
| `wait` | `boolean` | `false` | Whether to await the original method before calling `func` |
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
class Service {
|
|
46
|
+
@after({ func: ({ response }) => console.log(response), wait: true })
|
|
47
|
+
async save(data: string) { return db.save(data); }
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Also exported: `afterFn(method, config)` -- standalone wrapper function.
|
|
52
|
+
|
|
53
|
+
### `@before(config)`
|
|
54
|
+
|
|
55
|
+
Executes a function before the decorated method runs.
|
|
56
|
+
|
|
57
|
+
| Option | Type | Default | Description |
|
|
58
|
+
|--------|------|---------|-------------|
|
|
59
|
+
| `func` | `(() => void) \| string` | required | Callback or method name on `this` |
|
|
60
|
+
| `wait` | `boolean` | `false` | Whether to await the before function |
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
class Service {
|
|
64
|
+
@before({ func: 'validateState', wait: true })
|
|
65
|
+
process() { /* runs after validateState */ }
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Also exported: `beforeFn(method, config)`.
|
|
70
|
+
|
|
71
|
+
### `@bind`
|
|
72
|
+
|
|
73
|
+
Automatically binds a method to its class instance using lazy binding on first access.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
class Component {
|
|
77
|
+
@bind
|
|
78
|
+
handleClick(): void { /* `this` is always correct */ }
|
|
79
|
+
}
|
|
80
|
+
const fn = new Component().handleClick; // safely detached
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Also exported: `bindFn(method, context)`.
|
|
84
|
+
|
|
85
|
+
### `@debounce(delayMs)`
|
|
86
|
+
|
|
87
|
+
Debounces method calls -- only executes after `delayMs` of inactivity.
|
|
88
|
+
|
|
89
|
+
- **delayMs** (`number`) -- delay in milliseconds.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
class Editor {
|
|
93
|
+
@debounce(500)
|
|
94
|
+
autoSave(content: string): void { localStorage.setItem("draft", content); }
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Also exported: `debounceFn(method, delayMs)`.
|
|
99
|
+
|
|
100
|
+
### `@delay(delayMs)`
|
|
101
|
+
|
|
102
|
+
Delays method execution by `delayMs` milliseconds.
|
|
103
|
+
|
|
104
|
+
- **delayMs** (`number`) -- delay in milliseconds.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
class Animator {
|
|
108
|
+
@delay(1000)
|
|
109
|
+
fadeOut(el: HTMLElement): void { el.style.opacity = "0"; }
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Also exported: `delayFn(method, delayMs)`.
|
|
114
|
+
|
|
115
|
+
### `@delegate(keyResolver?)`
|
|
116
|
+
|
|
117
|
+
Deduplicates concurrent async method calls. Calls with the same key share a single promise.
|
|
118
|
+
|
|
119
|
+
- **keyResolver** (`(...args) => string`, optional) -- custom key function; defaults to `JSON.stringify(args)`.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
class Api {
|
|
123
|
+
@delegate((id: string) => id)
|
|
124
|
+
async fetchUser(id: string): Promise<User> { return fetch(`/users/${id}`).then(r => r.json()); }
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Also exported: `delegateFn(method, keyResolver?)`.
|
|
129
|
+
|
|
130
|
+
### `@execTime(arg?)`
|
|
131
|
+
|
|
132
|
+
Measures and reports method execution time. Supports both legacy and Stage 3 decorators.
|
|
133
|
+
|
|
134
|
+
- **arg** (`ReportFunction | string`, optional) -- custom reporter function, a label string, or a method name on `this`.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
class Monitor {
|
|
138
|
+
@execTime()
|
|
139
|
+
processData(data: unknown[]): void { /* logs execution time */ }
|
|
140
|
+
|
|
141
|
+
@execTime("DB Query")
|
|
142
|
+
async query(): Promise<void> { /* logs "DB Query execution time: Xms" */ }
|
|
143
|
+
|
|
144
|
+
@execTime((data) => metrics.record(data.execTime))
|
|
145
|
+
compute(): void { /* custom reporter */ }
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Also exported: `execTimeFn(method, arg?)`.
|
|
150
|
+
|
|
151
|
+
### `@selfExecute`
|
|
152
|
+
|
|
153
|
+
Class decorator that auto-instantiates the class on load.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
@selfExecute
|
|
157
|
+
class Singleton {
|
|
158
|
+
constructor() { console.log("Created"); }
|
|
159
|
+
}
|
|
160
|
+
// Instance created immediately when module loads
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### `@memoize(configOrTTL?)`
|
|
164
|
+
|
|
165
|
+
Caches synchronous method results by arguments.
|
|
166
|
+
|
|
167
|
+
| Overload | Parameter | Description |
|
|
168
|
+
|----------|-----------|-------------|
|
|
169
|
+
| `@memoize()` | none | Cache indefinitely |
|
|
170
|
+
| `@memoize(60000)` | `number` | Cache with TTL in ms |
|
|
171
|
+
| `@memoize({ cache, keyResolver, expirationTimeMs })` | `MemoizeConfig` | Full config |
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
class Calc {
|
|
175
|
+
@memoize(30000)
|
|
176
|
+
fibonacci(n: number): number { return n <= 1 ? n : this.fibonacci(n-1) + this.fibonacci(n-2); }
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Also exported: `memoizeFn(method, configOrTTL?)`.
|
|
181
|
+
|
|
182
|
+
### `@memoizeAsync(configOrTTL?)`
|
|
183
|
+
|
|
184
|
+
Caches async method results with promise deduplication. Same overloads as `@memoize`.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
class Api {
|
|
188
|
+
@memoizeAsync({ expirationTimeMs: 60000 })
|
|
189
|
+
async getConfig(): Promise<Config> { return fetch("/config").then(r => r.json()); }
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Also exported: `memoizeAsyncFn(method, configOrTTL?)`.
|
|
194
|
+
|
|
195
|
+
### `@observe` / `@observe(callback)`
|
|
196
|
+
|
|
197
|
+
Property decorator that watches assignments.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
class State {
|
|
201
|
+
@observe
|
|
202
|
+
count: number = 0; // logs "setting property State#count = <value>"
|
|
203
|
+
|
|
204
|
+
@observe((val: string) => analytics.track("name", val))
|
|
205
|
+
name: string = "";
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### `@rateLimit(config)`
|
|
210
|
+
|
|
211
|
+
Limits method calls to a fixed number within a time window.
|
|
212
|
+
|
|
213
|
+
| Option | Type | Description |
|
|
214
|
+
|--------|------|-------------|
|
|
215
|
+
| `timeSpanMs` | `number` | Time window in ms |
|
|
216
|
+
| `allowedCalls` | `number` | Max calls per window |
|
|
217
|
+
| `exceedHandler` | `() => void` | Called when limit exceeded |
|
|
218
|
+
| `keyResolver` | `(...args) => string` | Per-key limiting |
|
|
219
|
+
| `rateLimitCounter` | `RateLimitCounter` | Custom counter implementation |
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
class Api {
|
|
223
|
+
@rateLimit({ timeSpanMs: 1000, allowedCalls: 5 })
|
|
224
|
+
call(): void { /* max 5 per second */ }
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Also exported: `rateLimitFn(method, config)`, `SimpleRateLimitCounter`.
|
|
229
|
+
|
|
230
|
+
### `@readonly()`
|
|
231
|
+
|
|
232
|
+
Makes a method non-writable (prevents reassignment).
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
class Secure {
|
|
236
|
+
@readonly()
|
|
237
|
+
getSecret(): string { return this.secret; }
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### `@throttle(delayMs)`
|
|
242
|
+
|
|
243
|
+
Throttles method calls to at most once per `delayMs` milliseconds.
|
|
244
|
+
|
|
245
|
+
- **delayMs** (`number`) -- throttle interval.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
class Scroll {
|
|
249
|
+
@throttle(100)
|
|
250
|
+
onScroll(): void { /* max once per 100ms */ }
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Also exported: `throttleFn(method, delayMs)`.
|
|
255
|
+
|
|
256
|
+
### `@throttleAsync(parallelCalls?)`
|
|
257
|
+
|
|
258
|
+
Limits concurrent async method executions. Excess calls are queued.
|
|
259
|
+
|
|
260
|
+
- **parallelCalls** (`number`, default `1`) -- max concurrent executions.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
class Processor {
|
|
264
|
+
@throttleAsync(3)
|
|
265
|
+
async process(item: Item): Promise<Result> { /* max 3 concurrent */ }
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Also exported: `throttleAsyncFn(method, parallelCalls?)`, `ThrottleAsyncExecutor`.
|
|
270
|
+
|
|
271
|
+
## Types
|
|
272
|
+
|
|
273
|
+
All decorator types are exported: `Method`, `Decorator`, `AsyncMethod`, `AsyncDecorator`.
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
Apache-2.0
|
package/lib/_utils.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/_utils.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Copyright 2026 ResQ
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @fileoverview Inlined utilities for the decorators package.
|
|
19
|
+
* These are minimal copies of utilities from the monorepo to keep this package zero-dependency.
|
|
20
|
+
*/
|
|
21
|
+
declare const isPromise: (value: unknown) => value is Promise<unknown>;
|
|
22
|
+
declare const isFunction: (value: unknown) => value is Function;
|
|
23
|
+
declare const isNumber: (value: unknown) => value is number;
|
|
24
|
+
declare const isString: (value: unknown) => value is string;
|
|
25
|
+
declare const logger: {
|
|
26
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
27
|
+
};
|
|
28
|
+
declare class Queue<T> {
|
|
29
|
+
private firstItem;
|
|
30
|
+
private lastItem;
|
|
31
|
+
private size;
|
|
32
|
+
getSize(): number;
|
|
33
|
+
isEmpty(): boolean;
|
|
34
|
+
enqueue(item: T): void;
|
|
35
|
+
dequeue(): T | null;
|
|
36
|
+
}
|
|
37
|
+
declare class TaskExec {
|
|
38
|
+
private readonly tasks;
|
|
39
|
+
private handler;
|
|
40
|
+
exec(func: (...args: unknown[]) => unknown, ttl: number): void;
|
|
41
|
+
private handleNext;
|
|
42
|
+
private execNext;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { Queue, TaskExec, isFunction, isNumber, isPromise, isString, logger };
|
|
46
|
+
//# sourceMappingURL=_utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_utils.d.ts","names":[],"sources":["../src/_utils.ts"],"mappings":";;AAuBA;;;;;;;;;AAIA;;;;;;;;;cAJa,SAAA,GAAa,KAAA,cAAiB,KAAA,IAAS,OAAA;AAAA,cAIvC,UAAA,GAAc,KAAA,cAAiB,KAAA,IAAS,QAAA;AAAA,cAGxC,QAAA,GAAY,KAAA,cAAiB,KAAA;AAAA,cAG7B,QAAA,GAAY,KAAA,cAAiB,KAAA;AAAA,cAK7B,MAAA;wBACS,IAAA,GAAS,MAAA;AAAA;AAAA,cAalB,KAAA;EAAA,QACH,SAAA;EAAA,QACA,QAAA;EAAA,QACA,IAAA;EAED,OAAA,CAAA;EAIA,OAAA,CAAA;EAIA,OAAA,CAAQ,IAAA,EAAM,CAAA;EAcd,OAAA,CAAA,GAAW,CAAA;AAAA;AAAA,cAkBP,QAAA;EAAA,iBACM,KAAA;EAAA,QACT,OAAA;EAER,IAAA,CAAK,IAAA,MAAU,IAAA,yBAA6B,GAAA;EAAA,QAMpC,UAAA;EAAA,QAMA,QAAA;AAAA"}
|
package/lib/_utils.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//#region src/_utils.ts
|
|
2
|
+
/**
|
|
3
|
+
* Copyright 2026 ResQ
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @fileoverview Inlined utilities for the decorators package.
|
|
19
|
+
* These are minimal copies of utilities from the monorepo to keep this package zero-dependency.
|
|
20
|
+
*/
|
|
21
|
+
const isPromise = (value) => value instanceof Promise || typeof value === "object" && value !== null && typeof value.then === "function";
|
|
22
|
+
const isFunction = (value) => typeof value === "function";
|
|
23
|
+
const isNumber = (value) => typeof value === "number" && !Number.isNaN(value);
|
|
24
|
+
const isString = (value) => typeof value === "string";
|
|
25
|
+
const logger = { info(message, data) {
|
|
26
|
+
const suffix = data ? ` ${JSON.stringify(data)}` : "";
|
|
27
|
+
console.info(`INFO [decorators] ${message}${suffix}`);
|
|
28
|
+
} };
|
|
29
|
+
var Queue = class {
|
|
30
|
+
firstItem = null;
|
|
31
|
+
lastItem = null;
|
|
32
|
+
size = 0;
|
|
33
|
+
getSize() {
|
|
34
|
+
return this.size;
|
|
35
|
+
}
|
|
36
|
+
isEmpty() {
|
|
37
|
+
return this.size === 0;
|
|
38
|
+
}
|
|
39
|
+
enqueue(item) {
|
|
40
|
+
const newItem = {
|
|
41
|
+
next: null,
|
|
42
|
+
value: item
|
|
43
|
+
};
|
|
44
|
+
if (this.isEmpty()) {
|
|
45
|
+
this.firstItem = newItem;
|
|
46
|
+
this.lastItem = newItem;
|
|
47
|
+
} else {
|
|
48
|
+
if (this.lastItem) this.lastItem.next = newItem;
|
|
49
|
+
this.lastItem = newItem;
|
|
50
|
+
}
|
|
51
|
+
this.size += 1;
|
|
52
|
+
}
|
|
53
|
+
dequeue() {
|
|
54
|
+
let removedItem = null;
|
|
55
|
+
if (!this.isEmpty() && this.firstItem) {
|
|
56
|
+
removedItem = this.firstItem.value;
|
|
57
|
+
this.firstItem = this.firstItem.next;
|
|
58
|
+
this.size -= 1;
|
|
59
|
+
}
|
|
60
|
+
return removedItem;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var TaskExec = class {
|
|
64
|
+
tasks = [];
|
|
65
|
+
handler;
|
|
66
|
+
exec(func, ttl) {
|
|
67
|
+
this.tasks.push({
|
|
68
|
+
func,
|
|
69
|
+
execTime: Date.now() + ttl
|
|
70
|
+
});
|
|
71
|
+
this.tasks.sort((a, b) => a.execTime - b.execTime);
|
|
72
|
+
this.handleNext();
|
|
73
|
+
}
|
|
74
|
+
handleNext() {
|
|
75
|
+
if (!this.tasks.length) return;
|
|
76
|
+
const { execTime } = this.tasks[0];
|
|
77
|
+
this.execNext(Math.max(execTime - Date.now(), 0));
|
|
78
|
+
}
|
|
79
|
+
execNext(ttl) {
|
|
80
|
+
clearTimeout(this.handler);
|
|
81
|
+
this.handler = setTimeout(() => {
|
|
82
|
+
const task = this.tasks.shift();
|
|
83
|
+
if (task) task.func();
|
|
84
|
+
this.handleNext();
|
|
85
|
+
}, ttl);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
//#endregion
|
|
89
|
+
export { Queue, TaskExec, isFunction, isNumber, isPromise, isString, logger };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=_utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_utils.js","names":[],"sources":["../src/_utils.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @fileoverview Inlined utilities for the decorators package.\n * These are minimal copies of utilities from the monorepo to keep this package zero-dependency.\n */\n\n// --- Type guards ---\n\nexport const isPromise = (value: unknown): value is Promise<unknown> =>\n value instanceof Promise ||\n (typeof value === 'object' && value !== null && typeof (value as any).then === 'function');\n\nexport const isFunction = (value: unknown): value is Function =>\n typeof value === 'function';\n\nexport const isNumber = (value: unknown): value is number =>\n typeof value === 'number' && !Number.isNaN(value);\n\nexport const isString = (value: unknown): value is string =>\n typeof value === 'string';\n\n// --- Logger stub ---\n\nexport const logger = {\n info(message: string, data?: Record<string, unknown>): void {\n const suffix = data ? ` ${JSON.stringify(data)}` : '';\n console.info(`INFO [decorators] ${message}${suffix}`);\n },\n};\n\n// --- Queue (linked-list FIFO) ---\n\ninterface QueueNode<T> {\n next: QueueNode<T> | null;\n value: T;\n}\n\nexport class Queue<T> {\n private firstItem: QueueNode<T> | null = null;\n private lastItem: QueueNode<T> | null = null;\n private size = 0;\n\n public getSize(): number {\n return this.size;\n }\n\n public isEmpty(): boolean {\n return this.size === 0;\n }\n\n public enqueue(item: T): void {\n const newItem: QueueNode<T> = { next: null, value: item };\n if (this.isEmpty()) {\n this.firstItem = newItem;\n this.lastItem = newItem;\n } else {\n if (this.lastItem) {\n this.lastItem.next = newItem;\n }\n this.lastItem = newItem;\n }\n this.size += 1;\n }\n\n public dequeue(): T | null {\n let removedItem: T | null = null;\n if (!this.isEmpty() && this.firstItem) {\n removedItem = this.firstItem.value;\n this.firstItem = this.firstItem.next;\n this.size -= 1;\n }\n return removedItem;\n }\n}\n\n// --- TaskExec (simple priority-based delayed execution) ---\n\ninterface TimedTask {\n func: (...args: unknown[]) => unknown;\n execTime: number;\n}\n\nexport class TaskExec {\n private readonly tasks: TimedTask[] = [];\n private handler: ReturnType<typeof setTimeout> | undefined;\n\n exec(func: (...args: unknown[]) => unknown, ttl: number): void {\n this.tasks.push({ func, execTime: Date.now() + ttl });\n this.tasks.sort((a, b) => a.execTime - b.execTime);\n this.handleNext();\n }\n\n private handleNext(): void {\n if (!this.tasks.length) return;\n const { execTime } = this.tasks[0]!;\n this.execNext(Math.max(execTime - Date.now(), 0));\n }\n\n private execNext(ttl: number): void {\n clearTimeout(this.handler);\n this.handler = setTimeout(() => {\n const task = this.tasks.shift();\n if (task) task.func();\n this.handleNext();\n }, ttl);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,aAAa,UACxB,iBAAiB,WAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,OAAQ,MAAc,SAAS;AAEjF,MAAa,cAAc,UACzB,OAAO,UAAU;AAEnB,MAAa,YAAY,UACvB,OAAO,UAAU,YAAY,CAAC,OAAO,MAAM,MAAM;AAEnD,MAAa,YAAY,UACvB,OAAO,UAAU;AAInB,MAAa,SAAS,EACpB,KAAK,SAAiB,MAAsC;CAC1D,MAAM,SAAS,OAAO,IAAI,KAAK,UAAU,KAAK,KAAK;AACnD,SAAQ,KAAK,qBAAqB,UAAU,SAAS;GAExD;AASD,IAAa,QAAb,MAAsB;CACpB,YAAyC;CACzC,WAAwC;CACxC,OAAe;CAEf,UAAyB;AACvB,SAAO,KAAK;;CAGd,UAA0B;AACxB,SAAO,KAAK,SAAS;;CAGvB,QAAe,MAAe;EAC5B,MAAM,UAAwB;GAAE,MAAM;GAAM,OAAO;GAAM;AACzD,MAAI,KAAK,SAAS,EAAE;AAClB,QAAK,YAAY;AACjB,QAAK,WAAW;SACX;AACL,OAAI,KAAK,SACP,MAAK,SAAS,OAAO;AAEvB,QAAK,WAAW;;AAElB,OAAK,QAAQ;;CAGf,UAA2B;EACzB,IAAI,cAAwB;AAC5B,MAAI,CAAC,KAAK,SAAS,IAAI,KAAK,WAAW;AACrC,iBAAc,KAAK,UAAU;AAC7B,QAAK,YAAY,KAAK,UAAU;AAChC,QAAK,QAAQ;;AAEf,SAAO;;;AAWX,IAAa,WAAb,MAAsB;CACpB,QAAsC,EAAE;CACxC;CAEA,KAAK,MAAuC,KAAmB;AAC7D,OAAK,MAAM,KAAK;GAAE;GAAM,UAAU,KAAK,KAAK,GAAG;GAAK,CAAC;AACrD,OAAK,MAAM,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;AAClD,OAAK,YAAY;;CAGnB,aAA2B;AACzB,MAAI,CAAC,KAAK,MAAM,OAAQ;EACxB,MAAM,EAAE,aAAa,KAAK,MAAM;AAChC,OAAK,SAAS,KAAK,IAAI,WAAW,KAAK,KAAK,EAAE,EAAE,CAAC;;CAGnD,SAAiB,KAAmB;AAClC,eAAa,KAAK,QAAQ;AAC1B,OAAK,UAAU,iBAAiB;GAC9B,MAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,OAAI,KAAM,MAAK,MAAM;AACrB,QAAK,YAAY;KAChB,IAAI"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Decorator } from "../types.js";
|
|
2
|
+
import { AfterConfig } from "./after.types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/after/after.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* @fileoverview After decorator - executes a function after the decorated method completes.
|
|
7
|
+
* Useful for logging, cleanup, or triggering side effects after method execution.
|
|
8
|
+
*
|
|
9
|
+
* @module @resq/typescript/decorators/after
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* class MyService {
|
|
14
|
+
* @after({
|
|
15
|
+
* func: 'logCompletion',
|
|
16
|
+
* wait: true // Wait for after function to complete
|
|
17
|
+
* })
|
|
18
|
+
* async saveData(data: any) {
|
|
19
|
+
* await database.save(data);
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* logCompletion({ response, args }) {
|
|
23
|
+
* console.log('Save completed:', response);
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @copyright Copyright (c) 2026 ResQ
|
|
29
|
+
* @license MIT
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Decorator that executes a function after the decorated method completes.
|
|
33
|
+
* The after function receives the method's arguments and return value.
|
|
34
|
+
*
|
|
35
|
+
* @template T - The type of the class containing the decorated method
|
|
36
|
+
* @template D - The return type of the decorated method
|
|
37
|
+
* @param {AfterConfig<T, D>} config - Configuration for the after hook
|
|
38
|
+
* @returns {Decorator<T>} The decorator function
|
|
39
|
+
*
|
|
40
|
+
* @throws {Error} When applied to a non-method property
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* class DataProcessor {
|
|
45
|
+
* @after({
|
|
46
|
+
* func: function({ args, response }) {
|
|
47
|
+
* console.log(`Processed ${args[0]} items, result: ${response}`);
|
|
48
|
+
* },
|
|
49
|
+
* wait: false // Don't wait for after function
|
|
50
|
+
* })
|
|
51
|
+
* processItems(items: string[]): number {
|
|
52
|
+
* return items.length;
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function after<T = any, D = any>(config: AfterConfig<T, D>): Decorator<T>;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { after };
|
|
60
|
+
//# sourceMappingURL=after.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"after.d.ts","names":[],"sources":["../../src/after/after.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyEgB,KAAA,kBAAA,CAAwB,MAAA,EAAQ,WAAA,CAAY,CAAA,EAAG,CAAA,IAAK,SAAA,CAAU,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Method } from "../types.js";
|
|
2
|
+
import { AfterConfig } from "./after.types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/after/after.fn.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a method to execute an after hook function after the method completes.
|
|
7
|
+
*
|
|
8
|
+
* @template D - The return type of the original method
|
|
9
|
+
* @template A - The argument types of the original method
|
|
10
|
+
* @param {Method<D, A>} originalMethod - The method to wrap
|
|
11
|
+
* @param {AfterConfig<any, ReturnType<typeof originalMethod>>} config - Configuration for the after hook
|
|
12
|
+
* @returns {(...args: any[]) => Promise<D>} The wrapped method
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* class Service {
|
|
17
|
+
* process(data: string): string {
|
|
18
|
+
* return data.toUpperCase();
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* const service = new Service();
|
|
23
|
+
* const wrapped = afterFn(
|
|
24
|
+
* service.process.bind(service),
|
|
25
|
+
* {
|
|
26
|
+
* func: ({ args, response }) => {
|
|
27
|
+
* console.log(`Called with ${args[0]}, returned ${response}`);
|
|
28
|
+
* },
|
|
29
|
+
* wait: false
|
|
30
|
+
* }
|
|
31
|
+
* );
|
|
32
|
+
*
|
|
33
|
+
* await wrapped('hello'); // Logs: Called with hello, returned HELLO
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare function afterFn<D = any, A extends any[] = any[]>(originalMethod: Method<D, A>, config: AfterConfig<any, ReturnType<typeof originalMethod>>): (...args: unknown[]) => Promise<D>;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { afterFn };
|
|
39
|
+
//# sourceMappingURL=after.fn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"after.fn.d.ts","names":[],"sources":["../../src/after/after.fn.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkDgB,OAAA,kCAAA,CACd,cAAA,EAAgB,MAAA,CAAO,CAAA,EAAG,CAAA,GAC1B,MAAA,EAAQ,WAAA,MAAiB,UAAA,QAAkB,cAAA,SACtC,IAAA,gBAAoB,OAAA,CAAQ,CAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/after/after.fn.ts
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a method to execute an after hook function after the method completes.
|
|
4
|
+
*
|
|
5
|
+
* @template D - The return type of the original method
|
|
6
|
+
* @template A - The argument types of the original method
|
|
7
|
+
* @param {Method<D, A>} originalMethod - The method to wrap
|
|
8
|
+
* @param {AfterConfig<any, ReturnType<typeof originalMethod>>} config - Configuration for the after hook
|
|
9
|
+
* @returns {(...args: any[]) => Promise<D>} The wrapped method
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* class Service {
|
|
14
|
+
* process(data: string): string {
|
|
15
|
+
* return data.toUpperCase();
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* const service = new Service();
|
|
20
|
+
* const wrapped = afterFn(
|
|
21
|
+
* service.process.bind(service),
|
|
22
|
+
* {
|
|
23
|
+
* func: ({ args, response }) => {
|
|
24
|
+
* console.log(`Called with ${args[0]}, returned ${response}`);
|
|
25
|
+
* },
|
|
26
|
+
* wait: false
|
|
27
|
+
* }
|
|
28
|
+
* );
|
|
29
|
+
*
|
|
30
|
+
* await wrapped('hello'); // Logs: Called with hello, returned HELLO
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
function afterFn(originalMethod, config) {
|
|
34
|
+
const resolvedConfig = {
|
|
35
|
+
wait: false,
|
|
36
|
+
...config
|
|
37
|
+
};
|
|
38
|
+
return async function(...args) {
|
|
39
|
+
const afterFunc = typeof resolvedConfig.func === "string" ? this[resolvedConfig.func].bind(this) : resolvedConfig.func;
|
|
40
|
+
if (resolvedConfig.wait) {
|
|
41
|
+
const response = await originalMethod.apply(this, args);
|
|
42
|
+
afterFunc({
|
|
43
|
+
args,
|
|
44
|
+
response
|
|
45
|
+
});
|
|
46
|
+
return response;
|
|
47
|
+
}
|
|
48
|
+
const response = originalMethod.apply(this, args);
|
|
49
|
+
afterFunc({
|
|
50
|
+
args,
|
|
51
|
+
response
|
|
52
|
+
});
|
|
53
|
+
return response;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { afterFn };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=after.fn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"after.fn.js","names":[],"sources":["../../src/after/after.fn.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Method } from '../types.js';\nimport type { AfterConfig, AfterFunc } from './after.types.js';\n\n/**\n * Wraps a method to execute an after hook function after the method completes.\n *\n * @template D - The return type of the original method\n * @template A - The argument types of the original method\n * @param {Method<D, A>} originalMethod - The method to wrap\n * @param {AfterConfig<any, ReturnType<typeof originalMethod>>} config - Configuration for the after hook\n * @returns {(...args: any[]) => Promise<D>} The wrapped method\n *\n * @example\n * ```typescript\n * class Service {\n * process(data: string): string {\n * return data.toUpperCase();\n * }\n * }\n *\n * const service = new Service();\n * const wrapped = afterFn(\n * service.process.bind(service),\n * {\n * func: ({ args, response }) => {\n * console.log(`Called with ${args[0]}, returned ${response}`);\n * },\n * wait: false\n * }\n * );\n *\n * await wrapped('hello'); // Logs: Called with hello, returned HELLO\n * ```\n */\nexport function afterFn<D = any, A extends any[] = any[]>(\n originalMethod: Method<D, A>,\n config: AfterConfig<any, ReturnType<typeof originalMethod>>,\n): (...args: unknown[]) => Promise<D> {\n const resolvedConfig: AfterConfig<any, ReturnType<typeof originalMethod>> = {\n wait: false,\n ...config,\n };\n\n return async function (this: any, ...args: A): Promise<D> {\n const afterFunc: AfterFunc<ReturnType<typeof originalMethod>> =\n typeof resolvedConfig.func === 'string'\n ? this[resolvedConfig.func].bind(this)\n : resolvedConfig.func;\n\n if (resolvedConfig.wait) {\n const response = await originalMethod.apply(this, args);\n afterFunc({\n args,\n response,\n });\n return response;\n }\n\n const response = originalMethod.apply(this, args);\n afterFunc({\n args,\n response,\n });\n return response;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAgB,QACd,gBACA,QACoC;CACpC,MAAM,iBAAsE;EAC1E,MAAM;EACN,GAAG;EACJ;AAED,QAAO,eAA2B,GAAG,MAAqB;EACxD,MAAM,YACJ,OAAO,eAAe,SAAS,WAC3B,KAAK,eAAe,MAAM,KAAK,KAAK,GACpC,eAAe;AAErB,MAAI,eAAe,MAAM;GACvB,MAAM,WAAW,MAAM,eAAe,MAAM,MAAM,KAAK;AACvD,aAAU;IACR;IACA;IACD,CAAC;AACF,UAAO;;EAGT,MAAM,WAAW,eAAe,MAAM,MAAM,KAAK;AACjD,YAAU;GACR;GACA;GACD,CAAC;AACF,SAAO"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { afterFn } from "./after.fn.js";
|
|
2
|
+
//#region src/after/after.ts
|
|
3
|
+
/**
|
|
4
|
+
* Decorator that executes a function after the decorated method completes.
|
|
5
|
+
* The after function receives the method's arguments and return value.
|
|
6
|
+
*
|
|
7
|
+
* @template T - The type of the class containing the decorated method
|
|
8
|
+
* @template D - The return type of the decorated method
|
|
9
|
+
* @param {AfterConfig<T, D>} config - Configuration for the after hook
|
|
10
|
+
* @returns {Decorator<T>} The decorator function
|
|
11
|
+
*
|
|
12
|
+
* @throws {Error} When applied to a non-method property
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* class DataProcessor {
|
|
17
|
+
* @after({
|
|
18
|
+
* func: function({ args, response }) {
|
|
19
|
+
* console.log(`Processed ${args[0]} items, result: ${response}`);
|
|
20
|
+
* },
|
|
21
|
+
* wait: false // Don't wait for after function
|
|
22
|
+
* })
|
|
23
|
+
* processItems(items: string[]): number {
|
|
24
|
+
* return items.length;
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function after(config) {
|
|
30
|
+
return (target, propertyName, descriptor) => {
|
|
31
|
+
if (descriptor.value) {
|
|
32
|
+
descriptor.value = afterFn(descriptor.value, config);
|
|
33
|
+
return descriptor;
|
|
34
|
+
}
|
|
35
|
+
throw new Error("@after is applicable only on a methods.");
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { after };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=after.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"after.js","names":[],"sources":["../../src/after/after.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @fileoverview After decorator - executes a function after the decorated method completes.\n * Useful for logging, cleanup, or triggering side effects after method execution.\n *\n * @module @resq/typescript/decorators/after\n *\n * @example\n * ```typescript\n * class MyService {\n * @after({\n * func: 'logCompletion',\n * wait: true // Wait for after function to complete\n * })\n * async saveData(data: any) {\n * await database.save(data);\n * }\n *\n * logCompletion({ response, args }) {\n * console.log('Save completed:', response);\n * }\n * }\n * ```\n *\n * @copyright Copyright (c) 2026 ResQ\n * @license MIT\n */\n\nimport type { Decorator, Method } from '../types.js';\nimport { afterFn } from './after.fn.js';\nimport type { AfterConfig } from './after.types.js';\n\n/**\n * Decorator that executes a function after the decorated method completes.\n * The after function receives the method's arguments and return value.\n *\n * @template T - The type of the class containing the decorated method\n * @template D - The return type of the decorated method\n * @param {AfterConfig<T, D>} config - Configuration for the after hook\n * @returns {Decorator<T>} The decorator function\n *\n * @throws {Error} When applied to a non-method property\n *\n * @example\n * ```typescript\n * class DataProcessor {\n * @after({\n * func: function({ args, response }) {\n * console.log(`Processed ${args[0]} items, result: ${response}`);\n * },\n * wait: false // Don't wait for after function\n * })\n * processItems(items: string[]): number {\n * return items.length;\n * }\n * }\n * ```\n */\nexport function after<T = any, D = any>(config: AfterConfig<T, D>): Decorator<T> {\n return (\n target: T,\n propertyName: keyof T,\n descriptor: TypedPropertyDescriptor<Method<any>>,\n ): TypedPropertyDescriptor<Method<D>> => {\n if (descriptor.value) {\n descriptor.value = afterFn(descriptor.value, config);\n\n return descriptor;\n }\n throw new Error('@after is applicable only on a methods.');\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyEA,SAAgB,MAAwB,QAAyC;AAC/E,SACE,QACA,cACA,eACuC;AACvC,MAAI,WAAW,OAAO;AACpB,cAAW,QAAQ,QAAQ,WAAW,OAAO,OAAO;AAEpD,UAAO;;AAET,QAAM,IAAI,MAAM,0CAA0C"}
|