@doviui/dev-db 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -24
- package/cli.ts +199 -0
- package/package.json +5 -1
- package/src/generator.ts +1 -5
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
|
|
88
|
-
bunx @doviui/dev-db
|
|
87
|
+
# Generate from a single schema file
|
|
88
|
+
bunx @doviui/dev-db schema.ts
|
|
89
89
|
|
|
90
|
-
# Generate from a
|
|
91
|
-
bunx @doviui/dev-db
|
|
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
|
|
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
|
|
105
|
-
"generate:data:seed": "bunx @doviui/dev-db
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
❌ Schema validation failed:
|
|
330
|
+
Schema validation failed:
|
|
273
331
|
|
|
274
|
-
|
|
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
|
-
|
|
365
|
+
dev-db <path> [options]
|
|
308
366
|
|
|
309
367
|
Arguments:
|
|
310
|
-
|
|
368
|
+
<path> Path to schema file or directory containing schema files
|
|
311
369
|
|
|
312
370
|
Options:
|
|
313
|
-
-o, --output <dir> Output directory (default:
|
|
314
|
-
-s, --seed <number> Random seed for
|
|
315
|
-
-h, --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('
|
|
334
|
-
errors.forEach(err =>
|
|
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('
|
|
454
|
+
console.log('Mock data generated successfully!')
|
|
346
455
|
```
|
|
347
456
|
|
|
348
457
|
Add to package.json:
|
package/cli.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
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 } 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 relative to current working directory
|
|
88
|
+
const resolvedPath = filePath.startsWith('.') || filePath.startsWith('/')
|
|
89
|
+
? filePath
|
|
90
|
+
: `./${filePath}`;
|
|
91
|
+
|
|
92
|
+
const module = await import(resolvedPath);
|
|
93
|
+
const schema = module.default || module.schema;
|
|
94
|
+
|
|
95
|
+
if (!schema) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'Schema file must export a default object or named "schema" export'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return schema;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof Error) {
|
|
104
|
+
throw new Error(`Failed to load schema file: ${error.message}`);
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function loadSchemaFromDirectory(dirPath: string): Promise<Schema> {
|
|
111
|
+
try {
|
|
112
|
+
const resolvedDir = dirPath.startsWith('.') || dirPath.startsWith('/')
|
|
113
|
+
? dirPath
|
|
114
|
+
: `./${dirPath}`;
|
|
115
|
+
|
|
116
|
+
const files = readdirSync(resolvedDir);
|
|
117
|
+
const schemaFiles = files.filter(file => {
|
|
118
|
+
const ext = extname(file);
|
|
119
|
+
return ext === '.ts' || ext === '.js';
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (schemaFiles.length === 0) {
|
|
123
|
+
throw new Error(`No schema files (.ts or .js) found in directory: ${dirPath}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const mergedSchema: Schema = {};
|
|
127
|
+
|
|
128
|
+
for (const file of schemaFiles) {
|
|
129
|
+
const filePath = join(resolvedDir, file);
|
|
130
|
+
const schema = await loadSchemaFromFile(filePath);
|
|
131
|
+
|
|
132
|
+
// Merge schemas
|
|
133
|
+
Object.assign(mergedSchema, schema);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return mergedSchema;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error instanceof Error) {
|
|
139
|
+
throw new Error(`Failed to load schemas from directory: ${error.message}`);
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function loadSchema(path: string): Promise<Schema> {
|
|
146
|
+
const resolvedPath = path.startsWith('.') || path.startsWith('/')
|
|
147
|
+
? path
|
|
148
|
+
: `./${path}`;
|
|
149
|
+
|
|
150
|
+
const stats = statSync(resolvedPath);
|
|
151
|
+
|
|
152
|
+
if (stats.isDirectory()) {
|
|
153
|
+
return loadSchemaFromDirectory(path);
|
|
154
|
+
} else if (stats.isFile()) {
|
|
155
|
+
return loadSchemaFromFile(path);
|
|
156
|
+
} else {
|
|
157
|
+
throw new Error(`Path is neither a file nor a directory: ${path}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function main() {
|
|
162
|
+
const args = process.argv.slice(2);
|
|
163
|
+
const options = parseArgs(args);
|
|
164
|
+
|
|
165
|
+
if (options.help || !options.schemaPath) {
|
|
166
|
+
console.log(HELP_TEXT);
|
|
167
|
+
process.exit(options.help ? 0 : 1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const schema = await loadSchema(options.schemaPath);
|
|
172
|
+
|
|
173
|
+
const validator = new SchemaValidator();
|
|
174
|
+
const errors = validator.validate(schema);
|
|
175
|
+
|
|
176
|
+
if (errors.length > 0) {
|
|
177
|
+
console.error('Schema validation failed:\n');
|
|
178
|
+
errors.forEach((error) => {
|
|
179
|
+
const location = error.field ? `${error.table}.${error.field}` : error.table;
|
|
180
|
+
console.error(` ${location}: ${error.message}`);
|
|
181
|
+
});
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const generator = new MockDataGenerator(schema, {
|
|
186
|
+
outputDir: options.outputDir,
|
|
187
|
+
seed: options.seed,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await generator.generate();
|
|
191
|
+
|
|
192
|
+
console.log(`Generated mock data in: ${options.outputDir}`);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doviui/dev-db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
-
//
|
|
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
|
|