feat: cljs sdk (#12168)

Add clojurescript plugins API
This commit is contained in:
Tienson Qin
2025-10-21 16:32:12 +08:00
committed by GitHub
parent a9fc1290f2
commit d8809f0b7e
27 changed files with 2027 additions and 10 deletions

9
bb.edn
View File

@@ -10,7 +10,9 @@
logseq/graph-parser
{:local/root "deps/graph-parser"}
org.clj-commons/digest
{:mvn/version "1.4.100"}}
{:mvn/version "1.4.100"}
cheshire/cheshire
{:mvn/version "5.12.0"}}
:pods
{clj-kondo/clj-kondo {:version "2024.09.27"}
org.babashka/fswatcher {:version "0.0.3"}
@@ -19,6 +21,11 @@
{dev:desktop-watch
logseq.tasks.dev.desktop/watch
libs:generate-cljs-sdk
{:doc "Generate CLJS wrappers based on the JS SDK declarations"
:requires ([logseq.libs.sdk-generator :as sdk-gen])
:task (sdk-gen/run! (sdk-gen/parse-args *command-line-args*))}
dev:open-dev-electron-app
logseq.tasks.dev.desktop/open-dev-electron-app

View File

@@ -2,3 +2,4 @@ src/
webpack.*
.DS_Store
docs/
cljs-sdk/

View File

@@ -30,3 +30,16 @@ import "@logseq/libs"
#### Feedback
If you have any feedback or encounter any issues, feel free to join Logseq's discord group.
https://discord.gg/KpN4eHY
#### Generate CLJS SDK wrappers
To regenerate the ClojureScript facade from the JS SDK declarations (keeping the same argument shapes as the JS APIs while auto-converting to/from CLJS data):
```bash
yarn run generate:schema # emits dist/logseq-sdk-schema.json
bb libs:generate-cljs-sdk # emits logseq/core.cljs and per-proxy files under target/generated-cljs
```
Non-proxy methods (those defined on `ILSPluginUser`, e.g. `ready`, `provide-ui`) land in `logseq.core`. Each proxy (`IAppProxy`, `IEditorProxy`, ...) is emitted to its own namespace such as `logseq.app` or `logseq.editor`, preserving the original JS argument ordering while automatically bean-converting CLJS data.
Pass `--out-dir` to change the output location or `--ns-prefix` to pick a different namespace root.

38
libs/cljs-sdk/.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
# Clojure/Leiningen
pom.xml
pom.xml.asc
*.jar
*.class
/lib/
/classes/
/target/
/checkouts/
.lein-deps-sum
.lein-repl-history
.lein-plugins/
.lein-failures
.nrepl-port
# ClojureScript/Build
.cpcache/
.shadow-cljs/
dist/
out/
# Node.js
node_modules/
package-lock.json
coverage/
.nyc_output/
# IDE
.idea/
.vscode/
.calva/
.calva/output-window
.lsp/
.clj-kondo/
# OS
.DS_Store
*.log

21
libs/cljs-sdk/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Logseq
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.

31
libs/cljs-sdk/bb.edn Normal file
View File

@@ -0,0 +1,31 @@
{:paths ["src" "test"]
:deps {org.babashka/cli {:mvn/version "0.7.53"}
slipset/deps-deploy {:mvn/version "0.2.1"}}
:tasks
{:requires ([babashka.cli :as cli])
:init (do
(defn run [cmd]
(let [result (shell cmd)]
(when-not (zero? (:exit result))
(throw (ex-info "Command failed" result))))))
clean {:doc "Clean compiled files"
:task (do
(run "rm -rf dist")
(run "rm -rf .shadow-cljs")
(run "rm -rf node_modules")
(run "rm -rf out")
(run "rm -rf .nyc_output")
(run "rm -rf coverage"))}
install {:doc "Install dependencies"
:task (run "yarn install")}
build-jar {:doc "Build jar file"
:task (shell "clojure -T:build jar")}
deploy {:doc "Deploy jar to Clojars"
:task (shell "clojure -T:build deploy")}
release {:doc "Build release version and deploy to Clojars"
:depends [clean install build-jar deploy]}}}

46
libs/cljs-sdk/build.clj Normal file
View File

@@ -0,0 +1,46 @@
(ns build
(:require [clojure.data.json :as json]
[clojure.tools.build.api :as b]
[deps-deploy.deps-deploy :as dd]))
(def lib 'com.logseq/libs)
(def version
(-> (slurp "package.json")
(json/read-str :key-fn keyword)
:version))
(def class-dir "target/classes")
(def basis (delay (b/create-basis {:project "deps.edn"})))
(def jar-file (format "target/%s-%s.jar" (name lib) version))
(def pom-template
[[:description "ClojureScript wrapper for @logseq/libs"]
[:url "https://github.com/logseq/logseq"]
[:licenses
[:license
[:name "MIT License"]
[:url "https://opensource.org/licenses/MIT"]]]])
(def options
{:class-dir class-dir
:lib lib
:version version
:basis @basis
:jar-file jar-file
:src-dirs ["src"]
:pom-data pom-template})
(defn clean [_]
(b/delete {:path "target"}))
(defn jar [_]
(clean nil)
(b/write-pom options)
(b/copy-dir {:src-dirs (:paths @basis)
:target-dir class-dir})
(b/jar options))
(defn deploy [_]
(jar nil)
(dd/deploy {:installer :remote
:artifact jar-file
:pom-file (b/pom-path {:lib lib :class-dir class-dir})}))

20
libs/cljs-sdk/deps.edn Normal file
View File

@@ -0,0 +1,20 @@
{:paths ["src" "test"]
:deps {org.clojure/clojurescript {:mvn/version "1.11.60"}
cljs-bean/cljs-bean {:mvn/version "1.5.0"}}
:npm-deps {"@logseq/libs" "0.2.3"}
:aliases
{:dev
{:extra-paths ["example"]}
:test
{:extra-paths ["test"]
:extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}}
:ns-default build}
:build
{:deps {io.github.clojure/tools.build {:mvn/version "0.9.6"}
slipset/deps-deploy {:mvn/version "0.2.1"}
org.clojure/data.json {:mvn/version "2.4.0"}}
:ns-default build}}}

View File

@@ -0,0 +1,27 @@
{
"name": "@logseq/cljs-libs",
"version": "0.0.10",
"description": "Logseq plugin API wrapper",
"dependencies": {
"@logseq/libs": "^0.2.3"
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/logseq/logseq.git"
},
"keywords": [
"logseq",
"clojurescript",
"plugin",
"@logseq/libs"
],
"author": "logseq",
"license": "MIT",
"bugs": {
"url": "https://github.com/logseq/logseq/issues"
},
"homepage": "https://github.com/logseq/logseq#readme"
}

View File

@@ -0,0 +1,348 @@
;; Auto-generated via `bb libs:generate-cljs-sdk`
(ns com.logseq.app
(:require [com.logseq.core :as core]))
(def api-proxy (aget js/logseq "App"))
(defn- get-info-impl
[key]
(let [method (aget api-proxy "getInfo")
args [key]]
(core/call-method api-proxy method args)))
(defn get-info
([]
(get-info-impl nil))
([key]
(get-info-impl key)))
(defn get-user-info
[]
(let [method (aget api-proxy "getUserInfo")
args []]
(core/call-method api-proxy method args)))
(defn get-user-configs
[]
(let [method (aget api-proxy "getUserConfigs")
args []]
(core/call-method api-proxy method args)))
(defn register-search-service
[s]
(let [method (aget api-proxy "registerSearchService")
args [s]]
(core/call-method api-proxy method args)))
(defn register-command
[type opts action]
(let [method (aget api-proxy "registerCommand")
args [type opts action]]
(core/call-method api-proxy method args)))
(defn register-command-palette
[opts action]
(let [method (aget api-proxy "registerCommandPalette")
args [opts action]]
(core/call-method api-proxy method args)))
(defn- register-command-shortcut-impl
[keybinding action opts]
(let [method (aget api-proxy "registerCommandShortcut")
args [keybinding action opts]]
(core/call-method api-proxy method args)))
(defn register-command-shortcut
"Supported key names"
([keybinding action]
(register-command-shortcut-impl keybinding action nil))
([keybinding action opts]
(register-command-shortcut-impl keybinding action opts)))
(defn invoke-external-command
"Supported all registered palette commands"
[type & args]
(let [method (aget api-proxy "invokeExternalCommand")
rest-args (vec args)
args (into [type] rest-args)]
(core/call-method api-proxy method args)))
(defn invoke-external-plugin
"Call external plugin command provided by models or registered commands"
[type & args]
(let [method (aget api-proxy "invokeExternalPlugin")
rest-args (vec args)
args (into [type] rest-args)]
(core/call-method api-proxy method args)))
(defn get-external-plugin
[pid]
(let [method (aget api-proxy "getExternalPlugin")
args [pid]]
(core/call-method api-proxy method args)))
(defn get-state-from-store
"Get state from app store\nvalid state is here\nhttps://github.com/logseq/logseq/blob/master/src/main/frontend/state.cljs#L27"
[path]
(let [method (aget api-proxy "getStateFromStore")
args [path]]
(core/call-method api-proxy method args)))
(defn set-state-from-store
[path value]
(let [method (aget api-proxy "setStateFromStore")
args [path value]]
(core/call-method api-proxy method args)))
(defn relaunch
[]
(let [method (aget api-proxy "relaunch")
args []]
(core/call-method api-proxy method args)))
(defn quit
[]
(let [method (aget api-proxy "quit")
args []]
(core/call-method api-proxy method args)))
(defn open-external-link
[url]
(let [method (aget api-proxy "openExternalLink")
args [url]]
(core/call-method api-proxy method args)))
(defn exec-git-command
[args]
(let [method (aget api-proxy "execGitCommand")
args [args]]
(core/call-method api-proxy method args)))
(defn get-current-graph
[]
(let [method (aget api-proxy "getCurrentGraph")
args []]
(core/call-method api-proxy method args)))
(defn check-current-is-db-graph
[]
(let [method (aget api-proxy "checkCurrentIsDbGraph")
args []]
(core/call-method api-proxy method args)))
(defn get-current-graph-configs
[& keys]
(let [method (aget api-proxy "getCurrentGraphConfigs")
rest-keys (vec keys)
args (into [] rest-keys)]
(core/call-method api-proxy method args)))
(defn set-current-graph-configs
[configs]
(let [method (aget api-proxy "setCurrentGraphConfigs")
args [configs]]
(core/call-method api-proxy method args)))
(defn get-current-graph-favorites
[]
(let [method (aget api-proxy "getCurrentGraphFavorites")
args []]
(core/call-method api-proxy method args)))
(defn get-current-graph-recent
[]
(let [method (aget api-proxy "getCurrentGraphRecent")
args []]
(core/call-method api-proxy method args)))
(defn get-current-graph-templates
[]
(let [method (aget api-proxy "getCurrentGraphTemplates")
args []]
(core/call-method api-proxy method args)))
(defn- push-state-impl
[k params query]
(let [method (aget api-proxy "pushState")
args [k params query]]
(core/call-method api-proxy method args)))
(defn push-state
([k]
(push-state-impl k nil nil))
([k params]
(push-state-impl k params nil))
([k params query]
(push-state-impl k params query)))
(defn- replace-state-impl
[k params query]
(let [method (aget api-proxy "replaceState")
args [k params query]]
(core/call-method api-proxy method args)))
(defn replace-state
([k]
(replace-state-impl k nil nil))
([k params]
(replace-state-impl k params nil))
([k params query]
(replace-state-impl k params query)))
(defn get-template
[name]
(let [method (aget api-proxy "getTemplate")
args [name]]
(core/call-method api-proxy method args)))
(defn exist-template
[name]
(let [method (aget api-proxy "existTemplate")
args [name]]
(core/call-method api-proxy method args)))
(defn- create-template-impl
[target name opts]
(let [method (aget api-proxy "createTemplate")
args [target name opts]]
(core/call-method api-proxy method args)))
(defn create-template
([target name]
(create-template-impl target name nil))
([target name opts]
(create-template-impl target name opts)))
(defn remove-template
[name]
(let [method (aget api-proxy "removeTemplate")
args [name]]
(core/call-method api-proxy method args)))
(defn insert-template
[target name]
(let [method (aget api-proxy "insertTemplate")
args [target name]]
(core/call-method api-proxy method args)))
(defn set-zoom-factor
[factor]
(let [method (aget api-proxy "setZoomFactor")
args [factor]]
(core/call-method api-proxy method args)))
(defn set-full-screen
[flag]
(let [method (aget api-proxy "setFullScreen")
args [flag]]
(core/call-method api-proxy method args)))
(defn set-left-sidebar-visible
[flag]
(let [method (aget api-proxy "setLeftSidebarVisible")
args [flag]]
(core/call-method api-proxy method args)))
(defn set-right-sidebar-visible
[flag]
(let [method (aget api-proxy "setRightSidebarVisible")
args [flag]]
(core/call-method api-proxy method args)))
(defn- clear-right-sidebar-blocks-impl
[opts]
(let [method (aget api-proxy "clearRightSidebarBlocks")
args [opts]]
(core/call-method api-proxy method args)))
(defn clear-right-sidebar-blocks
([]
(clear-right-sidebar-blocks-impl nil))
([opts]
(clear-right-sidebar-blocks-impl opts)))
(defn register-ui-item
[type opts]
(let [method (aget api-proxy "registerUIItem")
args [type opts]]
(core/call-method api-proxy method args)))
(defn register-page-menu-item
[tag action]
(let [method (aget api-proxy "registerPageMenuItem")
args [tag action]]
(core/call-method api-proxy method args)))
(defn on-current-graph-changed
[callback]
(let [method (aget api-proxy "onCurrentGraphChanged")
args [callback]]
(core/call-method api-proxy method args)))
(defn on-graph-after-indexed
[callback]
(let [method (aget api-proxy "onGraphAfterIndexed")
args [callback]]
(core/call-method api-proxy method args)))
(defn on-theme-mode-changed
[callback]
(let [method (aget api-proxy "onThemeModeChanged")
args [callback]]
(core/call-method api-proxy method args)))
(defn on-theme-changed
[callback]
(let [method (aget api-proxy "onThemeChanged")
args [callback]]
(core/call-method api-proxy method args)))
(defn on-today-journal-created
[callback]
(let [method (aget api-proxy "onTodayJournalCreated")
args [callback]]
(core/call-method api-proxy method args)))
(defn on-before-command-invoked
[condition callback]
(let [method (aget api-proxy "onBeforeCommandInvoked")
args [condition callback]]
(core/call-method api-proxy method args)))
(defn on-after-command-invoked
[condition callback]
(let [method (aget api-proxy "onAfterCommandInvoked")
args [condition callback]]
(core/call-method api-proxy method args)))
(defn on-block-renderer-slotted
"provide ui slot to specific block with UUID"
[condition callback]
(let [method (aget api-proxy "onBlockRendererSlotted")
args [condition callback]]
(core/call-method api-proxy method args)))
(defn on-macro-renderer-slotted
"provide ui slot to block `renderer` macro for `{{renderer arg1, arg2}}`"
[callback]
(let [method (aget api-proxy "onMacroRendererSlotted")
args [callback]]
(core/call-method api-proxy method args)))
(defn on-page-head-actions-slotted
[callback]
(let [method (aget api-proxy "onPageHeadActionsSlotted")
args [callback]]
(core/call-method api-proxy method args)))
(defn on-route-changed
[callback]
(let [method (aget api-proxy "onRouteChanged")
args [callback]]
(core/call-method api-proxy method args)))
(defn on-sidebar-visible-changed
[callback]
(let [method (aget api-proxy "onSidebarVisibleChanged")
args [callback]]
(core/call-method api-proxy method args)))

View File

@@ -0,0 +1,37 @@
;; Auto-generated via `bb libs:generate-cljs-sdk`
(ns com.logseq.assets
(:require [com.logseq.core :as core]))
(def api-proxy (aget js/logseq "Assets"))
(defn- list-files-of-current-graph-impl
[exts]
(let [method (aget api-proxy "listFilesOfCurrentGraph")
args [exts]]
(core/call-method api-proxy method args)))
(defn list-files-of-current-graph
([]
(list-files-of-current-graph-impl nil))
([exts]
(list-files-of-current-graph-impl exts)))
(defn make-sandbox-storage
[]
(let [method (aget api-proxy "makeSandboxStorage")
args []]
(core/call-method api-proxy method args)))
(defn make-url
"make assets scheme url based on current graph"
[path]
(let [method (aget api-proxy "makeUrl")
args [path]]
(core/call-method api-proxy method args)))
(defn built-in-open
"try to open asset type file in Logseq app"
[path]
(let [method (aget api-proxy "builtInOpen")
args [path]]
(core/call-method api-proxy method args)))

View File

@@ -0,0 +1,149 @@
;; Auto-generated via `bb libs:generate-cljs-sdk`
(ns com.logseq.core
(:require ["@logseq/libs"]
[cljs-bean.core :as bean]
[com.logseq.util :as util]))
(defn- normalize-result [result]
(if (instance? js/Promise result)
(.then result (fn [value] (normalize-result value)))
(util/->clj-tagged result)))
(defn call-method [owner method args]
(when-not method
(throw (js/Error. "Missing method on logseq namespace")))
(normalize-result (.apply method owner (bean/->js args))))
(def api-proxy js/logseq)
(defn- ready-impl
[model callback]
(let [method (aget api-proxy "ready")
args [model callback]]
(-> (call-method api-proxy method args)
(.then (fn []
(js/logseq._execCallableAPIAsync
"setSDKMetadata"
#js {:runtime "cljs"}))))))
(defn ready
([]
(ready-impl nil nil))
([model]
(ready-impl model nil))
([model callback]
(ready-impl model callback)))
(defn ensure-connected
[]
(let [method (aget api-proxy "ensureConnected")
args []]
(call-method api-proxy method args)))
(defn beforeunload
[callback]
(let [method (aget api-proxy "beforeunload")
args [callback]]
(call-method api-proxy method args)))
(defn provide-model
[model]
(let [method (aget api-proxy "provideModel")
args [model]]
(call-method api-proxy method args)))
(defn provide-theme
[theme]
(let [method (aget api-proxy "provideTheme")
args [theme]]
(call-method api-proxy method args)))
(defn provide-style
[style]
(let [method (aget api-proxy "provideStyle")
args [style]]
(call-method api-proxy method args)))
(defn provide-ui
[ui]
(let [method (aget api-proxy "provideUI")
args [ui]]
(call-method api-proxy method args)))
(defn use-settings-schema
[schema]
(let [method (aget api-proxy "useSettingsSchema")
args [schema]]
(call-method api-proxy method args)))
(defn update-settings
[attrs]
(let [method (aget api-proxy "updateSettings")
args [attrs]]
(call-method api-proxy method args)))
(defn on-settings-changed
[cb]
(let [method (aget api-proxy "onSettingsChanged")
args [cb]]
(call-method api-proxy method args)))
(defn show-settings-ui
[]
(let [method (aget api-proxy "showSettingsUI")
args []]
(call-method api-proxy method args)))
(defn hide-settings-ui
[]
(let [method (aget api-proxy "hideSettingsUI")
args []]
(call-method api-proxy method args)))
(defn set-main-ui-attrs
[attrs]
(let [method (aget api-proxy "setMainUIAttrs")
args [attrs]]
(call-method api-proxy method args)))
(defn set-main-ui-inline-style
[style]
(let [method (aget api-proxy "setMainUIInlineStyle")
args [style]]
(call-method api-proxy method args)))
(defn- hide-main-ui-impl
[opts]
(let [method (aget api-proxy "hideMainUI")
args [opts]]
(call-method api-proxy method args)))
(defn hide-main-ui
([]
(hide-main-ui-impl nil))
([opts]
(hide-main-ui-impl opts)))
(defn- show-main-ui-impl
[opts]
(let [method (aget api-proxy "showMainUI")
args [opts]]
(call-method api-proxy method args)))
(defn show-main-ui
([]
(show-main-ui-impl nil))
([opts]
(show-main-ui-impl opts)))
(defn toggle-main-ui
[]
(let [method (aget api-proxy "toggleMainUI")
args []]
(call-method api-proxy method args)))
(defn resolve-resource-full-url
[file-path]
(let [method (aget api-proxy "resolveResourceFullUrl")
args [file-path]]
(call-method api-proxy method args)))

View File

@@ -0,0 +1,34 @@
;; Auto-generated via `bb libs:generate-cljs-sdk`
(ns com.logseq.db
(:require [com.logseq.core :as core]))
(def api-proxy (aget js/logseq "DB"))
(defn q
"Run a DSL query"
[dsl]
(let [method (aget api-proxy "q")
args [dsl]]
(core/call-method api-proxy method args)))
(defn datascript-query
"Run a datascript query"
[query & inputs]
(let [method (aget api-proxy "datascriptQuery")
rest-inputs (vec inputs)
args (into [query] rest-inputs)]
(core/call-method api-proxy method args)))
(defn on-changed
"Hook all transaction data of DB"
[callback]
(let [method (aget api-proxy "onChanged")
args [callback]]
(core/call-method api-proxy method args)))
(defn on-block-changed
"Subscribe a specific block changed event"
[uuid callback]
(let [method (aget api-proxy "onBlockChanged")
args [uuid callback]]
(core/call-method api-proxy method args)))

View File

@@ -0,0 +1,432 @@
;; Auto-generated via `bb libs:generate-cljs-sdk`
(ns com.logseq.editor
(:require [com.logseq.core :as core]))
(def api-proxy (aget js/logseq "Editor"))
(defn register-slash-command
"register a custom command which will be added to the Logseq slash command list"
[tag action]
(let [method (aget api-proxy "registerSlashCommand")
args [tag action]]
(core/call-method api-proxy method args)))
(defn register-block-context-menu-item
"register a custom command in the block context menu (triggered by right-clicking the block dot)"
[label action]
(let [method (aget api-proxy "registerBlockContextMenuItem")
args [label action]]
(core/call-method api-proxy method args)))
(defn- register-highlight-context-menu-item-impl
[label action opts]
(let [method (aget api-proxy "registerHighlightContextMenuItem")
args [label action opts]]
(core/call-method api-proxy method args)))
(defn register-highlight-context-menu-item
"Current it's only available for pdf viewer"
([label action]
(register-highlight-context-menu-item-impl label action nil))
([label action opts]
(register-highlight-context-menu-item-impl label action opts)))
(defn check-editing
[]
(let [method (aget api-proxy "checkEditing")
args []]
(core/call-method api-proxy method args)))
(defn insert-at-editing-cursor
[content]
(let [method (aget api-proxy "insertAtEditingCursor")
args [content]]
(core/call-method api-proxy method args)))
(defn restore-editing-cursor
[]
(let [method (aget api-proxy "restoreEditingCursor")
args []]
(core/call-method api-proxy method args)))
(defn- exit-editing-mode-impl
[select-block]
(let [method (aget api-proxy "exitEditingMode")
args [select-block]]
(core/call-method api-proxy method args)))
(defn exit-editing-mode
([]
(exit-editing-mode-impl nil))
([select-block]
(exit-editing-mode-impl select-block)))
(defn get-editing-cursor-position
[]
(let [method (aget api-proxy "getEditingCursorPosition")
args []]
(core/call-method api-proxy method args)))
(defn get-editing-block-content
[]
(let [method (aget api-proxy "getEditingBlockContent")
args []]
(core/call-method api-proxy method args)))
(defn get-current-page
[]
(let [method (aget api-proxy "getCurrentPage")
args []]
(core/call-method api-proxy method args)))
(defn get-current-block
[]
(let [method (aget api-proxy "getCurrentBlock")
args []]
(core/call-method api-proxy method args)))
(defn get-selected-blocks
[]
(let [method (aget api-proxy "getSelectedBlocks")
args []]
(core/call-method api-proxy method args)))
(defn clear-selected-blocks
[]
(let [method (aget api-proxy "clearSelectedBlocks")
args []]
(core/call-method api-proxy method args)))
(defn get-current-page-blocks-tree
"get all blocks of the current page as a tree structure"
[]
(let [method (aget api-proxy "getCurrentPageBlocksTree")
args []]
(core/call-method api-proxy method args)))
(defn get-page-blocks-tree
"get all blocks for the specified page"
[src-page]
(let [method (aget api-proxy "getPageBlocksTree")
args [src-page]]
(core/call-method api-proxy method args)))
(defn get-page-linked-references
"get all page/block linked references"
[src-page]
(let [method (aget api-proxy "getPageLinkedReferences")
args [src-page]]
(core/call-method api-proxy method args)))
(defn get-pages-from-namespace
"get flatten pages from top namespace"
[namespace]
(let [method (aget api-proxy "getPagesFromNamespace")
args [namespace]]
(core/call-method api-proxy method args)))
(defn get-pages-tree-from-namespace
"construct pages tree from namespace pages"
[namespace]
(let [method (aget api-proxy "getPagesTreeFromNamespace")
args [namespace]]
(core/call-method api-proxy method args)))
(defn new-block-uuid
"Create a unique UUID string which can then be assigned to a block."
[]
(let [method (aget api-proxy "newBlockUUID")
args []]
(core/call-method api-proxy method args)))
(defn is-page-block
[block]
(let [method (aget api-proxy "isPageBlock")
args [block]]
(core/call-method api-proxy method args)))
(defn- insert-block-impl
[src-block content opts]
(let [method (aget api-proxy "insertBlock")
args [src-block content opts]]
(core/call-method api-proxy method args)))
(defn insert-block
([src-block content]
(insert-block-impl src-block content nil))
([src-block content opts]
(insert-block-impl src-block content opts)))
(defn- insert-batch-block-impl
[src-block batch opts]
(let [method (aget api-proxy "insertBatchBlock")
args [src-block batch opts]]
(core/call-method api-proxy method args)))
(defn insert-batch-block
([src-block batch]
(insert-batch-block-impl src-block batch nil))
([src-block batch opts]
(insert-batch-block-impl src-block batch opts)))
(defn- update-block-impl
[src-block content opts]
(let [method (aget api-proxy "updateBlock")
args [src-block content opts]]
(core/call-method api-proxy method args)))
(defn update-block
([src-block content]
(update-block-impl src-block content nil))
([src-block content opts]
(update-block-impl src-block content opts)))
(defn remove-block
[src-block]
(let [method (aget api-proxy "removeBlock")
args [src-block]]
(core/call-method api-proxy method args)))
(defn- get-block-impl
[src-block opts]
(let [method (aget api-proxy "getBlock")
args [src-block opts]]
(core/call-method api-proxy method args)))
(defn get-block
([src-block]
(get-block-impl src-block nil))
([src-block opts]
(get-block-impl src-block opts)))
(defn set-block-collapsed
[uuid opts]
(let [method (aget api-proxy "setBlockCollapsed")
args [uuid opts]]
(core/call-method api-proxy method args)))
(defn- get-page-impl
[src-page opts]
(let [method (aget api-proxy "getPage")
args [src-page opts]]
(core/call-method api-proxy method args)))
(defn get-page
([src-page]
(get-page-impl src-page nil))
([src-page opts]
(get-page-impl src-page opts)))
(defn- create-page-impl
[page-name properties opts]
(let [method (aget api-proxy "createPage")
args [page-name properties opts]]
(core/call-method api-proxy method args)))
(defn create-page
([page-name]
(create-page-impl page-name nil nil))
([page-name properties]
(create-page-impl page-name properties nil))
([page-name properties opts]
(create-page-impl page-name properties opts)))
(defn create-journal-page
[date]
(let [method (aget api-proxy "createJournalPage")
args [date]]
(core/call-method api-proxy method args)))
(defn delete-page
[page-name]
(let [method (aget api-proxy "deletePage")
args [page-name]]
(core/call-method api-proxy method args)))
(defn rename-page
[old-name new-name]
(let [method (aget api-proxy "renamePage")
args [old-name new-name]]
(core/call-method api-proxy method args)))
(defn- get-all-pages-impl
[repo]
(let [method (aget api-proxy "getAllPages")
args [repo]]
(core/call-method api-proxy method args)))
(defn get-all-pages
([]
(get-all-pages-impl nil))
([repo]
(get-all-pages-impl repo)))
(defn get-all-tags
[]
(let [method (aget api-proxy "getAllTags")
args []]
(core/call-method api-proxy method args)))
(defn get-all-properties
[]
(let [method (aget api-proxy "getAllProperties")
args []]
(core/call-method api-proxy method args)))
(defn get-tag-objects
[page-identity]
(let [method (aget api-proxy "getTagObjects")
args [page-identity]]
(core/call-method api-proxy method args)))
(defn- prepend-block-in-page-impl
[page content opts]
(let [method (aget api-proxy "prependBlockInPage")
args [page content opts]]
(core/call-method api-proxy method args)))
(defn prepend-block-in-page
([page content]
(prepend-block-in-page-impl page content nil))
([page content opts]
(prepend-block-in-page-impl page content opts)))
(defn- append-block-in-page-impl
[page content opts]
(let [method (aget api-proxy "appendBlockInPage")
args [page content opts]]
(core/call-method api-proxy method args)))
(defn append-block-in-page
([page content]
(append-block-in-page-impl page content nil))
([page content opts]
(append-block-in-page-impl page content opts)))
(defn get-previous-sibling-block
[src-block]
(let [method (aget api-proxy "getPreviousSiblingBlock")
args [src-block]]
(core/call-method api-proxy method args)))
(defn get-next-sibling-block
[src-block]
(let [method (aget api-proxy "getNextSiblingBlock")
args [src-block]]
(core/call-method api-proxy method args)))
(defn- move-block-impl
[src-block target-block opts]
(let [method (aget api-proxy "moveBlock")
args [src-block target-block opts]]
(core/call-method api-proxy method args)))
(defn move-block
([src-block target-block]
(move-block-impl src-block target-block nil))
([src-block target-block opts]
(move-block-impl src-block target-block opts)))
(defn- edit-block-impl
[src-block opts]
(let [method (aget api-proxy "editBlock")
args [src-block opts]]
(core/call-method api-proxy method args)))
(defn edit-block
([src-block]
(edit-block-impl src-block nil))
([src-block opts]
(edit-block-impl src-block opts)))
(defn select-block
[src-block]
(let [method (aget api-proxy "selectBlock")
args [src-block]]
(core/call-method api-proxy method args)))
(defn save-focused-code-editor-content
[]
(let [method (aget api-proxy "saveFocusedCodeEditorContent")
args []]
(core/call-method api-proxy method args)))
(defn get-property
[key]
(let [method (aget api-proxy "getProperty")
args [key]]
(core/call-method api-proxy method args)))
(defn- upsert-property-impl
[key schema opts]
(let [method (aget api-proxy "upsertProperty")
args [key schema opts]]
(core/call-method api-proxy method args)))
(defn upsert-property
([key]
(upsert-property-impl key nil nil))
([key schema]
(upsert-property-impl key schema nil))
([key schema opts]
(upsert-property-impl key schema opts)))
(defn remove-property
[key]
(let [method (aget api-proxy "removeProperty")
args [key]]
(core/call-method api-proxy method args)))
(defn upsert-block-property
[block key value]
(let [method (aget api-proxy "upsertBlockProperty")
args [block key value]]
(core/call-method api-proxy method args)))
(defn remove-block-property
[block key]
(let [method (aget api-proxy "removeBlockProperty")
args [block key]]
(core/call-method api-proxy method args)))
(defn get-block-property
[block key]
(let [method (aget api-proxy "getBlockProperty")
args [block key]]
(core/call-method api-proxy method args)))
(defn get-block-properties
[block]
(let [method (aget api-proxy "getBlockProperties")
args [block]]
(core/call-method api-proxy method args)))
(defn get-page-properties
[page]
(let [method (aget api-proxy "getPageProperties")
args [page]]
(core/call-method api-proxy method args)))
(defn- scroll-to-block-in-page-impl
[page-name block-id opts]
(let [method (aget api-proxy "scrollToBlockInPage")
args [page-name block-id opts]]
(core/call-method api-proxy method args)))
(defn scroll-to-block-in-page
([page-name block-id]
(scroll-to-block-in-page-impl page-name block-id nil))
([page-name block-id opts]
(scroll-to-block-in-page-impl page-name block-id opts)))
(defn open-in-right-sidebar
[id]
(let [method (aget api-proxy "openInRightSidebar")
args [id]]
(core/call-method api-proxy method args)))
(defn on-input-selection-end
[callback]
(let [method (aget api-proxy "onInputSelectionEnd")
args [callback]]
(core/call-method api-proxy method args)))

View File

@@ -0,0 +1,23 @@
;; Auto-generated via `bb libs:generate-cljs-sdk`
(ns com.logseq.git
(:require [com.logseq.core :as core]))
(def api-proxy (aget js/logseq "Git"))
(defn exec-command
[args]
(let [method (aget api-proxy "execCommand")
args [args]]
(core/call-method api-proxy method args)))
(defn load-ignore-file
[]
(let [method (aget api-proxy "loadIgnoreFile")
args []]
(core/call-method api-proxy method args)))
(defn save-ignore-file
[content]
(let [method (aget api-proxy "saveIgnoreFile")
args [content]]
(core/call-method api-proxy method args)))

View File

@@ -0,0 +1,49 @@
;; Auto-generated via `bb libs:generate-cljs-sdk`
(ns com.logseq.ui
(:require [com.logseq.core :as core]))
(def api-proxy (aget js/logseq "UI"))
(defn- show-msg-impl
[content status opts]
(let [method (aget api-proxy "showMsg")
args [content status opts]]
(core/call-method api-proxy method args)))
(defn show-msg
([content]
(show-msg-impl content nil nil))
([content status]
(show-msg-impl content status nil))
([content status opts]
(show-msg-impl content status opts)))
(defn close-msg
[key]
(let [method (aget api-proxy "closeMsg")
args [key]]
(core/call-method api-proxy method args)))
(defn query-element-rect
[selector]
(let [method (aget api-proxy "queryElementRect")
args [selector]]
(core/call-method api-proxy method args)))
(defn query-element-by-id
[id]
(let [method (aget api-proxy "queryElementById")
args [id]]
(core/call-method api-proxy method args)))
(defn check-slot-valid
[slot]
(let [method (aget api-proxy "checkSlotValid")
args [slot]]
(core/call-method api-proxy method args)))
(defn resolve-theme-css-props-vals
[props]
(let [method (aget api-proxy "resolveThemeCssPropsVals")
args [props]]
(core/call-method api-proxy method args)))

View File

@@ -0,0 +1,31 @@
(ns com.logseq.util
(:require [cljs-bean.core :as bean]
[clojure.string :as string]
[clojure.walk :as walk]))
(def ^:private kw-tag "___kw___")
(defn- decode-kw [v]
(if (and (string? v) (string/starts-with? v kw-tag))
(let [s (subs v (count kw-tag))
i (.indexOf s "/")]
(if (neg? i)
(keyword s) ; :name
(keyword (subs s 0 i) (subs s (inc i))))) ; :ns/name
v))
(defn ->clj-tagged [js]
(some->> js
bean/->clj
(walk/postwalk (fn [f]
(cond
(keyword? f)
(decode-kw (if-let [ns (namespace f)]
(str ns "/" (name f))
(name f)))
(string? f)
(decode-kw f)
:else
f)))))

121
libs/cljs-sdk/yarn.lock Normal file
View File

@@ -0,0 +1,121 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@logseq/libs@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@logseq/libs/-/libs-0.2.3.tgz#33c4b7ad1db02a2335269cd38eac7e5f5196f675"
integrity sha512-aMtZFsNvbFgVhiaA9K9DANPhIv+ZKaC5qW61si2UMsfwgykr4/hyT9Q7aoqf7ir3ZIwYDDhfEJqpthZCsUwbeA==
dependencies:
csstype "3.1.0"
debug "4.3.4"
deepmerge "4.3.1"
dompurify "2.5.4"
eventemitter3 "4.0.7"
fast-deep-equal "3.1.3"
lodash-es "4.17.21"
path "0.12.7"
snake-case "3.0.4"
csstype@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
debug@4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
deepmerge@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
dompurify@2.5.4:
version "2.5.4"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.5.4.tgz#347e91070963b22db31c7c8d0ce9a0a2c3c08746"
integrity sha512-l5NNozANzaLPPe0XaAwvg3uZcHtDBnziX/HjsY1UcDj1MxTK8Dd0Kv096jyPK5HRzs/XM5IMj20dW8Fk+HnbUA==
dot-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
dependencies:
no-case "^3.0.4"
tslib "^2.0.3"
eventemitter3@4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
fast-deep-equal@3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
lodash-es@4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
dependencies:
tslib "^2.0.3"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
no-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
dependencies:
lower-case "^2.0.2"
tslib "^2.0.3"
path@0.12.7:
version "0.12.7"
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==
dependencies:
process "^0.11.1"
util "^0.10.3"
process@^0.11.1:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
snake-case@3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c"
integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==
dependencies:
dot-case "^3.0.4"
tslib "^2.0.3"
tslib@^2.0.3:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
util@^0.10.3:
version "0.10.4"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
dependencies:
inherits "2.0.3"

View File

@@ -1,6 +1,6 @@
{
"name": "@logseq/libs",
"version": "0.2.1",
"version": "0.2.3",
"description": "Logseq SDK libraries",
"main": "dist/lsplugin.user.js",
"typings": "index.d.ts",
@@ -10,6 +10,7 @@
"dev:user": "npm run build:user -- --mode development --watch",
"build:core": "webpack --config webpack.config.core.js --mode production",
"dev:core": "npm run build:core -- --mode development --watch",
"generate:schema": "node scripts/extract-sdk-schema.js",
"build": "tsc && rm dist/*.js && npm run build:user",
"lint": "prettier --check \"src/**/*.{ts, js}\"",
"fix": "prettier --write \"src/**/*.{ts, js}\"",
@@ -27,6 +28,7 @@
"snake-case": "3.0.4"
},
"devDependencies": {
"ts-morph": "^22.0.0",
"@babel/core": "^7.20.2",
"@babel/preset-env": "^7.20.2",
"@types/debug": "^4.1.5",

View File

@@ -0,0 +1,184 @@
#!/usr/bin/env node
/**
* Extracts metadata about the Logseq JS SDK from the generated *.d.ts files.
*
* This script uses ts-morph so we can rely on the TypeScript compiler's view of
* the declarations. We intentionally read the emitted declaration files in
* dist/ so that consumers do not need to depend on the source layout.
*
* The resulting schema is written to dist/logseq-sdk-schema.json and contains
* a simplified representation that downstream tooling (Babashka) can consume.
*/
const fs = require('node:fs');
const path = require('node:path');
const { Project, Node } = require('ts-morph');
const ROOT = path.resolve(__dirname, '..');
const DIST_DIR = path.join(ROOT, 'dist');
const OUTPUT_FILE = path.join(DIST_DIR, 'logseq-sdk-schema.json');
const DECL_FILES = [
'LSPlugin.d.ts',
'LSPlugin.user.d.ts',
];
/**
* Interfaces whose methods will be turned into CLJS wrappers at runtime.
* These correspond to `logseq.<Namespace>` targets in the JS SDK.
*/
const TARGET_INTERFACES = [
'IAppProxy',
'IEditorProxy',
'IDBProxy',
'IUIProxy',
'IUtilsProxy',
'IGitProxy',
'IAssetsProxy',
];
/**
* Simple heuristics to determine whether a parameter should be converted via
* cljs-bean when crossing the JS <-> CLJS boundary.
*/
const BEAN_TO_JS_REGEX =
/(Record<|Array<|Partial<|UIOptions|UIContainerAttrs|StyleString|StyleOptions|object|any|unknown|IHookEvent|BlockEntity|PageEntity|Promise<\s*Record)/i;
const project = new Project({
compilerOptions: { allowJs: true },
});
DECL_FILES.forEach((file) => {
const full = path.join(DIST_DIR, file);
if (fs.existsSync(full)) {
project.addSourceFileAtPath(full);
}
});
const schema = {
generatedAt: new Date().toISOString(),
interfaces: {},
classes: {},
};
const serializeDoc = (symbol) => {
if (!symbol) return undefined;
const decl = symbol.getDeclarations()[0];
if (!decl) return undefined;
const docs = decl
.getJsDocs()
.map((doc) => doc.getComment())
.filter(Boolean);
return docs.length ? docs.join('\n\n') : undefined;
};
const serializeParameter = (signature, symbol, memberNode) => {
const name = symbol.getName();
const declaration = symbol.getDeclarations()[0];
let typeText;
let optional = symbol.isOptional?.() ?? false;
let rest = symbol.isRestParameter?.() ?? false;
if (declaration && Node.isParameterDeclaration(declaration)) {
typeText = declaration.getType().getText();
optional = declaration.hasQuestionToken?.() ?? false;
rest = declaration.isRestParameter?.() ?? false;
} else {
const location =
signature.getDeclaration?.() ??
memberNode ??
declaration ??
symbol.getDeclarations()[0];
typeText = symbol.getTypeAtLocation(location).getText();
}
const convertToJs = BEAN_TO_JS_REGEX.test(typeText);
return {
name,
type: typeText,
optional,
rest,
beanToJs: convertToJs,
};
};
const serializeSignature = (sig, memberNode) => {
const params = sig.getParameters().map((paramSymbol) =>
serializeParameter(sig, paramSymbol, memberNode)
);
const returnType = sig.getReturnType().getText();
return {
parameters: params,
returnType,
};
};
const serializeCallable = (symbol, member) => {
if (!symbol) return null;
const type = symbol.getTypeAtLocation(member);
const callSignatures = type.getCallSignatures();
if (!callSignatures.length) {
return null;
}
return {
name: symbol.getName(),
documentation: serializeDoc(symbol),
signatures: callSignatures.map((sig) => serializeSignature(sig, member)),
};
};
const sourceFiles = project.getSourceFiles();
sourceFiles.forEach((source) => {
source.getInterfaces().forEach((iface) => {
const name = iface.getName();
if (!TARGET_INTERFACES.includes(name)) {
return;
}
const interfaceSymbol = iface.getType().getSymbol();
const doc = serializeDoc(interfaceSymbol);
const methods = iface
.getMembers()
.map((member) => serializeCallable(member.getSymbol(), member))
.filter(Boolean);
schema.interfaces[name] = {
documentation: doc,
methods,
};
});
source.getClasses().forEach((cls) => {
const name = cls.getName();
if (name !== 'LSPluginUser') {
return;
}
const classSymbol = cls.getType().getSymbol();
const doc = serializeDoc(classSymbol);
const methods = cls
.getInstanceMethods()
.filter((method) => method.getName() !== 'constructor')
.map((method) => serializeCallable(method.getSymbol(), method))
.filter(Boolean);
const getters = cls.getGetAccessors().map((accessor) => ({
name: accessor.getName(),
documentation: serializeDoc(accessor.getSymbol()),
returnType: accessor.getReturnType().getText(),
}));
schema.classes[name] = {
documentation: doc,
methods,
getters,
};
});
});
fs.mkdirSync(DIST_DIR, { recursive: true });
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(schema, null, 2));
console.log(`Wrote ${OUTPUT_FILE}`);

View File

@@ -154,6 +154,7 @@ interface PluginLocalOptions {
url: string // Plugin package absolute fs location
name: string
version: string
runtime: string
mode: 'shadow' | 'iframe'
webPkg?: any // web plugin package.json data
settingsSchema?: SettingSchemaDesc[]
@@ -166,6 +167,7 @@ interface PluginLocalOptions {
interface PluginLocalSDKMetadata {
version: string
runtime: string
[key: string]: any
}
@@ -669,7 +671,7 @@ class PluginLocal extends EventEmitter<
? `<script src="${sdkPathRoot}/lsplugin.user.js?v=${tag}"></script>`
: `<script src="https://cdn.jsdelivr.net/npm/@logseq/libs/dist/lsplugin.user.min.js?v=${tag}"></script>`
}
</head>
<body>
<div id="app"></div>

View File

@@ -575,6 +575,7 @@ export class LSPluginUser
try {
await this._execCallableAPIAsync('setSDKMetadata', {
version: this._version,
runtime: 'js',
})
} catch (e) {
console.warn(e)

View File

@@ -1066,11 +1066,42 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
dependencies:
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
"@nodelib/fs.walk@^1.2.3":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
dependencies:
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@polka/url@^1.0.0-next.17":
version "1.0.0-next.17"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.17.tgz#25fdbdfd282c2f86ddf3fcefbd98be99cd2627e2"
integrity sha512-0p1rCgM3LLbAdwBnc7gqgnvjHg9KpbhcSphergHShlkWz8EdPawoMJ3/VbezI0mGC5eKCDzMaPgF9Yca6cKvrg==
"@ts-morph/common@~0.23.0":
version "0.23.0"
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.23.0.tgz#bd4ddbd3f484f29476c8bd985491592ae5fc147e"
integrity sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==
dependencies:
fast-glob "^3.3.2"
minimatch "^9.0.3"
mkdirp "^3.0.1"
path-browserify "^1.0.1"
"@types/debug@^4.1.5":
version "4.1.7"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
@@ -1434,6 +1465,13 @@ braces@^3.0.1:
dependencies:
fill-range "^7.0.1"
braces@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.1.1"
browserslist@^4.14.5:
version "4.16.8"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.8.tgz#cb868b0b554f137ba6e33de0ecff2eda403c4fb0"
@@ -1501,6 +1539,11 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
code-block-writer@^13.0.1:
version "13.0.3"
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-13.0.3.tgz#90f8a84763a5012da7af61319dd638655ae90b5b"
integrity sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -1709,6 +1752,17 @@ fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@^3.3.2:
version "3.3.3"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.8"
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@@ -1719,6 +1773,13 @@ fastest-levenshtein@^1.0.12:
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
fastq@^1.6.0:
version "1.19.1"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5"
integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
dependencies:
reusify "^1.0.4"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@@ -1726,6 +1787,13 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"
find-cache-dir@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
@@ -1763,6 +1831,13 @@ get-stream@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
glob-parent@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob-to-regexp@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
@@ -1868,6 +1943,18 @@ is-core-module@^2.9.0:
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
@@ -2028,6 +2115,11 @@ merge-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
@@ -2036,6 +2128,14 @@ micromatch@^4.0.0:
braces "^3.0.1"
picomatch "^2.2.3"
micromatch@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
dependencies:
braces "^3.0.3"
picomatch "^2.3.1"
mime-db@1.49.0:
version "1.49.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed"
@@ -2065,6 +2165,18 @@ minimatch@^5.0.1, minimatch@^5.1.0:
dependencies:
brace-expansion "^2.0.1"
minimatch@^9.0.3:
version "9.0.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
dependencies:
brace-expansion "^2.0.1"
mkdirp@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@@ -2150,6 +2262,11 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -2183,6 +2300,11 @@ picomatch@^2.2.3:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
@@ -2210,6 +2332,11 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -2314,6 +2441,18 @@ resolve@^1.9.0:
is-core-module "^2.2.0"
path-parse "^1.0.6"
reusify@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f"
integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
dependencies:
queue-microtask "^1.2.2"
safe-buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@@ -2535,6 +2674,14 @@ ts-loader@9.3.0:
micromatch "^4.0.0"
semver "^7.3.4"
ts-morph@^22.0.0:
version "22.0.0"
resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-22.0.0.tgz#5532c592fb6dddae08846f12c9ab0fc590b1d42e"
integrity sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==
dependencies:
"@ts-morph/common" "~0.23.0"
code-block-writer "^13.0.1"
tslib@^2.0.3:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,234 @@
(ns logseq.libs.sdk-generator
(:require [babashka.fs :as fs]
[cheshire.core :as json]
[clojure.string :as string]))
(def default-schema "libs/dist/logseq-sdk-schema.json")
(def default-output-dir "libs/cljs-sdk/src")
(def default-ns-prefix "com.logseq")
(def core-namespace "core")
(defn parse-args
[args]
(loop [opts {}
tokens args]
(if (empty? tokens)
opts
(let [[flag value & more] tokens]
(case flag
"--schema" (recur (assoc opts :schema value) more)
"--out-dir" (recur (assoc opts :out-dir value) more)
"--out" (recur (assoc opts :out-dir value) more)
"--ns-prefix" (recur (assoc opts :ns-prefix value) more)
(throw (ex-info (str "Unknown flag: " flag) {:flag flag})))))))
(defn camel->kebab [s]
(-> s
(string/replace #"([a-z0-9])([A-Z])" "$1-$2")
(string/replace #"([A-Z]+)([A-Z][a-z])" "$1-$2")
(string/lower-case)
(string/replace #"[^a-z0-9]+" "-")
(string/replace #"(^-|-$)" "")))
(defn interface->target [iface-name]
(-> iface-name
(string/replace #"^I" "")
(string/replace #"Proxy$" "")))
(defn interface->namespace [ns-prefix iface-name]
(str ns-prefix "." (camel->kebab (interface->target iface-name))))
(defn getter->interface-name [return-type]
(some->> (re-find #"\.(I[A-Za-z0-9]+)" return-type)
second))
(defn iface-key->string [k]
(cond
(string? k) k
(keyword? k) (name k)
:else (str k)))
(defn format-docstring [doc]
(when (and doc (not (string/blank? doc)))
(str " " (pr-str doc) "\n")))
(defn param->info
[{:keys [name optional rest rest?]}]
(let [sym (camel->kebab name)]
{:name name
:sym sym
:optional (boolean optional)
:rest (boolean (or rest rest?))}))
(defn emit-rest-binding [{:keys [sym]}]
(let [rest-var (str "rest-" sym)
line (str " " rest-var " (vec " sym ")\n")]
{:binding line
:var rest-var}))
(defn format-param-vector [params]
(str "[" (string/join " " params) "]"))
(defn emit-method-body
[method-name params {:keys [call]}]
(let [rest-param (some #(when (:rest %) %) params)
fixed-params (->> (if rest-param (vec (remove :rest params)) params)
(map :sym))
{:keys [binding var]} (when rest-param (emit-rest-binding rest-param))
rest-lines (if binding [binding] [])
args-expr (if rest-param
(str "(into [" (string/join " " fixed-params) "] " var ")")
(str "[" (string/join " " fixed-params) "]"))]
(str (format " (let [method (aget api-proxy \"%s\")\n" method-name)
(apply str rest-lines)
" args " args-expr "]\n"
" (" call " api-proxy method args)))\n")))
(defn emit-optional-def
[fn-name doc-str params impl-name helpers method-name]
(let [required (take-while (complement :optional) params)
total (count params)
param-syms (map :sym params)
arities (range (count required) (inc total))
header (str "\n(defn " fn-name "\n"
(or doc-str ""))]
(str "\n(defn- " impl-name "\n"
" " (format-param-vector param-syms) "\n"
(emit-method-body method-name params helpers)
header
(apply str
(map-indexed
(fn [idx arity]
(let [provided (take arity param-syms)
missing (- total arity)
call-args (concat provided (repeat missing "nil"))
param-vector (format-param-vector provided)
call-arg-str (string/join " " call-args)
call-arg-str (if (string/blank? call-arg-str) "" (str " " call-arg-str))]
(str " (" param-vector "\n"
" (" impl-name call-arg-str "))"
(when (not= (inc idx) (count arities))
"\n"))))
arities))
")\n")))
(defn emit-method
[{:keys [name documentation signatures]}
helpers]
(let [{:keys [parameters]} (apply max-key #(count (:parameters %)) signatures)
params (map param->info parameters)
fn-name (camel->kebab name)
doc-str (format-docstring documentation)
rest-param (some #(when (:rest %) %) params)
optional-params (filter :optional params)
impl-name (str fn-name "-impl")
method-body (emit-method-body name params helpers)]
(when-not (string/starts-with? name "_") ; system methods
(cond
rest-param
(let [fixed-syms (map :sym (vec (remove :rest params)))
param-vector (format-param-vector (concat fixed-syms ["&" (:sym rest-param)]))]
(str "\n(defn " fn-name "\n"
(or doc-str "")
" " param-vector "\n"
method-body))
(seq optional-params)
(emit-optional-def fn-name doc-str params impl-name helpers name)
:else
(let [param-vector (format-param-vector (map :sym params))]
(str "\n(defn " fn-name "\n"
(or doc-str "")
" " param-vector "\n"
method-body))))))
(defn emit-core-namespace
[ns-prefix {:keys [methods]}]
(let [ns (str ns-prefix "." core-namespace)
header (str ";; Auto-generated via `bb libs:generate-cljs-sdk`\n"
"(ns " ns "\n"
" (:require [\"@logseq/libs\"]
[cljs-bean.core :as bean]
[com.logseq.util :as util]))\n\n"
"(defn- normalize-result [result]\n"
" (if (instance? js/Promise result)\n"
" (.then result (fn [value] (normalize-result value)))\n"
" (util/->clj-tagged result)))\n\n"
"(defn call-method [owner method args]
(when-not method
(throw (js/Error. \"Missing method on logseq namespace\")))
(normalize-result (.apply method owner (bean/->js args))))\n")
helpers {:call "call-method"}
owner "\n(def api-proxy js/logseq)\n"
methods-str (->> methods
(map #(emit-method % helpers))
(apply str))]
[ns (str header owner methods-str)]))
(defn emit-proxy-namespace
[ns-prefix iface-name iface]
(let [ns (interface->namespace ns-prefix iface-name)
target (interface->target iface-name)
owner-expr (format "(aget js/logseq \"%s\")" target)
header (str ";; Auto-generated via `bb libs:generate-cljs-sdk`\n"
"(ns " ns "\n"
" (:require [com.logseq.core :as core]))\n")
helpers {:call "core/call-method"}
owner (format "\n(def api-proxy %s)\n" owner-expr)
methods-str (->> (:methods iface)
(map #(emit-method % helpers))
(apply str))]
[ns (str header owner methods-str)]))
(defn namespace->file
[out-dir ns]
(let [parts (string/split ns #"\.")
dir-parts (butlast parts)
file-name (str (last parts) ".cljs")]
(apply fs/path out-dir (concat dir-parts [file-name]))))
(defn ensure-schema! [schema-path]
(when-not (fs/exists? schema-path)
(throw (ex-info (str "Schema not found, run `yarn --cwd libs generate:schema` first: " schema-path)
{:schema schema-path}))))
(defn write-namespaces!
[out-dir namespaces]
(doseq [[ns content] namespaces]
(when ns
(let [file (namespace->file out-dir ns)]
(fs/create-dirs (fs/parent file))
(spit (str file) content)
(println "Generated" (str file))))))
(defn run!
([] (run! {}))
([opts]
(let [schema-path (fs/absolutize (or (:schema opts) default-schema))
out-dir (fs/absolutize (or (:out-dir opts) default-output-dir))
ns-prefix (or (:ns-prefix opts) default-ns-prefix)]
(ensure-schema! schema-path)
(let [schema (json/parse-string (slurp (str schema-path)) true)
interfaces (:interfaces schema)
ls-user (get-in schema [:classes :LSPluginUser])
_ (when-not ls-user
(throw (ex-info "Missing LSPluginUser metadata in schema" {:schema schema-path})))
getters (:getters ls-user)
proxy-names (->> getters
(keep #(some-> (getter->interface-name (:returnType %)) keyword))
(remove #{:IUtilsProxy})
distinct)
proxies (for [iface-key proxy-names
:let [iface (get interfaces iface-key)]
:when iface]
(emit-proxy-namespace ns-prefix (iface-key->string iface-key) iface))
core (emit-core-namespace ns-prefix ls-user)
namespaces (cons core proxies)]
(fs/create-dirs out-dir)
(write-namespaces! out-dir namespaces)
out-dir))))
(defn -main [& args]
(let [opts (parse-args args)]
(run! opts)))

View File

@@ -90,11 +90,10 @@
(defn get-property
[k]
(this-as this
(p/let [prop (-get-property this k)]
(some-> prop
(assoc :type (:logseq.property/type prop))
(sdk-utils/normalize-keyword-for-json)
(bean/->js)))))
(p/let [prop (-get-property this k)
prop' (some-> prop
(assoc :type (:logseq.property/type prop)))]
(sdk-utils/result->js prop'))))
(defn ->cardinality
[input]

View File

@@ -4,6 +4,7 @@
[clojure.walk :as walk]
[datascript.impl.entity :as de]
[frontend.db :as db]
[frontend.handler.plugin :as plugin-handler]
[frontend.util :as util]
[goog.object :as gobj]
[logseq.db.common.entity-util :as common-entity-util]
@@ -22,11 +23,27 @@
(contains? #{:logseq.property.embedding/hnsw-label-updated-at :block/tx-id} k))) m)
(into {})))
(def ^:private kw-tag "___kw___") ; unlikely in normal strings; change if you prefer
(defn- encode-kw [v]
(if (keyword? v)
;; __kw__ns/name or __kw__name
(str kw-tag (if-let [ns (namespace v)]
(str ns "/" (name v))
(name v)))
v))
(defn normalize-keyword-for-json
([input] (normalize-keyword-for-json input true))
([input camel-case?]
(when input
(let [input (cond
(let [pid (some-> (gobj/get js/window "$$callerPluginID"))
plugin (and pid (plugin-handler/get-plugin-inst pid))
runtime (some-> plugin
(gobj/get "sdk")
(gobj/get "runtime"))
cljs? (= "cljs" runtime)
input (cond
(de/entity? input) (common-entity-util/entity->map input)
(sequential? input) (map #(if (de/entity? %)
(common-entity-util/entity->map %)
@@ -35,6 +52,9 @@
(walk/prewalk
(fn [a]
(cond
(and cljs? (keyword? a))
(encode-kw a)
(keyword? a)
(if (keep-json-keyword? a)
(str a)