@wervt/nuxt 0.3.9 → 0.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brand.d.ts +16 -3
- package/dist/brand.js +22 -6
- package/dist/module.d.ts +3 -14
- package/dist/module.js +24 -59
- package/dist/provision.d.ts +1 -77
- package/dist/provision.js +1 -238
- package/dist/runtime/app/plugins/analytics.client.js +2 -2
- package/dist/runtime/app/plugins/feedback.client.js +11 -22
- package/dist/runtime/app/plugins/user.client.js +2 -7
- package/dist/runtime/app/plugins/webmcp.client.js +4 -41
- package/dist/runtime/app/snapshot.d.ts +2 -15
- package/dist/runtime/app/snapshot.js +1 -63
- package/dist/runtime/app/useShape.js +1 -1
- package/dist/runtime/app/useWervtFeedback.d.ts +1 -13
- package/dist/runtime/app/useWervtFeedback.js +3 -11
- package/dist/runtime/app/useWervtTrack.js +4 -8
- package/dist/runtime/app/useWervtUser.d.ts +3 -2
- package/dist/runtime/app/useWervtUser.js +3 -2
- package/dist/runtime/email/components.d.ts +18 -0
- package/dist/runtime/email/components.js +15 -4
- package/dist/runtime/email/invite.d.ts +13 -0
- package/dist/runtime/email/invite.js +30 -14
- package/dist/runtime/i18n.d.ts +3 -0
- package/dist/runtime/i18n.js +3 -0
- package/dist/runtime/server/access.d.ts +3 -22
- package/dist/runtime/server/access.js +7 -24
- package/dist/runtime/server/anthropic.d.ts +2 -7
- package/dist/runtime/server/anthropic.js +6 -37
- package/dist/runtime/server/db.d.ts +2 -17
- package/dist/runtime/server/db.js +1 -35
- package/dist/runtime/server/errors.d.ts +6 -0
- package/dist/runtime/server/errors.js +21 -0
- package/dist/runtime/server/identity.d.ts +3 -9
- package/dist/runtime/server/identity.js +4 -23
- package/dist/runtime/server/index.d.ts +1 -1
- package/dist/runtime/server/index.js +1 -1
- package/dist/runtime/server/mail.js +11 -29
- package/dist/runtime/server/mcp-claims.d.ts +2 -3
- package/dist/runtime/server/mcp-claims.js +1 -17
- package/dist/runtime/server/seed.d.ts +2 -16
- package/dist/runtime/server/seed.js +1 -26
- package/dist/runtime/server/shape.d.ts +2 -2
- package/dist/runtime/server/shape.js +5 -45
- package/dist/runtime/server/task-handlers.d.ts +1 -5
- package/dist/runtime/server/task-handlers.js +3 -9
- package/dist/runtime/server/task-run-nitro2.d.ts +1 -5
- package/dist/runtime/server/task-run-nitro3.d.ts +1 -5
- package/dist/runtime/server/tasks.d.ts +2 -32
- package/dist/runtime/server/tasks.js +1 -9
- package/dist/runtime/server/user-route.d.ts +1 -3
- package/dist/runtime/server/user.js +8 -11
- package/dist/runtime/shared/agent-token.d.ts +2 -7
- package/dist/runtime/shared/agent-token.js +1 -16
- package/dist/runtime/shared/i18n.d.ts +28 -0
- package/dist/runtime/shared/i18n.js +54 -0
- package/dist/runtime/shared/messages.d.ts +15 -0
- package/dist/runtime/shared/messages.js +33 -0
- package/dist/runtime/shared/shape.d.ts +2 -17
- package/dist/runtime/shared/shape.js +1 -15
- package/dist/runtime/shared/user.d.ts +3 -19
- package/dist/runtime/shared/user.js +1 -9
- package/package.json +9 -1
- package/dist/dev.d.ts +0 -31
- package/dist/dev.js +0 -158
- package/dist/jsdoc-imports.js +0 -25
- package/dist/seed.js +0 -51
- package/dist/storage.d.ts +0 -72
- package/dist/storage.js +0 -362
|
@@ -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
|
-
|
|
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 {
|
|
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
|
-
|
|
14
|
-
|
|
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 {
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 {
|
|
2
|
-
|
|
3
|
-
export declare function userFromClaims(claims: Record<string, unknown>): WervtUser | undefined;
|
|
1
|
+
import { userFromClaims } from "@wervt/core";
|
|
2
|
+
export { userFromClaims };
|
|
@@ -1,17 +1 @@
|
|
|
1
|
-
|
|
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";
|
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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<{
|
|
14
|
-
result?: undefined;
|
|
13
|
+
export declare function taskRunHandler(runTask: RunTask): import("nuxt/server").EventHandler<Promise<Response | {
|
|
15
14
|
error: string;
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { DEV_LOGIN_COOKIE, devUser, parseWervtUser } from "../shared/user.js";
|
|
2
|
-
import {
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
2
|
-
export
|
|
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
|
-
|
|
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";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const LOCALES: readonly ['en', 'de'];
|
|
2
|
+
export type Locale = (typeof LOCALES)[number];
|
|
3
|
+
export declare const DEFAULT_LOCALE: Locale;
|
|
4
|
+
export declare const isLocale: (value: unknown) => value is Locale;
|
|
5
|
+
/**
|
|
6
|
+
* The best supported language for an `Accept-Language` header: the highest-ranked one there is a
|
|
7
|
+
* catalog for, English when there's none.
|
|
8
|
+
*/
|
|
9
|
+
export declare function negotiateLocale(header: string | null | undefined): Locale;
|
|
10
|
+
/** One locale's texts, with the keys of the English catalog. */
|
|
11
|
+
export type Catalog<M> = { readonly [K in keyof M]: string; };
|
|
12
|
+
/** Every locale's catalog; English is the source of keys and placeholders. */
|
|
13
|
+
export type Messages<M> = {
|
|
14
|
+
en: M;
|
|
15
|
+
} & { [L in Exclude<Locale, 'en'>]: Catalog<M>; };
|
|
16
|
+
/** Checks the catalogs against English at compile time; returns them unchanged. */
|
|
17
|
+
export declare const defineMessages: <const M extends Record<string, string>>(messages: Messages<M>) => Messages<M>;
|
|
18
|
+
type Placeholders<S> = S extends `${string}{${infer K}}${infer Rest}` ? K | Placeholders<Rest> : never;
|
|
19
|
+
type Params<S> = [Placeholders<S>] extends [never] ? [] : [params: Record<Placeholders<S>, string | number>];
|
|
20
|
+
export type Translate<M> = <K extends keyof M & string>(key: K, ...params: Params<M[K]>) => string;
|
|
21
|
+
/** `t(key, params)` for one locale. */
|
|
22
|
+
export declare function translator<M extends Record<string, string>>(messages: Messages<M>, locale: Locale): Translate<M>;
|
|
23
|
+
/**
|
|
24
|
+
* What's wrong with the catalogs, for tests: keys English has and another locale lacks (or the
|
|
25
|
+
* other way round), empty texts and placeholders that differ from English.
|
|
26
|
+
*/
|
|
27
|
+
export declare function catalogProblems(messages: Messages<Record<string, string>>): string[];
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/runtime/shared/i18n.ts
|
|
2
|
+
const LOCALES = ["en", "de"];
|
|
3
|
+
const DEFAULT_LOCALE = "en";
|
|
4
|
+
const isLocale = (value) => typeof value === "string" && LOCALES.includes(value);
|
|
5
|
+
/**
|
|
6
|
+
* The best supported language for an `Accept-Language` header: the highest-ranked one there is a
|
|
7
|
+
* catalog for, English when there's none.
|
|
8
|
+
*/
|
|
9
|
+
function negotiateLocale(header) {
|
|
10
|
+
if (!header) return "en";
|
|
11
|
+
return header.split(",").map((part, index) => {
|
|
12
|
+
const [tag = "", ...params] = part.trim().split(";");
|
|
13
|
+
const q = params.map((p) => p.trim()).find((p) => p.startsWith("q="));
|
|
14
|
+
return {
|
|
15
|
+
lang: tag.trim().toLowerCase().split("-")[0],
|
|
16
|
+
q: q ? Number(q.slice(2)) : 1,
|
|
17
|
+
index
|
|
18
|
+
};
|
|
19
|
+
}).filter((entry) => entry.q > 0).sort((a, b) => b.q - a.q || a.index - b.index).map((entry) => entry.lang).find(isLocale) ?? "en";
|
|
20
|
+
}
|
|
21
|
+
/** Checks the catalogs against English at compile time; returns them unchanged. */
|
|
22
|
+
const defineMessages = (messages) => messages;
|
|
23
|
+
const fill = (text, params) => params ? text.replace(/\{(\w+)\}/g, (all, key) => String(params[key] ?? all)) : text;
|
|
24
|
+
/** `t(key, params)` for one locale. */
|
|
25
|
+
function translator(messages, locale) {
|
|
26
|
+
const catalog = messages[locale];
|
|
27
|
+
return (key, ...params) => fill(catalog[key] ?? messages.en[key], params[0]);
|
|
28
|
+
}
|
|
29
|
+
const placeholders = (text) => [...text.matchAll(/\{(\w+)\}/g)].map((m) => m[1]).sort();
|
|
30
|
+
/**
|
|
31
|
+
* What's wrong with the catalogs, for tests: keys English has and another locale lacks (or the
|
|
32
|
+
* other way round), empty texts and placeholders that differ from English.
|
|
33
|
+
*/
|
|
34
|
+
function catalogProblems(messages) {
|
|
35
|
+
const problems = [];
|
|
36
|
+
const source = messages.en;
|
|
37
|
+
for (const locale of LOCALES) {
|
|
38
|
+
const catalog = messages[locale];
|
|
39
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(source), ...Object.keys(catalog)])) {
|
|
40
|
+
const text = catalog[key];
|
|
41
|
+
if (!(key in source)) problems.push(`${locale}.${key}: not in en`);
|
|
42
|
+
else if (text === void 0) problems.push(`${locale}.${key}: missing`);
|
|
43
|
+
else if (!text.trim()) problems.push(`${locale}.${key}: empty`);
|
|
44
|
+
else {
|
|
45
|
+
const own = placeholders(text).join(",");
|
|
46
|
+
const expected = placeholders(source[key]).join(",");
|
|
47
|
+
if (own !== expected) problems.push(`${locale}.${key}: placeholders ${fmt(own)} differ from en ${fmt(expected)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return problems;
|
|
52
|
+
}
|
|
53
|
+
const fmt = (list) => list.split(",").filter(Boolean).map((p) => `{${p}}`).join(" ");
|
|
54
|
+
export { DEFAULT_LOCALE, LOCALES, catalogProblems, defineMessages, isLocale, negotiateLocale, translator };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Messages } from "./i18n.js";
|
|
2
|
+
export declare const mailMessages: Messages<{
|
|
3
|
+
readonly sentBy: "Sent by {app}";
|
|
4
|
+
readonly sentByVia: "Sent by {app} via wervt";
|
|
5
|
+
readonly securedBy: "Secured by wervt";
|
|
6
|
+
readonly docs: "Docs";
|
|
7
|
+
readonly inviteSubject: "{by} invited you to {app}";
|
|
8
|
+
readonly invitePreview: "Join {app}";
|
|
9
|
+
readonly inviteReason: "You received this email because {by} invited this address.";
|
|
10
|
+
readonly inviteEyebrow: "Invitation";
|
|
11
|
+
readonly inviteHeading: "You're invited to {app}";
|
|
12
|
+
readonly inviteText: "{by} invited you to {app}.";
|
|
13
|
+
readonly inviteAction: "Open {app}";
|
|
14
|
+
readonly inviteNote: "Sign in with this email address to get in.";
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineMessages } from "./i18n.js";
|
|
2
|
+
// src/runtime/shared/messages.ts
|
|
3
|
+
const mailMessages = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
sentBy: "Sent by {app}",
|
|
6
|
+
sentByVia: "Sent by {app} via wervt",
|
|
7
|
+
securedBy: "Secured by wervt",
|
|
8
|
+
docs: "Docs",
|
|
9
|
+
inviteSubject: "{by} invited you to {app}",
|
|
10
|
+
invitePreview: "Join {app}",
|
|
11
|
+
inviteReason: "You received this email because {by} invited this address.",
|
|
12
|
+
inviteEyebrow: "Invitation",
|
|
13
|
+
inviteHeading: "You're invited to {app}",
|
|
14
|
+
inviteText: "{by} invited you to {app}.",
|
|
15
|
+
inviteAction: "Open {app}",
|
|
16
|
+
inviteNote: "Sign in with this email address to get in."
|
|
17
|
+
},
|
|
18
|
+
de: {
|
|
19
|
+
sentBy: "Gesendet von {app}",
|
|
20
|
+
sentByVia: "Gesendet von {app} über wervt",
|
|
21
|
+
securedBy: "Gesichert durch wervt",
|
|
22
|
+
docs: "Doku",
|
|
23
|
+
inviteSubject: "{by} hat dich zu {app} eingeladen",
|
|
24
|
+
invitePreview: "Mach mit bei {app}",
|
|
25
|
+
inviteReason: "Du bekommst diese E-Mail, weil {by} diese Adresse eingeladen hat.",
|
|
26
|
+
inviteEyebrow: "Einladung",
|
|
27
|
+
inviteHeading: "Du bist zu {app} eingeladen",
|
|
28
|
+
inviteText: "{by} hat dich zu {app} eingeladen.",
|
|
29
|
+
inviteAction: "{app} öffnen",
|
|
30
|
+
inviteNote: "Melde dich mit dieser E-Mail-Adresse an, um reinzukommen."
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
export { mailMessages };
|
|
@@ -1,17 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export type ShapeResponse
|
|
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 };
|