@classytic/arc 2.4.3 → 2.6.2

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.
Files changed (82) hide show
  1. package/README.md +57 -6
  2. package/dist/{BaseController-CkM5dUh_.mjs → BaseController-AbbRx3e0.mjs} +5 -2
  3. package/dist/{ResourceRegistry-DeCIFlix.mjs → ResourceRegistry-C6ngvOnn.mjs} +1 -0
  4. package/dist/adapters/index.d.mts +2 -2
  5. package/dist/adapters/index.mjs +1 -1
  6. package/dist/{adapters-DTC4Ug66.mjs → adapters-CTn28N4y.mjs} +72 -11
  7. package/dist/audit/index.d.mts +32 -6
  8. package/dist/audit/index.mjs +32 -4
  9. package/dist/audit/mongodb.d.mts +1 -1
  10. package/dist/auth/index.d.mts +1 -1
  11. package/dist/auth/index.mjs +2 -2
  12. package/dist/cli/commands/docs.mjs +1 -1
  13. package/dist/cli/commands/init.mjs +12 -9
  14. package/dist/cli/commands/introspect.mjs +1 -1
  15. package/dist/core/index.d.mts +2 -2
  16. package/dist/core/index.mjs +2 -2
  17. package/dist/{createApp-CBgVaFyh.mjs → createApp-D2w0LdYJ.mjs} +431 -290
  18. package/dist/{defineResource-B22gcNvn.mjs → defineResource-Ckxg6HrZ.mjs} +125 -22
  19. package/dist/discovery/index.mjs +1 -1
  20. package/dist/docs/index.d.mts +1 -1
  21. package/dist/dynamic/index.d.mts +1 -1
  22. package/dist/dynamic/index.mjs +2 -2
  23. package/dist/{elevation-Ca_yveIO.d.mts → elevation-C_taLQrM.d.mts} +27 -1
  24. package/dist/{errorHandler-DMbGdzBG.mjs → errorHandler-r2595m8T.mjs} +1 -1
  25. package/dist/{errors-CPpvPHT0.d.mts → errors-CcVbl1-T.d.mts} +17 -1
  26. package/dist/{errors-rxhfP7Hf.mjs → errors-NoQKsbAT.mjs} +23 -1
  27. package/dist/{eventPlugin-iGrSEmwJ.d.mts → eventPlugin-DW45v4V5.d.mts} +30 -2
  28. package/dist/events/index.d.mts +2 -2
  29. package/dist/events/index.mjs +40 -10
  30. package/dist/factory/index.d.mts +44 -23
  31. package/dist/factory/index.mjs +152 -2
  32. package/dist/hooks/index.d.mts +1 -1
  33. package/dist/idempotency/index.d.mts +3 -3
  34. package/dist/idempotency/mongodb.d.mts +1 -1
  35. package/dist/idempotency/redis.d.mts +1 -1
  36. package/dist/{index-BL8CaQih.d.mts → index-B4uZm82R.d.mts} +2 -2
  37. package/dist/{index-yhxyjqNb.d.mts → index-DrCqa3Jq.d.mts} +4 -8
  38. package/dist/{index-Diqcm14c.d.mts → index-NGZksqM5.d.mts} +30 -1
  39. package/dist/index.d.mts +6 -6
  40. package/dist/index.mjs +8 -7
  41. package/dist/integrations/event-gateway.mjs +1 -1
  42. package/dist/integrations/index.d.mts +1 -1
  43. package/dist/integrations/mcp/index.d.mts +4 -2
  44. package/dist/integrations/mcp/index.mjs +1 -1
  45. package/dist/integrations/mcp/testing.d.mts +1 -1
  46. package/dist/integrations/mcp/testing.mjs +1 -1
  47. package/dist/{interface-DGmPxakH.d.mts → interface-CrN45qz1.d.mts} +229 -13
  48. package/dist/{mongodb-CUpYfxfD.d.mts → mongodb-kltrBPa1.d.mts} +10 -0
  49. package/dist/{mongodb-bga9AbkD.d.mts → mongodb-pMvOlR5_.d.mts} +1 -1
  50. package/dist/org/index.d.mts +1 -1
  51. package/dist/org/index.mjs +1 -1
  52. package/dist/permissions/index.d.mts +2 -2
  53. package/dist/permissions/index.mjs +2 -2
  54. package/dist/{permissions-Jk5x3sxz.mjs → permissions-C8ImI8gC.mjs} +44 -2
  55. package/dist/plugins/index.d.mts +1 -1
  56. package/dist/plugins/index.mjs +4 -4
  57. package/dist/plugins/tracing-entry.mjs +1 -1
  58. package/dist/presets/index.d.mts +1 -1
  59. package/dist/presets/index.mjs +1 -1
  60. package/dist/presets/multiTenant.d.mts +1 -1
  61. package/dist/presets/multiTenant.mjs +1 -1
  62. package/dist/{presets-OMPaHMTY.mjs → presets-BMfdy34e.mjs} +2 -2
  63. package/dist/{redis-CQ5YxMC5.d.mts → redis-D0Qc-9EW.d.mts} +1 -1
  64. package/dist/registry/index.d.mts +1 -1
  65. package/dist/registry/index.mjs +1 -1
  66. package/dist/{resourceToTools-PMFE8HIv.mjs → resourceToTools-DH3c3e-T.mjs} +81 -7
  67. package/dist/scope/index.d.mts +2 -2
  68. package/dist/scope/index.mjs +2 -2
  69. package/dist/{sse-BkViJPlT.mjs → sse-BF7GR7IB.mjs} +1 -1
  70. package/dist/testing/index.d.mts +26 -3
  71. package/dist/testing/index.mjs +46 -2
  72. package/dist/types/index.d.mts +3 -3
  73. package/dist/types/index.mjs +23 -2
  74. package/dist/{types-C6TQjtdi.mjs → types-BhtYdxZU.mjs} +26 -1
  75. package/dist/{types-Dt0-AI6E.d.mts → types-C1Z28coa.d.mts} +195 -6
  76. package/dist/{types-BJmgxNbF.d.mts → types-DurlBP2N.d.mts} +1 -1
  77. package/dist/utils/index.d.mts +2 -2
  78. package/dist/utils/index.mjs +1 -1
  79. package/package.json +6 -5
  80. package/skills/arc/SKILL.md +151 -4
  81. package/skills/arc/references/mcp.md +160 -2
  82. /package/dist/{interface-B4awm1RJ.d.mts → interface-gr-7qo9j.d.mts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.4.3",
3
+ "version": "2.6.2",
4
4
  "description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -220,7 +220,7 @@
220
220
  "node": ">=22"
221
221
  },
222
222
  "peerDependencies": {
223
- "@classytic/mongokit": ">=3.4.5",
223
+ "@classytic/mongokit": ">=3.5.0",
224
224
  "@classytic/streamline": ">=2.0.0",
225
225
  "@fastify/cors": "^11.0.0",
226
226
  "@fastify/helmet": "^13.0.0",
@@ -244,7 +244,7 @@
244
244
  "fastify-raw-body": "^5.0.0",
245
245
  "ioredis": "^5.0.0",
246
246
  "mongodb": "^6.0.0 || ^7.0.0",
247
- "mongoose": "^8.0.0 || ^9.0.0",
247
+ "mongoose": ">=9.0.0",
248
248
  "pino-pretty": "^13.0.0",
249
249
  "zod": "^4.0.0"
250
250
  },
@@ -333,11 +333,12 @@
333
333
  },
334
334
  "dependencies": {
335
335
  "fastify-plugin": "^5.0.1",
336
- "qs": "^6.14.1"
336
+ "qs": "^6.14.1",
337
+ "secure-json-parse": "^4.1.0"
337
338
  },
338
339
  "devDependencies": {
339
340
  "@biomejs/biome": "^2.4.10",
340
- "@classytic/mongokit": "^3.4.5",
341
+ "@classytic/mongokit": "^3.5.2",
341
342
  "@fastify/jwt": "^10.0.0",
342
343
  "@fastify/multipart": "^9.0.0",
343
344
  "@fastify/type-provider-typebox": "^6.0.0",
@@ -8,11 +8,11 @@ description: |
8
8
  Triggers: arc, fastify resource, defineResource, createApp, BaseController, arc preset,
9
9
  arc auth, arc events, arc jobs, arc websocket, arc mcp, arc plugin, arc testing, arc cli,
10
10
  arc permissions, arc hooks, arc pipeline, arc factory, arc cache, arc QueryCache.
11
- version: 2.4.2
11
+ version: 2.6.2
12
12
  license: MIT
13
13
  metadata:
14
14
  author: Classytic
15
- version: "2.4.1"
15
+ version: "2.6.2"
16
16
  tags:
17
17
  - fastify
18
18
  - rest-api
@@ -196,6 +196,30 @@ presets: ['softDelete', { name: 'multiTenant', tenantField: 'organizationId' }]
196
196
  // Bulk: presets: ['bulk'] or bulkPreset({ operations: ['createMany', 'updateMany'] })
197
197
  ```
198
198
 
199
+ ### tenantField — When to Use and When to Disable
200
+
201
+ Arc defaults `tenantField` to `'organizationId'` on BaseController. This silently adds `{ organizationId: scope.organizationId }` to every query when the user has an org context. Correct for per-org resources, wrong for company-wide resources.
202
+
203
+ ```typescript
204
+ // Per-org resource (default) — each org sees only its own data
205
+ defineResource({ name: 'invoice', ... });
206
+ // → queries auto-scoped: { organizationId: 'org-123' }
207
+
208
+ // Company-wide resource — ALL orgs share the same data
209
+ defineResource({ name: 'account-type', tenantField: false, ... });
210
+ // → no org filter applied, all users see all records
211
+
212
+ // Custom tenant field — your schema uses a different name
213
+ defineResource({ name: 'workspace-item', tenantField: 'workspaceId', ... });
214
+ // → queries scoped by workspaceId instead of organizationId
215
+ ```
216
+
217
+ When to use `tenantField: false`:
218
+ - Lookup tables (account types, categories, currencies)
219
+ - Platform-wide settings or config
220
+ - Cross-org reports or analytics
221
+ - Single-tenant apps where org scoping isn't needed
222
+
199
223
  ## QueryCache
200
224
 
201
225
  TanStack Query-inspired server cache with stale-while-revalidate and auto-invalidation on mutations.
@@ -314,6 +338,26 @@ const app = await createApp({
314
338
 
315
339
  ## Hooks
316
340
 
341
+ **Inline on resource (recommended):**
342
+
343
+ ```typescript
344
+ defineResource({
345
+ name: 'chat',
346
+ hooks: {
347
+ beforeCreate: async (ctx) => { ctx.data.slug = slugify(ctx.data.name); },
348
+ afterCreate: async (ctx) => { analytics.track('created', { id: ctx.data._id, user: ctx.user?.id }); },
349
+ beforeUpdate: async (ctx) => { console.log('Updating', ctx.meta?.id, 'existing:', ctx.meta?.existing); },
350
+ afterUpdate: async (ctx) => { await invalidateCache(ctx.data._id); },
351
+ beforeDelete: async (ctx) => { if (ctx.data.isProtected) throw new Error('Cannot delete'); },
352
+ afterDelete: async (ctx) => { await cleanupFiles(ctx.meta?.id); },
353
+ },
354
+ });
355
+ ```
356
+
357
+ `ResourceHookContext`: `{ data, user?, meta? }` — `data` is the document, `meta` has `id` and `existing` (for update/delete).
358
+
359
+ **App-level (cross-resource):**
360
+
317
361
  ```typescript
318
362
  import { createHookSystem, beforeCreate, afterUpdate } from '@classytic/arc/hooks';
319
363
 
@@ -386,8 +430,10 @@ defineResource({
386
430
  ## Error Classes
387
431
 
388
432
  ```typescript
389
- import { ArcError, NotFoundError, ValidationError, UnauthorizedError, ForbiddenError } from '@classytic/arc';
390
- throw new NotFoundError('Product not found'); // 404
433
+ import { ArcError, NotFoundError, ValidationError, createDomainError } from '@classytic/arc';
434
+ throw new NotFoundError('Product not found'); // 404
435
+ throw createDomainError('MEMBER_NOT_FOUND', 'Not found', 404); // domain error with code
436
+ throw createDomainError('SELF_REFERRAL', 'Cannot self-refer', 422, { field: 'referralCode' });
391
437
  ```
392
438
 
393
439
  Error handler catches: `ArcError` → `.statusCode` (Fastify) → `.status` (MongoKit, http-errors) → `errorMap` → Mongoose/MongoDB → fallback 500. DB-agnostic — any error with `.status` or `.statusCode` gets the correct HTTP response.
@@ -481,6 +527,107 @@ src/resources/order/
481
527
 
482
528
  Generate: `arc generate resource order --mcp` | Wire: `extraTools: [fulfillOrderTool]`
483
529
 
530
+ **Auto-load resources** — no barrel files, no manual `toPlugin()`:
531
+
532
+ ```typescript
533
+ import { createApp, loadResources } from '@classytic/arc/factory';
534
+
535
+ const app = await createApp({
536
+ resourcePrefix: '/api/v1', // optional URL prefix
537
+ resources: await loadResources(import.meta.url), // discovers *.resource.ts
538
+ auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET } },
539
+ });
540
+ ```
541
+
542
+ `loadResources()` discovers files matching `*.resource.{ts,js,mts,mjs}`, recursively. Pass `import.meta.url` for dev/prod parity (resolves to `src/` in dev, `dist/` in prod automatically). Discovers `default` export, `export const resource`, OR any named export with `toPlugin()` (e.g., `export const userResource`).
543
+
544
+ Options: `exclude`, `include`, `suffix`, `recursive`, `silent`.
545
+
546
+ **Per-resource opt-out of `resourcePrefix`** — for webhooks, admin routes:
547
+ ```typescript
548
+ defineResource({ name: 'webhook', prefix: '/hooks', skipGlobalPrefix: true })
549
+ // Registers at /hooks even with createApp({ resourcePrefix: '/api/v1' })
550
+ ```
551
+
552
+ **Boot sequence:**
553
+ ```typescript
554
+ const app = await createApp({
555
+ resourcePrefix: '/api/v1',
556
+ plugins: async (f) => { await connectDB(); }, // 1. infra (DB, docs)
557
+ bootstrap: [inventoryInit, accountingInit], // 2. domain init (engines)
558
+ resources: await loadResources(import.meta.url), // 3. routes
559
+ afterResources: async (f) => { subscribeEvents(f); }, // 4. post-wiring
560
+ onReady: async (f) => { logger.info('ready'); }, // 5. lifecycle
561
+ });
562
+ ```
563
+
564
+ **Audit per-resource opt-in** — no growing exclude lists:
565
+ ```typescript
566
+ // Register audit plugin with perResource mode
567
+ await fastify.register(auditPlugin, { autoAudit: { perResource: true } });
568
+
569
+ // Opt-in at the resource level
570
+ defineResource({ name: 'order', audit: true });
571
+ defineResource({ name: 'payment', audit: { operations: ['delete'] } });
572
+ defineResource({ name: 'product' }); // not audited
573
+
574
+ // Manual custom() for MCP/additionalRoutes/read auditing
575
+ app.post('/orders/:id/refund', async (req) => {
576
+ await app.audit.custom('order', req.params.id, 'refund', { reason }, { user });
577
+ });
578
+ ```
579
+
580
+ **Import compatibility:** `loadResources()` uses runtime `import()`. Works with relative imports (`./foo.js`) and Node.js `#` subpath imports (`#shared/utils.js` via `package.json` `imports`). Does **NOT** work with tsconfig path aliases (`@/*`, `~/`) — those are compile-time only.
581
+
582
+ **Vitest workaround** (rare): if resources need engine bootstrap or transitive `node_modules` imports that don't compose with dynamic import:
583
+ ```typescript
584
+ import { preloadResources } from '@classytic/arc/testing';
585
+
586
+ export const preloadedResources = preloadResources(
587
+ import.meta.glob('../../src/resources/**/*.resource.ts', { eager: true, import: 'default' }),
588
+ );
589
+ ```
590
+
591
+ **Unified role check** — checks both platform AND org roles:
592
+
593
+ ```typescript
594
+ import { roles } from '@classytic/arc/permissions';
595
+ permissions: {
596
+ create: roles('admin', 'editor'), // works with BA org roles + platform roles
597
+ delete: roles('admin'),
598
+ }
599
+ // Also: requireRoles(['admin'], { includeOrgRoles: true }) for backward compat
600
+ ```
601
+
602
+ **DX helpers:**
603
+
604
+ ```typescript
605
+ // Typed request for wrapHandler: false routes — no more (req as any).user
606
+ import type { ArcRequest } from '@classytic/arc';
607
+ handler: async (req: ArcRequest, reply) => { req.user?.id; req.scope; req.signal; }
608
+
609
+ // Response envelope — no manual { success, data } wrapping
610
+ import { envelope } from '@classytic/arc';
611
+ reply.send(envelope(data, { total: 100 }));
612
+
613
+ // Canonical org extraction — replaces 19 duplicated patterns
614
+ import { getOrgContext } from '@classytic/arc/scope';
615
+ const { userId, organizationId, roles, orgRoles } = getOrgContext(request);
616
+
617
+ // Domain errors with auto HTTP status mapping
618
+ import { createDomainError } from '@classytic/arc';
619
+ throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
620
+
621
+ // Resource lifecycle hook — wire singletons during registration
622
+ defineResource({ name: 'notification', onRegister: (f) => setSseManager(f.sseManager) });
623
+
624
+ // SSE auth — preAuth runs BEFORE auth middleware (EventSource can't set headers)
625
+ additionalRoutes: [{ preAuth: [(req) => { req.headers.authorization = `Bearer ${req.query.token}`; }] }]
626
+
627
+ // SSE streaming — auto headers + bypasses response wrapper
628
+ additionalRoutes: [{ streamResponse: true, handler: async (req, reply) => reply.send(stream) }]
629
+ ```
630
+
484
631
  ## Subpath Imports
485
632
 
486
633
  ```typescript
@@ -12,11 +12,34 @@ npm install @modelcontextprotocol/sdk zod
12
12
 
13
13
  ```typescript
14
14
  import { mcpPlugin } from '@classytic/arc/mcp';
15
+ import { createApp, loadResources } from '@classytic/arc/factory';
15
16
 
16
- await app.register(mcpPlugin, {
17
+ // Option A: Explicit resources
18
+ const app = await createApp({
17
19
  resources: [productResource, taskResource],
18
20
  auth: false,
19
- exclude: ['credential'],
21
+ plugins: async (f) => {
22
+ await f.register(mcpPlugin, { resources: [productResource, taskResource], auth: false });
23
+ },
24
+ });
25
+
26
+ // Option B: Auto-discover from directory
27
+ const resources = await loadResources('./src/resources');
28
+ const app = await createApp({
29
+ resources,
30
+ plugins: async (f) => {
31
+ await f.register(mcpPlugin, { resources, auth: false });
32
+ },
33
+ });
34
+ ```
35
+
36
+ Per-resource overrides:
37
+
38
+ ```typescript
39
+ await app.register(mcpPlugin, {
40
+ resources,
41
+ auth: false,
42
+ include: ['product', 'order'], // only these get MCP tools
20
43
  overrides: { product: { operations: ['list', 'get'] } },
21
44
  });
22
45
  ```
@@ -429,3 +452,138 @@ await app.register(mcpPlugin, {
429
452
  - `DELETE /mcp` — terminates session
430
453
 
431
454
  Sessions: lazily created, TTL-cached, LRU-evicted at max capacity, auto-cleaned on shutdown.
455
+
456
+ ## Health Endpoint
457
+
458
+ `GET /mcp/health` — no MCP protocol needed, plain JSON:
459
+
460
+ ```json
461
+ {
462
+ "status": "ok",
463
+ "mode": "stateless",
464
+ "tools": 11,
465
+ "resources": 2,
466
+ "toolNames": ["list_products", "get_product", ...],
467
+ "sessions": null
468
+ }
469
+ ```
470
+
471
+ Use to verify the MCP server is alive before configuring Claude CLI.
472
+
473
+ ## DX Helpers (v2.4.4)
474
+
475
+ ### ArcRequest — Typed Fastify Request
476
+
477
+ For `wrapHandler: false` routes, use `ArcRequest` instead of `(req as any).user`:
478
+
479
+ ```typescript
480
+ import type { ArcRequest } from '@classytic/arc';
481
+
482
+ handler: async (req: ArcRequest, reply) => {
483
+ req.user?.id; // typed
484
+ req.scope.organizationId; // typed (when member)
485
+ req.signal; // AbortSignal (Fastify 5 built-in)
486
+ }
487
+ ```
488
+
489
+ ### envelope() — Response Helper
490
+
491
+ ```typescript
492
+ import { envelope } from '@classytic/arc';
493
+
494
+ handler: async (req, reply) => {
495
+ const data = await service.getResults();
496
+ return reply.send(envelope(data));
497
+ // → { success: true, data }
498
+ return reply.send(envelope(data, { total: 100, page: 1 }));
499
+ // → { success: true, data, total: 100, page: 1 }
500
+ }
501
+ ```
502
+
503
+ ### getOrgContext() — Canonical Org Extraction
504
+
505
+ Eliminates duplicated `req.user.organizationId || req.headers['x-organization-id']` patterns:
506
+
507
+ ```typescript
508
+ import { getOrgContext } from '@classytic/arc/scope';
509
+
510
+ handler: async (req, reply) => {
511
+ const { userId, organizationId, roles, orgRoles } = getOrgContext(req);
512
+ // Works regardless of auth type (JWT, Better Auth, custom)
513
+ }
514
+ ```
515
+
516
+ ### createDomainError() — Error Factory
517
+
518
+ Eliminates manual `if (err.code) return status` mapping:
519
+
520
+ ```typescript
521
+ import { createDomainError } from '@classytic/arc';
522
+
523
+ throw createDomainError('MEMBER_NOT_FOUND', 'Member does not exist', 404);
524
+ throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
525
+ throw createDomainError('INSUFFICIENT_BALANCE', 'Not enough credits', 402, { balance: 0 });
526
+ // Arc's error handler auto-maps statusCode to HTTP response
527
+ ```
528
+
529
+ ### onRegister — Resource Lifecycle Hook
530
+
531
+ Called during plugin registration with the scoped Fastify instance:
532
+
533
+ ```typescript
534
+ defineResource({
535
+ name: 'notification',
536
+ onRegister: (fastify) => {
537
+ setSseManager(fastify.sseManager);
538
+ },
539
+ })
540
+ ```
541
+
542
+ ### preAuth — Pre-Auth Handlers for SSE/WebSocket
543
+
544
+ Run before auth middleware. Use for promoting `?token=` to `Authorization` header (EventSource can't set headers):
545
+
546
+ ```typescript
547
+ additionalRoutes: [{
548
+ method: 'GET',
549
+ path: '/stream',
550
+ wrapHandler: false,
551
+ permissions: requireAuth(),
552
+ preAuth: [(req) => {
553
+ const token = req.query?.token;
554
+ if (token) req.headers.authorization = `Bearer ${token}`;
555
+ }],
556
+ handler: sseHandler,
557
+ }]
558
+ ```
559
+
560
+ ### streamResponse — SSE Route Flag
561
+
562
+ Auto-sets SSE headers and bypasses Arc's response wrapper:
563
+
564
+ ```typescript
565
+ additionalRoutes: [{
566
+ method: 'POST',
567
+ path: '/stream',
568
+ streamResponse: true, // SSE headers + no { success, data } wrapper
569
+ permissions: requireAuth(),
570
+ handler: async (request, reply) => {
571
+ const { stream } = await generateStream({ abortSignal: request.signal });
572
+ return reply.send(stream);
573
+ },
574
+ }]
575
+ ```
576
+
577
+ ## Test Coverage
578
+
579
+ 165 test files, 2439 tests. MCP-specific:
580
+
581
+ | Test File | Tests | Covers |
582
+ |-----------|-------|--------|
583
+ | `mcp-auth-e2e.test.ts` | 16 | All auth modes, multi-tenancy, permission filters, async permissions |
584
+ | `mcp-dx-features.test.ts` | 14 | include, names, prefix, disableDefaultRoutes, mcpHandler, guards, CRUD lifecycle |
585
+ | `resourceToTools.test.ts` | 12 | Tool generation, annotations, field hiding, soft delete |
586
+ | `createMcpServer.test.ts` | 10 | Server creation, tool registration, InMemoryTransport |
587
+ | `guards.test.ts` | 8 | requireAuth, requireOrg, requireRole, customGuard, composition |
588
+ | `dx-features.test.ts` | 17 | envelope, getOrgContext, createDomainError, onRegister, preAuth, streamResponse |
589
+ | Others | 32 | fieldRulesToZod, defineTool, definePrompt, buildRequestContext, sessionCache, authCache |