Просмотр исходного кода

chore(libs): update AGENTS.md and add db_query_guide.md for improved documentation

charlie 5 дней назад
Родитель
Сommit
9837087c74
2 измененных файлов с 544 добавлено и 1 удалено
  1. 2 1
      libs/development-notes/AGENTS.md
  2. 542 0
      libs/development-notes/db_query_guide.md

+ 2 - 1
libs/development-notes/AGENTS.md

@@ -311,6 +311,7 @@ const exists = await logseq.UI.queryElementById('my-id')
 ```
 
 ### `logseq.Git` - Git Operations
+Only supported in file-based graphs for desktop Logseq.
 
 ```typescript
 const result = await logseq.Git.execCommand(['status'])
@@ -452,7 +453,7 @@ Create custom block renderers using `{{renderer : type, arg1, arg2}}`:
 logseq.App.onMacroRendererSlotted(({ slot, payload }) => {
   const [type, ...args] = payload.arguments
 
-  if (type !== ': my-renderer') return
+  if (type !== ':my-renderer') return
 
   logseq.provideUI({
     key: `my-renderer-${payload.uuid}`,

+ 542 - 0
libs/development-notes/db_query_guide.md

@@ -0,0 +1,542 @@
+## Logseq Datascript Query Skills Guide
+
+### 1. Logseq SDK Query APIs
+
+```typescript
+// DSL Query - Logseq's query language
+const results = await logseq.DB.q('[[page-name]]')
+const todos = await logseq.DB.q('(task TODO DOING)')
+
+// Datascript Query - Datalog syntax (more powerful)
+const results = await logseq.DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :where [?b :block/marker "TODO"]]
+`)
+
+// Query with parameters
+const results = await logseq.DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :in $ ?name
+   :where
+   [?p :block/name ?name]
+   [? b :block/page ?p]]
+`, `"my-page"`)
+
+// Watch database changes
+logseq.DB. onChanged(({ blocks, txData }) => {
+  console.log('Changed blocks:', blocks)
+})
+
+// Watch specific block changes
+logseq.DB.onBlockChanged(uuid, (block, txData) => {
+  console.log('Block changed:', block)
+})
+```
+
+---
+
+### 2. Datalog Query Syntax Basics
+
+#### 2.1 Basic Structure
+
+```clojure
+[: find <return-value>    ;; what to return
+ :in $ <params... >       ;; input parameters ($ = database)
+ :where <clauses...>]    ;; query conditions
+```
+
+#### 2.2 : find Clause
+
+```clojure
+;; Return entity IDs
+[:find ?b : where ...]
+
+;; Return multiple values
+[:find ?b ? name :where ...]
+
+;; Return full entity (commonly used)
+[:find (pull ?b [*]) :where ...]
+
+;; Return specific attributes
+[:find (pull ?b [: block/uuid :block/content]) :where ...]
+
+;; Aggregate functions
+[:find (count ? b) :where ...]
+[:find (min ? d) (max ?d) :where ...]
+```
+
+#### 2.3 :where Clause - Data Patterns
+
+```clojure
+;; Basic triple: [entity attribute value]
+[?b :block/marker "TODO"]         ;; ? b's marker equals "TODO"
+[? b :block/page ?p]               ;; ?b belongs to page ? p
+[?p :block/name ?name]            ;; ? p's name binds to ?name
+
+;; Multiple conditions (implicit AND)
+[?b :block/marker "TODO"]
+[?b : block/page ?p]
+[?p :block/journal? true]         ;; all three must be satisfied
+```
+
+#### 2.4 :in Clause - Parameter Passing
+
+```clojure
+;; $ is the database (required)
+[:find (pull ?b [*])
+ :in $
+ :where ...]
+
+;; Single parameter
+[:find (pull ?b [*])
+ :in $ ?marker
+ :where [?b :block/marker ? marker]]
+
+;; Multiple parameters
+[:find (pull ?b [*])
+ :in $ ?marker ? page-name
+ :where 
+ [?b :block/marker ?marker]
+ [? p :block/name ?page-name]
+ [?b : block/page ?p]]
+
+;; Collection parameter (match multiple values)
+[:find (pull ?b [*])
+ :in $ [?marker ...]
+ :where [?b :block/marker ?marker]]
+```
+
+---
+
+### 3. Schema Reference:  File Graph vs DB Graph
+
+#### 3.1 Block Attributes
+
+| Attribute | File Graph | DB Graph | Type | Description |
+|-----------|: ----------:|:--------:|------|-------------|
+| `:block/uuid` | ✅ | ✅ | UUID | Unique block identifier |
+| `:block/content` | ✅ | ❌ | String | Raw block content (File Graph only) |
+| `:block/title` | ❌ | ✅ | String | Block title/content (DB Graph only) |
+| `:block/page` | ✅ | ✅ | Ref | Parent page reference |
+| `:block/parent` | ✅ | ✅ | Ref | Parent block reference |
+| `:block/left` | ✅ | ❌ | Ref | Left sibling block |
+| `:block/order` | ❌ | ✅ | String | Block order in DB Graph |
+| `:block/refs` | ✅ | ✅ | Ref[] | Referenced pages/blocks |
+| `:block/marker` | ✅ | ❌ | String | Task marker (TODO/DOING/DONE) |
+| `:block/priority` | ✅ | ❌ | String | Priority (A/B/C) |
+| `:block/scheduled` | ✅ | ❌ | Int | Scheduled date (YYYYMMDD) |
+| `:block/deadline` | ✅ | ❌ | Int | Deadline date (YYYYMMDD) |
+| `:block/properties` | ✅ | ❌ | Map | Properties as key-value map |
+| `:block/tags` | ✅ | ✅ | Ref[] | Tag references |
+| `:block/link` | ❌ | ✅ | Ref | Link to class/tag in DB Graph |
+| `:block/tx-id` | ❌ | ✅ | Int | Transaction ID |
+| `:block/created-at` | ✅ | ❌ | Int | Creation timestamp (File Graph) |
+| `:block/updated-at` | ✅ | ❌ | Int | Update timestamp (File Graph) |
+
+#### 3.2 Page Attributes
+
+| Attribute | File Graph | DB Graph | Type | Description |
+|-----------|:----------:|:--------:|------|-------------|
+| `:block/name` | ✅ | ✅ | String | Page name (lowercase) |
+| `:block/original-name` | ✅ | ✅ | String | Original page name |
+| `:block/journal?` | ✅ | ✅ | Boolean | Is journal page |
+| `:block/journal-day` | ✅ | ✅ | Int | Journal date (YYYYMMDD) |
+| `:block/type` | ❌ | ✅ | String | Type ("page", "class", "property", etc.) |
+| `:block/format` | ✅ | ❌ | Keyword | Format (:markdown or :org) |
+| `:block/file` | ✅ | ❌ | Ref | Associated file reference |
+
+#### 3.3 DB Graph Specific - System Properties (Idents)
+
+| Attribute | Type | Description |
+|-----------|------|-------------|
+| `:logseq.property/created-at` | Int (ms) | Creation timestamp |
+| `:logseq.property/updated-at` | Int (ms) | Update timestamp |
+| `:logseq.property/icon` | Object | Icon definition `{type, id}` |
+| `:logseq.property/hide-empty-value` | Boolean | Hide property if empty |
+| `:logseq.property/closed-value-mode` | Boolean | Enum mode for property |
+| `:logseq.property/closed-values` | Array | Allowed enum values |
+| `:logseq.property/schema` | Object | Property schema definition |
+| `:logseq.property. class/extends` | Ref[] | Parent class references |
+| `:logseq.property.class/properties` | Ref[] | Class property references |
+
+#### 3.4 DB Graph Task System
+
+In DB Graph, tasks use a different system based on tags/classes:
+
+| Attribute | Description |
+|-----------|-------------|
+| `:block/tags` | Reference to task status class (e.g., `#logseq.class/Todo`) |
+| `:logseq.task/status` | Task status property |
+| `:logseq.task/priority` | Task priority property |
+| `:logseq.task/deadline` | Task deadline property |
+| `:logseq.task/scheduled` | Task scheduled property |
+
+---
+
+### 4. Common Query Examples
+
+#### 4.1 Task Queries
+
+**File Graph:**
+```typescript
+// All TODOs
+const todos = await logseq.DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :where [?b :block/marker "TODO"]]
+`)
+
+// TODO or DOING
+const activeTasks = await logseq.DB. datascriptQuery(`
+  [:find (pull ?b [*])
+   :in $ [?m ...]
+   :where [?b :block/marker ? m]]
+`, `["TODO" "DOING"]`)
+
+// Tasks with priority A
+const priorityTasks = await logseq.DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :where 
+   [?b :block/marker ? m]
+   [?b : block/priority "A"]]
+`)
+
+// Scheduled for today
+const today = new Date().toISOString().slice(0,10).replace(/-/g, '')
+const scheduled = await logseq.DB. datascriptQuery(`
+  [:find (pull ?b [*])
+   :in $ ? today
+   :where [?b : block/scheduled ?today]]
+`, parseInt(today))
+```
+
+**DB Graph:**
+```typescript
+// All tasks with TODO status
+const todos = await logseq.DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :where
+   [?b :block/tags ?tag]
+   [?tag :db/ident : logseq.class/Todo]]
+`)
+
+// Tasks by status property
+const tasks = await logseq.DB. datascriptQuery(`
+  [:find (pull ?b [*])
+   :where
+   [?b :logseq.task/status ?s]
+   [?s :db/ident :logseq. task/status. todo]]
+`)
+```
+
+#### 4.2 Page Queries
+
+**Both File Graph and DB Graph:**
+```typescript
+// All journal pages
+const journals = await logseq.DB.datascriptQuery(`
+  [:find (pull ?p [*])
+   :where [?p :block/journal?  true]]
+`)
+
+// Journals in specific month
+const monthJournals = await logseq. DB.datascriptQuery(`
+  [:find (pull ?p [*])
+   :where
+   [?p :block/journal?  true]
+   [?p : block/journal-day ?d]
+   [(>= ?d 20250101)]
+   [(<= ?d 20250131)]]
+`)
+
+// Blocks in a specific page
+const blocks = await logseq.DB. datascriptQuery(`
+  [:find (pull ?b [*])
+   :in $ ? name
+   :where
+   [? p :block/name ?name]
+   [?b :block/page ?p]]
+`, `"my-page"`)
+```
+
+**DB Graph only:**
+```typescript
+// Pages by type
+const pages = await logseq.DB.datascriptQuery(`
+  [:find (pull ?p [*])
+   :where [?p :block/type "page"]]
+`)
+
+// Class/Tag pages
+const classes = await logseq.DB. datascriptQuery(`
+  [:find (pull ?p [*])
+   :where [?p : block/type "class"]]
+`)
+
+// Recently created pages (last 7 days)
+const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000
+const recentPages = await logseq. DB.datascriptQuery(`
+  [:find (pull ?p [*])
+   :where
+   [?p :block/type "page"]
+   [?p :logseq.property/created-at ?created]
+   [(> ? created ${sevenDaysAgo})]]
+`)
+```
+
+#### 4.3 Reference Queries
+
+**Both File Graph and DB Graph:**
+```typescript
+// Blocks referencing a specific page
+const refs = await logseq.DB. datascriptQuery(`
+  [:find (pull ?b [*])
+   :in $ ?page
+   :where
+   [? p :block/name ?page]
+   [?b :block/refs ?p]]
+`, `"target-page"`)
+
+// Block references (backlinks)
+const blockRefs = await logseq. DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :in $ ?uuid
+   :where
+   [? target :block/uuid ?uuid]
+   [?b :block/refs ?target]]
+`, `#uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`)
+```
+
+#### 4.4 Property Queries
+
+**File Graph:**
+```typescript
+// Blocks with specific property
+const withProp = await logseq. DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :where
+   [?b :block/properties ?props]
+   [(get ?props :status)]]
+`)
+
+// Property equals specific value
+const statusDone = await logseq.DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :where
+   [?b :block/properties ?props]
+   [(get ?props :status) ?v]
+   [(= ?v "done")]]
+`)
+```
+
+**DB Graph:**
+```typescript
+// Blocks with property value (properties are direct attributes)
+const withProp = await logseq.DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :where
+   [?b : user. property/status ?v]]
+`)
+
+// Query by property ident
+const results = await logseq.DB. datascriptQuery(`
+  [:find (pull ?b [*])
+   :where
+   [? prop :db/ident : user.property/status]
+   [?b ? prop ? v]
+   [?v :block/title "done"]]
+`)
+```
+
+#### 4.5 Content Search
+
+**File Graph:**
+```typescript
+// Content contains keyword
+const results = await logseq.DB. datascriptQuery(`
+  [:find (pull ?b [*])
+   :where
+   [? b :block/content ?c]
+   [(clojure.string/includes?  ?c "keyword")]]
+`)
+```
+
+**DB Graph:**
+```typescript
+// Title contains keyword
+const results = await logseq.DB.datascriptQuery(`
+  [:find (pull ?b [*])
+   :where
+   [?b :block/title ?t]
+   [(clojure.string/includes?  ?t "keyword")]]
+`)
+```
+
+---
+
+### 5.  Predicate Functions
+
+```clojure
+;; Comparison
+[(> ? d 20250101)]
+[(>= ?d 20250101)]
+[(< ?d 20250131)]
+[(<= ?d 20250131)]
+[(= ?v "value")]
+[(not= ?v "value")]
+
+;; String operations
+[(clojure.string/includes? ? s "text")]
+[(clojure.string/starts-with? ?s "prefix")]
+[(clojure.string/ends-with? ?s "suffix")]
+[(clojure.string/blank? ?s)]
+
+;; Collection operations
+[(contains? #{"A" "B" "C"} ?v)]
+
+;; Null checks
+[(some?  ?v)]
+[(nil? ?v)]
+
+;; Math operations
+[(+ ?a ?b) ? sum]
+[(- ?a ?b) ?diff]
+[(* ?a ?b) ?product]
+```
+
+---
+
+### 6. NOT and OR Clauses
+
+```clojure
+;; NOT - exclude conditions
+[: find (pull ?b [*])
+ :where
+ [? b :block/marker ? m]
+ (not [?b :block/priority "C"])]
+
+;; OR - any condition matches
+[:find (pull ?b [*])
+ :where
+ (or [?b :block/marker "TODO"]
+     [?b : block/marker "DOING"])]
+
+;; OR with AND (or-join)
+[:find (pull ?b [*])
+ :where
+ (or-join [? b]
+   (and [?b :block/marker "TODO"]
+        [?b :block/priority "A"])
+   (and [?b :block/marker "DOING"]))]
+```
+
+---
+
+### 7. Aggregation Queries
+
+```typescript
+// Count
+const count = await logseq. DB.datascriptQuery(`
+  [:find (count ?b)
+   :where [?b : block/marker "TODO"]]
+`)
+
+// Group by page
+const grouped = await logseq.DB. datascriptQuery(`
+  [:find ? name (count ?b)
+   :where
+   [?b :block/marker "TODO"]
+   [?b :block/page ?p]
+   [?p :block/name ?name]]
+`)
+
+// Min/Max
+const range = await logseq.DB. datascriptQuery(`
+  [:find (min ?d) (max ?d)
+   :where
+   [?p :block/journal? true]
+   [?p :block/journal-day ?d]]
+`)
+```
+
+---
+
+### 8. Cross-Compatible Plugin Example
+
+```typescript
+import '@logseq/libs'
+
+async function detectGraphType(): Promise<'db' | 'file'> {
+  const result = await logseq.DB. datascriptQuery(`
+    [:find ? type . 
+     :where [_ :block/type ?type]]
+  `)
+  return result ?  'db' : 'file'
+}
+
+async function queryTodos(graphType: 'db' | 'file') {
+  if (graphType === 'file') {
+    return await logseq.DB.datascriptQuery(`
+      [:find (pull ?b [*])
+       :where [?b :block/marker "TODO"]]
+    `)
+  } else {
+    return await logseq.DB.datascriptQuery(`
+      [:find (pull ?b [*])
+       :where
+       [?b :block/tags ?tag]
+       [?tag :db/ident :logseq.class/Todo]]
+    `)
+  }
+}
+
+async function queryBlockContent(uuid: string, graphType: 'db' | 'file') {
+  const attr = graphType === 'file' ? ': block/content' : ':block/title'
+  return await logseq.DB.datascriptQuery(`
+    [:find ? content .
+     :in $ ?uuid
+     :where
+     [? b : block/uuid ?uuid]
+     [?b ${attr} ?content]]
+  `, `#uuid "${uuid}"`)
+}
+
+async function main() {
+  const graphType = await detectGraphType()
+  console.log('Graph type:', graphType)
+
+  logseq.Editor.registerSlashCommand('Query TODOs', async () => {
+    try {
+      const results = await queryTodos(graphType)
+      const todos = results?. flat() || []
+      
+      if (todos.length) {
+        const content = todos.map(t => `- ((${t.uuid}))`).join('\n')
+        await logseq.Editor.insertAtEditingCursor(content)
+      } else {
+        await logseq.UI.showMsg('No TODOs found', 'warning')
+      }
+    } catch (e) {
+      console.error('Query failed:', e)
+      await logseq.UI.showMsg('Query failed', 'error')
+    }
+  })
+}
+
+logseq.ready().then(main)
+```
+
+---
+
+### 9. Important Notes
+
+1. **Page names are lowercase**:  `:block/name` stores lowercase names; convert before querying
+2. **Parameter format**:
+    - Strings: `"value"`
+    - Numbers: `123`
+    - UUIDs: `#uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`
+    - Keywords: `:keyword`
+    - Collections: `["a" "b" "c"]`
+3. **Flatten results**: Query results usually need `.flat()` processing
+4. **Error handling**: Always wrap queries in try-catch
+5. **Performance**: Use `pull` with specific attributes instead of `[*]` when possible
+6. **Graph compatibility**: Check graph type before using graph-specific attributes