codeaura-embedded-runtime-agent 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/.env.example +4 -0
- package/.eslintignore +3 -0
- package/.eslintrc +97 -0
- package/CLAUDE.md +210 -0
- package/README.md +234 -0
- package/code_aura_embedded_runtime_agent_v_1_implementation_guide.md +253 -0
- package/index.js +2 -0
- package/package.json +37 -0
- package/src/agent/Agent.js +224 -0
- package/src/config/config.js +261 -0
- package/src/core/buffer/memoryBuffer.js +164 -0
- package/src/core/events/EventSchema.js +171 -0
- package/src/core/transport/httpTransport.js +242 -0
- package/src/framework/nodejs/sails/SailsAdapter.js +83 -0
- package/src/framework/nodejs/sails/sailsHook.js +248 -0
- package/src/instrumentation/nodejs/instrumentHelperTree.js +31 -0
- package/src/instrumentation/nodejs/wrappers/createEvent.js +111 -0
- package/src/instrumentation/nodejs/wrappers/handleAsyncFunction.js +39 -0
- package/src/instrumentation/nodejs/wrappers/index.js +12 -0
- package/src/instrumentation/nodejs/wrappers/wrapController.js +42 -0
- package/src/instrumentation/nodejs/wrappers/wrapFunction.js +108 -0
- package/src/instrumentation/nodejs/wrappers/wrapHelper.js +86 -0
- package/src/utils/executionContext.js +42 -0
- package/src/utils/logger.js +150 -0
- package/src/utils/publicApiClient.js +87 -0
- package/src/utils/sanitizeInputs.js +85 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# CodeAura Embedded Runtime Agent – V1 Implementation Guide
|
|
2
|
+
|
|
3
|
+
This document describes **how to implement Version 1 (V1)** of the CodeAura Embedded Runtime Agent.
|
|
4
|
+
|
|
5
|
+
The goal of V1 is **not completeness**, but to deliver a **working, stable, and understandable foundation** that proves the concept and can evolve safely.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Scope of V1
|
|
10
|
+
|
|
11
|
+
### What V1 MUST do
|
|
12
|
+
|
|
13
|
+
- Run inside a **Sails.js (Node.js) application**
|
|
14
|
+
- Capture runtime execution of:
|
|
15
|
+
- Controllers
|
|
16
|
+
- Helpers
|
|
17
|
+
- Measure:
|
|
18
|
+
- Execution start / end
|
|
19
|
+
- Duration
|
|
20
|
+
- Errors (if any)
|
|
21
|
+
- Emit structured execution events
|
|
22
|
+
- Send events to the CodeAura API via HTTP
|
|
23
|
+
|
|
24
|
+
### What V1 MUST NOT do
|
|
25
|
+
|
|
26
|
+
- Support multiple frameworks
|
|
27
|
+
- Support multiple languages
|
|
28
|
+
- Persist data to disk
|
|
29
|
+
- Implement complex retry or resilience logic
|
|
30
|
+
- Capture database internals
|
|
31
|
+
- Behave like a full APM solution
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. Core Concepts
|
|
36
|
+
|
|
37
|
+
### 2.1 Runtime Instrumentation
|
|
38
|
+
|
|
39
|
+
The agent **does not analyze source code statically**.
|
|
40
|
+
Instead, it instruments the application **at runtime** by wrapping functions while the application is starting.
|
|
41
|
+
|
|
42
|
+
This ensures:
|
|
43
|
+
- No breakpoints
|
|
44
|
+
- No debugger
|
|
45
|
+
- Real execution paths
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### 2.2 Function Wrapping (Key Mechanism)
|
|
50
|
+
|
|
51
|
+
The core mechanism of V1 is **function wrapping**:
|
|
52
|
+
|
|
53
|
+
1. Take an existing function
|
|
54
|
+
2. Replace it with a wrapper function
|
|
55
|
+
3. Measure execution
|
|
56
|
+
4. Capture metadata
|
|
57
|
+
5. Call the original function
|
|
58
|
+
|
|
59
|
+
This is applied to:
|
|
60
|
+
- Sails controllers
|
|
61
|
+
- Sails helpers
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 3. Step-by-Step Implementation Plan
|
|
66
|
+
|
|
67
|
+
### Step 1 — Agent Entry Point (`Agent.js`)
|
|
68
|
+
|
|
69
|
+
Responsibilities:
|
|
70
|
+
- Load configuration
|
|
71
|
+
- Initialize core services (buffer, transport)
|
|
72
|
+
- Detect runtime environment
|
|
73
|
+
- Initialize framework hooks
|
|
74
|
+
|
|
75
|
+
Pseudo-flow:
|
|
76
|
+
|
|
77
|
+
1. Agent.init(sails)
|
|
78
|
+
2. Validate config
|
|
79
|
+
3. Initialize EventBuffer
|
|
80
|
+
4. Initialize HttpTransport
|
|
81
|
+
5. Register Sails hook
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### Step 2 — Sails Hook (`sailsHook.js`)
|
|
86
|
+
|
|
87
|
+
Responsibilities:
|
|
88
|
+
- Hook into the Sails lifecycle
|
|
89
|
+
- Run once the app is loaded
|
|
90
|
+
- Access controllers and helpers
|
|
91
|
+
|
|
92
|
+
Key lifecycle point:
|
|
93
|
+
- `sails.on('lifted')`
|
|
94
|
+
|
|
95
|
+
At this stage:
|
|
96
|
+
- Controllers and helpers are fully registered
|
|
97
|
+
- Safe to wrap functions
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### Step 3 — Controller Instrumentation
|
|
102
|
+
|
|
103
|
+
Process:
|
|
104
|
+
|
|
105
|
+
1. Iterate over `sails.controllers`
|
|
106
|
+
2. For each controller action:
|
|
107
|
+
- Replace function with wrapped version
|
|
108
|
+
3. Wrapper logic:
|
|
109
|
+
- Record start time
|
|
110
|
+
- Execute original action
|
|
111
|
+
- Catch errors
|
|
112
|
+
- Record end time
|
|
113
|
+
- Emit event
|
|
114
|
+
|
|
115
|
+
Captured data:
|
|
116
|
+
- controller name
|
|
117
|
+
- action name
|
|
118
|
+
- duration
|
|
119
|
+
- HTTP metadata (method, route, status)
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### Step 4 — Helper Instrumentation
|
|
124
|
+
|
|
125
|
+
Process:
|
|
126
|
+
|
|
127
|
+
1. Iterate over `sails.helpers`
|
|
128
|
+
2. Wrap helper functions
|
|
129
|
+
3. Track:
|
|
130
|
+
- helper name
|
|
131
|
+
- duration
|
|
132
|
+
- errors
|
|
133
|
+
|
|
134
|
+
Important:
|
|
135
|
+
- Helpers may be async or sync
|
|
136
|
+
- Wrapper must support both
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### Step 5 — Event Creation (`EventSchema.js`)
|
|
141
|
+
|
|
142
|
+
Each execution produces an event:
|
|
143
|
+
|
|
144
|
+
Required fields:
|
|
145
|
+
- id
|
|
146
|
+
- timestamp
|
|
147
|
+
- type (controller | helper)
|
|
148
|
+
- name
|
|
149
|
+
- durationMs
|
|
150
|
+
- framework
|
|
151
|
+
- language
|
|
152
|
+
|
|
153
|
+
Optional:
|
|
154
|
+
- error
|
|
155
|
+
- HTTP metadata
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### Step 6 — Event Buffer (`memoryBuffer.js`)
|
|
160
|
+
|
|
161
|
+
Responsibilities:
|
|
162
|
+
- Temporarily store events in memory
|
|
163
|
+
- Prevent sending one HTTP request per event
|
|
164
|
+
|
|
165
|
+
Rules:
|
|
166
|
+
- Simple array-based buffer
|
|
167
|
+
- Flush when:
|
|
168
|
+
- buffer size reaches threshold
|
|
169
|
+
- timer interval expires
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### Step 7 — Transport (`httpTransport.js`)
|
|
174
|
+
|
|
175
|
+
Responsibilities:
|
|
176
|
+
- Send batched events to CodeAura API
|
|
177
|
+
|
|
178
|
+
Constraints:
|
|
179
|
+
- Best-effort delivery
|
|
180
|
+
- No retries in V1
|
|
181
|
+
- Fail silently (log errors)
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 4. Configuration Strategy
|
|
186
|
+
|
|
187
|
+
Minimal configuration:
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
{
|
|
191
|
+
enabled: true,
|
|
192
|
+
endpoint: "https://api.codeaura.dev/events",
|
|
193
|
+
apiKey: "YOUR_API_KEY"
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Configuration should:
|
|
198
|
+
- Be optional
|
|
199
|
+
- Have safe defaults
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 5. Error Handling Philosophy (V1)
|
|
204
|
+
|
|
205
|
+
Rule #1: **The agent must never crash the host application**.
|
|
206
|
+
|
|
207
|
+
Therefore:
|
|
208
|
+
- All agent code must be wrapped in try/catch
|
|
209
|
+
- Errors are logged, not thrown
|
|
210
|
+
- Instrumentation failure should disable itself gracefully
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 6. Testing Strategy (V1)
|
|
215
|
+
|
|
216
|
+
### Manual testing
|
|
217
|
+
|
|
218
|
+
- Create a sample Sails app
|
|
219
|
+
- Install the agent
|
|
220
|
+
- Trigger controller routes
|
|
221
|
+
- Observe event logs
|
|
222
|
+
|
|
223
|
+
### Success criteria
|
|
224
|
+
|
|
225
|
+
- App runs normally
|
|
226
|
+
- Events are emitted
|
|
227
|
+
- No visible behavior change
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 7. What Comes Next (V2+)
|
|
232
|
+
|
|
233
|
+
Once V1 is stable:
|
|
234
|
+
|
|
235
|
+
- Add Express adapter
|
|
236
|
+
- Introduce persistent buffering
|
|
237
|
+
- Add retry strategies
|
|
238
|
+
- Add data masking
|
|
239
|
+
- Add execution context correlation
|
|
240
|
+
- Introduce Python agent
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 8. Definition of Done (V1)
|
|
245
|
+
|
|
246
|
+
V1 is complete when:
|
|
247
|
+
|
|
248
|
+
- Agent can be installed via npm
|
|
249
|
+
- Sails controllers & helpers are traced
|
|
250
|
+
- Events reach CodeAura
|
|
251
|
+
- Code is readable and maintainable
|
|
252
|
+
- Architecture is ready for extension
|
|
253
|
+
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codeaura-embedded-runtime-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight runtime execution tracer for JavaScript applications",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"runtime",
|
|
8
|
+
"tracing",
|
|
9
|
+
"instrumentation",
|
|
10
|
+
"sailsjs",
|
|
11
|
+
"observability",
|
|
12
|
+
"codeaura"
|
|
13
|
+
],
|
|
14
|
+
"author": "CodeAura",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=14.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "npm run lint && npm run custom-tests && echo 'Done.'",
|
|
21
|
+
"lint": "./node_modules/eslint/bin/eslint.js . --max-warnings=0 --report-unused-disable-directives",
|
|
22
|
+
"lint-windows": "eslint . --max-warnings=0 --report-unused-disable-directives -c .eslintrc --ignore-path .eslintignore",
|
|
23
|
+
"custom-tests": "echo \"(No other custom tests yet.)\" && echo"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"axios": "^1.6.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@babel/core": "^7.23.6",
|
|
30
|
+
"babel-eslint": "^10.1.0",
|
|
31
|
+
"eslint": "^8.55.0"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/codeaura/codeaura-embedded-runtime-agent"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
|
|
2
|
+
const path = require("path"),
|
|
3
|
+
fs = require("fs"),
|
|
4
|
+
Config = require("../config/config"),
|
|
5
|
+
loggerModule = require("../utils/logger"),
|
|
6
|
+
MemoryBuffer = require("../core/buffer/memoryBuffer"),
|
|
7
|
+
HttpTransport = require("../core/transport/httpTransport");
|
|
8
|
+
|
|
9
|
+
let agentInstance = null;
|
|
10
|
+
|
|
11
|
+
class Agent {
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.config = null;
|
|
15
|
+
this.logger = null;
|
|
16
|
+
this.initialized = false;
|
|
17
|
+
this.buffer = null;
|
|
18
|
+
this.transport = null;
|
|
19
|
+
this.frameworkAdapter = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
readProjectConfig() {
|
|
23
|
+
let configPath = null,
|
|
24
|
+
rawContent = null,
|
|
25
|
+
projectConfig = null;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
configPath = path.join(process.cwd(), "codeaura-config.json");
|
|
29
|
+
rawContent = fs.readFileSync(configPath, "utf8");
|
|
30
|
+
projectConfig = JSON.parse(rawContent);
|
|
31
|
+
|
|
32
|
+
return projectConfig;
|
|
33
|
+
} catch (unusedE) {
|
|
34
|
+
console.log("[CodeAura Agent] codeaura-config.json not found or invalid at: " + path.join(process.cwd(), "codeaura-config.json"));
|
|
35
|
+
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
init(userConfig = {}) {
|
|
41
|
+
let projectConfig = null;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
projectConfig = this.readProjectConfig();
|
|
45
|
+
|
|
46
|
+
if (projectConfig && projectConfig.project) {
|
|
47
|
+
userConfig.projectId = projectConfig.project;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.config = new Config(userConfig);
|
|
51
|
+
|
|
52
|
+
this.logger = loggerModule.getLogger(this.config.get("logging"));
|
|
53
|
+
|
|
54
|
+
if (this.config.isEnabled()) {
|
|
55
|
+
this.logger.info("Initializing CodeAura Runtime Agent...");
|
|
56
|
+
this.logger.info("Project ID: " + (this.config.get("projectId") || "none"));
|
|
57
|
+
|
|
58
|
+
this.initializeCoreServices();
|
|
59
|
+
this.initializeFrameworkAdapter();
|
|
60
|
+
|
|
61
|
+
this.initialized = true;
|
|
62
|
+
this.logger.info("CodeAura Runtime Agent initialized successfully");
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (this.logger) {
|
|
66
|
+
this.logger.errorWithStack("Failed to initialize agent", error);
|
|
67
|
+
} else {
|
|
68
|
+
console.error("Failed to initialize agent", error);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
initializeCoreServices() {
|
|
76
|
+
try {
|
|
77
|
+
const transportConfig = this.config.get("transport"),
|
|
78
|
+
bufferConfig = this.config.get("buffer"),
|
|
79
|
+
agentConfig = this.config.getAll();
|
|
80
|
+
|
|
81
|
+
this.transport = new HttpTransport(transportConfig, agentConfig, this.logger);
|
|
82
|
+
this.buffer = new MemoryBuffer(bufferConfig, this.transport, this.logger, agentConfig);
|
|
83
|
+
|
|
84
|
+
this.logger.debug("Core services initialized successfully");
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this.logger.errorWithStack("Failed to initialize core services", error);
|
|
87
|
+
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
initializeFrameworkAdapter() {
|
|
93
|
+
try {
|
|
94
|
+
const frameworkConfig = this.config.get("framework");
|
|
95
|
+
|
|
96
|
+
let AdapterClass = null;
|
|
97
|
+
|
|
98
|
+
if (!frameworkConfig || typeof frameworkConfig !== "object" || !frameworkConfig.type) {
|
|
99
|
+
this.logger.info("No framework adapter configured, running in standalone mode");
|
|
100
|
+
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof frameworkConfig.type !== "string") {
|
|
105
|
+
throw new Error("Invalid framework configuration: 'type' must be a string");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.logger.debug("Loading framework adapter for: " + frameworkConfig.type);
|
|
109
|
+
|
|
110
|
+
AdapterClass = this.loadFrameworkAdapter(frameworkConfig.type);
|
|
111
|
+
|
|
112
|
+
this.frameworkAdapter = new AdapterClass(frameworkConfig, this);
|
|
113
|
+
if (typeof this.frameworkAdapter.init === "function") {
|
|
114
|
+
this.frameworkAdapter.init();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.logger.debug("Framework adapter initialized successfully");
|
|
118
|
+
} catch (error) {
|
|
119
|
+
this.logger.errorWithStack("Failed to initialize framework adapter", error);
|
|
120
|
+
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
loadFrameworkAdapter(frameworkType) {
|
|
126
|
+
try {
|
|
127
|
+
let adapterPath;
|
|
128
|
+
|
|
129
|
+
switch (frameworkType.toLowerCase()) {
|
|
130
|
+
case "sails":
|
|
131
|
+
adapterPath = path.resolve(__dirname, "../framework/nodejs/sails/SailsAdapter");
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
throw new Error(`Unsupported framework type: ${frameworkType}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const AdapterClass = require(adapterPath);
|
|
138
|
+
|
|
139
|
+
return AdapterClass;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
this.logger.errorWithStack(`Failed to load framework adapter: ${frameworkType}`, error);
|
|
142
|
+
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getConfig() {
|
|
148
|
+
return this.config;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getLogger() {
|
|
152
|
+
return this.logger;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
isInitialized() {
|
|
156
|
+
return this.initialized;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
shutdown() {
|
|
160
|
+
try {
|
|
161
|
+
if (this.initialized) {
|
|
162
|
+
if (this.buffer) {
|
|
163
|
+
this.buffer.shutdown();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (this.frameworkAdapter && typeof this.frameworkAdapter.shutdown === "function") {
|
|
167
|
+
this.frameworkAdapter.shutdown();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.initialized = false;
|
|
171
|
+
this.logger.info("Agent shutdown complete");
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
this.logger.errorWithStack("Error during agent shutdown", error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function init(sailsInstanceOrConfig, userConfig) {
|
|
180
|
+
if (!agentInstance) {
|
|
181
|
+
agentInstance = new Agent();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let finalConfig = {};
|
|
185
|
+
|
|
186
|
+
const isObject = (obj) => obj && typeof obj === "object" && !Array.isArray(obj),
|
|
187
|
+
|
|
188
|
+
isSailsInstance = isObject(sailsInstanceOrConfig) &&
|
|
189
|
+
sailsInstanceOrConfig.constructor &&
|
|
190
|
+
typeof sailsInstanceOrConfig.lift === "function" &&
|
|
191
|
+
typeof sailsInstanceOrConfig.hooks === "object";
|
|
192
|
+
|
|
193
|
+
if (isSailsInstance && isObject(userConfig)) {
|
|
194
|
+
finalConfig = userConfig;
|
|
195
|
+
finalConfig.framework = {
|
|
196
|
+
type: "sails",
|
|
197
|
+
instance: sailsInstanceOrConfig
|
|
198
|
+
};
|
|
199
|
+
} else if (!isSailsInstance && isObject(sailsInstanceOrConfig)) {
|
|
200
|
+
finalConfig = sailsInstanceOrConfig;
|
|
201
|
+
} else {
|
|
202
|
+
throw new Error("Invalid arguments: provide either (sailsInstance, config) or (config)");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
agentInstance.init(finalConfig);
|
|
206
|
+
|
|
207
|
+
return agentInstance;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getInstance() {
|
|
211
|
+
return agentInstance;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function shutdown() {
|
|
215
|
+
if (agentInstance) {
|
|
216
|
+
agentInstance.shutdown();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
init,
|
|
222
|
+
getInstance,
|
|
223
|
+
shutdown
|
|
224
|
+
};
|