Skip to content

trestleinc/bridge

Repository files navigation

@trestleinc/bridge

Reactive data layer for schema-driven Cards, Procedures, and Deliverables.

Bridge is a Convex component that provides a structured approach to defining data fields, collection procedures, and reactive triggers. It's designed for applications that need to dynamically define what data to collect and when to trigger automated workflows.

Features

  • Cards - Field definitions with types, security levels, and subject associations
  • Procedures - Data collection definitions (forms, imports, APIs) that specify which cards to collect
  • Deliverables - Reactive triggers with prerequisites and conditions that fire when data is ready
  • Evaluations - Execution tracking with scheduling, status management, and results

Installation

bun add @trestleinc/bridge

Quick Start

1. Install the Convex Component

// convex/convex.config.ts
import { defineApp } from 'convex/server';
import bridge from '@trestleinc/bridge/convex.config';

const app = defineApp();
app.use(bridge);

export default app;

2. Create a Bridge Instance

// convex/bridge.ts
import { bridge } from '@trestleinc/bridge/server';
import { components } from './_generated/api';

// Minimal configuration - hooks are optional
export const b = bridge.create(components.bridge, {
	// Bind subject types to your host tables for automatic context resolution
	subjects: {
		beneficiary: { table: 'beneficiaries' },
		event: { table: 'events' },
		eventInstance: {
			table: 'eventInstances',
			parents: [{ field: 'eventId', subject: 'event' }],
		},
	},
});

3. Use in Queries and Mutations

// convex/cards.ts
import { b } from './bridge';

// Export bridge resources directly as Convex functions
export const get = b.cards.get;
export const find = b.cards.find;
export const list = b.cards.list;
export const create = b.cards.create;

API Reference

Cards

Field definitions with types and security metadata.

Method Description
b.cards.get Get a card by ID
b.cards.find Find a card by organization and slug
b.cards.list List cards for an organization
b.cards.create Create a new card

Procedures

Data collection definitions (forms, imports, APIs).

Method Description
b.procedures.get Get a procedure by ID
b.procedures.list List procedures for an organization
b.procedures.create Create a new procedure
b.procedures.update Update an existing procedure
b.procedures.remove Delete a procedure
b.procedures.submit Validate card values against procedure schema

Deliverables

Reactive triggers with conditions.

Method Description
b.deliverables.get Get a deliverable by ID
b.deliverables.list List deliverables for an organization
b.deliverables.create Create a new deliverable
b.deliverables.update Update an existing deliverable
b.deliverables.evaluate Check and trigger ready deliverables

Evaluations

Execution records for triggered deliverables.

Method Description
b.evaluations.get Get an evaluation by ID
b.evaluations.list List evaluations for a deliverable
b.evaluations.start Start a scheduled evaluation
b.evaluations.cancel Cancel a pending evaluation
b.evaluations.complete Mark an evaluation as complete

Data Model

Card

interface Card {
	id: string;
	organizationId: string;
	slug: string;
	label: string;
	cardType: CardType; // 'STRING' | 'NUMBER' | 'BOOLEAN' | 'DATE' | 'EMAIL' | 'URL' | 'PHONE' | 'SSN' | 'ADDRESS' | 'SUBJECT' | 'ARRAY'
	securityLevel: SecurityLevel; // 'PUBLIC' | 'CONFIDENTIAL' | 'RESTRICTED'
	subject: string;
	createdBy: string;
	createdAt: number;
}

Procedure

interface Procedure {
	id: string;
	organizationId: string;
	name: string;
	description?: string;
	procedureType: ProcedureType; // 'form' | 'import' | 'api'
	subject?: {
		type: string;
		operation: Operation; // 'create' | 'update' | 'delete'
	};
	cards: ProcedureCard[]; // Cards to collect with field mappings
	createdAt: number;
	updatedAt: number;
}

Deliverable

interface Deliverable {
	id: string;
	organizationId: string;
	name: string;
	description?: string;
	subject: string;
	operations: DeliverableOperations;
	schedule?: Schedule;
	status: DeliverableStatus; // 'active' | 'paused'
	createdAt: number;
	updatedAt: number;
}

Evaluation

interface Evaluation {
	id: string;
	deliverableId: string;
	organizationId: string;
	operation: Operation;
	context: EvaluationContext;
	variables: Record<string, unknown>;
	status: EvaluationStatus; // 'pending' | 'running' | 'completed' | 'failed'
	scheduledFor?: number;
	result?: EvaluationResult;
	createdAt: number;
	completedAt?: number;
}

Bridge Methods

The bridge instance provides convenience methods for common operations:

submit

Validate and submit card values through a procedure:

const result = await b.submit(ctx, {
	procedureId: 'proc_123',
	organizationId: 'org_456',
	subject: 'beneficiary',
	subjectId: 'ben_789',
	values: { firstName: 'John', lastName: 'Doe' },
});

if (result.success) {
	// Write validated values to your tables
}

evaluate

Trigger deliverable evaluation for a subject. If subjects are bound, variables are auto-resolved from the host table:

// With auto-resolution (subjects bound) - no variables needed!
const readiness = await b.evaluate(ctx, {
	organizationId: 'org_456',
	subject: 'beneficiary',
	subjectId: 'ben_789',
	operation: 'create',
});

for (const r of readiness) {
	if (r.ready) {
		console.log(`Deliverable ${r.deliverableId} triggered, evaluation ${r.evaluationId}`);
	}
}

resolve

Manually resolve subject data from a bound host table:

const variables = await b.resolve(ctx, 'beneficiary', 'ben_789');
// { firstName: 'John', lastName: 'Doe', email: 'john@example.com' }

register & execute

Register callback handlers and execute deliverables:

// Register handlers at module level
b.register('automation', async (deliverable, context) => {
	// Execute automation logic
	return { success: true, data: { sent: true } };
});

// Execute in an action
const result = await b.execute(deliverable, 'create', {
	subject: 'beneficiary',
	subjectId: 'ben_789',
	variables: { firstName: 'John' },
});

aggregate

Aggregate context from subject hierarchy:

const aggregated = await b.aggregate(ctx, {
	subject: 'eventInstance',
	subjectId: 'ei_123',
});
// Returns variables from eventInstance + parent event + associated beneficiary

Subject Bindings

Bind subject types to your host tables for automatic context resolution:

const b = bridge.create(components.bridge, {
	subjects: {
		beneficiary: { table: 'beneficiaries' },
		event: { table: 'events' },
		eventInstance: {
			table: 'eventInstances',
			parents: [{ field: 'eventId', subject: 'event' }],
		},
	},
});

When subjects are bound, Bridge can automatically fetch subject data when evaluating deliverables.

Authorization Pattern

Important: Component hooks receive generic context types that may not be compatible with your host app's schema. Handle authorization in wrapper functions where you have properly typed context:

// convex/procedures.ts - Authorization at wrapper level
import { query, mutation } from './_generated/server';
import { components } from './_generated/api';
import { verifyOrgAccess } from './permissions';

export const procedureGet = query({
	args: { id: v.string() },
	handler: async (ctx, { id }) => {
		const procedure = await ctx.runQuery(components.bridge.public.procedureGet, { id });
		if (procedure) {
			// Verify with properly typed context from your schema
			await verifyOrgAccess(ctx, procedure.organizationId);
		}
		return procedure;
	},
});

Type Exports

Shared (@trestleinc/bridge)

All validators and types are consolidated in a single source of truth:

import {
	// Enums with display names
	CardType,
	SecurityLevel,
	ProcedureType,
	Operation,
	EvaluationStatus,
	DeliverableStatus,

	// Validators
	cardTypeValidator,
	securityLevelValidator,
	procedureTypeValidator,
	operationValidator,
	evaluationStatusValidator,
	deliverableStatusValidator,

	// Types (derived from validators)
	type Card,
	type Procedure,
	type Deliverable,
	type Evaluation,
	type ProcedureCard,
	type DeliverableOperation,
	type EvaluationContext,
	type EvaluationResult,

	// Branded IDs
	type CardId,
	type ProcedureId,
	type DeliverableId,
	type EvaluationId,
	type OrganizationId,

	// ID factory
	createId,

	// Duration utilities
	type Duration, // "30s" | "5m" | "2h" | "7d"
	parseDuration,
	formatDuration,
} from '@trestleinc/bridge';

Server (@trestleinc/bridge/server)

import {
	bridge, // Factory to create bridge instance
	clientApi, // Alternative API factory
	createTriggers, // Generate Convex trigger handlers
	createSubjectTrigger, // Single trigger handler
	extractAttributeChanges, // Detect changed attributes

	// Error types
	BridgeError,
	NotFoundError,
	ValidationError,
	AuthorizationError,
	ConflictError,
} from '@trestleinc/bridge/server';

Client (@trestleinc/bridge/client)

import {
	// Error types
	NetworkError,
	AuthorizationError,
	NotFoundError,
	ValidationError,
	NonRetriableError,
} from '@trestleinc/bridge/client';

Logger ($/shared/logger)

All logging is unified in shared/ using LogTape with ANSI colored console output:

import { getLogger } from '$/shared/logger';

// Get a LogTape logger with category
const logger = getLogger(['bridge', 'cards']);
logger.info('Card created', { cardId: 'card_123' });
logger.debug('Processing request', { procedureId });
logger.error('Failed to evaluate', { error });

// Categories help filter and organize log output
// ANSI colored output is configured automatically

Project Structure

src/
├── shared/
│   ├── index.ts         # All validators, types, branded IDs, enums, utilities
│   └── logger.ts        # Unified LogTape logger (ANSI colored console output)
├── client/
│   └── index.ts         # Error types
├── server/
│   ├── index.ts         # Factory function, triggers, error types
│   └── resources/       # Resource implementations (cards, procedures, etc.)
└── component/
    └── ...              # Convex component internals

Getter / Factory / Namespace Pattern

Bridge follows a consistent API design pattern across all entry points:

1. Factory Pattern - Create configured instances:

import { bridge } from '@trestleinc/bridge/server';

// Factory creates a configured bridge instance
const b = bridge.create(components.bridge, {
	subjects: {
		beneficiary: { table: 'beneficiaries' },
		event: { table: 'events' },
	},
});

2. Namespace Pattern - Organized resource access:

// Resources are organized under namespaces
b.cards.get; // Get a card
b.cards.list; // List cards
b.procedures.create; // Create a procedure
b.deliverables.evaluate; // Evaluate deliverables
b.evaluations.complete; // Complete an evaluation

3. Getter Pattern - Direct property access for resource methods:

// Each method returns a Convex function reference
export const get = b.cards.get; // Re-export as Convex query
export const list = b.cards.list; // Re-export as Convex query
```typescript
// Resources are organized under namespaces
b.cards.get          // Get a card
b.cards.list         // List cards
b.procedures.create  // Create a procedure
b.deliverables.evaluate // Evaluate deliverables
b.evaluations.complete  // Complete an evaluation

3. Getter Pattern - Direct property access for resource methods:

// Each method returns a Convex function reference
export const get = b.cards.get; // Re-export as Convex query
export const list = b.cards.list; // Re-export as Convex query
export const create = b.cards.create; // Re-export as Convex mutation

4. Single Entry Point - All exports consolidated per layer:

// Server - everything from one import
import { bridge, BridgeError, NotFoundError, createTriggers } from '@trestleinc/bridge/server';

// Client - error types from one import
import { NetworkError, ValidationError } from '@trestleinc/bridge/client';

// Logger - unified in shared
import { getLogger } from '$/shared/logger';

// Shared - all validators and types from one import (default export)
import {
	Card,
	Procedure,
	Deliverable,
	Evaluation,
	CardType,
	SecurityLevel,
	ProcedureType,
	cardDocValidator,
	procedureDocValidator,
	createId,
	parseDuration,
	Card,
	Procedure,
	Deliverable,
	Evaluation,
	CardType,
	SecurityLevel,
	ProcedureType,
	cardDocValidator,
	procedureDocValidator,
	createId,
	parseDuration,
} from '@trestleinc/bridge';

Technology Stack

  • Convex - Database and serverless functions
  • LogTape - Structured logging

License

Apache-2.0

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •