@classytic/arc 2.16.0 → 2.16.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.
Files changed (117) hide show
  1. package/dist/{BaseController-DlCCTIxJ.mjs → BaseController-Vk8I7C0x.mjs} +1 -1
  2. package/dist/{QueryCache-SvmT_9ti.d.mts → QueryCache-8YmAQ-zW.d.mts} +1 -1
  3. package/dist/{ResourceRegistry-f48hFk3m.mjs → ResourceRegistry-CzSzVK1m.mjs} +3 -0
  4. package/dist/audit/index.d.mts +1 -1
  5. package/dist/auth/index.d.mts +4 -4
  6. package/dist/auth/redis-session.d.mts +1 -1
  7. package/dist/{buildHandler-BZX6zzDM.mjs → buildHandler-C_SkBNF3.mjs} +6 -4
  8. package/dist/cache/index.d.mts +3 -3
  9. package/dist/cli/commands/describe.d.mts +1 -1
  10. package/dist/cli/commands/describe.mjs +1 -1
  11. package/dist/cli/commands/docs.mjs +2 -2
  12. package/dist/cli/commands/generate.d.mts +1 -1
  13. package/dist/cli/commands/generate.mjs +1 -1
  14. package/dist/cli/commands/init.d.mts +1 -1
  15. package/dist/cli/commands/init.mjs +1 -1
  16. package/dist/cli/commands/introspect.mjs +1 -1
  17. package/dist/cli/index.d.mts +2 -2
  18. package/dist/cli/index.mjs +2 -2
  19. package/dist/core/index.d.mts +2 -2
  20. package/dist/core/index.mjs +3 -3
  21. package/dist/{core-DBJ_j6rX.mjs → core-wKeGXn38.mjs} +5 -5
  22. package/dist/{createActionRouter-DUpN3Dd1.mjs → createActionRouter-eiOFtnC3.mjs} +2 -2
  23. package/dist/{createAggregationRouter-Dq-TUCuY.mjs → createAggregationRouter-C4unhBnV.mjs} +3 -3
  24. package/dist/{createApp-DNccuhyI.mjs → createApp-DdPBtmzp.mjs} +8 -8
  25. package/dist/{defineEvent-DRwY0fYm.mjs → defineEvent-NPKUJRt8.mjs} +1 -1
  26. package/dist/docs/index.d.mts +2 -2
  27. package/dist/docs/index.mjs +1 -1
  28. package/dist/{eventPlugin-C2cGqtRO.mjs → eventPlugin-CnJCUyM0.mjs} +1 -1
  29. package/dist/{eventPlugin-CtHC_av1.d.mts → eventPlugin-DjEx_J3j.d.mts} +1 -1
  30. package/dist/events/index.d.mts +3 -3
  31. package/dist/events/index.mjs +5 -5
  32. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  33. package/dist/events/transports/redis.d.mts +1 -1
  34. package/dist/factory/index.d.mts +2 -2
  35. package/dist/factory/index.mjs +4 -3
  36. package/dist/fastify-augmentation-CJm0vh-l.d.mts +42 -0
  37. package/dist/{fields-Anj0xdih.d.mts → fields-DqZLp5d6.d.mts} +1 -1
  38. package/dist/hooks/index.d.mts +1 -1
  39. package/dist/idempotency/index.d.mts +2 -2
  40. package/dist/idempotency/index.mjs +1 -1
  41. package/dist/idempotency/redis.d.mts +1 -1
  42. package/dist/{index-3oIimXQn.d.mts → index-3L-adFJO.d.mts} +3 -3
  43. package/dist/{index-B-ulKx5P.d.mts → index-CC_pgZjY.d.mts} +36 -16
  44. package/dist/{index-CkW0flkU.d.mts → index-DBDgy6Hw.d.mts} +79 -7
  45. package/dist/index.d.mts +5 -5
  46. package/dist/index.mjs +4 -4
  47. package/dist/integrations/event-gateway.d.mts +2 -2
  48. package/dist/integrations/event-gateway.mjs +1 -1
  49. package/dist/integrations/index.d.mts +2 -2
  50. package/dist/integrations/mcp/index.d.mts +4 -4
  51. package/dist/integrations/mcp/index.mjs +1 -1
  52. package/dist/integrations/mcp/testing.d.mts +1 -1
  53. package/dist/integrations/mcp/testing.mjs +1 -1
  54. package/dist/integrations/websocket-redis.d.mts +1 -1
  55. package/dist/integrations/websocket.d.mts +1 -1
  56. package/dist/{loadResourcesFromEntry-BLMEI2Xa.mjs → loadResourcesFromEntry-DCjYMgfC.mjs} +1 -1
  57. package/dist/{mcpPlugin-7vGV51ED.mjs → mcpPlugin-Dbgz9_Rj.mjs} +5 -5
  58. package/dist/middleware/index.d.mts +1 -1
  59. package/dist/{openapi-34T9yNwd.mjs → openapi-BRnd3pT7.mjs} +36 -9
  60. package/dist/permissions/index.d.mts +2 -2
  61. package/dist/pipeline/index.d.mts +1 -1
  62. package/dist/plugins/index.d.mts +5 -59
  63. package/dist/plugins/index.mjs +7 -7
  64. package/dist/plugins/tracing-entry.d.mts +1 -1
  65. package/dist/plugins/tracing-entry.mjs +1 -1
  66. package/dist/preloadResources-BOXzPhKU.mjs +45 -0
  67. package/dist/presets/filesUpload.d.mts +4 -4
  68. package/dist/presets/index.d.mts +1 -1
  69. package/dist/presets/multiTenant.d.mts +1 -1
  70. package/dist/presets/search.d.mts +2 -2
  71. package/dist/{queryCachePlugin-Biqzfbi5.d.mts → queryCachePlugin-Do4P_WBC.d.mts} +2 -2
  72. package/dist/{redis-Cyzrz6SX.d.mts → redis-cpqVb85w.d.mts} +1 -1
  73. package/dist/{redis-stream-DT-YjzrB.d.mts → redis-stream-BuiAS3aE.d.mts} +1 -1
  74. package/dist/registry/index.d.mts +30 -8
  75. package/dist/registry/index.mjs +2 -2
  76. package/dist/{registry-BBE23CDj.mjs → registry-BrlHkrRS.mjs} +14 -8
  77. package/dist/{routerShared-CZV5aabX.mjs → routerShared-ClV4cCaa.mjs} +37 -13
  78. package/dist/scope/index.d.mts +2 -2
  79. package/dist/testing/index.d.mts +2 -25
  80. package/dist/testing/index.mjs +2 -45
  81. package/dist/testing/storageContract.d.mts +1 -1
  82. package/dist/types/index.d.mts +4 -4
  83. package/dist/types/storage.d.mts +1 -1
  84. package/dist/{types-BsJMEQ4D.d.mts → types-BD0BCdf6.d.mts} +1 -1
  85. package/dist/{types-D-fYtKjb.d.mts → types-Dw--SyTq.d.mts} +34 -11
  86. package/dist/utils/index.d.mts +5 -4
  87. package/dist/utils/index.mjs +1 -1
  88. package/dist/{utils-DC5ycPfr.mjs → utils-DPXZnZUx.mjs} +69 -1
  89. package/dist/{validate-By96rH0r.mjs → validate-DFIP2VrM.mjs} +65 -3
  90. package/dist/{versioning-ZwX9tmbS.d.mts → versioning-5H4930tj.d.mts} +1 -1
  91. package/package.json +3 -3
  92. /package/dist/{EventTransport-C-2oAHtw.d.mts → EventTransport-B1GFiYUy.d.mts} +0 -0
  93. /package/dist/{EventTransport-Hxvv5QQz.mjs → EventTransport-ClEDfiMS.mjs} +0 -0
  94. /package/dist/{caching-TeHE8G-v.mjs → caching-8MfrrUYU.mjs} +0 -0
  95. /package/dist/{elevation-0YBpa663.d.mts → elevation-DvwtjDwu.d.mts} +0 -0
  96. /package/dist/{errorHandler-DpoXQHZ9.mjs → errorHandler-DNPzqaTT.mjs} +0 -0
  97. /package/dist/{errorHandler-mHuyWzZE.d.mts → errorHandler-DuqTqeqn.d.mts} +0 -0
  98. /package/dist/{errors-C1lX_jlm.d.mts → errors-4ZloYFvm.d.mts} +0 -0
  99. /package/dist/{externalPaths-DFg-2KTp.d.mts → externalPaths-ClykOQqa.d.mts} +0 -0
  100. /package/dist/{generate-CYac-OLv.mjs → generate-CcM24hoq.mjs} +0 -0
  101. /package/dist/{generate-BWFwgcCM.d.mts → generate-cRnsafMS.d.mts} +0 -0
  102. /package/dist/{init-Dv71MsJr.d.mts → init-D-vrTZ1G.d.mts} +0 -0
  103. /package/dist/{init-HDvoO9L5.mjs → init-DYzfniuX.mjs} +0 -0
  104. /package/dist/{interface-CH0OQudo.d.mts → interface-BVkZsvEe.d.mts} +0 -0
  105. /package/dist/{interface-NwJ_qPlY.d.mts → interface-DoE6UoC2.d.mts} +0 -0
  106. /package/dist/{loadResources-ChQEj8ih.mjs → loadResources-fol6JuvW.mjs} +0 -0
  107. /package/dist/{metrics-TuOmguhi.mjs → metrics-B-PU4-Yu.mjs} +0 -0
  108. /package/dist/{replyHelpers-C-gD32oF.mjs → replyHelpers-boS5Rx26.mjs} +0 -0
  109. /package/dist/{schemaIR-Ctc89DSn.mjs → schemaIR-DAOvBWgT.mjs} +0 -0
  110. /package/dist/{sessionManager-BqFegc0W.d.mts → sessionManager-DlmaHUAl.d.mts} +0 -0
  111. /package/dist/{sse-BY6sTy4P.mjs → sse-jh5OqFjR.mjs} +0 -0
  112. /package/dist/{storage-D2KZJAmn.d.mts → storage-HcAgP5Ni.d.mts} +0 -0
  113. /package/dist/{store-helpers-B0sunfZZ.mjs → store-helpers-5JgaxieH.mjs} +0 -0
  114. /package/dist/{tracing-Dm8n7Cnn.d.mts → tracing-bRqqUxFO.d.mts} +0 -0
  115. /package/dist/{types-DVfpSfx2.d.mts → types-CfapW2t8.d.mts} +0 -0
  116. /package/dist/{versioning-B6mimogM.mjs → versioning-CuIwUQZ2.mjs} +0 -0
  117. /package/dist/{websocket-BkjeGZRn.d.mts → websocket-BJ0w-3Dt.d.mts} +0 -0
@@ -1,6 +1,6 @@
1
- import { C as RequestContext, F as FastifyWithDecorators, H as ActionDefinition, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, bt as IRequestContext, ft as RouteMcpConfig, mt as RouteSchemaOptions, q as CrudController, st as ResourceConfig, vt as IController, yt as IControllerResponse } from "./index-CkW0flkU.mjs";
2
- import { i as RequestScope } from "./types-DVfpSfx2.mjs";
3
- import { c as PermissionCheck } from "./fields-Anj0xdih.mjs";
1
+ import { C as RequestContext, F as FastifyWithDecorators, H as ActionDefinition, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, bt as IRequestContext, ft as RouteMcpConfig, mt as RouteSchemaOptions, q as CrudController, st as ResourceConfig, vt as IController, yt as IControllerResponse } from "./index-DBDgy6Hw.mjs";
2
+ import { i as RequestScope } from "./types-CfapW2t8.mjs";
3
+ import { c as PermissionCheck } from "./fields-DqZLp5d6.mjs";
4
4
  import { FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
5
5
  import { z } from "zod";
6
6
  import { AggDateBucket, AggMeasure, AggTopN } from "@classytic/repo-core/repository";
@@ -240,9 +240,13 @@ declare function getEntityQuery(req: RequestWithExtras): Record<string, string>;
240
240
  //#endregion
241
241
  //#region src/core/fastifyAdapter.d.ts
242
242
  /**
243
- * Create IRequestContext from Fastify request
244
- *
245
- * Extracts framework-agnostic context from Fastify-specific request object
243
+ * Project `FastifyRequest` into the curated `IRequestContext` shape that
244
+ * arc's controllers receive. This is a developer-facing facade, not a
245
+ * portability boundary — arc is Fastify-only. The projection lifts the
246
+ * fields tenant-scoped controllers reach for (`organizationId`,
247
+ * `teamId`, `scope`, `context`, server accessor) onto a flat, typed
248
+ * surface so handler code doesn't have to dig through Fastify's
249
+ * decorator surface or `req.metadata._scope` internals.
246
250
  */
247
251
  declare function createRequestContext(req: FastifyRequest): IRequestContext;
248
252
  /**
@@ -258,25 +262,41 @@ declare function getControllerContext(req: IRequestContext): RequestContext;
258
262
  */
259
263
  declare function getControllerScope(req: IRequestContext): RequestScope;
260
264
  /**
261
- * Send IControllerResponse via Fastify reply
265
+ * Send a controller's `IControllerResponse` through Fastify's reply.
266
+ *
267
+ * The `IControllerResponse<T>` envelope is arc's internal pipeline
268
+ * contract — it's how custom `status`, `meta`, `headers`, and field
269
+ * permissions thread from a handler through interceptors to the wire
270
+ * without each step having to touch `reply` directly. Not a portability
271
+ * abstraction (arc is Fastify-only); a pipeline-internal one.
262
272
  *
263
- * Converts framework-agnostic response to Fastify response
264
- * Applies field masking if specified in request
273
+ * **Auto-envelope of bare returns.** Custom route handlers are
274
+ * expected to return the envelope shape so this function can apply
275
+ * field permissions, attach `fieldCaps`, and flatten paginated lists.
276
+ * A handler that returns raw `T` (array, plain object, primitive) used
277
+ * to silently produce a broken response — `response.data` was
278
+ * `undefined`, the body sent was `undefined`, and any declared
279
+ * `fields:` permission map was bypassed (the leak shape mentora
280
+ * flagged). 2.17 detects bare returns and wraps them into
281
+ * `{ data: response, status: 200 }` before the field-permission
282
+ * pipeline runs, so `fields:` applies to ALL custom routes whether the
283
+ * handler envelopes its return value or not. The envelope shape is
284
+ * still the canonical contract — auto-wrapping is a safety net, not
285
+ * an invitation to drop the envelope.
265
286
  */
266
287
  declare function sendControllerResponse<T>(reply: FastifyReply, response: IControllerResponse<T>, request?: FastifyRequest): void;
267
288
  /**
268
- * Create Fastify route handler from IController method
269
- *
270
- * Wraps framework-agnostic controller method in Fastify-specific handler
289
+ * Bridge an `IController` method (`(ctx: IRequestContext) => Promise<IControllerResponse<T>>`)
290
+ * into a Fastify route handler. Translates between arc's curated request
291
+ * context surface and Fastify's request/reply, and routes the envelope
292
+ * through `sendControllerResponse` so the pipeline applies field
293
+ * permissions, status, and meta uniformly. Internal plumbing, not a
294
+ * portability layer — arc is Fastify-only.
271
295
  *
272
296
  * @example
273
297
  * ```typescript
274
298
  * const controller = new BaseController(repository);
275
- *
276
- * // Create Fastify handler
277
299
  * const listHandler = createFastifyHandler(controller.list.bind(controller));
278
- *
279
- * // Register route
280
300
  * fastify.get('/products', listHandler);
281
301
  * ```
282
302
  */
@@ -1,7 +1,7 @@
1
- import { a as QueryCacheConfig } from "./QueryCache-SvmT_9ti.mjs";
2
- import { i as RequestScope } from "./types-DVfpSfx2.mjs";
3
- import { c as PermissionCheck, d as UserBase, n as FieldPermissionMap } from "./fields-Anj0xdih.mjs";
4
- import { n as DomainEvent } from "./EventTransport-C-2oAHtw.mjs";
1
+ import { a as QueryCacheConfig } from "./QueryCache-8YmAQ-zW.mjs";
2
+ import { i as RequestScope } from "./types-CfapW2t8.mjs";
3
+ import { c as PermissionCheck, d as UserBase, n as FieldPermissionMap } from "./fields-DqZLp5d6.mjs";
4
+ import { n as DomainEvent } from "./EventTransport-B1GFiYUy.mjs";
5
5
  import { KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
6
6
  import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
7
7
  import * as _$_classytic_repo_core_adapter0 from "@classytic/repo-core/adapter";
@@ -1569,8 +1569,39 @@ interface AggregationConfig {
1569
1569
  having?: AnyRecord;
1570
1570
  /** Order grouped rows by groupBy field, measure alias, or joined-alias path. */
1571
1571
  sort?: Record<string, 1 | -1>;
1572
- /** Hard cap on result rows. Applied at the IR level (LIMIT / `$limit`). */
1572
+ /**
1573
+ * Static hard cap on result rows. Always applied at the IR level
1574
+ * (LIMIT / `$limit`), regardless of URL params. Use when the
1575
+ * aggregation has a fixed-size shape ("top 10 categories"). Mutually
1576
+ * exclusive with `defaultLimit` / `maxLimit` — pick the static form
1577
+ * OR the URL-driven form, not both.
1578
+ */
1573
1579
  limit?: number;
1580
+ /**
1581
+ * URL-driven row limit — applied when the caller passes `?limit=N`.
1582
+ * When set, the auto-route reads `query.limit`, parses it as a
1583
+ * positive integer, caps it at `maxLimit` (or the framework default
1584
+ * if `maxLimit` is unset), and writes the result into `AggRequest.limit`.
1585
+ * When the caller omits `?limit`, this default applies.
1586
+ *
1587
+ * **DoS defense.** Auto-generated `/aggregations/:name` routes today
1588
+ * have no upper bound on response size. A `groupBy: 'userId'` on a
1589
+ * 10M-user table without `defaultLimit` returns a 10M-row JSON. Setting
1590
+ * `defaultLimit: 100, maxLimit: 1000` bounds the response shape while
1591
+ * letting power users request up to 1000 rows when they need them.
1592
+ *
1593
+ * Mutually exclusive with the static `limit` — boot error if both set.
1594
+ * Setting `defaultLimit` without `maxLimit` uses the framework
1595
+ * default cap (`AGG_FRAMEWORK_MAX_LIMIT = 1000`) — explicit is better.
1596
+ */
1597
+ defaultLimit?: number;
1598
+ /**
1599
+ * Hard ceiling for URL-driven `?limit=N`. Only meaningful when
1600
+ * `defaultLimit` is set; ignored otherwise. Defaults to
1601
+ * `AGG_FRAMEWORK_MAX_LIMIT` (1000) when omitted but `defaultLimit` is
1602
+ * present. Boot validation: must be ≥ `defaultLimit`.
1603
+ */
1604
+ maxLimit?: number;
1574
1605
  /**
1575
1606
  * Top-N-per-group filter. Keeps only the top `limit` rows per
1576
1607
  * partition, ranked by `sortBy`. The classic "top 3 products per
@@ -2707,13 +2738,37 @@ interface ResourceConfig<TDoc = AnyRecord> {
2707
2738
  /**
2708
2739
  * Custom routes beyond CRUD. Presets also merge their routes here.
2709
2740
  *
2710
- * @example
2741
+ * **Route handlers return `IControllerResponse<T>` shape** —
2742
+ * `{ data, status?, meta?, headers? }`. Returning a bare value (array,
2743
+ * object, primitive) is supported via auto-envelope (2.17+) but the
2744
+ * explicit envelope is the canonical contract — declared `fields:`
2745
+ * permissions, custom `status`, `meta`, and `headers` only work when
2746
+ * you return the envelope. Set `raw: true` on a route to opt out of
2747
+ * arc's pipeline entirely (custom streaming, SSE, manual `reply.send()`).
2748
+ *
2749
+ * **Path collisions with auto-CRUD are detected at `defineResource()` time.**
2750
+ * If you declare a route that shares method+path with an auto-CRUD op
2751
+ * (e.g. `POST /` collides with `create`), validation throws with the
2752
+ * exact `disabledRoutes` line to add. Use one of:
2753
+ * - `disabledRoutes: ['create']` — suppress the auto-CRUD op
2754
+ * - `crud: { list: true, get: true }` — opt-in allow-list (preferred)
2755
+ * - `disableDefaultRoutes: true` — turn off all auto-CRUD on this resource
2756
+ *
2757
+ * @example Custom route alongside default CRUD:
2711
2758
  * ```typescript
2712
2759
  * routes: [
2713
2760
  * { method: 'GET', path: '/stats', handler: 'getStats', permissions: auth() },
2714
2761
  * { method: 'POST', path: '/webhook', handler: webhookFn, raw: true, permissions: auth() },
2715
2762
  * ]
2716
2763
  * ```
2764
+ *
2765
+ * @example Custom POST replacing the auto-CRUD `create`:
2766
+ * ```typescript
2767
+ * disabledRoutes: ['create'],
2768
+ * routes: [
2769
+ * { method: 'POST', path: '/', handler: customCreate, permissions: requireAuth() },
2770
+ * ]
2771
+ * ```
2717
2772
  */
2718
2773
  routes?: RouteDefinition[];
2719
2774
  /**
@@ -3713,7 +3768,24 @@ interface RegistryEntry extends ResourceMetadata {
3713
3768
  field: string;
3714
3769
  maxRangeDays?: number;
3715
3770
  }; /** Whether the aggregation requires named filters — surfaced in docs. */
3716
- readonly requireFilters?: readonly string[]; /** MCP tool generation flag — `false` to skip, object for overrides. */
3771
+ readonly requireFilters?: readonly string[];
3772
+ /**
3773
+ * Static result-row cap (no URL override). Mutually exclusive with
3774
+ * `defaultLimit` / `maxLimit`. Surfaced so OpenAPI docs can mention
3775
+ * the fixed cap in the description.
3776
+ */
3777
+ readonly limit?: number;
3778
+ /**
3779
+ * URL-driven default limit (caller may pass `?limit=N`). When set,
3780
+ * OpenAPI docs render `?limit` as a query parameter with this
3781
+ * default and `maxLimit` as the maximum.
3782
+ */
3783
+ readonly defaultLimit?: number;
3784
+ /**
3785
+ * URL-driven limit ceiling (only meaningful with `defaultLimit`).
3786
+ * OpenAPI docs render this as the `maximum` constraint on `?limit`.
3787
+ */
3788
+ readonly maxLimit?: number; /** MCP tool generation flag — `false` to skip, object for overrides. */
3717
3789
  readonly mcp?: boolean | {
3718
3790
  readonly description?: string;
3719
3791
  readonly annotations?: Record<string, unknown>;
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
- import { $t as BaseController, A as RequestIdOptions, At as AggregationRateLimit, B as defineResource, C as RequestContext, Ct as AggMeasureShorthand, D as HealthCheck, Dt as AggregationIndexHint, E as GracefulShutdownOptions, Et as AggregationDateRangeRequirement, F as FastifyWithDecorators, J as CrudRouteKey, Jt as ArcRequest, Kt as AnyRecord, L as RequestWithExtras, N as FastifyRequestExtras, O as HealthOptions, Ot as AggregationMaterializedContext, P as FastifyWithAuth, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, St as AggMeasureInput, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, Vt as AuthPluginOptions, X as EventDefinition, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, _n as ArcListResult, a as InferAdapterDoc, an as SlugMixin, bt as IRequestContext, c as TypedController, cn as BaseControllerOptions, d as PaginationResult, dn as ControllerConstructionOptions, en as SoftDeleteExt, f as IntrospectionData, g as ArcInternalMetadata, gn as ArcGetResult, gt as ControllerLike, h as ResourceMetadata, hn as ArcDeleteResult, i as ValidationResult, in as SlugExt, it as RateLimitConfig, jt as AggregationsMap, k as IntrospectionPluginOptions, kt as AggregationMaterializedResult, l as TypedRepository, ln as BaseCrudController, m as RegistryStats, mn as ArcCreateResult, mt as RouteSchemaOptions, n as ConfigError, nn as TreeExt, o as InferDocType, on as BulkExt, p as RegistryEntry, q as CrudController, qt as ApiResponse, r as ValidateOptions, rn as TreeMixin, rt as PresetResult, s as InferResourceDoc, sn as BulkMixin, st as ResourceConfig, t as RouteHandlerMethod, tn as SoftDeleteMixin, tt as PresetFunction, u as TypedResourceConfig, un as ControllerConfigurableOptions, vn as ArcUpdateResult, vt as IController, w as ServiceContext, wt as AggregationCacheConfig, xt as RouteHandler, y as OwnershipCheck, yn as ListResult, yt as IControllerResponse } from "./index-CkW0flkU.mjs";
2
- import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, t as FieldPermission, u as PermissionResult } from "./fields-Anj0xdih.mjs";
3
- import { A as MAX_FILTER_DEPTH, C as DEFAULT_SORT, D as HOOK_PHASES, E as HOOK_OPERATIONS, F as RESERVED_QUERY_PARAMS, I as SYSTEM_FIELDS, M as MAX_SEARCH_LENGTH, N as MUTATION_OPERATIONS, O as HookOperation, P as MutationOperation, S as DEFAULT_MAX_LIMIT, T as DEFAULT_UPDATE_METHOD, _ as defineAggregation, b as DEFAULT_ID_FIELD, f as defineResourceVariants, j as MAX_REGEX_LENGTH, k as HookPhase, m as defineAction, s as getControllerScope, v as CRUD_OPERATIONS, w as DEFAULT_TENANT_FIELD, x as DEFAULT_LIMIT, y as CrudOperation } from "./index-B-ulKx5P.mjs";
4
- import { A as requireRoles, C as allOf, E as denyAll, M as when, O as requireAuth, S as createOrgPermissions, T as anyOf, a as presets_d_exports, c as readOnly, d as requireOrgRole, f as requireScopeContext, i as ownerWithAdminBypass, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig } from "./index-3oIimXQn.mjs";
5
- import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-C1lX_jlm.mjs";
1
+ import { $t as BaseController, A as RequestIdOptions, At as AggregationRateLimit, B as defineResource, C as RequestContext, Ct as AggMeasureShorthand, D as HealthCheck, Dt as AggregationIndexHint, E as GracefulShutdownOptions, Et as AggregationDateRangeRequirement, F as FastifyWithDecorators, J as CrudRouteKey, Jt as ArcRequest, Kt as AnyRecord, L as RequestWithExtras, N as FastifyRequestExtras, O as HealthOptions, Ot as AggregationMaterializedContext, P as FastifyWithAuth, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, St as AggMeasureInput, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, Vt as AuthPluginOptions, X as EventDefinition, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, _n as ArcListResult, a as InferAdapterDoc, an as SlugMixin, bt as IRequestContext, c as TypedController, cn as BaseControllerOptions, d as PaginationResult, dn as ControllerConstructionOptions, en as SoftDeleteExt, f as IntrospectionData, g as ArcInternalMetadata, gn as ArcGetResult, gt as ControllerLike, h as ResourceMetadata, hn as ArcDeleteResult, i as ValidationResult, in as SlugExt, it as RateLimitConfig, jt as AggregationsMap, k as IntrospectionPluginOptions, kt as AggregationMaterializedResult, l as TypedRepository, ln as BaseCrudController, m as RegistryStats, mn as ArcCreateResult, mt as RouteSchemaOptions, n as ConfigError, nn as TreeExt, o as InferDocType, on as BulkExt, p as RegistryEntry, q as CrudController, qt as ApiResponse, r as ValidateOptions, rn as TreeMixin, rt as PresetResult, s as InferResourceDoc, sn as BulkMixin, st as ResourceConfig, t as RouteHandlerMethod, tn as SoftDeleteMixin, tt as PresetFunction, u as TypedResourceConfig, un as ControllerConfigurableOptions, vn as ArcUpdateResult, vt as IController, w as ServiceContext, wt as AggregationCacheConfig, xt as RouteHandler, y as OwnershipCheck, yn as ListResult, yt as IControllerResponse } from "./index-DBDgy6Hw.mjs";
2
+ import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, t as FieldPermission, u as PermissionResult } from "./fields-DqZLp5d6.mjs";
3
+ import { A as MAX_FILTER_DEPTH, C as DEFAULT_SORT, D as HOOK_PHASES, E as HOOK_OPERATIONS, F as RESERVED_QUERY_PARAMS, I as SYSTEM_FIELDS, M as MAX_SEARCH_LENGTH, N as MUTATION_OPERATIONS, O as HookOperation, P as MutationOperation, S as DEFAULT_MAX_LIMIT, T as DEFAULT_UPDATE_METHOD, _ as defineAggregation, b as DEFAULT_ID_FIELD, f as defineResourceVariants, j as MAX_REGEX_LENGTH, k as HookPhase, m as defineAction, s as getControllerScope, v as CRUD_OPERATIONS, w as DEFAULT_TENANT_FIELD, x as DEFAULT_LIMIT, y as CrudOperation } from "./index-CC_pgZjY.mjs";
4
+ import { A as requireRoles, C as allOf, E as denyAll, M as when, O as requireAuth, S as createOrgPermissions, T as anyOf, a as presets_d_exports, c as readOnly, d as requireOrgRole, f as requireScopeContext, i as ownerWithAdminBypass, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig } from "./index-3L-adFJO.mjs";
5
+ import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-4ZloYFvm.mjs";
6
6
 
7
7
  //#region src/index.d.ts
8
8
  declare const version: string;
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
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-TrJVIJl0.mjs";
2
2
  import { d as createDomainError, i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-j4aJm1Wg.mjs";
3
- import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-DlCCTIxJ.mjs";
3
+ import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-Vk8I7C0x.mjs";
4
4
  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-CTxMrreC.mjs";
5
- import { v as getControllerScope } from "./routerShared-CZV5aabX.mjs";
6
- import { a as defineResource, i as defineResourceVariants, o as ResourceDefinition, s as defineAction, u as defineAggregation } from "./core-DBJ_j6rX.mjs";
5
+ import { v as getControllerScope } from "./routerShared-ClV4cCaa.mjs";
6
+ import { a as defineResource, i as defineResourceVariants, o as ResourceDefinition, s as defineAction, u as defineAggregation } from "./core-wKeGXn38.mjs";
7
7
  //#region src/index.ts
8
- const version = "2.16.0";
8
+ const version = "2.16.1";
9
9
  //#endregion
10
10
  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, defineAction, defineAggregation, defineResource, defineResourceVariants, denyAll, fields, fullPublic, getControllerScope, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, version, when };
@@ -1,5 +1,5 @@
1
- import { n as DomainEvent } from "../EventTransport-C-2oAHtw.mjs";
2
- import { a as WebSocketMessage, i as WebSocketClient } from "../websocket-BkjeGZRn.mjs";
1
+ import { n as DomainEvent } from "../EventTransport-B1GFiYUy.mjs";
2
+ import { a as WebSocketMessage, i as WebSocketClient } from "../websocket-BJ0w-3Dt.mjs";
3
3
  import { FastifyPluginAsync, FastifyRequest } from "fastify";
4
4
 
5
5
  //#region src/integrations/event-gateway.d.ts
@@ -4,7 +4,7 @@ const eventGatewayPluginImpl = async (fastify, opts = {}) => {
4
4
  const { auth = true, orgScoped = false, roomPolicy, maxMessageBytes, maxSubscriptionsPerClient, authenticate } = opts;
5
5
  if (auth && !authenticate && !fastify.hasDecorator("authenticate")) throw new Error("[arc-event-gateway] auth is true but fastify.authenticate is not registered. Register an auth plugin first, provide a custom authenticate function, or set auth: false.");
6
6
  if (opts.sse !== false) {
7
- const { default: ssePlugin } = await import("../sse-BY6sTy4P.mjs").then((n) => n.r);
7
+ const { default: ssePlugin } = await import("../sse-jh5OqFjR.mjs").then((n) => n.r);
8
8
  await fastify.register(ssePlugin, {
9
9
  path: opts.sse?.path ?? "/events/stream",
10
10
  requireAuth: auth,
@@ -1,7 +1,7 @@
1
- import { a as WebSocketMessage, i as WebSocketClient, o as WebSocketPluginOptions } from "../websocket-BkjeGZRn.mjs";
1
+ import { a as WebSocketMessage, i as WebSocketClient, o as WebSocketPluginOptions } from "../websocket-BJ0w-3Dt.mjs";
2
2
  import { EventGatewayOptions } from "./event-gateway.mjs";
3
3
  import { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, QueueStats } from "./jobs.mjs";
4
- import { c as McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-BsJMEQ4D.mjs";
4
+ import { c as McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-BD0BCdf6.mjs";
5
5
  import { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike } from "./streamline.mjs";
6
6
  import { WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription } from "./webhooks.mjs";
7
7
  export { type BetterAuthHandler, type CallToolResult, type CreateMcpServerConfig, type CrudOperation, type EventGatewayOptions, type JobDefinition, type JobDispatchOptions, type JobDispatcher, type JobMeta, type JobsPluginOptions, type McpAuthResult, type McpPluginOptions, type McpResourceConfig, type PromptDefinition, type QueueStats, type StreamlinePluginOptions, type ToolAnnotations, type ToolContext, type ToolDefinition, type WebSocketClient, type WebSocketMessage, type WebSocketPluginOptions, type WebhookDeliveryRecord, type WebhookManager, type WebhookPluginOptions, type WebhookStore, type WebhookSubscription, type WorkflowLike, type WorkflowRunLike };
@@ -1,7 +1,7 @@
1
- import { V as ResourceDefinition, bt as IRequestContext, yt as IControllerResponse } from "../../index-CkW0flkU.mjs";
2
- import { i as RequestScope } from "../../types-DVfpSfx2.mjs";
3
- import { c as PermissionCheck } from "../../fields-Anj0xdih.mjs";
4
- import { a as CrudDescriptionOverride, c as McpAuthResult, d as PromptDefinition, f as PromptResult, g as ToolDefinition, h as ToolContext, i as CrudDescriptionMeta, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, p as SessionEntry, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpResourceConfig } from "../../types-BsJMEQ4D.mjs";
1
+ import { V as ResourceDefinition, bt as IRequestContext, yt as IControllerResponse } from "../../index-DBDgy6Hw.mjs";
2
+ import { i as RequestScope } from "../../types-CfapW2t8.mjs";
3
+ import { c as PermissionCheck } from "../../fields-DqZLp5d6.mjs";
4
+ import { a as CrudDescriptionOverride, c as McpAuthResult, d as PromptDefinition, f as PromptResult, g as ToolDefinition, h as ToolContext, i as CrudDescriptionMeta, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, p as SessionEntry, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpResourceConfig } from "../../types-BD0BCdf6.mjs";
5
5
  import { ErrorContract } from "@classytic/repo-core/errors";
6
6
  import { FastifyPluginAsync } from "fastify";
7
7
  import { z } from "zod";
@@ -1,4 +1,4 @@
1
- import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-7vGV51ED.mjs";
1
+ import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-Dbgz9_Rj.mjs";
2
2
  //#region src/integrations/mcp/defineTool.ts
3
3
  /**
4
4
  * Define a type-safe MCP tool.
@@ -1,4 +1,4 @@
1
- import { c as McpAuthResult, l as McpPluginOptions } from "../../types-BsJMEQ4D.mjs";
1
+ import { c as McpAuthResult, l as McpPluginOptions } from "../../types-BD0BCdf6.mjs";
2
2
 
3
3
  //#region src/integrations/mcp/testing.d.ts
4
4
  interface TestMcpClientOptions {
@@ -1,4 +1,4 @@
1
- import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-7vGV51ED.mjs";
1
+ import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-Dbgz9_Rj.mjs";
2
2
  //#region src/integrations/mcp/testing.ts
3
3
  /**
4
4
  * @classytic/arc/mcp/testing — MCP Test Utilities
@@ -1,4 +1,4 @@
1
- import { c as WebSocketAdapter } from "../websocket-BkjeGZRn.mjs";
1
+ import { c as WebSocketAdapter } from "../websocket-BJ0w-3Dt.mjs";
2
2
 
3
3
  //#region src/integrations/websocket-redis.d.ts
4
4
  interface RedisLike {
@@ -1,2 +1,2 @@
1
- import { a as WebSocketMessage, c as WebSocketAdapter, i as WebSocketClient, n as websocketPlugin, o as WebSocketPluginOptions, r as AuthResult, s as LocalWebSocketAdapter, t as RoomManager } from "../websocket-BkjeGZRn.mjs";
1
+ import { a as WebSocketMessage, c as WebSocketAdapter, i as WebSocketClient, n as websocketPlugin, o as WebSocketPluginOptions, r as AuthResult, s as LocalWebSocketAdapter, t as RoomManager } from "../websocket-BJ0w-3Dt.mjs";
2
2
  export { AuthResult, LocalWebSocketAdapter, RoomManager, WebSocketAdapter, WebSocketClient, WebSocketMessage, WebSocketPluginOptions, websocketPlugin };
@@ -1,4 +1,4 @@
1
- import { t as ResourceRegistry } from "./ResourceRegistry-f48hFk3m.mjs";
1
+ import { t as ResourceRegistry } from "./ResourceRegistry-CzSzVK1m.mjs";
2
2
  import { resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  //#region src/cli/utils/loadResourcesFromEntry.ts
@@ -1,13 +1,13 @@
1
1
  import { p as isArcError } from "./errors-j4aJm1Wg.mjs";
2
- import { t as BaseController } from "./BaseController-DlCCTIxJ.mjs";
2
+ import { t as BaseController } from "./BaseController-Vk8I7C0x.mjs";
3
3
  import { L as normalizePermissionResult } from "./permissions-CTxMrreC.mjs";
4
4
  import { t as executePipeline } from "./pipe-DiCyvyPN.mjs";
5
- import { u as resolvePipelineSteps } from "./routerShared-CZV5aabX.mjs";
6
- import { r as validateAggregations } from "./validate-By96rH0r.mjs";
7
- import { n as executeAggregation } from "./buildHandler-BZX6zzDM.mjs";
5
+ import { u as resolvePipelineSteps } from "./routerShared-ClV4cCaa.mjs";
6
+ import { r as validateAggregations } from "./validate-DFIP2VrM.mjs";
7
+ import { n as executeAggregation } from "./buildHandler-C_SkBNF3.mjs";
8
8
  import { t as pluralize } from "./pluralize-B9M8xvy-.mjs";
9
9
  import { t as resolveActionPermission } from "./actionPermissions-Bjmvn7Eb.mjs";
10
- import { i as shouldRejectAdditionalProperties, r as schemaIRToZodShape, t as normalizeSchemaIR } from "./schemaIR-Ctc89DSn.mjs";
10
+ import { i as shouldRejectAdditionalProperties, r as schemaIRToZodShape, t as normalizeSchemaIR } from "./schemaIR-DAOvBWgT.mjs";
11
11
  import { createHash, randomUUID } from "node:crypto";
12
12
  import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
13
13
  import fp from "fastify-plugin";
@@ -1,4 +1,4 @@
1
- import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-CkW0flkU.mjs";
1
+ import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-DBDgy6Hw.mjs";
2
2
  import { RouteHandlerMethod } from "fastify";
3
3
 
4
4
  //#region src/middleware/middleware.d.ts
@@ -2,7 +2,7 @@ import { t as getUserRoles } from "./types-D57iXYb8.mjs";
2
2
  import { n as convertRouteSchema } from "./schemaConverter-De34B1ZG.mjs";
3
3
  import { t as pluralize } from "./pluralize-B9M8xvy-.mjs";
4
4
  import { t as resolveActionPermission } from "./actionPermissions-Bjmvn7Eb.mjs";
5
- import { t as buildActionBodySchema } from "./createActionRouter-DUpN3Dd1.mjs";
5
+ import { t as buildActionBodySchema } from "./createActionRouter-eiOFtnC3.mjs";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/docs/openapi/canonical-schemas.ts
8
8
  /**
@@ -647,13 +647,7 @@ function appendAggregationPaths(paths, resource, basePath, additionalSecurity) {
647
647
  if (!paths[path]) paths[path] = {};
648
648
  paths[path].get = createOperation(resource, `aggregation.${agg.name}`, agg.summary ?? `Aggregation: ${agg.name}`, {
649
649
  description: descLines.join("\n"),
650
- parameters: [{
651
- name: "querystring",
652
- in: "query",
653
- required: false,
654
- schema: querystring,
655
- description: "Filter narrowing — composes with base filter + tenant scope."
656
- }],
650
+ parameters: buildAggregationParameters(agg, querystring),
657
651
  responses: {
658
652
  "200": {
659
653
  description: "Aggregation result",
@@ -701,6 +695,37 @@ function normalizeGroupByForOpenApi(groupBy) {
701
695
  if (!groupBy) return [];
702
696
  return typeof groupBy === "string" ? [groupBy] : groupBy;
703
697
  }
698
+ /**
699
+ * Build the OpenAPI `parameters` array for an aggregation route.
700
+ *
701
+ * Always includes the filter-narrowing `querystring` slot. When the
702
+ * aggregation declares URL-driven paging (`defaultLimit`), adds an
703
+ * explicit `?limit` integer parameter with the declared default +
704
+ * maximum. Auto-generated FE clients pick the constraint up; humans
705
+ * reading the docs see the cap without reading source.
706
+ */
707
+ function buildAggregationParameters(agg, querystring) {
708
+ const params = [{
709
+ name: "querystring",
710
+ in: "query",
711
+ required: false,
712
+ schema: querystring,
713
+ description: "Filter narrowing — composes with base filter + tenant scope."
714
+ }];
715
+ if (agg.defaultLimit !== void 0) params.push({
716
+ name: "limit",
717
+ in: "query",
718
+ required: false,
719
+ schema: {
720
+ type: "integer",
721
+ minimum: 1,
722
+ default: agg.defaultLimit,
723
+ maximum: agg.maxLimit ?? 1e3
724
+ },
725
+ description: `Result row cap. Default ${agg.defaultLimit}; values above ${agg.maxLimit ?? 1e3} are capped silently. Invalid values fall back to the default.`
726
+ });
727
+ return params;
728
+ }
704
729
  function setNestedSchemaProp(target, path, leaf) {
705
730
  if (path.length === 1) {
706
731
  target[path[0]] = leaf;
@@ -850,7 +875,9 @@ function appendCustomRoutePaths(paths, resource, basePath, additionalSecurity) {
850
875
  }
851
876
  if (routeSchema?.response) {
852
877
  const responseSchemas = routeSchema.response;
853
- for (const [statusCode, schema] of Object.entries(responseSchemas)) extras.responses[statusCode] = {
878
+ const responses = extras.responses ?? {};
879
+ extras.responses = responses;
880
+ for (const [statusCode, schema] of Object.entries(responseSchemas)) responses[statusCode] = {
854
881
  description: schema.description || `Response ${statusCode}`,
855
882
  content: { "application/json": { schema } }
856
883
  };
@@ -1,3 +1,3 @@
1
- import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, f as getUserRoles, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, p as normalizeRoles, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission, u as PermissionResult } from "../fields-Anj0xdih.mjs";
2
- import { A as requireRoles, C as allOf, D as not, E as denyAll, F as RequireAgentScopeOptions, I as RequireMandateOptions, L as requireAgentScope, M as when, N as applyPermissionResult, O as requireAuth, P as normalizePermissionResult, R as requireDPoP, S as createOrgPermissions, T as anyOf, _ as ConnectEventsOptions, a as presets_d_exports, b as PermissionEventBus, c as readOnly, d as requireOrgRole, f as requireScopeContext, g as createRoleHierarchy, h as RoleHierarchy, i as ownerWithAdminBypass, j as roles, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig, z as requireMandate } from "../index-3oIimXQn.mjs";
1
+ import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, f as getUserRoles, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, p as normalizeRoles, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission, u as PermissionResult } from "../fields-DqZLp5d6.mjs";
2
+ import { A as requireRoles, C as allOf, D as not, E as denyAll, F as RequireAgentScopeOptions, I as RequireMandateOptions, L as requireAgentScope, M as when, N as applyPermissionResult, O as requireAuth, P as normalizePermissionResult, R as requireDPoP, S as createOrgPermissions, T as anyOf, _ as ConnectEventsOptions, a as presets_d_exports, b as PermissionEventBus, c as readOnly, d as requireOrgRole, f as requireScopeContext, g as createRoleHierarchy, h as RoleHierarchy, i as ownerWithAdminBypass, j as roles, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig, z as requireMandate } from "../index-3L-adFJO.mjs";
3
3
  export { ConnectEventsOptions, DynamicPermissionMatrix, DynamicPermissionMatrixConfig, FieldPermission, FieldPermissionMap, FieldPermissionType, PermissionCheck, PermissionContext, PermissionEventBus, PermissionResult, RequireAgentScopeOptions, RequireMandateOptions, RoleHierarchy, UserBase, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, presets_d_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
@@ -1,4 +1,4 @@
1
- import { Ft as OperationFilter, It as PipelineConfig, Lt as PipelineContext, Mt as Guard, Nt as Interceptor, Pt as NextFunction, Rt as PipelineStep, yt as IControllerResponse, zt as Transform } from "../index-CkW0flkU.mjs";
1
+ import { Ft as OperationFilter, It as PipelineConfig, Lt as PipelineContext, Mt as Guard, Nt as Interceptor, Pt as NextFunction, Rt as PipelineStep, yt as IControllerResponse, zt as Transform } from "../index-DBDgy6Hw.mjs";
2
2
 
3
3
  //#region src/pipeline/guard.d.ts
4
4
  interface GuardOptions {
@@ -1,66 +1,12 @@
1
- import { An as HookSystem, Kt as AnyRecord, Q as MiddlewareConfig, dt as RouteDefinition, mt as RouteSchemaOptions, nt as PresetHook, z as ResourceRegistry } from "../index-CkW0flkU.mjs";
2
- import { t as ExternalOpenApiPaths } from "../externalPaths-DFg-2KTp.mjs";
3
- import { _ as HealthOptions, a as MetricsCollector, c as metricsPlugin, d as ssePlugin, f as CachingOptions, g as HealthCheck, h as cachingPlugin, i as MetricEntry, l as SSEOptions, m as _default$1, n as _default$7, o as MetricsOptions, p as CachingRule, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as _default$6, v as _default$3, y as healthPlugin } from "../versioning-ZwX9tmbS.mjs";
4
- import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-mHuyWzZE.mjs";
5
- import { t as TracingOptions } from "../tracing-Dm8n7Cnn.mjs";
1
+ import { Kt as AnyRecord, Q as MiddlewareConfig, dt as RouteDefinition, mt as RouteSchemaOptions, nt as PresetHook } from "../index-DBDgy6Hw.mjs";
2
+ import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-CJm0vh-l.mjs";
3
+ import { _ as HealthOptions, a as MetricsCollector, c as metricsPlugin, d as ssePlugin, f as CachingOptions, g as HealthCheck, h as cachingPlugin, i as MetricEntry, l as SSEOptions, m as _default$1, n as _default$7, o as MetricsOptions, p as CachingRule, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as _default$6, v as _default$3, y as healthPlugin } from "../versioning-5H4930tj.mjs";
4
+ import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-DuqTqeqn.mjs";
5
+ import { t as TracingOptions } from "../tracing-bRqqUxFO.mjs";
6
6
  import { PaginatedResult } from "@classytic/repo-core/pagination";
7
7
  import { FastifyInstance, FastifyPluginAsync } from "fastify";
8
8
  import * as _$node_stream0 from "node:stream";
9
9
 
10
- //#region src/core/arcCorePlugin.d.ts
11
- interface ArcCorePluginOptions {
12
- /** Enable event emission for CRUD operations (requires eventPlugin) */
13
- emitEvents?: boolean;
14
- /** Hook system instance (for testing/custom setup) */
15
- hookSystem?: HookSystem;
16
- /** Resource registry instance (for testing/custom setup) */
17
- registry?: ResourceRegistry;
18
- }
19
- interface PluginMeta {
20
- name: string;
21
- version?: string;
22
- options?: Record<string, unknown>;
23
- registeredAt: string;
24
- }
25
- interface ArcCore {
26
- /** Instance-scoped hook system */
27
- hooks: HookSystem;
28
- /** Instance-scoped resource registry */
29
- registry: ResourceRegistry;
30
- /** Whether event emission is enabled */
31
- emitEvents: boolean;
32
- /** External OpenAPI paths contributed by auth adapters or third-party integrations */
33
- externalOpenApiPaths: ExternalOpenApiPaths[];
34
- /** Registered plugins for introspection */
35
- plugins: Map<string, PluginMeta>;
36
- }
37
- declare module "fastify" {
38
- interface FastifyInstance {
39
- /**
40
- * Arc core decorator. Optional because:
41
- *
42
- * 1. Consumers who import any `@classytic/arc/*` subpath get this module
43
- * augmentation merged into every `FastifyInstance` they see — even
44
- * instances on apps that never register {@link arcCorePlugin}. Making
45
- * it required would force those apps to assert or guard every
46
- * property access.
47
- * 2. Hosts that extend `FastifyInstance` to narrow `arc` to their own
48
- * type (`interface X extends FastifyInstance { arc?: MyArc }`) were
49
- * previously blocked because non-optional `arc: ArcCore` conflicts
50
- * with the re-declaration. An optional field lets hosts re-declare
51
- * with a compatible subtype without fighting TS.
52
- *
53
- * Inside Arc's own code, any call site that runs *after* `arcCorePlugin`
54
- * has registered treats this as non-null — see the call sites in
55
- * `factory/registerAuth.ts` and `factory/createApp.ts` which assert
56
- * with `fastify.arc!` or narrow explicitly.
57
- */
58
- arc?: ArcCore;
59
- }
60
- }
61
- declare const arcCorePlugin: FastifyPluginAsync<ArcCorePluginOptions>;
62
- declare const _default: FastifyPluginAsync<ArcCorePluginOptions>;
63
- //#endregion
64
10
  //#region src/plugins/createPlugin.d.ts
65
11
  interface PluginResourceResult {
66
12
  /** Additional routes to add to the resource (v2.8 `RouteDefinition` shape) */
@@ -3,13 +3,13 @@ import { c as getOrgId } from "../types-Bi0r0vjG.mjs";
3
3
  import { t as requestContext } from "../requestContext-SSaaTgW8.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-BzkXkvVv.mjs";
5
5
  import { t as HookSystem } from "../HookSystem-Cmf7-Etp.mjs";
6
- import { t as ResourceRegistry } from "../ResourceRegistry-f48hFk3m.mjs";
7
- import { n as caching_default, t as cachingPlugin } from "../caching-TeHE8G-v.mjs";
8
- import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-DpoXQHZ9.mjs";
9
- import { n as metrics_default, t as metricsPlugin } from "../metrics-TuOmguhi.mjs";
10
- import { t as replyHelpersPlugin } from "../replyHelpers-C-gD32oF.mjs";
11
- import { n as sse_default, t as ssePlugin } from "../sse-BY6sTy4P.mjs";
12
- import { n as versioning_default, t as versioningPlugin } from "../versioning-B6mimogM.mjs";
6
+ import { t as ResourceRegistry } from "../ResourceRegistry-CzSzVK1m.mjs";
7
+ import { n as caching_default, t as cachingPlugin } from "../caching-8MfrrUYU.mjs";
8
+ import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-DNPzqaTT.mjs";
9
+ import { n as metrics_default, t as metricsPlugin } from "../metrics-B-PU4-Yu.mjs";
10
+ import { t as replyHelpersPlugin } from "../replyHelpers-boS5Rx26.mjs";
11
+ import { n as sse_default, t as ssePlugin } from "../sse-jh5OqFjR.mjs";
12
+ import { n as versioning_default, t as versioningPlugin } from "../versioning-CuIwUQZ2.mjs";
13
13
  import { randomUUID } from "node:crypto";
14
14
  import fp from "fastify-plugin";
15
15
  //#region src/core/arcCorePlugin.ts
@@ -1,2 +1,2 @@
1
- import { a as traced, i as isTracingAvailable, n as _default, r as createSpan, t as TracingOptions } from "../tracing-Dm8n7Cnn.mjs";
1
+ import { a as traced, i as isTracingAvailable, n as _default, r as createSpan, t as TracingOptions } from "../tracing-bRqqUxFO.mjs";
2
2
  export { type TracingOptions, createSpan, isTracingAvailable, traced, _default as tracingPlugin };
@@ -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.16.0";
61
+ const resolvedVersion = serviceVersion ?? "2.16.1";
62
62
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
63
63
  const provider = new NodeTracerProvider({ resource: { attributes: {
64
64
  "service.name": serviceName,
@@ -0,0 +1,45 @@
1
+ //#region src/testing/preloadResources.ts
2
+ /**
3
+ * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
4
+ *
5
+ * Accepts either:
6
+ * - `{ import: 'default' }` form: values are the resource directly
7
+ * - default form: values are the full module — picks first export with `toPlugin()`
8
+ *
9
+ * Throws if any module doesn't yield a valid `ResourceLike`.
10
+ */
11
+ function preloadResources(globResult) {
12
+ const resources = [];
13
+ for (const [path, value] of Object.entries(globResult)) {
14
+ const resource = pickResource(value);
15
+ if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
16
+ resources.push(resource);
17
+ }
18
+ return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
19
+ }
20
+ /**
21
+ * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
22
+ *
23
+ * Use this when resources depend on prior bootstrap (e.g., engine init) and
24
+ * cannot be evaluated at import time of the preload file.
25
+ */
26
+ async function preloadResourcesAsync(globResult) {
27
+ return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
28
+ const resource = pickResource(await loader());
29
+ if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
30
+ return resource;
31
+ }))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
32
+ }
33
+ function pickResource(value) {
34
+ if (!value || typeof value !== "object") return void 0;
35
+ if (typeof value.toPlugin === "function") return value;
36
+ const mod = value;
37
+ const candidates = [
38
+ mod.default,
39
+ mod.resource,
40
+ ...Object.values(mod)
41
+ ];
42
+ for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
43
+ }
44
+ //#endregion
45
+ export { preloadResourcesAsync as n, preloadResources as t };