@classytic/arc 2.14.2 → 2.15.3

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 (47) hide show
  1. package/README.md +44 -0
  2. package/dist/{BaseController-Dv60tU83.mjs → BaseController-dx3m2J8V.mjs} +102 -2
  3. package/dist/auth/index.d.mts +1 -1
  4. package/dist/{buildHandler-jSZ6Fdvi.mjs → buildHandler-CcFOpJLh.mjs} +2 -19
  5. package/dist/cli/commands/describe.d.mts +1 -1
  6. package/dist/core/index.d.mts +3 -3
  7. package/dist/core/index.mjs +2 -2
  8. package/dist/{core-D29kkRL5.mjs → core-CvmOqEms.mjs} +70 -13
  9. package/dist/{createAggregationRouter-DhR-Ofiz.mjs → createAggregationRouter-B0bPDf5b.mjs} +7 -5
  10. package/dist/{createApp-BarYhXCZ.mjs → createApp-PFegs47-.mjs} +64 -4
  11. package/dist/docs/index.d.mts +1 -1
  12. package/dist/factory/index.d.mts +2 -2
  13. package/dist/factory/index.mjs +1 -1
  14. package/dist/hooks/index.d.mts +1 -1
  15. package/dist/{index-D1-Kp_dP.d.mts → index-BstGxcc3.d.mts} +1 -1
  16. package/dist/{index-Dwc0orNd.d.mts → index-BswOSJCE.d.mts} +88 -17
  17. package/dist/{index-Bt0F3nJj.d.mts → index-bRjYu21O.d.mts} +1 -1
  18. package/dist/index.d.mts +4 -4
  19. package/dist/index.mjs +3 -3
  20. package/dist/integrations/index.d.mts +1 -1
  21. package/dist/integrations/mcp/index.d.mts +2 -2
  22. package/dist/integrations/mcp/index.mjs +1 -1
  23. package/dist/integrations/mcp/testing.d.mts +1 -1
  24. package/dist/integrations/mcp/testing.mjs +1 -1
  25. package/dist/middleware/index.d.mts +1 -1
  26. package/dist/org/index.d.mts +1 -1
  27. package/dist/pipeline/index.d.mts +1 -1
  28. package/dist/plugins/index.d.mts +2 -35
  29. package/dist/plugins/tracing-entry.mjs +1 -1
  30. package/dist/presets/filesUpload.d.mts +1 -1
  31. package/dist/presets/index.d.mts +1 -1
  32. package/dist/presets/multiTenant.d.mts +1 -1
  33. package/dist/presets/multiTenant.mjs +2 -1
  34. package/dist/presets/search.d.mts +1 -1
  35. package/dist/registry/index.d.mts +1 -1
  36. package/dist/{resourceToTools-BM686jB4.mjs → resourceToTools-tFYUNmM0.mjs} +2 -2
  37. package/dist/testing/index.d.mts +2 -2
  38. package/dist/testing/index.mjs +1 -1
  39. package/dist/types/index.d.mts +1 -1
  40. package/dist/{types-C6ONJ_Z2.d.mts → types-BQsjgQzS.d.mts} +1 -1
  41. package/dist/{types-NGtx3uxV.d.mts → types-DrBaUwyV.d.mts} +40 -4
  42. package/dist/utils/index.d.mts +1 -1
  43. package/dist/{versioning-DTTvc80y.d.mts → versioning-hmkPcDlX.d.mts} +34 -1
  44. package/package.json +5 -5
  45. package/skills/arc/SKILL.md +77 -0
  46. package/skills/arc-code-review/references/anti-patterns.md +44 -0
  47. package/skills/arc-code-review/references/arc-cheatsheet.md +27 -0
package/README.md CHANGED
@@ -155,6 +155,50 @@ Custom checks return `{ granted, reason?, filters?, scope? }` — `filters` prop
155
155
 
156
156
  ---
157
157
 
158
+ ## Aggregations
159
+
160
+ Add `aggregations: { … }` to a resource and arc registers `GET /:prefix/aggregations/:name` per entry. Each runs a portable `$match → $group → $project → $sort → $limit` pipeline against the kit's `repo.aggregate(req, options)` — same shape across mongokit / sqlitekit / prismakit, so dashboards work unchanged across backends.
161
+
162
+ ```typescript
163
+ import { defineResource, defineAggregation } from '@classytic/arc';
164
+
165
+ defineResource({
166
+ name: 'transaction',
167
+ adapter,
168
+ presets: [multiTenantPreset({ tenantField: 'organizationId' })],
169
+ permissions: { list: canViewRevenue() },
170
+
171
+ aggregations: {
172
+ byPaymentMethod: defineAggregation({
173
+ groupBy: 'method',
174
+ measures: { total: 'sum:amount', count: 'count' },
175
+ sort: { total: -1 },
176
+ cache: { staleTime: 60, swr: true, tags: ['revenue'] },
177
+ permissions: canViewRevenue(),
178
+ }),
179
+ byDay: defineAggregation({
180
+ dateBuckets: { day: { field: 'createdAt', interval: 'day' } },
181
+ groupBy: 'flow',
182
+ measures: { total: 'sum:amount', count: 'count' },
183
+ requireDateRange: { field: 'createdAt', maxRangeDays: 365 },
184
+ cache: { staleTime: 60, swr: true, tags: ['revenue'] },
185
+ permissions: canViewRevenue(),
186
+ }),
187
+ },
188
+ });
189
+ ```
190
+
191
+ Caller filters via query string compose with the declaration:
192
+
193
+ ```
194
+ GET /api/transactions/aggregations/byPaymentMethod?status=verified
195
+ GET /api/transactions/aggregations/byDay?createdAt[gte]=2026-01-01&createdAt[lt]=2026-02-01
196
+ ```
197
+
198
+ Tenant scope flows through `repo.aggregate(req, options)` — the kit's multi-tenant plugin handles type-coercion (string → ObjectId for mongokit `fieldType: 'objectId'`, UUID/text for sqlitekit, etc.). Arc itself stays out of the filter slot because it's DB-agnostic. Safety guards on the declaration: `requireFilters`, `requireDateRange { maxRangeDays }`, `maxGroups`. SWR cache + tag invalidation tie aggregations to CRUD writes. Every aggregation auto-exports as an MCP tool with the same permissions and filter validation.
199
+
200
+ ---
201
+
158
202
  ## Authentication
159
203
 
160
204
  Discriminated union on `type`:
@@ -481,9 +481,20 @@ var BaseCrudController = class {
481
481
  resourceName;
482
482
  tenantField;
483
483
  idField = "_id";
484
- /** Composable access control (ID filtering, policy checks, org scope, ownership) */
484
+ /**
485
+ * Composable access control (ID filtering, policy checks, org scope, ownership).
486
+ *
487
+ * Not `readonly` since 2.15.0 — `configure()` rebuilds it when the host
488
+ * supplies tenant/idField/matchesFilter post-construction. Same model as
489
+ * `queryResolver` after `setQueryParser` shipped in 2.10.9.
490
+ */
485
491
  accessControl;
486
- /** Composable body sanitization (field permissions, system fields) */
492
+ /**
493
+ * Composable body sanitization (field permissions, system fields).
494
+ *
495
+ * Not `readonly` since 2.15.0 — `configure()` rebuilds it when the host
496
+ * supplies schemaOptions/onFieldWriteDenied post-construction.
497
+ */
487
498
  bodySanitizer;
488
499
  /**
489
500
  * Composable query resolution (parsing, pagination, sort, select/populate).
@@ -548,6 +559,94 @@ var BaseCrudController = class {
548
559
  this.queryResolver.setParser(queryParser);
549
560
  }
550
561
  /**
562
+ * Apply resource-level options to a custom controller AFTER construction.
563
+ *
564
+ * Closes the pre-2.15 footgun where `defineResource({ controller, tenantField,
565
+ * schemaOptions, ... })` warned that the options were "dropped" because the
566
+ * user-supplied controller never received them. Hosts had to remember to
567
+ * forward each one through `super(repo, { ... })` — easy to miss, silently
568
+ * mis-scopes queries when missed.
569
+ *
570
+ * `defineResource()` now calls `controller.configure(resolvedOpts)` after
571
+ * `resolveOrAutoCreateController()` runs. Configure-aware controllers receive
572
+ * the resolved values; arc skips the dropped-options warn for them.
573
+ *
574
+ * Only the keys that affect cross-cutting state (tenant scope, schema/field
575
+ * rules, sort/limit policy, cache, write-denial policy) are honoured —
576
+ * `repository` / `resourceName` are constructor-only because they participate
577
+ * in mixin composition. Each known key rebuilds the affected sub-component
578
+ * (AccessControl / BodySanitizer / QueryResolver) so referentially-stable
579
+ * consumers don't see stale state.
580
+ *
581
+ * Idempotent: safe to call zero, one, or many times before first request;
582
+ * arc calls it exactly once.
583
+ *
584
+ * Type narrowed to `ControllerConfigurableOptions` — `resourceName` is
585
+ * construction-only and intentionally excluded so accidental "rename
586
+ * the resource at runtime" calls fail to typecheck.
587
+ */
588
+ configure(options) {
589
+ let rebuildAccessControl = false;
590
+ let rebuildBodySanitizer = false;
591
+ let rebuildQueryResolver = false;
592
+ let bodySanitizerOnFieldWriteDenied;
593
+ let resolverDefaultSort;
594
+ if (options.tenantField !== void 0) {
595
+ this.tenantField = options.tenantField;
596
+ rebuildAccessControl = true;
597
+ rebuildQueryResolver = true;
598
+ }
599
+ if (options.idField !== void 0) {
600
+ this.idField = options.idField;
601
+ rebuildAccessControl = true;
602
+ }
603
+ if (options.matchesFilter !== void 0) {
604
+ this._matchesFilter = options.matchesFilter;
605
+ rebuildAccessControl = true;
606
+ }
607
+ if (options.schemaOptions !== void 0) {
608
+ this.schemaOptions = options.schemaOptions;
609
+ rebuildBodySanitizer = true;
610
+ rebuildQueryResolver = true;
611
+ }
612
+ if (options.onFieldWriteDenied !== void 0) {
613
+ bodySanitizerOnFieldWriteDenied = options.onFieldWriteDenied;
614
+ rebuildBodySanitizer = true;
615
+ }
616
+ if (options.queryParser !== void 0) this.setQueryParser(options.queryParser);
617
+ if (options.maxLimit !== void 0) {
618
+ this.maxLimit = options.maxLimit;
619
+ rebuildQueryResolver = true;
620
+ }
621
+ if (options.defaultLimit !== void 0) {
622
+ this.defaultLimit = options.defaultLimit;
623
+ rebuildQueryResolver = true;
624
+ }
625
+ if (options.defaultSort !== void 0) {
626
+ resolverDefaultSort = options.defaultSort;
627
+ rebuildQueryResolver = true;
628
+ }
629
+ if (options.cache !== void 0) this._cacheConfig = options.cache;
630
+ if (options.presetFields !== void 0) this._presetFields = options.presetFields;
631
+ if (rebuildAccessControl) this.accessControl = new AccessControl({
632
+ tenantField: this.tenantField,
633
+ idField: this.idField,
634
+ matchesFilter: this._matchesFilter
635
+ });
636
+ if (rebuildBodySanitizer) this.bodySanitizer = new BodySanitizer({
637
+ schemaOptions: this.schemaOptions,
638
+ onFieldWriteDenied: bodySanitizerOnFieldWriteDenied
639
+ });
640
+ if (rebuildQueryResolver) this.queryResolver = new QueryResolver({
641
+ queryParser: this.queryParser,
642
+ maxLimit: this.maxLimit,
643
+ defaultLimit: this.defaultLimit,
644
+ defaultSort: resolverDefaultSort,
645
+ schemaOptions: this.schemaOptions,
646
+ tenantField: this.tenantField
647
+ });
648
+ }
649
+ /**
551
650
  * Get the tenant field name if multi-tenant scoping is enabled.
552
651
  * Returns `undefined` when `tenantField` is `false`.
553
652
  */
@@ -602,6 +701,7 @@ var BaseCrudController = class {
602
701
  if (presetFields && typeof presetFields === "object") {
603
702
  for (const [key, value] of Object.entries(presetFields)) if (value != null && out[key] == null) out[key] = value;
604
703
  }
704
+ if (this.tenantField && out[this.tenantField] == null && scope && isElevated(scope)) out.bypassTenant = true;
605
705
  const userId = getUserId(req.user);
606
706
  if (userId) out.userId = userId;
607
707
  if (req.user) out.user = req.user;
@@ -1,4 +1,4 @@
1
- import { Rt as AuthHelpers, zt as AuthPluginOptions } from "../index-Dwc0orNd.mjs";
1
+ import { Rt as AuthHelpers, zt as AuthPluginOptions } from "../index-BswOSJCE.mjs";
2
2
  import { c as PermissionCheck } from "../fields-COhcH3fk.mjs";
3
3
  import { t as ExternalOpenApiPaths } from "../externalPaths-BD5nw6St.mjs";
4
4
  import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-C4Le_UB3.mjs";
@@ -78,10 +78,8 @@ function adapterSupportsAggregate(repo) {
78
78
  }
79
79
  /** Compile to canonical `AggRequest` for `repo.aggregate()` at request time. */
80
80
  function compileAggRequest(normalized, callerFilter, tenantOptions) {
81
- const baseFilter = normalized.compiled.filter ?? {};
82
81
  const filter = {
83
- ...extractTenantFilter(tenantOptions),
84
- ...baseFilter,
82
+ ...normalized.compiled.filter ?? {},
85
83
  ...callerFilter
86
84
  };
87
85
  const executionHints = buildExecutionHints(normalized.base);
@@ -374,21 +372,6 @@ function assertFieldAllowed(context, ref, input) {
374
372
  }
375
373
  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.`);
376
374
  }
377
- function extractTenantFilter(tenantOptions) {
378
- const out = {};
379
- const optionOnlyKeys = new Set([
380
- "userId",
381
- "user",
382
- "session",
383
- "requestId"
384
- ]);
385
- for (const [key, value] of Object.entries(tenantOptions)) {
386
- if (optionOnlyKeys.has(key)) continue;
387
- if (value === void 0 || value === null) continue;
388
- out[key] = value;
389
- }
390
- return out;
391
- }
392
375
  //#endregion
393
376
  //#region src/core/aggregation/buildHandler.ts
394
377
  /**
@@ -444,7 +427,7 @@ async function executeAggregation(normalized, deps, ctx) {
444
427
  };
445
428
  let result;
446
429
  try {
447
- result = await repo.aggregate(aggReq);
430
+ result = await repo.aggregate(aggReq, tenantOptions);
448
431
  } catch (err) {
449
432
  return mapAggregateError(err, aggregationName);
450
433
  }
@@ -1,4 +1,4 @@
1
- import { V as ResourceDefinition, ft as RouteSchemaOptions, rt as RateLimitConfig } from "../../index-Dwc0orNd.mjs";
1
+ import { V as ResourceDefinition, ft as RouteSchemaOptions, rt as RateLimitConfig } from "../../index-BswOSJCE.mjs";
2
2
 
3
3
  //#region src/cli/commands/describe.d.ts
4
4
  interface DescribedResource {
@@ -1,3 +1,3 @@
1
- import { $t as SoftDeleteMixin, B as defineResource, Ct as AggregationConfig, Dt as AggregationMaterializedResult, Et as AggregationMaterializedContext, Ot as AggregationRateLimit, Qt as SoftDeleteExt, St as AggregationCacheConfig, Tt as AggregationIndexHint, V as ResourceDefinition, Zt as BaseController, _n as BodySanitizerConfig, an as BulkMixin, bt as AggMeasureInput, cn as QueryResolver, en as TreeExt, gn as BodySanitizer, hn as ListResult, in as BulkExt, kt as AggregationsMap, ln as QueryResolverConfig, nn as SlugExt, on as BaseControllerOptions, rn as SlugMixin, sn as BaseCrudController, tn as TreeMixin, vn as AccessControl, wt as AggregationDateRangeRequirement, xt as AggMeasureShorthand, yn as AccessControlConfig } from "../index-Dwc0orNd.mjs";
2
- import { A as MAX_SEARCH_LENGTH, C as DEFAULT_UPDATE_METHOD, D as HookPhase, E as HookOperation, M as MutationOperation, N as RESERVED_QUERY_PARAMS, O as MAX_FILTER_DEPTH, P as SYSTEM_FIELDS, S as DEFAULT_TENANT_FIELD, T as HOOK_PHASES, _ as CrudOperation, a as createRequestContext, b as DEFAULT_MAX_LIMIT, c as sendControllerResponse, d as getEntityQuery, f as defineResourceVariants, g as CRUD_OPERATIONS, h as defineAggregation, i as createFastifyHandler, j as MUTATION_OPERATIONS, k as MAX_REGEX_LENGTH, l as getEntityId, m as createPermissionMiddleware, n as isFieldReadable, o as getControllerContext, p as createCrudRouter, r as createCrudHandlers, s as getControllerScope, t as collectReadBlockedFields, u as getEntityIdField, v as DEFAULT_ID_FIELD, w as HOOK_OPERATIONS, x as DEFAULT_SORT, y as DEFAULT_LIMIT } from "../index-D1-Kp_dP.mjs";
3
- export { AccessControl, AccessControlConfig, AggMeasureInput, AggMeasureShorthand, AggregationCacheConfig, AggregationConfig, AggregationDateRangeRequirement, AggregationIndexHint, AggregationMaterializedContext, AggregationMaterializedResult, AggregationRateLimit, AggregationsMap, BaseController, BaseControllerOptions, BaseCrudController, BodySanitizer, BodySanitizerConfig, BulkExt, BulkMixin, CRUD_OPERATIONS, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, ListResult, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugExt, SlugMixin, SoftDeleteExt, SoftDeleteMixin, TreeExt, TreeMixin, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAggregation, defineResource, defineResourceVariants, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, sendControllerResponse };
1
+ import { $t as SoftDeleteMixin, B as defineResource, Ct as AggregationConfig, Dt as AggregationMaterializedResult, Et as AggregationMaterializedContext, Ot as AggregationRateLimit, Qt as SoftDeleteExt, St as AggregationCacheConfig, Tt as AggregationIndexHint, V as ResourceDefinition, Zt as BaseController, _n as ListResult, an as BulkMixin, bn as AccessControl, bt as AggMeasureInput, cn as ControllerConfigurableOptions, dn as QueryResolverConfig, en as TreeExt, in as BulkExt, kt as AggregationsMap, ln as ControllerConstructionOptions, nn as SlugExt, on as BaseControllerOptions, rn as SlugMixin, sn as BaseCrudController, tn as TreeMixin, un as QueryResolver, vn as BodySanitizer, wt as AggregationDateRangeRequirement, xn as AccessControlConfig, xt as AggMeasureShorthand, yn as BodySanitizerConfig } from "../index-BswOSJCE.mjs";
2
+ import { A as MAX_SEARCH_LENGTH, C as DEFAULT_UPDATE_METHOD, D as HookPhase, E as HookOperation, M as MutationOperation, N as RESERVED_QUERY_PARAMS, O as MAX_FILTER_DEPTH, P as SYSTEM_FIELDS, S as DEFAULT_TENANT_FIELD, T as HOOK_PHASES, _ as CrudOperation, a as createRequestContext, b as DEFAULT_MAX_LIMIT, c as sendControllerResponse, d as getEntityQuery, f as defineResourceVariants, g as CRUD_OPERATIONS, h as defineAggregation, i as createFastifyHandler, j as MUTATION_OPERATIONS, k as MAX_REGEX_LENGTH, l as getEntityId, m as createPermissionMiddleware, n as isFieldReadable, o as getControllerContext, p as createCrudRouter, r as createCrudHandlers, s as getControllerScope, t as collectReadBlockedFields, u as getEntityIdField, v as DEFAULT_ID_FIELD, w as HOOK_OPERATIONS, x as DEFAULT_SORT, y as DEFAULT_LIMIT } from "../index-BstGxcc3.mjs";
3
+ export { AccessControl, AccessControlConfig, AggMeasureInput, AggMeasureShorthand, AggregationCacheConfig, AggregationConfig, AggregationDateRangeRequirement, AggregationIndexHint, AggregationMaterializedContext, AggregationMaterializedResult, AggregationRateLimit, AggregationsMap, BaseController, BaseControllerOptions, BaseCrudController, BodySanitizer, BodySanitizerConfig, BulkExt, BulkMixin, CRUD_OPERATIONS, ControllerConfigurableOptions, ControllerConstructionOptions, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, ListResult, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugExt, SlugMixin, SoftDeleteExt, SoftDeleteMixin, TreeExt, TreeMixin, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAggregation, defineResource, defineResourceVariants, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, sendControllerResponse };
@@ -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
- 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";
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-dx3m2J8V.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-D29kkRL5.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-CvmOqEms.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 };
@@ -1,11 +1,11 @@
1
1
  import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-Cxde4rpC.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
3
  import { A as assertValidConfig, l as getDefaultCrudSchemas } from "./utils-_h9B3c57.mjs";
4
- import { t as BaseController } from "./BaseController-Dv60tU83.mjs";
4
+ import { t as BaseController } from "./BaseController-dx3m2J8V.mjs";
5
5
  import { t as applyPresets } from "./presets-BbkjdPeH.mjs";
6
6
  import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-De34B1ZG.mjs";
7
7
  import { t as hasEvents } from "./typeGuards-BzkXkvVv.mjs";
8
- import { b as buildRequestScopeProjection, c as buildPreHandlerChain, d as resolveRoutePreHandlers, f as resolveRouterPluginMw, h as createFastifyHandler, i as buildAuthMiddleware, l as buildRateLimitConfig, m as createCrudHandlers, o as buildCrudPermissionMw, p as selectPluginMw, r as buildArcDecorator, s as buildPipelineHandler, u as resolvePipelineSteps } from "./routerShared-DrOa-26E.mjs";
8
+ import { b as buildRequestScopeProjection, c as buildPreHandlerChain, d as resolveRoutePreHandlers, f as resolveRouterPluginMw, g as createRequestContext, h as createFastifyHandler, i as buildAuthMiddleware, l as buildRateLimitConfig, m as createCrudHandlers, o as buildCrudPermissionMw, p as selectPluginMw, r as buildArcDecorator, s as buildPipelineHandler, u as resolvePipelineSteps } from "./routerShared-DrOa-26E.mjs";
9
9
  import { t as resolveActionPermission } from "./actionPermissions-CyUkQu6O.mjs";
10
10
  //#region src/core/aggregation/defineAggregation.ts
11
11
  /**
@@ -283,6 +283,7 @@ function resolveOrAutoCreateController(resolvedConfig, adapter, repository, hasC
283
283
  const userController = resolvedConfig.controller;
284
284
  if (userController) {
285
285
  threadQueryParser(userController, resolvedConfig);
286
+ threadConfigureLifecycle(userController, resolvedConfig, adapter);
286
287
  warnOnDroppedAuthorOptions(resolvedConfig);
287
288
  warnOnDroppedPresetOptions(resolvedConfig);
288
289
  return userController;
@@ -291,11 +292,57 @@ function resolveOrAutoCreateController(resolvedConfig, adapter, repository, hasC
291
292
  return buildBaseController(resolvedConfig, adapter, repository);
292
293
  }
293
294
  /**
295
+ * Forward resource-level options into a user-supplied controller via
296
+ * the duck-typed `configure(opts)` lifecycle hook. Closes the pre-2.15
297
+ * gap where `tenantField` / `schemaOptions` / `idField` / `defaultSort`
298
+ * / `cache` / `onFieldWriteDenied` had no path into a host controller
299
+ * unless the host remembered to forward them through `super(repo,
300
+ * { ... })`. `BaseController` / `BaseCrudController` ship `configure()`;
301
+ * custom controllers can opt in by adding the method.
302
+ *
303
+ * **Gate by `_declaredKeys`**: only forward keys the user literally
304
+ * passed at the resource level. Without this gate, arc-inferred values
305
+ * (e.g. `inferTenantFieldFromAdapter` setting `tenantField: false`
306
+ * because the model's organizationId path doesn't exist) would clobber
307
+ * the matching value the host already set in the controller's
308
+ * constructor (e.g. `new BaseController(repo, { tenantField:
309
+ * "companyId" })`). Resource-level explicit values still win — the
310
+ * snapshot captures what the user typed before any inference runs.
311
+ */
312
+ function threadConfigureLifecycle(controller, resolvedConfig, adapter) {
313
+ const ctrl = controller;
314
+ if (typeof ctrl.configure !== "function") return;
315
+ const declared = resolvedConfig._declaredKeys;
316
+ const wasDeclared = (key) => declared ? declared.has(key) : true;
317
+ const opts = {};
318
+ if (resolvedConfig.tenantField !== void 0 && wasDeclared("tenantField")) opts.tenantField = resolvedConfig.tenantField;
319
+ if (resolvedConfig.schemaOptions !== void 0 && wasDeclared("schemaOptions")) opts.schemaOptions = resolvedConfig.schemaOptions;
320
+ if (resolvedConfig.idField !== void 0 && wasDeclared("idField")) opts.idField = resolvedConfig.idField;
321
+ if (resolvedConfig.defaultSort !== void 0 && wasDeclared("defaultSort")) opts.defaultSort = resolvedConfig.defaultSort;
322
+ if (resolvedConfig.cache !== void 0 && wasDeclared("cache")) opts.cache = resolvedConfig.cache;
323
+ if (resolvedConfig.onFieldWriteDenied !== void 0 && wasDeclared("onFieldWriteDenied")) opts.onFieldWriteDenied = resolvedConfig.onFieldWriteDenied;
324
+ if (resolvedConfig.queryParser !== void 0 && wasDeclared("queryParser")) opts.queryParser = resolvedConfig.queryParser;
325
+ if (adapter?.matchesFilter !== void 0) opts.matchesFilter = adapter.matchesFilter;
326
+ if (resolvedConfig._controllerOptions) opts.presetFields = {
327
+ slugField: resolvedConfig._controllerOptions.slugField,
328
+ parentField: resolvedConfig._controllerOptions.parentField
329
+ };
330
+ if (Object.keys(opts).length === 0) return;
331
+ ctrl.configure(opts);
332
+ }
333
+ /**
294
334
  * Forward a resource-level `queryParser` into a user-supplied
295
335
  * controller via duck-typed `setQueryParser`. Without this the
296
336
  * controller's internal default would silently override the
297
337
  * resource's parser, drifting `[contains]` / `[like]` semantics
298
338
  * away from what the OpenAPI schema advertises.
339
+ *
340
+ * **Fail-loud (2.15.0):** older versions warned and continued —
341
+ * letting the resource register with a silently-shadowed parser. The
342
+ * "ship and pray they read the log" path produced 90-minute "why
343
+ * doesn't `[contains]` work" debugs in production. Now throws at
344
+ * registration so the misconfig surfaces immediately, with the same
345
+ * fix-it message in the error.
299
346
  */
300
347
  function threadQueryParser(controller, resolvedConfig) {
301
348
  if (!resolvedConfig.queryParser) return;
@@ -304,7 +351,7 @@ function threadQueryParser(controller, resolvedConfig) {
304
351
  ctrl.setQueryParser(resolvedConfig.queryParser);
305
352
  return;
306
353
  }
307
- arcLog("defineResource").warn(`Resource "${resolvedConfig.name}" declares a custom \`queryParser\` but its controller does not expose \`setQueryParser(qp)\`. The parser will NOT be threaded into the controller's query resolution — operator filters (\`[contains]\`, \`[like]\`, etc.) may fall back to the controller's internal default. Extend \`BaseController\` / \`BaseCrudController\` (both implement \`setQueryParser\`) OR add the method to your custom controller to honor the resource-level parser.`);
354
+ throw new Error(`Resource "${resolvedConfig.name}" declares a custom \`queryParser\` but its controller does not expose \`setQueryParser(qp)\`. The parser would be silently dropped, drifting \`[contains]\` / \`[like]\` semantics away from the OpenAPI schema. Extend \`BaseController\` / \`BaseCrudController\` (both implement \`setQueryParser\`) OR add a \`setQueryParser(qp)\` method to your custom controller that actually wires the parser into the controller's query resolution path. (arc 2.15.0 hardened this to a registration-time throw — pre-2.15 it was a warn.)`);
308
355
  }
309
356
  /**
310
357
  * Warn when the user supplies their own controller AND declares
@@ -314,15 +361,18 @@ function threadQueryParser(controller, resolvedConfig) {
314
361
  * fix.
315
362
  */
316
363
  function warnOnDroppedAuthorOptions(resolvedConfig) {
364
+ if (typeof resolvedConfig.controller?.configure === "function") return;
365
+ const declared = resolvedConfig._declaredKeys;
366
+ const isDeclared = (key) => declared ? declared.has(key) : true;
317
367
  const dropped = [];
318
- if (resolvedConfig.tenantField !== void 0) dropped.push("tenantField");
319
- if (resolvedConfig.schemaOptions !== void 0 && Object.keys(resolvedConfig.schemaOptions).length > 0) dropped.push("schemaOptions");
320
- if (resolvedConfig.idField !== void 0) dropped.push("idField");
321
- if (resolvedConfig.defaultSort !== void 0) dropped.push("defaultSort");
322
- if (resolvedConfig.cache !== void 0) dropped.push("cache");
323
- if (resolvedConfig.onFieldWriteDenied !== void 0) dropped.push("onFieldWriteDenied");
368
+ if (resolvedConfig.tenantField !== void 0 && isDeclared("tenantField")) dropped.push("tenantField");
369
+ if (resolvedConfig.schemaOptions !== void 0 && Object.keys(resolvedConfig.schemaOptions).length > 0 && isDeclared("schemaOptions")) dropped.push("schemaOptions");
370
+ if (resolvedConfig.idField !== void 0 && isDeclared("idField")) dropped.push("idField");
371
+ if (resolvedConfig.defaultSort !== void 0 && isDeclared("defaultSort")) dropped.push("defaultSort");
372
+ if (resolvedConfig.cache !== void 0 && isDeclared("cache")) dropped.push("cache");
373
+ if (resolvedConfig.onFieldWriteDenied !== void 0 && isDeclared("onFieldWriteDenied")) dropped.push("onFieldWriteDenied");
324
374
  if (dropped.length === 0) return;
325
- arcLog("defineResource").warn(`Resource "${resolvedConfig.name}" declares a custom controller AND resource-level option(s) [${dropped.join(", ")}]. Arc only threads these when it auto-builds the controller — when you pass your own, they are dropped silently and the controller falls back to its own defaults (e.g. tenantField → 'organizationId'). Forward them to your controller's \`super(repo, { ... })\` call. Same root cause as the \`queryParser\` warn above.`);
375
+ arcLog("defineResource").warn(`Resource "${resolvedConfig.name}" declares a custom controller AND resource-level option(s) [${dropped.join(", ")}]. Arc only threads these when it auto-builds the controller — when you pass your own, they are dropped silently and the controller falls back to its own defaults (e.g. tenantField → 'organizationId'). Either implement \`configure(opts)\` on the controller (arc 2.15+ canonical) or forward them via \`super(repo, { ... })\`. Same root cause as the \`queryParser\` warn above.`);
326
376
  }
327
377
  /**
328
378
  * Warn when a preset injected `_controllerOptions` (slugLookup,
@@ -636,8 +686,10 @@ function stripSystemManagedFromBodyRequired(schemas, schemaOptions) {
636
686
  */
637
687
  function applyPresetsAndAutoInject(config) {
638
688
  const originalPresets = (config.presets ?? []).map((p) => typeof p === "string" ? p : p.name);
689
+ const declaredKeys = new Set(Object.keys(config).filter((k) => config[k] !== void 0));
639
690
  const resolvedConfig = config.presets?.length ? applyPresets(config, config.presets) : { ...config };
640
691
  resolvedConfig._appliedPresets = originalPresets;
692
+ resolvedConfig._declaredKeys = declaredKeys;
641
693
  inferTenantFieldFromAdapter(resolvedConfig);
642
694
  resolvedConfig.schemaOptions = autoInjectTenantFieldRules(resolvedConfig.schemaOptions, resolvedConfig.tenantField);
643
695
  return resolvedConfig;
@@ -788,6 +840,7 @@ function normalizeListQuerySchema(listQuerySchema) {
788
840
  for (const key of Object.keys(normalizedProps)) normalizedProps[key] = NORMALIZED_PROPS[key] ?? {};
789
841
  }
790
842
  return {
843
+ type: "object",
791
844
  ...listQuerySchema,
792
845
  ...normalizedProps ? { properties: normalizedProps } : {},
793
846
  additionalProperties: listQuerySchema.additionalProperties ?? true
@@ -945,10 +998,13 @@ function buildResourcePlugin(resource) {
945
998
  });
946
999
  }
947
1000
  if (resource.aggregations && Object.keys(resource.aggregations).length > 0) {
948
- const { createAggregationRouter } = await import("./createAggregationRouter-DhR-Ofiz.mjs");
1001
+ const { createAggregationRouter } = await import("./createAggregationRouter-B0bPDf5b.mjs");
949
1002
  const repoForAgg = resource.controller?.repository;
950
1003
  const buildOptions = (req) => {
951
- return resource.controller?.tenantRepoOptions?.(req) ?? {};
1004
+ const ctrl = resource.controller;
1005
+ if (!ctrl?.tenantRepoOptions) return {};
1006
+ const ctx = createRequestContext(req);
1007
+ return ctrl.tenantRepoOptions(ctx);
952
1008
  };
953
1009
  createAggregationRouter(typedInstance, {
954
1010
  tag: resource.tag,
@@ -959,7 +1015,8 @@ function buildResourcePlugin(resource) {
959
1015
  permissions: resource.permissions,
960
1016
  routeGuards: resource.routeGuards,
961
1017
  repository: repoForAgg,
962
- buildOptions
1018
+ buildOptions,
1019
+ middlewares: resource.middlewares?.aggregations
963
1020
  });
964
1021
  }
965
1022
  if (resource.events && Object.keys(resource.events).length > 0) typedInstance.log?.debug?.(`Resource '${resource.name}' defined ${Object.keys(resource.events).length} events`);
@@ -1,13 +1,13 @@
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-jSZ6Fdvi.mjs";
3
+ import { r as validateAggregations, t as buildAggregationHandler } from "./buildHandler-CcFOpJLh.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
7
7
  * empty — same convention `createActionRouter` follows.
8
8
  */
9
9
  function createAggregationRouter(fastify, config) {
10
- const { tag, resourceName, aggregations, fields: fieldPermissions, schemaOptions, permissions: resourcePermissions, routeGuards = [], repository, buildOptions } = config;
10
+ const { tag, resourceName, aggregations, fields: fieldPermissions, schemaOptions, permissions: resourcePermissions, routeGuards = [], repository, buildOptions, middlewares = [] } = config;
11
11
  if (!aggregations || Object.keys(aggregations).length === 0) return;
12
12
  const normalized = validateAggregations(resourceName, aggregations, schemaOptions);
13
13
  const arcDecorator = buildArcDecorator({
@@ -23,7 +23,8 @@ function createAggregationRouter(fastify, config) {
23
23
  arcDecorator,
24
24
  routeGuards,
25
25
  repository,
26
- buildOptions
26
+ buildOptions,
27
+ middlewares
27
28
  });
28
29
  fastify.log?.debug?.({
29
30
  aggregations: normalized.map((a) => a.name),
@@ -31,7 +32,7 @@ function createAggregationRouter(fastify, config) {
31
32
  }, `[createAggregationRouter] registered ${normalized.length} aggregation route(s)`);
32
33
  }
33
34
  function registerOne(fastify, normalized, ctx) {
34
- const { tag, arcDecorator, routeGuards, repository, buildOptions } = ctx;
35
+ const { tag, arcDecorator, routeGuards, repository, buildOptions, middlewares } = ctx;
35
36
  const { name } = normalized;
36
37
  const config = normalized.base;
37
38
  const authMw = buildAuthMiddleware(fastify, config.permissions);
@@ -50,7 +51,8 @@ function registerOne(fastify, normalized, ctx) {
50
51
  authMw,
51
52
  permissionMw,
52
53
  pluginMw: selectPluginMw("GET", resolveRouterPluginMw(fastify, false)),
53
- routeGuards
54
+ routeGuards,
55
+ customMws: middlewares
54
56
  });
55
57
  const rateLimitConfig = buildRateLimitConfig(config.rateLimit ? {
56
58
  max: config.rateLimit.max,
@@ -230,8 +230,9 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
230
230
  trackPlugin("arc-request-id");
231
231
  }
232
232
  if (config.arcPlugins?.health !== false) {
233
- await fastify.register(healthPlugin);
234
- trackPlugin("arc-health");
233
+ const healthOpts = typeof config.arcPlugins?.health === "object" && config.arcPlugins.health !== null ? config.arcPlugins.health : {};
234
+ await fastify.register(healthPlugin, healthOpts);
235
+ trackPlugin("arc-health", healthOpts);
235
236
  }
236
237
  if (config.arcPlugins?.gracefulShutdown !== false) {
237
238
  await fastify.register(gracefulShutdownPlugin);
@@ -707,9 +708,65 @@ async function registerUtilityPlugins(fastify, config) {
707
708
  */
708
709
  var createApp_exports = /* @__PURE__ */ __exportAll({
709
710
  ArcFactory: () => ArcFactory,
710
- createApp: () => createApp
711
+ DEFAULT_LOGGER_REDACT_PATHS: () => DEFAULT_LOGGER_REDACT_PATHS,
712
+ createApp: () => createApp,
713
+ resolveLoggerConfig: () => resolveLoggerConfig
711
714
  });
712
715
  const MEMORY_STORE_NAMES = new Set(["memory", "memory-cache"]);
716
+ /**
717
+ * Default redact paths layered into Fastify's pino logger when the host
718
+ * doesn't supply a `logger.redact` of their own. Covers the common token /
719
+ * cookie / password leak surfaces — `Authorization` and `Cookie` headers
720
+ * (and their case-variants because Node lower-cases incoming headers but
721
+ * outgoing logs may carry either), API-key headers, and any nested
722
+ * `password` / `token` / `secret` / `apiKey` fields anywhere in the log
723
+ * tree.
724
+ *
725
+ * Hosts that need NO redaction (test fixtures, security audits) opt out
726
+ * with `logger: { redact: [] }`. Hosts that need MORE redaction supply
727
+ * their own `redact` and arc steps aside — this default never overrides
728
+ * an explicit setting. (2.15.1)
729
+ */
730
+ const DEFAULT_LOGGER_REDACT_PATHS = [
731
+ "req.headers.authorization",
732
+ "req.headers[\"x-api-key\"]",
733
+ "req.headers[\"x-internal-api-key\"]",
734
+ "req.headers.cookie",
735
+ "req.headers[\"set-cookie\"]",
736
+ "res.headers[\"set-cookie\"]",
737
+ "*.password",
738
+ "*.passwordHash",
739
+ "*.token",
740
+ "*.accessToken",
741
+ "*.refreshToken",
742
+ "*.secret",
743
+ "*.apiKey"
744
+ ];
745
+ /**
746
+ * Resolve the host's `logger` option, layering safe redact defaults
747
+ * when the host hasn't supplied any. Returns the value Fastify expects
748
+ * for its `logger` server option.
749
+ *
750
+ * Three branches:
751
+ * - `logger === false` → pass through (no logger).
752
+ * - `logger === undefined` → enable with default redact paths.
753
+ * - `logger === true` → enable with default redact paths.
754
+ * - `logger` is an object → respect explicit `redact` (host wins);
755
+ * otherwise inject defaults.
756
+ */
757
+ function resolveLoggerConfig(logger) {
758
+ if (logger === false) return false;
759
+ if (logger === true || logger === void 0) return { redact: [...DEFAULT_LOGGER_REDACT_PATHS] };
760
+ if (typeof logger === "object" && logger !== null) {
761
+ const objLogger = logger;
762
+ if (objLogger.redact !== void 0) return logger;
763
+ return {
764
+ ...objLogger,
765
+ redact: [...DEFAULT_LOGGER_REDACT_PATHS]
766
+ };
767
+ }
768
+ return logger;
769
+ }
713
770
  function validateAuthOptions(options) {
714
771
  const authConfig = options.auth;
715
772
  if (authConfig === false || !authConfig) return;
@@ -766,14 +823,17 @@ async function createApp(options) {
766
823
  ...options
767
824
  };
768
825
  const fastify = Fastify({
769
- logger: config.logger ?? true,
826
+ logger: resolveLoggerConfig(config.logger),
770
827
  trustProxy: config.trustProxy ?? false,
771
828
  pluginTimeout: config.pluginTimeout ?? 1e4,
829
+ ...config.bodyLimit !== void 0 ? { bodyLimit: config.bodyLimit } : {},
830
+ allowErrorHandlerOverride: true,
772
831
  routerOptions: { querystringParser: (str) => qs.parse(str) },
773
832
  ajv: { customOptions: {
774
833
  coerceTypes: true,
775
834
  useDefaults: true,
776
835
  removeAdditional: false,
836
+ strictTypes: false,
777
837
  keywords: ["example", ...config.ajv?.keywords ?? []]
778
838
  } }
779
839
  });
@@ -1,4 +1,4 @@
1
- import { p as RegistryEntry } from "../index-Dwc0orNd.mjs";
1
+ import { p as RegistryEntry } from "../index-BswOSJCE.mjs";
2
2
  import { t as ExternalOpenApiPaths } from "../externalPaths-BD5nw6St.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
@@ -1,5 +1,5 @@
1
- import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as ResourceModule, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, p as loadResources, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-NGtx3uxV.mjs";
2
- import { FastifyInstance } from "fastify";
1
+ import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as ResourceModule, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, p as loadResources, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-DrBaUwyV.mjs";
2
+ import { FastifyInstance, FastifyServerOptions } from "fastify";
3
3
 
4
4
  //#region src/factory/createApp.d.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-BarYhXCZ.mjs";
1
+ import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-PFegs47-.mjs";
2
2
  import { t as loadResources } from "../loadResources-DBMQg_Aj.mjs";
3
3
  //#region src/factory/edge.ts
4
4
  /**
@@ -1,2 +1,2 @@
1
- import { An as afterUpdate, Cn as HookOperation, Dn as HookSystemOptions, En as HookSystem, Fn as defineHook, Mn as beforeDelete, Nn as beforeUpdate, On as afterCreate, Pn as createHookSystem, Sn as HookHandler, Tn as HookRegistration, bn as DefineHookOptions, jn as beforeCreate, kn as afterDelete, wn as HookPhase, xn as HookContext } from "../index-Dwc0orNd.mjs";
1
+ import { An as afterCreate, Cn as HookContext, Dn as HookRegistration, En as HookPhase, Fn as beforeUpdate, In as createHookSystem, Ln as defineHook, Mn as afterUpdate, Nn as beforeCreate, On as HookSystem, Pn as beforeDelete, Sn as DefineHookOptions, Tn as HookOperation, jn as afterDelete, kn as HookSystemOptions, wn as HookHandler } from "../index-BswOSJCE.mjs";
2
2
  export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -1,4 +1,4 @@
1
- import { C as RequestContext, Ct as AggregationConfig, F as FastifyWithDecorators, K as ArcFieldRule, L as RequestWithExtras, T as CrudRouterOptions, V as ResourceDefinition, Wt as AnyRecord, _t as IControllerResponse, at as ResourceConfig, ft as RouteSchemaOptions, gt as IController, q as CrudController, vt as IRequestContext } from "./index-Dwc0orNd.mjs";
1
+ import { C as RequestContext, Ct as AggregationConfig, F as FastifyWithDecorators, K as ArcFieldRule, L as RequestWithExtras, T as CrudRouterOptions, V as ResourceDefinition, Wt as AnyRecord, _t as IControllerResponse, at as ResourceConfig, ft as RouteSchemaOptions, gt as IController, q as CrudController, vt as IRequestContext } from "./index-BswOSJCE.mjs";
2
2
  import { i as RequestScope } from "./types-CTYvcwHe.mjs";
3
3
  import { c as PermissionCheck } from "./fields-COhcH3fk.mjs";
4
4
  import { FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";