@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.
Files changed (187) hide show
  1. package/README.md +277 -0
  2. package/lib/_utils.d.ts +46 -0
  3. package/lib/_utils.d.ts.map +1 -0
  4. package/lib/_utils.js +91 -0
  5. package/lib/_utils.js.map +1 -0
  6. package/lib/after/after.d.ts +60 -0
  7. package/lib/after/after.d.ts.map +1 -0
  8. package/lib/after/after.fn.d.ts +39 -0
  9. package/lib/after/after.fn.d.ts.map +1 -0
  10. package/lib/after/after.fn.js +59 -0
  11. package/lib/after/after.fn.js.map +1 -0
  12. package/lib/after/after.js +41 -0
  13. package/lib/after/after.js.map +1 -0
  14. package/lib/after/after.types.d.ts +86 -0
  15. package/lib/after/after.types.d.ts.map +1 -0
  16. package/lib/after/after.types.js +0 -0
  17. package/lib/after/index.d.ts +3 -0
  18. package/lib/after/index.js +2 -0
  19. package/lib/before/before.d.ts +61 -0
  20. package/lib/before/before.d.ts.map +1 -0
  21. package/lib/before/before.fn.d.ts +39 -0
  22. package/lib/before/before.fn.d.ts.map +1 -0
  23. package/lib/before/before.fn.js +51 -0
  24. package/lib/before/before.fn.js.map +1 -0
  25. package/lib/before/before.js +40 -0
  26. package/lib/before/before.js.map +1 -0
  27. package/lib/before/before.types.d.ts +48 -0
  28. package/lib/before/before.types.d.ts.map +1 -0
  29. package/lib/before/before.types.js +0 -0
  30. package/lib/before/index.d.ts +3 -0
  31. package/lib/before/index.js +2 -0
  32. package/lib/bind/bind.d.ts +75 -0
  33. package/lib/bind/bind.d.ts.map +1 -0
  34. package/lib/bind/bind.fn.d.ts +46 -0
  35. package/lib/bind/bind.fn.d.ts.map +1 -0
  36. package/lib/bind/bind.fn.js +39 -0
  37. package/lib/bind/bind.fn.js.map +1 -0
  38. package/lib/bind/bind.js +64 -0
  39. package/lib/bind/bind.js.map +1 -0
  40. package/lib/bind/bind.types.d.ts +36 -0
  41. package/lib/bind/bind.types.d.ts.map +1 -0
  42. package/lib/bind/bind.types.js +0 -0
  43. package/lib/bind/index.d.ts +3 -0
  44. package/lib/bind/index.js +2 -0
  45. package/lib/debounce/debounce.d.ts +34 -0
  46. package/lib/debounce/debounce.d.ts.map +1 -0
  47. package/lib/debounce/debounce.fn.d.ts +40 -0
  48. package/lib/debounce/debounce.fn.d.ts.map +1 -0
  49. package/lib/debounce/debounce.fn.js +47 -0
  50. package/lib/debounce/debounce.fn.js.map +1 -0
  51. package/lib/debounce/debounce.js +48 -0
  52. package/lib/debounce/debounce.js.map +1 -0
  53. package/lib/debounce/index.d.ts +2 -0
  54. package/lib/debounce/index.js +2 -0
  55. package/lib/delay/delay.d.ts +35 -0
  56. package/lib/delay/delay.d.ts.map +1 -0
  57. package/lib/delay/delay.fn.d.ts +33 -0
  58. package/lib/delay/delay.fn.d.ts.map +1 -0
  59. package/lib/delay/delay.fn.js +38 -0
  60. package/lib/delay/delay.fn.js.map +1 -0
  61. package/lib/delay/delay.js +43 -0
  62. package/lib/delay/delay.js.map +1 -0
  63. package/lib/delay/index.d.ts +2 -0
  64. package/lib/delay/index.js +2 -0
  65. package/lib/delegate/delegate.d.ts +48 -0
  66. package/lib/delegate/delegate.d.ts.map +1 -0
  67. package/lib/delegate/delegate.fn.d.ts +57 -0
  68. package/lib/delegate/delegate.fn.d.ts.map +1 -0
  69. package/lib/delegate/delegate.fn.js +55 -0
  70. package/lib/delegate/delegate.fn.js.map +1 -0
  71. package/lib/delegate/delegate.js +56 -0
  72. package/lib/delegate/delegate.js.map +1 -0
  73. package/lib/delegate/delegate.types.d.ts +45 -0
  74. package/lib/delegate/delegate.types.d.ts.map +1 -0
  75. package/lib/delegate/delegate.types.js +0 -0
  76. package/lib/delegate/index.d.ts +3 -0
  77. package/lib/delegate/index.js +2 -0
  78. package/lib/exec-time/exec-time.d.ts +42 -0
  79. package/lib/exec-time/exec-time.d.ts.map +1 -0
  80. package/lib/exec-time/exec-time.fn.d.ts +50 -0
  81. package/lib/exec-time/exec-time.fn.d.ts.map +1 -0
  82. package/lib/exec-time/exec-time.fn.js +91 -0
  83. package/lib/exec-time/exec-time.fn.js.map +1 -0
  84. package/lib/exec-time/exec-time.js +55 -0
  85. package/lib/exec-time/exec-time.js.map +1 -0
  86. package/lib/exec-time/exec-time.types.d.ts +70 -0
  87. package/lib/exec-time/exec-time.types.d.ts.map +1 -0
  88. package/lib/exec-time/exec-time.types.js +0 -0
  89. package/lib/exec-time/index.d.ts +4 -0
  90. package/lib/exec-time/index.js +3 -0
  91. package/lib/execute/execute.d.ts +78 -0
  92. package/lib/execute/execute.d.ts.map +1 -0
  93. package/lib/execute/execute.js +82 -0
  94. package/lib/execute/execute.js.map +1 -0
  95. package/lib/execute/index.d.ts +2 -0
  96. package/lib/execute/index.js +2 -0
  97. package/lib/index.d.ts +30 -0
  98. package/lib/index.js +19 -0
  99. package/lib/memoize/index.d.ts +3 -0
  100. package/lib/memoize/index.js +2 -0
  101. package/lib/memoize/memoize.d.ts +67 -0
  102. package/lib/memoize/memoize.d.ts.map +1 -0
  103. package/lib/memoize/memoize.fn.d.ts +69 -0
  104. package/lib/memoize/memoize.fn.d.ts.map +1 -0
  105. package/lib/memoize/memoize.fn.js +43 -0
  106. package/lib/memoize/memoize.fn.js.map +1 -0
  107. package/lib/memoize/memoize.js +40 -0
  108. package/lib/memoize/memoize.js.map +1 -0
  109. package/lib/memoize/memoize.types.d.ts +107 -0
  110. package/lib/memoize/memoize.types.d.ts.map +1 -0
  111. package/lib/memoize/memoize.types.js +0 -0
  112. package/lib/memoize-async/index.d.ts +4 -0
  113. package/lib/memoize-async/index.js +3 -0
  114. package/lib/memoize-async/memoize-async.d.ts +68 -0
  115. package/lib/memoize-async/memoize-async.d.ts.map +1 -0
  116. package/lib/memoize-async/memoize-async.fn.d.ts +69 -0
  117. package/lib/memoize-async/memoize-async.fn.d.ts.map +1 -0
  118. package/lib/memoize-async/memoize-async.fn.js +52 -0
  119. package/lib/memoize-async/memoize-async.fn.js.map +1 -0
  120. package/lib/memoize-async/memoize-async.js +15 -0
  121. package/lib/memoize-async/memoize-async.js.map +1 -0
  122. package/lib/memoize-async/memoize-async.types.d.ts +74 -0
  123. package/lib/memoize-async/memoize-async.types.d.ts.map +1 -0
  124. package/lib/memoize-async/memoize-async.types.js +0 -0
  125. package/lib/observer/index.d.ts +3 -0
  126. package/lib/observer/index.js +2 -0
  127. package/lib/observer/observer.d.ts +54 -0
  128. package/lib/observer/observer.d.ts.map +1 -0
  129. package/lib/observer/observer.js +85 -0
  130. package/lib/observer/observer.js.map +1 -0
  131. package/lib/observer/observer.types.d.ts +41 -0
  132. package/lib/observer/observer.types.d.ts.map +1 -0
  133. package/lib/observer/observer.types.js +0 -0
  134. package/lib/rate-limit/index.d.ts +4 -0
  135. package/lib/rate-limit/index.js +3 -0
  136. package/lib/rate-limit/rate-limit.d.ts +58 -0
  137. package/lib/rate-limit/rate-limit.d.ts.map +1 -0
  138. package/lib/rate-limit/rate-limit.fn.d.ts +43 -0
  139. package/lib/rate-limit/rate-limit.fn.d.ts.map +1 -0
  140. package/lib/rate-limit/rate-limit.fn.js +56 -0
  141. package/lib/rate-limit/rate-limit.fn.js.map +1 -0
  142. package/lib/rate-limit/rate-limit.js +65 -0
  143. package/lib/rate-limit/rate-limit.js.map +1 -0
  144. package/lib/rate-limit/rate-limit.types.d.ts +148 -0
  145. package/lib/rate-limit/rate-limit.types.d.ts.map +1 -0
  146. package/lib/rate-limit/rate-limit.types.js +0 -0
  147. package/lib/rate-limit/simple-rate-limit-counter.d.ts +89 -0
  148. package/lib/rate-limit/simple-rate-limit-counter.d.ts.map +1 -0
  149. package/lib/rate-limit/simple-rate-limit-counter.js +98 -0
  150. package/lib/rate-limit/simple-rate-limit-counter.js.map +1 -0
  151. package/lib/readonly/index.d.ts +3 -0
  152. package/lib/readonly/index.js +2 -0
  153. package/lib/readonly/readonly.d.ts +39 -0
  154. package/lib/readonly/readonly.d.ts.map +1 -0
  155. package/lib/readonly/readonly.js +43 -0
  156. package/lib/readonly/readonly.js.map +1 -0
  157. package/lib/readonly/readonly.types.d.ts +40 -0
  158. package/lib/readonly/readonly.types.d.ts.map +1 -0
  159. package/lib/readonly/readonly.types.js +0 -0
  160. package/lib/throttle/index.d.ts +2 -0
  161. package/lib/throttle/index.js +2 -0
  162. package/lib/throttle/throttle.d.ts +35 -0
  163. package/lib/throttle/throttle.d.ts.map +1 -0
  164. package/lib/throttle/throttle.fn.d.ts +42 -0
  165. package/lib/throttle/throttle.fn.d.ts.map +1 -0
  166. package/lib/throttle/throttle.fn.js +52 -0
  167. package/lib/throttle/throttle.fn.js.map +1 -0
  168. package/lib/throttle/throttle.js +43 -0
  169. package/lib/throttle/throttle.js.map +1 -0
  170. package/lib/throttle-async/index.d.ts +2 -0
  171. package/lib/throttle-async/index.js +2 -0
  172. package/lib/throttle-async/throttle-async-executor.d.ts +79 -0
  173. package/lib/throttle-async/throttle-async-executor.d.ts.map +1 -0
  174. package/lib/throttle-async/throttle-async-executor.js +122 -0
  175. package/lib/throttle-async/throttle-async-executor.js.map +1 -0
  176. package/lib/throttle-async/throttle-async.d.ts +68 -0
  177. package/lib/throttle-async/throttle-async.d.ts.map +1 -0
  178. package/lib/throttle-async/throttle-async.fn.d.ts +41 -0
  179. package/lib/throttle-async/throttle-async.fn.d.ts.map +1 -0
  180. package/lib/throttle-async/throttle-async.fn.js +46 -0
  181. package/lib/throttle-async/throttle-async.fn.js.map +1 -0
  182. package/lib/throttle-async/throttle-async.js +45 -0
  183. package/lib/throttle-async/throttle-async.js.map +1 -0
  184. package/lib/types.d.ts +81 -0
  185. package/lib/types.d.ts.map +1 -0
  186. package/lib/types.js +0 -0
  187. 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
@@ -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"}