@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
package/dist/audit/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { d as UserBase } from "../base-
|
|
3
|
-
import "../index-
|
|
2
|
+
import { d as UserBase } from "../base-CZyixEQR.mjs";
|
|
3
|
+
import "../index-WH7xgvxS.mjs";
|
|
4
4
|
import { FastifyPluginAsync } from "fastify";
|
|
5
5
|
import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
6
6
|
//#region src/audit/stores/interface.d.ts
|
|
@@ -37,6 +37,8 @@ interface AuditEntry {
|
|
|
37
37
|
}
|
|
38
38
|
interface AuditContext {
|
|
39
39
|
user?: UserBase;
|
|
40
|
+
/** The actor from the request's VALIDATED scope (user or service client). Wins over `user`. */
|
|
41
|
+
actorId?: string;
|
|
40
42
|
organizationId?: string;
|
|
41
43
|
requestId?: string;
|
|
42
44
|
ipAddress?: string;
|
|
@@ -86,7 +88,7 @@ interface AuditQueryOptions {
|
|
|
86
88
|
/**
|
|
87
89
|
* Create audit entry from context
|
|
88
90
|
*/
|
|
89
|
-
declare function createAuditEntry(resource: string, documentId: string, action: AuditAction, context: AuditContext, data?: {
|
|
91
|
+
export declare function createAuditEntry(resource: string, documentId: string, action: AuditAction, context: AuditContext, data?: {
|
|
90
92
|
before?: Record<string, unknown>;
|
|
91
93
|
after?: Record<string, unknown>;
|
|
92
94
|
metadata?: Record<string, unknown>;
|
|
@@ -191,8 +193,13 @@ declare module "fastify" {
|
|
|
191
193
|
audit: AuditLogger;
|
|
192
194
|
}
|
|
193
195
|
interface FastifyRequest {
|
|
194
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Audit context for the current request. The actor and organization are read from
|
|
198
|
+
* the VALIDATED scope when this is read — never captured before auth ran.
|
|
199
|
+
*/
|
|
195
200
|
auditContext?: AuditContext;
|
|
201
|
+
/** Extra fields a handler attaches to the audit row arc writes for this request. */
|
|
202
|
+
auditMetadata?: Record<string, unknown>;
|
|
196
203
|
}
|
|
197
204
|
}
|
|
198
205
|
interface AuditLogger {
|
|
@@ -206,6 +213,11 @@ interface AuditLogger {
|
|
|
206
213
|
restore: (resource: string, documentId: string, data: Record<string, unknown>, context?: AuditContext) => Promise<void>;
|
|
207
214
|
/** Log a custom action */
|
|
208
215
|
custom: (resource: string, documentId: string, action: string, data?: Record<string, unknown>, context?: AuditContext) => Promise<void>;
|
|
216
|
+
/**
|
|
217
|
+
* A logger bound to one request: every entry's actor and organization come from that
|
|
218
|
+
* request's validated scope, so a caller can neither forget nor forge them.
|
|
219
|
+
*/
|
|
220
|
+
forRequest: (request: AuditRequestLike) => RequestAuditLogger;
|
|
209
221
|
/** Query audit logs (if stores support it) */
|
|
210
222
|
query: (options: AuditQueryOptions) => Promise<AuditEntry[]>;
|
|
211
223
|
/**
|
|
@@ -215,7 +227,33 @@ interface AuditLogger {
|
|
|
215
227
|
*/
|
|
216
228
|
purge: (cutoff: Date) => Promise<number>;
|
|
217
229
|
}
|
|
218
|
-
|
|
230
|
+
/** {@link AuditLogger} writes with the context bound to one request. */
|
|
231
|
+
interface RequestAuditLogger {
|
|
232
|
+
create: (resource: string, documentId: string, data: Record<string, unknown>) => Promise<void>;
|
|
233
|
+
update: (resource: string, documentId: string, before: Record<string, unknown>, after: Record<string, unknown>) => Promise<void>;
|
|
234
|
+
delete: (resource: string, documentId: string, data: Record<string, unknown>) => Promise<void>;
|
|
235
|
+
restore: (resource: string, documentId: string, data: Record<string, unknown>) => Promise<void>;
|
|
236
|
+
custom: (resource: string, documentId: string, action: string, data?: Record<string, unknown>) => Promise<void>;
|
|
237
|
+
}
|
|
238
|
+
/** What {@link auditContextOf} reads — a FastifyRequest, or arc's `IRequestContext`. */
|
|
239
|
+
interface AuditRequestLike {
|
|
240
|
+
readonly scope?: unknown;
|
|
241
|
+
readonly user?: unknown;
|
|
242
|
+
readonly id?: string;
|
|
243
|
+
readonly ip?: string;
|
|
244
|
+
readonly headers?: Record<string, unknown>;
|
|
245
|
+
readonly method?: string;
|
|
246
|
+
readonly url?: string;
|
|
247
|
+
readonly routeOptions?: {
|
|
248
|
+
readonly url?: string;
|
|
249
|
+
};
|
|
250
|
+
readonly metadata?: unknown;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The audit context of a request, from its VALIDATED scope: the actor is the scope's user
|
|
254
|
+
* (or service client) and the organization is the scope's active organization.
|
|
255
|
+
*/
|
|
256
|
+
export declare function auditContextOf(request: AuditRequestLike): AuditContext;
|
|
219
257
|
declare const _default: FastifyPluginAsync<AuditPluginOptions>;
|
|
220
258
|
//#endregion
|
|
221
259
|
//#region src/audit/repository-audit-adapter.d.ts
|
|
@@ -237,14 +275,14 @@ interface RepositoryAuditStoreOptions {
|
|
|
237
275
|
*/
|
|
238
276
|
readonly sessionProvider?: () => unknown;
|
|
239
277
|
}
|
|
240
|
-
declare function repositoryAsAuditStore(repository: RepositoryLike, options?: RepositoryAuditStoreOptions): AuditStore;
|
|
278
|
+
export declare function repositoryAsAuditStore(repository: RepositoryLike, options?: RepositoryAuditStoreOptions): AuditStore;
|
|
241
279
|
//#endregion
|
|
242
280
|
//#region src/audit/stores/memory.d.ts
|
|
243
281
|
interface MemoryAuditStoreOptions {
|
|
244
282
|
/** Maximum entries to keep (default: 1000) */
|
|
245
283
|
maxEntries?: number;
|
|
246
284
|
}
|
|
247
|
-
declare class MemoryAuditStore implements AuditStore {
|
|
285
|
+
export declare class MemoryAuditStore implements AuditStore {
|
|
248
286
|
readonly name = "memory";
|
|
249
287
|
private entries;
|
|
250
288
|
private maxEntries;
|
|
@@ -259,4 +297,4 @@ declare class MemoryAuditStore implements AuditStore {
|
|
|
259
297
|
clear(): void;
|
|
260
298
|
}
|
|
261
299
|
//#endregion
|
|
262
|
-
export { type AuditAction, type AuditContext, type AuditEntry, type AuditLogger, type AuditPluginOptions, type AuditQueryOptions, type AuditStore, type AuditStoreOptions,
|
|
300
|
+
export { type AuditAction, type AuditContext, type AuditEntry, type AuditLogger, type AuditPluginOptions, type AuditQueryOptions, type AuditRequestLike, type AuditStore, type AuditStoreOptions, type MemoryAuditStoreOptions, type RepositoryAuditStoreOptions, type RequestAuditLogger, _default as auditPlugin };
|
package/dist/audit/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { g as getUserId, i as getClientId } from "../types-fvO6Ovzx.mjs";
|
|
3
|
+
import { t as transactionContext } from "../transactionContext-orvnGu4F.mjs";
|
|
4
|
+
import { n as declareRuntimeCapability } from "../runtimeCapabilities-DIMKMV2G.mjs";
|
|
4
5
|
import { and, anyOf, eq, gte, lt, lte } from "@classytic/repo-core/filter";
|
|
5
6
|
import fp from "fastify-plugin";
|
|
6
7
|
//#region src/audit/repository-audit-adapter.ts
|
|
@@ -86,7 +87,7 @@ function createAuditEntry(resource, documentId, action, context, data) {
|
|
|
86
87
|
resource,
|
|
87
88
|
documentId,
|
|
88
89
|
action,
|
|
89
|
-
userId: extractUserId(context.user),
|
|
90
|
+
userId: context.actorId ?? extractUserId(context.user),
|
|
90
91
|
organizationId: context.organizationId,
|
|
91
92
|
before: data?.before,
|
|
92
93
|
after: data?.after,
|
|
@@ -182,6 +183,7 @@ const auditPlugin = async (fastify, opts = {}) => {
|
|
|
182
183
|
if (!enabled) {
|
|
183
184
|
fastify.decorate("audit", createNoopLogger());
|
|
184
185
|
fastify.decorateRequest("auditContext", void 0);
|
|
186
|
+
fastify.decorateRequest("auditMetadata", void 0);
|
|
185
187
|
fastify.log?.debug?.("Audit plugin disabled");
|
|
186
188
|
return;
|
|
187
189
|
}
|
|
@@ -220,6 +222,16 @@ const auditPlugin = async (fastify, opts = {}) => {
|
|
|
220
222
|
...data
|
|
221
223
|
} }));
|
|
222
224
|
},
|
|
225
|
+
forRequest(request) {
|
|
226
|
+
const ctx = () => auditContextOf(request);
|
|
227
|
+
return {
|
|
228
|
+
create: (resource, documentId, data) => audit.create(resource, documentId, data, ctx()),
|
|
229
|
+
update: (resource, documentId, before, after) => audit.update(resource, documentId, before, after, ctx()),
|
|
230
|
+
delete: (resource, documentId, data) => audit.delete(resource, documentId, data, ctx()),
|
|
231
|
+
restore: (resource, documentId, data) => audit.restore(resource, documentId, data, ctx()),
|
|
232
|
+
custom: (resource, documentId, action, data) => audit.custom(resource, documentId, action, data, ctx())
|
|
233
|
+
};
|
|
234
|
+
},
|
|
223
235
|
async query(options) {
|
|
224
236
|
for (const store of stores) if (store.query) return store.query(options);
|
|
225
237
|
return [];
|
|
@@ -231,27 +243,10 @@ const auditPlugin = async (fastify, opts = {}) => {
|
|
|
231
243
|
}
|
|
232
244
|
};
|
|
233
245
|
fastify.decorate("audit", audit);
|
|
234
|
-
fastify.decorateRequest("auditContext",
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const scope = request.scope;
|
|
239
|
-
request.auditContext = {
|
|
240
|
-
user,
|
|
241
|
-
organizationId: scope?.kind === "member" ? scope.organizationId : scope?.kind === "elevated" ? scope.organizationId : void 0,
|
|
242
|
-
requestId: request.id,
|
|
243
|
-
ipAddress: request.ip,
|
|
244
|
-
userAgent: request.headers["user-agent"],
|
|
245
|
-
endpoint: void 0,
|
|
246
|
-
duration: void 0
|
|
247
|
-
};
|
|
248
|
-
});
|
|
249
|
-
fastify.addHook("onResponse", async (request, reply) => {
|
|
250
|
-
if (request.auditContext) {
|
|
251
|
-
request.auditContext.endpoint = `${request.method} ${request.routeOptions?.url ?? request.url}`;
|
|
252
|
-
request.auditContext.duration = Math.round(reply.elapsedTime);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
246
|
+
fastify.decorateRequest("auditContext", { getter() {
|
|
247
|
+
return auditContextOf(this);
|
|
248
|
+
} });
|
|
249
|
+
fastify.decorateRequest("auditMetadata", void 0);
|
|
255
250
|
const retention = opts.retention;
|
|
256
251
|
let retentionTimer = null;
|
|
257
252
|
if (retention) {
|
|
@@ -307,8 +302,10 @@ const auditPlugin = async (fastify, opts = {}) => {
|
|
|
307
302
|
} else if (excludeResources.has(ctx.resource)) return;
|
|
308
303
|
const docId = autoAuditExtractId(ctx.result);
|
|
309
304
|
const scope = ctx.context?._scope;
|
|
305
|
+
const scopedActor = scope ? getUserId(scope) ?? getClientId(scope) : void 0;
|
|
310
306
|
const auditCtx = {
|
|
311
307
|
user: ctx.user,
|
|
308
|
+
...scopedActor ? { actorId: String(scopedActor) } : {},
|
|
312
309
|
organizationId: scope ? autoAuditGetOrgId(scope) : void 0
|
|
313
310
|
};
|
|
314
311
|
try {
|
|
@@ -331,6 +328,24 @@ const auditPlugin = async (fastify, opts = {}) => {
|
|
|
331
328
|
}
|
|
332
329
|
fastify.log?.debug?.({ stores: stores.map((s) => s.name) }, "Audit plugin enabled");
|
|
333
330
|
};
|
|
331
|
+
/**
|
|
332
|
+
* The audit context of a request, from its VALIDATED scope: the actor is the scope's user
|
|
333
|
+
* (or service client) and the organization is the scope's active organization.
|
|
334
|
+
*/
|
|
335
|
+
function auditContextOf(request) {
|
|
336
|
+
const scope = request.metadata?._scope ?? request.scope;
|
|
337
|
+
const actorId = scope ? getUserId(scope) ?? getClientId(scope) : void 0;
|
|
338
|
+
const userAgent = request.headers?.["user-agent"];
|
|
339
|
+
return {
|
|
340
|
+
user: request.user,
|
|
341
|
+
...actorId ? { actorId: String(actorId) } : {},
|
|
342
|
+
organizationId: scope ? autoAuditGetOrgId(scope) : void 0,
|
|
343
|
+
requestId: request.id,
|
|
344
|
+
ipAddress: request.ip,
|
|
345
|
+
userAgent: typeof userAgent === "string" ? userAgent : void 0,
|
|
346
|
+
endpoint: request.method && request.routeOptions?.url ? `${request.method} ${request.routeOptions.url}` : void 0
|
|
347
|
+
};
|
|
348
|
+
}
|
|
334
349
|
/** Extract document ID from a result */
|
|
335
350
|
function autoAuditExtractId(doc) {
|
|
336
351
|
if (!doc || typeof doc !== "object") return "";
|
|
@@ -361,6 +376,13 @@ function createNoopLogger() {
|
|
|
361
376
|
delete: noop,
|
|
362
377
|
restore: noop,
|
|
363
378
|
custom: noop,
|
|
379
|
+
forRequest: () => ({
|
|
380
|
+
create: noop,
|
|
381
|
+
update: noop,
|
|
382
|
+
delete: noop,
|
|
383
|
+
restore: noop,
|
|
384
|
+
custom: noop
|
|
385
|
+
}),
|
|
364
386
|
query: async () => [],
|
|
365
387
|
purge: async () => 0,
|
|
366
388
|
_noop: true
|
|
@@ -372,4 +394,4 @@ var auditPlugin_default = fp(auditPlugin, {
|
|
|
372
394
|
dependencies: ["arc-core"]
|
|
373
395
|
});
|
|
374
396
|
//#endregion
|
|
375
|
-
export { MemoryAuditStore, auditPlugin_default as auditPlugin,
|
|
397
|
+
export { MemoryAuditStore, auditContextOf, auditPlugin_default as auditPlugin, createAuditEntry, repositoryAsAuditStore };
|
package/dist/auth/audit.d.mts
CHANGED
|
@@ -9,13 +9,13 @@ import { FastifyInstance } from "fastify";
|
|
|
9
9
|
* unknown events still flow through when matched, just with the BA-supplied
|
|
10
10
|
* name verbatim. Use this list for autocomplete-friendly defaults.
|
|
11
11
|
*/
|
|
12
|
-
type AuthEventName = "session.create" | "session.delete" | "session.update" | "user.create" | "user.update" | "user.delete" | "mfa.enroll" | "mfa.verify" | "mfa.failed" | "mfa.disable" | "password.reset.request" | "password.reset.complete" | "email.verify" | "org.create" | "org.delete" | "org.invite.create" | "org.invite.accept" | "org.invite.reject" | "org.member.add" | "org.member.remove" | "org.member.role.update" | "apikey.create" | "apikey.delete" | "apikey.failed" | "endpoint.before" | "endpoint.after" | "endpoint.error";
|
|
12
|
+
export type AuthEventName = "session.create" | "session.delete" | "session.update" | "user.create" | "user.update" | "user.delete" | "mfa.enroll" | "mfa.verify" | "mfa.failed" | "mfa.disable" | "password.reset.request" | "password.reset.complete" | "email.verify" | "org.create" | "org.delete" | "org.invite.create" | "org.invite.accept" | "org.invite.reject" | "org.member.add" | "org.member.remove" | "org.member.role.update" | "apikey.create" | "apikey.delete" | "apikey.failed" | "endpoint.before" | "endpoint.after" | "endpoint.error";
|
|
13
13
|
/**
|
|
14
14
|
* Resolved auth event ready for audit. The bridge constructs this from
|
|
15
15
|
* BA's hook context, then forwards to `app.audit.custom('auth', subjectId,
|
|
16
16
|
* event.name, event.payload)`.
|
|
17
17
|
*/
|
|
18
|
-
interface AuthEvent {
|
|
18
|
+
export interface AuthEvent {
|
|
19
19
|
/** Canonical name (see {@link AuthEventName}). */
|
|
20
20
|
name: string;
|
|
21
21
|
/**
|
|
@@ -34,7 +34,7 @@ interface AuthEvent {
|
|
|
34
34
|
/**
|
|
35
35
|
* Options for `wireBetterAuthAudit`.
|
|
36
36
|
*/
|
|
37
|
-
interface WireBetterAuthAuditOptions {
|
|
37
|
+
export interface WireBetterAuthAuditOptions {
|
|
38
38
|
/**
|
|
39
39
|
* Glob patterns selecting which events to audit. `*` matches a single
|
|
40
40
|
* segment (`session.*` matches `session.create` but not `session.create.foo`),
|
|
@@ -72,7 +72,7 @@ interface WireBetterAuthAuditOptions {
|
|
|
72
72
|
* Result of `wireBetterAuthAudit` — duck-typed BA `hooks` + `databaseHooks`
|
|
73
73
|
* config plus an `attach(app)` method to connect the live audit logger.
|
|
74
74
|
*/
|
|
75
|
-
interface BetterAuthAuditBridge {
|
|
75
|
+
export interface BetterAuthAuditBridge {
|
|
76
76
|
/**
|
|
77
77
|
* Spread into `betterAuth({ hooks })`. BA's top-level `hooks.before` /
|
|
78
78
|
* `hooks.after` slots take a single async function each (not an array).
|
|
@@ -157,7 +157,7 @@ interface BetterAuthAuditBridge {
|
|
|
157
157
|
* Single BA hook entry — duck-typed to avoid pinning a BA major version.
|
|
158
158
|
* The shape mirrors `better-auth`'s public `hooks.{before,after}` API.
|
|
159
159
|
*/
|
|
160
|
-
interface BetterAuthHookEntry {
|
|
160
|
+
export interface BetterAuthHookEntry {
|
|
161
161
|
matcher: (ctx: BetterAuthHookContext) => boolean;
|
|
162
162
|
/**
|
|
163
163
|
* Returns a (possibly empty) object so BA's `runBeforeHooks` /
|
|
@@ -169,7 +169,7 @@ interface BetterAuthHookEntry {
|
|
|
169
169
|
handler: (ctx: BetterAuthHookContext) => Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
170
170
|
}
|
|
171
171
|
/** Subset of BA's hook context the bridge actually reads. */
|
|
172
|
-
interface BetterAuthHookContext {
|
|
172
|
+
export interface BetterAuthHookContext {
|
|
173
173
|
path?: string;
|
|
174
174
|
method?: string;
|
|
175
175
|
body?: Record<string, unknown>;
|
|
@@ -198,6 +198,6 @@ interface SessionLike {
|
|
|
198
198
|
userAgent?: string;
|
|
199
199
|
impersonatedBy?: string;
|
|
200
200
|
}
|
|
201
|
-
declare function wireBetterAuthAudit(opts?: WireBetterAuthAuditOptions): BetterAuthAuditBridge;
|
|
201
|
+
export declare function wireBetterAuthAudit(opts?: WireBetterAuthAuditOptions): BetterAuthAuditBridge;
|
|
202
202
|
//#endregion
|
|
203
|
-
export {
|
|
203
|
+
export {};
|
package/dist/auth/audit.mjs
CHANGED
package/dist/auth/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { i as RequestScope } from "../types-Ddl9OjmE.mjs";
|
|
3
|
+
import { l as PermissionCheck } from "../base-CZyixEQR.mjs";
|
|
4
|
+
import { At as AuthHelpers, jt as AuthPluginOptions } from "../index-WH7xgvxS.mjs";
|
|
4
5
|
import { t as ExternalOpenApiPaths } from "../externalPaths-CmGOxOtD.mjs";
|
|
5
6
|
import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-qimlKV0P.mjs";
|
|
6
7
|
import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest } from "fastify";
|
|
@@ -17,7 +18,6 @@ declare module "fastify" {
|
|
|
17
18
|
auth: AuthHelpers;
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
declare const authPlugin: FastifyPluginAsync<AuthPluginOptions>;
|
|
21
21
|
declare const _default: FastifyPluginAsync<AuthPluginOptions>;
|
|
22
22
|
//#endregion
|
|
23
23
|
//#region src/auth/betterAuth.d.ts
|
|
@@ -50,6 +50,30 @@ interface BetterAuthAdapterOptions {
|
|
|
50
50
|
* @default false
|
|
51
51
|
*/
|
|
52
52
|
orgContext?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Effective roles in the active organization — the seam for an organization HIERARCHY (roles held
|
|
55
|
+
* at a parent, e.g. a head office, apply in its child organizations). Called with the caller's own
|
|
56
|
+
* membership roles there (`null` when not a member); returns the roles the request carries, or
|
|
57
|
+
* `null` to leave it non-member. Requires `orgContext`.
|
|
58
|
+
*/
|
|
59
|
+
resolveOrgRoles?: (input: {
|
|
60
|
+
userId: string;
|
|
61
|
+
organizationId: string;
|
|
62
|
+
memberRoles: readonly string[] | null;
|
|
63
|
+
}) => readonly string[] | null | Promise<readonly string[] | null>;
|
|
64
|
+
/**
|
|
65
|
+
* Where the caller's own membership roles come from. Default `'better-auth'`: arc asks Better
|
|
66
|
+
* Auth (`getActiveMember`, then `getActiveMemberRole`) and passes the answer to `resolveOrgRoles`
|
|
67
|
+
* as `memberRoles`.
|
|
68
|
+
*
|
|
69
|
+
* `'resolver'`: `resolveOrgRoles` reads membership itself, so arc skips Better Auth's lookup — one
|
|
70
|
+
* fewer membership read per authenticated request — and calls it with `memberRoles: null`. Choose
|
|
71
|
+
* it ONLY for a resolver that never reads `memberRoles`; one that extends them would lose them.
|
|
72
|
+
* Requires `resolveOrgRoles`.
|
|
73
|
+
*
|
|
74
|
+
* @default 'better-auth'
|
|
75
|
+
*/
|
|
76
|
+
memberLookup?: "better-auth" | "resolver";
|
|
53
77
|
/**
|
|
54
78
|
* OpenAPI documentation for auth endpoints.
|
|
55
79
|
* - `true` (default): auto-extract from auth.api if available
|
|
@@ -86,16 +110,20 @@ interface BetterAuthAdapterOptions {
|
|
|
86
110
|
*/
|
|
87
111
|
exposeAuthErrors?: boolean;
|
|
88
112
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
113
|
+
* Credentials other than a session, by `Authorization` scheme (RFC 7235) — a paired device, an
|
|
114
|
+
* API key. A request presenting a registered scheme is authenticated by its verifier ONLY: the
|
|
115
|
+
* scope it returns (usually `{ kind: 'service', … }`) is installed; `null` is a 401. Gate such
|
|
116
|
+
* routes with `requireServiceScope(...)`. `Bearer` is the session's and cannot be registered.
|
|
92
117
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*
|
|
118
|
+
* @example
|
|
119
|
+
* credentials: { Device: (value) => verifyDevice(value) } // Authorization: Device <sn>:<token>
|
|
120
|
+
*/
|
|
121
|
+
credentials?: Readonly<Record<string, CredentialVerifier>>;
|
|
122
|
+
/**
|
|
123
|
+
* Per-route `@fastify/rate-limit` config for the auth catch-all. No default:
|
|
124
|
+
* the same route serves the chatty `get-session` heartbeat, so a strict
|
|
125
|
+
* login-style limit would 429 every SPA that polls sessions. Pick a ceiling
|
|
126
|
+
* above your poll cadence, or exempt the heartbeat:
|
|
99
127
|
*
|
|
100
128
|
* @example
|
|
101
129
|
* ```ts
|
|
@@ -173,7 +201,9 @@ declare module "fastify" {
|
|
|
173
201
|
* fastify.get('/me', { preHandler: [authenticate] }, handler);
|
|
174
202
|
* ```
|
|
175
203
|
*/
|
|
176
|
-
|
|
204
|
+
/** Verifies one credential scheme's value; the scope to install, or `null` when it is not valid. */
|
|
205
|
+
type CredentialVerifier = (credentials: string, request: FastifyRequest) => Promise<RequestScope | null>;
|
|
206
|
+
export declare function createBetterAuthAdapter(options: BetterAuthAdapterOptions): BetterAuthAdapterResult;
|
|
177
207
|
//#endregion
|
|
178
208
|
//#region src/auth/betterAuthOpenApi.d.ts
|
|
179
209
|
interface BetterAuthOpenApiOptions {
|
|
@@ -211,7 +241,7 @@ interface BetterAuthOpenApiOptions {
|
|
|
211
241
|
* endpoints, converts their Zod schemas to JSON Schema via `z.toJSONSchema()`,
|
|
212
242
|
* and returns a complete `ExternalOpenApiPaths` object ready for Arc's spec builder.
|
|
213
243
|
*/
|
|
214
|
-
declare function extractBetterAuthOpenApi(authApi: Record<string, unknown>, options?: BetterAuthOpenApiOptions): ExternalOpenApiPaths;
|
|
244
|
+
export declare function extractBetterAuthOpenApi(authApi: Record<string, unknown>, options?: BetterAuthOpenApiOptions): ExternalOpenApiPaths;
|
|
215
245
|
//#endregion
|
|
216
246
|
//#region src/auth/trustedOrigins.d.ts
|
|
217
247
|
/**
|
|
@@ -288,6 +318,6 @@ interface MirrorTrustedOriginsOptions {
|
|
|
288
318
|
* array.
|
|
289
319
|
* - `[canonicalUrl]` when `corsOrigins` is `false` / `undefined`.
|
|
290
320
|
*/
|
|
291
|
-
declare function mirrorTrustedOriginsFromCors(options: MirrorTrustedOriginsOptions): string[];
|
|
321
|
+
export declare function mirrorTrustedOriginsFromCors(options: MirrorTrustedOriginsOptions): string[];
|
|
292
322
|
//#endregion
|
|
293
|
-
export { type AuthPluginOptions, type BetterAuthAdapterOptions, type BetterAuthAdapterResult, type BetterAuthHandler, type BetterAuthOpenApiOptions, type CorsOriginsConfig, MemorySessionStore, type MemorySessionStoreOptions, type MirrorTrustedOriginsOptions, type SessionCookieOptions, type SessionData, type SessionManagerOptions, type SessionManagerResult, type SessionStore, _default as authPlugin,
|
|
323
|
+
export { type AuthPluginOptions, type BetterAuthAdapterOptions, type BetterAuthAdapterResult, type BetterAuthHandler, type BetterAuthOpenApiOptions, type CorsOriginsConfig, type CredentialVerifier, MemorySessionStore, type MemorySessionStoreOptions, type MirrorTrustedOriginsOptions, type SessionCookieOptions, type SessionData, type SessionManagerOptions, type SessionManagerResult, type SessionStore, _default as authPlugin, createSessionManager };
|