@mariachi/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/mariachi.mdc +36 -0
- package/.mariachi/architecture.md +46 -0
- package/.mariachi/conventions.md +109 -0
- package/.mariachi/packages.md +66 -0
- package/.mariachi/patterns.md +71 -0
- package/README.md +42 -0
- package/dist/index.cjs +423 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +211 -0
- package/dist/index.d.ts +211 -0
- package/dist/index.js +362 -0
- package/dist/index.js.map +1 -0
- package/docs/adr/001-adapter-pattern.md +42 -0
- package/docs/ai-guide.md +222 -0
- package/docs/architecture.md +110 -0
- package/docs/conventions.md +109 -0
- package/docs/improvements/20260224231420_notifications_realtime_nats.md +82 -0
- package/docs/integrations.md +70 -0
- package/docs/packages.md +66 -0
- package/docs/patterns.md +71 -0
- package/docs/recipes/add-background-job.md +156 -0
- package/docs/recipes/add-domain-entity.md +248 -0
- package/docs/recipes/add-integration.md +198 -0
- package/docs/recipes/add-webhook-endpoint.md +169 -0
- package/docs/recipes/wiring-and-bootstrap.md +250 -0
- package/docs/runbook.md +84 -0
- package/package.json +38 -0
package/docs/ai-guide.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Mariachi AI Guide
|
|
2
|
+
|
|
3
|
+
Concise reference for AI assistants generating code on the Mariachi framework. For quick lookups, see [architecture.md](./architecture.md), [patterns.md](./patterns.md), [conventions.md](./conventions.md), and [packages.md](./packages.md). For step-by-step instructions, see the [recipes](./recipes/).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
Three-layer request flow:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
HTTP → Facade (FastifyAdapter, auth, rate limit)
|
|
13
|
+
→ Controller (Zod validation, communication.call())
|
|
14
|
+
→ Service (business logic, DB, cache, events)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Apps map to layers:
|
|
18
|
+
|
|
19
|
+
| App | Layer | Purpose |
|
|
20
|
+
|-----|-------|---------|
|
|
21
|
+
| API app | Facade + Controller | HTTP servers, controllers, auth |
|
|
22
|
+
| Services app | Service | Domain logic, handler registration |
|
|
23
|
+
| Worker app | Background | BullMQ job workers, schedules |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Decision Tree: Which Component to Use
|
|
28
|
+
|
|
29
|
+
**"I need to handle an HTTP request"**
|
|
30
|
+
→ Add a controller extending `BaseController`. Register on a server.
|
|
31
|
+
|
|
32
|
+
**"I need to run business logic"**
|
|
33
|
+
→ Create a service in your services app. Register a handler via `communication.register()`. Call it from a controller via `communication.call('<domain>.<action>', ctx, input)`.
|
|
34
|
+
|
|
35
|
+
**"I need to run something in the background"**
|
|
36
|
+
→ Define a job with a Zod schema and retry config. Enqueue via `jobQueue.enqueue(jobName, data)` from a service.
|
|
37
|
+
→ See [recipes/add-background-job.md](./recipes/add-background-job.md).
|
|
38
|
+
|
|
39
|
+
**"I need to react to something that happened"**
|
|
40
|
+
→ Use the event bus: `eventBus.publish('user.created', payload)` and `eventBus.subscribe('user.created', handler)`. Adapter: Redis pub/sub or NATS.
|
|
41
|
+
|
|
42
|
+
**"I need to run something on a schedule"**
|
|
43
|
+
→ Add a schedule entry: `{ name, cron, jobName, data }`.
|
|
44
|
+
|
|
45
|
+
**"I need to cache data"**
|
|
46
|
+
→ Use `cache.getOrSet(key, ttl, fetchFn)` from `@mariachi/cache`. Redis-backed.
|
|
47
|
+
|
|
48
|
+
**"I need to store files"**
|
|
49
|
+
→ Use `@mariachi/storage` with S3 adapter.
|
|
50
|
+
|
|
51
|
+
**"I need real-time updates"**
|
|
52
|
+
→ Use `@mariachi/realtime` with `WSAdapter`. Supports channels, broadcast, and per-user messaging.
|
|
53
|
+
|
|
54
|
+
**"I need to accept webhooks from a third party"**
|
|
55
|
+
→ Create a `WebhookController`. Choose `mode: 'direct'` (sync via communication) or `mode: 'queue'` (async via jobs).
|
|
56
|
+
→ See [recipes/add-webhook-endpoint.md](./recipes/add-webhook-endpoint.md).
|
|
57
|
+
|
|
58
|
+
**"I need to integrate with an external service"**
|
|
59
|
+
→ Use `defineIntegrationFn()` from `@mariachi/integrations`. See [recipes/add-integration.md](./recipes/add-integration.md).
|
|
60
|
+
|
|
61
|
+
**"I need to wire up and bootstrap an app from scratch"**
|
|
62
|
+
→ See [recipes/wiring-and-bootstrap.md](./recipes/wiring-and-bootstrap.md). Shows the full initialization sequence from config to running servers.
|
|
63
|
+
|
|
64
|
+
**"I need to add a new domain entity end-to-end"**
|
|
65
|
+
→ See [recipes/add-domain-entity.md](./recipes/add-domain-entity.md).
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Package Cheat Sheet
|
|
70
|
+
|
|
71
|
+
### bootstrap (lifecycle)
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { bootstrap } from '@mariachi/lifecycle';
|
|
75
|
+
const { config, logger, startup, shutdown, health } = bootstrap();
|
|
76
|
+
startup.register({ name: 'my-service', priority: 10, fn: async () => { ... } });
|
|
77
|
+
await startup.runAll(logger);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### communication
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { createCommunication } from '@mariachi/communication';
|
|
84
|
+
const communication = createCommunication();
|
|
85
|
+
|
|
86
|
+
// Register a handler (in services app)
|
|
87
|
+
communication.register('users.create', {
|
|
88
|
+
schema: { input: CreateUserInput, output: UserOutput },
|
|
89
|
+
handler: (ctx, input) => UsersService.create(ctx, input),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Call a handler (in API controller)
|
|
93
|
+
const result = await communication.call('users.create', ctx, input);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### controller (api-facade)
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { BaseController, type HttpContext } from '@mariachi/api-facade';
|
|
100
|
+
|
|
101
|
+
export class OrdersController extends BaseController {
|
|
102
|
+
readonly prefix = 'orders';
|
|
103
|
+
|
|
104
|
+
init() {
|
|
105
|
+
this.post(this.buildPath(), this.create);
|
|
106
|
+
this.get(this.buildPath(':id'), this.getById);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
create = async (ctx: HttpContext, body: unknown) => {
|
|
110
|
+
const input = CreateOrderInput.parse(body);
|
|
111
|
+
return communication.call('orders.create', ctx, input);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### server (api-facade)
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { FastifyAdapter } from '@mariachi/api-facade';
|
|
120
|
+
|
|
121
|
+
const server = new FastifyAdapter({ name: 'public' })
|
|
122
|
+
.withAuth(['session', 'api-key'])
|
|
123
|
+
.withRateLimit({ perUser: 1000, perApiKey: 5000, window: '1h' });
|
|
124
|
+
|
|
125
|
+
server.registerController(new OrdersController());
|
|
126
|
+
await server.listen(3000);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### database schema
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { defineTable } from '@mariachi/database';
|
|
133
|
+
import { column } from '@mariachi/database';
|
|
134
|
+
|
|
135
|
+
export const ordersTable = defineTable('orders', {
|
|
136
|
+
id: column.uuid().primaryKey().defaultRandom(),
|
|
137
|
+
tenantId: column.text().notNull(),
|
|
138
|
+
userId: column.text().notNull(),
|
|
139
|
+
total: column.numeric().notNull(),
|
|
140
|
+
status: column.text().notNull(),
|
|
141
|
+
createdAt: column.timestamp().notNull().defaultNow(),
|
|
142
|
+
updatedAt: column.timestamp().notNull().defaultNow(),
|
|
143
|
+
deletedAt: column.timestamp(),
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### repository (database-postgres)
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { DrizzleRepository } from '@mariachi/database-postgres';
|
|
151
|
+
import { orders } from '../compiled-schemas';
|
|
152
|
+
|
|
153
|
+
export class DrizzleOrdersRepository extends DrizzleRepository<Order> {
|
|
154
|
+
constructor(db: DrizzleDb) {
|
|
155
|
+
super(orders, db, { tenantColumn: 'tenantId' });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Inherited methods: `findById`, `findMany`, `create`, `update`, `softDelete`, `hardDelete`, `paginate`, `count`.
|
|
161
|
+
|
|
162
|
+
### jobs
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { z } from 'zod';
|
|
166
|
+
|
|
167
|
+
export const ProcessOrderJob = {
|
|
168
|
+
name: 'orders.process',
|
|
169
|
+
schema: z.object({ orderId: z.string() }),
|
|
170
|
+
retry: { attempts: 3, backoff: 'exponential' as const },
|
|
171
|
+
handler: async (data, ctx) => { ... },
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### events
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const eventBus = createEventBus({ adapter: 'redis', url: process.env.REDIS_URL });
|
|
179
|
+
await eventBus.publish('order.created', { orderId: '123' });
|
|
180
|
+
await eventBus.subscribe('order.created', async (event) => { ... });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### cache
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const cache = createCache({ adapter: 'redis', url: process.env.REDIS_URL });
|
|
187
|
+
const user = await cache.getOrSet(
|
|
188
|
+
cache.key('users', userId),
|
|
189
|
+
3600,
|
|
190
|
+
() => repo.findById(ctx, userId),
|
|
191
|
+
);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### testing
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { createTestHarness, createTestContext, TestRepository } from '@mariachi/testing';
|
|
198
|
+
|
|
199
|
+
const harness = createTestHarness();
|
|
200
|
+
const ctx = createTestContext();
|
|
201
|
+
const repo = new TestRepository<User>();
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Common Gotchas
|
|
207
|
+
|
|
208
|
+
1. **Communication handlers must be registered before `call()`**. Call your handler registration (e.g. `registerServiceHandlers(communication)`) before starting the API server.
|
|
209
|
+
|
|
210
|
+
2. **No in-memory job adapter exists**. The worker uses BullMQ. Use `@mariachi/testing`'s `TestJobQueue` in tests.
|
|
211
|
+
|
|
212
|
+
3. **`createCommunication()` returns an `InProcessAdapter`**. The communication layer is in-process only.
|
|
213
|
+
|
|
214
|
+
4. **Soft deletes are the default**. `DrizzleRepository.softDelete()` sets `deletedAt`. All queries automatically filter out soft-deleted rows. Use `hardDelete()` only when explicitly needed.
|
|
215
|
+
|
|
216
|
+
5. **Tenant isolation is automatic in `DrizzleRepository`**. When `tenantColumn` is set and `ctx.tenantId` is present, all queries are scoped to that tenant.
|
|
217
|
+
|
|
218
|
+
6. **`bootstrap()` registers Config and Logger in the DI container**. Other services pull them via `getContainer().resolve(KEYS.Logger)`.
|
|
219
|
+
|
|
220
|
+
7. **Controller route handlers receive `(ctx, body, params, query)`**. Use the controller's handler signature, not raw Fastify request.
|
|
221
|
+
|
|
222
|
+
8. **Some planned adapters are not implemented**. This guide reflects actual code only (Postgres, Redis, BullMQ, etc.).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Mariachi Framework Architecture
|
|
2
|
+
|
|
3
|
+
Mariachi is an LLM-optimized TypeScript backend framework with adapter-based abstractions. It provides a modular structure where external dependencies (databases, caches, message queues, third-party APIs) are hidden behind config-driven adapters, enabling vendor independence and testability.
|
|
4
|
+
|
|
5
|
+
## Three-Layer Architecture
|
|
6
|
+
|
|
7
|
+
Requests flow through three layers: **Facade** (HTTP/entry), **Controller** (routing and validation), and **Service** (business logic). Controllers delegate to services via the communication layer.
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart TB
|
|
11
|
+
subgraph Facade
|
|
12
|
+
HTTP[HTTP Request]
|
|
13
|
+
Fastify[FastifyAdapter]
|
|
14
|
+
Auth[Auth Strategies]
|
|
15
|
+
RateLimit[Rate Limiting]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
subgraph Controller
|
|
19
|
+
Ctrl[Controller]
|
|
20
|
+
Validation[Input Validation]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
subgraph Communication
|
|
24
|
+
Comm[Communication Layer]
|
|
25
|
+
Middleware[Middleware Pipeline]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
subgraph Service
|
|
29
|
+
Svc[Service]
|
|
30
|
+
BusinessLogic[Business Logic]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
HTTP --> Fastify
|
|
34
|
+
Fastify --> Auth
|
|
35
|
+
Auth --> RateLimit
|
|
36
|
+
RateLimit --> Ctrl
|
|
37
|
+
Ctrl --> Validation
|
|
38
|
+
Validation --> Comm
|
|
39
|
+
Comm --> Middleware
|
|
40
|
+
Middleware --> Svc
|
|
41
|
+
Svc --> BusinessLogic
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Layer | Responsibility |
|
|
45
|
+
|-------|----------------|
|
|
46
|
+
| **Facade** | HTTP server (Fastify), auth strategies, rate limiting, route registration |
|
|
47
|
+
| **Controller** | Parse/validate input, call communication layer, return response |
|
|
48
|
+
| **Service** | Business logic, database access, external integrations |
|
|
49
|
+
|
|
50
|
+
## Package Overview
|
|
51
|
+
|
|
52
|
+
| Package | Purpose |
|
|
53
|
+
|---------|---------|
|
|
54
|
+
| `@mariachi/core` | Shared types, errors, context, container |
|
|
55
|
+
| `@mariachi/config` | Typed config, secrets, feature flags |
|
|
56
|
+
| `@mariachi/observability` | Logging (Pino), tracing (OTEL), metrics, error tracking |
|
|
57
|
+
| `@mariachi/lifecycle` | Startup/shutdown, health checks, bootstrap |
|
|
58
|
+
| `@mariachi/communication` | Inter-module communication, middleware pipeline |
|
|
59
|
+
| `@mariachi/database` | Drizzle ORM, PostgreSQL, repositories |
|
|
60
|
+
| `@mariachi/cache` | Redis, in-memory, distributed locks |
|
|
61
|
+
| `@mariachi/events` | Event bus, Redis Pub/Sub |
|
|
62
|
+
| `@mariachi/jobs` | BullMQ job queue, workers, scheduler |
|
|
63
|
+
| `@mariachi/auth` | JWT, API keys, RBAC |
|
|
64
|
+
| `@mariachi/tenancy` | Multi-tenant isolation |
|
|
65
|
+
| `@mariachi/rate-limit` | Redis sliding window rate limiting |
|
|
66
|
+
| `@mariachi/audit` | Append-only audit logging |
|
|
67
|
+
| `@mariachi/api-facade` | Fastify server adapter, auth strategies |
|
|
68
|
+
| `@mariachi/storage` | S3, local file storage |
|
|
69
|
+
| `@mariachi/notifications` | Email (Resend), in-app notifications |
|
|
70
|
+
| `@mariachi/billing` | Stripe billing, webhooks |
|
|
71
|
+
| `@mariachi/search` | Typesense, full-text search |
|
|
72
|
+
| `@mariachi/ai` | AI SDK, sessions, tools, prompts, agent loops |
|
|
73
|
+
| `@mariachi/integrations` | Third-party integration pattern |
|
|
74
|
+
| `@mariachi/testing` | In-memory test doubles, factories |
|
|
75
|
+
| `@mariachi/create` | Scaffolding, validation |
|
|
76
|
+
| `@mariachi/cli` | CLI binary |
|
|
77
|
+
|
|
78
|
+
## Monolith vs Microservice
|
|
79
|
+
|
|
80
|
+
Mariachi is designed as a **modular monolith**. All packages can live in one codebase and share the same process. The communication layer (`@mariachi/communication`) uses an in-process adapter by default, routing procedure calls directly to registered handlers.
|
|
81
|
+
|
|
82
|
+
For future scaling, the communication layer can be swapped for a transport adapter (e.g., message queue, gRPC) without changing controllers or services. The framework does not prescribe microservices; teams can extract services later if needed.
|
|
83
|
+
|
|
84
|
+
## Adapter Pattern
|
|
85
|
+
|
|
86
|
+
External dependencies are abstracted behind adapters. Each package exposes a factory (e.g., `createCache`, `createSearch`) that selects the implementation based on config.
|
|
87
|
+
|
|
88
|
+
```mermaid
|
|
89
|
+
flowchart LR
|
|
90
|
+
subgraph Config
|
|
91
|
+
C[Config]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
subgraph Factory
|
|
95
|
+
F[createX]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
subgraph Adapters
|
|
99
|
+
A1[RedisAdapter]
|
|
100
|
+
A2[MemoryAdapter]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
C --> F
|
|
104
|
+
F -->|adapter: redis| A1
|
|
105
|
+
F -->|adapter: memory| A2
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Example:** `createSearch(config)` returns `TypesenseSearchAdapter` when `config.adapter === 'typesense'`, or `MemorySearchAdapter` when `config.adapter === 'memory'`. Tests use in-memory adapters; production uses Redis, PostgreSQL, Stripe, etc.
|
|
109
|
+
|
|
110
|
+
Adapters implement a common interface. The factory is the single place that maps config to implementation, keeping application code vendor-agnostic.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Mariachi Conventions
|
|
2
|
+
|
|
3
|
+
## TypeScript and ESM
|
|
4
|
+
|
|
5
|
+
- **All packages use ESM** (`"type": "module"` in every `package.json`)
|
|
6
|
+
- **Build tool:** tsup (ESM + CJS dual output, declaration files, sourcemaps)
|
|
7
|
+
- **TypeScript:** strict mode, ES2022 target, `"moduleResolution": "bundler"`
|
|
8
|
+
- **Relative imports are extensionless:** `import { foo } from './bar'` (not `'./bar.js'`). The `"moduleResolution": "bundler"` tsconfig setting and tsup handle resolution.
|
|
9
|
+
- **Cross-package imports use bare specifiers:** `import { Context } from '@mariachi/core'` (no extension)
|
|
10
|
+
- **All packages export from `src/index.ts`** as the single public entry point
|
|
11
|
+
|
|
12
|
+
### tsup Config (every package)
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
entry: ['src/index.ts'],
|
|
17
|
+
format: ['esm', 'cjs'],
|
|
18
|
+
dts: true,
|
|
19
|
+
clean: true,
|
|
20
|
+
sourcemap: true,
|
|
21
|
+
splitting: false,
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### tsconfig (every package)
|
|
26
|
+
|
|
27
|
+
Extend a shared base or use standard options. No additional compiler options needed unless the package has special requirements.
|
|
28
|
+
|
|
29
|
+
## File Organization
|
|
30
|
+
|
|
31
|
+
- One file per concern: `types.ts`, `schema.ts`, `adapter.ts`, `middleware.ts`
|
|
32
|
+
- Adapters go in `src/adapters/` subdirectory
|
|
33
|
+
- Middleware goes in `src/middleware/` subdirectory
|
|
34
|
+
- Schema definitions go in `src/schema/` subdirectory
|
|
35
|
+
- Tests go alongside source in `test/` or `__tests__/` subdirectory
|
|
36
|
+
|
|
37
|
+
## Dependency Rules
|
|
38
|
+
|
|
39
|
+
- `@mariachi/core` has zero internal dependencies (only `zod`)
|
|
40
|
+
- Other packages may depend on `@mariachi/core` freely
|
|
41
|
+
- Packages should depend on abstractions, not implementations (e.g., depend on `@mariachi/database`, not `@mariachi/database-postgres`)
|
|
42
|
+
- Apps may depend on any package
|
|
43
|
+
- Circular dependencies between packages are not allowed
|
|
44
|
+
|
|
45
|
+
## Error Handling
|
|
46
|
+
|
|
47
|
+
- Always throw typed errors extending `MariachiError` from `@mariachi/core`
|
|
48
|
+
- Each package has its own error class: `DatabaseError`, `AuthError`, `CacheError`, etc.
|
|
49
|
+
- Never throw raw `Error` or string exceptions across package boundaries
|
|
50
|
+
- Errors map to HTTP status codes via `errorToHttpStatus()` in the API facade layer
|
|
51
|
+
|
|
52
|
+
## Anti-Patterns (Do Not Do These)
|
|
53
|
+
|
|
54
|
+
**Do not import services from controllers.**
|
|
55
|
+
Controllers must only call `communication.call()`. Never import a service or its handler directly.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// WRONG
|
|
59
|
+
import { UsersService } from '../../services/users/users.service';
|
|
60
|
+
const result = await UsersService.create(ctx, input);
|
|
61
|
+
|
|
62
|
+
// CORRECT
|
|
63
|
+
const result = await communication.call('users.create', ctx, input);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Do not use `process.env` outside `@mariachi/config`.**
|
|
67
|
+
All configuration flows through `loadConfig()` and `useConfig()`. Direct env access scatters configuration and bypasses validation.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// WRONG
|
|
71
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
72
|
+
|
|
73
|
+
// CORRECT
|
|
74
|
+
const config = useConfig();
|
|
75
|
+
const dbUrl = config.database.url;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Exception: `PORT`, `ADMIN_PORT`, `WEBHOOK_PORT` in app entry points are acceptable since they're startup-only values.
|
|
79
|
+
|
|
80
|
+
**Do not skip context propagation.**
|
|
81
|
+
Every operation takes a `Context` as its first argument. Never create a new context mid-flow or drop context between layers.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// WRONG
|
|
85
|
+
const user = await UsersService.create({ email, name });
|
|
86
|
+
|
|
87
|
+
// CORRECT
|
|
88
|
+
const user = await UsersService.create(ctx, { email, name });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Do not expose the Drizzle client directly.**
|
|
92
|
+
All database access goes through repository classes that extend `DrizzleRepository`. The ORM is an implementation detail.
|
|
93
|
+
|
|
94
|
+
**Do not hardcode adapter choices.**
|
|
95
|
+
Use factory functions (`createCache`, `createSearch`, `createJobQueue`) with config. Never instantiate adapters directly in application code.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// WRONG
|
|
99
|
+
const cache = new RedisCacheAdapter({ url: 'redis://localhost' });
|
|
100
|
+
|
|
101
|
+
// CORRECT
|
|
102
|
+
const cache = createCache({ adapter: 'redis', url: config.redis.url });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Do not write migration files by hand.**
|
|
106
|
+
Schema changes go through the `defineTable` DSL in `@mariachi/database`. Migrations are generated by `drizzle-kit`.
|
|
107
|
+
|
|
108
|
+
**Do not register the same procedure name twice.**
|
|
109
|
+
Communication procedure names (`users.create`, `billing.charge`, etc.) must be globally unique. Duplicate registrations will silently overwrite each other.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Notifications, Realtime, and NATS
|
|
2
|
+
|
|
3
|
+
> **Date:** 2026-02-24
|
|
4
|
+
> **Status:** Draft
|
|
5
|
+
> **Scope:** `@mariachi/events`, `@mariachi/notifications`, `@mariachi/realtime` (new)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
The current architecture has unclear boundaries between three distinct concerns:
|
|
12
|
+
|
|
13
|
+
1. **Internal domain events** — service-to-service messaging (`@mariachi/events`, Redis Pub/Sub only)
|
|
14
|
+
2. **Transactional notifications** — user-facing messages (`@mariachi/notifications`, email only, no queue, no multi-channel)
|
|
15
|
+
3. **Realtime delivery** — pushing state to connected clients (does not exist)
|
|
16
|
+
|
|
17
|
+
There is no way to push updates to a connected browser. Transactional notifications are fire-and-forget with no retry. The event bus has no durable delivery option and no way to expose topics to authenticated clients.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌─────────────────────────────────┐
|
|
25
|
+
│ NATS Server │
|
|
26
|
+
│ (subjects, JetStream, auth) │
|
|
27
|
+
└──────────┬──────────┬────────────┘
|
|
28
|
+
│ │
|
|
29
|
+
┌───────────────┘ └───────────────┐
|
|
30
|
+
│ │
|
|
31
|
+
┌──────────▼──────────┐ ┌───────────▼──────────┐
|
|
32
|
+
│ @mariachi/events │ │ @mariachi/realtime │
|
|
33
|
+
│ (internal pub/sub) │ │ (client-facing) │
|
|
34
|
+
│ │ │ │
|
|
35
|
+
│ Adapters: │ │ WebSocket / SSE │
|
|
36
|
+
│ - Redis Pub/Sub │ │ NATS bridge │
|
|
37
|
+
│ - NATS │◄──────────────────│ Auth via facade │
|
|
38
|
+
│ - NATS JetStream │ subscribes to │ Presence tracking │
|
|
39
|
+
│ (durable) │ internal events │ Channel management │
|
|
40
|
+
└──────────┬──────────┘ └──────────────────────┘
|
|
41
|
+
│ ▲
|
|
42
|
+
│ events trigger │
|
|
43
|
+
│ notifications │ pushes to
|
|
44
|
+
▼ │ connected clients
|
|
45
|
+
┌──────────────────────────────────┐ │
|
|
46
|
+
│ @mariachi/notifications │ │
|
|
47
|
+
│ (transactional, multi-channel) │ │
|
|
48
|
+
│ │ │
|
|
49
|
+
│ 1. Receive notification intent │ │
|
|
50
|
+
│ 2. Check user preferences (DB) │ │
|
|
51
|
+
│ 3. Enqueue via @mariachi/jobs │ │
|
|
52
|
+
│ 4. Deliver via channel: │ │
|
|
53
|
+
│ - Email (Resend) │ │
|
|
54
|
+
│ - SMS (Twilio) │ │
|
|
55
|
+
│ - Push (FCM) │ │
|
|
56
|
+
│ - In-App (DB write) ──────────┼──────────────────┘
|
|
57
|
+
└──────────────────────────────────┘
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Summary of Changes
|
|
63
|
+
|
|
64
|
+
| Package | Change |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `@mariachi/events` | Add `NATSEventBusAdapter` + `NATSJetStreamAdapter`, update factory, add `nats` dep |
|
|
67
|
+
| `@mariachi/notifications` | Rewrite as multi-channel router. Add `notify()` method, SMS/Push adapters, job-based delivery, delivery tracking DB schema, user preference resolution |
|
|
68
|
+
| `@mariachi/realtime` | **New package.** WebSocket/SSE server, NATS bridge, auth integration, presence, channel management. Redis-backed connection state |
|
|
69
|
+
| `@mariachi/jobs` | Add notification delivery job definition |
|
|
70
|
+
| `@mariachi/auth` | Channel authorization support (can user subscribe to this realtime channel?) |
|
|
71
|
+
|
|
72
|
+
### Implementation order
|
|
73
|
+
|
|
74
|
+
1. `@mariachi/events` — add NATS adapter (no breaking changes)
|
|
75
|
+
2. `@mariachi/notifications` — rewrite with multi-channel routing + job queue delivery
|
|
76
|
+
3. `@mariachi/realtime` — new package, depends on events + auth + cache
|
|
77
|
+
4. Wire in-app notifications → realtime push
|
|
78
|
+
5. Add NATS subject naming convention enforcement to `@mariachi/create` validator
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
For full design details (NATS adapter code, notification types, realtime API, client protocol), see the framework repository's `docs/improvements/` or the source ADR.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Integrations
|
|
2
|
+
|
|
3
|
+
How to add and configure third-party integrations in Mariachi.
|
|
4
|
+
|
|
5
|
+
## How to Add an Integration
|
|
6
|
+
|
|
7
|
+
1. **Generate the scaffold** (optional, if using `@mariachi/cli`):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
mariachi generate integration <name>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. **Define credentials** in `integrations/<name>/credentials.ts` using Zod:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
|
|
18
|
+
export const MyCredentials = z.object({
|
|
19
|
+
apiKey: z.string().min(1),
|
|
20
|
+
// ... other fields
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type MyCredentials = z.infer<typeof MyCredentials>;
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. **Define integration functions** in `integrations/<name>/index.ts` using `defineIntegrationFn`:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { defineIntegrationFn } from '@mariachi/integrations';
|
|
30
|
+
import type { IntegrationContext } from '@mariachi/integrations';
|
|
31
|
+
import { MyCredentials } from './credentials';
|
|
32
|
+
import { InputSchema, OutputSchema } from './types';
|
|
33
|
+
|
|
34
|
+
export interface MyIntegrationContext extends IntegrationContext {
|
|
35
|
+
credentials: MyCredentials;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const myAction = defineIntegrationFn({
|
|
39
|
+
name: 'my.action',
|
|
40
|
+
input: InputSchema,
|
|
41
|
+
output: OutputSchema,
|
|
42
|
+
handler: async (input, ctx) => {
|
|
43
|
+
const creds = (ctx as MyIntegrationContext).credentials;
|
|
44
|
+
// Call external API with creds
|
|
45
|
+
return result;
|
|
46
|
+
},
|
|
47
|
+
retry: { attempts: 3, backoff: 'exponential' },
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
4. **Register in the registry** (optional):
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
registry.register({
|
|
55
|
+
name: 'my',
|
|
56
|
+
description: 'My integration',
|
|
57
|
+
credentialSchema: MyCredentials,
|
|
58
|
+
functions: ['my.action'],
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Credential Requirements
|
|
63
|
+
|
|
64
|
+
- Credentials are validated with Zod schemas.
|
|
65
|
+
- Store secrets via `@mariachi/config` (e.g., env adapter); never hardcode.
|
|
66
|
+
- Each integration defines its own credential schema.
|
|
67
|
+
|
|
68
|
+
## Step-by-step recipe
|
|
69
|
+
|
|
70
|
+
For a full walkthrough with credentials, client, types, and tests, see [recipes/add-integration.md](./recipes/add-integration.md).
|
package/docs/packages.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Mariachi Packages
|
|
2
|
+
|
|
3
|
+
Quick reference for all 28 packages. Use this to decide which package to reach for.
|
|
4
|
+
|
|
5
|
+
## Foundation
|
|
6
|
+
|
|
7
|
+
| Package | Use when you need... | Factory / Entry |
|
|
8
|
+
|---------|---------------------|-----------------|
|
|
9
|
+
| `@mariachi/core` | Types, errors, context, DI container, `Result<T,E>` | `getContainer()`, `createContext()`, `KEYS` |
|
|
10
|
+
| `@mariachi/config` | Typed config from env, secrets, feature flags | `loadConfig()`, `useConfig()` |
|
|
11
|
+
| `@mariachi/observability` | Logging, tracing, metrics, error tracking | `createObservability(config)` |
|
|
12
|
+
| `@mariachi/lifecycle` | App bootstrap, startup/shutdown hooks, health checks | `bootstrap()` |
|
|
13
|
+
|
|
14
|
+
## Communication & API
|
|
15
|
+
|
|
16
|
+
| Package | Use when you need... | Factory / Entry |
|
|
17
|
+
|---------|---------------------|-----------------|
|
|
18
|
+
| `@mariachi/communication` | Inter-module procedure calls with middleware | `createCommunication()` |
|
|
19
|
+
| `@mariachi/api-facade` | HTTP servers with auth, rate limiting, controllers | `new FastifyAdapter()`, `BaseController` |
|
|
20
|
+
| `@mariachi/server` | Low-level Fastify adapter (used by api-facade) | `FastifyServerAdapter` |
|
|
21
|
+
| `@mariachi/webhooks` | Inbound webhook endpoints with auth and logging | `WebhookController`, `WebhookServer` |
|
|
22
|
+
|
|
23
|
+
## Data & Storage
|
|
24
|
+
|
|
25
|
+
| Package | Use when you need... | Factory / Entry |
|
|
26
|
+
|---------|---------------------|-----------------|
|
|
27
|
+
| `@mariachi/database` | Schema definitions, repository interface, types | `defineTable()`, `column`, `Database` |
|
|
28
|
+
| `@mariachi/database-postgres` | PostgreSQL + Drizzle ORM implementation | `createPostgresDatabase()`, `DrizzleRepository` |
|
|
29
|
+
| `@mariachi/cache` | Redis caching, distributed locks, memoization | `createCache()`, `createLock()` |
|
|
30
|
+
| `@mariachi/storage` | File/object storage (S3, local) | `Storage`, `DefaultStorage` |
|
|
31
|
+
|
|
32
|
+
## Async & Events
|
|
33
|
+
|
|
34
|
+
| Package | Use when you need... | Factory / Entry |
|
|
35
|
+
|---------|---------------------|-----------------|
|
|
36
|
+
| `@mariachi/events` | Pub/sub event bus (Redis or NATS) | `createEventBus()` |
|
|
37
|
+
| `@mariachi/jobs` | Background job queue and scheduling (BullMQ) | `createJobQueue()`, `defineJob` |
|
|
38
|
+
| `@mariachi/realtime` | WebSocket connections, channels, broadcasting | `Realtime`, `WSAdapter` |
|
|
39
|
+
|
|
40
|
+
## Security & Access
|
|
41
|
+
|
|
42
|
+
| Package | Use when you need... | Factory / Entry |
|
|
43
|
+
|---------|---------------------|-----------------|
|
|
44
|
+
| `@mariachi/auth` | JWT, API keys, OAuth, RBAC | `createAuth()`, `createAuthorization()` |
|
|
45
|
+
| `@mariachi/auth-clerk` | Clerk authentication, webhook verification (Svix) | `createClerkAuth()`, `ClerkWebhookController` |
|
|
46
|
+
| `@mariachi/tenancy` | Multi-tenant isolation (subdomain, header, JWT) | `createTenantResolver()`, `createTenancyMiddleware()` |
|
|
47
|
+
| `@mariachi/rate-limit` | Redis sliding-window rate limiting | `createRateLimiter()` |
|
|
48
|
+
|
|
49
|
+
## Domain Features
|
|
50
|
+
|
|
51
|
+
| Package | Use when you need... | Factory / Entry |
|
|
52
|
+
|---------|---------------------|-----------------|
|
|
53
|
+
| `@mariachi/billing` | Stripe payments, subscriptions, credits, webhooks | `createBilling()`, `StripeAdapter` |
|
|
54
|
+
| `@mariachi/notifications` | Email (Resend), SMS, push, in-app notifications | `Notifications`, `createEmailAdapter()` |
|
|
55
|
+
| `@mariachi/search` | Full-text search (Typesense) | `createSearch()` |
|
|
56
|
+
| `@mariachi/ai` | AI/LLM sessions, tools, prompts, agent loops | `createAI()`, `runAgent()` |
|
|
57
|
+
| `@mariachi/audit` | Append-only audit logging | `createAuditLogger()` |
|
|
58
|
+
| `@mariachi/integrations` | Third-party integration pattern | `defineIntegrationFn()` |
|
|
59
|
+
|
|
60
|
+
## Tooling
|
|
61
|
+
|
|
62
|
+
| Package | Use when you need... | Factory / Entry |
|
|
63
|
+
|---------|---------------------|-----------------|
|
|
64
|
+
| `@mariachi/testing` | In-memory test doubles, factories | `createTestHarness()`, `TestRepository` |
|
|
65
|
+
| `@mariachi/create` | Code scaffolding and validation rules | `mariachi generate` |
|
|
66
|
+
| `@mariachi/cli` | CLI binary | `mariachi init/generate/validate` |
|