@classytic/arc 2.41.1 → 2.43.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/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
- package/dist/{EventTransport-DSqD_Xsg.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
- package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
- package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
- package/dist/{ResourceRegistry-MGpXoxHZ.mjs → ResourceRegistry-CuCARu2B.mjs} +44 -13
- package/dist/actionPermissions-BotX1YOa.mjs +776 -0
- package/dist/audit/index.d.mts +43 -5
- package/dist/audit/index.mjs +48 -26
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +43 -13
- package/dist/auth/index.mjs +130 -155
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-DawKGoXb.mjs → authorizationDecision-DQYrkl5f.mjs} +7 -102
- package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
- package/dist/boundedMap-BULzfXYU.mjs +57 -0
- package/dist/{buildHandler-CvxKOc0m.mjs → buildHandler-CRQ8EcX5.mjs} +4 -4
- package/dist/cache/index.d.mts +22 -34
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-DbCBxlgw.mjs → caching-Cc2M1DwO.mjs} +3 -3
- package/dist/claimed-B10RTfkM.mjs +8 -0
- package/dist/cleanup/index.d.mts +6 -10
- package/dist/cleanup/index.mjs +27 -73
- package/dist/cli/commands/describe.d.mts +4 -2
- package/dist/cli/commands/describe.mjs +7 -4
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -2
- package/dist/cli/commands/init.mjs +2 -2
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-D5Ubn1fe.mjs → constants-Cu7Mx7_U.mjs} +4 -2
- package/dist/context/index.d.mts +2 -5
- package/dist/context/index.mjs +18 -38
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +8 -8
- package/dist/{core-5lazhva9.mjs → core-Cb0LjccA.mjs} +5 -4
- package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
- package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
- package/dist/{preloadResources-f3boenVZ.mjs → createApp-D-953ZAq.mjs} +299 -390
- package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
- package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
- package/dist/{defineResource-DxkMTjRn.mjs → defineResource-DIC7JZGU.mjs} +118 -1024
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +10 -4
- package/dist/docs/index.mjs +4 -4
- package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
- package/dist/{elevation-CkvG0LG2.mjs → elevation-DG8SrLN-.mjs} +41 -12
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-BnfDviL1.mjs} +26 -5
- package/dist/{errors-BH7YU_9O.mjs → errors-CtrywMHP.mjs} +58 -23
- package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
- package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
- package/dist/{eventPlugin-zZY-unhr.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
- package/dist/{subscribe-helpers-DatDkcuG.mjs → eventTypes-Cdhm6ZlV.mjs} +151 -17
- package/dist/events/index.d.mts +33 -8
- package/dist/events/index.mjs +49 -53
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +139 -157
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +2 -2
- package/dist/factory/index.d.mts +32 -17
- package/dist/factory/index.mjs +213 -32
- package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DhUJ-mvo.d.mts} +11 -25
- package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
- package/dist/{fields-BHrkoF5y.mjs → fields-ToPYpMqS.mjs} +1 -1
- package/dist/filter-merge-IpGywvym.mjs +99 -0
- package/dist/{generate-BLRf2m3y.mjs → generate-DRTkLlIl.mjs} +2 -2
- package/dist/{headers-BIXWKrzQ.mjs → headers-BbkIF6vA.mjs} +2 -2
- package/dist/health-D-Ft6JM6.mjs +22 -0
- package/dist/{health-ZRHeDHXH.mjs → health-DqbsG-_w.mjs} +115 -55
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +31 -7
- package/dist/idempotency/index.mjs +155 -98
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +48 -11
- package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
- package/dist/{preloadResources-CjS2OfpU.d.mts → index-C8zVGYsk.d.mts} +37 -38
- package/dist/{index-D4u76eQW.d.mts → index-CRoPtISf.d.mts} +23 -73
- package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
- package/dist/index-JdbZAIsm.d.mts +510 -0
- package/dist/{index-DHbyPo5j.d.mts → index-mqaqZNpe.d.mts} +253 -254
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +11 -11
- package/dist/{init-CpLkjU18.mjs → init-B_oUYDXI.mjs} +1 -1
- package/dist/integrations/event-gateway.d.mts +8 -3
- package/dist/integrations/event-gateway.mjs +4 -4
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +18 -5
- package/dist/integrations/mcp/index.d.mts +72 -59
- package/dist/integrations/mcp/index.mjs +19 -4
- package/dist/integrations/mcp/testing.d.mts +2 -2
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +78 -136
- package/dist/integrations/streamline.mjs +116 -54
- package/dist/integrations/webhooks.d.mts +60 -120
- package/dist/integrations/webhooks.mjs +36 -80
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.d.mts +3 -3
- package/dist/integrations/websocket.mjs +2 -2
- package/dist/{interface-DEWNOMza.d.mts → interface-C0395yUN.d.mts} +13 -1
- package/dist/{jsonBody-ELR9lfeU.mjs → jsonBody-B4YVGcql.mjs} +2 -2
- package/dist/{keys-cpZCtcIE.mjs → keys-B2KtgMLJ.mjs} +7 -18
- package/dist/{loadResources-BPfWiI5s.mjs → loadResources-Dn4lalw2.mjs} +3 -3
- package/dist/{loadResourcesFromEntry-ixtfL3J2.mjs → loadResourcesFromEntry-3sLYbuse.mjs} +2 -2
- package/dist/logger/index.d.mts +1 -8
- package/dist/logger/index.mjs +3 -153
- package/dist/logger-DXjfIqjV.mjs +160 -0
- package/dist/{mcpPlugin-BX5PujI0.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
- package/dist/{memory-Iotz0cCx.mjs → memory-9Cp7611C.mjs} +36 -34
- package/dist/{metrics-NMsmpRmY.mjs → metrics-CWI11eRu.mjs} +39 -72
- package/dist/middleware/index.d.mts +2 -2
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +2 -2
- package/dist/{module-DpLJG2_N.mjs → module-BSvOQC7E.mjs} +8 -8
- package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
- package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
- package/dist/{openapi-CmFf3yq_.mjs → openapi-DT_G4VhY.mjs} +48 -10
- package/dist/outbox-admin/index.d.mts +3 -3
- package/dist/outbox-admin/index.mjs +6 -18
- package/dist/{outbox-module-D2z5WkDY.mjs → outbox-module-Bjgzl6tg.mjs} +203 -33
- package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
- package/dist/permissions/index.d.mts +5 -4
- package/dist/permissions/index.mjs +8 -6
- package/dist/{permissions-D8TBH44E.mjs → permissions-Cm0uYHvd.mjs} +149 -33
- package/dist/{pipe-FMWNheSO.mjs → pipe-BqeGT6UO.mjs} +2 -2
- package/dist/pipeline/index.d.mts +2 -2
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plan-FVR6LP59.mjs +565 -0
- package/dist/{BaseController-CYEwjyjN.mjs → plugin-BccET1tL.mjs} +1256 -345
- package/dist/plugins/index.d.mts +14 -11
- package/dist/plugins/index.mjs +44 -22
- package/dist/plugins/response-cache.mjs +8 -14
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-CXWZIzTv.mjs → pluralize-CVIOcgMF.mjs} +1 -1
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +26 -31
- package/dist/presets/index.mjs +4 -4
- package/dist/presets/multiTenant.d.mts +2 -107
- package/dist/presets/multiTenant.mjs +22 -162
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-ZJSPgmT2.mjs → presets-Cpvj6fGO.mjs} +99 -107
- package/dist/{projection-BzIgzoKZ.mjs → projection-Ce4aQhQk.mjs} +2 -2
- package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
- package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
- package/dist/{queryCachePlugin-DZHPOm9p.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
- package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
- package/dist/{realtime-8tIQWZz4.mjs → realtime-DmPE9S54.mjs} +25 -21
- package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
- package/dist/{redis-Bzi1y3F9.d.mts → redis-vLvtcGy_.d.mts} +13 -24
- package/dist/redisShapes-CQksY67q.d.mts +55 -0
- package/dist/registry/index.d.mts +9 -4
- package/dist/registry/index.mjs +4 -4
- package/dist/{registry-DNkgGQcK.mjs → registry-Bcz9G1bh.mjs} +10 -8
- package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
- package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
- package/dist/{requestContext-cDkAUr-P.mjs → requestContext-Dsu9P1bV.mjs} +1 -1
- package/dist/requirement-CbiLdQ1n.d.mts +22 -0
- package/dist/requirement-KDdoRcOP.mjs +35 -0
- package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
- package/dist/resourceRateLimit-dSjVr1pk.mjs +180 -0
- package/dist/{rolldown-runtime-eysyzFAF.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
- package/dist/routeValidator-D36SW8qt.mjs +70 -0
- package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
- package/dist/{schedules-j6uXXAeJ.mjs → schedules-C-OpmHBq.mjs} +46 -37
- package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BORek3O-.mjs} +2 -2
- package/dist/{schemaIR-CTvPDoPB.mjs → schemaIR-C7Vbp71I.mjs} +2 -2
- package/dist/schemas/index.d.mts +10 -0
- package/dist/schemas/index.mjs +8 -3
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +6 -6
- package/dist/{scope-BUi1xV1I.mjs → scope-DQthe88b.mjs} +4 -4
- package/dist/shutdown-MqA81z_P.mjs +59 -0
- package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
- package/dist/{sse-BeqVKQKu.mjs → sse-CYzQBVFe.mjs} +11 -15
- package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-B5_Afiqn.mjs} +2 -2
- package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
- package/dist/{streaming-IcRFjWQU.mjs → streaming-DjTQhZrN.mjs} +1 -1
- package/dist/subscriberIdentity-CW39uA8G.mjs +28 -0
- package/dist/tenancy--ed9eH_z.mjs +43 -0
- package/dist/tenantPreparation-BKon6Yox.mjs +67 -0
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +193 -175
- package/dist/testing/index.mjs +286 -212
- package/dist/testing/outboxStoreContract.mjs +137 -2
- package/dist/testing/requiredPorts.d.mts +17 -0
- package/dist/testing/requiredPorts.mjs +65 -0
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/timing-qczZ0YC-.mjs +46 -0
- package/dist/{transactionContext-CPbdlnVw.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
- package/dist/{typeGuards-BLjPgVBm.mjs → typeGuards-CySF12G-.mjs} +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CWQn9-IC.d.mts → types-B4Aq4Sdj.d.mts} +22 -9
- package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
- package/dist/{types-CgenB1gv.mjs → types-Bk9NOgOC.mjs} +4 -4
- package/dist/usage/index.d.mts +2 -3
- package/dist/usage/index.mjs +5 -5
- package/dist/{userHelpers-ChRvkuSZ.mjs → userHelpers-aklawRaK.mjs} +1 -1
- package/dist/utils/index.d.mts +84 -223
- package/dist/utils/index.mjs +13 -12
- package/dist/{utils-CxcctrAh.mjs → utils-ec1oTo1n.mjs} +315 -404
- package/dist/{validate-DrYWnOzI.mjs → validate-9yzHC1e2.mjs} +6 -14
- package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
- package/dist/{versioning-DKkT4hNJ.mjs → versioning-fhhWHV74.mjs} +4 -4
- package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
- package/dist/{workScope-CxKqoIFn.mjs → workScope-zpPyge4t.mjs} +2 -2
- package/package.json +16 -12
- package/skills/arc/SKILL.md +13 -4
- package/skills/arc/references/auth.md +8 -5
- package/skills/arc/references/integrations.md +1 -1
- package/skills/arc/references/mcp.md +8 -6
- package/skills/arc/references/multi-tenancy.md +19 -14
- package/skills/arc/references/production.md +27 -10
- package/dist/actionPermissions-BoPGQ5-4.mjs +0 -350
- package/dist/index-CFfLKMH4.d.mts +0 -290
- package/dist/interface-YzIbF7Ii.mjs +0 -17
- package/dist/redis-stream-BPKscsTB.d.mts +0 -260
- package/dist/registerRoute-Bsnk6NiZ.mjs +0 -364
- package/dist/sync/changelog.d.mts +0 -3
- package/dist/sync/changelog.mjs +0 -3
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
|
|
2
2
|
import { P as ArcHeaders, i as RequestScope } from "./types-Ddl9OjmE.mjs";
|
|
3
|
-
import { d as UserBase, l as PermissionCheck, t as AnyRecord } from "./base-
|
|
4
|
-
import { a as QueryCacheConfig } from "./QueryCache
|
|
5
|
-
import "./elevation-
|
|
6
|
-
import
|
|
3
|
+
import { d as UserBase, l as PermissionCheck, t as AnyRecord } from "./base-CZyixEQR.mjs";
|
|
4
|
+
import { a as QueryCacheConfig } from "./QueryCache--5uAuYPW.mjs";
|
|
5
|
+
import "./elevation-BW2J3tNj.mjs";
|
|
6
|
+
import "./requirement-CbiLdQ1n.mjs";
|
|
7
|
+
import { n as FieldPermissionMap } from "./fields-BBSpf8pS.mjs";
|
|
7
8
|
import { n as EncryptionDirective } from "./types-ByHn9rEO.mjs";
|
|
8
|
-
import { n as DomainEvent } from "./EventTransport-
|
|
9
|
+
import { n as DomainEvent } from "./EventTransport-CiicKz2Z.mjs";
|
|
9
10
|
import { Filter } from "@classytic/repo-core/filter";
|
|
10
|
-
import { AggDateBucket, AggMeasure, AggTopN, QueryOptions,
|
|
11
|
+
import { AggDateBucket, AggMeasure, AggTopN, QueryOptions, TenantPurgeStrategy, TransactionHandle } from "@classytic/repo-core/repository";
|
|
11
12
|
import { KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
|
|
12
|
-
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod
|
|
13
|
+
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
|
|
13
14
|
import { DataAdapter, RepositoryLike } from "@classytic/repo-core/adapter";
|
|
14
15
|
import { LookupSpec } from "@classytic/repo-core/lookup";
|
|
15
16
|
import { FieldRule, SchemaBuilderOptions } from "@classytic/repo-core/schema";
|
|
@@ -608,6 +609,12 @@ interface ControllerConfigurableOptions {
|
|
|
608
609
|
* own `idField` when unset.
|
|
609
610
|
*/
|
|
610
611
|
idField?: string;
|
|
612
|
+
/**
|
|
613
|
+
* Further unique keys a single-row read and an action target accept, tried in order after
|
|
614
|
+
* `idField` (e.g. `['rmaNumber']` beside `_id`). Each lookup carries the same tenant and row
|
|
615
|
+
* policy as the primary one — an alternate key never widens what a caller can reach.
|
|
616
|
+
*/
|
|
617
|
+
lookupFields?: readonly string[];
|
|
611
618
|
/**
|
|
612
619
|
* Custom filter matching for policy enforcement.
|
|
613
620
|
* Provided by the DataAdapter for non-MongoDB databases (SQL, etc.).
|
|
@@ -645,6 +652,10 @@ interface ControllerConfigurableOptions {
|
|
|
645
652
|
* `configure()` like `writes`.
|
|
646
653
|
*/
|
|
647
654
|
transactional?: boolean;
|
|
655
|
+
/** Optimistic concurrency — see `ResourceConfig.concurrency`. */
|
|
656
|
+
concurrency?: {
|
|
657
|
+
versionField?: string;
|
|
658
|
+
};
|
|
648
659
|
}
|
|
649
660
|
/**
|
|
650
661
|
* Full constructor options — union of construction-only + configurable.
|
|
@@ -1103,9 +1114,9 @@ declare class QueryResolver {
|
|
|
1103
1114
|
/** Sanitize advanced populate options against allowedPopulate */
|
|
1104
1115
|
private sanitizePopulateOptions;
|
|
1105
1116
|
/**
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1108
|
-
*
|
|
1117
|
+
* Client lookups, deny-by-default. An allowed one is structurally validated, and its joined side
|
|
1118
|
+
* is filtered by the resource's server-owned `LookupPolicy.scope` — the base collection's tenant
|
|
1119
|
+
* filter never reaches a join, and a client-supplied `where` is replaced, never merged.
|
|
1109
1120
|
*/
|
|
1110
1121
|
private sanitizeLookups;
|
|
1111
1122
|
/**
|
|
@@ -1147,10 +1158,7 @@ declare class QueryResolver {
|
|
|
1147
1158
|
//#region src/core/BaseCrudController.d.ts
|
|
1148
1159
|
/**
|
|
1149
1160
|
* Framework-agnostic CRUD controller implementing IController.
|
|
1150
|
-
*
|
|
1151
|
-
* Composes AccessControl, BodySanitizer, and QueryResolver. All shared
|
|
1152
|
-
* state and helpers are `protected` so the preset mixins (SoftDelete,
|
|
1153
|
-
* Tree, Slug, Bulk) can extend cleanly.
|
|
1161
|
+
* Composes AccessControl, BodySanitizer, and QueryResolver.
|
|
1154
1162
|
*
|
|
1155
1163
|
* @template TDoc - The document type.
|
|
1156
1164
|
* @template TRepository - The repository type (defaults to RepositoryLike).
|
|
@@ -1164,29 +1172,14 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1164
1172
|
protected resourceName?: string;
|
|
1165
1173
|
protected tenantField: string | false;
|
|
1166
1174
|
protected idField: string;
|
|
1167
|
-
/**
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
* supplies tenant/idField/matchesFilter post-construction. Same model as
|
|
1172
|
-
* `queryResolver` after `setQueryParser` shipped in 2.10.9.
|
|
1173
|
-
*/
|
|
1175
|
+
/** Alternate unique keys, each with its own access control over the SAME scope. */
|
|
1176
|
+
protected lookupAccess: AccessControl[];
|
|
1177
|
+
private _lookupFields;
|
|
1178
|
+
/** Access control (ID filtering, policy checks, org scope, ownership). */
|
|
1174
1179
|
accessControl: AccessControl;
|
|
1175
|
-
/**
|
|
1176
|
-
* Composable body sanitization (field permissions, system fields).
|
|
1177
|
-
*
|
|
1178
|
-
* Not `readonly` — `configure()` rebuilds it when the host
|
|
1179
|
-
* supplies schemaOptions/onFieldWriteDenied post-construction.
|
|
1180
|
-
*/
|
|
1180
|
+
/** Body sanitization (field permissions, system fields). */
|
|
1181
1181
|
bodySanitizer: BodySanitizer;
|
|
1182
|
-
/**
|
|
1183
|
-
* Composable query resolution (parsing, pagination, sort, select/populate).
|
|
1184
|
-
*
|
|
1185
|
-
* Not `readonly` — `setQueryParser()` rebuilds this resolver to swap in a
|
|
1186
|
-
* different parser (e.g. mongokit's `QueryParser`). `defineResource` calls
|
|
1187
|
-
* it automatically when a resource supplies both `controller` and
|
|
1188
|
-
* `queryParser`.
|
|
1189
|
-
*/
|
|
1182
|
+
/** Query resolution (parsing, pagination, sort, select/populate). */
|
|
1190
1183
|
queryResolver: QueryResolver;
|
|
1191
1184
|
protected _matchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;
|
|
1192
1185
|
protected _presetFields: {
|
|
@@ -1195,82 +1188,51 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1195
1188
|
};
|
|
1196
1189
|
protected _cacheConfig?: ResourceCacheConfig;
|
|
1197
1190
|
/**
|
|
1198
|
-
* Retained
|
|
1199
|
-
*
|
|
1200
|
-
* falling back to the sub-component defaults (`-createdAt` sort,
|
|
1201
|
-
* `reject` write-denial) — `defaultSort: false` matters for kits
|
|
1202
|
-
* without a `createdAt` column, `strip` for legacy write semantics.
|
|
1191
|
+
* Retained so partial `configure()` rebuilds reuse them instead of falling back
|
|
1192
|
+
* to sub-component defaults (`-createdAt` sort breaks kits without that column).
|
|
1203
1193
|
*/
|
|
1204
1194
|
protected _defaultSort?: string | false;
|
|
1205
1195
|
protected _onFieldWriteDenied?: FieldWriteDenialPolicy;
|
|
1206
1196
|
protected _onImmutableWrite?: FieldWriteDenialPolicy;
|
|
1207
1197
|
/**
|
|
1208
|
-
* Domain commands bound to the write slots
|
|
1209
|
-
*
|
|
1210
|
-
* injection, actor stamp, hook sandwich) runs exactly as it does without
|
|
1211
|
-
* one, which is the entire point of the seam.
|
|
1198
|
+
* Domain commands bound to the write slots. Consulted at the PERSISTENCE step
|
|
1199
|
+
* only — sanitizer, tenant injection, actor stamp, and hooks run unchanged.
|
|
1212
1200
|
*/
|
|
1213
1201
|
protected _writes?: ResourceWrites;
|
|
1214
1202
|
/** Transactional write envelope — see `ResourceConfig.transactional`. */
|
|
1215
1203
|
protected _transactional: boolean;
|
|
1204
|
+
/** Version field under optimistic concurrency — `null` when the resource did not opt in. */
|
|
1205
|
+
protected _versionField: string | null;
|
|
1216
1206
|
constructor(repository: TRepository, options?: BaseControllerOptions);
|
|
1217
1207
|
/**
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
1220
|
-
*
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
*
|
|
1225
|
-
*
|
|
1226
|
-
*
|
|
1208
|
+
* Bind the resource this controller serves — hooks, audit, events and cache key on it.
|
|
1209
|
+
* `defineResource` calls it for a supplied controller; an unset name is assigned, a
|
|
1210
|
+
* different one throws (the controller would run another resource's hooks).
|
|
1211
|
+
*/
|
|
1212
|
+
bindResourceName(name: string): void;
|
|
1213
|
+
/**
|
|
1214
|
+
* Swap the controller's query parser. Mutates the existing `QueryResolver` in
|
|
1215
|
+
* place, so the resolver stays referentially stable. `defineResource` calls this
|
|
1216
|
+
* (duck-typed) when a resource supplies both `controller` and `queryParser`.
|
|
1227
1217
|
*/
|
|
1228
1218
|
setQueryParser(queryParser: QueryParserInterface): void;
|
|
1229
1219
|
/**
|
|
1230
|
-
* Apply resource-level options to a
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1234
|
-
* user-supplied controller never received them. Hosts had to remember to
|
|
1235
|
-
* forward each one through `super(repo, { ... })` — easy to miss, silently
|
|
1236
|
-
* mis-scopes queries when missed.
|
|
1237
|
-
*
|
|
1238
|
-
* `defineResource()` now calls `controller.configure(resolvedOpts)` after
|
|
1239
|
-
* `resolveOrAutoCreateController()` runs. Configure-aware controllers receive
|
|
1240
|
-
* the resolved values; arc skips the dropped-options warn for them.
|
|
1241
|
-
*
|
|
1242
|
-
* Only the keys that affect cross-cutting state (tenant scope, schema/field
|
|
1243
|
-
* rules, sort/limit policy, cache, write-denial policy) are honoured —
|
|
1244
|
-
* `repository` / `resourceName` are constructor-only because they participate
|
|
1245
|
-
* in mixin composition. Each known key rebuilds the affected sub-component
|
|
1246
|
-
* (AccessControl / BodySanitizer / QueryResolver) so referentially-stable
|
|
1247
|
-
* consumers don't see stale state.
|
|
1220
|
+
* Apply resource-level options to a controller AFTER construction.
|
|
1221
|
+
* `defineResource()` calls this once after resolving the controller, so custom
|
|
1222
|
+
* controllers receive tenant scope, field rules, sort/limit, cache, and
|
|
1223
|
+
* write-denial policy without forwarding them through `super(repo, { … })`.
|
|
1248
1224
|
*
|
|
1249
|
-
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
* Type narrowed to `ControllerConfigurableOptions` — `resourceName` is
|
|
1253
|
-
* construction-only and intentionally excluded so accidental "rename
|
|
1254
|
-
* the resource at runtime" calls fail to typecheck.
|
|
1225
|
+
* Each key rebuilds only the affected sub-component. Idempotent.
|
|
1226
|
+
* `repository` / `resourceName` are construction-only and excluded by type.
|
|
1255
1227
|
*/
|
|
1256
1228
|
configure(options: ControllerConfigurableOptions): void;
|
|
1257
|
-
/**
|
|
1258
|
-
* Get the tenant field name if multi-tenant scoping is enabled.
|
|
1259
|
-
* Returns `undefined` when `tenantField` is `false`.
|
|
1260
|
-
*/
|
|
1229
|
+
/** Tenant field name, or `undefined` when `tenantField` is `false`. */
|
|
1261
1230
|
protected getTenantField(): string | undefined;
|
|
1262
1231
|
/**
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1267
|
-
* and duck-typed consumers (aggregation router, MCP bridge) keep the
|
|
1268
|
-
* same `protected` entry point.
|
|
1269
|
-
*
|
|
1270
|
-
* Method kept named `tenantRepoOptions` for back-compat with hosts
|
|
1271
|
-
* that spread `...this.tenantRepoOptions(req)` (10+ call sites in
|
|
1272
|
-
* arc, plus host overrides). The bag has always grown over time —
|
|
1273
|
-
* hosts that don't want audit forwarding never read those keys.
|
|
1232
|
+
* Canonical repo-options bag (tenant scope, audit attribution, trace
|
|
1233
|
+
* correlation) — see `buildTenantRepoOptions` in `crud/requestPipeline.ts`.
|
|
1234
|
+
* Kept as a `protected` entry point for host overrides and duck-typed
|
|
1235
|
+
* consumers (aggregation router, MCP bridge).
|
|
1274
1236
|
*/
|
|
1275
1237
|
protected tenantRepoOptions(req: IRequestContext): AnyRecord;
|
|
1276
1238
|
/** Extract typed Arc internal metadata from request */
|
|
@@ -1285,18 +1247,11 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1285
1247
|
*/
|
|
1286
1248
|
protected resolveRepoId(id: string, existing: AnyRecord | null): string;
|
|
1287
1249
|
/**
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1291
|
-
* repo: (1) extract `:id`, (2) fetch under access control + tenant scope,
|
|
1292
|
-
* (3) verify ownership, (4) translate the route id to the repo's primary
|
|
1293
|
-
* key. Returning `{id, existing, repoId}` keeps the call sites a single
|
|
1294
|
-
* line and makes drift between `update` and `delete` structurally
|
|
1295
|
-
* impossible — there is one preflight, one denial-reason mapping, one
|
|
1296
|
-
* ownership check.
|
|
1250
|
+
* Shared preflight for mutations: extract `:id`, fetch under access control +
|
|
1251
|
+
* tenant scope, verify ownership, translate the route id to the repo primary
|
|
1252
|
+
* key. One path so `update` and `delete` cannot drift.
|
|
1297
1253
|
*
|
|
1298
|
-
*
|
|
1299
|
-
* need to widen the fetch (`{ includeDeleted: true }`).
|
|
1254
|
+
* `extraFetchOptions` widens the fetch (e.g. restore's `{ includeDeleted: true }`).
|
|
1300
1255
|
*/
|
|
1301
1256
|
protected loadMutableTarget(req: IRequestContext, extraFetchOptions?: AnyRecord): Promise<{
|
|
1302
1257
|
id: string;
|
|
@@ -1304,18 +1259,45 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1304
1259
|
repoId: string;
|
|
1305
1260
|
}>;
|
|
1306
1261
|
/**
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1262
|
+
* Whether the target of an id-bound ACTION is a row this request may read — the same
|
|
1263
|
+
* compound filter (id + tenant + `_policyFilters`) `GET /:id` uses. The action router
|
|
1264
|
+
* refuses the action with a 404 when it is not, so a caller can act on a row iff it can
|
|
1265
|
+
* read it. Skips the read when neither tenancy nor a row policy narrows the request.
|
|
1266
|
+
*/
|
|
1267
|
+
private buildLookupAccess;
|
|
1268
|
+
/**
|
|
1269
|
+
* THE single-row lookup — `GET /:id` and the action-target fence both read through it.
|
|
1270
|
+
* The primary key first; when it finds nothing (or the id cannot be that key's type), each
|
|
1271
|
+
* `lookupFields` key in turn, under the same tenant and row policy.
|
|
1272
|
+
*/
|
|
1273
|
+
protected fetchByAnyKey(id: string, req: IRequestContext, options: QueryOptions | undefined): Promise<{
|
|
1274
|
+
doc: TDoc | null;
|
|
1275
|
+
reason: FetchDenialReason | null;
|
|
1276
|
+
}>;
|
|
1277
|
+
/** Whether {@link isActionTargetVisible} can answer — it reads through the repository. */
|
|
1278
|
+
canFenceActionTargets(): boolean;
|
|
1279
|
+
isActionTargetVisible(req: IRequestContext, id: string): Promise<boolean>;
|
|
1280
|
+
/** `ETag: "<version>"` for a versioned document under optimistic concurrency. */
|
|
1281
|
+
protected etagHeaders(doc: unknown): Record<string, string> | undefined;
|
|
1282
|
+
/**
|
|
1283
|
+
* The version an update must apply at. `If-Match` must name the CURRENT version or the
|
|
1284
|
+
* request fails 412 before anything is written; without it (or with `*`) the write is a CAS
|
|
1285
|
+
* on the version just loaded, so a concurrent write in between is a 409, never overwritten.
|
|
1286
|
+
*/
|
|
1287
|
+
protected expectedVersion(req: IRequestContext, existing: unknown): {
|
|
1288
|
+
version?: number;
|
|
1289
|
+
fromClient: boolean;
|
|
1290
|
+
};
|
|
1291
|
+
/**
|
|
1292
|
+
* 404 thrower. Maps the `fetchDetailed()` denial reason into `details.code` so
|
|
1293
|
+
* "doesn't exist" is distinguishable from "filtered by policy/org scope".
|
|
1311
1294
|
*/
|
|
1312
1295
|
protected throwNotFound(reason?: FetchDenialReason | null): never;
|
|
1313
1296
|
/** Resolve cache config for a specific operation, merging per-op overrides */
|
|
1314
1297
|
protected resolveCacheConfig(operation: "list" | "byId"): QueryCacheConfig | null;
|
|
1315
1298
|
/**
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
* Universal resources (tenantField: false) get shared cache keys.
|
|
1299
|
+
* User/org IDs for cache key scoping. orgId is included only when `tenantField`
|
|
1300
|
+
* is set; universal resources share cache keys.
|
|
1319
1301
|
*/
|
|
1320
1302
|
protected cacheScope(req: IRequestContext): {
|
|
1321
1303
|
userId?: string;
|
|
@@ -1325,20 +1307,13 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1325
1307
|
protected cacheResponse<T>(data: T, cacheStatus: CacheStatus): IControllerResponse<T>;
|
|
1326
1308
|
/** Required route-id helper shared by get/update/delete. Throws on missing id. */
|
|
1327
1309
|
protected requireIdParam(req: IRequestContext): string;
|
|
1328
|
-
/**
|
|
1329
|
-
* Normalizes `repo.exists()` return shapes across adapters. Per
|
|
1330
|
-
* StandardRepo's contract, `exists` may return `boolean`, `{ _id }`,
|
|
1331
|
-
* or `null` — every truthy non-null shape collapses to `true`.
|
|
1332
|
-
*/
|
|
1310
|
+
/** Normalizes `repo.exists()` (`boolean | { _id } | null`) to a boolean. */
|
|
1333
1311
|
protected isExistsTruthy(result: unknown): boolean;
|
|
1334
1312
|
/** Assemble the per-request hook context from the protected accessors. */
|
|
1335
1313
|
private hookOpContext;
|
|
1336
1314
|
/**
|
|
1337
|
-
* Run
|
|
1338
|
-
*
|
|
1339
|
-
* `ArcError` (status 400, code `BEFORE_<OP>_HOOK_ERROR`) when the
|
|
1340
|
-
* before-hook fails. Full contract on `executeHookedOp` in
|
|
1341
|
-
* `crud/requestPipeline.ts`.
|
|
1315
|
+
* Run before + around hooks (or just the executor if none are wired) and
|
|
1316
|
+
* return the result. A failing before-hook throws 400 `BEFORE_<OP>_HOOK_ERROR`.
|
|
1342
1317
|
*/
|
|
1343
1318
|
protected runHookedOpUntilResult<TInput, TResult>(req: IRequestContext, args: {
|
|
1344
1319
|
op: "create" | "update" | "delete";
|
|
@@ -1348,14 +1323,10 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1348
1323
|
}, executor: (processed: TInput) => Promise<TResult>): Promise<TResult>;
|
|
1349
1324
|
/**
|
|
1350
1325
|
* Run a write's PERSISTENCE step, transactionally when the resource asked.
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
* transaction), transient conflicts re-run `fn` with jittered bounded
|
|
1356
|
-
* backoff, everything else — including `VersionConflictError` — surfaces
|
|
1357
|
-
* once. Hooks stay OUTSIDE: before/around ran already and must not re-run;
|
|
1358
|
-
* after-hooks run post-commit, which they already do by running after this.
|
|
1326
|
+
* Transactional mode uses `retryingTransaction`: `fn` gets the TX-BOUND repo and
|
|
1327
|
+
* re-runs on transient conflicts; anything else (incl. `VersionConflictError`)
|
|
1328
|
+
* surfaces once. Hooks stay OUTSIDE so before/around never re-run and after
|
|
1329
|
+
* runs post-commit.
|
|
1359
1330
|
*/
|
|
1360
1331
|
protected runWritePersistence<T>(fn: (repo: TRepository, uow?: TransactionHandle) => Promise<T>): Promise<T>;
|
|
1361
1332
|
/** Assemble the `WriteContext` a `create` verb receives. */
|
|
@@ -1363,43 +1334,26 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1363
1334
|
/**
|
|
1364
1335
|
* Assemble the `MutationWriteContext` an `update` / `delete` verb receives.
|
|
1365
1336
|
*
|
|
1366
|
-
* `existing` is the document
|
|
1367
|
-
*
|
|
1368
|
-
*
|
|
1369
|
-
* it over rather than letting the verb re-fetch is deliberate: a second
|
|
1370
|
-
* read can observe a different document than the one the request was
|
|
1371
|
-
* authorised against.
|
|
1372
|
-
*
|
|
1373
|
-
* `id` is the REPOSITORY PRIMARY KEY (`repoId`), never the raw route param —
|
|
1374
|
-
* see the note on `MutationWriteContext.id`. Callers pass `resolveRepoId`'s
|
|
1375
|
-
* output.
|
|
1337
|
+
* `existing` is the document the request was AUTHORISED against — handed over
|
|
1338
|
+
* so the verb does not re-fetch and possibly observe a different document.
|
|
1339
|
+
* `id` is the REPOSITORY PRIMARY KEY (`resolveRepoId` output), never the route param.
|
|
1376
1340
|
*/
|
|
1377
1341
|
protected mutationWriteContext(req: IRequestContext, id: string, existing: unknown, repo?: TRepository, uow?: TransactionHandle): MutationWriteContext;
|
|
1378
1342
|
/**
|
|
1379
|
-
* Run
|
|
1380
|
-
*
|
|
1381
|
-
* wants downstream after-handlers to receive — typically the result for
|
|
1382
|
-
* create/update, the original input (`existing`) for delete.
|
|
1343
|
+
* Run after-hooks for `op`. No-op without hooks or `resourceName`. `data` is the
|
|
1344
|
+
* result for create/update, `existing` for delete.
|
|
1383
1345
|
*/
|
|
1384
1346
|
protected runAfterHook(req: IRequestContext, op: "create" | "update" | "delete" | "list" | "read", data: AnyRecord, meta?: Record<string, unknown>): Promise<void>;
|
|
1385
1347
|
/**
|
|
1386
|
-
* Per-
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1389
|
-
* readers must produce 1 DB query, not 1,000). Keys already encode the
|
|
1390
|
-
* resolved query + user/org scope + resource version, so coalescing can
|
|
1391
|
-
* never cross tenants, scopes, or invalidation generations. Per-process
|
|
1392
|
-
* by design; distributed refresh leases are a store-level concern
|
|
1393
|
-
* (planned with the repo-core CacheEngine façade, which ships
|
|
1394
|
-
* single-flight natively).
|
|
1348
|
+
* Per-process single-flight for cache fills: concurrent requests for the SAME
|
|
1349
|
+
* key coalesce onto one repository query. Keys encode query + user/org scope +
|
|
1350
|
+
* resource version, so coalescing never crosses tenants or invalidation generations.
|
|
1395
1351
|
*/
|
|
1396
1352
|
private readonly inFlightCacheFills;
|
|
1397
1353
|
private cacheSingleFlight;
|
|
1398
1354
|
/**
|
|
1399
|
-
* Register a background SWR refresh for `key` unless one is
|
|
1400
|
-
*
|
|
1401
|
-
* burst of stale readers arriving in the same tick schedules exactly one
|
|
1402
|
-
* revalidation.
|
|
1355
|
+
* Register a background SWR refresh for `key` unless one is in flight.
|
|
1356
|
+
* Registration is SYNCHRONOUS so a same-tick burst of stale readers schedules one.
|
|
1403
1357
|
*/
|
|
1404
1358
|
private scheduleCacheRefresh;
|
|
1405
1359
|
/** Cached `list()` flow with SWR semantics. Returns null when cache is disabled. */
|
|
@@ -1408,19 +1362,14 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1408
1362
|
protected withGetCache(req: IRequestContext, id: string, options: ControllerQueryOptions): Promise<IControllerResponse<TDoc> | null>;
|
|
1409
1363
|
list(req: IRequestContext): Promise<IControllerResponse<ListResult<TDoc>>>;
|
|
1410
1364
|
/**
|
|
1411
|
-
* Resource-dispatch verbs router
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
* Verbs (mutually exclusive — first match wins):
|
|
1365
|
+
* Resource-dispatch verbs router; `null` for a regular list query.
|
|
1366
|
+
* First match wins:
|
|
1415
1367
|
* - `?_count=true` → `{ count: number }` via `repo.count()`
|
|
1416
1368
|
* - `?_distinct=field` → `unknown[]` via `repo.distinct(field)`
|
|
1417
1369
|
* - `?_exists=true` → `{ exists: boolean }` via `repo.exists()`
|
|
1418
1370
|
*
|
|
1419
|
-
* All
|
|
1420
|
-
*
|
|
1421
|
-
* method get a `501` so failures surface loudly instead of falling
|
|
1422
|
-
* back to a full table scan. Matching + repo invocation live in
|
|
1423
|
-
* `crud/resourceVerbs.ts`.
|
|
1371
|
+
* All share the resolved filter (query + policy + tenant scope). A missing repo
|
|
1372
|
+
* method is a `501`, never a fallback to a full scan.
|
|
1424
1373
|
*/
|
|
1425
1374
|
protected dispatchResourceVerb(req: IRequestContext): Promise<IControllerResponse<unknown>> | null;
|
|
1426
1375
|
/** Resolve filter + tenant/audit options for a dispatch verb. */
|
|
@@ -1438,13 +1387,8 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1438
1387
|
exists: boolean;
|
|
1439
1388
|
}>>;
|
|
1440
1389
|
/**
|
|
1441
|
-
* True when `field` is safe to expose via `_distinct`.
|
|
1442
|
-
*
|
|
1443
|
-
* Read-side gate only — only `hidden: true` blocks. `systemManaged`
|
|
1444
|
-
* is a *write* rule (clients can't PATCH the value); the field is
|
|
1445
|
-
* still in every list response, so blocking `_distinct` adds nothing
|
|
1446
|
-
* but inconvenience. See `core/fieldRulePredicates.ts` for the
|
|
1447
|
-
* canonical predicate shared with `QueryResolver`.
|
|
1390
|
+
* True when `field` is safe to expose via `_distinct`. Only `hidden: true`
|
|
1391
|
+
* blocks — `systemManaged` is a write rule and the field is already readable.
|
|
1448
1392
|
*/
|
|
1449
1393
|
protected isFieldExposedForRead(field: string): boolean;
|
|
1450
1394
|
/** Execute list query through hooks (extracted for cache revalidation) */
|
|
@@ -1467,7 +1411,7 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1467
1411
|
//#region src/core/mixins/bulk.d.ts
|
|
1468
1412
|
/** Public surface contributed by BulkMixin. */
|
|
1469
1413
|
interface BulkExt {
|
|
1470
|
-
bulkCreate(req: IRequestContext): Promise<IControllerResponse<
|
|
1414
|
+
bulkCreate(req: IRequestContext): Promise<IControllerResponse<object[]>>;
|
|
1471
1415
|
bulkUpdate(req: IRequestContext): Promise<IControllerResponse<{
|
|
1472
1416
|
matchedCount: number;
|
|
1473
1417
|
modifiedCount: number;
|
|
@@ -1476,69 +1420,42 @@ interface BulkExt {
|
|
|
1476
1420
|
deletedCount: number;
|
|
1477
1421
|
}>>;
|
|
1478
1422
|
}
|
|
1479
|
-
declare function BulkMixin<TBase extends MixinConstructor<BaseCrudController
|
|
1423
|
+
declare function BulkMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<BulkExt>;
|
|
1480
1424
|
//#endregion
|
|
1481
1425
|
//#region src/core/mixins/slug.d.ts
|
|
1482
1426
|
/** Public surface contributed by SlugMixin. */
|
|
1483
1427
|
interface SlugExt {
|
|
1484
|
-
getBySlug(req: IRequestContext): Promise<IControllerResponse<
|
|
1428
|
+
getBySlug(req: IRequestContext): Promise<IControllerResponse<object>>;
|
|
1485
1429
|
}
|
|
1486
|
-
declare function SlugMixin<TBase extends MixinConstructor<BaseCrudController
|
|
1430
|
+
declare function SlugMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<SlugExt>;
|
|
1487
1431
|
//#endregion
|
|
1488
1432
|
//#region src/core/mixins/tree.d.ts
|
|
1489
1433
|
/** Public surface contributed by TreeMixin. */
|
|
1490
1434
|
interface TreeExt {
|
|
1491
|
-
getTree(req: IRequestContext): Promise<IControllerResponse<
|
|
1492
|
-
getChildren(req: IRequestContext): Promise<IControllerResponse<
|
|
1435
|
+
getTree(req: IRequestContext): Promise<IControllerResponse<object[]>>;
|
|
1436
|
+
getChildren(req: IRequestContext): Promise<IControllerResponse<object[]>>;
|
|
1493
1437
|
}
|
|
1494
|
-
declare function TreeMixin<TBase extends MixinConstructor<BaseCrudController
|
|
1438
|
+
declare function TreeMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<TreeExt>;
|
|
1495
1439
|
//#endregion
|
|
1496
1440
|
//#region src/core/mixins/softDelete.d.ts
|
|
1497
1441
|
/** Public surface contributed by SoftDeleteMixin. */
|
|
1498
1442
|
interface SoftDeleteExt {
|
|
1499
|
-
getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginationResult<
|
|
1500
|
-
restore(req: IRequestContext): Promise<IControllerResponse<
|
|
1443
|
+
getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginationResult<object>>>;
|
|
1444
|
+
restore(req: IRequestContext): Promise<IControllerResponse<object>>;
|
|
1501
1445
|
}
|
|
1502
|
-
declare function SoftDeleteMixin<TBase extends MixinConstructor<BaseCrudController
|
|
1446
|
+
declare function SoftDeleteMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<SoftDeleteExt>;
|
|
1503
1447
|
//#endregion
|
|
1504
1448
|
//#region src/core/BaseController.d.ts
|
|
1505
|
-
/**
|
|
1506
|
-
* The `Arc*Result` types above stay: `src/index.ts` re-exports them from THIS
|
|
1507
|
-
* path. `BaseCrudController` and the four mixins (+ their `*Ext` types) were
|
|
1508
|
-
* also re-exported here and are not, as of 2.37.1 — `core/index.ts` takes them
|
|
1509
|
-
* straight from `./BaseCrudController.js` and `./mixins/*.js`, so this file's
|
|
1510
|
-
* copies had no importer. Two public paths to one symbol is a drift seam, not
|
|
1511
|
-
* convenience: composition belongs to `core/index.ts`, and this module is the
|
|
1512
|
-
* composed `BaseController` itself.
|
|
1513
|
-
*/
|
|
1514
1449
|
/**
|
|
1515
1450
|
* Fully-composed controller shape: all CRUD methods + every preset method
|
|
1516
1451
|
* (SoftDelete / Tree / Slug / Bulk) typed over the caller-supplied `TDoc`.
|
|
1517
1452
|
*
|
|
1518
|
-
*
|
|
1519
|
-
*
|
|
1520
|
-
* `
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
1523
|
-
* DECLARATION MERGING, not a generic mixin chain: TypeScript cannot thread one
|
|
1524
|
-
* `TDoc` through four chained mixin factories without losing the
|
|
1525
|
-
* `extends Constructor<Base>` constraint. So the runtime pins the chain base to
|
|
1526
|
-
* `AnyRecord` while the TYPE surface threads `TDoc`, and merging bridges them.
|
|
1527
|
-
*
|
|
1528
|
-
* `TDoc extends AnyRecord` is LOAD-BEARING: inherited methods return
|
|
1529
|
-
* `ListResult<AnyRecord>` and the merged interface returns `ListResult<TDoc>`,
|
|
1530
|
-
* so TS's derived-assignable-to-base check needs the bound. Without it the
|
|
1531
|
-
* class declaration fails with `Type 'TDoc[]' is not assignable to type
|
|
1532
|
-
* 'AnyRecord[]'`. Hosts pick one of:
|
|
1533
|
-
* (a) drop the generic — lose return narrowing;
|
|
1534
|
-
* (b) `interface IUser extends AnyRecord` — preferred when you own the type;
|
|
1535
|
-
* (c) `ArcListResult<typeof this>` etc. when overriding — reads the return
|
|
1536
|
-
* type off the class, so the bound never applies.
|
|
1537
|
-
*
|
|
1538
|
-
* COST: every `TDoc`-narrowed method is redeclared on the interface, so a new
|
|
1539
|
-
* mixin method must be added here too.
|
|
1453
|
+
* DECLARATION MERGING, not a generic mixin chain: chained mixin factories cannot thread one
|
|
1454
|
+
* `TDoc`, so the runtime chain is instantiated over `object` and the TYPE surface threads `TDoc`.
|
|
1455
|
+
* Any `TDoc extends object` works — an interface-typed document needs no index signature.
|
|
1456
|
+
* A new mixin method must be redeclared here too.
|
|
1540
1457
|
*/
|
|
1541
|
-
interface BaseController<TDoc extends
|
|
1458
|
+
interface BaseController<TDoc extends object = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> {
|
|
1542
1459
|
accessControl: AccessControl;
|
|
1543
1460
|
bodySanitizer: BodySanitizer;
|
|
1544
1461
|
queryResolver: QueryResolver;
|
|
@@ -1567,14 +1484,14 @@ interface BaseController<TDoc extends AnyRecord = AnyRecord, _TRepository extend
|
|
|
1567
1484
|
deletedCount: number;
|
|
1568
1485
|
}>>;
|
|
1569
1486
|
}
|
|
1570
|
-
declare const BaseController_base:
|
|
1487
|
+
declare const BaseController_base: {
|
|
1488
|
+
new (repository: RepositoryLike, options?: BaseControllerOptions): BaseCrudController<object, RepositoryLike>;
|
|
1489
|
+
} & MixinConstructor<BulkExt> & MixinConstructor<SlugExt> & MixinConstructor<TreeExt> & MixinConstructor<SoftDeleteExt>;
|
|
1571
1490
|
/**
|
|
1572
1491
|
* Fully-composed controller: `BaseCrudController` + SoftDelete + Tree +
|
|
1573
|
-
* Slug + Bulk.
|
|
1574
|
-
* companion interface above gives every method full generic precision
|
|
1575
|
-
* on `TDoc` via declaration merging.
|
|
1492
|
+
* Slug + Bulk. The companion interface above types every method over `TDoc`.
|
|
1576
1493
|
*/
|
|
1577
|
-
declare class BaseController<TDoc extends
|
|
1494
|
+
declare class BaseController<TDoc extends object = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> extends BaseController_base {
|
|
1578
1495
|
readonly _phantom?: [TDoc, _TRepository];
|
|
1579
1496
|
}
|
|
1580
1497
|
//#endregion
|
|
@@ -1924,6 +1841,9 @@ interface RouteDefinition {
|
|
|
1924
1841
|
* Accepts a controller-method NAME as well, same as {@link handler} — the
|
|
1925
1842
|
* lookup is identical and only the calling convention differs, so a
|
|
1926
1843
|
* Fastify-native controller method stays reachable by name.
|
|
1844
|
+
*
|
|
1845
|
+
* An async raw handler that calls `reply.send()` must `return reply` (as `handleRaw`
|
|
1846
|
+
* does): resolving `undefined` while an async `preSerialization` hook runs sends an empty body.
|
|
1927
1847
|
*/
|
|
1928
1848
|
readonly rawHandler?: string | RawRouteHandler;
|
|
1929
1849
|
/** Logical operation name (pipeline keys, MCP tool naming). */
|
|
@@ -1941,8 +1861,8 @@ interface RouteDefinition {
|
|
|
1941
1861
|
/**
|
|
1942
1862
|
* Opt this custom route into the multiTenant preset's tenant scoping.
|
|
1943
1863
|
*
|
|
1944
|
-
* When `true` AND the resource declares `multiTenantPreset
|
|
1945
|
-
*
|
|
1864
|
+
* When `true` AND the resource declares `multiTenantPreset`, arc
|
|
1865
|
+
* prepends the preset's tenant
|
|
1946
1866
|
* filter + injection middleware to this route's chain — the same
|
|
1947
1867
|
* pair `update` gets. The handler can then read the resolved tenant
|
|
1948
1868
|
* via `getOrgId(req.scope)` / `req._tenantFields` (or, for write
|
|
@@ -2139,6 +2059,28 @@ interface ActionDefinition {
|
|
|
2139
2059
|
* resource root and drops `id` from the MCP tool's input shape.
|
|
2140
2060
|
*/
|
|
2141
2061
|
readonly id?: boolean;
|
|
2062
|
+
/**
|
|
2063
|
+
* Where an id-bound action's TARGET is authorized. Default: arc loads it through the
|
|
2064
|
+
* resource's access control (id + tenant + row policy, as `GET /:id` does) and answers
|
|
2065
|
+
* 404 when the caller could not read it. `{ scope: 'handler', reason }` hands that check
|
|
2066
|
+
* to the handler — for a target that is deliberately not a row of this resource's tenant.
|
|
2067
|
+
*/
|
|
2068
|
+
readonly target?: {
|
|
2069
|
+
readonly scope: "handler";
|
|
2070
|
+
readonly reason: string;
|
|
2071
|
+
};
|
|
2072
|
+
/**
|
|
2073
|
+
* `{ required: true }`: a call of THIS action without an idempotency key is refused (400
|
|
2074
|
+
* `arc.idempotency_key_required`) and a keyed one is deduplicated; other actions on the
|
|
2075
|
+
* same mount stay optional. Needs `idempotencyPlugin` enabled.
|
|
2076
|
+
*/
|
|
2077
|
+
readonly idempotency?: {
|
|
2078
|
+
readonly required: boolean;
|
|
2079
|
+
/** `'handler'`: the handler deduplicates; arc only demands and exposes the key. Needs `owner`. */
|
|
2080
|
+
readonly dedup?: "handler";
|
|
2081
|
+
/** The runner that owns at-most-once under `dedup: 'handler'`. */
|
|
2082
|
+
readonly owner?: string;
|
|
2083
|
+
};
|
|
2142
2084
|
/**
|
|
2143
2085
|
* MCP tool generation:
|
|
2144
2086
|
* - omitted/true: auto-generate
|
|
@@ -2817,6 +2759,16 @@ interface FieldRule$1 {
|
|
|
2817
2759
|
}
|
|
2818
2760
|
//#endregion
|
|
2819
2761
|
//#region src/types/resource/schemas.d.ts
|
|
2762
|
+
/**
|
|
2763
|
+
* One client-requestable join. The base collection's tenant filter never reaches a joined
|
|
2764
|
+
* collection, so its scope is REQUIRED and is always the server's — a client `where` is discarded.
|
|
2765
|
+
*/
|
|
2766
|
+
interface LookupPolicy {
|
|
2767
|
+
/** Filter on the JOINED rows (tenant, soft-delete, visibility). `{}` only for a genuinely global collection. */
|
|
2768
|
+
scope: (ctx: {
|
|
2769
|
+
scope: RequestScope | undefined;
|
|
2770
|
+
}) => Record<string, unknown>;
|
|
2771
|
+
}
|
|
2820
2772
|
/**
|
|
2821
2773
|
* Schema-shaping options for a resource.
|
|
2822
2774
|
*
|
|
@@ -2913,11 +2865,10 @@ interface RouteSchemaOptions extends SchemaBuilderOptions {
|
|
|
2913
2865
|
*/
|
|
2914
2866
|
allowedPopulate?: string[];
|
|
2915
2867
|
/**
|
|
2916
|
-
*
|
|
2917
|
-
*
|
|
2918
|
-
* disable the whitelist (kit-level rules still apply).
|
|
2868
|
+
* Client-requestable joins, keyed by collection, each with the SERVER-owned scope of its
|
|
2869
|
+
* joined side. Omit to deny every client lookup (the default).
|
|
2919
2870
|
*/
|
|
2920
|
-
allowedLookups?: string
|
|
2871
|
+
allowedLookups?: Readonly<Record<string, LookupPolicy>>;
|
|
2921
2872
|
};
|
|
2922
2873
|
}
|
|
2923
2874
|
/**
|
|
@@ -3070,6 +3021,14 @@ interface ResolvedTenantPurge {
|
|
|
3070
3021
|
}
|
|
3071
3022
|
//#endregion
|
|
3072
3023
|
//#region src/types/resource/config.d.ts
|
|
3024
|
+
/**
|
|
3025
|
+
* Built-in write routes whose retry must not execute twice: each listed op answers a
|
|
3026
|
+
* keyless call with 400 `arc.idempotency_key_required` and dedupes a keyed one. Needs
|
|
3027
|
+
* `idempotencyPlugin` enabled; boot refuses an op that is not mounted.
|
|
3028
|
+
*/
|
|
3029
|
+
interface ResourceIdempotencyConfig {
|
|
3030
|
+
readonly required: readonly ("create" | "update" | "delete")[];
|
|
3031
|
+
}
|
|
3073
3032
|
/** Standard controller type alias for CRUD operations. */
|
|
3074
3033
|
type CrudController<TDoc> = IController<TDoc>;
|
|
3075
3034
|
/** Resource-level permissions — only `PermissionCheck` functions allowed. */
|
|
@@ -3140,6 +3099,16 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
3140
3099
|
* that silently degrades is the defect, not a mode.
|
|
3141
3100
|
*/
|
|
3142
3101
|
transactional?: boolean;
|
|
3102
|
+
/**
|
|
3103
|
+
* Optimistic concurrency over HTTP (RFC 9110). Reads send `ETag: "<version>"`; an update
|
|
3104
|
+
* with `If-Match` applies only at that version (else 412), and EVERY update is a CAS write on
|
|
3105
|
+
* the version it loaded (else 409) — so the version, and the ETag, change on every write.
|
|
3106
|
+
* `versionField` defaults to `'__v'`. Boot-fatal on a repository without the
|
|
3107
|
+
* `optimisticConcurrency` capability.
|
|
3108
|
+
*/
|
|
3109
|
+
concurrency?: {
|
|
3110
|
+
versionField?: string;
|
|
3111
|
+
};
|
|
3143
3112
|
queryParser?: unknown;
|
|
3144
3113
|
permissions?: ResourcePermissions;
|
|
3145
3114
|
schemaOptions?: RouteSchemaOptions;
|
|
@@ -3382,6 +3351,11 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
3382
3351
|
* ```
|
|
3383
3352
|
*/
|
|
3384
3353
|
idField?: (keyof TDoc & string) | (string & {});
|
|
3354
|
+
/**
|
|
3355
|
+
* Further unique keys `GET /:id` and an action's `:id` accept, tried in order after `idField`
|
|
3356
|
+
* — a business number beside `_id`. Same tenant and row-policy scope as the primary lookup.
|
|
3357
|
+
*/
|
|
3358
|
+
lookupFields?: readonly ((keyof TDoc & string) | (string & {}))[];
|
|
3385
3359
|
/** For grouping in registry */
|
|
3386
3360
|
module?: string;
|
|
3387
3361
|
/** Domain events */
|
|
@@ -3513,6 +3487,8 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
3513
3487
|
* registered. Set to `false` to disable for this resource.
|
|
3514
3488
|
*/
|
|
3515
3489
|
rateLimit?: RateLimitConfig | false;
|
|
3490
|
+
/** Idempotency-key fence for the built-in write routes. See `ResourceIdempotencyConfig`. */
|
|
3491
|
+
idempotency?: ResourceIdempotencyConfig;
|
|
3516
3492
|
/**
|
|
3517
3493
|
* QueryCache configuration for this resource. Enables
|
|
3518
3494
|
* stale-while-revalidate and auto-invalidation. Requires
|
|
@@ -3656,7 +3632,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3656
3632
|
readonly permissions: ResourcePermissions;
|
|
3657
3633
|
readonly routes: readonly RouteDefinition[];
|
|
3658
3634
|
readonly middlewares: MiddlewareConfig;
|
|
3659
|
-
readonly routeGuards?: RouteHandlerMethod
|
|
3635
|
+
readonly routeGuards?: RouteHandlerMethod[];
|
|
3660
3636
|
readonly disableDefaultRoutes: boolean;
|
|
3661
3637
|
readonly disabledRoutes: readonly CrudRouteKey[];
|
|
3662
3638
|
readonly actions?: ActionsMap;
|
|
@@ -3664,6 +3640,8 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3664
3640
|
readonly aggregations?: AggregationsMap;
|
|
3665
3641
|
readonly events: Record<string, EventDefinition>;
|
|
3666
3642
|
readonly rateLimit?: RateLimitConfig | false;
|
|
3643
|
+
readonly idempotency?: ResourceIdempotencyConfig;
|
|
3644
|
+
readonly lookupFields?: readonly string[];
|
|
3667
3645
|
readonly audit?: boolean | {
|
|
3668
3646
|
operations?: ("create" | "update" | "delete")[];
|
|
3669
3647
|
};
|
|
@@ -3699,6 +3677,12 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3699
3677
|
*/
|
|
3700
3678
|
readonly resolvedTenantPurge: ResolvedTenantPurge;
|
|
3701
3679
|
readonly idField?: string;
|
|
3680
|
+
/**
|
|
3681
|
+
* The optimistic-concurrency version field, resolved (`concurrency: {}` → `'__v'`);
|
|
3682
|
+
* `undefined` when the resource did not opt in. Published in the resource manifest
|
|
3683
|
+
* so a client sends `If-Match` without repeating the field name.
|
|
3684
|
+
*/
|
|
3685
|
+
readonly versionField?: string;
|
|
3702
3686
|
readonly queryParser?: QueryParserInterface;
|
|
3703
3687
|
readonly _appliedPresets: string[];
|
|
3704
3688
|
_pendingHooks: Array<{
|
|
@@ -3800,7 +3784,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3800
3784
|
//#region src/core/defineResource.d.ts
|
|
3801
3785
|
/**
|
|
3802
3786
|
* `TDoc` is **unconstrained** at this layer. The previous `TDoc
|
|
3803
|
-
* extends
|
|
3787
|
+
* extends object` bound leaked out of `BaseController`'s
|
|
3804
3788
|
* mixin-composition requirement into every host's adapter boundary:
|
|
3805
3789
|
* Mongoose's `HydratedDocument<T>`, Prisma's generated row types,
|
|
3806
3790
|
* and any domain interface without an explicit index signature all
|
|
@@ -3811,7 +3795,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3811
3795
|
* known envelope fields.
|
|
3812
3796
|
*
|
|
3813
3797
|
* The cast moved inside `resolveOrAutoCreateController` where
|
|
3814
|
-
* `BaseController<TDoc extends
|
|
3798
|
+
* `BaseController<TDoc extends object>` actually requires it.
|
|
3815
3799
|
* One internal boundary cast replaces N host-side casts.
|
|
3816
3800
|
*/
|
|
3817
3801
|
/**
|
|
@@ -3864,8 +3848,19 @@ declare class ResourceRegistry {
|
|
|
3864
3848
|
* runtime-only handle is still reachable.
|
|
3865
3849
|
*/
|
|
3866
3850
|
private _adapters;
|
|
3851
|
+
/** The mounted definitions, in registration order — see `getDefinitions`. */
|
|
3852
|
+
private _definitions;
|
|
3867
3853
|
private _frozen;
|
|
3868
3854
|
constructor();
|
|
3855
|
+
/**
|
|
3856
|
+
* The live `ResourceDefinition`s that mounted, in registration order — what a host hands to
|
|
3857
|
+
* `mcpPlugin({ resources })` or a docs generator instead of re-collecting them. A resource
|
|
3858
|
+
* declaring `skipRegistry` (incl. `customRoutesOnly`) opted out and is absent. Fresh array per call.
|
|
3859
|
+
* One composed module's share: `getModuleResources(fastify, name)` from `@classytic/arc/factory`.
|
|
3860
|
+
*/
|
|
3861
|
+
getDefinitions(): ResourceDefinition<unknown>[];
|
|
3862
|
+
/** The live definition registered under `name`, or `undefined`. */
|
|
3863
|
+
getDefinition(name: string): ResourceDefinition<unknown> | undefined;
|
|
3869
3864
|
/**
|
|
3870
3865
|
* Get the live `DataAdapter` for a registered resource — the handle whose
|
|
3871
3866
|
* `.repository` cascade / cleanup helpers actually call. Returns `undefined`
|
|
@@ -3874,8 +3869,13 @@ declare class ResourceRegistry {
|
|
|
3874
3869
|
* consumers cast to their kit's adapter type at the use site.
|
|
3875
3870
|
*/
|
|
3876
3871
|
getAdapter<T = unknown>(name: string): T | undefined;
|
|
3872
|
+
/** Set only by an explicit `strictResources: false`: a duplicate name keeps the FIRST entry. */
|
|
3873
|
+
private _tolerateDuplicates;
|
|
3874
|
+
/** Accept a duplicate resource name (the host opted out of `strictResources`; it was warned). */
|
|
3875
|
+
tolerateDuplicateNames(): void;
|
|
3877
3876
|
/**
|
|
3878
|
-
* Register a resource
|
|
3877
|
+
* Register a resource. A duplicate name throws — a resource missing from the registry still
|
|
3878
|
+
* serves traffic but vanishes from introspection, the permission matrix and MCP.
|
|
3879
3879
|
*/
|
|
3880
3880
|
register(resource: ResourceDefinition<unknown>, options?: RegisterOptions): this;
|
|
3881
3881
|
/**
|
|
@@ -3955,10 +3955,6 @@ declare class ResourceRegistry {
|
|
|
3955
3955
|
* iterate over a defunct registry entry. Clear both halves together.
|
|
3956
3956
|
*/
|
|
3957
3957
|
reset(): void;
|
|
3958
|
-
/** @internal Alias for unfreeze() */
|
|
3959
|
-
_unfreeze(): void;
|
|
3960
|
-
/** @internal Alias for reset() */
|
|
3961
|
-
_clear(): void;
|
|
3962
3958
|
/**
|
|
3963
3959
|
* Group by key
|
|
3964
3960
|
*/
|
|
@@ -4132,6 +4128,8 @@ interface CrudRouterOptions {
|
|
|
4132
4128
|
* registered. Set to `false` to disable for this resource.
|
|
4133
4129
|
*/
|
|
4134
4130
|
rateLimit?: RateLimitConfig | false;
|
|
4131
|
+
/** Idempotency-key fence for the built-in write routes. */
|
|
4132
|
+
idempotency?: ResourceIdempotencyConfig;
|
|
4135
4133
|
/** PreHandler guards applied to every route (CRUD + custom + preset). */
|
|
4136
4134
|
routeGuards?: RouteHandlerMethod[];
|
|
4137
4135
|
/**
|
|
@@ -4167,6 +4165,8 @@ interface RequestContext {
|
|
|
4167
4165
|
* them without `as AnyRecord` casts.
|
|
4168
4166
|
*/
|
|
4169
4167
|
interface ArcInternalMetadata extends RequestContext {
|
|
4168
|
+
/** Trusted resolved tenant dimensions forwarded across transports. */
|
|
4169
|
+
_tenantFields?: Record<string, unknown>;
|
|
4170
4170
|
/** Policy filters from permission middleware */
|
|
4171
4171
|
_policyFilters?: Record<string, unknown>;
|
|
4172
4172
|
/** Request scope from scope resolution */
|
|
@@ -4241,6 +4241,8 @@ interface LookupOption {
|
|
|
4241
4241
|
single?: boolean;
|
|
4242
4242
|
/** Field selection on the joined collection */
|
|
4243
4243
|
select?: string | Record<string, 0 | 1>;
|
|
4244
|
+
/** Filter on the joined rows — set by arc from the resource's `LookupPolicy`, never by the client. */
|
|
4245
|
+
where?: Record<string, unknown>;
|
|
4244
4246
|
}
|
|
4245
4247
|
/**
|
|
4246
4248
|
* Mongoose-compatible populate option for advanced field selection.
|
|
@@ -4375,6 +4377,12 @@ interface ResourceMetadata {
|
|
|
4375
4377
|
capability?: string;
|
|
4376
4378
|
raw?: boolean;
|
|
4377
4379
|
schema?: unknown;
|
|
4380
|
+
/** The route's `config.idempotency` — `required: true` means a key is mandatory. */
|
|
4381
|
+
idempotency?: {
|
|
4382
|
+
required?: boolean;
|
|
4383
|
+
dedup?: "handler";
|
|
4384
|
+
owner?: string;
|
|
4385
|
+
};
|
|
4378
4386
|
}>;
|
|
4379
4387
|
routes: Array<{
|
|
4380
4388
|
method: string;
|
|
@@ -4400,6 +4408,10 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
4400
4408
|
* without re-reading the resource definition.
|
|
4401
4409
|
*/
|
|
4402
4410
|
tenantField?: string | false;
|
|
4411
|
+
/** The resource's primary key field (`_id` when unset). */
|
|
4412
|
+
idField?: string;
|
|
4413
|
+
/** Resolved optimistic-concurrency version field — set only under `concurrency`. */
|
|
4414
|
+
versionField?: string;
|
|
4403
4415
|
/**
|
|
4404
4416
|
* Resolved tenant-purge strategy — what arc actually runs when
|
|
4405
4417
|
* `cascadeDeleteForOrganization` fires for this resource. Computed
|
|
@@ -4436,6 +4448,8 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
4436
4448
|
disabledRoutes?: string[];
|
|
4437
4449
|
/** Rate limit config */
|
|
4438
4450
|
rateLimit?: RateLimitConfig | false;
|
|
4451
|
+
/** Built-in write ops that require an idempotency key (`defineResource({ idempotency })`). */
|
|
4452
|
+
idempotency?: ResourceIdempotencyConfig;
|
|
4439
4453
|
/** Per-resource audit opt-in flag (read by `auditPlugin` perResource mode) */
|
|
4440
4454
|
audit?: boolean | {
|
|
4441
4455
|
operations?: ("create" | "update" | "delete")[];
|
|
@@ -4461,6 +4475,12 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
4461
4475
|
* reading the action definition itself.
|
|
4462
4476
|
*/
|
|
4463
4477
|
readonly id?: boolean;
|
|
4478
|
+
/** `{ required: true }` — a call of this action requires an idempotency key. */
|
|
4479
|
+
readonly idempotency?: {
|
|
4480
|
+
readonly required: boolean;
|
|
4481
|
+
readonly dedup?: "handler";
|
|
4482
|
+
readonly owner?: string;
|
|
4483
|
+
};
|
|
4464
4484
|
/** MCP tool generation flag — `false` to skip, object for overrides */
|
|
4465
4485
|
readonly mcp?: boolean | {
|
|
4466
4486
|
readonly description?: string;
|
|
@@ -4584,26 +4604,5 @@ type InferDocType<T> = T extends DataAdapter<infer D> ? D : T extends ResourceCo
|
|
|
4584
4604
|
*/
|
|
4585
4605
|
type InferAdapterDoc<A> = A extends DataAdapter<infer D> ? D : unknown;
|
|
4586
4606
|
type InferResourceDoc<T> = T extends ResourceConfig<infer D> ? D : never;
|
|
4587
|
-
type TypedResourceConfig<TDoc> = ResourceConfig<TDoc>;
|
|
4588
|
-
type TypedController<TDoc> = IController<TDoc>;
|
|
4589
|
-
type TypedRepository<TDoc> = StandardRepo<TDoc>;
|
|
4590
|
-
//#endregion
|
|
4591
|
-
//#region src/types/validation.d.ts
|
|
4592
|
-
/**
|
|
4593
|
-
* Validation result types — produced by `validateResourceConfig` and
|
|
4594
|
-
* consumed by `assertValidConfig` / `formatValidationErrors`.
|
|
4595
|
-
*/
|
|
4596
|
-
interface ConfigError {
|
|
4597
|
-
field: string;
|
|
4598
|
-
message: string;
|
|
4599
|
-
code?: string;
|
|
4600
|
-
}
|
|
4601
|
-
interface ValidationResult {
|
|
4602
|
-
valid: boolean;
|
|
4603
|
-
errors: ConfigError[];
|
|
4604
|
-
}
|
|
4605
|
-
interface ValidateOptions {
|
|
4606
|
-
strict?: boolean;
|
|
4607
|
-
}
|
|
4608
4607
|
//#endregion
|
|
4609
|
-
export {
|
|
4608
|
+
export { ResourceHookContext as $, AroundHookHandler as $t, RequestWithExtras as A, WriteContext as An, AuthHelpers as At, OnTenantDeleteConfig as B, ServerAccessor as Bn, TreeMixin as Bt, RequestIdOptions as C, ControllerConstructionOptions as Cn, ActionEntry as Ct, FastifyWithAuth as D, BodySanitizerConfig as Dn, RouteMcpConfig as Dt, FastifyRequestExtras as E, BodySanitizer as En, RouteDefinition as Et, ResourceDefinition as F, IController as Fn, TokenPair as Ft, CrudRouteKey as G, BaseCrudController as Gt, PresetFunction as H, SlugMixin as Ht, CrudController as I, IControllerResponse as In, BaseController as It, MiddlewareConfig as J, AccessControl as Jt, CrudSchemas as K, QueryResolver as Kt, ResourceConfig as L, IRequestContext as Ln, SoftDeleteExt as Lt, ResourceRegistry as M, ControllerHandler as Mn, Authenticator as Mt, RouteRow as N, ControllerLike as Nn, AuthenticatorContext as Nt, FastifyWithDecorators as O, MutationWriteContext as On, RouteMethod as Ot, defineResource as P, FastifyHandler as Pn, JwtContext as Pt, FieldRule$1 as Q, FetchResult as Qt, ResourceIdempotencyConfig as R, RawRouteHandler as Rn, SoftDeleteMixin as Rt, IntrospectionPluginOptions as S, ControllerConfigurableOptions as Sn, ActionDefinition as St, EventsDecorator as T, MixinConstructor as Tn, ActionsMap as Tt, PresetHook as U, BulkExt as Ut, ResolvedTenantPurge as V, SlugExt as Vt, PresetResult as W, BulkMixin as Wt, RouteSchemaOptions as X, AccessControlRepository as Xt, OpenApiSchemas as Y, AccessControlConfig as Yt, ArcFieldRule as Z, FetchDenialReason as Zt, ServiceContext as _, ArcDeleteResult as _n, PipelineConfig as _t, IntrospectionData as a, HookRegistration as an, AggregationCacheConfig as at, HealthCheck as b, ArcUpdateResult as bn, Transform as bt, ResourceMetadata as c, afterCreate as cn, AggregationIndexHint as ct, LookupOption as d, beforeCreate as dn, AggregationRateLimit as dt, DefineHookOptions as en, ResourceHooks as et, OwnershipCheck as f, beforeDelete as fn, AggregationsMap as ft, RequestContext as g, ArcCreateResult as gn, OperationFilter as gt, QueryParserInterface as h, defineHook as hn, NextFunction as ht, PaginationResult as i, HookPhase as in, AggMeasureShorthand as it, RegisterOptions as j, WriteVerbKey as jn, AuthPluginOptions as jt, MiddlewareHandler as k, ResourceWrites as kn, RateLimitConfig as kt, ArcInternalMetadata as l, afterDelete as ln, AggregationMaterializedContext as lt, PopulateOption as m, createHookSystem as mn, Interceptor as mt, InferDocType as n, HookHandler as nn, EventDefinition as nt, RegistryEntry as o, HookSystem as on, AggregationConfig as ot, ParsedQuery as p, beforeUpdate as pn, Guard as pt, LookupPolicy as q, QueryResolverConfig as qt, InferResourceDoc as r, HookOperation as rn, AggMeasureInput as rt, RegistryStats as s, HookSystemOptions as sn, AggregationDateRangeRequirement as st, InferAdapterDoc as t, HookContext as tn, ResourceExtensions as tt, ControllerQueryOptions as u, afterUpdate as un, AggregationMaterializedResult as ut, CrudRouterOptions as v, ArcGetResult as vn, PipelineContext as vt, ArcDecorator as w, ListResult as wn, ActionHandlerFn as wt, HealthOptions as x, BaseControllerOptions as xn, ResourceCacheConfig as xt, GracefulShutdownOptions as y, ArcListResult as yn, PipelineStep as yt, ResourcePermissions as z, RouteHandler as zn, TreeExt as zt };
|