@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.
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +4 -14
- package/dist/module.d.ts +5 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +125 -94
- package/dist/runtime/api/_cron/evlog-cleanup.d.ts +6 -0
- package/dist/runtime/api/_cron/evlog-cleanup.js +6 -0
- package/dist/runtime/drain.d.ts +3 -0
- package/dist/runtime/drain.js +70 -0
- package/dist/runtime/tasks/evlog-cleanup.d.ts +3 -0
- package/dist/runtime/tasks/evlog-cleanup.js +51 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +8 -22
- package/dist/module.d.mts.map +0 -1
- package/dist/module.mjs.map +0 -1
- package/dist/schema/mysql.d.mts +0 -251
- package/dist/schema/mysql.d.mts.map +0 -1
- package/dist/schema/mysql.mjs +0 -30
- package/dist/schema/mysql.mjs.map +0 -1
- package/dist/schema/postgresql.d.mts +0 -251
- package/dist/schema/postgresql.d.mts.map +0 -1
- package/dist/schema/postgresql.mjs +0 -30
- package/dist/schema/postgresql.mjs.map +0 -1
- package/dist/schema/sqlite.d.mts +0 -275
- package/dist/schema/sqlite.d.mts.map +0 -1
- package/dist/schema/sqlite.mjs +0 -30
- package/dist/schema/sqlite.mjs.map +0 -1
- package/src/runtime/api/_cron/evlog-cleanup.ts +0 -7
- package/src/runtime/drain.ts +0 -85
- package/src/runtime/tasks/evlog-cleanup.ts +0 -56
package/dist/module.cjs
ADDED
package/dist/module.d.mts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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 };
|
package/dist/module.d.ts
ADDED
package/dist/module.json
ADDED
package/dist/module.mjs
CHANGED
|
@@ -1,99 +1,130 @@
|
|
|
1
|
-
import { existsSync, promises } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
98
|
-
export { module_default as default };
|
|
99
|
-
//# sourceMappingURL=module.mjs.map
|
|
130
|
+
export { module as default };
|
|
@@ -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,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
|
+
});
|
package/dist/types.d.mts
ADDED
package/dist/types.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evlog/nuxthub",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
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/
|
|
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/
|
|
21
|
+
"types": "./dist/types.d.mts",
|
|
34
22
|
"files": [
|
|
35
23
|
"dist",
|
|
36
|
-
"src/
|
|
37
|
-
"src/schema",
|
|
38
|
-
"README.md"
|
|
24
|
+
"src/schema"
|
|
39
25
|
],
|
|
40
26
|
"scripts": {
|
|
41
|
-
"build": "
|
|
42
|
-
"dev:prepare": "
|
|
27
|
+
"build": "nuxt-module-build build",
|
|
28
|
+
"dev:prepare": "nuxt-module-build prepare"
|
|
43
29
|
},
|
|
44
30
|
"dependencies": {
|
|
45
|
-
"evlog": "
|
|
31
|
+
"evlog": "^1.7.0",
|
|
46
32
|
"@nuxthub/core": "^0.10.6"
|
|
47
33
|
},
|
|
48
34
|
"devDependencies": {
|
|
49
|
-
"
|
|
35
|
+
"@nuxt/module-builder": "^0.8.4",
|
|
50
36
|
"typescript": "^5.9.3"
|
|
51
37
|
},
|
|
52
38
|
"peerDependencies": {
|
package/dist/module.d.mts.map
DELETED
|
@@ -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"}
|
package/dist/module.mjs.map
DELETED
|
@@ -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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"}
|