@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/dist/index.d.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
interface Logger {
|
|
4
|
+
info(obj: Record<string, unknown>, msg?: string): void;
|
|
5
|
+
warn(obj: Record<string, unknown>, msg?: string): void;
|
|
6
|
+
error(obj: Record<string, unknown>, msg?: string): void;
|
|
7
|
+
debug(obj: Record<string, unknown>, msg?: string): void;
|
|
8
|
+
child(bindings: Record<string, unknown>): Logger;
|
|
9
|
+
}
|
|
10
|
+
interface Context {
|
|
11
|
+
traceId: string;
|
|
12
|
+
userId: string | null;
|
|
13
|
+
tenantId: string | null;
|
|
14
|
+
scopes: string[];
|
|
15
|
+
identityType: string;
|
|
16
|
+
apiKeyId?: string;
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
logger: Logger;
|
|
19
|
+
server?: string;
|
|
20
|
+
}
|
|
21
|
+
declare function createContext(overrides: Partial<Context> & {
|
|
22
|
+
logger: Logger;
|
|
23
|
+
}): Context;
|
|
24
|
+
|
|
25
|
+
declare class MariachiError extends Error {
|
|
26
|
+
readonly code: string;
|
|
27
|
+
readonly metadata?: Record<string, unknown> | undefined;
|
|
28
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown> | undefined);
|
|
29
|
+
}
|
|
30
|
+
declare class ConfigError extends MariachiError {
|
|
31
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
32
|
+
}
|
|
33
|
+
declare class DatabaseError extends MariachiError {
|
|
34
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
35
|
+
}
|
|
36
|
+
declare class CacheError extends MariachiError {
|
|
37
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
38
|
+
}
|
|
39
|
+
declare class AuthError extends MariachiError {
|
|
40
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
41
|
+
}
|
|
42
|
+
declare class CommunicationError extends MariachiError {
|
|
43
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
44
|
+
}
|
|
45
|
+
declare class BillingError extends MariachiError {
|
|
46
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
47
|
+
}
|
|
48
|
+
declare class StorageError extends MariachiError {
|
|
49
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
50
|
+
}
|
|
51
|
+
declare class NotificationError extends MariachiError {
|
|
52
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
53
|
+
}
|
|
54
|
+
declare class SearchError extends MariachiError {
|
|
55
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
56
|
+
}
|
|
57
|
+
declare class EventsError extends MariachiError {
|
|
58
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
59
|
+
}
|
|
60
|
+
declare class JobsError extends MariachiError {
|
|
61
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
62
|
+
}
|
|
63
|
+
declare class RateLimitError extends MariachiError {
|
|
64
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
65
|
+
}
|
|
66
|
+
declare class TenancyError extends MariachiError {
|
|
67
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
68
|
+
}
|
|
69
|
+
declare class AuditError extends MariachiError {
|
|
70
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
71
|
+
}
|
|
72
|
+
declare class AIError extends MariachiError {
|
|
73
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
74
|
+
}
|
|
75
|
+
declare class IntegrationError extends MariachiError {
|
|
76
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
77
|
+
}
|
|
78
|
+
declare class LifecycleError extends MariachiError {
|
|
79
|
+
constructor(code: string, message: string, metadata?: Record<string, unknown>);
|
|
80
|
+
}
|
|
81
|
+
declare function errorToHttpStatus(error: MariachiError): number;
|
|
82
|
+
|
|
83
|
+
type Handler<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny> = (ctx: Context, input: z.infer<TInput>) => Promise<z.infer<TOutput>>;
|
|
84
|
+
type Middleware = (ctx: Context, next: () => Promise<void>) => Promise<void>;
|
|
85
|
+
interface HandlerRegistration<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
86
|
+
schema: {
|
|
87
|
+
input: TInput;
|
|
88
|
+
output: TOutput;
|
|
89
|
+
};
|
|
90
|
+
handler: Handler<TInput, TOutput>;
|
|
91
|
+
middleware?: Middleware[];
|
|
92
|
+
}
|
|
93
|
+
interface PaginationParams {
|
|
94
|
+
page: number;
|
|
95
|
+
pageSize: number;
|
|
96
|
+
}
|
|
97
|
+
interface PaginatedResult<T> {
|
|
98
|
+
data: T[];
|
|
99
|
+
total: number;
|
|
100
|
+
page: number;
|
|
101
|
+
pageSize: number;
|
|
102
|
+
totalPages: number;
|
|
103
|
+
}
|
|
104
|
+
interface SortParams {
|
|
105
|
+
field: string;
|
|
106
|
+
direction: 'asc' | 'desc';
|
|
107
|
+
}
|
|
108
|
+
interface Entity {
|
|
109
|
+
id: string;
|
|
110
|
+
}
|
|
111
|
+
interface BaseEntity extends Entity {
|
|
112
|
+
createdAt: Date;
|
|
113
|
+
updatedAt: Date;
|
|
114
|
+
deletedAt: Date | null;
|
|
115
|
+
}
|
|
116
|
+
interface TenantEntity extends BaseEntity {
|
|
117
|
+
tenantId: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
type Result<T, E = Error> = {
|
|
121
|
+
ok: true;
|
|
122
|
+
value: T;
|
|
123
|
+
} | {
|
|
124
|
+
ok: false;
|
|
125
|
+
error: E;
|
|
126
|
+
};
|
|
127
|
+
declare function ok<T>(value: T): Result<T, never>;
|
|
128
|
+
declare function err<E>(error: E): Result<never, E>;
|
|
129
|
+
declare function unwrap<T, E>(result: Result<T, E>): T;
|
|
130
|
+
declare function unwrapOr<T, E>(result: Result<T, E>, fallback: T): T;
|
|
131
|
+
declare function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;
|
|
132
|
+
declare function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>;
|
|
133
|
+
declare function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>;
|
|
134
|
+
declare function tryCatch<T>(fn: () => Promise<T>): Promise<Result<T, Error>>;
|
|
135
|
+
|
|
136
|
+
interface Container {
|
|
137
|
+
register<T>(key: string | symbol, instance: T): void;
|
|
138
|
+
resolve<T>(key: string | symbol): T;
|
|
139
|
+
has(key: string | symbol): boolean;
|
|
140
|
+
clear(): void;
|
|
141
|
+
}
|
|
142
|
+
declare function createContainer(): Container;
|
|
143
|
+
declare const KEYS: {
|
|
144
|
+
readonly Config: symbol;
|
|
145
|
+
readonly Logger: symbol;
|
|
146
|
+
readonly Tracer: symbol;
|
|
147
|
+
readonly Metrics: symbol;
|
|
148
|
+
readonly Database: symbol;
|
|
149
|
+
readonly Cache: symbol;
|
|
150
|
+
readonly EventBus: symbol;
|
|
151
|
+
readonly JobQueue: symbol;
|
|
152
|
+
readonly Auth: symbol;
|
|
153
|
+
readonly Authorization: symbol;
|
|
154
|
+
readonly Storage: symbol;
|
|
155
|
+
readonly Notifications: symbol;
|
|
156
|
+
readonly Billing: symbol;
|
|
157
|
+
readonly Search: symbol;
|
|
158
|
+
readonly AI: symbol;
|
|
159
|
+
readonly Communication: symbol;
|
|
160
|
+
readonly Lifecycle: symbol;
|
|
161
|
+
readonly RateLimit: symbol;
|
|
162
|
+
readonly Audit: symbol;
|
|
163
|
+
readonly Tenancy: symbol;
|
|
164
|
+
readonly Realtime: symbol;
|
|
165
|
+
};
|
|
166
|
+
declare function getContainer(): Container;
|
|
167
|
+
|
|
168
|
+
interface Span {
|
|
169
|
+
setAttribute(key: string, value: string | number | boolean): void;
|
|
170
|
+
setStatus(status: 'ok' | 'error', message?: string): void;
|
|
171
|
+
end(): void;
|
|
172
|
+
}
|
|
173
|
+
interface TracerAdapter {
|
|
174
|
+
startSpan(name: string, attributes?: Record<string, string>): Span;
|
|
175
|
+
withSpan<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T>;
|
|
176
|
+
}
|
|
177
|
+
interface MetricsAdapter {
|
|
178
|
+
increment(name: string, value?: number, tags?: Record<string, string>): void;
|
|
179
|
+
gauge(name: string, value: number, tags?: Record<string, string>): void;
|
|
180
|
+
histogram(name: string, value: number, tags?: Record<string, string>): void;
|
|
181
|
+
timing(name: string, value: number, tags?: Record<string, string>): void;
|
|
182
|
+
}
|
|
183
|
+
interface Instrumentable {
|
|
184
|
+
readonly logger: Logger;
|
|
185
|
+
readonly tracer?: TracerAdapter;
|
|
186
|
+
readonly metrics?: MetricsAdapter;
|
|
187
|
+
}
|
|
188
|
+
declare function withSpan<T>(tracer: TracerAdapter | undefined, name: string, attributes: Record<string, string | number | boolean>, fn: (span?: Span) => Promise<T>): Promise<T>;
|
|
189
|
+
declare function timed<T>(metrics: MetricsAdapter | undefined, metricName: string, tags?: Record<string, string>): {
|
|
190
|
+
end: (success: boolean) => void;
|
|
191
|
+
wrap: (fn: () => Promise<T>) => Promise<T>;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
interface RetryConfig {
|
|
195
|
+
attempts: number;
|
|
196
|
+
backoff: 'exponential' | 'linear' | 'fixed';
|
|
197
|
+
baseDelayMs: number;
|
|
198
|
+
maxDelayMs: number;
|
|
199
|
+
jitter: boolean;
|
|
200
|
+
retryOn?: (error: Error) => boolean;
|
|
201
|
+
}
|
|
202
|
+
declare const DEFAULT_RETRY_CONFIG: RetryConfig;
|
|
203
|
+
declare function retry<T>(fn: () => Promise<T>, config?: Partial<RetryConfig>): Promise<T>;
|
|
204
|
+
|
|
205
|
+
interface Disposable {
|
|
206
|
+
connect(): Promise<void>;
|
|
207
|
+
disconnect(): Promise<void>;
|
|
208
|
+
isHealthy(): Promise<boolean>;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export { AIError, AuditError, AuthError, type BaseEntity, BillingError, CacheError, CommunicationError, ConfigError, type Container, type Context, DEFAULT_RETRY_CONFIG, DatabaseError, type Disposable, type Entity, EventsError, type Handler, type HandlerRegistration, type Instrumentable, IntegrationError, JobsError, KEYS, LifecycleError, type Logger, MariachiError, type MetricsAdapter, type Middleware, NotificationError, type PaginatedResult, type PaginationParams, RateLimitError, type Result, type RetryConfig, SearchError, type SortParams, type Span, StorageError, TenancyError, type TenantEntity, type TracerAdapter, createContainer, createContext, err, errorToHttpStatus, flatMap, getContainer, map, mapErr, ok, retry, timed, tryCatch, unwrap, unwrapOr, withSpan };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
// src/context.ts
|
|
2
|
+
function createContext(overrides) {
|
|
3
|
+
return {
|
|
4
|
+
traceId: overrides.traceId ?? crypto.randomUUID(),
|
|
5
|
+
userId: overrides.userId ?? null,
|
|
6
|
+
tenantId: overrides.tenantId ?? null,
|
|
7
|
+
scopes: overrides.scopes ?? [],
|
|
8
|
+
identityType: overrides.identityType ?? "anonymous",
|
|
9
|
+
apiKeyId: overrides.apiKeyId,
|
|
10
|
+
sessionId: overrides.sessionId,
|
|
11
|
+
logger: overrides.logger,
|
|
12
|
+
server: overrides.server
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/errors.ts
|
|
17
|
+
var MariachiError = class extends Error {
|
|
18
|
+
constructor(code, message, metadata) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.metadata = metadata;
|
|
22
|
+
this.name = "MariachiError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var ConfigError = class extends MariachiError {
|
|
26
|
+
constructor(code, message, metadata) {
|
|
27
|
+
super(code, message, metadata);
|
|
28
|
+
this.name = "ConfigError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var DatabaseError = class extends MariachiError {
|
|
32
|
+
constructor(code, message, metadata) {
|
|
33
|
+
super(code, message, metadata);
|
|
34
|
+
this.name = "DatabaseError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var CacheError = class extends MariachiError {
|
|
38
|
+
constructor(code, message, metadata) {
|
|
39
|
+
super(code, message, metadata);
|
|
40
|
+
this.name = "CacheError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var AuthError = class extends MariachiError {
|
|
44
|
+
constructor(code, message, metadata) {
|
|
45
|
+
super(code, message, metadata);
|
|
46
|
+
this.name = "AuthError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var CommunicationError = class extends MariachiError {
|
|
50
|
+
constructor(code, message, metadata) {
|
|
51
|
+
super(code, message, metadata);
|
|
52
|
+
this.name = "CommunicationError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var BillingError = class extends MariachiError {
|
|
56
|
+
constructor(code, message, metadata) {
|
|
57
|
+
super(code, message, metadata);
|
|
58
|
+
this.name = "BillingError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var StorageError = class extends MariachiError {
|
|
62
|
+
constructor(code, message, metadata) {
|
|
63
|
+
super(code, message, metadata);
|
|
64
|
+
this.name = "StorageError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var NotificationError = class extends MariachiError {
|
|
68
|
+
constructor(code, message, metadata) {
|
|
69
|
+
super(code, message, metadata);
|
|
70
|
+
this.name = "NotificationError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var SearchError = class extends MariachiError {
|
|
74
|
+
constructor(code, message, metadata) {
|
|
75
|
+
super(code, message, metadata);
|
|
76
|
+
this.name = "SearchError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var EventsError = class extends MariachiError {
|
|
80
|
+
constructor(code, message, metadata) {
|
|
81
|
+
super(code, message, metadata);
|
|
82
|
+
this.name = "EventsError";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var JobsError = class extends MariachiError {
|
|
86
|
+
constructor(code, message, metadata) {
|
|
87
|
+
super(code, message, metadata);
|
|
88
|
+
this.name = "JobsError";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var RateLimitError = class extends MariachiError {
|
|
92
|
+
constructor(code, message, metadata) {
|
|
93
|
+
super(code, message, metadata);
|
|
94
|
+
this.name = "RateLimitError";
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var TenancyError = class extends MariachiError {
|
|
98
|
+
constructor(code, message, metadata) {
|
|
99
|
+
super(code, message, metadata);
|
|
100
|
+
this.name = "TenancyError";
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var AuditError = class extends MariachiError {
|
|
104
|
+
constructor(code, message, metadata) {
|
|
105
|
+
super(code, message, metadata);
|
|
106
|
+
this.name = "AuditError";
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var AIError = class extends MariachiError {
|
|
110
|
+
constructor(code, message, metadata) {
|
|
111
|
+
super(code, message, metadata);
|
|
112
|
+
this.name = "AIError";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var IntegrationError = class extends MariachiError {
|
|
116
|
+
constructor(code, message, metadata) {
|
|
117
|
+
super(code, message, metadata);
|
|
118
|
+
this.name = "IntegrationError";
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var LifecycleError = class extends MariachiError {
|
|
122
|
+
constructor(code, message, metadata) {
|
|
123
|
+
super(code, message, metadata);
|
|
124
|
+
this.name = "LifecycleError";
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var ERROR_CODE_TO_HTTP = {
|
|
128
|
+
"auth/unauthorized": 401,
|
|
129
|
+
"auth/forbidden": 403,
|
|
130
|
+
"auth/token-expired": 401,
|
|
131
|
+
"auth/invalid-token": 401,
|
|
132
|
+
"auth/invalid-api-key": 401,
|
|
133
|
+
"rate-limit/exceeded": 429,
|
|
134
|
+
"not-found": 404,
|
|
135
|
+
"validation/invalid-input": 400,
|
|
136
|
+
"billing/payment-failed": 402,
|
|
137
|
+
"conflict": 409
|
|
138
|
+
};
|
|
139
|
+
function errorToHttpStatus(error) {
|
|
140
|
+
for (const [prefix, status] of Object.entries(ERROR_CODE_TO_HTTP)) {
|
|
141
|
+
if (error.code === prefix || error.code.startsWith(`${prefix}/`)) {
|
|
142
|
+
return status;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return 500;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/result.ts
|
|
149
|
+
function ok(value) {
|
|
150
|
+
return { ok: true, value };
|
|
151
|
+
}
|
|
152
|
+
function err(error) {
|
|
153
|
+
return { ok: false, error };
|
|
154
|
+
}
|
|
155
|
+
function unwrap(result) {
|
|
156
|
+
if (result.ok) return result.value;
|
|
157
|
+
throw result.error;
|
|
158
|
+
}
|
|
159
|
+
function unwrapOr(result, fallback) {
|
|
160
|
+
if (result.ok) return result.value;
|
|
161
|
+
return fallback;
|
|
162
|
+
}
|
|
163
|
+
function map(result, fn) {
|
|
164
|
+
if (result.ok) return ok(fn(result.value));
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
function mapErr(result, fn) {
|
|
168
|
+
if (!result.ok) return err(fn(result.error));
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
function flatMap(result, fn) {
|
|
172
|
+
if (result.ok) return fn(result.value);
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
async function tryCatch(fn) {
|
|
176
|
+
try {
|
|
177
|
+
return ok(await fn());
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/container.ts
|
|
184
|
+
function createContainer() {
|
|
185
|
+
const store = /* @__PURE__ */ new Map();
|
|
186
|
+
return {
|
|
187
|
+
register(key, instance) {
|
|
188
|
+
store.set(key, instance);
|
|
189
|
+
},
|
|
190
|
+
resolve(key) {
|
|
191
|
+
const instance = store.get(key);
|
|
192
|
+
if (instance === void 0) {
|
|
193
|
+
throw new Error(`Container: no registration found for key "${String(key)}"`);
|
|
194
|
+
}
|
|
195
|
+
return instance;
|
|
196
|
+
},
|
|
197
|
+
has(key) {
|
|
198
|
+
return store.has(key);
|
|
199
|
+
},
|
|
200
|
+
clear() {
|
|
201
|
+
store.clear();
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
var KEYS = {
|
|
206
|
+
Config: /* @__PURE__ */ Symbol.for("mariachi.config"),
|
|
207
|
+
Logger: /* @__PURE__ */ Symbol.for("mariachi.logger"),
|
|
208
|
+
Tracer: /* @__PURE__ */ Symbol.for("mariachi.tracer"),
|
|
209
|
+
Metrics: /* @__PURE__ */ Symbol.for("mariachi.metrics"),
|
|
210
|
+
Database: /* @__PURE__ */ Symbol.for("mariachi.database"),
|
|
211
|
+
Cache: /* @__PURE__ */ Symbol.for("mariachi.cache"),
|
|
212
|
+
EventBus: /* @__PURE__ */ Symbol.for("mariachi.eventbus"),
|
|
213
|
+
JobQueue: /* @__PURE__ */ Symbol.for("mariachi.jobqueue"),
|
|
214
|
+
Auth: /* @__PURE__ */ Symbol.for("mariachi.auth"),
|
|
215
|
+
Authorization: /* @__PURE__ */ Symbol.for("mariachi.authorization"),
|
|
216
|
+
Storage: /* @__PURE__ */ Symbol.for("mariachi.storage"),
|
|
217
|
+
Notifications: /* @__PURE__ */ Symbol.for("mariachi.notifications"),
|
|
218
|
+
Billing: /* @__PURE__ */ Symbol.for("mariachi.billing"),
|
|
219
|
+
Search: /* @__PURE__ */ Symbol.for("mariachi.search"),
|
|
220
|
+
AI: /* @__PURE__ */ Symbol.for("mariachi.ai"),
|
|
221
|
+
Communication: /* @__PURE__ */ Symbol.for("mariachi.communication"),
|
|
222
|
+
Lifecycle: /* @__PURE__ */ Symbol.for("mariachi.lifecycle"),
|
|
223
|
+
RateLimit: /* @__PURE__ */ Symbol.for("mariachi.ratelimit"),
|
|
224
|
+
Audit: /* @__PURE__ */ Symbol.for("mariachi.audit"),
|
|
225
|
+
Tenancy: /* @__PURE__ */ Symbol.for("mariachi.tenancy"),
|
|
226
|
+
Realtime: /* @__PURE__ */ Symbol.for("mariachi.realtime")
|
|
227
|
+
};
|
|
228
|
+
var globalContainer = createContainer();
|
|
229
|
+
function getContainer() {
|
|
230
|
+
return globalContainer;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/instrumentable.ts
|
|
234
|
+
async function withSpan(tracer, name, attributes, fn) {
|
|
235
|
+
if (!tracer) return fn(void 0);
|
|
236
|
+
return tracer.withSpan(name, async (span) => {
|
|
237
|
+
for (const [k, v] of Object.entries(attributes)) {
|
|
238
|
+
span.setAttribute(k, v);
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const result = await fn(span);
|
|
242
|
+
span.setStatus("ok");
|
|
243
|
+
return result;
|
|
244
|
+
} catch (error) {
|
|
245
|
+
span.setStatus("error", error.message);
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function timed(metrics, metricName, tags) {
|
|
251
|
+
const start = performance.now();
|
|
252
|
+
return {
|
|
253
|
+
end(success) {
|
|
254
|
+
const durationMs = performance.now() - start;
|
|
255
|
+
metrics?.timing(metricName, durationMs, tags);
|
|
256
|
+
metrics?.increment(`${metricName}.count`, 1, tags);
|
|
257
|
+
if (!success) metrics?.increment(`${metricName}.error`, 1, tags);
|
|
258
|
+
},
|
|
259
|
+
async wrap(fn) {
|
|
260
|
+
try {
|
|
261
|
+
const result = await fn();
|
|
262
|
+
const durationMs = performance.now() - start;
|
|
263
|
+
metrics?.timing(metricName, durationMs, tags);
|
|
264
|
+
metrics?.increment(`${metricName}.count`, 1, tags);
|
|
265
|
+
return result;
|
|
266
|
+
} catch (error) {
|
|
267
|
+
const durationMs = performance.now() - start;
|
|
268
|
+
metrics?.timing(metricName, durationMs, tags);
|
|
269
|
+
metrics?.increment(`${metricName}.count`, 1, tags);
|
|
270
|
+
metrics?.increment(`${metricName}.error`, 1, tags);
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/retry.ts
|
|
278
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
279
|
+
attempts: 3,
|
|
280
|
+
backoff: "exponential",
|
|
281
|
+
baseDelayMs: 200,
|
|
282
|
+
maxDelayMs: 1e4,
|
|
283
|
+
jitter: true
|
|
284
|
+
};
|
|
285
|
+
function computeDelay(config, attempt) {
|
|
286
|
+
let delay;
|
|
287
|
+
switch (config.backoff) {
|
|
288
|
+
case "exponential":
|
|
289
|
+
delay = config.baseDelayMs * Math.pow(2, attempt);
|
|
290
|
+
break;
|
|
291
|
+
case "linear":
|
|
292
|
+
delay = config.baseDelayMs * (attempt + 1);
|
|
293
|
+
break;
|
|
294
|
+
case "fixed":
|
|
295
|
+
delay = config.baseDelayMs;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
delay = Math.min(delay, config.maxDelayMs);
|
|
299
|
+
if (config.jitter) {
|
|
300
|
+
delay = delay * (0.5 + Math.random() * 0.5);
|
|
301
|
+
}
|
|
302
|
+
return delay;
|
|
303
|
+
}
|
|
304
|
+
function sleep(ms) {
|
|
305
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
306
|
+
}
|
|
307
|
+
async function retry(fn, config = {}) {
|
|
308
|
+
const resolved = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
309
|
+
let lastError;
|
|
310
|
+
for (let attempt = 0; attempt < resolved.attempts; attempt++) {
|
|
311
|
+
try {
|
|
312
|
+
return await fn();
|
|
313
|
+
} catch (error) {
|
|
314
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
315
|
+
if (resolved.retryOn && !resolved.retryOn(lastError)) {
|
|
316
|
+
throw lastError;
|
|
317
|
+
}
|
|
318
|
+
if (attempt < resolved.attempts - 1) {
|
|
319
|
+
await sleep(computeDelay(resolved, attempt));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
throw lastError;
|
|
324
|
+
}
|
|
325
|
+
export {
|
|
326
|
+
AIError,
|
|
327
|
+
AuditError,
|
|
328
|
+
AuthError,
|
|
329
|
+
BillingError,
|
|
330
|
+
CacheError,
|
|
331
|
+
CommunicationError,
|
|
332
|
+
ConfigError,
|
|
333
|
+
DEFAULT_RETRY_CONFIG,
|
|
334
|
+
DatabaseError,
|
|
335
|
+
EventsError,
|
|
336
|
+
IntegrationError,
|
|
337
|
+
JobsError,
|
|
338
|
+
KEYS,
|
|
339
|
+
LifecycleError,
|
|
340
|
+
MariachiError,
|
|
341
|
+
NotificationError,
|
|
342
|
+
RateLimitError,
|
|
343
|
+
SearchError,
|
|
344
|
+
StorageError,
|
|
345
|
+
TenancyError,
|
|
346
|
+
createContainer,
|
|
347
|
+
createContext,
|
|
348
|
+
err,
|
|
349
|
+
errorToHttpStatus,
|
|
350
|
+
flatMap,
|
|
351
|
+
getContainer,
|
|
352
|
+
map,
|
|
353
|
+
mapErr,
|
|
354
|
+
ok,
|
|
355
|
+
retry,
|
|
356
|
+
timed,
|
|
357
|
+
tryCatch,
|
|
358
|
+
unwrap,
|
|
359
|
+
unwrapOr,
|
|
360
|
+
withSpan
|
|
361
|
+
};
|
|
362
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.ts","../src/errors.ts","../src/result.ts","../src/container.ts","../src/instrumentable.ts","../src/retry.ts"],"sourcesContent":["export interface Logger {\n info(obj: Record<string, unknown>, msg?: string): void;\n warn(obj: Record<string, unknown>, msg?: string): void;\n error(obj: Record<string, unknown>, msg?: string): void;\n debug(obj: Record<string, unknown>, msg?: string): void;\n child(bindings: Record<string, unknown>): Logger;\n}\n\nexport interface Context {\n traceId: string;\n userId: string | null;\n tenantId: string | null;\n scopes: string[];\n identityType: string;\n apiKeyId?: string;\n sessionId?: string;\n logger: Logger;\n server?: string;\n}\n\nexport function createContext(overrides: Partial<Context> & { logger: Logger }): Context {\n return {\n traceId: overrides.traceId ?? crypto.randomUUID(),\n userId: overrides.userId ?? null,\n tenantId: overrides.tenantId ?? null,\n scopes: overrides.scopes ?? [],\n identityType: overrides.identityType ?? 'anonymous',\n apiKeyId: overrides.apiKeyId,\n sessionId: overrides.sessionId,\n logger: overrides.logger,\n server: overrides.server,\n };\n}\n","export class MariachiError extends Error {\n constructor(\n public readonly code: string,\n message: string,\n public readonly metadata?: Record<string, unknown>,\n ) {\n super(message);\n this.name = 'MariachiError';\n }\n}\n\nexport class ConfigError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'ConfigError';\n }\n}\n\nexport class DatabaseError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'DatabaseError';\n }\n}\n\nexport class CacheError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'CacheError';\n }\n}\n\nexport class AuthError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'AuthError';\n }\n}\n\nexport class CommunicationError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'CommunicationError';\n }\n}\n\nexport class BillingError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'BillingError';\n }\n}\n\nexport class StorageError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'StorageError';\n }\n}\n\nexport class NotificationError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'NotificationError';\n }\n}\n\nexport class SearchError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'SearchError';\n }\n}\n\nexport class EventsError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'EventsError';\n }\n}\n\nexport class JobsError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'JobsError';\n }\n}\n\nexport class RateLimitError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'RateLimitError';\n }\n}\n\nexport class TenancyError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'TenancyError';\n }\n}\n\nexport class AuditError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'AuditError';\n }\n}\n\nexport class AIError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'AIError';\n }\n}\n\nexport class IntegrationError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'IntegrationError';\n }\n}\n\nexport class LifecycleError extends MariachiError {\n constructor(code: string, message: string, metadata?: Record<string, unknown>) {\n super(code, message, metadata);\n this.name = 'LifecycleError';\n }\n}\n\nconst ERROR_CODE_TO_HTTP: Record<string, number> = {\n 'auth/unauthorized': 401,\n 'auth/forbidden': 403,\n 'auth/token-expired': 401,\n 'auth/invalid-token': 401,\n 'auth/invalid-api-key': 401,\n 'rate-limit/exceeded': 429,\n 'not-found': 404,\n 'validation/invalid-input': 400,\n 'billing/payment-failed': 402,\n 'conflict': 409,\n};\n\nexport function errorToHttpStatus(error: MariachiError): number {\n for (const [prefix, status] of Object.entries(ERROR_CODE_TO_HTTP)) {\n if (error.code === prefix || error.code.startsWith(`${prefix}/`)) {\n return status;\n }\n }\n return 500;\n}\n","export type Result<T, E = Error> =\n | { ok: true; value: T }\n | { ok: false; error: E };\n\nexport function ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (result.ok) return result.value;\n throw result.error;\n}\n\nexport function unwrapOr<T, E>(result: Result<T, E>, fallback: T): T {\n if (result.ok) return result.value;\n return fallback;\n}\n\nexport function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {\n if (result.ok) return ok(fn(result.value));\n return result;\n}\n\nexport function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F> {\n if (!result.ok) return err(fn(result.error));\n return result;\n}\n\nexport function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E> {\n if (result.ok) return fn(result.value);\n return result;\n}\n\nexport async function tryCatch<T>(fn: () => Promise<T>): Promise<Result<T, Error>> {\n try {\n return ok(await fn());\n } catch (error) {\n return err(error instanceof Error ? error : new Error(String(error)));\n }\n}\n","const registry = new Map<string | symbol, unknown>();\n\nexport interface Container {\n register<T>(key: string | symbol, instance: T): void;\n resolve<T>(key: string | symbol): T;\n has(key: string | symbol): boolean;\n clear(): void;\n}\n\nexport function createContainer(): Container {\n const store = new Map<string | symbol, unknown>();\n\n return {\n register<T>(key: string | symbol, instance: T): void {\n store.set(key, instance);\n },\n\n resolve<T>(key: string | symbol): T {\n const instance = store.get(key);\n if (instance === undefined) {\n throw new Error(`Container: no registration found for key \"${String(key)}\"`);\n }\n return instance as T;\n },\n\n has(key: string | symbol): boolean {\n return store.has(key);\n },\n\n clear(): void {\n store.clear();\n },\n };\n}\n\nexport const KEYS = {\n Config: Symbol.for('mariachi.config'),\n Logger: Symbol.for('mariachi.logger'),\n Tracer: Symbol.for('mariachi.tracer'),\n Metrics: Symbol.for('mariachi.metrics'),\n Database: Symbol.for('mariachi.database'),\n Cache: Symbol.for('mariachi.cache'),\n EventBus: Symbol.for('mariachi.eventbus'),\n JobQueue: Symbol.for('mariachi.jobqueue'),\n Auth: Symbol.for('mariachi.auth'),\n Authorization: Symbol.for('mariachi.authorization'),\n Storage: Symbol.for('mariachi.storage'),\n Notifications: Symbol.for('mariachi.notifications'),\n Billing: Symbol.for('mariachi.billing'),\n Search: Symbol.for('mariachi.search'),\n AI: Symbol.for('mariachi.ai'),\n Communication: Symbol.for('mariachi.communication'),\n Lifecycle: Symbol.for('mariachi.lifecycle'),\n RateLimit: Symbol.for('mariachi.ratelimit'),\n Audit: Symbol.for('mariachi.audit'),\n Tenancy: Symbol.for('mariachi.tenancy'),\n Realtime: Symbol.for('mariachi.realtime'),\n} as const;\n\nconst globalContainer = createContainer();\n\nexport function getContainer(): Container {\n return globalContainer;\n}\n","import type { Logger } from './context';\n\nexport interface Span {\n setAttribute(key: string, value: string | number | boolean): void;\n setStatus(status: 'ok' | 'error', message?: string): void;\n end(): void;\n}\n\nexport interface TracerAdapter {\n startSpan(name: string, attributes?: Record<string, string>): Span;\n withSpan<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T>;\n}\n\nexport interface MetricsAdapter {\n increment(name: string, value?: number, tags?: Record<string, string>): void;\n gauge(name: string, value: number, tags?: Record<string, string>): void;\n histogram(name: string, value: number, tags?: Record<string, string>): void;\n timing(name: string, value: number, tags?: Record<string, string>): void;\n}\n\nexport interface Instrumentable {\n readonly logger: Logger;\n readonly tracer?: TracerAdapter;\n readonly metrics?: MetricsAdapter;\n}\n\nexport async function withSpan<T>(\n tracer: TracerAdapter | undefined,\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: (span?: Span) => Promise<T>,\n): Promise<T> {\n if (!tracer) return fn(undefined);\n return tracer.withSpan(name, async (span) => {\n for (const [k, v] of Object.entries(attributes)) {\n span.setAttribute(k, v);\n }\n try {\n const result = await fn(span);\n span.setStatus('ok');\n return result;\n } catch (error) {\n span.setStatus('error', (error as Error).message);\n throw error;\n }\n });\n}\n\nexport function timed<T>(\n metrics: MetricsAdapter | undefined,\n metricName: string,\n tags?: Record<string, string>,\n): { end: (success: boolean) => void; wrap: (fn: () => Promise<T>) => Promise<T> } {\n const start = performance.now();\n return {\n end(success: boolean) {\n const durationMs = performance.now() - start;\n metrics?.timing(metricName, durationMs, tags);\n metrics?.increment(`${metricName}.count`, 1, tags);\n if (!success) metrics?.increment(`${metricName}.error`, 1, tags);\n },\n async wrap(fn: () => Promise<T>): Promise<T> {\n try {\n const result = await fn();\n const durationMs = performance.now() - start;\n metrics?.timing(metricName, durationMs, tags);\n metrics?.increment(`${metricName}.count`, 1, tags);\n return result;\n } catch (error) {\n const durationMs = performance.now() - start;\n metrics?.timing(metricName, durationMs, tags);\n metrics?.increment(`${metricName}.count`, 1, tags);\n metrics?.increment(`${metricName}.error`, 1, tags);\n throw error;\n }\n },\n };\n}\n","export interface RetryConfig {\n attempts: number;\n backoff: 'exponential' | 'linear' | 'fixed';\n baseDelayMs: number;\n maxDelayMs: number;\n jitter: boolean;\n retryOn?: (error: Error) => boolean;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n attempts: 3,\n backoff: 'exponential',\n baseDelayMs: 200,\n maxDelayMs: 10_000,\n jitter: true,\n};\n\nfunction computeDelay(config: RetryConfig, attempt: number): number {\n let delay: number;\n switch (config.backoff) {\n case 'exponential':\n delay = config.baseDelayMs * Math.pow(2, attempt);\n break;\n case 'linear':\n delay = config.baseDelayMs * (attempt + 1);\n break;\n case 'fixed':\n delay = config.baseDelayMs;\n break;\n }\n delay = Math.min(delay, config.maxDelayMs);\n if (config.jitter) {\n delay = delay * (0.5 + Math.random() * 0.5);\n }\n return delay;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function retry<T>(\n fn: () => Promise<T>,\n config: Partial<RetryConfig> = {},\n): Promise<T> {\n const resolved: RetryConfig = { ...DEFAULT_RETRY_CONFIG, ...config };\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < resolved.attempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (resolved.retryOn && !resolved.retryOn(lastError)) {\n throw lastError;\n }\n if (attempt < resolved.attempts - 1) {\n await sleep(computeDelay(resolved, attempt));\n }\n }\n }\n\n throw lastError;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# ADR 001: Adapter Pattern for External Dependencies
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The framework integrates with many external systems: databases (PostgreSQL), caches (Redis), message queues (BullMQ), payment providers (Stripe), search engines (Typesense), AI providers (OpenAI), and others. Direct use of vendor SDKs creates:
|
|
10
|
+
|
|
11
|
+
- **Vendor lock-in** — Switching providers requires large refactors.
|
|
12
|
+
- **Testing friction** — Real services are slow and brittle in tests.
|
|
13
|
+
- **Configuration sprawl** — Each vendor has different setup patterns.
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
All external dependencies are placed behind adapters. Each package exposes:
|
|
18
|
+
|
|
19
|
+
1. A **common interface** (e.g., `CacheClient`, `SearchClient`).
|
|
20
|
+
2. A **factory function** (e.g., `createCache`, `createSearch`) that selects the implementation from config.
|
|
21
|
+
3. **Multiple implementations** — at least one production adapter (Redis, Stripe, etc.) and one in-memory adapter for tests.
|
|
22
|
+
|
|
23
|
+
Config drives the choice:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
createCache({ adapter: 'redis', url: '...' }); // production
|
|
27
|
+
createCache({ adapter: 'memory' }); // tests
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Consequences
|
|
31
|
+
|
|
32
|
+
**Positive:**
|
|
33
|
+
|
|
34
|
+
- Full portability: swap Redis for another cache without changing application code.
|
|
35
|
+
- Tests run fast with in-memory adapters; no external services required.
|
|
36
|
+
- Consistent configuration pattern across packages.
|
|
37
|
+
|
|
38
|
+
**Negative:**
|
|
39
|
+
|
|
40
|
+
- More code: each adapter is a thin wrapper around the vendor SDK.
|
|
41
|
+
- New adapters require implementing the full interface.
|
|
42
|
+
- Some vendor-specific features may not map cleanly to the abstraction.
|