@wervt/nuxt 0.3.2 → 0.3.4

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 (128) hide show
  1. package/README.md +2 -0
  2. package/dist/dev.d.ts +4 -4
  3. package/dist/dev.js +33 -11
  4. package/dist/env-types.js +13 -0
  5. package/dist/icons.js +30 -0
  6. package/dist/loading.js +30 -0
  7. package/dist/module.d.ts +0 -2
  8. package/dist/module.js +42 -2
  9. package/dist/node_modules/.pnpm/nitro@3.0.260903-beta/node_modules/nitro/dist/runtime/internal/task.js +1 -2
  10. package/dist/node_modules/.pnpm/nitro@3.0.260903-beta/node_modules/nitro/dist/runtime/task.js +1 -2
  11. package/dist/og-image.js +38 -0
  12. package/dist/preview.d.ts +1 -3
  13. package/dist/preview.js +1 -2
  14. package/dist/provision.d.ts +31 -4
  15. package/dist/provision.js +82 -21
  16. package/dist/route-tools.js +1 -2
  17. package/dist/runtime/app/plugins/analytics.client.d.ts +0 -2
  18. package/dist/runtime/app/plugins/analytics.client.js +1 -2
  19. package/dist/runtime/app/plugins/feedback.client.d.ts +0 -2
  20. package/dist/runtime/app/plugins/feedback.client.js +1 -2
  21. package/dist/runtime/app/plugins/update.client.d.ts +0 -2
  22. package/dist/runtime/app/plugins/update.client.js +1 -2
  23. package/dist/runtime/app/plugins/user.client.d.ts +0 -2
  24. package/dist/runtime/app/plugins/user.client.js +1 -2
  25. package/dist/runtime/app/plugins/webmcp.client.d.ts +0 -2
  26. package/dist/runtime/app/plugins/webmcp.client.js +1 -2
  27. package/dist/runtime/app/preview-error.d.ts +26 -0
  28. package/dist/runtime/app/preview-error.js +69 -0
  29. package/dist/runtime/app/snapshot.d.ts +15 -0
  30. package/dist/runtime/app/snapshot.js +63 -0
  31. package/dist/runtime/app/useShape.d.ts +8 -3
  32. package/dist/runtime/app/useShape.js +10 -55
  33. package/dist/runtime/app/useWervtFeedback.d.ts +1 -2
  34. package/dist/runtime/app/useWervtFeedback.js +1 -2
  35. package/dist/runtime/app/useWervtTrack.d.ts +1 -3
  36. package/dist/runtime/app/useWervtTrack.js +1 -2
  37. package/dist/runtime/app/useWervtUpdate.d.ts +1 -3
  38. package/dist/runtime/app/useWervtUpdate.js +2 -3
  39. package/dist/runtime/app/useWervtUser.d.ts +1 -3
  40. package/dist/runtime/app/useWervtUser.js +1 -2
  41. package/dist/runtime/email/components.d.ts +1 -3
  42. package/dist/runtime/email/components.js +1 -2
  43. package/dist/runtime/email/index.d.ts +2 -1
  44. package/dist/runtime/email/index.js +2 -1
  45. package/dist/runtime/email/invite.d.ts +73 -0
  46. package/dist/runtime/email/invite.js +59 -0
  47. package/dist/runtime/email/render.d.ts +1 -3
  48. package/dist/runtime/email/render.js +1 -2
  49. package/dist/runtime/server/access.d.ts +4 -5
  50. package/dist/runtime/server/access.js +4 -4
  51. package/dist/runtime/server/agent-token-plugin.d.ts +11 -0
  52. package/dist/runtime/server/agent-token-plugin.js +9 -0
  53. package/dist/runtime/server/anthropic.d.ts +19 -0
  54. package/dist/runtime/server/anthropic.js +52 -0
  55. package/dist/runtime/server/boot.d.ts +0 -2
  56. package/dist/runtime/server/boot.js +1 -2
  57. package/dist/runtime/server/db.d.ts +1 -3
  58. package/dist/runtime/server/db.js +1 -2
  59. package/dist/runtime/server/dev-auth.d.ts +0 -2
  60. package/dist/runtime/server/dev-auth.js +1 -2
  61. package/dist/runtime/server/email-preview.d.ts +0 -2
  62. package/dist/runtime/server/email-preview.js +1 -2
  63. package/dist/runtime/server/identity.d.ts +18 -0
  64. package/dist/runtime/server/identity.js +36 -0
  65. package/dist/runtime/server/index.d.ts +4 -3
  66. package/dist/runtime/server/index.js +4 -3
  67. package/dist/runtime/server/logger.d.ts +0 -2
  68. package/dist/runtime/server/logger.js +1 -2
  69. package/dist/runtime/server/mail.d.ts +1 -3
  70. package/dist/runtime/server/mail.js +1 -2
  71. package/dist/runtime/server/mcp-auth-server.d.ts +0 -2
  72. package/dist/runtime/server/mcp-auth-server.js +1 -2
  73. package/dist/runtime/server/mcp-auth.d.ts +1 -3
  74. package/dist/runtime/server/mcp-auth.js +1 -2
  75. package/dist/runtime/server/mcp-claims.d.ts +1 -3
  76. package/dist/runtime/server/mcp-claims.js +1 -2
  77. package/dist/runtime/server/mcp-metadata.d.ts +0 -2
  78. package/dist/runtime/server/mcp-metadata.js +1 -2
  79. package/dist/runtime/server/mcp.d.ts +0 -2
  80. package/dist/runtime/server/mcp.js +1 -2
  81. package/dist/runtime/server/preview-errors-plugin.d.ts +13 -0
  82. package/dist/runtime/server/preview-errors-plugin.js +19 -0
  83. package/dist/runtime/server/seed.d.ts +1 -3
  84. package/dist/runtime/server/seed.js +1 -2
  85. package/dist/runtime/server/shape.d.ts +1 -3
  86. package/dist/runtime/server/shape.js +1 -2
  87. package/dist/runtime/server/storage-plugin.d.ts +1 -3
  88. package/dist/runtime/server/storage-plugin.js +1 -2
  89. package/dist/runtime/server/task-handlers.d.ts +2 -3
  90. package/dist/runtime/server/task-handlers.js +1 -2
  91. package/dist/runtime/server/task-list-nitro2.d.ts +0 -2
  92. package/dist/runtime/server/task-list-nitro2.js +1 -2
  93. package/dist/runtime/server/task-list-nitro3.d.ts +0 -2
  94. package/dist/runtime/server/task-list-nitro3.js +1 -2
  95. package/dist/runtime/server/task-run-nitro2.d.ts +0 -2
  96. package/dist/runtime/server/task-run-nitro2.js +1 -2
  97. package/dist/runtime/server/task-run-nitro3.d.ts +0 -2
  98. package/dist/runtime/server/task-run-nitro3.js +1 -2
  99. package/dist/runtime/server/tasks.d.ts +1 -3
  100. package/dist/runtime/server/tasks.js +1 -2
  101. package/dist/runtime/server/tool-handlers.d.ts +1 -3
  102. package/dist/runtime/server/tool-handlers.js +1 -2
  103. package/dist/runtime/server/tool-list.js +1 -2
  104. package/dist/runtime/server/tool-run.js +1 -2
  105. package/dist/runtime/server/tools.d.ts +1 -2
  106. package/dist/runtime/server/tools.js +1 -2
  107. package/dist/runtime/server/user-route.d.ts +0 -2
  108. package/dist/runtime/server/user-route.js +1 -2
  109. package/dist/runtime/server/user.d.ts +9 -5
  110. package/dist/runtime/server/user.js +29 -5
  111. package/dist/runtime/server/validate.d.ts +1 -2
  112. package/dist/runtime/server/validate.js +1 -2
  113. package/dist/runtime/shared/agent-token.d.ts +7 -0
  114. package/dist/runtime/shared/agent-token.js +16 -0
  115. package/dist/runtime/shared/mark.d.ts +1 -0
  116. package/dist/runtime/shared/mark.js +3 -0
  117. package/dist/runtime/shared/preview-errors.d.ts +10 -0
  118. package/dist/runtime/shared/preview-errors.js +14 -0
  119. package/dist/runtime/shared/shape.d.ts +1 -2
  120. package/dist/runtime/shared/shape.js +1 -2
  121. package/dist/runtime/shared/user.d.ts +9 -3
  122. package/dist/runtime/shared/user.js +5 -3
  123. package/dist/seed.js +1 -2
  124. package/dist/shapes.js +1 -2
  125. package/dist/storage.d.ts +50 -4
  126. package/dist/storage.js +224 -5
  127. package/dist/studio-preview.js +34 -0
  128. package/package.json +17 -8
package/README.md CHANGED
@@ -20,6 +20,8 @@ export default defineNuxtConfig({
20
20
  - `useWervtUser()`: the signed-in wervt user, on the server and in the app.
21
21
  - `useWervtAccess(event)`: invites on invite-only apps (`wervt access <app> --invite`), as the
22
22
  signed-in user.
23
+ - `useAnthropic()` (`@wervt/nuxt/anthropic`): an Anthropic client without an API key, through the
24
+ app's wervt identity (`wervt identity enable <app>`); `useWorkloadIdentity()` for the raw token.
23
25
  - `useValidatedBody()` / `useValidatedQuery()` / `useValidatedParams()`: valibot validation.
24
26
  - `useWervtTrack()`: custom events for wervt's cookieless analytics (page views are counted automatically).
25
27
  - `wervt: { feedback: { key } }` and `useWervtFeedback()`: the kontor feedback widget, reports go into a kontor project's triage.
package/dist/dev.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { WervtRole } from "./runtime/shared/user.js";
1
2
  import "nuxt/schema";
2
- //#region src/dev.d.ts
3
3
  export interface DevOptions {
4
4
  /** Superuser URL of the local wervt Postgres. */
5
5
  adminUrl?: string;
@@ -18,7 +18,7 @@ export interface DevOptions {
18
18
  seed?: boolean;
19
19
  /**
20
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.
21
+ * Defaults to a dev user with a Twitch account linked. Without `role`, they're the app's owner.
22
22
  */
23
23
  user?: {
24
24
  id: string;
@@ -26,6 +26,6 @@ export interface DevOptions {
26
26
  name?: string;
27
27
  image?: string;
28
28
  accounts?: Record<string, string>;
29
+ role?: WervtRole;
29
30
  };
30
- }
31
- //#endregion
31
+ }
package/dist/dev.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { createStorage } from "./storage.js";
2
- import { appDatabaseUrl, dbRole, provisionDatabase, storageCredentials } from "./provision.js";
2
+ import { appDatabaseUrl, dbRole, migrateAs, provisionDatabase, storageCredentials } from "./provision.js";
3
3
  import { SEED_TASK, runDevSeed } from "./seed.js";
4
4
  import { useLogger } from "nuxt/kit";
5
5
  import { existsSync, watch } from "node:fs";
6
6
  import { readFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
- //#region src/dev.ts
8
+ // src/dev.ts
9
9
  const DEFAULTS = {
10
10
  adminUrl: "postgres://postgres:postgres@localhost:5432/wervt",
11
11
  dbSecret: "dev-secret",
@@ -22,6 +22,7 @@ const logger = useLogger("wervt");
22
22
  * points the dev server at the app's database and Electric.
23
23
  */
24
24
  async function setupDev(nuxt, app, options, features = {}) {
25
+ forgetOwnEnv(nuxt.options.rootDir);
25
26
  const config = {
26
27
  adminUrl: process.env.WERVT_PG_ADMIN_URL ?? options.adminUrl ?? DEFAULTS.adminUrl,
27
28
  dbSecret: process.env.WERVT_DB_SECRET ?? options.dbSecret ?? DEFAULTS.dbSecret,
@@ -43,13 +44,18 @@ async function setupDev(nuxt, app, options, features = {}) {
43
44
  secret: config.dbSecret,
44
45
  migrationsFolder
45
46
  };
47
+ const given = process.env.DATABASE_URL && process.env.WERVT_DB_SCHEMA ? {
48
+ url: process.env.DATABASE_URL,
49
+ schema: process.env.WERVT_DB_SCHEMA
50
+ } : void 0;
51
+ const database = given?.schema ?? dbRole(app);
46
52
  let serverUrl;
47
53
  let seedPending = false;
48
54
  const seed = async () => {
49
55
  if (!seedPending || !serverUrl) return;
50
56
  seedPending = false;
51
57
  try {
52
- if ((await runDevSeed(serverUrl)).status === "seeded") logger.success(`Seeded \`${dbRole(app)}\` (${SEED_TASK})`);
58
+ if ((await runDevSeed(serverUrl)).status === "seeded") logger.success(`Seeded \`${database}\` (${SEED_TASK})`);
53
59
  } catch (e) {
54
60
  logger.warn(`\`${SEED_TASK}\` failed:`, e instanceof Error ? e.message : e);
55
61
  }
@@ -60,21 +66,29 @@ async function setupDev(nuxt, app, options, features = {}) {
60
66
  });
61
67
  const migrate = async () => {
62
68
  try {
63
- const { fresh } = await provisionDatabase(provision);
64
- logger.success(`Database \`${dbRole(app)}\` is up to date`);
69
+ const { fresh } = given ? await migrateAs({
70
+ ...given,
71
+ migrationsFolder
72
+ }) : await provisionDatabase(provision);
73
+ logger.success(`Database \`${database}\` is up to date`);
65
74
  if (fresh && options.seed !== false) {
66
75
  seedPending = true;
67
76
  seed();
68
77
  }
69
78
  } catch (e) {
70
- logger.warn(`Could not migrate \`${dbRole(app)}\`. Is the wervt stack running (docker compose up -d)?`, e instanceof Error ? e.message : e);
79
+ 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);
71
80
  }
72
81
  };
73
82
  await migrate();
74
- process.env.DATABASE_URL ??= await appDatabaseUrl(provision);
75
- process.env.WERVT_DB_SCHEMA ??= dbRole(app);
76
- process.env.ELECTRIC_URL ??= config.electricUrl;
77
- process.env.ELECTRIC_SECRET ??= config.electricSecret;
83
+ const own = {
84
+ DATABASE_URL: await appDatabaseUrl(provision),
85
+ WERVT_DB_SCHEMA: dbRole(app),
86
+ ELECTRIC_URL: config.electricUrl,
87
+ ELECTRIC_SECRET: config.electricSecret
88
+ };
89
+ const set = Object.fromEntries(Object.entries(own).filter(([key]) => process.env[key] === void 0));
90
+ Object.assign(process.env, set);
91
+ process.env[OWN_ENV] = JSON.stringify(set);
78
92
  let timer;
79
93
  const watcher = watch(migrationsFolder, { recursive: true }, (_event, file) => {
80
94
  if (!file?.endsWith(".sql")) return;
@@ -86,6 +100,15 @@ async function setupDev(nuxt, app, options, features = {}) {
86
100
  });
87
101
  nuxt.hook("close", () => watcher.close());
88
102
  }
103
+ const OWN_ENV = "WERVT_DEV_OWN_ENV";
104
+ function forgetOwnEnv(rootDir) {
105
+ const own = process.env[OWN_ENV];
106
+ if (!own) return;
107
+ for (const [key, value] of Object.entries(JSON.parse(own))) if (process.env[key] === value) delete process.env[key];
108
+ delete process.env[OWN_ENV];
109
+ const file = join(rootDir, ".env");
110
+ if (existsSync(file)) process.loadEnvFile(file);
111
+ }
89
112
  /** The app's bucket in the local Garage, and WERVT_S3_* for ablage, like in production. */
90
113
  async function setupStorage(app, secret, options) {
91
114
  const s3Url = process.env.WERVT_S3_URL ?? options.s3Url ?? DEFAULTS.s3Url;
@@ -118,5 +141,4 @@ async function resolveAppName(rootDir, name) {
118
141
  }
119
142
  return name.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
120
143
  }
121
- //#endregion
122
144
  export { resolveAppName, setupDev };
@@ -0,0 +1,13 @@
1
+ // src/env-types.ts
2
+ /** `.nuxt/types/wervt-env.d.ts`: types for the `process.env` the isolate provides. */
3
+ const PROCESS_ENV_TYPES = `declare namespace NodeJS {
4
+ interface ProcessEnv {
5
+ [key: string]: string | undefined
6
+ }
7
+ interface Process {
8
+ env: ProcessEnv
9
+ }
10
+ }
11
+ declare var process: NodeJS.Process
12
+ `;
13
+ export { PROCESS_ENV_TYPES };
package/dist/icons.js ADDED
@@ -0,0 +1,30 @@
1
+ import { dependencyNames } from "./og-image.js";
2
+ // src/icons.ts
3
+ const ICONIFY_JSON = "@iconify-json/";
4
+ /** The whole Iconify set in one package; @nuxt/icon finds every collection in it. */
5
+ const ICONIFY_ALL = "@iconify/json";
6
+ /** Where the app's icons can come from without the Iconify API. */
7
+ async function iconSources(rootDir) {
8
+ const names = await dependencyNames(rootDir);
9
+ return {
10
+ collections: names.filter((name) => name.startsWith(ICONIFY_JSON)).map((name) => name.slice(14)).toSorted(),
11
+ everything: names.includes(ICONIFY_ALL)
12
+ };
13
+ }
14
+ /**
15
+ * @nuxt/icon defaults for a wervt build. Its `serverBundle: 'auto'` already resolves to the
16
+ * locally installed collections on the Deno preset (auto-discovery also covers layers and
17
+ * transitive deps), so only the API fallback needs changing: the isolate can't reach the Iconify
18
+ * API, and a server request would time out for every icon of a missing collection. The browser
19
+ * may still fall back. An app's own `icon` config wins over these.
20
+ */
21
+ function iconDefaults() {
22
+ return { fallbackToApi: "client-only" };
23
+ }
24
+ /** Why icons won't render on wervt, if they won't. Nuxt UI's own icons are Lucide icons. */
25
+ function iconWarning(sources, nuxtUi) {
26
+ if (sources.everything) return void 0;
27
+ if (nuxtUi && !sources.collections.includes("lucide")) return "Nuxt UI uses Lucide icons, but `@iconify-json/lucide` is not installed, so they won't render on wervt (the isolate can't reach the Iconify API). Run `pnpm add -D @iconify-json/lucide`, plus `@iconify-json/<name>` for every other icon collection the app uses.";
28
+ if (sources.collections.length === 0) return "No icon collection is installed, so icons won't render on wervt (the isolate can't reach the Iconify API). Install the collections the app uses, e.g. `pnpm add -D @iconify-json/lucide`.";
29
+ }
30
+ export { iconDefaults, iconSources, iconWarning };
@@ -0,0 +1,30 @@
1
+ import { WERVT_MARK } from "./runtime/shared/mark.js";
2
+ // src/loading.ts
3
+ const previewLoadingTemplate = () => `<!doctype html>
4
+ <html lang="de">
5
+ <head>
6
+ <meta charset="utf-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
8
+ <title>Einen Moment …</title>
9
+ <style>
10
+ :root { color-scheme: light dark; --fg: #0a0a0b; --muted: #6b6b70; --bg: #fafaf9; --a: #2f5bff; }
11
+ @media (prefers-color-scheme: dark) { :root { --fg: #fafaf9; --muted: #a1a1a6; --bg: #0a0a0b; --a: #5885ff; } }
12
+ html, body { height: 100%; margin: 0; }
13
+ body { display: grid; place-content: center; justify-items: center; gap: 1rem; background: var(--bg);
14
+ color: var(--fg); font: 500 1rem/1.4 system-ui, -apple-system, sans-serif; text-align: center; }
15
+ svg { width: 3rem; height: 3rem; color: var(--fg); animation: pulse 1.6s ease-in-out infinite; }
16
+ p { margin: 0; color: var(--muted); }
17
+ @keyframes pulse { 50% { opacity: .45; transform: scale(.94); } }
18
+ @media (prefers-reduced-motion: reduce) { svg { animation: none; } }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ ${WERVT_MARK}
23
+ <p id="m">Einen Moment, die App wird gerade aktualisiert …</p>
24
+ <script>
25
+ if (!navigator.language.toLowerCase().startsWith('de'))
26
+ document.getElementById('m').textContent = 'One moment, the app is being updated …'
27
+ <\/script>
28
+ </body>
29
+ </html>`;
30
+ export { previewLoadingTemplate };
package/dist/module.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { DevOptions } from "./dev.js";
2
2
  import { PreviewOptions } from "./preview.js";
3
- //#region src/module.d.ts
4
3
  export interface ModuleOptions {
5
4
  /** App name on wervt. Defaults to the package.json name. */
6
5
  app?: string;
@@ -82,5 +81,4 @@ export interface TasksManifest {
82
81
  timezone?: string;
83
82
  }
84
83
  declare const _default: import("nuxt/schema").NuxtModule<ModuleOptions, ModuleOptions, false>;
85
- //#endregion
86
84
  export { _default as default };
package/dist/module.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { SEED_TASK, unscheduleSeed } from "./seed.js";
2
2
  import { resolveAppName, setupDev } from "./dev.js";
3
+ import { setupStudioPreview } from "./studio-preview.js";
4
+ import { PROCESS_ENV_TYPES } from "./env-types.js";
5
+ import { dependencyNames, ogImageDefaults, ogImageWarning } from "./og-image.js";
6
+ import { iconDefaults, iconSources, iconWarning } from "./icons.js";
3
7
  import { findRouteTools } from "./route-tools.js";
4
8
  import { localFiles, previewManifest } from "./preview.js";
5
9
  import { findShapeRoutes, shapeRoutesTypes, shapesManifest } from "./shapes.js";
@@ -9,7 +13,7 @@ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
9
13
  import { builtinModules, createRequire } from "node:module";
10
14
  import { dirname, extname, join, relative, resolve, sep } from "node:path";
11
15
  import evlog from "evlog/nuxt";
12
- //#region src/module.ts
16
+ // src/module.ts
13
17
  const CONTENT_DATABASE = {
14
18
  type: "postgres",
15
19
  url: "{{DATABASE_URL}}"
@@ -19,6 +23,19 @@ var module_default = defineNuxtModule({
19
23
  name: "@wervt/nuxt",
20
24
  configKey: "wervt"
21
25
  },
26
+ async moduleDependencies(nuxt) {
27
+ if (nuxt.options.dev) return {};
28
+ return {
29
+ "@nuxt/icon": {
30
+ optional: true,
31
+ defaults: iconDefaults()
32
+ },
33
+ "nuxt-og-image": {
34
+ optional: true,
35
+ defaults: ogImageDefaults()
36
+ }
37
+ };
38
+ },
22
39
  async setup(options, nuxt) {
23
40
  const resolver = createResolver(import.meta.url);
24
41
  const nitro = nuxt.options.nitro;
@@ -51,6 +68,7 @@ var module_default = defineNuxtModule({
51
68
  });
52
69
  }
53
70
  }
71
+ addServerPlugin(resolver.resolve("./runtime/server/agent-token-plugin"));
54
72
  const useContentDatabase = !nuxt.options.dev && hasNuxtModule("@nuxt/content", nuxt);
55
73
  if (useContentDatabase) {
56
74
  const options = nuxt.options;
@@ -123,6 +141,16 @@ var module_default = defineNuxtModule({
123
141
  };
124
142
  });
125
143
  }
144
+ const nuxtUi = hasNuxtModule("@nuxt/ui", nuxt);
145
+ if (!nuxt.options.dev && !nuxt.options._prepare && (nuxtUi || hasNuxtModule("@nuxt/icon", nuxt))) {
146
+ const warning = iconWarning(await iconSources(nuxt.options.rootDir), nuxtUi);
147
+ if (warning) logger.warn(warning);
148
+ }
149
+ nuxt.hook("modules:done", async () => {
150
+ if (nuxt.options.dev || nuxt.options._prepare || !hasNuxtModule("nuxt-og-image", nuxt)) return;
151
+ const warning = ogImageWarning(await dependencyNames(nuxt.options.rootDir));
152
+ if (warning) logger.warn(warning);
153
+ });
126
154
  addServerHandler({
127
155
  route: "/_wervt/boot",
128
156
  method: "get",
@@ -248,6 +276,7 @@ var module_default = defineNuxtModule({
248
276
  if (nuxt.options.dev && nitroMajor >= 3) {
249
277
  if (!hasPackage(nuxt.options.rootDir, "vue")) logger.warn("`vue` is not a dependency of this app, so `nuxt dev` fails with \"module is not defined\". Run `pnpm add vue`.");
250
278
  }
279
+ if (nuxt.options.dev && process.env.WERVT_PREVIEW) setupStudioPreview(nuxt);
251
280
  if (nuxt.options.dev && !nuxt.options._prepare) {
252
281
  const app = await resolveAppName(nuxt.options.rootDir, options.app);
253
282
  await setupDev(nuxt, app, options.dev ?? {}, { storage: useAblage });
@@ -256,6 +285,18 @@ var module_default = defineNuxtModule({
256
285
  filename: "types/wervt-email.d.ts",
257
286
  getContents: () => "declare module '*.vue' {\n import type { DefineComponent } from 'vue'\n const component: DefineComponent<Record<string, unknown>>\n export default component\n}\n"
258
287
  }, { nitro: true });
288
+ addTypeTemplate({
289
+ filename: "types/wervt-env.d.ts",
290
+ getContents: () => PROCESS_ENV_TYPES
291
+ }, { nitro: true });
292
+ const sourceEnv = resolver.resolve("./env.d.ts");
293
+ if (existsSync(sourceEnv)) addTypeTemplate({
294
+ filename: "types/wervt-source-env.d.ts",
295
+ getContents: () => `/// <reference path=${JSON.stringify(sourceEnv)} />\n`
296
+ }, {
297
+ nitro: true,
298
+ nuxt: true
299
+ });
259
300
  if (nuxt.options.dev) {
260
301
  const templates = await findEmailTemplates([join(nuxt.options.srcDir, "emails"), join(nuxt.options.rootDir, "emails")]);
261
302
  addServerTemplate({
@@ -496,5 +537,4 @@ function exceptRuntime(pattern) {
496
537
  if (pattern.source.startsWith("^")) return pattern;
497
538
  return new RegExp(`^(?![\\s\\S]*[\\\\/]@wervt[\\\\/]nuxt[\\\\/]src[\\\\/]runtime[\\\\/])[\\s\\S]*?(?:${pattern.source})`, pattern.flags);
498
539
  }
499
- //#endregion
500
540
  export { module_default as default };
@@ -1,6 +1,6 @@
1
1
  import { tasks } from "#nitro/virtual/tasks";
2
2
  import { HTTPError } from "h3";
3
- //#region ../../node_modules/.pnpm/nitro@3.0.260903-beta/node_modules/nitro/dist/runtime/internal/task.mjs
3
+ // ../../node_modules/.pnpm/nitro@3.0.260903-beta/node_modules/nitro/dist/runtime/internal/task.mjs
4
4
  const __runningTasks__ = {};
5
5
  async function runTask(name, { payload = {}, context = {} } = {}) {
6
6
  if (__runningTasks__[name]) return __runningTasks__[name];
@@ -25,5 +25,4 @@ async function runTask(name, { payload = {}, context = {} } = {}) {
25
25
  delete __runningTasks__[name];
26
26
  }
27
27
  }
28
- //#endregion
29
28
  export { runTask };
@@ -1,2 +1 @@
1
- import { runTask } from "./internal/task.js";
2
- export { runTask };
1
+ export { runTask } from "./internal/task.js";
@@ -0,0 +1,38 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ // src/og-image.ts
4
+ /** The app's dependencies and devDependencies, by name. */
5
+ async function dependencyNames(rootDir) {
6
+ const pkg = JSON.parse(await readFile(join(rootDir, "package.json"), "utf8").catch(() => "{}"));
7
+ return Object.keys({
8
+ ...pkg.dependencies,
9
+ ...pkg.devDependencies
10
+ });
11
+ }
12
+ /**
13
+ * nuxt-og-image defaults for a wervt build. It treats the deno-server preset like Node and picks
14
+ * the native renderers (N-API bindings, sharp, a local Chrome), which an Edge Runtime isolate
15
+ * can't load, so OG image requests fail with a 500. The WebAssembly builds run there; without
16
+ * sharp, images are PNG only. An app's own `ogImage` config wins over these.
17
+ */
18
+ function ogImageDefaults() {
19
+ return { compatibility: { runtime: {
20
+ takumi: "wasm",
21
+ satori: "wasm",
22
+ resvg: "wasm",
23
+ sharp: false,
24
+ browser: false
25
+ } } };
26
+ }
27
+ /**
28
+ * Why OG images won't render on wervt, if they won't. The WebAssembly package renders; the native
29
+ * one is needed too because nuxt-og-image checks for the packages of the preset's own defaults
30
+ * (Node for deno-server) and fails the build without them, whatever `compatibility` says.
31
+ */
32
+ function ogImageWarning(dependencies) {
33
+ const has = (name) => dependencies.includes(name);
34
+ const missing = [...has("@takumi-rs/core") || has("@takumi-rs/wasm") || !has("satori") ? ["@takumi-rs/core", "@takumi-rs/wasm"] : [], ...has("satori") ? ["@resvg/resvg-js", "@resvg/resvg-wasm"] : []].filter((name) => !has(name));
35
+ if (missing.length === 0) return void 0;
36
+ return `OG images render with WebAssembly on wervt (an isolate can't load native renderers), and nuxt-og-image's build also wants the native package, but ${missing.map((name) => `\`${name}\``).join(", ")} ${missing.length > 1 ? "are" : "is"} not installed. Run \`pnpm add ${missing.join(" ")}\`.`;
37
+ }
38
+ export { dependencyNames, ogImageDefaults, ogImageWarning };
package/dist/preview.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/preview.d.ts
2
1
  export interface PreviewOptions {
3
2
  /** Defaults to the app's `app.head.title`. */
4
3
  title?: string;
@@ -10,5 +9,4 @@ export interface PreviewOptions {
10
9
  icon?: string;
11
10
  /** Defaults to the app's `theme-color` meta tag. */
12
11
  themeColor?: string;
13
- }
14
- //#endregion
12
+ }
package/dist/preview.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/preview.ts
1
+ // src/preview.ts
2
2
  /**
3
3
  * What the runtime shows. Without `preview`, only the app's name and icon: for some private apps
4
4
  * the title alone says too much, so the rest of the head is only used once the app opts in.
@@ -35,5 +35,4 @@ function localFiles(manifest) {
35
35
  if (!manifest) return [];
36
36
  return [manifest.image, manifest.icon].filter((path) => !!path && path.startsWith("/") && !path.startsWith("//"));
37
37
  }
38
- //#endregion
39
38
  export { localFiles, previewManifest };
@@ -1,5 +1,4 @@
1
- import { StorageError, StorageOptions, StorageUsage, createStorage } from "./storage.js";
2
- //#region src/provision.d.ts
1
+ import { MAX_KEY_BYTES, ObjectDetails, ObjectListing, StorageError, StorageOptions, StorageQuota, StorageUsage, StoredObject, createStorage } from "./storage.js";
3
2
  export interface ProvisionOptions {
4
3
  /** App name, `[a-z0-9-]+`. */
5
4
  app: string;
@@ -9,7 +8,24 @@ export interface ProvisionOptions {
9
8
  secret: string;
10
9
  /** Drizzle migrations folder. Skipped when undefined. */
11
10
  migrationsFolder?: string;
11
+ /** Timeouts for the app's role; DEFAULT_ROLE_TIMEOUTS where not given. */
12
+ timeouts?: Partial<RoleTimeouts>;
12
13
  }
14
+ /** Postgres durations (`30s`, `500ms`, `2min`) for the app's role; `0` turns one off. */
15
+ export interface RoleTimeouts {
16
+ /** Longest a single statement may run. */
17
+ statement: string;
18
+ /** Longest a transaction may sit idle, holding its locks and its pooled connection. */
19
+ idleInTransaction: string;
20
+ }
21
+ export declare const DEFAULT_ROLE_TIMEOUTS: RoleTimeouts;
22
+ /** A Postgres duration as the env gives it, e.g. `WERVT_STATEMENT_TIMEOUT`; the fallback when empty. */
23
+ export declare function parseDuration(value: string | undefined, fallback: string, name?: string): string;
24
+ /**
25
+ * Sets the timeouts on an existing app role, for apps that aren't redeployed. New sessions get
26
+ * them; open ones keep theirs. Returns false when the app has no role.
27
+ */
28
+ export declare function setRoleTimeouts({ app, adminUrl, timeouts }: Pick<ProvisionOptions, 'app' | 'adminUrl' | 'timeouts'>): Promise<boolean>;
13
29
  export declare function dbRole(app: string): string;
14
30
  export declare function hmac(secret: string, message: string): Promise<string>;
15
31
  /** The app's Garage bucket and S3 key, derived from WERVT_DB_SECRET like role passwords. */
@@ -32,6 +48,18 @@ export declare function appDatabaseUrl({ app, adminUrl, secret }: ProvisionOptio
32
48
  export declare function provisionDatabase(options: ProvisionOptions): Promise<{
33
49
  fresh: boolean;
34
50
  }>;
51
+ /**
52
+ * Runs the Drizzle migrations as the app's own role, into its schema. The role needs CREATE on
53
+ * the database while this runs (see `provisionDatabase`). `fresh` is true when no migration had
54
+ * been applied before.
55
+ */
56
+ export declare function migrateAs({ url, schema, migrationsFolder }: {
57
+ url: string;
58
+ schema: string;
59
+ migrationsFolder: string;
60
+ }): Promise<{
61
+ fresh: boolean;
62
+ }>;
35
63
  /** The role PgBouncer signs in with to look up app roles' password hashes (`auth_query`). */
36
64
  export declare const POOLER_ROLE = "pgbouncer";
37
65
  /**
@@ -46,5 +74,4 @@ export declare function setupPooler({ adminUrl, password }: {
46
74
  }): Promise<void>;
47
75
  /** Drops the app's schema (all its data) and role. */
48
76
  export declare function dropDatabase({ app, adminUrl }: Pick<ProvisionOptions, 'app' | 'adminUrl'>): Promise<void>;
49
- //#endregion
50
- export { StorageError, type StorageOptions, type StorageUsage, createStorage };
77
+ export { MAX_KEY_BYTES, type ObjectDetails, type ObjectListing, StorageError, type StorageOptions, type StorageQuota, type StorageUsage, type StoredObject, createStorage };
package/dist/provision.js CHANGED
@@ -1,8 +1,53 @@
1
- import { StorageError, createStorage } from "./storage.js";
1
+ import { MAX_KEY_BYTES, StorageError, createStorage } from "./storage.js";
2
2
  import { drizzle } from "drizzle-orm/postgres-js";
3
3
  import { migrate } from "drizzle-orm/postgres-js/migrator";
4
4
  import postgres from "postgres";
5
- //#region src/provision.ts
5
+ // src/provision.ts
6
+ const DEFAULT_ROLE_TIMEOUTS = {
7
+ statement: "30s",
8
+ idleInTransaction: "60s"
9
+ };
10
+ const DURATION_RE = /^\d+(?:ms|s|min|h)?$/;
11
+ /** A Postgres duration as the env gives it, e.g. `WERVT_STATEMENT_TIMEOUT`; the fallback when empty. */
12
+ function parseDuration(value, fallback, name = "timeout") {
13
+ const trimmed = value?.trim();
14
+ if (!trimmed) return fallback;
15
+ if (!DURATION_RE.test(trimmed)) throw new Error(`${name} must be a duration like 30s, 500ms or 2min (0 for none)`);
16
+ return trimmed;
17
+ }
18
+ function roleTimeouts(timeouts) {
19
+ const merged = {
20
+ ...DEFAULT_ROLE_TIMEOUTS,
21
+ ...timeouts
22
+ };
23
+ return {
24
+ statement: parseDuration(merged.statement, DEFAULT_ROLE_TIMEOUTS.statement),
25
+ idleInTransaction: parseDuration(merged.idleInTransaction, DEFAULT_ROLE_TIMEOUTS.idleInTransaction)
26
+ };
27
+ }
28
+ async function applyRoleTimeouts(admin, role, timeouts) {
29
+ await admin.unsafe(`alter role "${role}" set statement_timeout = '${timeouts.statement}'`);
30
+ await admin.unsafe(`alter role "${role}" set idle_in_transaction_session_timeout = '${timeouts.idleInTransaction}'`);
31
+ }
32
+ /**
33
+ * Sets the timeouts on an existing app role, for apps that aren't redeployed. New sessions get
34
+ * them; open ones keep theirs. Returns false when the app has no role.
35
+ */
36
+ async function setRoleTimeouts({ app, adminUrl, timeouts }) {
37
+ const role = dbRole(app);
38
+ const admin = postgres(adminUrl, {
39
+ max: 1,
40
+ onnotice: () => {}
41
+ });
42
+ try {
43
+ const [row] = await admin`select exists(select 1 from pg_roles where rolname = ${role})`;
44
+ if (!row?.exists) return false;
45
+ await applyRoleTimeouts(admin, role, roleTimeouts(timeouts));
46
+ return true;
47
+ } finally {
48
+ await admin.end();
49
+ }
50
+ }
6
51
  function dbRole(app) {
7
52
  return `app_${app.replaceAll("-", "_")}`;
8
53
  }
@@ -56,6 +101,7 @@ async function provisionDatabase(options) {
56
101
  await admin.unsafe(`create schema if not exists "${role}" authorization "${role}"`);
57
102
  await claimSchema(admin, role);
58
103
  await admin.unsafe(`alter role "${role}" set search_path = "${role}"`);
104
+ await applyRoleTimeouts(admin, role, roleTimeouts(options.timeouts));
59
105
  for (const db of [
60
106
  database,
61
107
  "postgres",
@@ -64,34 +110,50 @@ async function provisionDatabase(options) {
64
110
  await admin.unsafe(`grant connect on database "${database}" to "${role}"`);
65
111
  await admin.unsafe(`revoke all on schema public from public`);
66
112
  if (!migrationsFolder) return { fresh: false };
67
- const table = `"${role}"."__drizzle_migrations"`;
68
- const [state] = await admin`
69
- select to_regclass(${table}) is not null as exists`;
70
- let fresh = !state?.exists;
71
- if (!fresh) {
72
- const [row] = await admin.unsafe(`select exists(select 1 from ${table}) as applied`);
73
- fresh = !row?.applied;
74
- }
75
113
  await admin.unsafe(`grant create on database "${database}" to "${role}"`);
76
- const client = postgres(await appDatabaseUrl(options), {
77
- max: 1,
78
- onnotice: () => {}
79
- });
80
114
  try {
81
- await migrate(drizzle({ client }), {
82
- migrationsFolder,
83
- migrationsSchema: role
115
+ return await migrateAs({
116
+ url: await appDatabaseUrl(options),
117
+ schema: role,
118
+ migrationsFolder
84
119
  });
85
120
  } finally {
86
- await client.end();
87
121
  await admin.unsafe(`revoke create on database "${database}" from "${role}"`);
88
122
  }
89
- return { fresh };
90
123
  } finally {
91
124
  await admin.end();
92
125
  }
93
126
  }
94
127
  /**
128
+ * Runs the Drizzle migrations as the app's own role, into its schema. The role needs CREATE on
129
+ * the database while this runs (see `provisionDatabase`). `fresh` is true when no migration had
130
+ * been applied before.
131
+ */
132
+ async function migrateAs({ url, schema, migrationsFolder }) {
133
+ const client = postgres(url, {
134
+ max: 1,
135
+ onnotice: () => {}
136
+ });
137
+ try {
138
+ const table = `"${schema}"."__drizzle_migrations"`;
139
+ const [state] = await client`
140
+ select to_regclass(${table}) is not null as exists`;
141
+ let fresh = !state?.exists;
142
+ if (!fresh) {
143
+ const [row] = await client.unsafe(`select exists(select 1 from ${table}) as applied`);
144
+ fresh = !row?.applied;
145
+ }
146
+ await client.unsafe("set statement_timeout = 0; set idle_in_transaction_session_timeout = 0");
147
+ await migrate(drizzle({ client }), {
148
+ migrationsFolder,
149
+ migrationsSchema: schema
150
+ });
151
+ return { fresh };
152
+ } finally {
153
+ await client.end();
154
+ }
155
+ }
156
+ /**
95
157
  * Gives the role its schema and everything in it back. A dump restored with `--no-owner`
96
158
  * (Coolify's backups) leaves all of it owned by the restoring superuser.
97
159
  */
@@ -173,5 +235,4 @@ async function dropDatabase({ app, adminUrl }) {
173
235
  await admin.end();
174
236
  }
175
237
  }
176
- //#endregion
177
- export { POOLER_ROLE, StorageError, appDatabaseUrl, createStorage, dbPassword, dbRole, dropDatabase, hmac, provisionDatabase, setupPooler, storageCredentials };
238
+ export { DEFAULT_ROLE_TIMEOUTS, MAX_KEY_BYTES, POOLER_ROLE, StorageError, appDatabaseUrl, createStorage, dbPassword, dbRole, dropDatabase, hmac, migrateAs, parseDuration, provisionDatabase, setRoleTimeouts, setupPooler, storageCredentials };
@@ -1,6 +1,6 @@
1
1
  import { readFile, readdir } from "node:fs/promises";
2
2
  import { extname, join, sep } from "node:path";
3
- //#region src/route-tools.ts
3
+ // src/route-tools.ts
4
4
  const EXTENSIONS = /* @__PURE__ */ new Set([
5
5
  ".ts",
6
6
  ".js",
@@ -40,5 +40,4 @@ async function findRouteTools(dir, prefix = []) {
40
40
  }
41
41
  return found;
42
42
  }
43
- //#endregion
44
43
  export { findRouteTools, routeToolName };
@@ -1,4 +1,2 @@
1
- //#region src/runtime/app/plugins/analytics.client.d.ts
2
1
  declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
3
- //#endregion
4
2
  export { _default as default };
@@ -1,5 +1,5 @@
1
1
  import { defineNuxtPlugin, useRouter, useRuntimeConfig } from "#app";
2
- //#region src/runtime/app/plugins/analytics.client.ts
2
+ // src/runtime/app/plugins/analytics.client.ts
3
3
  var analytics_client_default = defineNuxtPlugin({
4
4
  name: "wervt:analytics",
5
5
  setup() {
@@ -12,5 +12,4 @@ var analytics_client_default = defineNuxtPlugin({
12
12
  });
13
13
  }
14
14
  });
15
- //#endregion
16
15
  export { analytics_client_default as default };
@@ -1,4 +1,2 @@
1
- //#region src/runtime/app/plugins/feedback.client.d.ts
2
1
  declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
3
- //#endregion
4
2
  export { _default as default };
@@ -1,6 +1,6 @@
1
1
  import { pendingFeedbackContext } from "../useWervtFeedback.js";
2
2
  import { defineNuxtPlugin, onNuxtReady, useRuntimeConfig } from "#app";
3
- //#region src/runtime/app/plugins/feedback.client.ts
3
+ // src/runtime/app/plugins/feedback.client.ts
4
4
  var feedback_client_default = defineNuxtPlugin({
5
5
  name: "wervt:feedback",
6
6
  setup() {
@@ -28,5 +28,4 @@ var feedback_client_default = defineNuxtPlugin({
28
28
  });
29
29
  }
30
30
  });
31
- //#endregion
32
31
  export { feedback_client_default as default };
@@ -1,4 +1,2 @@
1
- //#region src/runtime/app/plugins/update.client.d.ts
2
1
  declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
3
- //#endregion
4
2
  export { _default as default };