@evlog/nuxthub 0.0.1-alpha.1 → 0.0.1-alpha.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.
@@ -0,0 +1,5 @@
1
+ module.exports = function(...args) {
2
+ return import('./module.mjs').then(m => m.default.call(this, ...args))
3
+ }
4
+ const _meta = module.exports.meta = require('./module.json')
5
+ module.exports.getMeta = () => Promise.resolve(_meta)
package/dist/module.d.mts CHANGED
@@ -1,15 +1,5 @@
1
- import * as _nuxt_schema0 from "@nuxt/schema";
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
- //#region src/module.d.ts
4
- interface ModuleOptions {
5
- /**
6
- * How long to retain events before cleanup.
7
- * Supports "30d" (days), "24h" (hours), "60m" (minutes).
8
- * @default '30d'
9
- */
10
- retention?: string;
11
- }
12
- declare const _default: _nuxt_schema0.NuxtModule<ModuleOptions, ModuleOptions, false>;
13
- //#endregion
14
- export { ModuleOptions, _default as default };
15
- //# sourceMappingURL=module.d.mts.map
3
+ declare const _default: _nuxt_schema.NuxtModule<_nuxt_schema.ModuleOptions, _nuxt_schema.ModuleOptions, false>;
4
+
5
+ export { _default as default };
@@ -0,0 +1,5 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ declare const _default: _nuxt_schema.NuxtModule<_nuxt_schema.ModuleOptions, _nuxt_schema.ModuleOptions, false>;
4
+
5
+ export { _default as default };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "@evlog/nuxthub",
3
+ "version": "0.0.1-alpha.1",
4
+ "configKey": "@evlog/nuxthub",
5
+ "builder": {
6
+ "@nuxt/module-builder": "0.8.4",
7
+ "unbuild": "2.0.0"
8
+ }
9
+ }
package/dist/module.mjs CHANGED
@@ -1,99 +1,130 @@
1
- import { existsSync, promises } from "node:fs";
2
- import { fileURLToPath } from "node:url";
3
- import { join, resolve } from "node:path";
4
- import { addServerHandler, addServerPlugin, defineNuxtModule } from "@nuxt/kit";
5
- import { consola } from "consola";
6
- import { createEvlogError } from "evlog";
1
+ import { existsSync, promises } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { defineNuxtModule, createResolver, hasNuxtModule, installModule, addTypeTemplate, addServerPlugin, addServerHandler } from '@nuxt/kit';
4
+ import { consola } from 'consola';
5
+ import { createEvlogError } from 'evlog';
7
6
 
8
- //#region src/module.ts
9
7
  function retentionToCron(retention) {
10
- const match = retention.match(/^(\d+)(d|h|m)$/);
11
- if (!match) throw createEvlogError({
12
- message: `[evlog/nuxthub] Invalid retention format: "${retention}"`,
13
- why: "The retention value must be a number followed by a unit: d (days), h (hours), or m (minutes)",
14
- fix: `Change retention to a valid format, e.g., "30d", "24h", or "60m"`,
15
- link: "https://evlog.dev/nuxthub/retention"
16
- });
17
- const [, numStr, unit] = match;
18
- const num = Number(numStr);
19
- let totalMinutes;
20
- switch (unit) {
21
- case "m":
22
- totalMinutes = num;
23
- break;
24
- case "h":
25
- totalMinutes = num * 60;
26
- break;
27
- case "d":
28
- totalMinutes = num * 24 * 60;
29
- break;
30
- default: throw createEvlogError({
31
- message: `[evlog/nuxthub] Unknown retention unit: "${unit}"`,
32
- why: "The retention value must use one of the supported units: d (days), h (hours), or m (minutes)",
33
- fix: `Change retention to a valid format, e.g., "30d", "24h", or "60m"`,
34
- link: "https://evlog.dev/nuxthub/retention"
35
- });
36
- }
37
- const halfMinutes = Math.max(1, Math.floor(totalMinutes / 2));
38
- if (halfMinutes < 60) return `*/${halfMinutes} * * * *`;
39
- const halfHours = Math.floor(halfMinutes / 60);
40
- if (halfHours >= 24) return "0 3 * * *";
41
- return `0 */${halfHours} * * *`;
8
+ const match = retention.match(/^(\d+)(d|h|m)$/);
9
+ if (!match) {
10
+ throw createEvlogError({
11
+ message: `[evlog/nuxthub] Invalid retention format: "${retention}"`,
12
+ why: "The retention value must be a number followed by a unit: d (days), h (hours), or m (minutes)",
13
+ fix: `Change retention to a valid format, e.g., "30d", "24h", or "60m"`,
14
+ link: "https://evlog.dev/nuxthub/retention"
15
+ });
16
+ }
17
+ const [, numStr, unit] = match;
18
+ const num = Number(numStr);
19
+ let totalMinutes;
20
+ switch (unit) {
21
+ case "m":
22
+ totalMinutes = num;
23
+ break;
24
+ case "h":
25
+ totalMinutes = num * 60;
26
+ break;
27
+ case "d":
28
+ totalMinutes = num * 24 * 60;
29
+ break;
30
+ default:
31
+ throw createEvlogError({
32
+ message: `[evlog/nuxthub] Unknown retention unit: "${unit}"`,
33
+ why: "The retention value must use one of the supported units: d (days), h (hours), or m (minutes)",
34
+ fix: `Change retention to a valid format, e.g., "30d", "24h", or "60m"`,
35
+ link: "https://evlog.dev/nuxthub/retention"
36
+ });
37
+ }
38
+ const halfMinutes = Math.max(1, Math.floor(totalMinutes / 2));
39
+ if (halfMinutes < 60) {
40
+ return `*/${halfMinutes} * * * *`;
41
+ }
42
+ const halfHours = Math.floor(halfMinutes / 60);
43
+ if (halfHours >= 24) {
44
+ return "0 3 * * *";
45
+ }
46
+ return `0 */${halfHours} * * *`;
42
47
  }
43
- var module_default = defineNuxtModule({
44
- meta: {
45
- name: "@evlog/nuxthub",
46
- version: "0.0.1-alpha.1"
47
- },
48
- moduleDependencies: { "evlog/nuxt": {} },
49
- async onInstall(nuxt) {
50
- const shouldSetup = await consola.prompt("Do you want to create a vercel.json with a cron schedule for evlog cleanup?", {
51
- type: "confirm",
52
- initial: false
53
- });
54
- if (typeof shouldSetup !== "boolean" || !shouldSetup) return;
55
- const vercelJsonPath = resolve(nuxt.options.rootDir, "vercel.json");
56
- let config = {};
57
- if (existsSync(vercelJsonPath)) config = JSON.parse(await promises.readFile(vercelJsonPath, "utf-8"));
58
- const cron = retentionToCron((nuxt.options.evlog || {}).retention ?? "30d");
59
- const crons = config.crons || [];
60
- const existing = crons.findIndex((c) => c.path === "/api/_cron/evlog-cleanup");
61
- if (existing >= 0) crons[existing].schedule = cron;
62
- else crons.push({
63
- path: "/api/_cron/evlog-cleanup",
64
- schedule: cron
65
- });
66
- config.crons = crons;
67
- await promises.writeFile(vercelJsonPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
68
- consola.success("Created vercel.json with evlog cleanup cron schedule");
69
- },
70
- setup(_moduleOptions, nuxt) {
71
- const options = { retention: (nuxt.options.evlog || {}).retention ?? "30d" };
72
- const srcDir = resolve(fileURLToPath(new URL(".", import.meta.url)), "..", "src");
73
- const runtimeDir = join(srcDir, "runtime");
74
- nuxt.hook("hub:db:schema:extend", ({ paths, dialect }) => {
75
- paths.push(resolve(srcDir, "schema", `${dialect}.ts`));
76
- });
77
- addServerPlugin(join(runtimeDir, "drain"));
78
- addServerHandler({
79
- route: "/api/_cron/evlog-cleanup",
80
- handler: join(runtimeDir, "api", "_cron", "evlog-cleanup")
81
- });
82
- nuxt.hook("nitro:config", (nitroConfig) => {
83
- nitroConfig.experimental = nitroConfig.experimental || {};
84
- nitroConfig.experimental.tasks = true;
85
- nitroConfig.tasks = nitroConfig.tasks || {};
86
- nitroConfig.tasks["evlog:cleanup"] = { handler: join(runtimeDir, "tasks", "evlog-cleanup") };
87
- const cron = retentionToCron(options.retention);
88
- nitroConfig.scheduledTasks = nitroConfig.scheduledTasks || {};
89
- const existing = nitroConfig.scheduledTasks[cron];
90
- if (Array.isArray(existing)) existing.push("evlog:cleanup");
91
- else if (existing) nitroConfig.scheduledTasks[cron] = [existing, "evlog:cleanup"];
92
- else nitroConfig.scheduledTasks[cron] = ["evlog:cleanup"];
93
- });
94
- }
48
+ const module = defineNuxtModule({
49
+ meta: {
50
+ name: "@evlog/nuxthub",
51
+ version: "0.0.1-alpha.1"
52
+ },
53
+ async onInstall(nuxt) {
54
+ const shouldSetup = await consola.prompt(
55
+ "Do you want to create a vercel.json with a cron schedule for evlog cleanup?",
56
+ { type: "confirm", initial: false }
57
+ );
58
+ if (typeof shouldSetup !== "boolean" || !shouldSetup)
59
+ return;
60
+ const vercelJsonPath = resolve(nuxt.options.rootDir, "vercel.json");
61
+ let config = {};
62
+ if (existsSync(vercelJsonPath)) {
63
+ config = JSON.parse(await promises.readFile(vercelJsonPath, "utf-8"));
64
+ }
65
+ const evlogConfig = nuxt.options.evlog || {};
66
+ const retention = evlogConfig.retention ?? "30d";
67
+ const cron = retentionToCron(retention);
68
+ const crons = config.crons || [];
69
+ const existing = crons.findIndex((c) => c.path === "/api/_cron/evlog-cleanup");
70
+ if (existing >= 0) {
71
+ crons[existing].schedule = cron;
72
+ } else {
73
+ crons.push({ path: "/api/_cron/evlog-cleanup", schedule: cron });
74
+ }
75
+ config.crons = crons;
76
+ await promises.writeFile(vercelJsonPath, `${JSON.stringify(config, null, 2)}
77
+ `, "utf-8");
78
+ consola.success("Created vercel.json with evlog cleanup cron schedule");
79
+ },
80
+ async setup(_moduleOptions, nuxt) {
81
+ const { resolve: resolveModule } = createResolver(import.meta.url);
82
+ if (!hasNuxtModule("evlog/nuxt")) {
83
+ await installModule("evlog/nuxt");
84
+ }
85
+ if (!hasNuxtModule("@nuxthub/core")) {
86
+ await installModule("@nuxthub/core");
87
+ }
88
+ addTypeTemplate({
89
+ filename: "types/evlog-nuxthub.d.ts",
90
+ getContents: () => [
91
+ "declare module 'evlog/nuxt' {",
92
+ " interface ModuleOptions {",
93
+ " retention?: string",
94
+ " }",
95
+ "}",
96
+ "export {}"
97
+ ].join("\n")
98
+ });
99
+ const evlogConfig = nuxt.options.evlog || {};
100
+ const retention = evlogConfig.retention ?? "30d";
101
+ nuxt.hook("hub:db:schema:extend", ({ paths, dialect }) => {
102
+ paths.push(resolveModule(`../src/schema/${dialect}.ts`));
103
+ });
104
+ addServerPlugin(resolveModule("./runtime/drain"));
105
+ addServerHandler({
106
+ route: "/api/_cron/evlog-cleanup",
107
+ handler: resolveModule("./runtime/api/_cron/evlog-cleanup")
108
+ });
109
+ nuxt.hook("nitro:config", (nitroConfig) => {
110
+ nitroConfig.experimental = nitroConfig.experimental || {};
111
+ nitroConfig.experimental.tasks = true;
112
+ nitroConfig.tasks = nitroConfig.tasks || {};
113
+ nitroConfig.tasks["evlog:cleanup"] = {
114
+ handler: resolveModule("./runtime/tasks/evlog-cleanup")
115
+ };
116
+ const cron = retentionToCron(retention);
117
+ nitroConfig.scheduledTasks = nitroConfig.scheduledTasks || {};
118
+ const existing = nitroConfig.scheduledTasks[cron];
119
+ if (Array.isArray(existing)) {
120
+ existing.push("evlog:cleanup");
121
+ } else if (existing) {
122
+ nitroConfig.scheduledTasks[cron] = [existing, "evlog:cleanup"];
123
+ } else {
124
+ nitroConfig.scheduledTasks[cron] = ["evlog:cleanup"];
125
+ }
126
+ });
127
+ }
95
128
  });
96
129
 
97
- //#endregion
98
- export { module_default as default };
99
- //# sourceMappingURL=module.mjs.map
130
+ export { module as default };
@@ -0,0 +1,6 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ result?: unknown;
3
+ success: boolean;
4
+ }>>;
5
+ export default _default;
6
+ //# sourceMappingURL=evlog-cleanup.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { runTask } from "nitropack/runtime";
2
+ import { eventHandler } from "h3";
3
+ export default eventHandler(async () => {
4
+ const result = await runTask("evlog:cleanup");
5
+ return { success: true, ...result };
6
+ });
@@ -0,0 +1,3 @@
1
+ declare const _default: import("nitropack").NitroAppPlugin;
2
+ export default _default;
3
+ //# sourceMappingURL=drain.d.ts.map
@@ -0,0 +1,70 @@
1
+ import { defineNitroPlugin } from "nitropack/runtime";
2
+ import { db, schema } from "@nuxthub/db";
3
+ function parseDurationMs(event) {
4
+ if (typeof event.durationMs === "number") return event.durationMs;
5
+ if (typeof event.duration === "number") return event.duration;
6
+ if (typeof event.duration === "string") {
7
+ const str = event.duration;
8
+ const msMatch = str.match(/^([\d.]+)\s*ms$/);
9
+ if (msMatch) return Math.round(Number.parseFloat(msMatch[1]));
10
+ const sMatch = str.match(/^([\d.]+)\s*s$/);
11
+ if (sMatch) return Math.round(Number.parseFloat(sMatch[1]) * 1e3);
12
+ }
13
+ return null;
14
+ }
15
+ function extractRow(ctx) {
16
+ const { event, request } = ctx;
17
+ const indexed = /* @__PURE__ */ new Set([
18
+ "timestamp",
19
+ "level",
20
+ "service",
21
+ "environment",
22
+ "method",
23
+ "path",
24
+ "status",
25
+ "durationMs",
26
+ "duration",
27
+ "requestId",
28
+ "source",
29
+ "error"
30
+ ]);
31
+ const data = {};
32
+ for (const [key, value] of Object.entries(event)) {
33
+ if (!indexed.has(key) && value !== void 0) {
34
+ data[key] = value;
35
+ }
36
+ }
37
+ const errorValue = event.error;
38
+ let errorJson = null;
39
+ if (errorValue !== void 0 && errorValue !== null) {
40
+ errorJson = typeof errorValue === "string" ? errorValue : JSON.stringify(errorValue);
41
+ }
42
+ return {
43
+ id: crypto.randomUUID(),
44
+ timestamp: event.timestamp,
45
+ level: event.level,
46
+ service: event.service,
47
+ environment: event.environment,
48
+ method: (request?.method ?? event.method) || null,
49
+ path: (request?.path ?? event.path) || null,
50
+ status: typeof event.status === "number" ? event.status : null,
51
+ durationMs: parseDurationMs(event),
52
+ requestId: (request?.requestId ?? event.requestId) || null,
53
+ source: event.source || null,
54
+ error: errorJson,
55
+ data: Object.keys(data).length > 0 ? JSON.stringify(data) : null,
56
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
57
+ };
58
+ }
59
+ export default defineNitroPlugin((nitroApp) => {
60
+ nitroApp.hooks.hook("evlog:drain", async (ctx) => {
61
+ try {
62
+ const contexts = Array.isArray(ctx) ? ctx : [ctx];
63
+ if (contexts.length === 0) return;
64
+ const rows = contexts.map(extractRow);
65
+ await db.insert(schema.evlogEvents).values(rows);
66
+ } catch (error) {
67
+ console.error("[evlog/nuxthub] Failed to insert events:", error);
68
+ }
69
+ });
70
+ });
@@ -0,0 +1,3 @@
1
+ declare const _default: import("nitropack").Task<string>;
2
+ export default _default;
3
+ //# sourceMappingURL=evlog-cleanup.d.ts.map
@@ -0,0 +1,51 @@
1
+ import { defineTask, useRuntimeConfig } from "nitropack/runtime";
2
+ import { lt } from "drizzle-orm";
3
+ import { db, schema } from "@nuxthub/db";
4
+ import { createEvlogError } from "evlog";
5
+ function parseRetention(retention) {
6
+ const match = retention.match(/^(\d+)(d|h|m)$/);
7
+ if (!match) {
8
+ throw createEvlogError({
9
+ message: `[evlog/nuxthub] Invalid retention format: "${retention}"`,
10
+ why: "The retention value must be a number followed by a unit: d (days), h (hours), or m (minutes)",
11
+ fix: `Change retention to a valid format, e.g., "30d", "24h", or "60m"`,
12
+ link: "https://evlog.dev/nuxthub/retention"
13
+ });
14
+ }
15
+ const [, value, unit] = match;
16
+ switch (unit) {
17
+ case "d":
18
+ return Number(value) * 24 * 60 * 60 * 1e3;
19
+ case "h":
20
+ return Number(value) * 60 * 60 * 1e3;
21
+ case "m":
22
+ return Number(value) * 60 * 1e3;
23
+ default:
24
+ throw createEvlogError({
25
+ message: `[evlog/nuxthub] Unknown retention unit: "${unit}"`,
26
+ why: "The retention value must use one of the supported units: d (days), h (hours), or m (minutes)",
27
+ fix: `Change retention to a valid format, e.g., "30d", "24h", or "60m"`,
28
+ link: "https://evlog.dev/nuxthub/retention"
29
+ });
30
+ }
31
+ }
32
+ export default defineTask({
33
+ meta: {
34
+ name: "evlog:cleanup",
35
+ description: "Clean up expired evlog events based on retention policy"
36
+ },
37
+ async run() {
38
+ const config = useRuntimeConfig();
39
+ const retention = config.evlog?.retention ?? "30d";
40
+ const retentionMs = parseRetention(retention);
41
+ const cutoff = new Date(Date.now() - retentionMs).toISOString();
42
+ try {
43
+ const result = await db.delete(schema.evlogEvents).where(lt(schema.evlogEvents.createdAt, cutoff));
44
+ console.log(`[evlog/nuxthub] Cleanup: deleted events older than ${retention} (before ${cutoff})`, result);
45
+ return { result: "success" };
46
+ } catch (error) {
47
+ console.error("[evlog/nuxthub] Cleanup task failed:", error);
48
+ return { result: "error" };
49
+ }
50
+ }
51
+ });
@@ -0,0 +1,7 @@
1
+ import type { NuxtModule } from '@nuxt/schema'
2
+
3
+ import type { default as Module } from './module.js'
4
+
5
+ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
+
7
+ export { default } from './module.js'
@@ -0,0 +1,7 @@
1
+ import type { NuxtModule } from '@nuxt/schema'
2
+
3
+ import type { default as Module } from './module'
4
+
5
+ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
+
7
+ export { default } from './module'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evlog/nuxthub",
3
- "version": "0.0.1-alpha.1",
3
+ "version": "0.0.1-alpha.10",
4
4
  "description": "Self-hosted log retention for evlog using NuxtHub database storage",
5
5
  "author": "HugoRCD <contact@hrcd.fr>",
6
6
  "homepage": "https://evlog.dev",
@@ -13,40 +13,26 @@
13
13
  "type": "module",
14
14
  "exports": {
15
15
  ".": {
16
- "types": "./dist/module.d.mts",
16
+ "types": "./dist/types.d.mts",
17
17
  "import": "./dist/module.mjs"
18
- },
19
- "./schema/sqlite": {
20
- "types": "./dist/schema/sqlite.d.mts",
21
- "import": "./dist/schema/sqlite.mjs"
22
- },
23
- "./schema/postgresql": {
24
- "types": "./dist/schema/postgresql.d.mts",
25
- "import": "./dist/schema/postgresql.mjs"
26
- },
27
- "./schema/mysql": {
28
- "types": "./dist/schema/mysql.d.mts",
29
- "import": "./dist/schema/mysql.mjs"
30
18
  }
31
19
  },
32
20
  "main": "./dist/module.mjs",
33
- "types": "./dist/module.d.mts",
21
+ "types": "./dist/types.d.mts",
34
22
  "files": [
35
23
  "dist",
36
- "src/runtime",
37
- "src/schema",
38
- "README.md"
24
+ "src/schema"
39
25
  ],
40
26
  "scripts": {
41
- "build": "tsdown",
42
- "dev:prepare": "tsdown"
27
+ "build": "nuxt-module-build build",
28
+ "dev:prepare": "nuxt-module-build prepare"
43
29
  },
44
30
  "dependencies": {
45
- "evlog": "workspace:*",
31
+ "evlog": "^1.7.0",
46
32
  "@nuxthub/core": "^0.10.6"
47
33
  },
48
34
  "devDependencies": {
49
- "tsdown": "^0.20.3",
35
+ "@nuxt/module-builder": "^0.8.4",
50
36
  "typescript": "^5.9.3"
51
37
  },
52
38
  "peerDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"module.d.mts","names":[],"sources":["../src/module.ts"],"mappings":";;;UAQiB,aAAA;;;AAAjB;;;EAME,SAAA;AAAA;AAAA,cACD,QAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"module.mjs","names":["fsp"],"sources":["../src/module.ts"],"sourcesContent":["import { existsSync, promises as fsp } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { join, resolve } from 'node:path'\nimport { addServerHandler, addServerPlugin, defineNuxtModule } from '@nuxt/kit'\nimport { consola } from 'consola'\nimport type { NitroConfig } from 'nitropack'\nimport { createEvlogError } from 'evlog'\n\nexport interface ModuleOptions {\n /**\n * How long to retain events before cleanup.\n * Supports \"30d\" (days), \"24h\" (hours), \"60m\" (minutes).\n * @default '30d'\n */\n retention?: string\n}\n\nfunction retentionToCron(retention: string): string {\n const match = retention.match(/^(\\d+)(d|h|m)$/)\n if (!match) {\n throw createEvlogError({\n message: `[evlog/nuxthub] Invalid retention format: \"${retention}\"`,\n why: 'The retention value must be a number followed by a unit: d (days), h (hours), or m (minutes)',\n fix: `Change retention to a valid format, e.g., \"30d\", \"24h\", or \"60m\"`,\n link: 'https://evlog.dev/nuxthub/retention',\n })\n }\n\n const [, numStr, unit] = match\n const num = Number(numStr)\n\n // Convert retention to minutes\n let totalMinutes: number\n switch (unit) {\n case 'm':\n totalMinutes = num\n break\n case 'h':\n totalMinutes = num * 60\n break\n case 'd':\n totalMinutes = num * 24 * 60\n break\n default:\n throw createEvlogError({\n message: `[evlog/nuxthub] Unknown retention unit: \"${unit}\"`,\n why: 'The retention value must use one of the supported units: d (days), h (hours), or m (minutes)',\n fix: `Change retention to a valid format, e.g., \"30d\", \"24h\", or \"60m\"`,\n link: 'https://evlog.dev/nuxthub/retention',\n })\n }\n\n // Cleanup runs every half-retention period\n const halfMinutes = Math.max(1, Math.floor(totalMinutes / 2))\n\n if (halfMinutes < 60) {\n return `*/${halfMinutes} * * * *`\n }\n\n const halfHours = Math.floor(halfMinutes / 60)\n if (halfHours >= 24) {\n return '0 3 * * *'\n }\n\n return `0 */${halfHours} * * *`\n}\n\nexport default defineNuxtModule<ModuleOptions>({\n meta: {\n name: '@evlog/nuxthub',\n version: '0.0.1-alpha.1',\n },\n moduleDependencies: {\n 'evlog/nuxt': {},\n },\n async onInstall(nuxt) {\n const shouldSetup = await consola.prompt(\n 'Do you want to create a vercel.json with a cron schedule for evlog cleanup?',\n { type: 'confirm', initial: false },\n )\n if (typeof shouldSetup !== 'boolean' || !shouldSetup) return\n\n const vercelJsonPath = resolve(nuxt.options.rootDir, 'vercel.json')\n let config: Record<string, any> = {}\n if (existsSync(vercelJsonPath)) {\n config = JSON.parse(await fsp.readFile(vercelJsonPath, 'utf-8'))\n }\n\n const evlogConfig = (nuxt.options as any).evlog || {}\n const retention = evlogConfig.retention ?? '30d'\n const cron = retentionToCron(retention)\n\n const crons: Array<{ path: string, schedule: string }> = config.crons || []\n const existing = crons.findIndex(c => c.path === '/api/_cron/evlog-cleanup')\n if (existing >= 0) {\n crons[existing].schedule = cron\n } else {\n crons.push({ path: '/api/_cron/evlog-cleanup', schedule: cron })\n }\n config.crons = crons\n\n await fsp.writeFile(vercelJsonPath, `${JSON.stringify(config, null, 2)}\\n`, 'utf-8')\n consola.success('Created vercel.json with evlog cleanup cron schedule')\n },\n setup(_moduleOptions, nuxt) {\n // Read nuxthub options from evlog config key\n const evlogConfig = (nuxt.options as any).evlog || {}\n const options: Required<ModuleOptions> = {\n retention: evlogConfig.retention ?? '30d',\n }\n\n // Runtime files must be resolved from src/ so Nitro can bundle them\n // and resolve virtual imports like @nuxthub/db\n const distDir = fileURLToPath(new URL('.', import.meta.url))\n const srcDir = resolve(distDir, '..', 'src')\n const runtimeDir = join(srcDir, 'runtime')\n\n // Extend NuxtHub DB schema with dialect-specific evlog_events table\n // @ts-expect-error hub:db:schema:extend hook exists but is not in NuxtHooks type\n nuxt.hook('hub:db:schema:extend', ({ paths, dialect }: { paths: string[], dialect: string }) => {\n paths.push(resolve(srcDir, 'schema', `${dialect}.ts`))\n })\n\n // Register the drain server plugin\n addServerPlugin(join(runtimeDir, 'drain'))\n\n // Register the cron API route (works as Vercel cron target or manual trigger)\n addServerHandler({\n route: '/api/_cron/evlog-cleanup',\n handler: join(runtimeDir, 'api', '_cron', 'evlog-cleanup'),\n })\n\n // Register the cleanup task with automatic cron schedule based on retention\n // @ts-expect-error nitro:config hook exists but is not in NuxtHooks type\n nuxt.hook('nitro:config', (nitroConfig: NitroConfig) => {\n // Enable experimental tasks\n nitroConfig.experimental = nitroConfig.experimental || {}\n nitroConfig.experimental.tasks = true\n\n // Register the task handler\n nitroConfig.tasks = nitroConfig.tasks || {}\n nitroConfig.tasks['evlog:cleanup'] = {\n handler: join(runtimeDir, 'tasks', 'evlog-cleanup'),\n }\n\n // Schedule based on retention (e.g., 1m → every 1 min, 1h → every 30 min, 30d → daily 3AM)\n const cron = retentionToCron(options.retention!)\n nitroConfig.scheduledTasks = nitroConfig.scheduledTasks || {}\n const existing = nitroConfig.scheduledTasks[cron]\n if (Array.isArray(existing)) {\n existing.push('evlog:cleanup')\n } else if (existing) {\n nitroConfig.scheduledTasks[cron] = [existing, 'evlog:cleanup']\n } else {\n nitroConfig.scheduledTasks[cron] = ['evlog:cleanup']\n }\n })\n },\n})\n"],"mappings":"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"}