@classytic/arc 2.15.4 → 2.16.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 +1 -0
- package/bin/arc.js +12 -0
- package/dist/{BaseController-dx3m2J8V.mjs → BaseController-DlCCTIxJ.mjs} +61 -19
- package/dist/{HookSystem-Iiebom92.mjs → HookSystem-Cmf7-Etp.mjs} +8 -4
- package/dist/{QueryCache-D41bfdBB.d.mts → QueryCache-SvmT_9ti.d.mts} +1 -1
- package/dist/{ResourceRegistry-CTERg_2x.mjs → ResourceRegistry-f48hFk3m.mjs} +52 -9
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/index.mjs +4 -2
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +4 -4
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi--M_i87dQ.mjs → betterAuthOpenApi-ClWxaceA.mjs} +10 -6
- package/dist/buildHandler-BZX6zzDM.mjs +300 -0
- package/dist/cache/index.d.mts +3 -3
- package/dist/cache/index.mjs +3 -3
- package/dist/{caching-SM8gghN6.mjs → caching-TeHE8G-v.mjs} +1 -1
- package/dist/cli/commands/describe.d.mts +35 -1
- package/dist/cli/commands/describe.mjs +52 -12
- package/dist/cli/commands/docs.d.mts +1 -4
- package/dist/cli/commands/docs.mjs +4 -16
- package/dist/cli/commands/generate.d.mts +2 -20
- package/dist/cli/commands/generate.mjs +1 -546
- package/dist/cli/commands/init.d.mts +2 -40
- package/dist/cli/commands/init.mjs +1 -3045
- package/dist/cli/commands/introspect.mjs +53 -64
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{constants-Cxde4rpC.mjs → constants-TrJVIJl0.mjs} +7 -0
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/{core-CvmOqEms.mjs → core-DBJ_j6rX.mjs} +222 -44
- package/dist/createActionRouter-DUpN3Dd1.mjs +288 -0
- package/dist/{createAggregationRouter-B0bPDf5b.mjs → createAggregationRouter-Dq-TUCuY.mjs} +3 -2
- package/dist/{createApp-PFegs47-.mjs → createApp-DNccuhyI.mjs} +16 -14
- package/dist/{defineEvent-D5h7EvAx.mjs → defineEvent-DRwY0fYm.mjs} +1 -1
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/{errorHandler-Bk-AGhkU.mjs → errorHandler-DpoXQHZ9.mjs} +17 -14
- package/dist/errors-C1lX_jlm.d.mts +91 -0
- package/dist/{eventPlugin-CaKTYkYM.mjs → eventPlugin-C2cGqtRO.mjs} +1 -1
- package/dist/{eventPlugin-qXpqTebY.d.mts → eventPlugin-CtHC_av1.d.mts} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +5 -5
- 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 +1 -1
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-COhcH3fk.d.mts → fields-Anj0xdih.d.mts} +1 -1
- package/dist/generate-BWFwgcCM.d.mts +38 -0
- package/dist/generate-CYac-OLv.mjs +654 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +2 -2
- package/dist/idempotency/index.mjs +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-BTqLEvhu.d.mts → index-3oIimXQn.d.mts} +12 -12
- package/dist/{index-BstGxcc3.d.mts → index-B-ulKx5P.d.mts} +55 -4
- package/dist/{index-BswOSJCE.d.mts → index-CkW0flkU.d.mts} +355 -16
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +7 -8
- package/dist/init-Dv71MsJr.d.mts +71 -0
- package/dist/init-HDvoO9L5.mjs +3098 -0
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/jobs.mjs +3 -3
- package/dist/integrations/mcp/index.d.mts +239 -7
- package/dist/integrations/mcp/index.mjs +2 -528
- package/dist/integrations/mcp/testing.d.mts +2 -2
- package/dist/integrations/mcp/testing.mjs +6 -10
- package/dist/integrations/streamline.mjs +26 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +1 -0
- package/dist/loadResourcesFromEntry-BLMEI2Xa.mjs +51 -0
- package/dist/{resourceToTools-tFYUNmM0.mjs → mcpPlugin-7vGV51ED.mjs} +1021 -318
- package/dist/{memory-UBydS5ku.mjs → memory-QOLe11D5.mjs} +2 -0
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/{openapi-BHXhoX8O.mjs → openapi-34T9yNwd.mjs} +47 -36
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +1 -1
- package/dist/{permissions-ohQyv50e.mjs → permissions-CTxMrreC.mjs} +2 -2
- package/dist/{pipe-Zr0KXjQe.mjs → pipe-DiCyvyPN.mjs} +1 -0
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +5 -5
- package/dist/plugins/index.mjs +10 -10
- package/dist/plugins/response-cache.mjs +5 -5
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/{pluralize-DQgqgifU.mjs → pluralize-B9M8xvy-.mjs} +2 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +2 -2
- 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 +4 -3
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-BbkjdPeH.mjs → presets-C9BE6WaZ.mjs} +2 -2
- package/dist/{queryCachePlugin-m1XsgAIJ.mjs → queryCachePlugin-B4XMSSe7.mjs} +2 -2
- package/dist/{queryCachePlugin-CqMdLI2-.d.mts → queryCachePlugin-Biqzfbi5.d.mts} +2 -2
- package/dist/{redis-DiMkdHEl.d.mts → redis-Cyzrz6SX.d.mts} +1 -1
- package/dist/{redis-stream-D6HzR1Z_.d.mts → redis-stream-DT-YjzrB.d.mts} +1 -1
- package/dist/registry/index.d.mts +319 -2
- package/dist/registry/index.mjs +3 -3
- package/dist/registry-BBE23CDj.mjs +576 -0
- package/dist/{routerShared-DrOa-26E.mjs → routerShared-CZV5aabX.mjs} +3 -3
- package/dist/scope/index.d.mts +3 -3
- package/dist/scope/index.mjs +3 -3
- package/dist/{sse-Bz-5ZeTt.mjs → sse-BY6sTy4P.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +16 -7
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +5 -5
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-C_s5moIu.mjs → types-Bi0r0vjG.mjs} +53 -1
- package/dist/{types-BQsjgQzS.d.mts → types-BsJMEQ4D.d.mts} +106 -12
- package/dist/{types-DrBaUwyV.d.mts → types-D-fYtKjb.d.mts} +33 -10
- package/dist/{types-CTYvcwHe.d.mts → types-DVfpSfx2.d.mts} +42 -1
- package/dist/utils/index.d.mts +1286 -2
- package/dist/utils/index.mjs +1 -1
- package/dist/{utils-_h9B3c57.mjs → utils-DC5ycPfr.mjs} +89 -40
- package/dist/{buildHandler-CcFOpJLh.mjs → validate-By96rH0r.mjs} +8 -299
- package/dist/{versioning-hmkPcDlX.d.mts → versioning-ZwX9tmbS.d.mts} +1 -1
- package/package.json +21 -28
- package/skills/arc/SKILL.md +300 -706
- package/skills/arc/references/auth.md +19 -7
- package/skills/arc-code-review/SKILL.md +1 -1
- package/skills/arc-code-review/references/arc-cheatsheet.md +100 -322
- package/dist/createActionRouter-S3MLVYot.mjs +0 -220
- package/dist/index-bRjYu21O.d.mts +0 -1320
- package/dist/org/index.d.mts +0 -66
- package/dist/org/index.mjs +0 -486
- package/dist/org/types.d.mts +0 -82
- package/dist/org/types.mjs +0 -1
- package/dist/registry-I-ogLgL9.mjs +0 -46
- /package/dist/{EventTransport-CT_52aWU.d.mts → EventTransport-C-2oAHtw.d.mts} +0 -0
- /package/dist/{EventTransport-DLWoUMHy.mjs → EventTransport-Hxvv5QQz.mjs} +0 -0
- /package/dist/{actionPermissions-CyUkQu6O.mjs → actionPermissions-Bjmvn7Eb.mjs} +0 -0
- /package/dist/{elevation-BXOWoGCF.d.mts → elevation-0YBpa663.d.mts} +0 -0
- /package/dist/{elevation-DgoeTyfX.mjs → elevation-Dci0AYLT.mjs} +0 -0
- /package/dist/{errorHandler-DFr45ZG4.d.mts → errorHandler-mHuyWzZE.d.mts} +0 -0
- /package/dist/{externalPaths-BD5nw6St.d.mts → externalPaths-DFg-2KTp.d.mts} +0 -0
- /package/dist/{interface-beEtJyWM.d.mts → interface-CH0OQudo.d.mts} +0 -0
- /package/dist/{interface-DfLGcus7.d.mts → interface-NwJ_qPlY.d.mts} +0 -0
- /package/dist/{keys-CGcCbNyu.mjs → keys-DopsCuyQ.mjs} +0 -0
- /package/dist/{loadResources-DBMQg_Aj.mjs → loadResources-ChQEj8ih.mjs} +0 -0
- /package/dist/{metrics-Qnvwc-LQ.mjs → metrics-TuOmguhi.mjs} +0 -0
- /package/dist/{replyHelpers-CK-FNO8E.mjs → replyHelpers-C-gD32oF.mjs} +0 -0
- /package/dist/{schemaIR-lYhC2gE5.mjs → schemaIR-Ctc89DSn.mjs} +0 -0
- /package/dist/{sessionManager-C4Le_UB3.d.mts → sessionManager-BqFegc0W.d.mts} +0 -0
- /package/dist/{storage-Dfzt4VTl.d.mts → storage-D2KZJAmn.d.mts} +0 -0
- /package/dist/{store-helpers-BkIN9-vu.mjs → store-helpers-B0sunfZZ.mjs} +0 -0
- /package/dist/{tracing-QJVprktp.d.mts → tracing-Dm8n7Cnn.d.mts} +0 -0
- /package/dist/{versioning-BUrT5aP4.mjs → versioning-B6mimogM.mjs} +0 -0
- /package/dist/{websocket-ChC2rqe1.d.mts → websocket-BkjeGZRn.d.mts} +0 -0
package/dist/utils/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, 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-j4aJm1Wg.mjs";
|
|
2
|
-
import { A as assertValidConfig, C as withCompensation, D as CircuitState, E as CircuitBreakerRegistry, M as validateResourceConfig, N as simpleEqualityMatcher, O as createCircuitBreaker, S as defineCompensation, T as CircuitBreakerError, _ as ArcQueryParser, a as bareListResponse, b as defineGuard, c as errorDetailSchema, d as keysetListResponse, f as listResponse, g as responses, h as queryParams, i as aggregateListResponse, j as formatValidationErrors, k as createCircuitBreakerRegistry, l as getDefaultCrudSchemas, m as paginationSchema, n as createStateMachine, o as deleteResponse, p as offsetListResponse, r as scheduleBackground, s as errorContractSchema, t as getUserId, u as getListQueryParams, v as createQueryParser, w as CircuitBreaker, x as defineErrorMapper, y as handleRaw } from "../utils-
|
|
2
|
+
import { A as assertValidConfig, C as withCompensation, D as CircuitState, E as CircuitBreakerRegistry, M as validateResourceConfig, N as simpleEqualityMatcher, O as createCircuitBreaker, S as defineCompensation, T as CircuitBreakerError, _ as ArcQueryParser, a as bareListResponse, b as defineGuard, c as errorDetailSchema, d as keysetListResponse, f as listResponse, g as responses, h as queryParams, i as aggregateListResponse, j as formatValidationErrors, k as createCircuitBreakerRegistry, l as getDefaultCrudSchemas, m as paginationSchema, n as createStateMachine, o as deleteResponse, p as offsetListResponse, r as scheduleBackground, s as errorContractSchema, t as getUserId, u as getListQueryParams, v as createQueryParser, w as CircuitBreaker, x as defineErrorMapper, y as handleRaw } from "../utils-DC5ycPfr.mjs";
|
|
3
3
|
import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-De34B1ZG.mjs";
|
|
4
4
|
import { t as hasEvents } from "../typeGuards-BzkXkvVv.mjs";
|
|
5
5
|
export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UnauthorizedError, ValidationError, aggregateListResponse, assertValidConfig, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, formatValidationErrors, getDefaultCrudSchemas, getListQueryParams, getUserId, handleRaw, hasEvents, isArcError, isJsonSchema, isZodSchema, keysetListResponse, listResponse, offsetListResponse, paginationSchema, queryParams, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, validateResourceConfig, withCompensation };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { m as RESERVED_QUERY_PARAMS, t as CRUD_OPERATIONS } from "./constants-
|
|
1
|
+
import { m as RESERVED_QUERY_PARAMS, t as CRUD_OPERATIONS } from "./constants-TrJVIJl0.mjs";
|
|
2
2
|
import { arcLog } from "./logger/index.mjs";
|
|
3
3
|
import { t as ArcError } from "./errors-j4aJm1Wg.mjs";
|
|
4
|
-
import { r as getAvailablePresets } from "./presets-
|
|
4
|
+
import { r as getAvailablePresets } from "./presets-C9BE6WaZ.mjs";
|
|
5
5
|
import { errorContractSchema as errorContractSchema$1, errorDetailSchema as errorDetailSchema$1 } from "@classytic/repo-core/errors";
|
|
6
6
|
//#region src/utils/simpleEqualityMatcher.ts
|
|
7
7
|
/**
|
|
@@ -236,9 +236,11 @@ function validateRoutes(routes, errors) {
|
|
|
236
236
|
message: `Route path must start with "/" (got "${route.path}")`,
|
|
237
237
|
suggestion: `Change to "/${route.path}"`
|
|
238
238
|
});
|
|
239
|
-
|
|
239
|
+
const routeWithRefs = route;
|
|
240
|
+
if (!route.handler && typeof routeWithRefs.controllerMethod !== "function") errors.push({
|
|
240
241
|
field: `routes[${i}].handler`,
|
|
241
|
-
message: "Route handler
|
|
242
|
+
message: "Route must declare either `handler` (string / function) or `controllerMethod`",
|
|
243
|
+
suggestion: "Prefer `controllerMethod: (c: MyController) => c.method` for typed handler refs."
|
|
242
244
|
});
|
|
243
245
|
const routeKey = `${route.method} ${route.path}`;
|
|
244
246
|
if (seenRoutes.has(routeKey)) errors.push({
|
|
@@ -761,12 +763,26 @@ const DANGEROUS_REGEX_PATTERNS = /(\{[0-9,]+\}|\*\+|\+\+|\?\+|(\(.+\))\+|\(\?:|\
|
|
|
761
763
|
* Converts URL query parameters to a structured query format:
|
|
762
764
|
* - Pagination: ?page=1&limit=20
|
|
763
765
|
* - Sorting: ?sort=-createdAt,name (- prefix = descending)
|
|
764
|
-
* - Filtering:
|
|
766
|
+
* - Filtering — TWO supported forms (both produce identical output):
|
|
767
|
+
* - Bare (default, terse): `?status=active&price[gte]=100&price[lte]=500`
|
|
768
|
+
* - Bracket envelope: `?filter[status]=active&filter[price][gte]=100`
|
|
769
|
+
* The envelope form matches the convention used by JSON:API / Stripe /
|
|
770
|
+
* most modern REST style guides, and is the safe choice on busy URLs
|
|
771
|
+
* that might otherwise collide with reserved meta keys (`page`, `sort`,
|
|
772
|
+
* `select`, `populate`, etc.). Operator notation (`[gte]`, `[lte]`,
|
|
773
|
+
* `[ne]`, `[in]`, ...) works inside either form.
|
|
765
774
|
* - Search: ?search=keyword
|
|
766
775
|
* - Populate: ?populate=author,category
|
|
767
776
|
* - Field selection: ?select=name,price,status
|
|
768
777
|
* - Keyset pagination: ?after=cursor_value
|
|
769
778
|
*
|
|
779
|
+
* **NOTE on parser portability.** `ArcQueryParser` accepts both filter
|
|
780
|
+
* forms. Other parser implementations (`@classytic/mongokit`'s
|
|
781
|
+
* `QueryParser`, custom hosts-side parsers) may accept only one form —
|
|
782
|
+
* MongoKit historically only accepts the bare form. If your host swaps
|
|
783
|
+
* `queryParser` to a different implementation, double-check which forms
|
|
784
|
+
* it understands before publishing URLs that mix the two.
|
|
785
|
+
*
|
|
770
786
|
* For advanced MongoDB features ($lookup, aggregations), use MongoKit's QueryParser.
|
|
771
787
|
*/
|
|
772
788
|
var ArcQueryParser = class {
|
|
@@ -930,49 +946,82 @@ var ArcQueryParser = class {
|
|
|
930
946
|
if (typeof obj !== "object") return false;
|
|
931
947
|
return Object.values(obj).some((v) => this.exceedsDepth(v, currentDepth + 1));
|
|
932
948
|
}
|
|
949
|
+
/**
|
|
950
|
+
* Parse all filter entries from the query, supporting BOTH the bare
|
|
951
|
+
* top-level form and the bracket-wrapped `filter[...]` envelope.
|
|
952
|
+
*
|
|
953
|
+
* Two equivalent ways to express the same query:
|
|
954
|
+
* - Bare (legacy, arc default): `?status=active&price[gte]=40`
|
|
955
|
+
* - Bracket envelope (REST convention): `?filter[status]=active&filter[price][gte]=40`
|
|
956
|
+
*
|
|
957
|
+
* Both forms parse to the same `filters: { status: 'active', price: { $gte: 40 } }`.
|
|
958
|
+
* The envelope form mirrors the convention used by most REST API style
|
|
959
|
+
* guides (JSON:API, Stripe API, etc.) and disambiguates filter fields
|
|
960
|
+
* from reserved meta params on busy URLs — a query string with a field
|
|
961
|
+
* literally named `page` or `sort` can't collide with the framework's
|
|
962
|
+
* meta keys when wrapped under `filter[...]`. The bare form stays as
|
|
963
|
+
* the default since it's the shortest path for simple queries; expect
|
|
964
|
+
* it to remain supported indefinitely.
|
|
965
|
+
*
|
|
966
|
+
* Precedence when the same key appears in both forms:
|
|
967
|
+
* `?status=closed&filter[status]=active` → bare wins (`status: closed`).
|
|
968
|
+
* Mixing should be rare; the deterministic rule avoids silent surprises.
|
|
969
|
+
*/
|
|
933
970
|
parseFilters(query) {
|
|
934
971
|
const filters = {};
|
|
972
|
+
const envelope = query.filter;
|
|
973
|
+
if (typeof envelope === "object" && envelope !== null && !Array.isArray(envelope)) for (const [key, value] of Object.entries(envelope)) this.applyFilterEntry(filters, key, value);
|
|
935
974
|
for (const [key, value] of Object.entries(query)) {
|
|
936
975
|
if (RESERVED_QUERY_PARAMS.has(key)) continue;
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
976
|
+
this.applyFilterEntry(filters, key, value);
|
|
977
|
+
}
|
|
978
|
+
return filters;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Apply one filter entry — used by both the bracket-envelope branch and
|
|
982
|
+
* the bare top-level branch of `parseFilters`. Centralising the field-
|
|
983
|
+
* validation + operator-conversion logic keeps the two forms in
|
|
984
|
+
* lockstep; a future regex/security tweak only changes one place.
|
|
985
|
+
*/
|
|
986
|
+
applyFilterEntry(filters, key, value) {
|
|
987
|
+
if (value === void 0 || value === null) return;
|
|
988
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_.]*(?:\[[a-z]+\])?$/.test(key)) return;
|
|
989
|
+
const bareKey = key.replace(/\[[a-z]+\]$/, "");
|
|
990
|
+
if (this._allowedFilterFields && !this._allowedFilterFields.has(bareKey)) return;
|
|
991
|
+
if (this.exceedsDepth(value)) return;
|
|
992
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
993
|
+
const operatorObj = value;
|
|
994
|
+
const operatorKeys = Object.keys(operatorObj);
|
|
995
|
+
const allOperators = operatorKeys.every((op) => this.operators[op] && (!this._allowedOperators || this._allowedOperators.has(op)));
|
|
996
|
+
const allKnownOperators = operatorKeys.every((op) => this.operators[op]);
|
|
997
|
+
if (allOperators && operatorKeys.length > 0) {
|
|
998
|
+
const mongoFilters = {};
|
|
999
|
+
let needsCaseInsensitive = false;
|
|
1000
|
+
for (const [op, opValue] of Object.entries(operatorObj)) {
|
|
1001
|
+
const mongoOp = this.operators[op];
|
|
1002
|
+
if (mongoOp) {
|
|
1003
|
+
mongoFilters[mongoOp] = this.parseFilterValue(opValue, op);
|
|
1004
|
+
if (op === "contains" || op === "like") needsCaseInsensitive = true;
|
|
955
1005
|
}
|
|
956
|
-
if (needsCaseInsensitive) mongoFilters.$options = "i";
|
|
957
|
-
filters[key] = mongoFilters;
|
|
958
|
-
continue;
|
|
959
1006
|
}
|
|
960
|
-
if (
|
|
1007
|
+
if (needsCaseInsensitive) mongoFilters.$options = "i";
|
|
1008
|
+
filters[key] = mongoFilters;
|
|
1009
|
+
return;
|
|
961
1010
|
}
|
|
962
|
-
|
|
963
|
-
if (!match) continue;
|
|
964
|
-
const [, fieldName, operator] = match;
|
|
965
|
-
if (!fieldName) continue;
|
|
966
|
-
if (operator && this.operators[operator] && (!this._allowedOperators || this._allowedOperators.has(operator))) {
|
|
967
|
-
const mongoOp = this.operators[operator];
|
|
968
|
-
const parsedValue = this.parseFilterValue(value, operator);
|
|
969
|
-
if (!filters[fieldName]) filters[fieldName] = {};
|
|
970
|
-
const fieldFilter = filters[fieldName];
|
|
971
|
-
fieldFilter[mongoOp] = parsedValue;
|
|
972
|
-
if (operator === "contains" || operator === "like") fieldFilter.$options = "i";
|
|
973
|
-
} else if (!operator) filters[fieldName] = this.parseFilterValue(value);
|
|
1011
|
+
if (allKnownOperators && this._allowedOperators) return;
|
|
974
1012
|
}
|
|
975
|
-
|
|
1013
|
+
const match = key.match(/^([a-zA-Z_][a-zA-Z0-9_.]*)(?:\[([a-z]+)\])?$/);
|
|
1014
|
+
if (!match) return;
|
|
1015
|
+
const [, fieldName, operator] = match;
|
|
1016
|
+
if (!fieldName) return;
|
|
1017
|
+
if (operator && this.operators[operator] && (!this._allowedOperators || this._allowedOperators.has(operator))) {
|
|
1018
|
+
const mongoOp = this.operators[operator];
|
|
1019
|
+
const parsedValue = this.parseFilterValue(value, operator);
|
|
1020
|
+
if (!filters[fieldName]) filters[fieldName] = {};
|
|
1021
|
+
const fieldFilter = filters[fieldName];
|
|
1022
|
+
fieldFilter[mongoOp] = parsedValue;
|
|
1023
|
+
if (operator === "contains" || operator === "like") fieldFilter.$options = "i";
|
|
1024
|
+
} else if (!operator) filters[fieldName] = this.parseFilterValue(value);
|
|
976
1025
|
}
|
|
977
1026
|
parseFilterValue(value, operator) {
|
|
978
1027
|
if (operator === "in" || operator === "nin") {
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
1
2
|
//#region src/core/aggregation/validate.ts
|
|
3
|
+
var validate_exports = /* @__PURE__ */ __exportAll({
|
|
4
|
+
ArcAggregationConfigError: () => ArcAggregationConfigError,
|
|
5
|
+
adapterSupportsAggregate: () => adapterSupportsAggregate,
|
|
6
|
+
compileAggRequest: () => compileAggRequest,
|
|
7
|
+
validateAggregations: () => validateAggregations
|
|
8
|
+
});
|
|
2
9
|
/** Thrown on aggregation misconfig at boot time. */
|
|
3
10
|
var ArcAggregationConfigError = class extends Error {
|
|
4
11
|
name = "ArcAggregationConfigError";
|
|
@@ -373,302 +380,4 @@ function assertFieldAllowed(context, ref, input) {
|
|
|
373
380
|
if (blockedFields.has(ref)) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggregationName}" references field "${ref}" in ${context}, but the field is blocked from aggregation (\`hidden: true\` or \`aggregable: false\` in schemaOptions.fieldRules). Aggregating hidden fields would leak cardinality information.`);
|
|
374
381
|
}
|
|
375
382
|
//#endregion
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Framework-agnostic aggregation execution. Runs safety guards,
|
|
379
|
-
* compiles the AggRequest, dispatches to the materialized hook or
|
|
380
|
-
* `repo.aggregate()`, and applies the post-execution `maxGroups` cap.
|
|
381
|
-
*
|
|
382
|
-
* Returns an envelope describing the response — Fastify wrappers
|
|
383
|
-
* apply it to a reply, MCP wrappers convert it to a tool-call result.
|
|
384
|
-
*
|
|
385
|
-
* **Does NOT run the per-aggregation permission check.** Auth runs
|
|
386
|
-
* upstream (Fastify preHandler chain or MCP `evaluatePermission`)
|
|
387
|
-
* because the permission shape differs by surface (FastifyRequest vs
|
|
388
|
-
* MCP session). Both surfaces fail-closed BEFORE reaching this
|
|
389
|
-
* function; this is purely the runtime executor.
|
|
390
|
-
*/
|
|
391
|
-
async function executeAggregation(normalized, deps, ctx) {
|
|
392
|
-
const { repo } = deps;
|
|
393
|
-
const config = normalized.base;
|
|
394
|
-
const aggregationName = normalized.name;
|
|
395
|
-
const { query, tenantOptions } = ctx;
|
|
396
|
-
const guardError = checkRequestGuards(query, config);
|
|
397
|
-
if (guardError) return {
|
|
398
|
-
status: 400,
|
|
399
|
-
body: guardError
|
|
400
|
-
};
|
|
401
|
-
const aggReq = compileAggRequest(normalized, extractCallerFilter(query), tenantOptions);
|
|
402
|
-
if (config.materialized) {
|
|
403
|
-
const matCtx = {
|
|
404
|
-
filter: aggReq.filter,
|
|
405
|
-
orgId: pickString(tenantOptions.organizationId),
|
|
406
|
-
userId: pickString(tenantOptions.userId),
|
|
407
|
-
requestId: pickString(tenantOptions.requestId),
|
|
408
|
-
query
|
|
409
|
-
};
|
|
410
|
-
return {
|
|
411
|
-
status: 200,
|
|
412
|
-
headers: { "x-aggregation-source": "materialized" },
|
|
413
|
-
body: { rows: (await config.materialized(matCtx)).rows }
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
if (!adapterSupportsAggregate(repo)) return {
|
|
417
|
-
status: 501,
|
|
418
|
-
body: {
|
|
419
|
-
code: "arc.adapter.capability_required",
|
|
420
|
-
message: `Aggregation "${aggregationName}" is not supported: the resource's storage adapter does not implement repo.aggregate(). Use a kit that ships StandardRepo.aggregate (mongokit / sqlitekit), or remove the aggregations entry.`,
|
|
421
|
-
status: 501,
|
|
422
|
-
meta: {
|
|
423
|
-
capability: "aggregate",
|
|
424
|
-
aggregation: aggregationName
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
let result;
|
|
429
|
-
try {
|
|
430
|
-
result = await repo.aggregate(aggReq, tenantOptions);
|
|
431
|
-
} catch (err) {
|
|
432
|
-
return mapAggregateError(err, aggregationName);
|
|
433
|
-
}
|
|
434
|
-
if (config.maxGroups !== void 0 && result.rows.length > config.maxGroups) return {
|
|
435
|
-
status: 422,
|
|
436
|
-
body: {
|
|
437
|
-
code: "arc.aggregation.max_groups_exceeded",
|
|
438
|
-
message: `Aggregation "${aggregationName}" produced ${result.rows.length} groups, exceeding maxGroups (${config.maxGroups}). Narrow the filter or raise the cap.`,
|
|
439
|
-
status: 422,
|
|
440
|
-
meta: {
|
|
441
|
-
aggregation: aggregationName,
|
|
442
|
-
produced: result.rows.length,
|
|
443
|
-
maxGroups: config.maxGroups
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
return {
|
|
448
|
-
status: 200,
|
|
449
|
-
body: { rows: result.rows }
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Build the Fastify handler for a single aggregation.
|
|
454
|
-
*
|
|
455
|
-
* The returned function calls the repo (or materialized hook), shapes
|
|
456
|
-
* the response envelope, and writes status/headers via Fastify's
|
|
457
|
-
* `reply` API. Errors throw — the router's error handler converts to
|
|
458
|
-
* the standard arc response shape.
|
|
459
|
-
*/
|
|
460
|
-
/**
|
|
461
|
-
* Build the Fastify handler for a single aggregation.
|
|
462
|
-
*
|
|
463
|
-
* Caching lives in the kit's repo-core `cachePlugin` — when the host
|
|
464
|
-
* declares `cache:` on the aggregation, `compileAggRequest` translates
|
|
465
|
-
* to `aggReq.cache: CacheOptions` and the kit handles SWR + tag
|
|
466
|
-
* invalidation + version-bump on writes. Arc passes the request
|
|
467
|
-
* through; no duplicate cache layer at the HTTP handler.
|
|
468
|
-
*/
|
|
469
|
-
function buildAggregationHandler(normalized, deps) {
|
|
470
|
-
const { buildOptions } = deps;
|
|
471
|
-
return async (request, reply) => {
|
|
472
|
-
const result = await executeAggregation(normalized, deps, {
|
|
473
|
-
query: request.query ?? {},
|
|
474
|
-
tenantOptions: buildOptions(request)
|
|
475
|
-
});
|
|
476
|
-
reply.status(result.status);
|
|
477
|
-
if (result.headers) for (const [k, v] of Object.entries(result.headers)) reply.header(k, v);
|
|
478
|
-
return result.body;
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
function pickString(value) {
|
|
482
|
-
return typeof value === "string" ? value : void 0;
|
|
483
|
-
}
|
|
484
|
-
function checkRequestGuards(query, config) {
|
|
485
|
-
if (config.requireFilters) {
|
|
486
|
-
for (const field of config.requireFilters) if (!hasFilterOnField(query, field)) return {
|
|
487
|
-
code: "arc.aggregation.required_filter_missing",
|
|
488
|
-
message: `Aggregation requires filter on "${field}" — supply ?${field}=... or ?${field}[op]=... in the query string.`,
|
|
489
|
-
status: 400,
|
|
490
|
-
meta: { field }
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
if (config.requireDateRange) {
|
|
494
|
-
const { field, maxRangeDays } = config.requireDateRange;
|
|
495
|
-
const range = parseDateRange(query, field);
|
|
496
|
-
if (!range) return {
|
|
497
|
-
code: "arc.aggregation.required_date_range_missing",
|
|
498
|
-
message: `Aggregation requires a bounded date range on "${field}" — supply ?${field}[gte]=... and ?${field}[lt]=... (or ?${field}[lte]=...).`,
|
|
499
|
-
status: 400,
|
|
500
|
-
meta: { field }
|
|
501
|
-
};
|
|
502
|
-
if (maxRangeDays !== void 0) {
|
|
503
|
-
const days = (range.upper.getTime() - range.lower.getTime()) / 864e5;
|
|
504
|
-
if (days > maxRangeDays) return {
|
|
505
|
-
code: "arc.aggregation.date_range_exceeded",
|
|
506
|
-
message: `Aggregation date range on "${field}" exceeds the cap (${maxRangeDays} days). Requested range: ${days.toFixed(1)} days. Narrow the range and retry.`,
|
|
507
|
-
status: 400,
|
|
508
|
-
meta: {
|
|
509
|
-
field,
|
|
510
|
-
maxRangeDays,
|
|
511
|
-
requestedDays: days
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
return null;
|
|
517
|
-
}
|
|
518
|
-
function hasFilterOnField(query, field) {
|
|
519
|
-
const direct = query[field];
|
|
520
|
-
if (direct !== void 0 && direct !== "") return true;
|
|
521
|
-
for (const key of Object.keys(query)) if (key.startsWith(`${field}[`)) return true;
|
|
522
|
-
return false;
|
|
523
|
-
}
|
|
524
|
-
function parseDateRange(query, field) {
|
|
525
|
-
let gte;
|
|
526
|
-
let lte;
|
|
527
|
-
const nested = query[field];
|
|
528
|
-
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
529
|
-
const ops = nested;
|
|
530
|
-
gte = pickString(ops.gte) ?? pickString(ops.gt);
|
|
531
|
-
lte = pickString(ops.lte) ?? pickString(ops.lt);
|
|
532
|
-
}
|
|
533
|
-
if (!gte) gte = pickString(query[`${field}[gte]`]) ?? pickString(query[`${field}[gt]`]);
|
|
534
|
-
if (!lte) lte = pickString(query[`${field}[lte]`]) ?? pickString(query[`${field}[lt]`]);
|
|
535
|
-
if (!gte || !lte) return null;
|
|
536
|
-
const lower = new Date(gte);
|
|
537
|
-
const upper = new Date(lte);
|
|
538
|
-
if (Number.isNaN(lower.getTime()) || Number.isNaN(upper.getTime())) return null;
|
|
539
|
-
if (upper <= lower) return null;
|
|
540
|
-
return {
|
|
541
|
-
lower,
|
|
542
|
-
upper
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
/**
|
|
546
|
-
* Bracket-syntax operator shorthand → canonical Mongo operator. Mirrors
|
|
547
|
-
* the `operators` map in `ArcQueryParser` so the aggregation route emits
|
|
548
|
-
* the same shape the CRUD list route produces. Aggregations don't run
|
|
549
|
-
* through the resource-level QueryParser (they have their own URL→IR
|
|
550
|
-
* compile path), so this translation has to happen in arc itself —
|
|
551
|
-
* downstream kits' filter compilers expect canonical `$gte/$lte/$in/...`
|
|
552
|
-
* keys, not bare `gte/lte/in/...` shorthand.
|
|
553
|
-
*/
|
|
554
|
-
const OPERATOR_SHORTHAND = {
|
|
555
|
-
eq: "$eq",
|
|
556
|
-
ne: "$ne",
|
|
557
|
-
gt: "$gt",
|
|
558
|
-
gte: "$gte",
|
|
559
|
-
lt: "$lt",
|
|
560
|
-
lte: "$lte",
|
|
561
|
-
in: "$in",
|
|
562
|
-
nin: "$nin",
|
|
563
|
-
like: "$regex",
|
|
564
|
-
contains: "$regex",
|
|
565
|
-
regex: "$regex",
|
|
566
|
-
exists: "$exists",
|
|
567
|
-
size: "$size",
|
|
568
|
-
type: "$type"
|
|
569
|
-
};
|
|
570
|
-
const SHORTHAND_RANGE_OPS = new Set([
|
|
571
|
-
"gt",
|
|
572
|
-
"gte",
|
|
573
|
-
"lt",
|
|
574
|
-
"lte"
|
|
575
|
-
]);
|
|
576
|
-
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
577
|
-
function tryCoerceDate(v) {
|
|
578
|
-
if (typeof v !== "string" || !ISO_DATE_RE.test(v)) return v;
|
|
579
|
-
const d = new Date(v);
|
|
580
|
-
return Number.isNaN(d.getTime()) ? v : d;
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* Translate a qs-parsed nested-operator object (`{ field: { gte, lte } }`)
|
|
584
|
-
* into Mongo-shape (`{ field: { $gte: Date, $lte: Date } }`). Only fires
|
|
585
|
-
* when EVERY key is a known shorthand operator — leaves user-data
|
|
586
|
-
* objects untouched so callers can still equality-match on a stored
|
|
587
|
-
* sub-document.
|
|
588
|
-
*/
|
|
589
|
-
function expandShorthandOperators(value) {
|
|
590
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
591
|
-
const nested = value;
|
|
592
|
-
const keys = Object.keys(nested);
|
|
593
|
-
if (keys.length === 0) return value;
|
|
594
|
-
if (!keys.every((k) => !k.startsWith("$") && OPERATOR_SHORTHAND[k] !== void 0)) return value;
|
|
595
|
-
const expanded = {};
|
|
596
|
-
for (const [op, opVal] of Object.entries(nested)) {
|
|
597
|
-
const mongoOp = OPERATOR_SHORTHAND[op];
|
|
598
|
-
if (!mongoOp) continue;
|
|
599
|
-
expanded[mongoOp] = SHORTHAND_RANGE_OPS.has(op) ? tryCoerceDate(opVal) : opVal;
|
|
600
|
-
}
|
|
601
|
-
return expanded;
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Strip control params (page/limit/sort/select/...) and the resource-
|
|
605
|
-
* dispatch verbs from the query, leaving only filter predicates the
|
|
606
|
-
* caller used to narrow the aggregation. Bracket-syntax operator
|
|
607
|
-
* shorthand (`createdAt[gte]=...`) gets translated to canonical Mongo-
|
|
608
|
-
* shape here so kits don't have to reimplement the URL grammar — same
|
|
609
|
-
* contract `ArcQueryParser` enforces for the CRUD list route.
|
|
610
|
-
*
|
|
611
|
-
* The resulting record is shallow-merged into the AggRequest filter
|
|
612
|
-
* via `compileAggRequest`.
|
|
613
|
-
*/
|
|
614
|
-
function extractCallerFilter(query) {
|
|
615
|
-
const out = {};
|
|
616
|
-
const reserved = new Set([
|
|
617
|
-
"page",
|
|
618
|
-
"limit",
|
|
619
|
-
"after",
|
|
620
|
-
"sort",
|
|
621
|
-
"select",
|
|
622
|
-
"populate",
|
|
623
|
-
"search",
|
|
624
|
-
"_count",
|
|
625
|
-
"_distinct",
|
|
626
|
-
"_exists"
|
|
627
|
-
]);
|
|
628
|
-
for (const [key, value] of Object.entries(query)) {
|
|
629
|
-
if (reserved.has(key)) continue;
|
|
630
|
-
if (value === void 0 || value === "") continue;
|
|
631
|
-
out[key] = expandShorthandOperators(value);
|
|
632
|
-
}
|
|
633
|
-
return out;
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Map a kit-thrown error to the framework-agnostic execute response.
|
|
637
|
-
* Detects two well-known signals:
|
|
638
|
-
* - "unsupported" / "not implemented" → 501 with upgrade hint
|
|
639
|
-
* - timeout markers → 504
|
|
640
|
-
* - everything else → 500
|
|
641
|
-
*/
|
|
642
|
-
function mapAggregateError(err, aggregationName) {
|
|
643
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
644
|
-
const lower = message.toLowerCase();
|
|
645
|
-
if (lower.includes("unsupported") || lower.includes("not implemented")) return {
|
|
646
|
-
status: 501,
|
|
647
|
-
body: {
|
|
648
|
-
code: "arc.adapter.capability_required",
|
|
649
|
-
message: `Aggregation "${aggregationName}" failed: ${message}. The kit may not yet support this feature (e.g. lookups in aggregate). Upgrade the kit or remove the unsupported field.`,
|
|
650
|
-
status: 501,
|
|
651
|
-
meta: { aggregation: aggregationName }
|
|
652
|
-
}
|
|
653
|
-
};
|
|
654
|
-
if (lower.includes("maxtimems") || lower.includes("timeout") || lower.includes("timed out")) return {
|
|
655
|
-
status: 504,
|
|
656
|
-
body: {
|
|
657
|
-
code: "arc.gateway_timeout",
|
|
658
|
-
message: `Aggregation "${aggregationName}" timed out: ${message}. Narrow the filter or raise the timeout.`,
|
|
659
|
-
status: 504,
|
|
660
|
-
meta: { aggregation: aggregationName }
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
return {
|
|
664
|
-
status: 500,
|
|
665
|
-
body: {
|
|
666
|
-
code: "arc.internal_error",
|
|
667
|
-
message: `Aggregation "${aggregationName}" failed: ${message}`,
|
|
668
|
-
status: 500,
|
|
669
|
-
meta: { aggregation: aggregationName }
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
//#endregion
|
|
674
|
-
export { executeAggregation as n, validateAggregations as r, buildAggregationHandler as t };
|
|
383
|
+
export { validate_exports as i, compileAggRequest as n, validateAggregations as r, adapterSupportsAggregate as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify - clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -48,14 +48,6 @@
|
|
|
48
48
|
"types": "./dist/auth/index.d.mts",
|
|
49
49
|
"default": "./dist/auth/index.mjs"
|
|
50
50
|
},
|
|
51
|
-
"./org": {
|
|
52
|
-
"types": "./dist/org/index.d.mts",
|
|
53
|
-
"default": "./dist/org/index.mjs"
|
|
54
|
-
},
|
|
55
|
-
"./org/types": {
|
|
56
|
-
"types": "./dist/org/types.d.mts",
|
|
57
|
-
"default": "./dist/org/types.mjs"
|
|
58
|
-
},
|
|
59
51
|
"./hooks": {
|
|
60
52
|
"types": "./dist/hooks/index.d.mts",
|
|
61
53
|
"default": "./dist/hooks/index.mjs"
|
|
@@ -227,8 +219,8 @@
|
|
|
227
219
|
"lint:fix": "biome check --fix src/",
|
|
228
220
|
"lint:all": "biome check src/ tests/",
|
|
229
221
|
"test": "vitest run",
|
|
230
|
-
"test:main": "vitest run",
|
|
231
|
-
"test:perf": "node --expose-gc ./node_modules/vitest/vitest.mjs run --config vitest.perf.config.ts",
|
|
222
|
+
"test:main": "vitest run --no-file-parallelism",
|
|
223
|
+
"test:perf": "node --expose-gc ./node_modules/vitest/vitest.mjs run --config vitest.perf.config.ts --no-file-parallelism",
|
|
232
224
|
"test:ci": "npm run test:main && npm run test:perf",
|
|
233
225
|
"test:watch": "vitest",
|
|
234
226
|
"test:ui": "vitest --ui",
|
|
@@ -245,8 +237,8 @@
|
|
|
245
237
|
"node": ">=22"
|
|
246
238
|
},
|
|
247
239
|
"peerDependencies": {
|
|
248
|
-
"@classytic/primitives": ">=0.
|
|
249
|
-
"@classytic/repo-core": ">=0.
|
|
240
|
+
"@classytic/primitives": ">=0.6.0",
|
|
241
|
+
"@classytic/repo-core": ">=0.5.0",
|
|
250
242
|
"@classytic/streamline": ">=2.3.3",
|
|
251
243
|
"@fastify/cors": ">=11.0.0",
|
|
252
244
|
"@fastify/helmet": ">=13.0.0",
|
|
@@ -349,18 +341,18 @@
|
|
|
349
341
|
},
|
|
350
342
|
"dependencies": {
|
|
351
343
|
"fastify-plugin": "^5.0.1",
|
|
352
|
-
"qs": "^6.15.
|
|
344
|
+
"qs": "^6.15.2",
|
|
353
345
|
"secure-json-parse": "^4.1.0"
|
|
354
346
|
},
|
|
355
347
|
"devDependencies": {
|
|
356
348
|
"@better-auth/drizzle-adapter": "^1.6.9",
|
|
357
349
|
"@better-auth/mongo-adapter": "^1.6.9",
|
|
358
|
-
"@biomejs/biome": "^2.4.
|
|
350
|
+
"@biomejs/biome": "^2.4.15",
|
|
359
351
|
"@classytic/dev-tools": "^0.2.0",
|
|
360
|
-
"@classytic/mongokit": "^3.
|
|
361
|
-
"@classytic/primitives": "^0.
|
|
362
|
-
"@classytic/repo-core": "^0.
|
|
363
|
-
"@classytic/sqlitekit": "^0.
|
|
352
|
+
"@classytic/mongokit": "^3.14.0",
|
|
353
|
+
"@classytic/primitives": "^0.6.0",
|
|
354
|
+
"@classytic/repo-core": "^0.5.0",
|
|
355
|
+
"@classytic/sqlitekit": "^0.4.0",
|
|
364
356
|
"@classytic/streamline": "^2.3.3",
|
|
365
357
|
"@fastify/cors": "^11.2.0",
|
|
366
358
|
"@fastify/helmet": "^13.0.2",
|
|
@@ -374,27 +366,28 @@
|
|
|
374
366
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
375
367
|
"@opentelemetry/api": "^1.9.1",
|
|
376
368
|
"@sinclair/typebox": "^0.34.0",
|
|
377
|
-
"@types/node": "^22.
|
|
378
|
-
"@types/qs": "^6.
|
|
369
|
+
"@types/node": "^22.19.19",
|
|
370
|
+
"@types/qs": "^6.15.1",
|
|
379
371
|
"@vitest/coverage-v8": "^3.2.4",
|
|
380
372
|
"ajv": "^8.18.0",
|
|
381
373
|
"better-auth": "^1.6.9",
|
|
382
|
-
"better-sqlite3": "^12.
|
|
383
|
-
"bullmq": "^5.
|
|
374
|
+
"better-sqlite3": "^12.10.0",
|
|
375
|
+
"bullmq": "^5.76.9",
|
|
384
376
|
"dotenv": "^17.4.2",
|
|
385
377
|
"drizzle-orm": "^0.45.2",
|
|
386
|
-
"fast-check": "^4.
|
|
378
|
+
"fast-check": "^4.8.0",
|
|
379
|
+
"fastify": "^5.8.5",
|
|
387
380
|
"fastify-raw-body": "^5.0.0",
|
|
388
381
|
"ioredis": "^5.10.1",
|
|
389
382
|
"jsonwebtoken": "^9.0.0",
|
|
390
|
-
"knip": "^6.
|
|
383
|
+
"knip": "^6.14.1",
|
|
391
384
|
"mongodb": "^7.1.0",
|
|
392
385
|
"mongodb-memory-server": "^11.0.1",
|
|
393
|
-
"mongoose": "
|
|
386
|
+
"mongoose": "^9.6.2",
|
|
394
387
|
"tsdown": "^0.21.7",
|
|
395
388
|
"typescript": "^6.0.2",
|
|
396
389
|
"vitest": "^3.0.0",
|
|
397
|
-
"ws": "^8.
|
|
390
|
+
"ws": "^8.20.1",
|
|
398
391
|
"zod": "^4.3.6"
|
|
399
392
|
},
|
|
400
393
|
"keywords": [
|
|
@@ -420,4 +413,4 @@
|
|
|
420
413
|
"type": "git",
|
|
421
414
|
"url": "https://github.com/classytic/arc.git"
|
|
422
415
|
}
|
|
423
|
-
}
|
|
416
|
+
}
|