ddlseed 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ddlseed contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,164 @@
1
- # Temporary Holding Version
1
+ # ddlseed
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![CI](https://github.com/brianmojena/ddlseed/actions/workflows/ci.yml/badge.svg)](https://github.com/brianmojena/ddlseed/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ ![Node](https://img.shields.io/badge/node-%E2%89%A518-brightgreen)
6
+
7
+ **Deterministic, FK-consistent test data from your SQL schema. One command, zero config, fully offline.**
8
+
9
+ ```
10
+ ddlseed --schema schema.sql --rows 100 --seed 42 --format sql > seed.sql
11
+ ```
12
+
13
+ ## The problem it solves
14
+
15
+ Populating a development database or CI pipeline with test data is a chore:
16
+
17
+ - **Faker-style libraries** generate realistic values *per column* but leave it to you to
18
+ keep foreign keys, unique constraints and primary keys consistent across tables.
19
+ - **Snaplet/seed** (the popular schema-aware generator) needs a *live database connection*
20
+ and writes into it — no way to work offline from a schema dump.
21
+ - **Neosync** is a full platform (Docker, Temporal, Postgres) — heavy infrastructure for a seed file.
22
+ - **Commercial tools** (Seedfast, Mockaroo, Tonic…) are paid SaaS with row limits.
23
+
24
+ `ddlseed` is the missing middle ground: a single CLI that reads **just your DDL**
25
+ (`CREATE TABLE` statements — no database connection, no server, no accounts) and emits
26
+ **ready-to-load SQL INSERTs, JSON or CSV**, with foreign keys, primary keys, unique
27
+ constraints and NOT NULL respected by construction. Same seed → same data, every time.
28
+
29
+ ## Who it's for
30
+
31
+ - Backend developers who need realistic local/CI fixtures for a relational schema.
32
+ - Teams that want reproducible seed data in code review (`--seed 42` diffs cleanly).
33
+ - Anyone who has a `.sql` schema dump and no running database (offline, air-gapped, new laptop).
34
+
35
+ ## Installation
36
+
37
+ Requires Node.js ≥ 18. The only runtime dependency is `@faker-js/faker` (MIT).
38
+
39
+ **From source** (the package is not yet published to npm):
40
+
41
+ ```bash
42
+ git clone https://github.com/brianmojena/ddlseed.git ddlseed
43
+ cd ddlseed
44
+ npm install
45
+ npm run build
46
+ node dist/cli.js --help # run it
47
+ npm link # optional: exposes `ddlseed` on your PATH
48
+ ```
49
+
50
+ **Once published**, it will be simply:
51
+
52
+ ```bash
53
+ npm install -g ddlseed
54
+ # or run without installing:
55
+ npx ddlseed --help
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ```
61
+ ddlseed --schema <file.sql> [options]
62
+
63
+ Options:
64
+ -s, --schema <file> SQL DDL file with CREATE TABLE statements (required)
65
+ -n, --rows <n> Rows per table (default: 50)
66
+ --seed <n> Seed for deterministic output (default: 42)
67
+ -f, --format <fmt> sql | json | csv | all (default: sql)
68
+ -o, --out <dir> Output directory (default: stdout)
69
+ --dialect <d> sqlite | postgres | mysql | auto (default: auto)
70
+ --ref-date <date> Anchor date for timestamps, ISO 8601 (default: 2026-01-01)
71
+ -h, --help Show help
72
+ -v, --version Show version
73
+ ```
74
+
75
+ ### Examples
76
+
77
+ ```bash
78
+ # 100 rows per table as SQL, written to ./seed (seed.sql + data.json + <table>.csv)
79
+ ddlseed --schema schema.sql --rows 100 --seed 7 --format all --out ./seed
80
+
81
+ # SQL to stdout, pipe it straight into psql / sqlite3 / mysql
82
+ ddlseed --schema schema.sql --rows 200 | sqlite3 dev.db
83
+
84
+ # JSON for a REST mock server
85
+ ddlseed --schema schema.sql --format json > fixtures.json
86
+
87
+ # CSV per table for spreadsheets / BI tools
88
+ ddlseed --schema schema.sql --format csv --out ./csv
89
+ ```
90
+
91
+ ### What it understands
92
+
93
+ - Dialects: **SQLite**, **PostgreSQL** and **MySQL** DDL (auto-detected, override with `--dialect`).
94
+ - `CREATE TABLE` with column-level and table-level constraints:
95
+ `PRIMARY KEY` (single and composite), `FOREIGN KEY … REFERENCES` (with
96
+ `ON DELETE/UPDATE` actions), `UNIQUE`, `NOT NULL`, `DEFAULT`, `CHECK` (ignored),
97
+ `AUTO_INCREMENT` / `AUTOINCREMENT` / `SERIAL` / `GENERATED … AS IDENTITY`,
98
+ `ENUM('a','b')` and quoted identifiers (`"x"`, `` `x` ``, `[x]`).
99
+ - Comments and other statements (`CREATE INDEX`, `INSERT`, …) are skipped.
100
+ - Self-referencing tables (e.g. `parent_id REFERENCES same_table(id)`) are handled.
101
+
102
+ ### How values are generated
103
+
104
+ Column values are chosen by a name-then-type heuristic: a column called `email`
105
+ gets a realistic email, `first_name` a person's first name, `price` a decimal,
106
+ `created_at` a recent timestamp; everything else falls back to its SQL type.
107
+ UUIDs, JSON columns, ENUM choices, VARCHAR length limits and NULL probability
108
+ (~10 % on nullable columns) are all respected. Timestamps are anchored to a fixed
109
+ reference date so that the **same seed always produces byte-identical output**.
110
+
111
+ Output tables are emitted in foreign-key order, wrapped in a transaction —
112
+ the generated `seed.sql` loads cleanly with `PRAGMA foreign_keys = ON`.
113
+
114
+ ## Demo
115
+
116
+ ```bash
117
+ npm install
118
+ npm run demo
119
+ ```
120
+
121
+ generates data for the example blog schema in [`examples/blog.sql`](examples/blog.sql)
122
+ into `demo-out/` and prints a preview. Then try loading it for real:
123
+
124
+ ```bash
125
+ sqlite3 test.db < examples/blog.sql # SQLite needs JSON→TEXT: see scripts/demo.mjs
126
+ sqlite3 test.db < demo-out/seed.sql
127
+ sqlite3 test.db "SELECT COUNT(*) FROM posts;"
128
+ ```
129
+
130
+ ## Development
131
+
132
+ ```bash
133
+ npm install # install dependencies
134
+ npm run build # compile TypeScript to dist/
135
+ npm test # build + run the full test suite (vitest)
136
+ npm run demo # build + reproducible demo
137
+ ```
138
+
139
+ The test suite (53 tests) covers the parser, dependency ordering, generator
140
+ (FK consistency, uniqueness, determinism), writers, the CLI end-to-end, and an
141
+ **integration test that loads the generated SQL into a real SQLite database**
142
+ (via `node:sqlite`) and asserts zero orphan rows and zero duplicate keys.
143
+
144
+ ## How it compares
145
+
146
+ | Tool | Works from DDL only (offline) | FK-consistent | Emits SQL/JSON/CSV files | Deterministic | Free & open source | No server |
147
+ | --- | --- | --- | --- | --- | --- | --- |
148
+ | **ddlseed** | ✅ | ✅ | ✅ | ✅ | ✅ MIT | ✅ |
149
+ | faker.js / Mimesis | ❌ (column-level only) | ❌ manual | ❌ you write it | ✅ | ✅ | ✅ |
150
+ | supabase-community/seed | ❌ needs live DB | ✅ | ❌ writes into the DB | ✅ | ✅ | ❌ |
151
+ | shuttle-hq/synth | ❌ handwritten configs | ✅ | ✅ | ✅ | ✅ | ✅ |
152
+ | Neosync | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ Docker+Temporal |
153
+ | Benerator CE | ❌ JDBC + XML | ✅ | ✅ | ✅ | ⚠️ Java+XML | ⚠️ |
154
+ | Seedfast / Mockaroo / Tonic | ⚠️ | ✅ | ✅ | ✅ | ❌ paid SaaS | ❌ |
155
+
156
+ *Table reflects capabilities as verified in November 2026 — see `docs/DECISION.md` for the full research log.*
157
+
158
+ ## Contributing
159
+
160
+ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md).
161
+
162
+ ## License
163
+
164
+ [MIT](LICENSE) — free forever, no accounts, no telemetry, no network calls.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { generateData } from './generator.js';
5
+ import { parseSchema } from './parser.js';
6
+ import { DdlSeedError } from './types.js';
7
+ import { generationOrder } from './generator.js';
8
+ import { toCsv, toCsvAll, toJson, toSql } from './writers.js';
9
+ const VERSION = '0.1.0';
10
+ function usage() {
11
+ return `ddlseed ${VERSION} — deterministic, FK-consistent test data from your SQL schema.
12
+
13
+ USAGE
14
+ ddlseed --schema <file.sql> [options]
15
+
16
+ OPTIONS
17
+ -s, --schema <file> SQL DDL file with CREATE TABLE statements (required)
18
+ -n, --rows <n> Rows per table (default: 50)
19
+ --seed <n> Seed for deterministic output (default: 42)
20
+ -f, --format <fmt> sql | json | csv | all (default: sql)
21
+ -o, --out <dir> Output directory (default: stdout)
22
+ --dialect <d> sqlite | postgres | mysql | auto (default: auto)
23
+ --ref-date <date> Anchor date for timestamps, ISO 8601 (default: 2026-01-01)
24
+ -h, --help Show this help
25
+ -v, --version Show version
26
+
27
+ EXAMPLES
28
+ ddlseed --schema schema.sql --rows 100 --seed 7 --format all --out ./seed
29
+ ddlseed --schema schema.sql --format sql > seed.sql
30
+
31
+ Output (with --out): seed.sql, data.json, <table>.csv per table.
32
+ Without --out: the chosen format is printed to stdout.`;
33
+ }
34
+ function parseArgs(argv) {
35
+ const args = { rows: 50, seed: 42, format: 'sql', help: false, version: false };
36
+ for (let i = 0; i < argv.length; i++) {
37
+ const a = argv[i];
38
+ const next = () => {
39
+ if (i + 1 >= argv.length)
40
+ throw new DdlSeedError(`Missing value for ${a}`);
41
+ return argv[++i];
42
+ };
43
+ switch (a) {
44
+ case '-s':
45
+ case '--schema':
46
+ args.schema = next();
47
+ break;
48
+ case '-n':
49
+ case '--rows': {
50
+ const v = Number(next());
51
+ if (!Number.isInteger(v) || v < 0)
52
+ throw new DdlSeedError(`--rows must be a non-negative integer`);
53
+ args.rows = v;
54
+ break;
55
+ }
56
+ case '--seed': {
57
+ const v = Number(next());
58
+ if (!Number.isInteger(v))
59
+ throw new DdlSeedError(`--seed must be an integer`);
60
+ args.seed = v;
61
+ break;
62
+ }
63
+ case '-f':
64
+ case '--format': {
65
+ const v = next();
66
+ if (!['sql', 'json', 'csv', 'all'].includes(v))
67
+ throw new DdlSeedError(`--format must be sql, json, csv or all`);
68
+ args.format = v;
69
+ break;
70
+ }
71
+ case '-o':
72
+ case '--out':
73
+ args.out = next();
74
+ break;
75
+ case '--dialect': {
76
+ const v = next();
77
+ if (!['sqlite', 'postgres', 'mysql', 'auto'].includes(v))
78
+ throw new DdlSeedError(`--dialect must be sqlite, postgres, mysql or auto`);
79
+ args.dialect = v === 'auto' ? undefined : v;
80
+ break;
81
+ }
82
+ case '--ref-date':
83
+ args.refDate = next();
84
+ break;
85
+ case '-h':
86
+ case '--help':
87
+ args.help = true;
88
+ break;
89
+ case '-v':
90
+ case '--version':
91
+ args.version = true;
92
+ break;
93
+ default: throw new DdlSeedError(`Unknown option: ${a}\n\n${usage()}`);
94
+ }
95
+ }
96
+ return args;
97
+ }
98
+ function run(args) {
99
+ if (args.version) {
100
+ process.stdout.write(`ddlseed ${VERSION}\n`);
101
+ return;
102
+ }
103
+ if (args.help) {
104
+ process.stdout.write(usage() + '\n');
105
+ return;
106
+ }
107
+ if (!args.schema) {
108
+ throw new DdlSeedError(`--schema is required.\n\n${usage()}`);
109
+ }
110
+ const sql = fs.readFileSync(args.schema, 'utf8');
111
+ const schema = parseSchema(sql, args.dialect);
112
+ if (schema.tables.length === 0) {
113
+ throw new DdlSeedError(`No CREATE TABLE statements found in "${args.schema}".`);
114
+ }
115
+ const data = generateData(schema, {
116
+ rows: args.rows,
117
+ seed: args.seed,
118
+ refDate: args.refDate,
119
+ });
120
+ if (args.out) {
121
+ fs.mkdirSync(args.out, { recursive: true });
122
+ const written = [];
123
+ if (args.format === 'sql' || args.format === 'all') {
124
+ const p = path.join(args.out, 'seed.sql');
125
+ fs.writeFileSync(p, toSql(schema, data, schema.dialect));
126
+ written.push(p);
127
+ }
128
+ if (args.format === 'json' || args.format === 'all') {
129
+ const p = path.join(args.out, 'data.json');
130
+ fs.writeFileSync(p, toJson(schema, data));
131
+ written.push(p);
132
+ }
133
+ if (args.format === 'csv' || args.format === 'all') {
134
+ for (const t of generationOrder(schema)) {
135
+ const p = path.join(args.out, `${t.name}.csv`);
136
+ fs.writeFileSync(p, toCsv(schema, data, t));
137
+ written.push(p);
138
+ }
139
+ }
140
+ process.stderr.write(`ddlseed: ${schema.tables.length} tables, ${args.rows} rows/table ->\n`);
141
+ for (const p of written)
142
+ process.stderr.write(` ${p}\n`);
143
+ return;
144
+ }
145
+ // stdout output
146
+ if (args.format === 'sql') {
147
+ process.stdout.write(toSql(schema, data, schema.dialect));
148
+ }
149
+ else if (args.format === 'json') {
150
+ process.stdout.write(toJson(schema, data));
151
+ }
152
+ else if (args.format === 'csv') {
153
+ process.stdout.write(toCsvAll(schema, data));
154
+ }
155
+ else {
156
+ process.stdout.write(toSql(schema, data, schema.dialect));
157
+ }
158
+ }
159
+ try {
160
+ run(parseArgs(process.argv.slice(2)));
161
+ }
162
+ catch (err) {
163
+ const msg = err instanceof DdlSeedError
164
+ ? err.message
165
+ : err instanceof Error ? err.message : String(err);
166
+ process.stderr.write(`ddlseed: error: ${msg}\n`);
167
+ process.exit(1);
168
+ }
@@ -0,0 +1,18 @@
1
+ import { Dialect, Row, Schema, TableDef } from './types.js';
2
+ export interface GenerateOptions {
3
+ /** Rows per table. */
4
+ rows?: number;
5
+ /** Seed for deterministic output. Same seed => same data. */
6
+ seed?: number;
7
+ /** Anchor date for generated timestamps (determinism). Default: fixed 2026-01-01. */
8
+ refDate?: Date | string;
9
+ }
10
+ interface ResolvedFk {
11
+ columns: string[];
12
+ refTable: TableDef;
13
+ refColumns: string[];
14
+ }
15
+ export declare function validateSchema(schema: Schema): Map<TableDef, ResolvedFk[]>;
16
+ export declare function generateData(schema: Schema, options?: GenerateOptions): Map<string, Row[]>;
17
+ export declare function generationOrder(schema: Schema): TableDef[];
18
+ export type { Dialect };