@classytic/arc 2.20.0 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BaseController-D-MHZ27_.mjs → BaseController-C6FlRWxF.mjs} +2 -2
- package/dist/{EventTransport-IEn7Tn-Q.d.mts → EventTransport-QmY7A258.d.mts} +0 -1
- package/dist/{QueryCache-TRfXau3W.d.mts → QueryCache-Z1B38koh.d.mts} +0 -1
- package/dist/{actionPermissions-C9RMiIpc.mjs → actionPermissions-ze70xXJi.mjs} +1 -1
- package/dist/audit/index.d.mts +9 -5
- package/dist/auth/audit.d.mts +7 -4
- package/dist/auth/audit.mjs +5 -0
- package/dist/auth/index.d.mts +35 -14
- package/dist/auth/index.mjs +6 -3
- package/dist/auth/redis-session.d.mts +1 -2
- package/dist/cache/index.d.mts +2 -3
- package/dist/cli/commands/describe.d.mts +1 -2
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/context/index.d.mts +2 -2
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -4
- package/dist/{core-CWhyGkiM.mjs → core-b6fgFgnd.mjs} +129 -17
- package/dist/{createActionRouter-HlQso0jO.mjs → createActionRouter-h1cEEaEX.mjs} +10 -6
- package/dist/{createAggregationRouter-5LzPzgw1.mjs → createAggregationRouter-B1C0kvTL.mjs} +3 -3
- package/dist/{createApp-D6uwzCKu.mjs → createApp-Ci7yG5gv.mjs} +390 -192
- package/dist/discovery/index.d.mts +0 -1
- package/dist/docs/index.d.mts +1 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/{elevation-xGUBq6p_.d.mts → elevation-C9s5hFP_.d.mts} +0 -1
- package/dist/encryption/index.d.mts +2 -144
- package/dist/encryption/index.mjs +9 -5
- package/dist/{errorHandler-Dxf6P_kV.mjs → errorHandler-BTzXSwIg.mjs} +24 -3
- package/dist/{errorHandler-BtUUDPEa.d.mts → errorHandler-C6yv4ZTW.d.mts} +13 -1
- package/dist/{errors-DOHkjQlD.d.mts → errors-DHcHVAgk.d.mts} +0 -1
- package/dist/{eventPlugin-DbBFd3x_.d.mts → eventPlugin-A6EbthXR.d.mts} +10 -6
- package/dist/events/index.d.mts +9 -6
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -2
- package/dist/factory/index.d.mts +30 -9
- package/dist/factory/index.mjs +35 -4
- package/dist/{fastify-augmentation-CCQJEDgh.d.mts → fastify-augmentation-9_fuetB4.d.mts} +2 -3
- package/dist/fields-D3lhgzhD.d.mts +121 -0
- package/dist/health-ScyjIVo5.mjs +202 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +4 -3
- package/dist/idempotency/index.mjs +2 -0
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{types-BHMKlMBU.d.mts → index-BEu4wjbL.d.mts} +241 -80
- package/dist/{index-DUa8WHz3.d.mts → index-D8MKYGDa.d.mts} +88 -7
- package/dist/{index-_--GyzcO.d.mts → index-DbtgCOCj.d.mts} +1324 -1228
- package/dist/{index-CdFkIMEd.d.mts → index-ogIHvOjo.d.mts} +36 -7
- package/dist/index.d.mts +8 -7
- package/dist/index.mjs +6 -6
- package/dist/{init-YH_nslQR.mjs → init-DleK3p9f.mjs} +272 -139
- package/dist/integrations/event-gateway.d.mts +2 -3
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/jobs.d.mts +0 -1
- package/dist/integrations/mcp/index.d.mts +6 -7
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -2
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +2 -2
- package/dist/integrations/webhooks.d.mts +8 -5
- package/dist/integrations/webhooks.mjs +9 -5
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -2
- package/dist/integrations/websocket-redis.d.mts +1 -2
- package/dist/integrations/websocket.d.mts +2 -2
- package/dist/jsonBody-BlzAoGCe.mjs +43 -0
- package/dist/{loadResources-B8UmpITt.mjs → loadResources-DpU8pfjV.mjs} +4 -3
- package/dist/logger/index.d.mts +26 -1
- package/dist/logger/index.mjs +39 -1
- package/dist/{mcpPlugin-CuW6L_uD.mjs → mcpPlugin-ByA2tf6D.mjs} +8 -4
- package/dist/middleware/index.d.mts +1 -2
- package/dist/{module-JYH9V0I8.d.mts → module-B4u10iOJ.d.mts} +71 -1
- package/dist/{openapi-Dq6RbEGK.mjs → openapi-BRpMZD7C.mjs} +3 -3
- package/dist/permissions/index.d.mts +4 -3
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-CgbOLJZG.mjs → permissions-BJMuKMl7.mjs} +33 -1
- package/dist/pipeline/index.d.mts +1 -2
- package/dist/plugins/index.d.mts +96 -7
- package/dist/plugins/index.mjs +86 -268
- package/dist/plugins/response-cache.d.mts +5 -3
- package/dist/plugins/response-cache.mjs +1 -0
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +7 -4
- package/dist/presets/filesUpload.d.mts +4 -5
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +1 -2
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -2
- package/dist/presets/search.d.mts +2 -3
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-DWsYQdbV.mjs → presets-gw0ciIUD.mjs} +1 -1
- package/dist/{pushref-registry-DnCqL3aa.d.mts → pushref-registry-BbcnpNTm.d.mts} +4 -2
- package/dist/{queryCachePlugin-xzFyKAjT.d.mts → queryCachePlugin-CfYWq-Ak.d.mts} +1 -2
- package/dist/rateLimitKey-BoWRH3e7.mjs +14 -0
- package/dist/{redis-C3W0qbz8.d.mts → redis-UDtLaNj6.d.mts} +0 -1
- package/dist/{redis-stream-DsP1vqZB.d.mts → redis-stream-iVR2l7aS.d.mts} +1 -2
- package/dist/{registerRoute-DGSREwPw.mjs → registerRoute-xHc42fwB.mjs} +38 -18
- package/dist/registry/index.d.mts +2 -2
- package/dist/requestId-5BhTVzBv.mjs +113 -0
- package/dist/schemaConverter-Ds5reSGs.mjs +168 -0
- package/dist/{schemaIR-ajetw3Fv.mjs → schemaIR-B-6SPe7g.mjs} +2 -2
- package/dist/schemas/index.d.mts +0 -1
- package/dist/scim/index.d.mts +0 -1
- package/dist/scim/index.mjs +6 -7
- package/dist/scope/index.d.mts +2 -3
- package/dist/scope/index.mjs +2 -76
- package/dist/scope-k15RXfcq.mjs +64 -0
- package/dist/{sessionManager-BXG-HGwG.d.mts → sessionManager-BHOM3N_0.d.mts} +10 -6
- package/dist/{storage-Co_PQpeQ.d.mts → storage-DLUZ04kK.d.mts} +8 -4
- package/dist/testing/index.d.mts +83 -4
- package/dist/testing/index.mjs +74 -4
- package/dist/testing/storageContract.d.mts +1 -2
- package/dist/{tracing-D8jlGXP7.d.mts → tracing-CsGKQJGg.d.mts} +0 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-C9b2HR5W.d.mts → types-Bc42CUyk.d.mts} +1 -2
- package/dist/types-Cc91nXuA.d.mts +132 -0
- package/dist/{fields-C7ahdGYf.d.mts → types-Csxga7d3.d.mts} +2 -123
- package/dist/{types-LNebCoJx.d.mts → types-DH0GupI7.d.mts} +8 -4
- package/dist/usage/index.d.mts +142 -0
- package/dist/usage/index.mjs +0 -0
- package/dist/utils/index.d.mts +220 -144
- package/dist/utils/index.mjs +3 -3
- package/dist/{utils-ChjYQpjn.mjs → utils-FhJ88TJE.mjs} +129 -39
- package/dist/{versioning-D_eBCfNr.d.mts → versioning-DLwklPlG.d.mts} +1 -7
- package/dist/{websocket-MlYlrYq0.d.mts → websocket-uuWqM6UY.d.mts} +5 -4
- package/llms.txt +39 -0
- package/package.json +453 -447
- package/skills/arc/SKILL.md +14 -8
- package/skills/arc/references/api-reference.md +34 -0
- package/skills/arc/references/auth.md +21 -4
- package/skills/arc/references/mcp.md +1 -1
- package/skills/arc/references/production.md +44 -2
- package/skills/arc-code-review/SKILL.md +1 -1
- package/skills/arc-code-review/references/anti-patterns.md +71 -18
- package/skills/arc-code-review/references/arc-cheatsheet.md +15 -2
- package/skills/arc-code-review/references/migration-recipes.md +7 -7
- package/skills/arc-code-review/references/mongokit-migration.md +4 -3
- package/skills/arc-module-publishing/SKILL.md +243 -0
- package/skills/arc-module-publishing/references/authoring-modules.md +327 -0
- package/skills/arc-module-publishing/references/package-manifest.md +157 -0
- package/skills/arc-module-publishing/references/publishing-workflow.md +182 -0
- package/dist/preloadResources-CHw3_oA_.mjs +0 -45
- package/dist/schemaConverter-U0MnO9vd.mjs +0 -110
|
@@ -2,8 +2,8 @@ import { h as SYSTEM_FIELDS, o as DEFAULT_TENANT_FIELD } from "./constants-DNre0
|
|
|
2
2
|
import { arcLog } from "./logger/index.mjs";
|
|
3
3
|
import { d as createDomainError, f as createError, i as NotFoundError, r as ForbiddenError } from "./errors-DkO8Nk6-.mjs";
|
|
4
4
|
import { b as isElevated, c as getOrgId, n as PUBLIC_SCOPE, x as isMember } from "./types-bHblZ9YN.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { F as simpleEqualityMatcher, _ as ArcQueryParser, r as scheduleBackground, t as getUserId } from "./utils-FhJ88TJE.mjs";
|
|
6
|
+
import { I as applyFieldWritePermissions, R as resolveEffectiveRoles } from "./permissions-BJMuKMl7.mjs";
|
|
7
7
|
import { t as getUserRoles } from "./types-XTApa8b6.mjs";
|
|
8
8
|
import { t as buildQueryKey } from "./keys-Cd316u_y.mjs";
|
|
9
9
|
//#region src/core/AccessControl.ts
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DeadLetteredEvent, DomainEvent, DomainEvent as DomainEvent$1, EventHandler, EventHandler as EventHandler$1, EventLogger, EventLogger as EventLogger$1, EventTransport, EventTransport as EventTransport$1, PublishManyResult } from "@classytic/primitives/events";
|
|
2
|
-
|
|
3
2
|
//#region src/events/EventTransport.d.ts
|
|
4
3
|
interface MemoryEventTransportOptions {
|
|
5
4
|
/** Logger for error/warning messages (default: console) */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { b as isElevated, c as getOrgId, g as getUserId, n as PUBLIC_SCOPE, x as isMember } from "./types-bHblZ9YN.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { F as applyFieldReadPermissions, R as resolveEffectiveRoles } from "./permissions-BJMuKMl7.mjs";
|
|
3
3
|
import { t as getUserRoles } from "./types-XTApa8b6.mjs";
|
|
4
4
|
import { t as executePipeline } from "./pipe-BOLmGIxa.mjs";
|
|
5
5
|
import { isPaginatedResult, toCanonicalList } from "@classytic/repo-core/pagination";
|
package/dist/audit/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../index-DbtgCOCj.mjs";
|
|
2
|
+
import { i as UserBase } from "../types-Csxga7d3.mjs";
|
|
2
3
|
import { FastifyPluginAsync } from "fastify";
|
|
3
4
|
import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
4
|
-
|
|
5
5
|
//#region src/audit/stores/interface.d.ts
|
|
6
6
|
type AuditAction = "create" | "update" | "delete" | "restore" | "custom";
|
|
7
7
|
interface AuditEntry {
|
|
@@ -126,7 +126,9 @@ interface AuditPluginOptions {
|
|
|
126
126
|
* Set `purgeIntervalMs: 0` to skip the timer (manual purge only).
|
|
127
127
|
*/
|
|
128
128
|
retention?: {
|
|
129
|
-
/** Max entry age in ms. Entries with `timestamp < now - maxAgeMs` are purged. */
|
|
129
|
+
/** Max entry age in ms. Entries with `timestamp < now - maxAgeMs` are purged. */
|
|
130
|
+
maxAgeMs: number;
|
|
131
|
+
/** Interval between purges in ms. Default 86_400_000 (24h). 0 disables the timer. */
|
|
130
132
|
purgeIntervalMs?: number;
|
|
131
133
|
};
|
|
132
134
|
/**
|
|
@@ -159,8 +161,10 @@ interface AuditPluginOptions {
|
|
|
159
161
|
* - `false`: Disable auto-audit (manual `fastify.audit.*()` calls only)
|
|
160
162
|
*/
|
|
161
163
|
autoAudit?: boolean | {
|
|
162
|
-
operations?: ("create" | "update" | "delete")[];
|
|
163
|
-
|
|
164
|
+
operations?: ("create" | "update" | "delete")[];
|
|
165
|
+
/** Allowlist — only listed resources are audited (mutually exclusive with exclude) */
|
|
166
|
+
include?: string[];
|
|
167
|
+
/** Denylist — audit everything except listed resources */
|
|
164
168
|
exclude?: string[];
|
|
165
169
|
/**
|
|
166
170
|
* Per-resource opt-in mode: only audit resources with `audit: true` in their
|
package/dist/auth/audit.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { FastifyInstance } from "fastify";
|
|
2
|
-
|
|
3
2
|
//#region src/auth/audit.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Canonical auth event names emitted by the bridge. Hosts pattern-match
|
|
@@ -143,9 +142,13 @@ interface BetterAuthAuditBridge {
|
|
|
143
142
|
* is called again. Useful for Prometheus exporters that scrape periodically.
|
|
144
143
|
*/
|
|
145
144
|
getStats(): {
|
|
146
|
-
/** Events buffered during pre-`attach` phase that were dropped due to `bufferSize` overflow. */
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
/** Events buffered during pre-`attach` phase that were dropped due to `bufferSize` overflow. */
|
|
146
|
+
droppedFromBuffer: number;
|
|
147
|
+
/** Events that reached `app.audit.custom(...)` but threw (audit store failure). */
|
|
148
|
+
dispatchFailures: number;
|
|
149
|
+
/** Total `dispatch` calls — useful to verify BA is actually invoking the bridge's hooks. */
|
|
150
|
+
dispatchAttempts: number;
|
|
151
|
+
/** Events currently buffered awaiting `attach`. */
|
|
149
152
|
pendingBuffered: number;
|
|
150
153
|
};
|
|
151
154
|
}
|
package/dist/auth/audit.mjs
CHANGED
|
@@ -100,6 +100,11 @@ const ENDPOINT_TO_EVENT = [
|
|
|
100
100
|
test: /\/api-key\/delete\b/,
|
|
101
101
|
phase: "after",
|
|
102
102
|
name: "apikey.delete"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
test: /\/sign-out\b/,
|
|
106
|
+
phase: "after",
|
|
107
|
+
name: "session.delete"
|
|
103
108
|
}
|
|
104
109
|
];
|
|
105
110
|
function classifyEndpoint(phase, ctx) {
|
package/dist/auth/index.d.mts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Ht as AuthPluginOptions, Vt as AuthHelpers } from "../index-
|
|
2
|
-
import {
|
|
1
|
+
import { Ht as AuthPluginOptions, Vt as AuthHelpers } from "../index-DbtgCOCj.mjs";
|
|
2
|
+
import { t as PermissionCheck } from "../types-Csxga7d3.mjs";
|
|
3
3
|
import { t as ExternalOpenApiPaths } from "../externalPaths-BE6wFpYv.mjs";
|
|
4
|
-
import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-
|
|
4
|
+
import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-BHOM3N_0.mjs";
|
|
5
5
|
import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest } from "fastify";
|
|
6
|
-
|
|
7
6
|
//#region src/auth/authPlugin.d.ts
|
|
8
7
|
declare module "fastify" {
|
|
9
8
|
interface FastifyInstance {
|
|
@@ -17,14 +16,6 @@ declare module "fastify" {
|
|
|
17
16
|
auth: AuthHelpers;
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Extract Bearer token from Authorization header.
|
|
22
|
-
*
|
|
23
|
-
* Exported for property-based test coverage — the contract is:
|
|
24
|
-
* - header must start with exactly `"Bearer "` (case-sensitive, one space)
|
|
25
|
-
* - everything after that prefix is returned verbatim (no trim, no parse)
|
|
26
|
-
* - missing header → `null`; any other shape → `null`
|
|
27
|
-
*/
|
|
28
19
|
declare const authPlugin: FastifyPluginAsync<AuthPluginOptions>;
|
|
29
20
|
declare const _default: FastifyPluginAsync<AuthPluginOptions>;
|
|
30
21
|
//#endregion
|
|
@@ -93,6 +84,34 @@ interface BetterAuthAdapterOptions {
|
|
|
93
84
|
* When true, includes the actual error message for debugging.
|
|
94
85
|
*/
|
|
95
86
|
exposeAuthErrors?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Per-route rate limit for the Better Auth catch-all (`/api/auth/*`),
|
|
89
|
+
* applied via `@fastify/rate-limit`'s route config (requires the
|
|
90
|
+
* limiter to be registered, which createApp does by default).
|
|
91
|
+
*
|
|
92
|
+
* Credential endpoints deserve a tighter bucket than the global limit
|
|
93
|
+
* (brute-force / credential-stuffing surface), BUT the same catch-all
|
|
94
|
+
* also serves Better Auth's chatty `get-session` heartbeat — so arc
|
|
95
|
+
* ships NO default here; a forced strict limit (the classic "5/min on
|
|
96
|
+
* /login") would 429 every SPA that polls sessions. Pick a ceiling that
|
|
97
|
+
* covers your session-poll cadence, or key the bucket smarter:
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* createBetterAuthAdapter({
|
|
102
|
+
* auth,
|
|
103
|
+
* // simple ceiling over the whole auth surface
|
|
104
|
+
* rateLimit: { max: 60, timeWindow: '1 minute' },
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* // OR keep the global limiter but exempt the heartbeat:
|
|
108
|
+
* createApp({ rateLimit: { max: 100, skipPaths: ['/api/auth/get-session'] } });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
rateLimit?: {
|
|
112
|
+
max: number;
|
|
113
|
+
timeWindow: string | number;
|
|
114
|
+
} | false;
|
|
96
115
|
}
|
|
97
116
|
interface BetterAuthAdapterResult {
|
|
98
117
|
/** Fastify plugin that registers catch-all auth routes */
|
|
@@ -177,8 +196,10 @@ interface BetterAuthOpenApiOptions {
|
|
|
177
196
|
*/
|
|
178
197
|
userFields?: Record<string, {
|
|
179
198
|
type: string;
|
|
180
|
-
description?: string;
|
|
181
|
-
|
|
199
|
+
description?: string;
|
|
200
|
+
/** Whether this field is required in sign-up (default: false) */
|
|
201
|
+
required?: boolean;
|
|
202
|
+
/** Whether this field is accepted in request body (default: true). Set false for output-only fields. */
|
|
182
203
|
input?: boolean;
|
|
183
204
|
}>;
|
|
184
205
|
}
|
package/dist/auth/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { d as createDomainError, l as UnauthorizedError, p as isArcError, t as ArcError } from "../errors-DkO8Nk6-.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { h as requireOrgRole, m as requireOrgMembership, v as requireTeamMembership } from "../permissions-BJMuKMl7.mjs";
|
|
3
3
|
import { n as normalizeRoles, t as getUserRoles } from "../types-XTApa8b6.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { o as toJsonSchema } from "../schemaConverter-Ds5reSGs.mjs";
|
|
5
5
|
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
6
6
|
import fp from "fastify-plugin";
|
|
7
7
|
//#region src/auth/authPlugin.ts
|
|
@@ -605,7 +605,8 @@ function createBetterAuthAdapter(options) {
|
|
|
605
605
|
if (openapiOpt === false) extractedOpenApi = void 0;
|
|
606
606
|
else if (typeof openapiOpt === "object") extractedOpenApi = openapiOpt;
|
|
607
607
|
const betterAuthPlugin = async (fastify) => {
|
|
608
|
-
|
|
608
|
+
const routeOpts = options.rateLimit && typeof options.rateLimit === "object" ? { config: { rateLimit: options.rateLimit } } : {};
|
|
609
|
+
fastify.all(`${normalizedBase}/*`, routeOpts, async (request, reply) => {
|
|
609
610
|
try {
|
|
610
611
|
const fetchRequest = toFetchRequest(request);
|
|
611
612
|
await sendFetchResponse(await auth.handler(fetchRequest), reply);
|
|
@@ -619,6 +620,8 @@ function createBetterAuthAdapter(options) {
|
|
|
619
620
|
});
|
|
620
621
|
if (!fastify.hasDecorator("authenticate")) fastify.decorate("authenticate", authenticate);
|
|
621
622
|
if (!fastify.hasDecorator("optionalAuthenticate")) fastify.decorate("optionalAuthenticate", optionalAuthenticate);
|
|
623
|
+
if (!fastify.hasRequestDecorator("user")) fastify.decorateRequest("user", void 0);
|
|
624
|
+
if (!fastify.hasRequestDecorator("session")) fastify.decorateRequest("session", void 0);
|
|
622
625
|
if (!extractedOpenApi && openapiOpt !== false && auth.api && typeof auth.api === "object") {
|
|
623
626
|
const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi-CV1lxpRX.mjs");
|
|
624
627
|
extractedOpenApi = extractBetterAuthOpenApi(auth.api, {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { i as SessionData, s as SessionStore } from "../sessionManager-
|
|
2
|
-
|
|
1
|
+
import { i as SessionData, s as SessionStore } from "../sessionManager-BHOM3N_0.mjs";
|
|
3
2
|
//#region src/auth/redis-session.d.ts
|
|
4
3
|
/** Minimal Redis client interface — compatible with ioredis */
|
|
5
4
|
interface RedisLike {
|
package/dist/cache/index.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { n as CacheStats, r as CacheStore, t as CacheLogger } from "../interface-C1_WLTMK.mjs";
|
|
2
|
-
import { a as QueryCacheConfig, i as QueryCache, n as CacheResult, r as CacheStatus, t as CacheEnvelope } from "../QueryCache-
|
|
3
|
-
import { i as queryCachePlugin, n as QueryCacheDefaults, r as QueryCachePluginOptions, t as CrossResourceRule } from "../queryCachePlugin-
|
|
4
|
-
|
|
2
|
+
import { a as QueryCacheConfig, i as QueryCache, n as CacheResult, r as CacheStatus, t as CacheEnvelope } from "../QueryCache-Z1B38koh.mjs";
|
|
3
|
+
import { i as queryCachePlugin, n as QueryCacheDefaults, r as QueryCachePluginOptions, t as CrossResourceRule } from "../queryCachePlugin-CfYWq-Ak.mjs";
|
|
5
4
|
//#region src/cache/keys.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* Cache Key Utilities
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { Nt as RateLimitConfig, V as ResourceDefinition, et as RouteSchemaOptions } from "../../index-DbtgCOCj.mjs";
|
|
3
2
|
//#region src/cli/commands/describe.d.ts
|
|
4
3
|
interface DescribedResource {
|
|
5
4
|
name: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as buildOpenApiSpec } from "../../openapi-
|
|
1
|
+
import { t as buildOpenApiSpec } from "../../openapi-BRpMZD7C.mjs";
|
|
2
2
|
import { t as loadResourcesFromEntry } from "../../loadResourcesFromEntry-OMCvIEE7.mjs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as init } from "../../init-
|
|
1
|
+
import { t as init } from "../../init-DleK3p9f.mjs";
|
|
2
2
|
export { init };
|
package/dist/cli/index.mjs
CHANGED
|
@@ -2,6 +2,6 @@ import { describe } from "./commands/describe.mjs";
|
|
|
2
2
|
import { exportDocs } from "./commands/docs.mjs";
|
|
3
3
|
import { doctor } from "./commands/doctor.mjs";
|
|
4
4
|
import { t as generate } from "../generate-usRSdQmP.mjs";
|
|
5
|
-
import { t as init } from "../init-
|
|
5
|
+
import { t as init } from "../init-DleK3p9f.mjs";
|
|
6
6
|
import { introspect } from "./commands/introspect.mjs";
|
|
7
7
|
export { describe, doctor, exportDocs, generate, init, introspect };
|
package/dist/context/index.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
|
|
3
2
|
//#region src/context/requestContext.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Shape of the request-scoped context store.
|
|
@@ -104,7 +103,8 @@ declare const transactionContext: {
|
|
|
104
103
|
* Get the current DB session, or `undefined` if not inside a
|
|
105
104
|
* `transactionContext.run(...)` scope.
|
|
106
105
|
*/
|
|
107
|
-
get(): DbSession | undefined;
|
|
106
|
+
get(): DbSession | undefined;
|
|
107
|
+
/** The underlying AsyncLocalStorage instance. Exposed for advanced integrations. */
|
|
108
108
|
storage: AsyncLocalStorage<unknown>;
|
|
109
109
|
};
|
|
110
110
|
//#endregion
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as
|
|
3
|
-
export { AccessControl, type AccessControlConfig, type AggMeasureInput, type AggMeasureShorthand, type AggregationCacheConfig, type AggregationConfig, type AggregationDateRangeRequirement, type AggregationIndexHint, type AggregationMaterializedContext, type AggregationMaterializedResult, type AggregationRateLimit, type AggregationsMap, BaseController, type BaseControllerOptions, BaseCrudController, BodySanitizer, type BodySanitizerConfig, type BulkExt, BulkMixin, CRUD_OPERATIONS, type ControllerConfigurableOptions, type ControllerConstructionOptions, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, type DefineActionConfig, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, type ListResult, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, type QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, type SlugExt, SlugMixin, type SoftDeleteExt, SoftDeleteMixin, type TreeExt, TreeMixin, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAction, defineAggregation, defineResource, defineResourceVariants, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, sendControllerResponse };
|
|
1
|
+
import { B as defineResource, Bn as BodySanitizerConfig, Fn as BaseControllerOptions, In as ControllerConfigurableOptions, Ln as ControllerConstructionOptions, Rn as ListResult, V as ResourceDefinition, an as SlugExt, cn as BulkMixin, ct as AggMeasureShorthand, dn as QueryResolverConfig, dt as AggregationDateRangeRequirement, en as BaseController, fn as AccessControl, ft as AggregationIndexHint, gt as AggregationsMap, ht as AggregationRateLimit, in as TreeMixin, ln as BaseCrudController, lt as AggregationCacheConfig, mt as AggregationMaterializedResult, nn as SoftDeleteMixin, on as SlugMixin, pn as AccessControlConfig, pt as AggregationMaterializedContext, rn as TreeExt, sn as BulkExt, st as AggMeasureInput, tn as SoftDeleteExt, un as QueryResolver, ut as AggregationConfig, zn as BodySanitizer } from "../index-DbtgCOCj.mjs";
|
|
2
|
+
import { A as HOOK_OPERATIONS, B as SYSTEM_FIELDS, C as CrudOperation, D as DEFAULT_SORT, E as DEFAULT_MAX_LIMIT, F as MAX_REGEX_LENGTH, I as MAX_SEARCH_LENGTH, L as MUTATION_OPERATIONS, M as HookOperation, N as HookPhase, O as DEFAULT_TENANT_FIELD, P as MAX_FILTER_DEPTH, R as MutationOperation, S as CRUD_OPERATIONS, T as DEFAULT_LIMIT, _ as DefineActionConfig, a as createRequestContext, b as createPermissionMiddleware, c as sendControllerResponse, d as getEntityQuery, f as defineResourceVariants, g as mergeResourceConfig, h as SeamedResourceConfig, i as createFastifyHandler, j as HOOK_PHASES, k as DEFAULT_UPDATE_METHOD, l as getEntityId, m as ResourceSeams, n as isFieldReadable, o as getControllerContext, p as AdapterLike, r as createCrudHandlers, s as getControllerScope, t as collectReadBlockedFields, u as getEntityIdField, v as defineAction, w as DEFAULT_ID_FIELD, x as defineAggregation, y as createCrudRouter, z as RESERVED_QUERY_PARAMS } from "../index-D8MKYGDa.mjs";
|
|
3
|
+
export { AccessControl, type AccessControlConfig, type AdapterLike, type AggMeasureInput, type AggMeasureShorthand, type AggregationCacheConfig, type AggregationConfig, type AggregationDateRangeRequirement, type AggregationIndexHint, type AggregationMaterializedContext, type AggregationMaterializedResult, type AggregationRateLimit, type AggregationsMap, BaseController, type BaseControllerOptions, BaseCrudController, BodySanitizer, type BodySanitizerConfig, type BulkExt, BulkMixin, CRUD_OPERATIONS, type ControllerConfigurableOptions, type ControllerConstructionOptions, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, type DefineActionConfig, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, type ListResult, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, type QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, type ResourceSeams, SYSTEM_FIELDS, type SeamedResourceConfig, type SlugExt, SlugMixin, type SoftDeleteExt, SoftDeleteMixin, type TreeExt, TreeMixin, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAction, defineAggregation, defineResource, defineResourceVariants, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, mergeResourceConfig, sendControllerResponse };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as DEFAULT_SORT, c as HOOK_OPERATIONS, d as MAX_REGEX_LENGTH, f as MAX_SEARCH_LENGTH, h as SYSTEM_FIELDS, i as DEFAULT_MAX_LIMIT, l as HOOK_PHASES, m as RESERVED_QUERY_PARAMS, n as DEFAULT_ID_FIELD, o as DEFAULT_TENANT_FIELD, p as MUTATION_OPERATIONS, r as DEFAULT_LIMIT, s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS, u as MAX_FILTER_DEPTH } from "../constants-DNre0wRg.mjs";
|
|
2
|
-
import { a as BulkMixin, c as collectReadBlockedFields, f as AccessControl, i as SlugMixin, l as isFieldReadable, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, s as QueryResolver, t as BaseController, u as BodySanitizer } from "../BaseController-
|
|
3
|
-
import { a as defineResource, c as
|
|
4
|
-
import { a as createCrudHandlers, c as getControllerContext, l as getControllerScope, o as createFastifyHandler, s as createRequestContext, u as sendControllerResponse } from "../actionPermissions-
|
|
5
|
-
export { AccessControl, BaseController, BaseCrudController, BodySanitizer, BulkMixin, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, QueryResolver, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugMixin, SoftDeleteMixin, TreeMixin, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAction, defineAggregation, defineResource, defineResourceVariants, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, sendControllerResponse };
|
|
2
|
+
import { a as BulkMixin, c as collectReadBlockedFields, f as AccessControl, i as SlugMixin, l as isFieldReadable, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, s as QueryResolver, t as BaseController, u as BodySanitizer } from "../BaseController-C6FlRWxF.mjs";
|
|
3
|
+
import { a as defineResource, c as defineAction, d as defineAggregation, i as defineResourceVariants, l as createCrudRouter, n as getEntityIdField, o as ResourceDefinition, r as getEntityQuery, s as mergeResourceConfig, t as getEntityId, u as createPermissionMiddleware } from "../core-b6fgFgnd.mjs";
|
|
4
|
+
import { a as createCrudHandlers, c as getControllerContext, l as getControllerScope, o as createFastifyHandler, s as createRequestContext, u as sendControllerResponse } from "../actionPermissions-ze70xXJi.mjs";
|
|
5
|
+
export { AccessControl, BaseController, BaseCrudController, BodySanitizer, BulkMixin, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, QueryResolver, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugMixin, SoftDeleteMixin, TreeMixin, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAction, defineAggregation, defineResource, defineResourceVariants, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, mergeResourceConfig, sendControllerResponse };
|
|
@@ -2,16 +2,16 @@ import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-DN
|
|
|
2
2
|
import { arcLog } from "./logger/index.mjs";
|
|
3
3
|
import { r as ForbiddenError } from "./errors-DkO8Nk6-.mjs";
|
|
4
4
|
import { b as isElevated, n as PUBLIC_SCOPE, x as isMember } from "./types-bHblZ9YN.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import { d as DEFAULT_FIELD_WRITE_DENIAL_POLICY, t as BaseController } from "./BaseController-
|
|
7
|
-
import { a as selectPluginMw, c as
|
|
8
|
-
import {
|
|
5
|
+
import { M as assertValidConfig, l as getDefaultCrudSchemas } from "./utils-FhJ88TJE.mjs";
|
|
6
|
+
import { d as DEFAULT_FIELD_WRITE_DENIAL_POLICY, t as BaseController } from "./BaseController-C6FlRWxF.mjs";
|
|
7
|
+
import { a as selectPluginMw, c as buildRouteHooks, d as buildAuthMiddleware, i as resolveRouterPluginMw, l as resolveRoutePreHandlers, n as buildRateLimitConfig, p as buildArcDecorator, r as buildRouteConfig, s as buildCrudPermissionMw, t as tryRegisterRoute, u as routeHookOptions } from "./registerRoute-xHc42fwB.mjs";
|
|
8
|
+
import { I as applyFieldWritePermissions, R as resolveEffectiveRoles, w as requireAuth } from "./permissions-BJMuKMl7.mjs";
|
|
9
9
|
import { t as getUserRoles } from "./types-XTApa8b6.mjs";
|
|
10
|
-
import { t as applyPresets } from "./presets-
|
|
11
|
-
import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-
|
|
10
|
+
import { t as applyPresets } from "./presets-gw0ciIUD.mjs";
|
|
11
|
+
import { n as convertRouteSchema, r as ensureSchemaConverter, t as convertOpenApiSchemas } from "./schemaConverter-Ds5reSGs.mjs";
|
|
12
12
|
import { i as pipeUIMessageStreamToReply, r as isReadableStream } from "./streaming-BQcW6_Kq.mjs";
|
|
13
13
|
import { t as hasEvents } from "./typeGuards-C9KXUly6.mjs";
|
|
14
|
-
import { a as createCrudHandlers, d as buildRequestScopeProjection, i as resolvePipelineSteps, o as createFastifyHandler, r as buildPipelineHandler, s as createRequestContext, t as resolveActionPermission } from "./actionPermissions-
|
|
14
|
+
import { a as createCrudHandlers, d as buildRequestScopeProjection, i as resolvePipelineSteps, o as createFastifyHandler, r as buildPipelineHandler, s as createRequestContext, t as resolveActionPermission } from "./actionPermissions-ze70xXJi.mjs";
|
|
15
15
|
import { t as pluralize } from "./pluralize-BhrgwZb9.mjs";
|
|
16
16
|
//#region src/core/middlewares/fieldWrite.ts
|
|
17
17
|
/**
|
|
@@ -151,7 +151,7 @@ function createCustomRoutes(fastify, routes, controller, options) {
|
|
|
151
151
|
const fieldWriteMw = buildFieldWritePreHandler(fieldPermissions, onFieldWriteDenied);
|
|
152
152
|
if (fieldWriteMw) customPreHandlers.push(fieldWriteMw);
|
|
153
153
|
}
|
|
154
|
-
const
|
|
154
|
+
const hooks = buildRouteHooks({
|
|
155
155
|
preAuth: route.preAuth ?? [],
|
|
156
156
|
arcDecorator,
|
|
157
157
|
authMw: buildAuthMiddleware(fastify, route.permissions),
|
|
@@ -165,7 +165,7 @@ function createCustomRoutes(fastify, routes, controller, options) {
|
|
|
165
165
|
method: route.method,
|
|
166
166
|
url: route.path,
|
|
167
167
|
schema,
|
|
168
|
-
|
|
168
|
+
...routeHookOptions(hooks),
|
|
169
169
|
handler: isStream ? async (request, reply) => {
|
|
170
170
|
reply.raw.setHeader("Content-Type", "text/event-stream");
|
|
171
171
|
reply.raw.setHeader("Cache-Control", "no-cache");
|
|
@@ -274,7 +274,7 @@ function createCrudRouter(fastify, controller, options = {}) {
|
|
|
274
274
|
for (const spec of crudTable) {
|
|
275
275
|
if (disabledRoutes.includes(spec.op)) continue;
|
|
276
276
|
const permission = permissions[spec.op];
|
|
277
|
-
const
|
|
277
|
+
const hooks = buildRouteHooks({
|
|
278
278
|
arcDecorator,
|
|
279
279
|
authMw: buildAuthMiddleware(fastify, permission),
|
|
280
280
|
permissionMw: buildCrudPermissionMw(permission, resourceName, spec.op),
|
|
@@ -293,7 +293,7 @@ function createCrudRouter(fastify, controller, options = {}) {
|
|
|
293
293
|
summary,
|
|
294
294
|
...spec.hasIdParams ? { params: idParamsSchema } : {}
|
|
295
295
|
}, defaultSchemas[spec.op], schemas[spec.op]),
|
|
296
|
-
|
|
296
|
+
...routeHookOptions(hooks),
|
|
297
297
|
handler: handlers[spec.op],
|
|
298
298
|
...buildRouteConfig(rateLimitConfig, extensions)
|
|
299
299
|
}, {
|
|
@@ -359,6 +359,58 @@ function defineAction(config) {
|
|
|
359
359
|
};
|
|
360
360
|
}
|
|
361
361
|
//#endregion
|
|
362
|
+
//#region src/core/defineResource/seams.ts
|
|
363
|
+
/** Plain object test — literals only; class instances must replace, not merge. */
|
|
364
|
+
function isPlainObject(value) {
|
|
365
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
366
|
+
const proto = Object.getPrototypeOf(value);
|
|
367
|
+
return proto === Object.prototype || proto === null;
|
|
368
|
+
}
|
|
369
|
+
/** Recursive plain-object merge; nested arrays REPLACE (they're value lists). */
|
|
370
|
+
function mergePlain(base, next) {
|
|
371
|
+
const out = { ...base };
|
|
372
|
+
for (const [key, value] of Object.entries(next)) {
|
|
373
|
+
if (value === void 0) continue;
|
|
374
|
+
const existing = out[key];
|
|
375
|
+
out[key] = isPlainObject(existing) && isPlainObject(value) ? mergePlain(existing, value) : value;
|
|
376
|
+
}
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Canonical slot-aware merge of a base resource config with host seams.
|
|
381
|
+
* Later seams win over earlier ones; `undefined` values never clobber.
|
|
382
|
+
*
|
|
383
|
+
* Merge semantics (value-driven — no per-slot registry to drift):
|
|
384
|
+
*
|
|
385
|
+
* - **Top-level arrays CONCAT** — `routes`, `middlewares`, `presets`,
|
|
386
|
+
* `routeGuards`, `disabledRoutes`: additive collections, base first.
|
|
387
|
+
* - **Plain objects merge recursively** — `permissions`, `actions`,
|
|
388
|
+
* `schemaOptions` (incl. `fieldRules`), `aggregations`, `customSchemas`,
|
|
389
|
+
* `fields`, `cache`, `events`: per-key composition. Arrays NESTED inside
|
|
390
|
+
* them replace (they're value lists — enums, sort specs), not concat.
|
|
391
|
+
* - **Everything else last-wins** — class instances (`adapter`,
|
|
392
|
+
* `controller`, `queryParser`), functions, scalars.
|
|
393
|
+
*
|
|
394
|
+
* The single `as` at the return is the one documented erasure for the
|
|
395
|
+
* {@link AdapterLike}-widened seam slots — inside the helper so no module
|
|
396
|
+
* author ever writes one (mirrors mongokit's one-cast-at-the-boundary rule).
|
|
397
|
+
*/
|
|
398
|
+
function mergeResourceConfig(base, ...seams) {
|
|
399
|
+
const out = { ...base };
|
|
400
|
+
for (const seam of seams) {
|
|
401
|
+
if (!seam) continue;
|
|
402
|
+
for (const [key, value] of Object.entries(seam)) {
|
|
403
|
+
if (value === void 0) continue;
|
|
404
|
+
const existing = out[key];
|
|
405
|
+
if (Array.isArray(existing) && Array.isArray(value)) out[key] = [...existing, ...value];
|
|
406
|
+
else if (isPlainObject(existing) && isPlainObject(value)) out[key] = mergePlain(existing, value);
|
|
407
|
+
else if (Array.isArray(value)) out[key] = [...value];
|
|
408
|
+
else out[key] = value;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return out;
|
|
412
|
+
}
|
|
413
|
+
//#endregion
|
|
362
414
|
//#region src/core/defineResource/controller.ts
|
|
363
415
|
/**
|
|
364
416
|
* Phase 4 — pick (or auto-create) the resource's controller.
|
|
@@ -652,7 +704,7 @@ function resolveIdField(config, repository) {
|
|
|
652
704
|
* enabled but no adapter" (a confusing false-positive error).
|
|
653
705
|
*/
|
|
654
706
|
function normalizeResourceConfig(config) {
|
|
655
|
-
return resolveCrudAllowList(expandCustomRoutesOnly(expandReferenceData(config)));
|
|
707
|
+
return resolveCrudAllowList(expandHistory(expandCustomRoutesOnly(expandReferenceData(config))));
|
|
656
708
|
}
|
|
657
709
|
/**
|
|
658
710
|
* Expand the `referenceData: true` shorthand. Reference data is the
|
|
@@ -706,6 +758,61 @@ function expandCustomRoutesOnly(config) {
|
|
|
706
758
|
};
|
|
707
759
|
}
|
|
708
760
|
/**
|
|
761
|
+
* Expand the 2.22 `history: true` shorthand — the per-record change
|
|
762
|
+
* timeline. Injects `GET /:id/history` (audit-entry page for one
|
|
763
|
+
* document, newest-first per the audit store's contract) and implies
|
|
764
|
+
* `audit: true` so the rows the route reads actually get written.
|
|
765
|
+
*
|
|
766
|
+
* The gate defaults STRICTER than reads (history exposes before/after
|
|
767
|
+
* snapshots): resource `update` permission → `get` → route auth.
|
|
768
|
+
* `history: { permissions }` overrides. The flag is CONSUMED here
|
|
769
|
+
* (set to undefined) — later phases and the registry see only its
|
|
770
|
+
* expansion, same contract as `customRoutesOnly`.
|
|
771
|
+
*/
|
|
772
|
+
function expandHistory(config) {
|
|
773
|
+
if (!config.history) return config;
|
|
774
|
+
const opts = config.history === true ? {} : config.history;
|
|
775
|
+
const permissions = opts.permissions ?? config.permissions?.update ?? config.permissions?.get ?? requireAuth();
|
|
776
|
+
const defaultLimit = opts.limit ?? 50;
|
|
777
|
+
const resourceName = config.name;
|
|
778
|
+
const historyRoute = {
|
|
779
|
+
method: "GET",
|
|
780
|
+
path: "/:id/history",
|
|
781
|
+
summary: `Change history for a ${resourceName} record (audit-backed timeline)`,
|
|
782
|
+
raw: true,
|
|
783
|
+
handler: async (request, reply) => {
|
|
784
|
+
const audit = request.server.audit;
|
|
785
|
+
if (!audit?.query || audit._noop === true) return reply.code(503).send({
|
|
786
|
+
code: "history.audit_unavailable",
|
|
787
|
+
message: `history for '${resourceName}' requires auditPlugin to be registered (and enabled)`,
|
|
788
|
+
status: 503
|
|
789
|
+
});
|
|
790
|
+
const { id } = request.params;
|
|
791
|
+
const q = request.query;
|
|
792
|
+
const limit = Math.min(Number(q.limit) > 0 ? Number(q.limit) : defaultLimit, 200);
|
|
793
|
+
const offset = Number(q.offset) > 0 ? Number(q.offset) : 0;
|
|
794
|
+
const entries = await audit.query({
|
|
795
|
+
resource: resourceName,
|
|
796
|
+
documentId: id,
|
|
797
|
+
limit,
|
|
798
|
+
offset
|
|
799
|
+
});
|
|
800
|
+
return reply.send({
|
|
801
|
+
data: entries,
|
|
802
|
+
limit,
|
|
803
|
+
offset
|
|
804
|
+
});
|
|
805
|
+
},
|
|
806
|
+
permissions
|
|
807
|
+
};
|
|
808
|
+
return {
|
|
809
|
+
...config,
|
|
810
|
+
history: void 0,
|
|
811
|
+
audit: config.audit ?? true,
|
|
812
|
+
routes: [...config.routes ?? [], historyRoute]
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
709
816
|
* Normalise the 2.16 `crud:` positive-form allow-list into the canonical
|
|
710
817
|
* `{ disabledRoutes, disableDefaultRoutes }` pair the rest of arc reads.
|
|
711
818
|
*
|
|
@@ -1068,7 +1175,10 @@ function buildGeneratedCrudSchemas(openApi, customSchemas) {
|
|
|
1068
1175
|
for (const [op, customSchema] of Object.entries(customSchemas)) {
|
|
1069
1176
|
const key = op;
|
|
1070
1177
|
const converted = convertRouteSchema(customSchema);
|
|
1071
|
-
schemas[key] = schemas[key] ?
|
|
1178
|
+
schemas[key] = schemas[key] ? {
|
|
1179
|
+
...schemas[key],
|
|
1180
|
+
...converted
|
|
1181
|
+
} : converted;
|
|
1072
1182
|
}
|
|
1073
1183
|
}
|
|
1074
1184
|
return schemas;
|
|
@@ -1218,7 +1328,8 @@ function normalizeActionsToRouterConfig(actions, resourceActionPermissions, tag,
|
|
|
1218
1328
|
* Fastify instance via `resource._sharedStateRegisteredOn`.
|
|
1219
1329
|
*/
|
|
1220
1330
|
function buildResourcePlugin(resource) {
|
|
1221
|
-
return async function resourcePlugin(fastify,
|
|
1331
|
+
return async function resourcePlugin(fastify, opts) {
|
|
1332
|
+
await ensureSchemaConverter();
|
|
1222
1333
|
const sharedRoot = fastify.server;
|
|
1223
1334
|
const isFirstMount = !resource._sharedStateRegisteredOn.has(sharedRoot);
|
|
1224
1335
|
if (isFirstMount) resource._sharedStateRegisteredOn.add(sharedRoot);
|
|
@@ -1254,6 +1365,7 @@ function buildResourcePlugin(resource) {
|
|
|
1254
1365
|
fastify.log?.warn?.(`[Arc] Resource '${resource.name}' declares events (${eventNames}) but \`eventPlugin\` is not registered. The events will silently NOT be emitted on CRUD mutations. Either register the plugin (\`await fastify.register(eventPlugin, { transport: ... })\`) or remove the \`events\` declaration from the resource.`);
|
|
1255
1366
|
}
|
|
1256
1367
|
}
|
|
1368
|
+
if (opts?.arcMountRoutes === false) return;
|
|
1257
1369
|
await fastify.register(async (instance) => {
|
|
1258
1370
|
const typedInstance = instance;
|
|
1259
1371
|
let schemas = buildGeneratedCrudSchemas(resource._registryMeta?.openApiSchemas, resource.customSchemas);
|
|
@@ -1283,7 +1395,7 @@ function buildResourcePlugin(resource) {
|
|
|
1283
1395
|
extensions: resource.extensions
|
|
1284
1396
|
});
|
|
1285
1397
|
if (resource.actions && Object.keys(resource.actions).length > 0) {
|
|
1286
|
-
const { createActionRouter } = await import("./createActionRouter-
|
|
1398
|
+
const { createActionRouter } = await import("./createActionRouter-h1cEEaEX.mjs").then((n) => n.n);
|
|
1287
1399
|
createActionRouter(typedInstance, {
|
|
1288
1400
|
...normalizeActionsToRouterConfig(resource.actions, resource.actionPermissions, resource.tag, resource.permissions, resource.name, typedInstance.log),
|
|
1289
1401
|
resourceName: resource.name,
|
|
@@ -1298,7 +1410,7 @@ function buildResourcePlugin(resource) {
|
|
|
1298
1410
|
});
|
|
1299
1411
|
}
|
|
1300
1412
|
if (resource.aggregations && Object.keys(resource.aggregations).length > 0) {
|
|
1301
|
-
const [{ createAggregationRouter }, { adapterSupportsAggregate, ArcAggregationConfigError }] = await Promise.all([import("./createAggregationRouter-
|
|
1413
|
+
const [{ createAggregationRouter }, { adapterSupportsAggregate, ArcAggregationConfigError }] = await Promise.all([import("./createAggregationRouter-B1C0kvTL.mjs"), import("./validate-C2mGgSXg.mjs").then((n) => n.r)]);
|
|
1302
1414
|
const repoForAgg = resource.controller?.repository ?? resource.repository;
|
|
1303
1415
|
if (!adapterSupportsAggregate(repoForAgg)) {
|
|
1304
1416
|
const undispatchable = Object.entries(resource.aggregations).filter(([, cfg]) => !cfg.materialized).map(([name]) => name);
|
|
@@ -2008,4 +2120,4 @@ function getEntityQuery(req) {
|
|
|
2008
2120
|
return { [getEntityIdField(req)]: id };
|
|
2009
2121
|
}
|
|
2010
2122
|
//#endregion
|
|
2011
|
-
export { defineResource as a,
|
|
2123
|
+
export { defineResource as a, defineAction as c, defineAggregation as d, defineResourceVariants as i, createCrudRouter as l, getEntityIdField as n, ResourceDefinition as o, getEntityQuery as r, mergeResourceConfig as s, getEntityId as t, createPermissionMiddleware as u };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
2
|
import "./constants-DNre0wRg.mjs";
|
|
3
3
|
import { f as createError } from "./errors-DkO8Nk6-.mjs";
|
|
4
|
-
import { a as selectPluginMw, c as
|
|
5
|
-
import { i as resolvePipelineSteps, n as buildActionPipelineHandler, u as sendControllerResponse } from "./actionPermissions-
|
|
6
|
-
import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-
|
|
4
|
+
import { a as selectPluginMw, c as buildRouteHooks, f as buildAuthMiddlewareForPermissions, i as resolveRouterPluginMw, n as buildRateLimitConfig, o as buildActionPermissionMw, p as buildArcDecorator, r as buildRouteConfig, t as tryRegisterRoute, u as routeHookOptions } from "./registerRoute-xHc42fwB.mjs";
|
|
5
|
+
import { i as resolvePipelineSteps, n as buildActionPipelineHandler, u as sendControllerResponse } from "./actionPermissions-ze70xXJi.mjs";
|
|
6
|
+
import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-B-6SPe7g.mjs";
|
|
7
7
|
//#region src/core/createActionRouter.ts
|
|
8
8
|
var createActionRouter_exports = /* @__PURE__ */ __exportAll({
|
|
9
9
|
buildActionBodySchema: () => buildActionBodySchema,
|
|
@@ -108,19 +108,23 @@ function createActionRouter(fastify, config) {
|
|
|
108
108
|
scopedError.meta = { action: submitted };
|
|
109
109
|
throw scopedError;
|
|
110
110
|
};
|
|
111
|
-
const
|
|
111
|
+
const hooks = buildRouteHooks({
|
|
112
112
|
arcDecorator,
|
|
113
113
|
authMw,
|
|
114
114
|
permissionMw,
|
|
115
115
|
pluginMw: selectPluginMw("POST", pluginMw),
|
|
116
116
|
routeGuards
|
|
117
|
-
})
|
|
117
|
+
});
|
|
118
|
+
const preHandler = [actionValidationFormatter, ...hooks.preHandler];
|
|
118
119
|
tryRegisterRoute(fastify, {
|
|
119
120
|
method: "POST",
|
|
120
121
|
url: urlPath,
|
|
121
122
|
schema: routeSchema,
|
|
122
123
|
attachValidation: true,
|
|
123
|
-
|
|
124
|
+
...routeHookOptions({
|
|
125
|
+
onRequest: hooks.onRequest,
|
|
126
|
+
preHandler
|
|
127
|
+
}),
|
|
124
128
|
...buildRouteConfig(rateLimitConfig, extensions),
|
|
125
129
|
handler: async (req, reply) => {
|
|
126
130
|
const { action, ...data } = req.body;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { f as createError, l as UnauthorizedError, r as ForbiddenError } from "./errors-DkO8Nk6-.mjs";
|
|
2
|
-
import { a as selectPluginMw, c as
|
|
2
|
+
import { a as selectPluginMw, c as buildRouteHooks, d as buildAuthMiddleware, i as resolveRouterPluginMw, n as buildRateLimitConfig, p as buildArcDecorator, u as routeHookOptions } from "./registerRoute-xHc42fwB.mjs";
|
|
3
3
|
import { n as validateAggregations } from "./validate-C2mGgSXg.mjs";
|
|
4
4
|
import { t as buildAggregationHandler } from "./buildHandler-D_2ULWxZ.mjs";
|
|
5
5
|
//#region src/core/aggregation/createAggregationRouter.ts
|
|
@@ -48,7 +48,7 @@ function registerOne(fastify, normalized, ctx) {
|
|
|
48
48
|
throw new ForbiddenError(reason ?? "You do not have permission to access this aggregation.");
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
-
const
|
|
51
|
+
const hooks = buildRouteHooks({
|
|
52
52
|
arcDecorator,
|
|
53
53
|
authMw,
|
|
54
54
|
permissionMw,
|
|
@@ -73,7 +73,7 @@ function registerOne(fastify, normalized, ctx) {
|
|
|
73
73
|
method: "GET",
|
|
74
74
|
url: `/aggregations/${name}`,
|
|
75
75
|
schema: routeSchema,
|
|
76
|
-
|
|
76
|
+
...routeHookOptions(hooks),
|
|
77
77
|
...rateLimitConfig ? { config: rateLimitConfig } : {},
|
|
78
78
|
handler: async (req, reply) => {
|
|
79
79
|
try {
|