weifuwu 0.8.0 → 0.8.1
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/dist/index.js +101 -61
- package/dist/serve.d.ts +2 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10718,9 +10718,24 @@ var require_built3 = __commonJS({
|
|
|
10718
10718
|
|
|
10719
10719
|
// serve.ts
|
|
10720
10720
|
import http from "node:http";
|
|
10721
|
-
async function readBody(req) {
|
|
10721
|
+
async function readBody(req, maxSize) {
|
|
10722
|
+
if (maxSize) {
|
|
10723
|
+
const cl = parseInt(req.headers["content-length"] ?? "0", 10);
|
|
10724
|
+
if (cl > maxSize) {
|
|
10725
|
+
const err = new Error("Request body too large");
|
|
10726
|
+
err.status = 413;
|
|
10727
|
+
throw err;
|
|
10728
|
+
}
|
|
10729
|
+
}
|
|
10722
10730
|
const chunks = [];
|
|
10731
|
+
let total = 0;
|
|
10723
10732
|
for await (const chunk of req) {
|
|
10733
|
+
total += chunk.byteLength;
|
|
10734
|
+
if (maxSize && total > maxSize) {
|
|
10735
|
+
const err = new Error("Request body too large");
|
|
10736
|
+
err.status = 413;
|
|
10737
|
+
throw err;
|
|
10738
|
+
}
|
|
10724
10739
|
chunks.push(chunk);
|
|
10725
10740
|
}
|
|
10726
10741
|
return Buffer.concat(chunks);
|
|
@@ -10766,11 +10781,16 @@ function serve(handler, options) {
|
|
|
10766
10781
|
const hostname = options?.hostname ?? "0.0.0.0";
|
|
10767
10782
|
const server = http.createServer(async (req, res) => {
|
|
10768
10783
|
try {
|
|
10769
|
-
const body = await readBody(req);
|
|
10784
|
+
const body = await readBody(req, options?.maxBodySize);
|
|
10770
10785
|
const [request, query] = createRequest(req, body);
|
|
10771
10786
|
const response = await handler(request, { params: {}, query });
|
|
10772
10787
|
await sendResponse(res, response);
|
|
10773
|
-
} catch {
|
|
10788
|
+
} catch (err) {
|
|
10789
|
+
if (err?.status === 413) {
|
|
10790
|
+
res.writeHead(413, { "Content-Type": "text/plain" });
|
|
10791
|
+
res.end("Request Body Too Large");
|
|
10792
|
+
return;
|
|
10793
|
+
}
|
|
10774
10794
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
10775
10795
|
res.end("Internal Server Error");
|
|
10776
10796
|
}
|
|
@@ -11013,8 +11033,13 @@ var Router = class _Router {
|
|
|
11013
11033
|
return mw(innerReq, ctx2, dispatch);
|
|
11014
11034
|
}
|
|
11015
11035
|
return await new Promise((resolve3) => {
|
|
11016
|
-
|
|
11017
|
-
|
|
11036
|
+
try {
|
|
11037
|
+
upgradeSocket(wss, req, socket, head, match.handler, ctx2);
|
|
11038
|
+
resolve3(new Response(null, { status: 101 }));
|
|
11039
|
+
} catch {
|
|
11040
|
+
socket.destroy();
|
|
11041
|
+
resolve3(new Response("WebSocket upgrade failed", { status: 500 }));
|
|
11042
|
+
}
|
|
11018
11043
|
});
|
|
11019
11044
|
};
|
|
11020
11045
|
Promise.resolve(dispatch(webReq, ctx)).then((result) => {
|
|
@@ -11815,7 +11840,7 @@ function validate(schemas) {
|
|
|
11815
11840
|
if (issues.length > 0) {
|
|
11816
11841
|
return Response.json({ error: "Validation failed", issues }, { status: 400 });
|
|
11817
11842
|
}
|
|
11818
|
-
ctx.parsed = parsed;
|
|
11843
|
+
ctx.parsed = { ...ctx.parsed, ...parsed };
|
|
11819
11844
|
return next(req, ctx);
|
|
11820
11845
|
};
|
|
11821
11846
|
}
|
|
@@ -11831,7 +11856,11 @@ function getCookies(req) {
|
|
|
11831
11856
|
const name15 = pair.slice(0, idx).trim();
|
|
11832
11857
|
const value = pair.slice(idx + 1).trim();
|
|
11833
11858
|
if (name15) {
|
|
11834
|
-
|
|
11859
|
+
try {
|
|
11860
|
+
cookies[name15] = decodeURIComponent(value);
|
|
11861
|
+
} catch {
|
|
11862
|
+
cookies[name15] = value;
|
|
11863
|
+
}
|
|
11835
11864
|
}
|
|
11836
11865
|
}
|
|
11837
11866
|
return cookies;
|
|
@@ -11874,67 +11903,45 @@ function upload(options) {
|
|
|
11874
11903
|
const saveDir = options?.dir;
|
|
11875
11904
|
return async (req, ctx, next) => {
|
|
11876
11905
|
const ct = req.headers.get("content-type") ?? "";
|
|
11877
|
-
if (!ct.includes("multipart/form-data"))
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
return Response.json({ error: "
|
|
11906
|
+
if (!ct.includes("multipart/form-data")) return next(req, ctx);
|
|
11907
|
+
let formData;
|
|
11908
|
+
try {
|
|
11909
|
+
formData = await req.formData();
|
|
11910
|
+
} catch {
|
|
11911
|
+
return Response.json({ error: "Invalid multipart data" }, { status: 400 });
|
|
11883
11912
|
}
|
|
11884
|
-
const boundary = match[1] ?? match[2];
|
|
11885
|
-
const body = await req.text();
|
|
11886
|
-
const rawParts = body.split(`--${boundary}`).filter((p) => p && !p.startsWith("--") && !p.startsWith("\r\n--"));
|
|
11887
11913
|
const files = {};
|
|
11888
11914
|
const fields = {};
|
|
11889
|
-
for (const
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
const headers = {};
|
|
11894
|
-
while (i < lines.length && lines[i].length > 0) {
|
|
11895
|
-
const sep3 = lines[i].indexOf(": ");
|
|
11896
|
-
if (sep3 !== -1) headers[lines[i].slice(0, sep3).toLowerCase()] = lines[i].slice(sep3 + 2);
|
|
11897
|
-
i++;
|
|
11898
|
-
}
|
|
11899
|
-
i++;
|
|
11900
|
-
const bodyValue = lines.slice(i).join("\r\n");
|
|
11901
|
-
const disposition = headers["content-disposition"] ?? "";
|
|
11902
|
-
const nameMatch = disposition.match(/name="([^"]*)"/);
|
|
11903
|
-
if (!nameMatch) continue;
|
|
11904
|
-
const name15 = nameMatch[1];
|
|
11905
|
-
const filenameMatch = disposition.match(/filename="([^"]*)"/);
|
|
11906
|
-
const filename = filenameMatch?.[1];
|
|
11907
|
-
if (filename) {
|
|
11908
|
-
const buf = Buffer.from(bodyValue.replace(/\r?\n$/, ""), "binary");
|
|
11909
|
-
if (options?.allowedTypes) {
|
|
11910
|
-
const mime = headers["content-type"] ?? "application/octet-stream";
|
|
11911
|
-
if (!options.allowedTypes.includes(mime)) {
|
|
11912
|
-
return Response.json({ error: `File type not allowed: ${mime}` }, { status: 415 });
|
|
11913
|
-
}
|
|
11915
|
+
for (const [key, value] of formData) {
|
|
11916
|
+
if (value instanceof File) {
|
|
11917
|
+
if (options?.allowedTypes && !options.allowedTypes.includes(value.type)) {
|
|
11918
|
+
return Response.json({ error: `File type not allowed: ${value.type}` }, { status: 415 });
|
|
11914
11919
|
}
|
|
11915
|
-
if (options?.maxFileSize &&
|
|
11916
|
-
return Response.json({ error: `File too large: ${
|
|
11920
|
+
if (options?.maxFileSize && value.size > options.maxFileSize) {
|
|
11921
|
+
return Response.json({ error: `File too large: ${value.name}` }, { status: 413 });
|
|
11917
11922
|
}
|
|
11923
|
+
const buf = Buffer.from(await value.arrayBuffer());
|
|
11918
11924
|
const uf = {
|
|
11919
|
-
name:
|
|
11920
|
-
type:
|
|
11925
|
+
name: value.name,
|
|
11926
|
+
type: value.type,
|
|
11921
11927
|
size: buf.byteLength,
|
|
11922
11928
|
buffer: saveDir ? void 0 : buf
|
|
11923
11929
|
};
|
|
11924
11930
|
if (saveDir) {
|
|
11925
|
-
const
|
|
11931
|
+
const safeName = value.name.replace(/[/\\]/g, "");
|
|
11932
|
+
const filePath = join2(saveDir, `${randomUUID()}-${safeName}`);
|
|
11926
11933
|
await mkdir(saveDir, { recursive: true });
|
|
11927
11934
|
await writeFile(filePath, buf);
|
|
11928
11935
|
uf.path = filePath;
|
|
11929
11936
|
}
|
|
11930
|
-
if (files[
|
|
11931
|
-
const existing = files[
|
|
11932
|
-
files[
|
|
11937
|
+
if (files[key]) {
|
|
11938
|
+
const existing = files[key];
|
|
11939
|
+
files[key] = Array.isArray(existing) ? [...existing, uf] : [existing, uf];
|
|
11933
11940
|
} else {
|
|
11934
|
-
files[
|
|
11941
|
+
files[key] = uf;
|
|
11935
11942
|
}
|
|
11936
11943
|
} else {
|
|
11937
|
-
fields[
|
|
11944
|
+
fields[key] = value;
|
|
11938
11945
|
}
|
|
11939
11946
|
}
|
|
11940
11947
|
ctx.parsed = { ...ctx.parsed, files, fields };
|
|
@@ -13542,12 +13549,21 @@ function queue(opts) {
|
|
|
13542
13549
|
if (!running) return;
|
|
13543
13550
|
try {
|
|
13544
13551
|
const now = Date.now();
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13552
|
+
while (true) {
|
|
13553
|
+
const result = await redis2.zpopmin(jobKey);
|
|
13554
|
+
if (result.length < 2) break;
|
|
13555
|
+
const raw = result[0];
|
|
13556
|
+
const score = parseInt(result[1], 10);
|
|
13557
|
+
if (score > now) {
|
|
13558
|
+
await redis2.zadd(jobKey, score, raw);
|
|
13559
|
+
break;
|
|
13560
|
+
}
|
|
13561
|
+
let job;
|
|
13562
|
+
try {
|
|
13563
|
+
job = JSON.parse(raw);
|
|
13564
|
+
} catch {
|
|
13565
|
+
continue;
|
|
13566
|
+
}
|
|
13551
13567
|
const handler = handlers.get(job.type);
|
|
13552
13568
|
if (handler) {
|
|
13553
13569
|
handler(job).then(() => {
|
|
@@ -13555,11 +13571,13 @@ function queue(opts) {
|
|
|
13555
13571
|
try {
|
|
13556
13572
|
const nextRun = cronNext(job.schedule);
|
|
13557
13573
|
const nextJob = { ...job, id: crypto3.randomUUID(), runAt: nextRun, createdAt: Date.now() };
|
|
13558
|
-
redis2.zadd(jobKey, nextRun, JSON.stringify(nextJob))
|
|
13574
|
+
redis2.zadd(jobKey, nextRun, JSON.stringify(nextJob)).catch(() => {
|
|
13575
|
+
});
|
|
13559
13576
|
} catch {
|
|
13560
13577
|
}
|
|
13561
13578
|
}
|
|
13562
|
-
}).catch(() => {
|
|
13579
|
+
}).catch((e) => {
|
|
13580
|
+
console.error("[queue] handler error:", e);
|
|
13563
13581
|
});
|
|
13564
13582
|
}
|
|
13565
13583
|
}
|
|
@@ -13844,6 +13862,10 @@ async function getUserTable(sql, tenantId, slug) {
|
|
|
13844
13862
|
`;
|
|
13845
13863
|
return row ?? null;
|
|
13846
13864
|
}
|
|
13865
|
+
function requireAdmin(ctx) {
|
|
13866
|
+
if (ctx.tenant?.role !== "admin") return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
13867
|
+
return null;
|
|
13868
|
+
}
|
|
13847
13869
|
function buildRouter(sql, usersTable) {
|
|
13848
13870
|
const r = new Router();
|
|
13849
13871
|
r.post("/sys/tenants", async (req, ctx) => {
|
|
@@ -13869,6 +13891,8 @@ function buildRouter(sql, usersTable) {
|
|
|
13869
13891
|
return Response.json(rows);
|
|
13870
13892
|
});
|
|
13871
13893
|
r.post("/sys/tenants/invite", async (req, ctx) => {
|
|
13894
|
+
const err = requireAdmin(ctx);
|
|
13895
|
+
if (err) return err;
|
|
13872
13896
|
const { email, role = "member" } = await req.json();
|
|
13873
13897
|
const [user2] = await sql`
|
|
13874
13898
|
SELECT id FROM ${sql(usersTable)} WHERE "email" = ${email} LIMIT 1
|
|
@@ -13886,6 +13910,8 @@ function buildRouter(sql, usersTable) {
|
|
|
13886
13910
|
return Response.json({ ok: true }, { status: 201 });
|
|
13887
13911
|
});
|
|
13888
13912
|
r.delete("/sys/tenants/members/:userId", async (req, ctx) => {
|
|
13913
|
+
const err = requireAdmin(ctx);
|
|
13914
|
+
if (err) return err;
|
|
13889
13915
|
const userId = parseInt(ctx.params.userId, 10);
|
|
13890
13916
|
await sql`
|
|
13891
13917
|
DELETE FROM "_tenant_members"
|
|
@@ -13894,6 +13920,8 @@ function buildRouter(sql, usersTable) {
|
|
|
13894
13920
|
return Response.json({ ok: true });
|
|
13895
13921
|
});
|
|
13896
13922
|
r.post("/sys/tables", async (req, ctx) => {
|
|
13923
|
+
const err = requireAdmin(ctx);
|
|
13924
|
+
if (err) return err;
|
|
13897
13925
|
const body = await req.json();
|
|
13898
13926
|
const slugErr = validateSlug(body.slug);
|
|
13899
13927
|
if (slugErr) return Response.json({ error: slugErr }, { status: 400 });
|
|
@@ -13932,6 +13960,8 @@ function buildRouter(sql, usersTable) {
|
|
|
13932
13960
|
return Response.json(table);
|
|
13933
13961
|
});
|
|
13934
13962
|
r.patch("/sys/tables/:slug", async (req, ctx) => {
|
|
13963
|
+
const err = requireAdmin(ctx);
|
|
13964
|
+
if (err) return err;
|
|
13935
13965
|
const body = await req.json();
|
|
13936
13966
|
if (!body.fields || !Array.isArray(body.fields)) {
|
|
13937
13967
|
return Response.json({ error: "fields array required" }, { status: 400 });
|
|
@@ -13952,6 +13982,8 @@ function buildRouter(sql, usersTable) {
|
|
|
13952
13982
|
return Response.json({ ...table, fields: merged });
|
|
13953
13983
|
});
|
|
13954
13984
|
r.delete("/sys/tables/:slug", async (_req, ctx) => {
|
|
13985
|
+
const err = requireAdmin(ctx);
|
|
13986
|
+
if (err) return err;
|
|
13955
13987
|
await sql.unsafe(dropTableSQL(ctx.tenant.id, ctx.params.slug));
|
|
13956
13988
|
await sql`
|
|
13957
13989
|
DELETE FROM "_user_tables"
|
|
@@ -24407,15 +24439,19 @@ function createWSHandler(deps) {
|
|
|
24407
24439
|
VALUES (${channel_id}, ${am.member_id}, 'agent', ${result.output})
|
|
24408
24440
|
`.then(([r]) => {
|
|
24409
24441
|
broadcastToChannel(channel_id, { type: "message", data: r });
|
|
24442
|
+
}).catch((e) => {
|
|
24443
|
+
console.error("[messager] agent reply insert failed:", e);
|
|
24410
24444
|
});
|
|
24411
24445
|
}
|
|
24412
|
-
}).catch(() => {
|
|
24446
|
+
}).catch((e) => {
|
|
24447
|
+
console.error("[messager] agent run failed:", e);
|
|
24413
24448
|
});
|
|
24414
24449
|
}
|
|
24415
24450
|
}
|
|
24416
24451
|
break;
|
|
24417
24452
|
}
|
|
24418
24453
|
case "typing": {
|
|
24454
|
+
if (channel_id) subscribe(ws, userId, channel_id);
|
|
24419
24455
|
broadcastToChannel(channel_id, {
|
|
24420
24456
|
type: "typing",
|
|
24421
24457
|
channel_id,
|
|
@@ -24426,6 +24462,7 @@ function createWSHandler(deps) {
|
|
|
24426
24462
|
}
|
|
24427
24463
|
case "read": {
|
|
24428
24464
|
if (!channel_id || !last_message_id) return;
|
|
24465
|
+
subscribe(ws, userId, channel_id);
|
|
24429
24466
|
await sql`
|
|
24430
24467
|
UPDATE "_channel_members"
|
|
24431
24468
|
SET last_read_id = ${last_message_id}, last_read_at = NOW()
|
|
@@ -24570,9 +24607,12 @@ function buildRouter3(deps) {
|
|
|
24570
24607
|
VALUES (${channelId}, ${am.member_id}, 'agent', ${result.output})
|
|
24571
24608
|
`.then(([r2]) => {
|
|
24572
24609
|
broadcastToChannel(channelId, { type: "message", data: r2 });
|
|
24610
|
+
}).catch((e) => {
|
|
24611
|
+
console.error("[messager] agent reply insert failed:", e);
|
|
24573
24612
|
});
|
|
24574
24613
|
}
|
|
24575
|
-
}).catch(() => {
|
|
24614
|
+
}).catch((e) => {
|
|
24615
|
+
console.error("[messager] agent run failed:", e);
|
|
24576
24616
|
});
|
|
24577
24617
|
}
|
|
24578
24618
|
}
|
package/dist/serve.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface ServeOptions {
|
|
|
6
6
|
hostname?: string;
|
|
7
7
|
signal?: AbortSignal;
|
|
8
8
|
websocket?: (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
|
|
9
|
+
maxBodySize?: number;
|
|
9
10
|
}
|
|
10
11
|
export interface Server {
|
|
11
12
|
stop: () => void;
|
|
@@ -13,7 +14,7 @@ export interface Server {
|
|
|
13
14
|
readonly hostname: string;
|
|
14
15
|
ready: Promise<void>;
|
|
15
16
|
}
|
|
16
|
-
export declare function readBody(req: IncomingMessage): Promise<Buffer>;
|
|
17
|
+
export declare function readBody(req: IncomingMessage, maxSize?: number): Promise<Buffer>;
|
|
17
18
|
export declare function createRequest(req: IncomingMessage, body: Buffer): [Request, Record<string, string>];
|
|
18
19
|
export declare function sendResponse(res: ServerResponse, response: Response): Promise<void>;
|
|
19
20
|
export declare function serve(handler: Handler, options?: ServeOptions): Server;
|