@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,49 +1,12 @@
1
1
  import { defineNuxtPlugin, onNuxtReady, useRuntimeConfig } from "#app";
2
+ import { hasWebMcp, registerTools } from "@wervt/client";
2
3
  // src/runtime/app/plugins/webmcp.client.ts
3
- function pageModelContext() {
4
- const context = document.modelContext ?? navigator.modelContext;
5
- return typeof context?.registerTool === "function" ? context : void 0;
6
- }
7
4
  var webmcp_client_default = defineNuxtPlugin({
8
5
  name: "wervt:webmcp",
9
6
  setup() {
10
- const context = pageModelContext();
11
- if (!context) return;
12
- const base = useRuntimeConfig().app.baseURL.replace(/\/$/, "");
13
- async function execute(name, input) {
14
- const res = await fetch(`${base}/_wervt/tools/${encodeURIComponent(name)}`, {
15
- method: "POST",
16
- headers: { "content-type": "application/json" },
17
- body: JSON.stringify(input ?? {})
18
- });
19
- const body = await res.json().catch(() => void 0);
20
- if (res.ok) return body?.result ?? null;
21
- const issues = body?.issues ?? body?.data;
22
- const message = (typeof body?.error === "string" ? body.error : body?.message) ?? `${res.status} ${res.statusText}`;
23
- throw new Error(issues ? `${message} ${JSON.stringify(issues)}` : message);
24
- }
25
- onNuxtReady(async () => {
26
- let tools;
27
- try {
28
- const res = await fetch(`${base}/_wervt/tools`);
29
- if (!res.ok) return;
30
- tools = await res.json();
31
- } catch {
32
- return;
33
- }
34
- for (const tool of tools) try {
35
- const result = context.registerTool({
36
- name: tool.name,
37
- description: tool.description,
38
- inputSchema: tool.inputSchema,
39
- ...tool.readOnly ? { annotations: { readOnlyHint: true } } : {},
40
- execute: (input) => execute(tool.name, input)
41
- });
42
- if (result instanceof Promise) result.catch((e) => console.warn(`wervt: could not register tool ${tool.name}`, e));
43
- } catch (e) {
44
- console.warn(`wervt: could not register tool ${tool.name}`, e);
45
- }
46
- });
7
+ if (!hasWebMcp()) return;
8
+ const base = useRuntimeConfig().app.baseURL;
9
+ onNuxtReady(() => registerTools({ base }));
47
10
  }
48
11
  });
49
12
  export { webmcp_client_default as default };
@@ -1,15 +1,2 @@
1
- import { Message, Offset, Row } from "@electric-sql/client";
2
- export interface ShapeSnapshot<T> {
3
- rows: [string, T][];
4
- offset: Offset;
5
- handle?: string;
6
- /**
7
- * The route answered 401: the shape needs a signed-in user and there is none, e.g. an anonymous
8
- * visitor of a public app, or the deploy's page check. No rows, and nothing to stream.
9
- */
10
- unauthorized?: true;
11
- }
12
- /** The rows of the shape at `url` (absolute) as of now, without subscribing. */
13
- export declare function loadSnapshot<T extends Row<unknown>>(url: string, fetchClient?: typeof fetch): Promise<ShapeSnapshot<T>>;
14
- /** Applies Electric change messages to `rows`. Returns whether anything changed. */
15
- export declare function applyMessages<T extends Row<unknown>>(rows: Map<string, T>, messages: Message<T>[]): boolean;
1
+ import { ShapeSnapshot, applyMessages, loadSnapshot } from "@wervt/client/shape";
2
+ export { type ShapeSnapshot, applyMessages, loadSnapshot };
@@ -1,63 +1 @@
1
- import { shapeParser } from "../shared/shape.js";
2
- import { ShapeStream, isChangeMessage, isControlMessage } from "@electric-sql/client";
3
- // src/runtime/app/snapshot.ts
4
- /** The rows of the shape at `url` (absolute) as of now, without subscribing. */
5
- async function loadSnapshot(url, fetchClient) {
6
- const rows = /* @__PURE__ */ new Map();
7
- let fail = () => {};
8
- const stream = new ShapeStream({
9
- url,
10
- subscribe: false,
11
- fetchClient,
12
- parser: shapeParser,
13
- onError: (e) => fail(e)
14
- });
15
- try {
16
- await new Promise((resolve, reject) => {
17
- fail = reject;
18
- stream.subscribe((messages) => {
19
- applyMessages(rows, messages);
20
- if (stream.isUpToDate) resolve();
21
- }, (e) => reject(e));
22
- });
23
- } catch (e) {
24
- if (e?.status === 401) return {
25
- rows: [],
26
- offset: "-1",
27
- unauthorized: true
28
- };
29
- throw e;
30
- } finally {
31
- stream.unsubscribeAll();
32
- }
33
- return {
34
- rows: [...rows],
35
- offset: stream.lastOffset,
36
- handle: stream.shapeHandle
37
- };
38
- }
39
- /** Applies Electric change messages to `rows`. Returns whether anything changed. */
40
- function applyMessages(rows, messages) {
41
- let changed = false;
42
- for (const message of messages) if (isChangeMessage(message)) {
43
- const { key, value } = message;
44
- switch (message.headers.operation) {
45
- case "insert":
46
- rows.set(key, value);
47
- break;
48
- case "update":
49
- rows.set(key, {
50
- ...rows.get(key),
51
- ...value
52
- });
53
- break;
54
- case "delete": rows.delete(key);
55
- }
56
- changed = true;
57
- } else if (isControlMessage(message) && message.headers.control === "must-refetch") {
58
- rows.clear();
59
- changed = true;
60
- }
61
- return changed;
62
- }
63
- export { applyMessages, loadSnapshot };
1
+ export { applyMessages, loadSnapshot } from "@wervt/client/shape";
@@ -1,5 +1,5 @@
1
- import { shapeParser } from "../shared/shape.js";
2
1
  import { applyMessages, loadSnapshot } from "./snapshot.js";
2
+ import { shapeParser } from "../shared/shape.js";
3
3
  import { onNuxtReady, useAsyncData, useRequestEvent, useRequestFetch } from "#app";
4
4
  import { getCurrentScope, onScopeDispose, ref, shallowRef } from "vue";
5
5
  import { ShapeStream } from "@electric-sql/client";
@@ -1,14 +1,3 @@
1
- interface KontorWidget {
2
- open(): void;
3
- close(): void;
4
- setContext(values: Record<string, unknown>): void;
5
- }
6
- declare global {
7
- interface Window {
8
- kontor?: KontorWidget;
9
- }
10
- }
11
- export declare const pendingFeedbackContext: Record<string, unknown>;
12
1
  /**
13
2
  * Controls the feedback widget (`wervt: { feedback: { key } }`): open it from your own button,
14
3
  * or add context to every report, e.g. what the user was looking at.
@@ -19,5 +8,4 @@ export declare function useWervtFeedback(): {
19
8
  open: () => void | undefined;
20
9
  close: () => void | undefined;
21
10
  setContext(values: Record<string, unknown>): void;
22
- };
23
- export {};
11
+ };
@@ -1,5 +1,5 @@
1
+ import { feedback } from "@wervt/client";
1
2
  // src/runtime/app/useWervtFeedback.ts
2
- const pendingFeedbackContext = {};
3
3
  /**
4
4
  * Controls the feedback widget (`wervt: { feedback: { key } }`): open it from your own button,
5
5
  * or add context to every report, e.g. what the user was looking at.
@@ -7,14 +7,6 @@ const pendingFeedbackContext = {};
7
7
  * @example useWervtFeedback().setContext({ room: room.id })
8
8
  */
9
9
  function useWervtFeedback() {
10
- const widget = () => import.meta.client ? window.kontor : void 0;
11
- return {
12
- open: () => widget()?.open(),
13
- close: () => widget()?.close(),
14
- setContext(values) {
15
- Object.assign(pendingFeedbackContext, values);
16
- widget()?.setContext(values);
17
- }
18
- };
10
+ return feedback;
19
11
  }
20
- export { pendingFeedbackContext, useWervtFeedback };
12
+ export { useWervtFeedback };
@@ -1,4 +1,5 @@
1
1
  import { useRuntimeConfig } from "#app";
2
+ import { track } from "@wervt/client";
2
3
  // src/runtime/app/useWervtTrack.ts
3
4
  /**
4
5
  * Custom events for wervt's analytics, e.g. `track('signup')` or `track('click', url)`. They
@@ -9,15 +10,10 @@ import { useRuntimeConfig } from "#app";
9
10
  function useWervtTrack() {
10
11
  const config = useRuntimeConfig();
11
12
  const enabled = config.public.wervt?.analytics;
12
- const base = config.app.baseURL.replace(/\/$/, "");
13
+ const base = config.app.baseURL;
13
14
  return (name, label) => {
14
- if (!enabled || import.meta.server || typeof navigator === "undefined") return;
15
- const body = JSON.stringify({
16
- path: location.pathname,
17
- name,
18
- label
19
- });
20
- navigator.sendBeacon?.(`${base}/_wervt/view`, new Blob([body], { type: "application/json" }));
15
+ if (!enabled || import.meta.server) return;
16
+ track(name, label, { base });
21
17
  };
22
18
  }
23
19
  export { useWervtTrack };
@@ -1,4 +1,5 @@
1
1
  import { WervtUser } from "../shared/user.js";
2
+ import { loginUrl, logoutUrl } from "@wervt/client";
2
3
  /**
3
4
  * The signed-in wervt user, or `null`. Read during SSR and kept in the payload; apps without
4
5
  * SSR load it from `/_wervt/user` before they render. Public apps get one when the visitor
@@ -6,6 +7,6 @@ import { WervtUser } from "../shared/user.js";
6
7
  */
7
8
  export declare function useWervtUser(): import("vue").Ref<WervtUser | null, WervtUser | null>;
8
9
  /** Sign in with wervt, then come back to `redirect` (a path in this app). */
9
- export declare const wervtLoginUrl: (redirect?: string) => string;
10
+ export declare const wervtLoginUrl: typeof loginUrl;
10
11
  /** Sign out of wervt (all apps), then come back to `redirect`. */
11
- export declare const wervtLogoutUrl: (redirect?: string) => string;
12
+ export declare const wervtLogoutUrl: typeof logoutUrl;
@@ -1,5 +1,6 @@
1
1
  import { DEV_LOGIN_COOKIE, devUser, parseWervtUser } from "../shared/user.js";
2
2
  import { useCookie, useRequestHeaders, useState } from "#app";
3
+ import { loginUrl, logoutUrl } from "@wervt/client";
3
4
  // src/runtime/app/useWervtUser.ts
4
5
  /**
5
6
  * The signed-in wervt user, or `null`. Read during SSR and kept in the payload; apps without
@@ -11,7 +12,7 @@ function useWervtUser() {
11
12
  return useState("wervt-user", () => import.meta.server ? parseWervtUser(useRequestHeaders(["x-wervt-user"])["x-wervt-user"]) ?? devUser(String(devLogin?.value) === "1") ?? null : null);
12
13
  }
13
14
  /** Sign in with wervt, then come back to `redirect` (a path in this app). */
14
- const wervtLoginUrl = (redirect = "/") => `/_wervt/login?redirect=${encodeURIComponent(redirect)}`;
15
+ const wervtLoginUrl = loginUrl;
15
16
  /** Sign out of wervt (all apps), then come back to `redirect`. */
16
- const wervtLogoutUrl = (redirect = "/") => `/_wervt/logout?redirect=${encodeURIComponent(redirect)}`;
17
+ const wervtLogoutUrl = logoutUrl;
17
18
  export { useWervtUser, wervtLoginUrl, wervtLogoutUrl };
@@ -1,25 +1,5 @@
1
+ import { WervtAccess, WervtAccess as WervtAccess$1, WervtInvite } from "@wervt/core";
1
2
  import { RequestEvent } from "nuxt/server";
2
- export interface WervtInvite {
3
- email: string;
4
- /** Who invited them. */
5
- by: string;
6
- /** ISO time. */
7
- at: string;
8
- }
9
- export interface WervtAccess {
10
- /** Lets `email` into the app. `invited` is false when they could already get in. */
11
- invite(email: string): Promise<{
12
- email: string;
13
- invited: boolean;
14
- }>;
15
- /** Takes back an invite (not the owner's allow list). */
16
- revoke(email: string): Promise<{
17
- email: string;
18
- revoked: boolean;
19
- }>;
20
- /** Everyone the app invited, with who invited them and when. */
21
- list(): Promise<WervtInvite[]>;
22
- }
23
3
  /**
24
4
  * Invites on an invite-only app (`wervt access <app> --invite`), as the user of this request: the
25
5
  * runtime vouches for them, and only someone with access can invite. Everyone with access may
@@ -30,4 +10,5 @@ export interface WervtAccess {
30
10
  * 409 when the app isn't invite-only, 429 past the limits (25 invites per person and day, 500 per
31
11
  * app).
32
12
  */
33
- export declare function useWervtAccess(event: RequestEvent): WervtAccess;
13
+ export declare function useWervtAccess(event: RequestEvent): WervtAccess$1;
14
+ export type { WervtAccess, WervtInvite };
@@ -1,4 +1,6 @@
1
1
  import { DEV_LOGIN_COOKIE, devUser } from "../shared/user.js";
2
+ import { withH3Errors } from "./errors.js";
3
+ import { appAccess, runtimeUrl } from "@wervt/core";
2
4
  import { createError, getCookie } from "nuxt/server";
3
5
  // src/runtime/server/access.ts
4
6
  const devInvites = [];
@@ -13,31 +15,12 @@ const devInvites = [];
13
15
  * app).
14
16
  */
15
17
  function useWervtAccess(event) {
16
- const runtime = process.env.WERVT_RUNTIME_URL;
17
- if (!runtime) return devAccess(event);
18
- async function call(method, body) {
19
- const res = await fetch(new URL("/_wervt/access", runtime), {
20
- method,
21
- headers: {
22
- "content-type": "application/json",
23
- "x-wervt-app": process.env.WERVT_APP ?? "",
24
- "x-wervt-token": process.env.WERVT_TOKEN ?? "",
25
- "x-wervt-user": event.req.headers.get("x-wervt-user") ?? "",
26
- "x-wervt-user-proof": event.req.headers.get("x-wervt-user-proof") ?? ""
27
- },
28
- body: body && JSON.stringify(body)
29
- });
30
- const data = await res.json().catch(() => ({}));
31
- if (!res.ok) throw createError({
32
- status: res.status,
33
- message: data.error ?? `access ${res.status}`
34
- });
35
- return data;
36
- }
18
+ if (!runtimeUrl()) return devAccess(event);
19
+ const access = appAccess(event.req);
37
20
  return {
38
- invite: (email) => call("POST", { email }),
39
- revoke: (email) => call("DELETE", { email }),
40
- list: async () => (await call("GET")).invited
21
+ invite: (email) => withH3Errors(access.invite(email)),
22
+ revoke: (email) => withH3Errors(access.revoke(email)),
23
+ list: () => withH3Errors(access.list())
41
24
  };
42
25
  }
43
26
  function devAccess(event) {
@@ -1,11 +1,6 @@
1
+ import { AnthropicOptions } from "@wervt/core/anthropic";
1
2
  import Anthropic from "@anthropic-ai/sdk";
2
- export interface UseAnthropicOptions {
3
- /**
4
- * The key for `nuxt dev`, where there is no workload identity. Default: `ANTHROPIC_API_KEY`.
5
- * Never used on wervt.
6
- */
7
- devApiKey?: string;
8
- }
3
+ export type UseAnthropicOptions = AnthropicOptions;
9
4
  /**
10
5
  * An Anthropic client that authenticates as this app through its wervt identity: no API key, only
11
6
  * IDs from the Anthropic Console in the app's env (`ANTHROPIC_FEDERATION_RULE_ID`,
@@ -1,10 +1,6 @@
1
- import { useWorkloadIdentity } from "./identity.js";
2
- import { createError } from "nuxt/server";
3
- import Anthropic from "@anthropic-ai/sdk";
4
- import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
1
+ import { toH3Error } from "./errors.js";
2
+ import { getAnthropic } from "@wervt/core/anthropic";
5
3
  // src/runtime/server/anthropic.ts
6
- const BASE_URL = "https://api.anthropic.com";
7
- let client;
8
4
  /**
9
5
  * An Anthropic client that authenticates as this app through its wervt identity: no API key, only
10
6
  * IDs from the Anthropic Console in the app's env (`ANTHROPIC_FEDERATION_RULE_ID`,
@@ -16,37 +12,10 @@ let client;
16
12
  * (`wervt identity status <app>`). In `nuxt dev` it uses `options.devApiKey` instead.
17
13
  */
18
14
  function useAnthropic(options = {}) {
19
- return client ??= create(options);
20
- }
21
- function create(options) {
22
- if (!process.env.WERVT_RUNTIME_URL) {
23
- const apiKey = options.devApiKey ?? process.env.ANTHROPIC_API_KEY;
24
- if (!apiKey) throw createError({
25
- status: 503,
26
- message: "useAnthropic() in `nuxt dev` needs an API key (ANTHROPIC_API_KEY or devApiKey)."
27
- });
28
- return new Anthropic({ apiKey });
15
+ try {
16
+ return getAnthropic(options);
17
+ } catch (e) {
18
+ throw toH3Error(e);
29
19
  }
30
- const federationRuleId = process.env.ANTHROPIC_FEDERATION_RULE_ID;
31
- const organizationId = process.env.ANTHROPIC_ORGANIZATION_ID;
32
- if (!federationRuleId || !organizationId) throw createError({
33
- status: 503,
34
- message: "useAnthropic() needs ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID (wervt env <app> --set …)."
35
- });
36
- const identity = useWorkloadIdentity();
37
- return new Anthropic({
38
- apiKey: null,
39
- authToken: null,
40
- baseURL: BASE_URL,
41
- credentials: oidcFederationProvider({
42
- identityTokenProvider: () => identity.token(BASE_URL),
43
- federationRuleId,
44
- organizationId,
45
- serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID || void 0,
46
- workspaceId: process.env.ANTHROPIC_WORKSPACE_ID || void 0,
47
- baseURL: BASE_URL,
48
- fetch: (...args) => fetch(...args)
49
- })
50
- });
51
20
  }
52
21
  export { useAnthropic };
@@ -1,17 +1,2 @@
1
- import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
- import { AnyRelations, EmptyRelations } from "drizzle-orm";
3
- /**
4
- * Drizzle client for the app's database. In production the wervt runtime injects
5
- * DATABASE_URL for the app's own role, whose search_path is its own schema, so
6
- * tables are declared without a schema.
7
- *
8
- * Pass the app's relations (from `defineRelations`) to use `db.query.*`. All clients
9
- * share one connection pool.
10
- *
11
- * @example
12
- * const db = useDb({ relations })
13
- * const room = await db.query.rooms.findFirst({ where: { id }, with: { players: true } })
14
- */
15
- export declare function useDb<TRelations extends AnyRelations = EmptyRelations>(options?: {
16
- relations?: TRelations;
17
- }): PostgresJsDatabase<TRelations>;
1
+ import { useDb } from "@wervt/core/db";
2
+ export { useDb };
@@ -1,35 +1 @@
1
- import { drizzle } from "drizzle-orm/postgres-js";
2
- import postgres from "postgres";
3
- // src/runtime/server/db.ts
4
- let client;
5
- const clients = /* @__PURE__ */ new Map();
6
- /**
7
- * Drizzle client for the app's database. In production the wervt runtime injects
8
- * DATABASE_URL for the app's own role, whose search_path is its own schema, so
9
- * tables are declared without a schema.
10
- *
11
- * Pass the app's relations (from `defineRelations`) to use `db.query.*`. All clients
12
- * share one connection pool.
13
- *
14
- * @example
15
- * const db = useDb({ relations })
16
- * const room = await db.query.rooms.findFirst({ where: { id }, with: { players: true } })
17
- */
18
- function useDb(options) {
19
- if (!process.env.DATABASE_URL) throw new Error("DATABASE_URL is not set");
20
- client ??= postgres(process.env.DATABASE_URL, {
21
- max: 2,
22
- idle_timeout: 20
23
- });
24
- const relations = options?.relations;
25
- let db = clients.get(relations);
26
- if (!db) {
27
- db = drizzle({
28
- client,
29
- relations
30
- });
31
- clients.set(relations, db);
32
- }
33
- return db;
34
- }
35
- export { useDb };
1
+ export { useDb } from "@wervt/core/db";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Turns @wervt/core's errors into h3's, which Nitro answers with their status. Any other error
3
+ * would become a 500 without its message.
4
+ */
5
+ export declare function toH3Error(error: unknown): unknown;
6
+ export declare function withH3Errors<T>(promise: Promise<T>): Promise<T>;
@@ -0,0 +1,21 @@
1
+ import { WervtError } from "@wervt/core";
2
+ import { createError } from "nuxt/server";
3
+ // src/runtime/server/errors.ts
4
+ /**
5
+ * Turns @wervt/core's errors into h3's, which Nitro answers with their status. Any other error
6
+ * would become a 500 without its message.
7
+ */
8
+ function toH3Error(error) {
9
+ return error instanceof WervtError ? createError({
10
+ status: error.status,
11
+ message: error.message
12
+ }) : error;
13
+ }
14
+ async function withH3Errors(promise) {
15
+ try {
16
+ return await promise;
17
+ } catch (e) {
18
+ throw toH3Error(e);
19
+ }
20
+ }
21
+ export { toH3Error, withH3Errors };
@@ -1,11 +1,4 @@
1
- export interface WorkloadIdentity {
2
- /**
3
- * A fresh JWT that says "I am this wervt app", for `audience` (a provider on wervt's list, e.g.
4
- * `https://api.anthropic.com`). Valid for 5 minutes and single-use: trade it for a provider
5
- * token right away. Each call signs a new one; providers' SDKs cache what they get for it.
6
- */
7
- token(audience: string): Promise<string>;
8
- }
1
+ import { WorkloadIdentity, WorkloadIdentity as WorkloadIdentity$1 } from "@wervt/core";
9
2
  /**
10
3
  * The app's workload identity: tokens signed by wervt's issuer, so the app can authenticate to an
11
4
  * AI provider (Anthropic's Workload Identity Federation) without an API key. Its owner turns it
@@ -15,4 +8,5 @@ export interface WorkloadIdentity {
15
8
  * doesn't sign for, 429 past 10 tokens a minute, 503 when the issuer is off. In `nuxt dev` there
16
9
  * is no issuer, so it always throws.
17
10
  */
18
- export declare function useWorkloadIdentity(): WorkloadIdentity;
11
+ export declare function useWorkloadIdentity(): WorkloadIdentity$1;
12
+ export type { WorkloadIdentity };
@@ -1,4 +1,5 @@
1
- import { createError } from "nuxt/server";
1
+ import { withH3Errors } from "./errors.js";
2
+ import { workloadIdentity } from "@wervt/core";
2
3
  // src/runtime/server/identity.ts
3
4
  /**
4
5
  * The app's workload identity: tokens signed by wervt's issuer, so the app can authenticate to an
@@ -10,27 +11,7 @@ import { createError } from "nuxt/server";
10
11
  * is no issuer, so it always throws.
11
12
  */
12
13
  function useWorkloadIdentity() {
13
- return { async token(audience) {
14
- const runtime = process.env.WERVT_RUNTIME_URL;
15
- if (!runtime) throw createError({
16
- status: 503,
17
- message: "Workload identity only exists on wervt, not in `nuxt dev`."
18
- });
19
- const res = await fetch(new URL("/_wervt/identity-token", runtime), {
20
- method: "POST",
21
- headers: {
22
- "content-type": "application/json",
23
- "x-wervt-app": process.env.WERVT_APP ?? "",
24
- "x-wervt-token": process.env.WERVT_TOKEN ?? ""
25
- },
26
- body: JSON.stringify({ audience })
27
- });
28
- const body = await res.json().catch(() => ({}));
29
- if (!res.ok || !body.token) throw createError({
30
- status: res.status,
31
- message: body.error ?? `identity token failed (${res.status})`
32
- });
33
- return body.token;
34
- } };
14
+ const identity = workloadIdentity();
15
+ return { token: (audience) => withH3Errors(identity.token(audience)) };
35
16
  }
36
17
  export { useWorkloadIdentity };
@@ -8,8 +8,8 @@ import { createEvlogError, log, useLogger } from "./logger.js";
8
8
  import { SendMailOptions, sendMail } from "./mail.js";
9
9
  import { McpTokenContext } from "./mcp-auth.js";
10
10
  import { seedTable } from "./seed.js";
11
- import { ShapeResponse } from "../shared/shape.js";
12
11
  import { ShapeOptions, ShapeRow, proxyShape } from "./shape.js";
12
+ import { ShapeResponse } from "../shared/shape.js";
13
13
  import { AccountMergedPayload, Task, TaskEvent, TaskResult, defineTask } from "./tasks.js";
14
14
  import { SafeResult, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, vh } from "./validate.js";
15
15
  import { Tool, ToolRoute, defineTool, defineToolRoute } from "./tools.js";
@@ -1,8 +1,8 @@
1
1
  import { wervtInstance } from "../shared/instance.js";
2
2
  import { renderEmail } from "../email/render.js";
3
3
  import { useWervtAccess } from "./access.js";
4
- import { useWorkloadIdentity } from "./identity.js";
5
4
  import { useDb } from "./db.js";
5
+ import { useWorkloadIdentity } from "./identity.js";
6
6
  import { createEvlogError, log, useLogger } from "./logger.js";
7
7
  import { sendMail } from "./mail.js";
8
8
  import { seedTable } from "./seed.js";
@@ -1,5 +1,6 @@
1
1
  import { renderEmail } from "../email/render.js";
2
- import { createError } from "nuxt/server";
2
+ import { withH3Errors } from "./errors.js";
3
+ import { sendMail as sendMail$1 } from "@wervt/core";
3
4
  // src/runtime/server/mail.ts
4
5
  /**
5
6
  * Renders a template and sends it through wervt. Throws on failure, with the status the platform
@@ -11,33 +12,14 @@ import { createError } from "nuxt/server";
11
12
  async function sendMail(options) {
12
13
  const { subject, html, text } = await renderEmail(options.template, options.props);
13
14
  const template = options.template.__name;
14
- const runtime = process.env.WERVT_RUNTIME_URL;
15
- if (!runtime) {
16
- console.info(`[mail] to ${options.to}: ${subject}\n${text}`);
17
- return { id: "dev" };
18
- }
19
- const res = await fetch(new URL("/_wervt/mail", runtime), {
20
- method: "POST",
21
- headers: {
22
- "content-type": "application/json",
23
- "x-wervt-app": process.env.WERVT_APP ?? "",
24
- "x-wervt-token": process.env.WERVT_TOKEN ?? ""
25
- },
26
- body: JSON.stringify({
27
- to: options.to,
28
- subject,
29
- html,
30
- text,
31
- replyTo: options.replyTo,
32
- fromName: options.fromName,
33
- template
34
- })
35
- });
36
- const body = await res.json().catch(() => ({}));
37
- if (!res.ok) throw createError({
38
- status: res.status,
39
- message: body.error ?? `sending mail failed (${res.status})`
40
- });
41
- return { id: body.id ?? "" };
15
+ return withH3Errors(sendMail$1({
16
+ to: options.to,
17
+ subject,
18
+ html,
19
+ text,
20
+ replyTo: options.replyTo,
21
+ fromName: options.fromName,
22
+ template
23
+ }));
42
24
  }
43
25
  export { sendMail };
@@ -1,3 +1,2 @@
1
- import { WervtUser } from "../shared/user.js";
2
- /** The wervt user an access token's claims name, or undefined when they're incomplete. */
3
- export declare function userFromClaims(claims: Record<string, unknown>): WervtUser | undefined;
1
+ import { userFromClaims } from "@wervt/core";
2
+ export { userFromClaims };
@@ -1,17 +1 @@
1
- // src/runtime/server/mcp-claims.ts
2
- /** The wervt user an access token's claims name, or undefined when they're incomplete. */
3
- function userFromClaims(claims) {
4
- const { sub, email, name, picture, accounts } = claims;
5
- if (typeof sub !== "string" || !sub || typeof email !== "string") return void 0;
6
- return {
7
- id: sub,
8
- email: email.toLowerCase(),
9
- name: typeof name === "string" ? name : void 0,
10
- image: typeof picture === "string" ? picture : void 0,
11
- accounts: isAccounts(accounts) ? accounts : void 0
12
- };
13
- }
14
- function isAccounts(value) {
15
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((id) => typeof id === "string");
16
- }
17
- export { userFromClaims };
1
+ export { userFromClaims } from "@wervt/core";