@classytic/arc 2.14.1 → 2.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{buildHandler-BamHHpH8.mjs → buildHandler-jSZ6Fdvi.mjs} +64 -4
- package/dist/core/index.mjs +1 -1
- package/dist/{core-DEdN6zKD.mjs → core-D29kkRL5.mjs} +1 -1
- package/dist/{createAggregationRouter-Bk-58SbZ.mjs → createAggregationRouter-DhR-Ofiz.mjs} +1 -1
- package/dist/index.mjs +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/{resourceToTools-CZ-ZhS7v.mjs → resourceToTools-BM686jB4.mjs} +1 -1
- package/dist/testing/index.mjs +10 -13
- package/package.json +1 -1
|
@@ -560,13 +560,73 @@ function parseDateRange(query, field) {
|
|
|
560
560
|
};
|
|
561
561
|
}
|
|
562
562
|
/**
|
|
563
|
+
* Bracket-syntax operator shorthand → canonical Mongo operator. Mirrors
|
|
564
|
+
* the `operators` map in `ArcQueryParser` so the aggregation route emits
|
|
565
|
+
* the same shape the CRUD list route produces. Aggregations don't run
|
|
566
|
+
* through the resource-level QueryParser (they have their own URL→IR
|
|
567
|
+
* compile path), so this translation has to happen in arc itself —
|
|
568
|
+
* downstream kits' filter compilers expect canonical `$gte/$lte/$in/...`
|
|
569
|
+
* keys, not bare `gte/lte/in/...` shorthand.
|
|
570
|
+
*/
|
|
571
|
+
const OPERATOR_SHORTHAND = {
|
|
572
|
+
eq: "$eq",
|
|
573
|
+
ne: "$ne",
|
|
574
|
+
gt: "$gt",
|
|
575
|
+
gte: "$gte",
|
|
576
|
+
lt: "$lt",
|
|
577
|
+
lte: "$lte",
|
|
578
|
+
in: "$in",
|
|
579
|
+
nin: "$nin",
|
|
580
|
+
like: "$regex",
|
|
581
|
+
contains: "$regex",
|
|
582
|
+
regex: "$regex",
|
|
583
|
+
exists: "$exists",
|
|
584
|
+
size: "$size",
|
|
585
|
+
type: "$type"
|
|
586
|
+
};
|
|
587
|
+
const SHORTHAND_RANGE_OPS = new Set([
|
|
588
|
+
"gt",
|
|
589
|
+
"gte",
|
|
590
|
+
"lt",
|
|
591
|
+
"lte"
|
|
592
|
+
]);
|
|
593
|
+
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
594
|
+
function tryCoerceDate(v) {
|
|
595
|
+
if (typeof v !== "string" || !ISO_DATE_RE.test(v)) return v;
|
|
596
|
+
const d = new Date(v);
|
|
597
|
+
return Number.isNaN(d.getTime()) ? v : d;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Translate a qs-parsed nested-operator object (`{ field: { gte, lte } }`)
|
|
601
|
+
* into Mongo-shape (`{ field: { $gte: Date, $lte: Date } }`). Only fires
|
|
602
|
+
* when EVERY key is a known shorthand operator — leaves user-data
|
|
603
|
+
* objects untouched so callers can still equality-match on a stored
|
|
604
|
+
* sub-document.
|
|
605
|
+
*/
|
|
606
|
+
function expandShorthandOperators(value) {
|
|
607
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
608
|
+
const nested = value;
|
|
609
|
+
const keys = Object.keys(nested);
|
|
610
|
+
if (keys.length === 0) return value;
|
|
611
|
+
if (!keys.every((k) => !k.startsWith("$") && OPERATOR_SHORTHAND[k] !== void 0)) return value;
|
|
612
|
+
const expanded = {};
|
|
613
|
+
for (const [op, opVal] of Object.entries(nested)) {
|
|
614
|
+
const mongoOp = OPERATOR_SHORTHAND[op];
|
|
615
|
+
if (!mongoOp) continue;
|
|
616
|
+
expanded[mongoOp] = SHORTHAND_RANGE_OPS.has(op) ? tryCoerceDate(opVal) : opVal;
|
|
617
|
+
}
|
|
618
|
+
return expanded;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
563
621
|
* Strip control params (page/limit/sort/select/...) and the resource-
|
|
564
622
|
* dispatch verbs from the query, leaving only filter predicates the
|
|
565
|
-
* caller used to narrow the aggregation.
|
|
623
|
+
* caller used to narrow the aggregation. Bracket-syntax operator
|
|
624
|
+
* shorthand (`createdAt[gte]=...`) gets translated to canonical Mongo-
|
|
625
|
+
* shape here so kits don't have to reimplement the URL grammar — same
|
|
626
|
+
* contract `ArcQueryParser` enforces for the CRUD list route.
|
|
566
627
|
*
|
|
567
628
|
* The resulting record is shallow-merged into the AggRequest filter
|
|
568
|
-
* via `compileAggRequest`.
|
|
569
|
-
* preserved — the kit's filter compiler handles them.
|
|
629
|
+
* via `compileAggRequest`.
|
|
570
630
|
*/
|
|
571
631
|
function extractCallerFilter(query) {
|
|
572
632
|
const out = {};
|
|
@@ -585,7 +645,7 @@ function extractCallerFilter(query) {
|
|
|
585
645
|
for (const [key, value] of Object.entries(query)) {
|
|
586
646
|
if (reserved.has(key)) continue;
|
|
587
647
|
if (value === void 0 || value === "") continue;
|
|
588
|
-
out[key] = value;
|
|
648
|
+
out[key] = expandShorthandOperators(value);
|
|
589
649
|
}
|
|
590
650
|
return out;
|
|
591
651
|
}
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as DEFAULT_SORT, c as HOOK_OPERATIONS, d as MAX_REGEX_LENGTH, f as MAX_SEARCH_LENGTH, h as SYSTEM_FIELDS, i as DEFAULT_MAX_LIMIT, l as HOOK_PHASES, m as RESERVED_QUERY_PARAMS, n as DEFAULT_ID_FIELD, o as DEFAULT_TENANT_FIELD, p as MUTATION_OPERATIONS, r as DEFAULT_LIMIT, s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS, u as MAX_FILTER_DEPTH } from "../constants-Cxde4rpC.mjs";
|
|
2
2
|
import { a as BulkMixin, c as collectReadBlockedFields, d as AccessControl, i as SlugMixin, l as isFieldReadable, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, s as QueryResolver, t as BaseController, u as BodySanitizer } from "../BaseController-Dv60tU83.mjs";
|
|
3
3
|
import { _ as getControllerContext, g as createRequestContext, h as createFastifyHandler, m as createCrudHandlers, v as getControllerScope, y as sendControllerResponse } from "../routerShared-DrOa-26E.mjs";
|
|
4
|
-
import { a as defineResource, c as createPermissionMiddleware, i as defineResourceVariants, l as defineAggregation, n as getEntityIdField, o as ResourceDefinition, r as getEntityQuery, s as createCrudRouter, t as getEntityId } from "../core-
|
|
4
|
+
import { a as defineResource, c as createPermissionMiddleware, i as defineResourceVariants, l as defineAggregation, n as getEntityIdField, o as ResourceDefinition, r as getEntityQuery, s as createCrudRouter, t as getEntityId } from "../core-D29kkRL5.mjs";
|
|
5
5
|
export { AccessControl, BaseController, BaseCrudController, BodySanitizer, BulkMixin, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, QueryResolver, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugMixin, SoftDeleteMixin, TreeMixin, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAggregation, defineResource, defineResourceVariants, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, sendControllerResponse };
|
|
@@ -945,7 +945,7 @@ function buildResourcePlugin(resource) {
|
|
|
945
945
|
});
|
|
946
946
|
}
|
|
947
947
|
if (resource.aggregations && Object.keys(resource.aggregations).length > 0) {
|
|
948
|
-
const { createAggregationRouter } = await import("./createAggregationRouter-
|
|
948
|
+
const { createAggregationRouter } = await import("./createAggregationRouter-DhR-Ofiz.mjs");
|
|
949
949
|
const repoForAgg = resource.controller?.repository;
|
|
950
950
|
const buildOptions = (req) => {
|
|
951
951
|
return resource.controller?.tenantRepoOptions?.(req) ?? {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { f as createError, l as UnauthorizedError, r as ForbiddenError } from "./errors-j4aJm1Wg.mjs";
|
|
2
2
|
import { c as buildPreHandlerChain, f as resolveRouterPluginMw, i as buildAuthMiddleware, l as buildRateLimitConfig, p as selectPluginMw, r as buildArcDecorator } from "./routerShared-DrOa-26E.mjs";
|
|
3
|
-
import { r as validateAggregations, t as buildAggregationHandler } from "./buildHandler-
|
|
3
|
+
import { r as validateAggregations, t as buildAggregationHandler } from "./buildHandler-jSZ6Fdvi.mjs";
|
|
4
4
|
//#region src/core/aggregation/createAggregationRouter.ts
|
|
5
5
|
/**
|
|
6
6
|
* Register one Fastify route per aggregation. No-op when the map is
|
package/dist/index.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import { t as getUserId } from "./utils-_h9B3c57.mjs";
|
|
|
4
4
|
import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-Dv60tU83.mjs";
|
|
5
5
|
import { C as allowPublic, D as requireAuth, O as requireOwnership, S as allOf, T as denyAll, _ as requireOrgMembership, a as presets_exports, b as requireServiceScope, c as readOnly, d as applyFieldWritePermissions, f as fields, g as requireOrgInScope, h as createOrgPermissions, i as ownerWithAdminBypass, j as when, k as requireRoles, m as createDynamicPermissionMatrix, n as authenticated, o as publicRead, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as applyFieldReadPermissions, v as requireOrgRole, w as anyOf, x as requireTeamMembership, y as requireScopeContext } from "./permissions-ohQyv50e.mjs";
|
|
6
6
|
import { v as getControllerScope } from "./routerShared-DrOa-26E.mjs";
|
|
7
|
-
import { a as defineResource, i as defineResourceVariants, l as defineAggregation, o as ResourceDefinition } from "./core-
|
|
7
|
+
import { a as defineResource, i as defineResourceVariants, l as defineAggregation, o as ResourceDefinition } from "./core-D29kkRL5.mjs";
|
|
8
8
|
//#region src/index.ts
|
|
9
|
-
const version = "2.14.
|
|
9
|
+
const version = "2.14.2";
|
|
10
10
|
//#endregion
|
|
11
11
|
export { ArcError, BaseController, BaseCrudController, BulkMixin, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, ForbiddenError, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, NotFoundError, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugMixin, SoftDeleteMixin, TreeMixin, UnauthorizedError, ValidationError, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDomainError, createDynamicPermissionMatrix, createOrgPermissions, defineAggregation, defineResource, defineResourceVariants, denyAll, fields, fullPublic, getControllerScope, getUserId, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, version, when };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-
|
|
1
|
+
import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-BM686jB4.mjs";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import fp from "fastify-plugin";
|
|
4
4
|
//#region src/integrations/mcp/defineTool.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-
|
|
1
|
+
import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-BM686jB4.mjs";
|
|
2
2
|
//#region src/integrations/mcp/testing.ts
|
|
3
3
|
/**
|
|
4
4
|
* @classytic/arc/mcp/testing — MCP Test Utilities
|
|
@@ -58,7 +58,7 @@ try {
|
|
|
58
58
|
function createTracerProvider(options) {
|
|
59
59
|
if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
|
|
60
60
|
const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
|
|
61
|
-
const resolvedVersion = serviceVersion ?? "2.14.
|
|
61
|
+
const resolvedVersion = serviceVersion ?? "2.14.2";
|
|
62
62
|
const exporter = new OTLPTraceExporter({ url: exporterUrl });
|
|
63
63
|
const provider = new NodeTracerProvider({ resource: { attributes: {
|
|
64
64
|
"service.name": serviceName,
|
|
@@ -3,7 +3,7 @@ import { t as BaseController } from "./BaseController-Dv60tU83.mjs";
|
|
|
3
3
|
import { L as normalizePermissionResult } from "./permissions-ohQyv50e.mjs";
|
|
4
4
|
import { t as executePipeline } from "./pipe-Zr0KXjQe.mjs";
|
|
5
5
|
import { u as resolvePipelineSteps } from "./routerShared-DrOa-26E.mjs";
|
|
6
|
-
import { n as executeAggregation, r as validateAggregations } from "./buildHandler-
|
|
6
|
+
import { n as executeAggregation, r as validateAggregations } from "./buildHandler-jSZ6Fdvi.mjs";
|
|
7
7
|
import { t as resolveActionPermission } from "./actionPermissions-CyUkQu6O.mjs";
|
|
8
8
|
import { i as shouldRejectAdditionalProperties, r as schemaIRToZodShape, t as normalizeSchemaIR } from "./schemaIR-lYhC2gE5.mjs";
|
|
9
9
|
import { t as pluralize } from "./pluralize-DQgqgifU.mjs";
|
package/dist/testing/index.mjs
CHANGED
|
@@ -564,9 +564,8 @@ var HttpTestHarness = class {
|
|
|
564
564
|
});
|
|
565
565
|
expect(res.statusCode).toBeLessThan(300);
|
|
566
566
|
const body = JSON.parse(res.body);
|
|
567
|
-
expect(body.
|
|
568
|
-
|
|
569
|
-
createdId = body.data._id;
|
|
567
|
+
expect(body._id).toBeDefined();
|
|
568
|
+
createdId = body._id;
|
|
570
569
|
});
|
|
571
570
|
if (enabledRoutes.has("list")) it("GET should list resources", async () => {
|
|
572
571
|
const { app } = this.getOptions();
|
|
@@ -576,9 +575,7 @@ var HttpTestHarness = class {
|
|
|
576
575
|
headers: this.adminHeaders()
|
|
577
576
|
});
|
|
578
577
|
expect(res.statusCode).toBe(200);
|
|
579
|
-
const
|
|
580
|
-
expect(body.success).toBe(true);
|
|
581
|
-
const list = body.data ?? body.data;
|
|
578
|
+
const list = JSON.parse(res.body).data;
|
|
582
579
|
expect(Array.isArray(list)).toBe(true);
|
|
583
580
|
});
|
|
584
581
|
if (enabledRoutes.has("get")) {
|
|
@@ -591,7 +588,7 @@ var HttpTestHarness = class {
|
|
|
591
588
|
headers: this.adminHeaders()
|
|
592
589
|
});
|
|
593
590
|
expect(res.statusCode).toBe(200);
|
|
594
|
-
expect(JSON.parse(res.body).
|
|
591
|
+
expect(JSON.parse(res.body)._id).toBe(createdId);
|
|
595
592
|
});
|
|
596
593
|
it("GET /:id with non-existent ID should return 404", async () => {
|
|
597
594
|
const { app } = this.getOptions();
|
|
@@ -601,7 +598,7 @@ var HttpTestHarness = class {
|
|
|
601
598
|
headers: this.adminHeaders()
|
|
602
599
|
});
|
|
603
600
|
expect(res.statusCode).toBe(404);
|
|
604
|
-
expect(JSON.parse(res.body).
|
|
601
|
+
expect(JSON.parse(res.body).status).toBe(404);
|
|
605
602
|
});
|
|
606
603
|
}
|
|
607
604
|
if (enabledRoutes.has("update")) for (const verb of updateMethods) {
|
|
@@ -616,7 +613,7 @@ var HttpTestHarness = class {
|
|
|
616
613
|
payload
|
|
617
614
|
});
|
|
618
615
|
expect(res.statusCode).toBe(200);
|
|
619
|
-
expect(JSON.parse(res.body).
|
|
616
|
+
expect(JSON.parse(res.body)._id).toBe(createdId);
|
|
620
617
|
});
|
|
621
618
|
it(`${verb} /:id with non-existent ID should return 404`, async () => {
|
|
622
619
|
const { app, fixtures } = this.getOptions();
|
|
@@ -640,7 +637,7 @@ var HttpTestHarness = class {
|
|
|
640
637
|
headers: this.adminHeaders(),
|
|
641
638
|
payload: fixtures.valid
|
|
642
639
|
});
|
|
643
|
-
deleteId = JSON.parse(createRes.body).
|
|
640
|
+
deleteId = JSON.parse(createRes.body)._id;
|
|
644
641
|
}
|
|
645
642
|
if (!deleteId) return;
|
|
646
643
|
expect((await app.inject({
|
|
@@ -731,9 +728,9 @@ var HttpTestHarness = class {
|
|
|
731
728
|
});
|
|
732
729
|
expect(res.statusCode).toBeLessThan(400);
|
|
733
730
|
const body = JSON.parse(res.body);
|
|
734
|
-
if (body.
|
|
731
|
+
if (body._id && enabledRoutes.has("delete")) await app.inject({
|
|
735
732
|
method: "DELETE",
|
|
736
|
-
url: `${this.getBaseUrl()}/${body.
|
|
733
|
+
url: `${this.getBaseUrl()}/${body._id}`,
|
|
737
734
|
headers: this.adminHeaders()
|
|
738
735
|
});
|
|
739
736
|
});
|
|
@@ -753,7 +750,7 @@ var HttpTestHarness = class {
|
|
|
753
750
|
payload: fixtures.invalid
|
|
754
751
|
});
|
|
755
752
|
expect(res.statusCode).toBeGreaterThanOrEqual(400);
|
|
756
|
-
expect(JSON.parse(res.body).
|
|
753
|
+
expect(JSON.parse(res.body).status).toBeGreaterThanOrEqual(400);
|
|
757
754
|
});
|
|
758
755
|
});
|
|
759
756
|
}
|