@hachej/boring-automation 0.1.79 → 0.1.80

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.
@@ -172,6 +172,7 @@ interface BoringAutomationServerPluginOptions {
172
172
  store?: AutomationStore;
173
173
  dispatcherResolver?: WorkspaceAgentDispatcherResolver;
174
174
  actorResolver?: (request: FastifyRequest) => Promise<VerifiedAutomationActor> | VerifiedAutomationActor;
175
+ storeForRequest?: (request: FastifyRequest, actor: VerifiedAutomationActor) => Promise<AutomationStore> | AutomationStore;
175
176
  }
176
177
  declare function createBoringAutomationServerPlugin(options?: BoringAutomationServerPluginOptions): WorkspaceServerPlugin;
177
178
  declare function defaultBoringAutomationServerPlugin(options?: BoringAutomationServerPluginOptions, ctx?: Pick<WorkspaceAgentServerPluginContext, "workspaceRoot"> & Partial<Pick<WorkspaceAgentServerPluginContext, "trusted">>): WorkspaceServerPlugin;
@@ -515,6 +515,162 @@ function clone(value) {
515
515
  return JSON.parse(JSON.stringify(value));
516
516
  }
517
517
 
518
+ // src/server/postgresStore.ts
519
+ import { randomUUID as randomUUID2 } from "crypto";
520
+ var PostgresAutomationStore = class {
521
+ constructor(sql, actor, clock = () => /* @__PURE__ */ new Date()) {
522
+ this.sql = sql;
523
+ this.actor = actor;
524
+ this.clock = clock;
525
+ }
526
+ sql;
527
+ actor;
528
+ clock;
529
+ async listAutomations() {
530
+ const rows = await this.sql`
531
+ SELECT id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
532
+ FROM boring_automation_automations
533
+ WHERE workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
534
+ ORDER BY created_at, id
535
+ `;
536
+ return rows.map(toAutomation);
537
+ }
538
+ async getAutomation(id) {
539
+ const rows = await this.sql`
540
+ SELECT id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
541
+ FROM boring_automation_automations
542
+ WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
543
+ `;
544
+ return rows[0] ? toAutomation(rows[0]) : null;
545
+ }
546
+ async createAutomation(input) {
547
+ const now = this.clock().toISOString();
548
+ const id = randomUUID2();
549
+ const prompt = input.prompt ?? "";
550
+ const rows = await this.sql`
551
+ INSERT INTO boring_automation_automations (id, workspace_id, owner_user_id, title, enabled, cron, timezone, model, prompt, created_at, updated_at)
552
+ VALUES (${id}, ${this.actor.workspaceId}, ${this.actor.userId}, ${input.title}, ${input.enabled ?? true}, ${input.cron}, ${input.timezone}, ${input.model}, ${prompt}, ${now}, ${now})
553
+ RETURNING id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
554
+ `;
555
+ return toAutomation(rows[0]);
556
+ }
557
+ async updateAutomation(id, patch) {
558
+ const current = await this.getAutomation(id);
559
+ if (!current) throw automationNotFound(id);
560
+ const next = { ...current, ...patch, updatedAt: this.clock().toISOString() };
561
+ const rows = await this.sql`
562
+ UPDATE boring_automation_automations
563
+ SET title = ${next.title}, enabled = ${next.enabled}, cron = ${next.cron}, timezone = ${next.timezone}, model = ${next.model}, updated_at = ${next.updatedAt}
564
+ WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
565
+ RETURNING id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
566
+ `;
567
+ if (!rows[0]) throw automationNotFound(id);
568
+ return toAutomation(rows[0]);
569
+ }
570
+ async deleteAutomation(id) {
571
+ const result = await this.sql`
572
+ DELETE FROM boring_automation_automations
573
+ WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
574
+ `;
575
+ if (result.count === 0) throw automationNotFound(id);
576
+ }
577
+ async getPrompt(automationId) {
578
+ const automation = await this.getAutomation(automationId);
579
+ if (!automation) throw automationNotFound(automationId);
580
+ const rows = await this.sql`
581
+ SELECT prompt FROM boring_automation_automations
582
+ WHERE id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
583
+ `;
584
+ return rows[0]?.prompt ?? "";
585
+ }
586
+ async updatePrompt(automationId, body) {
587
+ const result = await this.sql`
588
+ UPDATE boring_automation_automations SET prompt = ${body}, updated_at = ${this.clock().toISOString()}
589
+ WHERE id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
590
+ `;
591
+ if (result.count === 0) throw automationNotFound(automationId);
592
+ }
593
+ async reconcileOrphanedRuns(automationId) {
594
+ await this.sql`
595
+ UPDATE boring_automation_runs
596
+ SET status = 'failed', completed_at = ${this.clock().toISOString()}, error = 'Automation host restarted before the run completed', updated_at = ${this.clock().toISOString()}
597
+ WHERE automation_id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId} AND status IN ('queued', 'running')
598
+ `;
599
+ }
600
+ async beginRun(input) {
601
+ const automation = await this.getAutomation(input.automationId);
602
+ if (!automation) throw automationNotFound(input.automationId);
603
+ const active = await this.sql`
604
+ SELECT id FROM boring_automation_runs
605
+ WHERE automation_id = ${input.automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId} AND status IN ('queued', 'running')
606
+ LIMIT 1
607
+ `;
608
+ if (active[0]) throw runAlreadyActive(input.automationId);
609
+ try {
610
+ const now = input.createdAt ?? this.clock().toISOString();
611
+ const id = randomUUID2();
612
+ const rows = await this.sql`
613
+ INSERT INTO boring_automation_runs (id, automation_id, workspace_id, owner_user_id, session_id, status, trigger, scheduled_for, started_at, completed_at, duration_ms, input_tokens, output_tokens, total_tokens, prompt_snapshot, model_snapshot, error, created_at, updated_at)
614
+ VALUES (${id}, ${input.automationId}, ${this.actor.workspaceId}, ${this.actor.userId}, NULL, 'queued', ${input.trigger}, ${input.scheduledFor ?? null}, NULL, NULL, NULL, NULL, NULL, NULL, ${input.promptSnapshot}, ${input.modelSnapshot}, NULL, ${now}, ${now})
615
+ RETURNING *
616
+ `;
617
+ return toRun(rows[0]);
618
+ } catch (error) {
619
+ if (isUniqueViolation(error)) {
620
+ if (constraintName(error) === "boring_automation_runs_active_once_idx") throw runAlreadyActive(input.automationId);
621
+ if (input.trigger === "scheduled" && input.scheduledFor) throw runAlreadyRecorded(input.automationId, input.scheduledFor);
622
+ }
623
+ throw error;
624
+ }
625
+ }
626
+ async updateRunLifecycle(runId, patch) {
627
+ const current = await this.findRun(runId);
628
+ if (!current) throw runNotFound(runId);
629
+ const next = { ...current, ...patch, updatedAt: this.clock().toISOString() };
630
+ const rows = await this.sql`
631
+ UPDATE boring_automation_runs
632
+ SET session_id = ${next.sessionId}, status = ${next.status}, started_at = ${next.startedAt}, completed_at = ${next.completedAt}, duration_ms = ${next.durationMs}, input_tokens = ${next.inputTokens}, output_tokens = ${next.outputTokens}, total_tokens = ${next.totalTokens}, error = ${next.error}, updated_at = ${next.updatedAt}
633
+ WHERE id = ${runId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
634
+ RETURNING *
635
+ `;
636
+ if (!rows[0]) throw runNotFound(runId);
637
+ return toRun(rows[0]);
638
+ }
639
+ async listRuns(automationId) {
640
+ const rows = await this.sql`
641
+ SELECT * FROM boring_automation_runs
642
+ WHERE automation_id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
643
+ ORDER BY created_at DESC, id DESC
644
+ `;
645
+ return rows.map(toRun);
646
+ }
647
+ async findRun(runId) {
648
+ const rows = await this.sql`
649
+ SELECT * FROM boring_automation_runs
650
+ WHERE id = ${runId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
651
+ `;
652
+ return rows[0] ? toRun(rows[0]) : null;
653
+ }
654
+ };
655
+ function toAutomation(row) {
656
+ return { id: row.id, title: row.title, enabled: row.enabled, cron: row.cron, timezone: row.timezone, model: row.model, promptRef: `hosted:${row.id}`, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) };
657
+ }
658
+ function toRun(row) {
659
+ return { id: row.id, automationId: row.automation_id, sessionId: row.session_id, status: row.status, trigger: row.trigger, scheduledFor: nullableIso(row.scheduled_for), startedAt: nullableIso(row.started_at), completedAt: nullableIso(row.completed_at), durationMs: row.duration_ms, inputTokens: row.input_tokens, outputTokens: row.output_tokens, totalTokens: row.total_tokens, promptSnapshot: row.prompt_snapshot, modelSnapshot: row.model_snapshot, error: row.error, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) };
660
+ }
661
+ function iso(value) {
662
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
663
+ }
664
+ function nullableIso(value) {
665
+ return value === null ? null : iso(value);
666
+ }
667
+ function isUniqueViolation(error) {
668
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "23505");
669
+ }
670
+ function constraintName(error) {
671
+ return error && typeof error === "object" && "constraint_name" in error ? String(error.constraint_name) : void 0;
672
+ }
673
+
518
674
  // src/server/manualRunExecutor.ts
519
675
  var ManualRunExecutor = class {
520
676
  constructor(options) {
@@ -934,172 +1090,25 @@ async function runBoringAutomationMigrations(sql) {
934
1090
  `);
935
1091
  }
936
1092
 
937
- // src/server/postgresStore.ts
938
- import { randomUUID as randomUUID2 } from "crypto";
939
- var PostgresAutomationStore = class {
940
- constructor(sql, actor, clock = () => /* @__PURE__ */ new Date()) {
941
- this.sql = sql;
942
- this.actor = actor;
943
- this.clock = clock;
944
- }
945
- sql;
946
- actor;
947
- clock;
948
- async listAutomations() {
949
- const rows = await this.sql`
950
- SELECT id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
951
- FROM boring_automation_automations
952
- WHERE workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
953
- ORDER BY created_at, id
954
- `;
955
- return rows.map(toAutomation);
956
- }
957
- async getAutomation(id) {
958
- const rows = await this.sql`
959
- SELECT id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
960
- FROM boring_automation_automations
961
- WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
962
- `;
963
- return rows[0] ? toAutomation(rows[0]) : null;
964
- }
965
- async createAutomation(input) {
966
- const now = this.clock().toISOString();
967
- const id = randomUUID2();
968
- const prompt = input.prompt ?? "";
969
- const rows = await this.sql`
970
- INSERT INTO boring_automation_automations (id, workspace_id, owner_user_id, title, enabled, cron, timezone, model, prompt, created_at, updated_at)
971
- VALUES (${id}, ${this.actor.workspaceId}, ${this.actor.userId}, ${input.title}, ${input.enabled ?? true}, ${input.cron}, ${input.timezone}, ${input.model}, ${prompt}, ${now}, ${now})
972
- RETURNING id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
973
- `;
974
- return toAutomation(rows[0]);
975
- }
976
- async updateAutomation(id, patch) {
977
- const current = await this.getAutomation(id);
978
- if (!current) throw automationNotFound(id);
979
- const next = { ...current, ...patch, updatedAt: this.clock().toISOString() };
980
- const rows = await this.sql`
981
- UPDATE boring_automation_automations
982
- SET title = ${next.title}, enabled = ${next.enabled}, cron = ${next.cron}, timezone = ${next.timezone}, model = ${next.model}, updated_at = ${next.updatedAt}
983
- WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
984
- RETURNING id, title, enabled, cron, timezone, model, prompt, created_at, updated_at
985
- `;
986
- if (!rows[0]) throw automationNotFound(id);
987
- return toAutomation(rows[0]);
988
- }
989
- async deleteAutomation(id) {
990
- const result = await this.sql`
991
- DELETE FROM boring_automation_automations
992
- WHERE id = ${id} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
993
- `;
994
- if (result.count === 0) throw automationNotFound(id);
995
- }
996
- async getPrompt(automationId) {
997
- const automation = await this.getAutomation(automationId);
998
- if (!automation) throw automationNotFound(automationId);
999
- const rows = await this.sql`
1000
- SELECT prompt FROM boring_automation_automations
1001
- WHERE id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
1002
- `;
1003
- return rows[0]?.prompt ?? "";
1004
- }
1005
- async updatePrompt(automationId, body) {
1006
- const result = await this.sql`
1007
- UPDATE boring_automation_automations SET prompt = ${body}, updated_at = ${this.clock().toISOString()}
1008
- WHERE id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
1009
- `;
1010
- if (result.count === 0) throw automationNotFound(automationId);
1011
- }
1012
- async reconcileOrphanedRuns(automationId) {
1013
- await this.sql`
1014
- UPDATE boring_automation_runs
1015
- SET status = 'failed', completed_at = ${this.clock().toISOString()}, error = 'Automation host restarted before the run completed', updated_at = ${this.clock().toISOString()}
1016
- WHERE automation_id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId} AND status IN ('queued', 'running')
1017
- `;
1018
- }
1019
- async beginRun(input) {
1020
- const automation = await this.getAutomation(input.automationId);
1021
- if (!automation) throw automationNotFound(input.automationId);
1022
- const active = await this.sql`
1023
- SELECT id FROM boring_automation_runs
1024
- WHERE automation_id = ${input.automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId} AND status IN ('queued', 'running')
1025
- LIMIT 1
1026
- `;
1027
- if (active[0]) throw runAlreadyActive(input.automationId);
1028
- try {
1029
- const now = input.createdAt ?? this.clock().toISOString();
1030
- const id = randomUUID2();
1031
- const rows = await this.sql`
1032
- INSERT INTO boring_automation_runs (id, automation_id, workspace_id, owner_user_id, session_id, status, trigger, scheduled_for, started_at, completed_at, duration_ms, input_tokens, output_tokens, total_tokens, prompt_snapshot, model_snapshot, error, created_at, updated_at)
1033
- VALUES (${id}, ${input.automationId}, ${this.actor.workspaceId}, ${this.actor.userId}, NULL, 'queued', ${input.trigger}, ${input.scheduledFor ?? null}, NULL, NULL, NULL, NULL, NULL, NULL, ${input.promptSnapshot}, ${input.modelSnapshot}, NULL, ${now}, ${now})
1034
- RETURNING *
1035
- `;
1036
- return toRun(rows[0]);
1037
- } catch (error) {
1038
- if (isUniqueViolation(error)) {
1039
- if (constraintName(error) === "boring_automation_runs_active_once_idx") throw runAlreadyActive(input.automationId);
1040
- if (input.trigger === "scheduled" && input.scheduledFor) throw runAlreadyRecorded(input.automationId, input.scheduledFor);
1041
- }
1042
- throw error;
1043
- }
1044
- }
1045
- async updateRunLifecycle(runId, patch) {
1046
- const current = await this.findRun(runId);
1047
- if (!current) throw runNotFound(runId);
1048
- const next = { ...current, ...patch, updatedAt: this.clock().toISOString() };
1049
- const rows = await this.sql`
1050
- UPDATE boring_automation_runs
1051
- SET session_id = ${next.sessionId}, status = ${next.status}, started_at = ${next.startedAt}, completed_at = ${next.completedAt}, duration_ms = ${next.durationMs}, input_tokens = ${next.inputTokens}, output_tokens = ${next.outputTokens}, total_tokens = ${next.totalTokens}, error = ${next.error}, updated_at = ${next.updatedAt}
1052
- WHERE id = ${runId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
1053
- RETURNING *
1054
- `;
1055
- if (!rows[0]) throw runNotFound(runId);
1056
- return toRun(rows[0]);
1057
- }
1058
- async listRuns(automationId) {
1059
- const rows = await this.sql`
1060
- SELECT * FROM boring_automation_runs
1061
- WHERE automation_id = ${automationId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
1062
- ORDER BY created_at DESC, id DESC
1063
- `;
1064
- return rows.map(toRun);
1065
- }
1066
- async findRun(runId) {
1067
- const rows = await this.sql`
1068
- SELECT * FROM boring_automation_runs
1069
- WHERE id = ${runId} AND workspace_id = ${this.actor.workspaceId} AND owner_user_id = ${this.actor.userId}
1070
- `;
1071
- return rows[0] ? toRun(rows[0]) : null;
1072
- }
1073
- };
1074
- function toAutomation(row) {
1075
- return { id: row.id, title: row.title, enabled: row.enabled, cron: row.cron, timezone: row.timezone, model: row.model, promptRef: `hosted:${row.id}`, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) };
1076
- }
1077
- function toRun(row) {
1078
- return { id: row.id, automationId: row.automation_id, sessionId: row.session_id, status: row.status, trigger: row.trigger, scheduledFor: nullableIso(row.scheduled_for), startedAt: nullableIso(row.started_at), completedAt: nullableIso(row.completed_at), durationMs: row.duration_ms, inputTokens: row.input_tokens, outputTokens: row.output_tokens, totalTokens: row.total_tokens, promptSnapshot: row.prompt_snapshot, modelSnapshot: row.model_snapshot, error: row.error, createdAt: iso(row.created_at), updatedAt: iso(row.updated_at) };
1079
- }
1080
- function iso(value) {
1081
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
1082
- }
1083
- function nullableIso(value) {
1084
- return value === null ? null : iso(value);
1085
- }
1086
- function isUniqueViolation(error) {
1087
- return Boolean(error && typeof error === "object" && "code" in error && error.code === "23505");
1088
- }
1089
- function constraintName(error) {
1090
- return error && typeof error === "object" && "constraint_name" in error ? String(error.constraint_name) : void 0;
1091
- }
1092
-
1093
1093
  // src/server/index.ts
1094
1094
  function createBoringAutomationServerPlugin(options = {}) {
1095
1095
  const store = options.store ?? createDefaultStore(options.workspaceRoot);
1096
- const manualRunExecutor = options.dispatcherResolver && options.actorResolver ? new ManualRunExecutor({ store, dispatcherResolver: options.dispatcherResolver, actorResolver: options.actorResolver }) : void 0;
1097
- const dueRunService = manualRunExecutor ? new DueRunService({ store, executor: manualRunExecutor }) : void 0;
1096
+ const manualRunExecutor = options.dispatcherResolver && options.actorResolver ? new ManualRunExecutor({
1097
+ store,
1098
+ storeForRequest: options.storeForRequest,
1099
+ dispatcherResolver: options.dispatcherResolver,
1100
+ actorResolver: options.actorResolver
1101
+ }) : void 0;
1102
+ const dueRunService = manualRunExecutor && !options.storeForRequest ? new DueRunService({ store, executor: manualRunExecutor }) : void 0;
1098
1103
  return defineServerPlugin({
1099
1104
  id: BORING_AUTOMATION_PLUGIN_ID,
1100
1105
  label: BORING_AUTOMATION_PLUGIN_LABEL,
1101
1106
  routes: async (app) => {
1102
- await automationRoutes(app, { store, manualRunExecutor, dueRunService });
1107
+ await automationRoutes(app, { store, storeForRequest: options.storeForRequest ? async (request) => {
1108
+ const actor = options.actorResolver ? await options.actorResolver(request) : void 0;
1109
+ if (!actor) throw new Error("automation actor resolver is unavailable");
1110
+ return await options.storeForRequest(request, actor);
1111
+ } : void 0, manualRunExecutor, dueRunService });
1103
1112
  }
1104
1113
  });
1105
1114
  }
@@ -1109,6 +1118,17 @@ function createDefaultStore(workspaceRoot) {
1109
1118
  }
1110
1119
  function defaultBoringAutomationServerPlugin(options, ctx) {
1111
1120
  const trusted = ctx?.trusted;
1121
+ if (!options?.store && trusted?.sql && trusted.workspaceAgentDispatcherResolver && trusted.actorResolver) {
1122
+ const sql = trusted.sql;
1123
+ const fallbackStore = new PostgresAutomationStore(sql, { workspaceId: "unbound", userId: "unbound" });
1124
+ return createBoringAutomationServerPlugin({
1125
+ ...options,
1126
+ store: fallbackStore,
1127
+ storeForRequest: async (_request, actor) => new PostgresAutomationStore(sql, actor),
1128
+ dispatcherResolver: options?.dispatcherResolver ?? trusted.workspaceAgentDispatcherResolver,
1129
+ actorResolver: options?.actorResolver ?? trusted.actorResolver
1130
+ });
1131
+ }
1112
1132
  return createBoringAutomationServerPlugin({
1113
1133
  ...options,
1114
1134
  workspaceRoot: options?.workspaceRoot ?? ctx?.workspaceRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-automation",
3
- "version": "0.1.79",
3
+ "version": "0.1.80",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -43,14 +43,14 @@
43
43
  "fastify": "^5.9.0",
44
44
  "react": "^18.0.0 || ^19.0.0",
45
45
  "react-dom": "^18.0.0 || ^19.0.0",
46
- "@hachej/boring-workspace": "0.1.79"
46
+ "@hachej/boring-workspace": "0.1.80"
47
47
  },
48
48
  "dependencies": {
49
49
  "croner": "^10.0.1",
50
50
  "lucide-react": "^1.21.0",
51
51
  "postgres": "^3.4.7",
52
52
  "zod": "^3.23.0",
53
- "@hachej/boring-ui-kit": "0.1.79"
53
+ "@hachej/boring-ui-kit": "0.1.80"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@testing-library/jest-dom": "^6.9.1",
@@ -66,7 +66,7 @@
66
66
  "tsup": "^8.4.0",
67
67
  "typescript": "~6.0.3",
68
68
  "vitest": "^4.1.9",
69
- "@hachej/boring-workspace": "0.1.79"
69
+ "@hachej/boring-workspace": "0.1.80"
70
70
  },
71
71
  "peerDependenciesMeta": {
72
72
  "fastify": {