@wervt/nuxt 0.3.6 → 0.3.8

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.
@@ -0,0 +1,98 @@
1
+ import { PropType } from "vue";
2
+ /**
3
+ * A saved log query matched at least as often as its alert allows: what matched, how often, a
4
+ * few of the entries, and a link to the logs.
5
+ */
6
+ export declare const LogAlertEmail: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
+ /** The app whose logs matched; `all apps` for a query across apps. */
8
+ app: {
9
+ type: StringConstructor;
10
+ required: true;
11
+ };
12
+ /** The saved query's name. */
13
+ name: {
14
+ type: StringConstructor;
15
+ required: true;
16
+ };
17
+ /** Its LogsQL. */
18
+ query: {
19
+ type: StringConstructor;
20
+ required: true;
21
+ };
22
+ count: {
23
+ type: NumberConstructor;
24
+ required: true;
25
+ };
26
+ threshold: {
27
+ type: NumberConstructor;
28
+ required: true;
29
+ };
30
+ /** How far back it counted, e.g. `5m`. */
31
+ window: {
32
+ type: StringConstructor;
33
+ required: true;
34
+ };
35
+ /** How long it stays quiet now, e.g. `1h`. */
36
+ cooldown: {
37
+ type: StringConstructor;
38
+ required: true;
39
+ };
40
+ /** The logs, filtered to the query. */
41
+ url: {
42
+ type: StringConstructor;
43
+ required: true;
44
+ };
45
+ /** First lines of the newest matching entries. */
46
+ samples: {
47
+ type: PropType<string[]>;
48
+ default: () => never[];
49
+ };
50
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
51
+ [key: string]: any;
52
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
53
+ /** The app whose logs matched; `all apps` for a query across apps. */
54
+ app: {
55
+ type: StringConstructor;
56
+ required: true;
57
+ };
58
+ /** The saved query's name. */
59
+ name: {
60
+ type: StringConstructor;
61
+ required: true;
62
+ };
63
+ /** Its LogsQL. */
64
+ query: {
65
+ type: StringConstructor;
66
+ required: true;
67
+ };
68
+ count: {
69
+ type: NumberConstructor;
70
+ required: true;
71
+ };
72
+ threshold: {
73
+ type: NumberConstructor;
74
+ required: true;
75
+ };
76
+ /** How far back it counted, e.g. `5m`. */
77
+ window: {
78
+ type: StringConstructor;
79
+ required: true;
80
+ };
81
+ /** How long it stays quiet now, e.g. `1h`. */
82
+ cooldown: {
83
+ type: StringConstructor;
84
+ required: true;
85
+ };
86
+ /** The logs, filtered to the query. */
87
+ url: {
88
+ type: StringConstructor;
89
+ required: true;
90
+ };
91
+ /** First lines of the newest matching entries. */
92
+ samples: {
93
+ type: PropType<string[]>;
94
+ default: () => never[];
95
+ };
96
+ }>> & Readonly<{}>, {
97
+ samples: string[];
98
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,114 @@
1
+ import { EmailButton, EmailDivider, EmailHeading, EmailMuted, EmailText, WervtEmail, emailTheme } from "./components.js";
2
+ import { defineComponent, h } from "vue";
3
+ // src/runtime/email/alert.ts
4
+ const code = (text) => h("code", { style: {
5
+ fontFamily: emailTheme.mono,
6
+ fontSize: "13px",
7
+ background: emailTheme.codeBackground,
8
+ border: `1px solid ${emailTheme.accentBorder}`,
9
+ borderRadius: "4px",
10
+ padding: "1px 5px"
11
+ } }, text);
12
+ /** `5m` → `5 minutes`. */
13
+ function spelledOut(duration) {
14
+ const match = duration.match(/^(\d+)([mhd])$/);
15
+ if (!match) return duration;
16
+ const amount = Number(match[1]);
17
+ const unit = {
18
+ m: "minute",
19
+ h: "hour",
20
+ d: "day"
21
+ }[match[2]];
22
+ return amount === 1 ? `1 ${unit}` : `${amount} ${unit}s`;
23
+ }
24
+ /**
25
+ * A saved log query matched at least as often as its alert allows: what matched, how often, a
26
+ * few of the entries, and a link to the logs.
27
+ */
28
+ const LogAlertEmail = defineComponent({
29
+ name: "LogAlertEmail",
30
+ props: {
31
+ /** The app whose logs matched; `all apps` for a query across apps. */
32
+ app: {
33
+ type: String,
34
+ required: true
35
+ },
36
+ /** The saved query's name. */
37
+ name: {
38
+ type: String,
39
+ required: true
40
+ },
41
+ /** Its LogsQL. */
42
+ query: {
43
+ type: String,
44
+ required: true
45
+ },
46
+ count: {
47
+ type: Number,
48
+ required: true
49
+ },
50
+ threshold: {
51
+ type: Number,
52
+ required: true
53
+ },
54
+ /** How far back it counted, e.g. `5m`. */
55
+ window: {
56
+ type: String,
57
+ required: true
58
+ },
59
+ /** How long it stays quiet now, e.g. `1h`. */
60
+ cooldown: {
61
+ type: String,
62
+ required: true
63
+ },
64
+ /** The logs, filtered to the query. */
65
+ url: {
66
+ type: String,
67
+ required: true
68
+ },
69
+ /** First lines of the newest matching entries. */
70
+ samples: {
71
+ type: Array,
72
+ default: () => []
73
+ }
74
+ },
75
+ setup(props) {
76
+ return () => h(WervtEmail, {
77
+ subject: `${props.app}: ${props.name} (${props.count} in ${props.window})`,
78
+ app: props.app,
79
+ preview: `${props.count} log ${props.count === 1 ? "entry" : "entries"} matched in the last ${spelledOut(props.window)}`,
80
+ footer: "Sent by wervt",
81
+ reason: `You received this email because you own or help run ${props.app} and it has an alert on this query.`
82
+ }, () => [
83
+ h(EmailHeading, { eyebrow: "Log alert" }, () => props.name),
84
+ h(EmailText, () => [
85
+ `${props.count} log ${props.count === 1 ? "entry" : "entries"} of ${props.app} matched `,
86
+ code(props.query),
87
+ ` in the last ${spelledOut(props.window)}. The alert fires at ${props.threshold}.`
88
+ ]),
89
+ ...props.samples.length ? [h(EmailMuted, () => "Newest matches:"), h("pre", {
90
+ class: "wv-code",
91
+ style: {
92
+ margin: "0 0 24px",
93
+ padding: "12px 14px",
94
+ fontFamily: emailTheme.mono,
95
+ fontSize: "12px",
96
+ lineHeight: "18px",
97
+ whiteSpace: "pre-wrap",
98
+ wordBreak: "break-word",
99
+ color: emailTheme.highlighted,
100
+ background: emailTheme.codeBackground,
101
+ border: `1px solid ${emailTheme.accentBorder}`,
102
+ borderRadius: "8px"
103
+ }
104
+ }, props.samples.join("\n"))] : [],
105
+ h(EmailButton, {
106
+ href: props.url,
107
+ color: "neutral"
108
+ }, () => "Open the logs"),
109
+ h(EmailDivider),
110
+ h(EmailMuted, () => `No more mails about this for ${spelledOut(props.cooldown)}, even if entries keep matching. Change or turn off the alert where the query is saved.`)
111
+ ]);
112
+ }
113
+ });
114
+ export { LogAlertEmail };
@@ -22,9 +22,14 @@ export declare const emailTheme: {
22
22
  radius: string;
23
23
  font: string;
24
24
  mono: string;
25
- /** Where the logo images live. PNG, because Gmail and Outlook don't show SVG. */
25
+ /**
26
+ * Where the logo images live. PNG, because Gmail and Outlook don't show SVG. Always wervt.app's,
27
+ * even on a self-hosted instance: its docs, which would serve them, may not run there.
28
+ */
26
29
  assets: string;
30
+ /** @deprecated wervt.app's only; `wervtInstance().site` is the instance the app runs on. */
27
31
  site: string;
32
+ /** @deprecated wervt.app's only; `wervtInstance().docs` is the instance's, if it runs any. */
28
33
  docs: string;
29
34
  };
30
35
  /**
@@ -1,3 +1,4 @@
1
+ import { wervtInstance } from "../shared/instance.js";
1
2
  import { defineComponent, h, inject } from "vue";
2
3
  // src/runtime/email/components.ts
3
4
  /** The template's subject, handed to renderEmail() while rendering. */
@@ -23,9 +24,14 @@ const emailTheme = {
23
24
  radius: "8px",
24
25
  font: "Poppins, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
25
26
  mono: "'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
26
- /** Where the logo images live. PNG, because Gmail and Outlook don't show SVG. */
27
+ /**
28
+ * Where the logo images live. PNG, because Gmail and Outlook don't show SVG. Always wervt.app's,
29
+ * even on a self-hosted instance: its docs, which would serve them, may not run there.
30
+ */
27
31
  assets: "https://wervt.app/email",
32
+ /** @deprecated wervt.app's only; `wervtInstance().site` is the instance the app runs on. */
28
33
  site: "https://wervt.app",
34
+ /** @deprecated wervt.app's only; `wervtInstance().docs` is the instance's, if it runs any. */
29
35
  docs: "https://wervt.app/getting-started/introduction"
30
36
  };
31
37
  const t = emailTheme;
@@ -198,7 +204,7 @@ function header(app) {
198
204
  verticalAlign: "middle"
199
205
  }
200
206
  }, h("a", {
201
- href: t.site,
207
+ href: wervtInstance().site,
202
208
  style: {
203
209
  textDecoration: "none",
204
210
  color: "#fafafa"
@@ -309,6 +315,7 @@ function footer(props) {
309
315
  color: t.muted,
310
316
  textDecoration: "none"
311
317
  };
318
+ const instance = wervtInstance();
312
319
  return table({ width: "100%" }, [h("tr", [tile ? h("td", {
313
320
  width: 40,
314
321
  style: {
@@ -357,22 +364,18 @@ function footer(props) {
357
364
  color: t.dimmed
358
365
  }
359
366
  }, brand ? brand.securedByWervt === false ? [] : [h("a", {
360
- href: t.site,
367
+ href: instance.site,
361
368
  class: "wv-muted",
362
369
  style: link
363
- }, "Secured by wervt")] : [
364
- h("a", {
365
- href: t.site,
366
- class: "wv-muted",
367
- style: link
368
- }, "wervt.app"),
369
- separator(),
370
- h("a", {
371
- href: t.docs,
372
- class: "wv-muted",
373
- style: link
374
- }, "Docs")
375
- ])
370
+ }, "Secured by wervt")] : [h("a", {
371
+ href: instance.site,
372
+ class: "wv-muted",
373
+ style: link
374
+ }, instance.domain), ...instance.docs ? [separator(), h("a", {
375
+ href: `${instance.docs}/getting-started/introduction`,
376
+ class: "wv-muted",
377
+ style: link
378
+ }, "Docs")] : []])
376
379
  ])])]);
377
380
  }
378
381
  function separator() {
@@ -1,4 +1,6 @@
1
+ import { LogAlertEmail } from "./alert.js";
1
2
  import { EmailBrand, EmailButton, EmailCode, EmailDivider, EmailHeading, EmailLink, EmailMuted, EmailText, WervtEmail, emailTheme } from "./components.js";
2
3
  import { InviteEmail } from "./invite.js";
4
+ import { WervtInstance, wervtInstance } from "../shared/instance.js";
3
5
  import { EmailProps, RenderedEmail, renderEmail } from "./render.js";
4
- export { type EmailBrand, EmailButton, EmailCode, EmailDivider, EmailHeading, EmailLink, EmailMuted, type EmailProps, EmailText, InviteEmail, type RenderedEmail, WervtEmail, emailTheme, renderEmail };
6
+ export { type EmailBrand, EmailButton, EmailCode, EmailDivider, EmailHeading, EmailLink, EmailMuted, type EmailProps, EmailText, InviteEmail, LogAlertEmail, type RenderedEmail, WervtEmail, type WervtInstance, emailTheme, renderEmail, wervtInstance };
@@ -1,4 +1,6 @@
1
+ import { wervtInstance } from "../shared/instance.js";
1
2
  import { EmailButton, EmailCode, EmailDivider, EmailHeading, EmailLink, EmailMuted, EmailText, WervtEmail, emailTheme } from "./components.js";
3
+ import { LogAlertEmail } from "./alert.js";
2
4
  import { InviteEmail } from "./invite.js";
3
5
  import { renderEmail } from "./render.js";
4
- export { EmailButton, EmailCode, EmailDivider, EmailHeading, EmailLink, EmailMuted, EmailText, InviteEmail, WervtEmail, emailTheme, renderEmail };
6
+ export { EmailButton, EmailCode, EmailDivider, EmailHeading, EmailLink, EmailMuted, EmailText, InviteEmail, LogAlertEmail, WervtEmail, emailTheme, renderEmail, wervtInstance };
@@ -1,4 +1,5 @@
1
1
  import { WervtRole, WervtUser } from "../shared/user.js";
2
+ import { WervtInstance, wervtInstance } from "../shared/instance.js";
2
3
  import { EmailProps, RenderedEmail, renderEmail } from "../email/render.js";
3
4
  import { WervtAccess, WervtInvite, useWervtAccess } from "./access.js";
4
5
  import { useDb } from "./db.js";
@@ -13,4 +14,4 @@ import { AccountMergedPayload, Task, TaskEvent, TaskResult, defineTask } from ".
13
14
  import { SafeResult, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, vh } from "./validate.js";
14
15
  import { Tool, ToolRoute, defineTool, defineToolRoute } from "./tools.js";
15
16
  import { requireWervtRole, useWervtUser } from "./user.js";
16
- export { type AccountMergedPayload, type EmailProps, type McpTokenContext, type RenderedEmail, type SafeResult, type SendMailOptions, type ShapeOptions, type ShapeResponse, type ShapeRow, type Task, type TaskEvent, type TaskResult, type Tool, type ToolRoute, type WervtAccess, type WervtInvite, type WervtRole, type WervtUser, type WorkloadIdentity, createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, requireWervtRole, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtAccess, useWervtUser, useWorkloadIdentity, vh };
17
+ export { type AccountMergedPayload, type EmailProps, type McpTokenContext, type RenderedEmail, type SafeResult, type SendMailOptions, type ShapeOptions, type ShapeResponse, type ShapeRow, type Task, type TaskEvent, type TaskResult, type Tool, type ToolRoute, type WervtAccess, type WervtInstance, type WervtInvite, type WervtRole, type WervtUser, type WorkloadIdentity, createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, requireWervtRole, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtAccess, useWervtUser, useWorkloadIdentity, vh, wervtInstance };
@@ -1,3 +1,4 @@
1
+ import { wervtInstance } from "../shared/instance.js";
1
2
  import { renderEmail } from "../email/render.js";
2
3
  import { useWervtAccess } from "./access.js";
3
4
  import { useWorkloadIdentity } from "./identity.js";
@@ -10,4 +11,4 @@ import { defineTask } from "./tasks.js";
10
11
  import { useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, vh } from "./validate.js";
11
12
  import { defineTool, defineToolRoute } from "./tools.js";
12
13
  import { requireWervtRole, useWervtUser } from "./user.js";
13
- export { createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, requireWervtRole, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtAccess, useWervtUser, useWorkloadIdentity, vh };
14
+ export { createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, requireWervtRole, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtAccess, useWervtUser, useWorkloadIdentity, vh, wervtInstance };
@@ -11,8 +11,8 @@ type Registry = Record<string, {
11
11
  }>;
12
12
  /** `POST /_wervt/tasks/:name`: runs a task when the control plane asks. */
13
13
  export declare function taskRunHandler(runTask: RunTask): import("nuxt/server").EventHandler<Promise<{
14
- result?: undefined;
15
14
  error: string;
15
+ result?: undefined;
16
16
  } | {
17
17
  error?: undefined;
18
18
  result: {} | null;
@@ -36,7 +36,7 @@ export declare const runTool: import("nuxt/server").EventHandler<Promise<{
36
36
  };
37
37
  result?: undefined;
38
38
  } | {
39
- error?: undefined;
40
39
  issues?: undefined;
41
40
  result: {} | null;
41
+ error?: undefined;
42
42
  }>>;
@@ -0,0 +1,17 @@
1
+ /** The wervt instance an app runs on: wervt.app, or a self-hosted one at its own domain. */
2
+ export interface WervtInstance {
3
+ /** E.g. `wervt.app` or `lightdrop.cloud`. */
4
+ domain: string;
5
+ /** The domain as a link. */
6
+ site: string;
7
+ /** The instance's docs site, or `undefined` when it runs none. */
8
+ docs?: string;
9
+ }
10
+ type Env = Record<string, string | undefined>;
11
+ /**
12
+ * Read at call time from what the runtime gives each isolate (`WERVT_BASE_DOMAIN`,
13
+ * `WERVT_DOCS_URL`, empty without docs), so an app built for wervt.app links to the right place
14
+ * on any other instance. Older runtimes pass neither, and were all wervt.app's.
15
+ */
16
+ export declare function wervtInstance(env?: Env): WervtInstance;
17
+ export {};
@@ -0,0 +1,17 @@
1
+ // src/runtime/shared/instance.ts
2
+ const processEnv = () => typeof process === "undefined" ? {} : process.env;
3
+ /**
4
+ * Read at call time from what the runtime gives each isolate (`WERVT_BASE_DOMAIN`,
5
+ * `WERVT_DOCS_URL`, empty without docs), so an app built for wervt.app links to the right place
6
+ * on any other instance. Older runtimes pass neither, and were all wervt.app's.
7
+ */
8
+ function wervtInstance(env = processEnv()) {
9
+ const domain = env.WERVT_BASE_DOMAIN || "wervt.app";
10
+ const site = `${/(^|\.)localhost(:\d+)?$/.test(domain) ? "http" : "https"}://${domain}`;
11
+ return {
12
+ domain,
13
+ site,
14
+ docs: env.WERVT_DOCS_URL === void 0 ? domain === "wervt.app" ? site : void 0 : env.WERVT_DOCS_URL.replace(/\/+$/, "") || void 0
15
+ };
16
+ }
17
+ export { wervtInstance };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wervt/nuxt",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "Nuxt module for apps hosted on wervt: database, live data, MCP and runtime setup.",
5
5
  "keywords": [
6
6
  "drizzle",