weifuwu 0.23.4 → 0.24.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.
Files changed (46) hide show
  1. package/README.md +431 -152
  2. package/cli.ts +3 -0
  3. package/dist/agent/types.d.ts +2 -1
  4. package/dist/ai/provider.d.ts +10 -1
  5. package/dist/analytics.d.ts +2 -2
  6. package/dist/cache.d.ts +4 -4
  7. package/dist/cli.js +3 -0
  8. package/dist/cors.d.ts +2 -2
  9. package/dist/csrf.d.ts +11 -5
  10. package/dist/deploy/types.d.ts +2 -2
  11. package/dist/env.d.ts +33 -0
  12. package/dist/flash.d.ts +5 -0
  13. package/dist/helmet.d.ts +2 -2
  14. package/dist/hub.d.ts +2 -1
  15. package/dist/i18n.d.ts +27 -2
  16. package/dist/iii/register-worker.d.ts +1 -1
  17. package/dist/iii/types.d.ts +5 -3
  18. package/dist/index.d.ts +8 -10
  19. package/dist/index.js +434 -359
  20. package/dist/kb/types.d.ts +8 -0
  21. package/dist/logdb/types.d.ts +2 -1
  22. package/dist/mailer.d.ts +2 -1
  23. package/dist/messager/types.d.ts +2 -1
  24. package/dist/opencode/types.d.ts +2 -1
  25. package/dist/permissions.d.ts +2 -2
  26. package/dist/postgres/module.d.ts +2 -1
  27. package/dist/postgres/types.d.ts +2 -2
  28. package/dist/queue/types.d.ts +2 -2
  29. package/dist/rate-limit.d.ts +3 -2
  30. package/dist/react.js +6 -6
  31. package/dist/redis/types.d.ts +2 -2
  32. package/dist/request-id.d.ts +16 -7
  33. package/dist/router.d.ts +3 -0
  34. package/dist/seo.d.ts +2 -2
  35. package/dist/serve.d.ts +1 -1
  36. package/dist/session.d.ts +9 -5
  37. package/dist/tailwind.d.ts +9 -0
  38. package/dist/tenant/types.d.ts +3 -3
  39. package/dist/theme.d.ts +25 -2
  40. package/dist/trace.d.ts +44 -0
  41. package/dist/types.d.ts +8 -17
  42. package/dist/upload.d.ts +9 -2
  43. package/dist/user/client.d.ts +11 -3
  44. package/dist/user/types.d.ts +21 -6
  45. package/dist/validate.d.ts +5 -0
  46. package/package.json +9 -9
@@ -41,6 +41,14 @@ export interface KBListEntry {
41
41
  title: string;
42
42
  chunks: number;
43
43
  }
44
+ export interface KBInjected {
45
+ search(query: string, searchOptions?: KBSearchOptions): Promise<KBSearchResult[]>;
46
+ }
47
+ declare module '../types.ts' {
48
+ interface Context {
49
+ kb?: KBInjected;
50
+ }
51
+ }
44
52
  export interface KBModule {
45
53
  /**
46
54
  * Ingest a document: chunk → embed → store.
@@ -1,5 +1,6 @@
1
1
  import type { PostgresClient } from '../postgres/types.ts';
2
2
  import type { Router } from '../router.ts';
3
+ import type { Closeable } from '../types.ts';
3
4
  export interface LogdbOptions {
4
5
  pg: PostgresClient;
5
6
  table?: string;
@@ -18,7 +19,7 @@ export interface LogEntryInput {
18
19
  message: string;
19
20
  metadata?: Record<string, unknown>;
20
21
  }
21
- export interface LogdbModule extends Router {
22
+ export interface LogdbModule extends Router, Closeable {
22
23
  log(input: LogEntryInput): Promise<LogEntry>;
23
24
  migrate(): Promise<void>;
24
25
  clean(retentionMonths: number): Promise<number>;
package/dist/mailer.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Transporter } from 'nodemailer';
2
+ import type { Closeable } from './types.ts';
2
3
  /** Options for sending an email. */
3
4
  export interface MailOptions {
4
5
  /** Recipient address(es). */
@@ -26,7 +27,7 @@ export interface MailerOptions {
26
27
  send?: (opts: MailOptions) => Promise<void>;
27
28
  }
28
29
  /** Mailer instance returned by {@link mailer}. */
29
- export interface Mailer {
30
+ export interface Mailer extends Closeable {
30
31
  /** Send an email. */
31
32
  send: (opts: MailOptions) => Promise<void>;
32
33
  /** Close the nodemailer transport. */
@@ -2,6 +2,7 @@ import type { Router } from '../router.ts';
2
2
  import type { AgentModule } from '../agent/types.ts';
3
3
  import type { PostgresClient } from '../postgres/types.ts';
4
4
  import type { Redis } from '../vendor.ts';
5
+ import type { Closeable } from '../types.ts';
5
6
  export interface MessagerOptions {
6
7
  pg: PostgresClient;
7
8
  agents?: AgentModule;
@@ -37,7 +38,7 @@ export interface Message {
37
38
  mime_type: string | null;
38
39
  created_at: string;
39
40
  }
40
- export interface MessagerModule extends Router {
41
+ export interface MessagerModule extends Router, Closeable {
41
42
  migrate: () => Promise<void>;
42
43
  wsHandler: () => any;
43
44
  send: (channelId: number, content: string, opts?: {
@@ -1,5 +1,6 @@
1
1
  import type { Router } from '../router.ts';
2
2
  import type { PostgresClient } from '../postgres/types.ts';
3
+ import type { Closeable } from '../types.ts';
3
4
  export interface Session {
4
5
  id: string;
5
6
  tenant_id: string | null;
@@ -74,7 +75,7 @@ export interface OpencodeOptions {
74
75
  skills?: SkillDef[];
75
76
  permissions?: OpencodePermissions;
76
77
  }
77
- export interface OpencodeModule extends Router {
78
+ export interface OpencodeModule extends Router, Closeable {
78
79
  migrate: () => Promise<void>;
79
80
  wsHandler: () => any;
80
81
  close: () => Promise<void>;
@@ -39,12 +39,12 @@ export interface PermissionsModule extends Middleware {
39
39
  * Middleware that rejects the request if the user does not have any of the specified roles.
40
40
  * Must be placed after `permissions()` middleware (which injects ctx.permissions.roles).
41
41
  */
42
- requireRole(...roles: string[]): Middleware;
42
+ requireRole(...roles: string[]): Middleware<Context, Context>;
43
43
  /**
44
44
  * Middleware that rejects the request if the user does not have all specified permissions.
45
45
  * Must be placed after `permissions()` middleware (which injects ctx.permissions.permissions).
46
46
  */
47
- requirePermission(...permissions: string[]): Middleware;
47
+ requirePermission(...permissions: string[]): Middleware<Context, Context>;
48
48
  /** Create the underlying tables. Safe to call multiple times. */
49
49
  migrate(): Promise<void>;
50
50
  }
@@ -1,7 +1,8 @@
1
1
  import type { PostgresClient } from './types.ts';
2
2
  import type { Sql } from '../vendor.ts';
3
3
  import type { ColumnBuilder, BoundTable, Table } from './schema/index.ts';
4
- export declare class PgModule {
4
+ import type { Closeable } from '../types.ts';
5
+ export declare class PgModule implements Closeable {
5
6
  protected sql: Sql<{}>;
6
7
  protected pg: PostgresClient;
7
8
  constructor(pg: PostgresClient);
@@ -1,5 +1,5 @@
1
1
  import type { Sql } from '../vendor.ts';
2
- import type { Context, Middleware } from '../types.ts';
2
+ import type { Context, Middleware, Closeable } from '../types.ts';
3
3
  import type { ColumnBuilder, BoundTable, Table } from './schema/index.ts';
4
4
  declare module '../types.ts' {
5
5
  interface Context {
@@ -22,7 +22,7 @@ export interface PostgresOptions {
22
22
  /** Called after every query completes. Receives query text, duration in ms, and row count. */
23
23
  onQuery?: (query: string, durationMs: number, rowCount: number) => void;
24
24
  }
25
- export interface PostgresClient extends Middleware<Context, Context & PostgresInjected> {
25
+ export interface PostgresClient extends Middleware<Context, Context & PostgresInjected>, Closeable {
26
26
  sql: Sql<{}>;
27
27
  /** Creates the migration tracking table (_weifuwu_migrations). Called once at startup. */
28
28
  migrate: () => Promise<void>;
@@ -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
  declare module '../types.ts' {
4
4
  interface Context {
5
5
  queue: Queue;
@@ -32,7 +32,7 @@ export interface QueueJobWithError<T = unknown> extends QueueJob<T> {
32
32
  error: string;
33
33
  failedAt: number;
34
34
  }
35
- export interface Queue extends Middleware<Context, Context & QueueInjected> {
35
+ export interface Queue extends Middleware<Context, Context & QueueInjected>, Closeable {
36
36
  /** Register a cron job. Uses queue's backend (memory/pg/redis) for execution. */
37
37
  cron(pattern: string, handler: () => void | Promise<void>): {
38
38
  stop: () => void;
@@ -34,6 +34,7 @@ export interface RateLimitOptions {
34
34
  * app.use(rateLimit({ store: 'redis', redis: new Redis(), max: 100 }))
35
35
  * ```
36
36
  */
37
- export declare function rateLimit(options?: RateLimitOptions): Middleware & {
38
- stop: () => void;
37
+ export declare function rateLimit(options?: RateLimitOptions): Middleware<Context, Context> & {
38
+ close: () => void;
39
+ stop?: () => void;
39
40
  };
package/dist/react.js CHANGED
@@ -629,9 +629,10 @@ function useFlashMessage() {
629
629
  // use-agent-stream.ts
630
630
  import { useState as useState6, useCallback as useCallback5, useRef as useRef4 } from "react";
631
631
  function useAgentStream(opts) {
632
- const { wsPath, channelId, onStreamEnd, onError } = opts;
632
+ const { wsPath, onStreamEnd, onError } = opts;
633
633
  const [streams, setStreams] = useState6({});
634
634
  const activeRef = useRef4(/* @__PURE__ */ new Set());
635
+ const streamsRef = useRef4({});
635
636
  const getAgentText = useCallback5(
636
637
  (agentId) => streams[agentId] || "",
637
638
  [streams]
@@ -652,20 +653,19 @@ function useAgentStream(opts) {
652
653
  case "agent_stream": {
653
654
  activeRef.current.add(agentId);
654
655
  const token = msg.data?.token || "";
655
- setStreams((prev) => {
656
- const current = prev[agentId] || "";
657
- return { ...prev, [agentId]: current + token };
658
- });
656
+ streamsRef.current[agentId] = (streamsRef.current[agentId] || "") + token;
657
+ setStreams({ ...streamsRef.current });
659
658
  break;
660
659
  }
661
660
  case "agent_stream_end": {
662
661
  activeRef.current.delete(agentId);
663
- const fullText = streams[agentId] || "";
662
+ const fullText = streamsRef.current[agentId] || "";
664
663
  onStreamEnd?.(agentId, fullText);
665
664
  break;
666
665
  }
667
666
  case "agent_error": {
668
667
  activeRef.current.delete(agentId);
668
+ delete streamsRef.current[agentId];
669
669
  onError?.(agentId, msg.data?.error || "Unknown error");
670
670
  break;
671
671
  }
@@ -1,5 +1,5 @@
1
1
  import type { Redis, RedisOptions as IORedisOptions } from '../vendor.ts';
2
- import type { Context, Middleware } from '../types.ts';
2
+ import type { Context, Middleware, Closeable } from '../types.ts';
3
3
  declare module '../types.ts' {
4
4
  interface Context {
5
5
  redis: Redis;
@@ -12,7 +12,7 @@ export type RedisOptions = IORedisOptions & {
12
12
  export interface RedisInjected {
13
13
  redis: Redis;
14
14
  }
15
- export interface RedisClient extends Middleware<Context, Context & RedisInjected> {
15
+ export interface RedisClient extends Middleware<Context, Context & RedisInjected>, Closeable {
16
16
  redis: Redis;
17
17
  close: () => Promise<void>;
18
18
  }
@@ -1,4 +1,9 @@
1
1
  import type { Context, Middleware } from './types.ts';
2
+ declare module './types.ts' {
3
+ interface Context {
4
+ requestId: string;
5
+ }
6
+ }
2
7
  /** Options for {@link requestId}. */
3
8
  export interface RequestIdOptions {
4
9
  /** Header name for request ID (default: `'X-Request-ID'`). */
@@ -9,18 +14,22 @@ export interface RequestIdOptions {
9
14
  /**
10
15
  * Request ID middleware.
11
16
  *
12
- * Reads an incoming `X-Request-ID` header (or custom header name) from the
13
- * request. If absent, generates a new UUID. Sets the response header and
14
- * injects `ctx.requestId`.
17
+ * @deprecated Use `trace()` from 'weifuwu' instead it injects `ctx.trace.requestId`
18
+ * along with `traceId` and `elapsed()` in a single middleware.
15
19
  *
16
20
  * ```ts
17
- * import { requestId } from 'weifuwu'
21
+ * // Old:
18
22
  * app.use(requestId())
23
+ * ctx.requestId
19
24
  *
20
- * app.get('/', (req, ctx) => {
21
- * console.log('request ID:', ctx.requestId)
22
- * })
25
+ * // New:
26
+ * app.use(trace())
27
+ * ctx.trace.requestId
23
28
  * ```
29
+ *
30
+ * Reads an incoming `X-Request-ID` header (or custom header name) from the
31
+ * request. If absent, generates a new UUID. Sets the response header and
32
+ * injects `ctx.requestId`.
24
33
  */
25
34
  export declare function requestId(options?: RequestIdOptions): Middleware<Context, Context & {
26
35
  requestId: string;
package/dist/router.d.ts CHANGED
@@ -25,6 +25,9 @@ export declare class Router<T extends Context = Context> {
25
25
  use<Out extends Context>(mw: Middleware<Context, Out>): Router<T & Out>;
26
26
  use(path: string, mw: Middleware<T, T>): Router<T>;
27
27
  use(path: string, router: Router<any>): Router<T>;
28
+ use(mod: Router & {
29
+ middleware: () => Middleware;
30
+ }): Router<T>;
28
31
  get(path: string, ...args: [...Middleware<T, T>[], Handler<T> | Router<any>]): Router<T>;
29
32
  post(path: string, ...args: [...Middleware<T, T>[], Handler<T> | Router<any>]): Router<T>;
30
33
  put(path: string, ...args: [...Middleware<T, T>[], Handler<T> | Router<any>]): Router<T>;
package/dist/seo.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Middleware } from './types.ts';
1
+ import type { Middleware, Context } from './types.ts';
2
2
  import { Router } from './router.ts';
3
3
  /** A rule in `robots.txt`. */
4
4
  export interface RobotsRule {
@@ -54,7 +54,7 @@ export interface SeoOptions {
54
54
  * }))
55
55
  * ```
56
56
  */
57
- export declare function seoMiddleware(options?: SeoOptions): Middleware;
57
+ export declare function seoMiddleware(options?: SeoOptions): Middleware<Context, Context>;
58
58
  /**
59
59
  * SEO module — serves `robots.txt` and `sitemap.xml`.
60
60
  *
package/dist/serve.d.ts CHANGED
@@ -29,7 +29,7 @@ export declare function createRequest(req: IncomingMessage, body: Buffer): [Requ
29
29
  export declare function sendResponse(res: ServerResponse, response: Response, opts?: {
30
30
  traceId?: string | null;
31
31
  }): Promise<void>;
32
- export declare function createTestServer(handler: Handler): Promise<{
32
+ export declare function createTestServer(handler: Handler, options?: ServeOptions): Promise<{
33
33
  server: Server;
34
34
  url: string;
35
35
  }>;
package/dist/session.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { Middleware } from './types.ts';
1
+ import type { Context, Middleware } from './types.ts';
2
2
  import type { Redis } from './vendor.ts';
3
+ import type { Closeable } from './types.ts';
3
4
  declare module './types.ts' {
4
5
  interface Context {
5
6
  session: Session;
@@ -27,10 +28,12 @@ export interface Session extends SessionData {
27
28
  [kStore]: SessionStore;
28
29
  [kTtl]: number;
29
30
  }
30
- export interface SessionStore {
31
+ export interface SessionStore extends Closeable {
31
32
  get(sid: string): Promise<Record<string, unknown> | null>;
32
33
  set(sid: string, data: Record<string, unknown>, ttl: number): Promise<void>;
33
34
  destroy(sid: string): Promise<void>;
35
+ /** Release resources. Default no-op. */
36
+ close(): Promise<void>;
34
37
  }
35
38
  export interface SessionOptions {
36
39
  /** Session store. 'memory' (default) or 'redis'. */
@@ -74,7 +77,7 @@ export declare class MemoryStore implements SessionStore {
74
77
  set(sid: string, data: Record<string, unknown>, ttl: number): Promise<void>;
75
78
  destroy(sid: string): Promise<void>;
76
79
  private cleanup;
77
- close(): void;
80
+ close(): Promise<void>;
78
81
  /** Testing only: return approximate count. */
79
82
  get size(): number;
80
83
  }
@@ -86,9 +89,10 @@ export declare class RedisStore implements SessionStore {
86
89
  get(sid: string): Promise<Record<string, unknown> | null>;
87
90
  set(sid: string, data: Record<string, unknown>, ttl: number): Promise<void>;
88
91
  destroy(sid: string): Promise<void>;
92
+ close(): Promise<void>;
89
93
  }
90
- export declare function session(options?: SessionOptions): Middleware & {
91
- close: () => void;
94
+ export declare function session(options?: SessionOptions): Middleware<Context, Context & SessionInjected> & {
95
+ close: () => Promise<void>;
92
96
  store: SessionStore;
93
97
  };
94
98
  export {};
@@ -1,5 +1,14 @@
1
1
  import { Router } from './router.ts';
2
2
  import type { Middleware } from './types.ts';
3
+ export interface TailwindInjected {
4
+ css: string;
5
+ url: string;
6
+ }
7
+ declare module './types.ts' {
8
+ interface Context {
9
+ tailwind?: TailwindInjected;
10
+ }
11
+ }
3
12
  export declare function addTailwindSource(dir: string): void;
4
13
  export declare function tailwindContext(dir: string): Middleware;
5
14
  export declare function tailwindRouter(dir: string): Router;
@@ -1,4 +1,4 @@
1
- import type { Context } from '../types.ts';
1
+ import type { Context, Handler } from '../types.ts';
2
2
  import type { Router } from '../router.ts';
3
3
  import type { PostgresClient } from '../postgres/types.ts';
4
4
  declare module '../types.ts' {
@@ -42,7 +42,7 @@ export interface TenantOptions {
42
42
  }
43
43
  export interface TenantModule extends Router {
44
44
  migrate: () => Promise<void>;
45
- middleware: () => (req: Request, ctx: Context, next: any) => Promise<Response>;
46
- graphql: () => any;
45
+ middleware: () => (req: Request, ctx: Context, next: Handler<Context>) => Promise<Response>;
46
+ graphql: () => Router;
47
47
  close: () => Promise<void>;
48
48
  }
package/dist/theme.d.ts CHANGED
@@ -1,8 +1,31 @@
1
- import type { Middleware } from './types.ts';
1
+ import type { Context, Middleware } from './types.ts';
2
+ import { Router } from './router.ts';
3
+ declare module './types.ts' {
4
+ interface Context {
5
+ theme: ThemeInjected;
6
+ }
7
+ }
8
+ export interface ThemeInjected {
9
+ value: string;
10
+ set: (value: string, loc?: string) => Response;
11
+ }
2
12
  export interface ThemeOptions {
3
13
  /** Default theme value (default: 'system'). */
4
14
  default?: string;
5
15
  /** Cookie name (default: 'theme'). Set to empty string to disable cookie. */
6
16
  cookie?: string;
7
17
  }
8
- export declare function theme(options?: ThemeOptions): Middleware;
18
+ /**
19
+ * Theme module. Returns a Router with an attached `.middleware()` method.
20
+ *
21
+ * ```ts
22
+ * const t = theme()
23
+ * app.use(t.middleware()) // → ctx.theme = { value, set }
24
+ * app.use('/', t) // → GET /__theme/dark (switch route)
25
+ * ```
26
+ */
27
+ export interface ThemeModule extends Router {
28
+ /** Middleware that injects `ctx.theme = { value, set }`. */
29
+ middleware: () => Middleware<Context, Context & ThemeInjected>;
30
+ }
31
+ export declare function theme(options?: ThemeOptions): ThemeModule;
package/dist/trace.d.ts CHANGED
@@ -1,3 +1,19 @@
1
+ import type { Context, Middleware } from './types.ts';
2
+ declare module './types.ts' {
3
+ interface Context {
4
+ trace: TraceInjected;
5
+ }
6
+ }
7
+ export interface TraceInjected {
8
+ /** Unique request identifier (from X-Request-ID header or auto-generated). */
9
+ requestId: string;
10
+ /** Unique trace identifier for the request. */
11
+ traceId: string;
12
+ /** Milliseconds elapsed since the trace started. */
13
+ elapsed: () => number;
14
+ /** Timestamp (ms) when the trace started. */
15
+ startTime: number;
16
+ }
1
17
  export interface TraceContext {
2
18
  /** Unique identifier for the current request trace. */
3
19
  traceId: string;
@@ -49,3 +65,31 @@ export declare function runWithTrace<T>(incomingTraceId: string | null, fn: () =
49
65
  * ```
50
66
  */
51
67
  export declare function traceElapsed(): number;
68
+ /** Options for {@link trace}. */
69
+ export interface TraceOptions {
70
+ /** Header name for request ID (default: `'X-Request-ID'`). */
71
+ header?: string;
72
+ /** Custom ID generator (default: `crypto.randomUUID`). */
73
+ generator?: () => string;
74
+ }
75
+ /**
76
+ * Request tracing middleware.
77
+ *
78
+ * Injects `ctx.trace = { requestId, traceId, elapsed, startTime }`.
79
+ * Reads/writes `X-Request-ID` header. Combines the functionality of `requestId()`
80
+ * with the per-request tracing from `AsyncLocalStorage`.
81
+ *
82
+ * ```ts
83
+ * import { trace } from 'weifuwu'
84
+ * app.use(trace())
85
+ *
86
+ * app.get('/', (req, ctx) => {
87
+ * console.log(ctx.trace.requestId) // 550e8400-e29b-...
88
+ * console.log(ctx.trace.traceId) // same as currentTraceId()
89
+ * console.log(ctx.trace.elapsed()) // ms since request start
90
+ * })
91
+ * ```
92
+ */
93
+ export declare function trace(options?: TraceOptions): Middleware<Context, Context & {
94
+ trace: TraceInjected;
95
+ }>;
package/dist/types.d.ts CHANGED
@@ -1,24 +1,7 @@
1
1
  export interface Context {
2
2
  params: Record<string, string>;
3
3
  query: Record<string, string>;
4
- user?: unknown;
5
- parsed?: Record<string, unknown>;
6
4
  mountPath?: string;
7
- theme?: {
8
- value: string;
9
- set?: (value: string, loc?: string) => Response;
10
- };
11
- flash?: {
12
- value: unknown;
13
- set: (data: unknown, location?: string) => Response;
14
- };
15
- i18n?: {
16
- locale: string;
17
- messages?: Record<string, unknown>;
18
- t: (key: string, params?: Record<string, string>, fallback?: string) => string;
19
- set?: (value: string, loc?: string) => Response;
20
- };
21
- env?: Record<string, string>;
22
5
  layoutStack?: {
23
6
  path: string;
24
7
  component: any;
@@ -28,3 +11,11 @@ export interface Context {
28
11
  export type Handler<T extends Context = Context> = (req: Request, ctx: T) => Response | Promise<Response>;
29
12
  export type Middleware<In extends Context = Context, Out extends In = In> = (req: Request, ctx: In, next: Handler<Out>) => Response | Promise<Response>;
30
13
  export type ErrorHandler<T extends Context = Context> = (error: Error, req: Request, ctx: T) => Response | Promise<Response>;
14
+ /**
15
+ * Interface for resources that require explicit cleanup (connections, pools, timers).
16
+ * All stateful modules implement this.
17
+ */
18
+ export interface Closeable {
19
+ /** Release all resources. Call once when shutting down. */
20
+ close(): Promise<void>;
21
+ }
package/dist/upload.d.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { Middleware } from './types.ts';
1
+ import type { Context, Middleware } from './types.ts';
2
+ declare module './types.ts' {
3
+ interface Context {
4
+ parsed: Record<string, unknown>;
5
+ }
6
+ }
2
7
  /** A parsed file from a multipart upload. */
3
8
  export interface UploadedFile {
4
9
  /** Original filename from the client. */
@@ -41,4 +46,6 @@ export interface UploadOptions {
41
46
  * })
42
47
  * ```
43
48
  */
44
- export declare function upload(options?: UploadOptions): Middleware;
49
+ export declare function upload(options?: UploadOptions): Middleware<Context, Context & {
50
+ parsed: Record<string, unknown>;
51
+ }>;
@@ -1,17 +1,25 @@
1
1
  import type { UserOptions, UserModule } from './types.ts';
2
2
  /**
3
3
  * User authentication module — local register/login, JWT verification, OAuth2 server, social login.
4
+ * Supports DB-less auth via tokens/verify/proxy options.
4
5
  *
5
6
  * ```ts
7
+ * // Full auth with DB
6
8
  * import { user, postgres } from 'weifuwu'
7
- *
8
9
  * const pg = postgres({ connection: DATABASE_URL })
9
10
  * const auth = user({ pg, jwtSecret: process.env.JWT_SECRET })
10
11
  *
11
12
  * await auth.migrate()
13
+ * app.use(auth.middleware()) // inject ctx.user
14
+ * app.use('/', auth) // /register, /login
15
+ *
16
+ * // DB-less token auth
17
+ * const auth = user({ tokens: ['sk-123', 'sk-456'] })
18
+ * app.use(auth.middleware()) // injects ctx.user for valid tokens
12
19
  *
13
- * app.use(auth.middleware()) // inject `ctx.user` on every request
14
- * app.use('/', auth) // mount auth routes: /register, /login
20
+ * // DB-less custom verify
21
+ * const auth = user({ verify: async (token) => validateToken(token) })
22
+ * app.use(auth.middleware())
15
23
  * ```
16
24
  */
17
25
  export declare function user(options: UserOptions): UserModule;
@@ -1,4 +1,4 @@
1
- import type { Middleware, Context } from '../types.ts';
1
+ import type { Middleware, Context, Closeable } from '../types.ts';
2
2
  import type { Router } from '../router.ts';
3
3
  import type { PostgresClient } from '../postgres/types.ts';
4
4
  /** A user record from the database. */
@@ -58,14 +58,29 @@ export interface OAuthProviderConfig {
58
58
  }
59
59
  /** Options for {@link user}. */
60
60
  export interface UserOptions {
61
- /** PostgreSQL client for user storage. */
62
- pg: PostgresClient;
63
- /** Secret key for JWT signing. */
64
- jwtSecret: string;
61
+ /** PostgreSQL client for user storage. Omit for DB-less token/verify/proxy auth. */
62
+ pg?: PostgresClient;
63
+ /** Secret key for JWT signing. Required for JWT auth and login/register routes. */
64
+ jwtSecret?: string;
65
65
  /** Custom table name for users (default: `'users'`). */
66
66
  table?: string;
67
67
  /** JWT expiration time (default: `'7d'`). */
68
68
  expiresIn?: string | number;
69
+ /** Static token(s) for simple bearer auth. No DB or JWT needed. */
70
+ tokens?: string[];
71
+ /** Custom verify function. Receives the token and request, returns user data or null. */
72
+ verify?: (token: string, req: Request) => unknown | Promise<unknown>;
73
+ /** Proxy auth — forward request to an external auth service for validation. */
74
+ proxy?: string | URL;
75
+ /** Custom header name for token extraction (default: `'Authorization'`). */
76
+ header?: string;
77
+ /**
78
+ * Function to load user data from a user ID stored in the session.
79
+ * Called when `ctx.session.userId` is present. Only used when `pg` is not provided.
80
+ * Return a falsy value to reject (e.g. if the user was deleted).
81
+ * If not provided and no pg, `ctx.user` is set to `{ id: userId }`.
82
+ */
83
+ resolveUser?: (userId: unknown) => unknown | Promise<unknown>;
69
84
  /** Enable OAuth2 server mode (authorization code flow). */
70
85
  oauth2?: OAuth2ServerOptions;
71
86
  /**
@@ -92,7 +107,7 @@ export interface UserInjected {
92
107
  * app.use('/', auth) // mounts routes: /register, /login
93
108
  * ```
94
109
  */
95
- export interface UserModule extends Router {
110
+ export interface UserModule extends Router, Closeable {
96
111
  /**
97
112
  * Strict auth middleware. Reads JWT from `Authorization: Bearer` header.
98
113
  * Returns 401 if no valid token is found.
@@ -1,5 +1,10 @@
1
1
  import type { ZodSchema } from 'zod';
2
2
  import type { Middleware } from './types.ts';
3
+ declare module './types.ts' {
4
+ interface Context {
5
+ parsed: Record<string, unknown>;
6
+ }
7
+ }
3
8
  export interface ValidationSchemas {
4
9
  body?: ZodSchema;
5
10
  query?: ZodSchema;
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.23.4",
3
+ "type": "module",
4
+ "version": "0.24.1",
4
5
  "description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
5
6
  "main": "./dist/index.js",
6
7
  "types": "./dist/index.d.ts",
@@ -24,7 +25,9 @@
24
25
  "start": "cd cli/template && node index.ts",
25
26
  "build": "esbuild index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --packages=external --define:__WFW_BUNDLED__=true && esbuild cli.ts --bundle --format=esm --platform=node --outfile=dist/cli.js --packages=external && esbuild react.ts --bundle --format=esm --outfile=dist/react.js --external:react --external:react-dom",
26
27
  "prepublishOnly": "npm run build && tsc --emitDeclarationOnly --outdir dist",
28
+ "typecheck": "tsc --noEmit",
27
29
  "test": "node --test 'test/**/*.test.ts'",
30
+ "test:coverage": "node --experimental-test-coverage --test 'test/**/*.test.ts'",
28
31
  "test:unit": "bash scripts/test-unit.sh",
29
32
  "test:ci": "node --test 'test/**/*.test.ts'"
30
33
  },
@@ -48,15 +51,12 @@
48
51
  "yaml": "^2.9.0",
49
52
  "zod": "^4.4.3"
50
53
  },
51
- "type": "module",
52
- "license": "MIT",
53
54
  "devDependencies": {
54
- "@types/jsonwebtoken": "^9.0.10",
55
- "@types/node": "^24.0.0",
56
- "@types/nodemailer": "^8.0.0",
57
- "@types/react": "^19",
58
- "@types/react-dom": "^19",
55
+ "@types/jsonwebtoken": "^9.0.9",
56
+ "@types/nodemailer": "^6.4.17",
59
57
  "@types/ws": "^8.18.1",
60
- "typescript": "^6.0.3"
58
+ "happy-dom": "^20.10.3",
59
+ "postcss": "^8.5.3",
60
+ "tailwindcss": "^4.0.0"
61
61
  }
62
62
  }