@cfast/db 0.5.0 → 0.6.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/dist/seed.d.ts ADDED
@@ -0,0 +1,258 @@
1
+ import { DrizzleTable } from '@cfast/permissions';
2
+ import { a as Db, i as InferRow } from './types-FUFR36h1.js';
3
+ import { Column } from 'drizzle-orm';
4
+
5
+ /**
6
+ * Schema-driven seed generator for `@cfast/db`.
7
+ *
8
+ * Introspects Drizzle schema metadata (column types, foreign keys, primary keys)
9
+ * to auto-generate realistic test data using the bundled `@faker-js/faker`.
10
+ * Supports:
11
+ *
12
+ * - Column-level `.seed()` overrides via `seedConfig()` wrapper
13
+ * - Table-level `.seed()` overrides via `tableSeed()` wrapper (count, per)
14
+ * - Automatic FK resolution from generated parent rows
15
+ * - `per` relational generation (N children per parent row)
16
+ * - `ctx` API for parent access, ref, index, and all
17
+ * - Topological sort for correct insert order
18
+ * - Many-to-many deduplication
19
+ * - Auth table detection for realistic emails
20
+ * - SQL transcript generation
21
+ *
22
+ * @module seed-generator
23
+ */
24
+
25
+ type AnyColumn = Column<any, any, any>;
26
+ /** Faker instance type -- matches `@faker-js/faker`'s default export. */
27
+ type Faker = any;
28
+ /**
29
+ * Context passed to column-level seed functions as the second argument.
30
+ */
31
+ type SeedContext = {
32
+ /** The parent row when `per` is used on the table. `undefined` for root tables. */
33
+ parent: Record<string, unknown> | undefined;
34
+ /** Pick a random existing row from any table already seeded. */
35
+ ref: (table: DrizzleTable) => Record<string, unknown>;
36
+ /** Zero-based position within the current batch (per-parent or global). */
37
+ index: number;
38
+ /** All generated rows for the given table (available after that table is seeded). */
39
+ all: (table: DrizzleTable) => Record<string, unknown>[];
40
+ };
41
+ /** Column-level seed function. Receives faker instance and optional context. */
42
+ type ColumnSeedFn = (faker: Faker, ctx: SeedContext) => unknown;
43
+ /** Table-level seed config attached via `tableSeed()`. */
44
+ type TableSeedConfig = {
45
+ /** Total count (or per-parent count when `per` is set). */
46
+ count: number;
47
+ /** Generate `count` rows per row in this parent table. */
48
+ per?: DrizzleTable;
49
+ };
50
+ /** Options for `db.seed().run()`. */
51
+ type SeedRunOptions = {
52
+ /** If provided, write the equivalent SQL INSERT statements to this path. */
53
+ transcript?: string;
54
+ };
55
+ /**
56
+ * Attaches a seed generator function to a Drizzle column.
57
+ *
58
+ * The column object is returned unmodified so this can be used inline in
59
+ * schema definitions without breaking Drizzle types.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const posts = sqliteTable("posts", {
64
+ * title: seedConfig(text("title"), f => f.lorem.sentence()),
65
+ * });
66
+ * ```
67
+ */
68
+ declare function seedConfig<T>(column: T, fn: ColumnSeedFn): T;
69
+ /**
70
+ * Attaches table-level seed config (count, per) to a Drizzle table.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * const posts = tableSeed(
75
+ * sqliteTable("posts", { ... }),
76
+ * { count: 5, per: users },
77
+ * );
78
+ * ```
79
+ */
80
+ declare function tableSeed<T extends DrizzleTable>(table: T, config: TableSeedConfig): T;
81
+ /** FK info extracted from Drizzle's internal symbol. */
82
+ type FkInfo = {
83
+ /** Column name in the current table (SQL name). */
84
+ columnName: string;
85
+ /** JS key of the column in the current table. */
86
+ columnKey: string;
87
+ /** Referenced table (Drizzle object). */
88
+ foreignTable: DrizzleTable;
89
+ /** Referenced column name (SQL name). */
90
+ foreignColumnName: string;
91
+ };
92
+ /** Extracts all inline foreign key definitions from a Drizzle table. */
93
+ declare function extractForeignKeys(table: DrizzleTable): FkInfo[];
94
+ /** Returns the JS key for the primary key column(s). Only supports single PK. */
95
+ declare function findPrimaryKeyColumn(table: DrizzleTable): {
96
+ key: string;
97
+ column: AnyColumn;
98
+ } | undefined;
99
+ /**
100
+ * Topologically sorts tables so parents are seeded before children.
101
+ * Uses Kahn's algorithm. Respects both FK and `per` dependencies.
102
+ */
103
+ declare function topologicalSort(tables: DrizzleTable[], fkMap: Map<DrizzleTable, FkInfo[]>): DrizzleTable[];
104
+ /**
105
+ * Generates seed data for all tables in a schema using Drizzle column metadata
106
+ * and the bundled `@faker-js/faker` instance.
107
+ *
108
+ * @param schema - The full Drizzle schema (`import * as schema from "./schema"`).
109
+ * @returns An engine with `generate()` and `run(db)` methods.
110
+ */
111
+ declare function createSeedEngine(schema: Record<string, unknown>): {
112
+ tables: object[];
113
+ fkMap: Map<object, FkInfo[]>;
114
+ /**
115
+ * Generate rows for all tables (or a subset).
116
+ * Returns a map of table -> rows[].
117
+ */
118
+ generate(tableOverrides?: Map<DrizzleTable, {
119
+ count: number;
120
+ }>): Map<DrizzleTable, Record<string, unknown>[]>;
121
+ /**
122
+ * Generate and insert seed data into the database.
123
+ */
124
+ run(db: Db, options?: SeedRunOptions & {
125
+ tableOverrides?: Map<DrizzleTable, {
126
+ count: number;
127
+ }>;
128
+ }): Promise<Map<DrizzleTable, Record<string, unknown>[]>>;
129
+ };
130
+ /**
131
+ * Creates a single-table seed generator for use with `db.query(table).seed(n)`.
132
+ */
133
+ declare function createSingleTableSeed(schema: Record<string, unknown>, table: DrizzleTable, count: number): {
134
+ generate: () => Map<object, Record<string, unknown>[]>;
135
+ run: (db: Db, options?: SeedRunOptions) => Promise<Map<object, Record<string, unknown>[]>>;
136
+ };
137
+ /**
138
+ * Checks if a value is a Drizzle table by looking for the IsDrizzleTable symbol.
139
+ */
140
+ declare function isTable(value: unknown): boolean;
141
+
142
+ /**
143
+ * One-liner seed: introspects the schema from the `db` instance, generates
144
+ * realistic data via the bundled `@faker-js/faker`, and inserts it.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * import { seed } from "@cfast/db/seed";
149
+ * await seed(db);
150
+ * ```
151
+ *
152
+ * @param db - A {@link Db} instance created via `createDb()`.
153
+ * @param options - Optional {@link SeedRunOptions} (e.g. `{ transcript: "./seed.sql" }`).
154
+ */
155
+ declare function seed(db: Db, options?: SeedRunOptions): Promise<void>;
156
+ /**
157
+ * A single seed entry — every row in `rows` is inserted into `table` at seed
158
+ * time. Row shape is inferred from the Drizzle table so typos in column names
159
+ * are caught by `tsc` instead of failing at runtime when `INSERT` rejects
160
+ * the statement.
161
+ *
162
+ * @typeParam TTable - The Drizzle table reference (e.g. `typeof usersTable`).
163
+ */
164
+ type SeedEntry<TTable extends DrizzleTable = DrizzleTable> = {
165
+ /**
166
+ * The Drizzle table to insert into. Must be imported from your schema
167
+ * (`import { users } from "~/db/schema"`) rather than passed as a string
168
+ * so the helper can infer row types and forward the reference to
169
+ * `db.insert()`.
170
+ */
171
+ table: TTable;
172
+ /**
173
+ * Rows to insert. The row shape is inferred from the table's
174
+ * `$inferSelect` — making a typo in a column name is a compile-time error.
175
+ *
176
+ * Entries are inserted in the order they appear, which lets you control
177
+ * foreign-key ordering just by ordering your `entries` array
178
+ * (`{ users }` before `{ posts }`, etc.).
179
+ */
180
+ rows: readonly InferRow<TTable>[];
181
+ };
182
+ /**
183
+ * Configuration passed to {@link defineSeed}.
184
+ */
185
+ type SeedConfig = {
186
+ /**
187
+ * Ordered list of seed entries. Each entry is flushed as a batched insert
188
+ * in list order, so place parent tables (users, orgs) before child tables
189
+ * (posts, memberships) that reference them via foreign keys.
190
+ */
191
+ entries: readonly SeedEntry[];
192
+ };
193
+ /**
194
+ * The compiled seed returned by {@link defineSeed}.
195
+ *
196
+ * Holds a frozen copy of the entry list so runner callers can introspect
197
+ * what would be seeded, plus a `run(db)` method that actually executes the
198
+ * inserts against a real {@link Db} instance.
199
+ */
200
+ type Seed = {
201
+ /** The frozen list of entries this seed will insert, in order. */
202
+ readonly entries: readonly SeedEntry[];
203
+ /**
204
+ * Executes every entry against the given {@link Db} in the order they were
205
+ * declared. Uses `db.unsafe()` internally so seed scripts don't need
206
+ * their own grants plumbing — seeding is a system task by definition.
207
+ *
208
+ * Entries with an empty `rows` array are skipped so callers can leave
209
+ * placeholder entries in the config without crashing the seed.
210
+ *
211
+ * @param db - A {@link Db} instance, typically created once at the top
212
+ * of a `scripts/seed.ts` file via `createDb({...})`.
213
+ */
214
+ run: (db: Db) => Promise<void>;
215
+ };
216
+ /**
217
+ * Declares a database seed in a portable, type-safe way.
218
+ *
219
+ * The canonical replacement for hand-rolled `scripts/seed.ts` files that
220
+ * called `db.mutate("tablename")` (a method that never existed) or reached
221
+ * straight into raw Drizzle. Use the scaffolded `scripts/seed.ts` in a
222
+ * `create-cfast` project for a ready-made example.
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * // scripts/seed.ts
227
+ * import { defineSeed, createDb } from "@cfast/db";
228
+ * import * as schema from "~/db/schema";
229
+ *
230
+ * const seed = defineSeed({
231
+ * entries: [
232
+ * {
233
+ * table: schema.users,
234
+ * rows: [
235
+ * { id: "u-1", email: "ada@example.com", name: "Ada" },
236
+ * { id: "u-2", email: "grace@example.com", name: "Grace" },
237
+ * ],
238
+ * },
239
+ * {
240
+ * table: schema.posts,
241
+ * rows: [
242
+ * { id: "p-1", authorId: "u-1", title: "Hello" },
243
+ * ],
244
+ * },
245
+ * ],
246
+ * });
247
+ *
248
+ * // In a worker/runner that already has a real D1 binding:
249
+ * const db = createDb({ d1, schema, grants: [], user: null });
250
+ * await seed.run(db);
251
+ * ```
252
+ *
253
+ * @param config - The {@link SeedConfig} with the ordered list of entries.
254
+ * @returns A {@link Seed} with a `.run(db)` executor.
255
+ */
256
+ declare function defineSeed(config: SeedConfig): Seed;
257
+
258
+ export { type ColumnSeedFn, type Faker, type Seed, type SeedConfig, type SeedContext, type SeedEntry, type SeedRunOptions, type TableSeedConfig, createSeedEngine, createSingleTableSeed, defineSeed, extractForeignKeys, findPrimaryKeyColumn, isTable, seed, seedConfig, tableSeed, topologicalSort };
package/dist/seed.js ADDED
@@ -0,0 +1,378 @@
1
+ // src/seed-generator.ts
2
+ import { faker } from "@faker-js/faker";
3
+ import { getTableColumns, getTableName } from "drizzle-orm";
4
+ function asTable(t) {
5
+ return t;
6
+ }
7
+ var columnSeedMap = /* @__PURE__ */ new WeakMap();
8
+ var tableSeedMap = /* @__PURE__ */ new WeakMap();
9
+ function getColumnSeedFn(col) {
10
+ const config = col.config;
11
+ if (config) {
12
+ const fn = columnSeedMap.get(config);
13
+ if (fn) return fn;
14
+ }
15
+ return columnSeedMap.get(col);
16
+ }
17
+ function seedConfig(column, fn) {
18
+ const config = column.config;
19
+ if (config && typeof config === "object") {
20
+ columnSeedMap.set(config, fn);
21
+ }
22
+ columnSeedMap.set(column, fn);
23
+ return column;
24
+ }
25
+ function tableSeed(table, config) {
26
+ tableSeedMap.set(table, config);
27
+ return table;
28
+ }
29
+ function extractForeignKeys(table) {
30
+ const fkSymbol = Object.getOwnPropertySymbols(table).find(
31
+ (s) => s.toString().includes("InlineForeignKeys")
32
+ );
33
+ if (!fkSymbol) return [];
34
+ const fks = table[fkSymbol];
35
+ const columns = getTableColumns(asTable(table));
36
+ const sqlNameToKey = /* @__PURE__ */ new Map();
37
+ for (const [key, col] of Object.entries(columns)) {
38
+ sqlNameToKey.set(col.name, key);
39
+ }
40
+ const result = [];
41
+ for (const fk of fks) {
42
+ if (typeof fk?.reference !== "function") continue;
43
+ const ref = fk.reference();
44
+ if (!ref?.foreignTable || !ref.foreignColumns?.length || !ref.columns?.length) continue;
45
+ const colSqlName = ref.columns[0].name;
46
+ result.push({
47
+ columnName: colSqlName,
48
+ columnKey: sqlNameToKey.get(colSqlName) ?? colSqlName,
49
+ foreignTable: ref.foreignTable,
50
+ foreignColumnName: ref.foreignColumns[0].name
51
+ });
52
+ }
53
+ return result;
54
+ }
55
+ function findPrimaryKeyColumn(table) {
56
+ const columns = getTableColumns(asTable(table));
57
+ for (const [key, col] of Object.entries(columns)) {
58
+ if (col.primary) {
59
+ return { key, column: col };
60
+ }
61
+ }
62
+ return void 0;
63
+ }
64
+ function generateDefaultValue(col, isPk, isNullable) {
65
+ if (isPk) return faker.string.uuid();
66
+ if (isNullable && faker.number.int({ min: 0, max: 9 }) === 0) return null;
67
+ const { dataType, columnType } = col;
68
+ if (columnType === "SQLiteBoolean" || dataType === "boolean") {
69
+ return faker.datatype.boolean();
70
+ }
71
+ if (columnType === "SQLiteTimestamp" || dataType === "date") {
72
+ return faker.date.recent();
73
+ }
74
+ if (columnType === "SQLiteReal") {
75
+ return faker.number.float({ min: 0, max: 1e3, fractionDigits: 2 });
76
+ }
77
+ if (columnType === "SQLiteInteger" || dataType === "number") {
78
+ return faker.number.int({ min: 0, max: 1e4 });
79
+ }
80
+ if (columnType === "SQLiteText" || dataType === "string") {
81
+ return faker.lorem.words(3);
82
+ }
83
+ if (dataType === "buffer") {
84
+ return faker.string.alphanumeric(16);
85
+ }
86
+ return faker.lorem.words(2);
87
+ }
88
+ var AUTH_TABLE_NAME = "users";
89
+ function isAuthUsersTable(table) {
90
+ return getTableName(asTable(table)) === AUTH_TABLE_NAME;
91
+ }
92
+ function generateAuthEmail(index) {
93
+ const roles = ["admin", "user", "editor", "viewer", "moderator"];
94
+ if (index < roles.length) {
95
+ return `${roles[index]}@example.com`;
96
+ }
97
+ return faker.internet.email().toLowerCase();
98
+ }
99
+ function topologicalSort(tables, fkMap) {
100
+ const tableSet = new Set(tables);
101
+ const inDegree = /* @__PURE__ */ new Map();
102
+ const dependents = /* @__PURE__ */ new Map();
103
+ for (const t of tables) {
104
+ if (!inDegree.has(t)) inDegree.set(t, 0);
105
+ if (!dependents.has(t)) dependents.set(t, /* @__PURE__ */ new Set());
106
+ }
107
+ for (const t of tables) {
108
+ const fks = fkMap.get(t) ?? [];
109
+ const deps = /* @__PURE__ */ new Set();
110
+ for (const fk of fks) {
111
+ if (tableSet.has(fk.foreignTable) && fk.foreignTable !== t) {
112
+ deps.add(fk.foreignTable);
113
+ }
114
+ }
115
+ const tConfig = tableSeedMap.get(t);
116
+ if (tConfig?.per && tableSet.has(tConfig.per) && tConfig.per !== t) {
117
+ deps.add(tConfig.per);
118
+ }
119
+ inDegree.set(t, deps.size);
120
+ for (const dep of deps) {
121
+ if (!dependents.has(dep)) dependents.set(dep, /* @__PURE__ */ new Set());
122
+ dependents.get(dep).add(t);
123
+ }
124
+ }
125
+ const queue = [];
126
+ for (const [t, deg] of inDegree) {
127
+ if (deg === 0) queue.push(t);
128
+ }
129
+ const sorted = [];
130
+ while (queue.length > 0) {
131
+ const current = queue.shift();
132
+ sorted.push(current);
133
+ for (const dep of dependents.get(current) ?? []) {
134
+ const newDeg = (inDegree.get(dep) ?? 1) - 1;
135
+ inDegree.set(dep, newDeg);
136
+ if (newDeg === 0) queue.push(dep);
137
+ }
138
+ }
139
+ for (const t of tables) {
140
+ if (!sorted.includes(t)) sorted.push(t);
141
+ }
142
+ return sorted;
143
+ }
144
+ function getDeduplicationKeys(_table, fks) {
145
+ if (fks.length >= 2) {
146
+ return fks.map((f) => f.columnKey);
147
+ }
148
+ return null;
149
+ }
150
+ function createSeedEngine(schema) {
151
+ const tables = [];
152
+ for (const value of Object.values(schema)) {
153
+ if (isTable(value)) {
154
+ tables.push(value);
155
+ }
156
+ }
157
+ const fkMap = /* @__PURE__ */ new Map();
158
+ for (const table of tables) {
159
+ fkMap.set(table, extractForeignKeys(table));
160
+ }
161
+ const sorted = topologicalSort(tables, fkMap);
162
+ return {
163
+ tables: sorted,
164
+ fkMap,
165
+ /**
166
+ * Generate rows for all tables (or a subset).
167
+ * Returns a map of table -> rows[].
168
+ */
169
+ generate(tableOverrides) {
170
+ const generated = /* @__PURE__ */ new Map();
171
+ for (const table of sorted) {
172
+ const config = tableOverrides?.get(table) ?? tableSeedMap.get(table);
173
+ const fks = fkMap.get(table) ?? [];
174
+ const columns = getTableColumns(asTable(table));
175
+ const pk = findPrimaryKeyColumn(table);
176
+ const isAuth = isAuthUsersTable(table);
177
+ const dedupKeys = getDeduplicationKeys(table, fks);
178
+ const count = config?.count ?? 10;
179
+ const perTable = config?.per;
180
+ const parentRows = perTable ? generated.get(perTable) ?? [] : [void 0];
181
+ const allRows = [];
182
+ const seenCombos = /* @__PURE__ */ new Set();
183
+ let globalIndex = 0;
184
+ for (const parentRow of parentRows) {
185
+ for (let i = 0; i < count; i++) {
186
+ const ctx = {
187
+ parent: parentRow,
188
+ ref: (t) => {
189
+ const rows = generated.get(t);
190
+ if (!rows || rows.length === 0) {
191
+ throw new Error(
192
+ `seedConfig ctx.ref(${getTableName(asTable(t))}): no rows generated yet`
193
+ );
194
+ }
195
+ return rows[faker.number.int({ min: 0, max: rows.length - 1 })];
196
+ },
197
+ index: i,
198
+ all: (t) => generated.get(t) ?? []
199
+ };
200
+ const row = {};
201
+ for (const [key, col] of Object.entries(columns)) {
202
+ const colObj = col;
203
+ const isPk = pk?.key === key;
204
+ const isNullable = !colObj.notNull;
205
+ const customSeedFn = getColumnSeedFn(colObj);
206
+ if (customSeedFn) {
207
+ row[key] = customSeedFn(faker, ctx);
208
+ continue;
209
+ }
210
+ const fk = fks.find((f) => f.columnKey === key);
211
+ if (fk) {
212
+ if (perTable && parentRow && getTableName(asTable(fk.foreignTable)) === getTableName(asTable(perTable))) {
213
+ const foreignColumns = getTableColumns(asTable(fk.foreignTable));
214
+ const foreignKey = Object.entries(foreignColumns).find(
215
+ ([, c]) => c.name === fk.foreignColumnName
216
+ );
217
+ if (foreignKey) {
218
+ row[key] = parentRow[foreignKey[0]];
219
+ continue;
220
+ }
221
+ }
222
+ const refRows = generated.get(fk.foreignTable);
223
+ if (refRows && refRows.length > 0) {
224
+ const randomRef = refRows[faker.number.int({ min: 0, max: refRows.length - 1 })];
225
+ const foreignColumns = getTableColumns(asTable(fk.foreignTable));
226
+ const foreignKey = Object.entries(foreignColumns).find(
227
+ ([, c]) => c.name === fk.foreignColumnName
228
+ );
229
+ if (foreignKey) {
230
+ row[key] = randomRef[foreignKey[0]];
231
+ continue;
232
+ }
233
+ }
234
+ }
235
+ if (isAuth && key === "email") {
236
+ row[key] = generateAuthEmail(globalIndex);
237
+ continue;
238
+ }
239
+ if (isAuth && key === "name") {
240
+ row[key] = faker.person.fullName();
241
+ continue;
242
+ }
243
+ const colAny = colObj;
244
+ if (colAny.defaultFn || colAny.onUpdateFn) {
245
+ continue;
246
+ }
247
+ if (colAny.hasDefault && colAny.default !== void 0) {
248
+ continue;
249
+ }
250
+ row[key] = generateDefaultValue(colObj, isPk, isNullable);
251
+ }
252
+ if (dedupKeys) {
253
+ const comboKey = dedupKeys.map((k) => String(row[k])).join(":");
254
+ if (seenCombos.has(comboKey)) continue;
255
+ seenCombos.add(comboKey);
256
+ }
257
+ allRows.push(row);
258
+ globalIndex++;
259
+ }
260
+ }
261
+ generated.set(table, allRows);
262
+ }
263
+ return generated;
264
+ },
265
+ /**
266
+ * Generate and insert seed data into the database.
267
+ */
268
+ async run(db, options) {
269
+ const generated = this.generate(options?.tableOverrides);
270
+ const unsafeDb = db.unsafe();
271
+ const transcriptLines = [];
272
+ for (const table of sorted) {
273
+ const rows = generated.get(table);
274
+ if (!rows || rows.length === 0) continue;
275
+ const tableName = getTableName(asTable(table));
276
+ if (options?.transcript) {
277
+ for (const row of rows) {
278
+ const colNames = Object.keys(row);
279
+ const values = colNames.map((k) => {
280
+ const v = row[k];
281
+ if (v === null) return "NULL";
282
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
283
+ if (v instanceof Date) return `'${v.toISOString()}'`;
284
+ return `'${String(v).replace(/'/g, "''")}'`;
285
+ });
286
+ transcriptLines.push(
287
+ `INSERT INTO "${tableName}" (${colNames.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")});`
288
+ );
289
+ }
290
+ }
291
+ const ops = rows.map(
292
+ (row) => unsafeDb.insert(table).values(row)
293
+ );
294
+ if (ops.length === 1) {
295
+ await ops[0].run({});
296
+ } else {
297
+ await unsafeDb.batch(ops).run({});
298
+ }
299
+ }
300
+ if (options?.transcript && transcriptLines.length > 0) {
301
+ try {
302
+ const fs = await new Function(
303
+ 'return import("node:fs/promises")'
304
+ )();
305
+ await fs.writeFile(
306
+ options.transcript,
307
+ transcriptLines.join("\n") + "\n",
308
+ "utf-8"
309
+ );
310
+ } catch {
311
+ }
312
+ }
313
+ return generated;
314
+ }
315
+ };
316
+ }
317
+ function createSingleTableSeed(schema, table, count) {
318
+ const engine = createSeedEngine(schema);
319
+ const overrides = /* @__PURE__ */ new Map();
320
+ overrides.set(table, { count });
321
+ for (const t of engine.tables) {
322
+ if (t !== table && !overrides.has(t)) {
323
+ overrides.set(t, { count: 0 });
324
+ }
325
+ }
326
+ return {
327
+ generate: () => engine.generate(overrides),
328
+ run: (db, options) => engine.run(db, { ...options, tableOverrides: overrides })
329
+ };
330
+ }
331
+ function isTable(value) {
332
+ if (!value || typeof value !== "object") return false;
333
+ const symbols = Object.getOwnPropertySymbols(value);
334
+ return symbols.some((s) => s.toString().includes("IsDrizzleTable"));
335
+ }
336
+
337
+ // src/seed.ts
338
+ async function seed(db, options) {
339
+ const engine = createSeedEngine(db._schema);
340
+ await engine.run(db, options);
341
+ }
342
+ function defineSeed(config) {
343
+ const entries = Object.freeze(
344
+ config.entries.map((entry) => ({
345
+ table: entry.table,
346
+ rows: Object.freeze([...entry.rows])
347
+ }))
348
+ );
349
+ return {
350
+ entries,
351
+ async run(db) {
352
+ const unsafeDb = db.unsafe();
353
+ for (const entry of entries) {
354
+ if (entry.rows.length === 0) continue;
355
+ const ops = entry.rows.map(
356
+ (row) => unsafeDb.insert(entry.table).values(row)
357
+ );
358
+ if (ops.length === 1) {
359
+ await ops[0].run({});
360
+ } else {
361
+ await unsafeDb.batch(ops).run({});
362
+ }
363
+ }
364
+ }
365
+ };
366
+ }
367
+ export {
368
+ createSeedEngine,
369
+ createSingleTableSeed,
370
+ defineSeed,
371
+ extractForeignKeys,
372
+ findPrimaryKeyColumn,
373
+ isTable,
374
+ seed,
375
+ seedConfig,
376
+ tableSeed,
377
+ topologicalSort
378
+ };