@classytic/arc 2.22.0 → 2.23.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 (86) hide show
  1. package/dist/{BaseController-C6FlRWxF.mjs → BaseController-BQ8KNvWC.mjs} +3 -2
  2. package/dist/{ResourceRegistry-7RnrEWNP.mjs → ResourceRegistry-y9Hnypus.mjs} +1 -0
  3. package/dist/{actionPermissions-ze70xXJi.mjs → actionPermissions-lszoJle1.mjs} +1 -1
  4. package/dist/applyPermissionResult-Tw4iXtlf.mjs +264 -0
  5. package/dist/audit/index.d.mts +1 -1
  6. package/dist/auth/index.d.mts +1 -1
  7. package/dist/auth/index.mjs +2 -2
  8. package/dist/cli/commands/describe.d.mts +1 -1
  9. package/dist/cli/commands/describe.mjs +1 -1
  10. package/dist/cli/commands/docs.mjs +2 -2
  11. package/dist/cli/commands/init.mjs +1 -1
  12. package/dist/cli/commands/introspect.mjs +1 -1
  13. package/dist/cli/index.mjs +1 -1
  14. package/dist/core/index.d.mts +2 -2
  15. package/dist/core/index.mjs +3 -3
  16. package/dist/{core-b6fgFgnd.mjs → core-DQ6rDTyH.mjs} +10 -9
  17. package/dist/{createActionRouter-h1cEEaEX.mjs → createActionRouter-BuOv-m4Y.mjs} +3 -3
  18. package/dist/{createAggregationRouter-B1C0kvTL.mjs → createAggregationRouter-CgJv_BU_.mjs} +1 -1
  19. package/dist/{createApp-Ci7yG5gv.mjs → createApp-CjOeWJ9_.mjs} +11 -2
  20. package/dist/docs/index.d.mts +1 -1
  21. package/dist/docs/index.mjs +1 -1
  22. package/dist/{errorHandler-BTzXSwIg.mjs → errorHandler-Bq2OeEQL.mjs} +10 -7
  23. package/dist/events/index.mjs +5 -2
  24. package/dist/factory/index.d.mts +20 -4
  25. package/dist/factory/index.mjs +31 -11
  26. package/dist/{fastify-augmentation-9_fuetB4.d.mts → fastify-augmentation-g_f9omSc.d.mts} +1 -1
  27. package/dist/hooks/index.d.mts +1 -1
  28. package/dist/idempotency/index.d.mts +7 -0
  29. package/dist/idempotency/index.mjs +5 -17
  30. package/dist/{index-DbtgCOCj.d.mts → index-BBsvQt7U.d.mts} +26 -4
  31. package/dist/{index-BEu4wjbL.d.mts → index-Ba10fdKp.d.mts} +11 -2
  32. package/dist/{index-D8MKYGDa.d.mts → index-CC5ruDGh.d.mts} +2 -2
  33. package/dist/{index-ogIHvOjo.d.mts → index-wMoWBLkn.d.mts} +67 -1
  34. package/dist/index.d.mts +4 -4
  35. package/dist/index.mjs +6 -5
  36. package/dist/{init-DleK3p9f.mjs → init-CjO4p_q1.mjs} +5 -2
  37. package/dist/integrations/event-gateway.mjs +1 -1
  38. package/dist/integrations/index.d.mts +1 -1
  39. package/dist/integrations/mcp/index.d.mts +66 -3
  40. package/dist/integrations/mcp/index.mjs +1 -1
  41. package/dist/integrations/mcp/testing.d.mts +1 -1
  42. package/dist/integrations/mcp/testing.mjs +1 -1
  43. package/dist/integrations/webhooks.d.mts +123 -3
  44. package/dist/integrations/webhooks.mjs +160 -5
  45. package/dist/interface-n5hpKg1d.mjs +16 -0
  46. package/dist/{loadResourcesFromEntry-OMCvIEE7.mjs → loadResourcesFromEntry-kvGT6EcO.mjs} +1 -1
  47. package/dist/{mcpPlugin-ByA2tf6D.mjs → mcpPlugin-BtzIL_kM.mjs} +308 -39
  48. package/dist/middleware/index.d.mts +1 -1
  49. package/dist/{openapi-BRpMZD7C.mjs → openapi-jSDx-VDv.mjs} +3 -3
  50. package/dist/permissions/index.d.mts +2 -2
  51. package/dist/permissions/index.mjs +3 -2
  52. package/dist/{permissions-BJMuKMl7.mjs → permissions-tbxFHxjZ.mjs} +77 -264
  53. package/dist/pipeline/index.d.mts +1 -1
  54. package/dist/plugins/index.d.mts +6 -6
  55. package/dist/plugins/index.mjs +5 -4
  56. package/dist/plugins/tracing-entry.mjs +1 -1
  57. package/dist/presets/filesUpload.d.mts +1 -1
  58. package/dist/presets/filesUpload.mjs +1 -1
  59. package/dist/presets/index.d.mts +1 -1
  60. package/dist/presets/index.mjs +1 -1
  61. package/dist/presets/multiTenant.d.mts +1 -1
  62. package/dist/presets/search.d.mts +1 -1
  63. package/dist/presets/search.mjs +1 -1
  64. package/dist/{presets-gw0ciIUD.mjs → presets-BOe4Ugkl.mjs} +1 -1
  65. package/dist/realtime-I8OrfnMR.mjs +375 -0
  66. package/dist/{registerRoute-xHc42fwB.mjs → registerRoute-CWO6mdYe.mjs} +1 -1
  67. package/dist/registry/index.d.mts +2 -2
  68. package/dist/registry/index.mjs +1 -1
  69. package/dist/{schemaConverter-Ds5reSGs.mjs → schemaConverter-DJvodiHj.mjs} +4 -1
  70. package/dist/{schemaIR-B-6SPe7g.mjs → schemaIR-Ca7ySNPx.mjs} +1 -1
  71. package/dist/scope/index.mjs +1 -1
  72. package/dist/scope-LONQ3mfZ.mjs +37 -0
  73. package/dist/simpleEqualityMatcher-Cg05HPsn.mjs +68 -0
  74. package/dist/{sse-CfUjwjDk.mjs → sse-0u4P4-OI.mjs} +7 -30
  75. package/dist/sseStream-BQ7Qaynp.mjs +57 -0
  76. package/dist/testing/index.d.mts +2 -2
  77. package/dist/testing/index.mjs +2 -2
  78. package/dist/types/index.d.mts +1 -1
  79. package/dist/{types-Bc42CUyk.d.mts → types-5OuqeTDX.d.mts} +37 -2
  80. package/dist/utils/index.d.mts +62 -2
  81. package/dist/utils/index.mjs +5 -3
  82. package/dist/{utils-FhJ88TJE.mjs → utils-CZyeRXTh.mjs} +21 -69
  83. package/dist/{versioning-DLwklPlG.d.mts → versioning-D6PvCwpq.d.mts} +43 -4
  84. package/package.json +64 -4
  85. package/skills/arc/references/production.md +734 -696
  86. package/dist/scope-k15RXfcq.mjs +0 -64
@@ -1,5 +1,5 @@
1
1
  import { t as CRUD_OPERATIONS } from "../constants-DNre0wRg.mjs";
2
- import { d as getModuleExports, h as preloadResourcesAsync, m as preloadResources, n as createApp } from "../createApp-Ci7yG5gv.mjs";
2
+ import { d as getModuleExports, h as preloadResourcesAsync, m as preloadResources, n as createApp } from "../createApp-CjOeWJ9_.mjs";
3
3
  import { runStorageContract } from "./storageContract.mjs";
4
4
  import Fastify from "fastify";
5
5
  import { afterAll, describe, expect, it, vi } from "vitest";
@@ -1116,7 +1116,7 @@ function pickDefaultAuth(authMode, callerAuth) {
1116
1116
  };
1117
1117
  }
1118
1118
  async function createTestApp(options = {}) {
1119
- const { createApp } = await import("../createApp-Ci7yG5gv.mjs").then((n) => n.r);
1119
+ const { createApp } = await import("../createApp-CjOeWJ9_.mjs").then((n) => n.r);
1120
1120
  const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
1121
1121
  let dbHandle;
1122
1122
  let dbUri;
@@ -1,4 +1,4 @@
1
- import { $ as OpenApiSchemas, $t as UserOrganization, A as RequestIdOptions, At as RouteDefinition, Bt as RouteHandler, C as RequestContext, D as HealthCheck, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Fn as BaseControllerOptions, Ft as ControllerLike, G as OnTenantDeleteConfig, Gt as JwtContext, H as CrudController, Ht as AuthPluginOptions, I as MiddlewareHandler, It as FastifyHandler, J as PresetHook, Jt as ApiResponse, K as ResolvedTenantPurge, Kt as TokenPair, L as RequestWithExtras, Lt as IController, M as EventsDecorator, Mt as RouteMethod, N as FastifyRequestExtras, Nt as RateLimitConfig, O as HealthOptions, Ot as ActionHandlerFn, P as FastifyWithAuth, Pt as ControllerHandler, Q as MiddlewareConfig, Qt as UserLike, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, Ut as Authenticator, Vt as AuthHelpers, W as ResourcePermissions, Wt as AuthenticatorContext, X as CrudRouteKey, Xt as JWTPayload, Y as PresetResult, Yt as ArcRequest, Z as CrudSchemas, Zt as ObjectId, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, qt as AnyRecord, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, zt as IRequestContext } from "../index-DbtgCOCj.mjs";
1
+ import { $ as OpenApiSchemas, $t as UserLike, A as RequestIdOptions, At as RouteDefinition, Bt as RouteHandler, C as RequestContext, D as HealthCheck, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Ft as ControllerLike, G as OnTenantDeleteConfig, Gt as AuthenticatorContext, H as CrudController, Ht as AuthHelpers, I as MiddlewareHandler, In as BaseControllerOptions, It as FastifyHandler, J as PresetHook, Jt as AnyRecord, K as ResolvedTenantPurge, Kt as JwtContext, L as RequestWithExtras, Lt as IController, M as EventsDecorator, Mt as RouteMethod, N as FastifyRequestExtras, Nt as RateLimitConfig, O as HealthOptions, Ot as ActionHandlerFn, P as FastifyWithAuth, Pt as ControllerHandler, Q as MiddlewareConfig, Qt as ObjectId, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, Ut as AuthPluginOptions, W as ResourcePermissions, Wt as Authenticator, X as CrudRouteKey, Xt as ArcRequest, Y as PresetResult, Yt as ApiResponse, Z as CrudSchemas, Zt as JWTPayload, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, en as UserOrganization, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, qt as TokenPair, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, zt as IRequestContext } from "../index-BBsvQt7U.mjs";
2
2
  import { i as RequestScope } from "../types-DH0GupI7.mjs";
3
3
  import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "../types-Csxga7d3.mjs";
4
4
  import { n as ElevationOptions, t as ElevationEvent } from "../elevation-C9s5hFP_.mjs";
@@ -1,4 +1,4 @@
1
- import { V as ResourceDefinition } from "./index-DbtgCOCj.mjs";
1
+ import { V as ResourceDefinition } from "./index-BBsvQt7U.mjs";
2
2
  import { z } from "zod";
3
3
  //#region src/integrations/mcp/types.d.ts
4
4
  /** Behavioral hints for MCP clients (tool annotations per MCP spec) */
@@ -157,6 +157,41 @@ interface CrudDescriptionMeta {
157
157
  * Arc's auto-derived filterable-fields blurb.
158
158
  */
159
159
  type CrudDescriptionOverride = string | ((meta: CrudDescriptionMeta) => string);
160
+ /**
161
+ * Execution wiring for MCP tools — the app-level services HTTP requests
162
+ * receive via Fastify decorators (`fastify.arc.hooks`, `fastify.events`,
163
+ * `fastify.audit`, `fastify.log`, `fastify.idempotency`) and MCP's
164
+ * synthetic-context path historically dropped.
165
+ *
166
+ * When present, tool handlers thread it into the request context so the
167
+ * controller pipeline achieves FULL HTTP parity: resource hooks run
168
+ * (before/around/after), the arcCorePlugin after-hook publishes
169
+ * `<resource>.<op>d` events (→ realtime feeds, webhooks, event-driven
170
+ * cache invalidation all see MCP-originated mutations), `ctx.server.*`
171
+ * works inside handlers, and mutating CRUD tools accept an optional
172
+ * `_idempotencyKey` input for retry-safe execution.
173
+ *
174
+ * `mcpPlugin` builds this automatically from its Fastify instance (lazy
175
+ * getters — decorator registration order doesn't matter). Standalone
176
+ * `resourceToTools()` / `createMcpServer()` callers pass their own, or
177
+ * omit it to keep the legacy dispatch-only behavior.
178
+ */
179
+ interface McpExecutionWiring {
180
+ /** Shared `HookSystem` — `fastify.arc.hooks`. */
181
+ readonly hooks?: unknown;
182
+ /** Event bus decorator — `fastify.events`. */
183
+ readonly events?: unknown;
184
+ /** Audit decorator — `fastify.audit`. */
185
+ readonly audit?: unknown;
186
+ /** Structured logger — `fastify.log`. */
187
+ readonly log?: unknown;
188
+ /**
189
+ * Idempotency store (the `IdempotencyStore` contract from
190
+ * `@classytic/arc/idempotency`). Presence advertises `_idempotencyKey`
191
+ * on mutating CRUD tools and enables executor-level check/lock/replay.
192
+ */
193
+ readonly idempotencyStore?: unknown;
194
+ }
160
195
  /** Per-resource MCP configuration overrides */
161
196
  interface McpResourceConfig {
162
197
  /** Which CRUD operations to expose (default: all enabled on the resource) */
@@ -392,4 +427,4 @@ interface CreateMcpServerConfig {
392
427
  /** CRUD operation type */
393
428
  type CrudOperation = "list" | "get" | "create" | "update" | "delete";
394
429
  //#endregion
395
- export { CrudDescriptionOverride as a, McpAuthResult as c, PromptDefinition as d, PromptResult as f, ToolDefinition as g, ToolContext as h, CrudDescriptionMeta as i, McpPluginOptions as l, ToolAnnotations as m, CallToolResult as n, CrudOperation as o, SessionEntry as p, CreateMcpServerConfig as r, McpAuthResolver as s, BetterAuthHandler as t, McpResourceConfig as u };
430
+ export { ToolDefinition as _, CrudDescriptionOverride as a, McpAuthResult as c, McpResourceConfig as d, PromptDefinition as f, ToolContext as g, ToolAnnotations as h, CrudDescriptionMeta as i, McpExecutionWiring as l, SessionEntry as m, CallToolResult as n, CrudOperation as o, PromptResult as p, CreateMcpServerConfig as r, McpAuthResolver as s, BetterAuthHandler as t, McpPluginOptions as u };
@@ -1,4 +1,4 @@
1
- import { $ as OpenApiSchemas, Qt as UserLike, S as QueryParserInterface, U as ResourceConfig, b as ParsedQuery, qt as AnyRecord } from "../index-DbtgCOCj.mjs";
1
+ import { $ as OpenApiSchemas, $t as UserLike, Jt as AnyRecord, S as QueryParserInterface, U as ResourceConfig, b as ParsedQuery } from "../index-BBsvQt7U.mjs";
2
2
  import { n as ErrorMapper } from "../errorHandler-C6yv4ZTW.mjs";
3
3
  import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createDomainError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorOptions, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-DHcHVAgk.mjs";
4
4
  import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
@@ -437,6 +437,34 @@ declare const NEUTRAL_DIALECT: FilterDialect;
437
437
  */
438
438
  declare function handleRaw<T>(handler: (request: FastifyRequest, reply: FastifyReply) => Promise<T>, statusCode?: number): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
439
439
  //#endregion
440
+ //#region src/utils/headers.d.ts
441
+ /**
442
+ * Canonical header typing + accessor.
443
+ *
444
+ * Node/Fastify header values are `string | string[] | undefined` — repeated
445
+ * headers arrive as arrays. `IRequestContext.headers` is DECLARED as
446
+ * `Record<string, string | undefined>` for ergonomic host code, but the HTTP
447
+ * adapter passes Fastify's headers object through unchanged, so `string[]`
448
+ * CAN appear at runtime behind the narrower type (the declared type is
449
+ * scheduled to widen to {@link ArcHeaders} in v3). Until then, read headers
450
+ * through {@link getHeader} instead of indexing — it's honest about arrays
451
+ * and deterministic about duplicates.
452
+ */
453
+ /** What a headers bag actually contains at runtime — HTTP or synthetic MCP. */
454
+ type ArcHeaders = Readonly<Record<string, string | readonly string[] | undefined>>;
455
+ /**
456
+ * Read a single header value. Lowercases the name (Node normalizes incoming
457
+ * header names to lowercase) and returns the FIRST value when the header
458
+ * was repeated.
459
+ *
460
+ * Duplicate policy: first-value is deterministic, but for security-sensitive
461
+ * headers (authorization material, forwarded-for chains, content-length) a
462
+ * duplicate usually signals smuggling or a broken proxy — consumers that
463
+ * care should check `Array.isArray(headers[name])` and REJECT instead of
464
+ * picking a winner.
465
+ */
466
+ declare function getHeader(headers: ArcHeaders | Record<string, string | undefined> | undefined, name: string): string | undefined;
467
+ //#endregion
440
468
  //#region src/utils/queryParser.d.ts
441
469
  interface ArcQueryParserOptions {
442
470
  /** Maximum allowed limit value (default: 1000) */
@@ -1215,6 +1243,38 @@ declare function convertRouteSchema(schema: Record<string, unknown>, target?: Js
1215
1243
  */
1216
1244
  declare function simpleEqualityMatcher(item: unknown, filters: Record<string, unknown>): boolean;
1217
1245
  //#endregion
1246
+ //#region src/utils/sseStream.d.ts
1247
+ interface SseStreamOptions {
1248
+ /** Heartbeat comment interval in ms (default: 30000). */
1249
+ heartbeatMs?: number;
1250
+ /**
1251
+ * Reconnection-delay hint written once at connect as the standard SSE
1252
+ * `retry:` field (WHATWG EventSource honors it natively). Omit to send
1253
+ * no hint (browser default ~3s).
1254
+ */
1255
+ retryMs?: number;
1256
+ }
1257
+ interface SseStream {
1258
+ /**
1259
+ * Write one SSE frame. `id` (optional) becomes the standard SSE `id:`
1260
+ * field — clients receive it as `lastEventId` (WHATWG), enabling
1261
+ * client-side dedup/resume bookkeeping even when the server keeps no
1262
+ * replay log. Returns `false` when the connection was destroyed
1263
+ * (backpressure) — the stream is already cleaned up; stop writing.
1264
+ */
1265
+ write(eventName: string, data: string, id?: string): boolean;
1266
+ /** Idempotent teardown: heartbeat cleared, cleanups run, socket ended. */
1267
+ close(): void;
1268
+ /** Register teardown work (unsubscribes etc.) — runs exactly once. */
1269
+ onCleanup(fn: () => void): void;
1270
+ }
1271
+ /**
1272
+ * Take over the reply socket and open an SSE stream. Call ONLY after every
1273
+ * gate (auth, permissions, validation) has passed — after `hijack()` no
1274
+ * JSON error can be sent.
1275
+ */
1276
+ declare function openSseStream(request: FastifyRequest, reply: FastifyReply, options?: SseStreamOptions): SseStream;
1277
+ //#endregion
1218
1278
  //#region src/utils/stateMachine.d.ts
1219
1279
  /**
1220
1280
  * State Machine Utility
@@ -1475,4 +1535,4 @@ declare function hasEvents(instance: FastifyInstance): instance is FastifyInstan
1475
1535
  */
1476
1536
  declare function getUserId(user: UserLike | null | undefined): string | undefined;
1477
1537
  //#endregion
1478
- export { ArcError, ArcQueryParser, type ArcQueryParserOptions, CircuitBreaker, CircuitBreakerError, type CircuitBreakerOptions, CircuitBreakerRegistry, type CircuitBreakerStats, CircuitState, type CompensationDefinition, type CompensationError, type CompensationHooks, type CompensationResult, type CompensationStep, type ConfigError, ConflictError, type ErrorOptions, type EventsDecorator, type FilterDialect, ForbiddenError, type Guard, type GuardConfig, type JsonSchema, type JsonSchemaTarget, MONGO_DIALECT, NEUTRAL_DIALECT, NotFoundError, OrgAccessDeniedError, OrgRequiredError, type PipeUIMessageStreamOptions, RateLimitError, ServiceUnavailableError, type StateMachine, type TransitionConfig, UI_MESSAGE_STREAM_HEADERS, UnauthorizedError, type ValidateOptions, ValidationError, type ValidationResult, aggregateListResponse, assertValidConfig, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, formatValidationErrors, forwardedStreamHeaders, getDefaultCrudSchemas, getListQueryParams, getUserId, handleRaw, hasEvents, isArcError, isJsonSchema, isReadableStream, isZodSchema, keysetListResponse, listResponse, offsetListResponse, paginationSchema, pipeUIMessageStreamToReply, promoteStreamTokenToHeader, queryParams, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, validateResourceConfig, withCompensation };
1538
+ export { ArcError, type ArcHeaders, ArcQueryParser, type ArcQueryParserOptions, CircuitBreaker, CircuitBreakerError, type CircuitBreakerOptions, CircuitBreakerRegistry, type CircuitBreakerStats, CircuitState, type CompensationDefinition, type CompensationError, type CompensationHooks, type CompensationResult, type CompensationStep, type ConfigError, ConflictError, type ErrorOptions, type EventsDecorator, type FilterDialect, ForbiddenError, type Guard, type GuardConfig, type JsonSchema, type JsonSchemaTarget, MONGO_DIALECT, NEUTRAL_DIALECT, NotFoundError, OrgAccessDeniedError, OrgRequiredError, type PipeUIMessageStreamOptions, RateLimitError, ServiceUnavailableError, type SseStream, type SseStreamOptions, type StateMachine, type TransitionConfig, UI_MESSAGE_STREAM_HEADERS, UnauthorizedError, type ValidateOptions, ValidationError, type ValidationResult, aggregateListResponse, assertValidConfig, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, formatValidationErrors, forwardedStreamHeaders, getDefaultCrudSchemas, getHeader, getListQueryParams, getUserId, handleRaw, hasEvents, isArcError, isJsonSchema, isReadableStream, isZodSchema, keysetListResponse, listResponse, offsetListResponse, openSseStream, paginationSchema, pipeUIMessageStreamToReply, promoteStreamTokenToHeader, queryParams, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, validateResourceConfig, withCompensation };
@@ -1,6 +1,8 @@
1
1
  import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-DkO8Nk6-.mjs";
2
- import { A as createCircuitBreaker, C as defineErrorMapper, D as CircuitBreakerError, E as CircuitBreaker, F as simpleEqualityMatcher, M as assertValidConfig, N as formatValidationErrors, O as CircuitBreakerRegistry, P as validateResourceConfig, S as defineGuard, T as withCompensation, _ as ArcQueryParser, a as bareListResponse, b as MONGO_DIALECT, c as errorDetailSchema, d as keysetListResponse, f as listResponse, g as responses, h as queryParams, i as aggregateListResponse, j as createCircuitBreakerRegistry, k as CircuitState, l as getDefaultCrudSchemas, m as paginationSchema, n as createStateMachine, o as deleteResponse, p as offsetListResponse, r as scheduleBackground, s as errorContractSchema, t as getUserId, u as getListQueryParams, v as createQueryParser, w as defineCompensation, x as NEUTRAL_DIALECT, y as handleRaw } from "../utils-FhJ88TJE.mjs";
3
- import { a as isZodSchema, i as isJsonSchema, n as convertRouteSchema, o as toJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-Ds5reSGs.mjs";
2
+ import { t as simpleEqualityMatcher } from "../simpleEqualityMatcher-Cg05HPsn.mjs";
3
+ import { A as CircuitState, C as defineGuard, D as CircuitBreaker, E as withCompensation, F as validateResourceConfig, M as createCircuitBreakerRegistry, N as assertValidConfig, O as CircuitBreakerError, P as formatValidationErrors, S as NEUTRAL_DIALECT, T as defineCompensation, _ as ArcQueryParser, a as bareListResponse, b as handleRaw, c as errorDetailSchema, d as keysetListResponse, f as listResponse, g as responses, h as queryParams, i as aggregateListResponse, j as createCircuitBreaker, k as CircuitBreakerRegistry, l as getDefaultCrudSchemas, m as paginationSchema, n as createStateMachine, o as deleteResponse, p as offsetListResponse, r as scheduleBackground, s as errorContractSchema, t as getUserId, u as getListQueryParams, v as createQueryParser, w as defineErrorMapper, x as MONGO_DIALECT, y as getHeader } from "../utils-CZyeRXTh.mjs";
4
+ import { a as isZodSchema, i as isJsonSchema, n as convertRouteSchema, o as toJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-DJvodiHj.mjs";
4
5
  import { a as promoteStreamTokenToHeader, i as pipeUIMessageStreamToReply, n as forwardedStreamHeaders, r as isReadableStream, t as UI_MESSAGE_STREAM_HEADERS } from "../streaming-BQcW6_Kq.mjs";
6
+ import { t as openSseStream } from "../sseStream-BQ7Qaynp.mjs";
5
7
  import { t as hasEvents } from "../typeGuards-C9KXUly6.mjs";
6
- export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, MONGO_DIALECT, NEUTRAL_DIALECT, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UI_MESSAGE_STREAM_HEADERS, UnauthorizedError, ValidationError, aggregateListResponse, assertValidConfig, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, formatValidationErrors, forwardedStreamHeaders, getDefaultCrudSchemas, getListQueryParams, getUserId, handleRaw, hasEvents, isArcError, isJsonSchema, isReadableStream, isZodSchema, keysetListResponse, listResponse, offsetListResponse, paginationSchema, pipeUIMessageStreamToReply, promoteStreamTokenToHeader, queryParams, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, validateResourceConfig, withCompensation };
8
+ export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, MONGO_DIALECT, NEUTRAL_DIALECT, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UI_MESSAGE_STREAM_HEADERS, UnauthorizedError, ValidationError, aggregateListResponse, assertValidConfig, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, formatValidationErrors, forwardedStreamHeaders, getDefaultCrudSchemas, getHeader, getListQueryParams, getUserId, handleRaw, hasEvents, isArcError, isJsonSchema, isReadableStream, isZodSchema, keysetListResponse, listResponse, offsetListResponse, openSseStream, paginationSchema, pipeUIMessageStreamToReply, promoteStreamTokenToHeader, queryParams, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, validateResourceConfig, withCompensation };
@@ -1,75 +1,8 @@
1
1
  import { m as RESERVED_QUERY_PARAMS, t as CRUD_OPERATIONS } from "./constants-DNre0wRg.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
3
  import { t as ArcError } from "./errors-DkO8Nk6-.mjs";
4
- import { r as getAvailablePresets } from "./presets-gw0ciIUD.mjs";
4
+ import { r as getAvailablePresets } from "./presets-BOe4Ugkl.mjs";
5
5
  import { errorContractSchema as errorContractSchema$1, errorDetailSchema as errorDetailSchema$1 } from "@classytic/repo-core/errors";
6
- //#region src/utils/simpleEqualityMatcher.ts
7
- /**
8
- * `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
9
- * matcher for `DataAdapter.matchesFilter` / `BaseController({ matchesFilter })`.
10
- *
11
- * **What it does:** for each `[key, expected]` in the filter, compares
12
- * `item[key]` to `expected` via string coercion (so Mongo `ObjectId` values
13
- * match their string representation) and returns `true` only if every
14
- * filter entry matches. Array item values are matched implicitly (contains).
15
- *
16
- * **What it does NOT do:**
17
- * - No `$eq` / `$ne` / `$in` / `$nin` / `$gt` / `$lt` / `$regex` / `$exists`
18
- * - No `$and` / `$or`
19
- * - No dot-path traversal (`"owner.id"`)
20
- * - No schema-specific coercion
21
- *
22
- * **Why it exists:** 95%+ of arc's `_policyFilters` are produced by built-in
23
- * permission helpers and are shaped like `{ ownerId: "u1" }` or
24
- * `{ organizationId: "org_x" }` — flat equality. For that common shape,
25
- * this helper is a safe, tested, 15-line defense-in-depth matcher that
26
- * hosts using minimal repos (no `getOne(compoundFilter)` DB path) can opt
27
- * into without arc shipping a full Mongo-syntax engine.
28
- *
29
- * **When to use:**
30
- * - Your adapter/repo doesn't natively filter on `getOne(compoundFilter)`
31
- * - Your `_policyFilters` are flat equality (from arc's built-in permission helpers)
32
- * - You want defense-in-depth on `validateItemAccess` / `fetchDetailed`'s `getById` fallback
33
- *
34
- * **When NOT to use:**
35
- * - Your `_policyFilters` use operators (`$in`, `$ne`, etc.) — supply a
36
- * native matcher (mongokit's repo does the filter at the DB layer; for
37
- * custom repos, wrap the kit's own predicate engine).
38
- * - You're a mongokit / sqlitekit / Prisma user — the DB-level filter
39
- * applied by `getOne(compoundFilter)` already covers this.
40
- *
41
- * @example
42
- * ```ts
43
- * import { simpleEqualityMatcher } from '@classytic/arc/utils';
44
- *
45
- * // On a custom adapter
46
- * const adapter: DataAdapter = {
47
- * repository,
48
- * type: 'custom',
49
- * name: 'in-memory',
50
- * matchesFilter: simpleEqualityMatcher,
51
- * };
52
- *
53
- * // Or directly on BaseController for ad-hoc controllers
54
- * new BaseController(repo, { matchesFilter: simpleEqualityMatcher });
55
- * ```
56
- */
57
- function simpleEqualityMatcher(item, filters) {
58
- if (!item || typeof item !== "object") return false;
59
- const obj = item;
60
- for (const [key, expected] of Object.entries(filters)) {
61
- if (expected && typeof expected === "object" && !Array.isArray(expected) && Object.getPrototypeOf(expected) === Object.prototype && Object.keys(expected).some((k) => k.startsWith("$"))) return false;
62
- const actual = obj[key];
63
- if (Array.isArray(actual)) {
64
- const expectedStr = String(expected);
65
- if (!actual.some((v) => String(v) === expectedStr)) return false;
66
- continue;
67
- }
68
- if (String(actual) !== String(expected)) return false;
69
- }
70
- return true;
71
- }
72
- //#endregion
73
6
  //#region src/core/validateResourceConfig.ts
74
7
  /**
75
8
  * Resource Configuration Validator
@@ -837,6 +770,25 @@ function handleRaw(handler, statusCode = 200) {
837
770
  };
838
771
  }
839
772
  //#endregion
773
+ //#region src/utils/headers.ts
774
+ /**
775
+ * Read a single header value. Lowercases the name (Node normalizes incoming
776
+ * header names to lowercase) and returns the FIRST value when the header
777
+ * was repeated.
778
+ *
779
+ * Duplicate policy: first-value is deterministic, but for security-sensitive
780
+ * headers (authorization material, forwarded-for chains, content-length) a
781
+ * duplicate usually signals smuggling or a broken proxy — consumers that
782
+ * care should check `Array.isArray(headers[name])` and REJECT instead of
783
+ * picking a winner.
784
+ */
785
+ function getHeader(headers, name) {
786
+ if (!headers) return void 0;
787
+ const value = headers[name.toLowerCase()];
788
+ if (Array.isArray(value)) return value[0];
789
+ return value;
790
+ }
791
+ //#endregion
840
792
  //#region src/utils/queryParser.ts
841
793
  /**
842
794
  * Arc Query Parser - Default URL-to-Query Parser
@@ -1778,4 +1730,4 @@ function getUserId(user) {
1778
1730
  return id ? String(id) : void 0;
1779
1731
  }
1780
1732
  //#endregion
1781
- export { createCircuitBreaker as A, defineErrorMapper as C, CircuitBreakerError as D, CircuitBreaker as E, simpleEqualityMatcher as F, assertValidConfig as M, formatValidationErrors as N, CircuitBreakerRegistry as O, validateResourceConfig as P, defineGuard as S, withCompensation as T, ArcQueryParser as _, bareListResponse as a, MONGO_DIALECT as b, errorDetailSchema$1 as c, keysetListResponse as d, listResponse as f, responses as g, queryParams as h, aggregateListResponse as i, createCircuitBreakerRegistry as j, CircuitState as k, getDefaultCrudSchemas as l, paginationSchema as m, createStateMachine as n, deleteResponse as o, offsetListResponse as p, scheduleBackground as r, errorContractSchema$1 as s, getUserId as t, getListQueryParams as u, createQueryParser as v, defineCompensation as w, NEUTRAL_DIALECT as x, handleRaw as y };
1733
+ export { CircuitState as A, defineGuard as C, CircuitBreaker as D, withCompensation as E, validateResourceConfig as F, createCircuitBreakerRegistry as M, assertValidConfig as N, CircuitBreakerError as O, formatValidationErrors as P, NEUTRAL_DIALECT as S, defineCompensation as T, ArcQueryParser as _, bareListResponse as a, handleRaw as b, errorDetailSchema$1 as c, keysetListResponse as d, listResponse as f, responses as g, queryParams as h, aggregateListResponse as i, createCircuitBreaker as j, CircuitBreakerRegistry as k, getDefaultCrudSchemas as l, paginationSchema as m, createStateMachine as n, deleteResponse as o, offsetListResponse as p, scheduleBackground as r, errorContractSchema$1 as s, getUserId as t, getListQueryParams as u, createQueryParser as v, defineErrorMapper as w, MONGO_DIALECT as x, getHeader as y };
@@ -26,7 +26,7 @@ interface HealthOptions {
26
26
  collectHttpMetrics?: boolean;
27
27
  }
28
28
  declare const healthPlugin: FastifyPluginAsync<HealthOptions>;
29
- declare const _default$4: FastifyPluginAsync<HealthOptions>;
29
+ declare const _default$5: FastifyPluginAsync<HealthOptions>;
30
30
  //#endregion
31
31
  //#region src/plugins/caching.d.ts
32
32
  interface CachingRule {
@@ -54,7 +54,7 @@ interface CachingOptions {
54
54
  rules?: CachingRule[];
55
55
  }
56
56
  declare const cachingPlugin: FastifyPluginAsync<CachingOptions>;
57
- declare const _default$3: FastifyPluginAsync<CachingOptions>;
57
+ declare const _default$4: FastifyPluginAsync<CachingOptions>;
58
58
  //#endregion
59
59
  //#region src/plugins/sse.d.ts
60
60
  interface SSEOptions {
@@ -79,7 +79,46 @@ interface SSEOptions {
79
79
  tokenQueryParam?: string | null;
80
80
  }
81
81
  declare const ssePlugin: FastifyPluginAsync<SSEOptions>;
82
- declare const _default$2: FastifyPluginAsync<SSEOptions>;
82
+ declare const _default$3: FastifyPluginAsync<SSEOptions>;
83
+ //#endregion
84
+ //#region src/plugins/realtime.d.ts
85
+ declare const CHANGE_OPERATIONS: readonly ["created", "updated", "deleted"];
86
+ type ChangeOperation = (typeof CHANGE_OPERATIONS)[number];
87
+ interface RealtimeOptions {
88
+ /**
89
+ * Route path — must contain `:resource` (default: `/realtime/:resource`).
90
+ */
91
+ path?: string;
92
+ /** Heartbeat interval in ms (default: 30000). */
93
+ heartbeat?: number;
94
+ /**
95
+ * Subscribable resources. Default: every registered resource — safe
96
+ * because each subscription runs that resource's own `list` permission;
97
+ * narrow it when only some resources should have a change feed at all.
98
+ */
99
+ resources?: readonly string[];
100
+ /** Operations streamed (default: created + updated + deleted). */
101
+ operations?: readonly ChangeOperation[];
102
+ /**
103
+ * Query parameter carrying the bearer token for browser `EventSource`
104
+ * clients (default `'token'`, matching the sse plugin / arc-next).
105
+ * Set `null` to disable promotion.
106
+ */
107
+ tokenQueryParam?: string | null;
108
+ /**
109
+ * Hard ceiling (ms) on any single connection's lifetime. Bounds
110
+ * permission STALENESS: the row filters are snapshotted at connect, so a
111
+ * caller whose permissions are revoked mid-connection keeps receiving
112
+ * their old view until they reconnect. Independently, arc ALWAYS closes
113
+ * the feed when the auth token's `exp` passes (forcing reconnect +
114
+ * re-authorization with fresh filters) — this option adds a cap for
115
+ * session/cookie auth that carries no `exp`, or to tighten below the
116
+ * token TTL. Omit for token-exp-only bounding.
117
+ */
118
+ maxConnectionMs?: number;
119
+ }
120
+ declare const realtimePlugin: FastifyPluginAsync<RealtimeOptions>;
121
+ declare const _default$2: FastifyPluginAsync<RealtimeOptions>;
83
122
  //#endregion
84
123
  //#region src/plugins/metrics.d.ts
85
124
  interface MetricsOptions {
@@ -148,4 +187,4 @@ declare module "fastify" {
148
187
  declare const versioningPlugin: FastifyPluginAsync<VersioningOptions>;
149
188
  declare const _default: FastifyPluginAsync<VersioningOptions>;
150
189
  //#endregion
151
- export { HealthOptions as _, MetricsCollector as a, metricsPlugin as c, ssePlugin as d, CachingOptions as f, HealthCheck as g, cachingPlugin as h, MetricEntry as i, SSEOptions as l, _default$3 as m, _default as n, MetricsOptions as o, CachingRule as p, versioningPlugin as r, _default$1 as s, VersioningOptions as t, _default$2 as u, _default$4 as v, healthPlugin as y };
190
+ export { healthPlugin as C, _default$5 as S, CachingRule as _, MetricsCollector as a, HealthCheck as b, metricsPlugin as c, _default$2 as d, realtimePlugin as f, CachingOptions as g, ssePlugin as h, MetricEntry as i, ChangeOperation as l, _default$3 as m, _default as n, MetricsOptions as o, SSEOptions as p, versioningPlugin as r, _default$1 as s, VersioningOptions as t, RealtimeOptions as u, _default$4 as v, HealthOptions as x, cachingPlugin as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.22.0",
3
+ "version": "2.23.0",
4
4
  "description": "Resource-oriented backend framework for Fastify - clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -222,6 +222,62 @@
222
222
  "access": "public"
223
223
  },
224
224
  "sideEffects": false,
225
+ "arc": {
226
+ "subpathStability": {
227
+ ".": "stable",
228
+ "./permissions": "stable",
229
+ "./cache": "stable",
230
+ "./types": "stable",
231
+ "./types/storage": "stable",
232
+ "./core": "stable",
233
+ "./presets": "stable",
234
+ "./presets/tenant": "stable",
235
+ "./presets/files-upload": "stable",
236
+ "./presets/search": "stable",
237
+ "./auth": "stable",
238
+ "./hooks": "stable",
239
+ "./middleware": "stable",
240
+ "./pipeline": "stable",
241
+ "./context": "stable",
242
+ "./logger": "stable",
243
+ "./registry": "stable",
244
+ "./utils": "stable",
245
+ "./cli": "stable",
246
+ "./plugins": "stable",
247
+ "./plugins/tracing": "stable",
248
+ "./audit": "stable",
249
+ "./docs": "stable",
250
+ "./encryption": "stable",
251
+ "./idempotency": "stable",
252
+ "./idempotency/redis": "stable",
253
+ "./events": "stable",
254
+ "./events/redis": "stable",
255
+ "./events/redis-stream": "stable",
256
+ "./testing": "stable",
257
+ "./testing/storage": "stable",
258
+ "./factory": "stable",
259
+ "./schemas": "stable",
260
+ "./migrations": "stable",
261
+ "./discovery": "stable",
262
+ "./integrations": "stable",
263
+ "./integrations/streamline": "stable",
264
+ "./integrations/websocket": "stable",
265
+ "./integrations/websocket-redis": "stable",
266
+ "./integrations/websocket-pushref-redis": "stable",
267
+ "./integrations/jobs": "stable",
268
+ "./integrations/event-gateway": "experimental",
269
+ "./integrations/webhooks": "stable",
270
+ "./auth/redis": "stable",
271
+ "./plugins/response-cache": "stable",
272
+ "./scope": "stable",
273
+ "./mcp": "stable",
274
+ "./mcp/testing": "stable",
275
+ "./scim": "stable",
276
+ "./auth/audit": "stable",
277
+ "./sync": "experimental",
278
+ "./usage": "experimental"
279
+ }
280
+ },
225
281
  "files": [
226
282
  "dist",
227
283
  "bin",
@@ -253,14 +309,15 @@
253
309
  "release": "npm run push -- main && npm run release:tag && npm publish",
254
310
  "check:peer-skew": "node scripts/check-peer-skew.mjs",
255
311
  "check:docs-drift": "node scripts/check-docs-drift.mjs",
256
- "prepublishOnly": "npm run check:peer-skew && npm run check:docs-drift && npm run typecheck && npm run lint && npm run build && npm run test:ci && npm run smoke"
312
+ "check:boundaries": "node scripts/check-boundaries.mjs",
313
+ "prepublishOnly": "npm run check:peer-skew && npm run check:docs-drift && npm run check:boundaries && npm run typecheck && npm run lint && npm run build && npm run test:ci && npm run smoke"
257
314
  },
258
315
  "engines": {
259
316
  "node": ">=22"
260
317
  },
261
318
  "peerDependencies": {
262
319
  "@classytic/primitives": ">=0.9.0",
263
- "@classytic/repo-core": ">=0.7.0",
320
+ "@classytic/repo-core": ">=0.14.0",
264
321
  "@classytic/streamline": ">=2.7.0",
265
322
  "@fastify/cors": ">=11.0.0",
266
323
  "@fastify/helmet": ">=13.0.0",
@@ -379,11 +436,14 @@
379
436
  "@better-auth/mongo-adapter": "^1.6.19",
380
437
  "@biomejs/biome": "^2.5.0",
381
438
  "@classytic/dev-tools": "^0.2.0",
439
+ "@classytic/gitkit": ">=0.1.0",
382
440
  "@classytic/mongokit": "^3.22.0",
441
+ "@classytic/pgkit": "^0.1.0",
383
442
  "@classytic/primitives": "^0.9.1",
384
- "@classytic/repo-core": "^0.13.0",
443
+ "@classytic/repo-core": "^0.14.0",
385
444
  "@classytic/sqlitekit": "^0.6.0",
386
445
  "@classytic/streamline": "^2.7.0",
446
+ "@electric-sql/pglite": "^0.5.4",
387
447
  "@fastify/cors": "^11.2.0",
388
448
  "@fastify/helmet": "^13.0.2",
389
449
  "@fastify/jwt": "^10.1.0",