1
0
Fork
You've already forked pear
0
forked from Eclosion-Tech/pear
No description
  • TypeScript 79.7%
  • Rust 20%
  • Dockerfile 0.1%
  • Shell 0.1%
2026年06月25日 15:47:04 -04:00
docs groundwork for db inheritance, 0.14.1 2026年06月11日 20:29:53 -04:00
extensions adds ai integrations via users/orcha agents,toolcalling, extensions; further db relation functionality, early inline formula resolution 2026年04月06日 15:55:42 -04:00
packages/pulp pear bridge setup 2026年06月07日 16:42:57 -04:00
server cmd run improvement for pear-bridge v0.17.0 2026年06月25日 10:43:05 -04:00
web fix toolcall spinner 2026年06月25日 12:28:53 -04:00
worker sub change 2026年06月25日 15:47:04 -04:00
.dockerignore fix failing build and updated docker compose 2026年03月10日 13:00:03 -04:00
.env.example Harness assessment pass 1: content model, self-hosted chat, truncation, fidelity 2026年06月01日 14:52:15 -04:00
.gitignore git ignore update 2026年06月16日 11:38:23 -04:00
docker-compose.yml chat file attachments and page/block links 2026年06月11日 17:43:41 -04:00
LICENSE Adds AGPLv3 license 2026年03月07日 20:37:12 -05:00
package.json fix worker build 2026年05月12日 11:20:47 -04:00
PEAR_MVP.md tool fix, doc update 2026年06月10日 13:59:10 -04:00
pnpm-lock.yaml chat file attachments and page/block links 2026年06月11日 17:43:41 -04:00
pnpm-workspace.yaml pull custom node tree into new package @eclosion-tech/pulp 2026年05月26日 18:04:49 -04:00
README.md tool fix, doc update 2026年06月10日 13:59:10 -04:00
ROADMAP.md groundwork for db inheritance, 0.14.1 2026年06月11日 20:29:53 -04:00

Pear

A self-hosted, relational-first Notion alternative built on SpacetimeDB.

Pages and database rows are the same entity — a page viewed in a grid is a row, a row opened fully is a page. Real-time data syncs over SpacetimeDB subscriptions; documents are typed component trees with Yjs-backed rich text, synced live through the database.


Philosophy

  • Pages are database entries. No distinction between a "page" and a "row". Same entity, different lens.
  • Relations are first-class. Linking two databases is a core primitive, not a power-user feature.
  • Self-hostable by default. Your data lives where you put it. No vendor lock-in.
  • The client is sovereign. A workspace is just a server you connect to. No central registry, no platform that can enumerate or revoke your access.

Stack

Layer Technology
Backend / sync SpacetimeDB (Rust module)
Frontend Next.js · React 19 · Tailwind CSS 3
Editor Component-tree editor (packages/pulp) — typed ComponentNode rows in SpacetimeDB, per-block Yjs rich text via ProseMirror. BlockNote retained as a legacy read path for unmigrated pages.
Worker Node (optional) — AI conversations, Orcha task execution, tool runtime
Auth Native SpacetimeDB email/password (default) · Any OIDC provider (optional)
Attachments S3-compatible storage (MinIO in Docker Compose by default)
Containerisation Docker Compose

Features

Workspace & navigation

  • Sidebar — Drag to reorder pages, collapse/expand subtrees, soft delete & restore from trash.
  • Quick switcher⌘K / Ctrl+K fuzzy search by title, plus semantic search over page embeddings when the query is long enough.
  • Breadcrumbs — Hierarchy in the header (workspace → parent → current).
  • Page icons — Emoji per page in the sidebar and headers.

Documents

  • Component-tree editor — every block is a typed ComponentNode row in SpacetimeDB. Slash commands, drag handles, multi-block selection, turn-into, undo/redo.
  • Rich text — per-block Yjs documents (ProseMirror), merged through the server; inline page links (live title sync).
  • Blocks — Headings, images and audio (uploads to blob storage), code, markdown, forms (inputs, buttons, selects), embedded AI conversations.
  • Audio — Record or upload; inline playback; transcript via Web Speech API where the browser supports it.
  • History — Snapshot timeline, preview, one-click restore; snapshots capture the full component tree.
  • Legacy pages — pages created before the component tree still render via BlockNote and migrate in place.

Databases

  • Grid, list & board views — Column resize persisted per view; frozen first column; filters and multi-column sorts; board view grouped by a Select property.
  • Property types — Text, Number, Date, Select (conditional options, per-option colors), Multi-select, Checkbox, URL, Relation, Person, Formula (client-evaluated expressions), Rollup, and AI columns (model-computed values).
  • Editing — Inline cells, fill handle, keyboard navigation (arrows, Tab, Enter), multi-row selection with bulk delete.
  • Rows — Open any row as a full page (routable URL) with properties panel.

Files

  • Attachments — Presigned upload/download via the web app; metadata in SpacetimeDB (Attachment table).

Import & API

  • Custom API endpoints — Expose any database as a versioned REST API (/api/e/{slug}) with field-level mappings, per-property type coercion, optional API-key auth, and an auto-generated OpenAPI 3.1 spec. See docs/API_ENDPOINTS.md.
  • Notion import — The import_notion reducer rebuilds a Notion workspace (pages, databases, properties, content) into an empty workspace; the bundled settings panel drives it through an external OAuth + transform service.

AI & extensions (optional)

  • AI users — Configurable model providers; conversations attached to pages or anchored to individual blocks (@mention a block to start a thread), persisted in SpacetimeDB.
  • Agent edit review — AI edits are bracketed with before/after snapshots; review and accept/reject them per page.
  • Orcha — Job/task coordination tables and reducers embedded in the same module (or point workers at an external Orcha DB). Page-scoped jobs from the in-app panel.
  • Extensions — Install MCP servers and config bundles; permissioned tool execution with audit logging. Built-in workspace tools extension seeded for new databases.

For a finer-grained shipped vs planned list, see ROADMAP.md.


Project structure

Pear/
├── server/
│ ├── spacetimedb/
│ │ └── src/ # Tables, reducers, types (lib.rs + modules; source of truth for API surface)
│ ├── docker/
│ │ └── entrypoint.sh # Starts SpacetimeDB, publishes WASM module
│ └── spacetime.json # Module path, TS bindings output dir
├── web/ # Next.js app (UI, API routes, embeddings, uploads)
├── worker/ # Optional Node worker (AI / Orcha / tools)
├── packages/
│ └── pulp/ # Storage-agnostic component-tree editor library
├── extensions/ # Example / built-in extension manifests
├── docs/ # Public feature docs (e.g. API endpoints)
├── docker-compose.yml
└── pnpm-workspace.yaml

Standalone OSS: this repository is self-contained. Install dependencies with pnpm from the repo root (see below). It does not rely on the Pear Cloud app’s node_modules or any package from a parent monorepo.


Getting started

Prerequisites

That's it for the Docker path. Rust, the SpacetimeDB CLI, and pnpm are only needed for local development.

1. Start the stack

docker compose up -d

Docker builds the Next.js client and the SpacetimeDB WASM module; the entrypoint publishes the module once SpacetimeDB is ready.

Service URL
SpacetimeDB http://localhost:3000
Web client http://localhost:3001

Compose also brings up MinIO (or your configured S3-compatible backend) for attachment uploads — see docker-compose.yml and .env.example for variables.

2. Create an account

Open http://localhost:3001 and register with any email and password. Authentication is handled natively by the SpacetimeDB module — no external auth service required.

3. Optional: AI worker

If you use AI users, conversations, or Orcha task execution, run the worker from the repo root:

pnpm worker

Copy and adjust variables from .env.example (and web/.env.local as needed).


Development (without Docker)

Prerequisites

Backend

# Start a local SpacetimeDB instance
spacetime start
# Build the module and publish
cd server/spacetimedb
cargo build --release --target wasm32-unknown-unknown
cd ..
spacetime publish -s local pear-dev
spacetime call -s local --yes pear-dev run_pending_migrations

Frontend

From the repository root (pnpm workspace — installs web/, worker/, packages/pulp, etc.):

pnpm install
pnpm --filter @pear/web dev # or: cd web && pnpm dev → http://localhost:3001

Do not symlink another project’s node_modules into web/; the lockfile in this repo is the source of truth.

Set the SpacetimeDB URI in web/.env.local if it differs from the default:

NEXT_PUBLIC_SPACETIMEDB_URI=ws://localhost:3000
NEXT_PUBLIC_SPACETIMEDB_DB_NAME=pear-dev

Regenerate TypeScript bindings

Run this after any change to the module source under server/spacetimedb/src/:

cd server
spacetime generate

Output is written to web/src/module_bindings/ (tracked in git).


Authentication

Default — native SpacetimeDB auth

Register and log in via the built-in email/password flow. No additional services required.

Optional — OIDC

Point Pear at any OpenID Connect provider (Authentik, Keycloak, Auth0, etc.) by setting these environment variables on the web service:

NEXT_PUBLIC_AUTH_MODE=oidc
NEXT_PUBLIC_OIDC_AUTHORITY=https://your-provider.example.com
NEXT_PUBLIC_OIDC_CLIENT_ID=pear

IdP requirements

For the Pear module to populate the workspace User row and auto-promote the first authenticated user to admin, the ID token (not the userinfo endpoint) must contain at minimum one of email, name, or preferred_username. SpacetimeDB validates the JWT against your provider's JWKS and the client_connected reducer reads the profile claims directly off the JWT payload — there is no userinfo round-trip from the module.

Both placements are valid per OIDC Core §5, but many IdPs default to the minimal-ID-token configuration (claims live in userinfo only). If your IdP does this, configure it to also emit the claims in the ID token when the corresponding scopes are granted:

Scope ID-token claims
email email, email_verified
profile name, preferred_username (others optional: given_name, family_name, picture, updated_at)

Provider-specific notes:

  • Keycloak — Client → Client scopesemail/profileMappers → ensure each mapper has "Add to ID token" enabled.
  • Authentik — Provider → Property mappings → make sure email and profile scope mappings have userinfo: true and id_token: true.
  • Auth0 — Action triggered on Post-Login: api.idToken.setCustomClaim('email', event.user.email) (built-in claims are normally already in the ID token by default for openid email profile scopes).
  • Cognito — User pool → App integration → App client → ensure email and name are checked under "Allowed read attributes" and the corresponding scopes are requested.

Verifying the ID token

After login, decode the ID token (JWT) at https://jwt.io or with jq:

echo "$ID_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

The payload should include email, name, and/or preferred_username alongside the standard iss/sub/aud/iat/exp. If only the standard claims are present, the User row will land is_authenticated=false and the Members section in workspace settings will appear empty — that's the smoke test.

Custom auth bridges

If you're wiring a non-standard auth provider (or building Pear into another product), pass the JWT to SpacetimeDB at connect time:

import { DbConnection } from "./module_bindings";
const conn = DbConnection.builder()
 .withUri(wsUri)
 .withDatabaseName(dbName)
 .withToken(idToken) // your IdP's ID token (RS256 JWT, validated by your IdP's JWKS)
 .onConnect((c, identity) => { /* ... */ })
 .build();

The identity SpacetimeDB derives is SHA-256(iss ‖ sub), so the same user from the same IdP always maps to the same Identity across browsers/devices. The first authenticated user on a fresh database is auto-promoted to admin; subsequent admin changes go through set_user_admin and are gated on the caller already being admin.


Backend API surface

The SpacetimeDB module is large and evolving. Rather than duplicating every reducer here:

  • Source of truth: server/spacetimedb/src/ — all #[reducer] functions and #[table] definitions (entry point lib.rs, grouped into modules).
  • High-level groups: authentication (register, login, ...), pages & Yjs (save_yjs_state, update_page_content, ...), component tree (insert_component, update_component_props, move_component, ...), snapshots, attachments, embeddings (set_page_embedding), database schema & property values & views, AI users & conversations (create_ai_user, send_message, record_compaction, ...), Orcha (create_job, claim_task, submit_result, ...), extensions (publish_extension, install_extension, permission/audit reducers, ...), HTTP API endpoints (endpoint CRUD reducers), and import (import_notion, ...).

Client call sites use the generated bindings under web/src/module_bindings/.


License

AGPL-3.0