@classytic/arc 2.21.0 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/{BaseController-BnLYgE9A.mjs → BaseController-C6FlRWxF.mjs} +1 -1
  2. package/dist/audit/index.d.mts +2 -2
  3. package/dist/auth/index.d.mts +30 -2
  4. package/dist/auth/index.mjs +5 -2
  5. package/dist/cli/commands/describe.d.mts +1 -1
  6. package/dist/cli/commands/docs.mjs +1 -1
  7. package/dist/cli/commands/init.mjs +1 -1
  8. package/dist/cli/index.mjs +1 -1
  9. package/dist/core/index.d.mts +2 -2
  10. package/dist/core/index.mjs +2 -2
  11. package/dist/{core-DZ6Xe1jR.mjs → core-b6fgFgnd.mjs} +123 -66
  12. package/dist/{createActionRouter-BWMDW-dL.mjs → createActionRouter-h1cEEaEX.mjs} +9 -5
  13. package/dist/{createAggregationRouter-l5mNdYrZ.mjs → createAggregationRouter-B1C0kvTL.mjs} +3 -3
  14. package/dist/{createApp-Bi61XxqU.mjs → createApp-Ci7yG5gv.mjs} +242 -56
  15. package/dist/docs/index.d.mts +1 -1
  16. package/dist/docs/index.mjs +1 -1
  17. package/dist/encryption/index.mjs +9 -5
  18. package/dist/{errorHandler-Dxf6P_kV.mjs → errorHandler-BTzXSwIg.mjs} +24 -3
  19. package/dist/{errorHandler-B0e-bq-O.d.mts → errorHandler-C6yv4ZTW.d.mts} +13 -0
  20. package/dist/factory/index.d.mts +29 -8
  21. package/dist/factory/index.mjs +34 -3
  22. package/dist/{fastify-augmentation-DafigGQp.d.mts → fastify-augmentation-9_fuetB4.d.mts} +2 -2
  23. package/dist/fields-D3lhgzhD.d.mts +121 -0
  24. package/dist/health-ScyjIVo5.mjs +202 -0
  25. package/dist/hooks/index.d.mts +1 -1
  26. package/dist/idempotency/index.mjs +2 -0
  27. package/dist/{types-DpxoNX5v.d.mts → index-BEu4wjbL.d.mts} +187 -66
  28. package/dist/{index-B5N-NaHs.d.mts → index-D8MKYGDa.d.mts} +3 -3
  29. package/dist/{index-Dtrj6XZk.d.mts → index-DbtgCOCj.d.mts} +1287 -1225
  30. package/dist/{index-B5PoNbfu.d.mts → index-ogIHvOjo.d.mts} +1 -1
  31. package/dist/index.d.mts +6 -5
  32. package/dist/index.mjs +3 -3
  33. package/dist/{init-0YhHkXGk.mjs → init-DleK3p9f.mjs} +118 -2
  34. package/dist/integrations/index.d.mts +1 -1
  35. package/dist/integrations/mcp/index.d.mts +3 -3
  36. package/dist/integrations/mcp/index.mjs +1 -1
  37. package/dist/integrations/mcp/testing.d.mts +1 -1
  38. package/dist/integrations/mcp/testing.mjs +1 -1
  39. package/dist/integrations/webhooks.d.mts +8 -4
  40. package/dist/integrations/webhooks.mjs +9 -5
  41. package/dist/jsonBody-BlzAoGCe.mjs +43 -0
  42. package/dist/logger/index.d.mts +26 -1
  43. package/dist/logger/index.mjs +39 -1
  44. package/dist/{mcpPlugin-B9XjjNVG.mjs → mcpPlugin-ByA2tf6D.mjs} +6 -2
  45. package/dist/middleware/index.d.mts +1 -1
  46. package/dist/{module-WeuU5jfd.d.mts → module-B4u10iOJ.d.mts} +26 -1
  47. package/dist/{openapi-Kyod_di6.mjs → openapi-BRpMZD7C.mjs} +2 -2
  48. package/dist/permissions/index.d.mts +3 -2
  49. package/dist/pipeline/index.d.mts +1 -1
  50. package/dist/plugins/index.d.mts +22 -5
  51. package/dist/plugins/index.mjs +11 -280
  52. package/dist/plugins/response-cache.mjs +1 -0
  53. package/dist/plugins/tracing-entry.mjs +7 -4
  54. package/dist/presets/filesUpload.d.mts +2 -2
  55. package/dist/presets/index.d.mts +1 -1
  56. package/dist/presets/multiTenant.d.mts +1 -1
  57. package/dist/presets/search.d.mts +2 -2
  58. package/dist/rateLimitKey-BoWRH3e7.mjs +14 -0
  59. package/dist/{registerRoute-oGAl0oA8.mjs → registerRoute-xHc42fwB.mjs} +37 -17
  60. package/dist/registry/index.d.mts +2 -2
  61. package/dist/requestId-5BhTVzBv.mjs +113 -0
  62. package/dist/schemaConverter-Ds5reSGs.mjs +168 -0
  63. package/dist/{schemaIR-ajetw3Fv.mjs → schemaIR-B-6SPe7g.mjs} +2 -2
  64. package/dist/scim/index.mjs +6 -7
  65. package/dist/scope/index.mjs +2 -76
  66. package/dist/scope-k15RXfcq.mjs +64 -0
  67. package/dist/testing/index.d.mts +83 -3
  68. package/dist/testing/index.mjs +70 -3
  69. package/dist/types/index.d.mts +2 -2
  70. package/dist/{types-DRj-Ut4n.d.mts → types-Bc42CUyk.d.mts} +1 -1
  71. package/dist/{fields-BGqrAa_u.d.mts → types-Csxga7d3.d.mts} +1 -121
  72. package/dist/usage/index.d.mts +142 -0
  73. package/dist/usage/index.mjs +0 -0
  74. package/dist/utils/index.d.mts +40 -18
  75. package/dist/utils/index.mjs +2 -2
  76. package/dist/{utils-CO8a3B_1.mjs → utils-FhJ88TJE.mjs} +59 -13
  77. package/dist/{versioning-DQ4mCvg0.d.mts → versioning-DLwklPlG.d.mts} +0 -5
  78. package/llms.txt +39 -0
  79. package/package.json +453 -447
  80. package/skills/arc/SKILL.md +14 -8
  81. package/skills/arc/references/api-reference.md +34 -0
  82. package/skills/arc/references/auth.md +21 -4
  83. package/skills/arc/references/mcp.md +1 -1
  84. package/skills/arc/references/production.md +44 -2
  85. package/skills/arc-code-review/SKILL.md +1 -1
  86. package/skills/arc-code-review/references/anti-patterns.md +71 -18
  87. package/skills/arc-code-review/references/arc-cheatsheet.md +15 -2
  88. package/skills/arc-code-review/references/migration-recipes.md +7 -7
  89. package/skills/arc-code-review/references/mongokit-migration.md +4 -3
  90. package/skills/arc-module-publishing/SKILL.md +2 -1
  91. package/skills/arc-module-publishing/references/authoring-modules.md +55 -0
  92. package/skills/arc-module-publishing/references/package-manifest.md +4 -2
  93. package/dist/preloadResources-CHw3_oA_.mjs +0 -45
  94. package/dist/schemaConverter-U0MnO9vd.mjs +0 -110
@@ -1,6 +1,6 @@
1
1
  import { r as CacheStore, t as CacheLogger } from "./interface-C1_WLTMK.mjs";
2
2
  import { i as RequestScope, n as Mandate } from "./types-DH0GupI7.mjs";
3
- import { c as PermissionCheck, l as PermissionContext, u as PermissionResult } from "./fields-BGqrAa_u.mjs";
3
+ import { n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-Csxga7d3.mjs";
4
4
  import { FastifyReply, FastifyRequest } from "fastify";
5
5
  //#region src/permissions/agent.d.ts
6
6
  /**
package/dist/index.d.mts CHANGED
@@ -1,8 +1,9 @@
1
- import { $t as UserOrganization, A as RequestIdOptions, An as ArcCreateResult, At as AggregationMaterializedResult, B as defineResource, C as RequestContext, Ct as AggMeasureInput, D as HealthCheck, Dt as AggregationDateRangeRequirement, E as GracefulShutdownOptions, Et as AggregationConfig, F as FastifyWithDecorators, Fn as BaseControllerOptions, Ht as AuthPluginOptions, In as ControllerConfigurableOptions, J as CrudRouteKey, Jt as ApiResponse, L as RequestWithExtras, Ln as ControllerConstructionOptions, Mn as ArcGetResult, Mt as AggregationsMap, N as FastifyRequestExtras, Nn as ArcListResult, O as HealthOptions, Ot as AggregationIndexHint, P as FastifyWithAuth, Pn as ArcUpdateResult, Q as MiddlewareConfig, Rn as ListResult, S as QueryParserInterface, St as RouteHandler, T as CrudRouterOptions, Tt as AggregationCacheConfig, V as ResourceDefinition, X as EventDefinition, Xt as JWTPayload, Y as CrudSchemas, Yt as ArcRequest, Z as FieldRule, _t as ControllerLike, a as InferAdapterDoc, an as SlugExt, bt as IControllerResponse, c as TypedController, cn as BulkMixin, d as PaginationResult, en as BaseController, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, ht as RouteSchemaOptions, i as ValidationResult, in as TreeMixin, it as RateLimitConfig, jn as ArcDeleteResult, jt as AggregationRateLimit, k as IntrospectionPluginOptions, kt as AggregationMaterializedContext, l as TypedRepository, ln as BaseCrudController, m as RegistryStats, n as ConfigError, nn as SoftDeleteMixin, o as InferDocType, on as SlugMixin, p as RegistryEntry, q as CrudController, qt as AnyRecord, r as ValidateOptions, rn as TreeExt, rt as PresetResult, s as InferResourceDoc, sn as BulkExt, st as ResourceConfig, t as RouteHandlerMethod, tn as SoftDeleteExt, tt as PresetFunction, u as TypedResourceConfig, w as ServiceContext, wt as AggMeasureShorthand, xt as IRequestContext, y as OwnershipCheck, yt as IController } from "./index-Dtrj6XZk.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-BGqrAa_u.mjs";
3
- import "./fastify-augmentation-DafigGQp.mjs";
4
- import { A as HOOK_OPERATIONS, B as SYSTEM_FIELDS, C as CrudOperation, D as DEFAULT_SORT, E as DEFAULT_MAX_LIMIT, F as MAX_REGEX_LENGTH, I as MAX_SEARCH_LENGTH, L as MUTATION_OPERATIONS, M as HookOperation, N as HookPhase, O as DEFAULT_TENANT_FIELD, P as MAX_FILTER_DEPTH, R as MutationOperation, S as CRUD_OPERATIONS, T as DEFAULT_LIMIT, f as defineResourceVariants, g as mergeResourceConfig, h as SeamedResourceConfig, j as HOOK_PHASES, k as DEFAULT_UPDATE_METHOD, m as ResourceSeams, p as AdapterLike, s as getControllerScope, v as defineAction, w as DEFAULT_ID_FIELD, x as defineAggregation, z as RESERVED_QUERY_PARAMS } from "./index-B5N-NaHs.mjs";
5
- import { A as requireOwnership, C as createOrgPermissions, D as denyAll, E as anyOf, N as when, S as createDynamicPermissionMatrix, T as allowPublic, b as DynamicPermissionMatrixConfig, c as publicReadAdminWrite, d as requireOrgMembership, f as requireOrgRole, h as requireTeamMembership, i as ownerWithAdminBypass, j as requireRoles, k as requireAuth, l as readOnly, m as requireServiceScope, n as authenticated, o as presets_d_exports, p as requireScopeContext, r as fullPublic, s as publicRead, t as adminOnly, u as requireOrgInScope, w as allOf, y as DynamicPermissionMatrix } from "./index-B5PoNbfu.mjs";
1
+ import { $t as UserOrganization, A as RequestIdOptions, An as ArcCreateResult, B as defineResource, Bt as RouteHandler, C as RequestContext, D as HealthCheck, E as GracefulShutdownOptions, F as FastifyWithDecorators, Fn as BaseControllerOptions, Ft as ControllerLike, H as CrudController, Ht as AuthPluginOptions, In as ControllerConfigurableOptions, Jt as ApiResponse, L as RequestWithExtras, Ln as ControllerConstructionOptions, Lt as IController, Mn as ArcGetResult, N as FastifyRequestExtras, Nn as ArcListResult, Nt as RateLimitConfig, O as HealthOptions, P as FastifyWithAuth, Pn as ArcUpdateResult, Q as MiddlewareConfig, Rn as ListResult, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, U as ResourceConfig, V as ResourceDefinition, X as CrudRouteKey, Xt as JWTPayload, Y as PresetResult, Yt as ArcRequest, Z as CrudSchemas, a as InferAdapterDoc, an as SlugExt, c as TypedController, cn as BulkMixin, ct as AggMeasureShorthand, d as PaginationResult, dt as AggregationDateRangeRequirement, en as BaseController, et as RouteSchemaOptions, f as IntrospectionData, ft as AggregationIndexHint, g as ArcInternalMetadata, gt as AggregationsMap, h as ResourceMetadata, ht as AggregationRateLimit, i as ValidationResult, in as TreeMixin, jn as ArcDeleteResult, k as IntrospectionPluginOptions, l as TypedRepository, ln as BaseCrudController, lt as AggregationCacheConfig, m as RegistryStats, mt as AggregationMaterializedResult, n as ConfigError, nn as SoftDeleteMixin, nt as FieldRule, o as InferDocType, on as SlugMixin, ot as EventDefinition, p as RegistryEntry, pt as AggregationMaterializedContext, q as PresetFunction, qt as AnyRecord, r as ValidateOptions, rn as TreeExt, s as InferResourceDoc, sn as BulkExt, st as AggMeasureInput, t as RouteHandlerMethod, tn as SoftDeleteExt, u as TypedResourceConfig, ut as AggregationConfig, w as ServiceContext, y as OwnershipCheck, zt as IRequestContext } from "./index-DbtgCOCj.mjs";
2
+ import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-Csxga7d3.mjs";
3
+ import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, t as FieldPermission } from "./fields-D3lhgzhD.mjs";
4
+ import "./fastify-augmentation-9_fuetB4.mjs";
5
+ import { A as HOOK_OPERATIONS, B as SYSTEM_FIELDS, C as CrudOperation, D as DEFAULT_SORT, E as DEFAULT_MAX_LIMIT, F as MAX_REGEX_LENGTH, I as MAX_SEARCH_LENGTH, L as MUTATION_OPERATIONS, M as HookOperation, N as HookPhase, O as DEFAULT_TENANT_FIELD, P as MAX_FILTER_DEPTH, R as MutationOperation, S as CRUD_OPERATIONS, T as DEFAULT_LIMIT, f as defineResourceVariants, g as mergeResourceConfig, h as SeamedResourceConfig, j as HOOK_PHASES, k as DEFAULT_UPDATE_METHOD, m as ResourceSeams, p as AdapterLike, s as getControllerScope, v as defineAction, w as DEFAULT_ID_FIELD, x as defineAggregation, z as RESERVED_QUERY_PARAMS } from "./index-D8MKYGDa.mjs";
6
+ import { A as requireOwnership, C as createOrgPermissions, D as denyAll, E as anyOf, N as when, S as createDynamicPermissionMatrix, T as allowPublic, b as DynamicPermissionMatrixConfig, c as publicReadAdminWrite, d as requireOrgMembership, f as requireOrgRole, h as requireTeamMembership, i as ownerWithAdminBypass, j as requireRoles, k as requireAuth, l as readOnly, m as requireServiceScope, n as authenticated, o as presets_d_exports, p as requireScopeContext, r as fullPublic, s as publicRead, t as adminOnly, u as requireOrgInScope, w as allOf, y as DynamicPermissionMatrix } from "./index-ogIHvOjo.mjs";
6
7
  import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-DHcHVAgk.mjs";
7
8
  //#region src/index.d.ts
8
9
  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-DNre0wRg.mjs";
2
2
  import { d as createDomainError, i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-DkO8Nk6-.mjs";
3
- import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-BnLYgE9A.mjs";
3
+ import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-C6FlRWxF.mjs";
4
4
  import { E as requireRoles, F as applyFieldReadPermissions, I as applyFieldWritePermissions, L as fields, O as when, S as denyAll, T as requireOwnership, _ as requireServiceScope, b as allowPublic, c as publicReadAdminWrite, d as createDynamicPermissionMatrix, f as createOrgPermissions, g as requireScopeContext, h as requireOrgRole, i as ownerWithAdminBypass, l as readOnly, m as requireOrgMembership, n as authenticated, o as presets_exports, p as requireOrgInScope, r as fullPublic, s as publicRead, t as adminOnly, v as requireTeamMembership, w as requireAuth, x as anyOf, y as allOf } from "./permissions-BJMuKMl7.mjs";
5
- import { a as mergeResourceConfig, c as defineAction, d as defineAggregation, i as defineResourceVariants, o as defineResource, s as ResourceDefinition } from "./core-DZ6Xe1jR.mjs";
5
+ import { a as defineResource, c as defineAction, d as defineAggregation, i as defineResourceVariants, o as ResourceDefinition, s as mergeResourceConfig } from "./core-b6fgFgnd.mjs";
6
6
  import { l as getControllerScope } from "./actionPermissions-ze70xXJi.mjs";
7
7
  //#region src/index.ts
8
- const version = "2.21.0";
8
+ const version = "2.22.0";
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, mergeResourceConfig, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, version, when };
@@ -1,3 +1,4 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
1
2
  import * as fs from "node:fs/promises";
2
3
  import * as path from "node:path";
3
4
  import { dirname, join } from "node:path";
@@ -76,6 +77,60 @@ export function createAdapter${ts ? "<TDoc = unknown>" : ""}(
76
77
  `;
77
78
  }
78
79
  //#endregion
80
+ //#region src/cli/commands/init/templates/agents.ts
81
+ function agentsGuideTemplate(config) {
82
+ return `# CLAUDE.md — agent guide for this app
83
+
84
+ > This file doubles as AGENTS.md — copy or symlink if your tool reads that name.
85
+
86
+ **This is an \`@classytic/arc\` application.** Resources are DECLARED, not hand-rolled:
87
+ one \`defineResource()\` = REST + validation + auth + permissions + events + caching +
88
+ OpenAPI + MCP tools. Data layer: ${config.adapter === "mongokit" ? "@classytic/mongokit (Mongoose)" : "a custom DataAdapter kit"}.
89
+
90
+ ## The one rule
91
+
92
+ **Hand-rolling is a bug.** Before writing ANY route, validation, pagination, auth check,
93
+ cron, counter, or audit code by hand, prove arc lacks it — check the table below, then
94
+ \`node_modules/@classytic/arc/skills/arc/SKILL.md\` (full framework guide + references).
95
+
96
+ ## About to write X? Arc already has Y
97
+
98
+ | If you're about to write… | Use instead |
99
+ |---|---|
100
+ | \`fastify.get/post/...\` CRUD routes for an entity | \`defineResource({ name, adapter, permissions })\` |
101
+ | \`if (req.user.role !== 'admin') return 403\` | \`permissions: { update: requireRoles(['admin']) }\` |
102
+ | Reading \`req.user._id\` / org id directly | \`getUserId(scope)\` / \`requireOrgId(scope)\` from \`@classytic/arc/scope\` |
103
+ | Hand-written \`schema: { body }\` / JSON Schema | \`schemaOptions.fieldRules\` (or \`customSchemas\` for wire≠model) |
104
+ | toJSON transforms hiding \`password\`/internals | \`fieldRules: { password: { hidden: true } }\` |
105
+ | Manual \`req.query\` filter/sort/pagination parsing | the resource's \`queryParser\` (allow-listed fields) |
106
+ | Soft delete / slug lookup / tree / bulk / file upload | \`presets: ['softDelete', 'slugLookup', ...]\` |
107
+ | \`setInterval\` sweeps + hand-rolled leader lock | \`schedulesPlugin\` (\`@classytic/arc/plugins\`) |
108
+ | A background/queue worker entrypoint | \`createWorker(sameOptions)\` (\`@classytic/arc/factory\`) |
109
+ | Usage counters / quotas / plan rate limits | \`usagePlugin\` + \`requireQuota\` + \`rateLimit.plan\` |
110
+ | Audit log rows / "who changed what" endpoints | \`audit: true\` + \`history: true\` on the resource |
111
+ | Manual idempotency keys / dedupe | \`idempotencyPlugin\` |
112
+ | Event emitters / webhooks plumbing | \`eventPlugin\` + resource \`events:\` (+ outbox for guarantees) |
113
+ | Custom MCP/AI tool handlers | generated automatically from every resource |
114
+
115
+ ## Load-bearing conventions
116
+
117
+ - \`request.user\` is \`undefined\` on public routes — always guard.
118
+ - Never read \`scope.organizationId\` directly — use \`@classytic/arc/scope\` accessors.
119
+ - The DB connection belongs in \`createApp({ beforeBoot })\` — not before \`createApp\`.
120
+ - Custom endpoints: \`routes:\`/\`actions:\` on the resource (they inherit auth/permissions/
121
+ OpenAPI/MCP) — a bare \`fastify.get()\` gets none of that.
122
+
123
+ ## Commands
124
+
125
+ \`\`\`bash
126
+ npm run dev # watch mode
127
+ npm test # vitest
128
+ npx arc generate resource <name> # scaffold a resource (model/repo/resource files)
129
+ npx arc describe # list registered resources + routes
130
+ \`\`\`
131
+ `;
132
+ }
133
+ //#endregion
79
134
  //#region src/cli/commands/init/templates/app.ts
80
135
  function indexTemplate(config) {
81
136
  const ts = config.typescript;
@@ -442,8 +497,8 @@ function betterAuthSetupTemplate(config) {
442
497
  import { mongodbAdapter } from '@better-auth/mongo-adapter';` : "";
443
498
  const stubsImport = useStubs ? `import { registerBetterAuthStubs } from '@classytic/mongokit/better-auth';` : "";
444
499
  const pluginImports = [
445
- useOrg ? `import { organization } from 'better-auth/plugins';` : "",
446
- useBearer ? `import { bearer } from 'better-auth/plugins';` : "",
500
+ useOrg ? `import { organization } from 'better-auth/plugins/organization';` : "",
501
+ useBearer ? `import { bearer } from 'better-auth/plugins/bearer';` : "",
447
502
  useApiKey ? `import { apiKey } from '@better-auth/api-key';` : ""
448
503
  ].filter(Boolean).join("\n");
449
504
  const dbAdapter = useMongo ? config.typescript ? `database: mongodbAdapter(mongoose.connection.getClient().db() as unknown as never),` : `database: mongodbAdapter(mongoose.connection.getClient().db()),` : `// Configure your database adapter here
@@ -985,6 +1040,11 @@ ${config.adapter === "mongokit" ? ` await mongoose.connection.collection('use
985
1040
  * `installDependencies` (runs the package manager's `install` against
986
1041
  * the declared ranges). One source — no drift.
987
1042
  */
1043
+ var dependency_plan_exports = /* @__PURE__ */ __exportAll({
1044
+ SCAFFOLD_DEP_VERSIONS: () => SCAFFOLD_DEP_VERSIONS,
1045
+ primeLatestScaffoldVersions: () => primeLatestScaffoldVersions,
1046
+ resolveScaffoldDependencies: () => resolveScaffoldDependencies
1047
+ });
988
1048
  const SCAFFOLD_DEP_VERSIONS = {
989
1049
  core: {
990
1050
  "@classytic/arc": "^2.20.0",
@@ -1069,6 +1129,51 @@ function floorLessThan(a, b) {
1069
1129
  * floor>` when the static pin's floor is BELOW the declared peer floor
1070
1130
  * (never lowered — static pins may legitimately be ahead of the floor).
1071
1131
  */
1132
+ const latestCache = /* @__PURE__ */ new Map();
1133
+ function allScaffoldPackageNames() {
1134
+ const names = /* @__PURE__ */ new Set();
1135
+ for (const group of Object.values(SCAFFOLD_DEP_VERSIONS)) for (const name of Object.keys(group)) names.add(name);
1136
+ return [...names];
1137
+ }
1138
+ async function primeLatestScaffoldVersions(options) {
1139
+ const timeoutMs = options?.timeoutMs ?? 3500;
1140
+ const registry = (options?.registry ?? process.env.npm_config_registry ?? "https://registry.npmjs.org").replace(/\/$/, "");
1141
+ const names = allScaffoldPackageNames();
1142
+ const results = await Promise.allSettled(names.map(async (name) => {
1143
+ const res = await fetch(`${registry}/${name}/latest`, {
1144
+ signal: AbortSignal.timeout(timeoutMs),
1145
+ headers: { accept: "application/json" }
1146
+ });
1147
+ if (!res.ok) throw new Error(String(res.status));
1148
+ const body = await res.json();
1149
+ if (typeof body.version === "string" && /^\d+\.\d+\.\d+$/.test(body.version)) latestCache.set(name, body.version);
1150
+ }));
1151
+ return {
1152
+ resolved: latestCache.size,
1153
+ total: results.length
1154
+ };
1155
+ }
1156
+ function parseBareVersion(v) {
1157
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
1158
+ return m ? [
1159
+ Number(m[1]),
1160
+ Number(m[2]),
1161
+ Number(m[3])
1162
+ ] : void 0;
1163
+ }
1164
+ function applyLatestOverlay(dependencies) {
1165
+ if (latestCache.size === 0) return;
1166
+ for (const [name, currentRange] of Object.entries(dependencies)) {
1167
+ const latest = latestCache.get(name);
1168
+ if (!latest || latest.includes("-")) continue;
1169
+ const latestV = parseBareVersion(latest);
1170
+ const currentFloor = parseFloor(currentRange);
1171
+ if (!latestV || !currentFloor) continue;
1172
+ if (!floorLessThan(currentFloor, latestV)) continue;
1173
+ if (latestV[0] !== currentFloor[0] && !name.startsWith("@classytic/")) continue;
1174
+ dependencies[name] = `^${latest}`;
1175
+ }
1176
+ }
1072
1177
  function overlayLiveArcVersions(dependencies) {
1073
1178
  const own = readOwnPackageJson();
1074
1179
  if (!own) return;
@@ -1102,6 +1207,8 @@ function resolveScaffoldDependencies(config) {
1102
1207
  }
1103
1208
  if (config.adapter === "mongokit") Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.adapterMongokit);
1104
1209
  if (config.typescript) Object.assign(devDependencies, SCAFFOLD_DEP_VERSIONS.devTypescript);
1210
+ applyLatestOverlay(dependencies);
1211
+ applyLatestOverlay(devDependencies);
1105
1212
  overlayLiveArcVersions(dependencies);
1106
1213
  return {
1107
1214
  dependencies: sortByKey(dependencies),
@@ -3030,6 +3137,7 @@ async function createProjectStructure(projectPath, config) {
3030
3137
  files["docker-compose.yml"] = dockerComposeTemplate(config);
3031
3138
  }
3032
3139
  if (config.edge) files["wrangler.toml"] = wranglerTemplate(config);
3140
+ files["CLAUDE.md"] = agentsGuideTemplate(config);
3033
3141
  files[".arcrc"] = `${JSON.stringify({
3034
3142
  adapter: config.adapter,
3035
3143
  auth: config.auth,
@@ -3329,6 +3437,14 @@ async function init(options = {}) {
3329
3437
  }
3330
3438
  const packageManager = detectPackageManager();
3331
3439
  console.log(`Using package manager: ${packageManager}\n`);
3440
+ if (!process.env.VITEST && process.env.ARC_INIT_OFFLINE !== "1") {
3441
+ const { primeLatestScaffoldVersions } = await Promise.resolve().then(() => dependency_plan_exports);
3442
+ const { resolved, total } = await primeLatestScaffoldVersions().catch(() => ({
3443
+ resolved: 0,
3444
+ total: 0
3445
+ }));
3446
+ console.log(resolved > 0 ? `Resolved ${resolved}/${total} dependency versions from npm (latest stable)\n` : "Registry unreachable — using pinned fallback versions\n");
3447
+ }
3332
3448
  await createProjectStructure(projectPath, config);
3333
3449
  if (!options.skipInstall) {
3334
3450
  console.log("\n📥 Installing dependencies...\n");
@@ -1,7 +1,7 @@
1
1
  import { c as WebSocketClient, l as WebSocketMessage, u as WebSocketPluginOptions } from "../websocket-uuWqM6UY.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-DRj-Ut4n.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-Bc42CUyk.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, CallToolResult, CreateMcpServerConfig, CrudOperation, EventGatewayOptions, JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, McpAuthResult, McpPluginOptions, McpResourceConfig, PromptDefinition, QueueStats, StreamlinePluginOptions, ToolAnnotations, ToolContext, ToolDefinition, WebSocketClient, WebSocketMessage, WebSocketPluginOptions, WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, WorkflowLike, WorkflowRunLike };
@@ -1,7 +1,7 @@
1
- import { V as ResourceDefinition, bt as IControllerResponse, xt as IRequestContext } from "../../index-Dtrj6XZk.mjs";
1
+ import { Rt as IControllerResponse, V as ResourceDefinition, zt as IRequestContext } from "../../index-DbtgCOCj.mjs";
2
2
  import { i as RequestScope } from "../../types-DH0GupI7.mjs";
3
- import { c as PermissionCheck } from "../../fields-BGqrAa_u.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-DRj-Ut4n.mjs";
3
+ import { t as PermissionCheck } from "../../types-Csxga7d3.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-Bc42CUyk.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, h as resolveToolCollisions, 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-B9XjjNVG.mjs";
1
+ import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, h as resolveToolCollisions, 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-ByA2tf6D.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-DRj-Ut4n.mjs";
1
+ import { c as McpAuthResult, l as McpPluginOptions } from "../../types-Bc42CUyk.mjs";
2
2
  //#region src/integrations/mcp/testing.d.ts
3
3
  interface TestMcpClientOptions {
4
4
  /** MCP plugin options (resources, overrides, etc.) — same as mcpPlugin config */
@@ -1,4 +1,4 @@
1
- import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-B9XjjNVG.mjs";
1
+ import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-ByA2tf6D.mjs";
2
2
  //#region src/integrations/mcp/testing.ts
3
3
  /**
4
4
  * @classytic/arc/mcp/testing — MCP Test Utilities
@@ -77,8 +77,9 @@ interface VerifySignatureOptions {
77
77
  *
78
78
  * ⚠ The `body` argument must be the exact bytes the sender signed. Fastify
79
79
  * parses JSON bodies by default and the re-serialised object will NOT match
80
- * the original signature. Register `@fastify/raw-body` or use `preParsing`
81
- * to capture `req.rawBody`, then pass that here.
80
+ * the original signature. Register `fastify-raw-body` (arc's createApp does
81
+ * this automatically with `global: false`) and opt the route in via
82
+ * `config: { rawBody: true }`, then pass `req.rawBody` here.
82
83
  *
83
84
  * @param body - Raw request body (string or Buffer — must be the exact bytes the sender signed)
84
85
  * @param secret - Shared secret between sender and receiver
@@ -90,8 +91,11 @@ interface VerifySignatureOptions {
90
91
  * ```typescript
91
92
  * import { verifySignature } from '@classytic/arc/integrations/webhooks';
92
93
  *
93
- * // Arc-to-Arc (default headers + format)
94
- * fastify.post('/webhooks/incoming', async (req, reply) => {
94
+ * // Arc-to-Arc (default headers + format). `config: { rawBody: true }` is
95
+ * // REQUIRED — arc registers fastify-raw-body with `global: false`, so
96
+ * // routes opt in per-route; without it `req.rawBody` is undefined and
97
+ * // verifySignature throws a TypeError.
98
+ * fastify.post('/webhooks/incoming', { config: { rawBody: true } }, async (req, reply) => {
95
99
  * const sig = req.headers['x-webhook-signature'] as string;
96
100
  * if (!verifySignature(req.rawBody, secret, sig)) {
97
101
  * return reply.status(401).send({ error: 'Invalid signature' });
@@ -48,8 +48,9 @@ function signPayload(payload, secret) {
48
48
  *
49
49
  * ⚠ The `body` argument must be the exact bytes the sender signed. Fastify
50
50
  * parses JSON bodies by default and the re-serialised object will NOT match
51
- * the original signature. Register `@fastify/raw-body` or use `preParsing`
52
- * to capture `req.rawBody`, then pass that here.
51
+ * the original signature. Register `fastify-raw-body` (arc's createApp does
52
+ * this automatically with `global: false`) and opt the route in via
53
+ * `config: { rawBody: true }`, then pass `req.rawBody` here.
53
54
  *
54
55
  * @param body - Raw request body (string or Buffer — must be the exact bytes the sender signed)
55
56
  * @param secret - Shared secret between sender and receiver
@@ -61,8 +62,11 @@ function signPayload(payload, secret) {
61
62
  * ```typescript
62
63
  * import { verifySignature } from '@classytic/arc/integrations/webhooks';
63
64
  *
64
- * // Arc-to-Arc (default headers + format)
65
- * fastify.post('/webhooks/incoming', async (req, reply) => {
65
+ * // Arc-to-Arc (default headers + format). `config: { rawBody: true }` is
66
+ * // REQUIRED — arc registers fastify-raw-body with `global: false`, so
67
+ * // routes opt in per-route; without it `req.rawBody` is undefined and
68
+ * // verifySignature throws a TypeError.
69
+ * fastify.post('/webhooks/incoming', { config: { rawBody: true } }, async (req, reply) => {
66
70
  * const sig = req.headers['x-webhook-signature'] as string;
67
71
  * if (!verifySignature(req.rawBody, secret, sig)) {
68
72
  * return reply.status(401).send({ error: 'Invalid signature' });
@@ -82,7 +86,7 @@ function signPayload(payload, secret) {
82
86
  * ```
83
87
  */
84
88
  function verifySignature(body, secret, signature, options) {
85
- if (typeof body !== "string" && !Buffer.isBuffer(body)) throw new TypeError("verifySignature: body must be a string or Buffer (the exact bytes the sender signed). Register @fastify/raw-body and pass req.rawBody — not req.body.");
89
+ if (typeof body !== "string" && !Buffer.isBuffer(body)) throw new TypeError("verifySignature: body must be a string or Buffer (the exact bytes the sender signed). Add `config: { rawBody: true }` to the route (arc registers fastify-raw-body with global: false) and pass req.rawBody — not req.body.");
86
90
  if (!signature) return false;
87
91
  const prefix = options?.prefix ?? "sha256=";
88
92
  const algorithm = options?.algorithm ?? "sha256";
@@ -0,0 +1,43 @@
1
+ import { t as ArcError } from "./errors-DkO8Nk6-.mjs";
2
+ import sjson from "secure-json-parse";
3
+ //#region src/utils/jsonBody.ts
4
+ /**
5
+ * Shared JSON request-body parsing for every arc content-type parser
6
+ * (`application/json`, `application/scim+json`, decrypted `application/jose`).
7
+ *
8
+ * One primitive, one contract:
9
+ * - empty body → `undefined` (DELETE/GET with `Content-Type: application/json`
10
+ * must not crash — the original reason arc replaces Fastify's parser)
11
+ * - prototype-poisoning payloads (`__proto__`, `constructor.prototype`)
12
+ * rejected via `secure-json-parse`, same protection Fastify's stock
13
+ * parser has
14
+ * - ANY parse failure → `ArcError` with `statusCode: 400` /
15
+ * `code: 'arc.bad_request'`. Client-sent bytes can never produce a 500:
16
+ * Fastify forwards parser errors to the error handler as-is, so an
17
+ * undecorated `SyntaxError` would fall through to the
18
+ * `arc.internal_error` fallback. The raw parser message stays on
19
+ * `cause` (logged, never on the wire).
20
+ */
21
+ /**
22
+ * Parse a JSON request body with prototype-poisoning protection and the
23
+ * arc 400 error contract.
24
+ *
25
+ * @param raw - Body text (string or Buffer). Empty/absent → `undefined`.
26
+ * @param message - Wire-safe error message for parse failures
27
+ * (default: `"Invalid JSON payload"`).
28
+ */
29
+ function parseJsonBody(raw, message = "Invalid JSON payload") {
30
+ const text = typeof raw === "string" ? raw : raw ? raw.toString("utf8") : "";
31
+ if (text.length === 0) return void 0;
32
+ try {
33
+ return sjson.parse(text);
34
+ } catch (cause) {
35
+ throw new ArcError(message, {
36
+ code: "arc.bad_request",
37
+ statusCode: 400,
38
+ cause
39
+ });
40
+ }
41
+ }
42
+ //#endregion
43
+ export { parseJsonBody as t };
@@ -77,5 +77,30 @@ declare function configureArcLogger(options: ArcLoggerOptions): void;
77
77
  * ```
78
78
  */
79
79
  declare function arcLog(module: string): ArcLogger;
80
+ /**
81
+ * Minimal structural slice of a pino-style logger (`fastify.log`). Each
82
+ * level accepts `(msg)` or `(mergeObject, msg)` — the pino calling
83
+ * convention, which differs from console's variadic `(...args)`.
84
+ */
85
+ interface PinoLike {
86
+ debug: (obj: unknown, msg?: string) => void;
87
+ info: (obj: unknown, msg?: string) => void;
88
+ warn: (obj: unknown, msg?: string) => void;
89
+ error: (obj: unknown, msg?: string) => void;
90
+ }
91
+ /**
92
+ * Bridge an `ArcLogWriter` onto a pino-style logger (`fastify.log`).
93
+ *
94
+ * arcLog call sites use the console convention — positional args like
95
+ * `log.warn("message", { userId })` — which pino would silently drop
96
+ * (extra args are printf interpolation values there). The bridge folds
97
+ * string args into the message, merges object args into the pino merge
98
+ * object, and maps `Error` instances to `{ err }` so pino's error
99
+ * serializer applies.
100
+ *
101
+ * `createApp` installs this automatically so arc-internal warnings flow
102
+ * through the host's transports, level, and redaction config.
103
+ */
104
+ declare function createPinoWriter(logger: PinoLike): ArcLogWriter;
80
105
  //#endregion
81
- export { ArcLogWriter, ArcLogger, ArcLoggerOptions, arcLog, configureArcLogger };
106
+ export { ArcLogWriter, ArcLogger, ArcLoggerOptions, PinoLike, arcLog, configureArcLogger, createPinoWriter };
@@ -44,6 +44,44 @@ function arcLog(module) {
44
44
  }
45
45
  };
46
46
  }
47
+ /**
48
+ * Bridge an `ArcLogWriter` onto a pino-style logger (`fastify.log`).
49
+ *
50
+ * arcLog call sites use the console convention — positional args like
51
+ * `log.warn("message", { userId })` — which pino would silently drop
52
+ * (extra args are printf interpolation values there). The bridge folds
53
+ * string args into the message, merges object args into the pino merge
54
+ * object, and maps `Error` instances to `{ err }` so pino's error
55
+ * serializer applies.
56
+ *
57
+ * `createApp` installs this automatically so arc-internal warnings flow
58
+ * through the host's transports, level, and redaction config.
59
+ */
60
+ function createPinoWriter(logger) {
61
+ const emit = (level) => (...args) => {
62
+ const parts = [];
63
+ let merge;
64
+ for (const arg of args) if (typeof arg === "string") parts.push(arg);
65
+ else if (arg instanceof Error) merge = {
66
+ ...merge ?? {},
67
+ err: arg
68
+ };
69
+ else if (arg !== null && typeof arg === "object") merge = {
70
+ ...merge ?? {},
71
+ ...arg
72
+ };
73
+ else parts.push(String(arg));
74
+ const msg = parts.join(" ");
75
+ if (merge) logger[level](merge, msg);
76
+ else logger[level](msg);
77
+ };
78
+ return {
79
+ debug: emit("debug"),
80
+ info: emit("info"),
81
+ warn: emit("warn"),
82
+ error: emit("error")
83
+ };
84
+ }
47
85
  function getWriter() {
48
86
  return globalOptions.writer ?? console;
49
87
  }
@@ -68,4 +106,4 @@ function isSuppressed() {
68
106
  return env === "1" || env === "true";
69
107
  }
70
108
  //#endregion
71
- export { arcLog, configureArcLogger };
109
+ export { arcLog, configureArcLogger, createPinoWriter };
@@ -1,13 +1,13 @@
1
1
  import { p as isArcError, t as ArcError } from "./errors-DkO8Nk6-.mjs";
2
2
  import { c as getOrgId, g as getUserId } from "./types-bHblZ9YN.mjs";
3
- import { t as BaseController } from "./BaseController-BnLYgE9A.mjs";
3
+ import { t as BaseController } from "./BaseController-C6FlRWxF.mjs";
4
4
  import { P as normalizePermissionResult } from "./permissions-BJMuKMl7.mjs";
5
5
  import { t as executePipeline } from "./pipe-BOLmGIxa.mjs";
6
6
  import { i as resolvePipelineSteps, t as resolveActionPermission } from "./actionPermissions-ze70xXJi.mjs";
7
7
  import { n as validateAggregations } from "./validate-C2mGgSXg.mjs";
8
8
  import { n as executeAggregation } from "./buildHandler-D_2ULWxZ.mjs";
9
9
  import { t as pluralize } from "./pluralize-BhrgwZb9.mjs";
10
- import { r as shouldRejectAdditionalProperties, t as normalizeSchemaIR } from "./schemaIR-ajetw3Fv.mjs";
10
+ import { r as shouldRejectAdditionalProperties, t as normalizeSchemaIR } from "./schemaIR-B-6SPe7g.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";
@@ -630,6 +630,7 @@ function defaultCrudDescription(op, displayName, softDelete, queryMeta) {
630
630
  if (queryMeta?.filterableFields?.length) parts.push(`Filterable fields: ${queryMeta.filterableFields.join(", ")}.`);
631
631
  if (queryMeta?.allowedOperators?.length) parts.push(`Filter operators: ${queryMeta.allowedOperators.join(", ")} (use field[op]=value syntax).`);
632
632
  if (queryMeta?.sortableFields?.length) parts.push(`Sortable fields: ${queryMeta.sortableFields.join(", ")}.`);
633
+ parts.push("Set _count: true for a count-only result, _exists: true for a boolean existence check, or _distinct: '<field>' for distinct values — all honor the same filters.");
633
634
  return parts.join(" ");
634
635
  }
635
636
  case "get": return `Get a single ${singular} by ID`;
@@ -772,6 +773,9 @@ function buildListShape(fieldRules, options) {
772
773
  const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators } = options;
773
774
  const allHidden = /* @__PURE__ */ new Set([...hiddenFields, ...extraHideFields]);
774
775
  const shape = { ...PAGINATION_SHAPE };
776
+ shape._count = z.boolean().optional().describe("Return only { count } of records matching the filters — no documents fetched. Cheapest way to answer 'how many'.");
777
+ shape._exists = z.boolean().optional().describe("Return only whether ANY record matches the filters (boolean existence check).");
778
+ shape._distinct = z.string().optional().describe("Return the distinct values of this field across records matching the filters.");
775
779
  if (!fieldRules) return shape;
776
780
  for (const name of filterableFields) {
777
781
  if (allHidden.has(name)) continue;
@@ -1,4 +1,4 @@
1
- import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-Dtrj6XZk.mjs";
1
+ import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-DbtgCOCj.mjs";
2
2
  import { RouteHandlerMethod } from "fastify";
3
3
  //#region src/middleware/middleware.d.ts
4
4
  interface NamedMiddleware {
@@ -1,4 +1,4 @@
1
- import { n as ErrorMapper } from "./errorHandler-B0e-bq-O.mjs";
1
+ import { n as ErrorMapper } from "./errorHandler-C6yv4ZTW.mjs";
2
2
  import { FastifyInstance } from "fastify";
3
3
  //#region src/factory/loadResources.d.ts
4
4
  /**
@@ -306,6 +306,31 @@ interface ArcModule<TExports = unknown> {
306
306
  * ```
307
307
  */
308
308
  bootstrap?: (fastify: FastifyInstance) => TExports | Promise<TExports>;
309
+ /**
310
+ * App-level resource NAMES this module authoritatively SUPERSEDES. When the
311
+ * host also lists (or `resourceDir`-discovers) a resource of the same name,
312
+ * arc DROPS the app copy and registers THIS module's version instead — so the
313
+ * host keeps NO hand-maintained "which resources did modules take over" filter
314
+ * set. Ownership lives on the atom that provides it (colocated, drift-proof):
315
+ *
316
+ * ```ts
317
+ * defineModule({ name: "order", owns: ["order", "quotation", "rfq"], resources: () => [...] })
318
+ * ```
319
+ *
320
+ * Semantics:
321
+ * - Purely a supersede-the-app-fork switch — it filters the *app* resource
322
+ * list, never the module's own `resources` (those always register).
323
+ * - Static + declarative: read at compose time, so it works regardless of
324
+ * whether `resources` is an array or a post-bootstrap factory (no early
325
+ * factory resolution, no boot-order coupling).
326
+ * - Idempotent + tolerant: an `owns` entry with no matching app resource is
327
+ * a silent no-op — a module may pre-declare a name before any fork exists.
328
+ * - Collected across ALL modules; the app-side supersession is their union.
329
+ *
330
+ * Contract: if you `owns` a name, PROVIDE it (in this module's `resources`).
331
+ * Dropping the app fork without supplying a replacement removes the route.
332
+ */
333
+ readonly owns?: readonly string[];
309
334
  /**
310
335
  * The module's resources. Array or factory — the factory form runs AFTER all
311
336
  * bootstraps, so `defineResource(...)` receives fully-booted engines. Flows
@@ -1,8 +1,8 @@
1
1
  import { t as getUserRoles } from "./types-XTApa8b6.mjs";
2
- import { n as convertRouteSchema } from "./schemaConverter-U0MnO9vd.mjs";
2
+ import { n as convertRouteSchema } from "./schemaConverter-Ds5reSGs.mjs";
3
3
  import { t as resolveActionPermission } from "./actionPermissions-ze70xXJi.mjs";
4
4
  import { t as pluralize } from "./pluralize-BhrgwZb9.mjs";
5
- import { t as buildActionBodySchema } from "./createActionRouter-BWMDW-dL.mjs";
5
+ import { t as buildActionBodySchema } from "./createActionRouter-h1cEEaEX.mjs";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/docs/openapi/canonical-schemas.ts
8
8
  /**
@@ -1,3 +1,4 @@
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-BGqrAa_u.mjs";
2
- import { A as requireOwnership, B as requireMandate, C as createOrgPermissions, D as denyAll, E as anyOf, F as normalizePermissionResult, I as RequireAgentScopeOptions, L as RequireMandateOptions, M as roles, N as when, O as not, P as applyPermissionResult, R as requireAgentScope, S as createDynamicPermissionMatrix, T as allowPublic, _ as createRoleHierarchy, a as permissionMatrix, b as DynamicPermissionMatrixConfig, c as publicReadAdminWrite, d as requireOrgMembership, f as requireOrgRole, g as RoleHierarchy, h as requireTeamMembership, i as ownerWithAdminBypass, j as requireRoles, k as requireAuth, l as readOnly, m as requireServiceScope, n as authenticated, o as presets_d_exports, p as requireScopeContext, r as fullPublic, s as publicRead, t as adminOnly, u as requireOrgInScope, v as ConnectEventsOptions, w as allOf, x as PermissionEventBus, y as DynamicPermissionMatrix, z as requireDPoP } from "../index-B5PoNbfu.mjs";
1
+ import { a as getUserRoles, i as UserBase, n as PermissionContext, o as normalizeRoles, r as PermissionResult, t as PermissionCheck } from "../types-Csxga7d3.mjs";
2
+ import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission } from "../fields-D3lhgzhD.mjs";
3
+ import { A as requireOwnership, B as requireMandate, C as createOrgPermissions, D as denyAll, E as anyOf, F as normalizePermissionResult, I as RequireAgentScopeOptions, L as RequireMandateOptions, M as roles, N as when, O as not, P as applyPermissionResult, R as requireAgentScope, S as createDynamicPermissionMatrix, T as allowPublic, _ as createRoleHierarchy, a as permissionMatrix, b as DynamicPermissionMatrixConfig, c as publicReadAdminWrite, d as requireOrgMembership, f as requireOrgRole, g as RoleHierarchy, h as requireTeamMembership, i as ownerWithAdminBypass, j as requireRoles, k as requireAuth, l as readOnly, m as requireServiceScope, n as authenticated, o as presets_d_exports, p as requireScopeContext, r as fullPublic, s as publicRead, t as adminOnly, u as requireOrgInScope, v as ConnectEventsOptions, w as allOf, x as PermissionEventBus, y as DynamicPermissionMatrix, z as requireDPoP } from "../index-ogIHvOjo.mjs";
3
4
  export { type ConnectEventsOptions, type DynamicPermissionMatrix, type DynamicPermissionMatrixConfig, type FieldPermission, type FieldPermissionMap, type FieldPermissionType, type PermissionCheck, type PermissionContext, type PermissionEventBus, type PermissionResult, type RequireAgentScopeOptions, type RequireMandateOptions, type RoleHierarchy, type UserBase, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, permissionMatrix, 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 { Bt as Transform, Ft as NextFunction, It as OperationFilter, Lt as PipelineConfig, Nt as Guard, Pt as Interceptor, Rt as PipelineContext, bt as IControllerResponse, zt as PipelineStep } from "../index-Dtrj6XZk.mjs";
1
+ import { Ct as PipelineStep, Rt as IControllerResponse, St as PipelineContext, _t as Guard, bt as OperationFilter, vt as Interceptor, wt as Transform, xt as PipelineConfig, yt as NextFunction } from "../index-DbtgCOCj.mjs";
2
2
  //#region src/pipeline/guard.d.ts
3
3
  interface GuardOptions {
4
4
  operations?: OperationFilter;