indusagi-coding-agent 0.1.28 → 0.1.30
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/CHANGELOG.md +23 -0
- package/LICENSE.md +22 -0
- package/README.md +2 -0
- package/dist/core/messages.d.ts +1 -76
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +1 -122
- package/dist/core/messages.js.map +1 -1
- package/dist/core/session-manager.d.ts +1 -447
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1203
- package/dist/core/session-manager.js.map +1 -1
- package/package.json +2 -2
- package/docs/COMPLETE-GUIDE.md +0 -300
- package/docs/COMPREHENSIVE-CLI-SUMMARY.md +0 -900
- package/docs/MODES-ARCHITECTURE.md +0 -565
- package/docs/PRINT-MODE-GUIDE.md +0 -456
- package/docs/README.md +0 -78
- package/docs/RPC-GUIDE.md +0 -705
- package/docs/UTILS-IMPLEMENTATION-SUMMARY.md +0 -647
- package/docs/UTILS-MODULE-OVERVIEW.md +0 -1480
- package/docs/UTILS-QA-CHECKLIST.md +0 -1061
- package/docs/UTILS-USAGE-GUIDE.md +0 -1419
- package/docs/compaction.md +0 -390
- package/docs/custom-provider.md +0 -538
- package/docs/development.md +0 -69
- package/docs/extensions.md +0 -1733
- package/docs/hooks.md +0 -378
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/json.md +0 -79
- package/docs/keybindings.md +0 -162
- package/docs/models.md +0 -193
- package/docs/packages.md +0 -163
- package/docs/prompt-templates.md +0 -67
- package/docs/providers.md +0 -147
- package/docs/rpc.md +0 -1048
- package/docs/sdk.md +0 -969
- package/docs/session.md +0 -412
- package/docs/settings.md +0 -219
- package/docs/shell-aliases.md +0 -13
- package/docs/skills.md +0 -226
- package/docs/subagents.md +0 -225
- package/docs/terminal-setup.md +0 -65
- package/docs/themes.md +0 -295
- package/docs/tree.md +0 -219
- package/docs/tui.md +0 -887
- package/docs/web-tools.md +0 -304
- package/docs/windows.md +0 -17
- package/examples/README.md +0 -25
- package/examples/extensions/README.md +0 -192
- package/examples/extensions/antigravity-image-gen.ts +0 -414
- package/examples/extensions/auto-commit-on-exit.ts +0 -49
- package/examples/extensions/bookmark.ts +0 -50
- package/examples/extensions/claude-rules.ts +0 -86
- package/examples/extensions/confirm-destructive.ts +0 -59
- package/examples/extensions/custom-compaction.ts +0 -115
- package/examples/extensions/custom-footer.ts +0 -65
- package/examples/extensions/custom-header.ts +0 -73
- package/examples/extensions/custom-provider-anthropic/index.ts +0 -605
- package/examples/extensions/custom-provider-anthropic/package-lock.json +0 -24
- package/examples/extensions/custom-provider-anthropic/package.json +0 -19
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +0 -350
- package/examples/extensions/custom-provider-gitlab-duo/package.json +0 -16
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +0 -83
- package/examples/extensions/dirty-repo-guard.ts +0 -56
- package/examples/extensions/doom-overlay/README.md +0 -46
- package/examples/extensions/doom-overlay/doom/build/doom.js +0 -21
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +0 -152
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +0 -72
- package/examples/extensions/doom-overlay/doom-component.ts +0 -133
- package/examples/extensions/doom-overlay/doom-engine.ts +0 -173
- package/examples/extensions/doom-overlay/doom-keys.ts +0 -105
- package/examples/extensions/doom-overlay/index.ts +0 -74
- package/examples/extensions/doom-overlay/wad-finder.ts +0 -51
- package/examples/extensions/event-bus.ts +0 -43
- package/examples/extensions/file-trigger.ts +0 -41
- package/examples/extensions/git-checkpoint.ts +0 -53
- package/examples/extensions/handoff.ts +0 -151
- package/examples/extensions/hello.ts +0 -25
- package/examples/extensions/inline-bash.ts +0 -94
- package/examples/extensions/input-transform.ts +0 -43
- package/examples/extensions/interactive-shell.ts +0 -196
- package/examples/extensions/mac-system-theme.ts +0 -47
- package/examples/extensions/message-renderer.ts +0 -60
- package/examples/extensions/modal-editor.ts +0 -86
- package/examples/extensions/model-status.ts +0 -31
- package/examples/extensions/notify.ts +0 -25
- package/examples/extensions/overlay-qa-tests.ts +0 -882
- package/examples/extensions/overlay-test.ts +0 -151
- package/examples/extensions/permission-gate.ts +0 -34
- package/examples/extensions/pirate.ts +0 -47
- package/examples/extensions/plan-mode/README.md +0 -65
- package/examples/extensions/plan-mode/index.ts +0 -341
- package/examples/extensions/plan-mode/utils.ts +0 -168
- package/examples/extensions/preset.ts +0 -399
- package/examples/extensions/protected-paths.ts +0 -30
- package/examples/extensions/qna.ts +0 -120
- package/examples/extensions/question.ts +0 -265
- package/examples/extensions/questionnaire.ts +0 -428
- package/examples/extensions/rainbow-editor.ts +0 -88
- package/examples/extensions/sandbox/index.ts +0 -318
- package/examples/extensions/sandbox/package-lock.json +0 -92
- package/examples/extensions/sandbox/package.json +0 -19
- package/examples/extensions/send-user-message.ts +0 -97
- package/examples/extensions/session-name.ts +0 -27
- package/examples/extensions/shutdown-command.ts +0 -63
- package/examples/extensions/snake.ts +0 -344
- package/examples/extensions/space-invaders.ts +0 -561
- package/examples/extensions/ssh.ts +0 -220
- package/examples/extensions/status-line.ts +0 -40
- package/examples/extensions/subagent/README.md +0 -172
- package/examples/extensions/subagent/agents/planner.md +0 -37
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/examples/extensions/subagent/agents/scout.md +0 -50
- package/examples/extensions/subagent/agents/worker.md +0 -24
- package/examples/extensions/subagent/agents.ts +0 -127
- package/examples/extensions/subagent/index.ts +0 -964
- package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/examples/extensions/subagent/prompts/implement.md +0 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
- package/examples/extensions/summarize.ts +0 -196
- package/examples/extensions/timed-confirm.ts +0 -70
- package/examples/extensions/todo.ts +0 -300
- package/examples/extensions/tool-override.ts +0 -144
- package/examples/extensions/tools.ts +0 -147
- package/examples/extensions/trigger-compact.ts +0 -40
- package/examples/extensions/truncated-tool.ts +0 -193
- package/examples/extensions/widget-placement.ts +0 -17
- package/examples/extensions/with-deps/index.ts +0 -36
- package/examples/extensions/with-deps/package-lock.json +0 -31
- package/examples/extensions/with-deps/package.json +0 -22
- package/examples/sdk/01-minimal.ts +0 -22
- package/examples/sdk/02-custom-model.ts +0 -50
- package/examples/sdk/03-custom-prompt.ts +0 -55
- package/examples/sdk/04-skills.ts +0 -46
- package/examples/sdk/05-tools.ts +0 -56
- package/examples/sdk/06-extensions.ts +0 -88
- package/examples/sdk/07-context-files.ts +0 -40
- package/examples/sdk/08-prompt-templates.ts +0 -47
- package/examples/sdk/09-api-keys-and-oauth.ts +0 -48
- package/examples/sdk/10-settings.ts +0 -38
- package/examples/sdk/11-sessions.ts +0 -48
- package/examples/sdk/12-full-control.ts +0 -82
- package/examples/sdk/13-codex-oauth.ts +0 -37
- package/examples/sdk/README.md +0 -144
|
@@ -1,1419 +0,0 @@
|
|
|
1
|
-
# Utils Module Usage Guide
|
|
2
|
-
|
|
3
|
-
**Version**: 1.0.0
|
|
4
|
-
**Last Updated**: February 2024
|
|
5
|
-
**Contains**: 40+ Real-World Examples
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Table of Contents
|
|
10
|
-
|
|
11
|
-
1. [Array Utilities Usage](#array-utilities-usage)
|
|
12
|
-
2. [Error Handling Patterns](#error-handling-patterns)
|
|
13
|
-
3. [Logging Usage Examples](#logging-usage-examples)
|
|
14
|
-
4. [File Operations Workflows](#file-operations-workflows)
|
|
15
|
-
5. [String & Date Formatting](#string--date-formatting)
|
|
16
|
-
6. [JSON Processing](#json-processing)
|
|
17
|
-
7. [Image Processing](#image-processing)
|
|
18
|
-
8. [Git Operations](#git-operations)
|
|
19
|
-
9. [Data Transformation Pipelines](#data-transformation-pipelines)
|
|
20
|
-
10. [Integration Patterns](#integration-patterns)
|
|
21
|
-
11. [Performance Optimization](#performance-optimization)
|
|
22
|
-
12. [Dependency Management](#dependency-management)
|
|
23
|
-
13. [Debugging Techniques](#debugging-techniques)
|
|
24
|
-
14. [Common Gotchas](#common-gotchas)
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Array Utilities Usage
|
|
29
|
-
|
|
30
|
-
### 1. Grouping and Categorizing Data
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
import { groupBy, partition, unique } from './utils/array';
|
|
34
|
-
|
|
35
|
-
// Example 1: Group users by role
|
|
36
|
-
const users = [
|
|
37
|
-
{ id: 1, name: 'Alice', role: 'admin' },
|
|
38
|
-
{ id: 2, name: 'Bob', role: 'user' },
|
|
39
|
-
{ id: 3, name: 'Charlie', role: 'admin' },
|
|
40
|
-
{ id: 4, name: 'Diana', role: 'user' }
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
const grouped = groupBy(users, user => user.role);
|
|
44
|
-
// Result:
|
|
45
|
-
// {
|
|
46
|
-
// admin: [{ id: 1, ... }, { id: 3, ... }],
|
|
47
|
-
// user: [{ id: 2, ... }, { id: 4, ... }]
|
|
48
|
-
// }
|
|
49
|
-
|
|
50
|
-
// Use case: Create permission groups
|
|
51
|
-
const permissions = {
|
|
52
|
-
admin: ['read', 'write', 'delete'],
|
|
53
|
-
user: ['read']
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
for (const [role, members] of Object.entries(grouped)) {
|
|
57
|
-
applyPermissions(members, permissions[role]);
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### 2. Partitioning Arrays
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
import { partition, unique } from './utils/array';
|
|
65
|
-
|
|
66
|
-
// Example 2: Separate active and inactive users
|
|
67
|
-
const [activeUsers, inactiveUsers] = partition(
|
|
68
|
-
users,
|
|
69
|
-
user => user.isActive
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// Example 3: Separate even and odd numbers
|
|
73
|
-
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
74
|
-
const [even, odd] = partition(numbers, n => n % 2 === 0);
|
|
75
|
-
// even: [2, 4, 6, 8]
|
|
76
|
-
// odd: [1, 3, 5, 7, 9]
|
|
77
|
-
|
|
78
|
-
// Practical: Split data for parallel processing
|
|
79
|
-
const [largeOrders, smallOrders] = partition(
|
|
80
|
-
orders,
|
|
81
|
-
order => order.total > 1000
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
processLargeOrdersWithPriority(largeOrders);
|
|
85
|
-
processSmallOrdersInBatch(smallOrders);
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### 3. Deduplication and Unique Values
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
import { unique } from './utils/array';
|
|
92
|
-
|
|
93
|
-
// Example 1: Remove duplicate IDs while preserving order
|
|
94
|
-
const records = [
|
|
95
|
-
{ id: 1, name: 'A' },
|
|
96
|
-
{ id: 2, name: 'B' },
|
|
97
|
-
{ id: 1, name: 'A' }, // duplicate
|
|
98
|
-
{ id: 3, name: 'C' }
|
|
99
|
-
];
|
|
100
|
-
|
|
101
|
-
const unique_ = unique(records, r => r.id);
|
|
102
|
-
// Result: [{ id: 1 }, { id: 2 }, { id: 3 }]
|
|
103
|
-
|
|
104
|
-
// Example 2: Get unique tags from multiple arrays
|
|
105
|
-
const tags1 = ['javascript', 'react', 'node'];
|
|
106
|
-
const tags2 = ['react', 'typescript', 'node'];
|
|
107
|
-
|
|
108
|
-
const allTags = unique([...tags1, ...tags2]);
|
|
109
|
-
// Result: ['javascript', 'react', 'node', 'typescript']
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### 4. Flattening Nested Arrays
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
import { flatten, chunk } from './utils/array';
|
|
116
|
-
|
|
117
|
-
// Example 1: Flatten directory structure
|
|
118
|
-
const fileStructure = [
|
|
119
|
-
'README.md',
|
|
120
|
-
['src/',
|
|
121
|
-
['utils.ts', 'index.ts'],
|
|
122
|
-
['tests/', ['utils.test.ts']]
|
|
123
|
-
],
|
|
124
|
-
'package.json'
|
|
125
|
-
];
|
|
126
|
-
|
|
127
|
-
const allFiles = flatten(fileStructure);
|
|
128
|
-
// Result: ['README.md', 'src/', 'utils.ts', 'index.ts', 'tests/', 'utils.test.ts', 'package.json']
|
|
129
|
-
|
|
130
|
-
// Example 2: Flatten with depth limit
|
|
131
|
-
const result = flatten(fileStructure, 2);
|
|
132
|
-
// Only flattens 2 levels deep
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### 5. Chunking for Batch Processing
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
import { chunk } from './utils/array';
|
|
139
|
-
|
|
140
|
-
// Example 1: Process large arrays in batches
|
|
141
|
-
const data = Array.from({ length: 1000 }, (_, i) => i);
|
|
142
|
-
const batchSize = 100;
|
|
143
|
-
|
|
144
|
-
for (const batch of chunk(data, batchSize)) {
|
|
145
|
-
await processBatch(batch);
|
|
146
|
-
// Prevents memory issues with large datasets
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Example 2: Create pagination
|
|
150
|
-
const itemsPerPage = 20;
|
|
151
|
-
const allItems = [...items];
|
|
152
|
-
const pages = chunk(allItems, itemsPerPage);
|
|
153
|
-
|
|
154
|
-
pages.forEach((page, index) => {
|
|
155
|
-
renderPage(page, index + 1); // Page numbers start at 1
|
|
156
|
-
});
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### 6. Sorting Custom Objects
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
import { sortBy } from './utils/array';
|
|
163
|
-
|
|
164
|
-
// Example: Sort users by age, then by name
|
|
165
|
-
const users = [
|
|
166
|
-
{ name: 'Charlie', age: 30 },
|
|
167
|
-
{ name: 'Alice', age: 25 },
|
|
168
|
-
{ name: 'Bob', age: 25 }
|
|
169
|
-
];
|
|
170
|
-
|
|
171
|
-
const sorted = sortBy(users, (a, b) => {
|
|
172
|
-
if (a.age !== b.age) return a.age - b.age;
|
|
173
|
-
return a.name.localeCompare(b.name);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// Result:
|
|
177
|
-
// [
|
|
178
|
-
// { name: 'Alice', age: 25 },
|
|
179
|
-
// { name: 'Bob', age: 25 },
|
|
180
|
-
// { name: 'Charlie', age: 30 }
|
|
181
|
-
// ]
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### 7. Set Operations
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
import { union, intersection, difference } from './utils/array';
|
|
188
|
-
|
|
189
|
-
// Example: Compare user permissions
|
|
190
|
-
const adminPermissions = ['read', 'write', 'delete', 'admin'];
|
|
191
|
-
const userPermissions = ['read', 'write'];
|
|
192
|
-
const guestPermissions = ['read'];
|
|
193
|
-
|
|
194
|
-
// All unique permissions
|
|
195
|
-
const allPermissions = union(adminPermissions, userPermissions, guestPermissions);
|
|
196
|
-
|
|
197
|
-
// Common permissions across all groups
|
|
198
|
-
const commonPermissions = intersection(adminPermissions, userPermissions, guestPermissions);
|
|
199
|
-
// Result: ['read']
|
|
200
|
-
|
|
201
|
-
// Exclusive admin permissions
|
|
202
|
-
const adminOnly = difference(adminPermissions, userPermissions, guestPermissions);
|
|
203
|
-
// Result: ['delete', 'admin']
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### 8. Finding Min/Max and Aggregations
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
import { minBy, maxBy, sum, average } from './utils/array';
|
|
210
|
-
|
|
211
|
-
// Example: Sales analytics
|
|
212
|
-
const sales = [
|
|
213
|
-
{ product: 'A', amount: 1500 },
|
|
214
|
-
{ product: 'B', amount: 2300 },
|
|
215
|
-
{ product: 'C', amount: 800 }
|
|
216
|
-
];
|
|
217
|
-
|
|
218
|
-
const bestProduct = maxBy(sales, s => s.amount);
|
|
219
|
-
const worstProduct = minBy(sales, s => s.amount);
|
|
220
|
-
const totalSales = sum(sales, s => s.amount);
|
|
221
|
-
const avgSale = average(sales, s => s.amount);
|
|
222
|
-
|
|
223
|
-
console.log(`Best: ${bestProduct.product} ($${bestProduct.amount})`);
|
|
224
|
-
console.log(`Worst: ${worstProduct.product} ($${worstProduct.amount})`);
|
|
225
|
-
console.log(`Total: $${totalSales}`);
|
|
226
|
-
console.log(`Average: $${avgSale.toFixed(2)}`);
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### 9. Zipping Arrays Together
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
import { zip, unzip } from './utils/array';
|
|
233
|
-
|
|
234
|
-
// Example 1: Combine related arrays
|
|
235
|
-
const names = ['Alice', 'Bob', 'Charlie'];
|
|
236
|
-
const ages = [25, 30, 35];
|
|
237
|
-
const cities = ['NYC', 'LA', 'Chicago'];
|
|
238
|
-
|
|
239
|
-
const combined = zip(names, ages, cities);
|
|
240
|
-
// Result: [
|
|
241
|
-
// ['Alice', 25, 'NYC'],
|
|
242
|
-
// ['Bob', 30, 'LA'],
|
|
243
|
-
// ['Charlie', 35, 'Chicago']
|
|
244
|
-
// ]
|
|
245
|
-
|
|
246
|
-
// Example 2: Create objects from zipped data
|
|
247
|
-
const people = combined.map(([name, age, city]) => ({
|
|
248
|
-
name, age, city
|
|
249
|
-
}));
|
|
250
|
-
|
|
251
|
-
// Example 3: Unzip to separate arrays
|
|
252
|
-
const [unzippedNames, unzippedAges, unzippedCities] = unzip(combined);
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### 10. Sampling and Shuffling
|
|
256
|
-
|
|
257
|
-
```typescript
|
|
258
|
-
import { shuffle, sample, sampleSize } from './utils/array';
|
|
259
|
-
|
|
260
|
-
// Example 1: Random selection
|
|
261
|
-
const questions = [/* 100 questions */];
|
|
262
|
-
const quiz = sampleSize(questions, 10); // Select 10 random questions
|
|
263
|
-
|
|
264
|
-
// Example 2: Shuffle for randomization
|
|
265
|
-
const shuffledCards = shuffle(deck); // Shuffle deck for card game
|
|
266
|
-
|
|
267
|
-
// Example 3: Pick single random item
|
|
268
|
-
const randomUser = sample(users);
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
|
-
## Error Handling Patterns
|
|
274
|
-
|
|
275
|
-
### 1. Basic Error Formatting
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
import { formatErrorForUser, ErrorCode } from './utils/error-handler';
|
|
279
|
-
|
|
280
|
-
// Pattern 1: Standard error handling
|
|
281
|
-
async function loadConfig() {
|
|
282
|
-
try {
|
|
283
|
-
const content = await fs.readFile('./config.json', 'utf-8');
|
|
284
|
-
return JSON.parse(content);
|
|
285
|
-
} catch (error) {
|
|
286
|
-
const formatted = formatErrorForUser(error);
|
|
287
|
-
console.error(`Error: ${formatted.message}`);
|
|
288
|
-
console.error('Try this:', formatted.suggestions[0]);
|
|
289
|
-
throw formatted.originalError;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### 2. API Error Response
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
import { formatErrorForUser } from './utils/error-handler';
|
|
298
|
-
|
|
299
|
-
// Express error middleware
|
|
300
|
-
app.use((err, req, res, next) => {
|
|
301
|
-
const formatted = formatErrorForUser(err);
|
|
302
|
-
|
|
303
|
-
// Log internally
|
|
304
|
-
internalLogger.error(formatted.technicalDetails);
|
|
305
|
-
|
|
306
|
-
// Return user-friendly response
|
|
307
|
-
res.status(500).json({
|
|
308
|
-
success: false,
|
|
309
|
-
error: formatted.message,
|
|
310
|
-
code: formatted.code,
|
|
311
|
-
suggestions: formatted.suggestions
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### 3. Promise Error Wrapper
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
import { handlePromise } from './utils/error-handler';
|
|
320
|
-
|
|
321
|
-
async function fetchUserData(userId) {
|
|
322
|
-
const [user, error] = await handlePromise(
|
|
323
|
-
fetch(`/api/users/${userId}`).then(r => r.json())
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
if (error) {
|
|
327
|
-
return {
|
|
328
|
-
success: false,
|
|
329
|
-
error: formatErrorForUser(error).message
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return { success: true, data: user };
|
|
334
|
-
}
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### 4. Retry with Exponential Backoff
|
|
338
|
-
|
|
339
|
-
```typescript
|
|
340
|
-
import { retryWithBackoff, formatErrorForUser } from './utils/error-handler';
|
|
341
|
-
|
|
342
|
-
// Resilient database connection
|
|
343
|
-
async function connectWithRetry() {
|
|
344
|
-
try {
|
|
345
|
-
return await retryWithBackoff(
|
|
346
|
-
async () => {
|
|
347
|
-
const conn = await db.connect();
|
|
348
|
-
console.log('Connected successfully');
|
|
349
|
-
return conn;
|
|
350
|
-
},
|
|
351
|
-
5, // Max 5 attempts
|
|
352
|
-
1000 // Start with 1s delay, doubles each attempt
|
|
353
|
-
);
|
|
354
|
-
} catch (error) {
|
|
355
|
-
const formatted = formatErrorForUser(error, { code: 'NETWORK' });
|
|
356
|
-
console.error(formatted.message);
|
|
357
|
-
// Fallback strategy
|
|
358
|
-
return await createOfflineCache();
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### 5. Error Categorization
|
|
364
|
-
|
|
365
|
-
```typescript
|
|
366
|
-
import { categorizeError, getRecoverySuggestions } from './utils/error-handler';
|
|
367
|
-
|
|
368
|
-
function handleError(error) {
|
|
369
|
-
const errorType = categorizeError(error);
|
|
370
|
-
const suggestions = getRecoverySuggestions(error);
|
|
371
|
-
|
|
372
|
-
switch (errorType) {
|
|
373
|
-
case 'TypeError':
|
|
374
|
-
showTypeErrorUI(suggestions);
|
|
375
|
-
break;
|
|
376
|
-
case 'ReferenceError':
|
|
377
|
-
showReferenceErrorUI(suggestions);
|
|
378
|
-
break;
|
|
379
|
-
case 'SyntaxError':
|
|
380
|
-
showSyntaxErrorUI(suggestions);
|
|
381
|
-
break;
|
|
382
|
-
default:
|
|
383
|
-
showGenericErrorUI(suggestions);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### 6. Sanitized Error Messages
|
|
389
|
-
|
|
390
|
-
```typescript
|
|
391
|
-
import { sanitizeErrorMessage, extractErrorMessage } from './utils/error-handler';
|
|
392
|
-
|
|
393
|
-
// Before sending to client
|
|
394
|
-
const rawError = error.message;
|
|
395
|
-
// Raw: "Error reading /home/user/secret.key: EACCES"
|
|
396
|
-
|
|
397
|
-
const safe = sanitizeErrorMessage(rawError);
|
|
398
|
-
// Safe: "Error reading file: Permission denied"
|
|
399
|
-
|
|
400
|
-
// Send to client
|
|
401
|
-
res.json({ error: safe });
|
|
402
|
-
|
|
403
|
-
// But log the technical details internally
|
|
404
|
-
internalLog.error(rawError);
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
---
|
|
408
|
-
|
|
409
|
-
## Logging Usage Examples
|
|
410
|
-
|
|
411
|
-
### 1. Basic Module Logging
|
|
412
|
-
|
|
413
|
-
```typescript
|
|
414
|
-
import { createLogger, setLogLevel } from './utils/logger';
|
|
415
|
-
|
|
416
|
-
// Create logger for module
|
|
417
|
-
const log = createLogger('UserService');
|
|
418
|
-
|
|
419
|
-
// Set global log level
|
|
420
|
-
setLogLevel('debug');
|
|
421
|
-
|
|
422
|
-
// Use in functions
|
|
423
|
-
export async function getUser(id: string) {
|
|
424
|
-
log.debug('Fetching user', { userId: id });
|
|
425
|
-
|
|
426
|
-
try {
|
|
427
|
-
const user = await db.users.findById(id);
|
|
428
|
-
log.info('User fetched successfully', { userId: id, name: user.name });
|
|
429
|
-
return user;
|
|
430
|
-
} catch (error) {
|
|
431
|
-
log.error('Failed to fetch user', { userId: id, error: String(error) });
|
|
432
|
-
throw error;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### 2. Performance Monitoring
|
|
438
|
-
|
|
439
|
-
```typescript
|
|
440
|
-
const log = createLogger('Database');
|
|
441
|
-
|
|
442
|
-
async function complexQuery() {
|
|
443
|
-
const timer = log.time('complex-query');
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
const result = await db.query(`
|
|
447
|
-
SELECT u.*, c.count FROM users u
|
|
448
|
-
LEFT JOIN (SELECT userId, count(*) as count FROM orders GROUP BY userId) c
|
|
449
|
-
ON u.id = c.userId
|
|
450
|
-
WHERE u.status = 'active'
|
|
451
|
-
`);
|
|
452
|
-
|
|
453
|
-
timer(); // Automatically logs: "complex-query: 234.50ms"
|
|
454
|
-
return result;
|
|
455
|
-
} catch (error) {
|
|
456
|
-
timer(); // Still logs timing even on error
|
|
457
|
-
log.error('Query failed', { error: String(error) });
|
|
458
|
-
throw error;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
### 3. Scoped Logging with Context
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
import { createScopedLogger } from './utils/logger';
|
|
467
|
-
|
|
468
|
-
// In request handler
|
|
469
|
-
app.post('/api/users', async (req, res) => {
|
|
470
|
-
const requestId = generateRequestId();
|
|
471
|
-
const requestLog = createScopedLogger(log, `req-${requestId}`, {
|
|
472
|
-
method: 'POST',
|
|
473
|
-
path: '/api/users',
|
|
474
|
-
ip: req.ip
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
requestLog.info('Processing request');
|
|
478
|
-
|
|
479
|
-
try {
|
|
480
|
-
const user = await createUser(req.body);
|
|
481
|
-
requestLog.info('User created', { userId: user.id });
|
|
482
|
-
res.json(user);
|
|
483
|
-
} catch (error) {
|
|
484
|
-
requestLog.error('User creation failed', { error: String(error) });
|
|
485
|
-
res.status(400).json({ error: 'Failed to create user' });
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
### 4. Batch Logging
|
|
491
|
-
|
|
492
|
-
```typescript
|
|
493
|
-
import { logBatch } from './utils/logger';
|
|
494
|
-
|
|
495
|
-
async function processBatch(items) {
|
|
496
|
-
const log = createLogger('BatchProcessor');
|
|
497
|
-
const results = [];
|
|
498
|
-
|
|
499
|
-
for (const item of items) {
|
|
500
|
-
try {
|
|
501
|
-
const result = await processItem(item);
|
|
502
|
-
results.push(`Processed: ${item.id}`);
|
|
503
|
-
} catch (error) {
|
|
504
|
-
results.push(`Failed: ${item.id}`);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Log all results as group
|
|
509
|
-
logBatch('Batch Processing Complete', results, log);
|
|
510
|
-
}
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
### 5. Performance Monitor
|
|
514
|
-
|
|
515
|
-
```typescript
|
|
516
|
-
import { createPerformanceMonitor } from './utils/logger';
|
|
517
|
-
|
|
518
|
-
const log = createLogger('API');
|
|
519
|
-
const monitor = createPerformanceMonitor(log, 200); // 200ms threshold
|
|
520
|
-
|
|
521
|
-
// Any operation over 200ms triggers warning instead of debug
|
|
522
|
-
await monitor('user-lookup', async () => {
|
|
523
|
-
return await db.users.find({ active: true });
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
// Result: "user-lookup completed { duration: 245.30ms }"
|
|
527
|
-
// Log level: WARN (because 245ms > 200ms threshold)
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
---
|
|
531
|
-
|
|
532
|
-
## File Operations Workflows
|
|
533
|
-
|
|
534
|
-
### 1. Safe File Reading with Validation
|
|
535
|
-
|
|
536
|
-
```typescript
|
|
537
|
-
import {
|
|
538
|
-
readFile,
|
|
539
|
-
exists,
|
|
540
|
-
getSize,
|
|
541
|
-
formatErrorForUser
|
|
542
|
-
} from './utils/file-operations';
|
|
543
|
-
import { parseJSON } from './utils/json-formatter';
|
|
544
|
-
|
|
545
|
-
async function loadConfig(path: string) {
|
|
546
|
-
try {
|
|
547
|
-
// Check existence first
|
|
548
|
-
if (!await exists(path)) {
|
|
549
|
-
console.log('Config not found, creating default...');
|
|
550
|
-
return getDefaultConfig();
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Check file size to prevent memory issues
|
|
554
|
-
const size = await getSize(path);
|
|
555
|
-
if (size > 1000000) { // 1MB limit
|
|
556
|
-
throw new Error('Config file too large');
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Read and parse
|
|
560
|
-
const content = await readFile(path);
|
|
561
|
-
const [config, error] = parseJSON(content);
|
|
562
|
-
|
|
563
|
-
if (error) {
|
|
564
|
-
throw new Error(`Invalid JSON: ${error.message}`);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
return config;
|
|
568
|
-
} catch (error) {
|
|
569
|
-
const formatted = formatErrorForUser(error);
|
|
570
|
-
console.error(formatted.message);
|
|
571
|
-
return getDefaultConfig();
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
### 2. Batch File Processing
|
|
577
|
-
|
|
578
|
-
```typescript
|
|
579
|
-
import {
|
|
580
|
-
batchProcessFiles,
|
|
581
|
-
readFile,
|
|
582
|
-
writeFile,
|
|
583
|
-
createLogger
|
|
584
|
-
} from './utils/file-operations';
|
|
585
|
-
|
|
586
|
-
const log = createLogger('FileProcessor');
|
|
587
|
-
|
|
588
|
-
async function convertFilesToUTF8() {
|
|
589
|
-
let converted = 0;
|
|
590
|
-
|
|
591
|
-
const count = await batchProcessFiles(
|
|
592
|
-
'./data',
|
|
593
|
-
async (filePath) => {
|
|
594
|
-
const content = await readFile(filePath, { encoding: 'latin1' });
|
|
595
|
-
const utf8Content = Buffer.from(content, 'latin1').toString('utf-8');
|
|
596
|
-
await writeFile(filePath, utf8Content);
|
|
597
|
-
converted++;
|
|
598
|
-
log.debug('Converted', { file: filePath });
|
|
599
|
-
},
|
|
600
|
-
{ extension: '.txt', recursive: true }
|
|
601
|
-
);
|
|
602
|
-
|
|
603
|
-
log.info('Conversion complete', { processed: converted, total: count });
|
|
604
|
-
}
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
### 3. Safe File Deletion with Backup
|
|
608
|
-
|
|
609
|
-
```typescript
|
|
610
|
-
import { deleteFile, exists } from './utils/file-operations';
|
|
611
|
-
|
|
612
|
-
async function safeDelete(filePath: string) {
|
|
613
|
-
try {
|
|
614
|
-
if (!await exists(filePath)) {
|
|
615
|
-
throw new Error('File not found');
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
const backupPath = await deleteFile(filePath, true);
|
|
619
|
-
console.log(`Deleted: ${filePath}`);
|
|
620
|
-
console.log(`Backup: ${backupPath}`);
|
|
621
|
-
|
|
622
|
-
return { success: true, backup: backupPath };
|
|
623
|
-
} catch (error) {
|
|
624
|
-
console.error('Failed to delete:', error.message);
|
|
625
|
-
return { success: false };
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
### 4. Directory Listing and Filtering
|
|
631
|
-
|
|
632
|
-
```typescript
|
|
633
|
-
import { listFiles, isFile, isDirectory } from './utils/file-operations';
|
|
634
|
-
|
|
635
|
-
async function analyzeProjectStructure() {
|
|
636
|
-
// Get all TypeScript files
|
|
637
|
-
const tsFiles = await listFiles('./src', {
|
|
638
|
-
extension: '.ts',
|
|
639
|
-
recursive: true,
|
|
640
|
-
filesOnly: true
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
// Get all test files
|
|
644
|
-
const testFiles = await listFiles('./src', {
|
|
645
|
-
extension: '.test.ts',
|
|
646
|
-
recursive: true
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
// Get all directories
|
|
650
|
-
const dirs = await listFiles('./src', {
|
|
651
|
-
recursive: true,
|
|
652
|
-
filesOnly: false
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
console.log(`Found ${tsFiles.length} TypeScript files`);
|
|
656
|
-
console.log(`Found ${testFiles.length} test files`);
|
|
657
|
-
console.log(`Found ${dirs.length} directories`);
|
|
658
|
-
}
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
### 5. File Metadata and Integrity
|
|
662
|
-
|
|
663
|
-
```typescript
|
|
664
|
-
import { getMetadata, copyFile } from './utils/file-operations';
|
|
665
|
-
|
|
666
|
-
async function backupWithVerification() {
|
|
667
|
-
const original = './important.json';
|
|
668
|
-
const backup = './important.json.backup';
|
|
669
|
-
|
|
670
|
-
// Get hash before copying
|
|
671
|
-
const originalMeta = await getMetadata(original, true);
|
|
672
|
-
console.log(`Original hash: ${originalMeta.hash}`);
|
|
673
|
-
|
|
674
|
-
// Copy with verification
|
|
675
|
-
await copyFile(original, backup, true);
|
|
676
|
-
|
|
677
|
-
// Verify copy
|
|
678
|
-
const backupMeta = await getMetadata(backup, true);
|
|
679
|
-
if (originalMeta.hash === backupMeta.hash) {
|
|
680
|
-
console.log('Backup verified - files are identical');
|
|
681
|
-
} else {
|
|
682
|
-
console.error('Backup verification failed - files differ!');
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
### 6. Line-by-Line Processing
|
|
688
|
-
|
|
689
|
-
```typescript
|
|
690
|
-
import { readLinesSync, appendFile } from './utils/file-operations';
|
|
691
|
-
|
|
692
|
-
async function processLogFile() {
|
|
693
|
-
let errorCount = 0;
|
|
694
|
-
let warningCount = 0;
|
|
695
|
-
|
|
696
|
-
await readLinesSync('./app.log', (line, index) => {
|
|
697
|
-
if (line.includes('[ERROR]')) {
|
|
698
|
-
errorCount++;
|
|
699
|
-
console.log(`Line ${index}: ${line}`);
|
|
700
|
-
} else if (line.includes('[WARN]')) {
|
|
701
|
-
warningCount++;
|
|
702
|
-
}
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
// Write summary
|
|
706
|
-
await appendFile('./summary.log',
|
|
707
|
-
`Errors: ${errorCount}, Warnings: ${warningCount}\n`);
|
|
708
|
-
}
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
---
|
|
712
|
-
|
|
713
|
-
## String & Date Formatting
|
|
714
|
-
|
|
715
|
-
### 1. String Case Conversions
|
|
716
|
-
|
|
717
|
-
```typescript
|
|
718
|
-
import {
|
|
719
|
-
capitalize,
|
|
720
|
-
toCamelCase,
|
|
721
|
-
toSnakeCase,
|
|
722
|
-
toKebabCase
|
|
723
|
-
} from './utils/string-formatter';
|
|
724
|
-
|
|
725
|
-
const text = 'hello world';
|
|
726
|
-
|
|
727
|
-
console.log(capitalize(text)); // 'Hello world'
|
|
728
|
-
console.log(toCamelCase('hello-world-example')); // 'helloWorldExample'
|
|
729
|
-
console.log(toSnakeCase('helloWorld')); // 'hello_world'
|
|
730
|
-
console.log(toKebabCase('HelloWorld')); // 'hello-world'
|
|
731
|
-
|
|
732
|
-
// Practical: URL slug generation
|
|
733
|
-
const title = 'Hello World Example!';
|
|
734
|
-
const slug = toKebabCase(title.toLowerCase());
|
|
735
|
-
// Result: 'hello-world-example'
|
|
736
|
-
```
|
|
737
|
-
|
|
738
|
-
### 2. Date Formatting and Manipulation
|
|
739
|
-
|
|
740
|
-
```typescript
|
|
741
|
-
import {
|
|
742
|
-
formatDate,
|
|
743
|
-
addDays,
|
|
744
|
-
getDuration,
|
|
745
|
-
getWeekNumber
|
|
746
|
-
} from './utils/date-formatter';
|
|
747
|
-
|
|
748
|
-
// Example 1: Format dates for display
|
|
749
|
-
const now = new Date();
|
|
750
|
-
console.log(formatDate(now, 'yyyy-MM-dd')); // '2024-02-15'
|
|
751
|
-
console.log(formatDate(now, 'yyyy-MM-dd HH:mm')); // '2024-02-15 14:30'
|
|
752
|
-
console.log(formatDate(now, 'MMMM d, yyyy')); // 'February 15, 2024'
|
|
753
|
-
|
|
754
|
-
// Example 2: Date arithmetic
|
|
755
|
-
const deadline = addDays(now, 7);
|
|
756
|
-
console.log(`Deadline: ${formatDate(deadline, 'yyyy-MM-dd')}`);
|
|
757
|
-
|
|
758
|
-
// Example 3: Duration calculation
|
|
759
|
-
const start = new Date('2024-02-01');
|
|
760
|
-
const end = new Date('2024-02-15');
|
|
761
|
-
const duration = getDuration(start, end);
|
|
762
|
-
console.log(`Duration: ${duration.days} days, ${duration.hours} hours`);
|
|
763
|
-
|
|
764
|
-
// Example 4: Week number for reporting
|
|
765
|
-
const weekNum = getWeekNumber(now);
|
|
766
|
-
console.log(`Current week: ${weekNum}`);
|
|
767
|
-
```
|
|
768
|
-
|
|
769
|
-
---
|
|
770
|
-
|
|
771
|
-
## JSON Processing
|
|
772
|
-
|
|
773
|
-
### 1. Safe JSON Parsing
|
|
774
|
-
|
|
775
|
-
```typescript
|
|
776
|
-
import { parseJSON, validateJSON } from './utils/json-formatter';
|
|
777
|
-
|
|
778
|
-
// Example: API response handling
|
|
779
|
-
async function handleApiResponse(response) {
|
|
780
|
-
const text = await response.text();
|
|
781
|
-
|
|
782
|
-
// Validate first
|
|
783
|
-
const isValid = validateJSON(text);
|
|
784
|
-
if (!isValid) {
|
|
785
|
-
console.error('Invalid JSON response');
|
|
786
|
-
return null;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// Safe parse
|
|
790
|
-
const [data, error] = parseJSON(text);
|
|
791
|
-
if (error) {
|
|
792
|
-
console.error('Parse error:', error.message);
|
|
793
|
-
return null;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
return data;
|
|
797
|
-
}
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
### 2. Flatten/Unflatten for APIs
|
|
801
|
-
|
|
802
|
-
```typescript
|
|
803
|
-
import { flattenJSON, unflattenJSON } from './utils/json-formatter';
|
|
804
|
-
|
|
805
|
-
// Flatten for form submission
|
|
806
|
-
const user = {
|
|
807
|
-
name: 'John',
|
|
808
|
-
address: {
|
|
809
|
-
street: '123 Main St',
|
|
810
|
-
city: 'New York',
|
|
811
|
-
zip: {
|
|
812
|
-
code: '10001'
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
const flat = flattenJSON(user);
|
|
818
|
-
// Result:
|
|
819
|
-
// {
|
|
820
|
-
// 'name': 'John',
|
|
821
|
-
// 'address.street': '123 Main St',
|
|
822
|
-
// 'address.city': 'New York',
|
|
823
|
-
// 'address.zip.code': '10001'
|
|
824
|
-
// }
|
|
825
|
-
|
|
826
|
-
// Send form data...
|
|
827
|
-
|
|
828
|
-
// Unflatten for storage
|
|
829
|
-
const restored = unflattenJSON(flat);
|
|
830
|
-
// Result: Same as original user object
|
|
831
|
-
```
|
|
832
|
-
|
|
833
|
-
### 3. JSON Merging
|
|
834
|
-
|
|
835
|
-
```typescript
|
|
836
|
-
import { mergeJSON } from './utils/json-formatter';
|
|
837
|
-
|
|
838
|
-
// Merge configuration files
|
|
839
|
-
const baseConfig = {
|
|
840
|
-
port: 3000,
|
|
841
|
-
timeout: 30000,
|
|
842
|
-
features: { auth: true, logging: true }
|
|
843
|
-
};
|
|
844
|
-
|
|
845
|
-
const envConfig = {
|
|
846
|
-
port: 8080,
|
|
847
|
-
features: { cache: true }
|
|
848
|
-
};
|
|
849
|
-
|
|
850
|
-
const merged = mergeJSON(baseConfig, envConfig);
|
|
851
|
-
// Result:
|
|
852
|
-
// {
|
|
853
|
-
// port: 8080, // Overridden
|
|
854
|
-
// timeout: 30000, // From base
|
|
855
|
-
// features: { auth: true, logging: true, cache: true } // Deep merged
|
|
856
|
-
// }
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
---
|
|
860
|
-
|
|
861
|
-
## Image Processing
|
|
862
|
-
|
|
863
|
-
### 1. Image Resizing Workflow
|
|
864
|
-
|
|
865
|
-
```typescript
|
|
866
|
-
import {
|
|
867
|
-
resizeImage,
|
|
868
|
-
getImageDimensions,
|
|
869
|
-
createLogger
|
|
870
|
-
} from './utils/image-operations';
|
|
871
|
-
|
|
872
|
-
const log = createLogger('ImageResize');
|
|
873
|
-
|
|
874
|
-
async function optimizeImage(inputPath, outputPath) {
|
|
875
|
-
try {
|
|
876
|
-
// Get original dimensions
|
|
877
|
-
const dims = await getImageDimensions(inputPath);
|
|
878
|
-
log.info('Original dimensions', dims);
|
|
879
|
-
|
|
880
|
-
// Resize to fit in 1920x1080
|
|
881
|
-
const resized = await resizeImage(inputPath, {
|
|
882
|
-
width: 1920,
|
|
883
|
-
height: 1080,
|
|
884
|
-
fit: 'inside'
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
log.info('Image resized', { path: outputPath });
|
|
888
|
-
return resized;
|
|
889
|
-
} catch (error) {
|
|
890
|
-
log.error('Resize failed', { error: String(error) });
|
|
891
|
-
throw error;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
```
|
|
895
|
-
|
|
896
|
-
### 2. Image Format Conversion
|
|
897
|
-
|
|
898
|
-
```typescript
|
|
899
|
-
import {
|
|
900
|
-
convertFormat,
|
|
901
|
-
detectFormat
|
|
902
|
-
} from './utils/image-operations';
|
|
903
|
-
|
|
904
|
-
async function convertToWebP(inputPath, outputPath) {
|
|
905
|
-
// Detect original format
|
|
906
|
-
const format = detectFormat(inputPath);
|
|
907
|
-
console.log(`Original format: ${format}`);
|
|
908
|
-
|
|
909
|
-
// Convert to WebP with compression
|
|
910
|
-
const result = await convertFormat(inputPath, 'webp', {
|
|
911
|
-
quality: 0.8
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
// Save result
|
|
915
|
-
const fs = require('fs').promises;
|
|
916
|
-
await fs.writeFile(outputPath, result);
|
|
917
|
-
console.log(`Converted to WebP: ${outputPath}`);
|
|
918
|
-
}
|
|
919
|
-
```
|
|
920
|
-
|
|
921
|
-
### 3. Batch Image Processing
|
|
922
|
-
|
|
923
|
-
```typescript
|
|
924
|
-
import {
|
|
925
|
-
resizeImage,
|
|
926
|
-
batchProcessFiles
|
|
927
|
-
} from './utils/file-operations';
|
|
928
|
-
|
|
929
|
-
async function createThumbnails() {
|
|
930
|
-
let created = 0;
|
|
931
|
-
|
|
932
|
-
const count = await batchProcessFiles(
|
|
933
|
-
'./images',
|
|
934
|
-
async (imagePath) => {
|
|
935
|
-
const outputPath = imagePath.replace('.', '_thumb.');
|
|
936
|
-
await resizeImage(imagePath, outputPath, {
|
|
937
|
-
width: 200,
|
|
938
|
-
height: 200,
|
|
939
|
-
fit: 'cover'
|
|
940
|
-
});
|
|
941
|
-
created++;
|
|
942
|
-
},
|
|
943
|
-
{ extension: '.jpg', recursive: true }
|
|
944
|
-
);
|
|
945
|
-
|
|
946
|
-
console.log(`Created ${created} thumbnails out of ${count} images`);
|
|
947
|
-
}
|
|
948
|
-
```
|
|
949
|
-
|
|
950
|
-
---
|
|
951
|
-
|
|
952
|
-
## Git Operations
|
|
953
|
-
|
|
954
|
-
### 1. Repository Status Check
|
|
955
|
-
|
|
956
|
-
```typescript
|
|
957
|
-
import {
|
|
958
|
-
checkIsRepository,
|
|
959
|
-
getGitStatus,
|
|
960
|
-
getCurrentBranch,
|
|
961
|
-
createLogger
|
|
962
|
-
} from './utils/git';
|
|
963
|
-
|
|
964
|
-
const log = createLogger('Git');
|
|
965
|
-
|
|
966
|
-
async function checkRepoStatus() {
|
|
967
|
-
try {
|
|
968
|
-
if (!await checkIsRepository()) {
|
|
969
|
-
log.error('Not a git repository');
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
const branch = await getCurrentBranch();
|
|
974
|
-
const status = await getGitStatus();
|
|
975
|
-
|
|
976
|
-
log.info('Repository status', {
|
|
977
|
-
branch,
|
|
978
|
-
modified: status.modified.length,
|
|
979
|
-
untracked: status.untracked.length,
|
|
980
|
-
staged: status.staged.length
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
if (status.modified.length > 0) {
|
|
984
|
-
log.warn('Uncommitted changes:', status.modified);
|
|
985
|
-
}
|
|
986
|
-
} catch (error) {
|
|
987
|
-
log.error('Failed to check status', { error: String(error) });
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
```
|
|
991
|
-
|
|
992
|
-
### 2. Commit History Analysis
|
|
993
|
-
|
|
994
|
-
```typescript
|
|
995
|
-
import { getCommitHistory, formatDate } from './utils/git';
|
|
996
|
-
|
|
997
|
-
async function analyzeCommits() {
|
|
998
|
-
const commits = await getCommitHistory(50);
|
|
999
|
-
|
|
1000
|
-
// Group by author
|
|
1001
|
-
const byAuthor = {};
|
|
1002
|
-
for (const commit of commits) {
|
|
1003
|
-
if (!byAuthor[commit.author]) {
|
|
1004
|
-
byAuthor[commit.author] = [];
|
|
1005
|
-
}
|
|
1006
|
-
byAuthor[commit.author].push(commit);
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
// Show statistics
|
|
1010
|
-
for (const [author, commits] of Object.entries(byAuthor)) {
|
|
1011
|
-
console.log(`${author}: ${commits.length} commits`);
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
```
|
|
1015
|
-
|
|
1016
|
-
---
|
|
1017
|
-
|
|
1018
|
-
## Data Transformation Pipelines
|
|
1019
|
-
|
|
1020
|
-
### 1. Multi-Step Transformation
|
|
1021
|
-
|
|
1022
|
-
```typescript
|
|
1023
|
-
import { transform } from './utils/data-transformer';
|
|
1024
|
-
|
|
1025
|
-
async function processUserData(users) {
|
|
1026
|
-
const result = await transform(users)
|
|
1027
|
-
.pipe(filter(u => u.status === 'active'))
|
|
1028
|
-
.pipe(map(u => ({
|
|
1029
|
-
...u,
|
|
1030
|
-
email: u.email.toLowerCase(),
|
|
1031
|
-
joinedYear: new Date(u.joinDate).getFullYear()
|
|
1032
|
-
})))
|
|
1033
|
-
.pipe(sortBy((a, b) => b.joinedYear - a.joinedYear))
|
|
1034
|
-
.pipe(chunk(10))
|
|
1035
|
-
.execute();
|
|
1036
|
-
|
|
1037
|
-
return result; // Array of arrays, grouped by year
|
|
1038
|
-
}
|
|
1039
|
-
```
|
|
1040
|
-
|
|
1041
|
-
### 2. Parallel Processing
|
|
1042
|
-
|
|
1043
|
-
```typescript
|
|
1044
|
-
async function processLargeDataset(items) {
|
|
1045
|
-
const result = await transform(items)
|
|
1046
|
-
.pipe(parallel(async (item) => {
|
|
1047
|
-
return await expensiveOperation(item);
|
|
1048
|
-
}, 4)) // Process 4 items in parallel
|
|
1049
|
-
.execute();
|
|
1050
|
-
|
|
1051
|
-
return result;
|
|
1052
|
-
}
|
|
1053
|
-
```
|
|
1054
|
-
|
|
1055
|
-
---
|
|
1056
|
-
|
|
1057
|
-
## Integration Patterns
|
|
1058
|
-
|
|
1059
|
-
### 1. Complete API Handler
|
|
1060
|
-
|
|
1061
|
-
```typescript
|
|
1062
|
-
import {
|
|
1063
|
-
createLogger,
|
|
1064
|
-
formatErrorForUser,
|
|
1065
|
-
retryWithBackoff,
|
|
1066
|
-
handlePromise
|
|
1067
|
-
} from './utils';
|
|
1068
|
-
|
|
1069
|
-
const log = createLogger('API');
|
|
1070
|
-
|
|
1071
|
-
app.get('/api/data/:id', async (req, res) => {
|
|
1072
|
-
const requestLog = createScopedLogger(log, `req-${req.id}`, {
|
|
1073
|
-
path: req.path,
|
|
1074
|
-
method: req.method
|
|
1075
|
-
});
|
|
1076
|
-
|
|
1077
|
-
requestLog.info('Request started');
|
|
1078
|
-
const timer = requestLog.time('total');
|
|
1079
|
-
|
|
1080
|
-
try {
|
|
1081
|
-
// Fetch with retries
|
|
1082
|
-
const [data, error] = await handlePromise(
|
|
1083
|
-
retryWithBackoff(
|
|
1084
|
-
() => fetchFromDatabase(req.params.id),
|
|
1085
|
-
3,
|
|
1086
|
-
1000
|
|
1087
|
-
)
|
|
1088
|
-
);
|
|
1089
|
-
|
|
1090
|
-
if (error) {
|
|
1091
|
-
const formatted = formatErrorForUser(error);
|
|
1092
|
-
requestLog.error('Database error', { error: formatted.code });
|
|
1093
|
-
return res.status(500).json({
|
|
1094
|
-
error: formatted.message,
|
|
1095
|
-
code: formatted.code
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
requestLog.info('Data retrieved', { size: JSON.stringify(data).length });
|
|
1100
|
-
timer();
|
|
1101
|
-
res.json(data);
|
|
1102
|
-
} catch (error) {
|
|
1103
|
-
const formatted = formatErrorForUser(error);
|
|
1104
|
-
requestLog.error('Unexpected error', { error: formatted.message });
|
|
1105
|
-
timer();
|
|
1106
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
1107
|
-
}
|
|
1108
|
-
});
|
|
1109
|
-
```
|
|
1110
|
-
|
|
1111
|
-
### 2. CLI Command Implementation
|
|
1112
|
-
|
|
1113
|
-
```typescript
|
|
1114
|
-
import {
|
|
1115
|
-
createLogger,
|
|
1116
|
-
readFile,
|
|
1117
|
-
writeFile,
|
|
1118
|
-
formatErrorForUser
|
|
1119
|
-
} from './utils';
|
|
1120
|
-
|
|
1121
|
-
async function processCommand(input, output) {
|
|
1122
|
-
const log = createLogger('CLI');
|
|
1123
|
-
|
|
1124
|
-
try {
|
|
1125
|
-
log.info('Starting process', { input, output });
|
|
1126
|
-
|
|
1127
|
-
const timer = log.time('process');
|
|
1128
|
-
|
|
1129
|
-
// Read input
|
|
1130
|
-
const content = await readFile(input);
|
|
1131
|
-
log.debug('File read', { size: content.length });
|
|
1132
|
-
|
|
1133
|
-
// Process
|
|
1134
|
-
const processed = await processContent(content);
|
|
1135
|
-
log.debug('Content processed', { resultSize: processed.length });
|
|
1136
|
-
|
|
1137
|
-
// Write output
|
|
1138
|
-
await writeFile(output, processed, { createDirs: true });
|
|
1139
|
-
log.info('Process complete', { output });
|
|
1140
|
-
|
|
1141
|
-
timer();
|
|
1142
|
-
} catch (error) {
|
|
1143
|
-
const formatted = formatErrorForUser(error);
|
|
1144
|
-
log.error(formatted.message);
|
|
1145
|
-
console.error(`Error: ${formatted.message}`);
|
|
1146
|
-
process.exit(1);
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
```
|
|
1150
|
-
|
|
1151
|
-
---
|
|
1152
|
-
|
|
1153
|
-
## Performance Optimization
|
|
1154
|
-
|
|
1155
|
-
### 1. Batch Operations
|
|
1156
|
-
|
|
1157
|
-
```typescript
|
|
1158
|
-
// BAD: Individual operations
|
|
1159
|
-
for (const file of files) {
|
|
1160
|
-
const content = await readFile(file);
|
|
1161
|
-
const processed = processContent(content);
|
|
1162
|
-
await writeFile(file, processed);
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
// GOOD: Batch processing
|
|
1166
|
-
const count = await batchProcessFiles(
|
|
1167
|
-
dir,
|
|
1168
|
-
async (file) => {
|
|
1169
|
-
const content = await readFile(file);
|
|
1170
|
-
const processed = processContent(content);
|
|
1171
|
-
await writeFile(file, processed);
|
|
1172
|
-
}
|
|
1173
|
-
);
|
|
1174
|
-
```
|
|
1175
|
-
|
|
1176
|
-
### 2. Efficient Array Handling
|
|
1177
|
-
|
|
1178
|
-
```typescript
|
|
1179
|
-
// BAD: Multiple iterations
|
|
1180
|
-
const active = data.filter(x => x.isActive);
|
|
1181
|
-
const mapped = active.map(x => transform(x));
|
|
1182
|
-
const sorted = mapped.sort((a, b) => a.id - b.id);
|
|
1183
|
-
|
|
1184
|
-
// GOOD: Single pipeline
|
|
1185
|
-
const result = await transform(data)
|
|
1186
|
-
.pipe(filter(x => x.isActive))
|
|
1187
|
-
.pipe(map(x => transform(x)))
|
|
1188
|
-
.pipe(sortBy((a, b) => a.id - b.id))
|
|
1189
|
-
.execute();
|
|
1190
|
-
```
|
|
1191
|
-
|
|
1192
|
-
---
|
|
1193
|
-
|
|
1194
|
-
## Dependency Management
|
|
1195
|
-
|
|
1196
|
-
### 1. Import Organization
|
|
1197
|
-
|
|
1198
|
-
```typescript
|
|
1199
|
-
// Organize imports by category
|
|
1200
|
-
|
|
1201
|
-
// Node.js modules
|
|
1202
|
-
import * as path from 'path';
|
|
1203
|
-
import { promises as fs } from 'fs';
|
|
1204
|
-
|
|
1205
|
-
// Utilities
|
|
1206
|
-
import {
|
|
1207
|
-
readFile,
|
|
1208
|
-
writeFile,
|
|
1209
|
-
listFiles,
|
|
1210
|
-
formatErrorForUser
|
|
1211
|
-
} from './utils/file-operations';
|
|
1212
|
-
|
|
1213
|
-
import {
|
|
1214
|
-
createLogger,
|
|
1215
|
-
setLogLevel
|
|
1216
|
-
} from './utils/logger';
|
|
1217
|
-
|
|
1218
|
-
import {
|
|
1219
|
-
groupBy,
|
|
1220
|
-
unique,
|
|
1221
|
-
chunk
|
|
1222
|
-
} from './utils/array';
|
|
1223
|
-
|
|
1224
|
-
// Application code
|
|
1225
|
-
import { processData } from './processor';
|
|
1226
|
-
```
|
|
1227
|
-
|
|
1228
|
-
### 2. Avoiding Circular Dependencies
|
|
1229
|
-
|
|
1230
|
-
```typescript
|
|
1231
|
-
// BAD: Direct circular dependency
|
|
1232
|
-
// file-a.ts
|
|
1233
|
-
import { funcB } from './file-b';
|
|
1234
|
-
export const funcA = () => funcB();
|
|
1235
|
-
|
|
1236
|
-
// file-b.ts
|
|
1237
|
-
import { funcA } from './file-a';
|
|
1238
|
-
export const funcB = () => funcA();
|
|
1239
|
-
|
|
1240
|
-
// GOOD: Use shared interface
|
|
1241
|
-
// types.ts
|
|
1242
|
-
export interface Processor {
|
|
1243
|
-
process(data: any): any;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
// file-a.ts
|
|
1247
|
-
import type { Processor } from './types';
|
|
1248
|
-
export class ProcessorA implements Processor {
|
|
1249
|
-
process(data) { /* ... */ }
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// file-b.ts
|
|
1253
|
-
import type { Processor } from './types';
|
|
1254
|
-
export class ProcessorB implements Processor {
|
|
1255
|
-
process(data) { /* ... */ }
|
|
1256
|
-
}
|
|
1257
|
-
```
|
|
1258
|
-
|
|
1259
|
-
---
|
|
1260
|
-
|
|
1261
|
-
## Debugging Techniques
|
|
1262
|
-
|
|
1263
|
-
### 1. Structured Logging for Debugging
|
|
1264
|
-
|
|
1265
|
-
```typescript
|
|
1266
|
-
const log = createLogger('MyModule');
|
|
1267
|
-
setLogLevel('debug'); // Enable debug messages
|
|
1268
|
-
|
|
1269
|
-
// Example: Track variable changes
|
|
1270
|
-
const value = initialValue;
|
|
1271
|
-
log.debug('Initial value', { value });
|
|
1272
|
-
|
|
1273
|
-
// Process
|
|
1274
|
-
const processed = await process(value);
|
|
1275
|
-
log.debug('After processing', { value: processed });
|
|
1276
|
-
|
|
1277
|
-
// Conditional debugging
|
|
1278
|
-
if (getLogLevel() <= LogLevel.DEBUG) {
|
|
1279
|
-
log.debug('Expensive operation', {
|
|
1280
|
-
...hugeObject,
|
|
1281
|
-
timestamp: Date.now()
|
|
1282
|
-
});
|
|
1283
|
-
}
|
|
1284
|
-
```
|
|
1285
|
-
|
|
1286
|
-
### 2. Error Context for Debugging
|
|
1287
|
-
|
|
1288
|
-
```typescript
|
|
1289
|
-
try {
|
|
1290
|
-
const result = await operation();
|
|
1291
|
-
} catch (error) {
|
|
1292
|
-
const formatted = formatErrorForUser(error);
|
|
1293
|
-
|
|
1294
|
-
// Log full context
|
|
1295
|
-
internalLog.error('Operation failed', {
|
|
1296
|
-
operation: 'fetchUserData',
|
|
1297
|
-
input: userId,
|
|
1298
|
-
stack: formatted.technicalDetails,
|
|
1299
|
-
timestamp: new Date().toISOString()
|
|
1300
|
-
});
|
|
1301
|
-
|
|
1302
|
-
// Show user-friendly message
|
|
1303
|
-
showError(formatted.message);
|
|
1304
|
-
}
|
|
1305
|
-
```
|
|
1306
|
-
|
|
1307
|
-
---
|
|
1308
|
-
|
|
1309
|
-
## Common Gotchas
|
|
1310
|
-
|
|
1311
|
-
### 1. Array Modification
|
|
1312
|
-
|
|
1313
|
-
```typescript
|
|
1314
|
-
// Gotcha: Modifying original array
|
|
1315
|
-
const original = [1, 2, 3];
|
|
1316
|
-
const sorted = sortBy(original, (a, b) => b - a);
|
|
1317
|
-
|
|
1318
|
-
// safe - sortBy returns new array
|
|
1319
|
-
console.log(original); // [1, 2, 3] - unchanged
|
|
1320
|
-
console.log(sorted); // [3, 2, 1] - new array
|
|
1321
|
-
|
|
1322
|
-
// But this modifies original:
|
|
1323
|
-
const mapped = original.map(x => x * 2);
|
|
1324
|
-
original[0] = 999; // Changes affect mapped? No, primitives are safe.
|
|
1325
|
-
```
|
|
1326
|
-
|
|
1327
|
-
### 2. Async Gotchas
|
|
1328
|
-
|
|
1329
|
-
```typescript
|
|
1330
|
-
// Gotcha: Forgetting await
|
|
1331
|
-
const file = readFile('file.txt'); // Returns Promise!
|
|
1332
|
-
const content = file; // This is a Promise, not string
|
|
1333
|
-
|
|
1334
|
-
// Correct:
|
|
1335
|
-
const content = await readFile('file.txt');
|
|
1336
|
-
|
|
1337
|
-
// Gotcha: Promise.all vs sequential
|
|
1338
|
-
// Sequential (slow)
|
|
1339
|
-
const file1 = await readFile('f1.txt');
|
|
1340
|
-
const file2 = await readFile('f2.txt');
|
|
1341
|
-
|
|
1342
|
-
// Parallel (fast)
|
|
1343
|
-
const [file1, file2] = await Promise.all([
|
|
1344
|
-
readFile('f1.txt'),
|
|
1345
|
-
readFile('f2.txt')
|
|
1346
|
-
]);
|
|
1347
|
-
```
|
|
1348
|
-
|
|
1349
|
-
### 3. Logging Level Gotcha
|
|
1350
|
-
|
|
1351
|
-
```typescript
|
|
1352
|
-
// Gotcha: Debug log always evaluates expression
|
|
1353
|
-
log.debug('User: ' + JSON.stringify(hugeObject)); // String created even if debug disabled!
|
|
1354
|
-
|
|
1355
|
-
// Better:
|
|
1356
|
-
if (getLogLevel() <= LogLevel.DEBUG) {
|
|
1357
|
-
log.debug('User', hugeObject); // Only if debug is enabled
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
// Or use lazy evaluation (if supported)
|
|
1361
|
-
log.debug(() => `User: ${JSON.stringify(hugeObject)}`);
|
|
1362
|
-
```
|
|
1363
|
-
|
|
1364
|
-
### 4. File Path Gotcha
|
|
1365
|
-
|
|
1366
|
-
```typescript
|
|
1367
|
-
// Gotcha: Relative paths from wrong directory
|
|
1368
|
-
const content = await readFile('./data.json');
|
|
1369
|
-
// Works when run from project root
|
|
1370
|
-
// Fails when run from subdirectory!
|
|
1371
|
-
|
|
1372
|
-
// Better:
|
|
1373
|
-
const content = await readFile(
|
|
1374
|
-
path.resolve(__dirname, '../../data.json')
|
|
1375
|
-
);
|
|
1376
|
-
// Or
|
|
1377
|
-
const content = await readFile(
|
|
1378
|
-
validatePath('./data.json')
|
|
1379
|
-
);
|
|
1380
|
-
```
|
|
1381
|
-
|
|
1382
|
-
### 5. Error Message Gotcha
|
|
1383
|
-
|
|
1384
|
-
```typescript
|
|
1385
|
-
// Gotcha: Showing sensitive information
|
|
1386
|
-
catch (error) {
|
|
1387
|
-
console.error(error.message);
|
|
1388
|
-
// Client sees: "/home/user/app/config/db.ts line 42"
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// Correct:
|
|
1392
|
-
catch (error) {
|
|
1393
|
-
const safe = sanitizeErrorMessage(error.message);
|
|
1394
|
-
console.error(safe);
|
|
1395
|
-
// Client sees: "Database connection failed"
|
|
1396
|
-
}
|
|
1397
|
-
```
|
|
1398
|
-
|
|
1399
|
-
---
|
|
1400
|
-
|
|
1401
|
-
## Best Practices Summary
|
|
1402
|
-
|
|
1403
|
-
1. **Always handle errors**: Use formatErrorForUser for user-facing errors
|
|
1404
|
-
2. **Use appropriate log levels**: Don't log everything at INFO
|
|
1405
|
-
3. **Batch file operations**: Process multiple files together
|
|
1406
|
-
4. **Validate inputs**: Use type guards and path validators
|
|
1407
|
-
5. **Create new arrays**: Functions like sortBy return new arrays
|
|
1408
|
-
6. **Use pipelines**: Chain transformations with transform()
|
|
1409
|
-
7. **Scope loggers**: Create scoped loggers with context for requests
|
|
1410
|
-
8. **Check before deleting**: Verify file exists before operations
|
|
1411
|
-
9. **Sanitize errors**: Always sanitize before showing to users
|
|
1412
|
-
10. **Test edge cases**: Handle empty arrays, null values, etc.
|
|
1413
|
-
|
|
1414
|
-
---
|
|
1415
|
-
|
|
1416
|
-
**Last Updated**: February 2024
|
|
1417
|
-
**Maintained By**: Indusagi Core Team
|
|
1418
|
-
**License**: MIT
|
|
1419
|
-
|