lumisjs 1.0.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -0
  3. package/SECURITY.md +160 -0
  4. package/index.js +90 -0
  5. package/package.json +68 -0
  6. package/src/cache/CacheManager.js +393 -0
  7. package/src/cache/MemoryAdapter.js +259 -0
  8. package/src/cache/MultiLevelCache.js +362 -0
  9. package/src/cache/RedisAdapter.js +329 -0
  10. package/src/cache/SQLiteAdapter.js +280 -0
  11. package/src/cache/index.js +15 -0
  12. package/src/client/Client.js +554 -0
  13. package/src/client/ShardingManager.js +274 -0
  14. package/src/config/ConfigValidator.js +172 -0
  15. package/src/config/index.js +7 -0
  16. package/src/dashboard/DashboardManager.js +362 -0
  17. package/src/datagen/DataGenerator.js +47 -0
  18. package/src/datagen/apis/GraphqlMocker.js +16 -0
  19. package/src/datagen/apis/OpenApiMocker.js +18 -0
  20. package/src/datagen/cli.js +130 -0
  21. package/src/datagen/formatters/csv.js +45 -0
  22. package/src/datagen/formatters/index.js +15 -0
  23. package/src/datagen/formatters/json.js +33 -0
  24. package/src/datagen/formatters/mongo.js +22 -0
  25. package/src/datagen/formatters/sql.js +32 -0
  26. package/src/datagen/index.js +17 -0
  27. package/src/datagen/plugin/PluginManager.js +43 -0
  28. package/src/datagen/plugins/example-plugin.js +21 -0
  29. package/src/datagen/schema/SchemaGenerator.js +172 -0
  30. package/src/datagen/schema/loadSchema.js +14 -0
  31. package/src/datagen/utils/seed.js +26 -0
  32. package/src/di/ServiceContainer.js +229 -0
  33. package/src/errors/ErrorCodes.js +110 -0
  34. package/src/errors/LumisError.js +85 -0
  35. package/src/game/EconomyManager.js +161 -0
  36. package/src/game/GameSessionManager.js +76 -0
  37. package/src/game/GuildManager.js +197 -0
  38. package/src/game/InventoryManager.js +140 -0
  39. package/src/game/LevelingSystem.js +194 -0
  40. package/src/game/MusicManager.js +587 -0
  41. package/src/health/HealthChecker.js +170 -0
  42. package/src/health/index.js +7 -0
  43. package/src/performance/PerformanceMonitor.js +244 -0
  44. package/src/performance/index.js +7 -0
  45. package/src/security/InputSanitizer.js +185 -0
  46. package/src/security/SecurityMiddleware.js +231 -0
  47. package/src/security/index.js +9 -0
  48. package/src/shutdown/GracefulShutdown.js +159 -0
  49. package/src/shutdown/index.js +7 -0
  50. package/src/utils/StructuredLogger.js +118 -0
@@ -0,0 +1,362 @@
1
+ 'use strict';
2
+
3
+ const http = require('http');
4
+ const { URL } = require('url');
5
+ const helmet = require('helmet');
6
+
7
+ /**
8
+ * Built-in Dashboard & Analytics API.
9
+ * Provides an HTTP REST API + real-time metrics for monitoring your bot.
10
+ *
11
+ * @example
12
+ * const client = new Client({
13
+ * dashboard: { enabled: true, port: 3000, auth: 'my-secret-key' }
14
+ * });
15
+ * // GET /api/stats
16
+ * // GET /api/guilds
17
+ * // GET /api/commands
18
+ * // GET /api/health
19
+ * // GET /api/cache
20
+ * // GET /api/plugins
21
+ * // GET /api/services
22
+ * // GET /api/middleware
23
+ */
24
+ class DashboardManager {
25
+ constructor(client, options = {}) {
26
+ this.client = client;
27
+ this.options = {
28
+ enabled: options.enabled || false,
29
+ port: options.port || 3000,
30
+ host: options.host || '0.0.0.0',
31
+ auth: options.auth || null,
32
+ cors: options.cors !== false,
33
+ ...options,
34
+ };
35
+
36
+ /** @type {http.Server|null} */
37
+ this._server = null;
38
+
39
+ /** @type {Map<string, number>} Command usage counters */
40
+ this._commandUsage = new Map();
41
+
42
+ /** @type {Array} Recent errors */
43
+ this._recentErrors = [];
44
+
45
+ /** @type {number} */
46
+ this._requestCount = 0;
47
+
48
+ /** @type {Map<string, Function>} Custom API routes */
49
+ this._customRoutes = new Map();
50
+
51
+ // Track command usage
52
+ this._trackUsage();
53
+
54
+ // Auto-start if enabled
55
+ if (this.options.enabled) {
56
+ this.client.on?.('ready', () => this.start());
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Start the dashboard HTTP server.
62
+ * @returns {Promise<void>}
63
+ */
64
+ async start() {
65
+ if (this._server) return;
66
+
67
+ this._server = http.createServer((req, res) => {
68
+ this._handleRequest(req, res);
69
+ });
70
+
71
+ return new Promise((resolve, reject) => {
72
+ this._server.listen(this.options.port, this.options.host, () => {
73
+ this.client.logger?.info(`📊 Dashboard running at http://localhost:${this.options.port}`);
74
+ resolve();
75
+ });
76
+
77
+ this._server.on('error', (err) => {
78
+ this.client.logger?.error('Dashboard server error:', err.message);
79
+ reject(err);
80
+ });
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Stop the dashboard server.
86
+ */
87
+ async stop() {
88
+ if (!this._server) return;
89
+ return new Promise((resolve) => {
90
+ this._server.close(() => {
91
+ this._server = null;
92
+ resolve();
93
+ });
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Register a custom API route.
99
+ * @param {string} path - e.g. '/api/custom'
100
+ * @param {Function} handler - (req, res, client) => void
101
+ */
102
+ route(path, handler) {
103
+ this._customRoutes.set(path, handler);
104
+ return this;
105
+ }
106
+
107
+ /** @private */
108
+ _handleRequest(req, res) {
109
+ this._requestCount++;
110
+
111
+ // Apply Helmet security headers
112
+ helmet({
113
+ contentSecurityPolicy: {
114
+ directives: {
115
+ defaultSrc: ["'self'"],
116
+ scriptSrc: ["'self'"],
117
+ styleSrc: ["'self'", "'unsafe-inline'"],
118
+ imgSrc: ["'self'", "data:", "https:"],
119
+ },
120
+ },
121
+ })(req, res, () => {});
122
+
123
+ // CORS
124
+ if (this.options.cors) {
125
+ res.setHeader('Access-Control-Allow-Origin', this.options.corsOrigin || '*');
126
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
127
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
128
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
129
+ if (req.method === 'OPTIONS') {
130
+ res.writeHead(204);
131
+ res.end();
132
+ return;
133
+ }
134
+ }
135
+
136
+ // Auth
137
+ if (this.options.auth) {
138
+ const authHeader = req.headers.authorization;
139
+ if (!authHeader || authHeader !== `Bearer ${this.options.auth}`) {
140
+ this._json(res, 401, { error: 'Unauthorized' });
141
+ return;
142
+ }
143
+ }
144
+
145
+ const url = new URL(req.url, `http://${req.headers.host}`);
146
+ const pathname = url.pathname;
147
+
148
+ // Custom routes first
149
+ if (this._customRoutes.has(pathname)) {
150
+ try {
151
+ this._customRoutes.get(pathname)(req, res, this.client);
152
+ } catch (err) {
153
+ this._json(res, 500, { error: err.message });
154
+ }
155
+ return;
156
+ }
157
+
158
+ // Built-in routes
159
+ switch (pathname) {
160
+ case '/api/stats':
161
+ return this._routeStats(res);
162
+ case '/api/guilds':
163
+ return this._routeGuilds(res);
164
+ case '/api/commands':
165
+ return this._routeCommands(res);
166
+ case '/api/health':
167
+ return this._routeHealth(res);
168
+ case '/api/cache':
169
+ return this._routeCache(res);
170
+ case '/api/plugins':
171
+ return this._routePlugins(res);
172
+ case '/api/services':
173
+ return this._routeServices(res);
174
+ case '/api/middleware':
175
+ return this._routeMiddleware(res);
176
+ case '/api/errors':
177
+ return this._routeErrors(res);
178
+ default:
179
+ this._json(res, 404, { error: 'Not Found', availableRoutes: [
180
+ '/api/stats', '/api/guilds', '/api/commands', '/api/health',
181
+ '/api/cache', '/api/plugins', '/api/services', '/api/middleware',
182
+ '/api/errors',
183
+ ]});
184
+ }
185
+ }
186
+
187
+ /** @private */
188
+ _routeStats(res) {
189
+ const memUsage = process.memoryUsage();
190
+
191
+ this._json(res, 200, {
192
+ bot: {
193
+ user: this.client.user ? {
194
+ id: this.client.user.id,
195
+ tag: this.client.user.tag,
196
+ avatar: this.client.user.avatar,
197
+ } : null,
198
+ uptime: this.client.uptime,
199
+ uptimeFormatted: this._formatUptime(this.client.uptime),
200
+ ping: this.client.ping,
201
+ ready: this.client.ready,
202
+ readyAt: this.client.readyAt,
203
+ },
204
+ counts: {
205
+ guilds: this.client.guilds?.size || 0,
206
+ channels: this.client.channels?.size || 0,
207
+ users: this.client.users?.size || 0,
208
+ commands: this.client.commands?.commands?.size || 0,
209
+ },
210
+ memory: {
211
+ rss: Math.round(memUsage.rss / 1024 / 1024) + 'MB',
212
+ heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024) + 'MB',
213
+ heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024) + 'MB',
214
+ },
215
+ system: {
216
+ nodeVersion: process.version,
217
+ platform: process.platform,
218
+ pid: process.pid,
219
+ },
220
+ shard: this.client.shard || null,
221
+ dashboard: {
222
+ requests: this._requestCount,
223
+ }
224
+ });
225
+ }
226
+
227
+ /** @private */
228
+ _routeGuilds(res) {
229
+ const guilds = [];
230
+ if (this.client.guilds) {
231
+ for (const [id, guild] of this.client.guilds) {
232
+ guilds.push({
233
+ id,
234
+ name: guild.name,
235
+ memberCount: guild.memberCount,
236
+ icon: guild.icon,
237
+ ownerId: guild.ownerId,
238
+ });
239
+ }
240
+ }
241
+ this._json(res, 200, { total: guilds.length, guilds });
242
+ }
243
+
244
+ /** @private */
245
+ _routeCommands(res) {
246
+ const commands = [];
247
+ if (this.client.commands?.commands) {
248
+ for (const [name, cmd] of this.client.commands.commands) {
249
+ commands.push({
250
+ name,
251
+ description: cmd.description || '',
252
+ cooldown: cmd.cooldown || 0,
253
+ guards: cmd.guards?.map(g => g.name) || [],
254
+ usage: this._commandUsage.get(name) || 0,
255
+ });
256
+ }
257
+ }
258
+ this._json(res, 200, { total: commands.length, commands });
259
+ }
260
+
261
+ /** @private */
262
+ _routeHealth(res) {
263
+ const healthy = this.client.ready && this.client.ws?.connected !== false;
264
+ this._json(res, healthy ? 200 : 503, {
265
+ status: healthy ? 'healthy' : 'unhealthy',
266
+ ready: this.client.ready,
267
+ uptime: this.client.uptime,
268
+ ping: this.client.ping,
269
+ timestamp: new Date().toISOString(),
270
+ });
271
+ }
272
+
273
+ /** @private */
274
+ _routeCache(res) {
275
+ this._json(res, 200, {
276
+ guilds: this.client.guilds?.size || 0,
277
+ channels: this.client.channels?.size || 0,
278
+ users: this.client.users?.size || 0,
279
+ adapter: this.client.cache?.adapter?.constructor?.name || 'unknown',
280
+ });
281
+ }
282
+
283
+ /** @private */
284
+ _routePlugins(res) {
285
+ const plugins = this.client.plugins?.list?.() || [];
286
+ this._json(res, 200, { total: plugins.length, plugins });
287
+ }
288
+
289
+ /** @private */
290
+ _routeServices(res) {
291
+ const services = this.client.services?.list?.() || [];
292
+ this._json(res, 200, { total: services.length, services });
293
+ }
294
+
295
+ /** @private */
296
+ _routeMiddleware(res) {
297
+ const stats = this.client.middleware?.stats || { global: 0, command: 0, group: 0, total: 0 };
298
+ this._json(res, 200, stats);
299
+ }
300
+
301
+ /** @private */
302
+ _routeErrors(res) {
303
+ this._json(res, 200, {
304
+ total: this._recentErrors.length,
305
+ errors: this._recentErrors.slice(-50)
306
+ });
307
+ }
308
+
309
+ /** @private */
310
+ _trackUsage() {
311
+ this.client.on?.('interactionCreate', (interaction) => {
312
+ if (interaction?.commandName) {
313
+ const current = this._commandUsage.get(interaction.commandName) || 0;
314
+ this._commandUsage.set(interaction.commandName, current + 1);
315
+ }
316
+ });
317
+
318
+ this.client.on?.('error', (error) => {
319
+ this._recentErrors.push({
320
+ message: error?.message || String(error),
321
+ timestamp: new Date().toISOString(),
322
+ });
323
+ if (this._recentErrors.length > 100) {
324
+ this._recentErrors = this._recentErrors.slice(-100);
325
+ }
326
+ });
327
+ }
328
+
329
+ /** @private */
330
+ _json(res, status, data) {
331
+ res.writeHead(status, { 'Content-Type': 'application/json' });
332
+ res.end(JSON.stringify(data, null, 2));
333
+ }
334
+
335
+ /** @private */
336
+ _formatUptime(ms) {
337
+ if (!ms) return '0s';
338
+ const seconds = Math.floor(ms / 1000);
339
+ const minutes = Math.floor(seconds / 60);
340
+ const hours = Math.floor(minutes / 60);
341
+ const days = Math.floor(hours / 24);
342
+
343
+ const parts = [];
344
+ if (days > 0) parts.push(`${days}d`);
345
+ if (hours % 24 > 0) parts.push(`${hours % 24}h`);
346
+ if (minutes % 60 > 0) parts.push(`${minutes % 60}m`);
347
+ if (seconds % 60 > 0) parts.push(`${seconds % 60}s`);
348
+ return parts.join(' ') || '0s';
349
+ }
350
+
351
+ /**
352
+ * Destroy the dashboard.
353
+ */
354
+ async destroy() {
355
+ await this.stop();
356
+ this._commandUsage.clear();
357
+ this._recentErrors = [];
358
+ this._customRoutes.clear();
359
+ }
360
+ }
361
+
362
+ module.exports = DashboardManager;
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const { SchemaGenerator } = require('./schema/SchemaGenerator');
4
+
5
+ class DataGenerator {
6
+ /**
7
+ * @param {object} options
8
+ * @param {object} options.schema - JSON Schema definition
9
+ * @param {string|number} [options.seed] - Optional seed for reproducible output
10
+ * @param {string} [options.locale='en'] - Locale hint for faker
11
+ * @param {object} [options.pluginManager] - Optional plugin manager
12
+ * @param {number} [options.count=1] - Default number of records to generate
13
+ */
14
+ constructor({ schema, seed, locale = 'en', pluginManager, count = 1 } = {}) {
15
+ if (!schema) throw new TypeError('schema is required');
16
+ this.schema = schema;
17
+ this.seed = seed;
18
+ this.locale = locale;
19
+ this.pluginManager = pluginManager;
20
+ this.count = count;
21
+
22
+ this._generator = new SchemaGenerator({ schema, seed, locale, pluginManager });
23
+ }
24
+
25
+ /**
26
+ * Generate a single record.
27
+ * @returns {any}
28
+ */
29
+ generate() {
30
+ return this._generator.generate();
31
+ }
32
+
33
+ /**
34
+ * Generate multiple records.
35
+ * @param {number} [count] - Overrides instance count if provided
36
+ * @returns {any[]}
37
+ */
38
+ generateMany(count) {
39
+ const n = count ?? this.count;
40
+ if (typeof n !== 'number' || n < 1 || !Number.isInteger(n)) {
41
+ throw new RangeError('count must be a positive integer');
42
+ }
43
+ return Array.from({ length: n }, () => this._generator.generate());
44
+ }
45
+ }
46
+
47
+ module.exports = { DataGenerator };
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Minimal GraphQL mock API helper.
5
+ *
6
+ * TODO: hook into graphql-js and generate resolvers based on schema definitions.
7
+ */
8
+
9
+ function mockFromGraphqlSchema(schema, options = {}) {
10
+ return {
11
+ schema,
12
+ mocks: {},
13
+ };
14
+ }
15
+
16
+ module.exports = { mockFromGraphqlSchema };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Minimal OpenAPI mocker helper.
5
+ *
6
+ * TODO: implement full OpenAPI parsing + mock data generation.
7
+ */
8
+
9
+ function mockFromOpenApi(openApiSpec, options = {}) {
10
+ // Placeholder: Walk through the OpenAPI paths and generate responses for each operation.
11
+ // Real implementation should support request/response schemas, security, and status codes.
12
+ return {
13
+ info: openApiSpec.info,
14
+ paths: {},
15
+ };
16
+ }
17
+
18
+ module.exports = { mockFromOpenApi };
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { DataGenerator } = require('./DataGenerator');
6
+ const { loadSchema } = require('./schema/loadSchema');
7
+ const { formatters } = require('./formatters');
8
+ const { PluginManager } = require('./plugin/PluginManager');
9
+
10
+ function parseArgs(argv) {
11
+ const args = {};
12
+ let i = 0;
13
+ while (i < argv.length) {
14
+ const arg = argv[i];
15
+ if (arg.startsWith('--')) {
16
+ const key = arg.slice(2);
17
+ const next = argv[i + 1];
18
+ if (next && !next.startsWith('--')) {
19
+ args[key] = next;
20
+ i += 2;
21
+ } else {
22
+ args[key] = true;
23
+ i += 1;
24
+ }
25
+ } else {
26
+ // positional
27
+ if (!args._) args._ = [];
28
+ args._.push(arg);
29
+ i += 1;
30
+ }
31
+ }
32
+ return args;
33
+ }
34
+
35
+ function showHelp() {
36
+ console.log(`
37
+ lumis-datagen — schema-based mock data generator
38
+
39
+ Usage:
40
+ lumis-datagen --schema schema.json --count 1000 --out out.json [options]
41
+
42
+ Options:
43
+ --schema <file> JSON schema file (required)
44
+ --count <n> Number of records to generate (default: 1)
45
+ --out <file> Output file path (default: stdout)
46
+ --format <fmt> Output format: json|csv|sql|mongo (default: json)
47
+ --seed <value> Deterministic seed for repeatable data
48
+ --locale <locale> Locale for locale-aware generators (default: en)
49
+ --plugin-dir <dir> Path to load plugins from
50
+ --batch <size> Batch size for streaming output
51
+ --help Show this help message
52
+ `);
53
+ }
54
+
55
+ function ensureExists(filePath) {
56
+ if (!fs.existsSync(filePath)) {
57
+ throw new Error(`File not found: ${filePath}`);
58
+ }
59
+ }
60
+
61
+ function runCli({ argv, cwd, rootDir }) {
62
+ const opts = parseArgs(argv);
63
+ if (opts.help || opts.h) {
64
+ showHelp();
65
+ process.exit(0);
66
+ }
67
+
68
+ const schemaPath = opts.schema || opts.s;
69
+ if (!schemaPath) {
70
+ console.error('Error: --schema is required');
71
+ showHelp();
72
+ process.exit(1);
73
+ }
74
+
75
+ const schemaFile = path.isAbsolute(schemaPath) ? schemaPath : path.join(cwd, schemaPath);
76
+ ensureExists(schemaFile);
77
+
78
+ const count = Number(opts.count || opts.c || 1);
79
+ const outPath = opts.out || opts.o;
80
+ const format = (opts.format || 'json').toLowerCase();
81
+ const seed = opts.seed || opts._seed || undefined;
82
+ const locale = opts.locale || 'en';
83
+ const pluginDir = opts['plugin-dir'] || opts.p || null;
84
+ const batchSize = Number(opts.batch || 0);
85
+
86
+ const schema = loadSchema(schemaFile);
87
+ const pluginManager = new PluginManager({ pluginDir, rootDir, locale });
88
+
89
+ const generator = new DataGenerator({ schema, seed, locale, pluginManager });
90
+ const outputFormatter = formatters[format];
91
+ if (!outputFormatter) {
92
+ console.error(`Unknown format: ${format}`);
93
+ process.exit(1);
94
+ }
95
+
96
+ const stream = outputFormatter(streamGenerator(generator, count, batchSize), { schema, count, locale });
97
+
98
+ if (outPath) {
99
+ const outFile = path.isAbsolute(outPath) ? outPath : path.join(cwd, outPath);
100
+ const writeStream = fs.createWriteStream(outFile, { encoding: 'utf8' });
101
+ stream.pipe(writeStream);
102
+ writeStream.on('finish', () => {
103
+ console.log(`✅ Generated ${count} record(s) to ${outFile}`);
104
+ });
105
+ } else {
106
+ stream.pipe(process.stdout);
107
+ }
108
+ }
109
+
110
+ function* streamGenerator(generator, count, batchSize) {
111
+ if (!batchSize || batchSize < 1) {
112
+ for (let i = 0; i < count; i += 1) {
113
+ yield generator.generate();
114
+ }
115
+ return;
116
+ }
117
+
118
+ let remaining = count;
119
+ while (remaining > 0) {
120
+ const currentBatch = Math.min(batchSize, remaining);
121
+ const records = [];
122
+ for (let i = 0; i < currentBatch; i += 1) {
123
+ records.push(generator.generate());
124
+ }
125
+ yield records;
126
+ remaining -= currentBatch;
127
+ }
128
+ }
129
+
130
+ module.exports = { runCli };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ const { Readable } = require('stream');
4
+
5
+ function CsvFormatter(generator, { schema }) {
6
+ const stream = new Readable({ objectMode: true, read() {} });
7
+ const fields = Object.keys(schema.properties || {});
8
+ let headerWritten = false;
9
+
10
+ const toCsv = (record) => {
11
+ return fields
12
+ .map((key) => {
13
+ let value = record[key];
14
+ if (value === undefined || value === null) return '';
15
+ const str = String(value);
16
+ // escape "
17
+ const escaped = str.replace(/"/g, '""');
18
+ if (escaped.includes(',') || escaped.includes('"') || escaped.includes('\n')) {
19
+ return `"${escaped}"`;
20
+ }
21
+ return escaped;
22
+ })
23
+ .join(',');
24
+ };
25
+
26
+ stream._read = function () {
27
+ if (!headerWritten) {
28
+ this.push(fields.join(',') + '\n');
29
+ headerWritten = true;
30
+ return;
31
+ }
32
+
33
+ const { value, done } = generator.next ? generator.next() : { done: true };
34
+ if (done) {
35
+ this.push(null);
36
+ return;
37
+ }
38
+
39
+ this.push(toCsv(value) + '\n');
40
+ };
41
+
42
+ return stream;
43
+ }
44
+
45
+ module.exports = { CsvFormatter };
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const { JsonFormatter } = require('./json');
4
+ const { CsvFormatter } = require('./csv');
5
+ const { SqlFormatter } = require('./sql');
6
+ const { MongoFormatter } = require('./mongo');
7
+
8
+ const formatters = {
9
+ json: JsonFormatter,
10
+ csv: CsvFormatter,
11
+ sql: SqlFormatter,
12
+ mongo: MongoFormatter,
13
+ };
14
+
15
+ module.exports = { formatters };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const { Readable } = require('stream');
4
+
5
+ function JsonFormatter(generator, { count }) {
6
+ const stream = new Readable({ objectMode: true, read() {} });
7
+ let wroteHeader = false;
8
+ stream._read = function () {
9
+ if (!wroteHeader) {
10
+ this.push('[');
11
+ wroteHeader = true;
12
+ }
13
+
14
+ const { value, done } = generator.next ? generator.next() : { done: true };
15
+ if (done) {
16
+ this.push(']');
17
+ this.push(null);
18
+ return;
19
+ }
20
+
21
+ const json = JSON.stringify(value, null, 2);
22
+ if (this._firstRecord) {
23
+ this._firstRecord = false;
24
+ this.push(`\n${json}`);
25
+ } else {
26
+ this.push(`,\n${json}`);
27
+ }
28
+ };
29
+ stream._firstRecord = true;
30
+ return stream;
31
+ }
32
+
33
+ module.exports = { JsonFormatter };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const { Readable } = require('stream');
4
+
5
+ function MongoFormatter(generator, { schema }) {
6
+ const collection = schema.collection || 'data';
7
+ const stream = new Readable({ objectMode: true, read() {} });
8
+
9
+ stream._read = function () {
10
+ const { value, done } = generator.next ? generator.next() : { done: true };
11
+ if (done) {
12
+ this.push(null);
13
+ return;
14
+ }
15
+
16
+ this.push(`db.getCollection('${collection}').insertOne(${JSON.stringify(value, null, 2)});\n`);
17
+ };
18
+
19
+ return stream;
20
+ }
21
+
22
+ module.exports = { MongoFormatter };