chore: update version to 0.2.11 and add developer notes for Logseq DB properties

This commit is contained in:
charlie
2026-01-04 16:29:02 +08:00
parent 4b7d6c73c8
commit a2bfb39632
3 changed files with 700 additions and 1 deletions

View File

@@ -0,0 +1,406 @@
# Logseq DB Properties SDK Reference
## Overview
Logseq has two storage modes:
- **File-based**: Properties stored as `key:: value` in markdown files
- **DB-based**: Properties stored in SQLite with structured schema, types, and relationships
This guide covers **DB-based properties API** for plugin development.
---
## Core Concepts
### 1. Property Schema (Global Definition)
Properties must be defined globally before use. Think of it as creating columns in a database.
### 2. Tags as Classes
Tags act as classes/types. Pages/blocks can be tagged to inherit property schemas.
### 3. Property Values
After defining properties, assign values to specific blocks/pages.
---
## API Reference
### Property Schema APIs
#### `upsertProperty(name, options)`
Define or update a property schema globally.
**Parameters:**
- `name` (string): Property name
- `options` (object):
- `type`: `'default'` | `'node'` | `'date'` | `'number'`
- `cardinality`: `'one'` | `'many'` (for `node` type)
**Returns:** Property object with `uuid`
**Example:**
```typescript
// Simple text property
await logseq.Editor.upsertProperty('author', { type: 'default' })
// Reference property (links to other pages)
await logseq.Editor.upsertProperty('tags', {
type: 'node',
cardinality: 'many'
})
// Date property
await logseq.Editor.upsertProperty('publishedDate', { type: 'date' })
```
---
### Tag (Class) Management APIs
#### `createTag(name, options? )`
Create a tag as a class/type definition.
**Parameters:**
- `name` (string): Tag name
- `options` (optional object): `{ uuid: string }`
**Returns:** Tag object with `uuid` and `id`
**Example:**
```typescript
const bookTag = await logseq.Editor.createTag('book')
const articleTag = await logseq.Editor.createTag('article', {
uuid: 'custom-uuid-123'
})
```
#### `getTag(name)`
Retrieve an existing tag by name.
**Returns:** Tag object or `null`
**Example:**
```typescript
const tag = await logseq.Editor. getTag('book')
if (! tag) {
// Tag doesn't exist, create it
}
```
#### `addTagProperty(tagUUID, propertyName)`
Add a property to a tag's schema. Pages tagged with this tag will have this property available.
**Parameters:**
- `tagUUID` (string): Tag UUID
- `propertyName` (string): Property name (must be defined via `upsertProperty` first)
**Example:**
```typescript
// Define properties globally
await logseq.Editor. upsertProperty('author', { type: 'default' })
await logseq.Editor.upsertProperty('isbn', { type: 'default' })
// Add properties to 'book' tag
const bookTag = await logseq. Editor.createTag('book')
await logseq.Editor.addTagProperty(bookTag.uuid, 'author')
await logseq.Editor.addTagProperty(bookTag.uuid, 'isbn')
```
---
### Block/Page Property Value APIs
#### `upsertBlockProperty(blockUUID, propertyName, value)`
Set property value for a specific block or page.
**Parameters:**
- `blockUUID` (string): Block or page UUID
- `propertyName` (string): Property name
- `value` (any): Property value
- For `node` type with `cardinality: 'many'`: Use array of page IDs
- For `node` type with `cardinality: 'one'`: Use single page ID
- For other types: Use primitive values
**Example:**
```typescript
const page = await logseq.Editor.getPage('my-page-uuid')
// Set simple value
await logseq.Editor.upsertBlockProperty(page.uuid, 'author', 'John Doe')
// Set number
await logseq.Editor.upsertBlockProperty(page.uuid, 'year', 2024)
// Set single reference
await logseq.Editor. upsertBlockProperty(page.uuid, 'category', categoryPageID)
// Set multiple references
await logseq.Editor.upsertBlockProperty(
page.uuid,
'tags',
[tagPageID1, tagPageID2, tagPageID3]
)
```
#### `addBlockTag(blockUUID, tagUUID)`
Tag a block or page, making it an instance of that tag's class.
**Parameters:**
- `blockUUID` (string): Block or page UUID
- `tagUUID` (string): Tag UUID
**Example:**
```typescript
const page = await logseq. Editor.createPage('Moby Dick')
const bookTag = await logseq.Editor.getTag('book')
await logseq.Editor.addBlockTag(page.uuid, bookTag.uuid)
```
---
### Tag Inheritance
#### Set Tag Parent (Class Inheritance)
Invoke API of `logseq.Editor.setTagExtends`
**Example:**
```typescript
// Create parent tag
const rootTag = await logseq.Editor.createTag('Media')
// Create child tag
const bookTag = await logseq.Editor.createTag('book')
// Set inheritance
await logseq.Editor.addTagExtends(bookTag.uuid, rootTag.uuid)
// Use special property `:logseq.property.class/extends` to create tag hierarchy.
// await logseq.Editor. upsertBlockProperty(
// bookTag.uuid,
// ':logseq.property.class/extends',
// [rootTag.id] // Use . id, not .uuid
// )
```
---
### Special Properties
#### Hide Empty Values
```typescript
const prop = await logseq.Editor. upsertProperty('optionalField', { type: 'default' })
await logseq.Editor.upsertBlockProperty(
prop.uuid,
': logseq.property/hide-empty-value',
true
)
```
---
## Common Patterns
### Pattern 1: Define a Schema with Class Hierarchy
```typescript
async function setupBookSchema() {
// 1. Create root class
const mediaTag = await logseq.Editor.createTag('Media')
// 2. Define common properties
await logseq. Editor.upsertProperty('title', { type: 'default' })
await logseq.Editor.upsertProperty('year', { type: 'number' })
await logseq.Editor.upsertProperty('tags', { type: 'node', cardinality: 'many' })
// 3. Add properties to root class
await logseq. Editor.addTagProperty(mediaTag.uuid, 'title')
await logseq.Editor.addTagProperty(mediaTag.uuid, 'year')
await logseq.Editor. addTagProperty(mediaTag.uuid, 'tags')
// 4. Create subclass
const bookTag = await logseq.Editor.createTag('book')
await logseq.Editor.upsertBlockProperty(
bookTag.uuid,
':logseq.property.class/extends',
[mediaTag.id]
)
// 5. Add book-specific properties
await logseq.Editor.upsertProperty('author', { type: 'default' })
await logseq.Editor.upsertProperty('isbn', { type: 'default' })
await logseq.Editor.addTagProperty(bookTag.uuid, 'author')
await logseq.Editor.addTagProperty(bookTag.uuid, 'isbn')
}
```
### Pattern 2: Create an Instance
```typescript
async function createBookInstance(bookData) {
// 1. Create page with stable UUID
const pageUUID = generateStableUUID(bookData.id)
let page = await logseq.Editor.getPage(pageUUID)
if (!page) {
page = await logseq.Editor.createPage(
bookData.title,
{},
{ customUUID: pageUUID, redirect: false }
)
}
// 2. Tag the page
const bookTag = await logseq.Editor.getTag('book')
await logseq.Editor.addBlockTag(page.uuid, bookTag. uuid)
// 3. Set property values
await logseq. Editor.upsertBlockProperty(page.uuid, 'title', bookData.title)
await logseq.Editor.upsertBlockProperty(page.uuid, 'author', bookData.author)
await logseq.Editor.upsertBlockProperty(page.uuid, 'isbn', bookData.isbn)
await logseq.Editor.upsertBlockProperty(page.uuid, 'year', bookData.year)
return page. uuid
}
```
### Pattern 3: Link Related Entities
```typescript
async function linkBookToCategories(bookPageUUID, categoryKeys) {
// Get or create category pages
const categoryIDs = await Promise.all(
categoryKeys.map(async (key) => {
const catUUID = generateStableUUID(key)
let catPage = await logseq.Editor.getPage(catUUID)
if (!catPage) {
catPage = await logseq.Editor.createPage(key, {}, {
customUUID: catUUID
})
}
return catPage.id // Use . id for references
})
)
// Link book to categories
await logseq.Editor.upsertBlockProperty(
bookPageUUID,
'tags', // Must be 'node' type with 'many' cardinality
categoryIDs
)
}
```
### Pattern 4: Batch Schema Setup from JSON
```typescript
async function setupSchemaFromConfig(config) {
// config = { "book": { "fields": ["author", "isbn"], "parent": "Media" } }
for (const [tagName, tagConfig] of Object.entries(config)) {
let tag = await logseq.Editor.getTag(tagName)
if (!tag) {
tag = await logseq.Editor.createTag(tagName)
// Set parent if specified
if (tagConfig. parent) {
const parentTag = await logseq. Editor.getTag(tagConfig. parent)
if (parentTag) {
await logseq.Editor.upsertBlockProperty(
tag. uuid,
':logseq.property.class/extends',
[parentTag.id]
)
}
}
}
// Add properties
for (const fieldName of tagConfig.fields) {
await logseq.Editor.upsertProperty(fieldName, { type: 'default' })
await logseq.Editor.addTagProperty(tag.uuid, fieldName)
}
}
}
```
---
## Best Practices
### 1. Use Stable UUIDs
Generate deterministic UUIDs from stable identifiers:
```typescript
import { v5 as uuidv5 } from 'uuid'
const NAMESPACE = 'your-plugin-namespace-uuid'
function generateStableUUID(id: string): string {
return uuidv5(id, NAMESPACE)
}
```
### 2. Check Before Creating
Always check if tags/pages exist before creating:
```typescript
let tag = await logseq.Editor.getTag('book')
if (!tag) {
tag = await logseq.Editor.createTag('book')
}
```
### 3. Property Definition Order
1. Define property globally with `upsertProperty`
2. Create tag with `createTag`
3. Add property to tag with `addTagProperty`
4. Create page and tag it with `addBlockTag`
5. Set values with `upsertBlockProperty`
### 4. Use . id for References
When setting `node` type properties, use `.id` not `.uuid`:
```typescript
await logseq.Editor.upsertBlockProperty(
page. uuid,
'category',
categoryPage.id // ✅ Correct
// categoryPage.uuid // ❌ Wrong
)
```
### 5. Error Handling
Wrap API calls in try-catch for robustness:
```typescript
try {
await logseq.Editor.upsertBlockProperty(uuid, 'field', value)
} catch (error) {
console.error(`Failed to set property: ${error}`)
// Handle gracefully
}
```
---
## Real-World Example: Zotero Plugin
See complete implementation: [xyhp915/logseq-zotero-plugin](https://github.com/xyhp915/logseq-zotero-plugin/blob/main/src/handlers.ts#L19-L207)
Key techniques:
- Schema defined from `z_item_types. json` metadata
- Tag hierarchy: `Zotero``book`, `article`, etc.
- Stable UUIDs via `v5(itemKey, namespace)`
- Relationship properties for collections
- Nested blocks for attachments
---
## Reference
- **Type Definitions**: See `@logseq/libs` package
- **Examples**: [logseq-zotero-plugin/handlers.ts](https://github.com/xyhp915/logseq-zotero-plugin/blob/main/src/handlers. ts)
- **Search More**: [GitHub Code Search](https://github.com/search?type=code&q=repo:xyhp915/logseq-zotero-plugin+upsertBlockProperty)