@classytic/arc 2.11.4 → 2.14.0
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-DECn6zaU.mjs +1399 -0
- package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CBxLLbn3.mjs} +7 -20
- package/dist/createAggregationRouter-CRIBv4sC.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 +24 -11
- 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-noXno2CV.mjs +968 -0
- 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-DLL32us3.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-DrOa-26E.mjs} +41 -36
- package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-lYhC2gE5.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/openapi-D7G1V7ex.mjs +0 -557
- /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
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { t as executePipeline } from "./pipe-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { t as
|
|
1
|
+
import { p as isArcError } from "./errors-j4aJm1Wg.mjs";
|
|
2
|
+
import { t as BaseController } from "./BaseController-DX_T-bDB.mjs";
|
|
3
|
+
import { L as normalizePermissionResult } from "./permissions-ohQyv50e.mjs";
|
|
4
|
+
import { t as executePipeline } from "./pipe-Zr0KXjQe.mjs";
|
|
5
|
+
import { u as resolvePipelineSteps } from "./routerShared-DrOa-26E.mjs";
|
|
6
|
+
import { n as executeAggregation, r as validateAggregations } from "./buildHandler-olo-gt94.mjs";
|
|
7
|
+
import { t as resolveActionPermission } from "./actionPermissions-CyUkQu6O.mjs";
|
|
8
|
+
import { i as shouldRejectAdditionalProperties, r as schemaIRToZodShape, t as normalizeSchemaIR } from "./schemaIR-lYhC2gE5.mjs";
|
|
9
|
+
import { t as pluralize } from "./pluralize-DQgqgifU.mjs";
|
|
10
|
+
import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
|
|
8
11
|
import { z } from "zod";
|
|
9
12
|
//#region src/integrations/mcp/createMcpServer.ts
|
|
10
13
|
/**
|
|
@@ -416,15 +419,11 @@ async function evaluatePermission(check, session, resource, action, input) {
|
|
|
416
419
|
* Convert a controller response envelope into an MCP `CallToolResult`.
|
|
417
420
|
* Carries `meta` into the serialized payload so consumers see pagination
|
|
418
421
|
* totals, stripped-field arrays, etc.
|
|
422
|
+
*
|
|
423
|
+
* Errors are not represented here — controllers throw `ArcError` and the
|
|
424
|
+
* MCP tool wrapper catches them via {@link toCallToolError}.
|
|
419
425
|
*/
|
|
420
426
|
function toCallToolResult(result) {
|
|
421
|
-
if (!result.success) return {
|
|
422
|
-
content: [{
|
|
423
|
-
type: "text",
|
|
424
|
-
text: result.error ?? "Operation failed"
|
|
425
|
-
}],
|
|
426
|
-
isError: true
|
|
427
|
-
};
|
|
428
427
|
const output = result.meta ? {
|
|
429
428
|
data: result.data,
|
|
430
429
|
...result.meta
|
|
@@ -435,6 +434,70 @@ function toCallToolResult(result) {
|
|
|
435
434
|
}] };
|
|
436
435
|
}
|
|
437
436
|
/**
|
|
437
|
+
* Wrap a raw success payload as an MCP `CallToolResult`. Use when the
|
|
438
|
+
* tool produced a value directly (action handler return, aggregation
|
|
439
|
+
* rows, etc.) instead of an `IControllerResponse` envelope.
|
|
440
|
+
*
|
|
441
|
+
* Emits the value as JSON with no envelope — same no-envelope contract
|
|
442
|
+
* the HTTP wire follows. The `isError: true` flag on `CallToolResult`
|
|
443
|
+
* is the success/error discriminant for MCP, mirroring HTTP status.
|
|
444
|
+
*/
|
|
445
|
+
function toCallToolSuccess(value) {
|
|
446
|
+
return { content: [{
|
|
447
|
+
type: "text",
|
|
448
|
+
text: JSON.stringify(value)
|
|
449
|
+
}] };
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Wrap an error as an MCP `CallToolResult` with the canonical
|
|
453
|
+
* `ErrorContract` shape inside the text payload. Single source of truth
|
|
454
|
+
* for MCP error serialization — every tool surface (CRUD, action, route,
|
|
455
|
+
* aggregation) routes through here so the JSON shape an agent sees is
|
|
456
|
+
* identical to what an HTTP client sees.
|
|
457
|
+
*
|
|
458
|
+
* Accepts:
|
|
459
|
+
* - An `ArcError` (or any `HttpError`-shaped throw) → routes through
|
|
460
|
+
* `toErrorContract()` for the canonical conversion.
|
|
461
|
+
* - A partial contract `{code, message, status, details?}` → used as-is.
|
|
462
|
+
* - Any other `Error` → falls back to `arc.internal_error` 500.
|
|
463
|
+
*/
|
|
464
|
+
function toCallToolError(input) {
|
|
465
|
+
let contract;
|
|
466
|
+
if (input instanceof Error) if (isArcError(input) || isHttpError(input)) contract = toErrorContract(input);
|
|
467
|
+
else contract = {
|
|
468
|
+
code: "arc.internal_error",
|
|
469
|
+
message: input.message || "Internal Server Error",
|
|
470
|
+
status: 500
|
|
471
|
+
};
|
|
472
|
+
else contract = {
|
|
473
|
+
code: input.code,
|
|
474
|
+
message: input.message,
|
|
475
|
+
...input.status !== void 0 ? { status: input.status } : {},
|
|
476
|
+
...input.details ? { details: input.details } : {}
|
|
477
|
+
};
|
|
478
|
+
return {
|
|
479
|
+
content: [{
|
|
480
|
+
type: "text",
|
|
481
|
+
text: JSON.stringify(contract)
|
|
482
|
+
}],
|
|
483
|
+
isError: true
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Build the canonical permission-denied `CallToolResult` for an MCP
|
|
488
|
+
* tool. Discriminates 401 (no session — "Authentication required") from
|
|
489
|
+
* 403 (session present, denied — "Permission denied"). Mirrors the
|
|
490
|
+
* status split the HTTP `errorHandler` plugin uses.
|
|
491
|
+
*/
|
|
492
|
+
function permissionDeniedResult(args) {
|
|
493
|
+
const authenticated = args.session != null;
|
|
494
|
+
return toCallToolError({
|
|
495
|
+
code: authenticated ? "arc.forbidden" : "arc.unauthorized",
|
|
496
|
+
message: args.reason ?? (authenticated ? `Permission denied for '${args.operation}' on '${args.resource}'` : "Authentication required"),
|
|
497
|
+
status: authenticated ? 403 : 401
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
438
501
|
* Auto-create a BaseController from the resource's adapter for MCP use.
|
|
439
502
|
* Called when the resource has an adapter but no controller
|
|
440
503
|
* (e.g. `disableDefaultRoutes: true` skips controller creation in
|
|
@@ -482,32 +545,24 @@ function createActionToolHandler(actionName, handler, permissions, resourceName,
|
|
|
482
545
|
const session = ctx.session;
|
|
483
546
|
if (allowedKeys) {
|
|
484
547
|
const extras = Object.keys(input).filter((k) => !allowedKeys.has(k));
|
|
485
|
-
if (extras.length > 0) return {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
})
|
|
496
|
-
}],
|
|
497
|
-
isError: true
|
|
498
|
-
};
|
|
548
|
+
if (extras.length > 0) return toCallToolError({
|
|
549
|
+
code: "arc.bad_request",
|
|
550
|
+
message: `Unknown properties not allowed: ${extras.join(", ")}`,
|
|
551
|
+
status: 400,
|
|
552
|
+
details: [{
|
|
553
|
+
path: "input",
|
|
554
|
+
code: "unknown_properties",
|
|
555
|
+
message: `Unexpected fields: ${extras.join(", ")}`
|
|
556
|
+
}]
|
|
557
|
+
});
|
|
499
558
|
}
|
|
500
559
|
const permResult = await evaluatePermission(permissions, session, resourceName, actionName, input);
|
|
501
|
-
if (permResult && !permResult.granted) return {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
})
|
|
508
|
-
}],
|
|
509
|
-
isError: true
|
|
510
|
-
};
|
|
560
|
+
if (permResult && !permResult.granted) return permissionDeniedResult({
|
|
561
|
+
resource: resourceName,
|
|
562
|
+
operation: `action.${actionName}`,
|
|
563
|
+
reason: permResult.reason,
|
|
564
|
+
session
|
|
565
|
+
});
|
|
511
566
|
const reqCtx = buildRequestContext({
|
|
512
567
|
...input,
|
|
513
568
|
action: actionName
|
|
@@ -515,26 +570,111 @@ function createActionToolHandler(actionName, handler, permissions, resourceName,
|
|
|
515
570
|
const id = typeof input.id === "string" ? input.id : "";
|
|
516
571
|
const { id: _discardId, ...data } = input;
|
|
517
572
|
try {
|
|
518
|
-
|
|
519
|
-
return { content: [{
|
|
520
|
-
type: "text",
|
|
521
|
-
text: JSON.stringify({
|
|
522
|
-
success: true,
|
|
523
|
-
data: result
|
|
524
|
-
})
|
|
525
|
-
}] };
|
|
573
|
+
return toCallToolSuccess(await handler(id, data, reqCtx));
|
|
526
574
|
} catch (err) {
|
|
527
|
-
return
|
|
528
|
-
content: [{
|
|
529
|
-
type: "text",
|
|
530
|
-
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
531
|
-
}],
|
|
532
|
-
isError: true
|
|
533
|
-
};
|
|
575
|
+
return toCallToolError(err instanceof Error ? err : new Error(String(err)));
|
|
534
576
|
}
|
|
535
577
|
};
|
|
536
578
|
}
|
|
537
579
|
//#endregion
|
|
580
|
+
//#region src/integrations/mcp/aggregation-tools.ts
|
|
581
|
+
/**
|
|
582
|
+
* Build MCP tools for a resource's aggregations.
|
|
583
|
+
*
|
|
584
|
+
* Returns an empty array when the resource has no aggregations or
|
|
585
|
+
* every aggregation opts out via `mcp: false`.
|
|
586
|
+
*/
|
|
587
|
+
function buildAggregationTools(args) {
|
|
588
|
+
const { resourceName, displayName, aggregations, schemaOptions, repo, buildOptionsFromSession, prefix } = args;
|
|
589
|
+
if (!aggregations || Object.keys(aggregations).length === 0) return [];
|
|
590
|
+
const normalized = validateAggregations(resourceName, aggregations, schemaOptions);
|
|
591
|
+
const tools = [];
|
|
592
|
+
for (const norm of normalized) {
|
|
593
|
+
const config = norm.base;
|
|
594
|
+
if (config.mcp === false) continue;
|
|
595
|
+
const mcpCfg = typeof config.mcp === "object" ? config.mcp : void 0;
|
|
596
|
+
const description = buildAggregationToolDescription(norm, mcpCfg?.description);
|
|
597
|
+
const annotations = mcpCfg?.annotations ? { ...mcpCfg.annotations } : {
|
|
598
|
+
readOnlyHint: true,
|
|
599
|
+
idempotentHint: true
|
|
600
|
+
};
|
|
601
|
+
const toolName = prefix ? `${prefix}_aggregation_${norm.name}_${resourceName}` : `aggregation_${norm.name}_${resourceName}`;
|
|
602
|
+
tools.push({
|
|
603
|
+
name: toolName,
|
|
604
|
+
description,
|
|
605
|
+
annotations,
|
|
606
|
+
inputSchema: buildAggregationInputSchema(norm),
|
|
607
|
+
handler: createAggregationToolHandler({
|
|
608
|
+
normalized: norm,
|
|
609
|
+
permissions: config.permissions,
|
|
610
|
+
resourceName,
|
|
611
|
+
repo,
|
|
612
|
+
buildOptionsFromSession
|
|
613
|
+
})
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
return tools;
|
|
617
|
+
}
|
|
618
|
+
function buildAggregationToolDescription(norm, override) {
|
|
619
|
+
if (override) return override;
|
|
620
|
+
const config = norm.base;
|
|
621
|
+
const parts = [];
|
|
622
|
+
if (config.summary) parts.push(config.summary);
|
|
623
|
+
if (config.description && config.description !== config.summary) parts.push(config.description);
|
|
624
|
+
const groupSummary = describeGroupBy(config.groupBy);
|
|
625
|
+
const measureSummary = Object.keys(norm.compiled.measures).join(", ");
|
|
626
|
+
if (groupSummary) parts.push(`Groups by ${groupSummary}; measures: ${measureSummary}.`);
|
|
627
|
+
else parts.push(`Scalar aggregate. Measures: ${measureSummary}.`);
|
|
628
|
+
if (config.requireDateRange) parts.push(`Requires a bounded date range on \`${config.requireDateRange.field}\`.` + (config.requireDateRange.maxRangeDays ? ` Max range: ${config.requireDateRange.maxRangeDays} days.` : ""));
|
|
629
|
+
if (config.requireFilters?.length) parts.push(`Requires filters on: ${config.requireFilters.join(", ")}.`);
|
|
630
|
+
return parts.join(" ");
|
|
631
|
+
}
|
|
632
|
+
function describeGroupBy(groupBy) {
|
|
633
|
+
if (!groupBy) return "";
|
|
634
|
+
if (typeof groupBy === "string") return `\`${groupBy}\``;
|
|
635
|
+
if (groupBy.length === 0) return "";
|
|
636
|
+
return groupBy.map((g) => `\`${g}\``).join(", ");
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Build the MCP tool input schema.
|
|
640
|
+
*
|
|
641
|
+
* Aggregation tools accept a single `filter` object mapping field
|
|
642
|
+
* name → value or operator object. Open-shape so agents can pass any
|
|
643
|
+
* caller-side narrows; safety guards inside `executeAggregation`
|
|
644
|
+
* enforce required fields.
|
|
645
|
+
*/
|
|
646
|
+
function buildAggregationInputSchema(norm) {
|
|
647
|
+
const config = norm.base;
|
|
648
|
+
const shape = { filter: z.record(z.string(), z.unknown()).optional().describe("Caller-supplied filter narrowing. Keys are field names (or `'<alias>.<field>'` joined-alias paths when the aggregation declares lookups). Values are literals or operator objects (`{ gte, lte, in, ... }`).") };
|
|
649
|
+
if (config.requireFilters?.length) shape.requireFiltersHint = z.literal(config.requireFilters.join(", ")).optional().describe(`REQUIRED FILTERS — supply ${config.requireFilters.map((f) => `\`filter.${f}\``).join(" + ")} for this aggregation to run. Missing fields → 400.`);
|
|
650
|
+
if (config.requireDateRange) shape.requireDateRangeHint = z.literal(config.requireDateRange.field).optional().describe(`REQUIRED DATE RANGE — supply \`filter['${config.requireDateRange.field}'] = { gte: '<lower>', lte: '<upper>' }\`.` + (config.requireDateRange.maxRangeDays ? ` Range capped at ${config.requireDateRange.maxRangeDays} days.` : ""));
|
|
651
|
+
return shape;
|
|
652
|
+
}
|
|
653
|
+
function createAggregationToolHandler(args) {
|
|
654
|
+
const { normalized, permissions, resourceName, repo, buildOptionsFromSession } = args;
|
|
655
|
+
return async (input, ctx) => {
|
|
656
|
+
const permResult = await evaluatePermission(permissions, ctx.session, resourceName, `aggregation:${normalized.name}`, input);
|
|
657
|
+
if (permResult && !permResult.granted) return permissionDeniedResult({
|
|
658
|
+
resource: resourceName,
|
|
659
|
+
operation: `aggregation:${normalized.name}`,
|
|
660
|
+
reason: permResult.reason,
|
|
661
|
+
session: ctx.session
|
|
662
|
+
});
|
|
663
|
+
const filterInput = input.filter ?? {};
|
|
664
|
+
const tenantOptions = buildOptionsFromSession(ctx.session);
|
|
665
|
+
if (permResult?.filters) Object.assign(tenantOptions, { _policyFilters: permResult.filters });
|
|
666
|
+
const result = await executeAggregation(normalized, {
|
|
667
|
+
repo,
|
|
668
|
+
buildOptions: () => tenantOptions
|
|
669
|
+
}, {
|
|
670
|
+
query: filterInput,
|
|
671
|
+
tenantOptions
|
|
672
|
+
});
|
|
673
|
+
if (result.status === 200) return toCallToolSuccess(result.body);
|
|
674
|
+
return toCallToolError(result.body);
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
//#endregion
|
|
538
678
|
//#region src/integrations/mcp/crud-tools.ts
|
|
539
679
|
const ALL_CRUD_OPS = [
|
|
540
680
|
"list",
|
|
@@ -928,16 +1068,12 @@ function createCustomRouteHandler(route, controller, hasId, options) {
|
|
|
928
1068
|
return async (input, _ctx) => {
|
|
929
1069
|
const session = _ctx.session;
|
|
930
1070
|
const permResult = await evaluatePermission(permissions, session, resourceName, operationName, input);
|
|
931
|
-
if (permResult && !permResult.granted) return {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
})
|
|
938
|
-
}],
|
|
939
|
-
isError: true
|
|
940
|
-
};
|
|
1071
|
+
if (permResult && !permResult.granted) return permissionDeniedResult({
|
|
1072
|
+
resource: resourceName,
|
|
1073
|
+
operation: operationName,
|
|
1074
|
+
reason: permResult.reason,
|
|
1075
|
+
session
|
|
1076
|
+
});
|
|
941
1077
|
try {
|
|
942
1078
|
const reqCtx = buildRequestContext(input, session, hasId ? "update" : "create", permResult?.filters, permResult?.scope);
|
|
943
1079
|
if (typeof route.handler === "function") {
|
|
@@ -948,16 +1084,10 @@ function createCustomRouteHandler(route, controller, hasId, options) {
|
|
|
948
1084
|
operation: operationName
|
|
949
1085
|
}, async (ctx) => {
|
|
950
1086
|
const raw = await fn(ctx);
|
|
951
|
-
return raw !== null && typeof raw === "object" && "
|
|
952
|
-
success: true,
|
|
953
|
-
data: raw
|
|
954
|
-
};
|
|
1087
|
+
return raw !== null && typeof raw === "object" && "data" in raw ? raw : { data: raw };
|
|
955
1088
|
}, operationName));
|
|
956
1089
|
const out = await fn(reqCtx);
|
|
957
|
-
return toCallToolResult(out !== null && typeof out === "object" && "
|
|
958
|
-
success: true,
|
|
959
|
-
data: out
|
|
960
|
-
});
|
|
1090
|
+
return toCallToolResult(out !== null && typeof out === "object" && "data" in out ? out : { data: out });
|
|
961
1091
|
}
|
|
962
1092
|
if (!ctrl) return {
|
|
963
1093
|
content: [{
|
|
@@ -1154,6 +1284,29 @@ function resourceToTools(resource, config = {}) {
|
|
|
1154
1284
|
handler: createActionToolHandler(actionName, handler, actionPerms, resource.name, resource.permissions, rawSchema)
|
|
1155
1285
|
});
|
|
1156
1286
|
}
|
|
1287
|
+
if (resource.aggregations && Object.keys(resource.aggregations).length > 0) {
|
|
1288
|
+
const repoForAgg = resource.controller?.repository;
|
|
1289
|
+
const buildOptionsFromSession = (session) => {
|
|
1290
|
+
const s = session;
|
|
1291
|
+
const out = {};
|
|
1292
|
+
const orgId = s?.scope?.organizationId;
|
|
1293
|
+
if (orgId) out.organizationId = orgId;
|
|
1294
|
+
const userId = s?.scope?.userId ?? s?.user?.id;
|
|
1295
|
+
if (userId) out.userId = userId;
|
|
1296
|
+
if (s?.user) out.user = s.user;
|
|
1297
|
+
if (s?.requestId) out.requestId = s.requestId;
|
|
1298
|
+
return out;
|
|
1299
|
+
};
|
|
1300
|
+
tools.push(...buildAggregationTools({
|
|
1301
|
+
resourceName: resource.name,
|
|
1302
|
+
displayName: resource.displayName ?? resource.name,
|
|
1303
|
+
aggregations: resource.aggregations,
|
|
1304
|
+
schemaOptions: resource.schemaOptions,
|
|
1305
|
+
repo: repoForAgg,
|
|
1306
|
+
buildOptionsFromSession,
|
|
1307
|
+
prefix
|
|
1308
|
+
}));
|
|
1309
|
+
}
|
|
1157
1310
|
return tools;
|
|
1158
1311
|
}
|
|
1159
1312
|
//#endregion
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { t as
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
1
|
+
import { f as createError } from "./errors-j4aJm1Wg.mjs";
|
|
2
|
+
import { b as isMember, c as getOrgId, h as getUserId, n as PUBLIC_SCOPE, y as isElevated } from "./types-C_s5moIu.mjs";
|
|
3
|
+
import { t as requestContext } from "./requestContext-SSaaTgW8.mjs";
|
|
4
|
+
import { I as evaluateAndApplyPermission, p as resolveEffectiveRoles, u as applyFieldReadPermissions } from "./permissions-ohQyv50e.mjs";
|
|
5
|
+
import { t as getUserRoles } from "./types-D57iXYb8.mjs";
|
|
6
|
+
import { t as executePipeline } from "./pipe-Zr0KXjQe.mjs";
|
|
7
|
+
import { isPaginatedResult, toCanonicalList } from "@classytic/repo-core/pagination";
|
|
6
8
|
//#region src/scope/projection.ts
|
|
7
9
|
/**
|
|
8
10
|
* Compute the request-scope projection. Returns `undefined` when no
|
|
@@ -165,32 +167,44 @@ function sendControllerResponse(reply, response, request) {
|
|
|
165
167
|
return result;
|
|
166
168
|
};
|
|
167
169
|
if (response.headers) for (const [key, value] of Object.entries(response.headers)) reply.header(key, value);
|
|
168
|
-
if (response.
|
|
169
|
-
const
|
|
170
|
-
const
|
|
170
|
+
if (response.data && typeof response.data === "object" && Array.isArray(response.data.data)) {
|
|
171
|
+
const payload = response.data;
|
|
172
|
+
const filteredRows = hasFieldRestrictions ? applyPermissions(payload.data) : payload.data;
|
|
173
|
+
let canonical;
|
|
174
|
+
if (isPaginatedResult(payload)) canonical = toCanonicalList({
|
|
175
|
+
...payload,
|
|
176
|
+
data: filteredRows
|
|
177
|
+
});
|
|
178
|
+
else if (typeof payload.page === "number" && typeof payload.limit === "number" && typeof payload.total === "number") canonical = toCanonicalList({
|
|
179
|
+
method: "offset",
|
|
180
|
+
data: filteredRows,
|
|
181
|
+
page: payload.page,
|
|
182
|
+
limit: payload.limit,
|
|
183
|
+
total: payload.total,
|
|
184
|
+
pages: payload.pages ?? Math.ceil(payload.total / Math.max(1, payload.limit)),
|
|
185
|
+
hasNext: payload.hasNext ?? false,
|
|
186
|
+
hasPrev: payload.hasPrev ?? false
|
|
187
|
+
});
|
|
188
|
+
else canonical = toCanonicalList(filteredRows);
|
|
171
189
|
reply.code(response.status ?? 200).send({
|
|
172
|
-
|
|
173
|
-
docs: filteredDocs,
|
|
174
|
-
page: paginatedData.page,
|
|
175
|
-
limit: paginatedData.limit,
|
|
176
|
-
total: paginatedData.total,
|
|
177
|
-
pages: paginatedData.pages,
|
|
178
|
-
hasNext: paginatedData.hasNext,
|
|
179
|
-
hasPrev: paginatedData.hasPrev,
|
|
190
|
+
...canonical,
|
|
180
191
|
...response.meta ?? {},
|
|
181
192
|
...fieldCaps ? { fieldPermissions: fieldCaps } : {}
|
|
182
193
|
});
|
|
183
194
|
return;
|
|
184
195
|
}
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
196
|
+
const rawData = hasFieldRestrictions ? applyPermissions(response.data) : response.data;
|
|
197
|
+
const filteredData = rawData !== null && typeof rawData === "object" && typeof rawData.toJSON === "function" ? rawData.toJSON() : rawData;
|
|
198
|
+
const status = response.status ?? 200;
|
|
199
|
+
if (filteredData !== null && typeof filteredData === "object" && !Array.isArray(filteredData) && (response.meta || fieldCaps)) {
|
|
200
|
+
reply.code(status).send({
|
|
201
|
+
...filteredData,
|
|
202
|
+
...response.meta ?? {},
|
|
203
|
+
...fieldCaps ? { fieldPermissions: fieldCaps } : {}
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
reply.code(status).send(filteredData);
|
|
194
208
|
}
|
|
195
209
|
/**
|
|
196
210
|
* Create Fastify route handler from IController method
|
|
@@ -370,15 +384,7 @@ function buildActionPermissionMw(actionEnum, actionPermissions, globalAuth, reso
|
|
|
370
384
|
return async (req, reply) => {
|
|
371
385
|
const body = req.body ?? {};
|
|
372
386
|
const action = body.action;
|
|
373
|
-
if (!action || !enumSet.has(action)) {
|
|
374
|
-
sendControllerResponse(reply, {
|
|
375
|
-
success: false,
|
|
376
|
-
status: 400,
|
|
377
|
-
error: `Invalid action '${action ?? ""}'. Valid actions: ${validActions.join(", ")}`,
|
|
378
|
-
meta: { validActions }
|
|
379
|
-
}, req);
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
387
|
+
if (!action || !enumSet.has(action)) throw createError(400, `Invalid action '${action ?? ""}'. Valid actions: ${validActions.join(", ")}`, { validActions });
|
|
382
388
|
const permissionCheck = actionPermissions[action] ?? globalAuth;
|
|
383
389
|
if (!permissionCheck) return;
|
|
384
390
|
const { action: _discard, ...data } = body;
|
|
@@ -432,7 +438,6 @@ function buildPipelineHandler(controllerMethod, steps, operation, resourceName)
|
|
|
432
438
|
*/
|
|
433
439
|
function buildActionPipelineHandler(handler, steps, operation, resourceName) {
|
|
434
440
|
if (steps.length === 0) return async (id, data, req) => ({
|
|
435
|
-
success: true,
|
|
436
441
|
status: 200,
|
|
437
442
|
data: await handler(id, data, req)
|
|
438
443
|
});
|
|
@@ -442,7 +447,6 @@ function buildActionPipelineHandler(handler, steps, operation, resourceName) {
|
|
|
442
447
|
resource: resourceName,
|
|
443
448
|
operation
|
|
444
449
|
}, async (_ctx) => ({
|
|
445
|
-
success: true,
|
|
446
450
|
status: 200,
|
|
447
451
|
data: await handler(id, data, req)
|
|
448
452
|
}), operation);
|
|
@@ -556,6 +560,7 @@ function resolveRoutePreHandlers(preHandler, fastify, routeId) {
|
|
|
556
560
|
const msg = err instanceof Error ? err.message : String(err);
|
|
557
561
|
throw new TypeError(`Route ${routeId}: preHandler factory threw during route registration: ${msg}.\nIf you intended to pass a single handler (e.g. \`multipartBody({...})\`), wrap it in an array: \`preHandler: [yourHandler]\`. The factory form is \`(fastify) => RouteHandlerMethod[]\` — it must return an array.`, { cause: err instanceof Error ? err : void 0 });
|
|
558
562
|
}
|
|
563
|
+
if (result && typeof result.then === "function") result.catch(() => void 0);
|
|
559
564
|
if (!Array.isArray(result)) throw new TypeError(`Route ${routeId}: preHandler factory must return an array of handlers, got ${describeValue(result)}.\nCommon cause: passing a single \`RouteHandlerMethod\` (e.g. \`multipartBody({...})\`) where an array was expected. Wrap it: \`preHandler: [yourHandler]\`. The factory form \`(fastify) => RouteHandlerMethod[]\` is for cases that need the Fastify instance — e.g. \`(fastify) => [fastify.authenticate, myHandler]\`.`);
|
|
560
565
|
return result.filter((h) => typeof h === "function");
|
|
561
566
|
}
|
package/dist/schemas/index.d.mts
CHANGED
|
@@ -1,15 +1,54 @@
|
|
|
1
|
+
import { ErrorContract, ErrorDetail } from "@classytic/repo-core/errors";
|
|
1
2
|
import { FastifyPluginAsyncTypebox, FastifyPluginCallbackTypebox, TypeBoxTypeProvider, TypeBoxValidatorCompiler } from "@fastify/type-provider-typebox";
|
|
2
3
|
import * as _$_sinclair_typebox0 from "@sinclair/typebox";
|
|
3
4
|
import { Static, TObject, TSchema, TSchema as TSchema$1, Type } from "@sinclair/typebox";
|
|
4
5
|
|
|
5
6
|
//#region src/schemas/index.d.ts
|
|
6
7
|
/**
|
|
7
|
-
* Paginated list response —
|
|
8
|
-
*
|
|
8
|
+
* Paginated list response — full union of every wire shape `toCanonicalList`
|
|
9
|
+
* emits. Mirrors `PaginatedResult<T>` from `@classytic/repo-core/pagination`:
|
|
10
|
+
*
|
|
11
|
+
* - `{ method: 'offset', data, page, limit, total, pages, hasNext, hasPrev }`
|
|
12
|
+
* - `{ method: 'keyset', data, limit, hasMore, next: string | null }`
|
|
13
|
+
* - `{ method: 'aggregate', ...same as offset }`
|
|
14
|
+
* - `{ data }` (bare list, no `method`)
|
|
15
|
+
*
|
|
16
|
+
* Use `ArcOffsetListResponse` / `ArcKeysetListResponse` /
|
|
17
|
+
* `ArcAggregateListResponse` / `ArcBareListResponse` to pin a single
|
|
18
|
+
* variant when your endpoint never emits the others. HTTP status
|
|
19
|
+
* discriminates success vs error — no `success` field.
|
|
9
20
|
*/
|
|
10
|
-
declare function ArcListResponse<T extends TSchema$1>(itemSchema: T): _$_sinclair_typebox0.TObject<{
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
declare function ArcListResponse<T extends TSchema$1>(itemSchema: T): _$_sinclair_typebox0.TUnion<[_$_sinclair_typebox0.TObject<{
|
|
22
|
+
method: _$_sinclair_typebox0.TLiteral<"offset">;
|
|
23
|
+
data: _$_sinclair_typebox0.TArray<T>;
|
|
24
|
+
page: _$_sinclair_typebox0.TInteger;
|
|
25
|
+
limit: _$_sinclair_typebox0.TInteger;
|
|
26
|
+
total: _$_sinclair_typebox0.TInteger;
|
|
27
|
+
pages: _$_sinclair_typebox0.TInteger;
|
|
28
|
+
hasNext: _$_sinclair_typebox0.TBoolean;
|
|
29
|
+
hasPrev: _$_sinclair_typebox0.TBoolean;
|
|
30
|
+
}>, _$_sinclair_typebox0.TObject<{
|
|
31
|
+
method: _$_sinclair_typebox0.TLiteral<"keyset">;
|
|
32
|
+
data: _$_sinclair_typebox0.TArray<T>;
|
|
33
|
+
limit: _$_sinclair_typebox0.TInteger;
|
|
34
|
+
hasMore: _$_sinclair_typebox0.TBoolean;
|
|
35
|
+
next: _$_sinclair_typebox0.TUnion<[_$_sinclair_typebox0.TString, _$_sinclair_typebox0.TNull]>;
|
|
36
|
+
}>, _$_sinclair_typebox0.TObject<{
|
|
37
|
+
method: _$_sinclair_typebox0.TLiteral<"aggregate">;
|
|
38
|
+
data: _$_sinclair_typebox0.TArray<T>;
|
|
39
|
+
page: _$_sinclair_typebox0.TInteger;
|
|
40
|
+
limit: _$_sinclair_typebox0.TInteger;
|
|
41
|
+
total: _$_sinclair_typebox0.TInteger;
|
|
42
|
+
pages: _$_sinclair_typebox0.TInteger;
|
|
43
|
+
hasNext: _$_sinclair_typebox0.TBoolean;
|
|
44
|
+
hasPrev: _$_sinclair_typebox0.TBoolean;
|
|
45
|
+
}>, _$_sinclair_typebox0.TObject<{
|
|
46
|
+
data: _$_sinclair_typebox0.TArray<T>;
|
|
47
|
+
}>]>;
|
|
48
|
+
/** Offset variant — `{ method: 'offset', data, page, limit, total, pages, hasNext, hasPrev }`. */
|
|
49
|
+
declare function ArcOffsetListResponse<T extends TSchema$1>(itemSchema: T): _$_sinclair_typebox0.TObject<{
|
|
50
|
+
method: _$_sinclair_typebox0.TLiteral<"offset">;
|
|
51
|
+
data: _$_sinclair_typebox0.TArray<T>;
|
|
13
52
|
page: _$_sinclair_typebox0.TInteger;
|
|
14
53
|
limit: _$_sinclair_typebox0.TInteger;
|
|
15
54
|
total: _$_sinclair_typebox0.TInteger;
|
|
@@ -17,47 +56,78 @@ declare function ArcListResponse<T extends TSchema$1>(itemSchema: T): _$_sinclai
|
|
|
17
56
|
hasNext: _$_sinclair_typebox0.TBoolean;
|
|
18
57
|
hasPrev: _$_sinclair_typebox0.TBoolean;
|
|
19
58
|
}>;
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
59
|
+
/** Keyset variant — `{ method: 'keyset', data, limit, hasMore, next: string | null }`. */
|
|
60
|
+
declare function ArcKeysetListResponse<T extends TSchema$1>(itemSchema: T): _$_sinclair_typebox0.TObject<{
|
|
61
|
+
method: _$_sinclair_typebox0.TLiteral<"keyset">;
|
|
62
|
+
data: _$_sinclair_typebox0.TArray<T>;
|
|
63
|
+
limit: _$_sinclair_typebox0.TInteger;
|
|
64
|
+
hasMore: _$_sinclair_typebox0.TBoolean;
|
|
65
|
+
next: _$_sinclair_typebox0.TUnion<[_$_sinclair_typebox0.TString, _$_sinclair_typebox0.TNull]>;
|
|
26
66
|
}>;
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
67
|
+
/** Aggregate variant — `{ method: 'aggregate', ...same as offset }`. */
|
|
68
|
+
declare function ArcAggregateListResponse<T extends TSchema$1>(itemSchema: T): _$_sinclair_typebox0.TObject<{
|
|
69
|
+
method: _$_sinclair_typebox0.TLiteral<"aggregate">;
|
|
70
|
+
data: _$_sinclair_typebox0.TArray<T>;
|
|
71
|
+
page: _$_sinclair_typebox0.TInteger;
|
|
72
|
+
limit: _$_sinclair_typebox0.TInteger;
|
|
73
|
+
total: _$_sinclair_typebox0.TInteger;
|
|
74
|
+
pages: _$_sinclair_typebox0.TInteger;
|
|
75
|
+
hasNext: _$_sinclair_typebox0.TBoolean;
|
|
76
|
+
hasPrev: _$_sinclair_typebox0.TBoolean;
|
|
77
|
+
}>;
|
|
78
|
+
/** Bare variant — `{ data }`, no `method` discriminant. */
|
|
79
|
+
declare function ArcBareListResponse<T extends TSchema$1>(itemSchema: T): _$_sinclair_typebox0.TObject<{
|
|
80
|
+
data: _$_sinclair_typebox0.TArray<T>;
|
|
34
81
|
}>;
|
|
35
82
|
/**
|
|
36
|
-
* Delete response — `{
|
|
83
|
+
* Delete response — `{ message, id?, soft? }` raw at the top level.
|
|
37
84
|
*/
|
|
38
85
|
declare function ArcDeleteResponse(): _$_sinclair_typebox0.TObject<{
|
|
39
|
-
|
|
40
|
-
|
|
86
|
+
message: _$_sinclair_typebox0.TString;
|
|
87
|
+
id: _$_sinclair_typebox0.TOptional<_$_sinclair_typebox0.TString>;
|
|
88
|
+
soft: _$_sinclair_typebox0.TOptional<_$_sinclair_typebox0.TBoolean>;
|
|
41
89
|
}>;
|
|
42
90
|
/**
|
|
43
|
-
*
|
|
91
|
+
* Single field-scoped error detail — `Type.Unsafe<ErrorDetail>` over the
|
|
92
|
+
* canonical JSON Schema `errorDetailSchema` from
|
|
93
|
+
* `@classytic/repo-core/errors`. One schema constant, two adapters
|
|
94
|
+
* (JSON-Schema + TypeBox), zero drift surface — the schema and the TS
|
|
95
|
+
* type both come from repo-core.
|
|
96
|
+
*
|
|
97
|
+
* Exported standalone so hosts can embed it in custom 422 / 409 response
|
|
98
|
+
* schemas.
|
|
44
99
|
*/
|
|
45
|
-
declare function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
100
|
+
declare function ArcErrorDetail(): _$_sinclair_typebox0.TUnsafe<ErrorDetail>;
|
|
101
|
+
/**
|
|
102
|
+
* Error response schema — `Type.Unsafe<ErrorContract>` over the canonical
|
|
103
|
+
* JSON Schema `errorContractSchema` from `@classytic/repo-core/errors`.
|
|
104
|
+
* Same trick as `ArcErrorDetail`: the schema bytes and the TS type both
|
|
105
|
+
* come from repo-core, so the JSON-Schema sibling
|
|
106
|
+
* (`errorContractSchema`) and this TypeBox helper cannot drift —
|
|
107
|
+
* literally one source.
|
|
108
|
+
*/
|
|
109
|
+
declare function ArcErrorResponse(): _$_sinclair_typebox0.TUnsafe<ErrorContract>;
|
|
51
110
|
/**
|
|
52
111
|
* Standard pagination + sorting + filtering query parameters.
|
|
53
112
|
* Matches Arc's list endpoint conventions.
|
|
113
|
+
*
|
|
114
|
+
* `select` accepts every shape `QueryResolver` preserves DB-agnostically
|
|
115
|
+
* (gotcha #5):
|
|
116
|
+
*
|
|
117
|
+
* - `string` — `"name email -password"` (Mongoose space-separated)
|
|
118
|
+
* - `string[]` — `["name", "email", "-password"]` (Arc parser)
|
|
119
|
+
* - `Record<string, 0 | 1>` — `{ name: 1, email: 1, password: 0 }` (Mongo projection)
|
|
120
|
+
*
|
|
121
|
+
* Narrowing `select` to `string` would have rejected the array and
|
|
122
|
+
* projection forms at the request-validation gate even though arc passes
|
|
123
|
+
* them through unchanged.
|
|
54
124
|
*/
|
|
55
125
|
declare function ArcPaginationQuery(): _$_sinclair_typebox0.TObject<{
|
|
56
126
|
page: _$_sinclair_typebox0.TOptional<_$_sinclair_typebox0.TInteger>;
|
|
57
127
|
limit: _$_sinclair_typebox0.TOptional<_$_sinclair_typebox0.TInteger>;
|
|
58
128
|
sort: _$_sinclair_typebox0.TOptional<_$_sinclair_typebox0.TString>;
|
|
59
|
-
select: _$_sinclair_typebox0.TOptional<_$_sinclair_typebox0.TString
|
|
129
|
+
select: _$_sinclair_typebox0.TOptional<_$_sinclair_typebox0.TUnion<[_$_sinclair_typebox0.TString, _$_sinclair_typebox0.TArray<_$_sinclair_typebox0.TString>, _$_sinclair_typebox0.TRecord<_$_sinclair_typebox0.TString, _$_sinclair_typebox0.TUnion<[_$_sinclair_typebox0.TLiteral<0>, _$_sinclair_typebox0.TLiteral<1>]>>]>>;
|
|
60
130
|
populate: _$_sinclair_typebox0.TOptional<_$_sinclair_typebox0.TAny>;
|
|
61
131
|
}>;
|
|
62
132
|
//#endregion
|
|
63
|
-
export { ArcDeleteResponse, ArcErrorResponse,
|
|
133
|
+
export { ArcAggregateListResponse, ArcBareListResponse, ArcDeleteResponse, ArcErrorDetail, ArcErrorResponse, ArcKeysetListResponse, ArcListResponse, ArcOffsetListResponse, ArcPaginationQuery, type FastifyPluginAsyncTypebox, type FastifyPluginCallbackTypebox, type Static, type TObject, type TSchema, Type, type TypeBoxTypeProvider, TypeBoxValidatorCompiler };
|