@classytic/arc 2.4.3 → 2.5.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/dist/{BaseController-CkM5dUh_.mjs → BaseController-CNwMYpDW.mjs} +1 -1
- package/dist/adapters/index.d.mts +2 -2
- package/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.mjs +2 -2
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/{createApp-CBgVaFyh.mjs → createApp-oic3-ieX.mjs} +3 -3
- package/dist/{defineResource-B22gcNvn.mjs → defineResource-BYm3CIoe.mjs} +85 -10
- package/dist/docs/index.d.mts +1 -1
- package/dist/dynamic/index.d.mts +1 -1
- package/dist/dynamic/index.mjs +2 -2
- package/dist/{elevation-Ca_yveIO.d.mts → elevation-C_taLQrM.d.mts} +27 -1
- package/dist/{errorHandler-DMbGdzBG.mjs → errorHandler-r2595m8T.mjs} +1 -1
- package/dist/{errors-CPpvPHT0.d.mts → errors-CcVbl1-T.d.mts} +17 -1
- package/dist/{errors-rxhfP7Hf.mjs → errors-NoQKsbAT.mjs} +23 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/{index-BL8CaQih.d.mts → index-TG7-pXDC.d.mts} +2 -2
- package/dist/{index-yhxyjqNb.d.mts → index-bX8T5bmn.d.mts} +4 -8
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +7 -6
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +4 -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/{interface-DGmPxakH.d.mts → interface-BnNjdl33.d.mts} +170 -8
- package/dist/org/index.d.mts +1 -1
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.mjs +1 -1
- package/dist/{permissions-Jk5x3sxz.mjs → permissions-D9_AAtvz.mjs} +1 -1
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/index.mjs +3 -3
- package/dist/plugins/tracing-entry.mjs +1 -1
- 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 +1 -1
- package/dist/{presets-OMPaHMTY.mjs → presets-CD3e6M7c.mjs} +2 -2
- package/dist/registry/index.d.mts +1 -1
- package/dist/{resourceToTools-PMFE8HIv.mjs → resourceToTools-B1B1svLx.mjs} +81 -7
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-BkViJPlT.mjs → sse-BF7GR7IB.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +1 -1
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +23 -2
- package/dist/{types-C6TQjtdi.mjs → types-BhtYdxZU.mjs} +26 -1
- package/dist/{types-BJmgxNbF.d.mts → types-ByCPlfZ1.d.mts} +1 -1
- package/dist/{types-Dt0-AI6E.d.mts → types-Guk83PDz.d.mts} +2 -2
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +1 -1
- package/package.json +4 -4
- package/skills/arc/SKILL.md +53 -2
- package/skills/arc/references/mcp.md +135 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as ElevationOptions } from "./elevation-
|
|
2
|
-
import {
|
|
1
|
+
import { n as ElevationOptions } from "./elevation-C_taLQrM.mjs";
|
|
2
|
+
import { g as Authenticator } from "./interface-BnNjdl33.mjs";
|
|
3
3
|
import { t as ExternalOpenApiPaths } from "./externalPaths-DpO-s7r8.mjs";
|
|
4
4
|
import { i as CacheStore } from "./interface-D_BWALyZ.mjs";
|
|
5
5
|
import { r as QueryCachePluginOptions } from "./queryCachePlugin-DcmETvcB.mjs";
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as NotFoundError, c as RateLimitError, d as ValidationError,
|
|
1
|
+
import { K as ParsedQuery, W as OpenApiSchemas, Z as QueryParserInterface, l as AnyRecord } from "../interface-BnNjdl33.mjs";
|
|
2
|
+
import { a as NotFoundError, c as RateLimitError, d as ValidationError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorDetails, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-CcVbl1-T.mjs";
|
|
3
3
|
import { a as CircuitBreakerStats, c as createCircuitBreakerRegistry, i as CircuitBreakerRegistry, n as CircuitBreakerError, o as CircuitState, r as CircuitBreakerOptions, s as createCircuitBreaker, t as CircuitBreaker } from "../circuitBreaker-JP2GdJ4b.mjs";
|
|
4
4
|
import { FastifyInstance } from "fastify";
|
|
5
5
|
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as createQueryParser, t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
|
|
2
2
|
import { a as createCircuitBreaker, i as CircuitState, n as CircuitBreakerError, o as createCircuitBreakerRegistry, r as CircuitBreakerRegistry, t as CircuitBreaker } from "../circuitBreaker-BOBOpN2w.mjs";
|
|
3
3
|
import { _ as defineCompensation, a as getListQueryParams, c as listResponse, d as paginateWrapper, f as paginationSchema, g as wrapResponse, h as successResponseSchema, i as getDefaultCrudSchemas, l as messageWrapper, m as responses, n as deleteResponse, o as itemResponse, p as queryParams, r as errorResponseSchema, s as itemWrapper, t as createStateMachine, u as mutationResponse, v as withCompensation } from "../utils-Dc0WhlIl.mjs";
|
|
4
|
-
import { a as OrgAccessDeniedError, c as ServiceUnavailableError,
|
|
4
|
+
import { a as OrgAccessDeniedError, c as ServiceUnavailableError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-NoQKsbAT.mjs";
|
|
5
5
|
import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-DjzHpFam.mjs";
|
|
6
6
|
import { t as hasEvents } from "../typeGuards-Cj5Rgvlg.mjs";
|
|
7
7
|
export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createError, createQueryParser, createStateMachine, defineCompensation, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, itemWrapper, listResponse, messageWrapper, mutationResponse, paginateWrapper, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
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.
|
|
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": "
|
|
247
|
+
"mongoose": ">=9.0.0",
|
|
248
248
|
"pino-pretty": "^13.0.0",
|
|
249
249
|
"zod": "^4.0.0"
|
|
250
250
|
},
|
|
@@ -337,7 +337,7 @@
|
|
|
337
337
|
},
|
|
338
338
|
"devDependencies": {
|
|
339
339
|
"@biomejs/biome": "^2.4.10",
|
|
340
|
-
"@classytic/mongokit": "^3.
|
|
340
|
+
"@classytic/mongokit": "^3.5.2",
|
|
341
341
|
"@fastify/jwt": "^10.0.0",
|
|
342
342
|
"@fastify/multipart": "^9.0.0",
|
|
343
343
|
"@fastify/type-provider-typebox": "^6.0.0",
|
package/skills/arc/SKILL.md
CHANGED
|
@@ -314,6 +314,26 @@ const app = await createApp({
|
|
|
314
314
|
|
|
315
315
|
## Hooks
|
|
316
316
|
|
|
317
|
+
**Inline on resource (recommended):**
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
defineResource({
|
|
321
|
+
name: 'chat',
|
|
322
|
+
hooks: {
|
|
323
|
+
beforeCreate: async (ctx) => { ctx.data.slug = slugify(ctx.data.name); },
|
|
324
|
+
afterCreate: async (ctx) => { analytics.track('created', { id: ctx.data._id, user: ctx.user?.id }); },
|
|
325
|
+
beforeUpdate: async (ctx) => { console.log('Updating', ctx.meta?.id, 'existing:', ctx.meta?.existing); },
|
|
326
|
+
afterUpdate: async (ctx) => { await invalidateCache(ctx.data._id); },
|
|
327
|
+
beforeDelete: async (ctx) => { if (ctx.data.isProtected) throw new Error('Cannot delete'); },
|
|
328
|
+
afterDelete: async (ctx) => { await cleanupFiles(ctx.meta?.id); },
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
`ResourceHookContext`: `{ data, user?, meta? }` — `data` is the document, `meta` has `id` and `existing` (for update/delete).
|
|
334
|
+
|
|
335
|
+
**App-level (cross-resource):**
|
|
336
|
+
|
|
317
337
|
```typescript
|
|
318
338
|
import { createHookSystem, beforeCreate, afterUpdate } from '@classytic/arc/hooks';
|
|
319
339
|
|
|
@@ -386,8 +406,10 @@ defineResource({
|
|
|
386
406
|
## Error Classes
|
|
387
407
|
|
|
388
408
|
```typescript
|
|
389
|
-
import { ArcError, NotFoundError, ValidationError,
|
|
390
|
-
throw new NotFoundError('Product not found');
|
|
409
|
+
import { ArcError, NotFoundError, ValidationError, createDomainError } from '@classytic/arc';
|
|
410
|
+
throw new NotFoundError('Product not found'); // 404
|
|
411
|
+
throw createDomainError('MEMBER_NOT_FOUND', 'Not found', 404); // domain error with code
|
|
412
|
+
throw createDomainError('SELF_REFERRAL', 'Cannot self-refer', 422, { field: 'referralCode' });
|
|
391
413
|
```
|
|
392
414
|
|
|
393
415
|
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 +503,35 @@ src/resources/order/
|
|
|
481
503
|
|
|
482
504
|
Generate: `arc generate resource order --mcp` | Wire: `extraTools: [fulfillOrderTool]`
|
|
483
505
|
|
|
506
|
+
**DX helpers** (v2.4.4):
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
// Typed request for wrapHandler: false routes — no more (req as any).user
|
|
510
|
+
import type { ArcRequest } from '@classytic/arc';
|
|
511
|
+
handler: async (req: ArcRequest, reply) => { req.user?.id; req.scope; req.signal; }
|
|
512
|
+
|
|
513
|
+
// Response envelope — no manual { success, data } wrapping
|
|
514
|
+
import { envelope } from '@classytic/arc';
|
|
515
|
+
reply.send(envelope(data, { total: 100 }));
|
|
516
|
+
|
|
517
|
+
// Canonical org extraction — replaces 19 duplicated patterns
|
|
518
|
+
import { getOrgContext } from '@classytic/arc/scope';
|
|
519
|
+
const { userId, organizationId, roles, orgRoles } = getOrgContext(request);
|
|
520
|
+
|
|
521
|
+
// Domain errors with auto HTTP status mapping
|
|
522
|
+
import { createDomainError } from '@classytic/arc';
|
|
523
|
+
throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
|
|
524
|
+
|
|
525
|
+
// Resource lifecycle hook — wire singletons during registration
|
|
526
|
+
defineResource({ name: 'notification', onRegister: (f) => setSseManager(f.sseManager) });
|
|
527
|
+
|
|
528
|
+
// SSE auth — preAuth runs BEFORE auth middleware (EventSource can't set headers)
|
|
529
|
+
additionalRoutes: [{ preAuth: [(req) => { req.headers.authorization = `Bearer ${req.query.token}`; }] }]
|
|
530
|
+
|
|
531
|
+
// SSE streaming — auto headers + bypasses response wrapper
|
|
532
|
+
additionalRoutes: [{ streamResponse: true, handler: async (req, reply) => reply.send(stream) }]
|
|
533
|
+
```
|
|
534
|
+
|
|
484
535
|
## Subpath Imports
|
|
485
536
|
|
|
486
537
|
```typescript
|
|
@@ -429,3 +429,138 @@ await app.register(mcpPlugin, {
|
|
|
429
429
|
- `DELETE /mcp` — terminates session
|
|
430
430
|
|
|
431
431
|
Sessions: lazily created, TTL-cached, LRU-evicted at max capacity, auto-cleaned on shutdown.
|
|
432
|
+
|
|
433
|
+
## Health Endpoint
|
|
434
|
+
|
|
435
|
+
`GET /mcp/health` — no MCP protocol needed, plain JSON:
|
|
436
|
+
|
|
437
|
+
```json
|
|
438
|
+
{
|
|
439
|
+
"status": "ok",
|
|
440
|
+
"mode": "stateless",
|
|
441
|
+
"tools": 11,
|
|
442
|
+
"resources": 2,
|
|
443
|
+
"toolNames": ["list_products", "get_product", ...],
|
|
444
|
+
"sessions": null
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Use to verify the MCP server is alive before configuring Claude CLI.
|
|
449
|
+
|
|
450
|
+
## DX Helpers (v2.4.4)
|
|
451
|
+
|
|
452
|
+
### ArcRequest — Typed Fastify Request
|
|
453
|
+
|
|
454
|
+
For `wrapHandler: false` routes, use `ArcRequest` instead of `(req as any).user`:
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import type { ArcRequest } from '@classytic/arc';
|
|
458
|
+
|
|
459
|
+
handler: async (req: ArcRequest, reply) => {
|
|
460
|
+
req.user?.id; // typed
|
|
461
|
+
req.scope.organizationId; // typed (when member)
|
|
462
|
+
req.signal; // AbortSignal (Fastify 5 built-in)
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### envelope() — Response Helper
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
import { envelope } from '@classytic/arc';
|
|
470
|
+
|
|
471
|
+
handler: async (req, reply) => {
|
|
472
|
+
const data = await service.getResults();
|
|
473
|
+
return reply.send(envelope(data));
|
|
474
|
+
// → { success: true, data }
|
|
475
|
+
return reply.send(envelope(data, { total: 100, page: 1 }));
|
|
476
|
+
// → { success: true, data, total: 100, page: 1 }
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### getOrgContext() — Canonical Org Extraction
|
|
481
|
+
|
|
482
|
+
Eliminates duplicated `req.user.organizationId || req.headers['x-organization-id']` patterns:
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
import { getOrgContext } from '@classytic/arc/scope';
|
|
486
|
+
|
|
487
|
+
handler: async (req, reply) => {
|
|
488
|
+
const { userId, organizationId, roles, orgRoles } = getOrgContext(req);
|
|
489
|
+
// Works regardless of auth type (JWT, Better Auth, custom)
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### createDomainError() — Error Factory
|
|
494
|
+
|
|
495
|
+
Eliminates manual `if (err.code) return status` mapping:
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import { createDomainError } from '@classytic/arc';
|
|
499
|
+
|
|
500
|
+
throw createDomainError('MEMBER_NOT_FOUND', 'Member does not exist', 404);
|
|
501
|
+
throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
|
|
502
|
+
throw createDomainError('INSUFFICIENT_BALANCE', 'Not enough credits', 402, { balance: 0 });
|
|
503
|
+
// Arc's error handler auto-maps statusCode to HTTP response
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### onRegister — Resource Lifecycle Hook
|
|
507
|
+
|
|
508
|
+
Called during plugin registration with the scoped Fastify instance:
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
defineResource({
|
|
512
|
+
name: 'notification',
|
|
513
|
+
onRegister: (fastify) => {
|
|
514
|
+
setSseManager(fastify.sseManager);
|
|
515
|
+
},
|
|
516
|
+
})
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### preAuth — Pre-Auth Handlers for SSE/WebSocket
|
|
520
|
+
|
|
521
|
+
Run before auth middleware. Use for promoting `?token=` to `Authorization` header (EventSource can't set headers):
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
additionalRoutes: [{
|
|
525
|
+
method: 'GET',
|
|
526
|
+
path: '/stream',
|
|
527
|
+
wrapHandler: false,
|
|
528
|
+
permissions: requireAuth(),
|
|
529
|
+
preAuth: [(req) => {
|
|
530
|
+
const token = req.query?.token;
|
|
531
|
+
if (token) req.headers.authorization = `Bearer ${token}`;
|
|
532
|
+
}],
|
|
533
|
+
handler: sseHandler,
|
|
534
|
+
}]
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### streamResponse — SSE Route Flag
|
|
538
|
+
|
|
539
|
+
Auto-sets SSE headers and bypasses Arc's response wrapper:
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
additionalRoutes: [{
|
|
543
|
+
method: 'POST',
|
|
544
|
+
path: '/stream',
|
|
545
|
+
streamResponse: true, // SSE headers + no { success, data } wrapper
|
|
546
|
+
permissions: requireAuth(),
|
|
547
|
+
handler: async (request, reply) => {
|
|
548
|
+
const { stream } = await generateStream({ abortSignal: request.signal });
|
|
549
|
+
return reply.send(stream);
|
|
550
|
+
},
|
|
551
|
+
}]
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## Test Coverage
|
|
555
|
+
|
|
556
|
+
165 test files, 2439 tests. MCP-specific:
|
|
557
|
+
|
|
558
|
+
| Test File | Tests | Covers |
|
|
559
|
+
|-----------|-------|--------|
|
|
560
|
+
| `mcp-auth-e2e.test.ts` | 16 | All auth modes, multi-tenancy, permission filters, async permissions |
|
|
561
|
+
| `mcp-dx-features.test.ts` | 14 | include, names, prefix, disableDefaultRoutes, mcpHandler, guards, CRUD lifecycle |
|
|
562
|
+
| `resourceToTools.test.ts` | 12 | Tool generation, annotations, field hiding, soft delete |
|
|
563
|
+
| `createMcpServer.test.ts` | 10 | Server creation, tool registration, InMemoryTransport |
|
|
564
|
+
| `guards.test.ts` | 8 | requireAuth, requireOrg, requireRole, customGuard, composition |
|
|
565
|
+
| `dx-features.test.ts` | 17 | envelope, getOrgContext, createDomainError, onRegister, preAuth, streamResponse |
|
|
566
|
+
| Others | 32 | fieldRulesToZod, defineTool, definePrompt, buildRequestContext, sessionCache, authCache |
|