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 +0 -19
- package/dist/cli.js +13 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +37 -503
- package/dist/postgres/index.d.ts +0 -1
- package/dist/postgres/module.d.ts +1 -5
- package/dist/postgres/types.d.ts +0 -7
- package/package.json +1 -3
- package/dist/mailer.d.ts +0 -51
- package/dist/postgres/schema/columns.d.ts +0 -99
- package/dist/postgres/schema/index.d.ts +0 -6
- package/dist/postgres/schema/sql.d.ts +0 -22
- package/dist/postgres/schema/table.d.ts +0 -141
- package/dist/postgres/schema/where.d.ts +0 -29
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(
|
|
1180
|
-
const params = new URLSearchParams(
|
|
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(
|
|
1187
|
+
function parseBody(text, ct) {
|
|
1188
1188
|
if (ct.includes("application/x-www-form-urlencoded")) {
|
|
1189
|
-
return parseFormBody(
|
|
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(
|
|
1195
|
+
return JSON.parse(text);
|
|
1196
1196
|
} catch {
|
|
1197
1197
|
}
|
|
1198
1198
|
}
|
|
1199
|
-
return
|
|
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
|
|
1697
|
-
controller.enqueue(encoder.encode(
|
|
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
|
|
2095
|
+
const sql = postgres2(schemaUrl.toString());
|
|
2096
2096
|
await adminSql.end();
|
|
2097
2097
|
return {
|
|
2098
|
-
sql
|
|
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
|
|
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
|
|
2124
|
+
const sql = postgres2(resolvedUrl);
|
|
2125
2125
|
try {
|
|
2126
|
-
await
|
|
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
|
|
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(
|
|
2409
|
-
const result = await aiEmbed({ model: this.embeddingModel(), value:
|
|
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
|
|
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
|
-
|
|
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 =
|
|
2486
|
+
ctx.sql = sql;
|
|
2921
2487
|
return next(req, ctx);
|
|
2922
2488
|
});
|
|
2923
2489
|
mw.__meta = { injects: ["sql"], depends: [] };
|
|
2924
|
-
mw.sql =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 = () =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
package/dist/postgres/index.d.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import type { PostgresClient } from './types.ts';
|
|
2
1
|
import type { SqlClient, Closeable } from '../types.ts';
|
|
3
|
-
import type {
|
|
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>;
|
package/dist/postgres/types.d.ts
CHANGED
|
@@ -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.
|
|
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;
|