@classytic/arc 1.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
- package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
- package/dist/HookSystem-BsGV-j2l.mjs +405 -0
- package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +82 -0
- package/dist/audit/index.d.mts.map +1 -0
- package/dist/audit/index.mjs +276 -0
- package/dist/audit/index.mjs.map +1 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-C3T5DTUx.mjs +141 -0
- package/dist/audited-C3T5DTUx.mjs.map +1 -0
- package/dist/auth/index.d.mts +189 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +1102 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth/redis-session.d.mts +44 -0
- package/dist/auth/redis-session.d.mts.map +1 -0
- package/dist/auth/redis-session.mjs +76 -0
- package/dist/auth/redis-session.mjs.map +1 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
- package/dist/cache/index.d.mts +146 -0
- package/dist/cache/index.d.mts.map +1 -0
- package/dist/cache/index.mjs +92 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/caching-Bl28lYsR.mjs +94 -0
- package/dist/caching-Bl28lYsR.mjs.map +1 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
- package/dist/cli/commands/describe.d.mts +19 -0
- package/dist/cli/commands/describe.d.mts.map +1 -0
- package/dist/cli/commands/describe.mjs +239 -0
- package/dist/cli/commands/describe.mjs.map +1 -0
- package/dist/cli/commands/docs.d.mts +14 -0
- package/dist/cli/commands/docs.d.mts.map +1 -0
- package/dist/cli/commands/docs.mjs +53 -0
- package/dist/cli/commands/docs.mjs.map +1 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
- package/dist/cli/commands/generate.d.mts.map +1 -0
- package/dist/cli/commands/generate.mjs +358 -0
- package/dist/cli/commands/generate.mjs.map +1 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
- package/dist/cli/commands/init.d.mts.map +1 -0
- package/dist/cli/commands/{init.js → init.mjs} +807 -616
- package/dist/cli/commands/init.mjs.map +1 -0
- package/dist/cli/commands/introspect.d.mts +11 -0
- package/dist/cli/commands/introspect.d.mts.map +1 -0
- package/dist/cli/commands/introspect.mjs +76 -0
- package/dist/cli/commands/introspect.mjs.map +1 -0
- package/dist/cli/index.d.mts +17 -0
- package/dist/cli/index.d.mts.map +1 -0
- package/dist/cli/index.mjs +157 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/constants-DdXFXQtN.mjs +85 -0
- package/dist/constants-DdXFXQtN.mjs.map +1 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-CUgNqegw.mjs +560 -0
- package/dist/createApp-CUgNqegw.mjs.map +1 -0
- package/dist/defineResource-k0_BDn8v.mjs +2197 -0
- package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
- package/dist/discovery/index.d.mts +47 -0
- package/dist/discovery/index.d.mts.map +1 -0
- package/dist/discovery/index.mjs +110 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/docs/index.d.mts +163 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +73 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/elevation-BRy3yFWT.mjs +113 -0
- package/dist/elevation-BRy3yFWT.mjs.map +1 -0
- package/dist/elevation-B_2dRLVP.d.mts +88 -0
- package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
- package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
- package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
- package/dist/errorHandler-C1okiriz.mjs +109 -0
- package/dist/errorHandler-C1okiriz.mjs.map +1 -0
- package/dist/errors-B9bZok84.mjs +212 -0
- package/dist/errors-B9bZok84.mjs.map +1 -0
- package/dist/errors-ChKiFz62.d.mts +125 -0
- package/dist/errors-ChKiFz62.d.mts.map +1 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
- package/dist/eventPlugin-DGR_B2on.mjs +230 -0
- package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
- package/dist/events/index.d.mts +54 -0
- package/dist/events/index.d.mts.map +1 -0
- package/dist/events/index.mjs +52 -0
- package/dist/events/index.mjs.map +1 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +178 -0
- package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
- package/dist/events/transports/redis.d.mts +77 -0
- package/dist/events/transports/redis.d.mts.map +1 -0
- package/dist/events/transports/redis.mjs +125 -0
- package/dist/events/transports/redis.mjs.map +1 -0
- package/dist/externalPaths-DlINfKbP.d.mts +51 -0
- package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
- package/dist/factory/index.d.mts +64 -0
- package/dist/factory/index.d.mts.map +1 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
- package/dist/fields-DyaDVX4J.d.mts +110 -0
- package/dist/fields-DyaDVX4J.d.mts.map +1 -0
- package/dist/fields-iagOozy0.mjs +115 -0
- package/dist/fields-iagOozy0.mjs.map +1 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +97 -0
- package/dist/idempotency/index.d.mts.map +1 -0
- package/dist/idempotency/index.mjs +320 -0
- package/dist/idempotency/index.mjs.map +1 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +115 -0
- package/dist/idempotency/mongodb.mjs.map +1 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +104 -0
- package/dist/idempotency/redis.mjs.map +1 -0
- package/dist/index.d.mts +261 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +105 -0
- package/dist/index.mjs.map +1 -0
- package/dist/integrations/event-gateway.d.mts +47 -0
- package/dist/integrations/event-gateway.d.mts.map +1 -0
- package/dist/integrations/event-gateway.mjs +44 -0
- package/dist/integrations/event-gateway.mjs.map +1 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +104 -0
- package/dist/integrations/jobs.d.mts.map +1 -0
- package/dist/integrations/jobs.mjs +124 -0
- package/dist/integrations/jobs.mjs.map +1 -0
- package/dist/integrations/streamline.d.mts +61 -0
- package/dist/integrations/streamline.d.mts.map +1 -0
- package/dist/integrations/streamline.mjs +126 -0
- package/dist/integrations/streamline.mjs.map +1 -0
- package/dist/integrations/websocket.d.mts +83 -0
- package/dist/integrations/websocket.d.mts.map +1 -0
- package/dist/integrations/websocket.mjs +289 -0
- package/dist/integrations/websocket.mjs.map +1 -0
- package/dist/interface-B01JvPVc.d.mts +78 -0
- package/dist/interface-B01JvPVc.d.mts.map +1 -0
- package/dist/interface-CZe8IkMf.d.mts +55 -0
- package/dist/interface-CZe8IkMf.d.mts.map +1 -0
- package/dist/interface-Ch8HU9uM.d.mts +1098 -0
- package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
- package/dist/keys-BqNejWup.mjs +43 -0
- package/dist/keys-BqNejWup.mjs.map +1 -0
- package/dist/logger-Df2O2WsW.mjs +79 -0
- package/dist/logger-Df2O2WsW.mjs.map +1 -0
- package/dist/memory-cQgelFOj.mjs +144 -0
- package/dist/memory-cQgelFOj.mjs.map +1 -0
- package/dist/migrations/index.d.mts +157 -0
- package/dist/migrations/index.d.mts.map +1 -0
- package/dist/migrations/index.mjs +261 -0
- package/dist/migrations/index.mjs.map +1 -0
- package/dist/mongodb-BfJVlUJH.mjs +94 -0
- package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
- package/dist/mongodb-CGzRbfAK.d.mts +119 -0
- package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
- package/dist/mongodb-JN-9JA7K.d.mts +72 -0
- package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
- package/dist/openapi-G3Cw7XuM.mjs +524 -0
- package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
- package/dist/org/index.d.mts +69 -0
- package/dist/org/index.d.mts.map +1 -0
- package/dist/org/index.mjs +514 -0
- package/dist/org/index.mjs.map +1 -0
- package/dist/org/types.d.mts +83 -0
- package/dist/org/types.d.mts.map +1 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +279 -0
- package/dist/permissions/index.d.mts.map +1 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/permissions/index.mjs.map +1 -0
- package/dist/plugins/index.d.mts +173 -0
- package/dist/plugins/index.d.mts.map +1 -0
- package/dist/plugins/index.mjs +523 -0
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/plugins/response-cache.d.mts +88 -0
- package/dist/plugins/response-cache.d.mts.map +1 -0
- package/dist/plugins/response-cache.mjs +284 -0
- package/dist/plugins/response-cache.mjs.map +1 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +186 -0
- package/dist/plugins/tracing-entry.mjs.map +1 -0
- package/dist/pluralize-CEweyOEm.mjs +87 -0
- package/dist/pluralize-CEweyOEm.mjs.map +1 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -169
- package/dist/policies/index.d.mts.map +1 -0
- package/dist/policies/index.mjs +322 -0
- package/dist/policies/index.mjs.map +1 -0
- package/dist/presets/{index.d.ts → index.d.mts} +63 -131
- package/dist/presets/index.d.mts.map +1 -0
- package/dist/presets/index.mjs +144 -0
- package/dist/presets/index.mjs.map +1 -0
- package/dist/presets/multiTenant.d.mts +25 -0
- package/dist/presets/multiTenant.d.mts.map +1 -0
- package/dist/presets/multiTenant.mjs +114 -0
- package/dist/presets/multiTenant.mjs.map +1 -0
- package/dist/presets-BITljm96.mjs +120 -0
- package/dist/presets-BITljm96.mjs.map +1 -0
- package/dist/presets-DzSMwlKj.d.mts +58 -0
- package/dist/presets-DzSMwlKj.d.mts.map +1 -0
- package/dist/prisma-DJbMt3yf.mjs +628 -0
- package/dist/prisma-DJbMt3yf.mjs.map +1 -0
- package/dist/prisma-Dg9GoVdj.d.mts +275 -0
- package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
- package/dist/redis-D-JAeLtm.d.mts +50 -0
- package/dist/redis-D-JAeLtm.d.mts.map +1 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
- package/dist/registry/index.d.mts +12 -0
- package/dist/registry/index.d.mts.map +1 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-QQD6ROJc.mjs +56 -0
- package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
- package/dist/schemaConverter-BwrmWroW.mjs +99 -0
- package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
- package/dist/schemas/index.d.mts +64 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +83 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/scope/index.d.mts +22 -0
- package/dist/scope/index.d.mts.map +1 -0
- package/dist/scope/index.mjs +66 -0
- package/dist/scope/index.mjs.map +1 -0
- package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
- package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
- package/dist/sse-B3c3_yZp.mjs +124 -0
- package/dist/sse-B3c3_yZp.mjs.map +1 -0
- package/dist/testing/index.d.mts +908 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +1977 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/tracing-Cc7vVQPp.d.mts +71 -0
- package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
- package/dist/typeGuards-DhMNLuvU.mjs +10 -0
- package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
- package/dist/types/index.d.mts +947 -0
- package/dist/types/index.d.mts.map +1 -0
- package/dist/types/index.mjs +15 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types-Beqn1Un7.mjs +39 -0
- package/dist/types-Beqn1Un7.mjs.map +1 -0
- package/dist/types-CIgB7UUl.d.mts +446 -0
- package/dist/types-CIgB7UUl.d.mts.map +1 -0
- package/dist/types-aYB4V7uN.d.mts +87 -0
- package/dist/types-aYB4V7uN.d.mts.map +1 -0
- package/dist/utils/index.d.mts +748 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1,1098 @@
|
|
|
1
|
+
import { n as FieldPermissionMap } from "./fields-DyaDVX4J.mjs";
|
|
2
|
+
import { i as UserBase } from "./types-aYB4V7uN.mjs";
|
|
3
|
+
import { AdditionalRoute, AnyRecord, ArcInternalMetadata, ControllerQueryOptions, CrudRouteKey, CrudSchemas, EventDefinition, IntrospectionData, MiddlewareConfig, OpenApiSchemas, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, ResourceCacheConfig, ResourceConfig, ResourceMetadata, ResourcePermissions, RouteSchemaOptions } from "./types/index.mjs";
|
|
4
|
+
import { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
|
|
5
|
+
|
|
6
|
+
//#region src/hooks/HookSystem.d.ts
|
|
7
|
+
type HookPhase = 'before' | 'around' | 'after';
|
|
8
|
+
type HookOperation = 'create' | 'update' | 'delete' | 'read' | 'list';
|
|
9
|
+
interface HookContext<T = AnyRecord> {
|
|
10
|
+
resource: string;
|
|
11
|
+
operation: HookOperation;
|
|
12
|
+
phase: HookPhase;
|
|
13
|
+
data?: T;
|
|
14
|
+
result?: T | T[];
|
|
15
|
+
user?: UserBase;
|
|
16
|
+
context?: RequestContext;
|
|
17
|
+
meta?: AnyRecord;
|
|
18
|
+
}
|
|
19
|
+
type HookHandler<T = AnyRecord> = (ctx: HookContext<T>) => void | Promise<void> | T | Promise<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Around hook handler — wraps the core operation.
|
|
22
|
+
* Call `next()` to proceed to the next around hook or the actual operation.
|
|
23
|
+
*/
|
|
24
|
+
type AroundHookHandler<T = AnyRecord> = (ctx: HookContext<T>, next: () => Promise<T | undefined>) => T | undefined | Promise<T | undefined>;
|
|
25
|
+
interface HookRegistration {
|
|
26
|
+
/** Hook name for dependency resolution and debugging */
|
|
27
|
+
name?: string;
|
|
28
|
+
resource: string;
|
|
29
|
+
operation: HookOperation;
|
|
30
|
+
phase: HookPhase;
|
|
31
|
+
handler: HookHandler;
|
|
32
|
+
priority: number;
|
|
33
|
+
/** Names of hooks that must execute before this one */
|
|
34
|
+
dependsOn?: string[];
|
|
35
|
+
}
|
|
36
|
+
interface HookSystemOptions {
|
|
37
|
+
/** Custom logger for error/warning reporting. Defaults to console */
|
|
38
|
+
logger?: {
|
|
39
|
+
error: (message: string, ...args: unknown[]) => void;
|
|
40
|
+
warn?: (message: string, ...args: unknown[]) => void;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
declare class HookSystem {
|
|
44
|
+
private hooks;
|
|
45
|
+
private logger;
|
|
46
|
+
private warn;
|
|
47
|
+
constructor(options?: HookSystemOptions);
|
|
48
|
+
/**
|
|
49
|
+
* Generate hook key
|
|
50
|
+
*/
|
|
51
|
+
private getKey;
|
|
52
|
+
/**
|
|
53
|
+
* Register a hook
|
|
54
|
+
* Supports both object parameter and positional arguments
|
|
55
|
+
*/
|
|
56
|
+
register<T = AnyRecord>(resourceOrOptions: string | {
|
|
57
|
+
name?: string;
|
|
58
|
+
resource: string;
|
|
59
|
+
operation: HookOperation;
|
|
60
|
+
phase: HookPhase;
|
|
61
|
+
handler: HookHandler<T>;
|
|
62
|
+
priority?: number;
|
|
63
|
+
dependsOn?: string[];
|
|
64
|
+
}, operation?: HookOperation, phase?: HookPhase, handler?: HookHandler<T>, priority?: number): () => void;
|
|
65
|
+
/**
|
|
66
|
+
* Register before hook
|
|
67
|
+
*/
|
|
68
|
+
before<T = AnyRecord>(resource: string, operation: HookOperation, handler: HookHandler<T>, priority?: number): () => void;
|
|
69
|
+
/**
|
|
70
|
+
* Register after hook
|
|
71
|
+
*/
|
|
72
|
+
after<T = AnyRecord>(resource: string, operation: HookOperation, handler: HookHandler<T>, priority?: number): () => void;
|
|
73
|
+
/**
|
|
74
|
+
* Register around hook — wraps the core operation.
|
|
75
|
+
* Call `next()` inside the handler to proceed.
|
|
76
|
+
*/
|
|
77
|
+
around<T = AnyRecord>(resource: string, operation: HookOperation, handler: AroundHookHandler<T>, priority?: number): () => void;
|
|
78
|
+
/**
|
|
79
|
+
* Execute around hooks as a nested middleware chain.
|
|
80
|
+
* Each around hook receives `next()` to call the next hook or the core operation.
|
|
81
|
+
*/
|
|
82
|
+
executeAround<T = AnyRecord>(resource: string, operation: HookOperation, data: T, execute: () => Promise<T | undefined>, options?: {
|
|
83
|
+
user?: UserBase;
|
|
84
|
+
context?: RequestContext;
|
|
85
|
+
meta?: AnyRecord;
|
|
86
|
+
}): Promise<T | undefined>;
|
|
87
|
+
/**
|
|
88
|
+
* Execute hooks for a given context
|
|
89
|
+
*/
|
|
90
|
+
execute<T = AnyRecord>(ctx: HookContext<T>): Promise<T | undefined>;
|
|
91
|
+
/**
|
|
92
|
+
* Execute before hooks
|
|
93
|
+
*/
|
|
94
|
+
executeBefore<T = AnyRecord>(resource: string, operation: HookOperation, data: T, options?: {
|
|
95
|
+
user?: UserBase;
|
|
96
|
+
context?: RequestContext;
|
|
97
|
+
meta?: AnyRecord;
|
|
98
|
+
}): Promise<T>;
|
|
99
|
+
/**
|
|
100
|
+
* Execute after hooks
|
|
101
|
+
* Errors in after hooks are logged but don't fail the request
|
|
102
|
+
*/
|
|
103
|
+
executeAfter<T = AnyRecord>(resource: string, operation: HookOperation, result: T | T[], options?: {
|
|
104
|
+
user?: UserBase;
|
|
105
|
+
context?: RequestContext;
|
|
106
|
+
meta?: AnyRecord;
|
|
107
|
+
}): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Topological sort with Kahn's algorithm.
|
|
110
|
+
* Hooks with `dependsOn` are ordered after their dependencies.
|
|
111
|
+
* Within the same dependency level, priority ordering is preserved.
|
|
112
|
+
* Hooks without names or dependencies pass through in their original order.
|
|
113
|
+
*/
|
|
114
|
+
private topologicalSort;
|
|
115
|
+
/**
|
|
116
|
+
* Get all registered hooks
|
|
117
|
+
*/
|
|
118
|
+
getAll(): HookRegistration[];
|
|
119
|
+
/**
|
|
120
|
+
* Get hooks for a specific resource
|
|
121
|
+
*/
|
|
122
|
+
getForResource(resource: string): HookRegistration[];
|
|
123
|
+
/**
|
|
124
|
+
* Get hooks matching filter criteria.
|
|
125
|
+
* Useful for debugging and testing specific hook combinations.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* // Find all before-create hooks for products (including wildcards)
|
|
130
|
+
* const hooks = hookSystem.getRegistered({
|
|
131
|
+
* resource: 'product',
|
|
132
|
+
* operation: 'create',
|
|
133
|
+
* phase: 'before',
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
getRegistered(filter?: {
|
|
138
|
+
resource?: string;
|
|
139
|
+
operation?: HookOperation;
|
|
140
|
+
phase?: HookPhase;
|
|
141
|
+
}): HookRegistration[];
|
|
142
|
+
/**
|
|
143
|
+
* Get a structured summary of all registered hooks for debugging.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* const info = hookSystem.inspect();
|
|
148
|
+
* // { total: 12, resources: { product: [...], '*': [...] }, summary: [...] }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
inspect(): {
|
|
152
|
+
total: number;
|
|
153
|
+
resources: Record<string, HookRegistration[]>;
|
|
154
|
+
summary: Array<{
|
|
155
|
+
name?: string;
|
|
156
|
+
key: string;
|
|
157
|
+
priority: number;
|
|
158
|
+
dependsOn?: string[];
|
|
159
|
+
}>;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Check if any hooks exist for a specific resource/operation/phase combination.
|
|
163
|
+
*/
|
|
164
|
+
has(resource: string, operation: HookOperation, phase: HookPhase): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Clear all hooks
|
|
167
|
+
*/
|
|
168
|
+
clear(): void;
|
|
169
|
+
/**
|
|
170
|
+
* Clear hooks for a specific resource
|
|
171
|
+
*/
|
|
172
|
+
clearResource(resource: string): void;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Create a new isolated HookSystem instance
|
|
176
|
+
*
|
|
177
|
+
* Use this for:
|
|
178
|
+
* - Test isolation (parallel test suites)
|
|
179
|
+
* - Multiple app instances with independent hooks
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* const hooks = createHookSystem();
|
|
183
|
+
* await app.register(arcCorePlugin, { hookSystem: hooks });
|
|
184
|
+
*
|
|
185
|
+
* @example With custom logger
|
|
186
|
+
* const hooks = createHookSystem({ logger: fastify.log });
|
|
187
|
+
*/
|
|
188
|
+
declare function createHookSystem(options?: HookSystemOptions): HookSystem;
|
|
189
|
+
interface DefineHookOptions<T = AnyRecord> {
|
|
190
|
+
/** Unique hook name (required for dependency resolution) */
|
|
191
|
+
name: string;
|
|
192
|
+
/** Target resource */
|
|
193
|
+
resource: string;
|
|
194
|
+
/** CRUD operation */
|
|
195
|
+
operation: HookOperation;
|
|
196
|
+
/** before or after */
|
|
197
|
+
phase: HookPhase;
|
|
198
|
+
/** Hook handler */
|
|
199
|
+
handler: HookHandler<T>;
|
|
200
|
+
/** Priority (lower = earlier, default: 10) */
|
|
201
|
+
priority?: number;
|
|
202
|
+
/** Names of hooks that must execute before this one */
|
|
203
|
+
dependsOn?: string[];
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Define a named hook with optional dependencies.
|
|
207
|
+
* Returns a registration object — call `register(hookSystem)` to activate.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* const generateSlug = defineHook({
|
|
212
|
+
* name: 'generateSlug',
|
|
213
|
+
* resource: 'product', operation: 'create', phase: 'before',
|
|
214
|
+
* handler: (ctx) => ({ ...ctx.data, slug: slugify(ctx.data.name) }),
|
|
215
|
+
* });
|
|
216
|
+
*
|
|
217
|
+
* const validateUniqueSlug = defineHook({
|
|
218
|
+
* name: 'validateUniqueSlug',
|
|
219
|
+
* resource: 'product', operation: 'create', phase: 'before',
|
|
220
|
+
* dependsOn: ['generateSlug'],
|
|
221
|
+
* handler: async (ctx) => { // check uniqueness },
|
|
222
|
+
* });
|
|
223
|
+
*
|
|
224
|
+
* // Register on a hook system
|
|
225
|
+
* generateSlug.register(hooks);
|
|
226
|
+
* validateUniqueSlug.register(hooks);
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
declare function defineHook<T = AnyRecord>(options: DefineHookOptions<T>): DefineHookOptions<T> & {
|
|
230
|
+
register: (hooks: HookSystem) => () => void;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Create a before-create hook registration for a given hook system
|
|
234
|
+
*/
|
|
235
|
+
declare function beforeCreate<T = AnyRecord>(hooks: HookSystem, resource: string, handler: HookHandler<T>, priority?: number): () => void;
|
|
236
|
+
/**
|
|
237
|
+
* Create an after-create hook registration for a given hook system
|
|
238
|
+
*/
|
|
239
|
+
declare function afterCreate<T = AnyRecord>(hooks: HookSystem, resource: string, handler: HookHandler<T>, priority?: number): () => void;
|
|
240
|
+
/**
|
|
241
|
+
* Create a before-update hook registration for a given hook system
|
|
242
|
+
*/
|
|
243
|
+
declare function beforeUpdate<T = AnyRecord>(hooks: HookSystem, resource: string, handler: HookHandler<T>, priority?: number): () => void;
|
|
244
|
+
/**
|
|
245
|
+
* Create an after-update hook registration for a given hook system
|
|
246
|
+
*/
|
|
247
|
+
declare function afterUpdate<T = AnyRecord>(hooks: HookSystem, resource: string, handler: HookHandler<T>, priority?: number): () => void;
|
|
248
|
+
/**
|
|
249
|
+
* Create a before-delete hook registration for a given hook system
|
|
250
|
+
*/
|
|
251
|
+
declare function beforeDelete<T = AnyRecord>(hooks: HookSystem, resource: string, handler: HookHandler<T>, priority?: number): () => void;
|
|
252
|
+
/**
|
|
253
|
+
* Create an after-delete hook registration for a given hook system
|
|
254
|
+
*/
|
|
255
|
+
declare function afterDelete<T = AnyRecord>(hooks: HookSystem, resource: string, handler: HookHandler<T>, priority?: number): () => void;
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/pipeline/types.d.ts
|
|
258
|
+
/**
|
|
259
|
+
* Pipeline context passed to guards, transforms, and interceptors.
|
|
260
|
+
* Extends IRequestContext with pipeline-specific metadata.
|
|
261
|
+
*/
|
|
262
|
+
interface PipelineContext extends IRequestContext {
|
|
263
|
+
/** Resource name being accessed */
|
|
264
|
+
resource: string;
|
|
265
|
+
/** CRUD operation being performed */
|
|
266
|
+
operation: 'list' | 'get' | 'create' | 'update' | 'delete' | string;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Which operations a pipeline step applies to.
|
|
270
|
+
* If omitted, applies to ALL operations.
|
|
271
|
+
*/
|
|
272
|
+
type OperationFilter = Array<'list' | 'get' | 'create' | 'update' | 'delete' | string>;
|
|
273
|
+
/**
|
|
274
|
+
* Guard — boolean check that short-circuits on failure.
|
|
275
|
+
* Return true to proceed, throw to deny.
|
|
276
|
+
*/
|
|
277
|
+
interface Guard {
|
|
278
|
+
readonly _type: 'guard';
|
|
279
|
+
readonly name: string;
|
|
280
|
+
readonly operations?: OperationFilter;
|
|
281
|
+
handler(ctx: PipelineContext): boolean | Promise<boolean>;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Transform — modifies request data before the handler.
|
|
285
|
+
* Returns modified context (or mutates in place).
|
|
286
|
+
*/
|
|
287
|
+
interface Transform {
|
|
288
|
+
readonly _type: 'transform';
|
|
289
|
+
readonly name: string;
|
|
290
|
+
readonly operations?: OperationFilter;
|
|
291
|
+
handler(ctx: PipelineContext): PipelineContext | void | Promise<PipelineContext | void>;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Next function passed to interceptors — calls the handler (or next interceptor).
|
|
295
|
+
*/
|
|
296
|
+
type NextFunction = () => Promise<IControllerResponse<unknown>>;
|
|
297
|
+
/**
|
|
298
|
+
* Intercept — wraps handler execution (before + after pattern).
|
|
299
|
+
*/
|
|
300
|
+
interface Interceptor {
|
|
301
|
+
readonly _type: 'interceptor';
|
|
302
|
+
readonly name: string;
|
|
303
|
+
readonly operations?: OperationFilter;
|
|
304
|
+
handler(ctx: PipelineContext, next: NextFunction): Promise<IControllerResponse<unknown>>;
|
|
305
|
+
}
|
|
306
|
+
type PipelineStep = Guard | Transform | Interceptor;
|
|
307
|
+
/**
|
|
308
|
+
* Pipeline configuration — can be a flat array or per-operation map.
|
|
309
|
+
*/
|
|
310
|
+
type PipelineConfig = PipelineStep[] | {
|
|
311
|
+
list?: PipelineStep[];
|
|
312
|
+
get?: PipelineStep[];
|
|
313
|
+
create?: PipelineStep[];
|
|
314
|
+
update?: PipelineStep[];
|
|
315
|
+
delete?: PipelineStep[];
|
|
316
|
+
[operation: string]: PipelineStep[] | undefined;
|
|
317
|
+
};
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/types/repository.d.ts
|
|
320
|
+
/**
|
|
321
|
+
* Repository Interface - Database-Agnostic CRUD Operations
|
|
322
|
+
*
|
|
323
|
+
* This is the standard interface that all repositories must implement.
|
|
324
|
+
* MongoKit Repository already implements this interface.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```typescript
|
|
328
|
+
* import type { CrudRepository } from '@classytic/arc';
|
|
329
|
+
*
|
|
330
|
+
* // Your repository automatically satisfies this interface
|
|
331
|
+
* const userRepo: CrudRepository<UserDocument> = new Repository(UserModel);
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
/**
|
|
335
|
+
* Query options for read operations
|
|
336
|
+
*/
|
|
337
|
+
interface QueryOptions {
|
|
338
|
+
/** Transaction session — adapters handle the actual type (e.g., Mongoose ClientSession) */
|
|
339
|
+
session?: unknown;
|
|
340
|
+
/** Field selection - include or exclude fields */
|
|
341
|
+
select?: string | string[] | Record<string, 0 | 1>;
|
|
342
|
+
/** Relations to populate - string, array, or Mongoose populate options */
|
|
343
|
+
populate?: string | string[] | Record<string, unknown>;
|
|
344
|
+
/** Return plain JS objects instead of Mongoose documents */
|
|
345
|
+
lean?: boolean;
|
|
346
|
+
/** Allow additional adapter-specific options */
|
|
347
|
+
[key: string]: unknown;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Pagination parameters for list operations
|
|
351
|
+
*/
|
|
352
|
+
interface PaginationParams<TDoc = unknown> {
|
|
353
|
+
/** Filter criteria */
|
|
354
|
+
filters?: Partial<TDoc> & Record<string, unknown>;
|
|
355
|
+
/** Sort specification - string ("-createdAt") or object ({ createdAt: -1 }) */
|
|
356
|
+
sort?: string | Record<string, 1 | -1>;
|
|
357
|
+
/** Page number (1-indexed) */
|
|
358
|
+
page?: number;
|
|
359
|
+
/** Items per page */
|
|
360
|
+
limit?: number;
|
|
361
|
+
/** Allow additional options (select, populate, etc.) */
|
|
362
|
+
[key: string]: unknown;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Paginated result from list operations
|
|
366
|
+
*/
|
|
367
|
+
interface PaginatedResult<TDoc> {
|
|
368
|
+
/** Documents for current page */
|
|
369
|
+
docs: TDoc[];
|
|
370
|
+
/** Current page number */
|
|
371
|
+
page: number;
|
|
372
|
+
/** Items per page */
|
|
373
|
+
limit: number;
|
|
374
|
+
/** Total document count */
|
|
375
|
+
total: number;
|
|
376
|
+
/** Total page count */
|
|
377
|
+
pages: number;
|
|
378
|
+
/** Has next page */
|
|
379
|
+
hasNext: boolean;
|
|
380
|
+
/** Has previous page */
|
|
381
|
+
hasPrev: boolean;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Standard CRUD Repository Interface
|
|
385
|
+
*
|
|
386
|
+
* Defines the contract for data access operations.
|
|
387
|
+
* All database adapters (MongoKit, Prisma, etc.) implement this interface.
|
|
388
|
+
*
|
|
389
|
+
* @typeParam TDoc - The document/entity type
|
|
390
|
+
*/
|
|
391
|
+
interface CrudRepository<TDoc> {
|
|
392
|
+
/**
|
|
393
|
+
* Get paginated list of documents
|
|
394
|
+
*/
|
|
395
|
+
getAll(params?: PaginationParams<TDoc>, options?: QueryOptions): Promise<PaginatedResult<TDoc>>;
|
|
396
|
+
/**
|
|
397
|
+
* Get single document by ID
|
|
398
|
+
*/
|
|
399
|
+
getById(id: string, options?: QueryOptions): Promise<TDoc | null>;
|
|
400
|
+
/**
|
|
401
|
+
* Create new document
|
|
402
|
+
*/
|
|
403
|
+
create(data: Partial<TDoc>, options?: {
|
|
404
|
+
session?: unknown;
|
|
405
|
+
[key: string]: unknown;
|
|
406
|
+
}): Promise<TDoc>;
|
|
407
|
+
/**
|
|
408
|
+
* Update document by ID
|
|
409
|
+
*/
|
|
410
|
+
update(id: string, data: Partial<TDoc>, options?: QueryOptions): Promise<TDoc | null>;
|
|
411
|
+
/**
|
|
412
|
+
* Delete document by ID
|
|
413
|
+
*/
|
|
414
|
+
delete(id: string, options?: {
|
|
415
|
+
session?: unknown;
|
|
416
|
+
[key: string]: unknown;
|
|
417
|
+
}): Promise<{
|
|
418
|
+
success: boolean;
|
|
419
|
+
message: string;
|
|
420
|
+
}>;
|
|
421
|
+
/** Allow custom methods (getBySlug, getTree, restore, etc.) */
|
|
422
|
+
[key: string]: unknown;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Extract document type from a repository
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```typescript
|
|
429
|
+
* type UserDoc = InferDoc<typeof userRepository>;
|
|
430
|
+
* // UserDoc is now the document type of userRepository
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
type InferDoc<R> = R extends CrudRepository<infer T> ? T : never;
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/core/defineResource.d.ts
|
|
436
|
+
/**
|
|
437
|
+
* Define a resource with database adapter
|
|
438
|
+
*
|
|
439
|
+
* This is the MAIN entry point for creating Arc resources.
|
|
440
|
+
* The adapter provides both repository and schema metadata.
|
|
441
|
+
*/
|
|
442
|
+
declare function defineResource<TDoc = AnyRecord>(config: ResourceConfig<TDoc>): ResourceDefinition<TDoc>;
|
|
443
|
+
interface ResolvedResourceConfig<TDoc = AnyRecord> extends ResourceConfig<TDoc> {
|
|
444
|
+
_appliedPresets?: string[];
|
|
445
|
+
_controllerOptions?: {
|
|
446
|
+
slugField?: string;
|
|
447
|
+
parentField?: string;
|
|
448
|
+
[key: string]: unknown;
|
|
449
|
+
};
|
|
450
|
+
_pendingHooks?: Array<{
|
|
451
|
+
operation: "create" | "update" | "delete" | "read" | "list";
|
|
452
|
+
phase: "before" | "after";
|
|
453
|
+
handler: (ctx: AnyRecord) => unknown;
|
|
454
|
+
priority: number;
|
|
455
|
+
}>;
|
|
456
|
+
}
|
|
457
|
+
declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
458
|
+
readonly name: string;
|
|
459
|
+
readonly displayName: string;
|
|
460
|
+
readonly tag: string;
|
|
461
|
+
readonly prefix: string;
|
|
462
|
+
readonly adapter?: DataAdapter<TDoc>;
|
|
463
|
+
readonly controller?: IController<TDoc>;
|
|
464
|
+
readonly schemaOptions: RouteSchemaOptions;
|
|
465
|
+
readonly customSchemas: CrudSchemas;
|
|
466
|
+
readonly permissions: ResourcePermissions;
|
|
467
|
+
readonly additionalRoutes: AdditionalRoute[];
|
|
468
|
+
readonly middlewares: MiddlewareConfig;
|
|
469
|
+
readonly disableDefaultRoutes: boolean;
|
|
470
|
+
readonly disabledRoutes: CrudRouteKey[];
|
|
471
|
+
readonly events: Record<string, EventDefinition>;
|
|
472
|
+
readonly rateLimit?: RateLimitConfig | false;
|
|
473
|
+
readonly updateMethod?: "PUT" | "PATCH" | "both";
|
|
474
|
+
readonly pipe?: PipelineConfig;
|
|
475
|
+
readonly fields?: FieldPermissionMap;
|
|
476
|
+
readonly cache?: ResourceCacheConfig;
|
|
477
|
+
readonly _appliedPresets: string[];
|
|
478
|
+
_pendingHooks: Array<{
|
|
479
|
+
operation: "create" | "update" | "delete" | "read" | "list";
|
|
480
|
+
phase: "before" | "after";
|
|
481
|
+
handler: (ctx: AnyRecord) => unknown;
|
|
482
|
+
priority: number;
|
|
483
|
+
}>;
|
|
484
|
+
_registryMeta?: RegisterOptions;
|
|
485
|
+
constructor(config: ResolvedResourceConfig<TDoc>);
|
|
486
|
+
/** Get repository from adapter (if available) */
|
|
487
|
+
get repository(): RepositoryLike | CrudRepository<TDoc> | undefined;
|
|
488
|
+
_validateControllerMethods(): void;
|
|
489
|
+
toPlugin(): FastifyPluginAsync;
|
|
490
|
+
/**
|
|
491
|
+
* Get event definitions for registry
|
|
492
|
+
*/
|
|
493
|
+
getEvents(): Array<{
|
|
494
|
+
name: string;
|
|
495
|
+
module: string;
|
|
496
|
+
schema?: AnyRecord;
|
|
497
|
+
description?: string;
|
|
498
|
+
}>;
|
|
499
|
+
/**
|
|
500
|
+
* Get resource metadata
|
|
501
|
+
*/
|
|
502
|
+
getMetadata(): ResourceMetadata;
|
|
503
|
+
}
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region src/registry/ResourceRegistry.d.ts
|
|
506
|
+
interface RegisterOptions {
|
|
507
|
+
module?: string;
|
|
508
|
+
/** Pre-generated OpenAPI schemas */
|
|
509
|
+
openApiSchemas?: OpenApiSchemas;
|
|
510
|
+
}
|
|
511
|
+
declare class ResourceRegistry {
|
|
512
|
+
private _resources;
|
|
513
|
+
private _frozen;
|
|
514
|
+
constructor();
|
|
515
|
+
/**
|
|
516
|
+
* Register a resource
|
|
517
|
+
*/
|
|
518
|
+
register(resource: ResourceDefinition<unknown>, options?: RegisterOptions): this;
|
|
519
|
+
/**
|
|
520
|
+
* Get resource by name
|
|
521
|
+
*/
|
|
522
|
+
get(name: string): RegistryEntry | undefined;
|
|
523
|
+
/**
|
|
524
|
+
* Get all resources
|
|
525
|
+
*/
|
|
526
|
+
getAll(): RegistryEntry[];
|
|
527
|
+
/**
|
|
528
|
+
* Get resources by module
|
|
529
|
+
*/
|
|
530
|
+
getByModule(moduleName: string): RegistryEntry[];
|
|
531
|
+
/**
|
|
532
|
+
* Get resources by preset
|
|
533
|
+
*/
|
|
534
|
+
getByPreset(presetName: string): RegistryEntry[];
|
|
535
|
+
/**
|
|
536
|
+
* Check if resource exists
|
|
537
|
+
*/
|
|
538
|
+
has(name: string): boolean;
|
|
539
|
+
/**
|
|
540
|
+
* Get registry statistics
|
|
541
|
+
*/
|
|
542
|
+
getStats(): RegistryStats;
|
|
543
|
+
/**
|
|
544
|
+
* Get full introspection data
|
|
545
|
+
*/
|
|
546
|
+
getIntrospection(): IntrospectionData;
|
|
547
|
+
/**
|
|
548
|
+
* Freeze registry (prevent further registrations)
|
|
549
|
+
*/
|
|
550
|
+
freeze(): void;
|
|
551
|
+
/**
|
|
552
|
+
* Check if frozen
|
|
553
|
+
*/
|
|
554
|
+
isFrozen(): boolean;
|
|
555
|
+
/**
|
|
556
|
+
* Unfreeze registry (allow new registrations)
|
|
557
|
+
*/
|
|
558
|
+
unfreeze(): void;
|
|
559
|
+
/**
|
|
560
|
+
* Reset registry — clear all resources and unfreeze
|
|
561
|
+
*/
|
|
562
|
+
reset(): void;
|
|
563
|
+
/** @internal Alias for unfreeze() */
|
|
564
|
+
_unfreeze(): void;
|
|
565
|
+
/** @internal Alias for reset() */
|
|
566
|
+
_clear(): void;
|
|
567
|
+
/**
|
|
568
|
+
* Group by key
|
|
569
|
+
*/
|
|
570
|
+
private _groupBy;
|
|
571
|
+
}
|
|
572
|
+
//#endregion
|
|
573
|
+
//#region src/types/handlers.d.ts
|
|
574
|
+
/**
|
|
575
|
+
* Minimal server accessor — exposes safe, read-only server decorators.
|
|
576
|
+
* Allows controller handlers to publish events, log, and audit
|
|
577
|
+
* without switching to `wrapHandler: false`.
|
|
578
|
+
*/
|
|
579
|
+
interface ServerAccessor {
|
|
580
|
+
/** Event bus — publish domain events from any handler */
|
|
581
|
+
events?: {
|
|
582
|
+
publish: <T>(type: string, payload: T, meta?: Partial<Record<string, unknown>>) => Promise<void>;
|
|
583
|
+
};
|
|
584
|
+
/** Audit logger — log custom audit entries */
|
|
585
|
+
audit?: {
|
|
586
|
+
create: (resource: string, documentId: string, data: Record<string, unknown>, context?: Record<string, unknown>) => Promise<void>;
|
|
587
|
+
update: (resource: string, documentId: string, before: Record<string, unknown>, after: Record<string, unknown>, context?: Record<string, unknown>) => Promise<void>;
|
|
588
|
+
delete: (resource: string, documentId: string, data: Record<string, unknown>, context?: Record<string, unknown>) => Promise<void>;
|
|
589
|
+
custom: (resource: string, documentId: string, action: string, data?: Record<string, unknown>, context?: Record<string, unknown>) => Promise<void>;
|
|
590
|
+
};
|
|
591
|
+
/** Logger — structured logging */
|
|
592
|
+
log?: {
|
|
593
|
+
info: (...args: unknown[]) => void;
|
|
594
|
+
warn: (...args: unknown[]) => void;
|
|
595
|
+
error: (...args: unknown[]) => void;
|
|
596
|
+
debug: (...args: unknown[]) => void;
|
|
597
|
+
};
|
|
598
|
+
/** QueryCache — stale-while-revalidate data cache */
|
|
599
|
+
queryCache?: {
|
|
600
|
+
get: <T>(key: string) => Promise<{
|
|
601
|
+
data: T;
|
|
602
|
+
status: 'fresh' | 'stale' | 'miss';
|
|
603
|
+
}>;
|
|
604
|
+
set: <T>(key: string, data: T, config: {
|
|
605
|
+
staleTime?: number;
|
|
606
|
+
gcTime?: number;
|
|
607
|
+
tags?: string[];
|
|
608
|
+
}) => Promise<void>;
|
|
609
|
+
getResourceVersion: (resource: string) => Promise<number>;
|
|
610
|
+
bumpResourceVersion: (resource: string) => Promise<void>;
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Request context passed to controller handlers
|
|
615
|
+
*/
|
|
616
|
+
interface IRequestContext {
|
|
617
|
+
/** Route parameters (e.g., { id: '123' }) */
|
|
618
|
+
params: Record<string, string>;
|
|
619
|
+
/** Query string parameters */
|
|
620
|
+
query: Record<string, unknown>;
|
|
621
|
+
/** Request body */
|
|
622
|
+
body: unknown;
|
|
623
|
+
/** Authenticated user or null */
|
|
624
|
+
user: UserBase | null;
|
|
625
|
+
/** Request headers */
|
|
626
|
+
headers: Record<string, string | undefined>;
|
|
627
|
+
/** Organization ID (for multi-tenant apps) */
|
|
628
|
+
organizationId?: string;
|
|
629
|
+
/** Team ID (for team-scoped resources) */
|
|
630
|
+
teamId?: string;
|
|
631
|
+
/**
|
|
632
|
+
* Organization/auth context from middleware.
|
|
633
|
+
* Contains orgRoles, orgScope, organizationId, and any custom fields
|
|
634
|
+
* set by the auth adapter or org-scope plugin.
|
|
635
|
+
*
|
|
636
|
+
* @example
|
|
637
|
+
* ```typescript
|
|
638
|
+
* async create(req: IRequestContext) {
|
|
639
|
+
* const roles = req.context?.orgRoles ?? [];
|
|
640
|
+
* if (roles.includes('manager')) { ... }
|
|
641
|
+
* }
|
|
642
|
+
* ```
|
|
643
|
+
*/
|
|
644
|
+
context?: RequestContext;
|
|
645
|
+
/** Internal metadata (includes context + Arc internals like _policyFilters, log) */
|
|
646
|
+
metadata?: Record<string, unknown>;
|
|
647
|
+
/**
|
|
648
|
+
* Fastify server accessor — publish events, log, and audit
|
|
649
|
+
* from any handler without switching to `wrapHandler: false`.
|
|
650
|
+
*
|
|
651
|
+
* @example
|
|
652
|
+
* ```typescript
|
|
653
|
+
* async reschedule(req: IRequestContext) {
|
|
654
|
+
* const result = await repo.reschedule(req.params.id, req.body);
|
|
655
|
+
* await req.server?.events?.publish('interview.rescheduled', { data: result });
|
|
656
|
+
* return { success: true, data: result };
|
|
657
|
+
* }
|
|
658
|
+
* ```
|
|
659
|
+
*/
|
|
660
|
+
server?: ServerAccessor;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Standard response from controller handlers
|
|
664
|
+
*/
|
|
665
|
+
interface IControllerResponse<T = unknown> {
|
|
666
|
+
/** Operation success status */
|
|
667
|
+
success: boolean;
|
|
668
|
+
/** Response data */
|
|
669
|
+
data?: T;
|
|
670
|
+
/** Error message (when success is false) */
|
|
671
|
+
error?: string;
|
|
672
|
+
/** HTTP status code (default: 200 for success, 400 for error) */
|
|
673
|
+
status?: number;
|
|
674
|
+
/** Additional metadata */
|
|
675
|
+
meta?: Record<string, unknown>;
|
|
676
|
+
/** Error details (for debugging) */
|
|
677
|
+
details?: Record<string, unknown>;
|
|
678
|
+
/** Custom response headers (e.g., X-Total-Count, Link, ETag) */
|
|
679
|
+
headers?: Record<string, string>;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Controller handler - Arc's standard pattern
|
|
683
|
+
*
|
|
684
|
+
* Receives a request context object, returns IControllerResponse.
|
|
685
|
+
* Use with `wrapHandler: true` in additionalRoutes.
|
|
686
|
+
*
|
|
687
|
+
* @example
|
|
688
|
+
* ```typescript
|
|
689
|
+
* const createProduct: ControllerHandler<Product> = async (req) => {
|
|
690
|
+
* const product = await productRepo.create(req.body);
|
|
691
|
+
* return { success: true, data: product, status: 201 };
|
|
692
|
+
* };
|
|
693
|
+
*
|
|
694
|
+
* additionalRoutes: [{
|
|
695
|
+
* method: 'POST',
|
|
696
|
+
* path: '/products',
|
|
697
|
+
* handler: createProduct,
|
|
698
|
+
* permissions: requireAuth(),
|
|
699
|
+
* wrapHandler: true, // Arc wraps this into Fastify handler
|
|
700
|
+
* }]
|
|
701
|
+
* ```
|
|
702
|
+
*/
|
|
703
|
+
type ControllerHandler<T = unknown> = (req: IRequestContext) => Promise<IControllerResponse<T>>;
|
|
704
|
+
/**
|
|
705
|
+
* Fastify native handler
|
|
706
|
+
*
|
|
707
|
+
* Standard Fastify request/reply pattern.
|
|
708
|
+
* Use with `wrapHandler: false` in additionalRoutes.
|
|
709
|
+
*
|
|
710
|
+
* @example
|
|
711
|
+
* ```typescript
|
|
712
|
+
* const downloadFile: FastifyHandler = async (request, reply) => {
|
|
713
|
+
* const file = await getFile(request.params.id);
|
|
714
|
+
* reply.header('Content-Type', file.mimeType);
|
|
715
|
+
* return reply.send(file.buffer);
|
|
716
|
+
* };
|
|
717
|
+
*
|
|
718
|
+
* additionalRoutes: [{
|
|
719
|
+
* method: 'GET',
|
|
720
|
+
* path: '/files/:id/download',
|
|
721
|
+
* handler: downloadFile,
|
|
722
|
+
* permissions: requireAuth(),
|
|
723
|
+
* wrapHandler: false, // Use as-is, no wrapping
|
|
724
|
+
* }]
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
727
|
+
type FastifyHandler = (request: FastifyRequest, reply: FastifyReply) => Promise<void> | void;
|
|
728
|
+
/**
|
|
729
|
+
* Union type for route handlers
|
|
730
|
+
*/
|
|
731
|
+
type RouteHandler = ControllerHandler | FastifyHandler;
|
|
732
|
+
/**
|
|
733
|
+
* Controller interface for CRUD operations (strict)
|
|
734
|
+
*/
|
|
735
|
+
interface IController<TDoc = unknown> {
|
|
736
|
+
list(req: IRequestContext): Promise<IControllerResponse<{
|
|
737
|
+
docs: TDoc[];
|
|
738
|
+
total: number;
|
|
739
|
+
}>>;
|
|
740
|
+
get(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
741
|
+
create(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
742
|
+
update(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
743
|
+
delete(req: IRequestContext): Promise<IControllerResponse<{
|
|
744
|
+
message: string;
|
|
745
|
+
}>>;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Flexible controller interface - accepts controllers with any handler style
|
|
749
|
+
* Use this when your controller uses Fastify native handlers
|
|
750
|
+
*/
|
|
751
|
+
interface ControllerLike {
|
|
752
|
+
list?: unknown;
|
|
753
|
+
get?: unknown;
|
|
754
|
+
create?: unknown;
|
|
755
|
+
update?: unknown;
|
|
756
|
+
delete?: unknown;
|
|
757
|
+
[key: string]: unknown;
|
|
758
|
+
}
|
|
759
|
+
//#endregion
|
|
760
|
+
//#region src/core/AccessControl.d.ts
|
|
761
|
+
interface AccessControlConfig {
|
|
762
|
+
/** Field name used for multi-tenant scoping (default: 'organizationId') */
|
|
763
|
+
tenantField: string;
|
|
764
|
+
/** Primary key field name (default: '_id') */
|
|
765
|
+
idField: string;
|
|
766
|
+
/**
|
|
767
|
+
* Custom filter matching for policy enforcement.
|
|
768
|
+
* Provided by the DataAdapter for non-MongoDB databases (SQL, etc.).
|
|
769
|
+
* Falls back to built-in MongoDB-style matching if not provided.
|
|
770
|
+
*/
|
|
771
|
+
matchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;
|
|
772
|
+
}
|
|
773
|
+
/** Minimal repository interface for access-controlled fetch operations */
|
|
774
|
+
interface AccessControlRepository {
|
|
775
|
+
getById(id: string, options?: unknown): Promise<unknown>;
|
|
776
|
+
getOne?: (filter: AnyRecord, options?: unknown) => Promise<unknown>;
|
|
777
|
+
}
|
|
778
|
+
declare class AccessControl {
|
|
779
|
+
private readonly tenantField;
|
|
780
|
+
private readonly idField;
|
|
781
|
+
private readonly _adapterMatchesFilter?;
|
|
782
|
+
/** Patterns that indicate dangerous regex (nested quantifiers, excessive backtracking).
|
|
783
|
+
* Uses [^...] character classes instead of .+ to avoid backtracking in the detector itself. */
|
|
784
|
+
private static readonly DANGEROUS_REGEX;
|
|
785
|
+
/** Forbidden paths that could lead to prototype pollution */
|
|
786
|
+
private static readonly FORBIDDEN_PATHS;
|
|
787
|
+
constructor(config: AccessControlConfig);
|
|
788
|
+
/**
|
|
789
|
+
* Build filter for single-item operations (get/update/delete)
|
|
790
|
+
* Combines ID filter with policy/org filters for proper security enforcement
|
|
791
|
+
*/
|
|
792
|
+
buildIdFilter(id: string, req: IRequestContext): AnyRecord;
|
|
793
|
+
/**
|
|
794
|
+
* Check if item matches policy filters (for get/update/delete operations)
|
|
795
|
+
* Validates that fetched item satisfies all policy constraints
|
|
796
|
+
*
|
|
797
|
+
* Delegates to adapter-provided matchesFilter if available (for SQL, etc.),
|
|
798
|
+
* otherwise falls back to built-in MongoDB-style matching.
|
|
799
|
+
*/
|
|
800
|
+
checkPolicyFilters(item: AnyRecord, req: IRequestContext): boolean;
|
|
801
|
+
/**
|
|
802
|
+
* Check org/tenant scope for a document — uses configurable tenantField.
|
|
803
|
+
*
|
|
804
|
+
* SECURITY: When org scope is active (orgId present), documents that are
|
|
805
|
+
* missing the tenant field are DENIED by default. This prevents legacy or
|
|
806
|
+
* unscoped records from leaking across tenants.
|
|
807
|
+
*/
|
|
808
|
+
checkOrgScope(item: AnyRecord | null, arcContext: ArcInternalMetadata | RequestContext | undefined): boolean;
|
|
809
|
+
/** Check ownership for update/delete (ownedByUser preset) */
|
|
810
|
+
checkOwnership(item: AnyRecord | null, req: IRequestContext): boolean;
|
|
811
|
+
/**
|
|
812
|
+
* Fetch a single document with full access control enforcement.
|
|
813
|
+
* Combines compound DB filter (ID + org + policy) with post-hoc fallback.
|
|
814
|
+
*
|
|
815
|
+
* Takes repository as a parameter to avoid coupling.
|
|
816
|
+
*
|
|
817
|
+
* Replaces the duplicated pattern in get/update/delete:
|
|
818
|
+
* buildIdFilter -> getOne (or getById + checkOrgScope + checkPolicyFilters)
|
|
819
|
+
*/
|
|
820
|
+
fetchWithAccessControl<TDoc>(id: string, req: IRequestContext, repository: AccessControlRepository, queryOptions?: unknown): Promise<TDoc | null>;
|
|
821
|
+
/** Extract typed Arc internal metadata from request */
|
|
822
|
+
private _meta;
|
|
823
|
+
/**
|
|
824
|
+
* Check if a value matches a MongoDB query operator
|
|
825
|
+
*/
|
|
826
|
+
private matchesOperator;
|
|
827
|
+
/**
|
|
828
|
+
* Check if item matches a single filter condition
|
|
829
|
+
* Supports nested paths (e.g., "owner.id", "metadata.status")
|
|
830
|
+
*/
|
|
831
|
+
private matchesFilter;
|
|
832
|
+
/**
|
|
833
|
+
* Built-in MongoDB-style policy filter matching.
|
|
834
|
+
* Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $regex, $and, $or
|
|
835
|
+
*/
|
|
836
|
+
private defaultMatchesPolicyFilters;
|
|
837
|
+
/**
|
|
838
|
+
* Get nested value from object using dot notation (e.g., "owner.id")
|
|
839
|
+
* Security: Validates path against forbidden patterns to prevent prototype pollution
|
|
840
|
+
*/
|
|
841
|
+
private getNestedValue;
|
|
842
|
+
/**
|
|
843
|
+
* Create a safe RegExp from a string, guarding against ReDoS.
|
|
844
|
+
* Returns null if the pattern is invalid or dangerous.
|
|
845
|
+
*/
|
|
846
|
+
private static safeRegex;
|
|
847
|
+
}
|
|
848
|
+
//#endregion
|
|
849
|
+
//#region src/core/BodySanitizer.d.ts
|
|
850
|
+
interface BodySanitizerConfig {
|
|
851
|
+
/** Schema options for field sanitization */
|
|
852
|
+
schemaOptions: RouteSchemaOptions;
|
|
853
|
+
}
|
|
854
|
+
declare class BodySanitizer {
|
|
855
|
+
private schemaOptions;
|
|
856
|
+
constructor(config: BodySanitizerConfig);
|
|
857
|
+
/**
|
|
858
|
+
* Strip readonly and system-managed fields from request body.
|
|
859
|
+
* Prevents clients from overwriting _id, timestamps, __v, etc.
|
|
860
|
+
*
|
|
861
|
+
* Also applies field-level write permissions when the request has
|
|
862
|
+
* field permission metadata.
|
|
863
|
+
*/
|
|
864
|
+
sanitize(body: AnyRecord, _operation: 'create' | 'update', req?: IRequestContext, meta?: ArcInternalMetadata): AnyRecord;
|
|
865
|
+
}
|
|
866
|
+
//#endregion
|
|
867
|
+
//#region src/core/QueryResolver.d.ts
|
|
868
|
+
interface QueryResolverConfig {
|
|
869
|
+
/** Query parser instance (default: Arc built-in parser) */
|
|
870
|
+
queryParser?: QueryParserInterface;
|
|
871
|
+
/** Maximum limit for pagination (default: 100) */
|
|
872
|
+
maxLimit?: number;
|
|
873
|
+
/** Default limit for pagination (default: 20) */
|
|
874
|
+
defaultLimit?: number;
|
|
875
|
+
/** Default sort field (default: '-createdAt') */
|
|
876
|
+
defaultSort?: string;
|
|
877
|
+
/** Schema options for field sanitization */
|
|
878
|
+
schemaOptions?: RouteSchemaOptions;
|
|
879
|
+
/** Field name used for multi-tenant scoping (default: 'organizationId') */
|
|
880
|
+
tenantField?: string;
|
|
881
|
+
}
|
|
882
|
+
declare class QueryResolver {
|
|
883
|
+
private queryParser;
|
|
884
|
+
private maxLimit;
|
|
885
|
+
private defaultLimit;
|
|
886
|
+
private defaultSort;
|
|
887
|
+
private schemaOptions;
|
|
888
|
+
private tenantField;
|
|
889
|
+
constructor(config?: QueryResolverConfig);
|
|
890
|
+
/**
|
|
891
|
+
* Resolve a request into parsed query options -- ONE parse per request.
|
|
892
|
+
* Combines what was previously _buildContext + _parseQueryOptions + _applyFilters.
|
|
893
|
+
*/
|
|
894
|
+
resolve(req: IRequestContext, meta?: ArcInternalMetadata): ControllerQueryOptions;
|
|
895
|
+
/**
|
|
896
|
+
* Convert parsed select object to string format
|
|
897
|
+
* Converts { name: 1, email: 1, password: 0 } -> 'name email -password'
|
|
898
|
+
*/
|
|
899
|
+
private selectToString;
|
|
900
|
+
/** Sanitize select fields */
|
|
901
|
+
private sanitizeSelect;
|
|
902
|
+
/** Sanitize populate fields */
|
|
903
|
+
private sanitizePopulate;
|
|
904
|
+
/** Get blocked fields from schema options */
|
|
905
|
+
private getBlockedFields;
|
|
906
|
+
}
|
|
907
|
+
//#endregion
|
|
908
|
+
//#region src/core/BaseController.d.ts
|
|
909
|
+
interface BaseControllerOptions {
|
|
910
|
+
/** Schema options for field sanitization */
|
|
911
|
+
schemaOptions?: RouteSchemaOptions;
|
|
912
|
+
/**
|
|
913
|
+
* Query parser instance.
|
|
914
|
+
* Default: Arc built-in query parser (adapter-agnostic).
|
|
915
|
+
* Swap in MongoKit QueryParser, pgkit parser, etc.
|
|
916
|
+
*/
|
|
917
|
+
queryParser?: QueryParserInterface;
|
|
918
|
+
/** Maximum limit for pagination (default: 100) */
|
|
919
|
+
maxLimit?: number;
|
|
920
|
+
/** Default limit for pagination (default: 20) */
|
|
921
|
+
defaultLimit?: number;
|
|
922
|
+
/** Default sort field (default: '-createdAt') */
|
|
923
|
+
defaultSort?: string;
|
|
924
|
+
/** Resource name for hook execution (e.g., 'product' -> 'product.created') */
|
|
925
|
+
resourceName?: string;
|
|
926
|
+
/**
|
|
927
|
+
* Field name used for multi-tenant scoping (default: 'organizationId').
|
|
928
|
+
* Override to match your schema: 'workspaceId', 'tenantId', 'teamId', etc.
|
|
929
|
+
*/
|
|
930
|
+
tenantField?: string;
|
|
931
|
+
/**
|
|
932
|
+
* Primary key field name (default: '_id').
|
|
933
|
+
* Override for non-MongoDB adapters (e.g., 'id' for SQL databases).
|
|
934
|
+
*/
|
|
935
|
+
idField?: string;
|
|
936
|
+
/**
|
|
937
|
+
* Custom filter matching for policy enforcement.
|
|
938
|
+
* Provided by the DataAdapter for non-MongoDB databases (SQL, etc.).
|
|
939
|
+
* Falls back to built-in MongoDB-style matching if not provided.
|
|
940
|
+
*/
|
|
941
|
+
matchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;
|
|
942
|
+
/** Cache configuration for the resource */
|
|
943
|
+
cache?: ResourceCacheConfig;
|
|
944
|
+
/** Internal preset fields map (slug, tree, etc.) */
|
|
945
|
+
presetFields?: {
|
|
946
|
+
slugField?: string;
|
|
947
|
+
parentField?: string;
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Framework-agnostic base controller implementing IController.
|
|
952
|
+
*
|
|
953
|
+
* Composes AccessControl, BodySanitizer, and QueryResolver for clean
|
|
954
|
+
* separation of concerns. CRUD methods delegate directly to these
|
|
955
|
+
* composed classes — no intermediate wrapper methods.
|
|
956
|
+
*
|
|
957
|
+
* @template TDoc - The document type
|
|
958
|
+
* @template TRepository - The repository type (defaults to RepositoryLike)
|
|
959
|
+
*/
|
|
960
|
+
declare class BaseController<TDoc = AnyRecord, TRepository extends RepositoryLike = RepositoryLike> implements IController<TDoc> {
|
|
961
|
+
protected repository: TRepository;
|
|
962
|
+
protected schemaOptions: RouteSchemaOptions;
|
|
963
|
+
protected queryParser: QueryParserInterface;
|
|
964
|
+
protected maxLimit: number;
|
|
965
|
+
protected defaultLimit: number;
|
|
966
|
+
protected defaultSort: string;
|
|
967
|
+
protected resourceName?: string;
|
|
968
|
+
protected tenantField: string;
|
|
969
|
+
protected idField: string;
|
|
970
|
+
/** Composable access control (ID filtering, policy checks, org scope, ownership) */
|
|
971
|
+
readonly accessControl: AccessControl;
|
|
972
|
+
/** Composable body sanitization (field permissions, system fields) */
|
|
973
|
+
readonly bodySanitizer: BodySanitizer;
|
|
974
|
+
/** Composable query resolution (parsing, pagination, sort, select/populate) */
|
|
975
|
+
readonly queryResolver: QueryResolver;
|
|
976
|
+
private _matchesFilter?;
|
|
977
|
+
private _presetFields;
|
|
978
|
+
private _cacheConfig?;
|
|
979
|
+
constructor(repository: TRepository, options?: BaseControllerOptions);
|
|
980
|
+
/** Extract typed Arc internal metadata from request */
|
|
981
|
+
private meta;
|
|
982
|
+
/** Get hook system from request context (instance-scoped) */
|
|
983
|
+
private getHooks;
|
|
984
|
+
/** Resolve cache config for a specific operation, merging per-op overrides */
|
|
985
|
+
private resolveCacheConfig;
|
|
986
|
+
/** Extract user/org IDs from request for cache key scoping */
|
|
987
|
+
private cacheScope;
|
|
988
|
+
list(req: IRequestContext): Promise<IControllerResponse<PaginatedResult<TDoc>>>;
|
|
989
|
+
/** Execute list query through hooks (extracted for cache revalidation) */
|
|
990
|
+
private executeListQuery;
|
|
991
|
+
get(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
992
|
+
/** Execute get query through hooks (extracted for cache revalidation) */
|
|
993
|
+
private executeGetQuery;
|
|
994
|
+
create(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
995
|
+
update(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
996
|
+
delete(req: IRequestContext): Promise<IControllerResponse<{
|
|
997
|
+
message: string;
|
|
998
|
+
}>>;
|
|
999
|
+
getBySlug(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
1000
|
+
getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginatedResult<TDoc>>>;
|
|
1001
|
+
restore(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
1002
|
+
getTree(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
|
|
1003
|
+
getChildren(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
|
|
1004
|
+
}
|
|
1005
|
+
//#endregion
|
|
1006
|
+
//#region src/adapters/interface.d.ts
|
|
1007
|
+
/**
|
|
1008
|
+
* Minimal repository interface for flexible adapter compatibility.
|
|
1009
|
+
* Any repository with these method signatures is accepted — no `as any` needed.
|
|
1010
|
+
*
|
|
1011
|
+
* CrudRepository<TDoc> and MongoKit Repository both satisfy this interface.
|
|
1012
|
+
*/
|
|
1013
|
+
interface RepositoryLike {
|
|
1014
|
+
getAll(params?: unknown): Promise<unknown>;
|
|
1015
|
+
getById(id: string, options?: unknown): Promise<unknown>;
|
|
1016
|
+
create(data: unknown, options?: unknown): Promise<unknown>;
|
|
1017
|
+
update(id: string, data: unknown, options?: unknown): Promise<unknown>;
|
|
1018
|
+
delete(id: string, options?: unknown): Promise<unknown>;
|
|
1019
|
+
[key: string]: unknown;
|
|
1020
|
+
}
|
|
1021
|
+
interface DataAdapter<TDoc = unknown> {
|
|
1022
|
+
/**
|
|
1023
|
+
* Repository implementing CRUD operations
|
|
1024
|
+
* Accepts CrudRepository, MongoKit Repository, or any compatible object
|
|
1025
|
+
*/
|
|
1026
|
+
repository: CrudRepository<TDoc> | RepositoryLike;
|
|
1027
|
+
/** Adapter identifier for introspection */
|
|
1028
|
+
readonly type: 'mongoose' | 'prisma' | 'drizzle' | 'typeorm' | 'custom';
|
|
1029
|
+
/** Human-readable name */
|
|
1030
|
+
readonly name: string;
|
|
1031
|
+
/**
|
|
1032
|
+
* Generate OpenAPI schemas for CRUD operations
|
|
1033
|
+
*
|
|
1034
|
+
* This method allows each adapter to generate schemas specific to its ORM/database.
|
|
1035
|
+
* For example, Mongoose adapter can use mongokit to generate schemas from Mongoose models.
|
|
1036
|
+
*
|
|
1037
|
+
* @param options - Schema generation options (field rules, populate settings, etc.)
|
|
1038
|
+
* @returns OpenAPI schemas for CRUD operations or null if not supported
|
|
1039
|
+
*/
|
|
1040
|
+
generateSchemas?(options?: RouteSchemaOptions): OpenApiSchemas | null;
|
|
1041
|
+
/** Extract schema metadata for OpenAPI/introspection */
|
|
1042
|
+
getSchemaMetadata?(): SchemaMetadata | null;
|
|
1043
|
+
/** Validate data against schema before persistence */
|
|
1044
|
+
validate?(data: unknown): Promise<ValidationResult> | ValidationResult;
|
|
1045
|
+
/** Health check for database connection */
|
|
1046
|
+
healthCheck?(): Promise<boolean>;
|
|
1047
|
+
/**
|
|
1048
|
+
* Custom filter matching for policy enforcement.
|
|
1049
|
+
* Falls back to built-in MongoDB-style matching if not provided.
|
|
1050
|
+
* Override this for SQL adapters or non-MongoDB query operators.
|
|
1051
|
+
*/
|
|
1052
|
+
matchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;
|
|
1053
|
+
/** Close/cleanup resources */
|
|
1054
|
+
close?(): Promise<void>;
|
|
1055
|
+
}
|
|
1056
|
+
interface SchemaMetadata {
|
|
1057
|
+
name: string;
|
|
1058
|
+
fields: Record<string, FieldMetadata>;
|
|
1059
|
+
indexes?: Array<{
|
|
1060
|
+
fields: string[];
|
|
1061
|
+
unique?: boolean;
|
|
1062
|
+
sparse?: boolean;
|
|
1063
|
+
}>;
|
|
1064
|
+
relations?: Record<string, RelationMetadata>;
|
|
1065
|
+
}
|
|
1066
|
+
interface FieldMetadata {
|
|
1067
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'object' | 'array' | 'objectId' | 'enum';
|
|
1068
|
+
required?: boolean;
|
|
1069
|
+
unique?: boolean;
|
|
1070
|
+
default?: unknown;
|
|
1071
|
+
enum?: Array<string | number>;
|
|
1072
|
+
min?: number;
|
|
1073
|
+
max?: number;
|
|
1074
|
+
minLength?: number;
|
|
1075
|
+
maxLength?: number;
|
|
1076
|
+
pattern?: string;
|
|
1077
|
+
description?: string;
|
|
1078
|
+
ref?: string;
|
|
1079
|
+
array?: boolean;
|
|
1080
|
+
}
|
|
1081
|
+
interface RelationMetadata {
|
|
1082
|
+
type: 'one-to-one' | 'one-to-many' | 'many-to-many';
|
|
1083
|
+
target: string;
|
|
1084
|
+
foreignKey?: string;
|
|
1085
|
+
through?: string;
|
|
1086
|
+
}
|
|
1087
|
+
interface ValidationResult {
|
|
1088
|
+
valid: boolean;
|
|
1089
|
+
errors?: Array<{
|
|
1090
|
+
field: string;
|
|
1091
|
+
message: string;
|
|
1092
|
+
code?: string;
|
|
1093
|
+
}>;
|
|
1094
|
+
}
|
|
1095
|
+
type AdapterFactory<TDoc> = (config: unknown) => DataAdapter<TDoc>;
|
|
1096
|
+
//#endregion
|
|
1097
|
+
export { beforeUpdate as $, PaginationParams as A, DefineHookOptions as B, RegisterOptions as C, CrudRepository as D, defineResource as E, OperationFilter as F, HookRegistration as G, HookHandler as H, PipelineConfig as I, afterCreate as J, HookSystem as K, PipelineContext as L, Guard as M, Interceptor as N, InferDoc as O, NextFunction as P, beforeDelete as Q, PipelineStep as R, RouteHandler as S, ResourceDefinition as T, HookOperation as U, HookContext as V, HookPhase as W, afterUpdate as X, afterDelete as Y, beforeCreate as Z, ControllerLike as _, RepositoryLike as a, IControllerResponse as b, BaseController as c, QueryResolverConfig as d, createHookSystem as et, BodySanitizer as f, ControllerHandler as g, AccessControlConfig as h, RelationMetadata as i, QueryOptions as j, PaginatedResult as k, BaseControllerOptions as l, AccessControl as m, DataAdapter as n, SchemaMetadata as o, BodySanitizerConfig as p, HookSystemOptions as q, FieldMetadata as r, ValidationResult as s, AdapterFactory as t, defineHook as tt, QueryResolver as u, FastifyHandler as v, ResourceRegistry as w, IRequestContext as x, IController as y, Transform as z };
|
|
1098
|
+
//# sourceMappingURL=interface-Ch8HU9uM.d.mts.map
|