@wervt/nuxt 0.3.8 → 0.3.10

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 (59) hide show
  1. package/dist/brand.d.ts +18 -4
  2. package/dist/brand.js +37 -6
  3. package/dist/module.d.ts +3 -14
  4. package/dist/module.js +27 -59
  5. package/dist/provision.d.ts +1 -77
  6. package/dist/provision.js +1 -238
  7. package/dist/runtime/app/plugins/analytics.client.js +2 -2
  8. package/dist/runtime/app/plugins/feedback.client.js +11 -22
  9. package/dist/runtime/app/plugins/user.client.js +2 -7
  10. package/dist/runtime/app/plugins/webmcp.client.js +4 -41
  11. package/dist/runtime/app/snapshot.d.ts +2 -15
  12. package/dist/runtime/app/snapshot.js +1 -63
  13. package/dist/runtime/app/useShape.js +1 -1
  14. package/dist/runtime/app/useWervtFeedback.d.ts +1 -13
  15. package/dist/runtime/app/useWervtFeedback.js +3 -11
  16. package/dist/runtime/app/useWervtTrack.js +4 -8
  17. package/dist/runtime/app/useWervtUser.d.ts +3 -2
  18. package/dist/runtime/app/useWervtUser.js +3 -2
  19. package/dist/runtime/server/access.d.ts +3 -22
  20. package/dist/runtime/server/access.js +7 -24
  21. package/dist/runtime/server/anthropic.d.ts +2 -7
  22. package/dist/runtime/server/anthropic.js +6 -37
  23. package/dist/runtime/server/db.d.ts +2 -17
  24. package/dist/runtime/server/db.js +1 -35
  25. package/dist/runtime/server/errors.d.ts +6 -0
  26. package/dist/runtime/server/errors.js +21 -0
  27. package/dist/runtime/server/identity.d.ts +3 -9
  28. package/dist/runtime/server/identity.js +4 -23
  29. package/dist/runtime/server/index.d.ts +1 -1
  30. package/dist/runtime/server/index.js +1 -1
  31. package/dist/runtime/server/mail.js +11 -29
  32. package/dist/runtime/server/mcp-claims.d.ts +2 -3
  33. package/dist/runtime/server/mcp-claims.js +1 -17
  34. package/dist/runtime/server/seed.d.ts +2 -16
  35. package/dist/runtime/server/seed.js +1 -26
  36. package/dist/runtime/server/shape.d.ts +2 -2
  37. package/dist/runtime/server/shape.js +5 -45
  38. package/dist/runtime/server/task-handlers.d.ts +1 -5
  39. package/dist/runtime/server/task-handlers.js +3 -9
  40. package/dist/runtime/server/task-run-nitro2.d.ts +1 -5
  41. package/dist/runtime/server/task-run-nitro3.d.ts +1 -5
  42. package/dist/runtime/server/tasks.d.ts +2 -32
  43. package/dist/runtime/server/tasks.js +1 -9
  44. package/dist/runtime/server/tool-handlers.d.ts +1 -1
  45. package/dist/runtime/server/user-route.d.ts +1 -3
  46. package/dist/runtime/server/user.js +8 -11
  47. package/dist/runtime/shared/agent-token.d.ts +2 -7
  48. package/dist/runtime/shared/agent-token.js +1 -16
  49. package/dist/runtime/shared/shape.d.ts +2 -17
  50. package/dist/runtime/shared/shape.js +1 -15
  51. package/dist/runtime/shared/user.d.ts +3 -19
  52. package/dist/runtime/shared/user.js +1 -9
  53. package/package.json +5 -1
  54. package/dist/dev.d.ts +0 -31
  55. package/dist/dev.js +0 -158
  56. package/dist/jsdoc-imports.js +0 -25
  57. package/dist/seed.js +0 -51
  58. package/dist/storage.d.ts +0 -72
  59. package/dist/storage.js +0 -362
@@ -1,16 +1,2 @@
1
- import { InferInsertModel } from "drizzle-orm";
2
- import { PgTable } from "drizzle-orm/pg-core";
3
- /**
4
- * Inserts `rows` into `table` only while it is empty, so a seed task can run again without
5
- * duplicating data or touching what users created. Returns how many rows it inserted.
6
- *
7
- * @example
8
- * // server/tasks/db/seed.ts
9
- * export default defineTask({
10
- * meta: { description: 'Fill an empty database with example data' },
11
- * async run() {
12
- * return { result: { todos: await seedTable(todos, [{ title: 'Try wervt' }]) } }
13
- * },
14
- * })
15
- */
16
- export declare function seedTable<T extends PgTable>(table: T, rows: InferInsertModel<T>[]): Promise<number>;
1
+ import { seedTable } from "@wervt/core/db";
2
+ export { seedTable };
@@ -1,26 +1 @@
1
- import { useDb } from "./db.js";
2
- import { sql } from "drizzle-orm";
3
- // src/runtime/server/seed.ts
4
- /**
5
- * Inserts `rows` into `table` only while it is empty, so a seed task can run again without
6
- * duplicating data or touching what users created. Returns how many rows it inserted.
7
- *
8
- * @example
9
- * // server/tasks/db/seed.ts
10
- * export default defineTask({
11
- * meta: { description: 'Fill an empty database with example data' },
12
- * async run() {
13
- * return { result: { todos: await seedTable(todos, [{ title: 'Try wervt' }]) } }
14
- * },
15
- * })
16
- */
17
- async function seedTable(table, rows) {
18
- if (rows.length === 0) return 0;
19
- const db = useDb();
20
- const target = table;
21
- const [existing] = await db.select({ one: sql`1` }).from(target).limit(1);
22
- if (existing) return 0;
23
- await db.insert(target).values(rows);
24
- return rows.length;
25
- }
26
- export { seedTable };
1
+ export { seedTable } from "@wervt/core/db";
@@ -1,4 +1,4 @@
1
- import { ShapeResponse } from "../shared/shape.js";
1
+ import { ShapeResponse } from "@wervt/core";
2
2
  import { RequestEvent } from "nuxt/server";
3
3
  import { InferSelectModel, Table } from "drizzle-orm";
4
4
  export interface ShapeOptions<T extends Table | string = Table | string> {
@@ -21,4 +21,4 @@ export type ShapeRow<T extends Table | string> = T extends Table ? InferSelectMo
21
21
  * In production the request goes through the wervt runtime gateway, which pins the
22
22
  * table to this app's schema. In dev it goes to Electric directly (ELECTRIC_URL).
23
23
  */
24
- export declare function proxyShape<T extends Table | string>(event: RequestEvent, { table: tableOrName, where, params, columns }: ShapeOptions<T>): Promise<ShapeResponse<ShapeRow<T>>>;
24
+ export declare function proxyShape<T extends Table | string>(event: RequestEvent, { table, ...options }: ShapeOptions<T>): Promise<ShapeResponse<ShapeRow<T>>>;
@@ -1,56 +1,16 @@
1
+ import { proxyShape as proxyShape$1 } from "@wervt/core";
1
2
  import { getTableName, isTable } from "drizzle-orm";
2
3
  // src/runtime/server/shape.ts
3
- const CLIENT_PARAMS = [
4
- "offset",
5
- "handle",
6
- "live",
7
- "live_sse",
8
- "cursor",
9
- "replica",
10
- "log"
11
- ];
12
4
  /**
13
5
  * Serves an Electric shape from a route. Do auth checks before calling it.
14
6
  *
15
7
  * In production the request goes through the wervt runtime gateway, which pins the
16
8
  * table to this app's schema. In dev it goes to Electric directly (ELECTRIC_URL).
17
9
  */
18
- async function proxyShape(event, { table: tableOrName, where, params, columns }) {
19
- const table = isTable(tableOrName) ? getTableName(tableOrName) : tableOrName;
20
- const runtime = process.env.WERVT_RUNTIME_URL;
21
- const url = runtime ? new URL("/_wervt/electric/v1/shape", runtime) : new URL("/v1/shape", process.env.ELECTRIC_URL ?? "http://localhost:3000");
22
- const query = new URL(event.req.url).searchParams;
23
- for (const key of CLIENT_PARAMS) {
24
- const value = query.get(key);
25
- if (value !== null) url.searchParams.set(key, value);
26
- }
27
- url.searchParams.set("table", runtime ? table : devTable(table));
28
- if (where) url.searchParams.set("where", where);
29
- params?.forEach((value, i) => url.searchParams.set(`params[${i + 1}]`, String(value)));
30
- if (columns) url.searchParams.set("columns", columns.map(quoteIdentifier).join(","));
31
- if (!runtime && process.env.ELECTRIC_SECRET) url.searchParams.set("secret", process.env.ELECTRIC_SECRET);
32
- const headers = {};
33
- if (runtime) {
34
- headers["x-wervt-app"] = process.env.WERVT_APP ?? "";
35
- headers["x-wervt-token"] = process.env.WERVT_TOKEN ?? "";
36
- }
37
- const res = await fetch(url, {
38
- headers,
39
- signal: event.req.signal
10
+ function proxyShape(event, { table, ...options }) {
11
+ return proxyShape$1(event.req, {
12
+ ...options,
13
+ table: isTable(table) ? getTableName(table) : table
40
14
  });
41
- const out = new Headers(res.headers);
42
- out.delete("content-encoding");
43
- out.delete("content-length");
44
- return new Response(res.body, {
45
- status: res.status,
46
- headers: out
47
- });
48
- }
49
- function devTable(table) {
50
- const schema = process.env.WERVT_DB_SCHEMA;
51
- return schema ? `${schema}.${table}` : table;
52
- }
53
- function quoteIdentifier(name) {
54
- return /^[a-z_][a-z0-9_]*$/.test(name) ? name : `"${name.replaceAll("\"", "\"\"")}"`;
55
15
  }
56
16
  export { proxyShape };
@@ -10,12 +10,8 @@ type Registry = Record<string, {
10
10
  resolve?: () => Promise<unknown>;
11
11
  }>;
12
12
  /** `POST /_wervt/tasks/:name`: runs a task when the control plane asks. */
13
- export declare function taskRunHandler(runTask: RunTask): import("nuxt/server").EventHandler<Promise<{
13
+ export declare function taskRunHandler(runTask: RunTask): import("nuxt/server").EventHandler<Promise<Response | {
14
14
  error: string;
15
- result?: undefined;
16
- } | {
17
- error?: undefined;
18
- result: {} | null;
19
15
  }>>;
20
16
  /** `GET /_wervt/tasks`: the tasks with the descriptions from `defineTask({ meta })`. */
21
17
  export declare function taskListHandler(tasks: Registry): import("nuxt/server").EventHandler<Promise<{
@@ -1,7 +1,8 @@
1
+ import { isTaskRequest, taskRunResponse } from "@wervt/core";
1
2
  import { defineEventHandler } from "nuxt/server";
2
3
  // src/runtime/server/task-handlers.ts
3
4
  function trusted(event) {
4
- if (event.req.headers.get("x-wervt-task") === "1") return true;
5
+ if (isTaskRequest(event.req)) return true;
5
6
  event.res.status = 404;
6
7
  return false;
7
8
  }
@@ -11,14 +12,7 @@ function taskRunHandler(runTask) {
11
12
  if (!trusted(event)) return { error: "not found" };
12
13
  const name = decodeURIComponent(new URL(event.req.url).pathname.split("/").pop() ?? "");
13
14
  const body = await event.req.json().catch(() => ({}));
14
- event.res.headers.set("cache-control", "no-store");
15
- try {
16
- return { result: (await runTask(name, { payload: body.payload ?? {} })).result ?? null };
17
- } catch (e) {
18
- console.error(`Task ${name} failed:`, e);
19
- event.res.status = e.status ?? 500;
20
- return { error: e instanceof Error ? e.message : String(e) };
21
- }
15
+ return taskRunResponse(name, () => runTask(name, { payload: body.payload ?? {} }));
22
16
  });
23
17
  }
24
18
  /** `GET /_wervt/tasks`: the tasks with the descriptions from `defineTask({ meta })`. */
@@ -1,8 +1,4 @@
1
- declare const _default: import("nuxt/server").EventHandler<Promise<{
1
+ declare const _default: import("nuxt/server").EventHandler<Promise<Response | {
2
2
  error: string;
3
- result?: undefined;
4
- } | {
5
- error?: undefined;
6
- result: {} | null;
7
3
  }>>;
8
4
  export { _default as default };
@@ -1,8 +1,4 @@
1
- declare const _default: import("nuxt/server").EventHandler<Promise<{
1
+ declare const _default: import("nuxt/server").EventHandler<Promise<Response | {
2
2
  error: string;
3
- result?: undefined;
4
- } | {
5
- error?: undefined;
6
- result: {} | null;
7
3
  }>>;
8
4
  export { _default as default };
@@ -1,32 +1,2 @@
1
- export interface TaskEvent<P = Record<string, unknown>> {
2
- name: string;
3
- /** `{ scheduledTime }` for scheduled runs, or what was passed to a manual run. */
4
- payload: P;
5
- context: Record<string, unknown>;
6
- }
7
- /**
8
- * Payload of the `wervt:account-merged` task: one person had two wervt accounts and an admin
9
- * merged `from` into `into`. `from` no longer exists; move its rows over to `into`.
10
- */
11
- export interface AccountMergedPayload {
12
- from: {
13
- id: string;
14
- email: string;
15
- };
16
- into: {
17
- id: string;
18
- email: string;
19
- };
20
- }
21
- export interface TaskResult<R = unknown> {
22
- result?: R;
23
- }
24
- export interface Task<R = unknown, P = Record<string, unknown>> {
25
- meta?: {
26
- name?: string;
27
- description?: string;
28
- };
29
- run(event: TaskEvent<P>): TaskResult<R> | Promise<TaskResult<R>>;
30
- }
31
- /** A task in `server/tasks/` (`server/tasks/presence/sweep.ts` is `presence:sweep`). */
32
- export declare function defineTask<R = unknown, P = Record<string, unknown>>(task: Task<R, P>): Task<R, P>;
1
+ import { AccountMergedPayload, Task, TaskEvent, TaskResult, defineTask } from "@wervt/core";
2
+ export { type AccountMergedPayload, type Task, type TaskEvent, type TaskResult, defineTask };
@@ -1,9 +1 @@
1
- // src/runtime/server/tasks.ts
2
- /** A task in `server/tasks/` (`server/tasks/presence/sweep.ts` is `presence:sweep`). */
3
- function defineTask(task) {
4
- if (typeof task.run !== "function") task.run = () => {
5
- throw new TypeError("Task must implement a `run` method!");
6
- };
7
- return task;
8
- }
9
- export { defineTask };
1
+ export { defineTask } from "@wervt/core";
@@ -36,7 +36,7 @@ export declare const runTool: import("nuxt/server").EventHandler<Promise<{
36
36
  };
37
37
  result?: undefined;
38
38
  } | {
39
+ error?: undefined;
39
40
  issues?: undefined;
40
41
  result: {} | null;
41
- error?: undefined;
42
42
  }>>;
@@ -1,6 +1,4 @@
1
- import { WervtUser } from "../shared/user.js";
2
- import "./user.js";
3
1
  declare const _default: import("nuxt/server").EventHandler<{
4
- user: WervtUser | null;
2
+ user: import("@wervt/core").WervtUser | null;
5
3
  }>;
6
4
  export { _default as default };
@@ -1,5 +1,7 @@
1
1
  import { DEV_LOGIN_COOKIE, devUser, parseWervtUser } from "../shared/user.js";
2
- import { createError, getCookie } from "nuxt/server";
2
+ import { toH3Error } from "./errors.js";
3
+ import { checkRole } from "@wervt/core";
4
+ import { getCookie } from "nuxt/server";
3
5
  // src/runtime/server/user.ts
4
6
  const MCP_USER = "wervtMcpUser";
5
7
  /**
@@ -23,15 +25,10 @@ function useWervtUser(event) {
23
25
  * Throws 401 without a signed-in user, 403 with another role.
24
26
  */
25
27
  function requireWervtRole(event, ...roles) {
26
- const user = useWervtUser(event);
27
- if (!user) throw createError({
28
- status: 401,
29
- message: "sign in at /_wervt/login first"
30
- });
31
- if (!user.role || !roles.includes(user.role)) throw createError({
32
- status: 403,
33
- message: `needs role ${roles.join(", ")}`
34
- });
35
- return user;
28
+ try {
29
+ return checkRole(useWervtUser(event), roles);
30
+ } catch (e) {
31
+ throw toH3Error(e);
32
+ }
36
33
  }
37
34
  export { DEV_LOGIN_COOKIE, parseWervtUser, requireWervtRole, useWervtUser };
@@ -1,7 +1,2 @@
1
- /** Who minted the agent token a request came with, and its id (the control plane's log). */
2
- export interface AgentToken {
3
- id: string;
4
- by: string;
5
- }
6
- /** Parses the `x-wervt-agent-token` header the runtime sets (and strips from client requests). */
7
- export declare function parseAgentToken(header: string | null | undefined): AgentToken | undefined;
1
+ import { AgentToken, parseAgentToken } from "@wervt/core";
2
+ export { type AgentToken, parseAgentToken };
@@ -1,16 +1 @@
1
- // src/runtime/shared/agent-token.ts
2
- /** Parses the `x-wervt-agent-token` header the runtime sets (and strips from client requests). */
3
- function parseAgentToken(header) {
4
- if (!header) return void 0;
5
- try {
6
- const value = JSON.parse(decodeURIComponent(header));
7
- if (typeof value?.id !== "string" || typeof value.by !== "string") return void 0;
8
- return {
9
- id: value.id,
10
- by: value.by
11
- };
12
- } catch {
13
- return;
14
- }
15
- }
16
- export { parseAgentToken };
1
+ export { parseAgentToken } from "@wervt/core";
@@ -1,17 +1,2 @@
1
- declare const shapeRow: unique symbol;
2
- export type ShapeResponse<Row> = Response & {
3
- readonly [shapeRow]?: Row;
4
- };
5
- /** The row type of a shape route's handler, `never` if it doesn't serve a typed shape. */
6
- export type ShapeRowOf<Handler> = Handler extends ((...args: never[]) => infer Result) ? Awaited<Result> extends ShapeResponse<infer Row> ? Row : never : never;
7
- /**
8
- * Postgres text of a `timestamp`/`timestamptz` as a Date, like Drizzle's default `mode: 'date'`
9
- * reads it. Drizzle stores `timestamp` without time zone as UTC, so it's read as UTC too.
10
- */
11
- export declare function parseTimestamp(value: string): Date;
12
- /** Electric parsers for the types Drizzle reads differently from Electric's defaults. */
13
- export declare const shapeParser: {
14
- timestamp: typeof parseTimestamp;
15
- timestamptz: typeof parseTimestamp;
16
- };
17
- export {};
1
+ import { ShapeResponse, ShapeRowOf, parseTimestamp, shapeParser } from "@wervt/core";
2
+ export { type ShapeResponse, type ShapeRowOf, parseTimestamp, shapeParser };
@@ -1,15 +1 @@
1
- // src/runtime/shared/shape.ts
2
- /**
3
- * Postgres text of a `timestamp`/`timestamptz` as a Date, like Drizzle's default `mode: 'date'`
4
- * reads it. Drizzle stores `timestamp` without time zone as UTC, so it's read as UTC too.
5
- */
6
- function parseTimestamp(value) {
7
- const iso = value.replace(" ", "T").replace(/([+-]\d{2})$/, "$1:00");
8
- return new Date(/(Z|[+-]\d{2}:\d{2})$/.test(iso) ? iso : `${iso}Z`);
9
- }
10
- /** Electric parsers for the types Drizzle reads differently from Electric's defaults. */
11
- const shapeParser = {
12
- timestamp: parseTimestamp,
13
- timestamptz: parseTimestamp
14
- };
15
- export { parseTimestamp, shapeParser };
1
+ export { parseTimestamp, shapeParser } from "@wervt/core";
@@ -1,22 +1,6 @@
1
- /**
2
- * What the user is to this app: its `owner` or one of its `member`s (`wervt members`), a wervt
3
- * `admin`, someone `invited` through the app (invite-only apps) or `allowed` by the app's list
4
- * (`wervt access --add`). `public` is anyone else signed in to a public app.
5
- */
6
- export type WervtRole = 'owner' | 'member' | 'admin' | 'invited' | 'allowed' | 'public';
7
- export interface WervtUser {
8
- id: string;
9
- email: string;
10
- name?: string;
11
- image?: string;
12
- /** Linked login accounts, provider id to the user's id there, e.g. `{ twitch: '1234' }`. */
13
- accounts?: Record<string, string>;
14
- /** Missing only for users an app's own `/mcp` tokens stand for (`wervt:mcp:token`). */
15
- role?: WervtRole;
16
- }
1
+ import { WervtRole, WervtUser, WervtUser as WervtUser$1, parseWervtUser } from "@wervt/core";
17
2
  /** Cookie set by the dev stand-in for `/_wervt/login` (see `server/dev-auth.ts`). */
18
3
  export declare const DEV_LOGIN_COOKIE = "wervt_dev_login";
19
- /** Parses the `x-wervt-user` header the runtime sets (and strips from client requests). */
20
- export declare function parseWervtUser(header: string | null | undefined): WervtUser | undefined;
21
4
  /** During `nuxt dev`: the configured dev user, after `/_wervt/login`. */
22
- export declare function devUser(loggedIn: boolean): WervtUser | undefined;
5
+ export declare function devUser(loggedIn: boolean): WervtUser$1 | undefined;
6
+ export { type WervtRole, type WervtUser, parseWervtUser };
@@ -1,15 +1,7 @@
1
+ import { parseWervtUser } from "@wervt/core";
1
2
  // src/runtime/shared/user.ts
2
3
  /** Cookie set by the dev stand-in for `/_wervt/login` (see `server/dev-auth.ts`). */
3
4
  const DEV_LOGIN_COOKIE = "wervt_dev_login";
4
- /** Parses the `x-wervt-user` header the runtime sets (and strips from client requests). */
5
- function parseWervtUser(header) {
6
- if (!header) return void 0;
7
- try {
8
- return JSON.parse(decodeURIComponent(header));
9
- } catch {
10
- return;
11
- }
12
- }
13
5
  /** During `nuxt dev`: the configured dev user, after `/_wervt/login`. */
14
6
  function devUser(loggedIn) {
15
7
  const user = process.env.WERVT_DEV_USER;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wervt/nuxt",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "Nuxt module for apps hosted on wervt: database, live data, MCP and runtime setup.",
5
5
  "keywords": [
6
6
  "drizzle",
@@ -49,6 +49,10 @@
49
49
  "dependencies": {
50
50
  "@electric-sql/client": "^1.5.28",
51
51
  "@valibot/to-json-schema": "^1.8.0",
52
+ "@wervt/client": "^0.0.2",
53
+ "@wervt/core": "^0.0.2",
54
+ "@wervt/nitro": "^0.0.2",
55
+ "@wervt/platform": "^0.0.2",
52
56
  "aws4fetch": "^1.0.20",
53
57
  "evlog": "^2.34.0",
54
58
  "h3-mcp": "0.2.0",
package/dist/dev.d.ts DELETED
@@ -1,31 +0,0 @@
1
- import { WervtRole } from "./runtime/shared/user.js";
2
- import "nuxt/schema";
3
- export interface DevOptions {
4
- /** Superuser URL of the local wervt Postgres. */
5
- adminUrl?: string;
6
- /** WERVT_DB_SECRET of the local setup. */
7
- dbSecret?: string;
8
- electricUrl?: string;
9
- electricSecret?: string;
10
- /** The local Garage, for apps with ablage: S3 API and admin API. */
11
- s3Url?: string;
12
- garageAdminUrl?: string;
13
- garageAdminToken?: string;
14
- /**
15
- * Runs the app's `db:seed` task (`server/tasks/db/seed.ts`) once its database was just created
16
- * and migrated. Defaults to true; `false` leaves seeding to `nuxt task run db:seed`.
17
- */
18
- seed?: boolean;
19
- /**
20
- * Who `/_wervt/login` signs in as during `nuxt dev` (there's no wervt auth locally).
21
- * Defaults to a dev user with a Twitch account linked. Without `role`, they're the app's owner.
22
- */
23
- user?: {
24
- id: string;
25
- email: string;
26
- name?: string;
27
- image?: string;
28
- accounts?: Record<string, string>;
29
- role?: WervtRole;
30
- };
31
- }
package/dist/dev.js DELETED
@@ -1,158 +0,0 @@
1
- import { createStorage } from "./storage.js";
2
- import { appDatabaseUrl, dbRole, migrateAs, provisionDatabase, storageCredentials } from "./provision.js";
3
- import { SEED_TASK, runDevSeed } from "./seed.js";
4
- import { useLogger } from "nuxt/kit";
5
- import { existsSync, watch } from "node:fs";
6
- import { readFile } from "node:fs/promises";
7
- import { join } from "node:path";
8
- // src/dev.ts
9
- const DEFAULTS = {
10
- adminUrl: "postgres://postgres:postgres@localhost:5432/wervt",
11
- dbSecret: "dev-secret",
12
- electricUrl: "http://localhost:3030",
13
- electricSecret: "dev-electric-secret",
14
- s3Url: "http://localhost:3900",
15
- garageAdminUrl: "http://localhost:3903",
16
- garageAdminToken: "dev-garage-admin"
17
- };
18
- const logger = useLogger("wervt");
19
- /**
20
- * `nuxt dev` against the local compose stack: provisions the app's role and schema,
21
- * runs its migrations (again whenever `drizzle/` changes), seeds a new database and
22
- * points the dev server at the app's database and Electric.
23
- */
24
- async function setupDev(nuxt, app, options, features = {}) {
25
- forgetOwnEnv(nuxt.options.rootDir);
26
- const config = {
27
- adminUrl: process.env.WERVT_PG_ADMIN_URL ?? options.adminUrl ?? DEFAULTS.adminUrl,
28
- dbSecret: process.env.WERVT_DB_SECRET ?? options.dbSecret ?? DEFAULTS.dbSecret,
29
- electricUrl: process.env.ELECTRIC_URL ?? options.electricUrl ?? DEFAULTS.electricUrl,
30
- electricSecret: process.env.ELECTRIC_SECRET ?? options.electricSecret ?? DEFAULTS.electricSecret
31
- };
32
- process.env.WERVT_DEV_USER ??= JSON.stringify(options.user ?? {
33
- id: "dev",
34
- email: "dev@wervt.localhost",
35
- name: "Dev",
36
- accounts: { twitch: "dev" }
37
- });
38
- if (features.storage) await setupStorage(app, config.dbSecret, options);
39
- const migrationsFolder = join(nuxt.options.rootDir, "drizzle");
40
- if (!existsSync(migrationsFolder)) {
41
- const watcher = watch(nuxt.options.rootDir, (_event, file) => {
42
- if (file !== "drizzle" || !existsSync(migrationsFolder)) return;
43
- watcher.close();
44
- logger.info("Migrations added, restarting…");
45
- nuxt.callHook("restart");
46
- });
47
- nuxt.hook("close", () => watcher.close());
48
- return;
49
- }
50
- const provision = {
51
- app,
52
- adminUrl: config.adminUrl,
53
- secret: config.dbSecret,
54
- migrationsFolder
55
- };
56
- const given = process.env.DATABASE_URL && process.env.WERVT_DB_SCHEMA ? {
57
- url: process.env.DATABASE_URL,
58
- schema: process.env.WERVT_DB_SCHEMA
59
- } : void 0;
60
- const database = given?.schema ?? dbRole(app);
61
- let serverUrl;
62
- let seedPending = false;
63
- const seed = async () => {
64
- if (!seedPending || !serverUrl) return;
65
- seedPending = false;
66
- try {
67
- if ((await runDevSeed(serverUrl)).status === "seeded") logger.success(`Seeded \`${database}\` (${SEED_TASK})`);
68
- } catch (e) {
69
- logger.warn(`\`${SEED_TASK}\` failed:`, e instanceof Error ? e.message : e);
70
- }
71
- };
72
- if (options.seed !== false) nuxt.hook("listen", (_server, listener) => {
73
- serverUrl = listener.url;
74
- seed();
75
- });
76
- const migrate = async () => {
77
- try {
78
- const { fresh } = given ? await migrateAs({
79
- ...given,
80
- migrationsFolder
81
- }) : await provisionDatabase(provision);
82
- logger.success(`Database \`${database}\` is up to date`);
83
- if (fresh && options.seed !== false) {
84
- seedPending = true;
85
- seed();
86
- }
87
- } catch (e) {
88
- logger.warn(given ? `Could not migrate \`${database}\`.` : `Could not migrate \`${database}\`. Is the wervt stack running (docker compose up -d)?`, e instanceof Error ? e.message : e);
89
- }
90
- };
91
- await migrate();
92
- const own = {
93
- DATABASE_URL: await appDatabaseUrl(provision),
94
- WERVT_DB_SCHEMA: dbRole(app),
95
- ELECTRIC_URL: config.electricUrl,
96
- ELECTRIC_SECRET: config.electricSecret
97
- };
98
- const set = Object.fromEntries(Object.entries(own).filter(([key]) => process.env[key] === void 0));
99
- Object.assign(process.env, set);
100
- process.env[OWN_ENV] = JSON.stringify(set);
101
- let timer;
102
- const watcher = watch(migrationsFolder, { recursive: true }, (_event, file) => {
103
- if (!file?.endsWith(".sql")) return;
104
- clearTimeout(timer);
105
- timer = setTimeout(() => {
106
- logger.info("Migrations changed, migrating…");
107
- migrate();
108
- }, 300);
109
- });
110
- nuxt.hook("close", () => watcher.close());
111
- }
112
- const OWN_ENV = "WERVT_DEV_OWN_ENV";
113
- function forgetOwnEnv(rootDir) {
114
- const own = process.env[OWN_ENV];
115
- if (!own) return;
116
- for (const [key, value] of Object.entries(JSON.parse(own))) if (process.env[key] === value) delete process.env[key];
117
- delete process.env[OWN_ENV];
118
- const file = join(rootDir, ".env");
119
- if (existsSync(file)) process.loadEnvFile(file);
120
- }
121
- /** The app's bucket in the local Garage, and WERVT_S3_* for ablage, like in production. */
122
- async function setupStorage(app, secret, options) {
123
- const s3Url = process.env.WERVT_S3_URL ?? options.s3Url ?? DEFAULTS.s3Url;
124
- const storage = createStorage({
125
- adminUrl: options.garageAdminUrl ?? DEFAULTS.garageAdminUrl,
126
- adminToken: process.env.WERVT_GARAGE_ADMIN_TOKEN ?? options.garageAdminToken ?? DEFAULTS.garageAdminToken,
127
- s3Url,
128
- secret
129
- });
130
- try {
131
- await storage.ensureLayout();
132
- const { bucket } = await storage.provision(app, "*");
133
- logger.success(`Storage bucket \`${bucket}\` is ready`);
134
- } catch (e) {
135
- return logger.warn("Could not set up storage. Is the wervt stack running (docker compose up -d)?", e instanceof Error ? e.message : e);
136
- }
137
- const creds = await storageCredentials(app, secret);
138
- process.env.WERVT_S3_URL ??= s3Url;
139
- process.env.WERVT_S3_PUBLIC_URL ??= s3Url;
140
- process.env.WERVT_S3_REGION ??= "garage";
141
- process.env.WERVT_S3_BUCKET ??= creds.bucket;
142
- process.env.WERVT_S3_ACCESS_KEY_ID ??= creds.accessKeyId;
143
- process.env.WERVT_S3_SECRET_ACCESS_KEY ??= creds.secretAccessKey;
144
- }
145
- /**
146
- * App name, normalised to `[a-z0-9-]+`: `WERVT_APP` first, which `wervt deploy` sets for its
147
- * build, so one codebase deployed as several apps (`--name`) builds each under its own name.
148
- * Then the module option, then package.json.
149
- */
150
- async function resolveAppName(rootDir, name) {
151
- name = process.env.WERVT_APP || name;
152
- if (!name) {
153
- const pkg = JSON.parse(await readFile(join(rootDir, "package.json"), "utf8").catch(() => "{}"));
154
- name = String(pkg.name ?? "app").replace(/^@[^/]+\//, "");
155
- }
156
- return name.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
157
- }
158
- export { resolveAppName, setupDev };
@@ -1,25 +0,0 @@
1
- // src/jsdoc-imports.ts
2
- /**
3
- * Deno's module graph reads JSDoc type imports (`{@link import('../internal/headers').x}`) as
4
- * real dependencies and refuses to boot when one can't be resolved. Packages ship such paths
5
- * relative to their own source tree (@anthropic-ai/sdk does), so once Nitro bundles them into
6
- * `server/_libs/…` the path points nowhere and the worker fails with "Module not found".
7
- * Dropping the `import(` from JSDoc lines keeps the comment readable and out of the graph. Only
8
- * lines that open a JSDoc block or continue one with `*` are touched, so code never changes.
9
- */
10
- function stripJsdocImports() {
11
- const JSDOC_LINE_RE = /^[ \t]*(?:\/\*\*|\*(?!\/)).*$/gm;
12
- const IMPORT_RE = /\bimport\(\s*(['"])/g;
13
- return {
14
- name: "wervt:strip-jsdoc-imports",
15
- renderChunk(code) {
16
- if (!code.includes("import(")) return null;
17
- const out = code.replace(JSDOC_LINE_RE, (line) => line.replace(IMPORT_RE, "$1"));
18
- return out === code ? null : {
19
- code: out,
20
- map: null
21
- };
22
- }
23
- };
24
- }
25
- export { stripJsdocImports };