@classytic/arc 2.11.4 → 2.13.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/README.md +16 -12
- package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
- package/dist/EventTransport-CT_52aWU.d.mts +34 -0
- package/dist/EventTransport-DLWoUMHy.mjs +103 -0
- package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/audit.d.mts +199 -0
- package/dist/auth/audit.mjs +288 -0
- package/dist/auth/index.d.mts +3 -3
- package/dist/auth/index.mjs +117 -191
- package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
- package/dist/buildHandler-olo-gt94.mjs +610 -0
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/describe.d.mts +89 -13
- package/dist/cli/commands/describe.mjs +56 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +147 -48
- package/dist/cli/commands/init.d.mts +13 -0
- package/dist/cli/commands/init.mjs +130 -87
- package/dist/cli/commands/introspect.mjs +8 -1
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/core-D72ia0EH.mjs +1399 -0
- package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
- package/dist/createAggregationRouter-CyecOxnO.mjs +114 -0
- package/dist/{createApp-C9bRrqlX.mjs → createApp-XX2-N0Yd.mjs} +28 -22
- package/dist/{defineEvent-D1Ky9M1D.mjs → defineEvent-D5h7EvAx.mjs} +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
- package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
- package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
- package/dist/errors-j4aJm1Wg.mjs +184 -0
- package/dist/{eventPlugin-Cts2-Tfj.mjs → eventPlugin-CaKTYkYM.mjs} +28 -4
- package/dist/{eventPlugin-DDJoNEPL.d.mts → eventPlugin-qXpqTebY.d.mts} +24 -1
- package/dist/events/index.d.mts +6 -6
- package/dist/events/index.mjs +11 -35
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-BRjxOAFp.d.mts → fields-COhcH3fk.d.mts} +23 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +1 -20
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-rHjXmJar.d.mts → index-BTqLEvhu.d.mts} +163 -3
- package/dist/{index-CXXRbnf8.d.mts → index-BtW7qYwa.d.mts} +660 -326
- package/dist/{index-m8mOOlFW.d.mts → index-Ds61mrJE.d.mts} +50 -4
- package/dist/{index-D9t1KNaB.d.mts → index-Dz5IKsrE.d.mts} +360 -219
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +9 -10
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +60 -11
- package/dist/integrations/streamline.mjs +75 -85
- package/dist/integrations/websocket.mjs +2 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +2 -2
- package/dist/migrations/index.d.mts +23 -3
- package/dist/migrations/index.mjs +0 -7
- package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
- package/dist/{openapi-D7G1V7ex.mjs → openapi-CiOMVW1p.mjs} +143 -13
- package/dist/org/index.d.mts +2 -2
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
- package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +16 -31
- package/dist/plugins/index.mjs +33 -13
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +6 -9
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +6 -8
- package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
- package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
- package/dist/{redis-stream-xTGxB2bm.d.mts → redis-stream-D6HzR1Z_.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
- package/dist/{resourceToTools-CxNmI6xF.mjs → resourceToTools-C5coh64w.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
- package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-7Vl611Qs.mjs} +1 -1
- package/dist/schemas/index.d.mts +100 -30
- package/dist/schemas/index.mjs +86 -29
- package/dist/scim/index.d.mts +264 -0
- package/dist/scim/index.mjs +963 -0
- package/dist/scope/index.d.mts +3 -3
- package/dist/scope/index.mjs +4 -4
- package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
- package/dist/{store-helpers-Cp4uKC1U.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -8
- package/dist/testing/index.mjs +16 -24
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-D7KpfiL1.d.mts → types-BvqwCCSx.d.mts} +73 -25
- package/dist/{types-DDyTPc6y.d.mts → types-CTYvcwHe.d.mts} +195 -1
- package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
- package/dist/{types-BQ9TJQNy.d.mts → types-DQHFc8PM.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +5 -5
- package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
- package/dist/{versioning-DsglKfM_.d.mts → versioning-DTTvc80y.d.mts} +1 -1
- package/package.json +24 -34
- package/skills/arc/SKILL.md +147 -51
- package/skills/arc/references/agent-auth.md +238 -0
- package/skills/arc/references/api-reference.md +187 -0
- package/skills/arc/references/auth.md +354 -7
- package/skills/arc/references/enterprise-auth.md +94 -0
- package/skills/arc/references/events.md +8 -6
- package/skills/arc/references/mcp.md +2 -2
- package/skills/arc/references/multi-tenancy.md +11 -2
- package/skills/arc/references/production.md +10 -9
- package/skills/arc/references/scim.md +247 -0
- package/skills/arc/references/testing.md +1 -1
- package/skills/arc-code-review/SKILL.md +141 -0
- package/skills/arc-code-review/references/anti-patterns.md +911 -0
- package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
- package/skills/arc-code-review/references/migration-recipes.md +700 -0
- package/skills/arc-code-review/references/mongokit-migration.md +386 -0
- package/skills/arc-code-review/references/scaffolding.md +230 -0
- package/skills/arc-code-review/references/severity.md +127 -0
- package/dist/EventTransport-BFQjw9pB.mjs +0 -133
- package/dist/EventTransport-CYNUXdCJ.d.mts +0 -293
- package/dist/adapters/index.d.mts +0 -3
- package/dist/adapters/index.mjs +0 -2
- package/dist/adapters-DUUiiimH.mjs +0 -964
- package/dist/auth/mongoose.d.mts +0 -191
- package/dist/auth/mongoose.mjs +0 -73
- package/dist/core-CbcQRIch.mjs +0 -1054
- package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
- package/dist/errorHandler-DEWmGWPz.d.mts +0 -114
- package/dist/errors-D5c-5BJL.mjs +0 -232
- package/dist/index-Rg8axYPz.d.mts +0 -370
- /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
- /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
- /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
- /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
- /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
- /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
- /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
- /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
- /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
- /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
- /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
- /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
- /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
- /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Severity Rubric
|
|
2
|
+
|
|
3
|
+
Score every finding by **blast radius × likelihood of harm × ease of detection in current tests**. Don't grade by code volume — a one-line auth bypass beats 200 lines of duplicated CRUD.
|
|
4
|
+
|
|
5
|
+
Severity legend: 🔴 Critical · 🟠 High · 🟡 Medium · 🟢 Low
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🔴 Critical — fix this release
|
|
10
|
+
|
|
11
|
+
A finding is **critical** if it meets any of:
|
|
12
|
+
|
|
13
|
+
- **Auth/authorization bypass.** `req.user.role` accessed without scope guard on a public route, missing tenant filter, hand-rolled idempotency that double-charges under retry, webhook signature verification on parsed body.
|
|
14
|
+
- **Data leakage.** Manual `toJSON` transform with sensitive fields not declared `hidden`. New sensitive fields (added later) inherit the leak silently.
|
|
15
|
+
- **Drift in security-critical wiring.** Custom controller that doesn't forward `tenantField`, so multi-tenant injection silently fails.
|
|
16
|
+
- **Silent failure modes.** Headers set in `onSend` (intermittent `ERR_HTTP_HEADERS_SENT` under load), `failOpen` events on a path that needs delivery guarantees.
|
|
17
|
+
- **Runtime crashes.** `req.user._id` access on `allowPublic()` routes (crashes only when unauthenticated request hits — usually missed in dev).
|
|
18
|
+
|
|
19
|
+
**Examples (from anti-patterns.md):** §4, §5, §9, §17, §18, §25, §27, §32.
|
|
20
|
+
|
|
21
|
+
**Action:** stop the audit, write a hot-fix recommendation, then continue. These can ship as standalone PRs ahead of the broader migration.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🟠 High — fix this quarter
|
|
26
|
+
|
|
27
|
+
A finding is **high** if:
|
|
28
|
+
|
|
29
|
+
- **Duplicated arc behavior** that *will* drift. Five `fastify.get/post/patch/delete` calls per resource where one `defineResource` would do — one resource gets a fix the others don't.
|
|
30
|
+
- **Wrong abstraction layer.** Driver imports (`mongoose`, `@prisma/client`, `drizzle-orm`) leaking into `services/`, `hooks/`, `routes/`. Forces every consumer to pull a database driver; complicates testing.
|
|
31
|
+
- **Missing arc capability** that's already a pain point. Manual idempotency, manual rate-limiting, manual event emission inconsistency, hand-rolled audit trail. The team is building tools arc already provides.
|
|
32
|
+
- **Whole-suite refactor.** No mongokit adoption (§28). Each Mongoose model is a 100+ LOC reduction.
|
|
33
|
+
|
|
34
|
+
**Examples:** §3, §7, §10, §19, §20, §22, §28.
|
|
35
|
+
|
|
36
|
+
**Action:** prioritize by occurrence count. A pattern repeated 30 times outranks an isolated high.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🟡 Medium — fix opportunistically
|
|
41
|
+
|
|
42
|
+
A finding is **medium** if:
|
|
43
|
+
|
|
44
|
+
- **Drift surface, not yet harmful.** Manual query parsing where the fields parsed are well-known and stable; manual cache invalidation where the cache strategy works today but breaks on rollout.
|
|
45
|
+
- **Documentation drift.** Hand-maintained OpenAPI/Swagger that's currently in sync but will inevitably diverge.
|
|
46
|
+
- **Preset adoption gap.** Soft-delete/multi-tenant/bulk wired manually instead of via preset. Code works but doesn't benefit from preset upgrades.
|
|
47
|
+
- **Style of contract.** Custom controller without forwarding hooks (works today, breaks when arc updates).
|
|
48
|
+
|
|
49
|
+
**Examples:** §1, §2, §6, §8, §14, §16, §21, §23, §24, §26.
|
|
50
|
+
|
|
51
|
+
**Action:** bundle into resource-by-resource refactor PRs. Don't open a separate "fix all medium" PR.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🟢 Low — fix when adjacent
|
|
56
|
+
|
|
57
|
+
A finding is **low** if:
|
|
58
|
+
|
|
59
|
+
- **Code-style only.** `console.log`, `any`, `@ts-ignore`, default exports, tsconfig path aliases breaking auto-discovery.
|
|
60
|
+
- **Cosmetic config.** Missing `displayName` / `module` on `defineResource`, no `arc-cheatsheet` subpath imports, missing `arc docs` script in package.json.
|
|
61
|
+
- **Nicety, not problem.** Manual auth registration instead of `createApp({ auth })` — works fine if app is small.
|
|
62
|
+
|
|
63
|
+
**Examples:** §11, §12, §13, §29, §30, §31.
|
|
64
|
+
|
|
65
|
+
**Action:** fix when touching the file for another reason. Don't write a dedicated PR.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Triage examples
|
|
70
|
+
|
|
71
|
+
### "Found 47 manual permission checks across 12 resources"
|
|
72
|
+
🔴 Critical (auth surface) → list each as a separate finding **only if** the check pattern differs. If they're all `if (!req.user.roles.includes('admin')) throw ...`, group as one finding with 47 occurrences.
|
|
73
|
+
|
|
74
|
+
### "Found `mongoose` imported in 8 service files"
|
|
75
|
+
🟠 High → architectural. Group by service module, not file, since a service rewrite touches all files in the module together.
|
|
76
|
+
|
|
77
|
+
### "Found 3 separate `softDelete` reimplementations across resources"
|
|
78
|
+
🟡 Medium → preset adoption. Bundle with the resource refactor.
|
|
79
|
+
|
|
80
|
+
### "Found `console.log` in 23 places"
|
|
81
|
+
🟢 Low → unless any are inside a hot path with sensitive data (then escalate to High for *those* specific calls).
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## When to escalate severity
|
|
86
|
+
|
|
87
|
+
Escalate one tier higher if:
|
|
88
|
+
|
|
89
|
+
- **The pattern is in a write path.** Any pattern in `POST/PATCH/DELETE` is one tier worse than the same pattern in `GET`.
|
|
90
|
+
- **The pattern is in `auth/`, `webhooks/`, `payments/`, `audit/`, or anything customer-money-touching.** All findings in these paths are at minimum 🟠.
|
|
91
|
+
- **The team has been bitten before.** If `git log` shows a hot-fix commit for a related bug, the underlying pattern is at minimum 🟠.
|
|
92
|
+
- **No tests cover the path.** A medium pattern with no test cover is high — drift will land in prod.
|
|
93
|
+
|
|
94
|
+
Escalate one tier lower if:
|
|
95
|
+
|
|
96
|
+
- **The path is feature-flagged off** in production.
|
|
97
|
+
- **The path is admin-only and there are <5 admins** with manually verified scope.
|
|
98
|
+
- **A migration is already in flight** for this pattern (don't double-count).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Reporting cadence
|
|
103
|
+
|
|
104
|
+
For a single audit:
|
|
105
|
+
|
|
106
|
+
- **Top 3 criticals** in the executive summary.
|
|
107
|
+
- **All highs** in the body.
|
|
108
|
+
- **Mediums grouped by category** (one row each: "12× manual query parsing").
|
|
109
|
+
- **Lows in an appendix** or omitted.
|
|
110
|
+
|
|
111
|
+
Don't dump every finding into one giant table — readers will skip. Give them the headline first.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Score → migration estimate
|
|
116
|
+
|
|
117
|
+
Rough conversion for the executive summary's "estimated effort" line:
|
|
118
|
+
|
|
119
|
+
| Critical findings | Highs | Mediums | Lows | Effort |
|
|
120
|
+
|---|---|---|---|---|
|
|
121
|
+
| 0 | <5 | <10 | any | S (1–3 days, 1 dev) |
|
|
122
|
+
| 0 | 5–15 | 10–30 | any | M (1–2 weeks, 1 dev) |
|
|
123
|
+
| 1–3 | 5–15 | 10–30 | any | M+ (2 weeks, 2 devs — split critical fixes) |
|
|
124
|
+
| any | >15 | >30 | any | L (1 month+, plan in phases) |
|
|
125
|
+
| >3 | any | any | any | Stop the audit, raise critical fixes first, then re-audit |
|
|
126
|
+
|
|
127
|
+
These are heuristics. Adjust for codebase size and test coverage.
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
//#region src/events/EventTransport.ts
|
|
2
|
-
/**
|
|
3
|
-
* In-memory event transport (default)
|
|
4
|
-
* Events are delivered synchronously within the process.
|
|
5
|
-
* Not suitable for multi-instance deployments.
|
|
6
|
-
*/
|
|
7
|
-
var MemoryEventTransport = class {
|
|
8
|
-
name = "memory";
|
|
9
|
-
handlers = /* @__PURE__ */ new Map();
|
|
10
|
-
logger;
|
|
11
|
-
constructor(options) {
|
|
12
|
-
this.logger = options?.logger ?? console;
|
|
13
|
-
}
|
|
14
|
-
async publish(event) {
|
|
15
|
-
const exactHandlers = this.handlers.get(event.type) ?? /* @__PURE__ */ new Set();
|
|
16
|
-
const wildcardHandlers = this.handlers.get("*") ?? /* @__PURE__ */ new Set();
|
|
17
|
-
const patternHandlers = /* @__PURE__ */ new Set();
|
|
18
|
-
for (const [pattern, handlers] of this.handlers.entries()) if (pattern.endsWith(".*")) {
|
|
19
|
-
const prefix = pattern.slice(0, -2);
|
|
20
|
-
if (event.type.startsWith(`${prefix}.`)) for (const h of handlers) patternHandlers.add(h);
|
|
21
|
-
}
|
|
22
|
-
const allHandlers = new Set([
|
|
23
|
-
...exactHandlers,
|
|
24
|
-
...wildcardHandlers,
|
|
25
|
-
...patternHandlers
|
|
26
|
-
]);
|
|
27
|
-
for (const handler of allHandlers) try {
|
|
28
|
-
await handler(event);
|
|
29
|
-
} catch (err) {
|
|
30
|
-
this.logger.error(`[EventTransport] Handler error for ${event.type}:`, err);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Reference `publishMany` implementation — delegates to `publish()` in order.
|
|
35
|
-
*
|
|
36
|
-
* Production transports (Kafka, Redis pipeline, SQS batch) should override
|
|
37
|
-
* this with a single batched network call. Memory transport has nothing to
|
|
38
|
-
* batch, so we just loop — the loop still returns a proper result map so
|
|
39
|
-
* `EventOutbox.relay` can exercise the batched code path in tests.
|
|
40
|
-
*/
|
|
41
|
-
async publishMany(events) {
|
|
42
|
-
const results = /* @__PURE__ */ new Map();
|
|
43
|
-
for (const event of events) try {
|
|
44
|
-
await this.publish(event);
|
|
45
|
-
results.set(event.meta.id, null);
|
|
46
|
-
} catch (err) {
|
|
47
|
-
results.set(event.meta.id, err instanceof Error ? err : new Error(String(err)));
|
|
48
|
-
}
|
|
49
|
-
return results;
|
|
50
|
-
}
|
|
51
|
-
async subscribe(pattern, handler) {
|
|
52
|
-
if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
|
|
53
|
-
this.handlers.get(pattern)?.add(handler);
|
|
54
|
-
return () => {
|
|
55
|
-
const set = this.handlers.get(pattern);
|
|
56
|
-
if (set) {
|
|
57
|
-
set.delete(handler);
|
|
58
|
-
if (set.size === 0) this.handlers.delete(pattern);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
async close() {
|
|
63
|
-
this.handlers.clear();
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* Create a domain event with auto-generated metadata.
|
|
68
|
-
*
|
|
69
|
-
* `id` and `timestamp` are filled in; everything else is caller-controlled.
|
|
70
|
-
* Set `schemaVersion` explicitly for any event type you plan to evolve.
|
|
71
|
-
*/
|
|
72
|
-
function createEvent(type, payload, meta) {
|
|
73
|
-
return {
|
|
74
|
-
type,
|
|
75
|
-
payload,
|
|
76
|
-
meta: {
|
|
77
|
-
id: crypto.randomUUID(),
|
|
78
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
79
|
-
...meta
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Create a child event that chains causation from a parent event.
|
|
85
|
-
*
|
|
86
|
-
* Rules:
|
|
87
|
-
* - `causationId` is set to the parent's `id` (direct cause)
|
|
88
|
-
* - `correlationId` is inherited from the parent if set, else falls back
|
|
89
|
-
* to the parent's `id` (root correlation)
|
|
90
|
-
* - `userId` / `organizationId` are inherited when not overridden so the
|
|
91
|
-
* whole chain stays scoped to the originating principal/tenant
|
|
92
|
-
*
|
|
93
|
-
* Caller-supplied `meta` wins over inherited fields — pass `{ userId: newActor }`
|
|
94
|
-
* to override when a subsystem acts on behalf of a different principal.
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* ```typescript
|
|
98
|
-
* const orderPlaced = createEvent('order.placed', { orderId: 'o1' }, {
|
|
99
|
-
* correlationId: req.id, userId: user.id,
|
|
100
|
-
* });
|
|
101
|
-
* await events.publish(orderPlaced);
|
|
102
|
-
*
|
|
103
|
-
* // Downstream handler emits a child event:
|
|
104
|
-
* const reserved = createChildEvent(orderPlaced, 'inventory.reserved', {
|
|
105
|
-
* orderId: 'o1', skus: ['sku-1', 'sku-2'],
|
|
106
|
-
* });
|
|
107
|
-
* // reserved.meta.causationId === orderPlaced.meta.id
|
|
108
|
-
* // reserved.meta.correlationId === orderPlaced.meta.correlationId
|
|
109
|
-
* // reserved.meta.userId === user.id (inherited)
|
|
110
|
-
* ```
|
|
111
|
-
*/
|
|
112
|
-
function createChildEvent(parent, type, payload, meta) {
|
|
113
|
-
const inherited = {
|
|
114
|
-
correlationId: parent.meta.correlationId ?? parent.meta.id,
|
|
115
|
-
causationId: parent.meta.id
|
|
116
|
-
};
|
|
117
|
-
if (parent.meta.userId !== void 0) inherited.userId = parent.meta.userId;
|
|
118
|
-
if (parent.meta.organizationId !== void 0) inherited.organizationId = parent.meta.organizationId;
|
|
119
|
-
if (parent.meta.source !== void 0) inherited.source = parent.meta.source;
|
|
120
|
-
if (parent.meta.idempotencyKey !== void 0) inherited.idempotencyKey = parent.meta.idempotencyKey;
|
|
121
|
-
return {
|
|
122
|
-
type,
|
|
123
|
-
payload,
|
|
124
|
-
meta: {
|
|
125
|
-
id: crypto.randomUUID(),
|
|
126
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
127
|
-
...inherited,
|
|
128
|
-
...meta
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
//#endregion
|
|
133
|
-
export { createChildEvent as n, createEvent as r, MemoryEventTransport as t };
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
//#region src/events/EventTransport.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Event Transport Interface
|
|
4
|
-
*
|
|
5
|
-
* Defines contract for event delivery backends.
|
|
6
|
-
* Implement for durable transports (Redis, RabbitMQ, Kafka, etc.)
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* // Redis Pub/Sub implementation
|
|
10
|
-
* class RedisEventTransport implements EventTransport {
|
|
11
|
-
* async publish(event) {
|
|
12
|
-
* await redis.publish(event.type, JSON.stringify(event));
|
|
13
|
-
* }
|
|
14
|
-
* async subscribe(pattern, handler) {
|
|
15
|
-
* redis.psubscribe(pattern);
|
|
16
|
-
* redis.on('pmessage', (p, channel, msg) => handler(JSON.parse(msg)));
|
|
17
|
-
* }
|
|
18
|
-
* }
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* Event metadata.
|
|
22
|
-
*
|
|
23
|
-
* Split out as a standalone interface so primitives / downstream packages can
|
|
24
|
-
* mirror it without re-declaring the DomainEvent wrapper. See events.ts in
|
|
25
|
-
* @classytic/primitives for the sibling shape.
|
|
26
|
-
*/
|
|
27
|
-
interface EventMeta {
|
|
28
|
-
/** Unique event ID (UUID v4 recommended) */
|
|
29
|
-
id: string;
|
|
30
|
-
/** Event timestamp */
|
|
31
|
-
timestamp: Date;
|
|
32
|
-
/**
|
|
33
|
-
* Schema version for this event type. Default: `1`.
|
|
34
|
-
*
|
|
35
|
-
* Use when the payload shape evolves so handlers can branch on version
|
|
36
|
-
* during migration windows (`if (event.meta.schemaVersion === 2) ...`).
|
|
37
|
-
* Bump ONLY when the payload contract changes in a breaking way.
|
|
38
|
-
*/
|
|
39
|
-
schemaVersion?: number;
|
|
40
|
-
/**
|
|
41
|
-
* Correlation ID — stays stable across an entire causal chain so a single
|
|
42
|
-
* user action can be traced through every downstream event. Spans service
|
|
43
|
-
* boundaries. Generated at the edge (HTTP request, CLI invocation) and
|
|
44
|
-
* inherited by every child event.
|
|
45
|
-
*/
|
|
46
|
-
correlationId?: string;
|
|
47
|
-
/**
|
|
48
|
-
* Causation ID — the `meta.id` of the direct parent event that caused
|
|
49
|
-
* this one. Forms a linked-list of cause-and-effect within a correlation.
|
|
50
|
-
*
|
|
51
|
-
* Distinct from correlationId: correlation groups, causation chains.
|
|
52
|
-
* Use {@link createChildEvent} to populate this automatically.
|
|
53
|
-
*/
|
|
54
|
-
causationId?: string;
|
|
55
|
-
/**
|
|
56
|
-
* Partition key hint for ordered transports (Kafka, Kinesis, Redis Streams
|
|
57
|
-
* consumer groups). Events with the same partitionKey are guaranteed to be
|
|
58
|
-
* delivered in publish order by transports that honour it.
|
|
59
|
-
*
|
|
60
|
-
* Defaults to `resourceId` if unset. Transports that don't support ordering
|
|
61
|
-
* (in-memory, simple pub/sub) ignore this field.
|
|
62
|
-
*/
|
|
63
|
-
partitionKey?: string;
|
|
64
|
-
/** Source resource (e.g. 'order', 'transaction') */
|
|
65
|
-
resource?: string;
|
|
66
|
-
/** Resource identifier */
|
|
67
|
-
resourceId?: string;
|
|
68
|
-
/** User who triggered the event */
|
|
69
|
-
userId?: string;
|
|
70
|
-
/** Organization context */
|
|
71
|
-
organizationId?: string;
|
|
72
|
-
/**
|
|
73
|
-
* Originating service or package (e.g. `'commerce'`, `'billing'`, `'arc-core'`).
|
|
74
|
-
*
|
|
75
|
-
* In a multi-service deployment, consumers route / log / alert by `source`
|
|
76
|
-
* without parsing `type` prefixes. Arc itself never populates this — hosts
|
|
77
|
-
* set it once per emitter (`app.events.publish('order.placed', p, { source: 'commerce' })`).
|
|
78
|
-
* Inherited by {@link createChildEvent} so downstream events carry the same
|
|
79
|
-
* source unless overridden.
|
|
80
|
-
*/
|
|
81
|
-
source?: string;
|
|
82
|
-
/**
|
|
83
|
-
* Idempotency key — stable hint that this event represents a specific
|
|
84
|
-
* operation exactly once. Consumers dedupe with `if (processed.has(meta.idempotencyKey)) return`.
|
|
85
|
-
*
|
|
86
|
-
* Survives every transport (Memory / Pub-Sub / Streams / Kafka) because it's
|
|
87
|
-
* part of the event, not a transport-side option. Distinct from `meta.id`
|
|
88
|
-
* (which is fresh per emit — a retry would produce a new id).
|
|
89
|
-
*
|
|
90
|
-
* Typical sources: HTTP `Idempotency-Key` header, outbox `dedupeKey`, or
|
|
91
|
-
* `{aggregate.type}:{aggregate.id}:{action}`. Inherited by child events.
|
|
92
|
-
*/
|
|
93
|
-
idempotencyKey?: string;
|
|
94
|
-
/**
|
|
95
|
-
* DDD aggregate marker — the aggregate that owns this event's invariant.
|
|
96
|
-
*
|
|
97
|
-
* Use when routing events by aggregate, doing event-sourcing replay, or
|
|
98
|
-
* enforcing consistency boundaries. Distinct from `resource` / `resourceId`
|
|
99
|
-
* (HTTP-origin entity) because an event emitted *by* one REST resource can
|
|
100
|
-
* *belong to* a different aggregate (e.g. `POST /orders/:id/ship` emits
|
|
101
|
-
* `shipment.dispatched` owned by a shipment aggregate).
|
|
102
|
-
*
|
|
103
|
-
* Downstream packages narrow `aggregate.type` to their own string union via
|
|
104
|
-
* interface extension:
|
|
105
|
-
*
|
|
106
|
-
* ```ts
|
|
107
|
-
* type CartAggregateType = 'cart' | 'cart-item';
|
|
108
|
-
* interface CartEventMeta extends EventMeta {
|
|
109
|
-
* aggregate?: { type: CartAggregateType; id: string };
|
|
110
|
-
* }
|
|
111
|
-
* ```
|
|
112
|
-
*
|
|
113
|
-
* Not inherited by {@link createChildEvent} — child events typically belong
|
|
114
|
-
* to a different aggregate than their parent.
|
|
115
|
-
*/
|
|
116
|
-
aggregate?: {
|
|
117
|
-
type: string;
|
|
118
|
-
id: string;
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
interface DomainEvent<T = unknown> {
|
|
122
|
-
/** Event type (e.g., 'product.created', 'order.shipped') */
|
|
123
|
-
type: string;
|
|
124
|
-
/** Event payload */
|
|
125
|
-
payload: T;
|
|
126
|
-
/** Event metadata */
|
|
127
|
-
meta: EventMeta;
|
|
128
|
-
}
|
|
129
|
-
type EventHandler<T = unknown> = (event: DomainEvent<T>) => void | Promise<void>;
|
|
130
|
-
/**
|
|
131
|
-
* A permanently-failed event routed to a dead-letter sink after retries
|
|
132
|
-
* have been exhausted. Mirrors the shape a caller would log, alert on, or
|
|
133
|
-
* replay from once the upstream issue is fixed.
|
|
134
|
-
*/
|
|
135
|
-
interface DeadLetteredEvent<T = unknown> {
|
|
136
|
-
/** The original event */
|
|
137
|
-
event: DomainEvent<T>;
|
|
138
|
-
/** Serialised failure reason (message + optional machine code + stack) */
|
|
139
|
-
error: {
|
|
140
|
-
message: string;
|
|
141
|
-
code?: string;
|
|
142
|
-
stack?: string;
|
|
143
|
-
};
|
|
144
|
-
/** How many delivery attempts were made before giving up */
|
|
145
|
-
attempts: number;
|
|
146
|
-
/** First failure timestamp */
|
|
147
|
-
firstFailedAt: Date;
|
|
148
|
-
/** Last failure timestamp (immediately before dead-lettering) */
|
|
149
|
-
lastFailedAt: Date;
|
|
150
|
-
/** Optional handler / subscriber name that last failed (for debug) */
|
|
151
|
-
handlerName?: string;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Minimal logger interface for event transports.
|
|
155
|
-
* Compatible with `console`, `pino`, `fastify.log`, and any custom logger.
|
|
156
|
-
*
|
|
157
|
-
* @example
|
|
158
|
-
* ```typescript
|
|
159
|
-
* // Use Fastify's logger
|
|
160
|
-
* new MemoryEventTransport({ logger: fastify.log });
|
|
161
|
-
*
|
|
162
|
-
* // Use a custom logger
|
|
163
|
-
* new MemoryEventTransport({ logger: { warn: myWarn, error: myError } });
|
|
164
|
-
*
|
|
165
|
-
* // Default: console (no logger option needed)
|
|
166
|
-
* new MemoryEventTransport();
|
|
167
|
-
* ```
|
|
168
|
-
*/
|
|
169
|
-
interface EventLogger {
|
|
170
|
-
warn(message: string, ...args: unknown[]): void;
|
|
171
|
-
error(message: string, ...args: unknown[]): void;
|
|
172
|
-
}
|
|
173
|
-
interface EventTransport {
|
|
174
|
-
/** Transport name for logging */
|
|
175
|
-
readonly name: string;
|
|
176
|
-
/**
|
|
177
|
-
* Publish an event to the transport
|
|
178
|
-
*/
|
|
179
|
-
publish(event: DomainEvent): Promise<void>;
|
|
180
|
-
/**
|
|
181
|
-
* Publish a batch of events to the transport (optional, v2.8.1+).
|
|
182
|
-
*
|
|
183
|
-
* Transports that can efficiently batch (Kafka producer, Redis pipeline,
|
|
184
|
-
* RabbitMQ publisher confirms, SQS send-message-batch) should implement
|
|
185
|
-
* this. {@link import('./outbox.js').EventOutbox.relay} auto-detects and
|
|
186
|
-
* uses it for much higher throughput than per-event publishing.
|
|
187
|
-
*
|
|
188
|
-
* **Contract**: the returned `PublishManyResult` must describe the
|
|
189
|
-
* per-event outcome so the caller can acknowledge successes and fail the
|
|
190
|
-
* rest. Partial success is allowed — the transport reports it per event.
|
|
191
|
-
*
|
|
192
|
-
* If not implemented, `EventOutbox.relay` falls back to calling
|
|
193
|
-
* {@link publish} once per event.
|
|
194
|
-
*
|
|
195
|
-
* @param events - Events to publish (in order)
|
|
196
|
-
* @returns Per-event outcome map keyed by `event.meta.id`
|
|
197
|
-
*/
|
|
198
|
-
publishMany?(events: readonly DomainEvent[]): Promise<PublishManyResult>;
|
|
199
|
-
/**
|
|
200
|
-
* Subscribe to events matching a pattern
|
|
201
|
-
* @param pattern - Event type pattern (e.g., 'product.*', '*')
|
|
202
|
-
* @param handler - Handler function
|
|
203
|
-
* @returns Unsubscribe function
|
|
204
|
-
*/
|
|
205
|
-
subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
|
|
206
|
-
/**
|
|
207
|
-
* Route a permanently-failed event to the transport's dead-letter sink
|
|
208
|
-
* (Kafka DLQ topic, SQS DLQ, Redis Stream `PEL` timeout handler, etc.).
|
|
209
|
-
*
|
|
210
|
-
* Called by {@link import('./outbox.js').EventOutbox} after exhausting
|
|
211
|
-
* retries. Transports that don't have a native DLQ can omit this —
|
|
212
|
-
* callers treat an absent `deadLetter` as "log and drop".
|
|
213
|
-
*/
|
|
214
|
-
deadLetter?(dlq: DeadLetteredEvent): Promise<void>;
|
|
215
|
-
/**
|
|
216
|
-
* Close transport connections
|
|
217
|
-
*/
|
|
218
|
-
close?(): Promise<void>;
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Per-event outcome returned by {@link EventTransport.publishMany}.
|
|
222
|
-
*
|
|
223
|
-
* The key is `event.meta.id`; the value is `null` for success or an `Error`
|
|
224
|
-
* for per-event failure. Transports MUST include an entry for every event
|
|
225
|
-
* in the input batch.
|
|
226
|
-
*/
|
|
227
|
-
type PublishManyResult = ReadonlyMap<string, Error | null>;
|
|
228
|
-
interface MemoryEventTransportOptions {
|
|
229
|
-
/** Logger for error/warning messages (default: console) */
|
|
230
|
-
logger?: EventLogger;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* In-memory event transport (default)
|
|
234
|
-
* Events are delivered synchronously within the process.
|
|
235
|
-
* Not suitable for multi-instance deployments.
|
|
236
|
-
*/
|
|
237
|
-
declare class MemoryEventTransport implements EventTransport {
|
|
238
|
-
readonly name = "memory";
|
|
239
|
-
private handlers;
|
|
240
|
-
private logger;
|
|
241
|
-
constructor(options?: MemoryEventTransportOptions);
|
|
242
|
-
publish(event: DomainEvent): Promise<void>;
|
|
243
|
-
/**
|
|
244
|
-
* Reference `publishMany` implementation — delegates to `publish()` in order.
|
|
245
|
-
*
|
|
246
|
-
* Production transports (Kafka, Redis pipeline, SQS batch) should override
|
|
247
|
-
* this with a single batched network call. Memory transport has nothing to
|
|
248
|
-
* batch, so we just loop — the loop still returns a proper result map so
|
|
249
|
-
* `EventOutbox.relay` can exercise the batched code path in tests.
|
|
250
|
-
*/
|
|
251
|
-
publishMany(events: readonly DomainEvent[]): Promise<PublishManyResult>;
|
|
252
|
-
subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
|
|
253
|
-
close(): Promise<void>;
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Create a domain event with auto-generated metadata.
|
|
257
|
-
*
|
|
258
|
-
* `id` and `timestamp` are filled in; everything else is caller-controlled.
|
|
259
|
-
* Set `schemaVersion` explicitly for any event type you plan to evolve.
|
|
260
|
-
*/
|
|
261
|
-
declare function createEvent<T>(type: string, payload: T, meta?: Partial<EventMeta>): DomainEvent<T>;
|
|
262
|
-
/**
|
|
263
|
-
* Create a child event that chains causation from a parent event.
|
|
264
|
-
*
|
|
265
|
-
* Rules:
|
|
266
|
-
* - `causationId` is set to the parent's `id` (direct cause)
|
|
267
|
-
* - `correlationId` is inherited from the parent if set, else falls back
|
|
268
|
-
* to the parent's `id` (root correlation)
|
|
269
|
-
* - `userId` / `organizationId` are inherited when not overridden so the
|
|
270
|
-
* whole chain stays scoped to the originating principal/tenant
|
|
271
|
-
*
|
|
272
|
-
* Caller-supplied `meta` wins over inherited fields — pass `{ userId: newActor }`
|
|
273
|
-
* to override when a subsystem acts on behalf of a different principal.
|
|
274
|
-
*
|
|
275
|
-
* @example
|
|
276
|
-
* ```typescript
|
|
277
|
-
* const orderPlaced = createEvent('order.placed', { orderId: 'o1' }, {
|
|
278
|
-
* correlationId: req.id, userId: user.id,
|
|
279
|
-
* });
|
|
280
|
-
* await events.publish(orderPlaced);
|
|
281
|
-
*
|
|
282
|
-
* // Downstream handler emits a child event:
|
|
283
|
-
* const reserved = createChildEvent(orderPlaced, 'inventory.reserved', {
|
|
284
|
-
* orderId: 'o1', skus: ['sku-1', 'sku-2'],
|
|
285
|
-
* });
|
|
286
|
-
* // reserved.meta.causationId === orderPlaced.meta.id
|
|
287
|
-
* // reserved.meta.correlationId === orderPlaced.meta.correlationId
|
|
288
|
-
* // reserved.meta.userId === user.id (inherited)
|
|
289
|
-
* ```
|
|
290
|
-
*/
|
|
291
|
-
declare function createChildEvent<T>(parent: DomainEvent, type: string, payload: T, meta?: Partial<EventMeta>): DomainEvent<T>;
|
|
292
|
-
//#endregion
|
|
293
|
-
export { EventMeta as a, MemoryEventTransportOptions as c, createEvent as d, EventLogger as i, PublishManyResult as l, DomainEvent as n, EventTransport as o, EventHandler as r, MemoryEventTransport as s, DeadLetteredEvent as t, createChildEvent as u };
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { An as FieldMetadata, Dn as AdapterRepositoryInput, En as AdapterFactory, Fn as asRepositoryLike, Mn as RepositoryLike, Nn as SchemaMetadata, Pn as ValidationResult, jn as RelationMetadata, kn as DataAdapter } from "../index-CXXRbnf8.mjs";
|
|
2
|
-
import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, d as DrizzleAdapterOptions, f as createDrizzleAdapter, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter, u as DrizzleAdapter } from "../index-Rg8axYPz.mjs";
|
|
3
|
-
export { AdapterFactory, AdapterRepositoryInput, DataAdapter, DrizzleAdapter, DrizzleAdapterOptions, FieldMetadata, MongooseAdapter, MongooseAdapterOptions, PrismaAdapter, PrismaAdapterOptions, PrismaQueryOptions, PrismaQueryParser, PrismaQueryParserOptions, RelationMetadata, RepositoryLike, SchemaMetadata, ValidationResult, asRepositoryLike, createDrizzleAdapter, createMongooseAdapter, createPrismaAdapter };
|
package/dist/adapters/index.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { a as createMongooseAdapter, c as createDrizzleAdapter, i as MongooseAdapter, n as PrismaQueryParser, o as asRepositoryLike, r as createPrismaAdapter, s as DrizzleAdapter, t as PrismaAdapter } from "../adapters-DUUiiimH.mjs";
|
|
2
|
-
export { DrizzleAdapter, MongooseAdapter, PrismaAdapter, PrismaQueryParser, asRepositoryLike, createDrizzleAdapter, createMongooseAdapter, createPrismaAdapter };
|