@classytic/arc 2.16.0 → 2.17.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 +22 -2
- package/dist/{BaseController-DlCCTIxJ.mjs → BaseController-Dmeh4Y_y.mjs} +5 -47
- package/dist/{QueryCache-SvmT_9ti.d.mts → QueryCache-8YmAQ-zW.d.mts} +1 -1
- package/dist/{ResourceRegistry-f48hFk3m.mjs → ResourceRegistry-CzSzVK1m.mjs} +3 -0
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/index.mjs +2 -1
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +2 -2
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/auth/redis-session.mjs +28 -2
- package/dist/{buildHandler-BZX6zzDM.mjs → buildHandler-C_SkBNF3.mjs} +6 -4
- package/dist/cache/index.d.mts +3 -3
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.d.mts +1 -1
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.d.mts +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +3 -3
- package/dist/{core-DBJ_j6rX.mjs → core-DYJvu7aI.mjs} +174 -31
- package/dist/{createActionRouter-DUpN3Dd1.mjs → createActionRouter-BHUMb1-y.mjs} +6 -3
- package/dist/{createAggregationRouter-Dq-TUCuY.mjs → createAggregationRouter-BYVvhfc5.mjs} +3 -3
- package/dist/{createApp-DNccuhyI.mjs → createApp-CzCEEsEm.mjs} +9 -9
- package/dist/{defineEvent-DRwY0fYm.mjs → defineEvent-NPKUJRt8.mjs} +1 -1
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-Dci0AYLT.mjs → elevation-6mfTLBC-.mjs} +1 -1
- package/dist/{errorHandler-DpoXQHZ9.mjs → errorHandler-wQejMBT3.mjs} +45 -4
- package/dist/{eventPlugin-C2cGqtRO.mjs → eventPlugin-CnJCUyM0.mjs} +1 -1
- package/dist/{eventPlugin-CtHC_av1.d.mts → eventPlugin-DjEx_J3j.d.mts} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +5 -5
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +34 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +17 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +4 -3
- package/dist/fastify-augmentation-7LIR0nDv.d.mts +42 -0
- package/dist/{fields-Anj0xdih.d.mts → fields-DqZLp5d6.d.mts} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +2 -2
- package/dist/idempotency/index.mjs +21 -5
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-3oIimXQn.d.mts → index-3L-adFJO.d.mts} +3 -3
- package/dist/{index-CkW0flkU.d.mts → index-C1JSBan-.d.mts} +256 -8
- package/dist/{index-B-ulKx5P.d.mts → index-CruZir1o.d.mts} +36 -16
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +5 -5
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/jobs.d.mts +36 -1
- package/dist/integrations/jobs.mjs +82 -9
- package/dist/integrations/mcp/index.d.mts +67 -5
- package/dist/integrations/mcp/index.mjs +93 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +2 -1
- package/dist/{loadResourcesFromEntry-BLMEI2Xa.mjs → loadResourcesFromEntry-DCjYMgfC.mjs} +1 -1
- package/dist/{mcpPlugin-7vGV51ED.mjs → mcpPlugin-hZE00H74.mjs} +114 -11
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/{multipartBody-BOvVSVCD.mjs → multipartBody-VjVWmZl8.mjs} +2 -1
- package/dist/{openapi-34T9yNwd.mjs → openapi-DKDs9NfD.mjs} +37 -10
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-CTxMrreC.mjs → permissions-B1Qy55fg.mjs} +169 -169
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/plugins/index.d.mts +5 -59
- package/dist/plugins/index.mjs +7 -7
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/preloadResources-BOXzPhKU.mjs +45 -0
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +2 -2
- 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/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-C9BE6WaZ.mjs → presets-BYbPobbF.mjs} +5 -3
- package/dist/{queryCachePlugin-Biqzfbi5.d.mts → queryCachePlugin-Do4P_WBC.d.mts} +2 -2
- package/dist/{redis-Cyzrz6SX.d.mts → redis-cpqVb85w.d.mts} +1 -1
- package/dist/{redis-stream-DT-YjzrB.d.mts → redis-stream-BuiAS3aE.d.mts} +1 -1
- package/dist/registry/index.d.mts +30 -8
- package/dist/registry/index.mjs +2 -2
- package/dist/{registry-BBE23CDj.mjs → registry-BrlHkrRS.mjs} +14 -8
- package/dist/{routerShared-CZV5aabX.mjs → routerShared-nCyJGOKX.mjs} +164 -17
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +2 -2
- package/dist/testing/index.d.mts +2 -25
- package/dist/testing/index.mjs +2 -45
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-D-fYtKjb.d.mts → types-BRmS8TNa.d.mts} +34 -11
- package/dist/{types-BsJMEQ4D.d.mts → types-CoMjNmkF.d.mts} +12 -1
- package/dist/utils/index.d.mts +83 -5
- package/dist/utils/index.mjs +2 -2
- package/dist/{utils-DC5ycPfr.mjs → utils-CvD3glpM.mjs} +168 -3
- package/dist/{validate-By96rH0r.mjs → validate-DFIP2VrM.mjs} +65 -3
- package/dist/{versioning-ZwX9tmbS.d.mts → versioning-5H4930tj.d.mts} +1 -1
- package/package.json +3 -3
- package/skills/arc/SKILL.md +43 -1
- package/skills/arc/references/api-reference.md +2 -0
- package/skills/arc/references/mcp.md +30 -1
- /package/dist/{EventTransport-C-2oAHtw.d.mts → EventTransport-B1GFiYUy.d.mts} +0 -0
- /package/dist/{EventTransport-Hxvv5QQz.mjs → EventTransport-ClEDfiMS.mjs} +0 -0
- /package/dist/{caching-TeHE8G-v.mjs → caching-8MfrrUYU.mjs} +0 -0
- /package/dist/{elevation-0YBpa663.d.mts → elevation-DvwtjDwu.d.mts} +0 -0
- /package/dist/{errorHandler-mHuyWzZE.d.mts → errorHandler-DuqTqeqn.d.mts} +0 -0
- /package/dist/{errors-C1lX_jlm.d.mts → errors-4ZloYFvm.d.mts} +0 -0
- /package/dist/{externalPaths-DFg-2KTp.d.mts → externalPaths-ClykOQqa.d.mts} +0 -0
- /package/dist/{generate-CYac-OLv.mjs → generate-CcM24hoq.mjs} +0 -0
- /package/dist/{generate-BWFwgcCM.d.mts → generate-cRnsafMS.d.mts} +0 -0
- /package/dist/{init-Dv71MsJr.d.mts → init-D-vrTZ1G.d.mts} +0 -0
- /package/dist/{init-HDvoO9L5.mjs → init-DYzfniuX.mjs} +0 -0
- /package/dist/{interface-CH0OQudo.d.mts → interface-BVkZsvEe.d.mts} +0 -0
- /package/dist/{interface-NwJ_qPlY.d.mts → interface-DoE6UoC2.d.mts} +0 -0
- /package/dist/{loadResources-ChQEj8ih.mjs → loadResources-fol6JuvW.mjs} +0 -0
- /package/dist/{metrics-TuOmguhi.mjs → metrics-B-PU4-Yu.mjs} +0 -0
- /package/dist/{replyHelpers-C-gD32oF.mjs → replyHelpers-boS5Rx26.mjs} +0 -0
- /package/dist/{schemaIR-Ctc89DSn.mjs → schemaIR-DAOvBWgT.mjs} +0 -0
- /package/dist/{sessionManager-BqFegc0W.d.mts → sessionManager-DlmaHUAl.d.mts} +0 -0
- /package/dist/{sse-BY6sTy4P.mjs → sse-jh5OqFjR.mjs} +0 -0
- /package/dist/{storage-D2KZJAmn.d.mts → storage-HcAgP5Ni.d.mts} +0 -0
- /package/dist/{store-helpers-B0sunfZZ.mjs → store-helpers-5JgaxieH.mjs} +0 -0
- /package/dist/{tracing-Dm8n7Cnn.d.mts → tracing-bRqqUxFO.d.mts} +0 -0
- /package/dist/{types-DVfpSfx2.d.mts → types-CfapW2t8.d.mts} +0 -0
- /package/dist/{types-D57iXYb8.mjs → types-DV9WDfeg.mjs} +0 -0
- /package/dist/{versioning-B6mimogM.mjs → versioning-CuIwUQZ2.mjs} +0 -0
- /package/dist/{websocket-BkjeGZRn.d.mts → websocket-BJ0w-3Dt.d.mts} +0 -0
|
@@ -1,4 +1,37 @@
|
|
|
1
|
+
import sjson from "secure-json-parse";
|
|
1
2
|
//#region src/events/transports/redis-stream.ts
|
|
3
|
+
/**
|
|
4
|
+
* Redis Stream Event Transport — Durable Event Delivery
|
|
5
|
+
*
|
|
6
|
+
* Uses Redis Streams (`XADD`/`XREADGROUP`) for persistent, at-least-once event
|
|
7
|
+
* delivery across multiple service instances. Unlike Pub/Sub, events are stored
|
|
8
|
+
* in Redis and survive crashes/restarts.
|
|
9
|
+
*
|
|
10
|
+
* **Delivery guarantee:** at-least-once. Failed messages are left unacked and
|
|
11
|
+
* reclaimed after `claimTimeoutMs`, which can result in duplicate handler
|
|
12
|
+
* execution. Consumers must be idempotent (e.g. use `event.meta.id` as a
|
|
13
|
+
* deduplication key) to achieve effectively-once processing.
|
|
14
|
+
*
|
|
15
|
+
* Key features:
|
|
16
|
+
* - Consumer groups: each event delivered to exactly one consumer per group
|
|
17
|
+
* - Crash recovery: pending entries are auto-claimed after `claimTimeoutMs`
|
|
18
|
+
* - Dead letter stream: events exceeding `maxRetries` are moved to a DLQ
|
|
19
|
+
* - Backpressure: configurable block time and batch size
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { RedisStreamTransport } from '@classytic/arc/events';
|
|
24
|
+
* import Redis from 'ioredis';
|
|
25
|
+
*
|
|
26
|
+
* const transport = new RedisStreamTransport(new Redis(), {
|
|
27
|
+
* stream: 'arc-events',
|
|
28
|
+
* group: 'api-service',
|
|
29
|
+
* consumer: 'worker-1',
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* await app.register(eventPlugin, { transport });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
2
35
|
var RedisStreamTransport = class {
|
|
3
36
|
name = "redis-stream";
|
|
4
37
|
redis;
|
|
@@ -350,7 +383,7 @@ function parseStreamFields(fields) {
|
|
|
350
383
|
}
|
|
351
384
|
if (!eventType || !rawData) return null;
|
|
352
385
|
try {
|
|
353
|
-
const parsed =
|
|
386
|
+
const parsed = sjson.parse(rawData, (key, value) => {
|
|
354
387
|
if (key === "timestamp" && typeof value === "string") return new Date(value);
|
|
355
388
|
return value;
|
|
356
389
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-
|
|
1
|
+
import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-B1GFiYUy.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/events/transports/redis.d.ts
|
|
4
4
|
interface RedisLike {
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
import sjson from "secure-json-parse";
|
|
1
2
|
//#region src/events/transports/redis.ts
|
|
2
3
|
/**
|
|
4
|
+
* Redis Event Transport
|
|
5
|
+
*
|
|
6
|
+
* Uses Redis Pub/Sub for cross-process event delivery.
|
|
7
|
+
* Optional dependency — only imported when explicitly used.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { RedisEventTransport } from '@classytic/arc/events/redis';
|
|
11
|
+
* import Redis from 'ioredis';
|
|
12
|
+
*
|
|
13
|
+
* const redis = new Redis();
|
|
14
|
+
* const transport = new RedisEventTransport(redis, { channel: 'arc-events' });
|
|
15
|
+
*
|
|
16
|
+
* await app.register(eventPlugin, { transport });
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
3
19
|
* DomainEvent contains a `Date` in `meta.timestamp`. We serialize it as an
|
|
4
20
|
* ISO string and revive it on the other end so subscribers always receive a
|
|
5
21
|
* proper Date object.
|
|
@@ -11,7 +27,7 @@ function serialize(event) {
|
|
|
11
27
|
});
|
|
12
28
|
}
|
|
13
29
|
function deserialize(raw) {
|
|
14
|
-
return
|
|
30
|
+
return sjson.parse(raw, (key, value) => {
|
|
15
31
|
if (key === "timestamp" && typeof value === "string") return new Date(value);
|
|
16
32
|
return value;
|
|
17
33
|
});
|
package/dist/factory/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as CustomPluginAuthOption, c as RawBodyOptions, d as
|
|
1
|
+
import { a as CustomPluginAuthOption, c as RawBodyOptions, d as preloadResourcesAsync, f as LoadResourcesOptions, h as loadResources, i as CustomAuthenticatorOption, l as UnderPressureOptions, m as ResourceModule, n as BetterAuthOption, o as JwtAuthOption, p as ResourceLike, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as preloadResources } from "../types-BRmS8TNa.mjs";
|
|
2
2
|
import { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
3
3
|
|
|
4
4
|
//#region src/factory/createApp.d.ts
|
|
@@ -95,4 +95,4 @@ declare const edgePreset: Partial<CreateAppOptions>;
|
|
|
95
95
|
*/
|
|
96
96
|
declare function getPreset(name: "production" | "development" | "testing" | "edge"): Partial<CreateAppOptions>;
|
|
97
97
|
//#endregion
|
|
98
|
-
export { ArcFactory, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type MultipartOptions, type RawBodyOptions, type ResourceLike, type ResourceModule, type UnderPressureOptions, createApp, developmentPreset, edgePreset, getPreset, loadResources, productionPreset, testingPreset, toFetchHandler };
|
|
98
|
+
export { ArcFactory, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type MultipartOptions, type RawBodyOptions, type ResourceLike, type ResourceModule, type UnderPressureOptions, createApp, developmentPreset, edgePreset, getPreset, loadResources, preloadResources, preloadResourcesAsync, productionPreset, testingPreset, toFetchHandler };
|
package/dist/factory/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
1
|
+
import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-BOXzPhKU.mjs";
|
|
2
|
+
import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-CzCEEsEm.mjs";
|
|
3
|
+
import { t as loadResources } from "../loadResources-fol6JuvW.mjs";
|
|
3
4
|
//#region src/factory/edge.ts
|
|
4
5
|
/**
|
|
5
6
|
* Convert a Fastify app into a Web Standards fetch handler.
|
|
@@ -46,4 +47,4 @@ function toFetchHandler(app, options = {}) {
|
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
//#endregion
|
|
49
|
-
export { ArcFactory, createApp, developmentPreset, edgePreset, getPreset, loadResources, productionPreset, testingPreset, toFetchHandler };
|
|
50
|
+
export { ArcFactory, createApp, developmentPreset, edgePreset, getPreset, loadResources, preloadResources, preloadResourcesAsync, productionPreset, testingPreset, toFetchHandler };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { An as HookSystem, z as ResourceRegistry } from "./index-C1JSBan-.mjs";
|
|
2
|
+
import { t as ExternalOpenApiPaths } from "./externalPaths-ClykOQqa.mjs";
|
|
3
|
+
import { FastifyPluginAsync } from "fastify";
|
|
4
|
+
|
|
5
|
+
//#region src/core/arcCorePlugin.d.ts
|
|
6
|
+
interface ArcCorePluginOptions {
|
|
7
|
+
/** Enable event emission for CRUD operations (requires eventPlugin) */
|
|
8
|
+
emitEvents?: boolean;
|
|
9
|
+
/** Hook system instance (for testing/custom setup) */
|
|
10
|
+
hookSystem?: HookSystem;
|
|
11
|
+
/** Resource registry instance (for testing/custom setup) */
|
|
12
|
+
registry?: ResourceRegistry;
|
|
13
|
+
}
|
|
14
|
+
interface PluginMeta {
|
|
15
|
+
name: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
options?: Record<string, unknown>;
|
|
18
|
+
registeredAt: string;
|
|
19
|
+
}
|
|
20
|
+
interface ArcCore {
|
|
21
|
+
/** Instance-scoped hook system */
|
|
22
|
+
hooks: HookSystem;
|
|
23
|
+
/** Instance-scoped resource registry */
|
|
24
|
+
registry: ResourceRegistry;
|
|
25
|
+
/** Whether event emission is enabled */
|
|
26
|
+
emitEvents: boolean;
|
|
27
|
+
/** External OpenAPI paths contributed by auth adapters or third-party integrations */
|
|
28
|
+
externalOpenApiPaths: ExternalOpenApiPaths[];
|
|
29
|
+
/** Registered plugins for introspection */
|
|
30
|
+
plugins: Map<string, PluginMeta>;
|
|
31
|
+
}
|
|
32
|
+
declare const arcCorePlugin: FastifyPluginAsync<ArcCorePluginOptions>;
|
|
33
|
+
declare const _default: FastifyPluginAsync<ArcCorePluginOptions>;
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/types/fastify-augmentation.d.ts
|
|
36
|
+
declare module "fastify" {
|
|
37
|
+
interface FastifyInstance {
|
|
38
|
+
arc?: ArcCore;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { arcCorePlugin as a, _default as i, ArcCorePluginOptions as n, PluginMeta as r, ArcCore as t };
|
package/dist/hooks/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { An as HookSystem, Dn as HookOperation, En as HookHandler, Fn as beforeCreate, In as beforeDelete, Ln as beforeUpdate, Mn as afterCreate, Nn as afterDelete, On as HookPhase, Pn as afterUpdate, Rn as createHookSystem, Tn as HookContext, jn as HookSystemOptions, kn as HookRegistration, wn as DefineHookOptions, zn as defineHook } from "../index-
|
|
1
|
+
import { An as HookSystem, Dn as HookOperation, En as HookHandler, Fn as beforeCreate, In as beforeDelete, Ln as beforeUpdate, Mn as afterCreate, Nn as afterDelete, On as HookPhase, Pn as afterUpdate, Rn as createHookSystem, Tn as HookContext, jn as HookSystemOptions, kn as HookRegistration, wn as DefineHookOptions, zn as defineHook } from "../index-C1JSBan-.mjs";
|
|
2
2
|
export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as createIdempotencyResult, n as IdempotencyResult, r as IdempotencyStore, t as IdempotencyLock } from "../interface-
|
|
2
|
-
import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-
|
|
1
|
+
import { i as createIdempotencyResult, n as IdempotencyResult, r as IdempotencyStore, t as IdempotencyLock } from "../interface-DoE6UoC2.mjs";
|
|
2
|
+
import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-cpqVb85w.mjs";
|
|
3
3
|
import { FastifyPluginAsync } from "fastify";
|
|
4
4
|
import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
5
5
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-
|
|
1
|
+
import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-5JgaxieH.mjs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import fp from "fastify-plugin";
|
|
4
4
|
import { and, eq, exists, gt, lt, or, startsWith } from "@classytic/repo-core/filter";
|
|
@@ -266,6 +266,12 @@ var MemoryIdempotencyStore = class {
|
|
|
266
266
|
*/
|
|
267
267
|
const HEADER_IDEMPOTENCY_REPLAYED = "x-idempotency-replayed";
|
|
268
268
|
const HEADER_IDEMPOTENCY_KEY = "x-idempotency-key";
|
|
269
|
+
/**
|
|
270
|
+
* Cap recursion depth in body fingerprinting. Real APIs almost never exceed
|
|
271
|
+
* ~10 levels; the cap is generous so well-formed payloads are unaffected,
|
|
272
|
+
* yet low enough to stop a `{"a":{"a":{...thousands deep...}}}` stack bomb.
|
|
273
|
+
*/
|
|
274
|
+
const MAX_FINGERPRINT_DEPTH = 32;
|
|
269
275
|
const idempotencyPlugin = async (fastify, opts = {}) => {
|
|
270
276
|
const { enabled = false, headerName = "idempotency-key", ttlMs = 864e5, lockTimeoutMs = 3e4, methods = [
|
|
271
277
|
"POST",
|
|
@@ -298,14 +304,24 @@ const idempotencyPlugin = async (fastify, opts = {}) => {
|
|
|
298
304
|
return true;
|
|
299
305
|
}
|
|
300
306
|
/**
|
|
301
|
-
* Normalize body for consistent hashing (sort keys recursively)
|
|
307
|
+
* Normalize body for consistent hashing (sort keys recursively).
|
|
308
|
+
*
|
|
309
|
+
* Recursion is bounded by `MAX_FINGERPRINT_DEPTH` to defeat a deep-nesting
|
|
310
|
+
* DoS: an attacker who sends a 1 MB JSON body of nested `{"a":{"a":...}}`
|
|
311
|
+
* would otherwise blow the call stack inside this hot path. The cap
|
|
312
|
+
* exceeds any sensible API shape (real bodies are <10 levels deep) while
|
|
313
|
+
* still being well under V8's default stack budget. At the limit we
|
|
314
|
+
* substitute a sentinel string so the hash still differs from siblings
|
|
315
|
+
* but recursion stops — the request is fingerprinted as if everything
|
|
316
|
+
* beyond the cap were `"<truncated>"`.
|
|
302
317
|
*/
|
|
303
|
-
function normalizeBody(obj) {
|
|
318
|
+
function normalizeBody(obj, depth = 0) {
|
|
304
319
|
if (obj === null || typeof obj !== "object") return obj;
|
|
305
|
-
if (
|
|
320
|
+
if (depth >= MAX_FINGERPRINT_DEPTH) return "<truncated>";
|
|
321
|
+
if (Array.isArray(obj)) return obj.map((item) => normalizeBody(item, depth + 1));
|
|
306
322
|
const sorted = {};
|
|
307
323
|
const keys = Object.keys(obj).sort();
|
|
308
|
-
for (const key of keys) sorted[key] = normalizeBody(obj[key]);
|
|
324
|
+
for (const key of keys) sorted[key] = normalizeBody(obj[key], depth + 1);
|
|
309
325
|
return sorted;
|
|
310
326
|
}
|
|
311
327
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as UpstashRedisLike, i as RedisIdempotencyStoreOptions, n as RedisClient, o as ioredisAsIdempotencyClient, r as RedisIdempotencyStore, s as upstashAsIdempotencyClient, t as IoredisLike } from "../redis-
|
|
1
|
+
import { a as UpstashRedisLike, i as RedisIdempotencyStoreOptions, n as RedisClient, o as ioredisAsIdempotencyClient, r as RedisIdempotencyStore, s as upstashAsIdempotencyClient, t as IoredisLike } from "../redis-cpqVb85w.mjs";
|
|
2
2
|
export { type IoredisLike, type RedisClient, RedisIdempotencyStore, type RedisIdempotencyStoreOptions, type UpstashRedisLike, ioredisAsIdempotencyClient, upstashAsIdempotencyClient };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as CacheStore, t as CacheLogger } from "./interface-
|
|
2
|
-
import { i as RequestScope, n as Mandate } from "./types-
|
|
3
|
-
import { c as PermissionCheck, l as PermissionContext, u as PermissionResult } from "./fields-
|
|
1
|
+
import { r as CacheStore, t as CacheLogger } from "./interface-BVkZsvEe.mjs";
|
|
2
|
+
import { i as RequestScope, n as Mandate } from "./types-CfapW2t8.mjs";
|
|
3
|
+
import { c as PermissionCheck, l as PermissionContext, u as PermissionResult } from "./fields-DqZLp5d6.mjs";
|
|
4
4
|
import { FastifyReply, FastifyRequest } from "fastify";
|
|
5
5
|
|
|
6
6
|
//#region src/permissions/agent.d.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as QueryCacheConfig } from "./QueryCache-
|
|
2
|
-
import { i as RequestScope } from "./types-
|
|
3
|
-
import { c as PermissionCheck, d as UserBase, n as FieldPermissionMap } from "./fields-
|
|
4
|
-
import { n as DomainEvent } from "./EventTransport-
|
|
1
|
+
import { a as QueryCacheConfig } from "./QueryCache-8YmAQ-zW.mjs";
|
|
2
|
+
import { i as RequestScope } from "./types-CfapW2t8.mjs";
|
|
3
|
+
import { c as PermissionCheck, d as UserBase, n as FieldPermissionMap } from "./fields-DqZLp5d6.mjs";
|
|
4
|
+
import { n as DomainEvent } from "./EventTransport-B1GFiYUy.mjs";
|
|
5
5
|
import { KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
|
|
6
6
|
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
|
|
7
7
|
import * as _$_classytic_repo_core_adapter0 from "@classytic/repo-core/adapter";
|
|
@@ -1569,8 +1569,39 @@ interface AggregationConfig {
|
|
|
1569
1569
|
having?: AnyRecord;
|
|
1570
1570
|
/** Order grouped rows by groupBy field, measure alias, or joined-alias path. */
|
|
1571
1571
|
sort?: Record<string, 1 | -1>;
|
|
1572
|
-
/**
|
|
1572
|
+
/**
|
|
1573
|
+
* Static hard cap on result rows. Always applied at the IR level
|
|
1574
|
+
* (LIMIT / `$limit`), regardless of URL params. Use when the
|
|
1575
|
+
* aggregation has a fixed-size shape ("top 10 categories"). Mutually
|
|
1576
|
+
* exclusive with `defaultLimit` / `maxLimit` — pick the static form
|
|
1577
|
+
* OR the URL-driven form, not both.
|
|
1578
|
+
*/
|
|
1573
1579
|
limit?: number;
|
|
1580
|
+
/**
|
|
1581
|
+
* URL-driven row limit — applied when the caller passes `?limit=N`.
|
|
1582
|
+
* When set, the auto-route reads `query.limit`, parses it as a
|
|
1583
|
+
* positive integer, caps it at `maxLimit` (or the framework default
|
|
1584
|
+
* if `maxLimit` is unset), and writes the result into `AggRequest.limit`.
|
|
1585
|
+
* When the caller omits `?limit`, this default applies.
|
|
1586
|
+
*
|
|
1587
|
+
* **DoS defense.** Auto-generated `/aggregations/:name` routes today
|
|
1588
|
+
* have no upper bound on response size. A `groupBy: 'userId'` on a
|
|
1589
|
+
* 10M-user table without `defaultLimit` returns a 10M-row JSON. Setting
|
|
1590
|
+
* `defaultLimit: 100, maxLimit: 1000` bounds the response shape while
|
|
1591
|
+
* letting power users request up to 1000 rows when they need them.
|
|
1592
|
+
*
|
|
1593
|
+
* Mutually exclusive with the static `limit` — boot error if both set.
|
|
1594
|
+
* Setting `defaultLimit` without `maxLimit` uses the framework
|
|
1595
|
+
* default cap (`AGG_FRAMEWORK_MAX_LIMIT = 1000`) — explicit is better.
|
|
1596
|
+
*/
|
|
1597
|
+
defaultLimit?: number;
|
|
1598
|
+
/**
|
|
1599
|
+
* Hard ceiling for URL-driven `?limit=N`. Only meaningful when
|
|
1600
|
+
* `defaultLimit` is set; ignored otherwise. Defaults to
|
|
1601
|
+
* `AGG_FRAMEWORK_MAX_LIMIT` (1000) when omitted but `defaultLimit` is
|
|
1602
|
+
* present. Boot validation: must be ≥ `defaultLimit`.
|
|
1603
|
+
*/
|
|
1604
|
+
maxLimit?: number;
|
|
1574
1605
|
/**
|
|
1575
1606
|
* Top-N-per-group filter. Keeps only the top `limit` rows per
|
|
1576
1607
|
* partition, ranked by `sortBy`. The classic "top 3 products per
|
|
@@ -2427,8 +2458,42 @@ interface RouteDefinition {
|
|
|
2427
2458
|
* @default false
|
|
2428
2459
|
*/
|
|
2429
2460
|
readonly tenantScope?: boolean;
|
|
2430
|
-
/**
|
|
2461
|
+
/**
|
|
2462
|
+
* SSE streaming mode. Two handler shapes work:
|
|
2463
|
+
*
|
|
2464
|
+
* - Write to `reply.raw` directly (NDJSON, custom SSE — historical contract).
|
|
2465
|
+
* - Return a Web `ReadableStream` (Vercel AI SDK's `result.toUIMessageStream()`,
|
|
2466
|
+
* `fetch().body`, etc.). 2.17.1+ auto-pipes these through
|
|
2467
|
+
* `pipeUIMessageStreamToReply()` so each chunk is JSON-encoded into
|
|
2468
|
+
* an SSE `data:` frame and client-disconnect cancels the source.
|
|
2469
|
+
* Pre-2.17.1 this crashed with `chunk must be a string or Buffer`.
|
|
2470
|
+
*/
|
|
2431
2471
|
readonly streamResponse?: boolean;
|
|
2472
|
+
/**
|
|
2473
|
+
* INTERNAL — marker stamped by built-in presets (softDelete, tree, …)
|
|
2474
|
+
* onto routes they emit. Read by the MCP collision detector
|
|
2475
|
+
* (`createMcpServer.resolveToolCollisions`) to attribute the route
|
|
2476
|
+
* source ("preset:softDelete vs user `actions.restore`") and to
|
|
2477
|
+
* auto-namespace the preset side when a user-authored tool legitimately
|
|
2478
|
+
* shadows it. Not part of the public surface — hosts must not set this.
|
|
2479
|
+
*
|
|
2480
|
+
* @internal
|
|
2481
|
+
*/
|
|
2482
|
+
readonly _presetSource?: string;
|
|
2483
|
+
/**
|
|
2484
|
+
* Apply the resource's `fields` (field-level write permissions) to this
|
|
2485
|
+
* custom route's request body.
|
|
2486
|
+
*
|
|
2487
|
+
* Default: `true` for body-bearing methods (POST/PUT/PATCH), `false`
|
|
2488
|
+
* otherwise. Set to `false` to opt out — handler accepts the body as
|
|
2489
|
+
* supplied with no field-write filtering. The resource-level
|
|
2490
|
+
* `onFieldWriteDenied: 'reject' | 'strip'` setting governs how denials
|
|
2491
|
+
* are surfaced.
|
|
2492
|
+
*
|
|
2493
|
+
* `raw: true` routes ALWAYS bypass this — they've opted out of the
|
|
2494
|
+
* pipeline entirely.
|
|
2495
|
+
*/
|
|
2496
|
+
readonly fieldWrite?: boolean;
|
|
2432
2497
|
/**
|
|
2433
2498
|
* Fastify route schema. Each slot (`body`, `querystring`, `params`,
|
|
2434
2499
|
* `headers`, `response[status]`) accepts a plain JSON Schema object
|
|
@@ -2707,13 +2772,37 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
2707
2772
|
/**
|
|
2708
2773
|
* Custom routes beyond CRUD. Presets also merge their routes here.
|
|
2709
2774
|
*
|
|
2710
|
-
*
|
|
2775
|
+
* **Route handlers return `IControllerResponse<T>` shape** —
|
|
2776
|
+
* `{ data, status?, meta?, headers? }`. Returning a bare value (array,
|
|
2777
|
+
* object, primitive) is supported via auto-envelope (2.17+) but the
|
|
2778
|
+
* explicit envelope is the canonical contract — declared `fields:`
|
|
2779
|
+
* permissions, custom `status`, `meta`, and `headers` only work when
|
|
2780
|
+
* you return the envelope. Set `raw: true` on a route to opt out of
|
|
2781
|
+
* arc's pipeline entirely (custom streaming, SSE, manual `reply.send()`).
|
|
2782
|
+
*
|
|
2783
|
+
* **Path collisions with auto-CRUD are detected at `defineResource()` time.**
|
|
2784
|
+
* If you declare a route that shares method+path with an auto-CRUD op
|
|
2785
|
+
* (e.g. `POST /` collides with `create`), validation throws with the
|
|
2786
|
+
* exact `disabledRoutes` line to add. Use one of:
|
|
2787
|
+
* - `disabledRoutes: ['create']` — suppress the auto-CRUD op
|
|
2788
|
+
* - `crud: { list: true, get: true }` — opt-in allow-list (preferred)
|
|
2789
|
+
* - `disableDefaultRoutes: true` — turn off all auto-CRUD on this resource
|
|
2790
|
+
*
|
|
2791
|
+
* @example Custom route alongside default CRUD:
|
|
2711
2792
|
* ```typescript
|
|
2712
2793
|
* routes: [
|
|
2713
2794
|
* { method: 'GET', path: '/stats', handler: 'getStats', permissions: auth() },
|
|
2714
2795
|
* { method: 'POST', path: '/webhook', handler: webhookFn, raw: true, permissions: auth() },
|
|
2715
2796
|
* ]
|
|
2716
2797
|
* ```
|
|
2798
|
+
*
|
|
2799
|
+
* @example Custom POST replacing the auto-CRUD `create`:
|
|
2800
|
+
* ```typescript
|
|
2801
|
+
* disabledRoutes: ['create'],
|
|
2802
|
+
* routes: [
|
|
2803
|
+
* { method: 'POST', path: '/', handler: customCreate, permissions: requireAuth() },
|
|
2804
|
+
* ]
|
|
2805
|
+
* ```
|
|
2717
2806
|
*/
|
|
2718
2807
|
routes?: RouteDefinition[];
|
|
2719
2808
|
/**
|
|
@@ -2886,6 +2975,86 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
2886
2975
|
skipValidation?: boolean;
|
|
2887
2976
|
/** Don't register in introspection */
|
|
2888
2977
|
skipRegistry?: boolean;
|
|
2978
|
+
/**
|
|
2979
|
+
* Default `limit` value when the request omits `?limit=`. Default 20.
|
|
2980
|
+
* Surfaced at the resource level (rather than only via a custom
|
|
2981
|
+
* `queryParser`) so hosts can declare it inline next to the resource
|
|
2982
|
+
* shape. Threads into both the auto-built controller's QueryResolver
|
|
2983
|
+
* AND the OpenAPI listQuery schema.
|
|
2984
|
+
*
|
|
2985
|
+
* For "fetch all" reference data, prefer the `referenceData: true`
|
|
2986
|
+
* shorthand below — it pins this AND `maxLimit` AND aggressive cache
|
|
2987
|
+
* defaults in one declaration.
|
|
2988
|
+
*/
|
|
2989
|
+
defaultLimit?: number;
|
|
2990
|
+
/**
|
|
2991
|
+
* URL-driven `limit` ceiling. Default 100. Requests with
|
|
2992
|
+
* `?limit=N` above this cap fail validation with a 400 that names
|
|
2993
|
+
* the cap (`Query parameter 'limit' must be <= 100 (got N)`,
|
|
2994
|
+
* `meta.cap: 100` on the wire envelope) — 2.17.0+ surfaces the cap
|
|
2995
|
+
* value programmatically so callers can self-correct.
|
|
2996
|
+
*/
|
|
2997
|
+
maxLimit?: number;
|
|
2998
|
+
/**
|
|
2999
|
+
* Mark this resource as **reference data** — small, mostly-static rows
|
|
3000
|
+
* (currencies, countries, plans, credentials, pipeline stages) that
|
|
3001
|
+
* callers want to fetch all of at once and cache aggressively.
|
|
3002
|
+
*
|
|
3003
|
+
* Sets the following defaults (each only when the corresponding
|
|
3004
|
+
* narrower flag is not explicitly set, so this stays composable):
|
|
3005
|
+
*
|
|
3006
|
+
* - `crud: { list: true, get: true }` — read-only surface (reference
|
|
3007
|
+
* data is rarely mutated through the public API; mutate via
|
|
3008
|
+
* migrations / admin tools).
|
|
3009
|
+
* - `defaultLimit: 1000` — one request returns everything by default.
|
|
3010
|
+
* - `maxLimit: 1000` — same cap so explicit `?limit=` requests
|
|
3011
|
+
* don't have to know the resource is reference data.
|
|
3012
|
+
* - `cache: { staleTime: 300, gcTime: 600 }` — 5 min fresh / 10 min
|
|
3013
|
+
* GC window so reference data isn't refetched on every request
|
|
3014
|
+
* (values are seconds per `ResourceCacheConfig`). No-op without
|
|
3015
|
+
* `queryCachePlugin` — same contract as the manual `cache` field;
|
|
3016
|
+
* a first-mount diagnostic fires when the plugin is missing.
|
|
3017
|
+
*
|
|
3018
|
+
* Pre-2.17.0 hosts achieved this by sprinkling `defaultLimit: 1000` +
|
|
3019
|
+
* a custom queryParser + a `cache:` block across every reference
|
|
3020
|
+
* resource. The shorthand collapses that to one flag.
|
|
3021
|
+
*
|
|
3022
|
+
* @example
|
|
3023
|
+
* ```ts
|
|
3024
|
+
* defineResource({
|
|
3025
|
+
* name: 'credential-type',
|
|
3026
|
+
* adapter,
|
|
3027
|
+
* referenceData: true,
|
|
3028
|
+
* });
|
|
3029
|
+
* ```
|
|
3030
|
+
*/
|
|
3031
|
+
referenceData?: boolean;
|
|
3032
|
+
/**
|
|
3033
|
+
* Shorthand for the "service resource" pattern — a resource that ships
|
|
3034
|
+
* ONLY custom routes (no auto-CRUD, no adapter, no validation, no
|
|
3035
|
+
* introspection registry entry). Setting `customRoutesOnly: true`
|
|
3036
|
+
* is equivalent to the three-flag combo:
|
|
3037
|
+
*
|
|
3038
|
+
* ```ts
|
|
3039
|
+
* { disableDefaultRoutes: true, skipValidation: true, skipRegistry: true }
|
|
3040
|
+
* ```
|
|
3041
|
+
*
|
|
3042
|
+
* Each of those flags stays public — power users that need to opt OUT
|
|
3043
|
+
* of one (e.g. `customRoutesOnly: true` + `skipRegistry: false` because
|
|
3044
|
+
* a custom-routes-only resource still wants OpenAPI docs) can pass the
|
|
3045
|
+
* narrow flag explicitly; explicit settings always win over the
|
|
3046
|
+
* shorthand. The shorthand only fills in flags that were not set.
|
|
3047
|
+
*
|
|
3048
|
+
* @example
|
|
3049
|
+
* ```ts
|
|
3050
|
+
* defineResource({
|
|
3051
|
+
* name: 'health',
|
|
3052
|
+
* customRoutesOnly: true,
|
|
3053
|
+
* routes: [{ method: 'GET', path: '/ping', handler: () => ({ ok: true }) }],
|
|
3054
|
+
* });
|
|
3055
|
+
* ```
|
|
3056
|
+
*/
|
|
3057
|
+
customRoutesOnly?: boolean;
|
|
2889
3058
|
/**
|
|
2890
3059
|
* Per-resource MCP opt-out.
|
|
2891
3060
|
*
|
|
@@ -3029,7 +3198,16 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3029
3198
|
readonly updateMethod?: "PUT" | "PATCH" | "both";
|
|
3030
3199
|
readonly pipe?: PipelineConfig;
|
|
3031
3200
|
readonly fields?: FieldPermissionMap;
|
|
3201
|
+
/**
|
|
3202
|
+
* Policy for field-write denials — `'reject'` (default, secure) raises 403
|
|
3203
|
+
* listing the denied fields; `'strip'` silently drops them. Honored by
|
|
3204
|
+
* both `BodySanitizer` (auto-CRUD) and `buildFieldWritePreHandler`
|
|
3205
|
+
* (custom routes) so the policy is consistent across all body-bearing
|
|
3206
|
+
* routes for the resource.
|
|
3207
|
+
*/
|
|
3208
|
+
readonly onFieldWriteDenied?: "reject" | "strip";
|
|
3032
3209
|
readonly cache?: ResourceCacheConfig;
|
|
3210
|
+
readonly referenceData: boolean;
|
|
3033
3211
|
/**
|
|
3034
3212
|
* Per-resource MCP opt-out. `false` keeps the resource out of every
|
|
3035
3213
|
* `mcpPlugin` registration regardless of the plugin's `expose` /
|
|
@@ -3066,8 +3244,34 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3066
3244
|
* `buildResourcePlugin` so the host's configured logger owns the
|
|
3067
3245
|
* output — the framework never speaks to `console.*` from `src/`
|
|
3068
3246
|
* outside of the CLI.
|
|
3247
|
+
*
|
|
3248
|
+
* Prefer the public {@link warnings} getter for programmatic access
|
|
3249
|
+
* (CI fail-on-warnings, lint integrations) — it returns a frozen
|
|
3250
|
+
* snapshot so callers can't accidentally mutate the diagnostic list.
|
|
3069
3251
|
*/
|
|
3070
3252
|
_diagnostics?: ResourceDiagnostic[];
|
|
3253
|
+
/**
|
|
3254
|
+
* Public view of {@link _diagnostics} for programmatic consumption.
|
|
3255
|
+
*
|
|
3256
|
+
* Returns the collected define-time diagnostics for this resource.
|
|
3257
|
+
* Empty when the resource validated cleanly. Useful for CI gates that
|
|
3258
|
+
* want to fail the build on any non-fatal warning:
|
|
3259
|
+
*
|
|
3260
|
+
* @example
|
|
3261
|
+
* ```ts
|
|
3262
|
+
* import { orderResource } from './resources/order.resource.js';
|
|
3263
|
+
* if (orderResource.warnings.length > 0) {
|
|
3264
|
+
* for (const w of orderResource.warnings) console.error(w.message);
|
|
3265
|
+
* process.exit(1);
|
|
3266
|
+
* }
|
|
3267
|
+
* ```
|
|
3268
|
+
*
|
|
3269
|
+
* **Scope.** Covers define-time only — diagnostics emitted at
|
|
3270
|
+
* first-mount (e.g. `cache.invalidateOn` without `queryCachePlugin`)
|
|
3271
|
+
* surface through `fastify.log.warn` instead and are not collected
|
|
3272
|
+
* here; they require a Fastify instance to detect.
|
|
3273
|
+
*/
|
|
3274
|
+
get warnings(): readonly ResourceDiagnostic[];
|
|
3071
3275
|
/**
|
|
3072
3276
|
* Per-host idempotency guard used by `buildResourcePlugin` to
|
|
3073
3277
|
* skip duplicate shared-state writes when the same resource is
|
|
@@ -3121,6 +3325,26 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3121
3325
|
* `BaseController<TDoc extends AnyRecord>` actually requires it.
|
|
3122
3326
|
* One internal boundary cast replaces N host-side casts.
|
|
3123
3327
|
*/
|
|
3328
|
+
/**
|
|
3329
|
+
* Narrow overload: when the host passes an `actions: { ... }` literal, the
|
|
3330
|
+
* returned `ResourceDefinition.actions` is typed as the exact captured map
|
|
3331
|
+
* (not the wide `ActionsMap | undefined`). Without this overload, hosts that
|
|
3332
|
+
* wrote `defineResource({ actions: { send: ..., receive: ... } })` saw
|
|
3333
|
+
* `resource.actions` typed `ActionsMap | undefined` and got
|
|
3334
|
+
* "'resource.actions' is possibly 'undefined'" on every `resource.actions.send`
|
|
3335
|
+
* call. TS infers `TActions` from the object literal at the call site —
|
|
3336
|
+
* no explicit generic needed.
|
|
3337
|
+
*/
|
|
3338
|
+
declare function defineResource<TDoc, TActions extends ActionsMap>(config: ResourceConfig<TDoc> & {
|
|
3339
|
+
actions: TActions;
|
|
3340
|
+
}): ResourceDefinition<TDoc> & {
|
|
3341
|
+
readonly actions: TActions;
|
|
3342
|
+
};
|
|
3343
|
+
/**
|
|
3344
|
+
* Wide overload: no `actions` declared. `ResourceDefinition<TDoc>.actions`
|
|
3345
|
+
* stays `ActionsMap | undefined` exactly as in pre-2.17.1 — no
|
|
3346
|
+
* back-compat breakage for hosts that never touched actions.
|
|
3347
|
+
*/
|
|
3124
3348
|
declare function defineResource<TDoc = AnyRecord>(config: ResourceConfig<TDoc>): ResourceDefinition<TDoc>;
|
|
3125
3349
|
//#endregion
|
|
3126
3350
|
//#region src/registry/ResourceRegistry.d.ts
|
|
@@ -3390,6 +3614,13 @@ interface CrudRouterOptions {
|
|
|
3390
3614
|
schemaOptions?: RouteSchemaOptions;
|
|
3391
3615
|
/** Field-level permissions (visibility, writability per role) */
|
|
3392
3616
|
fields?: FieldPermissionMap;
|
|
3617
|
+
/**
|
|
3618
|
+
* Policy for handling fields the caller lacks write permission for.
|
|
3619
|
+
* Auto-CRUD reads this via `BodySanitizer`; custom routes' field-write
|
|
3620
|
+
* preHandler honors the same setting. Default: `'reject'` — silent
|
|
3621
|
+
* stripping hides misconfigurations and real attacks.
|
|
3622
|
+
*/
|
|
3623
|
+
onFieldWriteDenied?: "reject" | "strip";
|
|
3393
3624
|
/** HTTP method for update routes. Default: 'PATCH' */
|
|
3394
3625
|
updateMethod?: "PUT" | "PATCH" | "both";
|
|
3395
3626
|
/**
|
|
@@ -3713,7 +3944,24 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
3713
3944
|
field: string;
|
|
3714
3945
|
maxRangeDays?: number;
|
|
3715
3946
|
}; /** Whether the aggregation requires named filters — surfaced in docs. */
|
|
3716
|
-
readonly requireFilters?: readonly string[];
|
|
3947
|
+
readonly requireFilters?: readonly string[];
|
|
3948
|
+
/**
|
|
3949
|
+
* Static result-row cap (no URL override). Mutually exclusive with
|
|
3950
|
+
* `defaultLimit` / `maxLimit`. Surfaced so OpenAPI docs can mention
|
|
3951
|
+
* the fixed cap in the description.
|
|
3952
|
+
*/
|
|
3953
|
+
readonly limit?: number;
|
|
3954
|
+
/**
|
|
3955
|
+
* URL-driven default limit (caller may pass `?limit=N`). When set,
|
|
3956
|
+
* OpenAPI docs render `?limit` as a query parameter with this
|
|
3957
|
+
* default and `maxLimit` as the maximum.
|
|
3958
|
+
*/
|
|
3959
|
+
readonly defaultLimit?: number;
|
|
3960
|
+
/**
|
|
3961
|
+
* URL-driven limit ceiling (only meaningful with `defaultLimit`).
|
|
3962
|
+
* OpenAPI docs render this as the `maximum` constraint on `?limit`.
|
|
3963
|
+
*/
|
|
3964
|
+
readonly maxLimit?: number; /** MCP tool generation flag — `false` to skip, object for overrides. */
|
|
3717
3965
|
readonly mcp?: boolean | {
|
|
3718
3966
|
readonly description?: string;
|
|
3719
3967
|
readonly annotations?: Record<string, unknown>;
|