@omnixal/openclaw-nats-plugin 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/cli/setup.ts +27 -0
  2. package/dashboard/bun.lock +253 -0
  3. package/dashboard/components.json +16 -0
  4. package/dashboard/index.html +22 -0
  5. package/dashboard/package.json +24 -0
  6. package/dashboard/src/App.svelte +107 -0
  7. package/dashboard/src/app.css +232 -0
  8. package/dashboard/src/lib/ConfigPanel.svelte +35 -0
  9. package/dashboard/src/lib/CronPanel.svelte +255 -0
  10. package/dashboard/src/lib/HealthCards.svelte +68 -0
  11. package/dashboard/src/lib/MetricsPanel.svelte +60 -0
  12. package/dashboard/src/lib/PendingTable.svelte +73 -0
  13. package/dashboard/src/lib/RoutesPanel.svelte +178 -0
  14. package/dashboard/src/lib/ThemeToggle.svelte +54 -0
  15. package/dashboard/src/lib/api.ts +141 -0
  16. package/dashboard/src/lib/components/ui/badge/badge.svelte +50 -0
  17. package/dashboard/src/lib/components/ui/badge/index.ts +2 -0
  18. package/dashboard/src/lib/components/ui/button/button.svelte +82 -0
  19. package/dashboard/src/lib/components/ui/button/index.ts +17 -0
  20. package/dashboard/src/lib/components/ui/card/card-action.svelte +20 -0
  21. package/dashboard/src/lib/components/ui/card/card-content.svelte +15 -0
  22. package/dashboard/src/lib/components/ui/card/card-description.svelte +20 -0
  23. package/dashboard/src/lib/components/ui/card/card-footer.svelte +20 -0
  24. package/dashboard/src/lib/components/ui/card/card-header.svelte +23 -0
  25. package/dashboard/src/lib/components/ui/card/card-title.svelte +20 -0
  26. package/dashboard/src/lib/components/ui/card/card.svelte +23 -0
  27. package/dashboard/src/lib/components/ui/card/index.ts +25 -0
  28. package/dashboard/src/lib/components/ui/table/index.ts +28 -0
  29. package/dashboard/src/lib/components/ui/table/table-body.svelte +20 -0
  30. package/dashboard/src/lib/components/ui/table/table-caption.svelte +20 -0
  31. package/dashboard/src/lib/components/ui/table/table-cell.svelte +23 -0
  32. package/dashboard/src/lib/components/ui/table/table-footer.svelte +20 -0
  33. package/dashboard/src/lib/components/ui/table/table-head.svelte +23 -0
  34. package/dashboard/src/lib/components/ui/table/table-header.svelte +20 -0
  35. package/dashboard/src/lib/components/ui/table/table-row.svelte +23 -0
  36. package/dashboard/src/lib/components/ui/table/table.svelte +22 -0
  37. package/dashboard/src/lib/utils.ts +29 -0
  38. package/dashboard/src/main.ts +7 -0
  39. package/dashboard/tsconfig.json +19 -0
  40. package/dashboard/vite.config.ts +30 -0
  41. package/package.json +6 -4
  42. package/plugins/nats-context-engine/http-handler.ts +1 -1
  43. package/plugins/nats-context-engine/index.ts +59 -0
  44. package/sidecar/bun.lock +8 -6
  45. package/sidecar/package.json +4 -4
  46. package/sidecar/src/app.module.ts +9 -2
  47. package/sidecar/src/consumer/consumer.controller.ts +4 -0
  48. package/sidecar/src/consumer/consumer.module.ts +2 -1
  49. package/sidecar/src/db/migrations/0004_familiar_zaladane.sql +17 -0
  50. package/sidecar/src/db/migrations/meta/0004_snapshot.json +306 -0
  51. package/sidecar/src/db/migrations/meta/_journal.json +7 -0
  52. package/sidecar/src/db/schema.ts +20 -0
  53. package/sidecar/src/health/health.service.ts +1 -1
  54. package/sidecar/src/index.ts +6 -0
  55. package/sidecar/src/metrics/metrics.controller.ts +16 -0
  56. package/sidecar/src/metrics/metrics.module.ts +10 -0
  57. package/sidecar/src/metrics/metrics.service.ts +64 -0
  58. package/sidecar/src/publisher/publisher.module.ts +2 -0
  59. package/sidecar/src/publisher/publisher.service.ts +6 -1
  60. package/sidecar/src/router/router.controller.ts +20 -12
  61. package/sidecar/src/router/router.repository.ts +39 -7
  62. package/sidecar/src/router/router.service.ts +10 -2
  63. package/sidecar/src/scheduler/scheduler.controller.ts +68 -0
  64. package/sidecar/src/scheduler/scheduler.module.ts +13 -0
  65. package/sidecar/src/scheduler/scheduler.repository.ts +64 -0
  66. package/sidecar/src/scheduler/scheduler.service.ts +138 -0
  67. package/sidecar/src/validation/schemas.ts +18 -0
  68. package/skills/nats-events/SKILL.md +41 -28
  69. package/dashboard/dist/assets/index--UFIkwvP.js +0 -2
  70. package/dashboard/dist/assets/index-CafgidIc.css +0 -2
  71. package/dashboard/dist/index.html +0 -13
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="card-header"
16
+ class={cn(
17
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import { cn, type WithElementRef } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="card-title"
16
+ class={cn("leading-none font-semibold", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </div>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import { cn, type WithElementRef } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="card"
16
+ class={cn(
17
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,25 @@
1
+ import Root from "./card.svelte";
2
+ import Content from "./card-content.svelte";
3
+ import Description from "./card-description.svelte";
4
+ import Footer from "./card-footer.svelte";
5
+ import Header from "./card-header.svelte";
6
+ import Title from "./card-title.svelte";
7
+ import Action from "./card-action.svelte";
8
+
9
+ export {
10
+ Root,
11
+ Content,
12
+ Description,
13
+ Footer,
14
+ Header,
15
+ Title,
16
+ Action,
17
+ //
18
+ Root as Card,
19
+ Content as CardContent,
20
+ Description as CardDescription,
21
+ Footer as CardFooter,
22
+ Header as CardHeader,
23
+ Title as CardTitle,
24
+ Action as CardAction,
25
+ };
@@ -0,0 +1,28 @@
1
+ import Root from "./table.svelte";
2
+ import Body from "./table-body.svelte";
3
+ import Caption from "./table-caption.svelte";
4
+ import Cell from "./table-cell.svelte";
5
+ import Footer from "./table-footer.svelte";
6
+ import Head from "./table-head.svelte";
7
+ import Header from "./table-header.svelte";
8
+ import Row from "./table-row.svelte";
9
+
10
+ export {
11
+ Root,
12
+ Body,
13
+ Caption,
14
+ Cell,
15
+ Footer,
16
+ Head,
17
+ Header,
18
+ Row,
19
+ //
20
+ Root as Table,
21
+ Body as TableBody,
22
+ Caption as TableCaption,
23
+ Cell as TableCell,
24
+ Footer as TableFooter,
25
+ Head as TableHead,
26
+ Header as TableHeader,
27
+ Row as TableRow,
28
+ };
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
11
+ </script>
12
+
13
+ <tbody
14
+ bind:this={ref}
15
+ data-slot="table-body"
16
+ class={cn("[&_tr:last-child]:border-0", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </tbody>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
11
+ </script>
12
+
13
+ <caption
14
+ bind:this={ref}
15
+ data-slot="table-caption"
16
+ class={cn("text-muted-foreground mt-4 text-sm", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </caption>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLTdAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLTdAttributes> = $props();
11
+ </script>
12
+
13
+ <td
14
+ bind:this={ref}
15
+ data-slot="table-cell"
16
+ class={cn(
17
+ "bg-clip-padding p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </td>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
11
+ </script>
12
+
13
+ <tfoot
14
+ bind:this={ref}
15
+ data-slot="table-footer"
16
+ class={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </tfoot>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLThAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLThAttributes> = $props();
11
+ </script>
12
+
13
+ <th
14
+ bind:this={ref}
15
+ data-slot="table-head"
16
+ class={cn(
17
+ "text-foreground h-10 bg-clip-padding px-2 text-start align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pe-0",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </th>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
11
+ </script>
12
+
13
+ <thead
14
+ bind:this={ref}
15
+ data-slot="table-header"
16
+ class={cn("[&_tr]:border-b", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </thead>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLTableRowElement>> = $props();
11
+ </script>
12
+
13
+ <tr
14
+ bind:this={ref}
15
+ data-slot="table-row"
16
+ class={cn(
17
+ "hover:[&,&>svelte-css-wrapper]:[&>th,td]:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </tr>
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { HTMLTableAttributes } from "svelte/elements";
3
+ import { cn, type WithElementRef } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLTableAttributes> = $props();
11
+ </script>
12
+
13
+ <div data-slot="table-container" class="relative w-full overflow-x-auto">
14
+ <table
15
+ bind:this={ref}
16
+ data-slot="table"
17
+ class={cn("w-full caption-bottom text-sm", className)}
18
+ {...restProps}
19
+ >
20
+ {@render children?.()}
21
+ </table>
22
+ </div>
@@ -0,0 +1,29 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
12
+ export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
13
+ export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
14
+
15
+ export function relativeAge(ts: number | null): string {
16
+ if (!ts) return '\u2014';
17
+ const seconds = Math.floor((Date.now() - ts) / 1000);
18
+ if (seconds < 60) return `${seconds}s ago`;
19
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
20
+ return `${Math.floor(seconds / 3600)}h ago`;
21
+ }
22
+
23
+ export function formatDuration(ms: number | null): string {
24
+ if (ms === null) return '\u2014';
25
+ const seconds = Math.floor(ms / 1000);
26
+ if (seconds < 60) return `${seconds}s`;
27
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
28
+ return `${Math.floor(seconds / 3600)}h`;
29
+ }
@@ -0,0 +1,7 @@
1
+ import './app.css';
2
+ import App from './App.svelte';
3
+ import { mount } from 'svelte';
4
+
5
+ const app = mount(App, { target: document.getElementById('app')! });
6
+
7
+ export default app;
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "$lib": ["./src/lib"],
15
+ "$lib/*": ["./src/lib/*"]
16
+ }
17
+ },
18
+ "include": ["src/**/*"]
19
+ }
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from 'vite';
2
+ import { svelte } from '@sveltejs/vite-plugin-svelte';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+ import path from 'node:path';
5
+
6
+ const BACKEND_PORT = process.env.BACKEND_PORT || 3104;
7
+
8
+ export default defineConfig({
9
+ plugins: [svelte(), tailwindcss()],
10
+ base: '/nats-dashboard/',
11
+ resolve: {
12
+ alias: {
13
+ $lib: path.resolve('./src/lib'),
14
+ },
15
+ },
16
+ server: {
17
+ proxy: {
18
+ '/nats-dashboard/api': {
19
+ target: `http://localhost:${BACKEND_PORT}`,
20
+ rewrite: (path) => path.replace('/nats-dashboard/api', '/api'),
21
+ changeOrigin: true,
22
+ headers: { Authorization: 'Bearer dev-nats-plugin-key' },
23
+ },
24
+ },
25
+ },
26
+ build: {
27
+ outDir: 'dist',
28
+ emptyDirFirst: true,
29
+ },
30
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnixal/openclaw-nats-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "NATS JetStream event-driven plugin for OpenClaw",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,8 +9,8 @@
9
9
  "nats-plugin": "./bin/cli.ts"
10
10
  },
11
11
  "scripts": {
12
- "build:dashboard": "cd dashboard && bun run build",
13
- "prepublishOnly": "bun run build:dashboard"
12
+ "build:dashboard": "cd dashboard && bun install --frozen-lockfile && bun run build",
13
+ "nats": "docker run -d --name nats -p 4222:4222 nats:2.10-alpine -js"
14
14
  },
15
15
  "files": [
16
16
  "index.ts",
@@ -21,7 +21,9 @@
21
21
  "sidecar/",
22
22
  "skills/",
23
23
  "docker/",
24
- "dashboard/dist/",
24
+ "dashboard/",
25
+ "!dashboard/node_modules",
26
+ "!dashboard/dist",
25
27
  "openclaw.plugin.json",
26
28
  "PLUGIN.md",
27
29
  "!**/*.test.ts",
@@ -55,7 +55,7 @@ async function proxyToSidecar(
55
55
  }
56
56
 
57
57
  let body: string | undefined;
58
- if (req.method === 'POST' || req.method === 'PUT') {
58
+ if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
59
59
  body = await readBody(req);
60
60
  }
61
61
 
@@ -172,6 +172,65 @@ export default function (api: any) {
172
172
  },
173
173
  });
174
174
 
175
+ // ── Cron Scheduler Tools ────────────────────────────────────────────
176
+
177
+ api.registerTool({
178
+ name: 'nats_cron_add',
179
+ description: 'Create or update a scheduled cron job that publishes a NATS event on a schedule. No LLM wake — fires directly.',
180
+ parameters: {
181
+ type: 'object',
182
+ properties: {
183
+ name: { type: 'string', description: 'Unique job name (e.g., daily-report, hourly-check)' },
184
+ cron: { type: 'string', description: 'Cron expression (e.g., "0 9 * * *" for daily at 9am)' },
185
+ subject: { type: 'string', description: 'NATS subject to publish (must start with agent.events.)' },
186
+ payload: { type: 'object', description: 'Event payload data' },
187
+ timezone: { type: 'string', description: 'Timezone (default: UTC). e.g., Europe/Moscow' },
188
+ },
189
+ required: ['name', 'cron', 'subject'],
190
+ },
191
+ async execute(_id: string, params: any) {
192
+ const result = await sidecarFetch('/api/cron', {
193
+ method: 'POST',
194
+ body: JSON.stringify({
195
+ name: params.name,
196
+ cron: params.cron,
197
+ subject: params.subject,
198
+ payload: params.payload ?? {},
199
+ timezone: params.timezone,
200
+ }),
201
+ });
202
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
203
+ },
204
+ });
205
+
206
+ api.registerTool({
207
+ name: 'nats_cron_remove',
208
+ description: 'Remove a scheduled cron job by name.',
209
+ parameters: {
210
+ type: 'object',
211
+ properties: {
212
+ name: { type: 'string', description: 'Job name to remove' },
213
+ },
214
+ required: ['name'],
215
+ },
216
+ async execute(_id: string, params: any) {
217
+ const result = await sidecarFetch(`/api/cron/${encodeURIComponent(params.name)}`, {
218
+ method: 'DELETE',
219
+ });
220
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
221
+ },
222
+ });
223
+
224
+ api.registerTool({
225
+ name: 'nats_cron_list',
226
+ description: 'List all scheduled cron jobs with their next run time and status.',
227
+ parameters: { type: 'object', properties: {} },
228
+ async execute() {
229
+ const result = await sidecarFetch('/api/cron');
230
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
231
+ },
232
+ });
233
+
175
234
  // ── Dashboard UI ─────────────────────────────────────────────────
176
235
 
177
236
  api.registerHttpRoute({
package/sidecar/bun.lock CHANGED
@@ -5,11 +5,11 @@
5
5
  "": {
6
6
  "name": "@ai-entrepreneur/nats-sidecar",
7
7
  "dependencies": {
8
- "@onebun/core": "^0.2.12",
8
+ "@onebun/core": "^0.2.14",
9
9
  "@onebun/drizzle": "^0.2.4",
10
- "@onebun/envs": "^0.2.1",
10
+ "@onebun/envs": "^0.2.2",
11
11
  "@onebun/logger": "^0.2.1",
12
- "@onebun/nats": "^0.2.5",
12
+ "@onebun/nats": "^0.2.6",
13
13
  "arktype": "^2.2.0",
14
14
  "ulid": "^2.3.0",
15
15
  },
@@ -104,17 +104,17 @@
104
104
 
105
105
  "@nats-io/transport-node": ["@nats-io/transport-node@3.3.1", "", { "dependencies": { "@nats-io/nats-core": "3.3.1", "@nats-io/nkeys": "2.0.3", "@nats-io/nuid": "2.0.3" } }, "sha512-GBvY0VcvyQEILgy5bjpqU1GpDYmSF06bW59I7cewZuNGS9u3AoV/gf+a+3ep45T/Z+UC661atq/b7x+QV12w+Q=="],
106
106
 
107
- "@onebun/core": ["@onebun/core@0.2.12", "", { "dependencies": { "@onebun/envs": "^0.2.1", "@onebun/logger": "^0.2.1", "@onebun/metrics": "^0.2.2", "@onebun/requests": "^0.2.1", "@onebun/trace": "^0.2.1", "arktype": "^2.0.0", "effect": "^3.13.10" }, "peerDependencies": { "testcontainers": ">=10.0.0" }, "optionalPeers": ["testcontainers"] }, "sha512-Vy3A0pmp/eZjwgEgdZApMKHPJ697LvQETmqEWyUqUr01zcfy0HYI9KcRBRIHlGoajAYghwH+D9w4N3Ip6pGCcA=="],
107
+ "@onebun/core": ["@onebun/core@0.2.14", "", { "dependencies": { "@onebun/envs": "^0.2.2", "@onebun/logger": "^0.2.1", "@onebun/metrics": "^0.2.2", "@onebun/requests": "^0.2.1", "@onebun/trace": "^0.2.1", "arktype": "^2.0.0", "effect": "^3.13.10" }, "peerDependencies": { "testcontainers": ">=10.0.0" }, "optionalPeers": ["testcontainers"] }, "sha512-uClu4Oez18y6BldubdE6R/I02Brrk5eXCoxxPatgRJ9qYRM+jKSTNmeZVoqqo7ai/rMYM9lJ1WuF9d7p6/RtDA=="],
108
108
 
109
109
  "@onebun/drizzle": ["@onebun/drizzle@0.2.4", "", { "dependencies": { "@onebun/envs": "^0.2.1", "@onebun/logger": "^0.2.1", "arktype": "^2.0.0", "drizzle-arktype": "^0.1.3", "drizzle-kit": "^0.31.6", "drizzle-orm": "^0.44.7", "effect": "^3.13.10" }, "peerDependencies": { "@onebun/core": ">=0.2.0" }, "bin": { "onebun-drizzle": "bin/drizzle-kit.ts" } }, "sha512-LbkW2hU9pTKZU/rlrHNdwhI4jYoMl+v22c3G2zc0L0aW77nW7ZCfp5YqOJYufWJbfOTSWEnNOVZQXMueYhBxsA=="],
110
110
 
111
- "@onebun/envs": ["@onebun/envs@0.2.1", "", { "dependencies": { "effect": "^3.13.10" } }, "sha512-kiXJcA4ct194+aNJK8zkrVuaAgPPVpTkcW8tJU9XN9KOh8003lENOOuUZUcieMCxdMWUgo08lp9UgiwawLan+Q=="],
111
+ "@onebun/envs": ["@onebun/envs@0.2.2", "", { "dependencies": { "effect": "^3.13.10" } }, "sha512-WIjc1LpGnecYArSWsZhheyUSYJlo+iz9SA7ZfIXQnt1vkLd7ILCmVCtODBvqG9Mh86CMmromf1lCyRkjNZyLoA=="],
112
112
 
113
113
  "@onebun/logger": ["@onebun/logger@0.2.1", "", { "dependencies": { "effect": "^3.13.10" } }, "sha512-u/zirsUSGBfbjVv274qqIHG5jzPBWY3vl8HzM6hjzsMCCpExgstkQiP1eP9rF1isIzhetwmyfBpYYc9eYsbrrw=="],
114
114
 
115
115
  "@onebun/metrics": ["@onebun/metrics@0.2.2", "", { "dependencies": { "@onebun/requests": "^0.2.1", "effect": "^3.13.10", "prom-client": "^15.1.3" } }, "sha512-8oN74MZeaWyyPHi5H3pZyY0V3cM8ORupHe2fR0gGYsZqHyM4S9UJStV29rNQlvhyesHzL7p5x3Ux6n/SRYBszw=="],
116
116
 
117
- "@onebun/nats": ["@onebun/nats@0.2.5", "", { "dependencies": { "@nats-io/jetstream": "^3.0.0-31", "@nats-io/transport-node": "^3.0.0-31", "effect": "^3.13.10" }, "peerDependencies": { "@onebun/core": ">=0.2.0" } }, "sha512-J+p9UPJGqBLeeBY6n50E6at1lyA+xmg+wzqw1HwN516t+XHzctmnrB8S5PmX/3sQI18fktFcJgAyWJfQA1piDA=="],
117
+ "@onebun/nats": ["@onebun/nats@0.2.6", "", { "dependencies": { "@nats-io/jetstream": "^3.0.0-31", "@nats-io/transport-node": "^3.0.0-31", "effect": "^3.13.10" }, "peerDependencies": { "@onebun/core": ">=0.2.0" } }, "sha512-b7b0PUu0eGDLPbstOVjaCW0GCKsBzQb8qyYQmjKPu6RCsm02qvUU8HWDytoy01mYnQxWpI93qVogGiQKd6Ps4A=="],
118
118
 
119
119
  "@onebun/requests": ["@onebun/requests@0.2.1", "", { "dependencies": { "effect": "^3.13.10" } }, "sha512-Wit+o3zRiuZOM7O0nAJ0rpFVLgkypJ1UR5uuHi0IZuiCvGmxv+Vus2+QqHoCL141L7SPO1Xlywt8dVqqu4NP7w=="],
120
120
 
@@ -446,6 +446,8 @@
446
446
 
447
447
  "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
448
448
 
449
+ "@onebun/drizzle/@onebun/envs": ["@onebun/envs@0.2.1", "", { "dependencies": { "effect": "^3.13.10" } }, "sha512-kiXJcA4ct194+aNJK8zkrVuaAgPPVpTkcW8tJU9XN9KOh8003lENOOuUZUcieMCxdMWUgo08lp9UgiwawLan+Q=="],
450
+
449
451
  "bcrypt-pbkdf/tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
450
452
 
451
453
  "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
@@ -11,14 +11,14 @@
11
11
  "typecheck": "bunx tsc --noEmit",
12
12
  "db:generate": "bunx onebun-drizzle generate",
13
13
  "db:push": "bunx onebun-drizzle push",
14
- "db:studio": "bunx onebun-drizzle studio"
14
+ "db:studio": "bunx onebun-drizzle studio",
15
15
  },
16
16
  "dependencies": {
17
- "@onebun/core": "^0.2.12",
17
+ "@onebun/core": "^0.2.14",
18
18
  "@onebun/drizzle": "^0.2.4",
19
- "@onebun/envs": "^0.2.1",
19
+ "@onebun/envs": "^0.2.2",
20
20
  "@onebun/logger": "^0.2.1",
21
- "@onebun/nats": "^0.2.5",
21
+ "@onebun/nats": "^0.2.6",
22
22
  "arktype": "^2.2.0",
23
23
  "ulid": "^2.3.0"
24
24
  },
@@ -1,5 +1,6 @@
1
- import { Module } from '@onebun/core';
1
+ import { getConfig, Module } from '@onebun/core';
2
2
  import { DrizzleModule, DatabaseType } from '@onebun/drizzle';
3
+ import { envSchema, type AppConfig } from './config';
3
4
  import { DedupModule } from './dedup/dedup.module';
4
5
  import { PublisherModule } from './publisher/publisher.module';
5
6
  import { PreHandlersModule } from './pre-handlers/pre-handlers.module';
@@ -8,6 +9,10 @@ import { ConsumerModule } from './consumer/consumer.module';
8
9
  import { PendingModule } from './pending/pending.module';
9
10
  import { HealthModule } from './health/health.module';
10
11
  import { RouterModule } from './router/router.module';
12
+ import { SchedulerModule } from './scheduler/scheduler.module';
13
+ import { MetricsModule } from './metrics/metrics.module';
14
+
15
+ const config = getConfig<AppConfig>(envSchema);
11
16
 
12
17
  @Module({
13
18
  imports: [
@@ -15,7 +20,7 @@ import { RouterModule } from './router/router.module';
15
20
  connection: {
16
21
  type: DatabaseType.SQLITE,
17
22
  options: {
18
- url: process.env.DB_PATH ?? './data/nats-sidecar.db',
23
+ url: config.get('database.url'),
19
24
  },
20
25
  },
21
26
  migrationsFolder: './src/db/migrations',
@@ -28,6 +33,8 @@ import { RouterModule } from './router/router.module';
28
33
  PendingModule,
29
34
  HealthModule,
30
35
  RouterModule,
36
+ SchedulerModule,
37
+ MetricsModule,
31
38
  ],
32
39
  })
33
40
  export class AppModule {}
@@ -3,6 +3,7 @@ import { PipelineService } from '../pre-handlers/pipeline.service';
3
3
  import { GatewayClientService } from '../gateway/gateway-client.service';
4
4
  import { PendingService } from '../pending/pending.service';
5
5
  import { RouterService } from '../router/router.service';
6
+ import { MetricsService } from '../metrics/metrics.service';
6
7
  import type { NatsEventEnvelope } from '../publisher/envelope';
7
8
 
8
9
  @Controller('/consumer')
@@ -12,6 +13,7 @@ export class ConsumerController extends BaseController {
12
13
  private gatewayClient: GatewayClientService,
13
14
  private pendingService: PendingService,
14
15
  private routerService: RouterService,
16
+ private metrics: MetricsService,
15
17
  ) {
16
18
  super();
17
19
  }
@@ -57,6 +59,8 @@ export class ConsumerController extends BaseController {
57
59
  priority: (ctx.enrichments['priority'] as number) ?? envelope.meta?.priority ?? 5,
58
60
  },
59
61
  });
62
+ await this.routerService.recordDelivery(route.id, envelope.subject);
63
+ this.metrics.recordConsume(envelope.subject);
60
64
  }
61
65
  await message.ack();
62
66
  } else {
@@ -3,9 +3,10 @@ import { ConsumerController } from './consumer.controller';
3
3
  import { PreHandlersModule } from '../pre-handlers/pre-handlers.module';
4
4
  import { PendingModule } from '../pending/pending.module';
5
5
  import { RouterModule } from '../router/router.module';
6
+ import { MetricsModule } from '../metrics/metrics.module';
6
7
 
7
8
  @Module({
8
- imports: [PreHandlersModule, PendingModule, RouterModule],
9
+ imports: [PreHandlersModule, PendingModule, RouterModule, MetricsModule],
9
10
  controllers: [ConsumerController],
10
11
  })
11
12
  export class ConsumerModule {}