weifuwu 0.23.2 → 0.23.4

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.
Files changed (45) hide show
  1. package/README.md +55 -0
  2. package/cli/template/.weifuwu/ssr/2e3a7e60.js +3 -3
  3. package/dist/analytics.d.ts +20 -0
  4. package/dist/client-locale.d.ts +20 -0
  5. package/dist/client-theme.d.ts +29 -0
  6. package/dist/compress.d.ts +14 -0
  7. package/dist/cors.d.ts +15 -0
  8. package/dist/cron-utils.d.ts +65 -0
  9. package/dist/csrf.d.ts +29 -0
  10. package/dist/env.d.ts +33 -5
  11. package/dist/flash.d.ts +66 -7
  12. package/dist/health.d.ts +18 -0
  13. package/dist/helmet.d.ts +15 -0
  14. package/dist/hub.d.ts +25 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +124 -43
  17. package/dist/mailer.d.ts +30 -0
  18. package/dist/permissions.d.ts +5 -3
  19. package/dist/postgres/schema/columns.d.ts +56 -0
  20. package/dist/postgres/schema/sql.d.ts +15 -0
  21. package/dist/postgres/schema/table.d.ts +54 -1
  22. package/dist/postgres/schema/where.d.ts +14 -0
  23. package/dist/rate-limit.d.ts +25 -3
  24. package/dist/request-id.d.ts +19 -0
  25. package/dist/seo.d.ts +65 -0
  26. package/dist/sse.d.ts +37 -0
  27. package/dist/static.d.ts +16 -0
  28. package/dist/stream.d.ts +15 -0
  29. package/dist/trace.d.ts +41 -5
  30. package/dist/types.d.ts +10 -1
  31. package/dist/upload.d.ts +30 -0
  32. package/dist/use-action.d.ts +28 -0
  33. package/dist/use-agent-stream.d.ts +32 -10
  34. package/dist/use-flash-message.d.ts +16 -0
  35. package/dist/use-websocket.d.ts +25 -0
  36. package/dist/user/client.d.ts +15 -0
  37. package/dist/user/oauth2.d.ts +10 -0
  38. package/dist/user/types.d.ts +58 -4
  39. package/dist/webhook.d.ts +40 -15
  40. package/package.json +5 -3
  41. package/cli/template/.weifuwu/ssr/560568d7.js +0 -14
  42. package/dist/kb.d.ts +0 -70
  43. package/dist/oauth-client.d.ts +0 -41
  44. package/dist/preferences.d.ts +0 -18
  45. package/dist/server.d.ts +0 -1
package/dist/index.js CHANGED
@@ -28,6 +28,9 @@ function traceElapsed() {
28
28
  // env.ts
29
29
  import { readFileSync } from "node:fs";
30
30
  import { resolve } from "node:path";
31
+ function isBundled() {
32
+ return true ? true : false;
33
+ }
31
34
  function isDev() {
32
35
  return process.env.NODE_ENV === "development";
33
36
  }
@@ -1619,22 +1622,6 @@ function compress(options) {
1619
1622
  }
1620
1623
 
1621
1624
  // helmet.ts
1622
- var DEFAULTS = {
1623
- contentSecurityPolicy: "default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests",
1624
- crossOriginEmbedderPolicy: "require-corp",
1625
- crossOriginOpenerPolicy: "same-origin",
1626
- crossOriginResourcePolicy: "same-origin",
1627
- originAgentCluster: "?1",
1628
- referrerPolicy: "no-referrer",
1629
- strictTransportSecurity: "max-age=15552000; includeSubDomains",
1630
- xContentTypeOptions: "nosniff",
1631
- xDnsPrefetchControl: "off",
1632
- xDownloadOptions: "noopen",
1633
- xFrameOptions: "SAMEORIGIN",
1634
- xPermittedCrossDomainPolicies: "none",
1635
- xXssProtection: "0",
1636
- permissionsPolicy: "camera=(),display-capture=(),fullscreen=(),geolocation=(),microphone=()"
1637
- };
1638
1625
  var HEADER_MAP = {
1639
1626
  "Content-Security-Policy": "contentSecurityPolicy",
1640
1627
  "Cross-Origin-Embedder-Policy": "crossOriginEmbedderPolicy",
@@ -1667,6 +1654,22 @@ function helmet(options) {
1667
1654
  return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
1668
1655
  };
1669
1656
  }
1657
+ var DEFAULTS = {
1658
+ contentSecurityPolicy: "default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests",
1659
+ crossOriginEmbedderPolicy: "require-corp",
1660
+ crossOriginOpenerPolicy: "same-origin",
1661
+ crossOriginResourcePolicy: "same-origin",
1662
+ originAgentCluster: "?1",
1663
+ referrerPolicy: "no-referrer",
1664
+ strictTransportSecurity: "max-age=15552000; includeSubDomains",
1665
+ xContentTypeOptions: "nosniff",
1666
+ xDnsPrefetchControl: "off",
1667
+ xDownloadOptions: "noopen",
1668
+ xFrameOptions: "SAMEORIGIN",
1669
+ xPermittedCrossDomainPolicies: "none",
1670
+ xXssProtection: "0",
1671
+ permissionsPolicy: "camera=(),display-capture=(),fullscreen=(),geolocation=(),microphone=()"
1672
+ };
1670
1673
 
1671
1674
  // request-id.ts
1672
1675
  import crypto2 from "node:crypto";
@@ -2474,12 +2477,15 @@ import postgresFactory from "postgres";
2474
2477
 
2475
2478
  // postgres/schema/sql.ts
2476
2479
  var SQL = class {
2480
+ /** Template string parts (interleaved with values). */
2477
2481
  strings;
2482
+ /** Bound parameter values. */
2478
2483
  values;
2479
2484
  constructor(strings, values) {
2480
2485
  this.strings = strings;
2481
2486
  this.values = values;
2482
2487
  }
2488
+ /** Serialize to a raw SQL string (interpolating values inline for DDL use). */
2483
2489
  toSQL() {
2484
2490
  let result = "";
2485
2491
  for (let i = 0; i < this.strings.length; i++) {
@@ -2497,31 +2503,43 @@ function sql(strings, ...values) {
2497
2503
 
2498
2504
  // postgres/schema/columns.ts
2499
2505
  var ColumnBuilder = class {
2506
+ /** Column name. */
2500
2507
  name;
2508
+ /** SQL type string (e.g. `'TEXT'`, `'INTEGER'`). */
2501
2509
  sqlType;
2510
+ /** Whether this column is PRIMARY KEY. */
2502
2511
  isPrimaryKey = false;
2512
+ /** Whether this column allows NULL. */
2503
2513
  isNullable = true;
2514
+ /** Whether this column has a UNIQUE constraint. */
2504
2515
  isUnique = false;
2516
+ /** Whether the value is auto-generated (e.g. SERIAL, UUID defaults). */
2505
2517
  isAutoGenerate = false;
2518
+ /** DEFAULT expression as a raw SQL string. */
2506
2519
  defaultExpr = null;
2520
+ /** Foreign key reference, if any. */
2507
2521
  ref = null;
2508
2522
  constructor(name, sqlType) {
2509
2523
  this.name = name;
2510
2524
  this.sqlType = sqlType;
2511
2525
  }
2526
+ /** Mark as PRIMARY KEY (implies NOT NULL). */
2512
2527
  primaryKey() {
2513
2528
  this.isPrimaryKey = true;
2514
2529
  this.isNullable = false;
2515
2530
  return this;
2516
2531
  }
2532
+ /** Add NOT NULL constraint. */
2517
2533
  notNull() {
2518
2534
  this.isNullable = false;
2519
2535
  return this;
2520
2536
  }
2537
+ /** Allow NULL values (default). */
2521
2538
  nullable() {
2522
2539
  this.isNullable = true;
2523
2540
  return this;
2524
2541
  }
2542
+ /** Set a DEFAULT value. Accepts raw SQL, string, number, or boolean. */
2525
2543
  default(expr) {
2526
2544
  if (expr instanceof SQL) {
2527
2545
  this.defaultExpr = expr.toSQL();
@@ -2532,10 +2550,12 @@ var ColumnBuilder = class {
2532
2550
  }
2533
2551
  return this;
2534
2552
  }
2553
+ /** Add UNIQUE constraint. */
2535
2554
  unique() {
2536
2555
  this.isUnique = true;
2537
2556
  return this;
2538
2557
  }
2558
+ /** Add FOREIGN KEY reference to another table. */
2539
2559
  references(table, column = "id", onDelete) {
2540
2560
  this.ref = { table, column, onDelete };
2541
2561
  return this;
@@ -2629,8 +2649,11 @@ function and(...conditions) {
2629
2649
 
2630
2650
  // postgres/schema/table.ts
2631
2651
  var Table = class {
2652
+ /** Database table name. */
2632
2653
  tableName;
2654
+ /** All column builders (order-preserving). */
2633
2655
  columns;
2656
+ /** Column builders keyed by property name. */
2634
2657
  builders;
2635
2658
  colEntries;
2636
2659
  constructor(tableName, builders) {
@@ -2644,10 +2667,14 @@ var Table = class {
2644
2667
  column: col2
2645
2668
  }));
2646
2669
  }
2670
+ /** Check if the table has a column with the given DB name. */
2647
2671
  hasColumn(dbName) {
2648
2672
  return this.colEntries.some((e) => e.db === dbName);
2649
2673
  }
2650
- /** Returns a BoundTable pre-bound to the given sql connection (no need to pass sql to every method). */
2674
+ /**
2675
+ * Bind this table schema to a SQL connection, returning a `BoundTable`
2676
+ * that can run queries without passing `sql` to every call.
2677
+ */
2651
2678
  bind(sql2) {
2652
2679
  return new BoundTable(sql2, this.tableName, this.builders);
2653
2680
  }
@@ -3772,6 +3799,18 @@ function user(options) {
3772
3799
  return null;
3773
3800
  }
3774
3801
  }
3802
+ function extractToken(req, cookieName) {
3803
+ const header = req.headers.get("Authorization");
3804
+ if (header?.startsWith("Bearer ")) return header.slice(7);
3805
+ if (cookieName) {
3806
+ const cookies = req.headers.get("cookie")?.split(";").map((c) => c.trim()).filter(Boolean) || [];
3807
+ for (const c of cookies) {
3808
+ const eq2 = c.indexOf("=");
3809
+ if (eq2 > 0 && c.slice(0, eq2) === cookieName) return c.slice(eq2 + 1);
3810
+ }
3811
+ }
3812
+ return null;
3813
+ }
3775
3814
  function middleware() {
3776
3815
  return async (req, ctx, next) => {
3777
3816
  const sessionUserId = ctx.session?.userId;
@@ -3788,8 +3827,7 @@ function user(options) {
3788
3827
  delete ctx.session?.userId;
3789
3828
  }
3790
3829
  }
3791
- const header = req.headers.get("Authorization");
3792
- const token = header?.startsWith("Bearer ") ? header.slice(7) : null;
3830
+ const token = extractToken(req);
3793
3831
  if (token) {
3794
3832
  const userData = await verify(token);
3795
3833
  if (userData) {
@@ -3800,11 +3838,36 @@ function user(options) {
3800
3838
  return new Response("Unauthorized", { status: 401, headers: { "WWW-Authenticate": "Bearer" } });
3801
3839
  };
3802
3840
  }
3841
+ function middlewareOptional(opts) {
3842
+ const cookieName = opts?.cookie;
3843
+ return async (req, ctx, next) => {
3844
+ const token = extractToken(req, cookieName);
3845
+ if (token) {
3846
+ const userData = await verify(token);
3847
+ if (userData) {
3848
+ ctx.user = userData;
3849
+ }
3850
+ }
3851
+ return next(req, ctx);
3852
+ };
3853
+ }
3854
+ async function parseBody2(req) {
3855
+ const ct = req.headers.get("content-type") || "";
3856
+ if (ct.includes("application/json")) {
3857
+ return req.json();
3858
+ }
3859
+ const form = await req.formData();
3860
+ const obj = {};
3861
+ for (const [key, val] of form) {
3862
+ obj[key] = val;
3863
+ }
3864
+ return obj;
3865
+ }
3803
3866
  function router() {
3804
3867
  const r2 = new Router();
3805
3868
  r2.post("/register", async (req) => {
3806
3869
  try {
3807
- const body = await req.json();
3870
+ const body = await parseBody2(req);
3808
3871
  const result = await register(body);
3809
3872
  return Response.json(result, { status: 201 });
3810
3873
  } catch (err) {
@@ -3817,7 +3880,7 @@ function user(options) {
3817
3880
  });
3818
3881
  r2.post("/login", async (req, ctx) => {
3819
3882
  try {
3820
- const body = await req.json();
3883
+ const body = await parseBody2(req);
3821
3884
  const result = await login(body);
3822
3885
  if (ctx.session) {
3823
3886
  ;
@@ -3861,6 +3924,7 @@ function user(options) {
3861
3924
  }
3862
3925
  const mod = r;
3863
3926
  mod.middleware = middleware;
3927
+ mod.middlewareOptional = middlewareOptional;
3864
3928
  mod.migrate = migrate;
3865
3929
  mod.register = register;
3866
3930
  mod.login = login;
@@ -6717,13 +6781,24 @@ async function compileVendorBundle() {
6717
6781
  const keys = Object.keys(mod).filter((k) => !k.startsWith("_") && k !== "default");
6718
6782
  modules[request] = keys;
6719
6783
  }
6720
- const reactTsPath = resolve3(import.meta.dirname ?? __dirname, "react.ts");
6721
- const reactSrc = readFileSync2(reactTsPath, "utf-8");
6784
+ const baseDir = import.meta.dirname ?? __dirname;
6785
+ const reactAbsPath = isBundled() ? resolve3(baseDir, "react.js") : resolve3(baseDir, "react.ts");
6786
+ const reactSrc = readFileSync2(reactAbsPath, "utf-8");
6722
6787
  const wfwKeys = [];
6723
- for (const line of reactSrc.split("\n")) {
6724
- const m = line.match(/^export\s+\{[^}]+\}\s*from/);
6725
- if (m) {
6726
- const names = line.slice(line.indexOf("{") + 1, line.indexOf("}")).split(",").map((s) => s.trim()).filter(Boolean);
6788
+ if (reactAbsPath.endsWith(".ts")) {
6789
+ for (const line of reactSrc.split("\n")) {
6790
+ const m = line.match(/^export\s+\{[^}]+\}\s*from/);
6791
+ if (m) {
6792
+ const names = line.slice(line.indexOf("{") + 1, line.indexOf("}")).split(",").map((s) => s.trim()).filter(Boolean);
6793
+ for (const n of names) {
6794
+ if (!n.startsWith("type ") && !wfwKeys.includes(n)) wfwKeys.push(n);
6795
+ }
6796
+ }
6797
+ }
6798
+ } else {
6799
+ const exportMatch = reactSrc.match(/\bexport\s*\{([^}]+)\}\s*;/);
6800
+ if (exportMatch) {
6801
+ const names = exportMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
6727
6802
  for (const n of names) {
6728
6803
  if (!n.startsWith("type ") && !wfwKeys.includes(n)) wfwKeys.push(n);
6729
6804
  }
@@ -6736,7 +6811,7 @@ async function compileVendorBundle() {
6736
6811
  if (unique.length > 0) stmts.push(`export { ${unique.join(", ")} } from ${JSON.stringify(request)};`);
6737
6812
  }
6738
6813
  const uidWfw = wfwKeys.filter((k) => !used.has(k) && used.add(k));
6739
- if (uidWfw.length > 0) stmts.push(`export { ${uidWfw.join(", ")} } from ${JSON.stringify(reactTsPath)};`);
6814
+ if (uidWfw.length > 0) stmts.push(`export { ${uidWfw.join(", ")} } from ${JSON.stringify(reactAbsPath)};`);
6740
6815
  const result = await esbuild.build({
6741
6816
  stdin: { contents: stmts.join("\n"), resolveDir: process.cwd() },
6742
6817
  format: "esm",
@@ -7105,12 +7180,6 @@ function liveRouter(dir) {
7105
7180
  const r = new Router();
7106
7181
  compileVendorBundle().catch(() => {
7107
7182
  });
7108
- r.get("/__wfw/v/bundle", async () => {
7109
- const code = await compileVendorBundle();
7110
- return new Response(code, {
7111
- headers: { "content-type": "application/javascript; charset=utf-8" }
7112
- });
7113
- });
7114
7183
  r.get("/__wfw/h/:hash", async (req, ctx) => {
7115
7184
  const hash = ctx.params.hash.replace(/\.js$/i, "");
7116
7185
  const code = hotBundleCache.get(hash);
@@ -7553,11 +7622,11 @@ function ssr(opts) {
7553
7622
  if (existsSync4(join5(dir, "app", "globals.css"))) {
7554
7623
  r.use("/", tailwindRouter(dir));
7555
7624
  }
7625
+ let devWatcher;
7556
7626
  if (isDev2) {
7557
7627
  r.use("/", liveRouter(dir));
7558
7628
  r.ws("/__weifuwu/livereload", liveWs());
7559
- const watcher = liveWatcher(dir);
7560
- r.close = watcher.close;
7629
+ devWatcher = liveWatcher(dir);
7561
7630
  }
7562
7631
  r.all("/*", async (req, ctx) => {
7563
7632
  const prefix = ctx.mountPath || "";
@@ -7566,7 +7635,17 @@ function ssr(opts) {
7566
7635
  const segments = relativePath.split("/").filter(Boolean);
7567
7636
  const resolved = await resolveRoute(dir, segments, routeCache);
7568
7637
  if (!resolved) {
7569
- return isDev2 ? Response.json({ error: "Not Found", path: "/" + segments.join("/"), method: req.method }, { status: 404 }) : new Response("Not Found", { status: 404 });
7638
+ if (isDev2) {
7639
+ const pages = discoverRoutes(dir).map((p) => p.path).sort();
7640
+ return Response.json({
7641
+ error: "Not Found",
7642
+ path: "/" + segments.join("/"),
7643
+ method: req.method,
7644
+ hint: "Available SSR pages",
7645
+ pages
7646
+ }, { status: 404 });
7647
+ }
7648
+ return new Response("Not Found", { status: 404 });
7570
7649
  }
7571
7650
  const mws = [
7572
7651
  ...resolved.errorFiles.map((f) => errorBoundary(f)),
@@ -7578,6 +7657,7 @@ function ssr(opts) {
7578
7657
  });
7579
7658
  const mod = r;
7580
7659
  mod.pages = () => discoverRoutes(dir);
7660
+ if (devWatcher) mod.close = devWatcher.close.bind(devWatcher);
7581
7661
  return mod;
7582
7662
  }
7583
7663
 
@@ -8929,7 +9009,6 @@ function flash(options) {
8929
9009
  value = raw;
8930
9010
  }
8931
9011
  }
8932
- ;
8933
9012
  ctx.flash = {
8934
9013
  value,
8935
9014
  set: makeSetFlash(name, referer)
@@ -10490,8 +10569,7 @@ function session(options) {
10490
10569
  await store2.set(newId, data, ttl);
10491
10570
  await store2.destroy(loadedSid);
10492
10571
  loadedSid = newId;
10493
- currentSession[kId] = newId;
10494
- currentSession[kCreatedAt] = data[kCreatedAt];
10572
+ Object.assign(currentSession, { [kId]: newId, [kCreatedAt]: data[kCreatedAt] });
10495
10573
  }
10496
10574
  const currentData = isSessionActive(currentSession) ? JSON.stringify(currentSession) : null;
10497
10575
  const wasSaved = currentSession[kSaved];
@@ -11402,7 +11480,8 @@ function permissions(options) {
11402
11480
  });
11403
11481
  function requireRole(...roles) {
11404
11482
  return (req, ctx, next) => {
11405
- if (!ctx.permissions?.roles || !roles.some((r) => ctx.permissions.roles.has(r))) {
11483
+ const p = ctx;
11484
+ if (!p.permissions?.roles || !roles.some((r) => p.permissions.roles.has(r))) {
11406
11485
  return Response.json(
11407
11486
  { error: `Forbidden: requires one of roles [${roles.join(", ")}]` },
11408
11487
  { status: 403 }
@@ -11413,12 +11492,13 @@ function permissions(options) {
11413
11492
  }
11414
11493
  function requirePermission(...perms) {
11415
11494
  return (req, ctx, next) => {
11416
- const userPerms = ctx.permissions?.permissions;
11495
+ const p = ctx;
11496
+ const userPerms = p.permissions?.permissions;
11417
11497
  if (!userPerms) {
11418
11498
  return Response.json({ error: "Forbidden: no permissions loaded" }, { status: 403 });
11419
11499
  }
11420
11500
  if (userPerms.has("*")) return next(req, ctx);
11421
- const missing = perms.filter((p) => !userPerms.has(p));
11501
+ const missing = perms.filter((p2) => !userPerms.has(p2));
11422
11502
  if (missing.length > 0) {
11423
11503
  return Response.json(
11424
11504
  { error: `Forbidden: missing permissions [${missing.join(", ")}]` },
@@ -11484,6 +11564,7 @@ export {
11484
11564
  helmet,
11485
11565
  i18n,
11486
11566
  iii,
11567
+ isBundled,
11487
11568
  isDev,
11488
11569
  isProd,
11489
11570
  knowledgeBase,
package/dist/mailer.d.ts CHANGED
@@ -1,20 +1,50 @@
1
1
  import type { Transporter } from 'nodemailer';
2
+ /** Options for sending an email. */
2
3
  export interface MailOptions {
4
+ /** Recipient address(es). */
3
5
  to: string | string[];
6
+ /** Email subject. */
4
7
  subject: string;
8
+ /** Plain text body. */
5
9
  text?: string;
10
+ /** HTML body. */
6
11
  html?: string;
12
+ /** Sender address (overrides `MailerOptions.from`). */
7
13
  from?: string;
14
+ /** CC recipient(s). */
8
15
  cc?: string | string[];
16
+ /** BCC recipient(s). */
9
17
  bcc?: string | string[];
10
18
  }
19
+ /** Options for {@link mailer}. */
11
20
  export interface MailerOptions {
21
+ /** Nodemailer transport string or pre-built transporter object. */
12
22
  transport?: string | Transporter;
23
+ /** Default sender address. */
13
24
  from?: string;
25
+ /** Custom send function (bypasses nodemailer). */
14
26
  send?: (opts: MailOptions) => Promise<void>;
15
27
  }
28
+ /** Mailer instance returned by {@link mailer}. */
16
29
  export interface Mailer {
30
+ /** Send an email. */
17
31
  send: (opts: MailOptions) => Promise<void>;
32
+ /** Close the nodemailer transport. */
18
33
  close: () => Promise<void>;
19
34
  }
35
+ /**
36
+ * Create a mailer instance.
37
+ *
38
+ * ```ts
39
+ * import { mailer } from 'weifuwu'
40
+ *
41
+ * const email = mailer({ transport: 'smtp://user:pass@smtp.example.com' })
42
+ * await email.send({
43
+ * to: 'user@example.com',
44
+ * subject: 'Hello',
45
+ * text: 'Hello from weifuwu!',
46
+ * })
47
+ * await email.close()
48
+ * ```
49
+ */
20
50
  export declare function mailer(options: MailerOptions): Mailer;
@@ -1,9 +1,11 @@
1
1
  import type { Sql } from './vendor.ts';
2
- import type { Middleware, Context } from './types.ts';
2
+ import type { Middleware, Context, Handler } from './types.ts';
3
3
  declare module './types.ts' {
4
4
  interface Context {
5
- roles: Set<string>;
6
- permissions: Set<string>;
5
+ permissions: {
6
+ roles: Set<string>;
7
+ permissions: Set<string>;
8
+ };
7
9
  }
8
10
  }
9
11
  export interface PermissionsOptions {
@@ -1,43 +1,99 @@
1
1
  import { SQL } from './sql.ts';
2
+ /** Reference to another table's column (foreign key). */
2
3
  export interface ColumnReference {
4
+ /** Referenced table name. */
3
5
  table: string;
6
+ /** Referenced column name (default: `'id'`). */
4
7
  column: string;
8
+ /** `ON DELETE` action (e.g. `'cascade'`, `'set null'`). */
5
9
  onDelete?: string;
6
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
+ */
7
20
  export declare class ColumnBuilder<T> {
21
+ /** Column name. */
8
22
  name: string;
23
+ /** SQL type string (e.g. `'TEXT'`, `'INTEGER'`). */
9
24
  sqlType: string;
25
+ /** Whether this column is PRIMARY KEY. */
10
26
  isPrimaryKey: boolean;
27
+ /** Whether this column allows NULL. */
11
28
  isNullable: boolean;
29
+ /** Whether this column has a UNIQUE constraint. */
12
30
  isUnique: boolean;
31
+ /** Whether the value is auto-generated (e.g. SERIAL, UUID defaults). */
13
32
  isAutoGenerate: boolean;
33
+ /** DEFAULT expression as a raw SQL string. */
14
34
  defaultExpr: string | null;
35
+ /** Foreign key reference, if any. */
15
36
  ref: ColumnReference | null;
16
37
  constructor(name: string, sqlType: string);
38
+ /** Mark as PRIMARY KEY (implies NOT NULL). */
17
39
  primaryKey(): this;
40
+ /** Add NOT NULL constraint. */
18
41
  notNull(): this;
42
+ /** Allow NULL values (default). */
19
43
  nullable(): this;
44
+ /** Set a DEFAULT value. Accepts raw SQL, string, number, or boolean. */
20
45
  default(expr: SQL | string | number | boolean): this;
46
+ /** Add UNIQUE constraint. */
21
47
  unique(): this;
48
+ /** Add FOREIGN KEY reference to another table. */
22
49
  references(table: string, column?: string, onDelete?: string): this;
23
50
  }
51
+ /** Auto-incrementing integer primary key (`SERIAL`). */
24
52
  export declare function serial(name: string): ColumnBuilder<number>;
53
+ /** UUID column. */
25
54
  export declare function uuid(name: string): ColumnBuilder<string>;
55
+ /** TEXT column. */
26
56
  export declare function text(name: string): ColumnBuilder<string>;
57
+ /** INTEGER column. */
27
58
  export declare function integer(name: string): ColumnBuilder<number>;
59
+ /** BOOLEAN column (exported as `boolean`). */
28
60
  export declare function boolean_(name: string): ColumnBuilder<boolean>;
29
61
  export { boolean_ as boolean };
62
+ /** TIMESTAMPTZ column (timestamp with time zone). */
30
63
  export declare function timestamptz(name: string): ColumnBuilder<string>;
64
+ /** JSONB column (stores arbitrary JSON data). */
31
65
  export declare function jsonb<T = unknown>(name: string): ColumnBuilder<T>;
66
+ /** TEXT[] column (PostgreSQL array of text). */
32
67
  export declare function textArray(name: string): ColumnBuilder<string[]>;
68
+ /** Vector column for pgvector (embedding storage). Requires `dimensions`. */
33
69
  export declare function vector(name: string, dims: number): ColumnBuilder<number[]>;
34
70
  export interface PartitionByDef {
35
71
  type: 'RANGE' | 'LIST' | 'HASH';
36
72
  column: string;
37
73
  }
38
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
+ */
39
87
  export declare function timestamps(): {
40
88
  readonly created_at: ColumnBuilder<string>;
41
89
  readonly updated_at: ColumnBuilder<string>;
42
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
+ */
43
99
  export declare function toDDL(col: ColumnBuilder<unknown>): string;
@@ -1,7 +1,22 @@
1
+ /**
2
+ * A parameterized SQL fragment with template strings and bound values.
3
+ * Used internally by the schema builder and where helpers.
4
+ */
1
5
  export declare class SQL {
6
+ /** Template string parts (interleaved with values). */
2
7
  strings: TemplateStringsArray;
8
+ /** Bound parameter values. */
3
9
  values: unknown[];
4
10
  constructor(strings: TemplateStringsArray, values: unknown[]);
11
+ /** Serialize to a raw SQL string (interpolating values inline for DDL use). */
5
12
  toSQL(): string;
6
13
  }
14
+ /**
15
+ * Tagged template helper for creating parameterized SQL fragments.
16
+ *
17
+ * ```ts
18
+ * sql`NOW()`
19
+ * sql`${column} ILIKE ${'%' + search + '%'}`
20
+ * ```
21
+ */
7
22
  export declare function sql(strings: TemplateStringsArray, ...values: unknown[]): SQL;
@@ -1,30 +1,69 @@
1
1
  import type { Sql } from '../../vendor.ts';
2
2
  import { ColumnBuilder, type PartitionByDef } from './columns.ts';
3
3
  import { SQL } from './sql.ts';
4
+ /** Options for table index creation. */
4
5
  export interface IndexOptions {
6
+ /** Whether the index should be UNIQUE. */
5
7
  unique?: boolean;
8
+ /** Index type: btree (default), hnsw (pgvector), gin (JSONB). */
6
9
  type?: 'btree' | 'hnsw' | 'gin';
10
+ /** Create index in DESC order. */
7
11
  desc?: boolean;
12
+ /** Custom operator class (e.g. `vector_cosine_ops`). */
8
13
  operator?: string;
9
14
  }
15
+ /** Options for CREATE TABLE. */
10
16
  export interface CreateOptions {
17
+ /** Partition by clause (RANGE, LIST, or HASH). */
11
18
  partitionBy?: PartitionByDef;
12
19
  }
20
+ /** Options for find/read queries. */
13
21
  export interface FindOptions {
22
+ /** ORDER BY clause: `{ column: 'asc' | 'desc' }`. */
14
23
  orderBy?: Record<string, 'asc' | 'desc'>;
24
+ /** LIMIT. */
15
25
  limit?: number;
26
+ /** OFFSET. */
16
27
  offset?: number;
28
+ /** Columns to SELECT (default: all). */
17
29
  select?: string[];
30
+ /** Include soft-deleted rows (also sets `withDeleted` context). */
18
31
  withDeleted?: boolean;
19
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
+ */
20
52
  export declare class Table<R extends Record<string, unknown>> {
53
+ /** Database table name. */
21
54
  readonly tableName: string;
55
+ /** All column builders (order-preserving). */
22
56
  readonly columns: ColumnBuilder<unknown>[];
57
+ /** Column builders keyed by property name. */
23
58
  readonly builders: Record<string, ColumnBuilder<unknown>>;
24
59
  private colEntries;
25
60
  constructor(tableName: string, builders: Record<string, ColumnBuilder<unknown>>);
61
+ /** Check if the table has a column with the given DB name. */
26
62
  hasColumn(dbName: string): boolean;
27
- /** Returns a BoundTable pre-bound to the given sql connection (no need to pass sql to every method). */
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
+ */
28
67
  bind(sql: Sql<{}>): BoundTable<R>;
29
68
  /** Returns the primary key column name (DB name), or 'id' as fallback. */
30
69
  private get pkColumn();
@@ -81,6 +120,20 @@ export declare class BoundTable<R extends Record<string, unknown>> {
81
120
  count(where?: Partial<R> | SQL | SQL[]): Promise<number>;
82
121
  withSql(sql: Sql<{}>): BoundTable<R>;
83
122
  }
123
+ /**
124
+ * Define a type-safe table schema.
125
+ *
126
+ * ```ts
127
+ * const users = pgTable('users', {
128
+ * id: serial('id').primaryKey(),
129
+ * name: text('name').notNull(),
130
+ * email: text('email').unique(),
131
+ * })
132
+ *
133
+ * // The generic type R preserves the column types:
134
+ * // Table<{ id: number; name: string; email: string }>
135
+ * ```
136
+ */
84
137
  export declare function pgTable<R extends Record<string, unknown>>(tableName: string, builders: {
85
138
  [K in keyof R]: ColumnBuilder<R[K]>;
86
139
  }): Table<R>;