README
¶
Orisun
Orisun is a batteries-included event store for systems that need durable event history, content-based consistency checks, and real-time delivery without running a separate broker.
It stores events transactionally in PostgreSQL or SQLite, publishes them through embedded NATS JetStream, and implements Command Context Consistency (CCC): each command defines its own consistency context by querying event data, then saves only if that context has not changed.
Why Orisun
- Content-based consistency: query events by JSON payload fields instead of pre-defining streams or aggregate roots.
- Durable source of truth: PostgreSQL or SQLite stores the event log, positions, checkpoints, indexes, and admin state.
- Real-time delivery included: embedded NATS JetStream handles live subscriptions and catch-up delivery.
- No-miss publishing: publisher checkpoints and ordered catch-up reads prevent skipped events even when wake-up signals are missed.
- Sequential per-boundary publishing: events publish in ascending
(transaction_id, global_id)order. - Production controls: explicit indexes, gRPC auth/TLS options, OpenTelemetry, pprof, PostgreSQL clustering, and PgBouncer guidance.
Contents
- Quick Start
- Core Model
- Using The API
- Delivery Guarantees
- Architecture
- Boundaries And Schemas
- Indexing
- Configuration
- Operations
- Clients
- Embedding
- Development
Storage Backends
Orisun supports two storage backends, chosen at startup via ORISUN_BACKEND:
| Backend | Use Case | Multi-Node | Driver |
|---|---|---|---|
postgres (default) |
Production, clustered deployments, large datasets | Yes — cluster nodes coordinate via PG advisory locks | pgx |
sqlite |
Embedded, single-node production, dev, edge, low-ops | No — single-node only; rejected at startup if ORISUN_NATS_CLUSTER_ENABLED=true |
zombiezen.com/go/sqlite (pure Go) |
For SQLite, set ORISUN_SQLITE_DIR to the directory holding the per-boundary {boundary}.db files. The directory is created on startup if missing. Example:
ORISUN_BACKEND=sqlite \
ORISUN_SQLITE_DIR=/var/lib/orisun/sqlite \
ORISUN_BOUNDARIES='[{"name":"orders"},{"name":"orisun_admin"}]' \
ORISUN_ADMIN_BOUNDARY=orisun_admin \
./orisun
The SQLite backend is a complete single-node implementation, not a development-only fallback. It includes the event log, admin state, index metadata, publisher checkpoints, projector checkpoints, JSON criteria queries, and the same CCC save semantics as PostgreSQL.
SQLite writes use WAL mode + synchronous=NORMAL, with one writer + N readers per boundary. A single SaveEvents call commits as one transaction, assigns stable global IDs, updates the transaction position, and wakes the publisher immediately after commit. Concurrent saves serialize through the boundary write pool, so committed SQLite events are published in the same ascending (transaction_id, global_id) order used by PostgreSQL.
The publisher checkpoint is stored in SQLite and catch-up reads always resume from the next durable position. Wake-up signals are only hints; if a signal is lost, periodic polling still drains the committed log. This means SQLite deployments keep the same no-skipped-events and sequential publishing guarantees as PostgreSQL on a single Orisun node.
Quick Start
Docker Compose With PostgreSQL
PostgreSQL is the default backend and the right choice for multi-node Orisun clusters. Create docker-compose.yml:
services:
postgres:
image: postgres:17.5-alpine3.22
environment:
POSTGRES_DB: orisun
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password@1
ports:
- "5434:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
orisun:
image: orexza/orisun:pg
environment:
ORISUN_PG_HOST: postgres
ORISUN_PG_USER: postgres
ORISUN_PG_PASSWORD: password@1
ORISUN_PG_NAME: orisun
ORISUN_ADMIN_USERNAME: admin
ORISUN_ADMIN_PASSWORD: changeit
ports:
- "5005:5005"
- "8991:8991"
volumes:
- orisun-data:/var/lib/orisun/data
depends_on:
- postgres
restart: unless-stopped
volumes:
postgres-data:
orisun-data:
Start Orisun:
docker compose up -d
Verify the gRPC API:
grpcurl -H "Authorization: Basic YWRtaW46Y2hhbmdlaXQ=" localhost:5005 list
Default credentials are admin:changeit.
Docker Compose With SQLite
SQLite is the simplest production-capable single-node setup. It does not need a separate database container.
Create docker-compose.yml:
services:
orisun:
image: orexza/orisun:sqlite
environment:
ORISUN_BACKEND: sqlite
ORISUN_SQLITE_DIR: /var/lib/orisun/sqlite
ORISUN_NATS_CLUSTER_ENABLED: "false"
ORISUN_BOUNDARIES: '[{"name":"orders","description":"orders"},{"name":"orisun_admin","description":"admin"}]'
ORISUN_ADMIN_BOUNDARY: orisun_admin
ORISUN_ADMIN_USERNAME: admin
ORISUN_ADMIN_PASSWORD: changeit
ports:
- "5005:5005"
- "8991:8991"
volumes:
- orisun-data:/var/lib/orisun
restart: unless-stopped
volumes:
orisun-data:
Start Orisun:
docker compose up -d
Verify the gRPC API:
grpcurl -H "Authorization: Basic YWRtaW46Y2hhbmdlaXQ=" localhost:5005 list
Default credentials are admin:changeit.
Binary With PostgreSQL
Download a release binary and run it against PostgreSQL:
ORISUN_PG_HOST=localhost \
ORISUN_PG_PORT=5432 \
ORISUN_PG_USER=postgres \
ORISUN_PG_PASSWORD=your_password \
ORISUN_PG_NAME=your_database \
ORISUN_PG_SCHEMAS="orisun_test_1:public,orisun_admin:admin" \
ORISUN_BOUNDARIES='[{"name":"orisun_test_1","description":"test"},{"name":"orisun_admin","description":"admin"}]' \
ORISUN_ADMIN_BOUNDARY=orisun_admin \
./orisun-darwin-arm64
For a deployment artifact that contains only the PostgreSQL backend, build the PG-only command:
./build.sh linux amd64 dev pg
./build/orisun-pg-linux-amd64
Binary With SQLite
Run the same binary without PostgreSQL:
ORISUN_BACKEND=sqlite \
ORISUN_SQLITE_DIR=/var/lib/orisun/sqlite \
ORISUN_NATS_CLUSTER_ENABLED=false \
ORISUN_BOUNDARIES='[{"name":"orders","description":"orders"},{"name":"orisun_admin","description":"admin"}]' \
ORISUN_ADMIN_BOUNDARY=orisun_admin \
ORISUN_ADMIN_USERNAME=admin \
ORISUN_ADMIN_PASSWORD=changeit \
./orisun-darwin-arm64
For a deployment artifact that contains only the SQLite backend, build the SQLite-only command:
./build.sh linux amd64 dev sqlite
./build/orisun-sqlite-linux-amd64
Core Model
Command Context Consistency
Traditional event stores often ask you to choose an aggregate stream up front. Orisun takes a different path: a command defines the events it cares about by querying the event payload.
Example: a money transfer command can define its context as all events where account_holder is Alice or Bob:
{
"criteria": [
{"tags": [{"key": "account_holder", "value": "alice"}]},
{"tags": [{"key": "account_holder", "value": "bob"}]}
]
}
The command flow is:
- Check: query matching events and build the command's context model.
- Decide: validate business rules in your application.
- Record: save events with the same context query and expected position.
- Retry on conflict: if the context changed, Orisun returns
ALREADY_EXISTS.
This gives commands the consistency scope they actually need without forcing unrelated events into the same aggregate.
Positions
Every event has a durable position:
| Field | Meaning |
|---|---|
transaction_id |
Backend commit position for the batch |
global_id |
Monotonic per-boundary event position |
Use {-1, -1} as the "before first event" position.
Using The API
All examples use the default basic auth header:
AUTH='Authorization: Basic YWRtaW46Y2hhbmdlaXQ='
Save An Event
grpcurl -H "$AUTH" -d @ localhost:5005 orisun.EventStore/SaveEvents <<EOF
{
"boundary": "orisun_test_1",
"query": {
"expected_position": {
"transaction_id": -1,
"global_id": -1
}
},
"events": [
{
"event_id": "user-001",
"event_type": "UserRegistered",
"data": "{\"email\":\"alice@example.com\",\"username\":\"alice\"}",
"metadata": "{\"source\":\"signup\"}"
}
]
}
EOF
Response:
{
"new_global_id": 0,
"latest_transaction_id": 123456789,
"latest_global_id": 0
}
Save A Batch
Batches are atomic. Events in one batch share the same transaction position and receive increasing global IDs.
grpcurl -H "$AUTH" -d @ localhost:5005 orisun.EventStore/SaveEvents <<EOF
{
"boundary": "orisun_test_1",
"query": {
"expected_position": {
"transaction_id": 123456789,
"global_id": 0
}
},
"events": [
{
"event_id": "profile-001",
"event_type": "UserProfileCompleted",
"data": "{\"user_id\":\"user-001\",\"phone\":\"+1234567890\"}",
"metadata": "{}"
},
{
"event_id": "email-001",
"event_type": "EmailVerified",
"data": "{\"user_id\":\"user-001\",\"email\":\"alice@example.com\"}",
"metadata": "{}"
}
]
}
EOF
Query Events
Read from the beginning:
grpcurl -H "$AUTH" -d @ localhost:5005 orisun.EventStore/GetEvents <<EOF
{
"boundary": "orisun_test_1",
"count": 100,
"direction": "ASC"
}
EOF
Page from a position:
grpcurl -H "$AUTH" -d @ localhost:5005 orisun.EventStore/GetEvents <<EOF
{
"boundary": "orisun_test_1",
"from_position": {
"transaction_id": 123456789,
"global_id": 0
},
"count": 100,
"direction": "ASC"
}
EOF
Query by JSON payload fields:
grpcurl -H "$AUTH" -d @ localhost:5005 orisun.EventStore/GetEvents <<EOF
{
"boundary": "orisun_test_1",
"query": {
"criteria": [
{"tags": [{"key": "username", "value": "alice"}]}
]
},
"count": 100,
"direction": "ASC"
}
EOF
Criteria entries are combined with OR. Tags within one criterion are combined with AND.
Subscribe
Catch-up subscriptions replay stored events, then switch to live NATS delivery:
grpcurl -H "$AUTH" -d @ localhost:5005 orisun.EventStore/CatchUpSubscribeToEvents <<EOF
{
"subscriber_name": "user-events",
"boundary": "orisun_test_1",
"after_position": {
"transaction_id": -1,
"global_id": -1
}
}
EOF
Filtered subscription:
grpcurl -H "$AUTH" -d @ localhost:5005 orisun.EventStore/CatchUpSubscribeToEvents <<EOF
{
"subscriber_name": "registrations",
"boundary": "orisun_test_1",
"after_position": {
"transaction_id": -1,
"global_id": -1
},
"query": {
"criteria": [
{"tags": [{"key": "eventType", "value": "UserRegistered"}]}
]
}
}
EOF
Delivery Guarantees
PostgreSQL or SQLite is the durable source of truth. NATS JetStream is the real-time delivery layer.
LISTEN/NOTIFY and polling are only wake-up signals. If a notification is missed, correctness is still preserved by the durable publisher checkpoint and periodic catch-up polling.
Per boundary:
| Guarantee | How Orisun enforces it |
|---|---|
| No skipped events | The publisher stores the last published (transaction_id, global_id) in the selected backend and always resumes from the next position. |
| Sequential publishing | The publisher drains events in ascending (transaction_id, global_id) order and rejects non-advancing batches before publishing. |
| Stable committed prefix | PostgreSQL ASC reads only expose transactions older than the current snapshot xmin; SQLite uses a single writer per boundary, so commits are already serialized. |
| Single active publisher | Clustered PostgreSQL nodes acquire a lock per boundary and fail over from the persisted checkpoint. SQLite runs one Orisun writer node, so there is only one publisher per boundary. |
Publishing is at-least-once at the boundary between NATS publish and checkpoint update. If NATS accepts an event and the checkpoint write fails, the event can be republished. Consumers should deduplicate by event_id or NATS message ID.
Architecture
Client
|
| gRPC
v
Orisun
|
| transactional write + CCC check
v
PostgreSQL or SQLite event log
|
| pg_notify, SQLite signal, or polling wake-up; checkpointed drain
v
Embedded NATS JetStream
|
| live + catch-up subscriptions
v
Subscribers
Main packages:
| Path | Responsibility |
|---|---|
cmd/ |
Server entry point, integration tests, benchmarks |
cmd/orisun-pg/ |
PostgreSQL-only server binary |
cmd/orisun-sqlite/ |
SQLite-only server binary |
embedded/postgres/ |
In-process PostgreSQL embedding package |
embedded/sqlite/ |
In-process SQLite embedding package |
orisun/ |
Core API, publisher loop, streams, generated protobuf bindings |
postgres/ |
PostgreSQL event store, migrations, admin DB, listener |
sqlite/ |
SQLite event store, admin DB, indexes, publisher checkpoints |
nats/ |
Embedded NATS and JetStream setup |
admin/ |
Auth, admin gRPC service, projections |
config/ |
Environment-backed configuration |
server/ |
Backend-neutral server bootstrap for command binaries |
clients/node/ |
TypeScript client package |
proto/ |
Protobuf source |
Backend support: PostgreSQL is the production multi-node clustering backend. SQLite is the production single-node backend and is intentionally rejected when NATS clustering is enabled, because SQLite must have exactly one active writer.
Backend-specific commands and embedding packages keep deployments small and explicit. cmd/orisun-pg imports postgres but not sqlite; cmd/orisun-sqlite imports sqlite but not postgres. The root cmd binary remains the all-backends entry point.
Boundaries And Schemas
A boundary is a logical domain. Boundaries are isolated even when they share the same PostgreSQL schema because each boundary uses prefixed tables.
Example:
ORISUN_PG_SCHEMAS=orders:public,payments:public,admin:admin
Creates tables like:
public.orders_orisun_es_event
public.payments_orisun_es_event
admin.admin_orisun_es_event
Each boundary has:
| Table | Purpose |
|---|---|
{boundary}_orisun_es_event |
Event log |
{boundary}_orisun_es_event_global_id_seq |
Per-boundary global ID sequence |
{boundary}_orisun_last_published_event_position |
NATS publisher checkpoint |
{boundary}_projector_checkpoint |
Projector checkpoint state |
Configured boundaries must match ORISUN_BOUNDARIES; requests to unknown boundaries are rejected.
Indexing
Criteria queries match JSON payload values. PostgreSQL uses JSONB expressions like:
data->>'account_holder' = 'alice'
Without an index, criteria reads and CCC consistency checks scan the full boundary event table. In production, create indexes for every JSON field used in criteria.tags. Index management is exposed on the EventStore gRPC service, not the Admin service. PostgreSQL uses JSONB expression indexes, and SQLite uses JSON expression indexes.
Create a simple index:
grpcurl -H "$AUTH" \
-d '{"boundary":"orders","name":"user_id","fields":[{"json_key":"user_id","value_type":"TEXT"}]}' \
localhost:5005 orisun.EventStore/CreateIndex
Create a composite index:
grpcurl -H "$AUTH" -d @ localhost:5005 orisun.EventStore/CreateIndex <<EOF
{
"boundary": "orders",
"name": "category_priority",
"fields": [
{"json_key": "category", "value_type": "TEXT"},
{"json_key": "priority", "value_type": "TEXT"}
]
}
EOF
Create a partial index:
grpcurl -H "$AUTH" -d @ localhost:5005 orisun.EventStore/CreateIndex <<EOF
{
"boundary": "orders",
"name": "placed_amount",
"fields": [{"json_key": "amount", "value_type": "NUMERIC"}],
"conditions": [{"key": "eventType", "operator": "=", "value": "OrderPlaced"}],
"condition_combinator": "AND"
}
EOF
Drop an index:
grpcurl -H "$AUTH" \
-d '{"boundary":"orders","name":"user_id"}' \
localhost:5005 orisun.EventStore/DropIndex
See ADMIN_API.md for the full admin API.
Configuration
Orisun reads environment variables with the ORISUN_ prefix.
Required Production Settings
| Variable | Description |
|---|---|
ORISUN_BACKEND |
postgres or sqlite; defaults to postgres |
ORISUN_PG_HOST |
PostgreSQL host |
ORISUN_PG_PORT |
PostgreSQL port |
ORISUN_PG_USER |
PostgreSQL user |
ORISUN_PG_PASSWORD |
PostgreSQL password |
ORISUN_PG_NAME |
PostgreSQL database |
ORISUN_PG_SCHEMAS |
Comma-separated boundary:schema mappings |
ORISUN_BOUNDARIES |
JSON array of boundary definitions |
ORISUN_ADMIN_BOUNDARY |
Boundary used for admin state |
For SQLite deployments, replace the PostgreSQL connection variables with ORISUN_SQLITE_DIR. SQLite is production-ready for single-node operation; leave ORISUN_NATS_CLUSTER_ENABLED=false.
Common Settings
| Variable | Default | Description |
|---|---|---|
ORISUN_GRPC_PORT |
5005 |
gRPC API port |
ORISUN_ADMIN_PORT |
8991 |
Admin HTTP port |
ORISUN_ADMIN_USERNAME |
admin |
Default admin username |
ORISUN_ADMIN_PASSWORD |
changeit |
Default admin password |
ORISUN_LOGGING_LEVEL |
INFO |
DEBUG, INFO, WARN, ERROR |
ORISUN_SQLITE_DIR |
./data/orisun/sqlite |
Directory for per-boundary SQLite files when ORISUN_BACKEND=sqlite |
ORISUN_PG_LISTEN_ENABLED |
true |
Use PostgreSQL LISTEN/NOTIFY for publisher wake-ups |
ORISUN_POLLING_PUBLISHER_BATCH_SIZE |
1000 |
Max events drained per publisher read batch |
ORISUN_NATS_PORT |
4224 |
Embedded NATS client port |
ORISUN_NATS_STORE_DIR |
./data/orisun/nats |
NATS data directory |
ORISUN_NATS_EVENT_STREAM_MAX_BYTES |
536870912 |
Per-boundary event stream memory cap |
ORISUN_NATS_EVENT_STREAM_MAX_MSGS |
-1 |
Per-boundary event stream message cap |
ORISUN_NATS_EVENT_STREAM_MAX_AGE |
5m |
Retention overlap for catch-up subscribers |
ORISUN_OTEL_ENABLED |
true |
Enable OpenTelemetry |
ORISUN_OTEL_ENDPOINT |
localhost:4317 |
OTLP gRPC endpoint |
ORISUN_PPROF_ENABLED |
false |
Enable pprof |
ORISUN_PPROF_PORT |
6060 |
pprof port |
PostgreSQL Pool Settings
| Variable | Default |
|---|---|
ORISUN_PG_WRITE_MAX_OPEN_CONNS |
25 |
ORISUN_PG_WRITE_MAX_IDLE_CONNS |
10 |
ORISUN_PG_WRITE_CONN_MAX_IDLE_TIME |
5m |
ORISUN_PG_WRITE_CONN_MAX_LIFETIME |
30m |
ORISUN_PG_READ_MAX_OPEN_CONNS |
50 |
ORISUN_PG_READ_MAX_IDLE_CONNS |
25 |
ORISUN_PG_READ_CONN_MAX_IDLE_TIME |
5m |
ORISUN_PG_READ_CONN_MAX_LIFETIME |
30m |
ORISUN_PG_ADMIN_MAX_OPEN_CONNS |
5 |
ORISUN_PG_ADMIN_MAX_IDLE_CONNS |
2 |
ORISUN_PG_ADMIN_CONN_MAX_IDLE_TIME |
5m |
ORISUN_PG_ADMIN_CONN_MAX_LIFETIME |
30m |
TLS Settings
| Variable | Default |
|---|---|
ORISUN_GRPC_TLS_ENABLED |
false |
ORISUN_GRPC_TLS_CERT_FILE |
/etc/orisun/tls/server.crt |
ORISUN_GRPC_TLS_KEY_FILE |
/etc/orisun/tls/server.key |
ORISUN_GRPC_TLS_CA_FILE |
/etc/orisun/tls/ca.crt |
ORISUN_GRPC_TLS_CLIENT_AUTH_REQUIRED |
false |
Cluster Settings
| Variable | Default |
|---|---|
ORISUN_NATS_CLUSTER_ENABLED |
false |
ORISUN_NATS_CLUSTER_NAME |
orisun-nats-cluster |
ORISUN_NATS_CLUSTER_HOST |
0.0.0.0 |
ORISUN_NATS_CLUSTER_PORT |
6222 |
ORISUN_NATS_CLUSTER_ROUTES |
nats://0.0.0.0:6223,nats://0.0.0.0:6224 |
ORISUN_NATS_CLUSTER_USERNAME |
nats |
ORISUN_NATS_CLUSTER_PASSWORD |
password@1 |
ORISUN_NATS_CLUSTER_TIMEOUT |
1800s |
Operations
Standalone Mode
Use one Orisun node with PostgreSQL:
ORISUN_PG_HOST=localhost \
ORISUN_PG_PORT=5432 \
ORISUN_PG_USER=postgres \
ORISUN_PG_PASSWORD=your_password \
ORISUN_PG_NAME=orisun \
ORISUN_PG_SCHEMAS=orders:public,admin:admin \
ORISUN_BOUNDARIES='[{"name":"orders","description":"orders"},{"name":"admin","description":"admin"}]' \
ORISUN_ADMIN_BOUNDARY=admin \
./orisun-linux-amd64
For embedded, edge, or low-ops single-node production deployments, use SQLite instead:
ORISUN_BACKEND=sqlite \
ORISUN_SQLITE_DIR=/var/lib/orisun/sqlite \
ORISUN_NATS_CLUSTER_ENABLED=false \
ORISUN_BOUNDARIES='[{"name":"orders","description":"orders"},{"name":"admin","description":"admin"}]' \
ORISUN_ADMIN_BOUNDARY=admin \
./orisun-linux-amd64
Clustered Mode
Clustered mode uses PostgreSQL, embedded NATS clustering, and one active publisher per boundary. SQLite is rejected at startup when NATS clustering is enabled.
Minimum recommendation: three nodes for JetStream quorum.
Each node should share:
- PostgreSQL database and schemas
ORISUN_BOUNDARIESORISUN_PG_SCHEMASORISUN_NATS_CLUSTER_NAME- NATS cluster credentials
Each node should have unique:
ORISUN_GRPC_PORTORISUN_NATS_PORTORISUN_NATS_CLUSTER_HOSTORISUN_NATS_SERVER_NAMEORISUN_NATS_STORE_DIR
Expected publisher behavior:
- A node logs successful lock acquisition when it owns a boundary.
- Other nodes may log lock contention. That is normal.
- If the owner exits, another node resumes from the PostgreSQL checkpoint.
PgBouncer
Session mode works out of the box.
For transaction mode:
- SQL functions use schema-qualified table references.
- The Go-side pool uses multi-statement transactions normally.
- The
pgxdriver default statement cache requires PgBouncer 1.21+ withmax_prepared_statements > 0. - Older PgBouncer deployments should use
cache_describeor simple protocol mode.
Runtime Tuning
Orisun honors standard Go runtime settings:
| Variable | Recommendation |
|---|---|
GOMAXPROCS |
Auto-set from cgroup CPU quota through automaxprocs |
GOMEMLIMIT |
Set to about 80 percent of container memory |
GOGC |
Tune upward for lower GC frequency if memory allows |
Effective values are logged at startup.
Troubleshooting
| Symptom | Check |
|---|---|
| Cannot connect | PostgreSQL host/port, gRPC port, firewall, Docker networking |
ALREADY_EXISTS |
Expected CCC conflict; re-query context and retry |
| Slow criteria queries | Missing JSON field indexes for the selected backend |
| Publisher lag | PostgreSQL listener health, SQLite signal/polling health, NATS health, boundary lock ownership |
| Duplicate delivery | Expected after publish/checkpoint failure; deduplicate by event_id |
| Cluster instability | NATS quorum, routes, unique ports, persistent store dirs |
Clients
Official client libraries are maintained separately:
| Language | Package | Repository |
|---|---|---|
| Go | github.com/oexza/orisun-client-go |
orisun-client-go |
| Java | com.orisunlabs:orisun-java-client:0.0.1 |
orisun-client-java |
| Node.js | @orisun/eventstore-client |
orisun-node-client |
Install:
go get github.com/oexza/orisun-client-go
npm install @orisun/eventstore-client
Generate custom clients from the proto files in proto/ or from the shared proto repository.
Embedding
Go services can embed Orisun directly instead of running the gRPC server as a separate process.
PostgreSQL embedding imports only the PostgreSQL backend:
import (
"context"
embeddedpg "github.com/oexza/Orisun/embedded/postgres"
"github.com/oexza/Orisun/config"
"github.com/oexza/Orisun/logging"
)
func start(ctx context.Context) (*embeddedpg.Store, error) {
cfg := config.InitializeConfig()
cfg.Backend.Type = "postgres"
logger := logging.InitializeDefaultLogger(cfg.Logging)
return embeddedpg.Start(ctx, cfg, logger)
}
SQLite embedding imports only the SQLite backend:
import (
"context"
embeddedsqlite "github.com/oexza/Orisun/embedded/sqlite"
"github.com/oexza/Orisun/config"
"github.com/oexza/Orisun/logging"
)
func start(ctx context.Context) (*embeddedsqlite.Store, error) {
cfg := config.InitializeConfig()
cfg.Backend.Type = "sqlite"
cfg.Nats.Cluster.Enabled = false
logger := logging.InitializeDefaultLogger(cfg.Logging)
return embeddedsqlite.Start(ctx, cfg, logger)
}
Both embedded stores expose the same high-level SaveEvents, GetEvents, and SubscribeToEvents methods through orisun.OrisunServer. They also expose boundary index management without requiring the admin gRPC service:
err := store.CreateBoundaryIndex(
ctx,
"orders",
"customer_id",
[]orisun.BoundaryIndexField{{JsonKey: "customer_id", ValueType: "text"}},
nil,
orisun.IndexCombinatorAND,
)
Development
Requirements
- Go 1.26.3+
- Docker for integration tests
taskfor optional development workflows
Common Commands
go test ./...
go build ./...
go fmt ./...
go vet ./...
go run ./cmd
go test ./... includes PostgreSQL Testcontainers coverage and SQLite end-to-end coverage. Docker must be available for the full integration suite.
Taskfile helpers:
task tools
task build
task build:pg
task build:sqlite
task run:sqlite
task live
task debug
Build release binaries:
./build.sh
./build.sh linux amd64
./build.sh darwin arm64
./build.sh windows amd64
./build.sh linux amd64 dev pg
./build.sh linux amd64 dev sqlite
Run benchmarks:
go test -bench=. -benchtime=3s ./cmd/benchmark_test.go
go test -run='^$' -bench=BenchmarkConsistencyCheck -benchtime=5s ./postgres/...
./collect_benchmarks.sh
Docker
Published images use one repository with flavor tags:
| Tag | Backend |
|---|---|
orexza/orisun:latest |
All backends |
orexza/orisun:pg |
PostgreSQL only |
orexza/orisun:sqlite |
SQLite only |
orexza/orisun:<version> |
All backends for a release version |
orexza/orisun:<version>-pg |
PostgreSQL-only release version |
orexza/orisun:<version>-sqlite |
SQLite-only release version |
Build locally:
docker build -t orisun:local .
Run the local Docker smoke test:
./scripts/test_docker.sh
Releasing
Create a normal release from main:
./scripts/release.sh 1.2.3
Attach curated release notes to the GitHub release:
./scripts/release.sh 1.2.3 --notes release-notes.md
The release script stores the notes verbatim in the annotated git tag, including markdown headings. The GitHub release workflow uses notes in this order:
- Manual
release_notesinput fromworkflow_dispatch - Annotated tag message from
scripts/release.sh --notes - Generated commit log since the previous tag
Security Notes
- Change
ORISUN_ADMIN_PASSWORDbefore production use. - Enable gRPC TLS in production-facing deployments.
- Protect PostgreSQL credentials and NATS cluster credentials.
- Use network policy/firewall rules for NATS cluster routes and PostgreSQL.
License
Directories
¶
| Path | Synopsis |
|---|---|
|
orisun-pg
command
|
|
|
orisun-sqlite
command
|
|
|
embedded
|
|
|
examples
|
|
|
basic_usage
command
|
|