weifuwu 0.25.0 → 0.25.2
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/cli/template/index.ts +1 -1
- package/dist/agent/run.d.ts +3 -3
- package/dist/analytics.d.ts +12 -2
- package/dist/compile.d.ts +0 -2
- package/dist/csrf.d.ts +2 -0
- package/dist/flash.d.ts +2 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +174 -129
- package/dist/module-server.d.ts +0 -1
- package/dist/opencode/rest.d.ts +2 -1
- package/dist/opencode/ws.d.ts +2 -2
- package/dist/queue/types.d.ts +0 -1
- package/dist/rate-limit.d.ts +10 -5
- package/dist/redis/client.d.ts +2 -0
- package/dist/redis/index.d.ts +2 -3
- package/dist/request-id.d.ts +4 -0
- package/dist/router.d.ts +0 -18
- package/dist/serve.d.ts +3 -1
- package/dist/server-registry.d.ts +0 -2
- package/dist/session.d.ts +20 -0
- package/dist/ssr.d.ts +0 -10
- package/dist/tenant/utils.d.ts +0 -1
- package/dist/types.d.ts +28 -2
- package/dist/upload.d.ts +4 -0
- package/dist/user/client.d.ts +6 -1
- package/dist/user/oauth-login.d.ts +4 -4
- package/dist/validate.d.ts +18 -0
- package/package.json +4 -3
package/cli/template/index.ts
CHANGED
package/dist/agent/run.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ import type { AIProvider } from '../ai/provider.ts';
|
|
|
5
5
|
import type { RunParams, RunResult, KnowledgeDoc } from './types.ts';
|
|
6
6
|
interface RunnerDeps {
|
|
7
7
|
sql: SqlClient;
|
|
8
|
-
agents: BoundTable<
|
|
9
|
-
runs: BoundTable<
|
|
10
|
-
knowledge: BoundTable<
|
|
8
|
+
agents: BoundTable<Record<string, unknown>>;
|
|
9
|
+
runs: BoundTable<Record<string, unknown>>;
|
|
10
|
+
knowledge: BoundTable<Record<string, unknown>>;
|
|
11
11
|
provider: AIProvider;
|
|
12
12
|
modelName?: string;
|
|
13
13
|
userTools?: Record<string, Tool>;
|
package/dist/analytics.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import type { Middleware, Closeable } from './types.ts';
|
|
2
2
|
import { Router } from './router.ts';
|
|
3
|
+
/** Tagged-template SQL function from the postgres.js library. */
|
|
4
|
+
type PgSql = (strings: TemplateStringsArray, ...values: unknown[]) => Promise<unknown[]>;
|
|
5
|
+
/** Schema table builder callback. */
|
|
6
|
+
type PgTable = (name: string, cols: Record<string, unknown>) => {
|
|
7
|
+
create(): Promise<void>;
|
|
8
|
+
createIndex(cols: string[], opts: {
|
|
9
|
+
unique: boolean;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
};
|
|
3
12
|
/** Options for {@link analytics}. */
|
|
4
13
|
export interface AnalyticsOptions {
|
|
5
14
|
/** Path prefixes to exclude from analytics (default: `['/__analytics', '/__wfw', '/static']`). */
|
|
6
15
|
excluded?: string[];
|
|
7
16
|
/** PostgreSQL client for persistent storage. Required for production use. */
|
|
8
17
|
pg?: {
|
|
9
|
-
sql:
|
|
10
|
-
table:
|
|
18
|
+
sql: PgSql;
|
|
19
|
+
table: PgTable;
|
|
11
20
|
};
|
|
12
21
|
}
|
|
13
22
|
/** Analytics module returned by {@link analytics}. */
|
|
@@ -33,3 +42,4 @@ export interface AnalyticsModule extends Router, Closeable {
|
|
|
33
42
|
* ```
|
|
34
43
|
*/
|
|
35
44
|
export declare function analytics(options?: AnalyticsOptions): AnalyticsModule;
|
|
45
|
+
export {};
|
package/dist/compile.d.ts
CHANGED
|
@@ -13,5 +13,3 @@ export declare function compile(path: string): Promise<any>;
|
|
|
13
13
|
export declare let vendorHash: string;
|
|
14
14
|
/** Build a single vendor bundle containing all needed vendor modules */
|
|
15
15
|
export declare function compileVendorBundle(): Promise<string>;
|
|
16
|
-
/** Clean up esbuild's internal worker pool. Call when you're done compiling. */
|
|
17
|
-
export declare function closeCompile(): Promise<void>;
|
package/dist/csrf.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface CsrfInjected {
|
|
|
8
8
|
token: string;
|
|
9
9
|
}
|
|
10
10
|
/** Options for {@link csrf}. */
|
|
11
|
+
/** CSRF protection module — a {@link Middleware} that injects `ctx.csrf`. */
|
|
12
|
+
export type CsrfModule = Middleware<Context, Context & CsrfInjected>;
|
|
11
13
|
export interface CsrfOptions {
|
|
12
14
|
/** Cookie name for CSRF token (default: `'_csrf'`). */
|
|
13
15
|
cookie?: string;
|
package/dist/flash.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ declare module './types.ts' {
|
|
|
29
29
|
flash: FlashInjected;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
/** Flash message module — a {@link Middleware} that injects `ctx.flash`. */
|
|
33
|
+
export type FlashModule = Middleware<Context, Context & FlashInjected>;
|
|
32
34
|
/** Options for {@link flash}. */
|
|
33
35
|
export interface FlashOptions {
|
|
34
36
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type { Context, Handler, Middleware, ErrorHandler } from './types.ts';
|
|
2
|
+
export { HttpError } from './types.ts';
|
|
2
3
|
export { currentTraceId, currentTrace, runWithTrace, traceElapsed, trace } from './trace.ts';
|
|
3
4
|
export type { TraceContext, TraceInjected, TraceOptions } from './trace.ts';
|
|
4
5
|
export { loadEnv, isDev, isProd, isBundled, getPublicEnv, env } from './env.ts';
|
|
@@ -14,11 +15,11 @@ export type { CORSOptions } from './cors.ts';
|
|
|
14
15
|
export { serveStatic } from './static.ts';
|
|
15
16
|
export type { ServeStaticOptions } from './static.ts';
|
|
16
17
|
export { validate } from './validate.ts';
|
|
17
|
-
export type { ValidationSchemas } from './validate.ts';
|
|
18
|
+
export type { ValidationSchemas, ValidateModule } from './validate.ts';
|
|
18
19
|
export { getCookies, setCookie, deleteCookie } from './cookie.ts';
|
|
19
20
|
export type { CookieOptions } from './cookie.ts';
|
|
20
21
|
export { upload } from './upload.ts';
|
|
21
|
-
export type { UploadOptions, UploadedFile } from './upload.ts';
|
|
22
|
+
export type { UploadOptions, UploadedFile, UploadModule } from './upload.ts';
|
|
22
23
|
export { rateLimit } from './rate-limit.ts';
|
|
23
24
|
export type { RateLimitOptions } from './rate-limit.ts';
|
|
24
25
|
export { compress } from './compress.ts';
|
|
@@ -26,7 +27,7 @@ export type { CompressOptions } from './compress.ts';
|
|
|
26
27
|
export { helmet } from './helmet.ts';
|
|
27
28
|
export type { HelmetOptions } from './helmet.ts';
|
|
28
29
|
export { requestId } from './request-id.ts';
|
|
29
|
-
export type { RequestIdOptions } from './request-id.ts';
|
|
30
|
+
export type { RequestIdOptions, RequestIdModule } from './request-id.ts';
|
|
30
31
|
export { createSSEStream, formatSSE, formatSSEData } from './sse.ts';
|
|
31
32
|
export type { SSEEvent } from './sse.ts';
|
|
32
33
|
export { testApp, TestApp, TestRequest, createTestDb, withTestDb } from './test-utils.ts';
|
|
@@ -69,13 +70,13 @@ export type { ThemeOptions, ThemeInjected } from './theme.ts';
|
|
|
69
70
|
export { i18n } from './i18n.ts';
|
|
70
71
|
export type { I18nOptions, I18nInjected } from './i18n.ts';
|
|
71
72
|
export { flash } from './flash.ts';
|
|
72
|
-
export type { FlashOptions, FlashInjected } from './flash.ts';
|
|
73
|
+
export type { FlashOptions, FlashInjected, FlashModule } from './flash.ts';
|
|
73
74
|
export { seo, seoMiddleware, seoTags } from './seo.ts';
|
|
74
75
|
export type { SeoOptions, RobotsRule, SitemapUrl, SitemapConfig, SeoHeadersConfig, SeoTagsConfig, } from './seo.ts';
|
|
75
76
|
export { mailer } from './mailer.ts';
|
|
76
77
|
export type { MailerOptions, MailOptions, Mailer } from './mailer.ts';
|
|
77
78
|
export { csrf } from './csrf.ts';
|
|
78
|
-
export type { CsrfOptions, CsrfInjected } from './csrf.ts';
|
|
79
|
+
export type { CsrfOptions, CsrfInjected, CsrfModule } from './csrf.ts';
|
|
79
80
|
export { logdb } from './logdb/index.ts';
|
|
80
81
|
export type { LogdbOptions, LogdbModule, LogEntry, LogEntryInput } from './logdb/types.ts';
|
|
81
82
|
export { iii, createWorker, registerWorker } from './iii/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,16 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// types.ts
|
|
8
|
+
var HttpError = class extends Error {
|
|
9
|
+
status;
|
|
10
|
+
constructor(message, status) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "HttpError";
|
|
13
|
+
this.status = status;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
7
17
|
// trace.ts
|
|
8
18
|
import crypto2 from "node:crypto";
|
|
9
19
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -110,14 +120,6 @@ function env() {
|
|
|
110
120
|
|
|
111
121
|
// serve.ts
|
|
112
122
|
import http from "node:http";
|
|
113
|
-
var HttpError = class extends Error {
|
|
114
|
-
status;
|
|
115
|
-
constructor(message, status) {
|
|
116
|
-
super(message);
|
|
117
|
-
this.status = status;
|
|
118
|
-
this.name = "HttpError";
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
123
|
var DEFAULT_MAX_BODY = 10 * 1024 * 1024;
|
|
122
124
|
async function readBody(req, maxSize) {
|
|
123
125
|
const limit = maxSize ?? DEFAULT_MAX_BODY;
|
|
@@ -254,6 +256,7 @@ function serve(handler, options) {
|
|
|
254
256
|
resolveReady();
|
|
255
257
|
return {
|
|
256
258
|
stop: () => Promise.resolve(),
|
|
259
|
+
close: () => Promise.resolve(),
|
|
257
260
|
ready,
|
|
258
261
|
get port() {
|
|
259
262
|
return 0;
|
|
@@ -287,30 +290,29 @@ function serve(handler, options) {
|
|
|
287
290
|
const displayHost = _cachedHostname === "0.0.0.0" ? "localhost" : _cachedHostname || "localhost";
|
|
288
291
|
console.log(`weifuwu listening on http://${displayHost}:${_cachedPort}`);
|
|
289
292
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}, timeoutMs);
|
|
308
|
-
server.on("close", () => {
|
|
309
|
-
clearTimeout(timer);
|
|
310
|
-
resolve16();
|
|
311
|
-
});
|
|
293
|
+
async function stop(timeoutMs = 1e4) {
|
|
294
|
+
if (shutdownHandler) {
|
|
295
|
+
process.off("SIGTERM", shutdownHandler);
|
|
296
|
+
process.off("SIGINT", shutdownHandler);
|
|
297
|
+
shutdownHandler = null;
|
|
298
|
+
}
|
|
299
|
+
if (!server.listening) return;
|
|
300
|
+
server.close();
|
|
301
|
+
server.closeIdleConnections();
|
|
302
|
+
return new Promise((resolve16) => {
|
|
303
|
+
const timer = setTimeout(() => {
|
|
304
|
+
server.closeAllConnections();
|
|
305
|
+
resolve16();
|
|
306
|
+
}, timeoutMs);
|
|
307
|
+
server.on("close", () => {
|
|
308
|
+
clearTimeout(timer);
|
|
309
|
+
resolve16();
|
|
312
310
|
});
|
|
313
|
-
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
close: stop,
|
|
315
|
+
stop,
|
|
314
316
|
ready,
|
|
315
317
|
get port() {
|
|
316
318
|
if (!server.listening) return 0;
|
|
@@ -517,7 +519,7 @@ var Router = class _Router {
|
|
|
517
519
|
* ```
|
|
518
520
|
*/
|
|
519
521
|
_checkMiddlewareMeta(mw, location) {
|
|
520
|
-
const meta = mw.__meta ?? mw.middleware
|
|
522
|
+
const meta = mw.__meta ?? (typeof mw === "object" && mw && "middleware" in mw ? mw.middleware().__meta : void 0);
|
|
521
523
|
if (!meta) return;
|
|
522
524
|
for (const dep of meta.depends) {
|
|
523
525
|
if (!this._ctxFields.has(dep)) {
|
|
@@ -1271,7 +1273,7 @@ function parseBody(text2, ct) {
|
|
|
1271
1273
|
return text2;
|
|
1272
1274
|
}
|
|
1273
1275
|
function validate(schemas) {
|
|
1274
|
-
|
|
1276
|
+
const mw = async (req, ctx, next) => {
|
|
1275
1277
|
const parsed = {};
|
|
1276
1278
|
const issues = [];
|
|
1277
1279
|
if (schemas?.params) {
|
|
@@ -1358,6 +1360,8 @@ function validate(schemas) {
|
|
|
1358
1360
|
ctx.parsed = { ...ctx.parsed, ...parsed };
|
|
1359
1361
|
return next(req, ctx);
|
|
1360
1362
|
};
|
|
1363
|
+
mw.__meta = { injects: ["parsed"], depends: [] };
|
|
1364
|
+
return mw;
|
|
1361
1365
|
}
|
|
1362
1366
|
|
|
1363
1367
|
// cookie.ts
|
|
@@ -1461,7 +1465,7 @@ function detectMimeFromExtension(filename) {
|
|
|
1461
1465
|
}
|
|
1462
1466
|
function upload(options) {
|
|
1463
1467
|
const saveDir = options?.dir;
|
|
1464
|
-
|
|
1468
|
+
const mw = async (req, ctx, next) => {
|
|
1465
1469
|
const ct = req.headers.get("content-type") ?? "";
|
|
1466
1470
|
if (!ct.includes("multipart/form-data")) return next(req, ctx);
|
|
1467
1471
|
try {
|
|
@@ -1517,6 +1521,8 @@ function upload(options) {
|
|
|
1517
1521
|
ctx.parsed = { ...ctx.parsed, files, fields };
|
|
1518
1522
|
return next(req, ctx);
|
|
1519
1523
|
};
|
|
1524
|
+
mw.__meta = { injects: ["parsed"], depends: [] };
|
|
1525
|
+
return mw;
|
|
1520
1526
|
}
|
|
1521
1527
|
|
|
1522
1528
|
// rate-limit.ts
|
|
@@ -1601,7 +1607,7 @@ function rateLimit(options) {
|
|
|
1601
1607
|
return addRateLimitHeaders(res, max, remaining, reset);
|
|
1602
1608
|
};
|
|
1603
1609
|
mw.__meta = { injects: [], depends: [] };
|
|
1604
|
-
mw.close = () => {
|
|
1610
|
+
mw.close = async () => {
|
|
1605
1611
|
if (interval) clearInterval(interval);
|
|
1606
1612
|
hits.clear();
|
|
1607
1613
|
};
|
|
@@ -1728,7 +1734,7 @@ import crypto3 from "node:crypto";
|
|
|
1728
1734
|
function requestId(options) {
|
|
1729
1735
|
const header = options?.header ?? "X-Request-ID";
|
|
1730
1736
|
const gen = options?.generator ?? (() => crypto3.randomUUID());
|
|
1731
|
-
|
|
1737
|
+
const mw = async (req, ctx, next) => {
|
|
1732
1738
|
const existing = req.headers.get(header);
|
|
1733
1739
|
const id2 = existing ?? gen();
|
|
1734
1740
|
ctx.requestId = id2;
|
|
@@ -1738,6 +1744,8 @@ function requestId(options) {
|
|
|
1738
1744
|
h.set(header, id2);
|
|
1739
1745
|
return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
|
|
1740
1746
|
};
|
|
1747
|
+
mw.__meta = { injects: ["requestId"], depends: [] };
|
|
1748
|
+
return mw;
|
|
1741
1749
|
}
|
|
1742
1750
|
|
|
1743
1751
|
// sse.ts
|
|
@@ -1763,7 +1771,7 @@ function createSSEStream(iterable, opts) {
|
|
|
1763
1771
|
controller.enqueue(encoder.encode(text2));
|
|
1764
1772
|
}
|
|
1765
1773
|
} catch (e) {
|
|
1766
|
-
if (e.name !== "AbortError") {
|
|
1774
|
+
if (e instanceof Error && e.name !== "AbortError") {
|
|
1767
1775
|
controller.enqueue(encoder.encode(formatSSE("error", { error: e.message })));
|
|
1768
1776
|
}
|
|
1769
1777
|
} finally {
|
|
@@ -1833,6 +1841,7 @@ var TestRequest = class {
|
|
|
1833
1841
|
}
|
|
1834
1842
|
/** Shortcut: set ctx.user */
|
|
1835
1843
|
withUser(user2) {
|
|
1844
|
+
;
|
|
1836
1845
|
this.ctxMixin.user = user2;
|
|
1837
1846
|
return this;
|
|
1838
1847
|
}
|
|
@@ -1999,7 +2008,7 @@ var TestApp = class {
|
|
|
1999
2008
|
}
|
|
2000
2009
|
this.wsConnections = [];
|
|
2001
2010
|
if (this.wsServer) {
|
|
2002
|
-
this.wsServer.
|
|
2011
|
+
this.wsServer.close();
|
|
2003
2012
|
this.wsServer = null;
|
|
2004
2013
|
}
|
|
2005
2014
|
}
|
|
@@ -2413,7 +2422,16 @@ async function getStreamObject() {
|
|
|
2413
2422
|
async function aiStream(handler, provider) {
|
|
2414
2423
|
const r = new Router();
|
|
2415
2424
|
r.post("/", async (req, ctx) => {
|
|
2416
|
-
|
|
2425
|
+
let options;
|
|
2426
|
+
try {
|
|
2427
|
+
options = await handler(req, ctx);
|
|
2428
|
+
} catch (err) {
|
|
2429
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2430
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
2431
|
+
status: 500,
|
|
2432
|
+
headers: { "Content-Type": "application/json" }
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2417
2435
|
if (provider && !options.model) {
|
|
2418
2436
|
options.model = provider.model();
|
|
2419
2437
|
}
|
|
@@ -3819,7 +3837,8 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3819
3837
|
if (email) {
|
|
3820
3838
|
const existingUser = await deps.findUserByEmail(email);
|
|
3821
3839
|
if (existingUser) {
|
|
3822
|
-
|
|
3840
|
+
const uid = existingUser.id;
|
|
3841
|
+
await linkProvider(uid, provider, providerId, email, name, avatarUrl);
|
|
3823
3842
|
return existingUser;
|
|
3824
3843
|
}
|
|
3825
3844
|
}
|
|
@@ -3827,7 +3846,8 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3827
3846
|
email || `${provider}_${providerId}@oauth.local`,
|
|
3828
3847
|
name || provider
|
|
3829
3848
|
);
|
|
3830
|
-
|
|
3849
|
+
const userId2 = newUser.id;
|
|
3850
|
+
await linkProvider(userId2, provider, providerId, email, name, avatarUrl);
|
|
3831
3851
|
return newUser;
|
|
3832
3852
|
}
|
|
3833
3853
|
function getProviderMeta(providerName) {
|
|
@@ -3857,8 +3877,9 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3857
3877
|
const state = crypto5.randomUUID();
|
|
3858
3878
|
const redirectUri = new URL(req.url);
|
|
3859
3879
|
redirectUri.pathname = redirectUri.pathname.replace(/\/[^/]+$/, "/") + providerName + "/callback";
|
|
3860
|
-
|
|
3861
|
-
|
|
3880
|
+
const sess = ctx.session;
|
|
3881
|
+
if (sess) {
|
|
3882
|
+
sess.oauthState = { state, provider: providerName };
|
|
3862
3883
|
}
|
|
3863
3884
|
const scope = config.scope ?? meta.scope;
|
|
3864
3885
|
const params = new URLSearchParams({
|
|
@@ -3885,11 +3906,12 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3885
3906
|
if (!code || !state) {
|
|
3886
3907
|
return Response.json({ error: "Missing code or state parameter" }, { status: 400 });
|
|
3887
3908
|
}
|
|
3888
|
-
const
|
|
3909
|
+
const sess = ctx.session;
|
|
3910
|
+
const savedState = sess?.oauthState;
|
|
3889
3911
|
if (!savedState || savedState.state !== state || savedState.provider !== providerName) {
|
|
3890
3912
|
return Response.json({ error: "Invalid state \u2014 possible CSRF attack" }, { status: 403 });
|
|
3891
3913
|
}
|
|
3892
|
-
if (
|
|
3914
|
+
if (sess) delete sess.oauthState;
|
|
3893
3915
|
const redirectUri = url.origin + url.pathname.replace(/\/callback$/, "");
|
|
3894
3916
|
let tokenRes;
|
|
3895
3917
|
try {
|
|
@@ -3947,9 +3969,10 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3947
3969
|
return Response.json({ error: "Failed to create/link user" }, { status: 500 });
|
|
3948
3970
|
}
|
|
3949
3971
|
const token = signToken(user2);
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3972
|
+
const sess2 = ctx.session;
|
|
3973
|
+
if (sess2) {
|
|
3974
|
+
sess2.userId = user2.id;
|
|
3975
|
+
sess2.role = user2.role;
|
|
3953
3976
|
}
|
|
3954
3977
|
const accept = req.headers.get("accept") ?? "";
|
|
3955
3978
|
if (accept.includes("application/json")) {
|
|
@@ -4147,9 +4170,7 @@ function user(options) {
|
|
|
4147
4170
|
const { email, password, name } = RegisterSchema.parse(data);
|
|
4148
4171
|
const existing = await findByEmail(email);
|
|
4149
4172
|
if (existing) {
|
|
4150
|
-
|
|
4151
|
-
err.status = 409;
|
|
4152
|
-
throw err;
|
|
4173
|
+
throw new HttpError("Email already registered", 409);
|
|
4153
4174
|
}
|
|
4154
4175
|
const hashed = hashPassword(password);
|
|
4155
4176
|
const row = await _users.insert({ email, password: hashed, name });
|
|
@@ -4162,14 +4183,10 @@ function user(options) {
|
|
|
4162
4183
|
const { data: rows } = await _users.readMany({ email });
|
|
4163
4184
|
const row = rows[0];
|
|
4164
4185
|
if (!row) {
|
|
4165
|
-
|
|
4166
|
-
err.status = 401;
|
|
4167
|
-
throw err;
|
|
4186
|
+
throw new HttpError("Invalid email or password", 401);
|
|
4168
4187
|
}
|
|
4169
4188
|
if (!verifyPassword(password, row.password)) {
|
|
4170
|
-
|
|
4171
|
-
err.status = 401;
|
|
4172
|
-
throw err;
|
|
4189
|
+
throw new HttpError("Invalid email or password", 401);
|
|
4173
4190
|
}
|
|
4174
4191
|
const userData = row;
|
|
4175
4192
|
const token = signToken(userData);
|
|
@@ -4359,7 +4376,7 @@ function user(options) {
|
|
|
4359
4376
|
return null;
|
|
4360
4377
|
}
|
|
4361
4378
|
function middleware() {
|
|
4362
|
-
|
|
4379
|
+
const mw = async (req, ctx, next) => {
|
|
4363
4380
|
const userData = await resolveUser(req, ctx);
|
|
4364
4381
|
if (userData) {
|
|
4365
4382
|
ctx.user = userData;
|
|
@@ -4370,9 +4387,11 @@ function user(options) {
|
|
|
4370
4387
|
headers: headerName.toLowerCase() === "authorization" ? { "WWW-Authenticate": "Bearer" } : void 0
|
|
4371
4388
|
});
|
|
4372
4389
|
};
|
|
4390
|
+
mw.__meta = { injects: ["user"], depends: [] };
|
|
4391
|
+
return mw;
|
|
4373
4392
|
}
|
|
4374
4393
|
function middlewareOptional(_opts) {
|
|
4375
|
-
|
|
4394
|
+
const mw = async (req, ctx, next) => {
|
|
4376
4395
|
const userData = await resolveUser(req, ctx);
|
|
4377
4396
|
if (userData) {
|
|
4378
4397
|
;
|
|
@@ -4380,6 +4399,8 @@ function user(options) {
|
|
|
4380
4399
|
}
|
|
4381
4400
|
return next(req, ctx);
|
|
4382
4401
|
};
|
|
4402
|
+
mw.__meta = { injects: ["user"], depends: [] };
|
|
4403
|
+
return mw;
|
|
4383
4404
|
}
|
|
4384
4405
|
async function parseBody2(req) {
|
|
4385
4406
|
const ct = req.headers.get("content-type") || "";
|
|
@@ -4404,8 +4425,9 @@ function user(options) {
|
|
|
4404
4425
|
if (err instanceof z2.ZodError) {
|
|
4405
4426
|
return Response.json({ error: "Validation failed", issues: err.issues }, { status: 400 });
|
|
4406
4427
|
}
|
|
4407
|
-
const status = err.status
|
|
4408
|
-
|
|
4428
|
+
const status = err instanceof HttpError ? err.status : 500;
|
|
4429
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4430
|
+
return Response.json({ error: message }, { status });
|
|
4409
4431
|
}
|
|
4410
4432
|
});
|
|
4411
4433
|
r.post("/login", async (req, ctx) => {
|
|
@@ -4426,8 +4448,9 @@ function user(options) {
|
|
|
4426
4448
|
if (err instanceof z2.ZodError) {
|
|
4427
4449
|
return Response.json({ error: "Validation failed", issues: err.issues }, { status: 400 });
|
|
4428
4450
|
}
|
|
4429
|
-
const status = err.status
|
|
4430
|
-
|
|
4451
|
+
const status = err instanceof HttpError ? err.status : 500;
|
|
4452
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4453
|
+
return Response.json({ error: message }, { status });
|
|
4431
4454
|
}
|
|
4432
4455
|
});
|
|
4433
4456
|
}
|
|
@@ -4446,7 +4469,8 @@ function user(options) {
|
|
|
4446
4469
|
if (err instanceof z2.ZodError) {
|
|
4447
4470
|
return Response.json({ error: "Validation failed", issues: err.issues }, { status: 400 });
|
|
4448
4471
|
}
|
|
4449
|
-
|
|
4472
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4473
|
+
return Response.json({ error: message }, { status: 500 });
|
|
4450
4474
|
}
|
|
4451
4475
|
});
|
|
4452
4476
|
r.delete("/api-keys/:id", middleware(), async (req, ctx) => {
|
|
@@ -4522,7 +4546,7 @@ function user(options) {
|
|
|
4522
4546
|
return mod;
|
|
4523
4547
|
}
|
|
4524
4548
|
|
|
4525
|
-
// redis/
|
|
4549
|
+
// redis/client.ts
|
|
4526
4550
|
import { Redis as IORedis } from "ioredis";
|
|
4527
4551
|
function redis(opts) {
|
|
4528
4552
|
const options = typeof opts === "string" ? { url: opts } : opts ?? {};
|
|
@@ -4718,15 +4742,12 @@ function createMemoryQueue(opts) {
|
|
|
4718
4742
|
running = true;
|
|
4719
4743
|
poll();
|
|
4720
4744
|
};
|
|
4721
|
-
mw.
|
|
4745
|
+
mw.close = async function close() {
|
|
4722
4746
|
running = false;
|
|
4723
4747
|
if (pollTimer) {
|
|
4724
4748
|
clearTimeout(pollTimer);
|
|
4725
4749
|
pollTimer = null;
|
|
4726
4750
|
}
|
|
4727
|
-
};
|
|
4728
|
-
mw.close = async function close() {
|
|
4729
|
-
mw.stop();
|
|
4730
4751
|
while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
|
|
4731
4752
|
};
|
|
4732
4753
|
mw.jobs = async function(limit) {
|
|
@@ -4891,15 +4912,12 @@ function createPgQueue(opts) {
|
|
|
4891
4912
|
running = true;
|
|
4892
4913
|
poll();
|
|
4893
4914
|
};
|
|
4894
|
-
mw.
|
|
4915
|
+
mw.close = async function close() {
|
|
4895
4916
|
running = false;
|
|
4896
4917
|
if (pollTimer) {
|
|
4897
4918
|
clearTimeout(pollTimer);
|
|
4898
4919
|
pollTimer = null;
|
|
4899
4920
|
}
|
|
4900
|
-
};
|
|
4901
|
-
mw.close = async function close() {
|
|
4902
|
-
mw.stop();
|
|
4903
4921
|
while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
|
|
4904
4922
|
};
|
|
4905
4923
|
mw.jobs = async function jobs(limit) {
|
|
@@ -5058,16 +5076,13 @@ function createRedisQueue(opts) {
|
|
|
5058
5076
|
running = true;
|
|
5059
5077
|
poll();
|
|
5060
5078
|
};
|
|
5061
|
-
mw.
|
|
5079
|
+
mw.close = async function close() {
|
|
5062
5080
|
running = false;
|
|
5063
5081
|
epoch++;
|
|
5064
5082
|
if (pollTimer) {
|
|
5065
5083
|
clearTimeout(pollTimer);
|
|
5066
5084
|
pollTimer = null;
|
|
5067
5085
|
}
|
|
5068
|
-
};
|
|
5069
|
-
mw.close = async function close() {
|
|
5070
|
-
mw.stop();
|
|
5071
5086
|
while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
|
|
5072
5087
|
redis2.disconnect();
|
|
5073
5088
|
};
|
|
@@ -6065,7 +6080,7 @@ function tenant(options) {
|
|
|
6065
6080
|
);
|
|
6066
6081
|
}
|
|
6067
6082
|
function middleware() {
|
|
6068
|
-
|
|
6083
|
+
const mw = async (req, ctx, next) => {
|
|
6069
6084
|
const user2 = ctx.user;
|
|
6070
6085
|
if (!user2) {
|
|
6071
6086
|
return new Response("Unauthorized", { status: 401 });
|
|
@@ -6101,6 +6116,8 @@ function tenant(options) {
|
|
|
6101
6116
|
ctx.tenant = { id: member.id, name: member.name, role: member.role };
|
|
6102
6117
|
return next(req, ctx);
|
|
6103
6118
|
};
|
|
6119
|
+
mw.__meta = { injects: ["tenant"], depends: ["user"] };
|
|
6120
|
+
return mw;
|
|
6104
6121
|
}
|
|
6105
6122
|
const r = buildRouter(sql2, usersTable);
|
|
6106
6123
|
const mod = r;
|
|
@@ -6179,17 +6196,13 @@ function buildRouter2(deps) {
|
|
|
6179
6196
|
if (!body.input && !body.messages) {
|
|
6180
6197
|
return Response.json({ error: "input or messages is required" }, { status: 400 });
|
|
6181
6198
|
}
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
});
|
|
6188
|
-
}
|
|
6189
|
-
return Response.json(result);
|
|
6190
|
-
} catch (err) {
|
|
6191
|
-
return Response.json({ error: err.message }, { status: 500 });
|
|
6199
|
+
const result = await runner.run(id2, body);
|
|
6200
|
+
if ("stream" in result) {
|
|
6201
|
+
return new Response(result.stream, {
|
|
6202
|
+
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" }
|
|
6203
|
+
});
|
|
6192
6204
|
}
|
|
6205
|
+
return Response.json(result);
|
|
6193
6206
|
});
|
|
6194
6207
|
r.get("/agents/:id/runs", async (_req, ctx) => {
|
|
6195
6208
|
const agentId = parseInt(ctx.params.id, 10);
|
|
@@ -6220,13 +6233,26 @@ function buildRouter2(deps) {
|
|
|
6220
6233
|
{ orderBy: { created_at: "desc" } }
|
|
6221
6234
|
);
|
|
6222
6235
|
const total = rows.length;
|
|
6223
|
-
const success = rows.filter(
|
|
6236
|
+
const success = rows.filter(
|
|
6237
|
+
(r2) => r2.status === "success" || r2.status === "stream"
|
|
6238
|
+
).length;
|
|
6224
6239
|
const error = rows.filter((r2) => r2.status === "error").length;
|
|
6225
|
-
const totalTokensIn = rows.reduce(
|
|
6226
|
-
|
|
6227
|
-
|
|
6240
|
+
const totalTokensIn = rows.reduce(
|
|
6241
|
+
(sum, r2) => sum + (r2.tokens_in || 0),
|
|
6242
|
+
0
|
|
6243
|
+
);
|
|
6244
|
+
const totalTokensOut = rows.reduce(
|
|
6245
|
+
(sum, r2) => sum + (r2.tokens_out || 0),
|
|
6246
|
+
0
|
|
6247
|
+
);
|
|
6248
|
+
const totalElapsed = rows.reduce(
|
|
6249
|
+
(sum, r2) => sum + (r2.elapsed_ms || 0),
|
|
6250
|
+
0
|
|
6251
|
+
);
|
|
6228
6252
|
const avgElapsed = total > 0 ? Math.round(totalElapsed / total) : 0;
|
|
6229
|
-
const sorted = [...rows].sort(
|
|
6253
|
+
const sorted = [...rows].sort(
|
|
6254
|
+
(a, b) => (a.elapsed_ms || 0) - (b.elapsed_ms || 0)
|
|
6255
|
+
);
|
|
6230
6256
|
const p95Idx = Math.ceil(sorted.length * 0.95) - 1;
|
|
6231
6257
|
const p95Elapsed = sorted.length > 0 ? sorted[p95Idx]?.elapsed_ms || 0 : 0;
|
|
6232
6258
|
return Response.json({
|
|
@@ -6252,7 +6278,10 @@ function buildRouter2(deps) {
|
|
|
6252
6278
|
const doc = await runner.addKnowledge(agentId, body.title || "", body.content);
|
|
6253
6279
|
return Response.json(doc, { status: 201 });
|
|
6254
6280
|
} catch (err) {
|
|
6255
|
-
return Response.json(
|
|
6281
|
+
return Response.json(
|
|
6282
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
6283
|
+
{ status: 500 }
|
|
6284
|
+
);
|
|
6256
6285
|
}
|
|
6257
6286
|
});
|
|
6258
6287
|
r.get("/agents/:id/knowledge", async (_req, ctx) => {
|
|
@@ -6304,7 +6333,12 @@ async function searchKnowledge(sql2, provider, agentId, query, limit = 5) {
|
|
|
6304
6333
|
`SELECT id, title, content, metadata, embedding <=> $1::vector AS _score FROM "_knowledge_documents" WHERE agent_id = $2 ORDER BY embedding <=> $1::vector LIMIT $3`,
|
|
6305
6334
|
[vec, agentId, limit]
|
|
6306
6335
|
);
|
|
6307
|
-
return docs.map((d) => ({
|
|
6336
|
+
return docs.map((d) => ({
|
|
6337
|
+
id: d.id,
|
|
6338
|
+
title: d.title,
|
|
6339
|
+
content: d.content,
|
|
6340
|
+
score: d._score
|
|
6341
|
+
}));
|
|
6308
6342
|
}
|
|
6309
6343
|
async function loadAgent(agents, agentId) {
|
|
6310
6344
|
const row = await agents.read(agentId);
|
|
@@ -7349,7 +7383,7 @@ async function deploy(config) {
|
|
|
7349
7383
|
await stopProcess({ child: app.process, port: app.currentPort });
|
|
7350
7384
|
}
|
|
7351
7385
|
}
|
|
7352
|
-
httpServer?.
|
|
7386
|
+
httpServer?.close();
|
|
7353
7387
|
},
|
|
7354
7388
|
ready: httpServer.ready,
|
|
7355
7389
|
url: `http://localhost:${config.port}/`,
|
|
@@ -7734,11 +7768,12 @@ function buildHeadPayload(opts) {
|
|
|
7734
7768
|
flash: ctx.flash,
|
|
7735
7769
|
loaderData
|
|
7736
7770
|
};
|
|
7737
|
-
|
|
7771
|
+
const rawUser = ctx.user;
|
|
7772
|
+
if (rawUser && typeof rawUser === "object") {
|
|
7738
7773
|
const safeUser = {};
|
|
7739
7774
|
for (const k of ["id", "name", "email", "role", "avatar"]) {
|
|
7740
|
-
if (k in
|
|
7741
|
-
safeUser[k] =
|
|
7775
|
+
if (k in rawUser) {
|
|
7776
|
+
safeUser[k] = rawUser[k];
|
|
7742
7777
|
}
|
|
7743
7778
|
}
|
|
7744
7779
|
ctxData.user = safeUser;
|
|
@@ -8049,7 +8084,6 @@ function moduleServer(opts) {
|
|
|
8049
8084
|
_setImportRoots(roots);
|
|
8050
8085
|
const router = new Router();
|
|
8051
8086
|
router.get("/__wfw/m/*", (async (req, ctx) => {
|
|
8052
|
-
const reqUrl = new URL(req.url);
|
|
8053
8087
|
const filePath = (ctx.params["*"] || "").split("?")[0];
|
|
8054
8088
|
const ext = filePath.split(".").pop();
|
|
8055
8089
|
if (ext !== "tsx" && ext !== "ts") {
|
|
@@ -8619,25 +8653,22 @@ async function getSession(sql2, id2) {
|
|
|
8619
8653
|
}
|
|
8620
8654
|
async function listSessions(sql2, userId2) {
|
|
8621
8655
|
const opts = { orderBy: { updated_at: "desc" } };
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
sql2,
|
|
8625
|
-
{ user_id: userId2, active: true },
|
|
8626
|
-
opts
|
|
8627
|
-
);
|
|
8628
|
-
return rows2;
|
|
8629
|
-
}
|
|
8630
|
-
const { data: rows } = await sessions.readMany(sql2, { active: true }, opts);
|
|
8656
|
+
const filter = userId2 !== void 0 ? { user_id: userId2, active: true } : { active: true };
|
|
8657
|
+
const { data: rows } = await sessions.readMany(sql2, filter, opts);
|
|
8631
8658
|
return rows;
|
|
8632
8659
|
}
|
|
8633
8660
|
async function deleteSession(sql2, id2) {
|
|
8634
8661
|
await sessions.update(sql2, id2, { active: false, updated_at: sql`NOW()` });
|
|
8635
8662
|
}
|
|
8636
8663
|
async function getHistory(sql2, sessionId, limit = 50) {
|
|
8637
|
-
const { data: rows } = await messages.readMany(
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8664
|
+
const { data: rows } = await messages.readMany(
|
|
8665
|
+
sql2,
|
|
8666
|
+
{ session_id: sessionId },
|
|
8667
|
+
{
|
|
8668
|
+
orderBy: { created_at: "asc" },
|
|
8669
|
+
limit
|
|
8670
|
+
}
|
|
8671
|
+
);
|
|
8641
8672
|
return rows;
|
|
8642
8673
|
}
|
|
8643
8674
|
async function addTextMessage(sql2, sessionId, role, content, tokensIn = 0, tokensOut = 0) {
|
|
@@ -8838,7 +8869,7 @@ function createBashTool(ctx) {
|
|
|
8838
8869
|
// opencode/tools/read.ts
|
|
8839
8870
|
import { tool as tool4 } from "ai";
|
|
8840
8871
|
import { z as z6 } from "zod";
|
|
8841
|
-
import { readFileSync as
|
|
8872
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
8842
8873
|
import { resolve as resolve9 } from "node:path";
|
|
8843
8874
|
function createReadTool(ctx) {
|
|
8844
8875
|
return tool4({
|
|
@@ -8853,7 +8884,7 @@ function createReadTool(ctx) {
|
|
|
8853
8884
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions.permissions)) {
|
|
8854
8885
|
return { error: "Path not allowed", content: null, totalLines: 0 };
|
|
8855
8886
|
}
|
|
8856
|
-
const content =
|
|
8887
|
+
const content = readFileSync6(resolved, "utf-8");
|
|
8857
8888
|
const lines = content.split("\n");
|
|
8858
8889
|
const totalLines = lines.length;
|
|
8859
8890
|
if (offset !== void 0) {
|
|
@@ -8904,7 +8935,7 @@ function createWriteTool(ctx) {
|
|
|
8904
8935
|
// opencode/tools/edit.ts
|
|
8905
8936
|
import { tool as tool6 } from "ai";
|
|
8906
8937
|
import { z as z8 } from "zod";
|
|
8907
|
-
import { readFileSync as
|
|
8938
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8908
8939
|
import { resolve as resolve11 } from "node:path";
|
|
8909
8940
|
function createEditTool(ctx) {
|
|
8910
8941
|
return tool6({
|
|
@@ -8920,7 +8951,7 @@ function createEditTool(ctx) {
|
|
|
8920
8951
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions.permissions)) {
|
|
8921
8952
|
return { error: "Path not allowed" };
|
|
8922
8953
|
}
|
|
8923
|
-
const content =
|
|
8954
|
+
const content = readFileSync7(resolved, "utf-8");
|
|
8924
8955
|
if (replaceAll) {
|
|
8925
8956
|
if (!content.includes(oldString)) {
|
|
8926
8957
|
return { error: "oldString not found in file", replaced: 0 };
|
|
@@ -9250,17 +9281,17 @@ async function buildRouter4(deps) {
|
|
|
9250
9281
|
FROM "_opencode_messages"
|
|
9251
9282
|
WHERE session_id = ${sessionId}
|
|
9252
9283
|
`;
|
|
9253
|
-
const
|
|
9284
|
+
const raw = rows[0] || {
|
|
9254
9285
|
message_count: 0,
|
|
9255
9286
|
total_tokens_in: 0,
|
|
9256
9287
|
total_tokens_out: 0
|
|
9257
9288
|
};
|
|
9258
9289
|
return Response.json({
|
|
9259
9290
|
session_id: sessionId,
|
|
9260
|
-
message_count:
|
|
9261
|
-
tokens_in:
|
|
9262
|
-
tokens_out:
|
|
9263
|
-
tokens_total:
|
|
9291
|
+
message_count: raw.message_count,
|
|
9292
|
+
tokens_in: raw.total_tokens_in,
|
|
9293
|
+
tokens_out: raw.total_tokens_out,
|
|
9294
|
+
tokens_total: Number(raw.total_tokens_in) + Number(raw.total_tokens_out)
|
|
9264
9295
|
});
|
|
9265
9296
|
});
|
|
9266
9297
|
try {
|
|
@@ -9316,8 +9347,13 @@ function createWSHandler2(deps) {
|
|
|
9316
9347
|
client.mountPath
|
|
9317
9348
|
);
|
|
9318
9349
|
ws.send(JSON.stringify({ type: "session_created", session: session2 }));
|
|
9319
|
-
} catch (
|
|
9320
|
-
ws.send(
|
|
9350
|
+
} catch (err) {
|
|
9351
|
+
ws.send(
|
|
9352
|
+
JSON.stringify({
|
|
9353
|
+
type: "error",
|
|
9354
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9355
|
+
})
|
|
9356
|
+
);
|
|
9321
9357
|
}
|
|
9322
9358
|
break;
|
|
9323
9359
|
}
|
|
@@ -9371,9 +9407,9 @@ function createWSHandler2(deps) {
|
|
|
9371
9407
|
break;
|
|
9372
9408
|
}
|
|
9373
9409
|
}
|
|
9374
|
-
} catch (
|
|
9375
|
-
if (
|
|
9376
|
-
ws.send(JSON.stringify({ type: "error", error:
|
|
9410
|
+
} catch (err) {
|
|
9411
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
9412
|
+
ws.send(JSON.stringify({ type: "error", error: err.message }));
|
|
9377
9413
|
}
|
|
9378
9414
|
}
|
|
9379
9415
|
break;
|
|
@@ -9856,6 +9892,7 @@ function theme(options) {
|
|
|
9856
9892
|
};
|
|
9857
9893
|
return next(req, ctx);
|
|
9858
9894
|
};
|
|
9895
|
+
mw.__meta = { injects: ["theme"], depends: [] };
|
|
9859
9896
|
class ThemeRouter extends Router {
|
|
9860
9897
|
middleware() {
|
|
9861
9898
|
return mw;
|
|
@@ -9957,6 +9994,7 @@ function i18n(options) {
|
|
|
9957
9994
|
};
|
|
9958
9995
|
return next(req, ctx);
|
|
9959
9996
|
};
|
|
9997
|
+
mw.__meta = { injects: ["i18n"], depends: [] };
|
|
9960
9998
|
class I18nRouter extends Router {
|
|
9961
9999
|
middleware() {
|
|
9962
10000
|
return mw;
|
|
@@ -10001,7 +10039,7 @@ function makeSetFlash(name, location) {
|
|
|
10001
10039
|
}
|
|
10002
10040
|
function flash(options) {
|
|
10003
10041
|
const name = options?.name ?? "flash";
|
|
10004
|
-
|
|
10042
|
+
const mw = async (req, ctx, next) => {
|
|
10005
10043
|
const raw = getCookies(req)[name] ?? null;
|
|
10006
10044
|
const referer = req.headers.get("referer") || "/";
|
|
10007
10045
|
let value = void 0;
|
|
@@ -10024,6 +10062,8 @@ function flash(options) {
|
|
|
10024
10062
|
}
|
|
10025
10063
|
return res;
|
|
10026
10064
|
};
|
|
10065
|
+
mw.__meta = { injects: ["flash"], depends: [] };
|
|
10066
|
+
return mw;
|
|
10027
10067
|
}
|
|
10028
10068
|
|
|
10029
10069
|
// seo.ts
|
|
@@ -10206,7 +10246,7 @@ function csrf(options) {
|
|
|
10206
10246
|
const headerName = options?.header ?? "x-csrf-token";
|
|
10207
10247
|
const bodyKey = options?.key ?? "_csrf";
|
|
10208
10248
|
const excluded = new Set(options?.excludeMethods ?? ["GET", "HEAD", "OPTIONS"]);
|
|
10209
|
-
|
|
10249
|
+
const mw = async (req, ctx, next) => {
|
|
10210
10250
|
const method = req.method.toUpperCase();
|
|
10211
10251
|
if (excluded.has(method)) {
|
|
10212
10252
|
let token = getCookies(req)[cookieName];
|
|
@@ -10243,6 +10283,8 @@ function csrf(options) {
|
|
|
10243
10283
|
}
|
|
10244
10284
|
return next(req, ctx);
|
|
10245
10285
|
};
|
|
10286
|
+
mw.__meta = { injects: ["csrf"], depends: [] };
|
|
10287
|
+
return mw;
|
|
10246
10288
|
}
|
|
10247
10289
|
|
|
10248
10290
|
// logdb/rest.ts
|
|
@@ -11880,6 +11922,7 @@ function s3(options) {
|
|
|
11880
11922
|
mw.url = url;
|
|
11881
11923
|
mw.list = list;
|
|
11882
11924
|
mw.client = client;
|
|
11925
|
+
mw.__meta = { injects: ["s3"], depends: [] };
|
|
11883
11926
|
return mw;
|
|
11884
11927
|
}
|
|
11885
11928
|
|
|
@@ -12142,6 +12185,7 @@ function permissions(options) {
|
|
|
12142
12185
|
mw.requireRole = requireRole;
|
|
12143
12186
|
mw.requirePermission = requirePermission;
|
|
12144
12187
|
mw.migrate = migrate;
|
|
12188
|
+
mw.__meta = { injects: ["permissions"], depends: ["user"] };
|
|
12145
12189
|
return mw;
|
|
12146
12190
|
}
|
|
12147
12191
|
|
|
@@ -12615,6 +12659,7 @@ function notifier(opts) {
|
|
|
12615
12659
|
}
|
|
12616
12660
|
export {
|
|
12617
12661
|
DEFAULT_MAX_BODY,
|
|
12662
|
+
HttpError,
|
|
12618
12663
|
MIGRATIONS_TABLE,
|
|
12619
12664
|
MemoryCache,
|
|
12620
12665
|
MemoryStore,
|
package/dist/module-server.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Router } from './router.ts';
|
|
2
2
|
export declare function clearModuleCache(filePath?: string): void;
|
|
3
|
-
export declare function _setImportRoots(roots: string[]): void;
|
|
4
3
|
export declare function transformModule(absPath: string, root: string, mountPath?: string): Promise<{
|
|
5
4
|
url: string;
|
|
6
5
|
code: string;
|
package/dist/opencode/rest.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Router } from '../router.ts';
|
|
2
2
|
import type { LanguageModel } from 'ai';
|
|
3
|
+
import type { SqlClient } from '../vendor.ts';
|
|
3
4
|
import type { SkillDef, SkillRegistry, OpencodePermissions, PendingQuestion } from './types.ts';
|
|
4
5
|
interface RestDeps {
|
|
5
|
-
sql:
|
|
6
|
+
sql: SqlClient;
|
|
6
7
|
model: LanguageModel;
|
|
7
8
|
workspace: string;
|
|
8
9
|
systemPrompt?: string;
|
package/dist/opencode/ws.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { WebSocket } from '../vendor.ts';
|
|
1
|
+
import type { WebSocket, SqlClient } from '../vendor.ts';
|
|
2
2
|
import type { LanguageModel } from 'ai';
|
|
3
3
|
import type { Context } from '../types.ts';
|
|
4
4
|
import type { PendingQuestion, SkillDef, SkillRegistry, OpencodePermissions } from './types.ts';
|
|
5
5
|
interface WsDeps {
|
|
6
|
-
sql:
|
|
6
|
+
sql: SqlClient;
|
|
7
7
|
model: LanguageModel;
|
|
8
8
|
workspace: string;
|
|
9
9
|
systemPrompt?: string;
|
package/dist/queue/types.d.ts
CHANGED
|
@@ -43,7 +43,6 @@ export interface Queue extends Middleware<Context, Context & QueueInjected>, Clo
|
|
|
43
43
|
}): Promise<string>;
|
|
44
44
|
process<T>(type: string, handler: (job: QueueJob<T>) => Promise<void>): void;
|
|
45
45
|
run(): Promise<void>;
|
|
46
|
-
stop(): void;
|
|
47
46
|
stats(): {
|
|
48
47
|
running: boolean;
|
|
49
48
|
inflight: number;
|
package/dist/rate-limit.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Redis } from './vendor.ts';
|
|
2
|
-
import type { Context, Middleware } from './types.ts';
|
|
2
|
+
import type { Context, Middleware, Closeable } from './types.ts';
|
|
3
3
|
/** Options for {@link rateLimit}. */
|
|
4
4
|
export interface RateLimitOptions {
|
|
5
5
|
/** Maximum requests within the window (default: 100). */
|
|
@@ -17,6 +17,14 @@ export interface RateLimitOptions {
|
|
|
17
17
|
/** Redis key prefix (default: `'ratelimit:'`). */
|
|
18
18
|
prefix?: string;
|
|
19
19
|
}
|
|
20
|
+
/** Rate limit module — middleware + stats. */
|
|
21
|
+
export interface RateLimitModule extends Middleware<Context, Context>, Closeable {
|
|
22
|
+
stats(): {
|
|
23
|
+
store: string;
|
|
24
|
+
entries?: number;
|
|
25
|
+
maxEntries: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
20
28
|
/**
|
|
21
29
|
* Rate limiting middleware (in-memory or Redis-backed).
|
|
22
30
|
*
|
|
@@ -34,7 +42,4 @@ export interface RateLimitOptions {
|
|
|
34
42
|
* app.use(rateLimit({ store: 'redis', redis: new Redis(), max: 100 }))
|
|
35
43
|
* ```
|
|
36
44
|
*/
|
|
37
|
-
export declare function rateLimit(options?: RateLimitOptions):
|
|
38
|
-
close: () => void;
|
|
39
|
-
stop?: () => void;
|
|
40
|
-
};
|
|
45
|
+
export declare function rateLimit(options?: RateLimitOptions): RateLimitModule;
|
package/dist/redis/index.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
3
|
-
export declare function redis(opts?: string | RedisOptions): RedisClient;
|
|
1
|
+
export { redis } from './client.ts';
|
|
2
|
+
export type { RedisOptions, RedisClient, RedisInjected } from './types.ts';
|
package/dist/request-id.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ declare module './types.ts' {
|
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
/** Options for {@link requestId}. */
|
|
8
|
+
/** Request ID module — a {@link Middleware} that injects `ctx.requestId`. */
|
|
9
|
+
export type RequestIdModule = Middleware<Context, Context & {
|
|
10
|
+
requestId: string;
|
|
11
|
+
}>;
|
|
8
12
|
export interface RequestIdOptions {
|
|
9
13
|
/** Header name for request ID (default: `'X-Request-ID'`). */
|
|
10
14
|
header?: string;
|
package/dist/router.d.ts
CHANGED
|
@@ -10,24 +10,6 @@ export type WebSocketHandler = {
|
|
|
10
10
|
error?: (ws: WebSocket, ctx: Context, error: Error) => void | Promise<void>;
|
|
11
11
|
};
|
|
12
12
|
type WsUpgradeHandler = (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
|
|
13
|
-
/**
|
|
14
|
-
* Middleware metadata for dependency checking.
|
|
15
|
-
* Middleware factories can attach this to their return value for runtime validation.
|
|
16
|
-
*
|
|
17
|
-
* ```ts
|
|
18
|
-
* function postgres(): PostgresClient {
|
|
19
|
-
* const mw = async (req, ctx, next) => { ... }
|
|
20
|
-
* mw.__meta = { injects: ['sql'], depends: [] }
|
|
21
|
-
* return Object.assign(mw, { sql, migrate, close })
|
|
22
|
-
* }
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
export interface MiddlewareMeta {
|
|
26
|
-
/** Fields this middleware injects into ctx. */
|
|
27
|
-
injects: string[];
|
|
28
|
-
/** Fields this middleware depends on (must be injected earlier). */
|
|
29
|
-
depends: string[];
|
|
30
|
-
}
|
|
31
13
|
export declare class Router<T extends Context = Context> {
|
|
32
14
|
private root;
|
|
33
15
|
private wsRoot;
|
package/dist/serve.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type IncomingMessage, type ServerResponse } from 'node:http';
|
|
2
2
|
import type { Duplex } from 'node:stream';
|
|
3
|
-
import type
|
|
3
|
+
import { type Handler } from './types.ts';
|
|
4
4
|
export interface ServeOptions {
|
|
5
5
|
port?: number;
|
|
6
6
|
hostname?: string;
|
|
@@ -18,6 +18,8 @@ export interface ServeOptions {
|
|
|
18
18
|
}
|
|
19
19
|
export interface Server {
|
|
20
20
|
stop: (timeoutMs?: number) => Promise<void>;
|
|
21
|
+
/** Alias for `stop()`. Prefer this for consistency with other modules. */
|
|
22
|
+
close: (timeoutMs?: number) => Promise<void>;
|
|
21
23
|
readonly port: number;
|
|
22
24
|
readonly hostname: string;
|
|
23
25
|
ready: Promise<void>;
|
|
@@ -8,5 +8,3 @@ export declare function getServerModule(absPath: string): any;
|
|
|
8
8
|
* Otherwise clear all modules.
|
|
9
9
|
*/
|
|
10
10
|
export declare function clearServerModule(absPath?: string): void;
|
|
11
|
-
/** Release resources. Call when shutting down. */
|
|
12
|
-
export declare function closeRegistry(): void;
|
package/dist/session.d.ts
CHANGED
|
@@ -80,6 +80,10 @@ export declare class MemoryStore implements SessionStore {
|
|
|
80
80
|
/** Testing only: return approximate count. */
|
|
81
81
|
get size(): number;
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Redis-backed session store.
|
|
85
|
+
* Pass to `session({ store: new RedisStore({ redis }) })`.
|
|
86
|
+
*/
|
|
83
87
|
export declare class RedisStore implements SessionStore {
|
|
84
88
|
private redis;
|
|
85
89
|
private prefix;
|
|
@@ -90,6 +94,22 @@ export declare class RedisStore implements SessionStore {
|
|
|
90
94
|
destroy(sid: string): Promise<void>;
|
|
91
95
|
close(): Promise<void>;
|
|
92
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Session middleware. Injects `ctx.session` with a persistent key-value store
|
|
99
|
+
* scoped to the request. Data is automatically saved to the store on response.
|
|
100
|
+
*
|
|
101
|
+
* Defaults to memory store. Use `{ store: 'redis', redis }` for multi-process setups.
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { session } from 'weifuwu'
|
|
105
|
+
* app.use(session())
|
|
106
|
+
*
|
|
107
|
+
* app.get('/visit', (req, ctx) => {
|
|
108
|
+
* ctx.session.count = (ctx.session.count ?? 0) + 1
|
|
109
|
+
* return Response.json({ visits: ctx.session.count })
|
|
110
|
+
* })
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
93
113
|
export declare function session(options?: SessionOptions): Middleware<Context, Context & SessionInjected> & {
|
|
94
114
|
close: () => Promise<void>;
|
|
95
115
|
store: SessionStore;
|
package/dist/ssr.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import { Router } from './router.ts';
|
|
2
|
-
interface ResolvedRoute {
|
|
3
|
-
routePath: string;
|
|
4
|
-
pageFile: string;
|
|
5
|
-
layoutFiles: string[];
|
|
6
|
-
errorFiles: string[];
|
|
7
|
-
notFoundFile: string | null;
|
|
8
|
-
}
|
|
9
|
-
/** Clear route cache (called by HMR watcher in dev mode). */
|
|
10
|
-
export declare function clearRouteCache(cache: Map<string, ResolvedRoute | null>): void;
|
|
11
2
|
export interface RouteEntry {
|
|
12
3
|
path: string;
|
|
13
4
|
file: string;
|
|
@@ -18,4 +9,3 @@ export declare function ssr(opts: {
|
|
|
18
9
|
close?: () => void;
|
|
19
10
|
pages?: () => RouteEntry[];
|
|
20
11
|
};
|
|
21
|
-
export {};
|
package/dist/tenant/utils.d.ts
CHANGED
|
@@ -5,6 +5,5 @@ export declare function sqlTypeForField(field: FieldDef): string;
|
|
|
5
5
|
export declare function validateSlug(slug: string): string | null;
|
|
6
6
|
export declare function validateFieldDefs(fields: FieldDef[]): string[];
|
|
7
7
|
export declare function formatDefault(field: FieldDef): string;
|
|
8
|
-
export declare function mapFieldToZod(field: FieldDef): string;
|
|
9
8
|
export declare function getRelationFields(fields: FieldDef[]): FieldDef[];
|
|
10
9
|
export declare function findRelation(fields: FieldDef[], targetSlug: string): FieldDef | undefined;
|
package/dist/types.d.ts
CHANGED
|
@@ -4,12 +4,25 @@ export interface Context {
|
|
|
4
4
|
mountPath?: string;
|
|
5
5
|
layoutStack?: {
|
|
6
6
|
path: string;
|
|
7
|
-
component:
|
|
7
|
+
component: unknown;
|
|
8
8
|
}[];
|
|
9
9
|
[key: string]: unknown;
|
|
10
10
|
}
|
|
11
11
|
export type Handler<T extends Context = Context> = (req: Request, ctx: T) => Response | Promise<Response>;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Metadata for middleware dependency checking.
|
|
14
|
+
* Middleware factories attach this for runtime validation.
|
|
15
|
+
*/
|
|
16
|
+
export interface MiddlewareMeta {
|
|
17
|
+
/** Fields this middleware injects into ctx. */
|
|
18
|
+
injects: string[];
|
|
19
|
+
/** Fields this middleware depends on (must be injected earlier). */
|
|
20
|
+
depends: string[];
|
|
21
|
+
}
|
|
22
|
+
export type Middleware<In extends Context = Context, Out extends In = In> = {
|
|
23
|
+
(req: Request, ctx: In, next: Handler<Out>): Response | Promise<Response>;
|
|
24
|
+
__meta?: MiddlewareMeta;
|
|
25
|
+
};
|
|
13
26
|
export type ErrorHandler<T extends Context = Context> = (error: Error, req: Request, ctx: T) => Response | Promise<Response>;
|
|
14
27
|
/**
|
|
15
28
|
* Interface for resources that require explicit cleanup (connections, pools, timers).
|
|
@@ -19,3 +32,16 @@ export interface Closeable {
|
|
|
19
32
|
/** Release all resources. Call once when shutting down. */
|
|
20
33
|
close(): Promise<void>;
|
|
21
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* HTTP error with an explicit status code.
|
|
37
|
+
* Throw from a handler or middleware to return a non-200 response.
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* if (!resource) throw new HttpError('Not found', 404)
|
|
41
|
+
* serve() catches it and returns the status code.
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare class HttpError extends Error {
|
|
45
|
+
status: number;
|
|
46
|
+
constructor(message: string, status: number);
|
|
47
|
+
}
|
package/dist/upload.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ declare module './types.ts' {
|
|
|
4
4
|
parsed: Record<string, unknown>;
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
+
/** Upload middleware — a {@link Middleware} that injects `ctx.parsed` with file fields. */
|
|
8
|
+
export type UploadModule = Middleware<Context, Context & {
|
|
9
|
+
parsed: Record<string, unknown>;
|
|
10
|
+
}>;
|
|
7
11
|
/** A parsed file from a multipart upload. */
|
|
8
12
|
export interface UploadedFile {
|
|
9
13
|
/** Original filename from the client. */
|
package/dist/user/client.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type { UserOptions, UserModule } from './types.ts';
|
|
1
|
+
import type { UserOptions, UserData, UserModule } from './types.ts';
|
|
2
|
+
declare module '../types.ts' {
|
|
3
|
+
interface Context {
|
|
4
|
+
user: UserData;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
8
|
* User authentication module — local register/login, JWT verification, OAuth2 server, social login.
|
|
4
9
|
* Supports DB-less auth via tokens/verify/proxy options.
|
|
@@ -9,13 +9,13 @@ interface OAuthLoginDeps {
|
|
|
9
9
|
/** Table for provider-user link, derived from usersTable. */
|
|
10
10
|
providerTable: string;
|
|
11
11
|
redirectUrl: string;
|
|
12
|
-
signToken: (user:
|
|
12
|
+
signToken: (user: Record<string, unknown>) => string;
|
|
13
13
|
/** Create a placeholder user for OAuth login (no password). */
|
|
14
|
-
createPlaceholderUser: (email: string, name: string) => Promise<
|
|
14
|
+
createPlaceholderUser: (email: string, name: string) => Promise<Record<string, unknown>>;
|
|
15
15
|
/** Find user by internal ID. */
|
|
16
|
-
findUserById: (id: number) => Promise<
|
|
16
|
+
findUserById: (id: number) => Promise<Record<string, unknown> | undefined>;
|
|
17
17
|
/** Find user by email. */
|
|
18
|
-
findUserByEmail: (email: string) => Promise<
|
|
18
|
+
findUserByEmail: (email: string) => Promise<Record<string, unknown> | undefined>;
|
|
19
19
|
}
|
|
20
20
|
export declare function registerOAuthLoginRoutes(router: Router, deps: OAuthLoginDeps, providers: Record<string, OAuthProviderConfig>): void;
|
|
21
21
|
export {};
|
package/dist/validate.d.ts
CHANGED
|
@@ -5,10 +5,28 @@ declare module './types.ts' {
|
|
|
5
5
|
parsed: Record<string, unknown>;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
+
/** Validation middleware — a {@link Middleware} that injects `ctx.parsed` with validated data. */
|
|
9
|
+
export type ValidateModule = Middleware;
|
|
8
10
|
export interface ValidationSchemas {
|
|
9
11
|
body?: ZodSchema;
|
|
10
12
|
query?: ZodSchema;
|
|
11
13
|
params?: ZodSchema;
|
|
12
14
|
headers?: ZodSchema;
|
|
13
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Request validation middleware using Zod schemas.
|
|
18
|
+
*
|
|
19
|
+
* Validates `params`, `query`, `body`, and/or `headers` against schemas.
|
|
20
|
+
* Returns 422 with error details on mismatch.
|
|
21
|
+
* Injects `ctx.parsed` with validated-and-transformed values.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { z } from 'zod'
|
|
25
|
+
*
|
|
26
|
+
* app.get('/users/:id', validate({
|
|
27
|
+
* params: z.object({ id: z.string() }),
|
|
28
|
+
* query: z.object({ include: z.string().optional() }),
|
|
29
|
+
* }), handler)
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
14
32
|
export declare function validate(schemas?: ValidationSchemas): Middleware;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weifuwu",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.25.
|
|
4
|
+
"version": "0.25.2",
|
|
5
5
|
"description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
"lint": "eslint --ext .ts,.tsx .",
|
|
32
32
|
"test": "node --test 'test/**/*.test.ts'",
|
|
33
33
|
"test:coverage": "node --experimental-test-coverage --test 'test/**/*.test.ts'",
|
|
34
|
-
"test:
|
|
35
|
-
"test:
|
|
34
|
+
"test:typecheck": "npx tsc -p tsconfig.test.json --noEmit",
|
|
35
|
+
"test:quick": "bash scripts/test-quick.sh",
|
|
36
|
+
"test:ci": "node --test --test-force-exit --test-timeout=60000 'test/**/*.test.ts'",
|
|
36
37
|
"prepare": "husky"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|