@cldmv/slothlet 2.7.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/lib/helpers/als-eventemitter.mjs +137 -0
- package/dist/lib/helpers/hooks.mjs +8 -0
- package/dist/slothlet.mjs +19 -0
- package/package.json +3 -3
- package/types/dist/lib/helpers/als-eventemitter.d.mts +33 -0
- package/types/dist/lib/helpers/als-eventemitter.d.mts.map +1 -1
- package/types/dist/lib/helpers/hooks.d.mts +12 -0
- package/types/dist/lib/helpers/hooks.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts.map +1 -1
package/README.md
CHANGED
|
@@ -522,10 +522,19 @@ Returns true if the API is loaded.
|
|
|
522
522
|
|
|
523
523
|
#### `slothlet.shutdown()` ⇒ `Promise<void>`
|
|
524
524
|
|
|
525
|
-
Gracefully shuts down the API and
|
|
525
|
+
Gracefully shuts down the API and performs comprehensive resource cleanup to prevent hanging processes.
|
|
526
|
+
|
|
527
|
+
**Cleanup includes:**
|
|
528
|
+
- Hook manager state and registered hooks
|
|
529
|
+
- AsyncLocalStorage context and bindings
|
|
530
|
+
- EventEmitter listeners and AsyncResource instances (including third-party libraries)
|
|
531
|
+
- Instance data and runtime coordination
|
|
526
532
|
|
|
527
533
|
**Returns:** `Promise<void>` - Resolves when shutdown is complete
|
|
528
534
|
|
|
535
|
+
> [!IMPORTANT]
|
|
536
|
+
> **🛡️ Process Cleanup**: The shutdown method now performs comprehensive cleanup of all EventEmitter listeners created after slothlet loads, including those from third-party libraries like pg-pool. This prevents hanging AsyncResource instances that could prevent your Node.js process from exiting cleanly.
|
|
537
|
+
|
|
529
538
|
> [!NOTE]
|
|
530
539
|
> **📚 For detailed API documentation with comprehensive parameter descriptions, method signatures, and examples, see [docs/API.md](https://github.com/CLDMV/slothlet/blob/HEAD/docs/API.md)**
|
|
531
540
|
|
|
@@ -649,6 +658,7 @@ console.log("TCP server started with context preservation");
|
|
|
649
658
|
- ✅ **Nested Events**: Works with any depth of EventEmitter nesting (server → socket → custom emitters)
|
|
650
659
|
- ✅ **Universal Support**: All EventEmitter methods (`on`, `once`, `addListener`) are automatically context-aware
|
|
651
660
|
- ✅ **Production Ready**: Uses Node.js AsyncResource patterns for reliable context propagation
|
|
661
|
+
- ✅ **Clean Shutdown**: Automatically cleans up all AsyncResource instances during shutdown to prevent hanging processes
|
|
652
662
|
- ✅ **Zero Overhead**: Only wraps listeners when context is active, minimal performance impact
|
|
653
663
|
|
|
654
664
|
> [!TIP]
|
|
@@ -26,6 +26,17 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
26
26
|
const defaultALS = new AsyncLocalStorage();
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
let originalMethods = null;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const globalResourceSet = new Set();
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const globalListenerTracker = new WeakMap();
|
|
37
|
+
const allPatchedListeners = new Set();
|
|
38
|
+
|
|
39
|
+
|
|
29
40
|
export function enableAlsForEventEmitters(als = defaultALS) {
|
|
30
41
|
|
|
31
42
|
const kPatched = Symbol.for("slothlet.als.patched");
|
|
@@ -52,6 +63,9 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
52
63
|
const resource = new AsyncResource("slothlet-als-listener");
|
|
53
64
|
|
|
54
65
|
|
|
66
|
+
globalResourceSet.add(resource);
|
|
67
|
+
|
|
68
|
+
|
|
55
69
|
const runtime_wrappedListener = function (...args) {
|
|
56
70
|
return resource.runInAsyncScope(
|
|
57
71
|
() => {
|
|
@@ -62,6 +76,9 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
62
76
|
);
|
|
63
77
|
};
|
|
64
78
|
|
|
79
|
+
|
|
80
|
+
runtime_wrappedListener._slothletResource = resource;
|
|
81
|
+
|
|
65
82
|
return runtime_wrappedListener;
|
|
66
83
|
}
|
|
67
84
|
|
|
@@ -81,6 +98,22 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
81
98
|
proto[addFnName] = function (event, listener) {
|
|
82
99
|
const map = runtime_ensureMap(this);
|
|
83
100
|
const wrapped = runtime_wrapListener(listener);
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if (!globalListenerTracker.has(this)) {
|
|
105
|
+
globalListenerTracker.set(this, new Set());
|
|
106
|
+
}
|
|
107
|
+
const listenerInfo = {
|
|
108
|
+
emitter: this,
|
|
109
|
+
event,
|
|
110
|
+
originalListener: listener,
|
|
111
|
+
wrappedListener: wrapped,
|
|
112
|
+
addMethod: addFnName
|
|
113
|
+
};
|
|
114
|
+
globalListenerTracker.get(this).add(listenerInfo);
|
|
115
|
+
allPatchedListeners.add(listenerInfo);
|
|
116
|
+
|
|
84
117
|
if (wrapped !== listener) map.set(listener, wrapped);
|
|
85
118
|
return orig.call(this, event, wrapped);
|
|
86
119
|
};
|
|
@@ -99,6 +132,30 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
99
132
|
const runtime_removeWrapper = function (event, listener) {
|
|
100
133
|
const map = runtime_ensureMap(this);
|
|
101
134
|
const wrapped = map.get(listener) || listener;
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if (globalListenerTracker.has(this)) {
|
|
138
|
+
const emitterListeners = globalListenerTracker.get(this);
|
|
139
|
+
for (const info of emitterListeners) {
|
|
140
|
+
if (info.originalListener === listener || info.wrappedListener === wrapped) {
|
|
141
|
+
emitterListeners.delete(info);
|
|
142
|
+
allPatchedListeners.delete(info);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if (wrapped && wrapped._slothletResource) {
|
|
150
|
+
const resource = wrapped._slothletResource;
|
|
151
|
+
globalResourceSet.delete(resource);
|
|
152
|
+
try {
|
|
153
|
+
resource.emitDestroy();
|
|
154
|
+
} catch (err) {
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
102
159
|
map.delete(listener);
|
|
103
160
|
return method.call(this, event, wrapped);
|
|
104
161
|
};
|
|
@@ -117,4 +174,84 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
117
174
|
if (this[kMap]) this[kMap] = new WeakMap();
|
|
118
175
|
return res;
|
|
119
176
|
};
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if (!originalMethods) {
|
|
180
|
+
originalMethods = {
|
|
181
|
+
on: origOn,
|
|
182
|
+
once: origOnce,
|
|
183
|
+
addListener: origAdd,
|
|
184
|
+
prependListener: origPre,
|
|
185
|
+
prependOnceListener: origPreO,
|
|
186
|
+
off: origOff,
|
|
187
|
+
removeListener: origRem,
|
|
188
|
+
removeAllListeners: origRemoveAll
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
export function cleanupAllSlothletListeners() {
|
|
196
|
+
let cleanedCount = 0;
|
|
197
|
+
let errorCount = 0;
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
for (const listenerInfo of allPatchedListeners) {
|
|
201
|
+
try {
|
|
202
|
+
const { emitter, event, wrappedListener } = listenerInfo;
|
|
203
|
+
if (emitter && typeof emitter.removeListener === "function") {
|
|
204
|
+
emitter.removeListener(event, wrappedListener);
|
|
205
|
+
cleanedCount++;
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
errorCount++;
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
allPatchedListeners.clear();
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if (process.env.NODE_ENV === "development" || process.env.SLOTHLET_DEBUG) {
|
|
218
|
+
console.log(`[slothlet] Cleaned up ${cleanedCount} listeners (${errorCount} errors)`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function disableAlsForEventEmitters() {
|
|
223
|
+
const kPatched = Symbol.for("slothlet.als.patched");
|
|
224
|
+
|
|
225
|
+
if (!EventEmitter.prototype[kPatched] || !originalMethods) return;
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
cleanupAllSlothletListeners();
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
for (const resource of globalResourceSet) {
|
|
232
|
+
try {
|
|
233
|
+
resource.emitDestroy();
|
|
234
|
+
} catch (err) {
|
|
235
|
+
|
|
236
|
+
console.warn("[slothlet] AsyncResource cleanup warning:", err.message);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
globalResourceSet.clear();
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
const proto = EventEmitter.prototype;
|
|
243
|
+
proto.on = originalMethods.on;
|
|
244
|
+
proto.once = originalMethods.once;
|
|
245
|
+
proto.addListener = originalMethods.addListener;
|
|
246
|
+
if (originalMethods.prependListener) proto.prependListener = originalMethods.prependListener;
|
|
247
|
+
if (originalMethods.prependOnceListener) proto.prependOnceListener = originalMethods.prependOnceListener;
|
|
248
|
+
if (originalMethods.off) proto.off = originalMethods.off;
|
|
249
|
+
proto.removeListener = originalMethods.removeListener;
|
|
250
|
+
proto.removeAllListeners = originalMethods.removeAllListeners;
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
delete EventEmitter.prototype[kPatched];
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
originalMethods = null;
|
|
120
257
|
}
|
|
@@ -53,6 +53,14 @@ export class HookManager {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
cleanup() {
|
|
57
|
+
this.hooks.clear();
|
|
58
|
+
this.reportedErrors = new WeakSet();
|
|
59
|
+
this.registrationOrder = 0;
|
|
60
|
+
this.enabled = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
56
64
|
off(nameOrPattern) {
|
|
57
65
|
|
|
58
66
|
if (this.hooks.has(nameOrPattern)) {
|
package/dist/slothlet.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
buildCategoryDecisions
|
|
32
32
|
} from "@cldmv/slothlet/helpers/api_builder";
|
|
33
33
|
import { updateInstanceData, cleanupInstance } from "./lib/helpers/instance-manager.mjs";
|
|
34
|
+
import { disableAlsForEventEmitters, cleanupAllSlothletListeners } from "./lib/helpers/als-eventemitter.mjs";
|
|
34
35
|
import { HookManager } from "./lib/helpers/hooks.mjs";
|
|
35
36
|
|
|
36
37
|
|
|
@@ -1152,12 +1153,30 @@ const slothletObject = {
|
|
|
1152
1153
|
this._boundAPIShutdown = null;
|
|
1153
1154
|
|
|
1154
1155
|
|
|
1156
|
+
if (this.hookManager) {
|
|
1157
|
+
this.hookManager.cleanup();
|
|
1158
|
+
this.hookManager = null;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
|
|
1155
1162
|
|
|
1156
1163
|
|
|
1157
1164
|
if (this.instanceId) {
|
|
1158
1165
|
await cleanupInstance(this.instanceId);
|
|
1159
1166
|
}
|
|
1160
1167
|
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
try {
|
|
1171
|
+
|
|
1172
|
+
cleanupAllSlothletListeners();
|
|
1173
|
+
|
|
1174
|
+
disableAlsForEventEmitters();
|
|
1175
|
+
} catch (cleanupError) {
|
|
1176
|
+
|
|
1177
|
+
console.warn("[slothlet] Warning: EventEmitter cleanup failed:", cleanupError.message);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1161
1180
|
if (apiError || internalError) throw apiError || internalError;
|
|
1162
1181
|
}
|
|
1163
1182
|
} finally {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cldmv/slothlet",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"moduleVersions": {
|
|
5
|
-
"lazy": "1.3.
|
|
6
|
-
"eager": "1.3.
|
|
5
|
+
"lazy": "1.3.1",
|
|
6
|
+
"eager": "1.3.1"
|
|
7
7
|
},
|
|
8
8
|
"description": "Slothlet: Modular API Loader for Node.js. Lazy mode dynamically loads API modules and submodules only when accessed, supporting both lazy and eager loading.",
|
|
9
9
|
"main": "./index.cjs",
|
|
@@ -19,5 +19,38 @@
|
|
|
19
19
|
* enableAlsForEventEmitters(als);
|
|
20
20
|
*/
|
|
21
21
|
export function enableAlsForEventEmitters(als?: AsyncLocalStorage<any>): void;
|
|
22
|
+
/**
|
|
23
|
+
* Disable AsyncLocalStorage context propagation for EventEmitter instances.
|
|
24
|
+
*
|
|
25
|
+
* @function disableAlsForEventEmitters
|
|
26
|
+
* @package
|
|
27
|
+
*
|
|
28
|
+
* @description
|
|
29
|
+
* Restores original EventEmitter methods, removing the AsyncLocalStorage
|
|
30
|
+
* context propagation. This should be called during cleanup to prevent
|
|
31
|
+
* hanging AsyncResource instances that can keep the event loop alive.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Disable ALS patching during shutdown
|
|
35
|
+
* disableAlsForEventEmitters();
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Clean up ALL listeners that went through slothlet's EventEmitter patching.
|
|
39
|
+
*
|
|
40
|
+
* @function cleanupAllSlothletListeners
|
|
41
|
+
* @package
|
|
42
|
+
*
|
|
43
|
+
* @description
|
|
44
|
+
* Removes all event listeners that were registered through slothlet's patched
|
|
45
|
+
* EventEmitter methods. This includes listeners from third-party libraries
|
|
46
|
+
* that got wrapped with AsyncResource instances. This nuclear cleanup option
|
|
47
|
+
* should be called during shutdown to prevent hanging listeners.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Clean up all patched listeners during shutdown
|
|
51
|
+
* cleanupAllSlothletListeners();
|
|
52
|
+
*/
|
|
53
|
+
export function cleanupAllSlothletListeners(): void;
|
|
54
|
+
export function disableAlsForEventEmitters(): void;
|
|
22
55
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23
56
|
//# sourceMappingURL=als-eventemitter.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"als-eventemitter.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/als-eventemitter.mjs"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"als-eventemitter.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/als-eventemitter.mjs"],"names":[],"mappings":"AAkDA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,8EAiNC;AAED;;;;;;;;;;;;;;GAcG;AACH;;;;;;;;;;;;;;;GAeG;AACH,oDAyBC;AAED,mDAmCC;kCApViC,kBAAkB"}
|
|
@@ -55,6 +55,18 @@ export class HookManager {
|
|
|
55
55
|
priority?: number;
|
|
56
56
|
pattern?: string;
|
|
57
57
|
}): string;
|
|
58
|
+
/**
|
|
59
|
+
* Clean up all hooks and resources
|
|
60
|
+
* @public
|
|
61
|
+
* @description
|
|
62
|
+
* Clears all registered hooks and resets internal state.
|
|
63
|
+
* Should be called during shutdown to prevent memory leaks.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // Clean up during shutdown
|
|
67
|
+
* manager.cleanup();
|
|
68
|
+
*/
|
|
69
|
+
public cleanup(): void;
|
|
58
70
|
/**
|
|
59
71
|
* @function off
|
|
60
72
|
* @public
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/hooks.mjs"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;GAaG;AACH;IACC;;;;;;OAMG;IACH,sBALW,OAAO,mBACP,MAAM,YAEd;QAA0B,cAAc,GAAhC,OAAO;KACjB,EAQA;IANA,iBAAsB;IACtB,uBAAoC;IACpC,wBAAqD;IACrD,qBAAsB;IACtB,0BAA0B;IAC1B,gCAAmC;IAGpC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,gBAnBW,MAAM,QACN,MAAM,+BAOd;QAAyB,QAAQ,GAAzB,MAAM;QACW,OAAO,GAAxB,MAAM;KACd,GAAU,MAAM,CA0BlB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,0BAdW,MAAM,GACJ,OAAO,CA6BnB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,oBAdW,MAAM,GACJ,IAAI,CA0BhB;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,mBAfW,MAAM,GACJ,KAAK,CAAC,MAAM,CAAC,CA4BzB;IAED;;;;;;;;;;;;OAYG;IACH,wBAVW,MAAM,GACJ,IAAI,CAchB;IAED;;;;;;;;;;;OAWG;IACH,kBATa,IAAI,CAWhB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,2BAkCC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,0BAwBC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,2BAqBC;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,0BAyBC;IAED;;;;;;;;;;;;;;OAcG;IACH,0BAkBC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,wBAmBC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,sBA4CC;IAED;;;;;;;;;;;;;OAaG;IACH,2BAuBC;IAED;;;;;;;;;;;;;OAaG;IACH,wBAiBC;IAED;;;;;;;;;;;;;;OAcG;IACH,sBAOC;CACD"}
|
|
1
|
+
{"version":3,"file":"hooks.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/hooks.mjs"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;GAaG;AACH;IACC;;;;;;OAMG;IACH,sBALW,OAAO,mBACP,MAAM,YAEd;QAA0B,cAAc,GAAhC,OAAO;KACjB,EAQA;IANA,iBAAsB;IACtB,uBAAoC;IACpC,wBAAqD;IACrD,qBAAsB;IACtB,0BAA0B;IAC1B,gCAAmC;IAGpC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,gBAnBW,MAAM,QACN,MAAM,+BAOd;QAAyB,QAAQ,GAAzB,MAAM;QACW,OAAO,GAAxB,MAAM;KACd,GAAU,MAAM,CA0BlB;IAED;;;;;;;;;;OAUG;IACH,uBAKC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,0BAdW,MAAM,GACJ,OAAO,CA6BnB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,oBAdW,MAAM,GACJ,IAAI,CA0BhB;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,mBAfW,MAAM,GACJ,KAAK,CAAC,MAAM,CAAC,CA4BzB;IAED;;;;;;;;;;;;OAYG;IACH,wBAVW,MAAM,GACJ,IAAI,CAchB;IAED;;;;;;;;;;;OAWG;IACH,kBATa,IAAI,CAWhB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,2BAkCC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,0BAwBC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,2BAqBC;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,0BAyBC;IAED;;;;;;;;;;;;;;OAcG;IACH,0BAkBC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,wBAmBC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,sBA4CC;IAED;;;;;;;;;;;;;OAaG;IACH,2BAuBC;IAED;;;;;;;;;;;;;OAaG;IACH,wBAiBC;IAED;;;;;;;;;;;;;;OAcG;IACH,sBAOC;CACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slothlet.d.mts","sourceRoot":"","sources":["../../dist/slothlet.mjs"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slothlet.d.mts","sourceRoot":"","sources":["../../dist/slothlet.mjs"],"names":[],"mappings":"AAkjDA;;;;;;;;;GASG;AACH,kDARW,WAAS,MAAM,UACf,WAAS,MAAM,QAwCzB;AAt6CD;;;;;;;GAOG;AACH,mBAJU,MAAM,CAIO;AAEvB;;;;;GAKG;AACH,sBAJU,MAAM,CAIU;AAE1B;;;;;GAKG;AACH,wBAJU,MAAM,CAIY;;;;;;;;;UAy5Cd,MAAM;;;;;;WAIN,OAAO;;;;;;;;WAGP,MAAM;;;;;;;;aAKN,MAAM;;;;;;;cAKN,MAAM;;;;;;;eAIN,MAAM;;;;;;;;YAIN,OAAO;;;;;;;eAKP,MAAM;;;;;;cAIN,MAAM;;;;;;gBAGN,MAAM;;;;;;eAMjB;QAA8B,UAAU,GAA7B,OAAO;QACY,gBAAgB,GAAnC,OAAO;QACY,gBAAgB,GAAnC,OAAO;QACW,KAAK,GAClC;YAAqC,KAAK,GAA/B,MAAM,EAAE;YACkB,gBAAgB,GAA1C,MAAM,EAAE;YACkB,KAAK,GAA/B,MAAM,EAAE;YACkB,KAAK,GAA/B,MAAM,EAAE;SACrB;KAAA;;AA18CD;;;;;;;;GAQG;AACH,mCAJW,eAAe,GACb,OAAO,CAAC,WAAS,MAAM,CAAC,CAiCpC"}
|