@classytic/arc 2.41.1 → 2.44.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-DZWDsaPa.mjs} +22 -17
- package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-yjEgn7Cm.mjs} +2 -2
- package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
- package/dist/{ResourceRegistry-MGpXoxHZ.mjs → ResourceRegistry-DJxHUpE4.mjs} +44 -13
- package/dist/acquire-BozJ-9yt.mjs +35 -0
- package/dist/actionPermissions-5N04zBL4.mjs +772 -0
- package/dist/audit/index.d.mts +46 -8
- package/dist/audit/index.mjs +48 -26
- package/dist/auth/audit.d.mts +8 -8
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +46 -16
- package/dist/auth/index.mjs +134 -156
- package/dist/auth/redis-session.d.mts +4 -5
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-DawKGoXb.mjs → authorizationDecision-wX3SA9w_.mjs} +7 -102
- package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
- package/dist/boundedMap-DWiKW7lP.mjs +57 -0
- package/dist/{buildHandler-CvxKOc0m.mjs → buildHandler-Bswu_vNb.mjs} +4 -4
- package/dist/cache/index.d.mts +31 -43
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-DbCBxlgw.mjs → caching-Drp68DIS.mjs} +3 -3
- package/dist/claimed-DYvQoZLv.mjs +8 -0
- package/dist/cleanup/index.d.mts +18 -22
- package/dist/cleanup/index.mjs +27 -73
- package/dist/cli/commands/describe.d.mts +7 -5
- package/dist/cli/commands/describe.mjs +7 -4
- package/dist/cli/commands/docs.d.mts +2 -3
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.d.mts +2 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -3
- package/dist/cli/commands/init.mjs +2 -3
- package/dist/cli/commands/introspect.d.mts +2 -3
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-D5Ubn1fe.mjs → constants-B2QHb6u6.mjs} +4 -2
- package/dist/context/index.d.mts +11 -14
- 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-COcqo0Cp.mjs} +15 -5
- package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-zG13rpPJ.mjs} +97 -39
- package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-CWRz96Vr.mjs} +11 -8
- package/dist/{preloadResources-f3boenVZ.mjs → createApp-BGLYtErt.mjs} +305 -394
- package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-DSztofa9.d.mts} +1 -1
- package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation--3xguLkk.mjs} +1 -1
- package/dist/{defineResource-DxkMTjRn.mjs → defineResource-gUp9ug66.mjs} +118 -1024
- package/dist/discovery/index.d.mts +6 -7
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +11 -5
- package/dist/docs/index.mjs +4 -4
- package/dist/{elevation-CkvG0LG2.mjs → elevation-BG4gnMsJ.mjs} +41 -12
- package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
- package/dist/encryption/index.d.mts +5 -5
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-COHw3N1b.mjs} +26 -5
- package/dist/{errors-BH7YU_9O.mjs → errors-CW4anJBy.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-BweHAVws.mjs} +82 -21
- package/dist/{subscribe-helpers-DatDkcuG.mjs → eventTypes-N4-omQFR.mjs} +151 -17
- package/dist/events/index.d.mts +43 -18
- 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 +5 -6
- package/dist/events/transports/redis.mjs +2 -2
- package/dist/factory/index.d.mts +42 -27
- package/dist/factory/index.mjs +213 -32
- package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DRlCIWE9.d.mts} +11 -25
- package/dist/{fields-BHrkoF5y.mjs → fields-A5crhsJb.mjs} +1 -1
- package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
- package/dist/filter-merge-DGM-GQ1J.mjs +99 -0
- package/dist/{generate-BLRf2m3y.mjs → generate-BXKo2Gc8.mjs} +2 -2
- package/dist/{headers-BIXWKrzQ.mjs → headers-DKZQKL-Z.mjs} +2 -2
- package/dist/health-BYAwA6YS.mjs +22 -0
- package/dist/{health-ZRHeDHXH.mjs → health-CrqUp3sz.mjs} +115 -55
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.mjs +2 -3
- package/dist/idempotency/index.d.mts +36 -10
- package/dist/idempotency/index.mjs +230 -111
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +77 -12
- package/dist/{index-D4u76eQW.d.mts → index-Beg3aNnK.d.mts} +23 -101
- package/dist/{preloadResources-CjS2OfpU.d.mts → index-BkV55WlV.d.mts} +37 -38
- package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
- package/dist/index-CUJvoyw3.d.mts +510 -0
- package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
- package/dist/{index-DHbyPo5j.d.mts → index-WH7xgvxS.d.mts} +304 -254
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +11 -11
- package/dist/{init-CpLkjU18.mjs → init-BPzK5Lnr.mjs} +1 -1
- package/dist/integrations/event-gateway.d.mts +11 -7
- 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 +102 -89
- package/dist/integrations/mcp/index.mjs +19 -4
- package/dist/integrations/mcp/testing.d.mts +8 -9
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +85 -144
- package/dist/integrations/streamline.mjs +131 -55
- package/dist/integrations/webhooks.d.mts +83 -143
- package/dist/integrations/webhooks.mjs +36 -80
- package/dist/integrations/websocket-pushref-redis.d.mts +5 -6
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +5 -6
- 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-JJzwH4Ro.d.mts} +34 -2
- package/dist/{jsonBody-ELR9lfeU.mjs → jsonBody-YIq-vGLv.mjs} +2 -2
- package/dist/{keys-cpZCtcIE.mjs → keys-CpoiZ8hU.mjs} +7 -18
- package/dist/{loadResources-BPfWiI5s.mjs → loadResources-6jO52-RR.mjs} +3 -3
- package/dist/{loadResourcesFromEntry-ixtfL3J2.mjs → loadResourcesFromEntry-MP8xEppu.mjs} +2 -2
- package/dist/logger/index.d.mts +9 -17
- package/dist/logger/index.mjs +2 -153
- package/dist/logger-D4V1zGLI.mjs +160 -0
- package/dist/{mcpPlugin-BX5PujI0.mjs → mcpPlugin-srPc7ItN.mjs} +446 -170
- package/dist/{memory-Iotz0cCx.mjs → memory-CTa5pOCW.mjs} +36 -34
- package/dist/{metrics-NMsmpRmY.mjs → metrics-DsZrvnpB.mjs} +39 -72
- package/dist/middleware/index.d.mts +5 -5
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.d.mts +13 -13
- package/dist/migrations/index.mjs +2 -2
- package/dist/{module-DpLJG2_N.mjs → module--L0iBIZz.mjs} +8 -8
- package/dist/mongodb-memory-server-CHhYSq5y.mjs +35539 -0
- package/dist/mongoose-CYJCLyi-.mjs +67451 -0
- package/dist/multiTenant-CAtYlW6H.d.mts +104 -0
- package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-Cc7eZ9cq.mjs} +46 -7
- package/dist/node-D8UZv_8c.mjs +41236 -0
- package/dist/{openapi-CmFf3yq_.mjs → openapi-CMegfsm8.mjs} +48 -10
- package/dist/outbox-admin/index.d.mts +5 -5
- package/dist/outbox-admin/index.mjs +6 -18
- package/dist/{outbox-module-D2z5WkDY.mjs → outbox-module-Ryk270jd.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-C5ijIz5Q.mjs} +149 -33
- package/dist/{pipe-FMWNheSO.mjs → pipe-D81zMtWs.mjs} +2 -2
- package/dist/pipeline/index.d.mts +7 -7
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plan-CJGMKoui.mjs +565 -0
- package/dist/{BaseController-CYEwjyjN.mjs → plugin-DALd9dcF.mjs} +1269 -346
- package/dist/plugins/index.d.mts +17 -14
- package/dist/plugins/index.mjs +44 -22
- package/dist/plugins/response-cache.d.mts +5 -6
- package/dist/plugins/response-cache.mjs +8 -14
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-CXWZIzTv.mjs → pluralize-COUNYo3d.mjs} +1 -1
- package/dist/presets/filesUpload.d.mts +8 -8
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +35 -40
- 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 +9 -10
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-ZJSPgmT2.mjs → presets-CONf2Qem.mjs} +99 -107
- package/dist/{projection-BzIgzoKZ.mjs → projection-wFoLqdss.mjs} +2 -2
- package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
- package/dist/{queryCachePlugin-DZHPOm9p.mjs → queryCachePlugin-BATECpyh.mjs} +12 -14
- package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
- package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-DVEZ_UpV.mjs} +1 -1
- package/dist/{realtime-8tIQWZz4.mjs → realtime-xyubMKSB.mjs} +25 -21
- package/dist/{redis-Bzi1y3F9.d.mts → redis-BDgv9Glw.d.mts} +17 -24
- package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
- package/dist/redisShapes-CQksY67q.d.mts +55 -0
- package/dist/registry/index.d.mts +15 -10
- package/dist/registry/index.mjs +4 -4
- package/dist/{registry-DNkgGQcK.mjs → registry-Bdzg-hdm.mjs} +13 -10
- package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-BFJxGPuh.mjs} +1 -1
- package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-Bnc6lJyy.mjs} +2 -2
- package/dist/{requestContext-cDkAUr-P.mjs → requestContext-C10UeFe5.mjs} +1 -1
- package/dist/requirement-Bu50QALH.mjs +35 -0
- package/dist/requirement-CbiLdQ1n.d.mts +22 -0
- package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
- package/dist/resourceRateLimit-urWPJxrt.mjs +180 -0
- package/dist/rolldown-runtime-MYrLNGnq.mjs +45 -0
- package/dist/routeValidator-DHtpzrzk.mjs +70 -0
- package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-DIMKMV2G.mjs} +1 -1
- package/dist/{schedules-j6uXXAeJ.mjs → schedules-CKTs5ys8.mjs} +46 -37
- package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BVjkQ5DD.mjs} +2 -2
- package/dist/{schemaIR-CTvPDoPB.mjs → schemaIR-BA0Hb21Y.mjs} +2 -2
- package/dist/schemas/index.d.mts +20 -10
- package/dist/schemas/index.mjs +8 -3
- package/dist/scim/index.d.mts +13 -13
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +4 -4
- package/dist/scope/index.mjs +6 -6
- package/dist/{scope-BUi1xV1I.mjs → scope-DINaj7Ax.mjs} +4 -4
- package/dist/shutdown-LpLMH8T1.mjs +59 -0
- package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-DbwIM1f1.mjs} +1 -1
- package/dist/{sse-BeqVKQKu.mjs → sse-BzJFPX_1.mjs} +11 -15
- package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-DQChzGTs.mjs} +2 -2
- package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-Coj9RhOZ.mjs} +1 -1
- package/dist/{streaming-IcRFjWQU.mjs → streaming-JzB5CAga.mjs} +1 -1
- package/dist/subscriberIdentity-DeyppIif.mjs +28 -0
- package/dist/tenancy-DxV8OuX2.mjs +43 -0
- package/dist/tenantPreparation-CBvvAcAj.mjs +67 -0
- package/dist/testing/cleanupStoreContract.d.mts +4 -5
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +215 -197
- package/dist/testing/index.mjs +289 -214
- package/dist/testing/outboxStoreContract.d.mts +4 -5
- package/dist/testing/outboxStoreContract.mjs +137 -2
- package/dist/testing/requiredPorts.d.mts +16 -0
- package/dist/testing/requiredPorts.mjs +65 -0
- package/dist/testing/storageContract.d.mts +4 -5
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/timing-CntavTlR.mjs +46 -0
- package/dist/{transactionContext-CPbdlnVw.mjs → transactionContext-orvnGu4F.mjs} +1 -1
- package/dist/{typeGuards-BLjPgVBm.mjs → typeGuards-kEaP_WmO.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-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
- package/dist/{types-CWQn9-IC.d.mts → types-D3Ixsh00.d.mts} +22 -9
- package/dist/{types-CgenB1gv.mjs → types-fvO6Ovzx.mjs} +4 -4
- package/dist/usage/index.d.mts +5 -6
- package/dist/usage/index.mjs +5 -5
- package/dist/{userHelpers-ChRvkuSZ.mjs → userHelpers-C76zD5pa.mjs} +1 -1
- package/dist/utils/index.d.mts +125 -264
- package/dist/utils/index.mjs +13 -12
- package/dist/{utils-CxcctrAh.mjs → utils-BpkVZ8rB.mjs} +315 -404
- package/dist/{validate-DrYWnOzI.mjs → validate-CWp-lsrm.mjs} +6 -14
- package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
- package/dist/{versioning-DKkT4hNJ.mjs → versioning-CHhL9P3o.mjs} +4 -4
- package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
- package/dist/{workScope-CxKqoIFn.mjs → workScope-n9a7d_lZ.mjs} +2 -2
- package/package.json +57 -59
- 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/rolldown-runtime-eysyzFAF.mjs +0 -15
- 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.
|
|
@@ -1031,6 +1042,50 @@ declare class AccessControl {
|
|
|
1031
1042
|
private _meta;
|
|
1032
1043
|
}
|
|
1033
1044
|
//#endregion
|
|
1045
|
+
//#region src/core/entityHelpers.d.ts
|
|
1046
|
+
/**
|
|
1047
|
+
* Read the resource's configured `idField` for the current request.
|
|
1048
|
+
* Falls back to the framework default (`_id`) when the route hasn't
|
|
1049
|
+
* bound an idField — keeps handlers safe to author without checking
|
|
1050
|
+
* the resource config.
|
|
1051
|
+
*/
|
|
1052
|
+
declare function getEntityIdField(req: RequestWithExtras): string;
|
|
1053
|
+
/**
|
|
1054
|
+
* Read the URL `:id` path param value as the resource handle.
|
|
1055
|
+
* Returns `undefined` when the route has no `:id` segment (collection
|
|
1056
|
+
* routes) — handlers that need the entity must be on row routes.
|
|
1057
|
+
*/
|
|
1058
|
+
declare function getEntityId(req: RequestWithExtras): string | undefined;
|
|
1059
|
+
/**
|
|
1060
|
+
* Compose a `findOne` filter that resolves the current request's
|
|
1061
|
+
* entity, regardless of whether the resource binds `_id` or a custom
|
|
1062
|
+
* field. Idiomatic shape for arc action handlers.
|
|
1063
|
+
*
|
|
1064
|
+
* ```ts
|
|
1065
|
+
* const doc = await Model.findOne(getEntityQuery(req));
|
|
1066
|
+
* ```
|
|
1067
|
+
*
|
|
1068
|
+
* Returns `{}` when the route has no entity context (collection routes
|
|
1069
|
+
* or tests bypassing the router) — caller decides what that means.
|
|
1070
|
+
*/
|
|
1071
|
+
declare function getEntityQuery(req: RequestWithExtras): Record<string, string>;
|
|
1072
|
+
/** The action-target fence's answer and, when it read one, the row (`GET /:id`'s scope). */
|
|
1073
|
+
interface ActionTargetCheck {
|
|
1074
|
+
readonly visible: boolean;
|
|
1075
|
+
readonly doc?: unknown;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* The row the action-target fence loaded for this request, so the handler need not read it
|
|
1079
|
+
* again. `undefined` when no fence read happened (untenanted resource, `scope: 'handler'`,
|
|
1080
|
+
* id-less action) — load it yourself then. It is a snapshot taken after the idempotency lock
|
|
1081
|
+
* and before the handler: a guarded write must still carry its precondition in the filter.
|
|
1082
|
+
*/
|
|
1083
|
+
declare function getActionTarget<TDoc = Record<string, unknown>>(req: {
|
|
1084
|
+
readonly arc?: {
|
|
1085
|
+
readonly actionTarget?: unknown;
|
|
1086
|
+
};
|
|
1087
|
+
}): TDoc | undefined;
|
|
1088
|
+
//#endregion
|
|
1034
1089
|
//#region src/core/QueryResolver.d.ts
|
|
1035
1090
|
interface QueryResolverConfig {
|
|
1036
1091
|
/** Query parser instance (default: Arc built-in parser) */
|
|
@@ -1103,9 +1158,9 @@ declare class QueryResolver {
|
|
|
1103
1158
|
/** Sanitize advanced populate options against allowedPopulate */
|
|
1104
1159
|
private sanitizePopulateOptions;
|
|
1105
1160
|
/**
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1108
|
-
*
|
|
1161
|
+
* Client lookups, deny-by-default. An allowed one is structurally validated, and its joined side
|
|
1162
|
+
* is filtered by the resource's server-owned `LookupPolicy.scope` — the base collection's tenant
|
|
1163
|
+
* filter never reaches a join, and a client-supplied `where` is replaced, never merged.
|
|
1109
1164
|
*/
|
|
1110
1165
|
private sanitizeLookups;
|
|
1111
1166
|
/**
|
|
@@ -1147,10 +1202,7 @@ declare class QueryResolver {
|
|
|
1147
1202
|
//#region src/core/BaseCrudController.d.ts
|
|
1148
1203
|
/**
|
|
1149
1204
|
* 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.
|
|
1205
|
+
* Composes AccessControl, BodySanitizer, and QueryResolver.
|
|
1154
1206
|
*
|
|
1155
1207
|
* @template TDoc - The document type.
|
|
1156
1208
|
* @template TRepository - The repository type (defaults to RepositoryLike).
|
|
@@ -1164,29 +1216,14 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1164
1216
|
protected resourceName?: string;
|
|
1165
1217
|
protected tenantField: string | false;
|
|
1166
1218
|
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
|
-
*/
|
|
1219
|
+
/** Alternate unique keys, each with its own access control over the SAME scope. */
|
|
1220
|
+
protected lookupAccess: AccessControl[];
|
|
1221
|
+
private _lookupFields;
|
|
1222
|
+
/** Access control (ID filtering, policy checks, org scope, ownership). */
|
|
1174
1223
|
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
|
-
*/
|
|
1224
|
+
/** Body sanitization (field permissions, system fields). */
|
|
1181
1225
|
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
|
-
*/
|
|
1226
|
+
/** Query resolution (parsing, pagination, sort, select/populate). */
|
|
1190
1227
|
queryResolver: QueryResolver;
|
|
1191
1228
|
protected _matchesFilter?: (item: unknown, filters: Record<string, unknown>) => boolean;
|
|
1192
1229
|
protected _presetFields: {
|
|
@@ -1195,82 +1232,51 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1195
1232
|
};
|
|
1196
1233
|
protected _cacheConfig?: ResourceCacheConfig;
|
|
1197
1234
|
/**
|
|
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.
|
|
1235
|
+
* Retained so partial `configure()` rebuilds reuse them instead of falling back
|
|
1236
|
+
* to sub-component defaults (`-createdAt` sort breaks kits without that column).
|
|
1203
1237
|
*/
|
|
1204
1238
|
protected _defaultSort?: string | false;
|
|
1205
1239
|
protected _onFieldWriteDenied?: FieldWriteDenialPolicy;
|
|
1206
1240
|
protected _onImmutableWrite?: FieldWriteDenialPolicy;
|
|
1207
1241
|
/**
|
|
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.
|
|
1242
|
+
* Domain commands bound to the write slots. Consulted at the PERSISTENCE step
|
|
1243
|
+
* only — sanitizer, tenant injection, actor stamp, and hooks run unchanged.
|
|
1212
1244
|
*/
|
|
1213
1245
|
protected _writes?: ResourceWrites;
|
|
1214
1246
|
/** Transactional write envelope — see `ResourceConfig.transactional`. */
|
|
1215
1247
|
protected _transactional: boolean;
|
|
1248
|
+
/** Version field under optimistic concurrency — `null` when the resource did not opt in. */
|
|
1249
|
+
protected _versionField: string | null;
|
|
1216
1250
|
constructor(repository: TRepository, options?: BaseControllerOptions);
|
|
1217
1251
|
/**
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
1220
|
-
*
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
*
|
|
1225
|
-
*
|
|
1226
|
-
*
|
|
1252
|
+
* Bind the resource this controller serves — hooks, audit, events and cache key on it.
|
|
1253
|
+
* `defineResource` calls it for a supplied controller; an unset name is assigned, a
|
|
1254
|
+
* different one throws (the controller would run another resource's hooks).
|
|
1255
|
+
*/
|
|
1256
|
+
bindResourceName(name: string): void;
|
|
1257
|
+
/**
|
|
1258
|
+
* Swap the controller's query parser. Mutates the existing `QueryResolver` in
|
|
1259
|
+
* place, so the resolver stays referentially stable. `defineResource` calls this
|
|
1260
|
+
* (duck-typed) when a resource supplies both `controller` and `queryParser`.
|
|
1227
1261
|
*/
|
|
1228
1262
|
setQueryParser(queryParser: QueryParserInterface): void;
|
|
1229
1263
|
/**
|
|
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.
|
|
1264
|
+
* Apply resource-level options to a controller AFTER construction.
|
|
1265
|
+
* `defineResource()` calls this once after resolving the controller, so custom
|
|
1266
|
+
* controllers receive tenant scope, field rules, sort/limit, cache, and
|
|
1267
|
+
* write-denial policy without forwarding them through `super(repo, { … })`.
|
|
1241
1268
|
*
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
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.
|
|
1248
|
-
*
|
|
1249
|
-
* Idempotent: safe to call zero, one, or many times before first request;
|
|
1250
|
-
* arc calls it exactly once.
|
|
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.
|
|
1269
|
+
* Each key rebuilds only the affected sub-component. Idempotent.
|
|
1270
|
+
* `repository` / `resourceName` are construction-only and excluded by type.
|
|
1255
1271
|
*/
|
|
1256
1272
|
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
|
-
*/
|
|
1273
|
+
/** Tenant field name, or `undefined` when `tenantField` is `false`. */
|
|
1261
1274
|
protected getTenantField(): string | undefined;
|
|
1262
1275
|
/**
|
|
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.
|
|
1276
|
+
* Canonical repo-options bag (tenant scope, audit attribution, trace
|
|
1277
|
+
* correlation) — see `buildTenantRepoOptions` in `crud/requestPipeline.ts`.
|
|
1278
|
+
* Kept as a `protected` entry point for host overrides and duck-typed
|
|
1279
|
+
* consumers (aggregation router, MCP bridge).
|
|
1274
1280
|
*/
|
|
1275
1281
|
protected tenantRepoOptions(req: IRequestContext): AnyRecord;
|
|
1276
1282
|
/** Extract typed Arc internal metadata from request */
|
|
@@ -1285,18 +1291,11 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1285
1291
|
*/
|
|
1286
1292
|
protected resolveRepoId(id: string, existing: AnyRecord | null): string;
|
|
1287
1293
|
/**
|
|
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.
|
|
1294
|
+
* Shared preflight for mutations: extract `:id`, fetch under access control +
|
|
1295
|
+
* tenant scope, verify ownership, translate the route id to the repo primary
|
|
1296
|
+
* key. One path so `update` and `delete` cannot drift.
|
|
1297
1297
|
*
|
|
1298
|
-
*
|
|
1299
|
-
* need to widen the fetch (`{ includeDeleted: true }`).
|
|
1298
|
+
* `extraFetchOptions` widens the fetch (e.g. restore's `{ includeDeleted: true }`).
|
|
1300
1299
|
*/
|
|
1301
1300
|
protected loadMutableTarget(req: IRequestContext, extraFetchOptions?: AnyRecord): Promise<{
|
|
1302
1301
|
id: string;
|
|
@@ -1304,18 +1303,50 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1304
1303
|
repoId: string;
|
|
1305
1304
|
}>;
|
|
1306
1305
|
/**
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1306
|
+
* Whether the target of an id-bound ACTION is a row this request may read — the same
|
|
1307
|
+
* compound filter (id + tenant + `_policyFilters`) `GET /:id` uses. The action router
|
|
1308
|
+
* refuses the action with a 404 when it is not, so a caller can act on a row iff it can
|
|
1309
|
+
* read it. Skips the read when neither tenancy nor a row policy narrows the request.
|
|
1310
|
+
*/
|
|
1311
|
+
private buildLookupAccess;
|
|
1312
|
+
/**
|
|
1313
|
+
* THE single-row lookup — `GET /:id` and the action-target fence both read through it.
|
|
1314
|
+
* The primary key first; when it finds nothing (or the id cannot be that key's type), each
|
|
1315
|
+
* `lookupFields` key in turn, under the same tenant and row policy.
|
|
1316
|
+
*/
|
|
1317
|
+
protected fetchByAnyKey(id: string, req: IRequestContext, options: QueryOptions | undefined): Promise<{
|
|
1318
|
+
doc: TDoc | null;
|
|
1319
|
+
reason: FetchDenialReason | null;
|
|
1320
|
+
}>;
|
|
1321
|
+
/** Whether {@link isActionTargetVisible} can answer — it reads through the repository. */
|
|
1322
|
+
canFenceActionTargets(): boolean;
|
|
1323
|
+
isActionTargetVisible(req: IRequestContext, id: string): Promise<boolean>;
|
|
1324
|
+
/**
|
|
1325
|
+
* The fence's answer plus the row it read (absent when the resource has no row scope, so
|
|
1326
|
+
* nothing was read). The router hands the row to the handler as `getActionTarget(req)`.
|
|
1327
|
+
*/
|
|
1328
|
+
loadActionTarget(req: IRequestContext, id: string): Promise<ActionTargetCheck>;
|
|
1329
|
+
/** `ETag: "<version>"` for a versioned document under optimistic concurrency. */
|
|
1330
|
+
protected etagHeaders(doc: unknown): Record<string, string> | undefined;
|
|
1331
|
+
/**
|
|
1332
|
+
* The version an update must apply at. `If-Match` must name the CURRENT version or the
|
|
1333
|
+
* request fails 412 before anything is written; without it (or with `*`) the write is a CAS
|
|
1334
|
+
* on the version just loaded, so a concurrent write in between is a 409, never overwritten.
|
|
1335
|
+
*/
|
|
1336
|
+
protected expectedVersion(req: IRequestContext, existing: unknown): {
|
|
1337
|
+
version?: number;
|
|
1338
|
+
fromClient: boolean;
|
|
1339
|
+
};
|
|
1340
|
+
/**
|
|
1341
|
+
* 404 thrower. Maps the `fetchDetailed()` denial reason into `details.code` so
|
|
1342
|
+
* "doesn't exist" is distinguishable from "filtered by policy/org scope".
|
|
1311
1343
|
*/
|
|
1312
1344
|
protected throwNotFound(reason?: FetchDenialReason | null): never;
|
|
1313
1345
|
/** Resolve cache config for a specific operation, merging per-op overrides */
|
|
1314
1346
|
protected resolveCacheConfig(operation: "list" | "byId"): QueryCacheConfig | null;
|
|
1315
1347
|
/**
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
* Universal resources (tenantField: false) get shared cache keys.
|
|
1348
|
+
* User/org IDs for cache key scoping. orgId is included only when `tenantField`
|
|
1349
|
+
* is set; universal resources share cache keys.
|
|
1319
1350
|
*/
|
|
1320
1351
|
protected cacheScope(req: IRequestContext): {
|
|
1321
1352
|
userId?: string;
|
|
@@ -1325,20 +1356,13 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1325
1356
|
protected cacheResponse<T>(data: T, cacheStatus: CacheStatus): IControllerResponse<T>;
|
|
1326
1357
|
/** Required route-id helper shared by get/update/delete. Throws on missing id. */
|
|
1327
1358
|
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
|
-
*/
|
|
1359
|
+
/** Normalizes `repo.exists()` (`boolean | { _id } | null`) to a boolean. */
|
|
1333
1360
|
protected isExistsTruthy(result: unknown): boolean;
|
|
1334
1361
|
/** Assemble the per-request hook context from the protected accessors. */
|
|
1335
1362
|
private hookOpContext;
|
|
1336
1363
|
/**
|
|
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`.
|
|
1364
|
+
* Run before + around hooks (or just the executor if none are wired) and
|
|
1365
|
+
* return the result. A failing before-hook throws 400 `BEFORE_<OP>_HOOK_ERROR`.
|
|
1342
1366
|
*/
|
|
1343
1367
|
protected runHookedOpUntilResult<TInput, TResult>(req: IRequestContext, args: {
|
|
1344
1368
|
op: "create" | "update" | "delete";
|
|
@@ -1348,14 +1372,10 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1348
1372
|
}, executor: (processed: TInput) => Promise<TResult>): Promise<TResult>;
|
|
1349
1373
|
/**
|
|
1350
1374
|
* 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.
|
|
1375
|
+
* Transactional mode uses `retryingTransaction`: `fn` gets the TX-BOUND repo and
|
|
1376
|
+
* re-runs on transient conflicts; anything else (incl. `VersionConflictError`)
|
|
1377
|
+
* surfaces once. Hooks stay OUTSIDE so before/around never re-run and after
|
|
1378
|
+
* runs post-commit.
|
|
1359
1379
|
*/
|
|
1360
1380
|
protected runWritePersistence<T>(fn: (repo: TRepository, uow?: TransactionHandle) => Promise<T>): Promise<T>;
|
|
1361
1381
|
/** Assemble the `WriteContext` a `create` verb receives. */
|
|
@@ -1363,43 +1383,26 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1363
1383
|
/**
|
|
1364
1384
|
* Assemble the `MutationWriteContext` an `update` / `delete` verb receives.
|
|
1365
1385
|
*
|
|
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.
|
|
1386
|
+
* `existing` is the document the request was AUTHORISED against — handed over
|
|
1387
|
+
* so the verb does not re-fetch and possibly observe a different document.
|
|
1388
|
+
* `id` is the REPOSITORY PRIMARY KEY (`resolveRepoId` output), never the route param.
|
|
1376
1389
|
*/
|
|
1377
1390
|
protected mutationWriteContext(req: IRequestContext, id: string, existing: unknown, repo?: TRepository, uow?: TransactionHandle): MutationWriteContext;
|
|
1378
1391
|
/**
|
|
1379
|
-
* Run
|
|
1380
|
-
*
|
|
1381
|
-
* wants downstream after-handlers to receive — typically the result for
|
|
1382
|
-
* create/update, the original input (`existing`) for delete.
|
|
1392
|
+
* Run after-hooks for `op`. No-op without hooks or `resourceName`. `data` is the
|
|
1393
|
+
* result for create/update, `existing` for delete.
|
|
1383
1394
|
*/
|
|
1384
1395
|
protected runAfterHook(req: IRequestContext, op: "create" | "update" | "delete" | "list" | "read", data: AnyRecord, meta?: Record<string, unknown>): Promise<void>;
|
|
1385
1396
|
/**
|
|
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).
|
|
1397
|
+
* Per-process single-flight for cache fills: concurrent requests for the SAME
|
|
1398
|
+
* key coalesce onto one repository query. Keys encode query + user/org scope +
|
|
1399
|
+
* resource version, so coalescing never crosses tenants or invalidation generations.
|
|
1395
1400
|
*/
|
|
1396
1401
|
private readonly inFlightCacheFills;
|
|
1397
1402
|
private cacheSingleFlight;
|
|
1398
1403
|
/**
|
|
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.
|
|
1404
|
+
* Register a background SWR refresh for `key` unless one is in flight.
|
|
1405
|
+
* Registration is SYNCHRONOUS so a same-tick burst of stale readers schedules one.
|
|
1403
1406
|
*/
|
|
1404
1407
|
private scheduleCacheRefresh;
|
|
1405
1408
|
/** Cached `list()` flow with SWR semantics. Returns null when cache is disabled. */
|
|
@@ -1408,19 +1411,14 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1408
1411
|
protected withGetCache(req: IRequestContext, id: string, options: ControllerQueryOptions): Promise<IControllerResponse<TDoc> | null>;
|
|
1409
1412
|
list(req: IRequestContext): Promise<IControllerResponse<ListResult<TDoc>>>;
|
|
1410
1413
|
/**
|
|
1411
|
-
* Resource-dispatch verbs router
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
* Verbs (mutually exclusive — first match wins):
|
|
1414
|
+
* Resource-dispatch verbs router; `null` for a regular list query.
|
|
1415
|
+
* First match wins:
|
|
1415
1416
|
* - `?_count=true` → `{ count: number }` via `repo.count()`
|
|
1416
1417
|
* - `?_distinct=field` → `unknown[]` via `repo.distinct(field)`
|
|
1417
1418
|
* - `?_exists=true` → `{ exists: boolean }` via `repo.exists()`
|
|
1418
1419
|
*
|
|
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`.
|
|
1420
|
+
* All share the resolved filter (query + policy + tenant scope). A missing repo
|
|
1421
|
+
* method is a `501`, never a fallback to a full scan.
|
|
1424
1422
|
*/
|
|
1425
1423
|
protected dispatchResourceVerb(req: IRequestContext): Promise<IControllerResponse<unknown>> | null;
|
|
1426
1424
|
/** Resolve filter + tenant/audit options for a dispatch verb. */
|
|
@@ -1438,13 +1436,8 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1438
1436
|
exists: boolean;
|
|
1439
1437
|
}>>;
|
|
1440
1438
|
/**
|
|
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`.
|
|
1439
|
+
* True when `field` is safe to expose via `_distinct`. Only `hidden: true`
|
|
1440
|
+
* blocks — `systemManaged` is a write rule and the field is already readable.
|
|
1448
1441
|
*/
|
|
1449
1442
|
protected isFieldExposedForRead(field: string): boolean;
|
|
1450
1443
|
/** Execute list query through hooks (extracted for cache revalidation) */
|
|
@@ -1467,7 +1460,7 @@ declare class BaseCrudController<TDoc = AnyRecord, TRepository extends Repositor
|
|
|
1467
1460
|
//#region src/core/mixins/bulk.d.ts
|
|
1468
1461
|
/** Public surface contributed by BulkMixin. */
|
|
1469
1462
|
interface BulkExt {
|
|
1470
|
-
bulkCreate(req: IRequestContext): Promise<IControllerResponse<
|
|
1463
|
+
bulkCreate(req: IRequestContext): Promise<IControllerResponse<object[]>>;
|
|
1471
1464
|
bulkUpdate(req: IRequestContext): Promise<IControllerResponse<{
|
|
1472
1465
|
matchedCount: number;
|
|
1473
1466
|
modifiedCount: number;
|
|
@@ -1476,69 +1469,42 @@ interface BulkExt {
|
|
|
1476
1469
|
deletedCount: number;
|
|
1477
1470
|
}>>;
|
|
1478
1471
|
}
|
|
1479
|
-
declare function BulkMixin<TBase extends MixinConstructor<BaseCrudController
|
|
1472
|
+
declare function BulkMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<BulkExt>;
|
|
1480
1473
|
//#endregion
|
|
1481
1474
|
//#region src/core/mixins/slug.d.ts
|
|
1482
1475
|
/** Public surface contributed by SlugMixin. */
|
|
1483
1476
|
interface SlugExt {
|
|
1484
|
-
getBySlug(req: IRequestContext): Promise<IControllerResponse<
|
|
1477
|
+
getBySlug(req: IRequestContext): Promise<IControllerResponse<object>>;
|
|
1485
1478
|
}
|
|
1486
|
-
declare function SlugMixin<TBase extends MixinConstructor<BaseCrudController
|
|
1479
|
+
declare function SlugMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<SlugExt>;
|
|
1487
1480
|
//#endregion
|
|
1488
1481
|
//#region src/core/mixins/tree.d.ts
|
|
1489
1482
|
/** Public surface contributed by TreeMixin. */
|
|
1490
1483
|
interface TreeExt {
|
|
1491
|
-
getTree(req: IRequestContext): Promise<IControllerResponse<
|
|
1492
|
-
getChildren(req: IRequestContext): Promise<IControllerResponse<
|
|
1484
|
+
getTree(req: IRequestContext): Promise<IControllerResponse<object[]>>;
|
|
1485
|
+
getChildren(req: IRequestContext): Promise<IControllerResponse<object[]>>;
|
|
1493
1486
|
}
|
|
1494
|
-
declare function TreeMixin<TBase extends MixinConstructor<BaseCrudController
|
|
1487
|
+
declare function TreeMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<TreeExt>;
|
|
1495
1488
|
//#endregion
|
|
1496
1489
|
//#region src/core/mixins/softDelete.d.ts
|
|
1497
1490
|
/** Public surface contributed by SoftDeleteMixin. */
|
|
1498
1491
|
interface SoftDeleteExt {
|
|
1499
|
-
getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginationResult<
|
|
1500
|
-
restore(req: IRequestContext): Promise<IControllerResponse<
|
|
1492
|
+
getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginationResult<object>>>;
|
|
1493
|
+
restore(req: IRequestContext): Promise<IControllerResponse<object>>;
|
|
1501
1494
|
}
|
|
1502
|
-
declare function SoftDeleteMixin<TBase extends MixinConstructor<BaseCrudController
|
|
1495
|
+
declare function SoftDeleteMixin<TBase extends MixinConstructor<BaseCrudController<object>>>(Base: TBase): TBase & MixinConstructor<SoftDeleteExt>;
|
|
1503
1496
|
//#endregion
|
|
1504
1497
|
//#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
1498
|
/**
|
|
1515
1499
|
* Fully-composed controller shape: all CRUD methods + every preset method
|
|
1516
1500
|
* (SoftDelete / Tree / Slug / Bulk) typed over the caller-supplied `TDoc`.
|
|
1517
1501
|
*
|
|
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.
|
|
1502
|
+
* DECLARATION MERGING, not a generic mixin chain: chained mixin factories cannot thread one
|
|
1503
|
+
* `TDoc`, so the runtime chain is instantiated over `object` and the TYPE surface threads `TDoc`.
|
|
1504
|
+
* Any `TDoc extends object` works — an interface-typed document needs no index signature.
|
|
1505
|
+
* A new mixin method must be redeclared here too.
|
|
1540
1506
|
*/
|
|
1541
|
-
interface BaseController<TDoc extends
|
|
1507
|
+
interface BaseController<TDoc extends object = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> {
|
|
1542
1508
|
accessControl: AccessControl;
|
|
1543
1509
|
bodySanitizer: BodySanitizer;
|
|
1544
1510
|
queryResolver: QueryResolver;
|
|
@@ -1567,14 +1533,14 @@ interface BaseController<TDoc extends AnyRecord = AnyRecord, _TRepository extend
|
|
|
1567
1533
|
deletedCount: number;
|
|
1568
1534
|
}>>;
|
|
1569
1535
|
}
|
|
1570
|
-
declare const BaseController_base:
|
|
1536
|
+
declare const BaseController_base: {
|
|
1537
|
+
new (repository: RepositoryLike, options?: BaseControllerOptions): BaseCrudController<object, RepositoryLike>;
|
|
1538
|
+
} & MixinConstructor<BulkExt> & MixinConstructor<SlugExt> & MixinConstructor<TreeExt> & MixinConstructor<SoftDeleteExt>;
|
|
1571
1539
|
/**
|
|
1572
1540
|
* 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.
|
|
1541
|
+
* Slug + Bulk. The companion interface above types every method over `TDoc`.
|
|
1576
1542
|
*/
|
|
1577
|
-
declare class BaseController<TDoc extends
|
|
1543
|
+
declare class BaseController<TDoc extends object = AnyRecord, _TRepository extends RepositoryLike = RepositoryLike<TDoc>> extends BaseController_base {
|
|
1578
1544
|
readonly _phantom?: [TDoc, _TRepository];
|
|
1579
1545
|
}
|
|
1580
1546
|
//#endregion
|
|
@@ -1924,6 +1890,9 @@ interface RouteDefinition {
|
|
|
1924
1890
|
* Accepts a controller-method NAME as well, same as {@link handler} — the
|
|
1925
1891
|
* lookup is identical and only the calling convention differs, so a
|
|
1926
1892
|
* Fastify-native controller method stays reachable by name.
|
|
1893
|
+
*
|
|
1894
|
+
* An async raw handler that calls `reply.send()` must `return reply` (as `handleRaw`
|
|
1895
|
+
* does): resolving `undefined` while an async `preSerialization` hook runs sends an empty body.
|
|
1927
1896
|
*/
|
|
1928
1897
|
readonly rawHandler?: string | RawRouteHandler;
|
|
1929
1898
|
/** Logical operation name (pipeline keys, MCP tool naming). */
|
|
@@ -1941,8 +1910,8 @@ interface RouteDefinition {
|
|
|
1941
1910
|
/**
|
|
1942
1911
|
* Opt this custom route into the multiTenant preset's tenant scoping.
|
|
1943
1912
|
*
|
|
1944
|
-
* When `true` AND the resource declares `multiTenantPreset
|
|
1945
|
-
*
|
|
1913
|
+
* When `true` AND the resource declares `multiTenantPreset`, arc
|
|
1914
|
+
* prepends the preset's tenant
|
|
1946
1915
|
* filter + injection middleware to this route's chain — the same
|
|
1947
1916
|
* pair `update` gets. The handler can then read the resolved tenant
|
|
1948
1917
|
* via `getOrgId(req.scope)` / `req._tenantFields` (or, for write
|
|
@@ -2139,6 +2108,28 @@ interface ActionDefinition {
|
|
|
2139
2108
|
* resource root and drops `id` from the MCP tool's input shape.
|
|
2140
2109
|
*/
|
|
2141
2110
|
readonly id?: boolean;
|
|
2111
|
+
/**
|
|
2112
|
+
* Where an id-bound action's TARGET is authorized. Default: arc loads it through the
|
|
2113
|
+
* resource's access control (id + tenant + row policy, as `GET /:id` does) and answers
|
|
2114
|
+
* 404 when the caller could not read it. `{ scope: 'handler', reason }` hands that check
|
|
2115
|
+
* to the handler — for a target that is deliberately not a row of this resource's tenant.
|
|
2116
|
+
*/
|
|
2117
|
+
readonly target?: {
|
|
2118
|
+
readonly scope: "handler";
|
|
2119
|
+
readonly reason: string;
|
|
2120
|
+
};
|
|
2121
|
+
/**
|
|
2122
|
+
* `{ required: true }`: a call of THIS action without an idempotency key is refused (400
|
|
2123
|
+
* `arc.idempotency_key_required`) and a keyed one is deduplicated; other actions on the
|
|
2124
|
+
* same mount stay optional. Needs `idempotencyPlugin` enabled.
|
|
2125
|
+
*/
|
|
2126
|
+
readonly idempotency?: {
|
|
2127
|
+
readonly required: boolean;
|
|
2128
|
+
/** `'handler'`: the handler deduplicates; arc only demands and exposes the key. Needs `owner`. */
|
|
2129
|
+
readonly dedup?: "handler";
|
|
2130
|
+
/** The runner that owns at-most-once under `dedup: 'handler'`. */
|
|
2131
|
+
readonly owner?: string;
|
|
2132
|
+
};
|
|
2142
2133
|
/**
|
|
2143
2134
|
* MCP tool generation:
|
|
2144
2135
|
* - omitted/true: auto-generate
|
|
@@ -2817,6 +2808,16 @@ interface FieldRule$1 {
|
|
|
2817
2808
|
}
|
|
2818
2809
|
//#endregion
|
|
2819
2810
|
//#region src/types/resource/schemas.d.ts
|
|
2811
|
+
/**
|
|
2812
|
+
* One client-requestable join. The base collection's tenant filter never reaches a joined
|
|
2813
|
+
* collection, so its scope is REQUIRED and is always the server's — a client `where` is discarded.
|
|
2814
|
+
*/
|
|
2815
|
+
interface LookupPolicy {
|
|
2816
|
+
/** Filter on the JOINED rows (tenant, soft-delete, visibility). `{}` only for a genuinely global collection. */
|
|
2817
|
+
scope: (ctx: {
|
|
2818
|
+
scope: RequestScope | undefined;
|
|
2819
|
+
}) => Record<string, unknown>;
|
|
2820
|
+
}
|
|
2820
2821
|
/**
|
|
2821
2822
|
* Schema-shaping options for a resource.
|
|
2822
2823
|
*
|
|
@@ -2913,11 +2914,10 @@ interface RouteSchemaOptions extends SchemaBuilderOptions {
|
|
|
2913
2914
|
*/
|
|
2914
2915
|
allowedPopulate?: string[];
|
|
2915
2916
|
/**
|
|
2916
|
-
*
|
|
2917
|
-
*
|
|
2918
|
-
* disable the whitelist (kit-level rules still apply).
|
|
2917
|
+
* Client-requestable joins, keyed by collection, each with the SERVER-owned scope of its
|
|
2918
|
+
* joined side. Omit to deny every client lookup (the default).
|
|
2919
2919
|
*/
|
|
2920
|
-
allowedLookups?: string
|
|
2920
|
+
allowedLookups?: Readonly<Record<string, LookupPolicy>>;
|
|
2921
2921
|
};
|
|
2922
2922
|
}
|
|
2923
2923
|
/**
|
|
@@ -3070,6 +3070,14 @@ interface ResolvedTenantPurge {
|
|
|
3070
3070
|
}
|
|
3071
3071
|
//#endregion
|
|
3072
3072
|
//#region src/types/resource/config.d.ts
|
|
3073
|
+
/**
|
|
3074
|
+
* Built-in write routes whose retry must not execute twice: each listed op answers a
|
|
3075
|
+
* keyless call with 400 `arc.idempotency_key_required` and dedupes a keyed one. Needs
|
|
3076
|
+
* `idempotencyPlugin` enabled; boot refuses an op that is not mounted.
|
|
3077
|
+
*/
|
|
3078
|
+
interface ResourceIdempotencyConfig {
|
|
3079
|
+
readonly required: readonly ("create" | "update" | "delete")[];
|
|
3080
|
+
}
|
|
3073
3081
|
/** Standard controller type alias for CRUD operations. */
|
|
3074
3082
|
type CrudController<TDoc> = IController<TDoc>;
|
|
3075
3083
|
/** Resource-level permissions — only `PermissionCheck` functions allowed. */
|
|
@@ -3140,6 +3148,16 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
3140
3148
|
* that silently degrades is the defect, not a mode.
|
|
3141
3149
|
*/
|
|
3142
3150
|
transactional?: boolean;
|
|
3151
|
+
/**
|
|
3152
|
+
* Optimistic concurrency over HTTP (RFC 9110). Reads send `ETag: "<version>"`; an update
|
|
3153
|
+
* with `If-Match` applies only at that version (else 412), and EVERY update is a CAS write on
|
|
3154
|
+
* the version it loaded (else 409) — so the version, and the ETag, change on every write.
|
|
3155
|
+
* `versionField` defaults to `'__v'`. Boot-fatal on a repository without the
|
|
3156
|
+
* `optimisticConcurrency` capability.
|
|
3157
|
+
*/
|
|
3158
|
+
concurrency?: {
|
|
3159
|
+
versionField?: string;
|
|
3160
|
+
};
|
|
3143
3161
|
queryParser?: unknown;
|
|
3144
3162
|
permissions?: ResourcePermissions;
|
|
3145
3163
|
schemaOptions?: RouteSchemaOptions;
|
|
@@ -3382,6 +3400,11 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
3382
3400
|
* ```
|
|
3383
3401
|
*/
|
|
3384
3402
|
idField?: (keyof TDoc & string) | (string & {});
|
|
3403
|
+
/**
|
|
3404
|
+
* Further unique keys `GET /:id` and an action's `:id` accept, tried in order after `idField`
|
|
3405
|
+
* — a business number beside `_id`. Same tenant and row-policy scope as the primary lookup.
|
|
3406
|
+
*/
|
|
3407
|
+
lookupFields?: readonly ((keyof TDoc & string) | (string & {}))[];
|
|
3385
3408
|
/** For grouping in registry */
|
|
3386
3409
|
module?: string;
|
|
3387
3410
|
/** Domain events */
|
|
@@ -3513,6 +3536,8 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
3513
3536
|
* registered. Set to `false` to disable for this resource.
|
|
3514
3537
|
*/
|
|
3515
3538
|
rateLimit?: RateLimitConfig | false;
|
|
3539
|
+
/** Idempotency-key fence for the built-in write routes. See `ResourceIdempotencyConfig`. */
|
|
3540
|
+
idempotency?: ResourceIdempotencyConfig;
|
|
3516
3541
|
/**
|
|
3517
3542
|
* QueryCache configuration for this resource. Enables
|
|
3518
3543
|
* stale-while-revalidate and auto-invalidation. Requires
|
|
@@ -3656,7 +3681,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3656
3681
|
readonly permissions: ResourcePermissions;
|
|
3657
3682
|
readonly routes: readonly RouteDefinition[];
|
|
3658
3683
|
readonly middlewares: MiddlewareConfig;
|
|
3659
|
-
readonly routeGuards?: RouteHandlerMethod
|
|
3684
|
+
readonly routeGuards?: RouteHandlerMethod[];
|
|
3660
3685
|
readonly disableDefaultRoutes: boolean;
|
|
3661
3686
|
readonly disabledRoutes: readonly CrudRouteKey[];
|
|
3662
3687
|
readonly actions?: ActionsMap;
|
|
@@ -3664,6 +3689,8 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3664
3689
|
readonly aggregations?: AggregationsMap;
|
|
3665
3690
|
readonly events: Record<string, EventDefinition>;
|
|
3666
3691
|
readonly rateLimit?: RateLimitConfig | false;
|
|
3692
|
+
readonly idempotency?: ResourceIdempotencyConfig;
|
|
3693
|
+
readonly lookupFields?: readonly string[];
|
|
3667
3694
|
readonly audit?: boolean | {
|
|
3668
3695
|
operations?: ("create" | "update" | "delete")[];
|
|
3669
3696
|
};
|
|
@@ -3699,6 +3726,12 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3699
3726
|
*/
|
|
3700
3727
|
readonly resolvedTenantPurge: ResolvedTenantPurge;
|
|
3701
3728
|
readonly idField?: string;
|
|
3729
|
+
/**
|
|
3730
|
+
* The optimistic-concurrency version field, resolved (`concurrency: {}` → `'__v'`);
|
|
3731
|
+
* `undefined` when the resource did not opt in. Published in the resource manifest
|
|
3732
|
+
* so a client sends `If-Match` without repeating the field name.
|
|
3733
|
+
*/
|
|
3734
|
+
readonly versionField?: string;
|
|
3702
3735
|
readonly queryParser?: QueryParserInterface;
|
|
3703
3736
|
readonly _appliedPresets: string[];
|
|
3704
3737
|
_pendingHooks: Array<{
|
|
@@ -3800,7 +3833,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3800
3833
|
//#region src/core/defineResource.d.ts
|
|
3801
3834
|
/**
|
|
3802
3835
|
* `TDoc` is **unconstrained** at this layer. The previous `TDoc
|
|
3803
|
-
* extends
|
|
3836
|
+
* extends object` bound leaked out of `BaseController`'s
|
|
3804
3837
|
* mixin-composition requirement into every host's adapter boundary:
|
|
3805
3838
|
* Mongoose's `HydratedDocument<T>`, Prisma's generated row types,
|
|
3806
3839
|
* and any domain interface without an explicit index signature all
|
|
@@ -3811,7 +3844,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3811
3844
|
* known envelope fields.
|
|
3812
3845
|
*
|
|
3813
3846
|
* The cast moved inside `resolveOrAutoCreateController` where
|
|
3814
|
-
* `BaseController<TDoc extends
|
|
3847
|
+
* `BaseController<TDoc extends object>` actually requires it.
|
|
3815
3848
|
* One internal boundary cast replaces N host-side casts.
|
|
3816
3849
|
*/
|
|
3817
3850
|
/**
|
|
@@ -3864,8 +3897,19 @@ declare class ResourceRegistry {
|
|
|
3864
3897
|
* runtime-only handle is still reachable.
|
|
3865
3898
|
*/
|
|
3866
3899
|
private _adapters;
|
|
3900
|
+
/** The mounted definitions, in registration order — see `getDefinitions`. */
|
|
3901
|
+
private _definitions;
|
|
3867
3902
|
private _frozen;
|
|
3868
3903
|
constructor();
|
|
3904
|
+
/**
|
|
3905
|
+
* The live `ResourceDefinition`s that mounted, in registration order — what a host hands to
|
|
3906
|
+
* `mcpPlugin({ resources })` or a docs generator instead of re-collecting them. A resource
|
|
3907
|
+
* declaring `skipRegistry` (incl. `customRoutesOnly`) opted out and is absent. Fresh array per call.
|
|
3908
|
+
* One composed module's share: `getModuleResources(fastify, name)` from `@classytic/arc/factory`.
|
|
3909
|
+
*/
|
|
3910
|
+
getDefinitions(): ResourceDefinition<unknown>[];
|
|
3911
|
+
/** The live definition registered under `name`, or `undefined`. */
|
|
3912
|
+
getDefinition(name: string): ResourceDefinition<unknown> | undefined;
|
|
3869
3913
|
/**
|
|
3870
3914
|
* Get the live `DataAdapter` for a registered resource — the handle whose
|
|
3871
3915
|
* `.repository` cascade / cleanup helpers actually call. Returns `undefined`
|
|
@@ -3874,8 +3918,13 @@ declare class ResourceRegistry {
|
|
|
3874
3918
|
* consumers cast to their kit's adapter type at the use site.
|
|
3875
3919
|
*/
|
|
3876
3920
|
getAdapter<T = unknown>(name: string): T | undefined;
|
|
3921
|
+
/** Set only by an explicit `strictResources: false`: a duplicate name keeps the FIRST entry. */
|
|
3922
|
+
private _tolerateDuplicates;
|
|
3923
|
+
/** Accept a duplicate resource name (the host opted out of `strictResources`; it was warned). */
|
|
3924
|
+
tolerateDuplicateNames(): void;
|
|
3877
3925
|
/**
|
|
3878
|
-
* Register a resource
|
|
3926
|
+
* Register a resource. A duplicate name throws — a resource missing from the registry still
|
|
3927
|
+
* serves traffic but vanishes from introspection, the permission matrix and MCP.
|
|
3879
3928
|
*/
|
|
3880
3929
|
register(resource: ResourceDefinition<unknown>, options?: RegisterOptions): this;
|
|
3881
3930
|
/**
|
|
@@ -3955,10 +4004,6 @@ declare class ResourceRegistry {
|
|
|
3955
4004
|
* iterate over a defunct registry entry. Clear both halves together.
|
|
3956
4005
|
*/
|
|
3957
4006
|
reset(): void;
|
|
3958
|
-
/** @internal Alias for unfreeze() */
|
|
3959
|
-
_unfreeze(): void;
|
|
3960
|
-
/** @internal Alias for reset() */
|
|
3961
|
-
_clear(): void;
|
|
3962
4007
|
/**
|
|
3963
4008
|
* Group by key
|
|
3964
4009
|
*/
|
|
@@ -3998,6 +4043,8 @@ interface RequestWithExtras extends FastifyRequest {
|
|
|
3998
4043
|
* can still read the entity handle.
|
|
3999
4044
|
*/
|
|
4000
4045
|
entityId?: string;
|
|
4046
|
+
/** The id-bound action's target as the fence read it — read via `getActionTarget(req)`. */
|
|
4047
|
+
actionTarget?: unknown;
|
|
4001
4048
|
};
|
|
4002
4049
|
context?: Record<string, unknown>;
|
|
4003
4050
|
_policyFilters?: Record<string, unknown>;
|
|
@@ -4132,6 +4179,8 @@ interface CrudRouterOptions {
|
|
|
4132
4179
|
* registered. Set to `false` to disable for this resource.
|
|
4133
4180
|
*/
|
|
4134
4181
|
rateLimit?: RateLimitConfig | false;
|
|
4182
|
+
/** Idempotency-key fence for the built-in write routes. */
|
|
4183
|
+
idempotency?: ResourceIdempotencyConfig;
|
|
4135
4184
|
/** PreHandler guards applied to every route (CRUD + custom + preset). */
|
|
4136
4185
|
routeGuards?: RouteHandlerMethod[];
|
|
4137
4186
|
/**
|
|
@@ -4167,6 +4216,8 @@ interface RequestContext {
|
|
|
4167
4216
|
* them without `as AnyRecord` casts.
|
|
4168
4217
|
*/
|
|
4169
4218
|
interface ArcInternalMetadata extends RequestContext {
|
|
4219
|
+
/** Trusted resolved tenant dimensions forwarded across transports. */
|
|
4220
|
+
_tenantFields?: Record<string, unknown>;
|
|
4170
4221
|
/** Policy filters from permission middleware */
|
|
4171
4222
|
_policyFilters?: Record<string, unknown>;
|
|
4172
4223
|
/** Request scope from scope resolution */
|
|
@@ -4241,6 +4292,8 @@ interface LookupOption {
|
|
|
4241
4292
|
single?: boolean;
|
|
4242
4293
|
/** Field selection on the joined collection */
|
|
4243
4294
|
select?: string | Record<string, 0 | 1>;
|
|
4295
|
+
/** Filter on the joined rows — set by arc from the resource's `LookupPolicy`, never by the client. */
|
|
4296
|
+
where?: Record<string, unknown>;
|
|
4244
4297
|
}
|
|
4245
4298
|
/**
|
|
4246
4299
|
* Mongoose-compatible populate option for advanced field selection.
|
|
@@ -4375,6 +4428,12 @@ interface ResourceMetadata {
|
|
|
4375
4428
|
capability?: string;
|
|
4376
4429
|
raw?: boolean;
|
|
4377
4430
|
schema?: unknown;
|
|
4431
|
+
/** The route's `config.idempotency` — `required: true` means a key is mandatory. */
|
|
4432
|
+
idempotency?: {
|
|
4433
|
+
required?: boolean;
|
|
4434
|
+
dedup?: "handler";
|
|
4435
|
+
owner?: string;
|
|
4436
|
+
};
|
|
4378
4437
|
}>;
|
|
4379
4438
|
routes: Array<{
|
|
4380
4439
|
method: string;
|
|
@@ -4400,6 +4459,10 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
4400
4459
|
* without re-reading the resource definition.
|
|
4401
4460
|
*/
|
|
4402
4461
|
tenantField?: string | false;
|
|
4462
|
+
/** The resource's primary key field (`_id` when unset). */
|
|
4463
|
+
idField?: string;
|
|
4464
|
+
/** Resolved optimistic-concurrency version field — set only under `concurrency`. */
|
|
4465
|
+
versionField?: string;
|
|
4403
4466
|
/**
|
|
4404
4467
|
* Resolved tenant-purge strategy — what arc actually runs when
|
|
4405
4468
|
* `cascadeDeleteForOrganization` fires for this resource. Computed
|
|
@@ -4436,6 +4499,8 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
4436
4499
|
disabledRoutes?: string[];
|
|
4437
4500
|
/** Rate limit config */
|
|
4438
4501
|
rateLimit?: RateLimitConfig | false;
|
|
4502
|
+
/** Built-in write ops that require an idempotency key (`defineResource({ idempotency })`). */
|
|
4503
|
+
idempotency?: ResourceIdempotencyConfig;
|
|
4439
4504
|
/** Per-resource audit opt-in flag (read by `auditPlugin` perResource mode) */
|
|
4440
4505
|
audit?: boolean | {
|
|
4441
4506
|
operations?: ("create" | "update" | "delete")[];
|
|
@@ -4461,6 +4526,12 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
4461
4526
|
* reading the action definition itself.
|
|
4462
4527
|
*/
|
|
4463
4528
|
readonly id?: boolean;
|
|
4529
|
+
/** `{ required: true }` — a call of this action requires an idempotency key. */
|
|
4530
|
+
readonly idempotency?: {
|
|
4531
|
+
readonly required: boolean;
|
|
4532
|
+
readonly dedup?: "handler";
|
|
4533
|
+
readonly owner?: string;
|
|
4534
|
+
};
|
|
4464
4535
|
/** MCP tool generation flag — `false` to skip, object for overrides */
|
|
4465
4536
|
readonly mcp?: boolean | {
|
|
4466
4537
|
readonly description?: string;
|
|
@@ -4584,26 +4655,5 @@ type InferDocType<T> = T extends DataAdapter<infer D> ? D : T extends ResourceCo
|
|
|
4584
4655
|
*/
|
|
4585
4656
|
type InferAdapterDoc<A> = A extends DataAdapter<infer D> ? D : unknown;
|
|
4586
4657
|
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
4658
|
//#endregion
|
|
4609
|
-
export {
|
|
4659
|
+
export { ResourceHookContext as $, AccessControl as $t, RequestWithExtras as A, MixinConstructor as An, AuthHelpers as At, OnTenantDeleteConfig as B, IController as Bn, TreeMixin as Bt, RequestIdOptions as C, ArcGetResult as Cn, ActionEntry as Ct, FastifyWithAuth as D, ControllerConfigurableOptions as Dn, RouteMcpConfig as Dt, FastifyRequestExtras as E, BaseControllerOptions as En, RouteDefinition as Et, ResourceDefinition as F, WriteContext as Fn, TokenPair as Ft, CrudRouteKey as G, ServerAccessor as Gn, BaseCrudController as Gt, PresetFunction as H, IRequestContext as Hn, SlugMixin as Ht, CrudController as I, WriteVerbKey as In, BaseController as It, MiddlewareConfig as J, ActionTargetCheck as Jt, CrudSchemas as K, QueryResolver as Kt, ResourceConfig as L, ControllerHandler as Ln, SoftDeleteExt as Lt, ResourceRegistry as M, BodySanitizerConfig as Mn, Authenticator as Mt, RouteRow as N, MutationWriteContext as Nn, AuthenticatorContext as Nt, FastifyWithDecorators as O, ControllerConstructionOptions as On, RouteMethod as Ot, defineResource as P, ResourceWrites as Pn, JwtContext as Pt, FieldRule$1 as Q, getEntityQuery as Qt, ResourceIdempotencyConfig as R, ControllerLike as Rn, SoftDeleteMixin as Rt, IntrospectionPluginOptions as S, ArcDeleteResult as Sn, ActionDefinition as St, EventsDecorator as T, ArcUpdateResult as Tn, ActionsMap as Tt, PresetHook as U, RawRouteHandler as Un, BulkExt as Ut, ResolvedTenantPurge as V, IControllerResponse as Vn, SlugExt as Vt, PresetResult as W, RouteHandler as Wn, BulkMixin as Wt, RouteSchemaOptions as X, getEntityId as Xt, OpenApiSchemas as Y, getActionTarget as Yt, ArcFieldRule as Z, getEntityIdField as Zt, ServiceContext as _, beforeDelete as _n, PipelineConfig as _t, IntrospectionData as a, DefineHookOptions as an, AggregationCacheConfig as at, HealthCheck as b, defineHook as bn, Transform as bt, ResourceMetadata as c, HookOperation as cn, AggregationIndexHint as ct, LookupOption as d, HookSystem as dn, AggregationRateLimit as dt, AccessControlConfig as en, ResourceHooks as et, OwnershipCheck as f, HookSystemOptions as fn, AggregationsMap as ft, RequestContext as g, beforeCreate as gn, OperationFilter as gt, QueryParserInterface as h, afterUpdate as hn, NextFunction as ht, PaginationResult as i, AroundHookHandler as in, AggMeasureShorthand as it, RegisterOptions as j, BodySanitizer as jn, AuthPluginOptions as jt, MiddlewareHandler as k, ListResult as kn, RateLimitConfig as kt, ArcInternalMetadata as l, HookPhase as ln, AggregationMaterializedContext as lt, PopulateOption as m, afterDelete as mn, Interceptor as mt, InferDocType as n, FetchDenialReason as nn, EventDefinition as nt, RegistryEntry as o, HookContext as on, AggregationConfig as ot, ParsedQuery as p, afterCreate as pn, Guard as pt, LookupPolicy as q, QueryResolverConfig as qt, InferResourceDoc as r, FetchResult as rn, AggMeasureInput as rt, RegistryStats as s, HookHandler as sn, AggregationDateRangeRequirement as st, InferAdapterDoc as t, AccessControlRepository as tn, ResourceExtensions as tt, ControllerQueryOptions as u, HookRegistration as un, AggregationMaterializedResult as ut, CrudRouterOptions as v, beforeUpdate as vn, PipelineContext as vt, ArcDecorator as w, ArcListResult as wn, ActionHandlerFn as wt, HealthOptions as x, ArcCreateResult as xn, ResourceCacheConfig as xt, GracefulShutdownOptions as y, createHookSystem as yn, PipelineStep as yt, ResourcePermissions as z, FastifyHandler as zn, TreeExt as zt };
|