feat: add SaaS multi-tenant CQRS example + tenant-aware backend#579
Open
g-dumas wants to merge 26 commits into
Open
feat: add SaaS multi-tenant CQRS example + tenant-aware backend#579g-dumas wants to merge 26 commits into
g-dumas wants to merge 26 commits into
Conversation
* Add CLAUDE.md with project guidance Provides development setup, build commands, project structure, and module documentation for working with the Edomata codebase. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * Enrich tutorials for non-expert developers Add beginner-friendly explanations throughout all 5 tutorials: - 0_getting_started: Problem statement, architecture diagram, aggregate and pure function explanations, Edomaton vs Stomaton decision guide - 1-1_eventsourcing: Event sourcing intro, Decision analogy, Scala syntax notes, ValidatedNec and fold explanations, testing benefits - 1-2_cqrs: CQRS intro, state diagrams, EitherNec vs Decision comparison, Stomaton vs Edomaton decision matrix - 2_backends: Backend concept, program/interpreter pattern, persistence tables explained, outbox and idempotency, minimal setup example - 3_processes: Process intro, outbox pattern diagram, stream explanation, process manager and saga patterns with examples Each technical term now includes real-world analogies and practical context for developers unfamiliar with Scala, event sourcing, DDD, or CQRS concepts. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Convert ASCII diagrams to Mermaid in tutorials Replace ASCII art diagrams with Mermaid for better rendering: - 0_getting_started: Layer architecture flowchart - 1-2_cqrs: Traditional vs CQRS comparison, order state diagram - 2_backends: Pure/backend architecture, database ER diagram - 3_processes: System architecture, outbox sequence diagram, saga flow Mermaid diagrams render properly in GitHub, documentation sites, and most modern markdown viewers. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: GitHub Actions Bot <actions@github.com> Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Adds fork-aware ticket workflow instructions so Claude Code automatically detects fork vs root repo and targets PRs accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
docs: add contribution workflow to CLAUDE.md
Adds context7.json so Edomata can be indexed on Context7, enabling AI coding assistants (Claude Code, Cursor, Copilot) to look up Edomata documentation directly. Fixes hnaderi#573 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add PGNaming sealed trait with Schema and Prefixed variants, allowing
tables to use a name prefix (e.g. auth_journal) instead of a dedicated
PostgreSQL schema (e.g. "auth".journal). This is useful for projects
using Flyway migrations in a single schema.
API: PGNamespace.prefixed("auth") or PGNaming.prefixed("auth")
Fixes hnaderi#574
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SkunkPrefixedPersistenceSuite and SkunkPrefixedCQRSSuite - Add DoobiePrefixedPersistenceSuite and DoobiePrefixedCQRSSuite - Document table prefix mode in skunk.md, doobie.md, and backends tutorial Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add PGSchema utility to generate DDL SQL strings for migration files (eventsourcing and cqrs variants with configurable payload types) - Add skipSetup parameter to all driver .from() methods to disable automatic schema/table creation at startup - Add 10 unit tests for PGSchema DDL generation - Document Flyway workflow in skunk.md and doobie.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add comprehensive AI agent documentation covering: - Table naming strategies (Schema vs Prefixed) - DDL extraction with PGSchema for Flyway workflows - skipSetup parameter for disabling automatic DDL - Guide for adding new tables with proper naming flow Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CLAUDE.md: SBT 1.12.1 -> 1.12.8, Doobie RC11 -> RC12, docker credentials test/test -> postgres/postgres - tutorials/2_backends.md: replace non-existent SkunkBackend API with actual Backend.builder().use(SkunkDriver(...)) pattern, fix connection credentials Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a pre-PR documentation audit process to CLAUDE.md that AI agents must run before opening any PR. Includes a detailed audit prompt that checks CLAUDE.md, backend docs, tutorials, and features against actual source code (versions, API signatures, file paths, code examples). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
examples/ is a root-level directory, not under modules/. Found by documentation audit agent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add language requirement: all commits, PRs, comments, docs, and branch names must be in English for this international open-source library. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
docs: add Context7 documentation indexing
docs: add expert-flow.ai to adopters
feat: table prefix mode, DDL extraction, and Flyway support
…ards Introduces edomata-saas, a cross-platform module providing generic multi-tenant CRUD abstractions. Services extending the SaaS base traits get automatic tenant isolation and role-based authorization before any business logic runs, for both event-sourcing and CQRS approaches. Key features: - SaaSDomainDSL / SaaSCQRSDomainDSL with guardedRouter (auto-guarded) and unsafeUnguardedRouter (explicit bypass for super-admin) - CrudState[A] enum with tenant+owner tracking - TenantScopedQuery / UnsafeCrossTenantQuery for read-side enforcement - Selective re-exports in package.scala to prevent raw DSL usage - 15 unit tests for guard logic - Documentation page (docs/tutorials/4_saas.md) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Full Todo CQRS example showing: domain types, SaaS service with guardedRouter, SkunkHandler for SQL read-model projection, TenantScopedQuery for tenant-scoped reads, admin bypass with unsafeUnguardedRouter, and backend wiring - Integration tests exercising guardedRouter through the Stomaton pipeline: tenant mismatch rejection, missing roles rejection, Create bypass, unsafe bypass, and notification emission - 9 new tests (24 total for the saas module) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New test suites: - SaaSDomainDSLSuite: 24 tests covering the event-sourcing guarded DSL (guardedRouter, unsafeUnguardedRouter, guarded, unsafeUnguarded, caller/command/entityState/aggregateId accessors, pure, unit, reject, decide, publish, validate, notifications, Deleted state handling) - TenantAwareReaderSuite: 8 tests covering read-side abstractions (TenantScopedQuery factory and tenant filtering, UnsafeCrossTenantQuery, TenantAwareReader trait implementation) - SaaSServiceSuite: 20 tests covering service traits, CQRS DSL methods, event-sourced service, CrudState patterns, types, and package re-exports (domain field, set, modifyS, decideS, eval, DomainModel transitions, TenantId/UserId round-trips, SaaSCommand covariance, CrudAction enum, CommandMessage/Decision/MessageMetadata re-exports) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded CallerIdentity with generic Auth type parameter throughout the SaaS module. Authorization logic is now defined by implementing the AuthPolicy[Auth] trait, which provides: - tenantId(auth): extract tenant from any auth context - authorize(auth, action): custom authorization check CallerIdentity remains as a default implementation, with RoleBasedPolicy for role-based access control. Changes: - Add AuthPolicy[Auth] trait and RoleBasedPolicy class - SaaSCommand[C] -> SaaSCommand[Auth, C] - DSLs, services, readers all parameterized by Auth - SaaSGuard.checkRoles -> SaaSGuard.checkAuthorization (delegates to policy) - caller accessor -> auth accessor - rolesFor param removed from DSL/Service constructors (moved to AuthPolicy) - Tests updated + custom AuthPolicy test (ApiKeyAuth example) - 77 tests pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite documentation to reflect the generic auth refactor: - Add AuthPolicy typeclass section with full API - Add Custom Auth Types section with JwtClaims, ApiKeyAuth examples - Update all code samples: SaaSCommand[Auth, C], auth accessor, RoleBasedPolicy, TenantScopedQuery with Auth type param - Rename "Role check" to "Authorization check" throughout - Update overview to emphasize pluggable authorization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: add multi-tenant SaaS CRUD module
…ions Add a complete Product Catalog example demonstrating: - Custom AuthPolicy (ApiKeyContext with scopes, not CallerIdentity) - Typed rejections (ProductRejection enum, not String) - Product lifecycle state machine (Draft → Published → Archived → Deleted) - SQL DDL for CQRS backend tables and custom read-model projection - Flyway migration workflow with skipSetup Include 27 pure domain unit tests covering CRUD lifecycle, state machine validation, scope-based authorization, multi-tenant isolation, admin bypass, notification emission, and price validation. Update SaaS tutorial with full Product Catalog section including generated SQL examples, custom auth patterns, and test snippets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add tenant-aware database tables to the SaaS module, enabling Row-Level Security, tenant-scoped indexes, and SQL-level audit queries. Changes: - Add TenantExtractor typeclass to extract tenant/owner from CrudState - Add SaaSPGSchema for DDL generation with tenant_id, owner_id columns, tenant indexes, and optional RLS policies - Create saas-skunk module with SaaSSkunkCQRSDriver that persists tenant_id/owner_id as first-class columns in states and outbox tables - Update ProductCatalogExample to use SaaS-aware driver - Add 13 new tests (TenantExtractor + SaaSPGSchema DDL validation) Test coverage: 93.7% statements, 100% branches on saas module. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ProductCatalogExample.scala) demonstrating customApiKeyContextauth with scopes, typedProductRejectionenum, and a product lifecycle state machine (Draft → Published → Archived → Deleted)tenant_idandowner_idas first-class SQL columns:TenantExtractor[S]typeclass to extract tenant/owner fromCrudStateSaaSPGSchemafor DDL generation with tenant indexes and optional Row-Level Security (RLS)saas-skunkmodule withSaaSSkunkCQRSDriverthat persists tenant columns in states and outbox tablesNew module:
edomata-saas-skunkDrop-in replacement for
SkunkCQRSDriverthat addstenant_id/owner_idcolumns:Generated DDL includes tenant-scoped indexes and optional RLS policies:
Test plan
sbt saasJVM/test— 116 tests passed (40 new + 76 existing), 0 failuressbt saasSkunkJVM/compile— compiles successfullysbt examplesJVM/compile— compiles successfullysbt scalafmtAll— code formatted🤖 Generated with Claude Code