@cfast/db 0.5.0 → 0.7.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,328 @@
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
+ import { SQLiteColumnBuilderBase, SQLiteTableExtraConfigValue } from 'drizzle-orm/sqlite-core';
5
+ import { BuildColumns } from 'drizzle-orm/column-builder';
6
+ import { SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core/table';
7
+
8
+ /**
9
+ * Schema-driven seed generator for `@cfast/db`.
10
+ *
11
+ * Introspects Drizzle schema metadata (column types, foreign keys, primary keys)
12
+ * to auto-generate realistic test data using the bundled `@faker-js/faker`.
13
+ * Supports:
14
+ *
15
+ * - Column-level `.seed()` overrides via `seedConfig()` wrapper
16
+ * - Table-level `.seed()` overrides via `tableSeed()` wrapper (count, per)
17
+ * - Automatic FK resolution from generated parent rows
18
+ * - `per` relational generation (N children per parent row)
19
+ * - `ctx` API for parent access, ref, index, and all
20
+ * - Topological sort for correct insert order
21
+ * - Many-to-many deduplication
22
+ * - Auth table detection for realistic emails
23
+ * - SQL transcript generation
24
+ *
25
+ * @module seed-generator
26
+ */
27
+
28
+ type AnyColumn = Column<any, any, any>;
29
+ /** Faker instance type -- matches `@faker-js/faker`'s default export. */
30
+ type Faker = any;
31
+ /**
32
+ * Context passed to column-level seed functions as the second argument.
33
+ */
34
+ type SeedContext = {
35
+ /** The parent row when `per` is used on the table. `undefined` for root tables. */
36
+ parent: Record<string, unknown> | undefined;
37
+ /** Pick a random existing row from any table already seeded. */
38
+ ref: (table: DrizzleTable) => Record<string, unknown>;
39
+ /** Zero-based position within the current batch (per-parent or global). */
40
+ index: number;
41
+ /** All generated rows for the given table (available after that table is seeded). */
42
+ all: (table: DrizzleTable) => Record<string, unknown>[];
43
+ };
44
+ /** Column-level seed function. Receives faker instance and optional context. */
45
+ type ColumnSeedFn = (faker: Faker, ctx: SeedContext) => unknown;
46
+ /** Table-level seed config attached via `tableSeed()`. */
47
+ type TableSeedConfig = {
48
+ /** Total count (or per-parent count when `per` is set). */
49
+ count: number;
50
+ /** Generate `count` rows per row in this parent table. */
51
+ per?: DrizzleTable;
52
+ };
53
+ /** Options for `db.seed().run()`. */
54
+ type SeedRunOptions = {
55
+ /** If provided, write the equivalent SQL INSERT statements to this path. */
56
+ transcript?: string;
57
+ };
58
+ /**
59
+ * Stores column-level seed functions keyed by the column builder's `config`
60
+ * object. Drizzle shares the same `config` reference between the builder
61
+ * (what the user passes to `sqliteTable(...)`) and the built column (what
62
+ * `getTableColumns()` returns), so we can look up the seed fn from either
63
+ * side. This avoids the builder-vs-column identity mismatch.
64
+ *
65
+ * Exported so the `.seed()` prototype patches in `seed.ts` can write to
66
+ * the same registries. Application code should use `.seed()` or
67
+ * `seedConfig()`/`tableSeed()` rather than accessing these directly.
68
+ */
69
+ declare const columnSeedMap: WeakMap<object, ColumnSeedFn>;
70
+ declare const tableSeedMap: WeakMap<object, TableSeedConfig>;
71
+ /**
72
+ * Attaches a seed generator function to a Drizzle column.
73
+ *
74
+ * The column object is returned unmodified so this can be used inline in
75
+ * schema definitions without breaking Drizzle types.
76
+ *
77
+ * @deprecated Use the `.seed()` method on column builders instead:
78
+ * ```ts
79
+ * text("title").seed(f => f.lorem.sentence())
80
+ * ```
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const posts = sqliteTable("posts", {
85
+ * title: seedConfig(text("title"), f => f.lorem.sentence()),
86
+ * });
87
+ * ```
88
+ */
89
+ declare function seedConfig<T>(column: T, fn: ColumnSeedFn): T;
90
+ /**
91
+ * Attaches table-level seed config (count, per) to a Drizzle table.
92
+ *
93
+ * @deprecated Use `table()` from `@cfast/db/seed` with `.seed()` instead:
94
+ * ```ts
95
+ * import { table } from "@cfast/db/seed";
96
+ * const posts = table("posts", { ... }).seed({ count: 5, per: users });
97
+ * ```
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const posts = tableSeed(
102
+ * sqliteTable("posts", { ... }),
103
+ * { count: 5, per: users },
104
+ * );
105
+ * ```
106
+ */
107
+ declare function tableSeed<T extends DrizzleTable>(table: T, config: TableSeedConfig): T;
108
+ /** FK info extracted from Drizzle's internal symbol. */
109
+ type FkInfo = {
110
+ /** Column name in the current table (SQL name). */
111
+ columnName: string;
112
+ /** JS key of the column in the current table. */
113
+ columnKey: string;
114
+ /** Referenced table (Drizzle object). */
115
+ foreignTable: DrizzleTable;
116
+ /** Referenced column name (SQL name). */
117
+ foreignColumnName: string;
118
+ };
119
+ /** Extracts all inline foreign key definitions from a Drizzle table. */
120
+ declare function extractForeignKeys(table: DrizzleTable): FkInfo[];
121
+ /** Returns the JS key for the primary key column(s). Only supports single PK. */
122
+ declare function findPrimaryKeyColumn(table: DrizzleTable): {
123
+ key: string;
124
+ column: AnyColumn;
125
+ } | undefined;
126
+ /**
127
+ * Topologically sorts tables so parents are seeded before children.
128
+ * Uses Kahn's algorithm. Respects both FK and `per` dependencies.
129
+ */
130
+ declare function topologicalSort(tables: DrizzleTable[], fkMap: Map<DrizzleTable, FkInfo[]>): DrizzleTable[];
131
+ /**
132
+ * Generates seed data for all tables in a schema using Drizzle column metadata
133
+ * and the bundled `@faker-js/faker` instance.
134
+ *
135
+ * @param schema - The full Drizzle schema (`import * as schema from "./schema"`).
136
+ * @returns An engine with `generate()` and `run(db)` methods.
137
+ */
138
+ declare function createSeedEngine(schema: Record<string, unknown>): {
139
+ tables: object[];
140
+ fkMap: Map<object, FkInfo[]>;
141
+ /**
142
+ * Generate rows for all tables (or a subset).
143
+ * Returns a map of table -> rows[].
144
+ */
145
+ generate(tableOverrides?: Map<DrizzleTable, {
146
+ count: number;
147
+ }>): Map<DrizzleTable, Record<string, unknown>[]>;
148
+ /**
149
+ * Generate and insert seed data into the database.
150
+ */
151
+ run(db: Db, options?: SeedRunOptions & {
152
+ tableOverrides?: Map<DrizzleTable, {
153
+ count: number;
154
+ }>;
155
+ }): Promise<Map<DrizzleTable, Record<string, unknown>[]>>;
156
+ };
157
+ /**
158
+ * Creates a single-table seed generator for use with `db.query(table).seed(n)`.
159
+ */
160
+ declare function createSingleTableSeed(schema: Record<string, unknown>, table: DrizzleTable, count: number): {
161
+ generate: () => Map<object, Record<string, unknown>[]>;
162
+ run: (db: Db, options?: SeedRunOptions) => Promise<Map<object, Record<string, unknown>[]>>;
163
+ };
164
+ /**
165
+ * Checks if a value is a Drizzle table by looking for the IsDrizzleTable symbol.
166
+ */
167
+ declare function isTable(value: unknown): boolean;
168
+
169
+ declare module "drizzle-orm/sqlite-core" {
170
+ interface SQLiteColumnBuilder<T, TRuntimeConfig, TTypeConfig, TExtraConfig> {
171
+ /**
172
+ * Attaches a seed generator function to this column.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * title: text("title").seed(f => f.lorem.sentence()),
177
+ * ```
178
+ */
179
+ seed(fn: ColumnSeedFn): this;
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Type representing a Drizzle table with an added `.seed()` method.
185
+ * The `.seed()` method returns the same table reference for chaining.
186
+ */
187
+ type SeedableTable<T> = T & {
188
+ seed(config: TableSeedConfig): SeedableTable<T>;
189
+ };
190
+ /**
191
+ * Creates a SQLite table with a `.seed()` method for configuring
192
+ * seed generation. Drop-in replacement for `sqliteTable()` from
193
+ * `drizzle-orm/sqlite-core`.
194
+ *
195
+ * @example
196
+ * ```ts
197
+ * import { table } from "@cfast/db/seed";
198
+ *
199
+ * const posts = table("posts", {
200
+ * title: text("title").seed(f => f.lorem.sentence()),
201
+ * authorId: text("author_id").references(() => users.id),
202
+ * }).seed({ count: 5, per: users });
203
+ * ```
204
+ */
205
+ declare function table<TTableName extends string, TColumnsMap extends Record<string, SQLiteColumnBuilderBase>>(name: TTableName, columns: TColumnsMap, extraConfig?: (self: BuildColumns<TTableName, TColumnsMap, "sqlite">) => SQLiteTableExtraConfigValue[]): SeedableTable<SQLiteTableWithColumns<{
206
+ name: TTableName;
207
+ schema: undefined;
208
+ columns: BuildColumns<TTableName, TColumnsMap, "sqlite">;
209
+ dialect: "sqlite";
210
+ }>>;
211
+
212
+ /**
213
+ * One-liner seed: introspects the schema from the `db` instance, generates
214
+ * realistic data via the bundled `@faker-js/faker`, and inserts it.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * import { seed } from "@cfast/db/seed";
219
+ * await seed(db);
220
+ * ```
221
+ *
222
+ * @param db - A {@link Db} instance created via `createDb()`.
223
+ * @param options - Optional {@link SeedRunOptions} (e.g. `{ transcript: "./seed.sql" }`).
224
+ */
225
+ declare function seed(db: Db, options?: SeedRunOptions): Promise<void>;
226
+ /**
227
+ * A single seed entry -- every row in `rows` is inserted into `table` at seed
228
+ * time. Row shape is inferred from the Drizzle table so typos in column names
229
+ * are caught by `tsc` instead of failing at runtime when `INSERT` rejects
230
+ * the statement.
231
+ *
232
+ * @typeParam TTable - The Drizzle table reference (e.g. `typeof usersTable`).
233
+ */
234
+ type SeedEntry<TTable extends DrizzleTable = DrizzleTable> = {
235
+ /**
236
+ * The Drizzle table to insert into. Must be imported from your schema
237
+ * (`import { users } from "~/db/schema"`) rather than passed as a string
238
+ * so the helper can infer row types and forward the reference to
239
+ * `db.insert()`.
240
+ */
241
+ table: TTable;
242
+ /**
243
+ * Rows to insert. The row shape is inferred from the table's
244
+ * `$inferSelect` -- making a typo in a column name is a compile-time error.
245
+ *
246
+ * Entries are inserted in the order they appear, which lets you control
247
+ * foreign-key ordering just by ordering your `entries` array
248
+ * (`{ users }` before `{ posts }`, etc.).
249
+ */
250
+ rows: readonly InferRow<TTable>[];
251
+ };
252
+ /**
253
+ * Configuration passed to {@link defineSeed}.
254
+ */
255
+ type SeedConfig = {
256
+ /**
257
+ * Ordered list of seed entries. Each entry is flushed as a batched insert
258
+ * in list order, so place parent tables (users, orgs) before child tables
259
+ * (posts, memberships) that reference them via foreign keys.
260
+ */
261
+ entries: readonly SeedEntry[];
262
+ };
263
+ /**
264
+ * The compiled seed returned by {@link defineSeed}.
265
+ *
266
+ * Holds a frozen copy of the entry list so runner callers can introspect
267
+ * what would be seeded, plus a `run(db)` method that actually executes the
268
+ * inserts against a real {@link Db} instance.
269
+ */
270
+ type Seed = {
271
+ /** The frozen list of entries this seed will insert, in order. */
272
+ readonly entries: readonly SeedEntry[];
273
+ /**
274
+ * Executes every entry against the given {@link Db} in the order they were
275
+ * declared. Uses `db.unsafe()` internally so seed scripts don't need
276
+ * their own grants plumbing -- seeding is a system task by definition.
277
+ *
278
+ * Entries with an empty `rows` array are skipped so callers can leave
279
+ * placeholder entries in the config without crashing the seed.
280
+ *
281
+ * @param db - A {@link Db} instance, typically created once at the top
282
+ * of a `scripts/seed.ts` file via `createDb({...})`.
283
+ */
284
+ run: (db: Db) => Promise<void>;
285
+ };
286
+ /**
287
+ * Declares a database seed in a portable, type-safe way.
288
+ *
289
+ * The canonical replacement for hand-rolled `scripts/seed.ts` files that
290
+ * called `db.mutate("tablename")` (a method that never existed) or reached
291
+ * straight into raw Drizzle. Use the scaffolded `scripts/seed.ts` in a
292
+ * `create-cfast` project for a ready-made example.
293
+ *
294
+ * @example
295
+ * ```ts
296
+ * // scripts/seed.ts
297
+ * import { defineSeed, createDb } from "@cfast/db";
298
+ * import * as schema from "~/db/schema";
299
+ *
300
+ * const seed = defineSeed({
301
+ * entries: [
302
+ * {
303
+ * table: schema.users,
304
+ * rows: [
305
+ * { id: "u-1", email: "ada@example.com", name: "Ada" },
306
+ * { id: "u-2", email: "grace@example.com", name: "Grace" },
307
+ * ],
308
+ * },
309
+ * {
310
+ * table: schema.posts,
311
+ * rows: [
312
+ * { id: "p-1", authorId: "u-1", title: "Hello" },
313
+ * ],
314
+ * },
315
+ * ],
316
+ * });
317
+ *
318
+ * // In a worker/runner that already has a real D1 binding:
319
+ * const db = createDb({ d1, schema, grants: [], user: null });
320
+ * await seed.run(db);
321
+ * ```
322
+ *
323
+ * @param config - The {@link SeedConfig} with the ordered list of entries.
324
+ * @returns A {@link Seed} with a `.run(db)` executor.
325
+ */
326
+ declare function defineSeed(config: SeedConfig): Seed;
327
+
328
+ export { type ColumnSeedFn, type Faker, type Seed, type SeedConfig, type SeedContext, type SeedEntry, type SeedRunOptions, type SeedableTable, type TableSeedConfig, columnSeedMap, createSeedEngine, createSingleTableSeed, defineSeed, extractForeignKeys, findPrimaryKeyColumn, isTable, seed, seedConfig, table, tableSeed, tableSeedMap, topologicalSort };