···11export * as schema from "./schema";
22export * from "drizzle-orm";
33export * from "./db";
44+export * from "./sync";
45// doing this because the external module not working see : https://github.com/vercel/next.js/issues/43433
56// export * from "./sync-db";
+2
packages/db/src/schema/index.ts
···1919export * from "./monitor_groups";
2020export * from "./viewers";
2121export * from "./api-keys";
2222+export * from "./page_components";
2323+export * from "./page_component_groups";
···11+import { createInsertSchema, createSelectSchema } from "drizzle-zod";
22+import type { z } from "zod";
33+44+import { pageComponent } from "./page_components";
55+66+export const selectPageComponentSchema = createSelectSchema(pageComponent);
77+88+export const insertPageComponentSchema = createInsertSchema(pageComponent, {
99+ name: (schema) => schema.min(1),
1010+}).refine(
1111+ (data) => {
1212+ // monitorId must be set when type='monitor'
1313+ if (data.type === "monitor" && !data.monitorId) {
1414+ return false;
1515+ }
1616+ // monitorId must be null when type='external'
1717+ if (data.type === "external" && data.monitorId) {
1818+ return false;
1919+ }
2020+ return true;
2121+ },
2222+ {
2323+ message:
2424+ "monitorId must be set when type is 'monitor' and must be null when type is 'external'",
2525+ },
2626+);
2727+2828+export type InsertPageComponent = z.infer<typeof insertPageComponentSchema>;
2929+export type PageComponent = z.infer<typeof selectPageComponentSchema>;
+515
packages/db/src/sync.ts
···11+import { and, eq, inArray } from "drizzle-orm";
22+33+import type { db } from "./db";
44+import {
55+ maintenance,
66+ maintenancesToPageComponents,
77+ monitor,
88+ pageComponent,
99+ pageComponentGroup,
1010+ statusReport,
1111+ statusReportsToPageComponents,
1212+} from "./schema";
1313+1414+// Type that works with both LibSQLDatabase and SQLiteTransaction
1515+// Using any to allow both db instance and transaction to be passed
1616+// biome-ignore lint/suspicious/noExplicitAny: Compatible with both db and transaction
1717+type DB = typeof db;
1818+// Extract transaction type from the callback parameter of db.transaction()
1919+type Transaction = Parameters<Parameters<DB["transaction"]>[0]>[0];
2020+2121+// ============================================================================
2222+// Monitor Group <-> Page Component Group Sync
2323+// ============================================================================
2424+2525+/**
2626+ * Syncs a monitor group insert to page_component_groups
2727+ */
2828+export async function syncMonitorGroupInsert(
2929+ db: DB | Transaction,
3030+ data: {
3131+ id: number;
3232+ workspaceId: number;
3333+ pageId: number;
3434+ name: string;
3535+ },
3636+) {
3737+ await db
3838+ .insert(pageComponentGroup)
3939+ .values({
4040+ id: data.id,
4141+ workspaceId: data.workspaceId,
4242+ pageId: data.pageId,
4343+ name: data.name,
4444+ })
4545+ .onConflictDoNothing();
4646+}
4747+4848+/**
4949+ * Syncs a monitor group delete to page_component_groups
5050+ */
5151+export async function syncMonitorGroupDelete(
5252+ db: DB | Transaction,
5353+ monitorGroupId: number,
5454+) {
5555+ await db
5656+ .delete(pageComponentGroup)
5757+ .where(eq(pageComponentGroup.id, monitorGroupId));
5858+}
5959+6060+/**
6161+ * Syncs multiple monitor group deletes to page_component_groups
6262+ */
6363+export async function syncMonitorGroupDeleteMany(
6464+ db: DB | Transaction,
6565+ monitorGroupIds: number[],
6666+) {
6767+ if (monitorGroupIds.length === 0) return;
6868+ await db
6969+ .delete(pageComponentGroup)
7070+ .where(inArray(pageComponentGroup.id, monitorGroupIds));
7171+}
7272+7373+// ============================================================================
7474+// Monitors to Pages <-> Page Component Sync
7575+// ============================================================================
7676+7777+/**
7878+ * Syncs a monitors_to_pages insert to page_component
7979+ * Requires monitor data to get name and workspace_id
8080+ */
8181+export async function syncMonitorsToPageInsert(
8282+ db: DB | Transaction,
8383+ data: {
8484+ monitorId: number;
8585+ pageId: number;
8686+ order?: number;
8787+ monitorGroupId?: number | null;
8888+ groupOrder?: number;
8989+ },
9090+) {
9191+ // Get monitor data for name and workspace_id
9292+ const monitorData = await db
9393+ .select({
9494+ id: monitor.id,
9595+ name: monitor.name,
9696+ externalName: monitor.externalName,
9797+ workspaceId: monitor.workspaceId,
9898+ })
9999+ .from(monitor)
100100+ .where(eq(monitor.id, data.monitorId))
101101+ .get();
102102+103103+ if (!monitorData || !monitorData.workspaceId) return;
104104+105105+ await db
106106+ .insert(pageComponent)
107107+ .values({
108108+ workspaceId: monitorData.workspaceId,
109109+ pageId: data.pageId,
110110+ type: "monitor",
111111+ monitorId: data.monitorId,
112112+ name: monitorData.externalName || monitorData.name,
113113+ order: data.order ?? 0,
114114+ groupId: data.monitorGroupId ?? null,
115115+ groupOrder: data.groupOrder ?? 0,
116116+ })
117117+ .onConflictDoNothing();
118118+}
119119+120120+/**
121121+ * Syncs multiple monitors_to_pages inserts to page_component
122122+ */
123123+export async function syncMonitorsToPageInsertMany(
124124+ db: DB | Transaction,
125125+ items: Array<{
126126+ monitorId: number;
127127+ pageId: number;
128128+ order?: number;
129129+ monitorGroupId?: number | null;
130130+ groupOrder?: number;
131131+ }>,
132132+) {
133133+ if (items.length === 0) return;
134134+135135+ // Get all monitor data in one query
136136+ const monitorIds = [...new Set(items.map((item) => item.monitorId))];
137137+ const monitors = await db
138138+ .select({
139139+ id: monitor.id,
140140+ name: monitor.name,
141141+ externalName: monitor.externalName,
142142+ workspaceId: monitor.workspaceId,
143143+ })
144144+ .from(monitor)
145145+ .where(inArray(monitor.id, monitorIds));
146146+147147+ const monitorMap = new Map(monitors.map((m) => [m.id, m]));
148148+149149+ const values = items
150150+ .map((item) => {
151151+ const m = monitorMap.get(item.monitorId);
152152+ if (!m || !m.workspaceId) return null;
153153+ return {
154154+ workspaceId: m.workspaceId,
155155+ pageId: item.pageId,
156156+ type: "monitor" as const,
157157+ monitorId: item.monitorId,
158158+ name: m.externalName || m.name,
159159+ order: item.order ?? 0,
160160+ groupId: item.monitorGroupId ?? null,
161161+ groupOrder: item.groupOrder ?? 0,
162162+ };
163163+ })
164164+ .filter((v): v is NonNullable<typeof v> => v !== null);
165165+166166+ if (values.length === 0) return;
167167+168168+ await db.insert(pageComponent).values(values).onConflictDoNothing();
169169+}
170170+171171+/**
172172+ * Syncs a monitors_to_pages delete to page_component
173173+ */
174174+export async function syncMonitorsToPageDelete(
175175+ db: DB | Transaction,
176176+ data: { monitorId: number; pageId: number },
177177+) {
178178+ await db
179179+ .delete(pageComponent)
180180+ .where(
181181+ and(
182182+ eq(pageComponent.monitorId, data.monitorId),
183183+ eq(pageComponent.pageId, data.pageId),
184184+ ),
185185+ );
186186+}
187187+188188+/**
189189+ * Syncs monitors_to_pages deletes for a specific page to page_component
190190+ */
191191+export async function syncMonitorsToPageDeleteByPage(
192192+ db: DB | Transaction,
193193+ pageId: number,
194194+) {
195195+ await db
196196+ .delete(pageComponent)
197197+ .where(
198198+ and(eq(pageComponent.pageId, pageId), eq(pageComponent.type, "monitor")),
199199+ );
200200+}
201201+202202+/**
203203+ * Syncs monitors_to_pages deletes for specific monitors to page_component
204204+ */
205205+export async function syncMonitorsToPageDeleteByMonitors(
206206+ db: DB | Transaction,
207207+ monitorIds: number[],
208208+) {
209209+ if (monitorIds.length === 0) return;
210210+ await db
211211+ .delete(pageComponent)
212212+ .where(inArray(pageComponent.monitorId, monitorIds));
213213+}
214214+215215+// ============================================================================
216216+// Status Report to Monitors <-> Status Report to Page Components Sync
217217+// ============================================================================
218218+219219+/**
220220+ * Syncs a status_report_to_monitors insert to status_report_to_page_component
221221+ */
222222+export async function syncStatusReportToMonitorInsert(
223223+ db: DB | Transaction,
224224+ data: { statusReportId: number; monitorId: number },
225225+) {
226226+ // Get the status report's page_id
227227+ const report = await db
228228+ .select({ pageId: statusReport.pageId })
229229+ .from(statusReport)
230230+ .where(eq(statusReport.id, data.statusReportId))
231231+ .get();
232232+233233+ // Find matching page_components
234234+ const components = await db
235235+ .select({ id: pageComponent.id })
236236+ .from(pageComponent)
237237+ .where(
238238+ and(
239239+ eq(pageComponent.monitorId, data.monitorId),
240240+ report?.pageId
241241+ ? eq(pageComponent.pageId, report.pageId)
242242+ : // If no page_id on status report, match all page_components with this monitor
243243+ eq(pageComponent.monitorId, data.monitorId),
244244+ ),
245245+ );
246246+247247+ if (components.length === 0) return;
248248+249249+ await db
250250+ .insert(statusReportsToPageComponents)
251251+ .values(
252252+ components.map((c) => ({
253253+ statusReportId: data.statusReportId,
254254+ pageComponentId: c.id,
255255+ })),
256256+ )
257257+ .onConflictDoNothing();
258258+}
259259+260260+/**
261261+ * Syncs multiple status_report_to_monitors inserts to status_report_to_page_component
262262+ */
263263+export async function syncStatusReportToMonitorInsertMany(
264264+ db: DB | Transaction,
265265+ statusReportId: number,
266266+ monitorIds: number[],
267267+) {
268268+ if (monitorIds.length === 0) return;
269269+270270+ // Get the status report's page_id
271271+ const report = await db
272272+ .select({ pageId: statusReport.pageId })
273273+ .from(statusReport)
274274+ .where(eq(statusReport.id, statusReportId))
275275+ .get();
276276+277277+ // Find matching page_components for all monitors
278278+ const components = await db
279279+ .select({ id: pageComponent.id, monitorId: pageComponent.monitorId })
280280+ .from(pageComponent)
281281+ .where(
282282+ and(
283283+ inArray(pageComponent.monitorId, monitorIds),
284284+ report?.pageId
285285+ ? eq(pageComponent.pageId, report.pageId)
286286+ : // If no page_id, we need to be careful - get components that match the monitor
287287+ inArray(pageComponent.monitorId, monitorIds),
288288+ ),
289289+ );
290290+291291+ if (components.length === 0) return;
292292+293293+ await db
294294+ .insert(statusReportsToPageComponents)
295295+ .values(
296296+ components.map((c) => ({
297297+ statusReportId,
298298+ pageComponentId: c.id,
299299+ })),
300300+ )
301301+ .onConflictDoNothing();
302302+}
303303+304304+/**
305305+ * Syncs a status_report_to_monitors delete to status_report_to_page_component
306306+ */
307307+export async function syncStatusReportToMonitorDelete(
308308+ db: DB | Transaction,
309309+ data: { statusReportId: number; monitorId: number },
310310+) {
311311+ // Find page_components with this monitor
312312+ const components = await db
313313+ .select({ id: pageComponent.id })
314314+ .from(pageComponent)
315315+ .where(eq(pageComponent.monitorId, data.monitorId));
316316+317317+ if (components.length === 0) return;
318318+319319+ await db.delete(statusReportsToPageComponents).where(
320320+ and(
321321+ eq(statusReportsToPageComponents.statusReportId, data.statusReportId),
322322+ inArray(
323323+ statusReportsToPageComponents.pageComponentId,
324324+ components.map((c) => c.id),
325325+ ),
326326+ ),
327327+ );
328328+}
329329+330330+/**
331331+ * Syncs status_report_to_monitors deletes for a specific status report
332332+ */
333333+export async function syncStatusReportToMonitorDeleteByStatusReport(
334334+ db: DB | Transaction,
335335+ statusReportId: number,
336336+) {
337337+ await db
338338+ .delete(statusReportsToPageComponents)
339339+ .where(eq(statusReportsToPageComponents.statusReportId, statusReportId));
340340+}
341341+342342+/**
343343+ * Syncs status_report_to_monitors deletes for specific monitors
344344+ */
345345+export async function syncStatusReportToMonitorDeleteByMonitors(
346346+ db: DB | Transaction,
347347+ monitorIds: number[],
348348+) {
349349+ if (monitorIds.length === 0) return;
350350+351351+ // Find page_components with these monitors
352352+ const components = await db
353353+ .select({ id: pageComponent.id })
354354+ .from(pageComponent)
355355+ .where(inArray(pageComponent.monitorId, monitorIds));
356356+357357+ if (components.length === 0) return;
358358+359359+ await db.delete(statusReportsToPageComponents).where(
360360+ inArray(
361361+ statusReportsToPageComponents.pageComponentId,
362362+ components.map((c) => c.id),
363363+ ),
364364+ );
365365+}
366366+367367+// ============================================================================
368368+// Maintenance to Monitor <-> Maintenance to Page Component Sync
369369+// ============================================================================
370370+371371+/**
372372+ * Syncs a maintenance_to_monitor insert to maintenance_to_page_component
373373+ */
374374+export async function syncMaintenanceToMonitorInsert(
375375+ db: DB | Transaction,
376376+ data: { maintenanceId: number; monitorId: number },
377377+) {
378378+ // Get the maintenance's page_id
379379+ const maint = await db
380380+ .select({ pageId: maintenance.pageId })
381381+ .from(maintenance)
382382+ .where(eq(maintenance.id, data.maintenanceId))
383383+ .get();
384384+385385+ // Find matching page_components
386386+ const components = await db
387387+ .select({ id: pageComponent.id })
388388+ .from(pageComponent)
389389+ .where(
390390+ and(
391391+ eq(pageComponent.monitorId, data.monitorId),
392392+ maint?.pageId
393393+ ? eq(pageComponent.pageId, maint.pageId)
394394+ : eq(pageComponent.monitorId, data.monitorId),
395395+ ),
396396+ );
397397+398398+ if (components.length === 0) return;
399399+400400+ await db
401401+ .insert(maintenancesToPageComponents)
402402+ .values(
403403+ components.map((c) => ({
404404+ maintenanceId: data.maintenanceId,
405405+ pageComponentId: c.id,
406406+ })),
407407+ )
408408+ .onConflictDoNothing();
409409+}
410410+411411+/**
412412+ * Syncs multiple maintenance_to_monitor inserts to maintenance_to_page_component
413413+ */
414414+export async function syncMaintenanceToMonitorInsertMany(
415415+ db: DB | Transaction,
416416+ maintenanceId: number,
417417+ monitorIds: number[],
418418+) {
419419+ if (monitorIds.length === 0) return;
420420+421421+ // Get the maintenance's page_id
422422+ const maint = await db
423423+ .select({ pageId: maintenance.pageId })
424424+ .from(maintenance)
425425+ .where(eq(maintenance.id, maintenanceId))
426426+ .get();
427427+428428+ // Find matching page_components for all monitors
429429+ const components = await db
430430+ .select({ id: pageComponent.id, monitorId: pageComponent.monitorId })
431431+ .from(pageComponent)
432432+ .where(
433433+ and(
434434+ inArray(pageComponent.monitorId, monitorIds),
435435+ maint?.pageId
436436+ ? eq(pageComponent.pageId, maint.pageId)
437437+ : inArray(pageComponent.monitorId, monitorIds),
438438+ ),
439439+ );
440440+441441+ if (components.length === 0) return;
442442+443443+ await db
444444+ .insert(maintenancesToPageComponents)
445445+ .values(
446446+ components.map((c) => ({
447447+ maintenanceId,
448448+ pageComponentId: c.id,
449449+ })),
450450+ )
451451+ .onConflictDoNothing();
452452+}
453453+454454+/**
455455+ * Syncs a maintenance_to_monitor delete to maintenance_to_page_component
456456+ */
457457+export async function syncMaintenanceToMonitorDelete(
458458+ db: DB | Transaction,
459459+ data: { maintenanceId: number; monitorId: number },
460460+) {
461461+ // Find page_components with this monitor
462462+ const components = await db
463463+ .select({ id: pageComponent.id })
464464+ .from(pageComponent)
465465+ .where(eq(pageComponent.monitorId, data.monitorId));
466466+467467+ if (components.length === 0) return;
468468+469469+ await db.delete(maintenancesToPageComponents).where(
470470+ and(
471471+ eq(maintenancesToPageComponents.maintenanceId, data.maintenanceId),
472472+ inArray(
473473+ maintenancesToPageComponents.pageComponentId,
474474+ components.map((c) => c.id),
475475+ ),
476476+ ),
477477+ );
478478+}
479479+480480+/**
481481+ * Syncs maintenance_to_monitor deletes for a specific maintenance
482482+ */
483483+export async function syncMaintenanceToMonitorDeleteByMaintenance(
484484+ db: DB | Transaction,
485485+ maintenanceId: number,
486486+) {
487487+ await db
488488+ .delete(maintenancesToPageComponents)
489489+ .where(eq(maintenancesToPageComponents.maintenanceId, maintenanceId));
490490+}
491491+492492+/**
493493+ * Syncs maintenance_to_monitor deletes for specific monitors
494494+ */
495495+export async function syncMaintenanceToMonitorDeleteByMonitors(
496496+ db: DB | Transaction,
497497+ monitorIds: number[],
498498+) {
499499+ if (monitorIds.length === 0) return;
500500+501501+ // Find page_components with these monitors
502502+ const components = await db
503503+ .select({ id: pageComponent.id })
504504+ .from(pageComponent)
505505+ .where(inArray(pageComponent.monitorId, monitorIds));
506506+507507+ if (components.length === 0) return;
508508+509509+ await db.delete(maintenancesToPageComponents).where(
510510+ inArray(
511511+ maintenancesToPageComponents.pageComponentId,
512512+ components.map((c) => c.id),
513513+ ),
514514+ );
515515+}