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.
- package/README.md +55 -0
- package/cli/template/.weifuwu/ssr/2e3a7e60.js +3 -3
- package/dist/analytics.d.ts +20 -0
- package/dist/client-locale.d.ts +20 -0
- package/dist/client-theme.d.ts +29 -0
- package/dist/compress.d.ts +14 -0
- package/dist/cors.d.ts +15 -0
- package/dist/cron-utils.d.ts +65 -0
- package/dist/csrf.d.ts +29 -0
- package/dist/env.d.ts +33 -5
- package/dist/flash.d.ts +66 -7
- package/dist/health.d.ts +18 -0
- package/dist/helmet.d.ts +15 -0
- package/dist/hub.d.ts +25 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +124 -43
- package/dist/mailer.d.ts +30 -0
- package/dist/permissions.d.ts +5 -3
- package/dist/postgres/schema/columns.d.ts +56 -0
- package/dist/postgres/schema/sql.d.ts +15 -0
- package/dist/postgres/schema/table.d.ts +54 -1
- package/dist/postgres/schema/where.d.ts +14 -0
- package/dist/rate-limit.d.ts +25 -3
- package/dist/request-id.d.ts +19 -0
- package/dist/seo.d.ts +65 -0
- package/dist/sse.d.ts +37 -0
- package/dist/static.d.ts +16 -0
- package/dist/stream.d.ts +15 -0
- package/dist/trace.d.ts +41 -5
- package/dist/types.d.ts +10 -1
- package/dist/upload.d.ts +30 -0
- package/dist/use-action.d.ts +28 -0
- package/dist/use-agent-stream.d.ts +32 -10
- package/dist/use-flash-message.d.ts +16 -0
- package/dist/use-websocket.d.ts +25 -0
- package/dist/user/client.d.ts +15 -0
- package/dist/user/oauth2.d.ts +10 -0
- package/dist/user/types.d.ts +58 -4
- package/dist/webhook.d.ts +40 -15
- package/package.json +5 -3
- package/cli/template/.weifuwu/ssr/560568d7.js +0 -14
- package/dist/kb.d.ts +0 -70
- package/dist/oauth-client.d.ts +0 -41
- package/dist/preferences.d.ts +0 -18
- 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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
6721
|
-
const
|
|
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
|
-
|
|
6724
|
-
const
|
|
6725
|
-
|
|
6726
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
|
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((
|
|
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;
|
package/dist/permissions.d.ts
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
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
|
-
/**
|
|
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>;
|