@classytic/arc 2.11.4 → 2.14.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.
- package/README.md +16 -12
- package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
- package/dist/EventTransport-CT_52aWU.d.mts +34 -0
- package/dist/EventTransport-DLWoUMHy.mjs +103 -0
- package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/audit.d.mts +199 -0
- package/dist/auth/audit.mjs +288 -0
- package/dist/auth/index.d.mts +3 -3
- package/dist/auth/index.mjs +117 -191
- package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
- package/dist/buildHandler-olo-gt94.mjs +610 -0
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/describe.d.mts +89 -13
- package/dist/cli/commands/describe.mjs +56 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +147 -48
- package/dist/cli/commands/init.d.mts +13 -0
- package/dist/cli/commands/init.mjs +130 -87
- package/dist/cli/commands/introspect.mjs +8 -1
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/core-DECn6zaU.mjs +1399 -0
- package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CBxLLbn3.mjs} +7 -20
- package/dist/createAggregationRouter-CRIBv4sC.mjs +114 -0
- package/dist/{createApp-C9bRrqlX.mjs → createApp-XX2-N0Yd.mjs} +28 -22
- package/dist/{defineEvent-D1Ky9M1D.mjs → defineEvent-D5h7EvAx.mjs} +1 -1
- package/dist/docs/index.d.mts +24 -11
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
- package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
- package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
- package/dist/errors-j4aJm1Wg.mjs +184 -0
- package/dist/{eventPlugin-Cts2-Tfj.mjs → eventPlugin-CaKTYkYM.mjs} +28 -4
- package/dist/{eventPlugin-DDJoNEPL.d.mts → eventPlugin-qXpqTebY.d.mts} +24 -1
- package/dist/events/index.d.mts +6 -6
- package/dist/events/index.mjs +11 -35
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-BRjxOAFp.d.mts → fields-COhcH3fk.d.mts} +23 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +1 -20
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-rHjXmJar.d.mts → index-BTqLEvhu.d.mts} +163 -3
- package/dist/{index-CXXRbnf8.d.mts → index-BtW7qYwa.d.mts} +660 -326
- package/dist/{index-m8mOOlFW.d.mts → index-Ds61mrJE.d.mts} +50 -4
- package/dist/{index-D9t1KNaB.d.mts → index-Dz5IKsrE.d.mts} +360 -219
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +9 -10
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +60 -11
- package/dist/integrations/streamline.mjs +75 -85
- package/dist/integrations/websocket.mjs +2 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +2 -2
- package/dist/migrations/index.d.mts +23 -3
- package/dist/migrations/index.mjs +0 -7
- package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
- package/dist/openapi-noXno2CV.mjs +968 -0
- package/dist/org/index.d.mts +2 -2
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
- package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +16 -31
- package/dist/plugins/index.mjs +33 -13
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +6 -9
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +6 -8
- package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
- package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
- package/dist/{redis-stream-xTGxB2bm.d.mts → redis-stream-D6HzR1Z_.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
- package/dist/{resourceToTools-CxNmI6xF.mjs → resourceToTools-DLL32us3.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-DrOa-26E.mjs} +41 -36
- package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-lYhC2gE5.mjs} +1 -1
- package/dist/schemas/index.d.mts +100 -30
- package/dist/schemas/index.mjs +86 -29
- package/dist/scim/index.d.mts +264 -0
- package/dist/scim/index.mjs +963 -0
- package/dist/scope/index.d.mts +3 -3
- package/dist/scope/index.mjs +4 -4
- package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
- package/dist/{store-helpers-Cp4uKC1U.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -8
- package/dist/testing/index.mjs +16 -24
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-D7KpfiL1.d.mts → types-BvqwCCSx.d.mts} +73 -25
- package/dist/{types-DDyTPc6y.d.mts → types-CTYvcwHe.d.mts} +195 -1
- package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
- package/dist/{types-BQ9TJQNy.d.mts → types-DQHFc8PM.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +5 -5
- package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
- package/dist/{versioning-DsglKfM_.d.mts → versioning-DTTvc80y.d.mts} +1 -1
- package/package.json +24 -34
- package/skills/arc/SKILL.md +147 -51
- package/skills/arc/references/agent-auth.md +238 -0
- package/skills/arc/references/api-reference.md +187 -0
- package/skills/arc/references/auth.md +354 -7
- package/skills/arc/references/enterprise-auth.md +94 -0
- package/skills/arc/references/events.md +8 -6
- package/skills/arc/references/mcp.md +2 -2
- package/skills/arc/references/multi-tenancy.md +11 -2
- package/skills/arc/references/production.md +10 -9
- package/skills/arc/references/scim.md +247 -0
- package/skills/arc/references/testing.md +1 -1
- package/skills/arc-code-review/SKILL.md +141 -0
- package/skills/arc-code-review/references/anti-patterns.md +911 -0
- package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
- package/skills/arc-code-review/references/migration-recipes.md +700 -0
- package/skills/arc-code-review/references/mongokit-migration.md +386 -0
- package/skills/arc-code-review/references/scaffolding.md +230 -0
- package/skills/arc-code-review/references/severity.md +127 -0
- package/dist/EventTransport-BFQjw9pB.mjs +0 -133
- package/dist/EventTransport-CYNUXdCJ.d.mts +0 -293
- package/dist/adapters/index.d.mts +0 -3
- package/dist/adapters/index.mjs +0 -2
- package/dist/adapters-DUUiiimH.mjs +0 -964
- package/dist/auth/mongoose.d.mts +0 -191
- package/dist/auth/mongoose.mjs +0 -73
- package/dist/core-CbcQRIch.mjs +0 -1054
- package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
- package/dist/errorHandler-DEWmGWPz.d.mts +0 -114
- package/dist/errors-D5c-5BJL.mjs +0 -232
- package/dist/index-Rg8axYPz.d.mts +0 -370
- package/dist/openapi-D7G1V7ex.mjs +0 -557
- /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
- /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
- /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
- /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
- /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
- /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
- /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
- /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
- /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
- /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
- /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
- /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
- /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
- /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
|
@@ -1,198 +1,15 @@
|
|
|
1
1
|
import { a as QueryCacheConfig } from "./QueryCache-D41bfdBB.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { c as PermissionCheck, d as UserBase, n as FieldPermissionMap } from "./fields-
|
|
4
|
-
import { n as DomainEvent } from "./EventTransport-
|
|
5
|
-
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
|
|
2
|
+
import { i as RequestScope } from "./types-CTYvcwHe.mjs";
|
|
3
|
+
import { c as PermissionCheck, d as UserBase, n as FieldPermissionMap } from "./fields-COhcH3fk.mjs";
|
|
4
|
+
import { n as DomainEvent } from "./EventTransport-CT_52aWU.mjs";
|
|
6
5
|
import { KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
|
|
7
|
-
import {
|
|
6
|
+
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
|
|
7
|
+
import * as _$_classytic_repo_core_adapter0 from "@classytic/repo-core/adapter";
|
|
8
|
+
import { DataAdapter, RepositoryLike } from "@classytic/repo-core/adapter";
|
|
9
|
+
import { AggDateBucket, AggMeasure, AggTopN, QueryOptions, StandardRepo } from "@classytic/repo-core/repository";
|
|
10
|
+
import { LookupSpec } from "@classytic/repo-core/lookup";
|
|
8
11
|
import { FieldRule, SchemaBuilderOptions } from "@classytic/repo-core/schema";
|
|
9
12
|
|
|
10
|
-
//#region src/adapters/interface.d.ts
|
|
11
|
-
/**
|
|
12
|
-
* Arc's structural repository contract.
|
|
13
|
-
*
|
|
14
|
-
* Defined as `MinimalRepo<TDoc> & Partial<StandardRepo<TDoc>>` — the
|
|
15
|
-
* repo-core 5-method floor (required) plus every other `StandardRepo`
|
|
16
|
-
* method (optional). This compound is the single shape arc accepts
|
|
17
|
-
* across its entire API surface:
|
|
18
|
-
*
|
|
19
|
-
* - `defineResource({ adapter: { repository, ... } })`
|
|
20
|
-
* - `auditPlugin({ repository })`, `idempotencyPlugin({ repository })`
|
|
21
|
-
* - `new EventOutbox({ repository, transport })`
|
|
22
|
-
*
|
|
23
|
-
* **Why compound and not `StandardRepo` alone:** forcing every kit to
|
|
24
|
-
* implement the full `StandardRepo` surface would break kits with
|
|
25
|
-
* partial capabilities (sqlitekit has no aggregation, prismakit has no
|
|
26
|
-
* native atomic CAS the same way). Feature-detection at the arc layer
|
|
27
|
-
* lets each kit declare only what it implements; arc's audit / outbox /
|
|
28
|
-
* idempotency plugins check `typeof repo.method === 'function'` at
|
|
29
|
-
* construction and throw with the list of missing primitives if a
|
|
30
|
-
* required subset isn't covered. See the store-backing contract matrix
|
|
31
|
-
* in the file header.
|
|
32
|
-
*
|
|
33
|
-
* **Why compound and not `MinimalRepo` alone:** arc's internal plugins
|
|
34
|
-
* still need the `StandardRepo` type info at call sites where they use
|
|
35
|
-
* optionals like `findOneAndUpdate` / `deleteMany`. Without the
|
|
36
|
-
* `Partial<StandardRepo>` half, every access would require
|
|
37
|
-
* `as StandardRepo` casts and the feature-detect pattern would be
|
|
38
|
-
* runtime-only with no type-level backing.
|
|
39
|
-
*
|
|
40
|
-
* **Hosts importing repo-core directly.** `MinimalRepo` and
|
|
41
|
-
* `StandardRepo` are repo-core's contract — hosts that want to reference
|
|
42
|
-
* them by name should `import type { MinimalRepo, StandardRepo } from
|
|
43
|
-
* '@classytic/repo-core/repository'` rather than go through arc. Arc
|
|
44
|
-
* doesn't re-export them from its root barrel on purpose: creating a
|
|
45
|
-
* second source of truth would force arc to either drift from repo-core
|
|
46
|
-
* or force-sync every time the contract iterates.
|
|
47
|
-
*
|
|
48
|
-
* ```ts
|
|
49
|
-
* const adapter: DataAdapter<Product> = {
|
|
50
|
-
* repository: myRepo, // any MinimalRepo<Product> — kit-agnostic
|
|
51
|
-
* type: 'drizzle', // or 'mongoose' | 'prisma' | 'custom'
|
|
52
|
-
* name: 'products',
|
|
53
|
-
* };
|
|
54
|
-
* defineResource({ adapter, ... });
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
type RepositoryLike<TDoc = unknown> = MinimalRepo<TDoc> & Partial<StandardRepo<TDoc>>;
|
|
58
|
-
/**
|
|
59
|
-
* Permissive structural input accepted at every adapter factory boundary.
|
|
60
|
-
*
|
|
61
|
-
* Wider than `RepositoryLike<TDoc>` on `getAll`'s `params`/`options` —
|
|
62
|
-
* uses method-shorthand syntax with `unknown` so kit-native repositories
|
|
63
|
-
* (mongokit's `Repository<TDoc>`, sqlitekit, prismakit) plug in directly,
|
|
64
|
-
* without `as RepositoryLike<TDoc>` casts on the host.
|
|
65
|
-
*
|
|
66
|
-
* **Why this exists.** repo-core 0.2 widened `MinimalRepo['getAll']`'s
|
|
67
|
-
* `params.filters` to a `Filter | Record<string, unknown>` IR union, but
|
|
68
|
-
* concrete kit `Repository` classes still type `filters` as the narrower
|
|
69
|
-
* `Record<string, unknown>`. Under `strictFunctionTypes` the kit's narrower
|
|
70
|
-
* function-property `getAll` is no longer assignable to the IR-aware one,
|
|
71
|
-
* which forced every host adapter glue file to write
|
|
72
|
-
* `repository as unknown as RepositoryLike<TDoc>`.
|
|
73
|
-
*
|
|
74
|
-
* Adapter factories accept this permissive shape, then call
|
|
75
|
-
* `asRepositoryLike()` once to widen for arc internals (audit, outbox,
|
|
76
|
-
* idempotency stores still see the strict `RepositoryLike` view). The
|
|
77
|
-
* documented escape hatch lives in arc, not at every host call site.
|
|
78
|
-
*/
|
|
79
|
-
interface AdapterRepositoryInput<TDoc = unknown> {
|
|
80
|
-
readonly idField?: string;
|
|
81
|
-
getAll(params?: unknown, options?: unknown): Promise<unknown>;
|
|
82
|
-
getById(id: string, options?: unknown): Promise<TDoc | null>;
|
|
83
|
-
create(data: Partial<TDoc>, options?: unknown): Promise<TDoc>;
|
|
84
|
-
update(id: string, data: Partial<TDoc>, options?: unknown): Promise<TDoc | null>;
|
|
85
|
-
delete(id: string, options?: unknown): Promise<{
|
|
86
|
-
success: boolean;
|
|
87
|
-
message: string;
|
|
88
|
-
id?: string;
|
|
89
|
-
soft?: boolean;
|
|
90
|
-
count?: number;
|
|
91
|
-
}>;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Widen a permissive `AdapterRepositoryInput<TDoc>` to arc's strict
|
|
95
|
-
* `RepositoryLike<TDoc>` view. Single-source escape hatch for the
|
|
96
|
-
* filter-IR drift documented on `AdapterRepositoryInput`.
|
|
97
|
-
*
|
|
98
|
-
* Arc internals (audit / outbox / idempotency, BaseController) still see
|
|
99
|
-
* the IR-aware `RepositoryLike`; only the call paths arc exercises are
|
|
100
|
-
* shared between the two views, and those use the narrower
|
|
101
|
-
* `Record<string, unknown>` filter shape both sides agree on.
|
|
102
|
-
*/
|
|
103
|
-
declare function asRepositoryLike<TDoc = unknown>(input: AdapterRepositoryInput<TDoc>): RepositoryLike<TDoc>;
|
|
104
|
-
interface DataAdapter<TDoc = unknown> {
|
|
105
|
-
/**
|
|
106
|
-
* Repository implementing CRUD operations. Any value that satisfies
|
|
107
|
-
* `RepositoryLike<TDoc>` — which includes `StandardRepo<TDoc>` (all
|
|
108
|
-
* methods implemented), `MinimalRepo<TDoc>` (5-method floor), or
|
|
109
|
-
* anything in between a kit declares. Arc feature-detects optional
|
|
110
|
-
* methods at runtime — kits only declare what they support.
|
|
111
|
-
*/
|
|
112
|
-
repository: RepositoryLike<TDoc>;
|
|
113
|
-
/** Adapter identifier for introspection */
|
|
114
|
-
readonly type: "mongoose" | "prisma" | "drizzle" | "typeorm" | "custom";
|
|
115
|
-
/** Human-readable name */
|
|
116
|
-
readonly name: string;
|
|
117
|
-
/**
|
|
118
|
-
* Generate OpenAPI schemas for CRUD operations. Each adapter produces
|
|
119
|
-
* schemas appropriate to its ORM/database (mongoose kits use mongokit's
|
|
120
|
-
* `buildCrudSchemasFromModel`; SQL kits introspect columns).
|
|
121
|
-
*
|
|
122
|
-
* @param options - Schema generation options (field rules, populate hints)
|
|
123
|
-
* @param context - Resource-level context (idField for params schema, name for logs).
|
|
124
|
-
* Adapters should honor `context.idField` when producing the params
|
|
125
|
-
* schema (e.g., skip the ObjectId pattern when idField is a custom
|
|
126
|
-
* string field).
|
|
127
|
-
*/
|
|
128
|
-
generateSchemas?(options?: RouteSchemaOptions, context?: AdapterSchemaContext): OpenApiSchemas | Record<string, unknown> | null;
|
|
129
|
-
/** Extract schema metadata for OpenAPI/introspection. */
|
|
130
|
-
getSchemaMetadata?(): SchemaMetadata | null;
|
|
131
|
-
/** Validate data against schema before persistence. */
|
|
132
|
-
validate?(data: unknown): Promise<ValidationResult$1> | ValidationResult$1;
|
|
133
|
-
/** Health check for database connection. */
|
|
134
|
-
healthCheck?(): Promise<boolean>;
|
|
135
|
-
/**
|
|
136
|
-
* Custom filter matching for in-memory policy enforcement. Falls back
|
|
137
|
-
* to arc's built-in shallow matcher when omitted. Override for SQL
|
|
138
|
-
* adapters, non-Mongo operators, or kits that compile Filter IR.
|
|
139
|
-
*/
|
|
140
|
-
matchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;
|
|
141
|
-
/** Close / cleanup resources. */
|
|
142
|
-
close?(): Promise<void>;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Context passed to `adapter.generateSchemas()` so adapters shape output
|
|
146
|
-
* to match resource-level configuration. All fields optional — adapters
|
|
147
|
-
* that ignore this still work; arc applies safety-net normalization.
|
|
148
|
-
*/
|
|
149
|
-
interface AdapterSchemaContext {
|
|
150
|
-
/** The idField configured on the resource. Defaults to "_id". */
|
|
151
|
-
idField?: string;
|
|
152
|
-
/** Resource name (for error messages / logging). */
|
|
153
|
-
resourceName?: string;
|
|
154
|
-
}
|
|
155
|
-
interface SchemaMetadata {
|
|
156
|
-
name: string;
|
|
157
|
-
fields: Record<string, FieldMetadata>;
|
|
158
|
-
indexes?: Array<{
|
|
159
|
-
fields: string[];
|
|
160
|
-
unique?: boolean;
|
|
161
|
-
sparse?: boolean;
|
|
162
|
-
}>;
|
|
163
|
-
relations?: Record<string, RelationMetadata>;
|
|
164
|
-
}
|
|
165
|
-
interface FieldMetadata {
|
|
166
|
-
type: "string" | "number" | "boolean" | "date" | "object" | "array" | "objectId" | "enum";
|
|
167
|
-
required?: boolean;
|
|
168
|
-
unique?: boolean;
|
|
169
|
-
default?: unknown;
|
|
170
|
-
enum?: Array<string | number>;
|
|
171
|
-
min?: number;
|
|
172
|
-
max?: number;
|
|
173
|
-
minLength?: number;
|
|
174
|
-
maxLength?: number;
|
|
175
|
-
pattern?: string;
|
|
176
|
-
description?: string;
|
|
177
|
-
ref?: string;
|
|
178
|
-
array?: boolean;
|
|
179
|
-
}
|
|
180
|
-
interface RelationMetadata {
|
|
181
|
-
type: "one-to-one" | "one-to-many" | "many-to-many";
|
|
182
|
-
target: string;
|
|
183
|
-
foreignKey?: string;
|
|
184
|
-
through?: string;
|
|
185
|
-
}
|
|
186
|
-
interface ValidationResult$1 {
|
|
187
|
-
valid: boolean;
|
|
188
|
-
errors?: Array<{
|
|
189
|
-
field: string;
|
|
190
|
-
message: string;
|
|
191
|
-
code?: string;
|
|
192
|
-
}>;
|
|
193
|
-
}
|
|
194
|
-
type AdapterFactory<TDoc> = (config: unknown) => DataAdapter<TDoc>;
|
|
195
|
-
//#endregion
|
|
196
13
|
//#region src/hooks/HookSystem.d.ts
|
|
197
14
|
type HookPhase = "before" | "around" | "after";
|
|
198
15
|
type HookOperation = "create" | "update" | "delete" | "restore" | "read" | "list";
|
|
@@ -605,6 +422,67 @@ declare class BodySanitizer {
|
|
|
605
422
|
sanitize(body: AnyRecord, _operation: "create" | "update", req?: IRequestContext, meta?: ArcInternalMetadata): AnyRecord;
|
|
606
423
|
}
|
|
607
424
|
//#endregion
|
|
425
|
+
//#region src/core/controllerTypes.d.ts
|
|
426
|
+
/**
|
|
427
|
+
* Union of every return shape repo-core's `MinimalRepo.getAll()` is
|
|
428
|
+
* contractually allowed to produce. See repo-core's `MinimalRepo.getAll`
|
|
429
|
+
* docstring for the three-way split:
|
|
430
|
+
*
|
|
431
|
+
* - `OffsetPaginationResult<TDoc>` — `page` param drives pagination.
|
|
432
|
+
* - `KeysetPaginationResult<TDoc>` — `sort` + optional `after` drives pagination.
|
|
433
|
+
* - `TDoc[]` — raw array when neither drives pagination.
|
|
434
|
+
*
|
|
435
|
+
* Arc passes the kit's response verbatim; consumers narrow on shape.
|
|
436
|
+
*/
|
|
437
|
+
type ListResult<TDoc> = OffsetPaginationResult<TDoc> | KeysetPaginationResult<TDoc> | TDoc[];
|
|
438
|
+
/**
|
|
439
|
+
* Discrete cache states reported via the `x-cache` response header.
|
|
440
|
+
*
|
|
441
|
+
* - `'HIT'` — fresh cache entry served, no upstream call made.
|
|
442
|
+
* - `'STALE'` — stale entry served, upstream refresh scheduled in the background.
|
|
443
|
+
* - `'MISS'` — no cache entry; upstream call ran and the result was cached.
|
|
444
|
+
*
|
|
445
|
+
* Exported as a literal union so test code and downstream clients can
|
|
446
|
+
* import + narrow without restating the literal triple.
|
|
447
|
+
*/
|
|
448
|
+
type CacheStatus = "HIT" | "STALE" | "MISS";
|
|
449
|
+
/**
|
|
450
|
+
* Controller-shape surface that the `Arc*Result` utilities read return
|
|
451
|
+
* types from. Internal — exported so the utility types can reference
|
|
452
|
+
* the minimal shape without a circular dependency on the full
|
|
453
|
+
* `BaseCrudController` / `BaseController` declarations.
|
|
454
|
+
*/
|
|
455
|
+
type ArcControllerLike = {
|
|
456
|
+
list: (...args: any[]) => unknown;
|
|
457
|
+
get: (...args: any[]) => unknown;
|
|
458
|
+
create: (...args: any[]) => unknown;
|
|
459
|
+
update: (...args: any[]) => unknown;
|
|
460
|
+
delete: (...args: any[]) => unknown;
|
|
461
|
+
};
|
|
462
|
+
/**
|
|
463
|
+
* Return type of the controller's `list` method.
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* ```ts
|
|
467
|
+
* class ProductController extends BaseController<Product> {
|
|
468
|
+
* async list(ctx: IRequestContext): ArcListResult<this> {
|
|
469
|
+
* // return shape inferred from BaseController.list — no need to
|
|
470
|
+
* // restate `Promise<IControllerResponse<ListResult<Product>>>`
|
|
471
|
+
* return super.list(ctx);
|
|
472
|
+
* }
|
|
473
|
+
* }
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
type ArcListResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["list"]>;
|
|
477
|
+
/** Return type of the controller's `get` method. See {@link ArcListResult}. */
|
|
478
|
+
type ArcGetResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["get"]>;
|
|
479
|
+
/** Return type of the controller's `create` method. See {@link ArcListResult}. */
|
|
480
|
+
type ArcCreateResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["create"]>;
|
|
481
|
+
/** Return type of the controller's `update` method. See {@link ArcListResult}. */
|
|
482
|
+
type ArcUpdateResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["update"]>;
|
|
483
|
+
/** Return type of the controller's `delete` method. See {@link ArcListResult}. */
|
|
484
|
+
type ArcDeleteResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["delete"]>;
|
|
485
|
+
//#endregion
|
|
608
486
|
//#region src/core/QueryResolver.d.ts
|
|
609
487
|
interface QueryResolverConfig {
|
|
610
488
|
/** Query parser instance (default: Arc built-in parser) */
|
|
@@ -635,6 +513,13 @@ declare class QueryResolver {
|
|
|
635
513
|
private schemaOptions;
|
|
636
514
|
private tenantField;
|
|
637
515
|
constructor(config?: QueryResolverConfig);
|
|
516
|
+
/**
|
|
517
|
+
* Swap the underlying parser. Mutates in place so the resolver instance
|
|
518
|
+
* stays referentially stable (hosts capturing a `queryResolver` ref via
|
|
519
|
+
* `defineResource({ controller })` keep that ref valid). Single source of
|
|
520
|
+
* truth — pairs with `BaseCrudController.setQueryParser()`.
|
|
521
|
+
*/
|
|
522
|
+
setParser(parser: QueryParserInterface): void;
|
|
638
523
|
/**
|
|
639
524
|
* Resolve a request into parsed query options -- ONE parse per request.
|
|
640
525
|
* Combines what was previously _buildContext + _parseQueryOptions + _applyFilters.
|
|
@@ -661,54 +546,6 @@ declare class QueryResolver {
|
|
|
661
546
|
}
|
|
662
547
|
//#endregion
|
|
663
548
|
//#region src/core/BaseCrudController.d.ts
|
|
664
|
-
/**
|
|
665
|
-
* Union of every return shape repo-core's `MinimalRepo.getAll()` is
|
|
666
|
-
* contractually allowed to produce. See repo-core's `MinimalRepo.getAll`
|
|
667
|
-
* docstring for the three-way split:
|
|
668
|
-
*
|
|
669
|
-
* - `OffsetPaginationResult<TDoc>` — `page` param drives pagination.
|
|
670
|
-
* - `KeysetPaginationResult<TDoc>` — `sort` + optional `after` drives pagination.
|
|
671
|
-
* - `TDoc[]` — raw array when neither drives pagination.
|
|
672
|
-
*
|
|
673
|
-
* Arc passes the kit's response verbatim; consumers narrow on shape.
|
|
674
|
-
*/
|
|
675
|
-
type ListResult<TDoc> = OffsetPaginationResult<TDoc> | KeysetPaginationResult<TDoc> | TDoc[];
|
|
676
|
-
/**
|
|
677
|
-
* Controller-shape surface that the `Arc*Result` utilities read return
|
|
678
|
-
* types from. Internal — exported so the utility types can reference
|
|
679
|
-
* the minimal shape without a circular dependency on the full
|
|
680
|
-
* `BaseCrudController` / `BaseController` declarations.
|
|
681
|
-
*/
|
|
682
|
-
type ArcControllerLike = {
|
|
683
|
-
list: (...args: any[]) => unknown;
|
|
684
|
-
get: (...args: any[]) => unknown;
|
|
685
|
-
create: (...args: any[]) => unknown;
|
|
686
|
-
update: (...args: any[]) => unknown;
|
|
687
|
-
delete: (...args: any[]) => unknown;
|
|
688
|
-
};
|
|
689
|
-
/**
|
|
690
|
-
* Return type of the controller's `list` method.
|
|
691
|
-
*
|
|
692
|
-
* @example
|
|
693
|
-
* ```ts
|
|
694
|
-
* class ProductController extends BaseController<Product> {
|
|
695
|
-
* async list(ctx: IRequestContext): ArcListResult<this> {
|
|
696
|
-
* // return shape inferred from BaseController.list — no need to
|
|
697
|
-
* // restate `Promise<IControllerResponse<ListResult<Product>>>`
|
|
698
|
-
* return super.list(ctx);
|
|
699
|
-
* }
|
|
700
|
-
* }
|
|
701
|
-
* ```
|
|
702
|
-
*/
|
|
703
|
-
type ArcListResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["list"]>;
|
|
704
|
-
/** Return type of the controller's `get` method. See {@link ArcListResult}. */
|
|
705
|
-
type ArcGetResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["get"]>;
|
|
706
|
-
/** Return type of the controller's `create` method. See {@link ArcListResult}. */
|
|
707
|
-
type ArcCreateResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["create"]>;
|
|
708
|
-
/** Return type of the controller's `update` method. See {@link ArcListResult}. */
|
|
709
|
-
type ArcUpdateResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["update"]>;
|
|
710
|
-
/** Return type of the controller's `delete` method. See {@link ArcListResult}. */
|
|
711
|
-
type ArcDeleteResult<TCtrl extends ArcControllerLike> = ReturnType<TCtrl["delete"]>;
|
|
712
549
|
interface BaseControllerOptions {
|
|
713
550
|
/** Schema options for field sanitization */
|
|
714
551
|
schemaOptions?: RouteSchemaOptions;
|
|
@@ -724,8 +561,8 @@ interface BaseControllerOptions {
|
|
|
724
561
|
defaultLimit?: number;
|
|
725
562
|
/**
|
|
726
563
|
* Default sort applied when the request doesn't specify one.
|
|
727
|
-
* - `string` (default: `'-createdAt'`)
|
|
728
|
-
* - `false`
|
|
564
|
+
* - `string` (default: `'-createdAt'`) — Mongo `-field` DESC convention.
|
|
565
|
+
* - `false` — disable the default sort entirely (SQL/Drizzle resources
|
|
729
566
|
* without a `createdAt` column).
|
|
730
567
|
*/
|
|
731
568
|
defaultSort?: string | false;
|
|
@@ -777,8 +614,6 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
777
614
|
protected queryParser: QueryParserInterface;
|
|
778
615
|
protected maxLimit: number;
|
|
779
616
|
protected defaultLimit: number;
|
|
780
|
-
/** `undefined` means "no default sort" (caller passed `false`). */
|
|
781
|
-
protected defaultSort: string | undefined;
|
|
782
617
|
protected resourceName?: string;
|
|
783
618
|
protected tenantField: string | false;
|
|
784
619
|
protected idField: string;
|
|
@@ -789,7 +624,7 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
789
624
|
/**
|
|
790
625
|
* Composable query resolution (parsing, pagination, sort, select/populate).
|
|
791
626
|
*
|
|
792
|
-
* Not `readonly`
|
|
627
|
+
* Not `readonly` — `setQueryParser()` rebuilds this resolver to swap in a
|
|
793
628
|
* different parser (e.g. mongokit's `QueryParser`). `defineResource` calls
|
|
794
629
|
* it automatically when a resource supplies both `controller` and
|
|
795
630
|
* `queryParser`.
|
|
@@ -803,18 +638,15 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
803
638
|
protected _cacheConfig?: ResourceCacheConfig;
|
|
804
639
|
constructor(repository: TRepository, options?: BaseControllerOptions);
|
|
805
640
|
/**
|
|
806
|
-
* Swap the controller's query parser.
|
|
807
|
-
*
|
|
641
|
+
* Swap the controller's query parser. Mutates the existing `QueryResolver`
|
|
642
|
+
* in place via `QueryResolver.setParser()` — the resolver instance stays
|
|
643
|
+
* referentially stable, and there is no second copy of `defaultSort` /
|
|
644
|
+
* `tenantField` / `schemaOptions` for the swap to drift away from.
|
|
808
645
|
*
|
|
809
646
|
* Closes the v2.10.9 gap where `defineResource({ controller, queryParser })`
|
|
810
|
-
* forwarded the parser only to auto-constructed controllers
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
* supplied — controllers that don't implement `setQueryParser` are left
|
|
814
|
-
* untouched.
|
|
815
|
-
*
|
|
816
|
-
* Idempotent + safe to call repeatedly. Does NOT touch `maxLimit` or
|
|
817
|
-
* `defaultLimit` — those are construction-time decisions.
|
|
647
|
+
* forwarded the parser only to auto-constructed controllers. `defineResource`
|
|
648
|
+
* calls this via duck-typing when both `controller` and `queryParser` are
|
|
649
|
+
* supplied; controllers that don't implement it are left untouched.
|
|
818
650
|
*/
|
|
819
651
|
setQueryParser(queryParser: QueryParserInterface): void;
|
|
820
652
|
/**
|
|
@@ -823,17 +655,39 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
823
655
|
*/
|
|
824
656
|
protected getTenantField(): string | undefined;
|
|
825
657
|
/**
|
|
826
|
-
* Build
|
|
658
|
+
* Build the canonical repo-options bag from the Fastify request.
|
|
659
|
+
*
|
|
660
|
+
* Forwards the cross-kit canonical set (see repo-core's
|
|
661
|
+
* `STANDARD_REPO_OPTION_KEYS`) into every CRUD repo call so kit
|
|
662
|
+
* plugins (multi-tenant, audit, audit-trail, observability) get
|
|
663
|
+
* what they need without per-resource wiring:
|
|
664
|
+
*
|
|
665
|
+
* - **Tenant scope** — `[tenantField]: orgId` from `RequestScope`.
|
|
666
|
+
* Plugin-scoped repos (mongokit's `multiTenantPlugin`) read tenant
|
|
667
|
+
* scope from the TOP of the options bag, not `data.organizationId`.
|
|
668
|
+
* Without this stamping, a tenant-scoped repo throws "Missing
|
|
669
|
+
* 'organizationId' in context" even when arc has injected the
|
|
670
|
+
* tenant into the request body.
|
|
671
|
+
* Multi-field tenancy from `_tenantFields` (populated by
|
|
672
|
+
* `multiTenantPreset`) is merged in.
|
|
827
673
|
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
* `
|
|
831
|
-
*
|
|
832
|
-
* injected the tenant into the request body.
|
|
674
|
+
* - **Audit attribution** — `userId` + `user` from the authenticated
|
|
675
|
+
* actor. Mongokit's audit-log / audit-trail plugins read these
|
|
676
|
+
* into the `who` column; sqlitekit's audit plugin reads the same
|
|
677
|
+
* names. No host-side forwarding needed.
|
|
833
678
|
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
679
|
+
* - **Trace correlation** — `requestId` from Fastify's request id
|
|
680
|
+
* for stitching logs / events / downstream calls.
|
|
681
|
+
*
|
|
682
|
+
* - **`session` is intentionally NOT auto-set.** Sessions are tied
|
|
683
|
+
* to explicit transaction scopes the controller doesn't manage;
|
|
684
|
+
* pass `session` inline at the call site when running inside a
|
|
685
|
+
* `withTransaction` helper.
|
|
686
|
+
*
|
|
687
|
+
* Method kept named `tenantRepoOptions` for back-compat with hosts
|
|
688
|
+
* that spread `...this.tenantRepoOptions(req)` (10+ call sites in
|
|
689
|
+
* arc, plus host overrides). The bag has always grown over time —
|
|
690
|
+
* hosts that don't want audit forwarding never read those keys.
|
|
837
691
|
*/
|
|
838
692
|
protected tenantRepoOptions(req: IRequestContext): AnyRecord;
|
|
839
693
|
/** Extract typed Arc internal metadata from request */
|
|
@@ -844,21 +698,40 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
844
698
|
* Resolve the repository primary key for mutation calls.
|
|
845
699
|
*
|
|
846
700
|
* When the resource declares a custom `idField` (slug, jobId, UUID), the
|
|
847
|
-
* default behavior is to translate the route id
|
|
701
|
+
* default behavior is to translate the route id → the fetched doc's `_id`
|
|
848
702
|
* because most Mongo repositories key mutation methods off `_id`.
|
|
849
703
|
*
|
|
850
704
|
* Exception: if the repo exposes a matching `idField` property (e.g.
|
|
851
705
|
* MongoKit's `new Repository(Model, [], {}, { idField: 'id' })`), the
|
|
852
|
-
* repo handles lookup itself
|
|
706
|
+
* repo handles lookup itself — pass the route id through unchanged.
|
|
853
707
|
*/
|
|
854
708
|
protected resolveRepoId(id: string, existing: AnyRecord | null): string;
|
|
855
709
|
/**
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
*
|
|
859
|
-
*
|
|
710
|
+
* Read-side preflight for mutable-target operations (`update`, `delete`).
|
|
711
|
+
*
|
|
712
|
+
* Bundles the four steps that every mutation must do before touching the
|
|
713
|
+
* repo: (1) extract `:id`, (2) fetch under access control + tenant scope,
|
|
714
|
+
* (3) verify ownership, (4) translate the route id to the repo's primary
|
|
715
|
+
* key. Returning `{id, existing, repoId}` keeps the call sites a single
|
|
716
|
+
* line and makes drift between `update` and `delete` structurally
|
|
717
|
+
* impossible — there is one preflight, one denial-reason mapping, one
|
|
718
|
+
* ownership check.
|
|
719
|
+
*
|
|
720
|
+
* Pass `extraFetchOptions` for callers (e.g. soft-delete restore) that
|
|
721
|
+
* need to widen the fetch (`{ includeDeleted: true }`).
|
|
860
722
|
*/
|
|
861
|
-
protected
|
|
723
|
+
protected loadMutableTarget(req: IRequestContext, extraFetchOptions?: AnyRecord): Promise<{
|
|
724
|
+
id: string;
|
|
725
|
+
existing: TDoc;
|
|
726
|
+
repoId: string;
|
|
727
|
+
}>;
|
|
728
|
+
/**
|
|
729
|
+
* Centralized 404 thrower. Maps the denial reason from `fetchDetailed()`
|
|
730
|
+
* into a `NotFoundError` so consumers can distinguish "doc doesn't
|
|
731
|
+
* exist" from "doc filtered by policy/org scope" via the error
|
|
732
|
+
* `details.code` set by the global error handler.
|
|
733
|
+
*/
|
|
734
|
+
protected throwNotFound(reason?: FetchDenialReason | null): never;
|
|
862
735
|
/** Resolve cache config for a specific operation, merging per-op overrides */
|
|
863
736
|
protected resolveCacheConfig(operation: "list" | "byId"): QueryCacheConfig | null;
|
|
864
737
|
/**
|
|
@@ -870,7 +743,86 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
870
743
|
userId?: string;
|
|
871
744
|
orgId?: string;
|
|
872
745
|
};
|
|
746
|
+
/** Shared `x-cache` response envelope builder. */
|
|
747
|
+
protected cacheResponse<T>(data: T, cacheStatus: CacheStatus): IControllerResponse<T>;
|
|
748
|
+
/** Required route-id helper shared by get/update/delete. Throws on missing id. */
|
|
749
|
+
protected requireIdParam(req: IRequestContext): string;
|
|
750
|
+
/**
|
|
751
|
+
* Normalizes `repo.exists()` return shapes across adapters. Per
|
|
752
|
+
* StandardRepo's contract, `exists` may return `boolean`, `{ _id }`,
|
|
753
|
+
* or `null` — every truthy non-null shape collapses to `true`.
|
|
754
|
+
*/
|
|
755
|
+
protected isExistsTruthy(result: unknown): boolean;
|
|
756
|
+
/**
|
|
757
|
+
* Run `executeBefore` then `executeAround` (or just the executor if no
|
|
758
|
+
* hooks are wired). Returns the around-phase result directly. Throws an
|
|
759
|
+
* `ArcError` (status 400, code `BEFORE_<OP>_HOOK_ERROR`) when the
|
|
760
|
+
* before-hook fails — the global error handler emits the canonical
|
|
761
|
+
* `ErrorContract` shape.
|
|
762
|
+
*
|
|
763
|
+
* The caller runs `executeAfter` separately via `runAfterHook` — typically
|
|
764
|
+
* after success-checking the result (delete checks `isDeleteSuccess`,
|
|
765
|
+
* update checks `if (!item)`).
|
|
766
|
+
*
|
|
767
|
+
* **Knobs:**
|
|
768
|
+
* - `meta` — passed verbatim into `executeBefore` / `executeAround` opts.
|
|
769
|
+
* - `pipeProcessedData` (default `true`) — whether `executeBefore`'s
|
|
770
|
+
* return value flows into `executeAround` as the data parameter.
|
|
771
|
+
* Set `false` for delete (current behaviour: discards before's
|
|
772
|
+
* return, passes original input to around).
|
|
773
|
+
*/
|
|
774
|
+
protected runHookedOpUntilResult<TInput, TResult>(req: IRequestContext, args: {
|
|
775
|
+
op: "create" | "update" | "delete";
|
|
776
|
+
input: TInput;
|
|
777
|
+
meta?: Record<string, unknown>;
|
|
778
|
+
pipeProcessedData?: boolean;
|
|
779
|
+
}, executor: (processed: TInput) => Promise<TResult>): Promise<TResult>;
|
|
780
|
+
/**
|
|
781
|
+
* Run `executeAfter` for the given op + data. No-op when hooks aren't
|
|
782
|
+
* wired or `resourceName` isn't set. Caller passes the data shape it
|
|
783
|
+
* wants downstream after-handlers to receive — typically the result for
|
|
784
|
+
* create/update, the original input (`existing`) for delete.
|
|
785
|
+
*/
|
|
786
|
+
protected runAfterHook(req: IRequestContext, op: "create" | "update" | "delete", data: AnyRecord, meta?: Record<string, unknown>): Promise<void>;
|
|
787
|
+
/** Cached `list()` flow with SWR semantics. Returns null when cache is disabled. */
|
|
788
|
+
protected withListCache(req: IRequestContext, options: ParsedQuery): Promise<IControllerResponse<ListResult<TDoc>> | null>;
|
|
789
|
+
/** Cached `get()` flow with SWR semantics. Returns null when cache is disabled. */
|
|
790
|
+
protected withGetCache(req: IRequestContext, id: string, options: ParsedQuery): Promise<IControllerResponse<TDoc> | null>;
|
|
873
791
|
list(req: IRequestContext): Promise<IControllerResponse<ListResult<TDoc>>>;
|
|
792
|
+
/**
|
|
793
|
+
* Resource-dispatch verbs router. Returns `null` when the request is
|
|
794
|
+
* a regular list query, otherwise returns the dispatch promise.
|
|
795
|
+
*
|
|
796
|
+
* Verbs (mutually exclusive — first match wins):
|
|
797
|
+
* - `?_count=true` → `{ count: number }` via `repo.count()`
|
|
798
|
+
* - `?_distinct=field` → `unknown[]` via `repo.distinct(field)`
|
|
799
|
+
* - `?_exists=true` → `{ exists: boolean }` via `repo.exists()`
|
|
800
|
+
*
|
|
801
|
+
* All verbs share the resolved filter (parsed query + policy filters
|
|
802
|
+
* + tenant scope). Adapters that don't ship the underlying repo
|
|
803
|
+
* method get a `501` so failures surface loudly instead of falling
|
|
804
|
+
* back to a full table scan.
|
|
805
|
+
*/
|
|
806
|
+
protected dispatchResourceVerb(req: IRequestContext): Promise<IControllerResponse<unknown>> | null;
|
|
807
|
+
/** Resolve filter + tenant/audit options for a dispatch verb. */
|
|
808
|
+
private resolveDispatchScope;
|
|
809
|
+
/** `?_count=true` → `repo.count(filter)` */
|
|
810
|
+
protected dispatchCount(req: IRequestContext): Promise<IControllerResponse<{
|
|
811
|
+
count: number;
|
|
812
|
+
}>>;
|
|
813
|
+
/** `?_distinct=field` → `repo.distinct(field, filter)` */
|
|
814
|
+
protected dispatchDistinct(req: IRequestContext, field: string): Promise<IControllerResponse<unknown[]>>;
|
|
815
|
+
/** `?_exists=true` → `repo.exists(filter)` */
|
|
816
|
+
protected dispatchExists(req: IRequestContext): Promise<IControllerResponse<{
|
|
817
|
+
exists: boolean;
|
|
818
|
+
}>>;
|
|
819
|
+
/**
|
|
820
|
+
* True when `field` is safe to expose via `_distinct`. Mirrors the
|
|
821
|
+
* `select` allowlist — fields marked `hidden` or `systemManaged` in
|
|
822
|
+
* `schemaOptions.fieldRules` are NOT exposed (would leak password
|
|
823
|
+
* hashes, internal flags, etc).
|
|
824
|
+
*/
|
|
825
|
+
protected isFieldExposedForRead(field: string): boolean;
|
|
874
826
|
/** Execute list query through hooks (extracted for cache revalidation) */
|
|
875
827
|
protected executeListQuery(options: ParsedQuery, req: IRequestContext): Promise<ListResult<TDoc>>;
|
|
876
828
|
get(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
@@ -999,7 +951,7 @@ declare function SoftDeleteMixin<TBase extends Constructor<BaseCrudController>>(
|
|
|
999
951
|
*
|
|
1000
952
|
* Spec reference: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-classes-with-other-types
|
|
1001
953
|
*/
|
|
1002
|
-
interface BaseController<TDoc extends AnyRecord = AnyRecord,
|
|
954
|
+
interface BaseController<TDoc extends AnyRecord = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> {
|
|
1003
955
|
readonly accessControl: AccessControl;
|
|
1004
956
|
readonly bodySanitizer: BodySanitizer;
|
|
1005
957
|
queryResolver: QueryResolver;
|
|
@@ -1034,8 +986,8 @@ declare const BaseController_base: (new (repository: any, options?: BaseControll
|
|
|
1034
986
|
* companion interface above gives every method full generic precision
|
|
1035
987
|
* on `TDoc` via declaration merging.
|
|
1036
988
|
*/
|
|
1037
|
-
declare class BaseController<TDoc extends AnyRecord = AnyRecord,
|
|
1038
|
-
readonly _phantom?: [TDoc,
|
|
989
|
+
declare class BaseController<TDoc extends AnyRecord = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> extends BaseController_base {
|
|
990
|
+
readonly _phantom?: [TDoc, _TRepository];
|
|
1039
991
|
}
|
|
1040
992
|
//#endregion
|
|
1041
993
|
//#region src/types/base.d.ts
|
|
@@ -1355,6 +1307,283 @@ type PipelineConfig = PipelineStep[] | {
|
|
|
1355
1307
|
[operation: string]: PipelineStep[] | undefined;
|
|
1356
1308
|
};
|
|
1357
1309
|
//#endregion
|
|
1310
|
+
//#region src/core/aggregation/types.d.ts
|
|
1311
|
+
/**
|
|
1312
|
+
* Sugar for measures: `'count'` / `'count:field'` / `'sum:price'` /
|
|
1313
|
+
* `'avg:rating'` / `'min:created'` / `'max:updated'` /
|
|
1314
|
+
* `'countDistinct:userId'` / `'percentile:latency:0.95'`.
|
|
1315
|
+
*
|
|
1316
|
+
* Compiles to the canonical `AggMeasure` IR at boot. Hosts who want
|
|
1317
|
+
* the full IR can pass an `AggMeasure` object directly.
|
|
1318
|
+
*
|
|
1319
|
+
* **Percentile.** `'percentile:<field>:<p>'` where `p` is a numeric
|
|
1320
|
+
* literal in `[0, 1]` (e.g. `'percentile:latency:0.95'` for P95).
|
|
1321
|
+
* Mongokit (≥3.13) compiles to `$percentile`; sqlitekit throws
|
|
1322
|
+
* `UnsupportedOperationError` (no native percentile in SQLite).
|
|
1323
|
+
*/
|
|
1324
|
+
type AggMeasureShorthand = "count" | `count:${string}` | `countDistinct:${string}` | `sum:${string}` | `avg:${string}` | `min:${string}` | `max:${string}` | `percentile:${string}:${number}`;
|
|
1325
|
+
/** Either canonical IR or shorthand string — both compile to `AggMeasure`. */
|
|
1326
|
+
type AggMeasureInput = AggMeasure | AggMeasureShorthand;
|
|
1327
|
+
/**
|
|
1328
|
+
* Cache config for an aggregation. Translates directly to the kit-side
|
|
1329
|
+
* `CacheOptions` (TanStack-shaped) which the unified
|
|
1330
|
+
* `@classytic/repo-core/cache` plugin reads from `req.cache`.
|
|
1331
|
+
*
|
|
1332
|
+
* Caching only fires when the kit's repo has the `cachePlugin` wired —
|
|
1333
|
+
* arc declares the policy; the kit handles SWR + tag invalidation +
|
|
1334
|
+
* version-bump on writes. Hosts without the plugin installed silently
|
|
1335
|
+
* fall through to a non-cached call.
|
|
1336
|
+
*/
|
|
1337
|
+
interface AggregationCacheConfig {
|
|
1338
|
+
/** Seconds the entry is fresh — no revalidation while inside this window. */
|
|
1339
|
+
staleTime?: number;
|
|
1340
|
+
/** Seconds the entry stays in cache past stale before eviction. Default: 60. */
|
|
1341
|
+
gcTime?: number;
|
|
1342
|
+
/**
|
|
1343
|
+
* Stale-while-revalidate. When stale entries serve immediately and a
|
|
1344
|
+
* background refresh updates the cache. Default: `true` for
|
|
1345
|
+
* aggregations (dashboards almost always benefit from stale-serve).
|
|
1346
|
+
*/
|
|
1347
|
+
swr?: boolean;
|
|
1348
|
+
/**
|
|
1349
|
+
* Group invalidation tags. Pass to `repo.cache?.invalidateByTags(tags)`
|
|
1350
|
+
* after a write to clear matching entries. The model name is
|
|
1351
|
+
* auto-tagged by the plugin — you only declare cross-cutting tags
|
|
1352
|
+
* (e.g. `'pricing'` to invalidate every aggregation that depends on
|
|
1353
|
+
* pricing across multiple resources).
|
|
1354
|
+
*/
|
|
1355
|
+
tags?: readonly string[];
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Per-aggregation rate limit. Layers on top of any global rate limit
|
|
1359
|
+
* via `@fastify/rate-limit` route-level config.
|
|
1360
|
+
*/
|
|
1361
|
+
interface AggregationRateLimit {
|
|
1362
|
+
/** Max requests per window. */
|
|
1363
|
+
max: number;
|
|
1364
|
+
/** Window in milliseconds. */
|
|
1365
|
+
windowMs: number;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Required date-range narrowing. Caller MUST send a bounded range on
|
|
1369
|
+
* this field, and the range MUST NOT exceed `maxRangeDays`.
|
|
1370
|
+
*
|
|
1371
|
+
* Prevents "all-time" scans on billion-row collections — the single
|
|
1372
|
+
* biggest performance footgun for live aggregation endpoints.
|
|
1373
|
+
*/
|
|
1374
|
+
interface AggregationDateRangeRequirement {
|
|
1375
|
+
/** Field whose range the caller must narrow (e.g. `'createdAt'`). */
|
|
1376
|
+
field: string;
|
|
1377
|
+
/**
|
|
1378
|
+
* Cap on the queryable range. A request asking for >N days is
|
|
1379
|
+
* rejected 400. Omit for "any range, but bounded" (lower + upper
|
|
1380
|
+
* required, no cap).
|
|
1381
|
+
*/
|
|
1382
|
+
maxRangeDays?: number;
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Boot-time index hint. Arc warns when the kit's schema doesn't have
|
|
1386
|
+
* an index whose leading keys match — flags the misconfig before
|
|
1387
|
+
* traffic hits the DB.
|
|
1388
|
+
*
|
|
1389
|
+
* Documented intent, NOT runtime-enforced. Kits with their own index
|
|
1390
|
+
* introspection (mongokit reads Mongoose schema, sqlitekit reads
|
|
1391
|
+
* Drizzle indexes) can act on the hint; kits without introspection
|
|
1392
|
+
* silently accept it.
|
|
1393
|
+
*/
|
|
1394
|
+
interface AggregationIndexHint {
|
|
1395
|
+
/** Leading-key columns the host expects the planner to use. */
|
|
1396
|
+
leadingKeys: readonly string[];
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Runtime context passed to the `materialized` hook.
|
|
1400
|
+
*
|
|
1401
|
+
* The hook returns pre-computed data instead of running the live
|
|
1402
|
+
* aggregation. Hosts use this for ultra-frequent dashboards backed by
|
|
1403
|
+
* rollup tables maintained out-of-band (cron / CDC).
|
|
1404
|
+
*/
|
|
1405
|
+
interface AggregationMaterializedContext {
|
|
1406
|
+
/** Compiled filter (host base + tenant + caller). */
|
|
1407
|
+
filter: AnyRecord;
|
|
1408
|
+
/** Tenant id when the resource is tenant-scoped. */
|
|
1409
|
+
orgId?: string;
|
|
1410
|
+
/** Authenticated user id, when present. */
|
|
1411
|
+
userId?: string;
|
|
1412
|
+
/** Fastify request id for tracing. */
|
|
1413
|
+
requestId?: string;
|
|
1414
|
+
/** Raw URL query params (post-validation). */
|
|
1415
|
+
query: Record<string, unknown>;
|
|
1416
|
+
}
|
|
1417
|
+
/** Materialized hook return shape — same envelope as `AggResult`. */
|
|
1418
|
+
interface AggregationMaterializedResult<TRow = AnyRecord> {
|
|
1419
|
+
rows: readonly TRow[];
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Single named aggregation declaration. Composes into `AggRequest` at
|
|
1423
|
+
* request time, with safety knobs layered on at the arc-handler level.
|
|
1424
|
+
*/
|
|
1425
|
+
interface AggregationConfig {
|
|
1426
|
+
/**
|
|
1427
|
+
* Pre-aggregate filter on the BASE rows (before lookups). Always
|
|
1428
|
+
* ANDed with auto-injected tenant scope + caller URL-narrowing
|
|
1429
|
+
* filters. Use for host-defined invariants (e.g. `archived: false`).
|
|
1430
|
+
*/
|
|
1431
|
+
filter?: AnyRecord;
|
|
1432
|
+
/**
|
|
1433
|
+
* Cross-table joins. Each `LookupSpec` reuses the IR
|
|
1434
|
+
* `@classytic/repo-core/lookup` defines for `lookupPopulate()`.
|
|
1435
|
+
* Same compile path the kit already ships.
|
|
1436
|
+
*
|
|
1437
|
+
* **Kit support is incremental.** A kit's `aggregate()` may not yet
|
|
1438
|
+
* compile lookups — boot validation against the adapter version
|
|
1439
|
+
* surfaces this loud, so hosts pin the kit major they need.
|
|
1440
|
+
*/
|
|
1441
|
+
lookups?: readonly LookupSpec[];
|
|
1442
|
+
/**
|
|
1443
|
+
* Group key(s). Dotted paths into joined aliases supported when
|
|
1444
|
+
* `lookups` is set: `'category.parent'` groups by the joined
|
|
1445
|
+
* `category` row's `parent` field.
|
|
1446
|
+
*/
|
|
1447
|
+
groupBy?: string | readonly string[];
|
|
1448
|
+
/**
|
|
1449
|
+
* Time-bucket group keys for time-series aggregations. Each entry
|
|
1450
|
+
* promotes a date column into a synthetic group key bucketed at
|
|
1451
|
+
* the chosen interval. The map key becomes a column on the output
|
|
1452
|
+
* row holding the canonical ISO-shaped bucket label
|
|
1453
|
+
* (`'2026-04'` for month, `'2026-W15'` for ISO week, etc.).
|
|
1454
|
+
*
|
|
1455
|
+
* Bucketed keys participate in grouping the same way `groupBy`
|
|
1456
|
+
* columns do — `sort: { month: 1 }`, `having`, pagination, and
|
|
1457
|
+
* `topN.partitionBy` all treat them as first-class.
|
|
1458
|
+
*
|
|
1459
|
+
* Aliases must NOT collide with a `groupBy` field name or measure
|
|
1460
|
+
* alias — boot validation throws on collision.
|
|
1461
|
+
*
|
|
1462
|
+
* @example "Daily revenue for the last quarter"
|
|
1463
|
+
* ```ts
|
|
1464
|
+
* dailyRevenue: defineAggregation({
|
|
1465
|
+
* dateBuckets: { day: { field: 'createdAt', interval: 'day' } },
|
|
1466
|
+
* measures: { revenue: 'sum:totalPrice' },
|
|
1467
|
+
* sort: { day: 1 },
|
|
1468
|
+
* permissions: requireRoles(['admin']),
|
|
1469
|
+
* }),
|
|
1470
|
+
* ```
|
|
1471
|
+
*
|
|
1472
|
+
* @example "15-minute traffic buckets (custom-bin form)"
|
|
1473
|
+
* ```ts
|
|
1474
|
+
* traffic: defineAggregation({
|
|
1475
|
+
* dateBuckets: {
|
|
1476
|
+
* slot: { field: 'ts', interval: { every: 15, unit: 'minute' } },
|
|
1477
|
+
* },
|
|
1478
|
+
* measures: { hits: 'count' },
|
|
1479
|
+
* permissions: allowPublic(),
|
|
1480
|
+
* }),
|
|
1481
|
+
* ```
|
|
1482
|
+
*/
|
|
1483
|
+
dateBuckets?: Record<string, AggDateBucket>;
|
|
1484
|
+
/** Named aggregations. At least one entry required. */
|
|
1485
|
+
measures: Record<string, AggMeasureInput>;
|
|
1486
|
+
/**
|
|
1487
|
+
* Post-aggregate filter referencing measure aliases.
|
|
1488
|
+
* Example: `{ revenue: { gt: 1000 } }` → `HAVING revenue > 1000`.
|
|
1489
|
+
*/
|
|
1490
|
+
having?: AnyRecord;
|
|
1491
|
+
/** Order grouped rows by groupBy field, measure alias, or joined-alias path. */
|
|
1492
|
+
sort?: Record<string, 1 | -1>;
|
|
1493
|
+
/** Hard cap on result rows. Applied at the IR level (LIMIT / `$limit`). */
|
|
1494
|
+
limit?: number;
|
|
1495
|
+
/**
|
|
1496
|
+
* Top-N-per-group filter. Keeps only the top `limit` rows per
|
|
1497
|
+
* partition, ranked by `sortBy`. The classic "top 3 products per
|
|
1498
|
+
* category" / "top 5 customers per region" dashboard primitive.
|
|
1499
|
+
*
|
|
1500
|
+
* Composes with `having` / `sort` — applies AFTER group + measures +
|
|
1501
|
+
* having, so `partitionBy` and `sortBy` may reference groupBy fields,
|
|
1502
|
+
* dateBucket aliases, or measure aliases. The top-level `sort` orders
|
|
1503
|
+
* the final row set across partitions.
|
|
1504
|
+
*
|
|
1505
|
+
* **Per-kit support.** Mongokit compiles to `$setWindowFields` (Mongo 5+,
|
|
1506
|
+
* runs in-engine — scales). Sqlitekit post-processes in JS (fine for
|
|
1507
|
+
* typical dashboards; prefer mongokit for >100k groups). See
|
|
1508
|
+
* `AggTopN` for full semantics.
|
|
1509
|
+
*/
|
|
1510
|
+
topN?: AggTopN;
|
|
1511
|
+
/**
|
|
1512
|
+
* Permission check. **REQUIRED.** Aggregations are read-shape but
|
|
1513
|
+
* different from list (different threat model — measures may expose
|
|
1514
|
+
* cardinality info even when individual rows are hidden). Boot error
|
|
1515
|
+
* if missing.
|
|
1516
|
+
*/
|
|
1517
|
+
permissions: PermissionCheck;
|
|
1518
|
+
/**
|
|
1519
|
+
* DB-level execution cap (ms). Mongokit threads to `maxTimeMS`;
|
|
1520
|
+
* sqlitekit threads to per-statement timeout where supported.
|
|
1521
|
+
* Default: kit's default (typically none).
|
|
1522
|
+
*/
|
|
1523
|
+
timeout?: number;
|
|
1524
|
+
/**
|
|
1525
|
+
* Reject 422 if the result row count exceeds this cap. Better than
|
|
1526
|
+
* silent truncation — caller knows the dashboard is incomplete.
|
|
1527
|
+
* Default: no cap (use `limit` for truncation semantics).
|
|
1528
|
+
*/
|
|
1529
|
+
maxGroups?: number;
|
|
1530
|
+
/**
|
|
1531
|
+
* Caller MUST provide filters on these fields (else 400 at request).
|
|
1532
|
+
* Use to require a tenant-side narrowing the host can't infer
|
|
1533
|
+
* (segment id, customer id, etc.).
|
|
1534
|
+
*/
|
|
1535
|
+
requireFilters?: readonly string[];
|
|
1536
|
+
/**
|
|
1537
|
+
* Caller MUST send a bounded date range on this field. Prevents
|
|
1538
|
+
* all-time scans on billion-row collections.
|
|
1539
|
+
*/
|
|
1540
|
+
requireDateRange?: AggregationDateRangeRequirement;
|
|
1541
|
+
/**
|
|
1542
|
+
* Documented index expectation. Arc warns at boot when the kit
|
|
1543
|
+
* exposes index introspection and no matching index exists. NOT
|
|
1544
|
+
* runtime-enforced — purely a misconfig signal.
|
|
1545
|
+
*/
|
|
1546
|
+
indexHint?: AggregationIndexHint;
|
|
1547
|
+
/**
|
|
1548
|
+
* Per-aggregation cache. Tenant-scoped keys; invalidates with the
|
|
1549
|
+
* resource's cache namespace + any explicit `tags`.
|
|
1550
|
+
*/
|
|
1551
|
+
cache?: AggregationCacheConfig;
|
|
1552
|
+
/**
|
|
1553
|
+
* Per-route rate limit. Wired to `@fastify/rate-limit` when the
|
|
1554
|
+
* plugin is registered.
|
|
1555
|
+
*/
|
|
1556
|
+
rateLimit?: AggregationRateLimit;
|
|
1557
|
+
/**
|
|
1558
|
+
* Pre-computed read replacement. When set, arc skips
|
|
1559
|
+
* `repo.aggregate()` and calls this function instead. Same wire
|
|
1560
|
+
* shape, same permissions, different data source.
|
|
1561
|
+
*
|
|
1562
|
+
* Use for homepage-counter dashboards backed by host-managed rollup
|
|
1563
|
+
* tables (cron / CDC). The hook receives the compiled context
|
|
1564
|
+
* (filter + scope) so the host can route the lookup to the right
|
|
1565
|
+
* pre-aggregated bucket.
|
|
1566
|
+
*/
|
|
1567
|
+
materialized?: (ctx: AggregationMaterializedContext) => Promise<AggregationMaterializedResult>;
|
|
1568
|
+
/** Optional summary rendered in OpenAPI + MCP tool description. */
|
|
1569
|
+
summary?: string;
|
|
1570
|
+
/** Optional longer description for OpenAPI / MCP. */
|
|
1571
|
+
description?: string;
|
|
1572
|
+
/**
|
|
1573
|
+
* MCP tool generation control. Mirrors `actions[name].mcp` semantics.
|
|
1574
|
+
*
|
|
1575
|
+
* - `undefined` (default) — generate an MCP tool for this aggregation
|
|
1576
|
+
* - `false` — skip MCP tool generation (REST route still works)
|
|
1577
|
+
* - `{ description?, annotations? }` — generate with overrides
|
|
1578
|
+
*/
|
|
1579
|
+
mcp?: boolean | {
|
|
1580
|
+
readonly description?: string;
|
|
1581
|
+
readonly annotations?: Record<string, unknown>;
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
/** Map of name → declaration. Keys become URL segments under `/aggregations/<name>`. */
|
|
1585
|
+
type AggregationsMap = Record<string, AggregationConfig>;
|
|
1586
|
+
//#endregion
|
|
1358
1587
|
//#region src/types/handlers.d.ts
|
|
1359
1588
|
/**
|
|
1360
1589
|
* Minimal server accessor — exposes safe, read-only server decorators.
|
|
@@ -1514,30 +1743,29 @@ interface IRequestContext<TBody = unknown, TParams extends Record<string, string
|
|
|
1514
1743
|
* async reschedule(req: IRequestContext) {
|
|
1515
1744
|
* const result = await repo.reschedule(req.params.id, req.body);
|
|
1516
1745
|
* await req.server?.events?.publish('interview.rescheduled', { data: result });
|
|
1517
|
-
* return {
|
|
1746
|
+
* return { data: result };
|
|
1518
1747
|
* }
|
|
1519
1748
|
* ```
|
|
1520
1749
|
*/
|
|
1521
1750
|
server?: ServerAccessor;
|
|
1522
1751
|
}
|
|
1523
1752
|
/**
|
|
1524
|
-
*
|
|
1753
|
+
* Controller response shape — the success-path return from any handler.
|
|
1754
|
+
*
|
|
1755
|
+
* Errors throw `ArcError` (or any `HttpError`-shaped class); the global
|
|
1756
|
+
* error handler catches them and emits an `ErrorContract`. There is no
|
|
1757
|
+
* `success` discriminator on the response — HTTP status is the wire
|
|
1758
|
+
* discriminator (2xx = data, 4xx/5xx = ErrorContract).
|
|
1525
1759
|
*/
|
|
1526
1760
|
interface IControllerResponse<T = unknown> {
|
|
1527
|
-
/**
|
|
1528
|
-
|
|
1529
|
-
/**
|
|
1530
|
-
data?: T;
|
|
1531
|
-
/** Error message (when success is false) */
|
|
1532
|
-
error?: string;
|
|
1533
|
-
/** HTTP status code (default: 200 for success, 400 for error) */
|
|
1761
|
+
/** Response payload — emitted directly to the wire (no envelope wrap). */
|
|
1762
|
+
data: T;
|
|
1763
|
+
/** HTTP status code. Defaults to 200. */
|
|
1534
1764
|
status?: number;
|
|
1535
|
-
/**
|
|
1536
|
-
meta?: Record<string, unknown>;
|
|
1537
|
-
/** Error details (for debugging) */
|
|
1538
|
-
details?: Record<string, unknown>;
|
|
1539
|
-
/** Custom response headers (e.g., X-Total-Count, Link, ETag) */
|
|
1765
|
+
/** Custom response headers (e.g. X-Total-Count, Link, ETag). */
|
|
1540
1766
|
headers?: Record<string, string>;
|
|
1767
|
+
/** Top-level metadata merged into list-shaped responses (e.g. `{ took }`). */
|
|
1768
|
+
meta?: Record<string, unknown>;
|
|
1541
1769
|
}
|
|
1542
1770
|
/**
|
|
1543
1771
|
* Controller handler — Arc's standard pattern.
|
|
@@ -1560,7 +1788,7 @@ interface IControllerResponse<T = unknown> {
|
|
|
1560
1788
|
* // Untyped req — body is unknown, must be narrowed
|
|
1561
1789
|
* const createProduct: ControllerHandler<Product> = async (req) => {
|
|
1562
1790
|
* const product = await productRepo.create(req.body as Partial<Product>);
|
|
1563
|
-
* return {
|
|
1791
|
+
* return { data: product, status: 201 };
|
|
1564
1792
|
* };
|
|
1565
1793
|
*
|
|
1566
1794
|
* // Fully typed — body, params, query, and response all inferred
|
|
@@ -1572,7 +1800,7 @@ interface IControllerResponse<T = unknown> {
|
|
|
1572
1800
|
* > = async (req) => {
|
|
1573
1801
|
* const upsert = req.query.upsert === "true";
|
|
1574
1802
|
* const product = await productRepo.update(req.params.id, req.body, { upsert });
|
|
1575
|
-
* return {
|
|
1803
|
+
* return { data: product };
|
|
1576
1804
|
* };
|
|
1577
1805
|
*
|
|
1578
1806
|
* routes: [{
|
|
@@ -2116,9 +2344,9 @@ interface ResourceHookContext {
|
|
|
2116
2344
|
* ```
|
|
2117
2345
|
*/
|
|
2118
2346
|
interface ResourceHooks {
|
|
2119
|
-
beforeCreate?: (ctx: ResourceHookContext) => Promise<AnyRecord |
|
|
2347
|
+
beforeCreate?: (ctx: ResourceHookContext) => Promise<AnyRecord | undefined> | AnyRecord | undefined;
|
|
2120
2348
|
afterCreate?: (ctx: ResourceHookContext) => Promise<void> | void;
|
|
2121
|
-
beforeUpdate?: (ctx: ResourceHookContext) => Promise<AnyRecord |
|
|
2349
|
+
beforeUpdate?: (ctx: ResourceHookContext) => Promise<AnyRecord | undefined> | AnyRecord | undefined;
|
|
2122
2350
|
afterUpdate?: (ctx: ResourceHookContext) => Promise<void> | void;
|
|
2123
2351
|
beforeDelete?: (ctx: ResourceHookContext) => Promise<void> | void;
|
|
2124
2352
|
afterDelete?: (ctx: ResourceHookContext) => Promise<void> | void;
|
|
@@ -2270,6 +2498,31 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
2270
2498
|
* Only applies when `actions` is defined.
|
|
2271
2499
|
*/
|
|
2272
2500
|
actionPermissions?: PermissionCheck;
|
|
2501
|
+
/**
|
|
2502
|
+
* Declarative aggregations (v2.13) — generate `GET /:resource/aggregations/:name`
|
|
2503
|
+
* routes from the portable `AggRequest` IR. Each entry pins permissions,
|
|
2504
|
+
* filters, lookups, measures, sort, limit, plus big-data safety knobs
|
|
2505
|
+
* (timeout, maxGroups, requireDateRange, indexHint, materialized).
|
|
2506
|
+
*
|
|
2507
|
+
* @example
|
|
2508
|
+
* ```ts
|
|
2509
|
+
* defineResource({
|
|
2510
|
+
* name: 'order',
|
|
2511
|
+
* aggregations: {
|
|
2512
|
+
* revenueByStatus: defineAggregation({
|
|
2513
|
+
* groupBy: 'status',
|
|
2514
|
+
* measures: { count: 'count', revenue: 'sum:totalPrice' },
|
|
2515
|
+
* permissions: requireRoles(['admin']),
|
|
2516
|
+
* requireDateRange: { field: 'createdAt', maxRangeDays: 90 },
|
|
2517
|
+
* timeout: 5000,
|
|
2518
|
+
* maxGroups: 1000,
|
|
2519
|
+
* cache: { staleTime: 60 },
|
|
2520
|
+
* }),
|
|
2521
|
+
* },
|
|
2522
|
+
* });
|
|
2523
|
+
* ```
|
|
2524
|
+
*/
|
|
2525
|
+
aggregations?: AggregationsMap;
|
|
2273
2526
|
disableCrud?: boolean;
|
|
2274
2527
|
disableDefaultRoutes?: boolean;
|
|
2275
2528
|
/** Specific routes to disable */
|
|
@@ -2360,31 +2613,13 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
2360
2613
|
};
|
|
2361
2614
|
}
|
|
2362
2615
|
//#endregion
|
|
2363
|
-
//#region src/core/defineResource.d.ts
|
|
2616
|
+
//#region src/core/defineResource/ResourceDefinition.d.ts
|
|
2364
2617
|
/**
|
|
2365
|
-
*
|
|
2366
|
-
*
|
|
2367
|
-
*
|
|
2368
|
-
*
|
|
2369
|
-
*
|
|
2370
|
-
* Staged into seven named phases so future refactors touch one phase at a
|
|
2371
|
-
* time instead of threading changes through a 450-line function:
|
|
2372
|
-
*
|
|
2373
|
-
* 1. validate — fail-fast structural checks
|
|
2374
|
-
* 2. resolveIdField — auto-derive `idField` from repository
|
|
2375
|
-
* 3. applyPresetsAndAutoInject — clone + apply presets + tenant-field rules
|
|
2376
|
-
* 4. resolveController — reuse user controller or auto-create BaseController
|
|
2377
|
-
* 5. buildResource — construct ResourceDefinition + validate methods
|
|
2378
|
-
* 6. wireHooks — push preset + inline `config.hooks` onto _pendingHooks
|
|
2379
|
-
* 7. resolveOpenApiSchemas — adapter schemas → parser listQuery → user override
|
|
2380
|
-
*
|
|
2381
|
-
* Each phase has a single responsibility; `resolvedConfig` is the canonical
|
|
2382
|
-
* post-preset, post-auto-inject config that every later phase reads. Raw
|
|
2383
|
-
* `config` is only consulted for things presets don't touch (adapter,
|
|
2384
|
-
* skipRegistry, skipValidation, hooks — which are wired separately from
|
|
2385
|
-
* preset hooks).
|
|
2618
|
+
* Constructor input shape — `ResourceConfig` plus the metadata
|
|
2619
|
+
* Phases 3-6 stamp on it. Defined locally because the class
|
|
2620
|
+
* constructor is the only consumer; no other code path needs this
|
|
2621
|
+
* type.
|
|
2386
2622
|
*/
|
|
2387
|
-
declare function defineResource<TDoc = AnyRecord>(config: ResourceConfig<TDoc>): ResourceDefinition<TDoc>;
|
|
2388
2623
|
interface ResolvedResourceConfig<TDoc = AnyRecord> extends ResourceConfig<TDoc> {
|
|
2389
2624
|
_appliedPresets?: string[];
|
|
2390
2625
|
_controllerOptions?: {
|
|
@@ -2404,6 +2639,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
2404
2639
|
readonly displayName: string;
|
|
2405
2640
|
readonly tag: string;
|
|
2406
2641
|
readonly prefix: string;
|
|
2642
|
+
readonly skipGlobalPrefix: boolean;
|
|
2407
2643
|
readonly adapter?: DataAdapter<TDoc>;
|
|
2408
2644
|
readonly controller?: IController<TDoc>;
|
|
2409
2645
|
readonly schemaOptions: RouteSchemaOptions;
|
|
@@ -2413,9 +2649,10 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
2413
2649
|
readonly middlewares: MiddlewareConfig;
|
|
2414
2650
|
readonly routeGuards?: RouteHandlerMethod$1[];
|
|
2415
2651
|
readonly disableDefaultRoutes: boolean;
|
|
2416
|
-
readonly disabledRoutes: CrudRouteKey[];
|
|
2652
|
+
readonly disabledRoutes: readonly CrudRouteKey[];
|
|
2417
2653
|
readonly actions?: ActionsMap;
|
|
2418
2654
|
readonly actionPermissions?: PermissionCheck;
|
|
2655
|
+
readonly aggregations?: AggregationsMap;
|
|
2419
2656
|
readonly events: Record<string, EventDefinition>;
|
|
2420
2657
|
readonly rateLimit?: RateLimitConfig | false;
|
|
2421
2658
|
readonly audit?: boolean | {
|
|
@@ -2425,7 +2662,6 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
2425
2662
|
readonly pipe?: PipelineConfig;
|
|
2426
2663
|
readonly fields?: FieldPermissionMap;
|
|
2427
2664
|
readonly cache?: ResourceCacheConfig;
|
|
2428
|
-
readonly skipGlobalPrefix: boolean;
|
|
2429
2665
|
readonly tenantField?: string | false;
|
|
2430
2666
|
readonly idField?: string;
|
|
2431
2667
|
readonly queryParser?: QueryParserInterface;
|
|
@@ -2437,32 +2673,78 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
2437
2673
|
priority: number;
|
|
2438
2674
|
}>;
|
|
2439
2675
|
_registryMeta?: RegisterOptions;
|
|
2676
|
+
/**
|
|
2677
|
+
* Per-host idempotency guard used by `buildResourcePlugin` to
|
|
2678
|
+
* skip duplicate shared-state writes when the same resource is
|
|
2679
|
+
* mounted at multiple prefixes (`/v1`, `/v2`). See the plugin
|
|
2680
|
+
* file for the full rationale; surfaced here as `readonly` so
|
|
2681
|
+
* the helper can consult it without a class-method indirection.
|
|
2682
|
+
*/
|
|
2683
|
+
readonly _sharedStateRegisteredOn: WeakSet<object>;
|
|
2440
2684
|
constructor(config: ResolvedResourceConfig<TDoc>);
|
|
2441
|
-
/**
|
|
2442
|
-
get repository(): RepositoryLike<TDoc> | undefined;
|
|
2685
|
+
/** Repository accessor — pulled off the adapter when one is wired. */
|
|
2686
|
+
get repository(): _$_classytic_repo_core_adapter0.RepositoryLike<TDoc> | undefined;
|
|
2687
|
+
/**
|
|
2688
|
+
* Validate that the wired controller implements every method
|
|
2689
|
+
* needed by enabled CRUD routes + every string-handler custom
|
|
2690
|
+
* route. Runs at the end of `defineResource()` (skippable via
|
|
2691
|
+
* `skipValidation: true`) so misconfigured resources fail at
|
|
2692
|
+
* boot, not on first request.
|
|
2693
|
+
*/
|
|
2443
2694
|
_validateControllerMethods(): void;
|
|
2444
|
-
toPlugin(): FastifyPluginAsync;
|
|
2445
2695
|
/**
|
|
2446
|
-
*
|
|
2696
|
+
* Build the Fastify plugin that materialises this resource into
|
|
2697
|
+
* routes, hooks, registry entries, and cache invalidation rules.
|
|
2698
|
+
* One-line delegate — the implementation lives in `./plugin.ts`.
|
|
2447
2699
|
*/
|
|
2700
|
+
toPlugin(): FastifyPluginAsync;
|
|
2701
|
+
/** Event definitions for registry consumption. */
|
|
2448
2702
|
getEvents(): Array<{
|
|
2449
2703
|
name: string;
|
|
2450
2704
|
module: string;
|
|
2451
2705
|
schema?: unknown;
|
|
2452
2706
|
description?: string;
|
|
2453
2707
|
}>;
|
|
2454
|
-
/**
|
|
2455
|
-
* Get resource metadata
|
|
2456
|
-
*/
|
|
2708
|
+
/** Resource metadata — shape consumed by registry / introspection. */
|
|
2457
2709
|
getMetadata(): ResourceMetadata;
|
|
2458
2710
|
}
|
|
2459
2711
|
//#endregion
|
|
2712
|
+
//#region src/core/defineResource.d.ts
|
|
2713
|
+
/**
|
|
2714
|
+
* `TDoc` is **unconstrained** at this layer. The previous `TDoc
|
|
2715
|
+
* extends AnyRecord` bound leaked out of `BaseController`'s
|
|
2716
|
+
* mixin-composition requirement into every host's adapter boundary:
|
|
2717
|
+
* Mongoose's `HydratedDocument<T>`, Prisma's generated row types,
|
|
2718
|
+
* and any domain interface without an explicit index signature all
|
|
2719
|
+
* failed to satisfy `Record<string, unknown>` even though at runtime
|
|
2720
|
+
* they ARE string-keyed objects. Hosts were forced to cast at every
|
|
2721
|
+
* adapter (`as RepositoryLike<Record<string, unknown>>`) — a type
|
|
2722
|
+
* escape with no runtime purpose, since arc's pipeline only reads
|
|
2723
|
+
* known envelope fields.
|
|
2724
|
+
*
|
|
2725
|
+
* The cast moved inside `resolveOrAutoCreateController` where
|
|
2726
|
+
* `BaseController<TDoc extends AnyRecord>` actually requires it.
|
|
2727
|
+
* One internal boundary cast replaces N host-side casts.
|
|
2728
|
+
*/
|
|
2729
|
+
declare function defineResource<TDoc = AnyRecord>(config: ResourceConfig<TDoc>): ResourceDefinition<TDoc>;
|
|
2730
|
+
//#endregion
|
|
2460
2731
|
//#region src/registry/ResourceRegistry.d.ts
|
|
2461
2732
|
interface RegisterOptions {
|
|
2462
2733
|
module?: string;
|
|
2463
2734
|
/** Pre-generated OpenAPI schemas */
|
|
2464
2735
|
openApiSchemas?: OpenApiSchemas;
|
|
2465
2736
|
}
|
|
2737
|
+
/**
|
|
2738
|
+
* One enumerated wire route. Matches `ResourceMetadata.routes[]`'s shape so
|
|
2739
|
+
* it slots straight into `IntrospectionData` without re-mapping.
|
|
2740
|
+
*/
|
|
2741
|
+
interface RouteRow {
|
|
2742
|
+
method: string;
|
|
2743
|
+
path: string;
|
|
2744
|
+
operation?: string;
|
|
2745
|
+
handler?: string;
|
|
2746
|
+
summary?: string;
|
|
2747
|
+
}
|
|
2466
2748
|
declare class ResourceRegistry {
|
|
2467
2749
|
private _resources;
|
|
2468
2750
|
private _frozen;
|
|
@@ -2493,12 +2775,36 @@ declare class ResourceRegistry {
|
|
|
2493
2775
|
has(name: string): boolean;
|
|
2494
2776
|
/**
|
|
2495
2777
|
* Get registry statistics
|
|
2778
|
+
*
|
|
2779
|
+
* `totalRoutes` is derived from `enumerateRoutes()` — single source of
|
|
2780
|
+
* truth shared with `getIntrospection()` and consistent with what
|
|
2781
|
+
* OpenAPI / Fastify actually mount. New route sources (e.g. v2.13
|
|
2782
|
+
* aggregations) light up here automatically.
|
|
2496
2783
|
*/
|
|
2497
2784
|
getStats(): RegistryStats;
|
|
2498
2785
|
/**
|
|
2499
2786
|
* Get full introspection data
|
|
2787
|
+
*
|
|
2788
|
+
* Routes come from `enumerateRoutes()` so consumers see the complete
|
|
2789
|
+
* surface — CRUD + custom + actions + aggregations — and match what
|
|
2790
|
+
* `getStats()` counts.
|
|
2500
2791
|
*/
|
|
2501
2792
|
getIntrospection(): IntrospectionData;
|
|
2793
|
+
/**
|
|
2794
|
+
* Single source of truth for "what routes does this resource expose?".
|
|
2795
|
+
*
|
|
2796
|
+
* Enumerates every wire route the resource will mount on Fastify:
|
|
2797
|
+
* - default CRUD (respecting `disabledRoutes` + `updateMethod`)
|
|
2798
|
+
* - host-declared `customRoutes` (alias: `routes`)
|
|
2799
|
+
* - the unified `POST /:id/action` endpoint when `actions` is set
|
|
2800
|
+
* - one `GET /:resource/aggregations/:name` per declared aggregation
|
|
2801
|
+
*
|
|
2802
|
+
* Both `getStats()` and `getIntrospection()` consume this list, so a
|
|
2803
|
+
* new route source (e.g. future webhook routes) only has to be added
|
|
2804
|
+
* here — the count and the introspection contract update together.
|
|
2805
|
+
* Mirrors the same set of paths emitted by `docs/openapi.ts`.
|
|
2806
|
+
*/
|
|
2807
|
+
enumerateRoutes(r: RegistryEntry): RouteRow[];
|
|
2502
2808
|
/**
|
|
2503
2809
|
* Freeze registry (prevent further registrations)
|
|
2504
2810
|
*/
|
|
@@ -2870,13 +3176,13 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
2870
3176
|
disableDefaultRoutes?: boolean;
|
|
2871
3177
|
openApiSchemas?: OpenApiSchemas;
|
|
2872
3178
|
registeredAt?: string;
|
|
2873
|
-
/** Field-level permissions metadata (for OpenAPI
|
|
3179
|
+
/** Field-level permissions metadata (for OpenAPI data) */
|
|
2874
3180
|
fieldPermissions?: Record<string, {
|
|
2875
3181
|
type: string;
|
|
2876
3182
|
roles?: readonly string[];
|
|
2877
3183
|
redactValue?: unknown;
|
|
2878
3184
|
}>;
|
|
2879
|
-
/** Pipeline step names (for OpenAPI
|
|
3185
|
+
/** Pipeline step names (for OpenAPI data) */
|
|
2880
3186
|
pipelineSteps?: Array<{
|
|
2881
3187
|
type: string;
|
|
2882
3188
|
name: string;
|
|
@@ -2914,6 +3220,34 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
2914
3220
|
* MCP as the fallback in `createActionToolHandler`. Added in 2.8.1.
|
|
2915
3221
|
*/
|
|
2916
3222
|
actionPermissions?: PermissionCheck;
|
|
3223
|
+
/**
|
|
3224
|
+
* Aggregation route metadata (v2.13). Mirrors the runtime config in
|
|
3225
|
+
* a doc-friendly shape so OpenAPI emission and MCP tool generation
|
|
3226
|
+
* read from one source.
|
|
3227
|
+
*
|
|
3228
|
+
* Each entry corresponds to a `GET /:resource/aggregations/:name`
|
|
3229
|
+
* route. Response shape (rows array of objects keyed by groupBy +
|
|
3230
|
+
* measure aliases) is derived at OpenAPI emission time from
|
|
3231
|
+
* `groupBy` + `measures` + `lookups`.
|
|
3232
|
+
*/
|
|
3233
|
+
aggregations?: Array<{
|
|
3234
|
+
readonly name: string;
|
|
3235
|
+
readonly summary?: string;
|
|
3236
|
+
readonly description?: string;
|
|
3237
|
+
readonly permissions: PermissionCheck;
|
|
3238
|
+
readonly groupBy?: string | readonly string[]; /** Measure aliases keyed to their op-tag (e.g. `'count'`, `'sum:price'`). */
|
|
3239
|
+
readonly measures: Readonly<Record<string, string>>; /** Lookup alias names (`as` or `from`) — used by OpenAPI to know which dotted-path output keys nest. */
|
|
3240
|
+
readonly lookupAliases: readonly string[]; /** Whether the aggregation requires a date range — surfaced in docs. */
|
|
3241
|
+
readonly requireDateRange?: {
|
|
3242
|
+
field: string;
|
|
3243
|
+
maxRangeDays?: number;
|
|
3244
|
+
}; /** Whether the aggregation requires named filters — surfaced in docs. */
|
|
3245
|
+
readonly requireFilters?: readonly string[]; /** MCP tool generation flag — `false` to skip, object for overrides. */
|
|
3246
|
+
readonly mcp?: boolean | {
|
|
3247
|
+
readonly description?: string;
|
|
3248
|
+
readonly annotations?: Record<string, unknown>;
|
|
3249
|
+
};
|
|
3250
|
+
}>;
|
|
2917
3251
|
}
|
|
2918
3252
|
interface RegistryStats {
|
|
2919
3253
|
total?: number;
|
|
@@ -2997,4 +3331,4 @@ interface ValidateOptions {
|
|
|
2997
3331
|
strict?: boolean;
|
|
2998
3332
|
}
|
|
2999
3333
|
//#endregion
|
|
3000
|
-
export { OpenApiSchemas as $,
|
|
3334
|
+
export { OpenApiSchemas as $, SoftDeleteMixin as $t, RequestIdOptions as A, afterUpdate as An, Guard as At, defineResource as B, Authenticator as Bt, RequestContext as C, HookOperation as Cn, AggregationConfig as Ct, HealthCheck as D, HookSystemOptions as Dn, AggregationMaterializedResult as Dt, GracefulShutdownOptions as E, HookSystem as En, AggregationMaterializedContext as Et, FastifyWithDecorators as F, defineHook as Fn, PipelineContext as Ft, ActionsMap as G, ApiResponse as Gt, ActionDefinition as H, JwtContext as Ht, MiddlewareHandler as I, PipelineStep as It, CrudRouteKey as J, ObjectId as Jt, ArcFieldRule as K, ArcRequest as Kt, RequestWithExtras as L, Transform as Lt, EventsDecorator as M, beforeDelete as Mn, NextFunction as Mt, FastifyRequestExtras as N, beforeUpdate as Nn, OperationFilter as Nt, HealthOptions as O, afterCreate as On, AggregationRateLimit as Ot, FastifyWithAuth as P, createHookSystem as Pn, PipelineConfig as Pt, MiddlewareConfig as Q, SoftDeleteExt as Qt, RegisterOptions as R, AuthHelpers as Rt, QueryParserInterface as S, HookHandler as Sn, AggregationCacheConfig as St, CrudRouterOptions as T, HookRegistration as Tn, AggregationIndexHint as Tt, ActionEntry as U, TokenPair as Ut, ResourceDefinition as V, AuthenticatorContext as Vt, ActionHandlerFn as W, AnyRecord as Wt, EventDefinition as X, UserOrganization as Xt, CrudSchemas as Y, UserLike as Yt, FieldRule$1 as Z, BaseController as Zt, ControllerQueryOptions as _, BodySanitizerConfig as _n, IControllerResponse as _t, InferAdapterDoc as a, BulkMixin as an, ResourceConfig as at, ParsedQuery as b, DefineHookOptions as bn, AggMeasureInput as bt, TypedController as c, QueryResolver as cn, ResourcePermissions as ct, PaginationResult as d, ArcDeleteResult as dn, RouteMethod as dt, TreeExt as en, PresetFunction as et, IntrospectionData as f, ArcGetResult as fn, RouteSchemaOptions as ft, ArcInternalMetadata as g, BodySanitizer as gn, IController as gt, ResourceMetadata as h, ListResult as hn, FastifyHandler as ht, ValidationResult as i, BulkExt as in, ResourceCacheConfig as it, ArcDecorator as j, beforeCreate as jn, Interceptor as jt, IntrospectionPluginOptions as k, afterDelete as kn, AggregationsMap as kt, TypedRepository as l, QueryResolverConfig as ln, RouteDefinition as lt, RegistryStats as m, ArcUpdateResult as mn, ControllerLike as mt, ConfigError as n, SlugExt as nn, PresetResult as nt, InferDocType as o, BaseControllerOptions as on, ResourceHookContext as ot, RegistryEntry as p, ArcListResult as pn, ControllerHandler as pt, CrudController as q, JWTPayload as qt, ValidateOptions as r, SlugMixin as rn, RateLimitConfig as rt, InferResourceDoc as s, BaseCrudController as sn, ResourceHooks as st, RouteHandlerMethod$1 as t, TreeMixin as tn, PresetHook as tt, TypedResourceConfig as u, ArcCreateResult as un, RouteMcpConfig as ut, LookupOption as v, AccessControl as vn, IRequestContext as vt, ServiceContext as w, HookPhase as wn, AggregationDateRangeRequirement as wt, PopulateOption as x, HookContext as xn, AggMeasureShorthand as xt, OwnershipCheck as y, AccessControlConfig as yn, RouteHandler as yt, ResourceRegistry as z, AuthPluginOptions as zt };
|