@cfast/db 0.6.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 CHANGED
@@ -1,6 +1,9 @@
1
1
  import { DrizzleTable } from '@cfast/permissions';
2
2
  import { a as Db, i as InferRow } from './types-FUFR36h1.js';
3
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';
4
7
 
5
8
  /**
6
9
  * Schema-driven seed generator for `@cfast/db`.
@@ -52,12 +55,30 @@ type SeedRunOptions = {
52
55
  /** If provided, write the equivalent SQL INSERT statements to this path. */
53
56
  transcript?: string;
54
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>;
55
71
  /**
56
72
  * Attaches a seed generator function to a Drizzle column.
57
73
  *
58
74
  * The column object is returned unmodified so this can be used inline in
59
75
  * schema definitions without breaking Drizzle types.
60
76
  *
77
+ * @deprecated Use the `.seed()` method on column builders instead:
78
+ * ```ts
79
+ * text("title").seed(f => f.lorem.sentence())
80
+ * ```
81
+ *
61
82
  * @example
62
83
  * ```ts
63
84
  * const posts = sqliteTable("posts", {
@@ -69,6 +90,12 @@ declare function seedConfig<T>(column: T, fn: ColumnSeedFn): T;
69
90
  /**
70
91
  * Attaches table-level seed config (count, per) to a Drizzle table.
71
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
+ *
72
99
  * @example
73
100
  * ```ts
74
101
  * const posts = tableSeed(
@@ -139,6 +166,49 @@ declare function createSingleTableSeed(schema: Record<string, unknown>, table: D
139
166
  */
140
167
  declare function isTable(value: unknown): boolean;
141
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
+
142
212
  /**
143
213
  * One-liner seed: introspects the schema from the `db` instance, generates
144
214
  * realistic data via the bundled `@faker-js/faker`, and inserts it.
@@ -154,7 +224,7 @@ declare function isTable(value: unknown): boolean;
154
224
  */
155
225
  declare function seed(db: Db, options?: SeedRunOptions): Promise<void>;
156
226
  /**
157
- * A single seed entry every row in `rows` is inserted into `table` at seed
227
+ * A single seed entry -- every row in `rows` is inserted into `table` at seed
158
228
  * time. Row shape is inferred from the Drizzle table so typos in column names
159
229
  * are caught by `tsc` instead of failing at runtime when `INSERT` rejects
160
230
  * the statement.
@@ -171,7 +241,7 @@ type SeedEntry<TTable extends DrizzleTable = DrizzleTable> = {
171
241
  table: TTable;
172
242
  /**
173
243
  * 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.
244
+ * `$inferSelect` -- making a typo in a column name is a compile-time error.
175
245
  *
176
246
  * Entries are inserted in the order they appear, which lets you control
177
247
  * foreign-key ordering just by ordering your `entries` array
@@ -203,7 +273,7 @@ type Seed = {
203
273
  /**
204
274
  * Executes every entry against the given {@link Db} in the order they were
205
275
  * declared. Uses `db.unsafe()` internally so seed scripts don't need
206
- * their own grants plumbing seeding is a system task by definition.
276
+ * their own grants plumbing -- seeding is a system task by definition.
207
277
  *
208
278
  * Entries with an empty `rows` array are skipped so callers can leave
209
279
  * placeholder entries in the config without crashing the seed.
@@ -255,4 +325,4 @@ type Seed = {
255
325
  */
256
326
  declare function defineSeed(config: SeedConfig): Seed;
257
327
 
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 };
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 };
package/dist/seed.js CHANGED
@@ -22,17 +22,17 @@ function seedConfig(column, fn) {
22
22
  columnSeedMap.set(column, fn);
23
23
  return column;
24
24
  }
25
- function tableSeed(table, config) {
26
- tableSeedMap.set(table, config);
27
- return table;
25
+ function tableSeed(table2, config) {
26
+ tableSeedMap.set(table2, config);
27
+ return table2;
28
28
  }
29
- function extractForeignKeys(table) {
30
- const fkSymbol = Object.getOwnPropertySymbols(table).find(
29
+ function extractForeignKeys(table2) {
30
+ const fkSymbol = Object.getOwnPropertySymbols(table2).find(
31
31
  (s) => s.toString().includes("InlineForeignKeys")
32
32
  );
33
33
  if (!fkSymbol) return [];
34
- const fks = table[fkSymbol];
35
- const columns = getTableColumns(asTable(table));
34
+ const fks = table2[fkSymbol];
35
+ const columns = getTableColumns(asTable(table2));
36
36
  const sqlNameToKey = /* @__PURE__ */ new Map();
37
37
  for (const [key, col] of Object.entries(columns)) {
38
38
  sqlNameToKey.set(col.name, key);
@@ -52,8 +52,8 @@ function extractForeignKeys(table) {
52
52
  }
53
53
  return result;
54
54
  }
55
- function findPrimaryKeyColumn(table) {
56
- const columns = getTableColumns(asTable(table));
55
+ function findPrimaryKeyColumn(table2) {
56
+ const columns = getTableColumns(asTable(table2));
57
57
  for (const [key, col] of Object.entries(columns)) {
58
58
  if (col.primary) {
59
59
  return { key, column: col };
@@ -86,8 +86,8 @@ function generateDefaultValue(col, isPk, isNullable) {
86
86
  return faker.lorem.words(2);
87
87
  }
88
88
  var AUTH_TABLE_NAME = "users";
89
- function isAuthUsersTable(table) {
90
- return getTableName(asTable(table)) === AUTH_TABLE_NAME;
89
+ function isAuthUsersTable(table2) {
90
+ return getTableName(asTable(table2)) === AUTH_TABLE_NAME;
91
91
  }
92
92
  function generateAuthEmail(index) {
93
93
  const roles = ["admin", "user", "editor", "viewer", "moderator"];
@@ -155,8 +155,8 @@ function createSeedEngine(schema) {
155
155
  }
156
156
  }
157
157
  const fkMap = /* @__PURE__ */ new Map();
158
- for (const table of tables) {
159
- fkMap.set(table, extractForeignKeys(table));
158
+ for (const table2 of tables) {
159
+ fkMap.set(table2, extractForeignKeys(table2));
160
160
  }
161
161
  const sorted = topologicalSort(tables, fkMap);
162
162
  return {
@@ -168,13 +168,13 @@ function createSeedEngine(schema) {
168
168
  */
169
169
  generate(tableOverrides) {
170
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);
171
+ for (const table2 of sorted) {
172
+ const config = tableOverrides?.get(table2) ?? tableSeedMap.get(table2);
173
+ const fks = fkMap.get(table2) ?? [];
174
+ const columns = getTableColumns(asTable(table2));
175
+ const pk = findPrimaryKeyColumn(table2);
176
+ const isAuth = isAuthUsersTable(table2);
177
+ const dedupKeys = getDeduplicationKeys(table2, fks);
178
178
  const count = config?.count ?? 10;
179
179
  const perTable = config?.per;
180
180
  const parentRows = perTable ? generated.get(perTable) ?? [] : [void 0];
@@ -258,7 +258,7 @@ function createSeedEngine(schema) {
258
258
  globalIndex++;
259
259
  }
260
260
  }
261
- generated.set(table, allRows);
261
+ generated.set(table2, allRows);
262
262
  }
263
263
  return generated;
264
264
  },
@@ -269,10 +269,10 @@ function createSeedEngine(schema) {
269
269
  const generated = this.generate(options?.tableOverrides);
270
270
  const unsafeDb = db.unsafe();
271
271
  const transcriptLines = [];
272
- for (const table of sorted) {
273
- const rows = generated.get(table);
272
+ for (const table2 of sorted) {
273
+ const rows = generated.get(table2);
274
274
  if (!rows || rows.length === 0) continue;
275
- const tableName = getTableName(asTable(table));
275
+ const tableName = getTableName(asTable(table2));
276
276
  if (options?.transcript) {
277
277
  for (const row of rows) {
278
278
  const colNames = Object.keys(row);
@@ -289,7 +289,7 @@ function createSeedEngine(schema) {
289
289
  }
290
290
  }
291
291
  const ops = rows.map(
292
- (row) => unsafeDb.insert(table).values(row)
292
+ (row) => unsafeDb.insert(table2).values(row)
293
293
  );
294
294
  if (ops.length === 1) {
295
295
  await ops[0].run({});
@@ -314,12 +314,12 @@ function createSeedEngine(schema) {
314
314
  }
315
315
  };
316
316
  }
317
- function createSingleTableSeed(schema, table, count) {
317
+ function createSingleTableSeed(schema, table2, count) {
318
318
  const engine = createSeedEngine(schema);
319
319
  const overrides = /* @__PURE__ */ new Map();
320
- overrides.set(table, { count });
320
+ overrides.set(table2, { count });
321
321
  for (const t of engine.tables) {
322
- if (t !== table && !overrides.has(t)) {
322
+ if (t !== table2 && !overrides.has(t)) {
323
323
  overrides.set(t, { count: 0 });
324
324
  }
325
325
  }
@@ -335,6 +335,24 @@ function isTable(value) {
335
335
  }
336
336
 
337
337
  // src/seed.ts
338
+ import { SQLiteColumnBuilder } from "drizzle-orm/sqlite-core/columns/common";
339
+ import { sqliteTable } from "drizzle-orm/sqlite-core";
340
+ SQLiteColumnBuilder.prototype.seed = function(fn) {
341
+ const config = this.config;
342
+ if (config && typeof config === "object") {
343
+ columnSeedMap.set(config, fn);
344
+ }
345
+ columnSeedMap.set(this, fn);
346
+ return this;
347
+ };
348
+ function table(name, columns, extraConfig) {
349
+ const t = sqliteTable(name, columns, extraConfig);
350
+ t.seed = (config) => {
351
+ tableSeedMap.set(t, config);
352
+ return t;
353
+ };
354
+ return t;
355
+ }
338
356
  async function seed(db, options) {
339
357
  const engine = createSeedEngine(db._schema);
340
358
  await engine.run(db, options);
@@ -365,6 +383,7 @@ function defineSeed(config) {
365
383
  };
366
384
  }
367
385
  export {
386
+ columnSeedMap,
368
387
  createSeedEngine,
369
388
  createSingleTableSeed,
370
389
  defineSeed,
@@ -373,6 +392,8 @@ export {
373
392
  isTable,
374
393
  seed,
375
394
  seedConfig,
395
+ table,
376
396
  tableSeed,
397
+ tableSeedMap,
377
398
  topologicalSort
378
399
  };
package/llms.txt CHANGED
@@ -478,22 +478,23 @@ auto-generate realistic test data using the bundled faker. Handles FK
478
478
  resolution, topological ordering, `per` relational generation,
479
479
  many-to-many deduplication, and auth table detection.
480
480
 
481
- #### Column-level seed overrides
481
+ #### Column-level `.seed()` method
482
482
 
483
- `seedConfig(column, fn)` attaches a custom generator to a column:
483
+ Chain `.seed(fn)` on any column builder to attach a custom generator:
484
484
 
485
485
  ```typescript
486
- import { seedConfig, tableSeed } from "@cfast/db/seed";
486
+ import { table } from "@cfast/db/seed";
487
+ import { text, integer, real } from "drizzle-orm/sqlite-core";
487
488
 
488
- const posts = sqliteTable("posts", {
489
+ const posts = table("posts", {
489
490
  id: text("id").primaryKey(),
490
- title: seedConfig(text("title").notNull(), f => f.lorem.sentence()),
491
- content: seedConfig(text("content"), f => f.lorem.paragraphs(3)),
491
+ title: text("title").notNull().seed(f => f.lorem.sentence()),
492
+ content: text("content").seed(f => f.lorem.paragraphs(3)),
492
493
  authorId: text("author_id").references(() => users.id),
493
- });
494
+ }).seed({ count: 10 });
494
495
  ```
495
496
 
496
- Fields without `seedConfig()` are auto-inferred from column type:
497
+ Fields without `.seed()` are auto-inferred from column type:
497
498
  - `text` -> `faker.lorem.words()`
498
499
  - `integer`/`real` -> `faker.number.int()`/`faker.number.float()`
499
500
  - `timestamp` -> `faker.date.recent()`
@@ -503,17 +504,37 @@ Fields without `seedConfig()` are auto-inferred from column type:
503
504
  - Nullable -> occasionally `null` (~10%)
504
505
  - Columns with `$defaultFn` or static defaults are skipped
505
506
 
506
- #### Table-level seed config
507
+ #### Table-level `.seed()` method
507
508
 
508
- `tableSeed(table, { count, per })` sets the row count and optional
509
- per-parent relationship:
509
+ Chain `.seed({ count, per })` on a table created with `table()` to set
510
+ the row count and optional per-parent relationship:
510
511
 
511
512
  ```typescript
512
- const users = tableSeed(sqliteTable("users", { ... }), { count: 10 });
513
- const posts = tableSeed(sqliteTable("posts", { ... }), { count: 5, per: users });
513
+ import { table } from "@cfast/db/seed";
514
+
515
+ const users = table("users", { ... }).seed({ count: 10 });
516
+ const posts = table("posts", { ... }).seed({ count: 5, per: users });
514
517
  // -> 5 per user = 50 total. authorId auto-filled with parent user's id.
515
518
  ```
516
519
 
520
+ `table()` is a drop-in replacement for `sqliteTable()` from
521
+ `drizzle-orm/sqlite-core` that adds the `.seed()` method. The returned
522
+ table is a standard Drizzle table in every way.
523
+
524
+ #### Deprecated wrapper functions
525
+
526
+ The older `seedConfig()` and `tableSeed()` wrapper functions still work
527
+ and write to the same internal registries. They are interchangeable with
528
+ the `.seed()` methods but considered deprecated:
529
+
530
+ ```typescript
531
+ // Deprecated -- prefer .seed() method API above
532
+ import { seedConfig, tableSeed } from "@cfast/db/seed";
533
+ const posts = tableSeed(sqliteTable("posts", {
534
+ title: seedConfig(text("title"), f => f.lorem.sentence()),
535
+ }), { count: 5 });
536
+ ```
537
+
517
538
  #### Seed context (ctx)
518
539
 
519
540
  Column-level seed functions receive `(faker, ctx)` where `ctx` provides:
@@ -524,10 +545,10 @@ Column-level seed functions receive `(faker, ctx)` where `ctx` provides:
524
545
  - `ctx.all(table)` -- all generated rows for a table
525
546
 
526
547
  ```typescript
527
- authorId: seedConfig(text("author_id").references(() => users.id),
528
- (faker, ctx) => ctx.parent?.id), // inherit from parent
529
- role: seedConfig(text("role"),
530
- (f, ctx) => ctx.index === 0 ? "admin" : f.helpers.arrayElement(["member", "viewer"])),
548
+ authorId: text("author_id").references(() => users.id)
549
+ .seed((faker, ctx) => ctx.parent?.id), // inherit from parent
550
+ role: text("role")
551
+ .seed((f, ctx) => ctx.index === 0 ? "admin" : f.helpers.arrayElement(["member", "viewer"])),
531
552
  ```
532
553
 
533
554
  #### Runtime API
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfast/db",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Permission-aware Drizzle queries for Cloudflare D1",
5
5
  "keywords": [
6
6
  "cfast",
@@ -33,7 +33,10 @@
33
33
  "dist",
34
34
  "llms.txt"
35
35
  ],
36
- "sideEffects": false,
36
+ "sideEffects": [
37
+ "./dist/seed.js",
38
+ "./src/seed.ts"
39
+ ],
37
40
  "publishConfig": {
38
41
  "access": "public"
39
42
  },