@classytic/arc 2.5.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -127,6 +127,6 @@ function transform(name, handlerOrOptions) {
127
127
  }
128
128
  //#endregion
129
129
  //#region src/index.ts
130
- const version = "2.5.0";
130
+ const version = "2.5.1";
131
131
  //#endregion
132
132
  export { ArcError, BaseController, 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, MongooseAdapter, NotFoundError, PrismaAdapter, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, UnauthorizedError, ValidationError, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, arcLog, assertValidConfig, authenticated, configureArcLogger, createDomainError, createDynamicPermissionMatrix, createMongooseAdapter, createOrgPermissions, createPrismaAdapter, defineResource, denyAll, envelope, fields, formatValidationErrors, fullPublic, getControllerScope, guard, intercept, middleware, ownerWithAdminBypass, presets_exports as permissions, pipe, publicRead, publicReadAdminWrite, readOnly, requestContext, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, sortMiddlewares, transform, validateResourceConfig, version, when };
@@ -75,6 +75,8 @@ interface FieldRulesToZodOptions {
75
75
  extraHideFields?: string[];
76
76
  /** Filterable fields — only used in list mode */
77
77
  filterableFields?: readonly string[];
78
+ /** Allowed filter operators — generates `field[op]` entries in list mode (e.g., price[gt], price[lte]) */
79
+ allowedOperators?: readonly string[];
78
80
  }
79
81
  /** Single field rule entry from Arc's schemaOptions.fieldRules */
80
82
  interface FieldRuleEntry {
@@ -1,4 +1,4 @@
1
- import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-CN0lwJrL.mjs";
1
+ import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-B1B1svLx.mjs";
2
2
  import { createHash } from "node:crypto";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/integrations/mcp/definePrompt.ts
@@ -1,4 +1,4 @@
1
- import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-CN0lwJrL.mjs";
1
+ import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-B1B1svLx.mjs";
2
2
  //#region src/integrations/mcp/testing.ts
3
3
  /**
4
4
  * @classytic/arc/mcp/testing — MCP Test Utilities
@@ -44,7 +44,7 @@ try {
44
44
  function createTracerProvider(options) {
45
45
  if (!isAvailable) return null;
46
46
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
47
- const resolvedVersion = serviceVersion ?? "2.5.0";
47
+ const resolvedVersion = serviceVersion ?? "2.5.1";
48
48
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
49
49
  const provider = new NodeTracerProvider({ resource: { attributes: {
50
50
  "service.name": serviceName,
@@ -148,16 +148,55 @@ function typeToZod(type) {
148
148
  default: return z.string();
149
149
  }
150
150
  }
151
- /** Build list/query shape with filterable fields + pagination */
151
+ /** Operators that apply to numeric/date fields */
152
+ const COMPARISON_OPS = new Set([
153
+ "gt",
154
+ "gte",
155
+ "lt",
156
+ "lte"
157
+ ]);
158
+ /** Map operator to a human-readable description suffix */
159
+ function opDescription(op, fieldName) {
160
+ switch (op) {
161
+ case "gt": return `${fieldName} greater than`;
162
+ case "gte": return `${fieldName} greater than or equal`;
163
+ case "lt": return `${fieldName} less than`;
164
+ case "lte": return `${fieldName} less than or equal`;
165
+ case "ne": return `${fieldName} not equal to`;
166
+ case "in": return `${fieldName} in comma-separated list`;
167
+ case "nin": return `${fieldName} not in comma-separated list`;
168
+ case "exists": return `${fieldName} exists (true/false)`;
169
+ default: return `${fieldName} ${op}`;
170
+ }
171
+ }
172
+ /** Build list/query shape with filterable fields, operators, and pagination */
152
173
  function buildListShape(fieldRules, options) {
153
- const { filterableFields = [], hiddenFields = [], extraHideFields = [] } = options;
174
+ const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators } = options;
154
175
  const allHidden = new Set([...hiddenFields, ...extraHideFields]);
155
176
  const shape = { ...PAGINATION_SHAPE };
156
- if (fieldRules) for (const name of filterableFields) {
177
+ if (!fieldRules) return shape;
178
+ for (const name of filterableFields) {
157
179
  if (allHidden.has(name)) continue;
158
180
  const rule = fieldRules[name];
159
181
  if (!rule) continue;
160
- shape[name] = buildFieldSchema(rule).optional();
182
+ const filterField = buildFieldSchema(rule);
183
+ shape[name] = filterField.optional();
184
+ if (allowedOperators?.length) {
185
+ const isNumericOrDate = rule.type === "number" || rule.type === "date";
186
+ for (const op of allowedOperators) {
187
+ if (COMPARISON_OPS.has(op) && !isNumericOrDate) continue;
188
+ if (op === "eq") continue;
189
+ if (op === "exists") {
190
+ shape[`${name}_${op}`] = z.boolean().optional().describe(opDescription(op, name));
191
+ continue;
192
+ }
193
+ if (op === "in" || op === "nin") {
194
+ shape[`${name}_${op}`] = z.string().optional().describe(opDescription(op, name));
195
+ continue;
196
+ }
197
+ shape[`${name}_${op}`] = filterField.optional().describe(opDescription(op, name));
198
+ }
199
+ }
161
200
  }
162
201
  return shape;
163
202
  }
@@ -193,7 +232,7 @@ function buildRequestContext(input, auth, operation, policyFilters) {
193
232
  case "list": return {
194
233
  ...base,
195
234
  params: {},
196
- query: { ...input },
235
+ query: expandOperatorKeys(input),
197
236
  body: void 0
198
237
  };
199
238
  case "get": return {
@@ -225,6 +264,40 @@ function buildRequestContext(input, auth, operation, policyFilters) {
225
264
  };
226
265
  }
227
266
  }
267
+ /** Convert MCP operator keys (`price_gt`) to MongoKit bracket notation (`price[gt]`). */
268
+ const OPERATOR_SUFFIXES = new Set([
269
+ "eq",
270
+ "ne",
271
+ "gt",
272
+ "gte",
273
+ "lt",
274
+ "lte",
275
+ "in",
276
+ "nin",
277
+ "exists"
278
+ ]);
279
+ function expandOperatorKeys(input) {
280
+ const out = {};
281
+ for (const [key, value] of Object.entries(input)) {
282
+ const lastUnderscore = key.lastIndexOf("_");
283
+ if (lastUnderscore > 0) {
284
+ const op = key.slice(lastUnderscore + 1);
285
+ if (OPERATOR_SUFFIXES.has(op)) {
286
+ const field = key.slice(0, lastUnderscore);
287
+ const existing = out[field];
288
+ if (existing && typeof existing === "object" && existing !== null) existing[op] = value;
289
+ else if (existing === void 0) out[field] = { [op]: value };
290
+ else out[field] = {
291
+ eq: existing,
292
+ [op]: value
293
+ };
294
+ continue;
295
+ }
296
+ }
297
+ out[key] = value;
298
+ }
299
+ return out;
300
+ }
228
301
  function buildScope(auth) {
229
302
  if (!auth) return { kind: "public" };
230
303
  if (auth.organizationId) return {
@@ -314,7 +387,8 @@ function resourceToTools(resource, config = {}) {
314
387
  hiddenFields,
315
388
  readonlyFields,
316
389
  extraHideFields: config.hideFields,
317
- filterableFields
390
+ filterableFields,
391
+ allowedOperators
318
392
  }),
319
393
  handler: createHandler(op, controller, resource.name, resource.permissions)
320
394
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {