penpot/backend/src/app/graph/ingest.clj
Andrey Antukh f38c7dd639
⬆️ Update deps (#11960)
* 🐛 Migrate openUIApi schema to Zod v4 function syntax

Zod 4 removed z.function().args(), which broke the
plugins-runtime build with implicit-any errors on every
openUIApi parameter and knock-on possibly-null errors on
the modal in plugin-manager.

Declare the inputs with z.function({ input: [...] }) so the
parameter and return types infer again; behavior is unchanged.

Add a regression spec covering delegation, optional args and
rejection of invalid theme and title values.

AI-assisted-by: muse-spark-1.3-contributor

* 📚 Fix deprecated markdown-it-anchor permalink option in docs

Migrate docs Eleventy config to the markdown-it-anchor v10 API.
Replace the deprecated boolean permalink option with
linkInsideHeader, keeping the same symbol and class.
Bump markdown-it-anchor to v10 and related docs deps.

AI-assisted-by: muse-spark-1.3-contributor

* ⬆️ Update deps

* ⬆️ Update base docker images

* 📎 Fix mcp tests
2026-09-29 09:07:34 +02:00

109 lines
4.5 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns app.graph.ingest
"Penpot file -> Ladybug graph projection."
(:require
[app.binfile.common :as bfc]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.types.file :as ctf]
[app.db :as db]
[app.graph.arrow :as graph.arrow]
[app.graph.ladybug :as ladybug]
[app.graph.meta :as graph.meta]
[app.graph.projection.document :as projection.document]
[app.graph.projection.transforms :as projection.transforms]
[app.graph.schema :as schema]
[app.graph.stats :as stats]
[app.srepl.helpers :as h])
(:import
com.ladybugdb.Connection
org.apache.arrow.memory.BufferAllocator))
(defn- fetch-file!
[system file-id]
(let [file-id (h/parse-uuid file-id)
file (db/run! system #(bfc/get-file % file-id :realize? true))]
(when-not file
(ex/raise :type :not-found
:code :file-not-found
:file-id (str file-id)))
(when-not (:data file)
(ex/raise :type :validation
:code :file-without-data
:hint "file has no data to project"
:file-id (str file-id)))
[file-id file]))
(defn- ingest-on-connection*!
[system ^Connection conn file-id ^BufferAllocator allocator
{:keys [db-path skip-stats? skip-validation?] :or {skip-stats? true}}]
(let [[file-id file] (fetch-file! system file-id)
db-path (or db-path (ladybug/db-path-for-file file-id))
data (:data file)]
(when-not skip-validation?
(ctf/check-file-data data))
(l/inf :hint "graph ingest"
:file-id (str file-id)
:revn (:revn file)
:db-path db-path
:schema schema/schema-version)
(let [ddl (schema/ddl-statements)
{:keys [nodes edges stats]}
(projection.document/projection-data data file)]
(ladybug/exec-on-connection! conn ddl)
(graph.arrow/load-projection! conn {:nodes nodes :edges edges} allocator)
(ladybug/exec-on-connection! conn ["CHECKPOINT;"])
(let [transforms (projection.transforms/apply-transforms! system conn data file)]
;; Written last: its presence doubles as the build-complete marker.
(graph.meta/write! conn {:file-id file-id
:revn (:revn file)})
{:file-id file-id
:revn (:revn file)
:name (or (:name data) (:name file))
:db-path db-path
:schema-version schema/schema-version
:projection {:stats stats
:nodes nodes
:edges edges}
:transforms transforms
:stats (when-not skip-stats?
(stats/summarize-connection conn))}))))
(defn ingest-on-connection!
"Project `file-id` into an already open Ladybug `conn`.
Takes `:arrow-alloc`, owned by the caller. The allocator must stay open
until the connection (and database) close — see
`app.graph.arrow/with-allocator!` — so a short-lived allocator made inside
this call would close too early and leak; the caller nests the allocator
outside its connection and passes it here."
[system ^Connection conn file-id & {:keys [arrow-alloc] :as opts}]
(when-not arrow-alloc
(ex/raise :type :validation
:code :missing-arrow-allocator
:hint "ingest-on-connection! requires :arrow-alloc; nest with-allocator! outside the connection"))
(ingest-on-connection*! system conn file-id arrow-alloc opts))
(defn ingest-file!
[system file-id & {:keys [db-path reset-db? skip-stats? skip-validation?]
:or {reset-db? true}}]
(let [db-path (or db-path (ladybug/db-path-for-file (h/parse-uuid file-id)))]
(when reset-db?
(ladybug/reset-db-path! db-path))
;; Allocator outermost: Ladybug holds the staged Arrow buffers until its
;; tables are dropped, which is no later than connection close, so the
;; allocator must be closed after the connection and the database.
(graph.arrow/with-allocator!
(fn [allocator]
(ladybug/with-connection! db-path
(fn [conn]
(ingest-on-connection*! system conn file-id allocator
{:db-path db-path
:skip-stats? skip-stats?
:skip-validation? skip-validation?})))))))