@evlog/nuxthub 0.0.1-alpha.3 → 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 CHANGED
@@ -1,15 +1,7 @@
1
1
  import * as _nuxt_schema0 from "@nuxt/schema";
2
2
 
3
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>;
4
+ declare const _default: _nuxt_schema0.NuxtModule<_nuxt_schema0.ModuleOptions, _nuxt_schema0.ModuleOptions, false>;
13
5
  //#endregion
14
- export { ModuleOptions, _default as default };
6
+ export { _default as default };
15
7
  //# sourceMappingURL=module.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.mts","names":[],"sources":["../src/module.ts"],"mappings":";;;UAQiB,aAAA;;;AAAjB;;;EAME,SAAA;AAAA;AAAA,cACD,QAAA"}
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 { fileURLToPath } from "node:url";
3
- import { join, resolve } from "node:path";
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 options = { retention: (nuxt.options.evlog || {}).retention ?? "30d" };
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(resolve(srcDir, "schema", `${dialect}.ts`));
74
+ paths.push(resolveModule(`../src/schema/${dialect}.ts`));
77
75
  });
78
- addServerPlugin(join(runtimeDir, "drain"));
76
+ addServerPlugin(resolveModule("./runtime/drain"));
79
77
  addServerHandler({
80
78
  route: "/api/_cron/evlog-cleanup",
81
- handler: join(runtimeDir, "api", "_cron", "evlog-cleanup")
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: join(runtimeDir, "tasks", "evlog-cleanup") };
88
- const cron = retentionToCron(options.retention);
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");
@@ -1 +1 @@
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, hasNuxtModule, installModule } 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 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 // 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 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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"}
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,7 @@
1
+ import * as nitropack from "nitropack";
2
+
3
+ //#region src/runtime/drain.d.ts
4
+ declare const _default: nitropack.NitroAppPlugin;
5
+ //#endregion
6
+ export { _default as default };
7
+ //# sourceMappingURL=drain.d.mts.map
@@ -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,7 @@
1
+ import * as nitropack from "nitropack";
2
+
3
+ //#region src/runtime/tasks/evlog-cleanup.d.ts
4
+ declare const _default: nitropack.Task<string>;
5
+ //#endregion
6
+ export { _default as default };
7
+ //# sourceMappingURL=evlog-cleanup.d.mts.map
@@ -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",
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
  ],
@@ -1,7 +0,0 @@
1
- import { runTask } from 'nitropack/runtime'
2
- import { eventHandler } from 'h3'
3
-
4
- export default eventHandler(async () => {
5
- const result = await runTask('evlog:cleanup')
6
- return { success: true, ...result }
7
- })
@@ -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
- })