weifuwu 0.27.3 → 0.27.5

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 CHANGED
@@ -407,24 +407,6 @@ Options: `dir`, `defaultLocale`, `cookie`, `param`, `header`
407
407
 
408
408
  ### Standalone utilities
409
409
 
410
- #### mailer()
411
-
412
- ```ts
413
- import { mailer } from 'weifuwu'
414
-
415
- const m = mailer({
416
- host: 'smtp.example.com',
417
- port: 587,
418
- auth: { user: '...', pass: '...' },
419
- from: 'noreply@example.com',
420
- })
421
-
422
- await m.send({ to: 'user@example.com', subject: 'Hello', text: '...' })
423
- await m.close()
424
- ```
425
-
426
- Options: `host`, `port`, `auth`, `from`, `secure`
427
-
428
410
  #### SSE
429
411
 
430
412
  ```ts
@@ -519,6 +501,5 @@ Creates a minimal API project with `app.ts`, `index.ts`, and TypeScript config.
519
501
  - `graphql`, `@graphql-tools/schema` — GraphQL
520
502
  - `ws` — WebSocket
521
503
  - `zod` — Schema validation
522
- - `nodemailer` — Email
523
504
 
524
505
  Zero build tools. Zero frontend framework dependencies.
package/dist/cli.js CHANGED
@@ -92,6 +92,19 @@ async function cmdInit(name, opts) {
92
92
  );
93
93
  await writeFile(join(targetDir, ".gitignore"), "node_modules\n.env\n");
94
94
  await writeFile(join(targetDir, ".env"), "PORT=3000\n");
95
+ await writeFile(
96
+ join(targetDir, "AGENTS.md"),
97
+ [
98
+ "# Project Guide for AI Agents",
99
+ "",
100
+ "Before making any changes to this project, first read the weifuwu framework documentation:",
101
+ "",
102
+ "./node_modules/weifuwu/README.md",
103
+ "",
104
+ "This file contains the framework API reference, conventions, and patterns used throughout the codebase.",
105
+ ""
106
+ ].join("\n")
107
+ );
95
108
  if (!opts.skipInstall) {
96
109
  console.log("\nInstalling dependencies...");
97
110
  execSync("npm install", { cwd: targetDir, stdio: "inherit" });
package/dist/index.d.ts CHANGED
@@ -55,7 +55,5 @@ export { i18n } from './middleware/i18n.ts';
55
55
  export type { I18nOptions, I18nInjected } from './middleware/i18n.ts';
56
56
  export { flash } from './middleware/flash.ts';
57
57
  export type { FlashOptions, FlashInjected, FlashModule } from './middleware/flash.ts';
58
- export { mailer } from './mailer.ts';
59
- export type { MailerOptions, MailOptions, Mailer } from './mailer.ts';
60
58
  export { csrf } from './middleware/csrf.ts';
61
59
  export type { CsrfOptions, CsrfInjected, CsrfModule } from './middleware/csrf.ts';
package/dist/index.js CHANGED
@@ -1176,27 +1176,27 @@ var MIME_TYPES = {
1176
1176
  };
1177
1177
 
1178
1178
  // middleware/validate.ts
1179
- function parseFormBody(text2) {
1180
- const params = new URLSearchParams(text2);
1179
+ function parseFormBody(text) {
1180
+ const params = new URLSearchParams(text);
1181
1181
  const result = {};
1182
1182
  for (const [key, value] of params) {
1183
1183
  result[key] = value;
1184
1184
  }
1185
1185
  return result;
1186
1186
  }
1187
- function parseBody(text2, ct) {
1187
+ function parseBody(text, ct) {
1188
1188
  if (ct.includes("application/x-www-form-urlencoded")) {
1189
- return parseFormBody(text2);
1189
+ return parseFormBody(text);
1190
1190
  }
1191
1191
  const isExplicitJson = ct.includes("application/json") || ct.includes("+json") || ct.includes("text/") || ct.includes("*/json");
1192
1192
  const isNotSpecialMultipart = !ct.includes("multipart/form-data") && !ct.includes("application/x-www-form-urlencoded");
1193
1193
  if (isExplicitJson || isNotSpecialMultipart) {
1194
1194
  try {
1195
- return JSON.parse(text2);
1195
+ return JSON.parse(text);
1196
1196
  } catch {
1197
1197
  }
1198
1198
  }
1199
- return text2;
1199
+ return text;
1200
1200
  }
1201
1201
  function validate(schemas) {
1202
1202
  const mw = async (req, ctx, next) => {
@@ -1693,8 +1693,8 @@ function createSSEStream(iterable, opts) {
1693
1693
  async start(controller) {
1694
1694
  try {
1695
1695
  for await (const event of iterable) {
1696
- const text2 = event.type ? formatSSE(event.type, event) : formatSSEData(event);
1697
- controller.enqueue(encoder.encode(text2));
1696
+ const text = event.type ? formatSSE(event.type, event) : formatSSEData(event);
1697
+ controller.enqueue(encoder.encode(text));
1698
1698
  }
1699
1699
  } catch (e) {
1700
1700
  if (e instanceof Error && e.name !== "AbortError") {
@@ -2092,17 +2092,17 @@ async function createTestDb(options) {
2092
2092
  await adminSql.unsafe('CREATE SCHEMA IF NOT EXISTS "' + schema.replace(/"/g, '""') + '"');
2093
2093
  const schemaUrl = new URL(dbUrl);
2094
2094
  schemaUrl.searchParams.set("search_path", schema);
2095
- const sql2 = postgres2(schemaUrl.toString());
2095
+ const sql = postgres2(schemaUrl.toString());
2096
2096
  await adminSql.end();
2097
2097
  return {
2098
- sql: sql2,
2098
+ sql,
2099
2099
  url: schemaUrl.toString(),
2100
2100
  schema,
2101
2101
  destroy: async () => {
2102
2102
  const destroySql = postgres2(dbUrl);
2103
2103
  await destroySql.unsafe('DROP SCHEMA IF EXISTS "' + schema.replace(/"/g, '""') + '" CASCADE');
2104
2104
  await destroySql.end();
2105
- await sql2.end();
2105
+ await sql.end();
2106
2106
  }
2107
2107
  };
2108
2108
  }
@@ -2121,15 +2121,15 @@ async function withTestDb(optionsOrFn, fn) {
2121
2121
  const resolvedUrl = dbUrl || process.env.TEST_DATABASE_URL || process.env.DATABASE_URL;
2122
2122
  if (!resolvedUrl) throw new Error("withTestDb: DATABASE_URL or TEST_DATABASE_URL required");
2123
2123
  const { default: postgres2 } = await import("postgres");
2124
- const sql2 = postgres2(resolvedUrl);
2124
+ const sql = postgres2(resolvedUrl);
2125
2125
  try {
2126
- await sql2.begin(async (txSql) => {
2126
+ await sql.begin(async (txSql) => {
2127
2127
  await callback(txSql);
2128
2128
  throw void 0;
2129
2129
  });
2130
2130
  } catch {
2131
2131
  } finally {
2132
- await sql2.end();
2132
+ await sql.end();
2133
2133
  }
2134
2134
  }
2135
2135
 
@@ -2405,8 +2405,8 @@ function aiProvider(options) {
2405
2405
  if (!_embedModel) _embedModel = client.embedding(m);
2406
2406
  return _embedModel;
2407
2407
  },
2408
- async embed(text2) {
2409
- const result = await aiEmbed({ model: this.embeddingModel(), value: text2 });
2408
+ async embed(text) {
2409
+ const result = await aiEmbed({ model: this.embeddingModel(), value: text });
2410
2410
  return result.embedding;
2411
2411
  },
2412
2412
  async embedMany(texts) {
@@ -2444,440 +2444,6 @@ import { openai, createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
2444
2444
 
2445
2445
  // postgres/client.ts
2446
2446
  import postgresFactory from "postgres";
2447
-
2448
- // postgres/schema/sql.ts
2449
- var SQL = class {
2450
- /** Template string parts (interleaved with values). */
2451
- strings;
2452
- /** Bound parameter values. */
2453
- values;
2454
- constructor(strings, values) {
2455
- this.strings = strings;
2456
- this.values = values;
2457
- }
2458
- /** Serialize to a raw SQL string (interpolating values inline for DDL use). */
2459
- toSQL() {
2460
- let result = "";
2461
- for (let i = 0; i < this.strings.length; i++) {
2462
- result += this.strings[i];
2463
- if (i < this.values.length) {
2464
- result += String(this.values[i]);
2465
- }
2466
- }
2467
- return result;
2468
- }
2469
- };
2470
-
2471
- // postgres/schema/columns.ts
2472
- function toDDL(col) {
2473
- const parts = [`"${col.name}"`, col.sqlType];
2474
- if (col.isPrimaryKey) parts.push("PRIMARY KEY");
2475
- if (!col.isPrimaryKey && !col.isNullable) parts.push("NOT NULL");
2476
- if (col.isUnique) parts.push("UNIQUE");
2477
- if (col.defaultExpr) parts.push(`DEFAULT ${col.defaultExpr}`);
2478
- if (col.ref) {
2479
- parts.push(`REFERENCES "${col.ref.table}"("${col.ref.column}")`);
2480
- if (col.ref.onDelete) parts.push(`ON DELETE ${col.ref.onDelete.toUpperCase()}`);
2481
- }
2482
- return parts.join(" ");
2483
- }
2484
-
2485
- // postgres/schema/where.ts
2486
- function combine(conditions, joiner) {
2487
- if (conditions.length === 0) return new SQL([""], []);
2488
- const strings = ["("];
2489
- const values = [];
2490
- for (let i = 0; i < conditions.length; i++) {
2491
- if (i > 0) strings[strings.length - 1] += ` ${joiner} `;
2492
- const s = conditions[i];
2493
- for (let j = 0; j < s.strings.length; j++) {
2494
- strings[strings.length - 1] += s.strings[j];
2495
- if (j < s.values.length) {
2496
- strings.push("");
2497
- values.push(s.values[j]);
2498
- }
2499
- }
2500
- }
2501
- strings[strings.length - 1] += ")";
2502
- return new SQL(strings, values);
2503
- }
2504
- function and(...conditions) {
2505
- return combine(conditions, "AND");
2506
- }
2507
-
2508
- // postgres/schema/table.ts
2509
- var Table = class {
2510
- /** Database table name. */
2511
- tableName;
2512
- /** All column builders (order-preserving). */
2513
- columns;
2514
- /** Column builders keyed by property name. */
2515
- builders;
2516
- colEntries;
2517
- constructor(tableName, builders) {
2518
- this.tableName = tableName;
2519
- this.builders = builders;
2520
- this.columns = Object.values(builders);
2521
- this.colEntries = Object.entries(builders).map(([prop, col]) => ({
2522
- prop,
2523
- db: col.name,
2524
- auto: col.isAutoGenerate,
2525
- column: col
2526
- }));
2527
- }
2528
- /** Check if the table has a column with the given DB name. */
2529
- hasColumn(dbName) {
2530
- return this.colEntries.some((e) => e.db === dbName);
2531
- }
2532
- /**
2533
- * Bind this table schema to a SQL connection, returning a `BoundTable`
2534
- * that can run queries without passing `sql` to every call.
2535
- */
2536
- bind(sql2) {
2537
- return new BoundTable(sql2, this.tableName, this.builders);
2538
- }
2539
- /** Returns the primary key column name (DB name), or 'id' as fallback. */
2540
- get pkColumn() {
2541
- const entry = this.colEntries.find((e) => e.column.isPrimaryKey);
2542
- return entry ? entry.db : "id";
2543
- }
2544
- /** Adds `deleted_at IS NULL` condition if the table has soft delete and not explicitly excluded. */
2545
- _softDeleteFilter(where, opts) {
2546
- if (!this.hasColumn("deleted_at")) return null;
2547
- if (opts?.withDeleted) return null;
2548
- if (where && typeof where === "object" && !Array.isArray(where) && !(where instanceof SQL)) {
2549
- if ("deleted_at" in where) return null;
2550
- }
2551
- return '"deleted_at" IS NULL';
2552
- }
2553
- async create(sql2, opts) {
2554
- const colDDL = this.columns.map(toDDL);
2555
- let ddl = `CREATE TABLE IF NOT EXISTS "${this.tableName}" (
2556
- ${colDDL.join(",\n ")}
2557
- )`;
2558
- if (opts?.partitionBy) {
2559
- ddl += ` PARTITION BY ${opts.partitionBy.type} ("${opts.partitionBy.column}")`;
2560
- }
2561
- await sql2.unsafe(ddl);
2562
- }
2563
- async drop(sql2, opts) {
2564
- const cascade = opts?.cascade ? " CASCADE" : "";
2565
- await sql2.unsafe(`DROP TABLE IF EXISTS "${this.tableName}"${cascade}`);
2566
- }
2567
- async createIndex(sql2, columns, opts) {
2568
- const cols = Array.isArray(columns) ? columns : [columns];
2569
- const name = `"${this.tableName}_${cols.join("_")}${opts?.unique ? "_uidx" : "_idx"}"`;
2570
- const unique = opts?.unique ? "UNIQUE" : "";
2571
- const using = opts?.type ? `USING ${opts.type.toUpperCase()}` : "";
2572
- const colList = cols.map((c) => opts?.desc ? `"${c}" DESC` : `"${c}"`).join(", ");
2573
- const operator = opts?.operator ? ` ${opts.operator}` : "";
2574
- const ddl = `CREATE ${unique} INDEX IF NOT EXISTS ${name} ON "${this.tableName}" ${using} (${colList}${operator})`.replace(
2575
- /\s+/g,
2576
- " "
2577
- );
2578
- await sql2.unsafe(ddl);
2579
- }
2580
- async createUniqueIndex(sql2, columns) {
2581
- await this.createIndex(sql2, columns, { unique: true });
2582
- }
2583
- // --- Private helpers ---
2584
- _buildConditions(where, startIndex) {
2585
- const conditions = [];
2586
- const values = [];
2587
- let w = where;
2588
- if (Array.isArray(w)) {
2589
- w = w.length > 0 ? and(...w) : void 0;
2590
- }
2591
- if (w instanceof SQL) {
2592
- let fragment = "";
2593
- for (let i = 0; i < w.strings.length; i++) {
2594
- fragment += w.strings[i];
2595
- if (i < w.values.length) {
2596
- fragment += `$${startIndex + values.length + 1}`;
2597
- values.push(w.values[i]);
2598
- }
2599
- }
2600
- conditions.push(fragment);
2601
- } else {
2602
- for (const [prop, value] of Object.entries(w || {})) {
2603
- if (value === void 0) continue;
2604
- const entry = this.colEntries.find((e) => e.prop === prop);
2605
- const db = entry ? entry.db : prop;
2606
- conditions.push(`"${db}" = $${startIndex + values.length + 1}`);
2607
- values.push(value);
2608
- }
2609
- }
2610
- return { conditions, values };
2611
- }
2612
- _buildSET(data) {
2613
- const sets = [];
2614
- const values = [];
2615
- const d = data;
2616
- for (const { prop, db } of this.colEntries) {
2617
- if (prop in d && d[prop] !== void 0) {
2618
- const val = d[prop];
2619
- if (val instanceof SQL) {
2620
- sets.push(`"${db}" = ${val.toSQL()}`);
2621
- } else {
2622
- sets.push(`"${db}" = $${sets.length + 1}`);
2623
- values.push(val);
2624
- }
2625
- }
2626
- }
2627
- if (this.hasColumn("updated_at") && !d.updated_at) {
2628
- sets.push('"updated_at" = NOW()');
2629
- }
2630
- return { sets, values };
2631
- }
2632
- // --- CRUD ---
2633
- async insert(sql2, data) {
2634
- const filtered = {};
2635
- for (const { prop, db, auto } of this.colEntries) {
2636
- if (auto) continue;
2637
- const val = data[prop];
2638
- if (val !== void 0) {
2639
- filtered[db] = val;
2640
- }
2641
- }
2642
- const [row] = await sql2`
2643
- INSERT INTO ${sql2(this.tableName)} ${sql2(filtered)} RETURNING *
2644
- `;
2645
- return row;
2646
- }
2647
- async insertMany(sql2, data) {
2648
- const filtered = [];
2649
- for (const item of data) {
2650
- const row = {};
2651
- for (const { prop, db, auto } of this.colEntries) {
2652
- if (auto) continue;
2653
- const val = item[prop];
2654
- if (val !== void 0) {
2655
- row[db] = val;
2656
- }
2657
- }
2658
- filtered.push(row);
2659
- }
2660
- const rows = await sql2`
2661
- INSERT INTO ${sql2(this.tableName)} ${sql2(filtered)} RETURNING *
2662
- `;
2663
- return rows;
2664
- }
2665
- async read(sql2, id, opts) {
2666
- const pk = this.pkColumn;
2667
- const columns = opts?.select?.length ? opts.select.map((c) => `"${c}"`).join(", ") : "*";
2668
- const softDel = this._softDeleteFilter(null, opts);
2669
- const extraAnd = softDel ? ` AND ${softDel}` : "";
2670
- const [row] = await sql2.unsafe(
2671
- `SELECT ${columns} FROM "${this.tableName}" WHERE "${pk}" = $1${extraAnd} LIMIT 1`,
2672
- [id]
2673
- );
2674
- return row ?? void 0;
2675
- }
2676
- async readMany(sql2, where, opts) {
2677
- const { conditions, values } = this._buildConditions(where, 0);
2678
- const softDel = this._softDeleteFilter(where, opts);
2679
- if (softDel) conditions.push(softDel);
2680
- const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
2681
- const [countRow] = await sql2.unsafe(
2682
- `SELECT COUNT(*) AS _total FROM "${this.tableName}"${whereClause}`,
2683
- values
2684
- );
2685
- const count = Number(countRow._total);
2686
- if (conditions.length === 0 && !opts?.orderBy && !opts?.limit && !opts?.offset && !opts?.select) {
2687
- const rows2 = await sql2`SELECT * FROM ${sql2(this.tableName)}`;
2688
- return { count, data: rows2 };
2689
- }
2690
- const columns = opts?.select?.length ? opts.select.map((c) => `"${c}"`).join(", ") : "*";
2691
- let query = `SELECT ${columns} FROM "${this.tableName}"${whereClause}`;
2692
- if (opts?.orderBy) {
2693
- const orders = Object.entries(opts.orderBy).map(([prop, dir]) => {
2694
- const entry = this.colEntries.find((e) => e.prop === prop);
2695
- return `"${entry?.db || prop}" ${dir.toUpperCase()}`;
2696
- }).join(", ");
2697
- query += ` ORDER BY ${orders}`;
2698
- }
2699
- if (opts?.limit) query += ` LIMIT ${opts.limit}`;
2700
- if (opts?.offset) query += ` OFFSET ${opts.offset}`;
2701
- const rows = await sql2.unsafe(query, values);
2702
- return { count, data: rows };
2703
- }
2704
- async update(sql2, id, data) {
2705
- const { sets, values: setValues } = this._buildSET(data);
2706
- if (sets.length === 0) return void 0;
2707
- const pk = this.pkColumn;
2708
- const [row] = await sql2.unsafe(
2709
- `UPDATE "${this.tableName}" SET ${sets.join(", ")} WHERE "${pk}" = $${setValues.length + 1} RETURNING *`,
2710
- [...setValues, id]
2711
- );
2712
- return row ?? void 0;
2713
- }
2714
- async updateMany(sql2, where, data) {
2715
- const { sets, values: setValues } = this._buildSET(data);
2716
- if (sets.length === 0) return 0;
2717
- const { conditions: wConditions, values: wValues } = this._buildConditions(
2718
- where,
2719
- setValues.length
2720
- );
2721
- if (wConditions.length === 0) return 0;
2722
- const rows = await sql2.unsafe(
2723
- `UPDATE "${this.tableName}" SET ${sets.join(", ")} WHERE ${wConditions.join(" AND ")} RETURNING 1`,
2724
- [...setValues, ...wValues]
2725
- );
2726
- return rows.length;
2727
- }
2728
- async delete(sql2, id) {
2729
- const pk = this.pkColumn;
2730
- if (this.hasColumn("deleted_at")) {
2731
- const [row2] = await sql2.unsafe(
2732
- `UPDATE "${this.tableName}" SET "deleted_at" = NOW() WHERE "${pk}" = $1 RETURNING *`,
2733
- [id]
2734
- );
2735
- return row2 ?? void 0;
2736
- }
2737
- const [row] = await sql2.unsafe(
2738
- `DELETE FROM "${this.tableName}" WHERE "${pk}" = $1 RETURNING *`,
2739
- [id]
2740
- );
2741
- return row ?? void 0;
2742
- }
2743
- async hardDelete(sql2, id) {
2744
- const pk = this.pkColumn;
2745
- const [row] = await sql2.unsafe(
2746
- `DELETE FROM "${this.tableName}" WHERE "${pk}" = $1 RETURNING *`,
2747
- [id]
2748
- );
2749
- return row ?? void 0;
2750
- }
2751
- async deleteMany(sql2, where) {
2752
- const { conditions, values } = this._buildConditions(where, 0);
2753
- if (conditions.length === 0) return 0;
2754
- if (this.hasColumn("deleted_at")) {
2755
- const rows2 = await sql2.unsafe(
2756
- `UPDATE "${this.tableName}" SET "deleted_at" = NOW() WHERE ${conditions.join(" AND ")} RETURNING 1`,
2757
- values
2758
- );
2759
- return rows2.length;
2760
- }
2761
- const rows = await sql2.unsafe(
2762
- `DELETE FROM "${this.tableName}" WHERE ${conditions.join(" AND ")} RETURNING 1`,
2763
- values
2764
- );
2765
- return rows.length;
2766
- }
2767
- async hardDeleteMany(sql2, where) {
2768
- const { conditions, values } = this._buildConditions(where, 0);
2769
- if (conditions.length === 0) return 0;
2770
- const rows = await sql2.unsafe(
2771
- `DELETE FROM "${this.tableName}" WHERE ${conditions.join(" AND ")} RETURNING 1`,
2772
- values
2773
- );
2774
- return rows.length;
2775
- }
2776
- async upsert(sql2, data, conflict) {
2777
- const filtered = {};
2778
- for (const { prop, db, auto } of this.colEntries) {
2779
- if (auto) continue;
2780
- const val = data[prop];
2781
- if (val !== void 0) {
2782
- filtered[db] = val;
2783
- }
2784
- }
2785
- const keys = Object.keys(filtered);
2786
- if (keys.length === 0) throw new Error("upsert: no data to insert");
2787
- const conflictCols = Array.isArray(conflict) ? conflict : [conflict];
2788
- const dbCols = keys.map((c) => `"${c}"`);
2789
- const placeholders = keys.map((_, i) => `$${i + 1}`);
2790
- const updateSet = keys.filter((k) => !conflictCols.includes(k)).map((k) => `"${k}" = EXCLUDED."${k}"`).join(", ");
2791
- if (!updateSet) {
2792
- const [row2] = await sql2.unsafe(
2793
- `INSERT INTO "${this.tableName}" (${dbCols.join(", ")}) VALUES (${placeholders.join(", ")}) ON CONFLICT (${conflictCols.map((c) => `"${c}"`).join(", ")}) DO NOTHING RETURNING *`,
2794
- Object.values(filtered)
2795
- );
2796
- return row2 ?? void 0;
2797
- }
2798
- const [row] = await sql2.unsafe(
2799
- `INSERT INTO "${this.tableName}" (${dbCols.join(", ")}) VALUES (${placeholders.join(", ")}) ON CONFLICT (${conflictCols.map((c) => `"${c}"`).join(", ")}) DO UPDATE SET ${updateSet} RETURNING *`,
2800
- Object.values(filtered)
2801
- );
2802
- return row;
2803
- }
2804
- async count(sql2, where) {
2805
- const { conditions, values } = this._buildConditions(where, 0);
2806
- const softDel = this._softDeleteFilter(where);
2807
- if (softDel) conditions.push(softDel);
2808
- const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
2809
- const [row] = await sql2.unsafe(
2810
- `SELECT COUNT(*) AS _total FROM "${this.tableName}"${whereClause}`,
2811
- values
2812
- );
2813
- return Number(row._total);
2814
- }
2815
- };
2816
- var BoundTable = class _BoundTable {
2817
- inner;
2818
- sql;
2819
- /** The underlying table name. */
2820
- get tableName() {
2821
- return this.inner.tableName;
2822
- }
2823
- constructor(sql2, tableName, builders) {
2824
- this.inner = new Table(tableName, builders);
2825
- this.sql = sql2;
2826
- }
2827
- async create(opts) {
2828
- await this.inner.create(this.sql, opts);
2829
- }
2830
- async drop(opts) {
2831
- await this.inner.drop(this.sql, opts);
2832
- }
2833
- async createIndex(columns, opts) {
2834
- await this.inner.createIndex(this.sql, columns, opts);
2835
- }
2836
- async createUniqueIndex(columns) {
2837
- await this.inner.createUniqueIndex(this.sql, columns);
2838
- }
2839
- async insert(data) {
2840
- return await this.inner.insert(this.sql, data);
2841
- }
2842
- async insertMany(data) {
2843
- return await this.inner.insertMany(this.sql, data);
2844
- }
2845
- async read(id, opts) {
2846
- return await this.inner.read(this.sql, id, opts);
2847
- }
2848
- async readMany(where, opts) {
2849
- return await this.inner.readMany(this.sql, where, opts);
2850
- }
2851
- async update(id, data) {
2852
- return await this.inner.update(this.sql, id, data);
2853
- }
2854
- async updateMany(where, data) {
2855
- return await this.inner.updateMany(this.sql, where, data);
2856
- }
2857
- async delete(id) {
2858
- return await this.inner.delete(this.sql, id);
2859
- }
2860
- async hardDelete(id) {
2861
- return await this.inner.hardDelete(this.sql, id);
2862
- }
2863
- async deleteMany(where) {
2864
- return await this.inner.deleteMany(this.sql, where);
2865
- }
2866
- async hardDeleteMany(where) {
2867
- return await this.inner.hardDeleteMany(this.sql, where);
2868
- }
2869
- async upsert(data, conflict) {
2870
- return await this.inner.upsert(this.sql, data, conflict);
2871
- }
2872
- async count(where) {
2873
- return await this.inner.count(this.sql, where);
2874
- }
2875
- withSql(sql2) {
2876
- return new _BoundTable(sql2, this.inner.tableName, this.inner.builders);
2877
- }
2878
- };
2879
-
2880
- // postgres/client.ts
2881
2447
  var MIGRATIONS_TABLE = "_weifuwu_migrations";
2882
2448
  var RETRYABLE_CODES = /* @__PURE__ */ new Set(["40P01", "40001"]);
2883
2449
  function isRetryable(err) {
@@ -2897,7 +2463,7 @@ function postgres(opts) {
2897
2463
  const sep2 = connStr.includes("?") ? "&" : "?";
2898
2464
  connStr = `${connStr}${sep2}options=-c%20statement_timeout%3D${stmtTimeout}`;
2899
2465
  }
2900
- const sql2 = postgresFactory(connStr, {
2466
+ const sql = postgresFactory(connStr, {
2901
2467
  max: options.max,
2902
2468
  ssl: options.ssl,
2903
2469
  idle_timeout: options.idle_timeout,
@@ -2907,7 +2473,7 @@ function postgres(opts) {
2907
2473
  options.signal.addEventListener(
2908
2474
  "abort",
2909
2475
  () => {
2910
- sql2.end();
2476
+ sql.end();
2911
2477
  },
2912
2478
  { once: true }
2913
2479
  );
@@ -2917,19 +2483,13 @@ function postgres(opts) {
2917
2483
  const _waiting = 0;
2918
2484
  const poolMax = options.max ?? 10;
2919
2485
  const mw = ((req, ctx, next) => {
2920
- ctx.sql = sql2;
2486
+ ctx.sql = sql;
2921
2487
  return next(req, ctx);
2922
2488
  });
2923
2489
  mw.__meta = { injects: ["sql"], depends: [] };
2924
- mw.sql = sql2;
2925
- mw.table = ((tableOrSchema, builders) => {
2926
- if (typeof tableOrSchema === "string") {
2927
- return new BoundTable(sql2, tableOrSchema, builders);
2928
- }
2929
- return new BoundTable(sql2, tableOrSchema.tableName, tableOrSchema.builders);
2930
- });
2490
+ mw.sql = sql;
2931
2491
  mw.migrate = async () => {
2932
- await sql2.unsafe(`
2492
+ await sql.unsafe(`
2933
2493
  CREATE TABLE IF NOT EXISTS "${MIGRATIONS_TABLE}" (
2934
2494
  name TEXT PRIMARY KEY,
2935
2495
  applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
@@ -2937,13 +2497,13 @@ function postgres(opts) {
2937
2497
  `);
2938
2498
  };
2939
2499
  mw.markMigrated = async (moduleName) => {
2940
- await sql2.unsafe(
2500
+ await sql.unsafe(
2941
2501
  `INSERT INTO "${MIGRATIONS_TABLE}" (name) VALUES ($1) ON CONFLICT DO NOTHING`,
2942
2502
  [moduleName]
2943
2503
  );
2944
2504
  };
2945
2505
  mw.isMigrated = async (moduleName) => {
2946
- const [row] = await sql2.unsafe(`SELECT 1 FROM "${MIGRATIONS_TABLE}" WHERE name = $1`, [
2506
+ const [row] = await sql.unsafe(`SELECT 1 FROM "${MIGRATIONS_TABLE}" WHERE name = $1`, [
2947
2507
  moduleName
2948
2508
  ]);
2949
2509
  return !!row;
@@ -2952,7 +2512,7 @@ function postgres(opts) {
2952
2512
  const maxRetries = retryOpts?.maxRetries ?? 3;
2953
2513
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
2954
2514
  try {
2955
- const result = await sql2.begin(fn);
2515
+ const result = await sql.begin(fn);
2956
2516
  return result;
2957
2517
  } catch (err) {
2958
2518
  if (attempt < maxRetries && isRetryable(err)) {
@@ -2971,7 +2531,7 @@ function postgres(opts) {
2971
2531
  waiting: _waiting,
2972
2532
  max: poolMax
2973
2533
  });
2974
- mw.close = () => sql2.end({ timeout: closeTimeout });
2534
+ mw.close = () => sql.end({ timeout: closeTimeout });
2975
2535
  return mw;
2976
2536
  }
2977
2537
 
@@ -3214,7 +2774,7 @@ function createMemoryQueue(opts) {
3214
2774
  return q;
3215
2775
  }
3216
2776
  function createPgQueue(opts) {
3217
- const sql2 = opts.pg.sql;
2777
+ const sql = opts.pg.sql;
3218
2778
  const pollInterval = opts?.pollInterval ?? 200;
3219
2779
  const table = (opts?.prefix ?? "queue") + "_jobs";
3220
2780
  const handlers = /* @__PURE__ */ new Map();
@@ -3224,10 +2784,10 @@ function createPgQueue(opts) {
3224
2784
  const MAX_FAILED = 1e3;
3225
2785
  async function ensureTable() {
3226
2786
  if (ready) return;
3227
- await sql2.unsafe(
2787
+ await sql.unsafe(
3228
2788
  `CREATE TABLE IF NOT EXISTS ${escapeIdent(table)} (id UUID PRIMARY KEY, type TEXT NOT NULL, payload JSONB NOT NULL DEFAULT '{}', run_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), schedule TEXT, status TEXT NOT NULL DEFAULT 'pending', error TEXT, failed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW())`
3229
2789
  );
3230
- await sql2.unsafe(
2790
+ await sql.unsafe(
3231
2791
  `CREATE INDEX IF NOT EXISTS ${escapeIdent(table + "_run_at_idx")} ON ${escapeIdent(table)} (run_at, status)`
3232
2792
  );
3233
2793
  ready = true;
@@ -3237,12 +2797,12 @@ function createPgQueue(opts) {
3237
2797
  try {
3238
2798
  await handler(job);
3239
2799
  _processed++;
3240
- await sql2.unsafe(`DELETE FROM ${escapeIdent(table)} WHERE id = $1`, [job.id]);
2800
+ await sql.unsafe(`DELETE FROM ${escapeIdent(table)} WHERE id = $1`, [job.id]);
3241
2801
  } catch (e) {
3242
2802
  _failed++;
3243
2803
  const msg = e.message;
3244
2804
  console.error("[queue] handler error:", msg);
3245
- await sql2.unsafe(
2805
+ await sql.unsafe(
3246
2806
  `UPDATE ${escapeIdent(table)} SET status = 'failed', error = $2, failed_at = NOW() WHERE id = $1`,
3247
2807
  [job.id, msg]
3248
2808
  );
@@ -3252,7 +2812,7 @@ function createPgQueue(opts) {
3252
2812
  if (job.schedule) {
3253
2813
  try {
3254
2814
  const nextRun = cronNext(job.schedule);
3255
- await sql2.unsafe(
2815
+ await sql.unsafe(
3256
2816
  `INSERT INTO ${escapeIdent(table)} (id, type, payload, run_at, schedule) VALUES ($1, $2, $3::jsonb, $4, $5)`,
3257
2817
  [
3258
2818
  crypto4.randomUUID(),
@@ -3271,7 +2831,7 @@ function createPgQueue(opts) {
3271
2831
  if (!running) return;
3272
2832
  try {
3273
2833
  while (running && inflight < MAX_CONCURRENT) {
3274
- const rows = await sql2.unsafe(
2834
+ const rows = await sql.unsafe(
3275
2835
  `UPDATE ${escapeIdent(table)} SET status = 'running' WHERE id = (SELECT id FROM ${escapeIdent(table)} WHERE run_at <= NOW() AND status = 'pending' ORDER BY run_at LIMIT 1 FOR UPDATE SKIP LOCKED) RETURNING *`
3276
2836
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3277
2837
  );
@@ -3319,7 +2879,7 @@ function createPgQueue(opts) {
3319
2879
  } else {
3320
2880
  runAt = /* @__PURE__ */ new Date();
3321
2881
  }
3322
- await sql2.unsafe(
2882
+ await sql.unsafe(
3323
2883
  `INSERT INTO ${escapeIdent(table)} (id, type, payload, run_at, schedule) VALUES ($1, $2, $3::jsonb, $4, $5)`,
3324
2884
  [id, type, JSON.stringify(payload), runAt.toISOString(), opts2?.schedule || null]
3325
2885
  );
@@ -3345,7 +2905,7 @@ function createPgQueue(opts) {
3345
2905
  while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
3346
2906
  };
3347
2907
  mw.jobs = async function jobs(limit) {
3348
- const rows = await sql2.unsafe(
2908
+ const rows = await sql.unsafe(
3349
2909
  `SELECT * FROM ${escapeIdent(table)} WHERE status = 'pending' ORDER BY run_at LIMIT $1`,
3350
2910
  [limit ?? 50]
3351
2911
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -3360,7 +2920,7 @@ function createPgQueue(opts) {
3360
2920
  }));
3361
2921
  };
3362
2922
  mw.failedJobs = async function failedJobs(limit) {
3363
- const rows = await sql2.unsafe(
2923
+ const rows = await sql.unsafe(
3364
2924
  `SELECT * FROM ${escapeIdent(table)} WHERE status = 'failed' ORDER BY failed_at DESC LIMIT $1`,
3365
2925
  [limit ?? 50]
3366
2926
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -3377,7 +2937,7 @@ function createPgQueue(opts) {
3377
2937
  }));
3378
2938
  };
3379
2939
  mw.retryFailed = async function retryFailed(jobId) {
3380
- const result = await sql2.unsafe(
2940
+ const result = await sql.unsafe(
3381
2941
  `UPDATE ${escapeIdent(table)} SET status = 'pending', error = NULL, failed_at = NULL, run_at = NOW() WHERE id = $1 AND status = 'failed' RETURNING id`,
3382
2942
  [jobId]
3383
2943
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -3385,7 +2945,7 @@ function createPgQueue(opts) {
3385
2945
  return result.length > 0;
3386
2946
  };
3387
2947
  mw.retryAllFailed = async function retryAllFailed(type) {
3388
- const result = await sql2.unsafe(
2948
+ const result = await sql.unsafe(
3389
2949
  type ? `UPDATE ${escapeIdent(table)} SET status = 'pending', error = NULL, failed_at = NULL, run_at = NOW() WHERE status = 'failed' AND type = $1 RETURNING id` : `UPDATE ${escapeIdent(table)} SET status = 'pending', error = NULL, failed_at = NULL, run_at = NOW() WHERE status = 'failed' RETURNING id`,
3390
2950
  type ? [type] : []
3391
2951
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -3828,31 +3388,6 @@ function flash(options) {
3828
3388
  return mw;
3829
3389
  }
3830
3390
 
3831
- // mailer.ts
3832
- import { createTransport } from "nodemailer";
3833
- function mailer(options) {
3834
- const sender = options.send;
3835
- const from = options.from;
3836
- let transporter = null;
3837
- if (!sender && options.transport) {
3838
- transporter = typeof options.transport === "string" ? createTransport(options.transport) : options.transport;
3839
- }
3840
- async function send(opts) {
3841
- if (sender) {
3842
- await sender(opts);
3843
- return;
3844
- }
3845
- if (!transporter) {
3846
- throw new Error("mailer: no transport configured \u2014 provide `transport` or `send` option");
3847
- }
3848
- await transporter.sendMail({ ...opts, from: opts.from ?? from });
3849
- }
3850
- async function close() {
3851
- transporter?.close();
3852
- }
3853
- return { send, close };
3854
- }
3855
-
3856
3391
  // middleware/csrf.ts
3857
3392
  function csrf(options) {
3858
3393
  const cookieName = options?.cookie ?? "_csrf";
@@ -3938,7 +3473,6 @@ export {
3938
3473
  isProd,
3939
3474
  loadEnv,
3940
3475
  logger,
3941
- mailer,
3942
3476
  openai,
3943
3477
  postgres,
3944
3478
  queue,
@@ -1,4 +1,3 @@
1
1
  export { postgres, MIGRATIONS_TABLE } from './client.ts';
2
2
  export { PgModule } from './module.ts';
3
3
  export type { PostgresOptions, PostgresClient, PostgresInjected } from './types.ts';
4
- export * from './schema/index.ts';
@@ -1,13 +1,9 @@
1
- import type { PostgresClient } from './types.ts';
2
1
  import type { SqlClient, Closeable } from '../types.ts';
3
- import type { ColumnBuilder, BoundTable, Table } from './schema/index.ts';
2
+ import type { PostgresClient } from './types.ts';
4
3
  export declare class PgModule implements Closeable {
5
4
  protected sql: SqlClient;
6
5
  protected pg: PostgresClient;
7
6
  constructor(pg: PostgresClient);
8
- table<R extends Record<string, unknown>>(tableOrSchema: string | Table<R>, builders?: {
9
- [K in keyof R]: ColumnBuilder<R[K]>;
10
- }): BoundTable<R>;
11
7
  transaction<T>(fn: (sql: SqlClient) => Promise<T>, retryOpts?: {
12
8
  maxRetries?: number;
13
9
  }): Promise<T>;
@@ -1,5 +1,4 @@
1
1
  import type { SqlClient, Context, Middleware, Closeable } from '../types.ts';
2
- import type { ColumnBuilder, BoundTable, Table } from './schema/index.ts';
3
2
  declare module '../types.ts' {
4
3
  interface Context {
5
4
  sql: SqlClient;
@@ -29,12 +28,6 @@ export interface PostgresClient extends Middleware<Context, Context & PostgresIn
29
28
  markMigrated: (moduleName: string) => Promise<void>;
30
29
  /** Check whether a module has already been migrated. */
31
30
  isMigrated: (moduleName: string) => Promise<boolean>;
32
- table: {
33
- <R extends Record<string, unknown>>(tableName: string, builders: {
34
- [K in keyof R]: ColumnBuilder<R[K]>;
35
- }): BoundTable<R>;
36
- <R extends Record<string, unknown>>(schema: Table<R>): BoundTable<R>;
37
- };
38
31
  transaction: <T>(fn: (sql: any) => Promise<T>, retryOpts?: {
39
32
  maxRetries?: number;
40
33
  }) => Promise<T>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.27.3",
4
+ "version": "0.27.5",
5
5
  "description": "Web-standard HTTP microframework for Node.js — (req, ctx) => Response",
6
6
  "exports": {
7
7
  ".": "./dist/index.js"
@@ -31,7 +31,6 @@
31
31
  "ai": "^6",
32
32
  "graphql": "^16",
33
33
  "ioredis": "^5.11.0",
34
- "nodemailer": "^8.0.10",
35
34
  "postgres": "^3.4.9",
36
35
  "ws": "^8",
37
36
  "zod": "^4.4.3"
@@ -48,7 +47,6 @@
48
47
  "devDependencies": {
49
48
  "@eslint/js": "^10.0.1",
50
49
  "@types/node": "^25.9.3",
51
- "@types/nodemailer": "^6.4.17",
52
50
  "@types/ws": "^8.18.1",
53
51
  "esbuild": "^0.28.0",
54
52
  "eslint": "^10.5.0",
package/dist/mailer.d.ts DELETED
@@ -1,51 +0,0 @@
1
- import { type Transporter } from 'nodemailer';
2
- import type { Closeable } from './types.ts';
3
- /** Options for sending an email. */
4
- export interface MailOptions {
5
- /** Recipient address(es). */
6
- to: string | string[];
7
- /** Email subject. */
8
- subject: string;
9
- /** Plain text body. */
10
- text?: string;
11
- /** HTML body. */
12
- html?: string;
13
- /** Sender address (overrides `MailerOptions.from`). */
14
- from?: string;
15
- /** CC recipient(s). */
16
- cc?: string | string[];
17
- /** BCC recipient(s). */
18
- bcc?: string | string[];
19
- }
20
- /** Options for {@link mailer}. */
21
- export interface MailerOptions {
22
- /** Nodemailer transport string or pre-built transporter object. */
23
- transport?: string | Transporter;
24
- /** Default sender address. */
25
- from?: string;
26
- /** Custom send function (bypasses nodemailer). */
27
- send?: (opts: MailOptions) => Promise<void>;
28
- }
29
- /** Mailer instance returned by {@link mailer}. */
30
- export interface Mailer extends Closeable {
31
- /** Send an email. */
32
- send: (opts: MailOptions) => Promise<void>;
33
- /** Close the nodemailer transport. */
34
- close: () => Promise<void>;
35
- }
36
- /**
37
- * Create a mailer instance.
38
- *
39
- * ```ts
40
- * import { mailer } from 'weifuwu'
41
- *
42
- * const email = mailer({ transport: 'smtp://user:pass@smtp.example.com' })
43
- * await email.send({
44
- * to: 'user@example.com',
45
- * subject: 'Hello',
46
- * text: 'Hello from weifuwu!',
47
- * })
48
- * await email.close()
49
- * ```
50
- */
51
- export declare function mailer(options: MailerOptions): Mailer;
@@ -1,99 +0,0 @@
1
- import { SQL } from './sql.ts';
2
- /** Reference to another table's column (foreign key). */
3
- export interface ColumnReference {
4
- /** Referenced table name. */
5
- table: string;
6
- /** Referenced column name (default: `'id'`). */
7
- column: string;
8
- /** `ON DELETE` action (e.g. `'cascade'`, `'set null'`). */
9
- onDelete?: string;
10
- }
11
- /**
12
- * Fluent column builder for DDL generation.
13
- *
14
- * ```ts
15
- * text('name').notNull().unique()
16
- * integer('user_id').references('users')
17
- * timestamptz('created_at').default(sql`NOW()`)
18
- * ```
19
- */
20
- export declare class ColumnBuilder<T> {
21
- /** Column name. */
22
- name: string;
23
- /** SQL type string (e.g. `'TEXT'`, `'INTEGER'`). */
24
- sqlType: string;
25
- /** Whether this column is PRIMARY KEY. */
26
- isPrimaryKey: boolean;
27
- /** Whether this column allows NULL. */
28
- isNullable: boolean;
29
- /** Whether this column has a UNIQUE constraint. */
30
- isUnique: boolean;
31
- /** Whether the value is auto-generated (e.g. SERIAL, UUID defaults). */
32
- isAutoGenerate: boolean;
33
- /** DEFAULT expression as a raw SQL string. */
34
- defaultExpr: string | null;
35
- /** Foreign key reference, if any. */
36
- ref: ColumnReference | null;
37
- constructor(name: string, sqlType: string);
38
- /** Mark as PRIMARY KEY (implies NOT NULL). */
39
- primaryKey(): this;
40
- /** Add NOT NULL constraint. */
41
- notNull(): this;
42
- /** Allow NULL values (default). */
43
- nullable(): this;
44
- /** Set a DEFAULT value. Accepts raw SQL, string, number, or boolean. */
45
- default(expr: SQL | string | number | boolean): this;
46
- /** Add UNIQUE constraint. */
47
- unique(): this;
48
- /** Add FOREIGN KEY reference to another table. */
49
- references(table: string, column?: string, onDelete?: string): this;
50
- }
51
- /** Auto-incrementing integer primary key (`SERIAL`). */
52
- export declare function serial(name: string): ColumnBuilder<number>;
53
- /** UUID column. */
54
- export declare function uuid(name: string): ColumnBuilder<string>;
55
- /** TEXT column. */
56
- export declare function text(name: string): ColumnBuilder<string>;
57
- /** INTEGER column. */
58
- export declare function integer(name: string): ColumnBuilder<number>;
59
- /** BOOLEAN column (exported as `boolean`). */
60
- export declare function boolean_(name: string): ColumnBuilder<boolean>;
61
- export { boolean_ as boolean };
62
- /** TIMESTAMPTZ column (timestamp with time zone). */
63
- export declare function timestamptz(name: string): ColumnBuilder<string>;
64
- /** JSONB column (stores arbitrary JSON data). */
65
- export declare function jsonb<T = unknown>(name: string): ColumnBuilder<T>;
66
- /** TEXT[] column (PostgreSQL array of text). */
67
- export declare function textArray(name: string): ColumnBuilder<string[]>;
68
- /** Vector column for pgvector (embedding storage). Requires `dimensions`. */
69
- export declare function vector(name: string, dims: number): ColumnBuilder<number[]>;
70
- export interface PartitionByDef {
71
- type: 'RANGE' | 'LIST' | 'HASH';
72
- column: string;
73
- }
74
- export declare function partitionBy(type: 'range' | 'list' | 'hash', column: string): PartitionByDef;
75
- /**
76
- * Create a pair of `created_at` / `updated_at` timestamp columns
77
- * that default to `NOW()` and are NOT NULL.
78
- *
79
- * ```ts
80
- * pgTable('users', {
81
- * id: serial('id').primaryKey(),
82
- * name: text('name'),
83
- * ...timestamps(),
84
- * })
85
- * ```
86
- */
87
- export declare function timestamps(): {
88
- readonly created_at: ColumnBuilder<string>;
89
- readonly updated_at: ColumnBuilder<string>;
90
- };
91
- /**
92
- * Convert a ColumnBuilder into a DDL column definition string.
93
- *
94
- * ```ts
95
- * toDDL(text('name').notNull())
96
- * // '"name" TEXT NOT NULL'
97
- * ```
98
- */
99
- export declare function toDDL(col: ColumnBuilder<unknown>): string;
@@ -1,6 +0,0 @@
1
- export { sql, SQL } from './sql.ts';
2
- export { ColumnBuilder, serial, uuid, text, integer, boolean as boolean, boolean_, timestamptz, jsonb, textArray, vector, toDDL, partitionBy, timestamps, } from './columns.ts';
3
- export type { PartitionByDef } from './columns.ts';
4
- export { pgTable, Table, BoundTable } from './table.ts';
5
- export type { IndexOptions, FindOptions, CreateOptions } from './table.ts';
6
- export { eq, ne, gt, gte, lt, lte, isNull, isNotNull, like, contains, in_, and, or, not, } from './where.ts';
@@ -1,22 +0,0 @@
1
- /**
2
- * A parameterized SQL fragment with template strings and bound values.
3
- * Used internally by the schema builder and where helpers.
4
- */
5
- export declare class SQL {
6
- /** Template string parts (interleaved with values). */
7
- strings: TemplateStringsArray;
8
- /** Bound parameter values. */
9
- values: unknown[];
10
- constructor(strings: TemplateStringsArray, values: unknown[]);
11
- /** Serialize to a raw SQL string (interpolating values inline for DDL use). */
12
- toSQL(): string;
13
- }
14
- /**
15
- * Tagged template helper for creating parameterized SQL fragments.
16
- *
17
- * ```ts
18
- * sql`NOW()`
19
- * sql`${column} ILIKE ${'%' + search + '%'}`
20
- * ```
21
- */
22
- export declare function sql(strings: TemplateStringsArray, ...values: unknown[]): SQL;
@@ -1,141 +0,0 @@
1
- import type { SqlClient } from '../../types.ts';
2
- import { ColumnBuilder, type PartitionByDef } from './columns.ts';
3
- import { SQL } from './sql.ts';
4
- /** Options for table index creation. */
5
- export interface IndexOptions {
6
- /** Whether the index should be UNIQUE. */
7
- unique?: boolean;
8
- /** Index type: btree (default), hnsw (pgvector), gin (JSONB). */
9
- type?: 'btree' | 'hnsw' | 'gin';
10
- /** Create index in DESC order. */
11
- desc?: boolean;
12
- /** Custom operator class (e.g. `vector_cosine_ops`). */
13
- operator?: string;
14
- }
15
- /** Options for CREATE TABLE. */
16
- export interface CreateOptions {
17
- /** Partition by clause (RANGE, LIST, or HASH). */
18
- partitionBy?: PartitionByDef;
19
- }
20
- /** Options for find/read queries. */
21
- export interface FindOptions {
22
- /** ORDER BY clause: `{ column: 'asc' | 'desc' }`. */
23
- orderBy?: Record<string, 'asc' | 'desc'>;
24
- /** LIMIT. */
25
- limit?: number;
26
- /** OFFSET. */
27
- offset?: number;
28
- /** Columns to SELECT (default: all). */
29
- select?: string[];
30
- /** Include soft-deleted rows (also sets `withDeleted` context). */
31
- withDeleted?: boolean;
32
- }
33
- /**
34
- * Type-safe table schema + CRUD operations.
35
- *
36
- * Create an instance with {@link pgTable}, then call `.bind(sql)` to get a
37
- * `BoundTable` for running queries.
38
- *
39
- * ```ts
40
- * const users = pgTable('users', {
41
- * id: serial('id').primaryKey(),
42
- * name: text('name').notNull(),
43
- * email: text('email').unique(),
44
- * })
45
- *
46
- * const db = users.bind(sql)
47
- * await db.create()
48
- * await db.insert({ name: 'Alice', email: 'a@b.com' })
49
- * const row = await db.findBy({ email: 'a@b.com' })
50
- * ```
51
- */
52
- export declare class Table<R extends Record<string, unknown>> {
53
- /** Database table name. */
54
- readonly tableName: string;
55
- /** All column builders (order-preserving). */
56
- readonly columns: ColumnBuilder<unknown>[];
57
- /** Column builders keyed by property name. */
58
- readonly builders: Record<string, ColumnBuilder<unknown>>;
59
- private colEntries;
60
- constructor(tableName: string, builders: Record<string, ColumnBuilder<unknown>>);
61
- /** Check if the table has a column with the given DB name. */
62
- hasColumn(dbName: string): boolean;
63
- /**
64
- * Bind this table schema to a SQL connection, returning a `BoundTable`
65
- * that can run queries without passing `sql` to every call.
66
- */
67
- bind(sql: SqlClient): BoundTable<R>;
68
- /** Returns the primary key column name (DB name), or 'id' as fallback. */
69
- private get pkColumn();
70
- /** Adds `deleted_at IS NULL` condition if the table has soft delete and not explicitly excluded. */
71
- private _softDeleteFilter;
72
- create(sql: SqlClient, opts?: CreateOptions): Promise<void>;
73
- drop(sql: SqlClient, opts?: {
74
- cascade?: boolean;
75
- }): Promise<void>;
76
- createIndex(sql: SqlClient, columns: string | string[], opts?: IndexOptions): Promise<void>;
77
- createUniqueIndex(sql: SqlClient, columns: string | string[]): Promise<void>;
78
- private _buildConditions;
79
- private _buildSET;
80
- insert(sql: SqlClient, data: Partial<R>): Promise<R>;
81
- insertMany(sql: SqlClient, data: Partial<R>[]): Promise<R[]>;
82
- read(sql: SqlClient, id: string | number, opts?: Pick<FindOptions, 'select' | 'withDeleted'>): Promise<R | undefined>;
83
- readMany(sql: SqlClient, where?: Partial<R> | SQL | SQL[], opts?: FindOptions): Promise<{
84
- count: number;
85
- data: R[];
86
- }>;
87
- update(sql: SqlClient, id: string | number, data: Partial<R>): Promise<R | undefined>;
88
- updateMany(sql: SqlClient, where: Partial<R> | SQL | SQL[], data: Partial<R>): Promise<number>;
89
- delete(sql: SqlClient, id: string | number): Promise<R | undefined>;
90
- hardDelete(sql: SqlClient, id: string | number): Promise<R | undefined>;
91
- deleteMany(sql: SqlClient, where: Partial<R> | SQL | SQL[]): Promise<number>;
92
- hardDeleteMany(sql: SqlClient, where: Partial<R> | SQL | SQL[]): Promise<number>;
93
- upsert(sql: SqlClient, data: Partial<R>, conflict: string | string[]): Promise<R>;
94
- count(sql: SqlClient, where?: Partial<R> | SQL | SQL[]): Promise<number>;
95
- }
96
- export declare class BoundTable<R extends Record<string, unknown>> {
97
- private inner;
98
- private sql;
99
- /** The underlying table name. */
100
- get tableName(): string;
101
- constructor(sql: SqlClient, tableName: string, builders: Record<string, ColumnBuilder<unknown>>);
102
- create(opts?: CreateOptions): Promise<void>;
103
- drop(opts?: {
104
- cascade?: boolean;
105
- }): Promise<void>;
106
- createIndex(columns: string | string[], opts?: IndexOptions): Promise<void>;
107
- createUniqueIndex(columns: string | string[]): Promise<void>;
108
- insert(data: Partial<R>): Promise<R>;
109
- insertMany(data: Partial<R>[]): Promise<R[]>;
110
- read(id: string | number, opts?: Pick<FindOptions, 'select' | 'withDeleted'>): Promise<R | undefined>;
111
- readMany(where?: Partial<R> | SQL | SQL[], opts?: FindOptions): Promise<{
112
- count: number;
113
- data: R[];
114
- }>;
115
- update(id: string | number, data: Partial<R>): Promise<R | undefined>;
116
- updateMany(where: Partial<R> | SQL | SQL[], data: Partial<R>): Promise<number>;
117
- delete(id: string | number): Promise<R | undefined>;
118
- hardDelete(id: string | number): Promise<R | undefined>;
119
- deleteMany(where: Partial<R> | SQL | SQL[]): Promise<number>;
120
- hardDeleteMany(where: Partial<R> | SQL | SQL[]): Promise<number>;
121
- upsert(data: Partial<R>, conflict: string | string[]): Promise<R>;
122
- count(where?: Partial<R> | SQL | SQL[]): Promise<number>;
123
- withSql(sql: SqlClient): BoundTable<R>;
124
- }
125
- /**
126
- * Define a type-safe table schema.
127
- *
128
- * ```ts
129
- * const users = pgTable('users', {
130
- * id: serial('id').primaryKey(),
131
- * name: text('name').notNull(),
132
- * email: text('email').unique(),
133
- * })
134
- *
135
- * // The generic type R preserves the column types:
136
- * // Table<{ id: number; name: string; email: string }>
137
- * ```
138
- */
139
- export declare function pgTable<R extends Record<string, unknown>>(tableName: string, builders: {
140
- [K in keyof R]: ColumnBuilder<R[K]>;
141
- }): Table<R>;
@@ -1,29 +0,0 @@
1
- import { SQL } from './sql.ts';
2
- /** Column equals value: `col = val`. */
3
- export declare function eq(col: string, val: unknown): SQL;
4
- /** Column not equals value: `col != val`. */
5
- export declare function ne(col: string, val: unknown): SQL;
6
- /** Column greater than value: `col > val`. */
7
- export declare function gt(col: string, val: unknown): SQL;
8
- /** Column greater than or equal value: `col >= val`. */
9
- export declare function gte(col: string, val: unknown): SQL;
10
- /** Column less than value: `col < val`. */
11
- export declare function lt(col: string, val: unknown): SQL;
12
- /** Column less than or equal value: `col <= val`. */
13
- export declare function lte(col: string, val: unknown): SQL;
14
- /** Column IS NULL. */
15
- export declare function isNull(col: string): SQL;
16
- /** Column IS NOT NULL. */
17
- export declare function isNotNull(col: string): SQL;
18
- /** Column LIKE pattern. */
19
- export declare function like(col: string, pattern: string): SQL;
20
- /** Negate a condition: `NOT (condition)`. */
21
- export declare function not(condition: SQL): SQL;
22
- /** JSONB containment: `col @> val` (does `col` contain `val`?). */
23
- export declare function contains(col: string, val: Record<string, unknown>): SQL;
24
- /** Column value is in array: `col = ANY(val)`. */
25
- export declare function in_(col: string, val: unknown[]): SQL;
26
- /** Combine conditions with AND. */
27
- export declare function and(...conditions: SQL[]): SQL;
28
- /** Combine conditions with OR. */
29
- export declare function or(...conditions: SQL[]): SQL;