@evlog/nuxthub 0.0.1-alpha.4 → 0.0.1-alpha.5
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.d.mts +2 -10
- package/dist/module.d.mts.map +1 -1
- package/dist/module.mjs +9 -11
- package/dist/module.mjs.map +1 -1
- package/dist/runtime/api/_cron/evlog-cleanup.d.mts +10 -0
- package/dist/runtime/api/_cron/evlog-cleanup.d.mts.map +1 -0
- package/dist/runtime/api/_cron/evlog-cleanup.mjs +14 -0
- package/dist/runtime/api/_cron/evlog-cleanup.mjs.map +1 -0
- package/dist/runtime/drain.d.mts +7 -0
- package/dist/runtime/drain.d.mts.map +1 -0
- package/dist/runtime/drain.mjs +70 -0
- package/dist/runtime/drain.mjs.map +1 -0
- package/dist/runtime/tasks/evlog-cleanup.d.mts +7 -0
- package/dist/runtime/tasks/evlog-cleanup.d.mts.map +1 -0
- package/dist/runtime/tasks/evlog-cleanup.mjs +50 -0
- package/dist/runtime/tasks/evlog-cleanup.mjs.map +1 -0
- package/package.json +1 -2
- 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.d.mts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import * as _nuxt_schema0 from "@nuxt/schema";
|
|
2
2
|
|
|
3
3
|
//#region src/module.d.ts
|
|
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>;
|
|
4
|
+
declare const _default: _nuxt_schema0.NuxtModule<_nuxt_schema0.ModuleOptions, _nuxt_schema0.ModuleOptions, false>;
|
|
13
5
|
//#endregion
|
|
14
|
-
export {
|
|
6
|
+
export { _default as default };
|
|
15
7
|
//# sourceMappingURL=module.d.mts.map
|
package/dist/module.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.mts","names":[],"sources":["../src/module.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.mts","names":[],"sources":["../src/module.ts"],"mappings":""}
|
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, promises } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { addServerHandler, addServerPlugin, defineNuxtModule, hasNuxtModule, installModule } from "@nuxt/kit";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { addServerHandler, addServerPlugin, createResolver, defineNuxtModule, hasNuxtModule, installModule } from "@nuxt/kit";
|
|
5
4
|
import { consola } from "consola";
|
|
6
5
|
import { createEvlogError } from "evlog";
|
|
7
6
|
|
|
@@ -67,25 +66,24 @@ var module_default = defineNuxtModule({
|
|
|
67
66
|
consola.success("Created vercel.json with evlog cleanup cron schedule");
|
|
68
67
|
},
|
|
69
68
|
async setup(_moduleOptions, nuxt) {
|
|
69
|
+
const { resolve: resolveModule } = createResolver(import.meta.url);
|
|
70
70
|
if (!hasNuxtModule("evlog/nuxt")) await installModule("evlog/nuxt");
|
|
71
71
|
if (!hasNuxtModule("@nuxthub/core")) await installModule("@nuxthub/core");
|
|
72
|
-
const
|
|
73
|
-
const srcDir = resolve(fileURLToPath(new URL(".", import.meta.url)), "..", "src");
|
|
74
|
-
const runtimeDir = join(srcDir, "runtime");
|
|
72
|
+
const retention = (nuxt.options.evlog || {}).retention ?? "30d";
|
|
75
73
|
nuxt.hook("hub:db:schema:extend", ({ paths, dialect }) => {
|
|
76
|
-
paths.push(
|
|
74
|
+
paths.push(resolveModule(`../src/schema/${dialect}.ts`));
|
|
77
75
|
});
|
|
78
|
-
addServerPlugin(
|
|
76
|
+
addServerPlugin(resolveModule("./runtime/drain"));
|
|
79
77
|
addServerHandler({
|
|
80
78
|
route: "/api/_cron/evlog-cleanup",
|
|
81
|
-
handler:
|
|
79
|
+
handler: resolveModule("./runtime/api/_cron/evlog-cleanup")
|
|
82
80
|
});
|
|
83
81
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
84
82
|
nitroConfig.experimental = nitroConfig.experimental || {};
|
|
85
83
|
nitroConfig.experimental.tasks = true;
|
|
86
84
|
nitroConfig.tasks = nitroConfig.tasks || {};
|
|
87
|
-
nitroConfig.tasks["evlog:cleanup"] = { handler:
|
|
88
|
-
const cron = retentionToCron(
|
|
85
|
+
nitroConfig.tasks["evlog:cleanup"] = { handler: resolveModule("./runtime/tasks/evlog-cleanup") };
|
|
86
|
+
const cron = retentionToCron(retention);
|
|
89
87
|
nitroConfig.scheduledTasks = nitroConfig.scheduledTasks || {};
|
|
90
88
|
const existing = nitroConfig.scheduledTasks[cron];
|
|
91
89
|
if (Array.isArray(existing)) existing.push("evlog:cleanup");
|
package/dist/module.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.mjs","names":["fsp"],"sources":["../src/module.ts"],"sourcesContent":["import { existsSync, promises as fsp } from 'node:fs'\nimport {
|
|
1
|
+
{"version":3,"file":"module.mjs","names":["fsp"],"sources":["../src/module.ts"],"sourcesContent":["import { existsSync, promises as fsp } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { addServerHandler, addServerPlugin, createResolver, defineNuxtModule, hasNuxtModule, installModule } from '@nuxt/kit'\nimport { consola } from 'consola'\nimport type { NitroConfig } from 'nitropack'\nimport { createEvlogError } from 'evlog'\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({\n meta: {\n name: '@evlog/nuxthub',\n version: '0.0.1-alpha.1',\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 async setup(_moduleOptions, nuxt) {\n const { resolve: resolveModule } = createResolver(import.meta.url)\n\n // Auto-install evlog/nuxt and @nuxthub/core if not already registered\n if (!hasNuxtModule('evlog/nuxt')) {\n await installModule('evlog/nuxt')\n }\n if (!hasNuxtModule('@nuxthub/core')) {\n await installModule('@nuxthub/core')\n }\n\n // Read nuxthub options from evlog config key\n const evlogConfig = (nuxt.options as any).evlog || {}\n const retention: string = evlogConfig.retention ?? '30d'\n\n // Extend NuxtHub DB schema with dialect-specific evlog_events table\n // Schema files stay as .ts — NuxtHub's build pipeline handles them\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(resolveModule(`../src/schema/${dialect}.ts`))\n })\n\n // Register the drain server plugin (resolved from dist/runtime/)\n addServerPlugin(resolveModule('./runtime/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: resolveModule('./runtime/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: resolveModule('./runtime/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(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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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as h3 from "h3";
|
|
2
|
+
|
|
3
|
+
//#region src/runtime/api/_cron/evlog-cleanup.d.ts
|
|
4
|
+
declare const _default: h3.EventHandler<h3.EventHandlerRequest, Promise<{
|
|
5
|
+
result?: unknown;
|
|
6
|
+
success: boolean;
|
|
7
|
+
}>>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { _default as default };
|
|
10
|
+
//# sourceMappingURL=evlog-cleanup.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evlog-cleanup.d.mts","names":[],"sources":["../../../../src/runtime/api/_cron/evlog-cleanup.ts"],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { runTask } from "nitropack/runtime";
|
|
2
|
+
import { eventHandler } from "h3";
|
|
3
|
+
|
|
4
|
+
//#region src/runtime/api/_cron/evlog-cleanup.ts
|
|
5
|
+
var evlog_cleanup_default = eventHandler(async () => {
|
|
6
|
+
return {
|
|
7
|
+
success: true,
|
|
8
|
+
...await runTask("evlog:cleanup")
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { evlog_cleanup_default as default };
|
|
14
|
+
//# sourceMappingURL=evlog-cleanup.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evlog-cleanup.mjs","names":[],"sources":["../../../../src/runtime/api/_cron/evlog-cleanup.ts"],"sourcesContent":["import { runTask } from 'nitropack/runtime'\nimport { eventHandler } from 'h3'\n\nexport default eventHandler(async () => {\n const result = await runTask('evlog:cleanup')\n return { success: true, ...result }\n})\n"],"mappings":";;;;AAGA,4BAAe,aAAa,YAAY;AAEtC,QAAO;EAAE,SAAS;EAAM,GADT,MAAM,QAAQ,gBAAgB;EACV;EACnC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drain.d.mts","names":[],"sources":["../../src/runtime/drain.ts"],"mappings":""}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { defineNitroPlugin } from "nitropack/runtime";
|
|
2
|
+
import { db, schema } from "@nuxthub/db";
|
|
3
|
+
|
|
4
|
+
//#region src/runtime/drain.ts
|
|
5
|
+
function parseDurationMs(event) {
|
|
6
|
+
if (typeof event.durationMs === "number") return event.durationMs;
|
|
7
|
+
if (typeof event.duration === "number") return event.duration;
|
|
8
|
+
if (typeof event.duration === "string") {
|
|
9
|
+
const str = event.duration;
|
|
10
|
+
const msMatch = str.match(/^([\d.]+)\s*ms$/);
|
|
11
|
+
if (msMatch) return Math.round(Number.parseFloat(msMatch[1]));
|
|
12
|
+
const sMatch = str.match(/^([\d.]+)\s*s$/);
|
|
13
|
+
if (sMatch) return Math.round(Number.parseFloat(sMatch[1]) * 1e3);
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function extractRow(ctx) {
|
|
18
|
+
const { event, request } = ctx;
|
|
19
|
+
const indexed = new Set([
|
|
20
|
+
"timestamp",
|
|
21
|
+
"level",
|
|
22
|
+
"service",
|
|
23
|
+
"environment",
|
|
24
|
+
"method",
|
|
25
|
+
"path",
|
|
26
|
+
"status",
|
|
27
|
+
"durationMs",
|
|
28
|
+
"duration",
|
|
29
|
+
"requestId",
|
|
30
|
+
"source",
|
|
31
|
+
"error"
|
|
32
|
+
]);
|
|
33
|
+
const data = {};
|
|
34
|
+
for (const [key, value] of Object.entries(event)) if (!indexed.has(key) && value !== void 0) data[key] = value;
|
|
35
|
+
const errorValue = event.error;
|
|
36
|
+
let errorJson = null;
|
|
37
|
+
if (errorValue !== void 0 && errorValue !== null) errorJson = typeof errorValue === "string" ? errorValue : JSON.stringify(errorValue);
|
|
38
|
+
return {
|
|
39
|
+
id: crypto.randomUUID(),
|
|
40
|
+
timestamp: event.timestamp,
|
|
41
|
+
level: event.level,
|
|
42
|
+
service: event.service,
|
|
43
|
+
environment: event.environment,
|
|
44
|
+
method: (request?.method ?? event.method) || null,
|
|
45
|
+
path: (request?.path ?? event.path) || null,
|
|
46
|
+
status: typeof event.status === "number" ? event.status : null,
|
|
47
|
+
durationMs: parseDurationMs(event),
|
|
48
|
+
requestId: (request?.requestId ?? event.requestId) || null,
|
|
49
|
+
source: event.source || null,
|
|
50
|
+
error: errorJson,
|
|
51
|
+
data: Object.keys(data).length > 0 ? JSON.stringify(data) : null,
|
|
52
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
var drain_default = defineNitroPlugin((nitroApp) => {
|
|
56
|
+
nitroApp.hooks.hook("evlog:drain", async (ctx) => {
|
|
57
|
+
try {
|
|
58
|
+
const contexts = Array.isArray(ctx) ? ctx : [ctx];
|
|
59
|
+
if (contexts.length === 0) return;
|
|
60
|
+
const rows = contexts.map(extractRow);
|
|
61
|
+
await db.insert(schema.evlogEvents).values(rows);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error("[evlog/nuxthub] Failed to insert events:", error);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
export { drain_default as default };
|
|
70
|
+
//# sourceMappingURL=drain.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drain.mjs","names":[],"sources":["../../src/runtime/drain.ts"],"sourcesContent":["import type { DrainContext, WideEvent } from 'evlog'\nimport { defineNitroPlugin } from 'nitropack/runtime'\n// @ts-expect-error nuxthub/db is a virtual module provided by @nuxthub/core\nimport { db, schema } from '@nuxthub/db'\n\ntype EventRow = typeof schema.evlogEvents.$inferInsert\n\nfunction parseDurationMs(event: WideEvent): number | null {\n if (typeof event.durationMs === 'number') return event.durationMs\n if (typeof event.duration === 'number') return event.duration\n if (typeof event.duration === 'string') {\n const str = event.duration as string\n const msMatch = str.match(/^([\\d.]+)\\s*ms$/)\n if (msMatch) return Math.round(Number.parseFloat(msMatch[1]))\n const sMatch = str.match(/^([\\d.]+)\\s*s$/)\n if (sMatch) return Math.round(Number.parseFloat(sMatch[1]) * 1000)\n }\n return null\n}\n\nfunction extractRow(ctx: DrainContext): EventRow {\n const { event, request } = ctx\n\n // Fields that go into indexed columns\n const indexed = new Set([\n 'timestamp',\n 'level',\n 'service',\n 'environment',\n 'method',\n 'path',\n 'status',\n 'durationMs',\n 'duration',\n 'requestId',\n 'source',\n 'error',\n ])\n\n // Collect remaining fields into data\n const data: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(event)) {\n if (!indexed.has(key) && value !== undefined) {\n data[key] = value\n }\n }\n\n const errorValue = event.error\n let errorJson: string | null = null\n if (errorValue !== undefined && errorValue !== null) {\n errorJson = typeof errorValue === 'string' ? errorValue : JSON.stringify(errorValue)\n }\n\n return {\n id: crypto.randomUUID(),\n timestamp: event.timestamp,\n level: event.level,\n service: event.service,\n environment: event.environment,\n method: (request?.method ?? event.method as string) || null,\n path: (request?.path ?? event.path as string) || null,\n status: typeof event.status === 'number' ? event.status : null,\n durationMs: parseDurationMs(event),\n requestId: (request?.requestId ?? event.requestId as string) || null,\n source: (event.source as string) || null,\n error: errorJson,\n data: Object.keys(data).length > 0 ? JSON.stringify(data) : null,\n createdAt: new Date().toISOString(),\n }\n}\n\nexport default defineNitroPlugin((nitroApp) => {\n nitroApp.hooks.hook('evlog:drain', async (ctx: DrainContext | DrainContext[]) => {\n try {\n const contexts = Array.isArray(ctx) ? ctx : [ctx]\n if (contexts.length === 0) return\n\n const rows = contexts.map(extractRow)\n\n await db.insert(schema.evlogEvents).values(rows)\n } catch (error) {\n console.error('[evlog/nuxthub] Failed to insert events:', error)\n }\n })\n})\n"],"mappings":";;;;AAOA,SAAS,gBAAgB,OAAiC;AACxD,KAAI,OAAO,MAAM,eAAe,SAAU,QAAO,MAAM;AACvD,KAAI,OAAO,MAAM,aAAa,SAAU,QAAO,MAAM;AACrD,KAAI,OAAO,MAAM,aAAa,UAAU;EACtC,MAAM,MAAM,MAAM;EAClB,MAAM,UAAU,IAAI,MAAM,kBAAkB;AAC5C,MAAI,QAAS,QAAO,KAAK,MAAM,OAAO,WAAW,QAAQ,GAAG,CAAC;EAC7D,MAAM,SAAS,IAAI,MAAM,iBAAiB;AAC1C,MAAI,OAAQ,QAAO,KAAK,MAAM,OAAO,WAAW,OAAO,GAAG,GAAG,IAAK;;AAEpE,QAAO;;AAGT,SAAS,WAAW,KAA6B;CAC/C,MAAM,EAAE,OAAO,YAAY;CAG3B,MAAM,UAAU,IAAI,IAAI;EACtB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CAGF,MAAM,OAAgC,EAAE;AACxC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KAAI,CAAC,QAAQ,IAAI,IAAI,IAAI,UAAU,OACjC,MAAK,OAAO;CAIhB,MAAM,aAAa,MAAM;CACzB,IAAI,YAA2B;AAC/B,KAAI,eAAe,UAAa,eAAe,KAC7C,aAAY,OAAO,eAAe,WAAW,aAAa,KAAK,UAAU,WAAW;AAGtF,QAAO;EACL,IAAI,OAAO,YAAY;EACvB,WAAW,MAAM;EACjB,OAAO,MAAM;EACb,SAAS,MAAM;EACf,aAAa,MAAM;EACnB,SAAS,SAAS,UAAU,MAAM,WAAqB;EACvD,OAAO,SAAS,QAAQ,MAAM,SAAmB;EACjD,QAAQ,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS;EAC1D,YAAY,gBAAgB,MAAM;EAClC,YAAY,SAAS,aAAa,MAAM,cAAwB;EAChE,QAAS,MAAM,UAAqB;EACpC,OAAO;EACP,MAAM,OAAO,KAAK,KAAK,CAAC,SAAS,IAAI,KAAK,UAAU,KAAK,GAAG;EAC5D,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC;;AAGH,oBAAe,mBAAmB,aAAa;AAC7C,UAAS,MAAM,KAAK,eAAe,OAAO,QAAuC;AAC/E,MAAI;GACF,MAAM,WAAW,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI;AACjD,OAAI,SAAS,WAAW,EAAG;GAE3B,MAAM,OAAO,SAAS,IAAI,WAAW;AAErC,SAAM,GAAG,OAAO,OAAO,YAAY,CAAC,OAAO,KAAK;WACzC,OAAO;AACd,WAAQ,MAAM,4CAA4C,MAAM;;GAElE;EACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evlog-cleanup.d.mts","names":[],"sources":["../../../src/runtime/tasks/evlog-cleanup.ts"],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createEvlogError } from "evlog";
|
|
2
|
+
import { defineTask, useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
import { db, schema } from "@nuxthub/db";
|
|
4
|
+
import { lt } from "drizzle-orm";
|
|
5
|
+
|
|
6
|
+
//#region src/runtime/tasks/evlog-cleanup.ts
|
|
7
|
+
function parseRetention(retention) {
|
|
8
|
+
const match = retention.match(/^(\d+)(d|h|m)$/);
|
|
9
|
+
if (!match) throw createEvlogError({
|
|
10
|
+
message: `[evlog/nuxthub] Invalid retention format: "${retention}"`,
|
|
11
|
+
why: "The retention value must be a number followed by a unit: d (days), h (hours), or m (minutes)",
|
|
12
|
+
fix: `Change retention to a valid format, e.g., "30d", "24h", or "60m"`,
|
|
13
|
+
link: "https://evlog.dev/nuxthub/retention"
|
|
14
|
+
});
|
|
15
|
+
const [, value, unit] = match;
|
|
16
|
+
switch (unit) {
|
|
17
|
+
case "d": return Number(value) * 24 * 60 * 60 * 1e3;
|
|
18
|
+
case "h": return Number(value) * 60 * 60 * 1e3;
|
|
19
|
+
case "m": return Number(value) * 60 * 1e3;
|
|
20
|
+
default: throw createEvlogError({
|
|
21
|
+
message: `[evlog/nuxthub] Unknown retention unit: "${unit}"`,
|
|
22
|
+
why: "The retention value must use one of the supported units: d (days), h (hours), or m (minutes)",
|
|
23
|
+
fix: `Change retention to a valid format, e.g., "30d", "24h", or "60m"`,
|
|
24
|
+
link: "https://evlog.dev/nuxthub/retention"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var evlog_cleanup_default = defineTask({
|
|
29
|
+
meta: {
|
|
30
|
+
name: "evlog:cleanup",
|
|
31
|
+
description: "Clean up expired evlog events based on retention policy"
|
|
32
|
+
},
|
|
33
|
+
async run() {
|
|
34
|
+
const retention = useRuntimeConfig().evlog?.retention ?? "30d";
|
|
35
|
+
const retentionMs = parseRetention(retention);
|
|
36
|
+
const cutoff = new Date(Date.now() - retentionMs).toISOString();
|
|
37
|
+
try {
|
|
38
|
+
const result = await db.delete(schema.evlogEvents).where(lt(schema.evlogEvents.createdAt, cutoff));
|
|
39
|
+
console.log(`[evlog/nuxthub] Cleanup: deleted events older than ${retention} (before ${cutoff})`, result);
|
|
40
|
+
return { result: "success" };
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error("[evlog/nuxthub] Cleanup task failed:", error);
|
|
43
|
+
return { result: "error" };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { evlog_cleanup_default as default };
|
|
50
|
+
//# sourceMappingURL=evlog-cleanup.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evlog-cleanup.mjs","names":[],"sources":["../../../src/runtime/tasks/evlog-cleanup.ts"],"sourcesContent":["import { defineTask, useRuntimeConfig } from 'nitropack/runtime'\nimport { lt } from 'drizzle-orm'\n// @ts-expect-error nuxthub/db is a virtual module provided by @nuxthub/core\nimport { db, schema } from '@nuxthub/db'\nimport { createEvlogError } from 'evlog'\n\nfunction parseRetention(retention: string): number {\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 [, value, unit] = match\n\n switch (unit) {\n case 'd': return Number(value) * 24 * 60 * 60 * 1000\n case 'h': return Number(value) * 60 * 60 * 1000\n case 'm': return Number(value) * 60 * 1000\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\nexport default defineTask({\n meta: {\n name: 'evlog:cleanup',\n description: 'Clean up expired evlog events based on retention policy',\n },\n async run() {\n const config = useRuntimeConfig()\n const retention = (config as any).evlog?.retention ?? '30d'\n const retentionMs = parseRetention(retention)\n const cutoff = new Date(Date.now() - retentionMs).toISOString()\n\n try {\n const result = await db.delete(schema.evlogEvents)\n .where(lt(schema.evlogEvents.createdAt, cutoff))\n\n console.log(`[evlog/nuxthub] Cleanup: deleted events older than ${retention} (before ${cutoff})`, result)\n return { result: 'success' }\n } catch (error) {\n console.error('[evlog/nuxthub] Cleanup task failed:', error)\n return { result: 'error' }\n }\n },\n})\n"],"mappings":";;;;;;AAMA,SAAS,eAAe,WAA2B;CACjD,MAAM,QAAQ,UAAU,MAAM,iBAAiB;AAC/C,KAAI,CAAC,MACH,OAAM,iBAAiB;EACrB,SAAS,8CAA8C,UAAU;EACjE,KAAK;EACL,KAAK;EACL,MAAM;EACP,CAAC;CAGJ,MAAM,GAAG,OAAO,QAAQ;AAExB,SAAQ,MAAR;EACE,KAAK,IAAK,QAAO,OAAO,MAAM,GAAG,KAAK,KAAK,KAAK;EAChD,KAAK,IAAK,QAAO,OAAO,MAAM,GAAG,KAAK,KAAK;EAC3C,KAAK,IAAK,QAAO,OAAO,MAAM,GAAG,KAAK;EACtC,QACE,OAAM,iBAAiB;GACrB,SAAS,4CAA4C,KAAK;GAC1D,KAAK;GACL,KAAK;GACL,MAAM;GACP,CAAC;;;AAIR,4BAAe,WAAW;CACxB,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,MAAM;EAEV,MAAM,YADS,kBAAkB,CACC,OAAO,aAAa;EACtD,MAAM,cAAc,eAAe,UAAU;EAC7C,MAAM,SAAS,IAAI,KAAK,KAAK,KAAK,GAAG,YAAY,CAAC,aAAa;AAE/D,MAAI;GACF,MAAM,SAAS,MAAM,GAAG,OAAO,OAAO,YAAY,CAC/C,MAAM,GAAG,OAAO,YAAY,WAAW,OAAO,CAAC;AAElD,WAAQ,IAAI,sDAAsD,UAAU,WAAW,OAAO,IAAI,OAAO;AACzG,UAAO,EAAE,QAAQ,WAAW;WACrB,OAAO;AACd,WAAQ,MAAM,wCAAwC,MAAM;AAC5D,UAAO,EAAE,QAAQ,SAAS;;;CAG/B,CAAC"}
|
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.5",
|
|
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",
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"types": "./dist/module.d.mts",
|
|
34
34
|
"files": [
|
|
35
35
|
"dist",
|
|
36
|
-
"src/runtime",
|
|
37
36
|
"src/schema",
|
|
38
37
|
"README.md"
|
|
39
38
|
],
|
package/src/runtime/drain.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import type { DrainContext, WideEvent } from 'evlog'
|
|
2
|
-
import { defineNitroPlugin } from 'nitropack/runtime'
|
|
3
|
-
// @ts-expect-error nuxthub/db is a virtual module provided by @nuxthub/core
|
|
4
|
-
import { db, schema } from '@nuxthub/db'
|
|
5
|
-
|
|
6
|
-
type EventRow = typeof schema.evlogEvents.$inferInsert
|
|
7
|
-
|
|
8
|
-
function parseDurationMs(event: WideEvent): number | null {
|
|
9
|
-
if (typeof event.durationMs === 'number') return event.durationMs
|
|
10
|
-
if (typeof event.duration === 'number') return event.duration
|
|
11
|
-
if (typeof event.duration === 'string') {
|
|
12
|
-
const str = event.duration as string
|
|
13
|
-
const msMatch = str.match(/^([\d.]+)\s*ms$/)
|
|
14
|
-
if (msMatch) return Math.round(Number.parseFloat(msMatch[1]))
|
|
15
|
-
const sMatch = str.match(/^([\d.]+)\s*s$/)
|
|
16
|
-
if (sMatch) return Math.round(Number.parseFloat(sMatch[1]) * 1000)
|
|
17
|
-
}
|
|
18
|
-
return null
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function extractRow(ctx: DrainContext): EventRow {
|
|
22
|
-
const { event, request } = ctx
|
|
23
|
-
|
|
24
|
-
// Fields that go into indexed columns
|
|
25
|
-
const indexed = new Set([
|
|
26
|
-
'timestamp',
|
|
27
|
-
'level',
|
|
28
|
-
'service',
|
|
29
|
-
'environment',
|
|
30
|
-
'method',
|
|
31
|
-
'path',
|
|
32
|
-
'status',
|
|
33
|
-
'durationMs',
|
|
34
|
-
'duration',
|
|
35
|
-
'requestId',
|
|
36
|
-
'source',
|
|
37
|
-
'error',
|
|
38
|
-
])
|
|
39
|
-
|
|
40
|
-
// Collect remaining fields into data
|
|
41
|
-
const data: Record<string, unknown> = {}
|
|
42
|
-
for (const [key, value] of Object.entries(event)) {
|
|
43
|
-
if (!indexed.has(key) && value !== undefined) {
|
|
44
|
-
data[key] = value
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const errorValue = event.error
|
|
49
|
-
let errorJson: string | null = null
|
|
50
|
-
if (errorValue !== undefined && errorValue !== null) {
|
|
51
|
-
errorJson = typeof errorValue === 'string' ? errorValue : JSON.stringify(errorValue)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
id: crypto.randomUUID(),
|
|
56
|
-
timestamp: event.timestamp,
|
|
57
|
-
level: event.level,
|
|
58
|
-
service: event.service,
|
|
59
|
-
environment: event.environment,
|
|
60
|
-
method: (request?.method ?? event.method as string) || null,
|
|
61
|
-
path: (request?.path ?? event.path as string) || null,
|
|
62
|
-
status: typeof event.status === 'number' ? event.status : null,
|
|
63
|
-
durationMs: parseDurationMs(event),
|
|
64
|
-
requestId: (request?.requestId ?? event.requestId as string) || null,
|
|
65
|
-
source: (event.source as string) || null,
|
|
66
|
-
error: errorJson,
|
|
67
|
-
data: Object.keys(data).length > 0 ? JSON.stringify(data) : null,
|
|
68
|
-
createdAt: new Date().toISOString(),
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export default defineNitroPlugin((nitroApp) => {
|
|
73
|
-
nitroApp.hooks.hook('evlog:drain', async (ctx: DrainContext | DrainContext[]) => {
|
|
74
|
-
try {
|
|
75
|
-
const contexts = Array.isArray(ctx) ? ctx : [ctx]
|
|
76
|
-
if (contexts.length === 0) return
|
|
77
|
-
|
|
78
|
-
const rows = contexts.map(extractRow)
|
|
79
|
-
|
|
80
|
-
await db.insert(schema.evlogEvents).values(rows)
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.error('[evlog/nuxthub] Failed to insert events:', error)
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
})
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { defineTask, useRuntimeConfig } from 'nitropack/runtime'
|
|
2
|
-
import { lt } from 'drizzle-orm'
|
|
3
|
-
// @ts-expect-error nuxthub/db is a virtual module provided by @nuxthub/core
|
|
4
|
-
import { db, schema } from '@nuxthub/db'
|
|
5
|
-
import { createEvlogError } from 'evlog'
|
|
6
|
-
|
|
7
|
-
function parseRetention(retention: string): number {
|
|
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
|
-
|
|
18
|
-
const [, value, unit] = match
|
|
19
|
-
|
|
20
|
-
switch (unit) {
|
|
21
|
-
case 'd': return Number(value) * 24 * 60 * 60 * 1000
|
|
22
|
-
case 'h': return Number(value) * 60 * 60 * 1000
|
|
23
|
-
case 'm': return Number(value) * 60 * 1000
|
|
24
|
-
default:
|
|
25
|
-
throw createEvlogError({
|
|
26
|
-
message: `[evlog/nuxthub] Unknown retention unit: "${unit}"`,
|
|
27
|
-
why: 'The retention value must use one of the supported units: d (days), h (hours), or m (minutes)',
|
|
28
|
-
fix: `Change retention to a valid format, e.g., "30d", "24h", or "60m"`,
|
|
29
|
-
link: 'https://evlog.dev/nuxthub/retention',
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default defineTask({
|
|
35
|
-
meta: {
|
|
36
|
-
name: 'evlog:cleanup',
|
|
37
|
-
description: 'Clean up expired evlog events based on retention policy',
|
|
38
|
-
},
|
|
39
|
-
async run() {
|
|
40
|
-
const config = useRuntimeConfig()
|
|
41
|
-
const retention = (config as any).evlog?.retention ?? '30d'
|
|
42
|
-
const retentionMs = parseRetention(retention)
|
|
43
|
-
const cutoff = new Date(Date.now() - retentionMs).toISOString()
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const result = await db.delete(schema.evlogEvents)
|
|
47
|
-
.where(lt(schema.evlogEvents.createdAt, cutoff))
|
|
48
|
-
|
|
49
|
-
console.log(`[evlog/nuxthub] Cleanup: deleted events older than ${retention} (before ${cutoff})`, result)
|
|
50
|
-
return { result: 'success' }
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.error('[evlog/nuxthub] Cleanup task failed:', error)
|
|
53
|
-
return { result: 'error' }
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
})
|