@doviui/dev-db 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -81,19 +81,27 @@ export default {
81
81
 
82
82
  #### Using the CLI directly
83
83
 
84
- Run the CLI to generate JSON files:
84
+ Run the CLI to generate JSON files from a single schema file or a directory:
85
85
 
86
86
  ```bash
87
- # Generate from a directory of schemas
88
- bunx @doviui/dev-db generate ./schemas -o ./mock-data
87
+ # Generate from a single schema file
88
+ bunx @doviui/dev-db schema.ts
89
89
 
90
- # Generate from a single file
91
- bunx @doviui/dev-db generate ./schemas/user.schema.ts -o ./data
90
+ # Generate from a directory of schema files
91
+ bunx @doviui/dev-db ./schemas
92
+
93
+ # Specify output directory
94
+ bunx @doviui/dev-db schema.ts -o ./data
92
95
 
93
96
  # Use a seed for reproducible data
94
- bunx @doviui/dev-db generate ./schemas --seed 42
97
+ bunx @doviui/dev-db ./schemas --seed 42
98
+
99
+ # Combine options
100
+ bunx @doviui/dev-db ./schemas -o ./mock-data -s 42
95
101
  ```
96
102
 
103
+ **Directory Support:** When a directory is provided, all `.ts` and `.js` files are loaded and their schemas are merged. This allows you to organize related tables across multiple files for better maintainability.
104
+
97
105
  #### Adding to package.json scripts
98
106
 
99
107
  Add generation to your project's scripts:
@@ -101,8 +109,8 @@ Add generation to your project's scripts:
101
109
  ```json
102
110
  {
103
111
  "scripts": {
104
- "generate:data": "bunx @doviui/dev-db generate ./schemas -o ./mock-data",
105
- "generate:data:seed": "bunx @doviui/dev-db generate ./schemas -o ./mock-data --seed 42"
112
+ "generate:data": "bunx @doviui/dev-db ./schemas -o ./mock-data",
113
+ "generate:data:seed": "bunx @doviui/dev-db ./schemas -o ./mock-data -s 42"
106
114
  }
107
115
  }
108
116
  ```
@@ -221,7 +229,11 @@ Chain modifiers to configure field behavior and constraints:
221
229
 
222
230
  ### Multi-Table Schemas
223
231
 
224
- Define multiple related tables in a single file for better organization:
232
+ You can organize schemas in two ways:
233
+
234
+ **Option 1: Single file with multiple tables**
235
+
236
+ Define multiple related tables in a single file:
225
237
 
226
238
  ```typescript
227
239
  // schemas/social.schema.ts
@@ -233,14 +245,60 @@ export default {
233
245
  id: t.bigserial().primaryKey(),
234
246
  username: t.varchar(50).unique()
235
247
  },
236
-
248
+
237
249
  Post: {
238
250
  $count: 500,
239
251
  id: t.bigserial().primaryKey(),
240
252
  user_id: t.foreignKey('User', 'id'),
241
253
  title: t.varchar(200)
242
254
  },
243
-
255
+
256
+ Comment: {
257
+ $count: 2000,
258
+ id: t.uuid().primaryKey(),
259
+ post_id: t.foreignKey('Post', 'id'),
260
+ user_id: t.foreignKey('User', 'id'),
261
+ content: t.text()
262
+ }
263
+ }
264
+ ```
265
+
266
+ **Option 2: Multiple files in a directory**
267
+
268
+ Organize each table in its own file for better maintainability:
269
+
270
+ ```typescript
271
+ // schemas/users.ts
272
+ import { t } from '@doviui/dev-db'
273
+
274
+ export default {
275
+ User: {
276
+ $count: 100,
277
+ id: t.bigserial().primaryKey(),
278
+ username: t.varchar(50).unique()
279
+ }
280
+ }
281
+ ```
282
+
283
+ ```typescript
284
+ // schemas/posts.ts
285
+ import { t } from '@doviui/dev-db'
286
+
287
+ export default {
288
+ Post: {
289
+ $count: 500,
290
+ id: t.bigserial().primaryKey(),
291
+ user_id: t.foreignKey('User', 'id'),
292
+ title: t.varchar(200)
293
+ }
294
+ }
295
+ ```
296
+
297
+ ```typescript
298
+ // schemas/comments.ts
299
+ import { t } from '@doviui/dev-db'
300
+
301
+ export default {
244
302
  Comment: {
245
303
  $count: 2000,
246
304
  id: t.uuid().primaryKey(),
@@ -251,6 +309,8 @@ export default {
251
309
  }
252
310
  ```
253
311
 
312
+ Then generate with: `bunx @doviui/dev-db ./schemas`
313
+
254
314
  ### Schema Validation
255
315
 
256
316
  dev-db validates schemas before generation to catch errors early:
@@ -267,11 +327,9 @@ export default {
267
327
 
268
328
  **Output:**
269
329
  ```
270
- 🔍 Validating schemas...
271
-
272
- ❌ Schema validation failed:
330
+ Schema validation failed:
273
331
 
274
- Post.user_id: Foreign key references non-existent table 'User'
332
+ Post.user_id: Foreign key references non-existent table 'User'
275
333
  ```
276
334
 
277
335
  ### Custom Data Generators
@@ -304,18 +362,66 @@ t.jsonb().generate((faker) => ({
304
362
  ## Command Line Interface
305
363
 
306
364
  ```bash
307
- bunx @doviui/dev-db generate <schemas> [options]
365
+ dev-db <path> [options]
308
366
 
309
367
  Arguments:
310
- schemas Path to schema file or directory
368
+ <path> Path to schema file or directory containing schema files
311
369
 
312
370
  Options:
313
- -o, --output <dir> Output directory (default: "./mock-data")
314
- -s, --seed <number> Random seed for reproducibility
315
- -h, --help Display help
316
- -v, --version Display version
371
+ -o, --output <dir> Output directory for generated JSON files (default: ./mock-data)
372
+ -s, --seed <number> Random seed for reproducible data generation
373
+ -h, --help Show help message
317
374
  ```
318
375
 
376
+ **Examples:**
377
+
378
+ ```bash
379
+ # Single file
380
+ bunx @doviui/dev-db schema.ts
381
+
382
+ # Directory of schemas
383
+ bunx @doviui/dev-db ./schemas
384
+
385
+ # Custom output directory
386
+ bunx @doviui/dev-db ./schemas -o ./data
387
+
388
+ # Reproducible generation with seed
389
+ bunx @doviui/dev-db schema.ts -s 42
390
+
391
+ # All options combined
392
+ bunx @doviui/dev-db ./schemas --output ./database --seed 12345
393
+ ```
394
+
395
+ **Schema File Format:**
396
+
397
+ Schema files must export a default object or named `schema` export:
398
+
399
+ ```typescript
400
+ import { t } from '@doviui/dev-db';
401
+
402
+ export default {
403
+ User: {
404
+ $count: 100,
405
+ id: t.bigserial().primaryKey(),
406
+ email: t.varchar(255).unique().notNull().generate('internet.email'),
407
+ name: t.varchar(100).notNull()
408
+ }
409
+ };
410
+ ```
411
+
412
+ **Directory Support:**
413
+
414
+ When a directory is provided, all `.ts` and `.js` files are loaded and their schemas are merged. This allows you to organize schemas across multiple files:
415
+
416
+ ```
417
+ schemas/
418
+ ├── users.ts // exports { User: { ... } }
419
+ ├── posts.ts // exports { Post: { ... } }
420
+ └── comments.ts // exports { Comment: { ... } }
421
+ ```
422
+
423
+ Running `bunx @doviui/dev-db ./schemas` will merge all three schemas and generate data for all tables.
424
+
319
425
  ### Programmatic API
320
426
 
321
427
  For more control, create a custom generation script:
@@ -330,8 +436,11 @@ const validator = new SchemaValidator()
330
436
  const errors = validator.validate(schema)
331
437
 
332
438
  if (errors.length > 0) {
333
- console.error('Schema validation failed:')
334
- errors.forEach(err => console.error(` • ${err.message}`))
439
+ console.error('Schema validation failed:')
440
+ errors.forEach(err => {
441
+ const location = err.field ? `${err.table}.${err.field}` : err.table
442
+ console.error(` ${location}: ${err.message}`)
443
+ })
335
444
  process.exit(1)
336
445
  }
337
446
 
@@ -342,7 +451,7 @@ const generator = new MockDataGenerator(schema, {
342
451
  })
343
452
 
344
453
  await generator.generate()
345
- console.log('Mock data generated successfully!')
454
+ console.log('Mock data generated successfully!')
346
455
  ```
347
456
 
348
457
  Add to package.json:
package/cli.ts ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * CLI for @doviui/dev-db - Generate mock JSON databases from TypeScript schemas
5
+ */
6
+
7
+ import { MockDataGenerator } from './src/generator';
8
+ import { SchemaValidator } from './src/validator';
9
+ import type { Schema } from './src/types';
10
+ import { readdirSync, statSync } from 'fs';
11
+ import { join, extname, resolve } from 'path';
12
+
13
+ const HELP_TEXT = `
14
+ @doviui/dev-db - Generate realistic mock JSON databases
15
+
16
+ USAGE:
17
+ dev-db <path> [options]
18
+
19
+ ARGUMENTS:
20
+ <path> Path to schema file or directory containing schema files
21
+
22
+ OPTIONS:
23
+ -o, --output <dir> Output directory for generated JSON files (default: ./mock-data)
24
+ -s, --seed <number> Random seed for reproducible data generation
25
+ -h, --help Show this help message
26
+
27
+ EXAMPLES:
28
+ dev-db schema.ts
29
+ dev-db ./schemas
30
+ dev-db schema.ts -o ./data -s 42
31
+ dev-db ./schemas --output ./database --seed 12345
32
+
33
+ SCHEMA FILE FORMAT:
34
+ Schema files must export a default object or named 'schema' export:
35
+
36
+ import { t } from '@doviui/dev-db';
37
+
38
+ export default {
39
+ User: {
40
+ $count: 100,
41
+ id: t.bigserial().primaryKey(),
42
+ email: t.varchar(255).unique().notNull().generate('internet.email'),
43
+ name: t.varchar(100).notNull()
44
+ }
45
+ };
46
+
47
+ DIRECTORY SUPPORT:
48
+ When a directory is provided, all .ts and .js files are loaded and their
49
+ schemas are merged. This allows you to organize schemas across multiple files.
50
+ `;
51
+
52
+ interface CLIOptions {
53
+ schemaPath?: string;
54
+ outputDir: string;
55
+ seed?: number;
56
+ help: boolean;
57
+ }
58
+
59
+ function parseArgs(args: string[]): CLIOptions {
60
+ const options: CLIOptions = {
61
+ outputDir: './mock-data',
62
+ help: false,
63
+ };
64
+
65
+ for (let i = 0; i < args.length; i++) {
66
+ const arg = args[i];
67
+ if (!arg) continue;
68
+
69
+ if (arg === '-h' || arg === '--help') {
70
+ options.help = true;
71
+ } else if (arg === '-o' || arg === '--output') {
72
+ const nextArg = args[++i];
73
+ if (nextArg) options.outputDir = nextArg;
74
+ } else if (arg === '-s' || arg === '--seed') {
75
+ const nextArg = args[++i];
76
+ if (nextArg !== undefined) options.seed = parseInt(nextArg, 10);
77
+ } else if (!arg.startsWith('-')) {
78
+ options.schemaPath = arg;
79
+ }
80
+ }
81
+
82
+ return options;
83
+ }
84
+
85
+ async function loadSchemaFromFile(filePath: string): Promise<Schema> {
86
+ try {
87
+ // Resolve path to absolute path based on current working directory
88
+ const absolutePath = resolve(process.cwd(), filePath);
89
+
90
+ const module = await import(absolutePath);
91
+ const schema = module.default || module.schema;
92
+
93
+ if (!schema) {
94
+ throw new Error(
95
+ 'Schema file must export a default object or named "schema" export'
96
+ );
97
+ }
98
+
99
+ return schema;
100
+ } catch (error) {
101
+ if (error instanceof Error) {
102
+ throw new Error(`Failed to load schema file: ${error.message}`);
103
+ }
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ async function loadSchemaFromDirectory(dirPath: string): Promise<Schema> {
109
+ try {
110
+ // Resolve path to absolute path based on current working directory
111
+ const absoluteDir = resolve(process.cwd(), dirPath);
112
+
113
+ const files = readdirSync(absoluteDir);
114
+ const schemaFiles = files.filter(file => {
115
+ const ext = extname(file);
116
+ return ext === '.ts' || ext === '.js';
117
+ });
118
+
119
+ if (schemaFiles.length === 0) {
120
+ throw new Error(`No schema files (.ts or .js) found in directory: ${dirPath}`);
121
+ }
122
+
123
+ const mergedSchema: Schema = {};
124
+
125
+ for (const file of schemaFiles) {
126
+ const filePath = join(absoluteDir, file);
127
+ const schema = await loadSchemaFromFile(filePath);
128
+
129
+ // Merge schemas
130
+ Object.assign(mergedSchema, schema);
131
+ }
132
+
133
+ return mergedSchema;
134
+ } catch (error) {
135
+ if (error instanceof Error) {
136
+ throw new Error(`Failed to load schemas from directory: ${error.message}`);
137
+ }
138
+ throw error;
139
+ }
140
+ }
141
+
142
+ async function loadSchema(path: string): Promise<Schema> {
143
+ // Resolve path to absolute path based on current working directory
144
+ const absolutePath = resolve(process.cwd(), path);
145
+
146
+ const stats = statSync(absolutePath);
147
+
148
+ if (stats.isDirectory()) {
149
+ return loadSchemaFromDirectory(path);
150
+ } else if (stats.isFile()) {
151
+ return loadSchemaFromFile(path);
152
+ } else {
153
+ throw new Error(`Path is neither a file nor a directory: ${path}`);
154
+ }
155
+ }
156
+
157
+ async function main() {
158
+ const args = process.argv.slice(2);
159
+ const options = parseArgs(args);
160
+
161
+ if (options.help || !options.schemaPath) {
162
+ console.log(HELP_TEXT);
163
+ process.exit(options.help ? 0 : 1);
164
+ }
165
+
166
+ try {
167
+ const schema = await loadSchema(options.schemaPath);
168
+
169
+ const validator = new SchemaValidator();
170
+ const errors = validator.validate(schema);
171
+
172
+ if (errors.length > 0) {
173
+ console.error('Schema validation failed:\n');
174
+ errors.forEach((error) => {
175
+ const location = error.field ? `${error.table}.${error.field}` : error.table;
176
+ console.error(` ${location}: ${error.message}`);
177
+ });
178
+ process.exit(1);
179
+ }
180
+
181
+ // Resolve output directory to absolute path
182
+ const absoluteOutputDir = resolve(process.cwd(), options.outputDir);
183
+
184
+ const generator = new MockDataGenerator(schema, {
185
+ outputDir: absoluteOutputDir,
186
+ seed: options.seed,
187
+ });
188
+
189
+ await generator.generate();
190
+
191
+ console.log(`Generated mock data in: ${absoluteOutputDir}`);
192
+ } catch (error) {
193
+ console.error('Error:', error instanceof Error ? error.message : error);
194
+ process.exit(1);
195
+ }
196
+ }
197
+
198
+ main();
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@doviui/dev-db",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "TypeScript-first mock database generator for rapid application development",
5
5
  "main": "index.ts",
6
6
  "module": "index.ts",
7
7
  "types": "index.ts",
8
8
  "type": "module",
9
+ "bin": {
10
+ "dev-db": "./cli.ts"
11
+ },
9
12
  "exports": {
10
13
  ".": {
11
14
  "import": "./index.ts",
@@ -13,6 +16,7 @@
13
16
  }
14
17
  },
15
18
  "files": [
19
+ "cli.ts",
16
20
  "index.ts",
17
21
  "src/**/*.ts",
18
22
  "README.md",
package/src/generator.ts CHANGED
@@ -337,14 +337,10 @@ export class MockDataGenerator {
337
337
  private async writeDataToFiles(): Promise<void> {
338
338
  const outputDir = this.options.outputDir ?? './mock-data';
339
339
 
340
- // Create output directory if it doesn't exist
341
- await Bun.write(`${outputDir}/.gitkeep`, '');
342
-
343
- // Write each table to a JSON file
340
+ // Write each table to a JSON file (directory is created automatically)
344
341
  for (const [tableName, data] of Object.entries(this.generatedData)) {
345
342
  const filePath = `${outputDir}/${tableName}.json`;
346
343
  await Bun.write(filePath, JSON.stringify(data, null, 2));
347
- console.log(` Generated ${data.length} records for ${tableName} -> ${filePath}`);
348
344
  }
349
345
  }
350
346