@sprinterai/core
Types, store interfaces, builders, and the module system. The foundation package with zero runtime dependencies beyond Zod.
Install
pnpm add @sprinterai/coreOverview
@sprinterai/core is the foundation of the platform. It defines all TypeScript types, abstract store interfaces, builder functions, and the module system. It has a single dependency: Zod.
Everything else in the platform depends on core. Core depends on nothing else.
Exports at a Glance
| Category | Key Exports |
|---|---|
| Builders | defineAgent, defineTool, defineEntityType, defineWorkflow, defineModule, defineApp, field, relation |
| Entity types | EntityTypeRecord, EntityRecord, FieldConfig, EntityJsonSchema, CreateEntityInput, UpdateEntityInput |
| Agent types | AgentDefinition, AgentConfig, AgentConnection, HeartbeatSchedule |
| Tool types | ToolDefinition, ToolSession, ToolRun |
| Workflow types | WorkflowDefinition, WorkflowRunRecord, WorkflowNodeRunRecord |
| Response types | CriteriaSetRecord, EntityResponseRecord, ResponseAggregation |
| Tenant types | TenantContext, AppRole, AppPermission, ROLE_IDS |
| Task types | TaskRecord, TaskStatus, TASK_STATUSES |
| Chat types | ChatType, ConversationListItem, SendMessageInput, ChatParticipant, ParticipantType, ParticipantRole |
| Memory types | UserMemory, MemorySource, AgentMemoryConfig |
| Store interfaces | EntityStore, AgentStore, ChatStore, TenantStore, ToolStore, ViewStore, TaskStore, MemoryStore |
| Render types | RenderFamily, RendererDescriptor, UIIntent, SurfaceType, SurfaceCategory, SurfaceTypeMeta, SurfaceDefinition, SurfaceDefinitionRegistry, SurfaceIntrospection |
| Block types | BlockType, BLOCK_TYPES, BlockConfig, BlockConfigSchema, DataSourceConfig, DataSourceConfigSchema, BlockWorkflow, ClickAction, ViewLayoutStyle, ViewTheme, FormFlowConfig, FormFlowStep, FormFlowField, FileUploadFieldConfig, ResourceDownloadBlockConfig, FormFlowOutputConfig |
| Block data schemas | BLOCK_DATA_SCHEMAS, getBlockDataSchema, ActivityBlockDataSchema, ChildEntityListBlockDataSchema, ConnectionListBlockDataSchema, DocumentsBlockDataSchema, EntityCardBlockDataSchema, EntityFilterBlockDataSchema, FieldCardBlockDataSchema, ImageBlockDataSchema, NotesBlockDataSchema, RadarBlockDataSchema, RankingBlockDataSchema, ResponseFormBlockDataSchema, RichTextBlockDataSchema, StatusBannerBlockDataSchema, SummaryBlockDataSchema, VideoBlockDataSchema |
| View types | ParsedViewRecord, ViewPageKind, ViewInventoryRecord, ViewHealth, parseViewBlocks, orderedBlocks, blockCount |
| View response | ViewResponse, ViewResponseStore, ViewResponseSchema |
| Entity markdown | generateEntityMarkdown, parseEntityMarkdown, extractWikilinkTitles, GenerateEntityMarkdownInput, ParsedEntityMarkdown |
| Plugin types | PluginManifest, PluginManifestSchema, PluginRecord, PluginVersionRecord, RendererBindingRecord, PLUGIN_STATUSES, VERSION_STATUSES, RENDERER_SOURCES, SANDBOX_ACTIONS |
| Aggregation types | AggregationFunction, AggregationOperator, AggregationFilter, AggregationItem, AggregationParams, AggregationResult, AggregationResultRow |
| Relation column types | RelationColumn, RelationAggregation, FilterRule, FilterOperator, RelationColumnResult |
| Model types | ModelCapability, MODEL_CAPABILITIES, ModelCatalogEntry, ModelInfo, ModelProvider, FALLBACK_MODEL_CATALOG, groupModelsByProvider, PROVIDER_ORDER, PROVIDER_LABELS, PROVIDER_DEFAULT_MODEL, DEFAULT_CHAT_MODEL_ID, DEFAULT_IMAGE_MODEL_ID |
| PDF types | PdfDocumentProxy, PdfHighlight, SelectionContext, SelectionAction, PdfWorkerConfig, PdfLoadOptions |
| Email types | EmailChannel, EmailMessage, SendEmailInput, SendEmailResult, EmailProvider, NotificationEmailContext, DigestEmailContext, NotificationPreferences |
| Email schemas | SendEmailInputSchema, NotificationPreferencesSchema, DEFAULT_NOTIFICATION_PREFERENCES |
| Automation types | AutomationType, UnifiedAutomation, AutomationRun, AutomationStore, AUTOMATION_TYPE_LABELS, AUTOMATION_TYPE_ICONS |
| Settings types | TenantSetting, SettingKey, JobFunction, DashboardPreferences, SettingsStore, JOB_FUNCTIONS, JOB_FUNCTION_LABELS |
| Branding types | BrandingConfig, BrandingConfigSchema, DEFAULT_BRANDING |
| Template types | WorkspaceTemplate, TemplateEntityType, TemplateAgent, TemplateView, TemplateSampleEntity, TemplateCategory, ApplyTemplateOptions, ApplyTemplateResult, TemplateStore, TEMPLATE_CATEGORIES, TEMPLATE_CATEGORY_LABELS |
| Module types | SprinterModule, AppDefinition |
| Eval types | EvalScorer, EvalDataset, EvalRun |
| Guardrail types | InputGuardrail, OutputGuardrail, GuardrailConfig |
| Observability | ObservabilityEvent, ObservabilityConfig |
| External data types | ExternalDataSource, ExternalDataPoint, DataPointMetadata, MetricItem, WebhookPayload, ExternalDataStore, EXTERNAL_DATA_SOURCE_TYPES |
| External data schemas | MetricItemSchema, WebhookPayloadSchema, CreateDataSourceSchema, UpdateDataSourceSchema, PollConfigSchema, ApiConfigSchema |
| Analytics types | AnalyticsEvent, AnalyticsProvider, AnalyticsEventType, ANALYTICS_EVENTS |
| View permissions | ViewAccessContext, canAccessView, canModifyView |
| Audit actions | AUDIT_ACTIONS, AuditAction, AuditStore, AuditLogFilters, ListAuditLogsResult |
Builders
defineEntityType
Creates a frozen entity type definition for seeding or module registration.
import { defineEntityType, field } from "@sprinterai/core";
const opportunity = defineEntityType({
slug: "opportunity",
name: "Opportunity",
description: "A deal in the pipeline",
icon: "target",
color: "#3B82F6",
schema: {
type: "object",
properties: {
name: field.text({ title: "Deal Name" }),
stage: field.select({
title: "Stage",
options: ["Sourced", "Screening", "Due Diligence", "Closed"],
}),
value: field.currency({ title: "Deal Value" }),
probability: field.percentage({ title: "Close Probability" }),
target_close: field.date({ title: "Target Close Date" }),
website: field.url({ title: "Company Website" }),
tags: field.array({ title: "Tags", itemType: "string" }),
},
required: ["name", "stage"],
},
config: {
ui: { defaultLayout: "kanban" },
},
});field builders
The field namespace provides typed helpers for JSON Schema properties:
| Builder | JSON Schema | Notes |
|---|---|---|
field.text() | { type: "string" } | Plain text |
field.select({ options }) | { type: "string", enum: [...] } | Enum select |
field.number() | { type: "number" } | With optional min/max |
field.integer() | { type: "integer" } | Integer variant |
field.boolean() | { type: "boolean" } | Toggle |
field.currency() | { type: "number", format: "currency" } | Money field |
field.percentage() | { type: "number", min: 0, max: 100 } | 0-100 range |
field.date() | { type: "string", format: "date" } | Date picker |
field.url() | { type: "string", format: "uri" } | URL field |
field.email() | { type: "string", format: "email" } | Email field |
field.array() | { type: "array", items: {...} } | List/tags |
defineAgent
import { defineAgent } from "@sprinterai/core";
const researcher = defineAgent({
slug: "researcher",
name: "Research Agent",
description: "Researches entities using web search and analysis",
systemPrompt: "You are a thorough research analyst...",
model: "claude-sonnet-4-20250514",
toolGroups: ["entity", "web"],
customTools: ["roi-calculator"],
});defineTool
import { defineTool } from "@sprinterai/core";
import { z } from "zod";
const scorer = defineTool({
slug: "deal-scorer",
name: "Deal Scorer",
description: "Score a deal on multiple dimensions",
category: "analysis",
requiredPermission: "entities.team.update",
inputSchema: z.object({
entityId: z.string(),
dimensions: z.array(z.string()),
}),
execute: async (input) => {
// scoring logic
return { scores: {} };
},
});defineWorkflow
import { defineWorkflow } from "@sprinterai/core";
const enrichment = defineWorkflow([
{ id: "fetch-basics", agentSlug: "researcher", fieldKey: "name" },
{ id: "fetch-financials", agentSlug: "analyst", fieldKey: "revenue", dependsOn: ["fetch-basics"] },
{ id: "score", agentSlug: "scorer", fieldKey: "score", dependsOn: ["fetch-financials"], outputType: "field" },
]);defineModule and defineApp
import { defineModule, defineApp } from "@sprinterai/core";
const dealFlow = defineModule({
name: "deal-flow",
description: "Deal pipeline and scoring",
entityTypes: [opportunity],
agents: [researcher],
tools: [scorer],
dependencies: ["core-entities"],
});
const app = defineApp({
name: "portfolio-intel",
description: "PE portfolio intelligence",
modules: [dealFlow],
});Surface Types
Surface type catalogue
30 named surface types are shipped in @sprinterai/core. They are organised into 7 categories:
| Category | Types |
|---|---|
core | grid, sequence, slides, page, form |
data | split, kanban, timeline, calendar, gallery, table |
interactive | swipe, wizard, chat, compare, rank |
spatial | canvas, map, graph |
narrative | story, scroll, carousel |
output | pdf, email, embed-card |
power | terminal, notebook, inbox, dashboard, focus |
Each type carries metadata (label, description, icon, interactive, fullscreen) available via SURFACE_TYPE_META or the getSurfaceTypeMeta helper.
import {
SURFACE_TYPES,
SURFACE_TYPE_META,
SurfaceTypeSchema,
getSurfaceTypeMeta,
surfaceTypesByCategory,
} from "@sprinterai/core";
// Validate a string against the known set
const result = SurfaceTypeSchema.safeParse(userInput);
// Metadata lookup with safe fallback
const meta = getSurfaceTypeMeta("kanban");
// { label: "Kanban", category: "data", interactive: true, fullscreen: false, ... }
// All types grouped by category
const grouped = surfaceTypesByCategory();
// { core: ["grid", "sequence", ...], data: ["split", "kanban", ...], ... }SurfaceDefinitionRegistry
For surfaces that need typed configuration schemas or capability flags beyond what SURFACE_TYPE_META carries, use SurfaceDefinitionRegistry and defineSurface:
import {
SurfaceDefinitionRegistry,
defineSurface,
surfaceDefinitions,
} from "@sprinterai/core";
import { z } from "zod";
// Define a surface with a typed config schema
const kanbanDef = defineSurface({
type: "kanban",
meta: { label: "Kanban", description: "...", category: "data", icon: "Columns3" },
capabilities: {
interactive: true,
fullscreen: false,
supportsPublishing: false,
requiresClientSide: true,
},
configSchema: z.object({ groupByField: z.string() }),
});
// Register in the singleton
surfaceDefinitions.register(kanbanDef);
// Introspect at runtime
const info = surfaceDefinitions.introspect("kanban");
// { type, category, label, description, interactive, fullscreen, supportsPublishing }
// All registered definitions
const all = surfaceDefinitions.introspectAll();SurfaceDefinitionRegistry is a class so applications can also instantiate their own scoped registry rather than using the singleton.
Aggregation Schemas
@sprinterai/core exports Zod schemas for parameterising entity aggregation queries. These schemas validate inputs at runtime boundaries (API routes, tool calls) before any query runs.
import {
AggregationParamsSchema,
AggregationFunctionSchema,
AggregationOperatorSchema,
} from "@sprinterai/core";
import type {
AggregationParams,
AggregationFunction,
AggregationFilter,
AggregationResult,
} from "@sprinterai/core";
// Validate aggregation query parameters
const params = AggregationParamsSchema.parse({
entityTypeSlug: "opportunity",
groupBy: "stage",
aggregations: [
{ field: "value", function: "sum", alias: "total_value" },
{ field: "id", function: "count", alias: "deal_count" },
],
filters: [
{ field: "status", operator: "eq", value: "active" },
],
});Functions: count, sum, avg, min, max
Operators: eq, neq, gt, gte, lt, lte, in, contains, is_null, is_not_null
AggregationFilterSchema enforces that value is required for all operators except is_null and is_not_null via a Zod .refine().
Result shape:
interface AggregationResultRow {
group?: string | null; // Present when groupBy was requested
[alias: string]: number | string | null | undefined;
}
interface AggregationResult {
results: AggregationResultRow[];
total: number;
}Relation Column Types
Relation columns are computed columns derived from related entities (for example, the number of activities attached to an account, or the latest interaction date). Core defines the configuration types; runtime provides the resolver.
import { RelationColumnSchema, FilterRuleSchema } from "@sprinterai/core";
import type { RelationColumn, FilterRule, RelationColumnResult } from "@sprinterai/core";
const activityCount: RelationColumn = {
label: "Activity Count",
relatedTypeSlug: "activity",
relationshipType: "has_activity",
aggregation: "count",
};
const lastCallDate: RelationColumn = {
label: "Last Call",
relatedTypeSlug: "activity",
relationshipType: "has_activity",
aggregation: "latest",
field: "date",
filters: [{ field: "activity_type", operator: "eq", value: "call" }],
};Aggregations: count, latest, sum, avg, min, max
Filter operators: eq, neq, gt, gte, lt, lte, in, contains
RelationColumnResult is a map of entityId → { columnKey → computed value } returned by resolveRelationColumns in @sprinterai/runtime.
See @sprinterai/runtime for resolveRelationColumns.
Analytics Events
@sprinterai/core defines the analytics contract so that @sprinterai/runtime and @sprinterai/supabase can both reference event types without a circular dependency.
import { ANALYTICS_EVENTS } from "@sprinterai/core";
import type { AnalyticsEvent, AnalyticsProvider, AnalyticsEventType } from "@sprinterai/core";ANALYTICS_EVENTS
15 standard event type constants, organized by domain:
| Domain | Constants |
|---|---|
| Entity | ENTITY_CREATED, ENTITY_UPDATED, ENTITY_DELETED |
| Workflow | WORKFLOW_STARTED, WORKFLOW_COMPLETED, WORKFLOW_FAILED |
| Agent | AGENT_INVOKED, AGENT_DELEGATED |
| Tool | TOOL_EXECUTED |
| Skill | SKILL_LOADED |
| Chat | CHAT_MESSAGE_SENT |
| Document | DOCUMENT_PROCESSED |
| Search | SEARCH_PERFORMED |
| Comment | COMMENT_CREATED |
| Notification | NOTIFICATION_SENT |
Use these constants at call sites to avoid raw strings and enable easy grep-based usage discovery.
AnalyticsProvider
Provider interface for any analytics backend (PostHog, Mixpanel, custom DB):
interface AnalyticsProvider {
track(event: AnalyticsEvent): void | Promise<void>;
}See @sprinterai/runtime for createAnalyticsRecorder and createAnalyticsHooks. See @sprinterai/supabase for SupabaseAnalyticsProvider.
View Permissions
Pure permission functions for scope-based view access control. Both functions are in @sprinterai/core/view:
import { canAccessView, canModifyView } from "@sprinterai/core/view";
import type { ViewAccessContext } from "@sprinterai/core/view";ViewAccessContext
interface ViewAccessContext {
tenantId: string;
userId: string;
/** Role-based permission strings — e.g. 'views.team.update', 'admin'. */
permissions: string[];
}canAccessView
Determines whether a user can read a view based on its scope:
| Scope | Who can access |
|---|---|
system | All authenticated users in the tenant |
tenant | All users in the tenant |
user | Creator only |
Tenant ID must always match. Returns false for cross-tenant access regardless of scope.
canModifyView
Determines whether a user can update or delete a view:
| Scope | Who can modify |
|---|---|
system | Admins only (admin or manage_views permission) |
tenant | Admins or the creator |
user | Creator only |
See @sprinterai/runtime for createViewPermissionEnforcer, which wraps these functions around a ViewStore.
Audit Actions
import { AUDIT_ACTIONS } from "@sprinterai/core";
import type { AuditAction, AuditStore, AuditLogFilters } from "@sprinterai/core";AUDIT_ACTIONS
Standard operation constants for AuditLogRecord.action:
INSERT UPDATE DELETE LOGIN LOGOUT
PERMISSION_CHANGE EXPORT IMPORT BULK_OPERATIONAuditStore interface
interface AuditStore {
listAuditLogs(tenantId: string, filters?: AuditLogFilters): Promise<ListAuditLogsResult>;
getAuditLog(tenantId: string, id: string): Promise<AuditLogRecord | null>;
createAuditLog(tenantId: string, entry: Omit<AuditLogRecord, 'id' | 'tenant_id' | 'created_at'>): Promise<AuditLogRecord>;
batchCreateAuditLogs(
tenantId: string,
entries: Array<Omit<AuditLogRecord, 'id' | 'tenant_id' | 'created_at'>>,
correlationId?: string,
): Promise<AuditLogRecord[]>;
}AuditLogFilters
| Field | Type | Description |
|---|---|---|
resourceType | string? | Filter by table name |
resourceId | string? | Filter by resource ID |
userId | string? | Filter by who made the change |
operation | string? | Single operation type (e.g. 'INSERT') |
operations | string[]? | Multiple operation types at once |
startDate | string? | ISO timestamp lower bound (inclusive) |
endDate | string? | ISO timestamp upper bound (inclusive) |
correlationId | string? | Find related entries from the same batch |
limit | number? | Default 50 |
offset | number? | For pagination |
See @sprinterai/runtime for createAuditContext, which auto-injects correlation IDs.
Model Catalog
@sprinterai/core owns the canonical model metadata layer. This lets any package (supabase, typespec, UI) read model capabilities and defaults without depending on @sprinterai/runtime.
Import from the root barrel:
import {
FALLBACK_MODEL_CATALOG,
PROVIDER_ORDER,
PROVIDER_LABELS,
PROVIDER_DEFAULT_MODEL,
DEFAULT_CHAT_MODEL_ID,
DEFAULT_IMAGE_MODEL_ID,
groupModelsByProvider,
} from "@sprinterai/core";
import type {
ModelCapability,
ModelCatalogEntry,
ModelInfo,
ModelProvider,
} from "@sprinterai/core";ModelCapability
11 capability flags grouped into three categories:
| Category | Capabilities |
|---|---|
| Input | text_input, image_input, video_input, audio_input |
| Output | text_output, image_output, audio_output |
| Features | tool_use, reasoning, code, web_search |
MODEL_CAPABILITY_LABELS maps each flag to a display string. MODEL_CAPABILITY_CATEGORIES groups them for UI rendering.
ModelCatalogEntry
Rich catalog entry with full metadata for model-selection UIs:
interface ModelCatalogEntry {
id: string; // Canonical ID, e.g. 'claude-sonnet-4-6'
provider: ModelProvider; // 'anthropic' | 'openai' | 'google' | 'xai' | 'deepseek'
name: string;
description?: string | null;
capabilities: ModelCapability[];
speedTier: 'fast' | 'medium' | 'slow';
reasoningLevel?: 'low' | 'medium' | 'high' | null; // Only for 'reasoning' models
costPer1mInput: number; // USD per 1 million input tokens
costPer1mOutput: number; // USD per 1 million output tokens
enabled?: boolean;
}ModelInfo
DB row shape for tenant-configurable model catalogs. Used when a tenant overrides the default catalog via a models table (not yet shipped in @sprinterai/supabase):
interface ModelInfo {
id?: string;
model_id: string;
display_name: string;
provider: ModelProvider;
speed_tier: 'fast' | 'medium' | 'slow';
capabilities?: ModelCapability[];
description?: string | null;
reasoning_level?: 'low' | 'medium' | 'high' | null;
cost_per_1m_input?: number;
cost_per_1m_output?: number;
enabled?: boolean;
sort_order?: number;
}FALLBACK_MODEL_CATALOG
17-entry default catalog used when a tenant has no custom model configuration:
| Provider | Models |
|---|---|
| anthropic | claude-haiku-4-5, claude-sonnet-4-5, claude-sonnet-4-6, claude-opus-4-6 |
| openai | gpt-4.1, gpt-4o, o3, o4-mini, gpt-image-1.5, gpt-image-1, dall-e-3 |
| gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-image | |
| xai | grok-3, grok-3-mini |
| deepseek | deepseek-chat, deepseek-reasoner |
groupModelsByProvider
Groups any list of ModelInfo (or ModelCatalogEntry) objects by provider, preserving PROVIDER_ORDER for display:
const { grouped, sortedProviders } = groupModelsByProvider(FALLBACK_MODEL_CATALOG);
// grouped: Map<ModelProvider, ModelCatalogEntry[]>
// sortedProviders: ['anthropic', 'openai', 'google', 'xai', 'deepseek'] (only those present)Provider constants
| Constant | Type | Description |
|---|---|---|
PROVIDER_ORDER | ModelProvider[] | Display order for provider groups |
PROVIDER_LABELS | Record<ModelProvider, string> | Human-readable provider names |
PROVIDER_DEFAULT_MODEL | Record<ModelProvider, string> | Per-provider fallback model ID |
DEFAULT_CHAT_MODEL_ID | string | 'claude-sonnet-4-6' |
DEFAULT_IMAGE_MODEL_ID | string | 'gpt-image-1.5' |
See @sprinterai/runtime for ModelConfig, MODEL_REGISTRY, and resolveModel, which add provider-SDK wiring on top of these core types.
Block Types
@sprinterai/core defines the complete block configuration schema used by views, the renderer layer, and the plugin system.
import {
BLOCK_TYPES,
BlockConfigSchema,
DataSourceConfigSchema,
VIEW_LAYOUTS,
VIEW_THEMES,
BLOCK_SIZES,
} from "@sprinterai/core";
import type {
BlockType,
BlockConfig,
DataSourceConfig,
BlockWorkflow,
ViewLayoutStyle,
ViewTheme,
BlockSize,
BlockRole,
FormFlowConfig,
} from "@sprinterai/core";BLOCK_TYPES catalog
33 canonical block types:
stat-cards, table, chart, radar, ranking, kanban, summary,
field-card, connection-list, data-table, activity, entity-card,
entity-feed, rich-text, status-banner, child-entity-list, custom,
image, video, excalidraw, react-flow, bubble-chart, entity-graph,
form-flow, canvas, menu, pdf-viewer, response-form, entity-filter,
notes, documents, tool, resource-downloadThe resource-download block renders a completion screen with a headline, message, and optional download button — used as the final step in a form-flow sequence.
BlockConfig
Serializable block config stored in the views.blocks JSONB column:
const block: BlockConfig = {
id: "blk-abc123",
type: "chart",
label: "Revenue by Stage",
size: "half", // legacy: 'full' | 'half' | 'third'
span: 6, // preferred: 12-column grid span (1-12)
row: 2, // optional explicit row placement (1-based)
height: 3, // grid row units
dataSourceId: "opportunities",
clickAction: { type: "navigate", target: "/entities/:id" },
};BlockConfigFlexibleSchema accepts both a BlockConfig[] array (legacy) and a Record<string, BlockConfig> flat map at Zod parse boundaries.
DataSourceConfig
Named entity query binding that blocks reference via dataSourceId. Views store a dataSources map of these at the view level:
const ds: DataSourceConfig = {
entityTypeSlug: "opportunity",
filters: [{ field: "status", operator: "eq", value: "active" }],
sort: { field: "updated_at", order: "desc" },
limit: 50,
relationColumns: {
activity_count: {
label: "Activities",
relatedTypeSlug: "activity",
relationshipType: "has_activity",
aggregation: "count",
},
},
};FormFlowConfig
Multi-step form configuration for the form-flow block type:
interface FormFlowConfig {
steps: FormFlowStep[];
completionMessage?: string;
allowSkipOptional?: boolean;
output?: FormFlowOutputConfig;
}
interface FormFlowStep {
id: string;
title: string;
description?: string;
fields: FormFlowField[];
instructions?: string;
}Field types: text, textarea, select, multi-select, number, slider, date, entity-picker, tag-input, rich-text, file-upload.
FileUploadFieldConfig
Per-field config for file-upload form-flow fields:
const FileUploadFieldConfigSchema = z.object({
accept: z.array(z.string()).optional(), // MIME types or extensions e.g. ['image/*', '.pdf']
maxBytes: z.number().optional(), // Maximum file size per file
multiple: z.boolean().optional(), // Allow multiple files
});ResourceDownloadBlockConfig
Config for the resource-download block, typically used as a form-flow completion screen:
const ResourceDownloadBlockConfigSchema = z.object({
title: z.string().optional(),
message: z.string().optional(),
downloadUrl: z.string().optional(),
downloadLabel: z.string().optional(),
});FormFlowOutputConfig
Discriminated union describing what happens when a form-flow completes:
type FormFlowOutputConfig =
| { type: 'entity'; entityTypeSlug: string } // Create an entity from collected fields
| { type: 'webhook'; webhookUrl: string } // POST collected data to an endpoint
| { type: 'none' }; // No action — show completion message onlyUse FormFlowOutputConfigSchema to validate at parse boundaries.
Block Data Schemas
@sprinterai/core exports a Zod schema for every block's runtime data prop. These schemas are the single source of truth for what shape a block component expects to receive and can be used to validate data before rendering.
import {
BLOCK_DATA_SCHEMAS,
getBlockDataSchema,
ActivityBlockDataSchema,
EntityCardBlockDataSchema,
RadarBlockDataSchema,
} from "@sprinterai/core";
import type {
ActivityBlockData,
EntityCardBlockData,
RadarBlockData,
} from "@sprinterai/core";
// Look up by block type string
const schema = getBlockDataSchema("activity");
// Returns ActivityBlockDataSchema, or undefined for unknown types
// Validate before rendering
const result = schema?.safeParse(rawData);
// All schemas in a keyed map
const allSchemas = BLOCK_DATA_SCHEMAS;
// Record<string, z.ZodType>Schema catalogue
| Block type | Schema | Data shape |
|---|---|---|
activity | ActivityBlockDataSchema | ActivityItem[] — id, action, timestamp, optional actor, details |
child-entity-list | ChildEntityListBlockDataSchema | ChildEntityItem[] — id, title, optional status, type, createdAt, href |
connection-list | ConnectionListBlockDataSchema | ConnectionItem[] — id, title, optional type, relationship, href |
documents | DocumentsBlockDataSchema | DocumentItem[] — id, name, optional type, size, url |
entity-card | EntityCardBlockDataSchema | Single entity object — title required, optional id, type, description, status, href, fields |
entity-filter | EntityFilterBlockDataSchema | Config-driven — optional filters array of { field, operator, value, active? } |
field-card | FieldCardBlockDataSchema | Record<string, unknown> — arbitrary key-value payload; config controls which fields are shown |
image | ImageBlockDataSchema | Config-driven — optional src, alt, caption, fit ('cover' | 'contain') |
notes | NotesBlockDataSchema | string — raw note text |
radar | RadarBlockDataSchema | RadarDataPoint[] — subject, value, optional fullMark |
ranking | RankingBlockDataSchema | RankingItem[] — title required, optional rank, value, change ('up' | 'down' | 'neutral') |
response-form | ResponseFormBlockDataSchema | Config-driven — optional criteria array of { id, label, type, max? } |
rich-text | RichTextBlockDataSchema | string — markdown content |
status-banner | StatusBannerBlockDataSchema | Config-driven — optional status ('info' | 'success' | 'warning' | 'error'), message |
summary | SummaryBlockDataSchema | string — summary text |
video | VideoBlockDataSchema | Config-driven — optional src, poster, caption |
Schema design principle: schemas are permissive on read. Optional fields degrade gracefully in the block UI. Required fields are only those without which the block renders nothing useful. Config-driven blocks (image, video, entity-filter, response-form, status-banner) expose their config shape under the same *BlockDataSchema naming convention for consistency.
View Types
ParsedViewRecord is the fully-typed representation of a view DB row with all JSONB fields deserialized.
import { parseViewBlocks, orderedBlocks, blockCount } from "@sprinterai/core";
import type { ParsedViewRecord, ViewPageKind, ViewInventoryRecord } from "@sprinterai/core";ParsedViewRecord
interface ParsedViewRecord {
id: string;
tenant_id: string;
entity_type_id: string | null;
entity_type_slug: string | null;
entity_id: string | null;
title: string;
slug: string;
page_type: ViewPageKind; // 'list' | 'workspace'
blocks: Record<string, BlockConfig>;
blockOrder: string[];
layout: ViewLayoutStyle; // 'stack' | 'grid-2' | 'grid-3' | 'bento' | 'single'
scope: 'default' | 'shared' | 'user';
dataSources: Record<string, DataSourceConfig>;
theme: ViewTheme; // 'dashboard' | 'page' | 'minimal' | 'embed'
surface_type: string;
surface_config: Record<string, unknown>;
is_default: boolean | null;
parent_view_id: string | null;
created_at: string;
updated_at: string;
}parseViewBlocks
Normalises raw JSONB from the database into { blocks, blockOrder }. Handles two wire formats:
- Legacy array (
BlockConfig[]) — assigns IDs where missing, converts to keyed map - Flat map (
Record<string, BlockConfig>) — uses as-is;blockOrderfalls back to key insertion order
const { blocks, blockOrder } = parseViewBlocks(rawBlocks, rawBlockOrder);Rendering helpers
// Ordered array of BlockConfig for rendering loops
const rendered = orderedBlocks(view);
// Block count (length of blockOrder)
const count = blockCount(view);ViewInventoryRecord
Admin health-check shape. Pairs a ParsedViewRecord with its linked entity type, entity, and parent view, plus a health classification ('healthy' | 'needs-repair' | 'malformed') and an issues array with code, severity, and message for each problem.
Plugin Types
The plugin system lets tenants install custom renderers that override built-in block rendering. Core defines the manifest schema and DB row types; resolver logic lives in the supabase and runtime layers.
import {
PluginManifestSchema,
CreatePluginInputSchema,
UpdatePluginInputSchema,
CreateVersionInputSchema,
CreateBindingInputSchema,
PLUGIN_STATUSES,
VERSION_STATUSES,
RENDERER_SOURCES,
SANDBOX_ACTIONS,
} from "@sprinterai/core";
import type {
PluginManifest,
PluginRecord,
PluginVersionRecord,
RendererBindingRecord,
} from "@sprinterai/core";PluginManifest
Stored as JSONB in plugin_versions.manifest and validated at the API boundary with PluginManifestSchema:
const manifest: PluginManifest = {
name: "custom-chart",
version: "1.2.0",
family: "block",
description: "Custom D3 chart renderer",
targets: ["record:chart", "record:chart:opportunity"],
dataBindings: { record: true, schema: true, viewConfig: true },
actions: ["update-field", "navigate"],
configSchema: { colorScheme: { type: "string" } },
};targets use family:blockType[:entityTypeSlug] notation. More-specific targets take priority during renderer resolution.
Status constants
| Constant | Values |
|---|---|
PLUGIN_STATUSES | draft, published, archived |
VERSION_STATUSES | draft, review, approved, rejected |
RENDERER_SOURCES | custom, plugin, preset, builtin |
SANDBOX_ACTIONS | navigate, update-field, submit-form, open-dialog, copy-to-clipboard, refresh-data |
CRUD input schemas
| Schema | Purpose |
|---|---|
CreatePluginInputSchema | POST /plugins — slug (lowercase alphanumeric), name, family |
UpdatePluginInputSchema | PATCH /plugins/:id — name, description, author, icon, status |
CreateVersionInputSchema | POST /plugins/:id/versions — semver version string, manifest, optional bundle |
CreateBindingInputSchema | POST /bindings — plugin_version_id, family, target_key, optional priority/config |
UpdateBindingInputSchema | PATCH /bindings/:id — priority, enabled, config |
PDF Types
Server-safe structural types for the PDF viewer. No pdfjs-dist import — all types are plain TypeScript interfaces usable in server components without the browser-only library.
import type {
PdfDocumentProxy,
PdfHighlight,
SelectionContext,
SelectionAction,
PdfWorkerConfig,
PdfLoadOptions,
} from "@sprinterai/core";PdfDocumentProxy
Minimal structural type matching the pdfjs-dist PDFDocumentProxy shape. Allows document-handling code to be typed without server-side pdfjs-dist imports:
type PdfDocumentProxy = {
numPages: number;
getPage: (pageNum: number) => Promise<unknown>;
getDestination: (dest: string) => Promise<unknown>;
getPageIndex: (pageRef: unknown) => Promise<number>;
destroy: () => void;
};PdfHighlight
Highlight annotation with normalized (0–1) coordinates relative to the page:
interface PdfHighlight {
id: string;
page: number; // 0-based page index
x: number; // normalized 0-1 within page
y: number;
width: number;
height: number;
color?: string; // CSS color string
text?: string; // captured text content of the highlighted region
}SelectionContext and SelectionAction
Types for the text-selection toolbar shown when a user highlights text:
interface SelectionContext {
text: string;
pageIndex: number;
coordinates: { x: number; y: number; width: number; height: number };
rect: { top: number; left: number; bottom: number; right: number; width: number; height: number };
}
interface SelectionAction {
id: string;
label: string;
icon?: string; // Lucide icon name
onAction: (context: SelectionContext) => void;
}Email Types
@sprinterai/core defines the email contracts used by @sprinterai/runtime's email engine and available to any package lower in the dependency chain.
Import from the ./email subpath export:
import type {
EmailChannel,
EmailMessage,
EmailProvider,
SendEmailInput,
SendEmailResult,
NotificationEmailContext,
DigestEmailContext,
NotificationPreferences,
} from "@sprinterai/core/email";
import {
SendEmailInputSchema,
NotificationPreferencesSchema,
DEFAULT_NOTIFICATION_PREFERENCES,
} from "@sprinterai/core/email";Core types
| Type | Description |
|---|---|
EmailChannel | 'transactional' | 'digest' | 'notification' |
EmailMessage | Wire-level message: to, subject, html, optional from, replyTo, tags |
SendEmailInput | Validated subset of EmailMessage; inferred from SendEmailInputSchema |
SendEmailResult | { id: string; success: boolean } returned by a provider |
EmailProvider | Interface with send(message) and optional sendBatch(messages) |
Render context types
| Type | Required fields | Optional fields |
|---|---|---|
NotificationEmailContext | title, body | recipientName, link, linkLabel, tenantName |
DigestEmailContext | period, items[] | recipientName, tenantName, summary |
items entries each carry title (required) plus optional description, link, and timestamp.
Notification preferences
NotificationPreferencesSchema validates user-level notification settings at API boundaries:
const prefs = NotificationPreferencesSchema.parse(rawUserSettings);
// {
// emailFrequency: "realtime" | "daily" | "weekly" | "never" (default: "never")
// emailNotifications: boolean (default: false)
// inAppNotifications: boolean (default: true)
// activityUpdates: boolean (default: true)
// }Use DEFAULT_NOTIFICATION_PREFERENCES as the fallback when a user has no saved preferences.
EmailConfig
EmailConfig (API key, from address) lives in packages/core/src/provider/provider-types.ts as part of the top-level ProviderConfig. Import it from the root:
import type { EmailConfig, ProviderConfig } from "@sprinterai/core";See @sprinterai/runtime for createEmailEngine, which consumes EmailProvider and EmailConfig.
Store Interfaces
Core defines abstract store interfaces that backends implement:
interface EntityStore {
getEntityType(slugOrId: string): Promise<EntityTypeRecord | null>;
listEntityTypes(): Promise<EntityTypeRecord[]>;
getEntity(id: string): Promise<EntityRecord | null>;
searchEntities(query: string, options?: { typeSlug?: string; tag?: string; limit?: number }): Promise<EntityRecord[]>;
createEntity(input: CreateEntityInput): Promise<EntityRecord>;
updateEntity(id: string, input: UpdateEntityInput): Promise<EntityRecord>;
deleteEntity(id: string): Promise<void>;
createRelation(fromId: string, toId: string, type: string): Promise<void>;
getEntityCounts(tenantId: string): Promise<Record<string, number>>;
}ChatStore
ChatStore covers message history, participant management, and unread tracking:
interface ChatStore {
// Core message operations
getChat(chatId: string): Promise<ConversationListItem | null>;
createChat(options: { title?: string; chatType: 'ai' | 'direct' | 'group'; agentSlug?: string; userId?: string }): Promise<ConversationListItem>;
getMessages(chatId: string, options?: { limit?: number; before?: string }): Promise<MessageRecord[]>;
saveMessage(chatId: string, input: SaveMessageInput): Promise<MessageRecord>;
getChatsByUser(userId: string, options?: { limit?: number }): Promise<ConversationListItem[]>;
// Participant management
getParticipants(chatId: string): Promise<ChatParticipant[]>;
addParticipant(chatId: string, tenantId: string, participantId: string, participantType: ParticipantType, options?: { role?: ParticipantRole; autoRespond?: boolean }): Promise<ChatParticipant>;
removeParticipant(chatId: string, participantId: string): Promise<void>;
// Unread tracking
markAsRead(chatId: string, userId: string): Promise<void>;
getUnreadCount(userId: string): Promise<number>;
}ChatParticipant is the full participant record returned by getParticipants and addParticipant:
interface ChatParticipant {
id: string;
chat_id: string;
tenant_id: string;
participant_type: ParticipantType; // 'user' | 'agent'
participant_id: string;
role: ParticipantRole; // 'owner' | 'member'
auto_respond: boolean;
joined_at: string;
last_read_at: string;
}Import the chat types from the root barrel:
import type {
ChatParticipant,
ParticipantType,
ParticipantRole,
ConversationListItem,
ChatStore,
} from "@sprinterai/core";Other stores follow the same pattern: AgentStore, TenantStore, ToolStore, ViewStore, TaskStore, MemoryStore.
See @sprinterai/runtime for in-memory implementations (including InMemoryChatStore) and inbox helpers (buildConversationList, computeUnreadCount, parseMentions, filterRespondingAgents). See @sprinterai/supabase for Supabase implementations.
Tenant and Permissions
import { ROLE_IDS, isAdminRole, mapRoleSlug } from "@sprinterai/core";
// ROLE_IDS maps slug -> UUID
const adminRoleId = ROLE_IDS.tenant_admin;
// Check if a role is admin-level
isAdminRole("system_admin"); // true
isAdminRole("viewer"); // false
// Map DB role slug to app role
mapRoleSlug("system_admin"); // "owner"Available roles: system_admin, tenant_admin, editor, member, viewer, guest.
63 granular permissions in the AppPermission type, format: {resource}.{level}.{action}.
Response Scoring
import { computeResponseScore, isNumericDimension } from "@sprinterai/core";
// Compute weighted score from dimension scores
const score = computeResponseScore(dimensionScores, criteriaSet);Automation Types
@sprinterai/core defines the unified automation contracts used by any UI or backend that schedules and monitors automated agent runs.
import type {
AutomationType,
UnifiedAutomation,
AutomationRun,
AutomationStore,
} from "@sprinterai/core";
import { AUTOMATION_TYPES, AUTOMATION_TYPE_LABELS, AUTOMATION_TYPE_ICONS } from "@sprinterai/core";AutomationType
type AutomationType = 'heartbeat' | 'source-sync';| Value | Label | Icon |
|---|---|---|
'heartbeat' | Agent Heartbeat | heart-pulse |
'source-sync' | Source Sync | refresh-cw |
UnifiedAutomation
A single view across both automation types, suitable for list/detail UIs:
interface UnifiedAutomation {
id: string;
name: string;
type: AutomationType;
icon: string;
schedule: string | null; // cron expression, or null if not scheduled
scheduleHuman: string; // human-readable schedule description
enabled: boolean;
lastRunAt: string | null;
lastRunStatus: string | null;
nextRunAt: string | null;
agentId?: string; // heartbeat automations only
entityId?: string; // source-sync automations only
}AutomationRun
Execution record for a single automation run:
interface AutomationRun {
id: string;
status: string;
started_at: string | null;
completed_at: string | null;
duration_ms: number | null;
tokens_used: number | null;
cost_cents: number | null;
error_log: string | null;
metadata: Record<string, unknown> | null;
chat_id: string | null; // linked chat session, if any
}AutomationStore
interface AutomationStore {
listAutomations(tenantId: string): Promise<UnifiedAutomation[]>;
getAutomation(tenantId: string, id: string): Promise<UnifiedAutomation | null>;
setEnabled(tenantId: string, id: string, enabled: boolean): Promise<void>;
listRuns(tenantId: string, automationId: string, limit?: number): Promise<AutomationRun[]>;
}See @sprinterai/runtime for cronToHuman, getNextRun, and AutomationPlan — the runtime utilities for schedule display and AI-assisted automation creation.
Settings Types
The settings module provides a typed key-value store contract for tenant and user preferences.
import type { TenantSetting, SettingKey, JobFunction, DashboardPreferences, SettingsStore } from "@sprinterai/core";
import { SETTING_KEYS, JOB_FUNCTIONS, JOB_FUNCTION_LABELS } from "@sprinterai/core";SettingKey
Seven built-in keys:
| Key | Stores |
|---|---|
'theme' | Tenant color/theme config |
'navigation' | Nav tree config |
'ai_limits' | Model usage limits |
'branding' | BrandingConfig (see below) |
'agent_context' | Default agent context |
'workflow_agent_defaults' | Workflow agent defaults |
'dashboard' | DashboardPreferences |
TenantSetting
interface TenantSetting {
id: string;
tenantId: string;
userId: string | null; // null = tenant-level; string = user-scoped
key: SettingKey;
value: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}DashboardPreferences
Stored under key 'dashboard', used for onboarding personalization:
interface DashboardPreferences {
jobFunction?: JobFunction; // 'executive' | 'sales' | 'content_ops' | 'operations' | 'analyst' | 'other'
pinnedEntityTypes?: string[];
onboardingCompleted?: boolean;
}SettingsStore
interface SettingsStore {
getSetting(tenantId: string, key: SettingKey, userId?: string): Promise<TenantSetting | null>;
setSetting(tenantId: string, key: SettingKey, value: Record<string, unknown>, userId?: string): Promise<TenantSetting>;
listSettings(tenantId: string, userId?: string): Promise<TenantSetting[]>;
deleteSetting(tenantId: string, key: SettingKey, userId?: string): Promise<void>;
}See @sprinterai/runtime for resolveSettings (cascade merger) and shouldShowDashboardRoleHint (onboarding gate).
Branding Types
Per-tenant app customization config, typically stored in SettingsStore under key 'branding'.
import { BrandingConfigSchema, DEFAULT_BRANDING } from "@sprinterai/core";
import type { BrandingConfig } from "@sprinterai/core";
// Validate incoming branding payload at the API boundary
const branding = BrandingConfigSchema.parse(rawValue);interface BrandingConfig {
appName?: string; // Sidebar header text (max 50 chars)
logoUrl?: string; // Custom logo image URL (takes precedence over logoIcon)
logoIcon?: string; // Icon name from the platform icon map (max 50 chars)
tagline?: string; // Subtitle under app name (max 100 chars)
}When no tenant setting is configured, fall back to:
const DEFAULT_BRANDING = {
appName: 'Sprinter',
tagline: 'AI-powered workspace',
};Workspace Templates
The template store contract supports rich workspace templates with views, sample entities, and industry categorization.
import type {
WorkspaceTemplate,
TemplateView,
TemplateSampleEntity,
TemplateCategory,
ApplyTemplateOptions,
ApplyTemplateResult,
TemplateStore,
} from "@sprinterai/core";
import { TEMPLATE_CATEGORIES, TEMPLATE_CATEGORY_LABELS } from "@sprinterai/core";WorkspaceTemplate fields
| Field | Type | Description |
|---|---|---|
entity_types | TemplateEntityType[] | Entity type definitions to create |
agents | TemplateAgent[] | Agent definitions to create |
views | TemplateView[] | View snapshots to create |
sample_entities | TemplateSampleEntity[] | Seed data (created only when opted in) |
nav_config | Record<string, unknown> | null | Navigation tree to apply |
theme | Record<string, unknown> | null | Theme to apply |
slug | string? | Unique reference slug |
category | TemplateCategory? | Industry/use-case classification |
is_featured | boolean? | Show in public gallery |
tenant_id | string | null? | null = global template |
Template categories
| Value | Label |
|---|---|
'pe' | Private Equity |
'manufacturing' | Manufacturing |
'legal' | Legal |
'consulting' | Consulting |
'general' | General |
ApplyTemplateOptions
interface ApplyTemplateOptions {
createSampleEntities?: boolean; // default: false
updateNavigation?: boolean; // default: true
applyTheme?: boolean; // default: false
}ApplyTemplateResult
interface ApplyTemplateResult {
entityTypesCreated: number;
agentsCreated: number;
navigationUpdated: boolean;
themeUpdated: boolean;
viewsCreated?: number;
sampleEntitiesCreated?: number;
}See @sprinterai/runtime for createTemplateCloner, which provides the store-agnostic engine for applying templates.
External Data Types
@sprinterai/core defines the contracts for ingesting external metric data via webhooks and API polling. Available from the root barrel or the ./external-data subpath:
import {
EXTERNAL_DATA_SOURCE_TYPES,
MetricItemSchema,
WebhookPayloadSchema,
CreateDataSourceSchema,
UpdateDataSourceSchema,
PollConfigSchema,
ApiConfigSchema,
} from "@sprinterai/core";
import type {
ExternalDataSourceType,
ExternalDataSource,
ExternalDataPoint,
DataPointMetadata,
MetricItem,
WebhookPayload,
CreateDataSourceInput,
UpdateDataSourceInput,
PollConfig,
ApiConfig,
CreateDataPointInput,
ExternalDataStore,
} from "@sprinterai/core";ExternalDataSource
A registered data source with routing token and optional signature secret:
interface ExternalDataSource {
id: string;
tenant_id: string;
name: string;
description: string | null;
source_type: ExternalDataSourceType; // 'webhook' | 'api'
token: string; // unique routing token in webhook URL
signature_secret: string | null; // HMAC-SHA256 secret; null = unsigned
signature_header: string; // header carrying the signature value
enabled: boolean;
last_received_at: string | null;
push_count: number;
created_at: string;
updated_at: string;
}ExternalDataPoint
A single recorded metric value from an external source:
interface ExternalDataPoint {
id: string;
data_source_id: string;
tenant_id: string;
metric_key: string; // e.g. 'monthly_revenue', 'active_users'
label: string; // display label
value: number;
metadata: DataPointMetadata;
recorded_at: string; // ISO timestamp — may be back-dated by the sender
created_at: string;
}
interface DataPointMetadata {
change?: string; // relative change string, e.g. "+3.2%" or "-120"
icon?: string; // Lucide icon name
unit?: string; // unit label, e.g. "%", "$", "users"
group?: string; // grouping key for chart series
[key: string]: unknown;
}Webhook payload schema
POST bodies sent to POST /api/webhooks/:token are validated against WebhookPayloadSchema:
// Validated payload shape
const payload = WebhookPayloadSchema.parse(rawBody);
// { metrics: MetricItem[] }
// MetricItem — one measurement per key
const item: MetricItem = {
key: "monthly_revenue",
label: "Monthly Revenue",
value: 125000,
change: "+12.4%", // optional
unit: "$", // optional
icon: "trending-up", // optional
recorded_at: "2026-04-01T00:00:00Z", // optional — defaults to now
};WebhookPayloadSchema enforces metrics.length >= 1 and metrics.length <= 100.
Poll / API config schemas
For API-polling sources:
// Polling source: scheduled pull
const pollConfig = PollConfigSchema.parse({
pollUrl: "https://api.example.com/metrics",
pollInterval: "1h", // '5m' | '15m' | '1h' | '6h' | '24h'
pollTransform: "data.metrics", // JSONPath to extract metrics array
pollHeaders: { Authorization: "Bearer token" },
});
// On-demand API source
const apiConfig = ApiConfigSchema.parse({
apiUrl: "https://api.example.com/stats",
apiHeaders: { "X-API-Key": "..." },
apiTransform: "results",
cacheTtl: 300, // seconds; default 300
});ExternalDataStore
Abstract store interface — implement per backend:
interface ExternalDataStore {
listDataSources(tenantId: string): Promise<ExternalDataSource[]>;
getDataSourceByToken(token: string): Promise<ExternalDataSource | null>;
createDataSource(tenantId: string, input: CreateDataSourceInput): Promise<ExternalDataSource>;
updateDataSource(id: string, tenantId: string, input: UpdateDataSourceInput): Promise<ExternalDataSource | null>;
deleteDataSource(id: string, tenantId: string): Promise<void>;
recordDataPoints(points: CreateDataPointInput[]): Promise<ExternalDataPoint[]>;
getLatestMetrics(dataSourceId: string): Promise<ExternalDataPoint[]>;
getMetricHistory(dataSourceId: string, metricKey: string, limit?: number): Promise<ExternalDataPoint[]>;
}See @sprinterai/runtime for createExternalDataReceiver, which handles token routing, signature verification, and bulk data point insertion.
Entity Markdown
@sprinterai/core provides three pure functions for serializing entities to and from Obsidian-compatible markdown. Import from the root barrel:
import {
generateEntityMarkdown,
parseEntityMarkdown,
extractWikilinkTitles,
} from "@sprinterai/core";
import type {
GenerateEntityMarkdownInput,
ParsedEntityMarkdown,
} from "@sprinterai/core";generateEntityMarkdown
Produces an Obsidian-compatible markdown string: YAML frontmatter with type, tags, and all content fields, followed by an H1 heading and optional body:
const md = generateEntityMarkdown({
title: "Acme Corp",
typeSlug: "account",
content: { stage: "active", arr: "1200000" },
tags: ["portfolio", "saas"],
body: "Notes about Acme Corp. See also [[Beta Inc]].",
});
// ---
// type: account
// tags: [portfolio, saas]
// stage: active
// arr: 1200000
// ---
//
// # Acme Corp
//
// Notes about Acme Corp. See also [[Beta Inc]].Array values in content are serialized as [a, b, c]. Null/undefined values are omitted.
parseEntityMarkdown
Inverse of generateEntityMarkdown. Extracts all frontmatter fields, the H1 title, body text, and any [[wikilink]] references found in the body:
const parsed = parseEntityMarkdown(markdown);
// {
// typeSlug: "account",
// title: "Acme Corp",
// body: "Notes about Acme Corp. See also [[Beta Inc]].",
// content: { stage: "active", arr: "1200000" },
// tags: ["portfolio", "saas"],
// wikilinkTitles: ["Beta Inc"],
// }Throws if the frontmatter delimiters (---) are missing, the type field is absent, or no H1 heading is found.
extractWikilinkTitles
Extracts all unique [[title]] patterns from a markdown string. Trims whitespace and deduplicates results:
extractWikilinkTitles("See [[Acme Corp]] and [[Acme Corp]] and [[Beta Inc]].");
// ["Acme Corp", "Beta Inc"]View Response
@sprinterai/core defines the ViewResponse type and ViewResponseStore interface for persisting embed form progress and completion. Import from the root barrel:
import type { ViewResponse, ViewResponseStore } from "@sprinterai/core";ViewResponse
Persists the state of an in-progress or completed embed form session:
interface ViewResponse {
id: string;
viewId: string;
publishToken: string;
sessionId: string;
data: Record<string, unknown>; // Collected form field values
completedAt: string | null; // ISO timestamp, or null if still in progress
createdAt: string;
updatedAt: string;
}ViewResponseStore
Abstract store interface for view response persistence:
interface ViewResponseStore {
// Create or update a response for the given session
upsertResponse(params: UpsertViewResponseParams): Promise<ViewResponse>;
// Look up an in-progress or completed response by session
getBySession(params: GetViewResponseBySessionParams): Promise<ViewResponse | null>;
// Mark a response as completed (sets completedAt to now)
completeResponse(params: CompleteViewResponseParams): Promise<ViewResponse | null>;
// List all responses for a view (requires tenantId for scoping)
listByView(viewId: string, tenantId: string): Promise<ViewResponse[]>;
}upsertResponse creates the record on first save and updates data on subsequent saves. completeResponse returns null when no matching session exists rather than throwing.