From fa8d2064e1c4859880074a1391f3422d6f1281e7 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Mon, 7 Jul 2025 12:54:42 +0530 Subject: [PATCH] Feat: v2 --- .env.example | 4 +- .gitignore | 6 +- LICENSE | 57 +- apps/webapp/.gitignore | 2 + apps/webapp/app/components/ErrorDisplay.tsx | 2 +- .../app/components/graph/graph-client.tsx | 20 + .../app/components/graph/graph-popover.tsx | 2 +- .../components/graph/graph-visualization.tsx | 4 +- apps/webapp/app/components/graph/graph.tsx | 1176 +++++------------ .../app/components/graph/node-colors.ts | 50 +- apps/webapp/app/components/logo/core.svg | 42 + apps/webapp/app/components/logo/logo.tsx | 287 +++- .../app/components/sidebar/app-sidebar.tsx | 37 +- .../app/components/sidebar/nav-main.tsx | 6 +- .../app/components/sidebar/nav-user.tsx | 35 +- apps/webapp/app/components/ui/header.tsx | 47 + apps/webapp/app/components/ui/sheet.tsx | 2 +- apps/webapp/app/components/ui/sidebar.tsx | 11 +- apps/webapp/app/env.server.ts | 3 + apps/webapp/app/models/workspace.server.ts | 3 +- apps/webapp/app/root.tsx | 4 +- .../app/routes/confirm-basic-details.tsx | 28 +- apps/webapp/app/routes/home.dashboard.tsx | 64 +- apps/webapp/app/routes/home.tsx | 16 +- .../routes/{home.api.tsx => settings.api.tsx} | 0 .../{home.logs.tsx => settings.logs.tsx} | 0 apps/webapp/app/routes/settings.tsx | 112 ++ .../app/services/knowledgeGraph.server.ts | 5 +- apps/webapp/app/services/postAuth.server.ts | 9 +- apps/webapp/app/tailwind.css | 3 +- apps/webapp/app/trigger/.gitkeep | 0 .../integrations/integration-run-schedule.ts | 38 + .../trigger/integrations/integration-run.ts | 90 ++ .../app/trigger/integrations/scheduler.ts | 64 + apps/webapp/package.json | 23 +- apps/webapp/trigger.config.ts | 23 + apps/webapp/tsconfig.json | 3 +- {docker => docker-build}/Dockerfile | 0 {docker => docker-build}/docker-compose.yaml | 0 .../scripts/entrypoint.sh | 0 .../scripts/wait-for-it.sh | 0 docker-compose.yaml | 2 +- .../20250706093221_add_activity/migration.sql | 119 ++ packages/database/prisma/schema.prisma | 118 +- pnpm-lock.yaml | 841 ++++++++++++ trigger/.env.example | 138 ++ trigger/docker-compose.yaml | 245 ++++ turbo.json | 5 +- 48 files changed, 2658 insertions(+), 1088 deletions(-) create mode 100644 apps/webapp/app/components/graph/graph-client.tsx create mode 100644 apps/webapp/app/components/logo/core.svg create mode 100644 apps/webapp/app/components/ui/header.tsx rename apps/webapp/app/routes/{home.api.tsx => settings.api.tsx} (100%) rename apps/webapp/app/routes/{home.logs.tsx => settings.logs.tsx} (100%) create mode 100644 apps/webapp/app/routes/settings.tsx create mode 100644 apps/webapp/app/trigger/.gitkeep create mode 100644 apps/webapp/app/trigger/integrations/integration-run-schedule.ts create mode 100644 apps/webapp/app/trigger/integrations/integration-run.ts create mode 100644 apps/webapp/app/trigger/integrations/scheduler.ts create mode 100644 apps/webapp/trigger.config.ts rename {docker => docker-build}/Dockerfile (100%) rename {docker => docker-build}/docker-compose.yaml (100%) rename {docker => docker-build}/scripts/entrypoint.sh (100%) rename {docker => docker-build}/scripts/wait-for-it.sh (100%) create mode 100644 packages/database/prisma/migrations/20250706093221_add_activity/migration.sql create mode 100644 trigger/.env.example create mode 100644 trigger/docker-compose.yaml diff --git a/.env.example b/.env.example index 3f9d5da..7010c91 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,7 @@ POSTGRES_PASSWORD=docker POSTGRES_DB=core LOGIN_ORIGIN=http://localhost:3000 -DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=echo" +DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=core" # This sets the URL used for direct connections to the database and should only be needed in limited circumstances # See: https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#fields:~:text=the%20shadow%20database.-,directUrl,-No @@ -41,5 +41,5 @@ MAGIC_LINK_SECRET=27192e6432564f4788d55c15131bd5ac NEO4J_AUTH=neo4j/27192e6432564f4788d55c15131bd5ac OLLAMA_URL=http://ollama:11434 -EMBEDDING_MODEL=bge-m3 +EMBEDDING_MODEL=GPT41 MODEL=GPT41 diff --git a/.gitignore b/.gitignore index 28a42c2..bf72edb 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,8 @@ yarn-error.log* .DS_Store *.pem -docker-compose.dev.yaml \ No newline at end of file +docker-compose.dev.yaml + +clickhouse/ +.vscode/ +registry/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index b2c1243..c4d101f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,44 @@ -MIT License +Sol License -Copyright (c) 2024 Poozle Inc +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) 2025 — Poozle Inc. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Additional Terms: + +As additional permission under GNU AGPL version 3 section 7, you +may combine or link a "work that uses the Library" with a publicly +distributed version of this library to produce a combined library or +application, then distribute that combined work under the terms of +your choice, with no requirement to comply with the obligations +normally placed on you by section 4 of the GNU AGPL version 3 +(or the corresponding section of a later version of the GNU AGPL +version 3 license). + +"Commons Clause" License Condition v1.0 + +The Software is provided to you by the Licensor under the License (defined below), subject to the following condition: + +Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software. + +For purposes of the foregoing, "Sell" means practicing any or all of the rights granted to you under the License to provide the Software to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/support services related to the Software), as part of a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice. + +Software: All files in this repository. + +License: GNU Affero General Public License v3.0 + +Licensor: Poozle Inc. diff --git a/apps/webapp/.gitignore b/apps/webapp/.gitignore index 80ec311..0abbe0b 100644 --- a/apps/webapp/.gitignore +++ b/apps/webapp/.gitignore @@ -3,3 +3,5 @@ node_modules /.cache /build .env + +.trigger \ No newline at end of file diff --git a/apps/webapp/app/components/ErrorDisplay.tsx b/apps/webapp/app/components/ErrorDisplay.tsx index f288cee..fc26741 100644 --- a/apps/webapp/app/components/ErrorDisplay.tsx +++ b/apps/webapp/app/components/ErrorDisplay.tsx @@ -48,7 +48,7 @@ type DisplayOptionsProps = { export function ErrorDisplay({ title, message, button }: DisplayOptionsProps) { return ( -
+
{title} {message && {message}} diff --git a/apps/webapp/app/components/graph/graph-client.tsx b/apps/webapp/app/components/graph/graph-client.tsx new file mode 100644 index 0000000..6fbbbfa --- /dev/null +++ b/apps/webapp/app/components/graph/graph-client.tsx @@ -0,0 +1,20 @@ +import { type GraphVisualizationProps } from "./graph-visualization"; +import { useState, useMemo, forwardRef, useRef, useEffect } from "react"; + +export function GraphVisualizationClient(props: GraphVisualizationProps) { + const [Component, setComponent] = useState(undefined); + + useEffect(() => { + if (typeof window === "undefined") return; + + import("./graph-visualization").then(({ GraphVisualization }) => { + setComponent(GraphVisualization); + }); + }, []); + + if (!Component) { + return null; + } + + return ; +} diff --git a/apps/webapp/app/components/graph/graph-popover.tsx b/apps/webapp/app/components/graph/graph-popover.tsx index 316eb63..36dd8d5 100644 --- a/apps/webapp/app/components/graph/graph-popover.tsx +++ b/apps/webapp/app/components/graph/graph-popover.tsx @@ -93,7 +93,7 @@ export function GraphPopovers({
; } -// Add ref type for zoomToLinkById export interface GraphRef { zoomToLinkById: (linkId: string) => void; } @@ -38,7 +40,7 @@ export const Graph = forwardRef( triplets, width = 1000, height = 800, - zoomOnMount = true, + zoomOnMount = false, onNodeClick, onEdgeClick, onBlur, @@ -46,69 +48,14 @@ export const Graph = forwardRef( }, ref, ) => { - const svgRef = useRef(null); + const containerRef = useRef(null); + const sigmaRef = useRef(null); + const graphRef = useRef(null); const [themeMode] = useTheme(); - // Function refs to keep track of reset functions - const resetLinksRef = useRef<(() => void) | null>(null); - const resetNodesRef = useRef<(() => void) | null>(null); - const handleLinkClickRef = useRef< - ((event: any, d: any, relation: IdValue) => void) | null - >(null); - const simulationRef = useRef | null>(null); - const zoomRef = useRef | null>( - null, - ); const isInitializedRef = useRef(false); - - // Add ref for zoomToLinkById - const graphRef = useRef({ - zoomToLinkById: (linkId: string) => { - if ( - !svgRef.current || - !resetLinksRef.current || - !resetNodesRef.current || - !handleLinkClickRef.current - ) - return; - const svgElement = d3.select(svgRef.current); - const linkGroups = svgElement.selectAll("g > g"); // Select all link groups - - let found = false; - - // Iterate through link groups to find matching relation - linkGroups.each(function (d: any) { - if (found) return; // Skip if already found - - if (d?.relationData) { - const relation = d.relationData.find( - (r: IdValue) => r.id === linkId, - ); - if (relation) { - found = true; - const resetLinks = resetLinksRef.current; - const resetNodes = resetNodesRef.current; - const handleLinkClick = handleLinkClickRef.current; - - if (resetLinks) resetLinks(); - if (resetNodes) resetNodes(); - if (handleLinkClick) - handleLinkClick({ stopPropagation: () => {} }, d, relation); - } - } - }); - - if (!found) { - console.warn(`Link with id ${linkId} not found`); - } - }, - }); - - // Expose the ref through forwardRef - useImperativeHandle(ref, () => graphRef.current); + const selectedNodeRef = useRef(null); + const selectedEdgeRef = useRef(null); // Memoize theme to prevent unnecessary recreation const theme = useMemo( @@ -122,7 +69,7 @@ export const Graph = forwardRef( dimmed: colors.pink[300], }, link: { - stroke: themeMode === "dark" ? colors.slate[600] : colors.slate[400], + stroke: colors.gray[400], selected: colors.blue[400], dimmed: themeMode === "dark" ? colors.slate[800] : colors.slate[200], label: { @@ -143,19 +90,15 @@ export const Graph = forwardRef( // Extract all unique labels from triplets const allLabels = useMemo(() => { - // Only calculate if we need to create our own map if (externalLabelColorMap) return []; - const labels = new Set(); - labels.add("Entity"); // Always include Entity as default - + labels.add("Entity"); triplets.forEach((triplet) => { if (triplet.source.primaryLabel) labels.add(triplet.source.primaryLabel); if (triplet.target.primaryLabel) labels.add(triplet.target.primaryLabel); }); - return Array.from(labels); }, [triplets, externalLabelColorMap]); @@ -167,12 +110,10 @@ export const Graph = forwardRef( // Create a mapping of node IDs to their data const nodeDataMap = useMemo(() => { const result = new Map(); - triplets.forEach((triplet) => { result.set(triplet.source.id, triplet.source); result.set(triplet.target.id, triplet.target); }); - return result; }, [triplets]); @@ -182,13 +123,8 @@ export const Graph = forwardRef( if (!node) { return getNodeColorByLabel(null, themeMode === "dark", labelColorMap); } - - // Get the full node data if we only have an ID const nodeData = nodeDataMap.get(node.id) || node; - - // Extract primaryLabel from node data const primaryLabel = nodeData.primaryLabel; - return getNodeColorByLabel( primaryLabel, themeMode === "dark", @@ -198,871 +134,363 @@ export const Graph = forwardRef( [labelColorMap, nodeDataMap, themeMode], ); - // Process graph data - const { nodes, links } = useMemo(() => { - const nodes = Array.from( - new Set(triplets.flatMap((t) => [t.source.id, t.target.id])), - ).map((id) => { - const nodeData = triplets.find( - (t) => t.source.id === id || t.target.id === id, - ); - const value = nodeData - ? nodeData.source.id === id - ? nodeData.source.value - : nodeData.target.value - : id; - return { - id, - value, - }; + // Process graph data for Sigma + const { nodes, edges } = useMemo(() => { + const nodeMap = new Map(); + triplets.forEach((triplet) => { + if (!nodeMap.has(triplet.source.id)) { + nodeMap.set(triplet.source.id, { + id: triplet.source.id, + label: triplet.source.value, + size: 10, + color: getNodeColor(triplet.source), + x: width, + y: height, + nodeData: triplet.source, + }); + } + if (!nodeMap.has(triplet.target.id)) { + nodeMap.set(triplet.target.id, { + id: triplet.target.id, + label: triplet.target.value, + size: 10, + color: getNodeColor(triplet.target), + x: width, + y: height, + nodeData: triplet.target, + }); + } }); const linkGroups = triplets.reduce( (groups, triplet) => { - // Skip isolated node edges (they are just placeholders for showing isolated nodes) if (triplet.relation.type === "_isolated_node_") { return groups; } - let key = `${triplet.source.id}-${triplet.target.id}`; const reverseKey = `${triplet.target.id}-${triplet.source.id}`; - if (groups[reverseKey]) { key = reverseKey; } - if (!groups[key]) { groups[key] = { + id: key, source: triplet.source.id, target: triplet.target.id, relations: [], relationData: [], - curveStrength: 0, + label: "", + color: theme.link.stroke, + size: 1, }; } groups[key].relations.push(triplet.relation.value); groups[key].relationData.push(triplet.relation); + groups[key].label = groups[key].relations + .join(", ") + .replace("HAS_", "") + .toLowerCase(); + return groups; }, - {} as Record< - string, - { - source: string; - target: string; - relations: string[]; - relationData: IdValue[]; - curveStrength: number; - } - >, + {} as Record, ); return { - nodes, - links: Object.values(linkGroups), + nodes: Array.from(nodeMap.values()), + edges: Object.values(linkGroups), }; - }, [triplets]); + }, [triplets, getNodeColor, theme.link.stroke, width, height]); + + // Helper function to reset highlights + const resetHighlights = useCallback(() => { + if (!graphRef.current) return; + const graph = graphRef.current; + graph.forEachNode((node) => { + graph.setNodeAttribute(node, "highlighted", false); + graph.setNodeAttribute( + node, + "color", + getNodeColor(graph.getNodeAttribute(node, "nodeData")), + ); + }); + graph.forEachEdge((edge) => { + graph.setEdgeAttribute(edge, "highlighted", false); + graph.setEdgeAttribute(edge, "color", theme.link.stroke); + }); + selectedNodeRef.current = null; + selectedEdgeRef.current = null; + }, [getNodeColor, theme.link.stroke]); + + // Add ref for zoomToLinkById + const graphRefMethods = useRef({ + zoomToLinkById: (linkId: string) => { + if (!sigmaRef.current || !graphRef.current) return; + try { + const graph = graphRef.current; + const sigma = sigmaRef.current; + const edge = graph.findEdge((edgeId, attrs) => { + return attrs.relationData?.some( + (rel: IdValue) => rel.id === linkId, + ); + }); + if (edge) { + const edgeAttrs = graph.getEdgeAttributes(edge); + const source = graph.source(edge); + const target = graph.target(edge); + resetHighlights(); + graph.setEdgeAttribute(edge, "highlighted", true); + graph.setNodeAttribute(source, "highlighted", true); + graph.setNodeAttribute(target, "highlighted", true); + const relation = edgeAttrs.relationData?.find( + (rel: IdValue) => rel.id === linkId, + ); + if (relation && onEdgeClick) { + onEdgeClick(relation.id); + } + const sourcePos = sigma.getNodeDisplayData(source); + const targetPos = sigma.getNodeDisplayData(target); + if (sourcePos && targetPos) { + const centerX = (sourcePos.x + targetPos.x) / 2; + const centerY = (sourcePos.y + targetPos.y) / 2; + sigma + .getCamera() + .animate( + { x: centerX, y: centerY, ratio: 0.5 }, + { duration: 500 }, + ); + } + } else { + console.warn(`Link with id ${linkId} not found`); + } + } catch (error) { + console.error("Error in zoomToLinkById:", error); + } + }, + }); + + useImperativeHandle(ref, () => graphRefMethods.current); - // Initialize or update visualization - This will run only once on mount useEffect(() => { - // Skip if already initialized or ref not available - if (isInitializedRef.current || !svgRef.current) return; - - // Mark as initialized to prevent re-running + if (isInitializedRef.current || !containerRef.current) return; isInitializedRef.current = true; - const svgElement = d3.select(svgRef.current); - svgElement.selectAll("*").remove(); + // Create graphology graph + const graph = new GraphologyGraph(); + graphRef.current = graph; - const g = svgElement.append("g"); + // Add nodes + nodes.forEach((node) => { + graph.addNode(node.id, node); + }); - // Drag handler function - const drag = ( - simulation: d3.Simulation, - ) => { - const originalSettings = { - velocityDecay: 0.4, - alphaDecay: 0.05, - }; - - function dragstarted(event: any) { - if (!event.active) { - simulation - .velocityDecay(0.7) - .alphaDecay(0.1) - .alphaTarget(0.1) - .restart(); - } - d3.select(event.sourceEvent.target.parentNode) - .select("circle") - .attr("stroke", theme.node.hover) - .attr("stroke-width", 3); - - event.subject.fx = event.subject.x; - event.subject.fy = event.subject.y; + // Add edges + edges.forEach((edge) => { + if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) { + graph.addEdge(edge.source, edge.target, { ...edge }); } + }); - function dragged(event: any) { - event.subject.x = event.x; - event.subject.y = event.y; - event.subject.fx = event.x; - event.subject.fy = event.y; - } - - function dragended(event: any) { - if (!event.active) { - simulation - .velocityDecay(originalSettings.velocityDecay) - .alphaDecay(originalSettings.alphaDecay) - .alphaTarget(0); - } - - // Keep the node fixed at its final position - event.subject.fx = event.x; - event.subject.fy = event.y; - - d3.select(event.sourceEvent.target.parentNode) - .select("circle") - .attr("stroke", theme.node.stroke) - .attr("stroke-width", 2); - } - - return d3 - .drag() - .on("start", dragstarted) - .on("drag", dragged) - .on("end", dragended); - }; - - // Setup zoom behavior - const zoom = d3 - .zoom() - .scaleExtent([0.1, 4]) - .on("zoom", (event) => { - g.attr("transform", event.transform); + // Apply layout + if (graph.order > 0) { + graph.forEachNode((node) => { + graph.setNodeAttribute(node, "x", width); + graph.setNodeAttribute(node, "y", height); }); - zoomRef.current = zoom; - // @ts-ignore - svgElement.call(zoom).call(zoom.transform, d3.zoomIdentity.scale(0.8)); - - // Identify which nodes are isolated (not in any links) - const nodeIdSet = new Set(nodes.map((n: any) => n.id)); - const linkedNodeIds = new Set(); - - links.forEach((link: any) => { - const sourceId = - typeof link.source === "string" ? link.source : link.source.id; - const targetId = - typeof link.target === "string" ? link.target : link.target.id; - linkedNodeIds.add(sourceId); - linkedNodeIds.add(targetId); - }); - - // Nodes that don't appear in any link are isolated - const isolatedNodeIds = new Set(); - nodeIdSet.forEach((nodeId: string) => { - if (!linkedNodeIds.has(nodeId)) { - isolatedNodeIds.add(nodeId); - } - }); - - // Enhanced simulation for improved aesthetics and readability - - // Parameters tuned for clear separation of clusters and minimal overlap, - // as seen in the provided image (distinct, well-separated groups). - const LINK_DISTANCE = 120; // Slightly shorter for tighter clusters - const LINK_STRENGTH = 0.6; // Stronger to keep clusters compact - const CHARGE_ISOLATED = -200; // Less repulsion for isolated nodes (keeps them closer) - const CHARGE_CONNECTED = -1000; // Strong repulsion for connected nodes (prevents crowding) - const COLLIDE_RADIUS = 32; // Smaller collision radius for less overlap - const COLLIDE_STRENGTH = 0.9; // Stronger collision to avoid overlap - const COLLIDE_ITER = 10; // More iterations for better separation - const CENTER_STRENGTH = 0.18; // Pull clusters more to center - const ISOLATED_RADIAL_DIST = 260; // Place isolated nodes further from center - const ISOLATED_RADIAL_STRENGTH = 0.28; // Stronger pull for isolated nodes - const NONISOLATED_RADIAL_STRENGTH = 0.06; // Slight pull for non-isolated - const VELOCITY_DECAY = 0.28; // Smoother, more stable layout - const ALPHA_DECAY = 0.035; - const ALPHA_MIN = 0.001; - - const simulation = d3 - .forceSimulation(nodes as d3.SimulationNodeDatum[]) - .force( - "link", - d3 - .forceLink(links) - .id((d: any) => d.id) - .distance(LINK_DISTANCE) - .strength(LINK_STRENGTH), - ) - .force( - "charge", - d3 - .forceManyBody() - .strength((d: any) => - isolatedNodeIds.has(d.id) ? CHARGE_ISOLATED : CHARGE_CONNECTED, - ) - .distanceMin(20) - .distanceMax(600) - .theta(0.9), - ) - .force( - "center", - d3.forceCenter(width / 2, height / 2).strength(CENTER_STRENGTH), - ) - .force( - "collide", - d3 - .forceCollide() - .radius(COLLIDE_RADIUS) - .strength(COLLIDE_STRENGTH) - .iterations(COLLIDE_ITER), - ) - // Special gravity for isolated nodes to keep them separated and visible - .force( - "isolatedGravity", - d3 - .forceRadial(ISOLATED_RADIAL_DIST, width / 2, height / 2) - .strength((d: any) => - isolatedNodeIds.has(d.id) - ? ISOLATED_RADIAL_STRENGTH - : NONISOLATED_RADIAL_STRENGTH, - ), - ) - .velocityDecay(VELOCITY_DECAY) - .alphaDecay(ALPHA_DECAY) - .alphaMin(ALPHA_MIN); - - simulationRef.current = simulation; - - const link = g.append("g").selectAll("g").data(links).join("g"); - - // Define reset functions - resetLinksRef.current = () => { - // @ts-ignore - link - .selectAll("path") - .attr("stroke", theme.link.stroke) - .attr("stroke-opacity", 0.6) - .attr("stroke-width", 1); - - // @ts-ignore - link.selectAll(".link-label rect").attr("fill", theme.link.label.bg); - // @ts-ignore - link.selectAll(".link-label text").attr("fill", theme.link.label.text); - }; - - // Create node groups - const node = g - .append("g") - .selectAll("g") - .data(nodes) - .join("g") - // @ts-ignore - .call(drag(simulation)) - .attr("cursor", "pointer"); - - resetNodesRef.current = () => { - // @ts-ignore - node - .selectAll("circle") - .attr("fill", (d: any) => getNodeColor(d)) - .attr("stroke", theme.node.stroke) - .attr("stroke-width", 1); - }; - - // Handle link click - handleLinkClickRef.current = (event: any, d: any, relation: IdValue) => { - if (event.stopPropagation) { - event.stopPropagation(); - } - - if (resetLinksRef.current) resetLinksRef.current(); - if (onEdgeClick) onEdgeClick(relation.id); - - // Reset all elements to default state - // @ts-ignore - link - .selectAll("path") - .attr("stroke", theme.link.stroke) - .attr("stroke-opacity", 0.6) - .attr("stroke-width", 1); - - // Reset non-highlighted nodes to their proper colors - // @ts-ignore - node - .selectAll("circle") - .attr("fill", (d: any) => getNodeColor(d)) - .attr("stroke", theme.node.stroke) - .attr("stroke-width", 1); - - // Find and highlight the corresponding path and label - const linkGroup = event.target?.closest("g") - ? d3.select(event.target.closest("g")) - : link.filter((l: any) => l === d); - - // @ts-ignore - linkGroup - // @ts-ignore - .selectAll("path") - .attr("stroke", theme.link.selected) - .attr("stroke-opacity", 1) - .attr("stroke-width", 2); - - // Update label styling - // @ts-ignore - linkGroup.select(".link-label rect").attr("fill", theme.link.selected); - // @ts-ignore - linkGroup.select(".link-label text").attr("fill", theme.node.text); - - // Highlight connected nodes - // @ts-ignore - node - .selectAll("circle") - .filter((n: any) => n.id === d.source.id || n.id === d.target.id) - .attr("fill", theme.node.selected) - .attr("stroke", theme.node.selected) - .attr("stroke-width", 2); - - const sourceNode = d.source; - const targetNode = d.target; - - // Calculate bounding box for the two connected nodes and the edge - if ( - sourceNode && - targetNode && - sourceNode.x !== undefined && - targetNode.x !== undefined - ) { - const padding = 100; // Increased padding for better view - const minX = Math.min(sourceNode.x, targetNode.x) - padding; - const minY = Math.min(sourceNode.y, targetNode.y) - padding; - const maxX = Math.max(sourceNode.x, targetNode.x) + padding; - const maxY = Math.max(sourceNode.y, targetNode.y) + padding; - - // Calculate transform to fit the connected nodes - const boundWidth = maxX - minX; - const boundHeight = maxY - minY; - const scale = - 0.9 * Math.min(width / boundWidth, height / boundHeight); - const midX = (minX + maxX) / 2; - const midY = (minY + maxY) / 2; - - if ( - isFinite(scale) && - isFinite(midX) && - isFinite(midY) && - zoomRef.current - ) { - const transform = d3.zoomIdentity - .translate(width / 2 - midX * scale, height / 2 - midY * scale) - .scale(scale); - - // Animate transition to new view - // @ts-ignore - svgElement - .transition() - .duration(750) - .ease(d3.easeCubicInOut) // Add easing for smoother transitions - .call(zoomRef.current.transform, transform); - } - } - }; - - // Create links with proper curve paths - link.each(function (d: any) { - const linkGroup = d3.select(this); - const relationCount = d.relations.length; - - // Calculate curve strengths based on number of relations - const baseStrength = 0.2; - const strengthStep = - relationCount > 1 ? baseStrength / (relationCount - 1) : 0; - - d.relations.forEach((relation: string, index: number) => { - const curveStrength = - relationCount > 1 ? -baseStrength + index * strengthStep * 2 : 0; - const fullRelation = d.relationData[index]; - - linkGroup - .append("path") - .attr("stroke", theme.link.stroke) - .attr("stroke-opacity", 0.6) - .attr("stroke-width", 1) - .attr("fill", "none") - .attr("data-curve-strength", curveStrength) - .attr("cursor", "pointer") - .attr( - "data-source", - typeof d.source === "object" ? d.source.id : d.source, - ) - .attr( - "data-target", - typeof d.target === "object" ? d.target.id : d.target, - ) - .on("click", (event) => { - if (handleLinkClickRef.current) { - handleLinkClickRef.current(event, d, fullRelation); - } - }); - - const labelGroup = linkGroup - .append("g") - .attr("class", "link-label") - .attr("cursor", "pointer") - .attr("data-curve-strength", curveStrength) - .on("click", (event) => { - if (handleLinkClickRef.current) { - handleLinkClickRef.current(event, d, fullRelation); - } - }); - - labelGroup - .append("rect") - .attr("fill", theme.link.label.bg) - .attr("rx", 4) - .attr("ry", 4) - .attr("opacity", 0.9); - - labelGroup - .append("text") - .attr("fill", theme.link.label.text) - .attr("font-size", "8px") - .attr("text-anchor", "middle") - .attr("dominant-baseline", "middle") - .attr("pointer-events", "none") - .text(relation); - - labelGroup.attr("data-curve-strength", curveStrength); + const layout = new ForceSupervisor(graph, { + isNodeFixed: (_, attr) => attr.highlighted, }); - }); + layout.start(); - // Create node circles - node - .append("circle") - .attr("r", 10) - .attr("fill", (d: any) => getNodeColor(d)) - .attr("stroke", theme.node.stroke) - .attr("stroke-width", 1) - .attr("filter", "drop-shadow(0 2px 4px rgba(0,0,0,0.2))") - .attr("data-id", (d: any) => d.id) - .attr("cursor", "pointer"); + const settings = forceAtlas2.inferSettings(graph); + forceAtlas2.assign(graph, { + iterations: 600, + settings: { + ...settings, + barnesHutOptimize: true, + strongGravityMode: true, + gravity: 0.05, + scalingRatio: 10, + slowDown: 5, + }, + }); - // Add node labels - node - .append("text") - .attr("x", 15) - .attr("y", "0.3em") - .attr("text-anchor", "start") - .attr("fill", theme.node.text) - .attr("font-weight", "500") - .attr("font-size", "12px") - .text((d: any) => d.value) - .attr("cursor", "pointer"); - - // Handle node clicks - function handleNodeClick(event: any, d: any) { - event.stopPropagation(); // Ensure the event doesn't bubble up - - if (resetLinksRef.current) resetLinksRef.current(); - if (resetNodesRef.current) resetNodesRef.current(); - - const selectedNodeId = d?.id; - - if (selectedNodeId && onNodeClick) { - onNodeClick(selectedNodeId); - - // Highlight the selected node - // @ts-ignore - node - .selectAll("circle") - .filter((n: any) => n.id === selectedNodeId) - .attr("fill", theme.node.selected) - .attr("stroke", theme.node.selected) - .attr("stroke-width", 2); - - // Find connected nodes and links - const connectedLinks: any[] = []; - const connectedNodes = new Set(); - - // Add the selected node to the connected nodes - const selectedNode = nodes.find((n: any) => n.id === selectedNodeId); - if (selectedNode) { - connectedNodes.add(selectedNode); - } - - // @ts-ignore - link.selectAll("path").each(function () { - const path = d3.select(this); - const source = path.attr("data-source"); - const target = path.attr("data-target"); - - if (source === selectedNodeId || target === selectedNodeId) { - const sourceNode = nodes.find((n: any) => n.id === source); - const targetNode = nodes.find((n: any) => n.id === target); - - if (sourceNode && targetNode) { - connectedLinks.push({ source: sourceNode, target: targetNode }); - connectedNodes.add(sourceNode); - connectedNodes.add(targetNode); - } - } - }); - - // Calculate bounding box of connected nodes - if (connectedNodes.size > 0 && zoomRef.current) { - let minX = Infinity, - minY = Infinity; - let maxX = -Infinity, - maxY = -Infinity; - - connectedNodes.forEach((node: any) => { - if (node.x !== undefined && node.y !== undefined) { - minX = Math.min(minX, node.x); - minY = Math.min(minY, node.y); - maxX = Math.max(maxX, node.x); - maxY = Math.max(maxY, node.y); - } - }); - - // Add padding - const padding = 50; - minX -= padding; - minY -= padding; - maxX += padding; - maxY += padding; - - // Calculate transform to fit connected nodes - const boundWidth = maxX - minX; - const boundHeight = maxY - minY; - const scale = - 0.9 * Math.min(width / boundWidth, height / boundHeight); - const midX = (minX + maxX) / 2; - const midY = (minY + maxY) / 2; - - if (isFinite(scale) && isFinite(midX) && isFinite(midY)) { - const transform = d3.zoomIdentity - .translate(width / 2 - midX * scale, height / 2 - midY * scale) - .scale(scale); - - // Animate transition to new view - // @ts-ignore - svgElement - .transition() - .duration(750) - .ease(d3.easeCubicInOut) // Add easing for smoother transitions - .call(zoomRef.current.transform, transform); - } - } - - // Highlight connected links - // @ts-ignore - link - .selectAll("path") - .attr("stroke", theme.link.stroke) - .attr("stroke-opacity", 0.6) - .attr("stroke-width", 1) - .filter(function () { - const path = d3.select(this); - return ( - path.attr("data-source") === selectedNodeId || - path.attr("data-target") === selectedNodeId - ); - }) - .attr("stroke", themeMode === "dark" ? "#ffffff" : colors.pink[600]) - .attr("stroke-width", 2); - } + noverlap.assign(graph, { + maxIterations: 150, + settings: { + margin: 5, + expansion: 1.1, + gridSize: 20, + }, + }); } - // Attach click handler to nodes - node.on("click", handleNodeClick); - - // Store a reference to the current SVG element - const svgRefCurrent = svgRef.current; - - // Add blur handler - svgElement.on("click", function (event) { - // Make sure we only handle clicks directly on the SVG element, not on its children - if (event.target === svgRefCurrent) { - if (onBlur) onBlur(); - if (resetLinksRef.current) resetLinksRef.current(); - if (resetNodesRef.current) resetNodesRef.current(); - } + // Create Sigma instance + const sigma = new Sigma(graph, containerRef.current, { + renderEdgeLabels: true, + defaultEdgeColor: theme.link.stroke, + defaultNodeColor: theme.node.fill, + enableEdgeEvents: true, + minCameraRatio: 0.5, + maxCameraRatio: 2, }); - // Update positions on simulation tick - simulation.on("tick", () => { - // Update link paths and labels - link.each(function (d: any) { - // Make sure d.source and d.target have x and y properties - if (!d.source.x && typeof d.source === "string") { - const sourceNode = nodes.find((n: any) => n.id === d.source); - // @ts-ignore - Node will have x,y properties from d3 simulation - if (sourceNode && sourceNode.x) { - d.source = sourceNode; - } - } + sigmaRef.current = sigma; - if (!d.target.x && typeof d.target === "string") { - const targetNode = nodes.find((n: any) => n.id === d.target); - // @ts-ignore - Node will have x,y properties from d3 simulation - if (targetNode && targetNode.x) { - d.target = targetNode; - } - } + // Set up camera for zoom on mount + if (zoomOnMount) { + setTimeout(() => { + sigma + .getCamera() + .animate(sigma.getCamera().getState(), { duration: 750 }); + }, 100); + } - const linkGroup = d3.select(this); - linkGroup.selectAll("path").each(function () { - const path = d3.select(this); - const curveStrength = +path.attr("data-curve-strength") || 0; + // --- Drag and Drop Implementation --- + let draggedNode: string | null = null; + let isDragging = false; - // Handle self-referencing nodes - if (d.source.id === d.target.id) { - // Create an elliptical path for self-references - const radiusX = 40; - const radiusY = 90; - const offset = radiusY + 20; + sigma.on("downNode", (e) => { + isDragging = true; + draggedNode = e.node; + graph.setNodeAttribute(draggedNode, "highlighted", true); + if (!sigma.getCustomBBox()) sigma.setCustomBBox(sigma.getBBox()); + }); - const cx = d.source.x; - const cy = d.source.y - offset; - const path_d = `M${d.source.x},${d.source.y} - C${cx - radiusX},${cy} - ${cx + radiusX},${cy} - ${d.source.x},${d.source.y}`; - path.attr("d", path_d); + sigma.on("moveBody", ({ event }) => { + if (!isDragging || !draggedNode) return; + const pos = sigma.viewportToGraph(event); + graph.setNodeAttribute(draggedNode, "x", pos.x); + graph.setNodeAttribute(draggedNode, "y", pos.y); + event.preventSigmaDefault?.(); + event.original?.preventDefault?.(); + event.original?.stopPropagation?.(); + }); - // Position the label - // @ts-ignore - const labelGroup = linkGroup - .selectAll(".link-label") - .filter(function () { - return ( - d3.select(this).attr("data-curve-strength") === - String(curveStrength) - ); - }); + const handleUp = () => { + if (draggedNode) { + graph.removeNodeAttribute(draggedNode, "highlighted"); + } + isDragging = false; + draggedNode = null; + }; + sigma.on("upNode", handleUp); + sigma.on("upStage", handleUp); - // Update both the group position and the rectangle/text within it - labelGroup.attr("transform", `translate(${cx}, ${cy - 10})`); + // --- End Drag and Drop --- - // Update the rectangle and text positioning - // @ts-ignore - const text = labelGroup.select("text"); - // @ts-ignore - const rect = labelGroup.select("rect"); - const textBBox = (text.node() as SVGTextElement)?.getBBox(); - - if (textBBox) { - rect - .attr("x", -textBBox.width / 2 - 6) - .attr("y", -textBBox.height / 2 - 4) - .attr("width", textBBox.width + 12) - .attr("height", textBBox.height + 8); - - text.attr("x", 0).attr("y", 0); - } - } else { - const dx = d.target.x - d.source.x; - const dy = d.target.y - d.source.y; - const dr = Math.sqrt(dx * dx + dy * dy); - - const midX = (d.source.x + d.target.x) / 2; - const midY = (d.source.y + d.target.y) / 2; - const normalX = -dy / dr; - const normalY = dx / dr; - const curveMagnitude = dr * curveStrength; - const controlX = midX + normalX * curveMagnitude; - const controlY = midY + normalY * curveMagnitude; - - const path_d = `M${d.source.x},${d.source.y} Q${controlX},${controlY} ${d.target.x},${d.target.y}`; - path.attr("d", path_d); - - const pathNode = path.node() as SVGPathElement; - if (pathNode) { - const pathLength = pathNode.getTotalLength(); - const midPoint = pathNode.getPointAtLength(pathLength / 2); - - // @ts-ignore - Intentionally ignoring d3 selection type issues as in the Svelte version - const labelGroup = linkGroup - .selectAll(".link-label") - .filter(function () { - return ( - d3.select(this).attr("data-curve-strength") === - String(curveStrength) - ); - }); - - if (midPoint) { - // @ts-ignore - Intentionally ignoring d3 selection type issues as in the Svelte version - const text = labelGroup.select("text"); - // @ts-ignore - Intentionally ignoring d3 selection type issues as in the Svelte version - const rect = labelGroup.select("rect"); - const textBBox = (text.node() as SVGTextElement)?.getBBox(); - - if (textBBox) { - const angle = - (Math.atan2( - d.target.y - d.source.y, - d.target.x - d.source.x, - ) * - 180) / - Math.PI; - const rotationAngle = - angle > 90 || angle < -90 ? angle - 180 : angle; - - labelGroup.attr( - "transform", - `translate(${midPoint.x}, ${midPoint.y}) rotate(${rotationAngle})`, - ); - - rect - .attr("x", -textBBox.width / 2 - 6) - .attr("y", -textBBox.height / 2 - 4) - .attr("width", textBBox.width + 12) - .attr("height", textBBox.height + 8); - - text.attr("x", 0).attr("y", 0); - } - } - } - } - }); + // Node click handler + sigma.on("clickNode", (event) => { + console.log(event); + const { node } = event; + // resetHighlights(); + if (onNodeClick) { + onNodeClick(node); + } + graph.setNodeAttribute(node, "highlighted", true); + graph.setNodeAttribute(node, "color", theme.node.selected); + selectedNodeRef.current = node; + graph.forEachEdge(node, (edge, _attributes, source, target) => { + graph.setEdgeAttribute(edge, "highlighted", true); + graph.setEdgeAttribute(edge, "color", theme.link.selected); + const otherNode = source === node ? target : source; + graph.setNodeAttribute(otherNode, "highlighted", true); + graph.setNodeAttribute(otherNode, "color", theme.node.selected); }); - - // Update node positions - node.attr("transform", (d: any) => `translate(${d.x},${d.y})`); + // const nodePosition = sigma.getNodeDisplayData(node); + // if (nodePosition) { + // sigma + // .getCamera() + // .animate( + // { x: nodePosition.x, y: nodePosition.y, ratio: 0.5 }, + // { duration: 500 }, + // ); + // } }); - // Handle zoom-to-fit on mount - let hasInitialized = false; - - simulation.on("end", () => { - if (hasInitialized || !zoomOnMount || !zoomRef.current) return; - hasInitialized = true; - - const bounds = g.node()?.getBBox(); - if (bounds) { - const fullWidth = width; - const fullHeight = height; - const currentWidth = bounds.width || 1; - const currentHeight = bounds.height || 1; - - // Only proceed if we have valid dimensions - if ( - currentWidth > 0 && - currentHeight > 0 && - fullWidth > 0 && - fullHeight > 0 - ) { - const midX = bounds.x + currentWidth / 2; - const midY = bounds.y + currentHeight / 2; - - // Calculate scale to fit with padding - const scale = - 0.8 * - Math.min(fullWidth / currentWidth, fullHeight / currentHeight); - - // Ensure we have valid numbers before creating transform - if (isFinite(midX) && isFinite(midY) && isFinite(scale)) { - const transform = d3.zoomIdentity - .translate( - fullWidth / 2 - midX * scale, - fullHeight / 2 - midY * scale, - ) - .scale(scale); - - // Smoothly animate to the new transform - // @ts-ignore - svgElement - .transition() - .duration(750) - .ease(d3.easeCubicInOut) // Add easing for smoother transitions - .call(zoomRef.current.transform, transform); - } else { - console.warn("Invalid transform values:", { midX, midY, scale }); - // Fallback to a simple center transform - const transform = d3.zoomIdentity - .translate(fullWidth / 2, fullHeight / 2) - .scale(0.8); - svgElement.call(zoomRef.current.transform, transform); - } + // Edge click handler + sigma.on("clickEdge", (event) => { + const { edge } = event; + resetHighlights(); + const edgeAttrs = graph.getEdgeAttributes(edge); + if (edgeAttrs.relationData && edgeAttrs.relationData.length > 0) { + const relation = edgeAttrs.relationData[0]; + if (onEdgeClick) { + onEdgeClick(relation.id); } } + graph.setEdgeAttribute(edge, "highlighted", true); + graph.setEdgeAttribute(edge, "color", theme.link.selected); + selectedEdgeRef.current = edge; + const source = graph.source(edge); + const target = graph.target(edge); + graph.setNodeAttribute(source, "highlighted", true); + graph.setNodeAttribute(source, "color", theme.node.selected); + graph.setNodeAttribute(target, "highlighted", true); + graph.setNodeAttribute(target, "color", theme.node.selected); + const sourcePos = sigma.getNodeDisplayData(source); + const targetPos = sigma.getNodeDisplayData(target); + if (sourcePos && targetPos) { + const centerX = (sourcePos.x + targetPos.x) / 2; + const centerY = (sourcePos.y + targetPos.y) / 2; + // sigma + // .getCamera() + // .animate({ x: centerX, y: centerY, ratio: 0.5 }, { duration: 500 }); + } }); - // Cleanup function - only called when component unmounts + // Background click handler + sigma.on("clickStage", () => { + resetHighlights(); + if (onBlur) { + onBlur(); + } + }); + + // Cleanup function return () => { - simulation.stop(); - // Save the ref to a variable before using it in cleanup - const currentSvgRef = svgRef.current; - if (currentSvgRef) { - d3.select(currentSvgRef).on("click", null); + if (sigmaRef.current) { + sigmaRef.current.kill(); + sigmaRef.current = null; + } + if (graphRef.current) { + graphRef.current.clear(); + graphRef.current = null; } isInitializedRef.current = false; }; - // We're keeping the dependency array empty to ensure initialization runs only once on mount - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [nodes, edges]); - // This effect updates the graph theme colors when the theme changes - useEffect(() => { - // Skip if not initialized - if (!svgRef.current || !isInitializedRef.current) return; - - const svgElement = d3.select(svgRef.current); - - // Update background - svgElement.style("background-color", "var(--background-3)"); - - // Update nodes - use getNodeColor for proper color assignment - svgElement - .selectAll("circle") - .attr("fill", (d: any) => getNodeColor(d)) - .attr("stroke", theme.node.stroke); - - // Update node labels - // @ts-ignore - svgElement.selectAll("text").attr("fill", theme.node.text); - - // Update links - // @ts-ignore - svgElement - .selectAll("path") - .attr("stroke", theme.link.stroke) - .attr("stroke-opacity", 0.6); - - // Update selected links if any - // @ts-ignore - svgElement - .selectAll("path.selected") - .attr("stroke", theme.link.selected) - .attr("stroke-opacity", 1); - - // Update link labels - // @ts-ignore - svgElement - .selectAll(".link-label rect") - .attr("fill", theme.link.label.bg); - // @ts-ignore - svgElement - .selectAll(".link-label text") - .attr("fill", theme.link.label.text); - - // This effect has many dependencies that would cause frequent re-renders - // We're disabling the exhaustive deps rule to prevent unnecessary re-renders - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [themeMode]); + // // Theme update effect + // useEffect(() => { + // if (!sigmaRef.current || !graphRef.current || !isInitializedRef.current) + // return; + // const graph = graphRef.current; + // graph.forEachNode((node) => { + // const nodeData = graph.getNodeAttribute(node, "nodeData"); + // const isHighlighted = graph.getNodeAttribute(node, "highlighted"); + // if (!isHighlighted) { + // graph.setNodeAttribute(node, "color", getNodeColor(nodeData)); + // } + // }); + // graph.forEachEdge((edge) => { + // const isHighlighted = graph.getEdgeAttribute(edge, "highlighted"); + // if (!isHighlighted) { + // graph.setEdgeAttribute(edge, "color", theme.link.stroke); + // } + // }); + // sigmaRef.current.setSetting("defaultEdgeColor", theme.link.stroke); + // sigmaRef.current.setSetting("defaultNodeColor", "red"); + // }, [theme, getNodeColor]); return ( - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/webapp/app/components/logo/logo.tsx b/apps/webapp/app/components/logo/logo.tsx index a854395..f94779d 100644 --- a/apps/webapp/app/components/logo/logo.tsx +++ b/apps/webapp/app/components/logo/logo.tsx @@ -56,41 +56,276 @@ export default function StaticLogo({ width, height }: LogoProps) { return ( - - - - - - + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + ); diff --git a/apps/webapp/app/components/sidebar/app-sidebar.tsx b/apps/webapp/app/components/sidebar/app-sidebar.tsx index 4949379..c1b10cd 100644 --- a/apps/webapp/app/components/sidebar/app-sidebar.tsx +++ b/apps/webapp/app/components/sidebar/app-sidebar.tsx @@ -8,43 +8,52 @@ import { SidebarMenu, SidebarMenuItem, } from "../ui/sidebar"; -import { DashboardIcon } from "@radix-ui/react-icons"; -import { Code, Search } from "lucide-react"; +import { Activity, LayoutGrid, MessageSquare, Network } from "lucide-react"; import { NavMain } from "./nav-main"; import { useUser } from "~/hooks/useUser"; import { NavUser } from "./nav-user"; -import { useWorkspace } from "~/hooks/useWorkspace"; +import Logo from "../logo/logo"; const data = { navMain: [ { - title: "Dashboard", + title: "Chat", + url: "/home/chat", + icon: MessageSquare, + }, + { + title: "Memory", url: "/home/dashboard", - icon: DashboardIcon, + icon: Network, }, { - title: "API", - url: "/home/api", - icon: Code, + title: "Activity", + url: "/home/activity", + icon: Activity, }, { - title: "Logs", - url: "/home/logs", - icon: Search, + title: "Integrations", + url: "/home/integrations", + icon: LayoutGrid, }, ], }; export function AppSidebar({ ...props }: React.ComponentProps) { const user = useUser(); - const workspace = useWorkspace(); return ( - + - {workspace.name} +
+ +
diff --git a/apps/webapp/app/components/sidebar/nav-main.tsx b/apps/webapp/app/components/sidebar/nav-main.tsx index f2bee0a..f3a6d46 100644 --- a/apps/webapp/app/components/sidebar/nav-main.tsx +++ b/apps/webapp/app/components/sidebar/nav-main.tsx @@ -1,3 +1,4 @@ +import { cn } from "~/lib/utils"; import { SidebarGroup, SidebarGroupContent, @@ -28,10 +29,13 @@ export const NavMain = ({ navigate(item.url)} > {item.icon && } - {item.title} ))} diff --git a/apps/webapp/app/components/sidebar/nav-user.tsx b/apps/webapp/app/components/sidebar/nav-user.tsx index d3f4d3d..7002560 100644 --- a/apps/webapp/app/components/sidebar/nav-user.tsx +++ b/apps/webapp/app/components/sidebar/nav-user.tsx @@ -1,4 +1,4 @@ -import { LogOut } from "lucide-react"; +import { LogOut, Settings } from "lucide-react"; import { AvatarText } from "../ui/avatar"; import { DropdownMenu, @@ -8,33 +8,48 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "../ui/dropdown-menu"; -import { SidebarMenu, SidebarMenuItem, useSidebar } from "../ui/sidebar"; +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "../ui/sidebar"; import type { User } from "~/models/user.server"; import { Button } from "../ui"; +import { cn } from "~/lib/utils"; +import { useLocation, useNavigate } from "@remix-run/react"; export function NavUser({ user }: { user: User }) { const { isMobile } = useSidebar(); + const location = useLocation(); + const navigate = useNavigate(); return ( + + + = { + "/home/dashboard": "Memory graph", + "/home/chat": "Chat", + "/home/api": "API", + "/home/logs": "Logs", +}; + +function getHeaderTitle(pathname: string): string { + // Try to match the most specific path first + for (const key of Object.keys(PAGE_TITLES)) { + if (pathname.startsWith(key)) { + return PAGE_TITLES[key]; + } + } + // Default fallback + return "Documents"; +} + +export function SiteHeader() { + const location = useLocation(); + const title = getHeaderTitle(location.pathname); + + return ( +
+
+

{title}

+
+ +
+
+
+ ); +} diff --git a/apps/webapp/app/components/ui/sheet.tsx b/apps/webapp/app/components/ui/sheet.tsx index 104a951..2f74842 100644 --- a/apps/webapp/app/components/ui/sheet.tsx +++ b/apps/webapp/app/components/ui/sheet.tsx @@ -29,7 +29,7 @@ const SheetOverlay = React.forwardRef< SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + "fixed z-50 gap-4 bg-background-2 p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", { variants: { side: { diff --git a/apps/webapp/app/components/ui/sidebar.tsx b/apps/webapp/app/components/ui/sidebar.tsx index 914fc28..726f8fb 100644 --- a/apps/webapp/app/components/ui/sidebar.tsx +++ b/apps/webapp/app/components/ui/sidebar.tsx @@ -263,8 +263,7 @@ function SidebarTrigger({ data-sidebar="trigger" data-slot="sidebar-trigger" variant="ghost" - size="icon" - className={cn("size-7", className)} + className={cn("size-8", className)} onClick={(event) => { onClick?.(event); toggleSidebar(); @@ -307,7 +306,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
); @@ -472,11 +471,11 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) { } const sidebarMenuButtonVariants = cva( - "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", + "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:!bg-background-3 active:!text-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", { variants: { variant: { - default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", + default: "hover:bg-grayAlpha-100 hover:text-accent-foreground", outline: "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", }, diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 646a677..783c07b 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -71,6 +71,9 @@ const EnvironmentSchema = z.object({ SMTP_USER: z.string().optional(), SMTP_PASSWORD: z.string().optional(), + //Trigger + TRIGGER_PROJECT_ID: z.string(), + // Model envs MODEL: z.string().default(LLMModelEnum.GPT41), EMBEDDING_MODEL: z.string().default("bge-m3"), diff --git a/apps/webapp/app/models/workspace.server.ts b/apps/webapp/app/models/workspace.server.ts index f7ba2cf..106e9d3 100644 --- a/apps/webapp/app/models/workspace.server.ts +++ b/apps/webapp/app/models/workspace.server.ts @@ -2,7 +2,6 @@ import { type Workspace } from "@core/database"; import { prisma } from "~/db.server"; interface CreateWorkspaceDto { - slug: string; name: string; integrations: string[]; userId: string; @@ -13,7 +12,7 @@ export async function createWorkspace( ): Promise { const workspace = await prisma.workspace.create({ data: { - slug: input.slug, + slug: input.name, name: input.name, userId: input.userId, }, diff --git a/apps/webapp/app/root.tsx b/apps/webapp/app/root.tsx index cda0df3..22870b8 100644 --- a/apps/webapp/app/root.tsx +++ b/apps/webapp/app/root.tsx @@ -97,7 +97,7 @@ export function ErrorBoundary() { - + @@ -123,7 +123,7 @@ function App() { - + diff --git a/apps/webapp/app/routes/confirm-basic-details.tsx b/apps/webapp/app/routes/confirm-basic-details.tsx index f90ceee..fcc517c 100644 --- a/apps/webapp/app/routes/confirm-basic-details.tsx +++ b/apps/webapp/app/routes/confirm-basic-details.tsx @@ -24,10 +24,6 @@ const schema = z.object({ .string() .min(3, "Your workspace name must be at least 3 characters") .max(50), - workspaceSlug: z - .string() - .min(3, "Your workspace slug must be at least 3 characters") - .max(50), }); export async function action({ request }: ActionFunctionArgs) { @@ -40,11 +36,10 @@ export async function action({ request }: ActionFunctionArgs) { return json(submission); } - const { workspaceSlug, workspaceName } = submission.value; + const { workspaceName } = submission.value; try { await createWorkspace({ - slug: workspaceSlug, integrations: [], name: workspaceName, userId, @@ -109,27 +104,6 @@ export default function ConfirmBasicDetails() { )}
-
- - - {fields.workspaceSlug.error && ( -
- {fields.workspaceSlug.error} -
- )} -
- + + + + + + {data.nav.map((item) => ( + + + + ))} + + + + + +
+
+ +
+
+ +
+ ); +} diff --git a/apps/webapp/app/services/knowledgeGraph.server.ts b/apps/webapp/app/services/knowledgeGraph.server.ts index 67b1f3a..cb2a573 100644 --- a/apps/webapp/app/services/knowledgeGraph.server.ts +++ b/apps/webapp/app/services/knowledgeGraph.server.ts @@ -47,7 +47,8 @@ import { createOllama } from "ollama-ai-provider"; const DEFAULT_EPISODE_WINDOW = 5; export class KnowledgeGraphService { - async getEmbedding(text: string, useOpenAI = false) { + async getEmbedding(text: string, useOpenAI = true) { + console.log(text, useOpenAI); if (useOpenAI) { // Use OpenAI embedding model when explicitly requested const { embedding } = await embed({ @@ -58,7 +59,7 @@ export class KnowledgeGraphService { } // Default to using Ollama - const ollamaUrl = process.env.OLLAMA_URL; + const ollamaUrl = env.OLLAMA_URL; const model = env.EMBEDDING_MODEL; const ollama = createOllama({ diff --git a/apps/webapp/app/services/postAuth.server.ts b/apps/webapp/app/services/postAuth.server.ts index e5d92fe..7c91726 100644 --- a/apps/webapp/app/services/postAuth.server.ts +++ b/apps/webapp/app/services/postAuth.server.ts @@ -1,4 +1,5 @@ import type { User } from "~/models/user.server"; +import { createWorkspace } from "~/models/workspace.server"; import { singleton } from "~/utils/singleton"; export async function postAuthentication({ @@ -10,5 +11,11 @@ export async function postAuthentication({ loginMethod: User["authenticationMethod"]; isNewUser: boolean; }) { - // console.log(user); + if (user.name && isNewUser && loginMethod === "GOOGLE") { + await createWorkspace({ + name: user.name, + userId: user.id, + integrations: [], + }); + } } diff --git a/apps/webapp/app/tailwind.css b/apps/webapp/app/tailwind.css index 05500ce..9b38b4c 100644 --- a/apps/webapp/app/tailwind.css +++ b/apps/webapp/app/tailwind.css @@ -322,8 +322,9 @@ @layer base { * { @apply border-border outline-ring/50; + --header-height: 44px; } body { - @apply bg-background text-foreground text-base; + @apply bg-background-2 text-foreground text-base; } } \ No newline at end of file diff --git a/apps/webapp/app/trigger/.gitkeep b/apps/webapp/app/trigger/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/webapp/app/trigger/integrations/integration-run-schedule.ts b/apps/webapp/app/trigger/integrations/integration-run-schedule.ts new file mode 100644 index 0000000..5ba4847 --- /dev/null +++ b/apps/webapp/app/trigger/integrations/integration-run-schedule.ts @@ -0,0 +1,38 @@ +import { PrismaClient } from '@prisma/client'; +import { IntegrationPayloadEventType } from '@redplanethq/sol-sdk'; +import { logger, schedules, tasks } from '@trigger.dev/sdk/v3'; + +import { integrationRun } from './integration-run'; + +const prisma = new PrismaClient(); + +export const integrationRunSchedule = schedules.task({ + id: 'integration-run-schedule', + run: async (payload) => { + const { externalId } = payload; + const integrationAccount = await prisma.integrationAccount.findUnique({ + where: { id: externalId }, + include: { + integrationDefinition: true, + workspace: true, + }, + }); + + if (!integrationAccount) { + const deletedSchedule = await schedules.del(externalId); + logger.info('Deleting schedule as integration account is not there'); + return deletedSchedule; + } + + const pat = await prisma.personalAccessToken.findFirst({ + where: { userId: integrationAccount.workspace.userId, name: 'default' }, + }); + + return await tasks.trigger('integration-run', { + event: IntegrationPayloadEventType.SCHEDULED_SYNC, + pat: pat.token, + integrationAccount, + integrationDefinition: integrationAccount.integrationDefinition, + }); + }, +}); diff --git a/apps/webapp/app/trigger/integrations/integration-run.ts b/apps/webapp/app/trigger/integrations/integration-run.ts new file mode 100644 index 0000000..9d14e2d --- /dev/null +++ b/apps/webapp/app/trigger/integrations/integration-run.ts @@ -0,0 +1,90 @@ +import createLoadRemoteModule, { + createRequires, +} from '@paciolan/remote-module-loader'; +import { + IntegrationAccount, + IntegrationDefinition, +} from '@redplanethq/sol-sdk'; +import { logger, task } from '@trigger.dev/sdk/v3'; +import axios from 'axios'; + +const fetcher = async (url: string) => { + // Handle remote URLs with axios + const response = await axios.get(url); + + return response.data; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const loadRemoteModule = async (requires: any) => + createLoadRemoteModule({ fetcher, requires }); + +function createAxiosInstance(token: string) { + const instance = axios.create(); + + instance.interceptors.request.use((config) => { + // Check if URL starts with /api and doesn't have a full host + if (config.url?.startsWith('/api')) { + config.url = `${process.env.BACKEND_HOST}${config.url.replace('/api/', '/')}`; + } + + if ( + config.url.includes(process.env.FRONTEND_HOST) || + config.url.includes(process.env.BACKEND_HOST) + ) { + config.headers.Authorization = `Bearer ${token}`; + } + + return config; + }); + + return instance; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const getRequires = (axios: any) => createRequires({ axios }); + +export const integrationRun = task({ + id: 'integration-run', + run: async ({ + pat, + eventBody, + integrationAccount, + integrationDefinition, + event, + }: { + pat: string; + // This is the event you want to pass to the integration + // eslint-disable-next-line @typescript-eslint/no-explicit-any + event: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + eventBody?: any; + integrationDefinition: IntegrationDefinition; + integrationAccount?: IntegrationAccount; + }) => { + const remoteModuleLoad = await loadRemoteModule( + getRequires(createAxiosInstance(pat)), + ); + + logger.info( + `${integrationDefinition.url}/${integrationDefinition.version}/backend/index.js`, + ); + + const integrationFunction = await remoteModuleLoad( + `${integrationDefinition.url}/${integrationDefinition.version}/backend/index.js`, + ); + + // const integrationFunction = await remoteModuleLoad( + // `${integrationDefinition.url}`, + // ); + + return await integrationFunction.run({ + integrationAccount, + integrationDefinition, + event, + eventBody: { + ...(eventBody ? eventBody : {}), + }, + }); + }, +}); diff --git a/apps/webapp/app/trigger/integrations/scheduler.ts b/apps/webapp/app/trigger/integrations/scheduler.ts new file mode 100644 index 0000000..2ca81a9 --- /dev/null +++ b/apps/webapp/app/trigger/integrations/scheduler.ts @@ -0,0 +1,64 @@ +import { PrismaClient } from "@prisma/client"; +import { logger, schedules, task } from "@trigger.dev/sdk/v3"; + +import { integrationRunSchedule } from "./integration-run-schedule"; + +const prisma = new PrismaClient(); + +export const scheduler = task({ + id: "scheduler", + run: async (payload: { integrationAccountId: string }) => { + const { integrationAccountId } = payload; + + const integrationAccount = await prisma.integrationAccount.findUnique({ + where: { id: integrationAccountId, deleted: null }, + include: { + integrationDefinition: true, + workspace: true, + }, + }); + + if (!integrationAccount) { + logger.error("Integration account not found"); + return null; + } + + if (!integrationAccount.workspace) { + return null; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const spec = integrationAccount.integrationDefinition.spec as any; + + if (spec.schedule && spec.schedule.frequency) { + const createdSchedule = await schedules.create({ + // The id of the scheduled task you want to attach to. + task: integrationRunSchedule.id, + // The schedule in cron format. + cron: spec.schedule.frequency, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + timezone: (integrationAccount.workspace.preferences as any).timezone, + // this is required, it prevents you from creating duplicate schedules. It will update the schedule if it already exists. + deduplicationKey: integrationAccount.id, + externalId: integrationAccount.id, + }); + + await prisma.integrationAccount.update({ + where: { + id: integrationAccount.id, + }, + data: { + settings: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...(integrationAccount.settings as any), + scheduleId: createdSchedule.id, + }, + }, + }); + + return createdSchedule; + } + + return "No schedule for this task"; + }, +}); diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 561fdef..787a769 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -17,8 +17,10 @@ "@conform-to/zod": "^0.6.1", "@core/database": "workspace:*", "@core/types": "workspace:*", - "@opentelemetry/api": "1.9.0", "@mjackson/headers": "0.11.1", + "@nichtsam/remix-auth-email-link": "3.0.0", + "@opentelemetry/api": "1.9.0", + "@prisma/client": "*", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.0.4", @@ -45,10 +47,10 @@ "@remix-run/server-runtime": "2.16.7", "@remix-run/v1-meta": "^0.1.3", "@remixicon/react": "^4.2.0", - "@tanstack/react-table": "^8.13.2", - "@prisma/client": "*", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/postcss": "^4.1.7", + "@tanstack/react-table": "^8.13.2", + "@trigger.dev/sdk": "^3.3.17", "ai": "4.3.14", "bullmq": "^5.53.2", "class-variance-authority": "^0.7.1", @@ -56,10 +58,14 @@ "compression": "^1.7.4", "cross-env": "^7.0.3", "d3": "^7.9.0", - "dayjs": "^1.11.10", "date-fns": "^4.1.0", - "express": "^4.18.1", + "dayjs": "^1.11.10", "emails": "workspace:*", + "express": "^4.18.1", + "graphology": "^0.26.0", + "graphology-layout-force": "^0.2.4", + "graphology-layout-forceatlas2": "^0.10.1", + "graphology-layout-noverlap": "^0.4.2", "ioredis": "^5.6.1", "isbot": "^4.1.0", "jose": "^5.2.3", @@ -72,14 +78,14 @@ "posthog-js": "^1.116.6", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-resizable-panels": "^1.0.9", + "react-virtualized": "^9.22.6", "remix-auth": "^4.2.0", - "@nichtsam/remix-auth-email-link": "3.0.0", "remix-auth-oauth2": "^3.4.1", "remix-themes": "^1.3.1", "remix-typedjson": "0.3.1", "remix-utils": "^7.7.0", - "react-resizable-panels": "^1.0.9", - "react-virtualized": "^9.22.6", + "sigma": "^3.0.2", "tailwind-merge": "^2.6.0", "tailwind-scrollbar-hide": "^2.0.0", "tailwindcss-animate": "^1.0.7", @@ -96,6 +102,7 @@ "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.7", + "@trigger.dev/build": "^3.3.17", "@types/compression": "^1.7.2", "@types/d3": "^7.4.3", "@types/express": "^4.17.13", diff --git a/apps/webapp/trigger.config.ts b/apps/webapp/trigger.config.ts new file mode 100644 index 0000000..49f7d21 --- /dev/null +++ b/apps/webapp/trigger.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from "@trigger.dev/sdk/v3"; +import { env } from "~/env.server"; + +export default defineConfig({ + project: env.TRIGGER_PROJECT_ID, + runtime: "node", + logLevel: "log", + // The max compute seconds a task is allowed to run. If the task run exceeds this duration, it will be stopped. + // You can override this on an individual task. + // See https://trigger.dev/docs/runs/max-duration + maxDuration: 3600, + retries: { + enabledInDev: true, + default: { + maxAttempts: 3, + minTimeoutInMs: 1000, + maxTimeoutInMs: 10000, + factor: 2, + randomize: true, + }, + }, + dirs: ["./app/trigger"], +}); diff --git a/apps/webapp/tsconfig.json b/apps/webapp/tsconfig.json index 19e3238..103ad86 100644 --- a/apps/webapp/tsconfig.json +++ b/apps/webapp/tsconfig.json @@ -6,7 +6,8 @@ "**/*.ts", "**/*.tsx", "tailwind.config.js", - "tailwind.config.js" + "tailwind.config.js", + "trigger.config.ts" ], "compilerOptions": { "types": ["@remix-run/node", "vite/client"], diff --git a/docker/Dockerfile b/docker-build/Dockerfile similarity index 100% rename from docker/Dockerfile rename to docker-build/Dockerfile diff --git a/docker/docker-compose.yaml b/docker-build/docker-compose.yaml similarity index 100% rename from docker/docker-compose.yaml rename to docker-build/docker-compose.yaml diff --git a/docker/scripts/entrypoint.sh b/docker-build/scripts/entrypoint.sh similarity index 100% rename from docker/scripts/entrypoint.sh rename to docker-build/scripts/entrypoint.sh diff --git a/docker/scripts/wait-for-it.sh b/docker-build/scripts/wait-for-it.sh similarity index 100% rename from docker/scripts/wait-for-it.sh rename to docker-build/scripts/wait-for-it.sh diff --git a/docker-compose.yaml b/docker-compose.yaml index d234ce0..77f9b3e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -37,7 +37,7 @@ services: postgres: container_name: core-postgres - image: postgres:15 + image: redplanethq/postgres:0.1.0 environment: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} diff --git a/packages/database/prisma/migrations/20250706093221_add_activity/migration.sql b/packages/database/prisma/migrations/20250706093221_add_activity/migration.sql new file mode 100644 index 0000000..d08043b --- /dev/null +++ b/packages/database/prisma/migrations/20250706093221_add_activity/migration.sql @@ -0,0 +1,119 @@ +-- CreateEnum +CREATE TYPE "WebhookDeliveryStatus" AS ENUM ('SUCCESS', 'FAILED'); + +-- CreateTable +CREATE TABLE "Activity" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deleted" TIMESTAMP(3), + "text" TEXT NOT NULL, + "sourceURL" TEXT, + "integrationAccountId" TEXT, + "rejectionReason" TEXT, + "workspaceId" TEXT NOT NULL, + + CONSTRAINT "Activity_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IntegrationAccount" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deleted" TIMESTAMP(3), + "integrationConfiguration" JSONB NOT NULL, + "accountId" TEXT, + "settings" JSONB, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "integratedById" TEXT NOT NULL, + "integrationDefinitionId" TEXT NOT NULL, + "workspaceId" TEXT NOT NULL, + + CONSTRAINT "IntegrationAccount_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IntegrationDefinitionV2" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deleted" TIMESTAMP(3), + "name" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "description" TEXT NOT NULL, + "icon" TEXT NOT NULL, + "config" JSONB, + "spec" JSONB NOT NULL DEFAULT '{}', + "version" TEXT, + "url" TEXT, + "workspaceId" TEXT, + + CONSTRAINT "IntegrationDefinitionV2_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "WebhookConfiguration" ( + "id" TEXT NOT NULL, + "url" TEXT NOT NULL, + "secret" TEXT, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "eventTypes" TEXT[], + "userId" TEXT, + "workspaceId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "WebhookConfiguration_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "WebhookDeliveryLog" ( + "id" TEXT NOT NULL, + "webhookConfigurationId" TEXT NOT NULL, + "activityId" TEXT, + "status" "WebhookDeliveryStatus" NOT NULL, + "responseStatusCode" INTEGER, + "responseBody" TEXT, + "error" TEXT, + "deliveredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "WebhookDeliveryLog_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "IntegrationAccount_accountId_integrationDefinitionId_worksp_key" ON "IntegrationAccount"("accountId", "integrationDefinitionId", "workspaceId"); + +-- CreateIndex +CREATE UNIQUE INDEX "IntegrationDefinitionV2_name_key" ON "IntegrationDefinitionV2"("name"); + +-- AddForeignKey +ALTER TABLE "Activity" ADD CONSTRAINT "Activity_integrationAccountId_fkey" FOREIGN KEY ("integrationAccountId") REFERENCES "IntegrationAccount"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Activity" ADD CONSTRAINT "Activity_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IntegrationAccount" ADD CONSTRAINT "IntegrationAccount_integratedById_fkey" FOREIGN KEY ("integratedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IntegrationAccount" ADD CONSTRAINT "IntegrationAccount_integrationDefinitionId_fkey" FOREIGN KEY ("integrationDefinitionId") REFERENCES "IntegrationDefinitionV2"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IntegrationAccount" ADD CONSTRAINT "IntegrationAccount_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IntegrationDefinitionV2" ADD CONSTRAINT "IntegrationDefinitionV2_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WebhookConfiguration" ADD CONSTRAINT "WebhookConfiguration_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WebhookConfiguration" ADD CONSTRAINT "WebhookConfiguration_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WebhookDeliveryLog" ADD CONSTRAINT "WebhookDeliveryLog_webhookConfigurationId_fkey" FOREIGN KEY ("webhookConfigurationId") REFERENCES "WebhookConfiguration"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WebhookDeliveryLog" ADD CONSTRAINT "WebhookDeliveryLog_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "Activity"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 7035de3..3f8ea3a 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -36,10 +36,12 @@ model User { referralSource String? personalAccessTokens PersonalAccessToken[] - InvitationCode InvitationCode? @relation(fields: [invitationCodeId], references: [id]) + InvitationCode InvitationCode? @relation(fields: [invitationCodeId], references: [id]) invitationCodeId String? Space Space[] Workspace Workspace? + IntegrationAccount IntegrationAccount[] + WebhookConfiguration WebhookConfiguration[] } model Workspace { @@ -54,9 +56,13 @@ model Workspace { integrations String[] - userId String? @unique - user User? @relation(fields: [userId], references: [id]) - IngestionQueue IngestionQueue[] + userId String? @unique + user User? @relation(fields: [userId], references: [id]) + IngestionQueue IngestionQueue[] + IntegrationAccount IntegrationAccount[] + IntegrationDefinitionV2 IntegrationDefinitionV2[] + Activity Activity[] + WebhookConfiguration WebhookConfiguration[] } enum AuthenticationMethod { @@ -193,6 +199,110 @@ model IngestionQueue { processedAt DateTime? } +// For Integrations + +model Activity { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deleted DateTime? + + text String + // Used to link the task or activity to external apps + sourceURL String? + + integrationAccount IntegrationAccount? @relation(fields: [integrationAccountId], references: [id]) + integrationAccountId String? + + rejectionReason String? + + workspace Workspace @relation(fields: [workspaceId], references: [id]) + workspaceId String + + WebhookDeliveryLog WebhookDeliveryLog[] +} + +model IntegrationAccount { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deleted DateTime? + + integrationConfiguration Json + accountId String? + settings Json? + isActive Boolean @default(true) + + integratedBy User @relation(references: [id], fields: [integratedById]) + integratedById String + integrationDefinition IntegrationDefinitionV2 @relation(references: [id], fields: [integrationDefinitionId]) + integrationDefinitionId String + workspace Workspace @relation(references: [id], fields: [workspaceId]) + workspaceId String + Activity Activity[] + + @@unique([accountId, integrationDefinitionId, workspaceId]) +} + +model IntegrationDefinitionV2 { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deleted DateTime? + + name String @unique + slug String + description String + icon String + config Json? + spec Json @default("{}") + version String? + url String? + + workspace Workspace? @relation(references: [id], fields: [workspaceId]) + workspaceId String? + + IntegrationAccount IntegrationAccount[] +} + +model WebhookConfiguration { + id String @id @default(cuid()) + url String + secret String? + isActive Boolean @default(true) + eventTypes String[] // List of event types this webhook is interested in, e.g. ["activity.created"] + user User? @relation(fields: [userId], references: [id]) + userId String? + workspace Workspace? @relation(fields: [workspaceId], references: [id]) + workspaceId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + WebhookDeliveryLog WebhookDeliveryLog[] +} + +model WebhookDeliveryLog { + id String @id @default(cuid()) + webhookConfiguration WebhookConfiguration @relation(fields: [webhookConfigurationId], references: [id]) + webhookConfigurationId String + + activity Activity? @relation(fields: [activityId], references: [id]) + activityId String? + + status WebhookDeliveryStatus + responseStatusCode Int? + responseBody String? + error String? + deliveredAt DateTime @default(now()) + + createdAt DateTime @default(now()) +} + +enum WebhookDeliveryStatus { + SUCCESS + FAILED +} + enum IngestionStatus { PENDING PROCESSING diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d36452..47a523e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -147,6 +147,9 @@ importers: '@tanstack/react-table': specifier: ^8.13.2 version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@trigger.dev/sdk': + specifier: ^3.3.17 + version: 3.3.17(zod@3.23.8) ai: specifier: 4.3.14 version: 4.3.14(react@18.3.1)(zod@3.23.8) @@ -180,6 +183,18 @@ importers: express: specifier: ^4.18.1 version: 4.21.2 + graphology: + specifier: ^0.26.0 + version: 0.26.0(graphology-types@0.24.8) + graphology-layout-force: + specifier: ^0.2.4 + version: 0.2.4(graphology-types@0.24.8) + graphology-layout-forceatlas2: + specifier: ^0.10.1 + version: 0.10.1(graphology-types@0.24.8) + graphology-layout-noverlap: + specifier: ^0.4.2 + version: 0.4.2(graphology-types@0.24.8) ioredis: specifier: ^5.6.1 version: 5.6.1 @@ -237,6 +252,9 @@ importers: remix-utils: specifier: ^7.7.0 version: 7.7.0(@remix-run/node@2.1.0(typescript@5.8.3))(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/router@1.23.0)(crypto-js@4.2.0)(react@18.3.1)(zod@3.23.8) + sigma: + specifier: ^3.0.2 + version: 3.0.2(graphology-types@0.24.8) tailwind-merge: specifier: ^2.6.0 version: 2.6.0 @@ -280,6 +298,9 @@ importers: '@tailwindcss/vite': specifier: ^4.1.7 version: 4.1.9(vite@6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)) + '@trigger.dev/build': + specifier: ^3.3.17 + version: 3.3.17(typescript@5.8.3) '@types/compression': specifier: ^1.7.2 version: 1.8.1 @@ -854,6 +875,9 @@ packages: resolution: {integrity: sha512-VLhlvEPDJ0Sd0pE6sAYTQkIqZCXVonaWlgRJIQQHzfjTXCadF77qqHj5NxaPSc4wCul0DJO/0MnejVqJAXUiRg==} engines: {node: '>=20.0.0'} + '@electric-sql/client@1.0.0-beta.1': + resolution: {integrity: sha512-Ei9jN3pDoGzc+a/bGqnB5ajb52IvSv7/n2btuyzUlcOHIR2kM9fqtYTJXPwZYKLkGZlHWlpHgWyRtrinkP2nHg==} + '@emnapi/core@1.4.3': resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} @@ -1466,6 +1490,19 @@ packages: '@fullhuman/postcss-purgecss@2.3.0': resolution: {integrity: sha512-qnKm5dIOyPGJ70kPZ5jiz0I9foVOic0j+cOzNDoo8KoCf6HjicIZ99UfO2OmE7vCYSKAAepEwJtNzpiiZAh9xw==} + '@google-cloud/precise-date@4.0.0': + resolution: {integrity: sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==} + engines: {node: '>=14.0.0'} + + '@grpc/grpc-js@1.13.4': + resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1511,6 +1548,12 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@jsonhero/path@1.0.21': + resolution: {integrity: sha512-gVUDj/92acpVoJwsVJ/RuWOaHyG4oFzn898WNGQItLCTQ+hOaVlEaImhwE1WqOTf+l3dGOUkbSiVKlb3q1hd1Q==} + '@jspm/core@2.1.0': resolution: {integrity: sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==} @@ -1665,10 +1708,132 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@opentelemetry/api-logs@0.52.1': + resolution: {integrity: sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==} + engines: {node: '>=14'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} + '@opentelemetry/context-async-hooks@1.25.1': + resolution: {integrity: sha512-UW/ge9zjvAEmRWVapOP0qyCvPulWU6cQxGxDbWEFfGOj1VBBZAuOqTo3X6yWmDTD3Xe15ysCZChHncr2xFMIfQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.25.1': + resolution: {integrity: sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-http@0.52.1': + resolution: {integrity: sha512-qKgywId2DbdowPZpOBXQKp0B8DfhfIArmSic15z13Nk/JAOccBUQdPwDjDnjsM5f0ckZFMVR2t/tijTUAqDZoA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.52.1': + resolution: {integrity: sha512-pVkSH20crBwMTqB3nIN4jpQKUEoB0Z94drIHpYyEqs7UBr+I0cpYyOR3bqjA/UasQUMROb3GX8ZX4/9cVRqGBQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/exporter-trace-otlp-http@0.52.1': + resolution: {integrity: sha512-05HcNizx0BxcFKKnS5rwOV+2GevLTVIRA0tRgWYyw4yCgR53Ic/xk83toYKts7kbzcI+dswInUg/4s8oyA+tqg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.52.1': + resolution: {integrity: sha512-pt6uX0noTQReHXNeEslQv7x311/F1gJzMnp1HD2qgypLRPbXDeMzzeTngRTUaUbP6hqWNtPxuLr4DEoZG+TcEQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/exporter-zipkin@1.25.1': + resolution: {integrity: sha512-RmOwSvkimg7ETwJbUOPTMhJm9A9bG1U8s7Zo3ajDh4zM7eYcycQ0dM7FbLD6NXWbI2yj7UY4q8BKinKYBQksyw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation@0.52.1': + resolution: {integrity: sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.52.1': + resolution: {integrity: sha512-z175NXOtX5ihdlshtYBe5RpGeBoTXVCKPPLiQlD6FHvpM4Ch+p2B0yWKYSrBfLH24H9zjJiBdTrtD+hLlfnXEQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.52.1': + resolution: {integrity: sha512-zo/YrSDmKMjG+vPeA9aBBrsQM9Q/f2zo6N04WMB3yNldJRsgpRBeLLwvAt/Ba7dpehDLOEFBd1i2JCoaFtpCoQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/otlp-transformer@0.52.1': + resolution: {integrity: sha512-I88uCZSZZtVa0XniRqQWKbjAUm73I8tpEy/uJYPPYw5d7BRdVk0RfTBQw8kSUl01oVWEuqxLDa802222MYyWHg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/propagator-b3@1.25.1': + resolution: {integrity: sha512-p6HFscpjrv7//kE+7L+3Vn00VEDUJB0n6ZrjkTYHrJ58QZ8B3ajSJhRbCcY6guQ3PDjTbxWklyvIN2ojVbIb1A==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@1.25.1': + resolution: {integrity: sha512-nBprRf0+jlgxks78G/xq72PipVK+4or9Ypntw0gVZYNTCSK8rg5SeaGV19tV920CMqBD/9UIOiFr23Li/Q8tiA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@1.25.1': + resolution: {integrity: sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.52.1': + resolution: {integrity: sha512-MBYh+WcPPsN8YpRHRmK1Hsca9pVlyyKd4BxOC4SsgHACnl/bPp4Cri9hWhVm5+2tiQ9Zf4qSc1Jshw9tOLGWQA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.25.1': + resolution: {integrity: sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-node@0.52.1': + resolution: {integrity: sha512-uEG+gtEr6eKd8CVWeKMhH2olcCHM9dEK68pe0qE0be32BcCRsvYURhHaD1Srngh1SQcnQzZ4TP324euxqtBOJA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@1.25.1': + resolution: {integrity: sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@1.25.1': + resolution: {integrity: sha512-nMcjFIKxnFqoez4gUmihdBrbpsEnAX/Xj16sGvZm+guceYE0NE00vLhpDVK6f3q8Q4VFI5xG8JjlXKMB/SkTTQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.25.1': + resolution: {integrity: sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==} + engines: {node: '>=14'} + '@oslojs/asn1@1.0.0': resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==} @@ -1706,6 +1871,36 @@ packages: '@prisma/engines@5.4.1': resolution: {integrity: sha512-vJTdY4la/5V3N7SFvWRmSMUh4mIQnyb/MNoDjzVbh9iLmEC+uEykj/1GPviVsorvfz7DbYSQC4RiwmlEpTEvGA==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@radix-ui/colors@1.0.1': resolution: {integrity: sha512-xySw8f0ZVsAEP+e7iLl3EvcBXX7gsIlC1Zso/sPBW9gIWerBTgz6axrjU+MZ39wD+WFi5h5zdWpsg3+hwt2Qsg==} @@ -3320,6 +3515,20 @@ packages: resolution: {integrity: sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==} engines: {node: '>=12'} + '@trigger.dev/build@3.3.17': + resolution: {integrity: sha512-dfreMuVeLAcZypS3kkUA9nWNviiuOPIQ3ldy2ywPCmwmbHyd0BE8tI5D3A4kmVq/f53TdRMls4c+cYafxlwubQ==} + engines: {node: '>=18.20.0'} + + '@trigger.dev/core@3.3.17': + resolution: {integrity: sha512-KjnRxCuHq4R+MnE0zPvIQ7EIz4QSpJL+1Yn74n2cCGjyHYgQ/g8rcARn0Nxf2s8jzE38CnyRufjUrwG8k+DJrw==} + engines: {node: '>=18.20.0'} + + '@trigger.dev/sdk@3.3.17': + resolution: {integrity: sha512-wjIjlQWKybYWw/J7LxFIOO1pXzxXoj9lxbFMvjb51JtfebxnQnh6aExN47nOGhVhV38wHYstfBI/8ClWwBnFYw==} + engines: {node: '>=18.20.0'} + peerDependencies: + zod: ^3.0.0 + '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} @@ -3550,6 +3759,9 @@ packages: '@types/serve-static@1.15.8': resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + '@types/shimmer@1.2.0': + resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -3851,6 +4063,11 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3910,6 +4127,10 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} + ansi-escapes@5.0.0: + resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} + engines: {node: '>=12'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -4228,6 +4449,9 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -4368,6 +4592,10 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + core-js@3.43.0: resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==} @@ -4391,6 +4619,10 @@ packages: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} + cronstrue@2.59.0: + resolution: {integrity: sha512-YKGmAy84hKH+hHIIER07VCAHf9u0Ldelx1uU6EBxsRPDXIA1m5fsKmJfyC3xBhw6cVC/1i83VdbL4PvepTrt8A==} + hasBin: true + cross-env@7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} @@ -5172,10 +5404,25 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.3: + resolution: {integrity: sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==} + engines: {node: '>=20.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + evt@2.5.9: + resolution: {integrity: sha512-GpjX476FSlttEGWHT8BdVMoI8wGXQGbEOtKcP4E+kggg+yJzXBZN2n4x7TS/zPBJ1DZqWI+rguZZApjjzQ0HpA==} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} @@ -5373,6 +5620,10 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} @@ -5441,6 +5692,34 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphology-layout-force@0.2.4: + resolution: {integrity: sha512-NYZz0YAnDkn5pkm30cvB0IScFoWGtbzJMrqaiH070dYlYJiag12Oc89dbVfaMaVR/w8DMIKxn/ix9Bqj+Umm9Q==} + peerDependencies: + graphology-types: '>=0.19.0' + + graphology-layout-forceatlas2@0.10.1: + resolution: {integrity: sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ==} + peerDependencies: + graphology-types: '>=0.19.0' + + graphology-layout-noverlap@0.4.2: + resolution: {integrity: sha512-13WwZSx96zim6l1dfZONcqLh3oqyRcjIBsqz2c2iJ3ohgs3605IDWjldH41Gnhh462xGB1j6VGmuGhZ2FKISXA==} + peerDependencies: + graphology-types: '>=0.19.0' + + graphology-types@0.24.8: + resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==} + + graphology-utils@2.5.2: + resolution: {integrity: sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==} + peerDependencies: + graphology-types: '>=0.23.0' + + graphology@0.26.0: + resolution: {integrity: sha512-8SSImzgUUYC89Z042s+0r/vMibY7GX/Emz4LDO5e7jYXhuoWfHISPFJYjpRLUSJGq6UQ6xlenvX1p/hJdfXuXg==} + peerDependencies: + graphology-types: '>=0.24.0' + gunzip-maybe@1.4.2: resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} hasBin: true @@ -5515,6 +5794,13 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + humanize-duration@3.33.0: + resolution: {integrity: sha512-vYJX7BSzn7EQ4SaP2lPYVy+icHDppB6k7myNeI3wrSRfwMS5+BHyGgzpHR0ptqJ2AQ6UuIKrclSg5ve6Ci4IAQ==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -5540,6 +5826,9 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-in-the-middle@1.14.2: + resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -5728,6 +6017,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -5760,6 +6053,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -6043,6 +6340,9 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -6283,6 +6583,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -6291,6 +6595,9 @@ packages: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true + minimal-polyfills@2.2.3: + resolution: {integrity: sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw==} + minimatch@10.0.2: resolution: {integrity: sha512-+9TJCIYXgZ2Dm5LxVCFsa8jOm+evMwXHFI0JM1XROmkfkpz8/iLLDh+TwSmyIBrs6C6Xu9294/fq8cBA+P6AqA==} engines: {node: 20 || >=22} @@ -6372,6 +6679,9 @@ packages: modern-ahocorasick@1.1.0: resolution: {integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==} + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + morgan@1.10.0: resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} engines: {node: '>= 0.8.0'} @@ -6536,6 +6846,10 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + num2fraction@1.2.2: resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==} @@ -6611,6 +6925,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -6716,6 +7034,10 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -7088,6 +7410,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protobufjs@7.5.3: + resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -7378,6 +7704,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@7.5.2: + resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} + engines: {node: '>=8.6.0'} + require-like@0.1.2: resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} @@ -7446,6 +7776,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-exclusive@2.2.19: + resolution: {integrity: sha512-K3mdoAi7tjJ/qT7Flj90L7QyPozwUaAG+CVhkdDje4HLKXUYC3N/Jzkau3flHVDLQVhiHBtcimVodMjN9egYbA==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -7558,6 +7891,9 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -7574,6 +7910,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + sigma@3.0.2: + resolution: {integrity: sha512-/BUbeOwPGruiBOm0YQQ6ZMcLIZ6tf/W+Jcm7dxZyAX0tK3WP9/sq7/NAWBxPIxVahdGjCJoGwej0Gdrv0DxlQQ==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -7588,6 +7927,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slug@6.1.0: + resolution: {integrity: sha512-x6vLHCMasg4DR2LPiyFGI0gJJhywY6DTiGhCrOMzb3SOk/0JVLIaL4UhyFSHu04SD3uAavrKY/K3zZ3i6iRcgA==} + smartwrap@2.0.2: resolution: {integrity: sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==} engines: {node: '>=6'} @@ -7600,6 +7942,10 @@ packages: resolution: {integrity: sha512-nU+ywttCyBitXIl9Xe0RSEfek4LneYkJxCeNnKCuhwoH4jGXO1ipIUw/VA/+Vvv2G1MTym11fzFC0SxkrcfXDw==} engines: {node: '>=10.0.0'} + socket.io-client@4.7.5: + resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==} + engines: {node: '>=10.0.0'} + socket.io-parser@4.2.4: resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} engines: {node: '>=10.0.0'} @@ -7752,6 +8098,10 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -7784,6 +8134,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -7800,6 +8154,10 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} + supports-hyperlinks@2.3.0: + resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} + engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -7873,6 +8231,10 @@ packages: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} + terminal-link@3.0.0: + resolution: {integrity: sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==} + engines: {node: '>=12'} + terser-webpack-plugin@5.3.14: resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} engines: {node: '>= 10.13.0'} @@ -7955,6 +8317,19 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsafe@1.8.5: + resolution: {integrity: sha512-LFWTWQrW6rwSY+IBNFl2ridGfUzVsPwrZ26T4KUJww/py8rzaQ/SY+MIz6YROozpUCaRcuISqagmlwub9YT9kw==} + + tsconfck@3.1.3: + resolution: {integrity: sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -8050,6 +8425,10 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -8092,10 +8471,17 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + ulid@2.4.0: + resolution: {integrity: sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==} + hasBin: true + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@7.8.0: resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} @@ -9356,6 +9742,10 @@ snapshots: '@edgefirst-dev/data@0.0.4': {} + '@electric-sql/client@1.0.0-beta.1': + optionalDependencies: + '@rollup/rollup-darwin-arm64': 4.43.0 + '@emnapi/core@1.4.3': dependencies: '@emnapi/wasi-threads': 1.0.2 @@ -9706,6 +10096,20 @@ snapshots: postcss: 7.0.32 purgecss: 2.3.0 + '@google-cloud/precise-date@4.0.0': {} + + '@grpc/grpc-js@1.13.4': + dependencies: + '@grpc/proto-loader': 0.7.15 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.3 + yargs: 17.7.2 + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -9755,6 +10159,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@js-sdsl/ordered-map@4.4.2': {} + + '@jsonhero/path@1.0.21': {} + '@jspm/core@2.1.0': {} '@manypkg/find-root@1.1.0': @@ -9914,8 +10322,171 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@opentelemetry/api-logs@0.52.1': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api@1.9.0': {} + '@opentelemetry/context-async-hooks@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/exporter-logs-otlp-http@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-grpc@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.13.4 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-zipkin@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 7.5.2 + semver: 7.7.2 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.13.4 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + protobufjs: 7.5.3 + + '@opentelemetry/propagator-b3@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/resources@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/sdk-logs@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + lodash.merge: 4.6.2 + + '@opentelemetry/sdk-node@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/sdk-trace-node@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + semver: 7.7.2 + + '@opentelemetry/semantic-conventions@1.25.1': {} + '@oslojs/asn1@1.0.0': dependencies: '@oslojs/binary': 1.0.0 @@ -9948,6 +10519,29 @@ snapshots: '@prisma/engines@5.4.1': {} + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + '@radix-ui/colors@1.0.1': {} '@radix-ui/number@1.1.1': {} @@ -11703,6 +12297,73 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 + '@trigger.dev/build@3.3.17(typescript@5.8.3)': + dependencies: + '@trigger.dev/core': 3.3.17 + pkg-types: 1.3.1 + tinyglobby: 0.2.14 + tsconfck: 3.1.3(typescript@5.8.3) + transitivePeerDependencies: + - bufferutil + - supports-color + - typescript + - utf-8-validate + + '@trigger.dev/core@3.3.17': + dependencies: + '@electric-sql/client': 1.0.0-beta.1 + '@google-cloud/precise-date': 4.0.0 + '@jsonhero/path': 1.0.21 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/exporter-logs-otlp-http': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + dequal: 2.0.3 + eventsource: 3.0.7 + eventsource-parser: 3.0.3 + execa: 8.0.1 + humanize-duration: 3.33.0 + jose: 5.10.0 + nanoid: 3.3.8 + socket.io-client: 4.7.5 + superjson: 2.2.2 + zod: 3.23.8 + zod-error: 1.5.0 + zod-validation-error: 1.5.0(zod@3.23.8) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@trigger.dev/sdk@3.3.17(zod@3.23.8)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/semantic-conventions': 1.25.1 + '@trigger.dev/core': 3.3.17 + chalk: 5.4.1 + cronstrue: 2.59.0 + debug: 4.4.1 + evt: 2.5.9 + slug: 6.1.0 + terminal-link: 3.0.0 + ulid: 2.4.0 + uncrypto: 0.1.3 + uuid: 9.0.1 + ws: 8.17.1 + zod: 3.23.8 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 @@ -11974,6 +12635,8 @@ snapshots: '@types/node': 24.0.0 '@types/send': 0.17.5 + '@types/shimmer@1.2.0': {} + '@types/unist@2.0.11': {} '@types/webpack@5.28.5(@swc/core@1.3.101(@swc/helpers@0.5.2))(esbuild@0.19.11)': @@ -12366,6 +13029,10 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -12424,6 +13091,10 @@ snapshots: ansi-colors@4.1.3: {} + ansi-escapes@5.0.0: + dependencies: + type-fest: 1.4.0 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -12802,6 +13473,8 @@ snapshots: ci-info@3.9.0: {} + cjs-module-lexer@1.4.3: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -12919,6 +13592,10 @@ snapshots: cookie@0.7.2: {} + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + core-js@3.43.0: {} core-util-is@1.0.3: {} @@ -12941,6 +13618,8 @@ snapshots: dependencies: luxon: 3.6.1 + cronstrue@2.59.0: {} + cross-env@7.0.3: dependencies: cross-spawn: 7.0.6 @@ -14015,6 +14694,18 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.3: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.3 + + evt@2.5.9: + dependencies: + minimal-polyfills: 2.2.3 + run-exclusive: 2.2.19 + tsafe: 1.8.5 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -14027,6 +14718,18 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + exit-hook@2.2.1: {} express@4.21.2: @@ -14259,6 +14962,8 @@ snapshots: get-stream@6.0.1: {} + get-stream@8.0.1: {} + get-symbol-description@1.1.0: dependencies: call-bound: 1.0.4 @@ -14344,6 +15049,32 @@ snapshots: graphemer@1.4.0: {} + graphology-layout-force@0.2.4(graphology-types@0.24.8): + dependencies: + graphology-types: 0.24.8 + graphology-utils: 2.5.2(graphology-types@0.24.8) + + graphology-layout-forceatlas2@0.10.1(graphology-types@0.24.8): + dependencies: + graphology-types: 0.24.8 + graphology-utils: 2.5.2(graphology-types@0.24.8) + + graphology-layout-noverlap@0.4.2(graphology-types@0.24.8): + dependencies: + graphology-types: 0.24.8 + graphology-utils: 2.5.2(graphology-types@0.24.8) + + graphology-types@0.24.8: {} + + graphology-utils@2.5.2(graphology-types@0.24.8): + dependencies: + graphology-types: 0.24.8 + + graphology@0.26.0(graphology-types@0.24.8): + dependencies: + events: 3.3.0 + graphology-types: 0.24.8 + gunzip-maybe@1.4.2: dependencies: browserify-zlib: 0.1.4 @@ -14436,6 +15167,10 @@ snapshots: human-signals@2.1.0: {} + human-signals@5.0.0: {} + + humanize-duration@3.33.0: {} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -14457,6 +15192,13 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@1.14.2: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -14633,6 +15375,8 @@ snapshots: is-stream@2.0.1: {} + is-stream@3.0.0: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -14665,6 +15409,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-what@4.1.16: {} + is-windows@1.0.2: {} isarray@1.0.0: {} @@ -14909,6 +15655,8 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + long@5.3.2: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -15324,10 +16072,14 @@ snapshots: mimic-fn@2.1.0: {} + mimic-fn@4.0.0: {} + min-indent@1.0.1: {} mini-svg-data-uri@1.4.4: {} + minimal-polyfills@2.2.3: {} + minimatch@10.0.2: dependencies: brace-expansion: 4.0.1 @@ -15402,6 +16154,8 @@ snapshots: modern-ahocorasick@1.1.0: {} + module-details-from-path@1.0.4: {} + morgan@1.10.0: dependencies: basic-auth: 2.0.1 @@ -15581,6 +16335,10 @@ snapshots: dependencies: path-key: 3.1.1 + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + num2fraction@1.2.2: {} object-assign@4.1.1: {} @@ -15660,6 +16418,10 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -15770,6 +16532,8 @@ snapshots: path-key@3.1.1: {} + path-key@4.0.0: {} + path-parse@1.0.7: {} path-scurry@1.11.1: @@ -16080,6 +16844,21 @@ snapshots: proto-list@1.2.4: {} + protobufjs@7.5.3: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 24.0.0 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -16463,6 +17242,14 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@7.5.2: + dependencies: + debug: 4.4.1 + module-details-from-path: 1.0.4 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + require-like@0.1.2: {} require-main-filename@2.0.0: {} @@ -16542,6 +17329,10 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.43.0 fsevents: 2.3.3 + run-exclusive@2.2.19: + dependencies: + minimal-polyfills: 2.2.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -16677,6 +17468,8 @@ snapshots: shell-quote@1.8.3: {} + shimmer@1.2.1: {} + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -16705,6 +17498,13 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + sigma@3.0.2(graphology-types@0.24.8): + dependencies: + events: 3.3.0 + graphology-utils: 2.5.2(graphology-types@0.24.8) + transitivePeerDependencies: + - graphology-types + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -16715,6 +17515,8 @@ snapshots: slash@3.0.0: {} + slug@6.1.0: {} + smartwrap@2.0.2: dependencies: array.prototype.flat: 1.3.3 @@ -16744,6 +17546,17 @@ snapshots: - supports-color - utf-8-validate + socket.io-client@4.7.5: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-client: 6.5.4 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 @@ -16931,6 +17744,8 @@ snapshots: strip-final-newline@2.0.0: {} + strip-final-newline@3.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -16960,6 +17775,10 @@ snapshots: pirates: 4.0.7 ts-interface-checker: 0.1.13 + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -16976,6 +17795,11 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-hyperlinks@2.3.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + supports-preserve-symlinks-flag@1.0.0: {} swr@2.3.3(react@18.3.1): @@ -17102,6 +17926,11 @@ snapshots: term-size@2.2.1: {} + terminal-link@3.0.0: + dependencies: + ansi-escapes: 5.0.0 + supports-hyperlinks: 2.3.0 + terser-webpack-plugin@5.3.14(@swc/core@1.3.101(@swc/helpers@0.5.2))(esbuild@0.19.11)(webpack@5.99.9): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -17183,6 +18012,12 @@ snapshots: ts-interface-checker@0.1.13: {} + tsafe@1.8.5: {} + + tsconfck@3.1.3(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + tsconfck@3.1.6(typescript@5.8.3): optionalDependencies: typescript: 5.8.3 @@ -17262,6 +18097,8 @@ snapshots: type-fest@0.8.1: {} + type-fest@1.4.0: {} + type-fest@4.41.0: {} type-is@1.6.18: @@ -17310,6 +18147,8 @@ snapshots: ufo@1.6.1: {} + ulid@2.4.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -17317,6 +18156,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + uncrypto@0.1.3: {} + undici-types@7.8.0: {} undici@6.21.3: {} diff --git a/trigger/.env.example b/trigger/.env.example new file mode 100644 index 0000000..67afd24 --- /dev/null +++ b/trigger/.env.example @@ -0,0 +1,138 @@ +# Trigger.dev self-hosting environment variables +# - These are the default values for the self-hosting stack +# - You should change them to suit your needs, especially the secrets +# - See the docs for more information: https://trigger.dev/docs/self-hosting/overview + +# Secrets +# - Do NOT use these defaults in production +# - Generate your own by running `openssl rand -hex 16` for each secret +SESSION_SECRET=2818143646516f6fffd707b36f334bbb +MAGIC_LINK_SECRET=44da78b7bbb0dfe709cf38931d25dcdd +ENCRYPTION_KEY=f686147ab967943ebbe9ed3b496e465a +MANAGED_WORKER_SECRET=447c29678f9eaf289e9c4b70d3dd8a7f + +# Worker token +# - This is the token for the worker to connect to the webapp +# - When running the combined stack, this is set automatically during bootstrap +# - For the split setup, you will have to set this manually. The token is available in the webapp logs but will only be shown once. +# - See the docs for more information: https://trigger.dev/docs/self-hosting/docker +# TRIGGER_WORKER_TOKEN= + +# Worker URLs +# - In split setups, uncomment and set to the public URL of your webapp +# TRIGGER_API_URL=https://trigger.example.com +# OTEL_EXPORTER_OTLP_ENDPOINT=https://trigger.example.com/otel + +# Postgres +# - Do NOT use these defaults in production +# - Especially if you decide to expose the database to the internet +# POSTGRES_USER=postgres +POSTGRES_USER=docker +POSTGRES_PASSWORD=docker +TRIGGER_DB=trigger + +DB_HOST=localhost +DB_PORT=5432 +DB_SCHEMA=sigma + + +# POSTGRES_DB=postgres +DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:${DB_PORT}/${TRIGGER_DB}?schema=public&sslmode=disable +DIRECT_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:${DB_PORT}/${TRIGGER_DB}?schema=public&sslmode=disable + +# Trigger image tag +# - This is the version of the webapp and worker images to use, they should be locked to a specific version in production +# - For example: TRIGGER_IMAGE_TAG=v4.0.0-v4-beta.21 +TRIGGER_IMAGE_TAG=v4-beta + +# Webapp +# - These should generally be set to the same value +# - In production, these should be set to the public URL of your webapp, e.g. https://trigger.example.com +APP_ORIGIN=http://localhost:8030 +LOGIN_ORIGIN=http://localhost:8030 +API_ORIGIN=http://localhost:8030 +DEV_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8030/otel +# You may need to set this when testing locally or when using the combined setup +# API_ORIGIN=http://webapp:3000 + +# Webapp - memory management +# - This sets the maximum memory allocation for Node.js heap in MiB (e.g. "4096" for 4GB) +# - It should be set according to your total webapp machine's memory or any container limits you have set +# - Setting this too high or low WILL cause crashes, inefficient memory utilization and high CPU usage +# - You should allow for some memory overhead, we suggest at least 20%, for example: +# - 2GB machine: NODE_MAX_OLD_SPACE_SIZE=1600 +# - 4GB machine: NODE_MAX_OLD_SPACE_SIZE=3200 +# - 6GB machine: NODE_MAX_OLD_SPACE_SIZE=4800 +# - 8GB machine: NODE_MAX_OLD_SPACE_SIZE=6400 +# NODE_MAX_OLD_SPACE_SIZE=8192 + +# ClickHouse +# - Do NOT use these defaults in production +CLICKHOUSE_USER=default +CLICKHOUSE_PASSWORD=password +CLICKHOUSE_URL=http://default:password@clickhouse:8123?secure=false +RUN_REPLICATION_CLICKHOUSE_URL=http://default:password@clickhouse:8123 + +# Docker Registry +# - When testing locally, the default values should be fine +# - When deploying to production, you will have to change these, especially the password and URL +# - See the docs for more information: https://trigger.dev/docs/self-hosting/docker#registry-setup +DOCKER_REGISTRY_URL=localhost:5000 +DOCKER_REGISTRY_USERNAME=registry-user +DOCKER_REGISTRY_PASSWORD=very-secure-indeed + +# Object store +# - You need to log into the Minio dashboard and create a bucket called "packets" +# - See the docs for more information: https://trigger.dev/docs/self-hosting/docker#object-storage +OBJECT_STORE_ACCESS_KEY_ID=admin +OBJECT_STORE_SECRET_ACCESS_KEY=very-safe-password +# You will have to uncomment and configure this for production +# OBJECT_STORE_BASE_URL=http://localhost:9000 +# Credentials to access the Minio dashboard at http://localhost:9001 +# - You should change these credentials and not use them for the `OBJECT_STORE_` env vars above +# - Instead, setup a non-root user with access the "packets" bucket +# MINIO_ROOT_USER=admin +# MINIO_ROOT_PASSWORD=very-safe-password + +# Other image tags +# - These are the versions of the other images to use +# - You should lock these to a specific version in production +# POSTGRES_IMAGE_TAG=14 +# REDIS_IMAGE_TAG=7 +# ELECTRIC_IMAGE_TAG=1.0.13 +# CLICKHOUSE_IMAGE_TAG=latest +# REGISTRY_IMAGE_TAG=2 +# MINIO_IMAGE_TAG=latest +# DOCKER_PROXY_IMAGE_TAG=latest +# TRAEFIK_IMAGE_TAG=v3.4 + +# Publish IPs +# - These are the IPs to publish the services to +# - Setting to 127.0.0.1 makes the service only accessible locally +# - When deploying to production, you will have to change these, depending on your setup +# WEBAPP_PUBLISH_IP=0.0.0.0 +# POSTGRES_PUBLISH_IP=127.0.0.1 +# REDIS_PUBLISH_IP=127.0.0.1 +# ELECTRIC_PUBLISH_IP=127.0.0.1 +# CLICKHOUSE_PUBLISH_IP=127.0.0.1 +# REGISTRY_PUBLISH_IP=127.0.0.1 +# MINIO_PUBLISH_IP=127.0.0.1 + +# Restart policy +# - Applies to all services, adjust as needed +# RESTART_POLICY=unless-stopped + +# Docker logging +# - See the official docs: https://docs.docker.com/engine/logging/configure/ +# LOGGING_DRIVER=local +# LOGGING_MAX_SIZE=20m +# LOGGING_MAX_FILES=5 +# LOGGING_COMPRESS=true + +# Traefik +# - Reverse proxy settings only serve as an example and require further configuration +# - See the partial overrides in docker-compose.traefik.yml for more details +# TRAEFIK_ENTRYPOINT=websecure +# TRAEFIK_HTTP_PUBLISH_IP=0.0.0.0 +# TRAEFIK_HTTPS_PUBLISH_IP=0.0.0.0 +# TRAEFIK_DASHBOARD_PUBLISH_IP=127.0.0.1 \ No newline at end of file diff --git a/trigger/docker-compose.yaml b/trigger/docker-compose.yaml new file mode 100644 index 0000000..9419488 --- /dev/null +++ b/trigger/docker-compose.yaml @@ -0,0 +1,245 @@ +x-logging: &logging-config + driver: ${LOGGING_DRIVER:-local} + options: + max-size: ${LOGGING_MAX_SIZE:-20m} + max-file: ${LOGGING_MAX_FILES:-5} + compress: ${LOGGING_COMPRESS:-true} + +services: + webapp: + image: ghcr.io/triggerdotdev/trigger.dev:${TRIGGER_IMAGE_TAG:-v4-beta} + restart: ${RESTART_POLICY:-unless-stopped} + logging: *logging-config + ports: + - ${WEBAPP_PUBLISH_IP:-0.0.0.0}:8030:3000 + depends_on: + - clickhouse + networks: + - webapp + - supervisor + volumes: + - shared:/home/node/shared + # Only needed for bootstrap + user: root + # Only needed for bootstrap + command: sh -c "chown -R node:node /home/node/shared && exec ./scripts/entrypoint.sh" + healthcheck: + test: ["CMD", "node", "-e", "http.get('http://localhost:3000/healthcheck', res => process.exit(res.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 10s + environment: + APP_ORIGIN: ${APP_ORIGIN:-http://localhost:8030} + LOGIN_ORIGIN: ${LOGIN_ORIGIN:-http://localhost:8030} + API_ORIGIN: ${API_ORIGIN:-http://localhost:8030} + ELECTRIC_ORIGIN: http://electric:3000 + DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable} + DIRECT_URL: ${DIRECT_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable} + SESSION_SECRET: ${SESSION_SECRET} + MAGIC_LINK_SECRET: ${MAGIC_LINK_SECRET} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} + MANAGED_WORKER_SECRET: ${MANAGED_WORKER_SECRET} + REDIS_HOST: host.docker.internal + REDIS_PORT: 6379 + REDIS_TLS_DISABLED: true + APP_LOG_LEVEL: info + DEV_OTEL_EXPORTER_OTLP_ENDPOINT: ${DEV_OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:8030/otel} + DEPLOY_REGISTRY_HOST: ${DOCKER_REGISTRY_URL:-localhost:5000} + OBJECT_STORE_BASE_URL: ${OBJECT_STORE_BASE_URL:-http://minio:9000} + OBJECT_STORE_ACCESS_KEY_ID: ${OBJECT_STORE_ACCESS_KEY_ID} + OBJECT_STORE_SECRET_ACCESS_KEY: ${OBJECT_STORE_SECRET_ACCESS_KEY} + GRACEFUL_SHUTDOWN_TIMEOUT: 1000 + # Bootstrap - this will automatically set up a worker group for you + # This will NOT work for split deployments + TRIGGER_BOOTSTRAP_ENABLED: 1 + TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME: bootstrap + TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH: /home/node/shared/worker_token + # ClickHouse configuration + CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://default:password@clickhouse:8123?secure=false} + CLICKHOUSE_LOG_LEVEL: ${CLICKHOUSE_LOG_LEVEL:-info} + # Run replication + RUN_REPLICATION_ENABLED: ${RUN_REPLICATION_ENABLED:-1} + RUN_REPLICATION_CLICKHOUSE_URL: ${RUN_REPLICATION_CLICKHOUSE_URL:-http://default:password@clickhouse:8123} + RUN_REPLICATION_LOG_LEVEL: ${RUN_REPLICATION_LOG_LEVEL:-info} + # Limits + # TASK_PAYLOAD_OFFLOAD_THRESHOLD: 524288 # 512KB + # TASK_PAYLOAD_MAXIMUM_SIZE: 3145728 # 3MB + # BATCH_TASK_PAYLOAD_MAXIMUM_SIZE: 1000000 # 1MB + # TASK_RUN_METADATA_MAXIMUM_SIZE: 262144 # 256KB + # DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT: 100 + # DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT: 100 + # Internal OTEL configuration + INTERNAL_OTEL_TRACE_LOGGING_ENABLED: ${INTERNAL_OTEL_TRACE_LOGGING_ENABLED:-0} + + electric: + image: electricsql/electric:${ELECTRIC_IMAGE_TAG:-1.0.13} + restart: ${RESTART_POLICY:-unless-stopped} + logging: *logging-config + depends_on: + - postgres + networks: + - webapp + environment: + DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable} + ELECTRIC_INSECURE: true + ELECTRIC_USAGE_REPORTING: false + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/v1/health"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + + clickhouse: + image: bitnami/clickhouse:${CLICKHOUSE_IMAGE_TAG:-latest} + restart: ${RESTART_POLICY:-unless-stopped} + logging: *logging-config + ports: + - ${CLICKHOUSE_PUBLISH_IP:-127.0.0.1}:9123:8123 + - ${CLICKHOUSE_PUBLISH_IP:-127.0.0.1}:9090:9000 + environment: + CLICKHOUSE_ADMIN_USER: ${CLICKHOUSE_USER:-default} + CLICKHOUSE_ADMIN_PASSWORD: ${CLICKHOUSE_PASSWORD:-password} + volumes: + - clickhouse:/bitnami/clickhouse + - ../clickhouse/override.xml:/bitnami/clickhouse/etc/config.d/override.xml:ro + networks: + - webapp + healthcheck: + test: ["CMD", "clickhouse-client", "--host", "localhost", "--port", "9000", "--user", "default", "--password", "password", "--query", "SELECT 1"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s + + registry: + image: registry:${REGISTRY_IMAGE_TAG:-2} + restart: ${RESTART_POLICY:-unless-stopped} + logging: *logging-config + ports: + - ${REGISTRY_PUBLISH_IP:-127.0.0.1}:5000:5000 + networks: + - webapp + volumes: + # registry-user:very-secure-indeed + - ../registry/auth.htpasswd:/auth/htpasswd:ro + environment: + REGISTRY_AUTH: htpasswd + REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm + REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:5000/"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + + minio: + image: bitnami/minio:${MINIO_IMAGE_TAG:-latest} + restart: ${RESTART_POLICY:-unless-stopped} + logging: *logging-config + ports: + - ${MINIO_PUBLISH_IP:-127.0.0.1}:9000:9000 + - ${MINIO_PUBLISH_IP:-127.0.0.1}:9001:9001 + networks: + - webapp + volumes: + - minio:/bitnami/minio/data + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER:-admin} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-very-safe-password} + MINIO_DEFAULT_BUCKETS: packets + MINIO_BROWSER: "on" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 5s + timeout: 10s + retries: 5 + start_period: 10s + + # Worker related + supervisor: + image: ghcr.io/triggerdotdev/supervisor:${TRIGGER_IMAGE_TAG:-v4-beta} + restart: ${RESTART_POLICY:-unless-stopped} + logging: *logging-config + depends_on: + - docker-proxy + networks: + - supervisor + - docker-proxy + - webapp + volumes: + - shared:/home/node/shared + # Only needed for bootstrap + user: root + # Only needed for bootstrap + command: sh -c "chown -R node:node /home/node/shared && exec /usr/bin/dumb-init -- pnpm run --filter supervisor start" + environment: + # This needs to match the token of the worker group you want to connect to + # TRIGGER_WORKER_TOKEN: ${TRIGGER_WORKER_TOKEN} + # Use the bootstrap token created by the webapp + TRIGGER_WORKER_TOKEN: file:///home/node/shared/worker_token + MANAGED_WORKER_SECRET: ${MANAGED_WORKER_SECRET} + TRIGGER_API_URL: ${TRIGGER_API_URL:-http://webapp:3000} + OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://webapp:3000/otel} + TRIGGER_WORKLOAD_API_DOMAIN: supervisor + TRIGGER_WORKLOAD_API_PORT_EXTERNAL: 8020 + # Optional settings + DEBUG: 1 + ENFORCE_MACHINE_PRESETS: 1 + TRIGGER_DEQUEUE_INTERVAL_MS: 1000 + DOCKER_HOST: tcp://docker-proxy:2375 + DOCKER_RUNNER_NETWORKS: webapp,supervisor + DOCKER_REGISTRY_URL: ${DOCKER_REGISTRY_URL:-localhost:5000} + DOCKER_REGISTRY_USERNAME: ${DOCKER_REGISTRY_USERNAME:-} + DOCKER_REGISTRY_PASSWORD: ${DOCKER_REGISTRY_PASSWORD:-} + DOCKER_AUTOREMOVE_EXITED_CONTAINERS: 0 + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "http.get('http://localhost:8020/health', res => process.exit(res.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))", + ] + interval: 30s + timeout: 10s + retries: 5 + start_period: 10s + + docker-proxy: + image: tecnativa/docker-socket-proxy:${DOCKER_PROXY_IMAGE_TAG:-latest} + restart: ${RESTART_POLICY:-unless-stopped} + logging: *logging-config + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - docker-proxy + environment: + - LOG_LEVEL=info + - POST=1 + - CONTAINERS=1 + - IMAGES=1 + - INFO=1 + - NETWORKS=1 + healthcheck: + test: ["CMD", "nc", "-z", "127.0.0.1", "2375"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 5s + +volumes: + shared: + clickhouse: + shared: + minio: + +networks: + docker-proxy: + name: docker-proxy + supervisor: + name: supervisor + webapp: + name: webapp diff --git a/turbo.json b/turbo.json index 932032e..b34e407 100644 --- a/turbo.json +++ b/turbo.json @@ -60,6 +60,9 @@ "MAGIC_LINK_SECRET", "ENABLE_EMAIL_LOGIN", "MODEL", - "OLLAMA_URL" + "OLLAMA_URL", + "TRIGGER_PROJECT_ID", + "TRIGGER_API_URL", + "TRIGGER_API_KEY" ] }