// Type-32/obsidian-bases-parser

Obsidian Bases Parser

A complete TypeScript implementation for Obsidian Bases (.base files) including schema definitions, lexer, parser, evaluator, and reactive query system.

AlphaFeb 2, 2026View on GitHub

Obsidian Bases TypeScript Schema

A complete TypeScript implementation for Obsidian Bases (.base files) including schema definitions, lexer, parser, evaluator, and reactive query system.

Based on the official Obsidian Bases documentation: https://help.obsidian.md/bases

Overview

This package provides:

  1. Complete TypeScript Schema - Type definitions for all Obsidian Bases constructs
  2. Lexer & Parser - Tokenize and parse filter/formula expressions into AST
  3. Evaluator - Execute parsed expressions against file/note contexts
  4. Builder APIs - Fluent interfaces for creating bases and filters
  5. Preset Utilities - Common filter patterns, formulas, and summaries
  6. YAML Serialization - Convert base objects to valid YAML
  7. Reactive Query System - Vue-integrated reactive querying with useBaseQuery() composable
  8. YAML Reader - Parse .base files from YAML strings

Installation

# Using npm
npm install @type32/obsidian-bases-parser

# Using yarn
yarn add @type32/obsidian-bases-parser

# Using pnpm
pnpm add @type32/obsidian-bases-parser

# Using bun
bun add @type32/obsidian-bases-parser

Dependencies

This package requires:

  • vue ^3.5.27 - For reactive system
  • js-yaml ^4.1.1 - For efficient YAML parsing and serialization

Quick Start

Creating a Base (Static)

import { createBase, PresetFilters, PresetFormulas } from './index';

const myBase = createBase()
  .withFilters(PresetFilters.byTag('project'))
  .addFormula('days_old', PresetFormulas.daysOld())
  .addFormula('status_icon', PresetFormulas.statusIcon())
  .addTableView('Active Projects', {
    order: ['file.name', 'status', 'formula.status_icon'],
    filters: PresetFilters.byStatus('active'),
  })
  .build();
import { createReactiveBase, PresetFilters, PresetFormulas } from './index';

const myBase = createReactiveBase()
  .addFilter(PresetFilters.byTag('project'))
  .addFormula('days_old', PresetFormulas.daysOld())
  .addFormula('status_icon', PresetFormulas.statusIcon())
  .addTableView('Active Projects', {
    order: ['file.name', 'status', 'formula.status_icon'],
    filters: PresetFilters.byStatus('active'),
  });

// Can be modified later!
myBase.addFormula('priority_label', PresetFormulas.priorityLabel());
myBase.setViewOrder('Active Projects', ['formula.priority_label', 'file.name']);

// Convert to YAML
const yaml = myBase.toYAML();

Parsing and Evaluating Filters

import { parseFilter, parseFilterExpression, evaluateFilter, validateFilter } from './index';

// Parse a filter expression into a FilterExpObject with extracted components
const parsed = parseFilterExpression('file.hasTag("book") && status == "reading"');

// Access extracted components for programmatic use
if (parsed.fileMethods.includes('hasTag')) {
  console.log('Tags referenced:', parsed.referencedTags); // ['book']
}

if (parsed.noteProperties.includes('status')) {
  console.log('Filter uses status property');
}

for (const comparison of parsed.comparisons) {
  console.log(`${comparison.left.name} ${comparison.operator} ${comparison.right.raw}`);
}

// Parse into AST (lower-level)
const result = parseFilter('file.hasTag("book") && status == "reading"');

// Evaluate against a context
const context = {
  file: {
    name: 'My Book.md',
    tags: ['book', 'reading'],
    // ... other file properties
  },
  note: {
    status: 'reading',
  },
};

const evaluation = evaluateFilter('file.hasTag("book")', context);
console.log(evaluation); // { type: 'BOOLEAN', value: true }

// Validate a filter
const validation = validateFilter({
  and: ['status == "done"', 'priority > 3'],
});

Serializing to YAML

import { serializeToYAML } from './index';

const yaml = serializeToYAML(myBase);
console.log(yaml);
// Output:
// filters:
//   and:
//     - file.hasTag("project")
// formulas:
//   days_old: '((now() - file.ctime) / 86400000).round(0)'
// ...

Note: YAML parsing and serialization is powered by js-yaml for robust, efficient, and standards-compliant handling of YAML files.

Schema Overview

Filter System

Filters can be:

  • Simple expressions: 'status == "done"'
  • Filter objects: { and: [...], or: [...], not: [...] }
  • Nested combinations: Complex boolean logic
import { Filter, FilterObject } from './index';

const simpleFilter: Filter = 'status == "done"';

const complexFilter: Filter = {
  or: [
    'file.hasTag("important")',
    {
      and: [
        'priority >= 3',
        {
          not: ['file.hasTag("archived")'],
        },
      ],
    },
  ],
};

View System

Views define how data is displayed:

import { View, TableView, CardsView, ListView, MapView } from './index';

const tableView: TableView = {
  type: 'table',
  name: 'My Table',
  order: ['file.name', 'status', 'due'],
  filters: { and: ['status != "done"'] },
  groupBy: { property: 'status', direction: 'ASC' },
  summaries: { priority: 'Average' },
  sort: [
    { property: 'file.name', direction: 'ASC' },
    { property: 'due', direction: 'DESC' },
  ],
  columnSize: {
    'file.name': 300,
    'status': 150,
  },
};

const cardsView: CardsView = {
  type: 'cards',
  name: 'Project Cards',
  order: ['priority', 'file.name'],
  cardSize: 250,
  image: 'cover',
  imageFit: 'cover',
  imageAspectRatio: 1.4,
};

Extended View Properties

Table Views:

  • sort - Separate sorting configuration (different from display order)
  • columnSize - Column width configuration (property name to width in pixels)

Cards Views:

  • cardSize - Card dimensions in pixels
  • image - Image source ('cover', 'first', or property name)
  • imageFit - Image fitting mode ('cover', 'contain', 'fill', or '')
  • imageAspectRatio - Image aspect ratio (number)

Formula System

Formulas compute values from properties:

import { Formulas } from './index';

const formulas: Formulas = {
  days_until_due: 'if(due, ((date(due) - today()) / 86400000).round(0), "")',
  is_overdue: 'if(due, date(due) < today() && status != "done", false)',
  priority_label: 'if(priority == 1, "๐Ÿ”ด High", if(priority == 2, "๐ŸŸก Medium", "๐ŸŸข Low"))',
};

Parser Features

The parser supports all Obsidian Bases syntax:

Operators

  • Comparison: ==, !=, >, <, >=, <=
  • Arithmetic: +, -, *, /, %
  • Boolean: &&, ||, !

Literals

  • Strings: "hello", 'world'
  • Numbers: 42, 3.14
  • Booleans: true, false
  • Durations: "1d", "2h", "3w"
  • Regular Expressions: /pattern/flags

Property Access

  • Dot notation: file.name, note.status
  • Bracket notation: properties["key"], tags[0]
  • Prefixed: file.*, note.*, formula.*, this.*

Function Calls

  • Global: now(), today(), date("2024-01-01")
  • Method: file.hasTag("tag"), string.contains("sub")
  • Chained: file.ctime.format("YYYY-MM-DD")

Special Syntax

  • Conditional: if(condition, trueValue, falseValue)
  • Date arithmetic: now() + "1d", today() - "1w"

Preset Utilities

Filters

import { PresetFilters } from './index';

PresetFilters.byTag('project');           // 'file.hasTag("project")'
PresetFilters.byExtension('md');          // 'file.ext == "md"'
PresetFilters.inFolder('Notes');          // 'file.inFolder("Notes")'
PresetFilters.modifiedWithin(7);          // 'file.mtime > now() - "7d"'
PresetFilters.byProperty('status', 'active'); // 'status == "active"'

Formulas

import { PresetFormulas } from './index';

PresetFormulas.daysUntilDue();            // Days until due date
PresetFormulas.isOverdue();               // Check if overdue
PresetFormulas.lastModified();            // Relative modification time
PresetFormulas.priorityLabel();           // Priority with icons
PresetFormulas.statusIcon();              // Status with icons
PresetFormulas.formatCurrency('price');   // Format as currency

Summaries

import { PresetSummaries } from './index';

PresetSummaries.customAverage(2);         // Average with rounding
PresetSummaries.countFilled();            // Count non-empty values
PresetSummaries.percentTrue();            // Percentage of true values
PresetSummaries.uniqueJoin(', ');         // Join unique values

Builder APIs

Base Builder

import { createBase } from './index';

const base = createBase()
  .withFilters({ and: ['file.hasTag("task")'] })
  .addFormula('days_old', '((now() - file.ctime) / 86400000).round(0)')
  .configureProperty('status', { displayName: 'Status' })
  .addTableView('Active Tasks', {
    order: ['file.name', 'status'],
    filters: { and: ['status != "done"'] },
  })
  .addCardsView('Task Cards', {
    order: ['file.name', 'status'],
  })
  .build();

Filter Builder

import { createFilter, and, or, not } from './index';

const filter = createFilter()
  .and('file.hasTag("project")')
  .and('file.ext == "md"')
  .not('file.hasTag("archived")')
  .build();

// Or start with a specific operator
const filter2 = and(
  'file.hasTag("important")',
  or('priority == 1', 'priority == 2')
);

Complete Base Example

import {
  createBase,
  PresetFilters,
  PresetFormulas,
  serializeToYAML,
} from './index';

const taskBase = createBase()
  .withFilters(
    and(
      PresetFilters.byTag('task'),
      PresetFilters.byExtension('md')
    )
  )
  .addFormula('days_until_due', PresetFormulas.daysUntilDue())
  .addFormula('is_overdue', PresetFormulas.isOverdue())
  .addFormula('priority_label', PresetFormulas.priorityLabel())
  .configureProperty('formula.days_until_due', { displayName: 'Days Left' })
  .addTableView('Active Tasks', {
    order: ['file.name', 'formula.priority_label', 'due'],
    filters: not(PresetFilters.byStatus('done')),
    groupBy: { property: 'status', direction: 'ASC' },
  })
  .addTableView('Completed Tasks', {
    order: ['completed_date', 'file.name'],
    filters: PresetFilters.byStatus('done'),
  })
  .build();

console.log(serializeToYAML(taskBase));

File Structure

obsidian-bases-parser/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ index.ts                    # Main exports
โ”‚   โ”œโ”€โ”€ obsidian-bases-schema.ts    # Type definitions
โ”‚   โ”œโ”€โ”€ obsidian-bases-parser.ts    # Lexer, parser, evaluator
โ”‚   โ”œโ”€โ”€ obsidian-bases-utils.ts     # Builders, presets, serialization
โ”‚   โ”œโ”€โ”€ obsidian-bases-reactive.ts  # Reactive system (Vue + ReactiveBase)
โ”‚   โ””โ”€โ”€ obsidian-bases-example.ts   # Usage examples
โ”œโ”€โ”€ examples/
โ”‚   โ”œโ”€โ”€ reactive-base-example.ts    # Comprehensive reactive base examples
โ”‚   โ””โ”€โ”€ simple-reactive-demo.ts     # Simple demonstration
โ”œโ”€โ”€ llms.txt                        # LLM overview
โ”œโ”€โ”€ llms-full.txt                   # Complete LLM documentation
โ””โ”€โ”€ README.md                       # This file

API Reference

Schema Types

TypeDescription
ObsidianBaseComplete base file structure
FilterFilter expression or filter object
FilterObjectRecursive and/or/not filter structure
FilterExpObjectParsed filter with extracted components
PropertyReferenceProperty reference with prefix and name
FunctionCallFunction call with name and arguments
ComparisonOperationComparison with operator and operands
ViewView configuration (table, cards, list, map)
FormulasFormula definitions
PropertiesProperty display configurations
SummariesCustom summary formulas

FilterExpObject Properties

When you parse a filter expression using parseFilterExpression(), you get a FilterExpObject with these accessible properties:

PropertyTypeDescription
rawstringThe original filter expression
expressionTypeExpressionTypeType: 'comparison', 'boolean', 'function_call', etc.
astASTExpressionThe parsed AST
propertiesPropertyReference[]All property references
functionCallsFunctionCall[]All function calls
comparisonsComparisonOperation[]All comparison operations
literalsLiteralValue[]All literal values
hasBooleanOperatorsbooleanHas &&, `
hasDateArithmeticbooleanHas date arithmetic like now() - "7d"
usesThisReferencebooleanUses this keyword
fileMethodsstring[]File methods used (e.g., 'hasTag', 'inFolder')
notePropertiesstring[]Note properties referenced
filePropertiesstring[]File properties referenced
formulaPropertiesstring[]Formula properties referenced
referencedTagsstring[]Tags from file.hasTag() calls
referencedFoldersstring[]Folders from file.inFolder() calls
isValidbooleanWhether parsing succeeded
errorsstring[]Parse errors if any

Parser Classes

ClassDescription
LexerTokenizes expressions into tokens
ParserBuilds AST from tokens
EvaluatorExecutes AST against context
FilterObjectParserParses recursive filter objects
FilterExpressionParserParses filter strings into FilterExpObject
ValidatorValidates filter/formula syntax

Builder Classes

ClassDescription
BaseBuilderFluent API for creating bases
FilterBuilderFluent API for building filters

Utility Functions

FunctionDescription
createBase()Create a new base builder
createFilter()Create a new filter builder
parseFilter()Parse a filter to AST
parseFilterExpression()Parse filter to FilterExpObject with components
parseFormula()Parse a formula to AST
evaluateFilter()Evaluate a filter against context
evaluateFormula()Evaluate a formula against context
validateFilter()Validate filter syntax
validateFormula()Validate formula syntax
serializeToYAML()Convert base to YAML
readBase()Parse YAML string into ObsidianBase

Reactive System (Vue Integration)

Class/FunctionDescription
ReactiveBaseReactive base object with modification methods
ReactiveBaseQueryReactive query class with automatic re-evaluation
useBase()Unified composable for complete base management
useBaseQuery()Vue composable for reactive queries
useBaseView()Vue composable for single view queries
createReactiveBase()Create a new ReactiveBase instance
createReactiveBaseFromYAML()Create a ReactiveBase from YAML string
createBaseQuery()Create a ReactiveBaseQuery instance
createBaseQueryFromYAML()Create a query from YAML string

Reactive Query System

The reactive query system integrates with Vue's reactivity system to provide automatic updates when source data changes.

Reactive Base Object

The ReactiveBase class provides a reactive, modifiable base configuration with methods to adjust filters, views, formulas, and more in real-time.

import { createReactiveBase, ReactiveBaseQuery } from './index';

// Create a reactive base
const base = createReactiveBase();

// Add filters dynamically
base.addFilter('file.hasTag("task")');
base.addFilter('status != "done"');

// Add formulas
base.addFormula('days_old', '((now() - file.ctime) / 86400000).round(0)');

// Add views
base.addTableView('Active Tasks', {
  order: ['file.name', 'formula.days_old'],
  filters: 'priority > 2',
});

// Use with reactive query
const query = new ReactiveBaseQuery(source, base.ref);
const results = query.getViewResults('Active Tasks');

// Modify the base - results automatically update!
base.setViewFilters('Active Tasks', 'priority > 1');
base.addFormula('priority_label', 'if(priority == 1, "High", "Low")');
base.setViewOrder('Active Tasks', ['formula.priority_label', 'file.name']);

// Convert to YAML for saving
const yaml = base.toYAML();

ReactiveBase API

The ReactiveBase class provides comprehensive methods for modifying base configurations:

Filters

  • setFilters(filters) - Replace all global filters
  • addFilter(expression) - Add a filter (AND with existing)
  • clearFilters() - Remove all global filters

Formulas

  • addFormula(name, expression) - Add or update a formula
  • removeFormula(name) - Remove a formula
  • setFormulas(formulas) - Replace all formulas
  • clearFormulas() - Remove all formulas

Properties

  • configureProperty(name, config) - Configure property display settings
  • removePropertyConfig(name) - Remove property configuration
  • setProperties(properties) - Replace all property configurations
  • clearProperties() - Remove all property configurations

Summaries

  • addSummary(name, expression) - Add or update a custom summary
  • removeSummary(name) - Remove a summary
  • setSummaries(summaries) - Replace all summaries
  • clearSummaries() - Remove all summaries

Views

  • addView(view) - Add a view
  • addTableView(name, options) - Add a table view
  • addCardsView(name, options) - Add a cards view
  • addListView(name, options) - Add a list view
  • addMapView(name, options) - Add a map view
  • removeView(name) - Remove a view by name
  • updateView(name, updater) - Update a view by name
  • getView(name) - Get a view configuration
  • setViewFilters(viewName, filters) - Update view filters
  • setViewOrder(viewName, order) - Update view order
  • setViewLimit(viewName, limit) - Update view limit
  • setViewGroupBy(viewName, groupBy) - Update view grouping
  • clearViews() - Remove all views

Utility Methods

  • toYAML() - Convert to YAML string
  • fromYAML(yaml) - Load from YAML string
  • clone() - Create a copy of the base
  • reset() - Reset to empty base
  • toObject() - Get plain object (non-reactive copy)

Dynamic Base Modifications

The key advantage of ReactiveBase is that all modifications are reactive and automatically propagate to any ReactiveBaseQuery instances using it:

import { ref } from 'vue';
import { createReactiveBase, ReactiveBaseQuery, BaseSource } from './index';

const source = ref<BaseSource[]>([/* your data */]);
const base = createReactiveBase();

// Initial setup
base.addFilter('file.hasTag("project")')
    .addTableView('Projects', { order: ['file.name'] });

// Create query
const query = new ReactiveBaseQuery(source, base.ref);
const results = query.getViewResults('Projects');

// Later: Add a new formula
base.addFormula('status_icon', 'if(status == "done", "โœ…", "โณ")');

// Update view to use new formula
base.setViewOrder('Projects', ['formula.status_icon', 'file.name']);

// Results automatically update with new formula and ordering!
console.log(results.value.items);

// Later: Change filters
base.clearFilters();
base.addFilter('file.hasTag("active-project")');

// Results automatically re-filter!
console.log(results.value.items);

Loading from YAML

You can create a reactive base from existing YAML:

import { createReactiveBaseFromYAML } from './index';

const yaml = `
filters:
  and:
    - file.hasTag("task")
formulas:
  days_old: '((now() - file.ctime) / 86400000).round(0)'
views:
  - type: table
    name: "Tasks"
    order:
      - file.name
`;

const base = createReactiveBaseFromYAML(yaml);

// Now modify it
base.addFormula('priority_label', 'if(priority == 1, "High", "Low")');
base.addTableView('High Priority', {
  filters: 'priority == 1',
  order: ['file.name'],
});

// Save back to YAML
const updatedYaml = base.toYAML();

Builder Pattern vs Reactive Base

Static Builder (Old Approach)

import { createBase } from './index';

// Build once
const base = createBase()
  .addFilter('file.hasTag("task")')
  .addFormula('days_old', '((now() - file.ctime) / 86400000).round(0)')
  .addTableView('Tasks', { order: ['file.name'] })
  .build(); // Returns a plain object

// โŒ Cannot modify after build
// You would need to rebuild from scratch

// Use with query
const query = new ReactiveBaseQuery(source, base);

Reactive Base (New Approach)

import { createReactiveBase } from './index';

// Create reactive base
const base = createReactiveBase()
  .addFilter('file.hasTag("task")')
  .addFormula('days_old', '((now() - file.ctime) / 86400000).round(0)')
  .addTableView('Tasks', { order: ['file.name'] });
// No .build() needed - stays reactive!

// โœ… Can modify anytime
base.addFormula('priority_label', 'if(priority == 1, "High", "Low")');
base.setViewOrder('Tasks', ['formula.priority_label', 'file.name']);

// Use with query - changes propagate automatically
const query = new ReactiveBaseQuery(source, base.ref);
const results = query.getViewResults('Tasks');

// Modifications automatically update results!
base.setViewFilters('Tasks', 'status != "done"');
console.log(results.value.items); // Automatically filtered!

Key Differences

FeatureStatic BuilderReactive Base
ModificationโŒ Immutable after .build()โœ… Fully mutable
ReactivityโŒ No automatic updatesโœ… Automatic updates
YAML Exportโœ… Via serializeToYAML()โœ… Via .toYAML()
YAML Importโœ… Via readBase()โœ… Via .fromYAML()
Use CaseStatic configurationsDynamic, real-time updates
Return TypePlain ObsidianBase objectReactiveBase instance

Recommendation: Use ReactiveBase when you need dynamic configuration changes, especially in interactive applications. Use the static builder when you have fixed configurations that won't change.

Reactive Query System

The reactive query system integrates with Vue's reactivity system to provide automatic updates when source data changes.

Basic Usage

import { ref } from 'vue';
import { readBase, useBaseQuery, BaseSource } from './index';

// Define your data type
interface Task {
  title: string;
  assignee: string;
}

// Create reactive source
const source = ref<BaseSource<Task>[]>([
  {
    id: '1',
    name: 'Task 1.md',
    basename: 'Task 1',
    tags: ['task', 'urgent'],
    properties: { status: 'todo', priority: 1 },
    data: { title: 'Complete docs', assignee: 'Alice' },
  },
  // ... more items
]);

// Read base from YAML
const yaml = `
filters:
  and:
    - file.hasTag("task")
formulas:
  priority_label: 'if(priority == 1, "๐Ÿ”ด High", "๐ŸŸก Medium")'
views:
  - type: table
    name: "Active Tasks"
    filters:
      and:
        - status != "done"
    order: [file.name, formula.priority_label]
`;

const base = readBase(yaml);

// Create reactive query
const { getViewResults, source: reactiveSource } = useBaseQuery(source, base);

// Get reactive results
const activeTasks = getViewResults('Active Tasks');

// Access in template or computed
// activeTasks.value.items - filtered, sorted items
// activeTasks.value.totalCount - total before limit
// activeTasks.value.groups - grouped items (if groupBy specified)
// activeTasks.value.summaries - calculated summaries

Vue Component Example

<template>
  <div>
    <h1>Tasks ({{ taskCount }})</h1>
    <ul>
      <li v-for="task in tasks" :key="task.id">
        {{ task.properties?.priority_label }} - {{ task.data?.title }}
      </li>
    </ul>
    <button @click="addTask">Add Task</button>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import { useBaseView, readBase, BaseSource } from './index';

interface Task {
  title: string;
}

const source = ref<BaseSource<Task>[]>([/* ... */]);

const base = readBase(`
  formulas:
    priority_label: 'if(priority == 1, "๐Ÿ”ด", "๐ŸŸข")'
  views:
    - type: list
      name: "Tasks"
      order: [formula.priority_label]
`);

// useBaseView returns ComputedRefs that auto-update
const { items: tasks, totalCount: taskCount } = useBaseView(
  source,
  base,
  'Tasks'
);

function addTask() {
  source.value.push({
    id: String(Date.now()),
    name: 'New Task.md',
    properties: { priority: 1 },
    data: { title: 'New Task' },
  });
  // tasks and taskCount automatically update!
}
</script>

useBase() - Unified Base Management

The useBase() composable provides a complete, unified interface for managing an entire base instance reactively. It combines base configuration, source data management, and querying into one convenient API.

import { ref } from 'vue';
import { useBase, BaseSource } from './index';

interface Task {
  title: string;
  description: string;
}

const {
  // Base & State
  base,
  isLoaded,
  hasChanges,
  state,
  
  // Load/Save
  load,
  save,
  
  // Source Management
  source,
  addItem,
  updateItem,
  removeItem,
  
  // Query Access
  getViewResults,
  viewNames,
  
  // Shortcuts
  addView,
  addFormula,
  setFilters,
} = useBase<Task>({
  source: ref([]),
  trackChanges: true,
});

// Load from YAML
load(yamlString);

// Modify base
addFormula('priority_label', 'if(priority == 1, "High", "Low")');
addView({ type: 'table', name: 'Tasks', order: ['file.name'] });

// Add data
addItem({
  id: '1',
  name: 'Task.md',
  properties: { status: 'todo' },
  data: { title: 'My Task', description: 'Details' },
});

// Query
const tasks = getViewResults('Tasks');
console.log(tasks.value.items);

// Save
const yaml = save();

Features

  • Unified API - One composable for everything
  • Reactive - All changes propagate automatically
  • Change Tracking - Know when there are unsaved changes
  • Source Management - CRUD operations on source data
  • Full Query Access - All reactive query features
  • Load/Save - YAML import/export
  • State & Statistics - View counts, item counts, etc.

Auto-save Pattern

const { save, hasChanges } = useBase({
  base: yamlString,
  trackChanges: true,
});

// Implement auto-save with debouncing
let saveTimeout: NodeJS.Timeout | null = null;

watch(hasChanges, (changed) => {
  if (changed) {
    if (saveTimeout) clearTimeout(saveTimeout);
    
    saveTimeout = setTimeout(async () => {
      const yaml = save();
      await writeFile('config.base', yaml);
    }, 1000); // Save after 1s of no changes
  }
});

See examples/use-base-example.ts and examples/use-base-vue-component.vue for complete examples.

Using ReactiveBaseQuery Class

import { ref } from 'vue';
import { ReactiveBaseQuery, readBase, BaseSource } from './index';

const source = ref<BaseSource[]>([/* ... */]);
const base = readBase(yamlString);

// Create query instance
const query = new ReactiveBaseQuery(source, base, { debug: true });

// Get computed results for different views
const tableResults = query.getViewResults('Table View');
const cardResults = query.getViewResults('Card View');

// Access reactive data
console.log(tableResults.value.items);
console.log(tableResults.value.totalCount);
console.log(tableResults.value.summaries);

// Update source - results auto-update
query.setSource([...newItems]);

// Or update base - results auto-update
query.setBase(newBase);

// Force refresh
query.refresh();

BaseSource Type

The BaseSource<T> type is flexible and allows custom data:

interface BaseSource<T = void> {
  // Obsidian-specific fields (all optional)
  id?: string;
  name?: string;           // File name (e.g., "My Note.md")
  basename?: string;       // Without extension (e.g., "My Note")
  path?: string;           // Full path (e.g., "Notes/My Note.md")
  folder?: string;         // Parent folder (e.g., "Notes")
  ext?: string;            // Extension (e.g., "md")
  size?: number;           // File size in bytes
  ctime?: Date | string;   // Created time
  mtime?: Date | string;   // Modified time
  tags?: string[];         // Tags
  links?: string[];        // Internal links
  backlinks?: string[];    // Backlinks
  embeds?: string[];       // Embeds
  properties?: Record<string, unknown>;  // Frontmatter properties

  // Your custom data
  data?: T;
}

Custom Formula Functions

const query = new ReactiveBaseQuery(source, base, {
  customFunctions: {
    calculateScore: (priority: number, complexity: number) => {
      return priority * complexity * 10;
    },
    formatDate: (date: Date, format: string) => {
      // Your date formatting logic
      return formattedDate;
    },
  },
});

License

MIT

References