sqlrooms.github.io/api/documents.md at main · sqlrooms/sqlrooms.github.io · GitHub
Skip to content

Latest commit

 

History

History
580 lines (488 loc) · 24.2 KB

File metadata and controls

580 lines (488 loc) · 24.2 KB
url https://sqlrooms.org/api/documents.md

@sqlrooms/documents

Artifact-scoped Markdown documents, structured block documents, and knowledge-index utilities for SQLRooms.

Usage

import {
  BlockDocumentArtifact,
  BlockDocumentsSliceConfig,
  BlockDocumentChartRendererProvider,
  BlockDocumentStatefulBlockRendererProvider,
  DocumentsSliceConfig,
  buildKnowledgeIndex,
  createBlockDocumentCommands,
  createBlockDocumentsSlice,
  createDocumentCommands,
  createDocumentsSlice,
  createMarkdownDocumentBlockDefinition,
} from '@sqlrooms/documents';
import {createDocumentsCrdtMirror} from '@sqlrooms/documents/crdt';
import {
  createArtifactTypeFromStatefulBlock,
  defineArtifactTypes,
} from '@sqlrooms/artifacts';

const documentBlockDefinition = createMarkdownDocumentBlockDefinition();

const artifactTypes = defineArtifactTypes({
  document: createArtifactTypeFromStatefulBlock(documentBlockDefinition),
  'block-document': {
    label: 'Block Document',
    defaultTitle: 'Block Document',
    component: BlockDocumentArtifact,
    onCreate: ({artifactId, store}) => {
      store.getState().blockDocuments.ensureBlockDocument(artifactId);
    },
    onEnsure: ({artifactId, store}) => {
      store.getState().blockDocuments.ensureBlockDocument(artifactId);
    },
    onDelete: ({artifactId, store}) => {
      store.getState().blockDocuments.removeBlockDocument(artifactId);
    },
  },
});

const roomStore = createRoomStore(
  persistSliceConfigs(
    {
      name: 'my-room',
      sliceConfigSchemas: {
        documents: DocumentsSliceConfig,
        blockDocuments: BlockDocumentsSliceConfig,
      },
    },
    (set, get, store) => ({
      ...createDocumentsSlice()(set, get, store),
      ...createBlockDocumentsSlice({
        onDeleteOwnedStatefulBlock: ({
          blockType,
          blockInstanceId,
          getState,
        }) => {
          if (blockType === 'dashboard') {
            getState().mosaicDashboard.removeDashboard(blockInstanceId);
          }
        },
      })(set, get, store),
    }),
  ),
);

MarkdownDocument uses the Tiptap-backed MarkdownDocumentEditor. It keeps Markdown as the controlled value, renders a rich document editing surface, and keeps the existing CodeMirror source panel for direct Markdown edits.

MarkdownDocumentEditor is also exported as a reusable controlled editor:

<MarkdownDocumentEditor
  value={markdown}
  assets={assets}
  onChange={setMarkdown}
/>

The rich editor is the primary surface. The optional Markdown source panel can be opened alongside it and edits the same canonical Markdown string:

<MarkdownDocumentEditor
  value={markdown}
  onChange={setMarkdown}
  sourcePanelOpen={showSource}
  onSourcePanelOpenChange={setShowSource}
/>

Document Markdown can reference document-owned assets with asset:// URLs:

![Revenue by week](asset://chart-revenue-week)

Pass the document asset map to MarkdownDocumentEditor to render those links as browser-loadable image data while preserving the canonical asset:// link in Markdown source. MarkdownDocument handles this automatically for artifacts stored in the documents slice.

The documents slice exposes upsertAsset, removeAsset, and getAsset for managing image assets alongside Markdown content. SVG assets may use utf8 or base64 encoding; PNG assets must use base64 encoding.

Block Documents

createBlockDocumentsSlice() exposes structured state for artifact types backed by composable blocks: text, lists, images, standalone Mosaic/vgplot charts, and direct stateful blocks such as dashboards, pivots, or Markdown documents.

The shared block vocabulary lives in @sqlrooms/blocks. @sqlrooms/documents builds on those contracts with the concrete Tiptap-backed BlockDocument editor, persistence slice, commands, and AI authoring helpers.

Block documents persist Tiptap/ProseMirror JSON as their canonical content and provide block DTO helpers for command and AI authoring surfaces:

import {
  BlockDocumentsSliceConfig,
  createBlockDocumentsSlice,
} from '@sqlrooms/documents';

const roomStore = createRoomStore(
  persistSliceConfigs(
    {
      name: 'my-room',
      sliceConfigSchemas: {
        blockDocuments: BlockDocumentsSliceConfig,
      },
    },
    (set, get, store) => ({
      ...createBlockDocumentsSlice()(set, get, store),
    }),
  ),
);

The slice can create block documents, replace the Tiptap JSON body, and append/insert/update/remove/reorder top-level blocks. Supported block DTOs include headings, paragraphs, lists, todos, images, chart images, standalone chart blocks, and direct stateful blocks.

BlockDocumentArtifact and BlockDocumentEditor provide the first rich editor surface for this structured state. BlockDocumentArtifact injects an editable, non-movable title node into the Tiptap document and reports title changes through onTitleChange, so hosts can keep artifact metadata and tab labels in sync. The editor owns Tiptap nodes for SQLRooms custom blocks, but chart and stateful block rendering are host-provided so @sqlrooms/documents does not import Mosaic, pivot, or other feature packages:

<BlockDocumentChartRendererProvider renderer={MosaicBlockDocumentChartRenderer}>
  <BlockDocumentStatefulBlockRendererProvider
    renderers={{
      dashboard: DashboardBlockRenderer,
      pivot: PivotBlockRenderer,
    }}
    blockTypes={[
      {
        blockType: 'dashboard',
        label: 'Dashboard',
        description: 'Interactive dashboard',
        createNode: (blockId) => ({
          type: 'blockDocumentStatefulBlock',
          attrs: {
            id: blockId,
            blockType: 'dashboard',
            blockInstanceId: createDashboardBlockState(blockId),
            ownership: 'owned',
            title: 'Dashboard',
            caption: '',
          },
        }),
      },
    ]}
  >
    <BlockDocumentArtifact
      artifactId={blockDocumentArtifactId}
      title="Worksheet"
      onTitleChange={(title) =>
        renameBlockDocument(blockDocumentArtifactId, title)
      }
    />
  </BlockDocumentStatefulBlockRendererProvider>
</BlockDocumentChartRendererProvider>

If no renderer is registered, chart and stateful blocks render a clear unsupported state while preserving their Tiptap JSON attributes. blockTypes controls the host-specific entries shown in the plus menu. When a block is converted through the handle menu, custom createNode callbacks receive an optional {initialText} value with the source block text; hosts can use it to seed stateful blocks such as embedded Markdown documents. Stateful block types can opt into persisted vertical resizing with resizableHeight, defaultHeight, minHeight, and maxHeight; the editor stores the resulting height on the block node and renders a bottom resize handle just below the block for writable documents. Interactive blocks can also opt into requireScrollModifier; ordinary wheel gestures then keep scrolling the document and show a short hint, while Cmd+scroll on macOS or Ctrl+scroll elsewhere scrolls nested overflow regions inside the block. Use scrollHintLabel to customize the hint target text.

Stateful Blocks

Use a statefulBlock block when the document should host a stateful SQLRooms surface directly, without wrapping it in an artifact shell:

blockDocuments.appendBlocks(blockDocumentArtifactId, [
  {
    id: 'pivot-block',
    type: 'statefulBlock',
    blockType: 'pivot',
    blockInstanceId: 'pivot-instance-1',
    ownership: 'owned',
    title: 'Embedded Pivot Table',
  },
]);

Hosts provide renderers through BlockDocumentStatefulBlockRendererProvider:

<BlockDocumentStatefulBlockRendererProvider
  renderers={{
    pivot: PivotBlockRenderer,
    dashboard: DashboardBlockRenderer,
  }}
  blockTypes={[
    {
      blockType: 'pivot',
      label: 'Pivot Table',
      description: 'Embedded pivot table',
    },
  ]}
>
  <BlockDocumentArtifact
    artifactId={blockDocumentArtifactId}
    title="Embedded Report"
    onTitleChange={(title) =>
      renameBlockDocument(blockDocumentArtifactId, title)
    }
  />
</BlockDocumentStatefulBlockRendererProvider>

Top-level artifacts should wrap stateful blocks or block containers at the workspace/tab layer. Block documents host the stateful block directly instead of embedding an artifact shell.

Owned stateful blocks are lifecycle-managed by the host app. Pass onCreateOwnedStatefulBlock to initialize feature state when a new owned block reference appears, and onDeleteOwnedStatefulBlock to clean it up when an owned block is removed from a document or when its owning block document is deleted. Blocks with ownership: 'shared' or ownership: 'external' are not cleaned up by the documents slice. Hosts can also pass onRenameOwnedStatefulBlock to synchronize block title changes into the backing feature state. Captions stay local to the blocks document. Stateful block renderers receive onTitleChange and onCaptionChange callbacks when a writable document lets the embedded surface edit its own block metadata.

The editor normalizes pasted or duplicated owned stateful blocks by assigning fresh top-level block IDs and fresh blockInstanceId values when a duplicate owned instance would otherwise point at the same backing state.

Standalone Chart Blocks

Standalone chart blocks are meant for focused, in-document charts. They store the target tableName, a Mosaic ChartConfig, an optional caption, and an optional selectionGroupId:

blockDocuments.appendBlocks(blockDocumentArtifactId, [
  {
    id: 'revenue-histogram',
    type: 'chart',
    tableName: 'sales',
    config: {
      chartType: 'histogram',
      settings: {field: 'revenue'},
    },
    selectionGroupId: 'overview',
    caption: 'Revenue distribution',
  },
]);

Hosts can render these blocks with the same Mosaic/vgplot chart implementation and settings UI used inside dashboard panels, without embedding a full dashboard. Charts with the same selectionGroupId in one block document share a crossfilter selection. Charts without a group get independent document/block-scoped selections.

Hosted Dashboards

Use a statefulBlock block when the document needs a multi-panel interactive dashboard. The block instance id should map to dashboard state in the host app's Mosaic slice, while the top-level artifact shell remains optional for workspace navigation.

Standalone chart blocks are best for one chart with local context. Dashboard stateful blocks are best for coordinated multi-panel views, richer dashboard layout, or when dashboard AI tools are the natural authoring path.

Commands

createDocumentCommands() registers AI- and palette-friendly commands for document artifacts:

  • document.list
  • document.get
  • document.create
  • document.set-markdown
  • document.append-markdown

createBlockDocumentCommands() registers commands for structured blocks document artifacts. By default the command IDs are:

  • block-document.list
  • block-document.get
  • block-document.create
  • block-document.append-blocks
  • block-document.insert-blocks
  • block-document.update-block
  • block-document.remove-block
  • block-document.move-block
  • block-document.create-chart-block
  • block-document.create-stateful-block

Hosts can pass artifactType, artifactLabel, and commandNamespace options to expose the same command surface under product-specific names while keeping the package API generic.

Hosts can pass statefulBlockTypes to expose supported feature-backed block types to block-document.create-stateful-block.

Structured block payloads may include an optional intent string. Use it for the durable natural-language purpose of an agent- or command-created block, such as the question a chart should answer or the job an embedded dashboard should serve. It is persisted with the block, unlike transient mutation metadata.

CRDT

@sqlrooms/documents/crdt exposes Loro Mirror bindings for document state:

createCrdtSlice({
  mirrors: {
    documentState: createDocumentsCrdtMirror(),
  },
});

createDocumentsCrdtMirror() syncs Markdown document bodies, block document Tiptap JSON content, document-owned assets, standalone chart block configs, block document/document artifact metadata, and document artifact tab order. The current artifact selection is kept local.

By default, the mirror treats block-document artifacts as block documents. Hosts with their own artifact type names can pass blockDocumentArtifactTypes, for example:

createDocumentsCrdtMirror({
  blockDocumentArtifactTypes: ['report'],
});

Hosted dashboard state should continue to use the host app's Mosaic persistence, or a future Mosaic-specific CRDT mirror.

Knowledge Index

buildKnowledgeIndex is a pure derived index. It does not persist data.

const index = buildKnowledgeIndex({
  documents: roomStore.getState().documents.config,
  artifacts: roomStore.getState().artifacts.config,
});

It extracts [[Document Title]] wikilinks, body hashtags such as #metrics, and optional frontmatter tags. Links are resolved against document artifact titles. Missing or ambiguous titles are reported as unresolved links.

Type Aliases

Variables

Functions

References

DocumentAssetType

Renames and re-exports DocumentAsset


DocumentsSliceConfigType

Renames and re-exports DocumentsSliceConfig


MarkdownDocumentStateType

Renames and re-exports MarkdownDocumentState


BlockDocumentBlockType

Renames and re-exports BlockDocumentBlock


BlockDocumentType

Renames and re-exports BlockDocument


BlockDocumentContentType

Renames and re-exports BlockDocumentContent


BlockDocumentMarkType

Renames and re-exports BlockDocumentMark


BlockDocumentNodeType

Renames and re-exports BlockDocumentNode


BlockDocumentsSliceConfigType

Renames and re-exports BlockDocumentsSliceConfig