The Full Plures Application Framework
| Category | Status |
|---|---|
| CI/CD | β Automated testing & builds |
| Version | 0.1.0 (Alpha) |
| Runtime Support | Node.js 18+, Deno (experimental) |
| Package Registries | npm β / JSR π§ (coming soon) |
| Test Coverage | 63 tests passing |
| Documentation | π In Progress |
| Integration | Status | Notes |
|---|---|---|
| Praxis Cloud | β Available | Azure-hosted relay for sync & monetization |
| PluresDB | π§ In Development | Local-first reactive datastore |
| Unum | π§ Planned | Identity & channels |
| Svelte | β Supported | Component generation |
| Tauri | π§ Planned | Cross-platform runtime |
| CodeCanvas | π§ Planned | Visual schema editor |
| State-Docs | π§ Planned | Documentation generation |
Praxis is not just a logic engineβit's a complete framework for building modern, local-first, distributed applications. It provides:
Praxis provides these integrated capabilities:
Schema-Driven Development
Local-First Architecture
Strong typing and functional programming
Fact<Tag, Payload>, Event<Tag, Payload>, Rule<Context, InFact, OutFact>Visual and Code Workflows
Cross-Platform and Cross-Language
praxis create, praxis generate, praxis canvas commandsstep functionsprotocolVersion field in state)Praxis.psm1) for cross-language usageA comprehensive example demonstrating all Praxis features:
npm install @plures/praxis
# Coming soon - JSR publishing in progress
deno add @plures/praxis
For now, you can use Praxis with Deno via import maps:
// import_map.json
{
"imports": {
"@plures/praxis": "npm:@plures/praxis@^0.1.0"
}
}
# Clone the repository
git clone https://github.com/plures/praxis.git
cd praxis
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
The Praxis CLI provides commands for creating and managing applications.
# Install Praxis globally
npm install -g @plures/praxis
# Create a new application
praxis create app my-app
cd my-app
npm install
# Generate code from schemas
praxis generate --schema src/schemas/app.schema.ts
# Open CodeCanvas for visual editing
praxis canvas src/schemas/app.schema.ts
# Start development server
npm run dev
# Build for production
npm run build
Available CLI commands:
praxis login - Authenticate with GitHub (device flow or token)praxis logout - Log out from Praxis Cloudpraxis whoami - Show current authenticated userpraxis create app [name] - Create new applicationpraxis create component [name] - Create new componentpraxis generate - Generate code from schemaspraxis canvas [schema] - Open visual editorpraxis orchestrate - Manage distributed systemspraxis cloud init - Connect to Praxis Cloudpraxis cloud status - Check cloud connectionpraxis cloud sync - Manually sync to cloudpraxis cloud usage - View cloud usage metricspraxis dev - Start development serverpraxis build - Build for productionSee docs/guides/getting-started.md for detailed instructions.
Connect your application to Praxis Cloud for automatic synchronization with GitHub-native authentication and billing:
# Authenticate with GitHub
npx praxis login
# Initialize cloud connection
npx praxis cloud init
# In your code
import { connectRelay } from "@plures/praxis/cloud";
const relay = await connectRelay("https://praxis-relay.azurewebsites.net", {
appId: "my-app",
authToken: process.env.GITHUB_TOKEN,
autoSync: true
});
// Sync automatically handles CRDT merge
await relay.sync({
type: "delta",
appId: "my-app",
clock: {},
facts: [...],
timestamp: Date.now()
});
See src/cloud/README.md and examples/cloud-sync for details.
import {
createPraxisEngine,
PraxisRegistry,
defineFact,
defineEvent,
defineRule,
} from "@plures/praxis";
// Define the context type
interface AuthContext {
currentUser: string | null;
}
// Define facts and events
const UserLoggedIn = defineFact<"UserLoggedIn", { userId: string }>("UserLoggedIn");
const Login = defineEvent<"LOGIN", { username: string }>("LOGIN");
// Define rules
const loginRule = defineRule<AuthContext>({
id: "auth.login",
description: "Process login event",
impl: (state, events) => {
const loginEvent = events.find(Login.is);
if (loginEvent) {
state.context.currentUser = loginEvent.payload.username;
return [UserLoggedIn.create({ userId: loginEvent.payload.username })];
}
return [];
},
});
// Create engine
const registry = new PraxisRegistry<AuthContext>();
registry.registerRule(loginRule);
const engine = createPraxisEngine({
initialContext: { currentUser: null },
registry,
});
// Dispatch events
const result = engine.step([Login.create({ username: "alice" })]);
console.log(result.state.facts); // [{ tag: "UserLoggedIn", payload: { userId: "alice" } }]
console.log(engine.getContext()); // { currentUser: "alice" }
import { defineConstraint } from "@plures/praxis";
const maxSessionsConstraint = defineConstraint<AuthContext>({
id: "auth.maxSessions",
description: "Only one user can be logged in at a time",
impl: (state) => {
return state.context.currentUser === null || "User already logged in";
},
});
registry.registerConstraint(maxSessionsConstraint);
import { createPraxisStore, createDerivedStore } from "@plures/praxis/svelte";
const stateStore = createPraxisStore(engine);
const userStore = createDerivedStore(engine, (ctx) => ctx.currentUser);
// In Svelte component:
// $: currentUser = $userStore;
// <button on:click={() => stateStore.dispatch([Login.create({ username: "alice" })])}>
// Login
// </button>
The language-neutral core protocol forms the foundation of Praxis:
// Facts and Events
interface PraxisFact {
tag: string;
payload: unknown;
}
interface PraxisEvent {
tag: string;
payload: unknown;
}
// State
interface PraxisState {
context: unknown;
facts: PraxisFact[];
meta?: Record<string, unknown>;
}
// Step Function (the conceptual core)
type PraxisStepFn = (
state: PraxisState,
events: PraxisEvent[],
config: PraxisStepConfig
) => PraxisStepResult;
This protocol is:
/praxis
βββ core/ # Core framework
β βββ schema/ # Schema system
β β βββ types.ts # Schema type definitions
β βββ logic/ # Logic engine (existing src/core/)
β β βββ protocol.ts # Language-neutral protocol
β β βββ rules.ts # Rules, constraints, and registry
β β βββ engine.ts # LogicEngine implementation
β β βββ actors.ts # Actor system
β β βββ introspection.ts # Introspection and visualization
β βββ component/ # Component generation
β β βββ generator.ts # Svelte component generator
β βββ runtime/ # Runtime abstractions
βββ integrations/ # Ecosystem integrations
β βββ pluresdb/ # PluresDB reactive datastore
β βββ unum/ # Unum identity and channels
β βββ adp/ # Architectural Decision Protocol
β βββ state-docs/ # State-Docs documentation
β βββ canvas/ # CodeCanvas visual editor
βββ cli/ # Command-line interface
β βββ index.ts # CLI entry point
β βββ commands/ # Command implementations
βββ templates/ # Project templates
β βββ basic-app/ # Basic application template
β βββ fullstack-app/ # Full-stack template
β βββ component/ # Component template
β βββ orchestrator/ # Distributed orchestration template
βββ examples/ # Example applications
β βββ offline-chat/ # Offline-first chat demo
β βββ knowledge-canvas/ # Knowledge management with Canvas
β βββ distributed-node/ # Self-orchestrating node demo
β βββ auth-basic/ # Login/logout example
β βββ cart/ # Shopping cart example
β βββ svelte-counter/ # Svelte integration example
β βββ hero-ecommerce/ # Comprehensive e-commerce demo
βββ docs/ # Framework documentation
βββ guides/ # User guides
β βββ getting-started.md # Getting started guide
β βββ canvas.md # CodeCanvas guide
β βββ orchestration.md # Orchestration guide
βββ api/ # API reference
βββ architecture/ # Architecture documentation
See FRAMEWORK.md for complete architecture documentation.
The repository includes four complete examples:
src/examples/hero-ecommerce)NEW! Comprehensive example demonstrating all Praxis features in a single application:
npm run build
node dist/examples/hero-ecommerce/index.js
examples/offline-chat)NEW! Demonstrates local-first architecture with PluresDB:
See examples/offline-chat/README.md
examples/knowledge-canvas)NEW! Showcases CodeCanvas integration for visual knowledge management:
See examples/knowledge-canvas/README.md
examples/distributed-node)NEW! Demonstrates distributed orchestration with DSC/MCP:
See examples/distributed-node/README.md
examples/terminal-node)NEW! Demonstrates the terminal node feature for command execution:
npm run build
node examples/terminal-node/index.js
See examples/terminal-node/README.md and docs/TERMINAL_NODE.md
src/examples/auth-basic)Login/logout with facts, rules, and constraints.
npm run build
node dist/examples/auth-basic/index.js
src/examples/cart)Shopping cart with multiple rules, constraints, and complex state management.
npm run build
node dist/examples/cart/index.js
src/examples/svelte-counter)Counter example showing Svelte v5 integration with reactive stores.
npm run build
node dist/examples/svelte-counter/index.js
PraxisFact, PraxisEvent, PraxisState - Protocol typesLogicEngine<TContext> - Main engine classPraxisRegistry<TContext> - Rule and constraint registryActor<TContext> - Actor interfaceActorManager<TContext> - Actor lifecycle managementdefineFact<TTag, TPayload>(tag) - Define a typed factdefineEvent<TTag, TPayload>(tag) - Define a typed eventdefineRule<TContext>(options) - Define a ruledefineConstraint<TContext>(options) - Define a constraintdefineModule<TContext>(options) - Bundle rules and constraintsfindEvent(events, definition) - Find first matching eventfindFact(facts, definition) - Find first matching factfilterEvents(events, definition) - Filter events by typefilterFacts(facts, definition) - Filter facts by typeNEW! Tools for examining and visualizing your Praxis logic:
import { createIntrospector, PRAXIS_PROTOCOL_VERSION } from "@plures/praxis";
const introspector = createIntrospector(registry);
// Get statistics
const stats = introspector.getStats();
console.log(`Rules: ${stats.ruleCount}, Constraints: ${stats.constraintCount}`);
// Generate JSON schema
const schema = introspector.generateSchema(PRAXIS_PROTOCOL_VERSION);
// Generate graph visualization
const graph = introspector.generateGraph();
// Export to Graphviz DOT format
const dot = introspector.exportDOT();
fs.writeFileSync('registry.dot', dot);
// Export to Mermaid format
const mermaid = introspector.exportMermaid();
// Search rules and constraints
const authRules = introspector.searchRules('auth');
const maxConstraints = introspector.searchConstraints('max');
Available methods:
getStats() - Get registry statisticsgenerateSchema(protocolVersion) - Generate JSON schemagenerateGraph() - Generate graph representationexportDOT() - Export to Graphviz DOT formatexportMermaid() - Export to Mermaid diagram formatgetRuleInfo(id) - Get detailed rule informationgetConstraintInfo(id) - Get detailed constraint informationsearchRules(query) - Search rules by textsearchConstraints(query) - Search constraints by textPraxis integrates with the full Plures ecosystem:
Local-first reactive datastore for offline-capable applications.
import { createPluresDB } from '@plures/pluresdb';
// Create database from schema
const db = createPluresDB({
name: 'my-app',
version: 1,
stores: {
// Generated from Praxis schema
users: { keyPath: 'id', indexes: ['email'] },
tasks: { keyPath: 'id', indexes: ['status', 'createdAt'] },
},
sync: {
enabled: true,
endpoint: 'ws://localhost:8080/sync',
conflictResolution: 'last-write-wins',
},
});
// Use with Praxis logic engine
engine.step([TaskCreated.create({ taskId, title })]);
await db.tasks.add({ id: taskId, title, status: 'pending' });
Status: Foundation in place (src/integrations/pluresdb.ts)
Documentation: docs/guides/pluresdb.md
Identity and channels for distributed systems.
import { createUnumIdentity, createChannel } from '@plures/unum';
// Create identity
const identity = await createUnumIdentity({
name: 'my-app-node',
keys: await generateKeys(),
});
// Create channel for messaging
const channel = await createChannel({
name: 'app-events',
participants: [identity.id],
});
// Integrate with Praxis actors
const unumActor = createActor('unum-bridge', identity, async (event) => {
// Bridge Praxis events to Unum channels
await channel.publish(event);
});
Status: Planned
Use Cases: Distributed messaging, identity management, authentication
Architectural Decision Protocol for guardrails and governance.
import { createADP } from '@plures/adp';
// Track architectural decisions from schemas
const adp = createADP({
source: 'praxis-schema',
decisions: [
{
id: 'ADR-001',
title: 'Use PluresDB for local-first storage',
context: 'Need offline-capable data storage',
decision: 'Adopt PluresDB',
consequences: ['Offline support', 'Sync complexity'],
},
],
});
// Enforce guardrails
adp.enforce({
rule: 'no-direct-database-access',
check: (code) => !code.includes('direct-sql'),
});
Status: Planned
Use Cases: Architecture documentation, compliance checking, guardrails
Living documentation generated from Praxis schemas.
import { generateStateDocs } from '@plures/state-docs';
// Generate documentation from schema
const docs = await generateStateDocs({
schema: appSchema,
logic: logicDefinitions,
components: componentDefinitions,
output: './docs',
format: 'markdown', // or 'html', 'pdf'
});
// Documentation includes:
// - Data model diagrams
// - Logic flow diagrams
// - Component catalog
// - API reference
// - Usage examples
Status: Planned
Documentation: See examples for State-Docs integration patterns
Visual IDE for schema and logic editing.
# Open Canvas for visual editing
praxis canvas src/schemas/app.schema.ts
# Features:
# - Visual schema design
# - Logic flow editor
# - Component preview
# - Real-time collaboration
# - Export to code
Status: Planned
Documentation: docs/guides/canvas.md
Cross-platform runtime for web, desktop, and mobile.
// Svelte v5 integration (available now)
import { createPraxisStore } from '@plures/praxis/svelte';
const stateStore = createPraxisStore(engine);
const userStore = createDerivedStore(engine, (ctx) => ctx.currentUser);
// In Svelte component:
// $: currentUser = $userStore;
// Desktop app with Tauri
npm run tauri:dev // Development
npm run tauri:build // Production
Status: Svelte integration available, Tauri templates planned
Platform Support: Web (now), Desktop (planned), Mobile (future)
NEW! Full PowerShell adapter for using Praxis from PowerShell scripts:
# Import module
Import-Module ./powershell/Praxis.psm1
# Initialize adapter
Initialize-PraxisAdapter -EnginePath "./dist/adapters/cli.js"
# Create state and events
$state = New-PraxisState -Context @{ count = 0 }
$event = New-PraxisEvent -Tag "INCREMENT" -Payload @{}
# Process step
$result = Invoke-PraxisStep -State $state -Events @($event) -ConfigPath "./config.json"
# Use result
Write-Host "Count: $($result.state.context.count)"
See powershell/README.md for complete documentation and examples.
Cross-language adapter for C# is planned with similar JSON-based protocol.
The core protocol is designed to be implemented in other languages:
C# (future):
PraxisState Step(PraxisState state, IEnumerable<PraxisEvent> events);
PowerShell (future):
$newState = Invoke-PraxisStep -State $state -Events $events
// Define your state interface AppState extends PraxisState { facts: { count: number; lastAction?: string; }; }
// Create a registry const registry = createRegistry<AppState, PraxisEvent>();
// Define rules using the DSL const incrementRule = rule<AppState, PraxisEvent>() .id('increment') .describe('Increment counter when increment event occurs') .on('INCREMENT') .when((state, event) => true) .then((state, event) => [{ type: 'LOG', payload: { message: 'Counter incremented' } }]) .build();
// Define constraints
const positiveCountConstraint = constraint
// Register rules and constraints registry.registerRule(incrementRule); registry.registerConstraint(positiveCountConstraint);
// Create a step function with custom reducer const step = createStepFunction<AppState, PraxisEvent>({ registry, checkConstraints: true, reducer: (state, event) => { if (event.type === 'INCREMENT') { return { ...state, facts: { ...state.facts, count: state.facts.count + 1, lastAction: 'increment', }, }; } return state; }, });
// Use the step function const initialState: AppState = { facts: { count: 0 }, };
const event: PraxisEvent = { type: 'INCREMENT', timestamp: Date.now(), };
const result = step(initialState, event); console.log(result.state.facts.count); // 1 console.log(result.effects); // [{ type: 'LOG', payload: { message: 'Counter incremented' } }]
## Core Concepts
### State and Events
**PraxisState** represents the current facts and context:
```typescript
interface PraxisState {
facts: Record<string, unknown>;
metadata?: {
version?: number;
lastUpdated?: number;
[key: string]: unknown;
};
}
PraxisEvent represents things that have happened:
interface PraxisEvent {
type: string;
timestamp: number;
data?: Record<string, unknown>;
metadata?: {
correlationId?: string;
source?: string;
[key: string]: unknown;
};
}
Rules are condition-action pairs that fire when conditions are met:
const myRule = rule<MyState, MyEvent>()
.id('my-rule')
.describe('What this rule does')
.priority(10) // Higher priority rules execute first
.on('EVENT_TYPE') // Optional: only check for specific event types
.when((state, event) => {
// Condition: return true to fire the rule
return state.facts.someValue > 10;
})
.then((state, event) => {
// Action: return effects to execute
return [
{ type: 'SEND_EMAIL', payload: { to: '[email protected]' } },
{ type: 'LOG', payload: { level: 'info', message: 'Rule fired' } },
];
})
.build();
Constraints are invariants that must hold true:
const myConstraint = constraint<MyState>()
.id('my-constraint')
.describe('What this constraint ensures')
.check((state) => {
// Return true if constraint is satisfied
return state.facts.balance >= 0;
})
.message('Balance cannot be negative')
.build();
The registry manages rules and constraints:
const registry = createRegistry<MyState, MyEvent>();
// Register rules and constraints
registry.registerRule(myRule);
registry.registerConstraint(myConstraint);
// Evaluate rules for a state transition
const effects = registry.evaluateRules(state, event);
// Check constraints
const violations = registry.checkConstraints(state);
// Get statistics
const stats = registry.getStats();
Step functions are pure functions that transition state:
// With registry integration
const step = createStepFunction({
registry,
checkConstraints: true,
reducer: (state, event) => {
// Your state transition logic
return newState;
},
});
// Simple step without registry
const simpleStep = step((state, event) => {
// Direct state transformation
return { ...state, facts: { ...state.facts, updated: true } };
});
// Compose multiple step functions
const composedStep = compose(step1, step2, step3);
Actors maintain their own state and respond to events:
import { createActor, createActorSystem } from '@plures/praxis';
// Create an actor
const myActor = createActor(
'actor-1',
initialState,
stepFunction,
'counter-actor'
);
// Create an actor system
const system = createActorSystem();
system.register(myActor);
// Send events to actors
const result = system.send('actor-1', event);
// Broadcast to all actors
const results = system.broadcast(event);
Flows represent sequences of events:
import { createFlow, advanceFlow } from '@plures/praxis';
// Define a flow
const flow = createFlow('onboarding-flow', [
{ id: 'step1', expectedEventType: 'USER_REGISTERED' },
{ id: 'step2', expectedEventType: 'EMAIL_VERIFIED' },
{ id: 'step3', expectedEventType: 'PROFILE_COMPLETED' },
]);
// Advance the flow with events
const { flow: updatedFlow, accepted } = advanceFlow(flow, event);
if (updatedFlow.complete) {
console.log('Flow completed!');
}
interface UserEvent extends PraxisEvent {
type: 'USER_LOGIN' | 'USER_LOGOUT';
data: {
userId: string;
sessionId: string;
};
}
const registry = createRegistry<AppState, UserEvent>();
Effects are side-effect descriptions that can be executed outside the pure core:
async function executeEffects(effects: Effect[]) {
for (const effect of effects) {
switch (effect.type) {
case 'SEND_EMAIL':
await emailService.send(effect.payload);
break;
case 'LOG':
console.log(effect.payload);
break;
// Add more effect handlers as needed
}
}
}
const result = step(state, event);
if (result.effects) {
await executeEffects(result.effects);
}
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Type check
npm run typecheck
# Run with Deno
deno task dev
# Run tests
deno task test
# Lint and format
deno task lint
deno task fmt
For more detailed development information, see CONTRIBUTING.md.
MIT License - see LICENSE for details.
Contributions are welcome! Please read our Contributing Guide to get started.
Please review our Code of Conduct before participating.
Praxis β Because application logic should be practical, provable, and portable.
Built with β€οΈ by the plures team