weifuwu 0.23.1 → 0.23.3
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 +25 -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.js +111 -45
- 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 +4 -2
- package/cli/template/.weifuwu/ssr/560568d7.js +0 -14
package/dist/index.js
CHANGED
|
@@ -1619,22 +1619,6 @@ function compress(options) {
|
|
|
1619
1619
|
}
|
|
1620
1620
|
|
|
1621
1621
|
// 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
1622
|
var HEADER_MAP = {
|
|
1639
1623
|
"Content-Security-Policy": "contentSecurityPolicy",
|
|
1640
1624
|
"Cross-Origin-Embedder-Policy": "crossOriginEmbedderPolicy",
|
|
@@ -1667,6 +1651,22 @@ function helmet(options) {
|
|
|
1667
1651
|
return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
|
|
1668
1652
|
};
|
|
1669
1653
|
}
|
|
1654
|
+
var DEFAULTS = {
|
|
1655
|
+
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",
|
|
1656
|
+
crossOriginEmbedderPolicy: "require-corp",
|
|
1657
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
1658
|
+
crossOriginResourcePolicy: "same-origin",
|
|
1659
|
+
originAgentCluster: "?1",
|
|
1660
|
+
referrerPolicy: "no-referrer",
|
|
1661
|
+
strictTransportSecurity: "max-age=15552000; includeSubDomains",
|
|
1662
|
+
xContentTypeOptions: "nosniff",
|
|
1663
|
+
xDnsPrefetchControl: "off",
|
|
1664
|
+
xDownloadOptions: "noopen",
|
|
1665
|
+
xFrameOptions: "SAMEORIGIN",
|
|
1666
|
+
xPermittedCrossDomainPolicies: "none",
|
|
1667
|
+
xXssProtection: "0",
|
|
1668
|
+
permissionsPolicy: "camera=(),display-capture=(),fullscreen=(),geolocation=(),microphone=()"
|
|
1669
|
+
};
|
|
1670
1670
|
|
|
1671
1671
|
// request-id.ts
|
|
1672
1672
|
import crypto2 from "node:crypto";
|
|
@@ -2441,7 +2441,7 @@ function aiProvider(options) {
|
|
|
2441
2441
|
return result.embedding;
|
|
2442
2442
|
},
|
|
2443
2443
|
async embedMany(texts) {
|
|
2444
|
-
const result = await aiEmbedMany({ model: this.embeddingModel(),
|
|
2444
|
+
const result = await aiEmbedMany({ model: this.embeddingModel(), values: texts });
|
|
2445
2445
|
return result.embeddings;
|
|
2446
2446
|
},
|
|
2447
2447
|
generateText(params) {
|
|
@@ -2474,12 +2474,15 @@ import postgresFactory from "postgres";
|
|
|
2474
2474
|
|
|
2475
2475
|
// postgres/schema/sql.ts
|
|
2476
2476
|
var SQL = class {
|
|
2477
|
+
/** Template string parts (interleaved with values). */
|
|
2477
2478
|
strings;
|
|
2479
|
+
/** Bound parameter values. */
|
|
2478
2480
|
values;
|
|
2479
2481
|
constructor(strings, values) {
|
|
2480
2482
|
this.strings = strings;
|
|
2481
2483
|
this.values = values;
|
|
2482
2484
|
}
|
|
2485
|
+
/** Serialize to a raw SQL string (interpolating values inline for DDL use). */
|
|
2483
2486
|
toSQL() {
|
|
2484
2487
|
let result = "";
|
|
2485
2488
|
for (let i = 0; i < this.strings.length; i++) {
|
|
@@ -2497,31 +2500,43 @@ function sql(strings, ...values) {
|
|
|
2497
2500
|
|
|
2498
2501
|
// postgres/schema/columns.ts
|
|
2499
2502
|
var ColumnBuilder = class {
|
|
2503
|
+
/** Column name. */
|
|
2500
2504
|
name;
|
|
2505
|
+
/** SQL type string (e.g. `'TEXT'`, `'INTEGER'`). */
|
|
2501
2506
|
sqlType;
|
|
2507
|
+
/** Whether this column is PRIMARY KEY. */
|
|
2502
2508
|
isPrimaryKey = false;
|
|
2509
|
+
/** Whether this column allows NULL. */
|
|
2503
2510
|
isNullable = true;
|
|
2511
|
+
/** Whether this column has a UNIQUE constraint. */
|
|
2504
2512
|
isUnique = false;
|
|
2513
|
+
/** Whether the value is auto-generated (e.g. SERIAL, UUID defaults). */
|
|
2505
2514
|
isAutoGenerate = false;
|
|
2515
|
+
/** DEFAULT expression as a raw SQL string. */
|
|
2506
2516
|
defaultExpr = null;
|
|
2517
|
+
/** Foreign key reference, if any. */
|
|
2507
2518
|
ref = null;
|
|
2508
2519
|
constructor(name, sqlType) {
|
|
2509
2520
|
this.name = name;
|
|
2510
2521
|
this.sqlType = sqlType;
|
|
2511
2522
|
}
|
|
2523
|
+
/** Mark as PRIMARY KEY (implies NOT NULL). */
|
|
2512
2524
|
primaryKey() {
|
|
2513
2525
|
this.isPrimaryKey = true;
|
|
2514
2526
|
this.isNullable = false;
|
|
2515
2527
|
return this;
|
|
2516
2528
|
}
|
|
2529
|
+
/** Add NOT NULL constraint. */
|
|
2517
2530
|
notNull() {
|
|
2518
2531
|
this.isNullable = false;
|
|
2519
2532
|
return this;
|
|
2520
2533
|
}
|
|
2534
|
+
/** Allow NULL values (default). */
|
|
2521
2535
|
nullable() {
|
|
2522
2536
|
this.isNullable = true;
|
|
2523
2537
|
return this;
|
|
2524
2538
|
}
|
|
2539
|
+
/** Set a DEFAULT value. Accepts raw SQL, string, number, or boolean. */
|
|
2525
2540
|
default(expr) {
|
|
2526
2541
|
if (expr instanceof SQL) {
|
|
2527
2542
|
this.defaultExpr = expr.toSQL();
|
|
@@ -2532,10 +2547,12 @@ var ColumnBuilder = class {
|
|
|
2532
2547
|
}
|
|
2533
2548
|
return this;
|
|
2534
2549
|
}
|
|
2550
|
+
/** Add UNIQUE constraint. */
|
|
2535
2551
|
unique() {
|
|
2536
2552
|
this.isUnique = true;
|
|
2537
2553
|
return this;
|
|
2538
2554
|
}
|
|
2555
|
+
/** Add FOREIGN KEY reference to another table. */
|
|
2539
2556
|
references(table, column = "id", onDelete) {
|
|
2540
2557
|
this.ref = { table, column, onDelete };
|
|
2541
2558
|
return this;
|
|
@@ -2629,8 +2646,11 @@ function and(...conditions) {
|
|
|
2629
2646
|
|
|
2630
2647
|
// postgres/schema/table.ts
|
|
2631
2648
|
var Table = class {
|
|
2649
|
+
/** Database table name. */
|
|
2632
2650
|
tableName;
|
|
2651
|
+
/** All column builders (order-preserving). */
|
|
2633
2652
|
columns;
|
|
2653
|
+
/** Column builders keyed by property name. */
|
|
2634
2654
|
builders;
|
|
2635
2655
|
colEntries;
|
|
2636
2656
|
constructor(tableName, builders) {
|
|
@@ -2644,10 +2664,14 @@ var Table = class {
|
|
|
2644
2664
|
column: col2
|
|
2645
2665
|
}));
|
|
2646
2666
|
}
|
|
2667
|
+
/** Check if the table has a column with the given DB name. */
|
|
2647
2668
|
hasColumn(dbName) {
|
|
2648
2669
|
return this.colEntries.some((e) => e.db === dbName);
|
|
2649
2670
|
}
|
|
2650
|
-
/**
|
|
2671
|
+
/**
|
|
2672
|
+
* Bind this table schema to a SQL connection, returning a `BoundTable`
|
|
2673
|
+
* that can run queries without passing `sql` to every call.
|
|
2674
|
+
*/
|
|
2651
2675
|
bind(sql2) {
|
|
2652
2676
|
return new BoundTable(sql2, this.tableName, this.builders);
|
|
2653
2677
|
}
|
|
@@ -3772,6 +3796,18 @@ function user(options) {
|
|
|
3772
3796
|
return null;
|
|
3773
3797
|
}
|
|
3774
3798
|
}
|
|
3799
|
+
function extractToken(req, cookieName) {
|
|
3800
|
+
const header = req.headers.get("Authorization");
|
|
3801
|
+
if (header?.startsWith("Bearer ")) return header.slice(7);
|
|
3802
|
+
if (cookieName) {
|
|
3803
|
+
const cookies = req.headers.get("cookie")?.split(";").map((c) => c.trim()).filter(Boolean) || [];
|
|
3804
|
+
for (const c of cookies) {
|
|
3805
|
+
const eq2 = c.indexOf("=");
|
|
3806
|
+
if (eq2 > 0 && c.slice(0, eq2) === cookieName) return c.slice(eq2 + 1);
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
return null;
|
|
3810
|
+
}
|
|
3775
3811
|
function middleware() {
|
|
3776
3812
|
return async (req, ctx, next) => {
|
|
3777
3813
|
const sessionUserId = ctx.session?.userId;
|
|
@@ -3788,8 +3824,7 @@ function user(options) {
|
|
|
3788
3824
|
delete ctx.session?.userId;
|
|
3789
3825
|
}
|
|
3790
3826
|
}
|
|
3791
|
-
const
|
|
3792
|
-
const token = header?.startsWith("Bearer ") ? header.slice(7) : null;
|
|
3827
|
+
const token = extractToken(req);
|
|
3793
3828
|
if (token) {
|
|
3794
3829
|
const userData = await verify(token);
|
|
3795
3830
|
if (userData) {
|
|
@@ -3800,11 +3835,36 @@ function user(options) {
|
|
|
3800
3835
|
return new Response("Unauthorized", { status: 401, headers: { "WWW-Authenticate": "Bearer" } });
|
|
3801
3836
|
};
|
|
3802
3837
|
}
|
|
3838
|
+
function middlewareOptional(opts) {
|
|
3839
|
+
const cookieName = opts?.cookie;
|
|
3840
|
+
return async (req, ctx, next) => {
|
|
3841
|
+
const token = extractToken(req, cookieName);
|
|
3842
|
+
if (token) {
|
|
3843
|
+
const userData = await verify(token);
|
|
3844
|
+
if (userData) {
|
|
3845
|
+
ctx.user = userData;
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
return next(req, ctx);
|
|
3849
|
+
};
|
|
3850
|
+
}
|
|
3851
|
+
async function parseBody2(req) {
|
|
3852
|
+
const ct = req.headers.get("content-type") || "";
|
|
3853
|
+
if (ct.includes("application/json")) {
|
|
3854
|
+
return req.json();
|
|
3855
|
+
}
|
|
3856
|
+
const form = await req.formData();
|
|
3857
|
+
const obj = {};
|
|
3858
|
+
for (const [key, val] of form) {
|
|
3859
|
+
obj[key] = val;
|
|
3860
|
+
}
|
|
3861
|
+
return obj;
|
|
3862
|
+
}
|
|
3803
3863
|
function router() {
|
|
3804
3864
|
const r2 = new Router();
|
|
3805
3865
|
r2.post("/register", async (req) => {
|
|
3806
3866
|
try {
|
|
3807
|
-
const body = await req
|
|
3867
|
+
const body = await parseBody2(req);
|
|
3808
3868
|
const result = await register(body);
|
|
3809
3869
|
return Response.json(result, { status: 201 });
|
|
3810
3870
|
} catch (err) {
|
|
@@ -3817,7 +3877,7 @@ function user(options) {
|
|
|
3817
3877
|
});
|
|
3818
3878
|
r2.post("/login", async (req, ctx) => {
|
|
3819
3879
|
try {
|
|
3820
|
-
const body = await req
|
|
3880
|
+
const body = await parseBody2(req);
|
|
3821
3881
|
const result = await login(body);
|
|
3822
3882
|
if (ctx.session) {
|
|
3823
3883
|
;
|
|
@@ -3861,6 +3921,7 @@ function user(options) {
|
|
|
3861
3921
|
}
|
|
3862
3922
|
const mod = r;
|
|
3863
3923
|
mod.middleware = middleware;
|
|
3924
|
+
mod.middlewareOptional = middlewareOptional;
|
|
3864
3925
|
mod.migrate = migrate;
|
|
3865
3926
|
mod.register = register;
|
|
3866
3927
|
mod.login = login;
|
|
@@ -3993,7 +4054,7 @@ function attachCron(q, handlers) {
|
|
|
3993
4054
|
function createMemoryQueue(opts) {
|
|
3994
4055
|
const pollInterval = opts?.pollInterval ?? 200;
|
|
3995
4056
|
const handlers = /* @__PURE__ */ new Map();
|
|
3996
|
-
const
|
|
4057
|
+
const pending = [];
|
|
3997
4058
|
const failed = [];
|
|
3998
4059
|
const MAX_FAILED = 1e3;
|
|
3999
4060
|
let running = false;
|
|
@@ -4004,8 +4065,8 @@ function createMemoryQueue(opts) {
|
|
|
4004
4065
|
const MAX_CONCURRENT = 16;
|
|
4005
4066
|
function insertJob(job) {
|
|
4006
4067
|
let i = 0;
|
|
4007
|
-
while (i <
|
|
4008
|
-
|
|
4068
|
+
while (i < pending.length && pending[i].runAt <= job.runAt) i++;
|
|
4069
|
+
pending.splice(i, 0, job);
|
|
4009
4070
|
}
|
|
4010
4071
|
async function execute(job, handler) {
|
|
4011
4072
|
inflight++;
|
|
@@ -4030,8 +4091,8 @@ function createMemoryQueue(opts) {
|
|
|
4030
4091
|
async function poll() {
|
|
4031
4092
|
if (!running) return;
|
|
4032
4093
|
const now = Date.now();
|
|
4033
|
-
while (running && inflight < MAX_CONCURRENT &&
|
|
4034
|
-
const job =
|
|
4094
|
+
while (running && inflight < MAX_CONCURRENT && pending.length > 0 && pending[0].runAt <= now) {
|
|
4095
|
+
const job = pending.shift();
|
|
4035
4096
|
const handler = handlers.get(job.type);
|
|
4036
4097
|
if (handler) execute(job, handler);
|
|
4037
4098
|
}
|
|
@@ -4081,8 +4142,8 @@ function createMemoryQueue(opts) {
|
|
|
4081
4142
|
mw.stop();
|
|
4082
4143
|
while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
|
|
4083
4144
|
};
|
|
4084
|
-
mw.jobs = async function
|
|
4085
|
-
return
|
|
4145
|
+
mw.jobs = async function(limit) {
|
|
4146
|
+
return pending.slice(0, limit ?? 50);
|
|
4086
4147
|
};
|
|
4087
4148
|
mw.failedJobs = async function failedJobs(limit) {
|
|
4088
4149
|
return failed.slice(0, limit ?? 50);
|
|
@@ -5626,7 +5687,7 @@ function createRunner(deps) {
|
|
|
5626
5687
|
}
|
|
5627
5688
|
}
|
|
5628
5689
|
async function addKnowledge(agentId, title, content) {
|
|
5629
|
-
const chunks = chunkContent(content);
|
|
5690
|
+
const chunks = chunkContent(content, 1024, 128);
|
|
5630
5691
|
const [first] = chunks;
|
|
5631
5692
|
const embedding = await provider.embed(first);
|
|
5632
5693
|
const vec = `[${embedding.join(",")}]`;
|
|
@@ -7105,12 +7166,6 @@ function liveRouter(dir) {
|
|
|
7105
7166
|
const r = new Router();
|
|
7106
7167
|
compileVendorBundle().catch(() => {
|
|
7107
7168
|
});
|
|
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
7169
|
r.get("/__wfw/h/:hash", async (req, ctx) => {
|
|
7115
7170
|
const hash = ctx.params.hash.replace(/\.js$/i, "");
|
|
7116
7171
|
const code = hotBundleCache.get(hash);
|
|
@@ -7553,11 +7608,11 @@ function ssr(opts) {
|
|
|
7553
7608
|
if (existsSync4(join5(dir, "app", "globals.css"))) {
|
|
7554
7609
|
r.use("/", tailwindRouter(dir));
|
|
7555
7610
|
}
|
|
7611
|
+
let devWatcher;
|
|
7556
7612
|
if (isDev2) {
|
|
7557
7613
|
r.use("/", liveRouter(dir));
|
|
7558
7614
|
r.ws("/__weifuwu/livereload", liveWs());
|
|
7559
|
-
|
|
7560
|
-
r.close = watcher.close;
|
|
7615
|
+
devWatcher = liveWatcher(dir);
|
|
7561
7616
|
}
|
|
7562
7617
|
r.all("/*", async (req, ctx) => {
|
|
7563
7618
|
const prefix = ctx.mountPath || "";
|
|
@@ -7566,7 +7621,17 @@ function ssr(opts) {
|
|
|
7566
7621
|
const segments = relativePath.split("/").filter(Boolean);
|
|
7567
7622
|
const resolved = await resolveRoute(dir, segments, routeCache);
|
|
7568
7623
|
if (!resolved) {
|
|
7569
|
-
|
|
7624
|
+
if (isDev2) {
|
|
7625
|
+
const pages = discoverRoutes(dir).map((p) => p.path).sort();
|
|
7626
|
+
return Response.json({
|
|
7627
|
+
error: "Not Found",
|
|
7628
|
+
path: "/" + segments.join("/"),
|
|
7629
|
+
method: req.method,
|
|
7630
|
+
hint: "Available SSR pages",
|
|
7631
|
+
pages
|
|
7632
|
+
}, { status: 404 });
|
|
7633
|
+
}
|
|
7634
|
+
return new Response("Not Found", { status: 404 });
|
|
7570
7635
|
}
|
|
7571
7636
|
const mws = [
|
|
7572
7637
|
...resolved.errorFiles.map((f) => errorBoundary(f)),
|
|
@@ -7578,6 +7643,7 @@ function ssr(opts) {
|
|
|
7578
7643
|
});
|
|
7579
7644
|
const mod = r;
|
|
7580
7645
|
mod.pages = () => discoverRoutes(dir);
|
|
7646
|
+
if (devWatcher) mod.close = devWatcher.close.bind(devWatcher);
|
|
7581
7647
|
return mod;
|
|
7582
7648
|
}
|
|
7583
7649
|
|
|
@@ -8929,7 +8995,6 @@ function flash(options) {
|
|
|
8929
8995
|
value = raw;
|
|
8930
8996
|
}
|
|
8931
8997
|
}
|
|
8932
|
-
;
|
|
8933
8998
|
ctx.flash = {
|
|
8934
8999
|
value,
|
|
8935
9000
|
set: makeSetFlash(name, referer)
|
|
@@ -10490,8 +10555,7 @@ function session(options) {
|
|
|
10490
10555
|
await store2.set(newId, data, ttl);
|
|
10491
10556
|
await store2.destroy(loadedSid);
|
|
10492
10557
|
loadedSid = newId;
|
|
10493
|
-
currentSession[kId]
|
|
10494
|
-
currentSession[kCreatedAt] = data[kCreatedAt];
|
|
10558
|
+
Object.assign(currentSession, { [kId]: newId, [kCreatedAt]: data[kCreatedAt] });
|
|
10495
10559
|
}
|
|
10496
10560
|
const currentData = isSessionActive(currentSession) ? JSON.stringify(currentSession) : null;
|
|
10497
10561
|
const wasSaved = currentSession[kSaved];
|
|
@@ -11402,7 +11466,8 @@ function permissions(options) {
|
|
|
11402
11466
|
});
|
|
11403
11467
|
function requireRole(...roles) {
|
|
11404
11468
|
return (req, ctx, next) => {
|
|
11405
|
-
|
|
11469
|
+
const p = ctx;
|
|
11470
|
+
if (!p.permissions?.roles || !roles.some((r) => p.permissions.roles.has(r))) {
|
|
11406
11471
|
return Response.json(
|
|
11407
11472
|
{ error: `Forbidden: requires one of roles [${roles.join(", ")}]` },
|
|
11408
11473
|
{ status: 403 }
|
|
@@ -11413,12 +11478,13 @@ function permissions(options) {
|
|
|
11413
11478
|
}
|
|
11414
11479
|
function requirePermission(...perms) {
|
|
11415
11480
|
return (req, ctx, next) => {
|
|
11416
|
-
const
|
|
11481
|
+
const p = ctx;
|
|
11482
|
+
const userPerms = p.permissions?.permissions;
|
|
11417
11483
|
if (!userPerms) {
|
|
11418
11484
|
return Response.json({ error: "Forbidden: no permissions loaded" }, { status: 403 });
|
|
11419
11485
|
}
|
|
11420
11486
|
if (userPerms.has("*")) return next(req, ctx);
|
|
11421
|
-
const missing = perms.filter((
|
|
11487
|
+
const missing = perms.filter((p2) => !userPerms.has(p2));
|
|
11422
11488
|
if (missing.length > 0) {
|
|
11423
11489
|
return Response.json(
|
|
11424
11490
|
{ error: `Forbidden: missing permissions [${missing.join(", ")}]` },
|
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>;
|