Files
Synapse/src/main/frontend/worker/handler/render_resource/engine.cljs
Dasemu d8996bd5e1 Fase B8+B9: stateful code kernel and live notes bound to external URLs
B8 - executable code blocks with a real kernel:
- worker/handler/kernel.cljs: one persistent SCI interpreter session per
  page, reused across block evaluations so defs/state survive between runs.
  Deliberately scoped to the same privileges the existing client-side
  single-shot evaluator already had (no shell/fs access, no call-api).
- Run button + result display wired into block.cljs's code-block UI.
- New :logseq.property.code/result and /executed-at properties (65.38).

B9 - live notes: pages tagged #LiveNote that fetch external content on a
refresh schedule and render it via the render-resource pipeline:
- worker/handler/live_notes.cljs: per-block credential storage, scheduled
  refresh (Node-only), thread-apis for manual refresh and auth.
- worker/handler/render_resource/live_note.cljs: reads back fetched content,
  watching [:entity block-uuid] for reactive invalidation.
- New :logseq.class/LiveNote + :logseq.property.livenote/* properties (65.39).

Bugs found and fixed along the way while actually exercising page creation
through page-handler/create! for the first time in a test (previously only
pure-fn tests existed for CalDAV/Event/Contact):
- logseq-property-namespaces (property.cljs) was missing the citation/event/
  contact/caldav/livenote namespaces, causing auto-create-ontology to treat
  already-built-in properties as new user idents.
- outliner/page.cljs's build-page-tx always added the generic #Page tag even
  when the caller tagged with a built-in class that extends #Page, which
  malli-schema rejects as conflicting; it also lost the property type when
  building ref-typed property values for new pages, breaking non-:default
  types like :number.
- entity-util/page? never recognized Event/Contact/LiveNote as pages (only
  Journal/Whiteboard/Tag/Property/Page had dedicated recognition), so pages
  tagged with those classes alone weren't treated as pages anywhere in the
  app; added built-in-page-child? alongside the existing per-class checks.
- caldav.cljs's upsert-page! passed a bare keyword as a tag where an entity
  was required, and two error logs passed raw error objects to glogi that
  triggered runaway recursion in this test environment; both fixed.
2026-08-26 22:41:23 +02:00

167 lines
6.8 KiB
Clojure

(ns frontend.worker.handler.render-resource.engine
"Renderer resource registry, batching, and thread API."
(:require [frontend.common.thread-api :refer [def-thread-api]]
[frontend.worker.handler.block :as block-handler]
[frontend.worker.handler.render-resource.basic :as basic]
[frontend.worker.handler.render-resource.common :as common]
[frontend.worker.handler.render-resource.live-note :as live-note]
[frontend.worker.handler.render-resource.property :as property]
[frontend.worker.handler.render-resource.query :as query]
[frontend.worker.handler.render-resource.similar :as similar]
[frontend.worker.handler.render-resource.view :as view]
[frontend.worker.state :as worker-state]))
(def resource-renderers
(merge basic/resource-renderers
property/resource-renderers
view/resource-renderers
query/resource-renderers
similar/resource-renderers
live-note/resource-renderers
{:block-sync-conflicts
(common/renderer nil
(fn [_db resource-key _runtime]
(common/fail!
"Renderer resource belongs to a non-DB provider"
{:provider :sync-state
:resource-key resource-key})))}))
(defn- resource-value
[db resource-key runtime]
(when-not (and (vector? resource-key) (seq resource-key))
(common/fail! "Invalid renderer resource key"
{:resource-key resource-key}))
(case (common/invalid-resource-key-value resource-key)
:function
(common/fail! "Renderer resource keys cannot contain functions"
{:resource-key resource-key})
:entity
(common/fail! "Renderer resource keys cannot contain graph entities"
{:resource-key resource-key})
(let [resource-kind (first resource-key)]
(if-let [{:keys [shape render]} (get resource-renderers resource-kind)]
(do
(when shape
(common/require-shape! resource-key resource-kind shape))
(render db resource-key runtime))
(common/fail! "Unknown renderer resource key"
{:resource-key resource-key})))))
(defn- resource-entry
[db resource-key runtime]
(let [[watch value slots] (resource-value db resource-key runtime)
watch-all? (= common/watch-all watch)
watch-keys (if watch-all? #{} watch)]
(when-not (set? watch-keys)
(common/fail! "Invalid renderer resource watch keys"
{:resource-key resource-key :watch watch}))
{:watch-keys watch-keys
:watch-all? watch-all?
:value value
:slots (or slots {})}))
(def ^:private snapshot-request-limits
{:blocks 1000 :children 25 :resources 25})
(defn- require-snapshot-request!
[request]
(when-not (and (map? request)
(= #{:blocks :children :resources} (set (keys request)))
(some seq (vals request))
(every? (fn [[kind values]]
(and (vector? values)
(<= (count values)
(get snapshot-request-limits kind))
(= values (vec (distinct values)))))
request))
(common/fail! "Invalid renderer snapshot request"
{:request request :limits snapshot-request-limits}))
request)
(defn- merge-slots
[left right]
(reduce-kv
(fn [slots key value]
(when-let [existing (get slots key)]
(when-not (= existing value)
(common/fail! "Conflicting renderer snapshot slots"
{:slot-key key})))
(assoc slots key value))
left
right))
(defn- block-snapshot-slots
[db block-uuids]
(let [{:keys [blocks groups]}
(block-handler/canonical-blocks db block-uuids)]
{:slots (reduce (fn [slots block-uuid]
(if (contains? blocks block-uuid)
slots
(assoc slots [:block block-uuid] {:missing? true})))
(common/block-slots blocks)
block-uuids)
:groups (into {}
(map (fn [block-uuid]
[[:block block-uuid]
(if-let [dependency-uuids (get groups block-uuid)]
(into #{} (map #(vector :block %)) dependency-uuids)
#{[:block block-uuid]})]))
block-uuids)}))
(defn- children-snapshot-groups
[db parent-uuids]
(into {}
(map (fn [parent-uuid]
(let [tree (block-handler/open-block-tree db parent-uuid)]
[[:children parent-uuid]
(common/block-bundle-slots tree)])))
parent-uuids))
(defn render-snapshots
[db {:keys [blocks children resources] :as request} runtime]
(require-snapshot-request! request)
(let [resource-entries (into {}
(map (fn [resource-key]
[resource-key
(resource-entry db resource-key runtime)]))
resources)
{block-slots :slots block-groups :groups}
(block-snapshot-slots db blocks)
children-groups (children-snapshot-groups db children)
base-slots (reduce merge-slots block-slots (vals children-groups))
slots (reduce-kv
(fn [slots resource-key entry]
(-> slots
(merge-slots (:slots entry))
(merge-slots
{[:resource resource-key]
{:watch {:keys (:watch-keys entry)
:all? (:watch-all? entry)}
:value (:value entry)}})))
base-slots
resource-entries)
groups (into {}
(concat
block-groups
(map (fn [parent-uuid]
[[:children parent-uuid]
(set (keys (get children-groups
[:children parent-uuid])))])
children)
(map (fn [[resource-key entry]]
[[:resource resource-key]
(conj (set (keys (:slots entry)))
[:resource resource-key])])
resource-entries)))]
{:basis-rev (common/basis-rev db)
:slots slots
:groups groups}))
(def-thread-api :thread-api/get-render-snapshots
[repo request]
(if-let [conn (worker-state/get-datascript-conn repo)]
(render-snapshots @conn request {:repo repo})
(common/fail! "Missing renderer snapshot database" {:repo repo})))