@classytic/arc 2.8.5 → 2.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -38
- package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-CbKKIflT.mjs} +193 -143
- package/dist/EventTransport-CUw5NNWe.d.mts +293 -0
- package/dist/{ResourceRegistry-C6uXlWe3.mjs → ResourceRegistry-BPd6NQDm.mjs} +1 -1
- package/dist/adapters/index.d.mts +3 -3
- package/dist/adapters/index.mjs +2 -2
- package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
- package/dist/audit/index.d.mts +135 -11
- package/dist/audit/index.mjs +107 -20
- package/dist/auth/index.d.mts +17 -9
- package/dist/auth/index.mjs +14 -7
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi-BuUcUEJq.mjs → betterAuthOpenApi-BBRVhjQN.mjs} +1 -1
- package/dist/cache/index.d.mts +17 -15
- package/dist/cache/index.mjs +15 -14
- package/dist/{caching-IMuYVjTL.mjs → caching-CBpK_SCM.mjs} +8 -3
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -6
- package/dist/{defineResource-tcgySDo1.mjs → core-CcR01lup.mjs} +58 -61
- package/dist/{createActionRouter-BORM8f17.mjs → createActionRouter-Bp_5c_2b.mjs} +3 -3
- package/dist/{createApp-B1EY8zxa.mjs → createApp-BuvPma24.mjs} +15 -14
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DtFxrG0s.mjs → elevation-C7hgL_aI.mjs} +22 -8
- package/dist/{errorHandler-f869_8PQ.mjs → errorHandler-Bb49BvPD.mjs} +59 -7
- package/dist/{errorHandler-Bah5JhBd.d.mts → errorHandler-DRQ3EqfL.d.mts} +37 -2
- package/dist/{eventPlugin-D9DKB2zM.d.mts → eventPlugin-CxWgpd6K.d.mts} +14 -2
- package/dist/{eventPlugin-CDjVTM82.mjs → eventPlugin-DCUjuiQT.mjs} +83 -5
- package/dist/events/index.d.mts +150 -36
- package/dist/events/index.mjs +355 -101
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +2 -2
- package/dist/{types-DZi1aYhm.d.mts → fields-Lo1VUDpt.d.mts} +121 -1
- package/dist/{fields-ipsbIRPK.mjs → fields-bxkeltzz.mjs} +18 -5
- package/dist/{filesUpload-C7r7HIeA.mjs → filesUpload-t21LS-py.mjs} +65 -7
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +32 -5
- package/dist/idempotency/index.mjs +119 -12
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-DtDzOBn8.d.mts → index-8qw4y6ff.d.mts} +4 -135
- package/dist/{index-BLXBmWud.d.mts → index-ChIw3776.d.mts} +283 -408
- package/dist/{interface-CMRutPfe.d.mts → index-Cl0uoKd5.d.mts} +1758 -2506
- package/dist/{index-C1meYuDn.d.mts → index-DStwgFUK.d.mts} +81 -7
- package/dist/index.d.mts +7 -8
- package/dist/index.mjs +11 -12
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +26 -8
- package/dist/integrations/mcp/index.mjs +96 -17
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/webhooks.d.mts +5 -0
- package/dist/integrations/webhooks.mjs +6 -0
- package/dist/interface-D218ikEo.d.mts +77 -0
- package/dist/{memory-Cp7_cAko.mjs → memory-B5Amv9A1.mjs} +23 -8
- package/dist/{openapi-CbKUJY_m.mjs → openapi-B5F8AddX.mjs} +3 -3
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -4
- package/dist/permissions/index.mjs +5 -5
- package/dist/{permissions-CH4cNwJi.mjs → permissions-Dk6mshja.mjs} +315 -397
- package/dist/plugins/index.d.mts +7 -7
- package/dist/plugins/index.mjs +14 -16
- package/dist/plugins/response-cache.mjs +2 -2
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +27 -5
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +3 -2
- package/dist/presets/index.mjs +4 -3
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +178 -0
- package/dist/presets/search.mjs +150 -0
- package/dist/{presets-C2xgzW6x.mjs → presets-fLJVXdVn.mjs} +1 -1
- package/dist/{queryCachePlugin-BJJGBTlu.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
- package/dist/{queryCachePlugin-BH-fidlv.mjs → queryCachePlugin-DQCEfJis.mjs} +9 -9
- package/dist/{queryParser-CgCtsjti.mjs → queryParser-DBqBB6AC.mjs} +1 -1
- package/dist/{redis-BM00zaPB.d.mts → redis-DqyeggCa.d.mts} +1 -1
- package/dist/{redis-stream-CrsfUmPt.d.mts → redis-stream-CakIQmwR.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{resourceToTools-8s-EsCCe.mjs → resourceToTools-BElv3xPT.mjs} +65 -48
- package/dist/{schemaConverter-Y7nCYaLJ.mjs → schemaConverter-BxFDdtXu.mjs} +1 -1
- package/dist/scope/index.d.mts +1 -1
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-Ad7ypl9e.mjs → sse-yBCgOLGu.mjs} +1 -1
- package/dist/store-helpers-ZCSMJJAX.mjs +57 -0
- package/dist/testing/index.d.mts +9 -17
- package/dist/testing/index.mjs +27 -83
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -31
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-BsbNMEDR.d.mts → types-Btdda02s.d.mts} +1 -1
- package/dist/{types-Ch9pTQbf.d.mts → types-Co8k3NyS.d.mts} +11 -9
- package/dist/types-Csi3FLfq.mjs +27 -0
- package/dist/utils/index.d.mts +208 -4
- package/dist/utils/index.mjs +5 -6
- package/dist/{utils-yYT3HDXt.mjs → utils-B2fNOD_i.mjs} +285 -2
- package/dist/{versioning-CDugduqI.mjs → versioning-C2U_bLY0.mjs} +3 -5
- package/package.json +20 -26
- package/skills/arc/SKILL.md +97 -23
- package/skills/arc/references/auth.md +94 -0
- package/skills/arc/references/events.md +200 -12
- package/skills/arc/references/mcp.md +4 -17
- package/skills/arc/references/multi-tenancy.md +43 -0
- package/skills/arc/references/production.md +34 -60
- package/dist/EventTransport-BXja8NOc.d.mts +0 -135
- package/dist/audit/mongodb.d.mts +0 -2
- package/dist/audit/mongodb.mjs +0 -2
- package/dist/circuitBreaker-cmi5XDv5.mjs +0 -284
- package/dist/circuitBreaker-dTtG-UyS.d.mts +0 -206
- package/dist/core-F0QoWBt2.mjs +0 -34
- package/dist/dynamic/index.d.mts +0 -93
- package/dist/dynamic/index.mjs +0 -122
- package/dist/fields-DpZQa_Q3.d.mts +0 -109
- package/dist/idempotency/mongodb.d.mts +0 -2
- package/dist/idempotency/mongodb.mjs +0 -123
- package/dist/interface-4y979v99.d.mts +0 -54
- package/dist/mongodb-BsP-WbhN.d.mts +0 -127
- package/dist/mongodb-CTcp0hQZ.d.mts +0 -80
- package/dist/mongodb-Utc5k_-0.mjs +0 -90
- package/dist/policies/index.d.mts +0 -432
- package/dist/policies/index.mjs +0 -318
- package/dist/rpc/index.d.mts +0 -90
- package/dist/rpc/index.mjs +0 -248
- /package/dist/{HookSystem-HprTmvVY.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
- /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
- /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
- /package/dist/{elevation-B6S5csVA.d.mts → elevation-C5SwtkAn.d.mts} +0 -0
- /package/dist/{errors-Ck2h67pm.d.mts → errors-CCSsMpXE.d.mts} +0 -0
- /package/dist/{errors-BF2bIOIS.mjs → errors-D5c-5BJL.mjs} +0 -0
- /package/dist/{externalPaths-BnkYrNzp.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
- /package/dist/{interface-DfLGcus7.d.mts → interface-CSbZdv_3.d.mts} +0 -0
- /package/dist/{loadResources-PWd0OCpV.mjs → loadResources-BAzJItAJ.mjs} +0 -0
- /package/dist/{logger-D1YrIImS.mjs → logger-DLg8-Ueg.mjs} +0 -0
- /package/dist/{metrics-B-PU4-Yu.mjs → metrics-DuhiSEZI.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
- /package/dist/{registry-BiTKT1Dg.mjs → registry-B3lRFBWo.mjs} +0 -0
- /package/dist/{replyHelpers-CxkYGT81.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
- /package/dist/{requestContext-DYvHl113.mjs → requestContext-xHIKedG6.mjs} +0 -0
- /package/dist/{sessionManager-DDCmiNIo.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
- /package/dist/{storage-Dfzt4VTl.d.mts → storage-CVk_SEn2.d.mts} +0 -0
- /package/dist/{tracing-DdN2-wHJ.d.mts → tracing-65B51Dw3.d.mts} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
- /package/dist/{types-ZUu_h0jp.mjs → types-DV9WDfeg.mjs} +0 -0
package/dist/audit/index.d.mts
CHANGED
|
@@ -1,20 +1,134 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as UserBase } from "../fields-Lo1VUDpt.mjs";
|
|
2
|
+
import { kt as RepositoryLike } from "../index-Cl0uoKd5.mjs";
|
|
2
3
|
import { FastifyPluginAsync } from "fastify";
|
|
3
4
|
|
|
5
|
+
//#region src/audit/stores/interface.d.ts
|
|
6
|
+
type AuditAction = "create" | "update" | "delete" | "restore" | "custom";
|
|
7
|
+
interface AuditEntry {
|
|
8
|
+
/** Unique audit log ID */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Resource name (e.g., 'product', 'user') */
|
|
11
|
+
resource: string;
|
|
12
|
+
/** Document/entity ID */
|
|
13
|
+
documentId: string;
|
|
14
|
+
/** Action performed */
|
|
15
|
+
action: AuditAction;
|
|
16
|
+
/** User who performed the action */
|
|
17
|
+
userId?: string;
|
|
18
|
+
/** Organization context */
|
|
19
|
+
organizationId?: string;
|
|
20
|
+
/** Previous state (for updates) */
|
|
21
|
+
before?: Record<string, unknown>;
|
|
22
|
+
/** New state (for creates/updates) */
|
|
23
|
+
after?: Record<string, unknown>;
|
|
24
|
+
/** Changed fields (for updates) */
|
|
25
|
+
changes?: string[];
|
|
26
|
+
/** Request ID for tracing */
|
|
27
|
+
requestId?: string;
|
|
28
|
+
/** IP address */
|
|
29
|
+
ipAddress?: string;
|
|
30
|
+
/** User agent */
|
|
31
|
+
userAgent?: string;
|
|
32
|
+
/** Custom metadata */
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
/** When the action occurred */
|
|
35
|
+
timestamp: Date;
|
|
36
|
+
}
|
|
37
|
+
interface AuditContext {
|
|
38
|
+
user?: UserBase;
|
|
39
|
+
organizationId?: string;
|
|
40
|
+
requestId?: string;
|
|
41
|
+
ipAddress?: string;
|
|
42
|
+
userAgent?: string;
|
|
43
|
+
/** HTTP method + route pattern (e.g., 'PATCH /api/products/:id') */
|
|
44
|
+
endpoint?: string;
|
|
45
|
+
/** Request duration in milliseconds */
|
|
46
|
+
duration?: number;
|
|
47
|
+
}
|
|
48
|
+
interface AuditStoreOptions {
|
|
49
|
+
/** Store name for logging */
|
|
50
|
+
name: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Abstract audit store interface
|
|
54
|
+
*/
|
|
55
|
+
interface AuditStore {
|
|
56
|
+
/** Store name */
|
|
57
|
+
readonly name: string;
|
|
58
|
+
/** Log an audit entry */
|
|
59
|
+
log(entry: AuditEntry): Promise<void>;
|
|
60
|
+
/** Query audit logs (optional - not all stores support querying) */
|
|
61
|
+
query?(options: AuditQueryOptions): Promise<AuditEntry[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Purge entries older than `cutoff`, return count deleted. Optional —
|
|
64
|
+
* stores that don't support deletion (append-only emitters like Kafka,
|
|
65
|
+
* S3 archivers) simply omit this method and are skipped by
|
|
66
|
+
* `fastify.audit.purge(...)`. Mongo-backed repositories can also rely
|
|
67
|
+
* on a server-side TTL index instead of calling this; the method is
|
|
68
|
+
* the DB-agnostic escape hatch.
|
|
69
|
+
*/
|
|
70
|
+
purgeOlderThan?(cutoff: Date): Promise<number>;
|
|
71
|
+
/** Close/cleanup (optional) */
|
|
72
|
+
close?(): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
interface AuditQueryOptions {
|
|
75
|
+
resource?: string;
|
|
76
|
+
documentId?: string;
|
|
77
|
+
userId?: string;
|
|
78
|
+
organizationId?: string;
|
|
79
|
+
action?: AuditAction | AuditAction[];
|
|
80
|
+
from?: Date;
|
|
81
|
+
to?: Date;
|
|
82
|
+
limit?: number;
|
|
83
|
+
offset?: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create audit entry from context
|
|
87
|
+
*/
|
|
88
|
+
declare function createAuditEntry(resource: string, documentId: string, action: AuditAction, context: AuditContext, data?: {
|
|
89
|
+
before?: Record<string, unknown>;
|
|
90
|
+
after?: Record<string, unknown>;
|
|
91
|
+
metadata?: Record<string, unknown>;
|
|
92
|
+
}): AuditEntry;
|
|
93
|
+
//#endregion
|
|
4
94
|
//#region src/audit/auditPlugin.d.ts
|
|
5
95
|
interface AuditPluginOptions {
|
|
6
96
|
/** Enable audit logging (default: false) */
|
|
7
97
|
enabled?: boolean;
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Repository managing the audit collection. Arc consumes it **directly**
|
|
100
|
+
* — no wrapping, no aliases, no proxy classes. Pass any object that
|
|
101
|
+
* implements arc's `RepositoryLike` (mongokit's `Repository`, prismakit's
|
|
102
|
+
* repo, a custom implementation). Arc calls `repository.create(entry)` to
|
|
103
|
+
* log and `repository.findAll(filter, options)` to query.
|
|
104
|
+
*
|
|
105
|
+
* If neither `repository` nor `customStores` is provided, falls back to
|
|
106
|
+
* `MemoryAuditStore` (intended for dev / tests only).
|
|
107
|
+
*/
|
|
108
|
+
repository?: RepositoryLike;
|
|
109
|
+
/**
|
|
110
|
+
* Custom audit stores — for backends that aren't repositories (Kafka, S3,
|
|
111
|
+
* OpenTelemetry exporter, etc.). Each must implement the `AuditStore`
|
|
112
|
+
* interface. `repository` and `customStores` compose: entries get logged
|
|
113
|
+
* to every store.
|
|
114
|
+
*/
|
|
17
115
|
customStores?: AuditStore[];
|
|
116
|
+
/**
|
|
117
|
+
* Retention policy — optional. Entries older than `maxAgeMs` are purged
|
|
118
|
+
* on a timer (`purgeIntervalMs`, default 24h). Stores that implement
|
|
119
|
+
* `purgeOlderThan` participate; append-only stores are skipped.
|
|
120
|
+
*
|
|
121
|
+
* Apps on MongoDB can instead declare a TTL index on the audit
|
|
122
|
+
* collection's `timestamp` field — server-side TTL is cheaper than a
|
|
123
|
+
* periodic delete. Both approaches coexist: `fastify.audit.purge(...)`
|
|
124
|
+
* is always available for manual / cron-driven purges.
|
|
125
|
+
*
|
|
126
|
+
* Set `purgeIntervalMs: 0` to skip the timer (manual purge only).
|
|
127
|
+
*/
|
|
128
|
+
retention?: {
|
|
129
|
+
/** Max entry age in ms. Entries with `timestamp < now - maxAgeMs` are purged. */maxAgeMs: number; /** Interval between purges in ms. Default 86_400_000 (24h). 0 disables the timer. */
|
|
130
|
+
purgeIntervalMs?: number;
|
|
131
|
+
};
|
|
18
132
|
/**
|
|
19
133
|
* Automatically audit CRUD operations via the hook system (default: true when enabled).
|
|
20
134
|
* When enabled, create/update/delete operations are auto-logged without manual calls.
|
|
@@ -78,10 +192,19 @@ interface AuditLogger {
|
|
|
78
192
|
custom: (resource: string, documentId: string, action: string, data?: Record<string, unknown>, context?: AuditContext) => Promise<void>;
|
|
79
193
|
/** Query audit logs (if stores support it) */
|
|
80
194
|
query: (options: AuditQueryOptions) => Promise<AuditEntry[]>;
|
|
195
|
+
/**
|
|
196
|
+
* Purge audit entries older than `cutoff` across every registered store.
|
|
197
|
+
* Returns the total number of entries deleted. Stores that don't support
|
|
198
|
+
* deletion (append-only emitters) are skipped silently.
|
|
199
|
+
*/
|
|
200
|
+
purge: (cutoff: Date) => Promise<number>;
|
|
81
201
|
}
|
|
82
202
|
declare const auditPlugin: FastifyPluginAsync<AuditPluginOptions>;
|
|
83
203
|
declare const _default: FastifyPluginAsync<AuditPluginOptions>;
|
|
84
204
|
//#endregion
|
|
205
|
+
//#region src/audit/repository-audit-adapter.d.ts
|
|
206
|
+
declare function repositoryAsAuditStore(repository: RepositoryLike): AuditStore;
|
|
207
|
+
//#endregion
|
|
85
208
|
//#region src/audit/stores/memory.d.ts
|
|
86
209
|
interface MemoryAuditStoreOptions {
|
|
87
210
|
/** Maximum entries to keep (default: 1000) */
|
|
@@ -94,6 +217,7 @@ declare class MemoryAuditStore implements AuditStore {
|
|
|
94
217
|
constructor(options?: MemoryAuditStoreOptions);
|
|
95
218
|
log(entry: AuditEntry): Promise<void>;
|
|
96
219
|
query(options?: AuditQueryOptions): Promise<AuditEntry[]>;
|
|
220
|
+
purgeOlderThan(cutoff: Date): Promise<number>;
|
|
97
221
|
close(): Promise<void>;
|
|
98
222
|
/** Get all entries (for testing) */
|
|
99
223
|
getAll(): AuditEntry[];
|
|
@@ -101,4 +225,4 @@ declare class MemoryAuditStore implements AuditStore {
|
|
|
101
225
|
clear(): void;
|
|
102
226
|
}
|
|
103
227
|
//#endregion
|
|
104
|
-
export { type AuditAction, type AuditContext, type AuditEntry, type AuditLogger, type AuditPluginOptions, type AuditQueryOptions, type AuditStore, type AuditStoreOptions, MemoryAuditStore, type MemoryAuditStoreOptions,
|
|
228
|
+
export { type AuditAction, type AuditContext, type AuditEntry, type AuditLogger, type AuditPluginOptions, type AuditQueryOptions, type AuditStore, type AuditStoreOptions, MemoryAuditStore, type MemoryAuditStoreOptions, _default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry, repositoryAsAuditStore };
|
package/dist/audit/index.mjs
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
|
-
import { t as MongoAuditStore } from "../mongodb-Utc5k_-0.mjs";
|
|
2
1
|
import fp from "fastify-plugin";
|
|
2
|
+
//#region src/audit/repository-audit-adapter.ts
|
|
3
|
+
function repositoryAsAuditStore(repository) {
|
|
4
|
+
return {
|
|
5
|
+
name: "repository",
|
|
6
|
+
async log(entry) {
|
|
7
|
+
const doc = {
|
|
8
|
+
_id: entry.id,
|
|
9
|
+
id: entry.id,
|
|
10
|
+
resource: entry.resource,
|
|
11
|
+
documentId: entry.documentId,
|
|
12
|
+
action: entry.action,
|
|
13
|
+
userId: entry.userId,
|
|
14
|
+
organizationId: entry.organizationId,
|
|
15
|
+
before: entry.before,
|
|
16
|
+
after: entry.after,
|
|
17
|
+
changes: entry.changes,
|
|
18
|
+
requestId: entry.requestId,
|
|
19
|
+
ipAddress: entry.ipAddress,
|
|
20
|
+
userAgent: entry.userAgent,
|
|
21
|
+
metadata: entry.metadata,
|
|
22
|
+
timestamp: entry.timestamp
|
|
23
|
+
};
|
|
24
|
+
await repository.create(doc);
|
|
25
|
+
},
|
|
26
|
+
async purgeOlderThan(cutoff) {
|
|
27
|
+
if (!repository.deleteMany) return 0;
|
|
28
|
+
return (await repository.deleteMany({ timestamp: { $lt: cutoff } })).deletedCount ?? 0;
|
|
29
|
+
},
|
|
30
|
+
async query(opts = {}) {
|
|
31
|
+
if (!repository.getAll) throw new Error("auditPlugin: repository.getAll is required for query(). It's on repo-core's MinimalRepo floor — every kit (mongokit, sqlitekit, custom) implements it.");
|
|
32
|
+
const filter = {};
|
|
33
|
+
if (opts.resource) filter.resource = opts.resource;
|
|
34
|
+
if (opts.documentId) filter.documentId = opts.documentId;
|
|
35
|
+
if (opts.userId) filter.userId = opts.userId;
|
|
36
|
+
if (opts.organizationId) filter.organizationId = opts.organizationId;
|
|
37
|
+
if (opts.action) {
|
|
38
|
+
const actions = Array.isArray(opts.action) ? opts.action : [opts.action];
|
|
39
|
+
filter.action = actions.length === 1 ? actions[0] : { $in: actions };
|
|
40
|
+
}
|
|
41
|
+
if (opts.from || opts.to) {
|
|
42
|
+
const range = {};
|
|
43
|
+
if (opts.from) range.$gte = opts.from;
|
|
44
|
+
if (opts.to) range.$lte = opts.to;
|
|
45
|
+
filter.timestamp = range;
|
|
46
|
+
}
|
|
47
|
+
const limit = opts.limit ?? 100;
|
|
48
|
+
const page = Math.floor((opts.offset ?? 0) / limit) + 1;
|
|
49
|
+
const result = await repository.getAll({
|
|
50
|
+
filters: filter,
|
|
51
|
+
sort: { timestamp: -1 },
|
|
52
|
+
page,
|
|
53
|
+
limit
|
|
54
|
+
});
|
|
55
|
+
return (Array.isArray(result) ? result : result.docs ?? []).map((d) => ({
|
|
56
|
+
id: String(d._id ?? d.id ?? ""),
|
|
57
|
+
resource: d.resource ?? "",
|
|
58
|
+
documentId: d.documentId ?? "",
|
|
59
|
+
action: d.action ?? "create",
|
|
60
|
+
userId: d.userId,
|
|
61
|
+
organizationId: d.organizationId,
|
|
62
|
+
before: d.before,
|
|
63
|
+
after: d.after,
|
|
64
|
+
changes: d.changes,
|
|
65
|
+
requestId: d.requestId,
|
|
66
|
+
ipAddress: d.ipAddress,
|
|
67
|
+
userAgent: d.userAgent,
|
|
68
|
+
metadata: d.metadata,
|
|
69
|
+
timestamp: d.timestamp ?? /* @__PURE__ */ new Date()
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
3
75
|
//#region src/audit/stores/interface.ts
|
|
4
76
|
/**
|
|
5
77
|
* Create audit entry from context
|
|
@@ -81,6 +153,11 @@ var MemoryAuditStore = class {
|
|
|
81
153
|
results = results.slice(offset, offset + limit);
|
|
82
154
|
return results;
|
|
83
155
|
}
|
|
156
|
+
async purgeOlderThan(cutoff) {
|
|
157
|
+
const before = this.entries.length;
|
|
158
|
+
this.entries = this.entries.filter((e) => e.timestamp >= cutoff);
|
|
159
|
+
return before - this.entries.length;
|
|
160
|
+
}
|
|
84
161
|
async close() {
|
|
85
162
|
this.entries = [];
|
|
86
163
|
}
|
|
@@ -96,28 +173,17 @@ var MemoryAuditStore = class {
|
|
|
96
173
|
//#endregion
|
|
97
174
|
//#region src/audit/auditPlugin.ts
|
|
98
175
|
const auditPlugin = async (fastify, opts = {}) => {
|
|
99
|
-
const { enabled = false,
|
|
176
|
+
const { enabled = false, repository, customStores = [] } = opts;
|
|
100
177
|
if (!enabled) {
|
|
101
178
|
fastify.decorate("audit", createNoopLogger());
|
|
102
179
|
fastify.decorateRequest("auditContext", void 0);
|
|
103
180
|
fastify.log?.debug?.("Audit plugin disabled");
|
|
104
181
|
return;
|
|
105
182
|
}
|
|
106
|
-
const stores = [
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
break;
|
|
111
|
-
case "mongodb":
|
|
112
|
-
if (!mongoConnection) throw new Error("Audit: mongoConnection required for mongodb store");
|
|
113
|
-
stores.push(new MongoAuditStore({
|
|
114
|
-
connection: mongoConnection,
|
|
115
|
-
collection: mongoCollection,
|
|
116
|
-
ttlDays
|
|
117
|
-
}));
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
if (stores.length === 0) throw new Error("Audit: at least one store must be configured");
|
|
183
|
+
const stores = [];
|
|
184
|
+
if (repository) stores.push(repositoryAsAuditStore(repository));
|
|
185
|
+
stores.push(...customStores);
|
|
186
|
+
if (stores.length === 0) stores.push(new MemoryAuditStore());
|
|
121
187
|
async function logToStores(entry) {
|
|
122
188
|
await Promise.all(stores.map((store) => store.log(entry)));
|
|
123
189
|
}
|
|
@@ -146,6 +212,11 @@ const auditPlugin = async (fastify, opts = {}) => {
|
|
|
146
212
|
async query(options) {
|
|
147
213
|
for (const store of stores) if (store.query) return store.query(options);
|
|
148
214
|
return [];
|
|
215
|
+
},
|
|
216
|
+
async purge(cutoff) {
|
|
217
|
+
let total = 0;
|
|
218
|
+
for (const store of stores) if (store.purgeOlderThan) total += await store.purgeOlderThan(cutoff);
|
|
219
|
+
return total;
|
|
149
220
|
}
|
|
150
221
|
};
|
|
151
222
|
fastify.decorate("audit", audit);
|
|
@@ -170,7 +241,22 @@ const auditPlugin = async (fastify, opts = {}) => {
|
|
|
170
241
|
request.auditContext.duration = Math.round(reply.elapsedTime);
|
|
171
242
|
}
|
|
172
243
|
});
|
|
244
|
+
const retention = opts.retention;
|
|
245
|
+
let retentionTimer = null;
|
|
246
|
+
if (retention) {
|
|
247
|
+
const interval = retention.purgeIntervalMs ?? 864e5;
|
|
248
|
+
if (interval > 0) {
|
|
249
|
+
retentionTimer = setInterval(() => {
|
|
250
|
+
const cutoff = new Date(Date.now() - retention.maxAgeMs);
|
|
251
|
+
audit.purge(cutoff).catch((err) => {
|
|
252
|
+
fastify.log?.warn?.({ err }, "audit retention purge failed");
|
|
253
|
+
});
|
|
254
|
+
}, interval);
|
|
255
|
+
retentionTimer.unref?.();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
173
258
|
fastify.addHook("onClose", async () => {
|
|
259
|
+
if (retentionTimer) clearInterval(retentionTimer);
|
|
174
260
|
await Promise.all(stores.map((store) => store.close?.()));
|
|
175
261
|
});
|
|
176
262
|
const autoAuditConfig = opts.autoAudit ?? true;
|
|
@@ -232,7 +318,7 @@ const auditPlugin = async (fastify, opts = {}) => {
|
|
|
232
318
|
}, "Auto-audit hooks registered");
|
|
233
319
|
});
|
|
234
320
|
}
|
|
235
|
-
fastify.log?.debug?.({ stores:
|
|
321
|
+
fastify.log?.debug?.({ stores: stores.map((s) => s.name) }, "Audit plugin enabled");
|
|
236
322
|
};
|
|
237
323
|
/** Extract document ID from a result */
|
|
238
324
|
function autoAuditExtractId(doc) {
|
|
@@ -264,7 +350,8 @@ function createNoopLogger() {
|
|
|
264
350
|
delete: noop,
|
|
265
351
|
restore: noop,
|
|
266
352
|
custom: noop,
|
|
267
|
-
query: async () => []
|
|
353
|
+
query: async () => [],
|
|
354
|
+
purge: async () => 0
|
|
268
355
|
};
|
|
269
356
|
}
|
|
270
357
|
var auditPlugin_default = fp(auditPlugin, {
|
|
@@ -273,4 +360,4 @@ var auditPlugin_default = fp(auditPlugin, {
|
|
|
273
360
|
dependencies: ["arc-core"]
|
|
274
361
|
});
|
|
275
362
|
//#endregion
|
|
276
|
-
export { MemoryAuditStore, auditPlugin_default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry };
|
|
363
|
+
export { MemoryAuditStore, auditPlugin_default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry, repositoryAsAuditStore };
|
package/dist/auth/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { t as ExternalOpenApiPaths } from "../externalPaths-
|
|
4
|
-
import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-
|
|
5
|
-
import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest
|
|
1
|
+
import { c as PermissionCheck } from "../fields-Lo1VUDpt.mjs";
|
|
2
|
+
import { A as AuthHelpers, j as AuthPluginOptions } from "../index-Cl0uoKd5.mjs";
|
|
3
|
+
import { t as ExternalOpenApiPaths } from "../externalPaths-BQ8QijNH.mjs";
|
|
4
|
+
import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-BkzVU8h2.mjs";
|
|
5
|
+
import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest } from "fastify";
|
|
6
6
|
|
|
7
7
|
//#region src/auth/authPlugin.d.ts
|
|
8
8
|
declare module "fastify" {
|
|
@@ -17,6 +17,14 @@ declare module "fastify" {
|
|
|
17
17
|
auth: AuthHelpers;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Extract Bearer token from Authorization header.
|
|
22
|
+
*
|
|
23
|
+
* Exported for property-based test coverage — the contract is:
|
|
24
|
+
* - header must start with exactly `"Bearer "` (case-sensitive, one space)
|
|
25
|
+
* - everything after that prefix is returned verbatim (no trim, no parse)
|
|
26
|
+
* - missing header → `null`; any other shape → `null`
|
|
27
|
+
*/
|
|
20
28
|
declare const authPlugin: FastifyPluginAsync<AuthPluginOptions>;
|
|
21
29
|
declare const _default: FastifyPluginAsync<AuthPluginOptions>;
|
|
22
30
|
//#endregion
|
|
@@ -90,9 +98,9 @@ interface BetterAuthAdapterResult {
|
|
|
90
98
|
/** Fastify plugin that registers catch-all auth routes */
|
|
91
99
|
plugin: FastifyPluginAsync;
|
|
92
100
|
/** Authenticate preHandler -- validates session via Better Auth */
|
|
93
|
-
authenticate: (request: FastifyRequest
|
|
101
|
+
authenticate: (request: FastifyRequest, reply: FastifyReply$1) => Promise<void>;
|
|
94
102
|
/** Optional authenticate -- resolves session silently, continues as unauthenticated on failure */
|
|
95
|
-
optionalAuthenticate: (request: FastifyRequest
|
|
103
|
+
optionalAuthenticate: (request: FastifyRequest, reply: FastifyReply$1) => Promise<void>;
|
|
96
104
|
/** Permission helpers bound to this auth adapter (available when orgContext is enabled) */
|
|
97
105
|
permissions: {
|
|
98
106
|
requireOrgRole: (...roles: string[]) => PermissionCheck;
|
|
@@ -109,7 +117,7 @@ declare module "fastify" {
|
|
|
109
117
|
* Validates session by calling Better Auth's session endpoint internally.
|
|
110
118
|
* Set by the Better Auth adapter plugin.
|
|
111
119
|
*/
|
|
112
|
-
authenticate: (request: FastifyRequest
|
|
120
|
+
authenticate: (request: FastifyRequest, reply: FastifyReply$1) => Promise<void>;
|
|
113
121
|
/**
|
|
114
122
|
* Optional authenticate middleware (Better Auth variant).
|
|
115
123
|
* Tries to resolve session silently — populates request.user if valid,
|
|
@@ -117,7 +125,7 @@ declare module "fastify" {
|
|
|
117
125
|
* Used on allowPublic() routes so downstream middleware can apply
|
|
118
126
|
* org-scoped queries when a user IS authenticated.
|
|
119
127
|
*/
|
|
120
|
-
optionalAuthenticate: (request: FastifyRequest
|
|
128
|
+
optionalAuthenticate: (request: FastifyRequest, reply: FastifyReply$1) => Promise<void>;
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
131
|
/**
|
package/dist/auth/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as normalizeRoles, t as getUserRoles } from "../types-
|
|
2
|
-
import { t as ArcError } from "../errors-
|
|
3
|
-
import {
|
|
4
|
-
import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi-
|
|
1
|
+
import { n as normalizeRoles, t as getUserRoles } from "../types-DV9WDfeg.mjs";
|
|
2
|
+
import { t as ArcError } from "../errors-D5c-5BJL.mjs";
|
|
3
|
+
import { _ as requireTeamMembership, m as requireOrgRole, p as requireOrgMembership } from "../permissions-Dk6mshja.mjs";
|
|
4
|
+
import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi-BBRVhjQN.mjs";
|
|
5
5
|
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
6
6
|
import fp from "fastify-plugin";
|
|
7
7
|
//#region src/auth/authPlugin.ts
|
|
@@ -21,7 +21,12 @@ function parseExpiresIn(input, defaultValue) {
|
|
|
21
21
|
}[match[2]?.toLowerCase() ?? "s"] ?? 1);
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Extract Bearer token from Authorization header
|
|
24
|
+
* Extract Bearer token from Authorization header.
|
|
25
|
+
*
|
|
26
|
+
* Exported for property-based test coverage — the contract is:
|
|
27
|
+
* - header must start with exactly `"Bearer "` (case-sensitive, one space)
|
|
28
|
+
* - everything after that prefix is returned verbatim (no trim, no parse)
|
|
29
|
+
* - missing header → `null`; any other shape → `null`
|
|
25
30
|
*/
|
|
26
31
|
function extractBearerToken(request) {
|
|
27
32
|
const auth = request.headers.authorization;
|
|
@@ -29,7 +34,7 @@ function extractBearerToken(request) {
|
|
|
29
34
|
return auth.slice(7);
|
|
30
35
|
}
|
|
31
36
|
const authPlugin = async (fastify, opts = {}) => {
|
|
32
|
-
const { jwt: jwtConfig, authenticate: appAuthenticator, onFailure, userProperty = "user", exposeAuthErrors = false, tokenExtractor, isRevoked } = opts;
|
|
37
|
+
const { jwt: jwtConfig, authenticate: appAuthenticator, onFailure, userProperty = "user", exposeAuthErrors = false, tokenExtractor, isRevoked, strictTokenType = true } = opts;
|
|
33
38
|
/** Extract token from request — uses custom extractor if provided, else Bearer header */
|
|
34
39
|
const resolveToken = (request) => {
|
|
35
40
|
if (tokenExtractor) return tokenExtractor(request);
|
|
@@ -84,6 +89,7 @@ const authPlugin = async (fastify, opts = {}) => {
|
|
|
84
89
|
if (token) {
|
|
85
90
|
const decoded = jwtContext.verify(token);
|
|
86
91
|
if (decoded.type === "refresh") throw new Error("Refresh tokens cannot be used for authentication");
|
|
92
|
+
if (strictTokenType && decoded.type !== "access") throw new Error("Invalid token type: expected access token");
|
|
87
93
|
user = decoded;
|
|
88
94
|
}
|
|
89
95
|
} else throw new Error("No authenticator configured. Provide auth.authenticate function or auth.jwt.secret.");
|
|
@@ -146,6 +152,7 @@ const authPlugin = async (fastify, opts = {}) => {
|
|
|
146
152
|
if (token) {
|
|
147
153
|
const decoded = jwtContext.verify(token);
|
|
148
154
|
if (decoded.type === "refresh") return;
|
|
155
|
+
if (strictTokenType && decoded.type !== "access") return;
|
|
149
156
|
user = decoded;
|
|
150
157
|
}
|
|
151
158
|
}
|
|
@@ -677,7 +684,7 @@ function createBetterAuthAdapter(options) {
|
|
|
677
684
|
if (!fastify.hasDecorator("authenticate")) fastify.decorate("authenticate", authenticate);
|
|
678
685
|
if (!fastify.hasDecorator("optionalAuthenticate")) fastify.decorate("optionalAuthenticate", optionalAuthenticate);
|
|
679
686
|
if (!extractedOpenApi && openapiOpt !== false && auth.api && typeof auth.api === "object") {
|
|
680
|
-
const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi-
|
|
687
|
+
const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi-BBRVhjQN.mjs").then((n) => n.t);
|
|
681
688
|
extractedOpenApi = extractBetterAuthOpenApi(auth.api, {
|
|
682
689
|
basePath,
|
|
683
690
|
userFields
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as SessionData, s as SessionStore } from "../sessionManager-
|
|
1
|
+
import { i as SessionData, s as SessionStore } from "../sessionManager-BkzVU8h2.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/auth/redis-session.d.ts
|
|
4
4
|
/** Minimal Redis client interface — compatible with ioredis */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import { a as toJsonSchema } from "./schemaConverter-
|
|
2
|
+
import { a as toJsonSchema } from "./schemaConverter-BxFDdtXu.mjs";
|
|
3
3
|
//#region src/auth/betterAuthOpenApi.ts
|
|
4
4
|
var betterAuthOpenApi_exports = /* @__PURE__ */ __exportAll({ extractBetterAuthOpenApi: () => extractBetterAuthOpenApi });
|
|
5
5
|
/**
|
package/dist/cache/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-
|
|
1
|
+
import { n as CacheStats, r as CacheStore, t as CacheLogger } from "../interface-D218ikEo.mjs";
|
|
2
|
+
import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-BKbWjgDG.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/cache/keys.d.ts
|
|
5
5
|
/**
|
|
@@ -24,8 +24,8 @@ declare function hashParams(params: Record<string, unknown>): string;
|
|
|
24
24
|
//#endregion
|
|
25
25
|
//#region src/cache/memory.d.ts
|
|
26
26
|
interface MemoryCacheStoreOptions {
|
|
27
|
-
/** Default TTL in
|
|
28
|
-
|
|
27
|
+
/** Default TTL in seconds (default: 60) */
|
|
28
|
+
defaultTtlSeconds?: number;
|
|
29
29
|
/** Hard upper bound for entries (default: 1000) */
|
|
30
30
|
maxEntries?: number;
|
|
31
31
|
/** Background cleanup interval in milliseconds (default: 30_000) */
|
|
@@ -59,7 +59,7 @@ interface MemoryCacheStoreOptions {
|
|
|
59
59
|
declare class MemoryCacheStore<TValue = unknown> implements CacheStore<TValue> {
|
|
60
60
|
readonly name = "memory-cache";
|
|
61
61
|
private readonly cache;
|
|
62
|
-
private readonly
|
|
62
|
+
private readonly defaultTtlSeconds;
|
|
63
63
|
private readonly maxEntries;
|
|
64
64
|
private readonly maxEntryBytes;
|
|
65
65
|
private readonly maxMemoryBytes;
|
|
@@ -72,9 +72,9 @@ declare class MemoryCacheStore<TValue = unknown> implements CacheStore<TValue> {
|
|
|
72
72
|
private _evictions;
|
|
73
73
|
constructor(options?: MemoryCacheStoreOptions);
|
|
74
74
|
get(key: string): Promise<TValue | undefined>;
|
|
75
|
-
set(key: string, value: TValue,
|
|
75
|
+
set(key: string, value: TValue, ttlSeconds?: number): Promise<void>;
|
|
76
76
|
delete(key: string): Promise<void>;
|
|
77
|
-
clear(): Promise<void>;
|
|
77
|
+
clear(pattern?: string): Promise<void>;
|
|
78
78
|
close(): Promise<void>;
|
|
79
79
|
stats(): CacheStats;
|
|
80
80
|
private removeEntry;
|
|
@@ -112,8 +112,8 @@ interface RedisCacheStoreOptions {
|
|
|
112
112
|
client: RedisCacheClient;
|
|
113
113
|
/** Key prefix for namespacing (default: 'arc:cache:') */
|
|
114
114
|
prefix?: string;
|
|
115
|
-
/** Default TTL in
|
|
116
|
-
|
|
115
|
+
/** Default TTL in seconds (default: 60) */
|
|
116
|
+
defaultTtlSeconds?: number;
|
|
117
117
|
/** Maximum serialized entry size in bytes. Oversized entries are skipped. */
|
|
118
118
|
maxEntryBytes?: number;
|
|
119
119
|
}
|
|
@@ -126,17 +126,19 @@ declare class RedisCacheStore<TValue = unknown> implements CacheStore<TValue> {
|
|
|
126
126
|
readonly name = "redis-cache";
|
|
127
127
|
private readonly client;
|
|
128
128
|
private readonly prefix;
|
|
129
|
-
private readonly
|
|
129
|
+
private readonly defaultTtlSeconds;
|
|
130
130
|
private readonly maxEntryBytes;
|
|
131
131
|
private _hits;
|
|
132
132
|
private _misses;
|
|
133
133
|
constructor(options: RedisCacheStoreOptions);
|
|
134
134
|
get(key: string): Promise<TValue | undefined>;
|
|
135
|
-
set(key: string, value: TValue,
|
|
135
|
+
set(key: string, value: TValue, ttlSeconds?: number): Promise<void>;
|
|
136
136
|
delete(key: string): Promise<void>;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Invalidate keys. Pass a glob pattern to delete a subset (`user:*:v2`);
|
|
139
|
+
* omit to clear every key under this store's prefix.
|
|
140
|
+
*/
|
|
141
|
+
clear(pattern?: string): Promise<void>;
|
|
140
142
|
stats(): CacheStats;
|
|
141
143
|
private scanAndDelete;
|
|
142
144
|
private withPrefix;
|
|
@@ -212,4 +214,4 @@ interface UpstashRedisLike {
|
|
|
212
214
|
*/
|
|
213
215
|
declare function upstashAsCacheClient(client: UpstashRedisLike): RedisCacheClient;
|
|
214
216
|
//#endregion
|
|
215
|
-
export { type CacheEnvelope, type CacheLogger, type CacheResult, type
|
|
217
|
+
export { type CacheEnvelope, type CacheLogger, type CacheResult, type CacheStats, type CacheStatus, type CacheStore, type CrossResourceRule, type IoredisLike, MemoryCacheStore, type MemoryCacheStoreOptions, QueryCache, type QueryCacheConfig, type QueryCacheDefaults, type QueryCachePluginOptions, type RedisCacheClient, RedisCacheStore, type RedisCacheStoreOptions, type RedisPipeline, type UpstashRedisLike, buildQueryKey, hashParams, ioredisAsCacheClient, queryCachePlugin, tagVersionKey, upstashAsCacheClient, versionKey };
|
package/dist/cache/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-qcD-TVJl.mjs";
|
|
2
|
-
import { t as MemoryCacheStore } from "../memory-
|
|
3
|
-
import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-
|
|
2
|
+
import { t as MemoryCacheStore } from "../memory-B5Amv9A1.mjs";
|
|
3
|
+
import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-DQCEfJis.mjs";
|
|
4
4
|
//#region src/cache/redis.ts
|
|
5
5
|
/**
|
|
6
6
|
* Redis-backed cache store.
|
|
@@ -11,14 +11,14 @@ var RedisCacheStore = class {
|
|
|
11
11
|
name = "redis-cache";
|
|
12
12
|
client;
|
|
13
13
|
prefix;
|
|
14
|
-
|
|
14
|
+
defaultTtlSeconds;
|
|
15
15
|
maxEntryBytes;
|
|
16
16
|
_hits = 0;
|
|
17
17
|
_misses = 0;
|
|
18
18
|
constructor(options) {
|
|
19
19
|
this.client = options.client;
|
|
20
20
|
this.prefix = options.prefix ?? "arc:cache:";
|
|
21
|
-
this.
|
|
21
|
+
this.defaultTtlSeconds = options.defaultTtlSeconds ?? 60;
|
|
22
22
|
this.maxEntryBytes = options.maxEntryBytes ?? 0;
|
|
23
23
|
}
|
|
24
24
|
async get(key) {
|
|
@@ -36,22 +36,23 @@ var RedisCacheStore = class {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
async set(key, value,
|
|
40
|
-
const
|
|
41
|
-
if (!Number.isFinite(
|
|
39
|
+
async set(key, value, ttlSeconds) {
|
|
40
|
+
const effectiveTtlSeconds = ttlSeconds ?? this.defaultTtlSeconds;
|
|
41
|
+
if (!Number.isFinite(effectiveTtlSeconds) || effectiveTtlSeconds <= 0) return;
|
|
42
42
|
const payload = JSON.stringify(value);
|
|
43
43
|
if (this.maxEntryBytes > 0 && Buffer.byteLength(payload, "utf8") > this.maxEntryBytes) return;
|
|
44
|
-
await this.client.set(this.withPrefix(key), payload, {
|
|
44
|
+
await this.client.set(this.withPrefix(key), payload, { EX: Math.ceil(effectiveTtlSeconds) });
|
|
45
45
|
}
|
|
46
46
|
async delete(key) {
|
|
47
47
|
await this.client.del(this.withPrefix(key));
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
async
|
|
54
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Invalidate keys. Pass a glob pattern to delete a subset (`user:*:v2`);
|
|
51
|
+
* omit to clear every key under this store's prefix.
|
|
52
|
+
*/
|
|
53
|
+
async clear(pattern) {
|
|
54
|
+
const scanPattern = pattern ? `${this.prefix}${pattern.includes("*") ? pattern : `${pattern}*`}` : `${this.prefix}*`;
|
|
55
|
+
await this.scanAndDelete(scanPattern);
|
|
55
56
|
}
|
|
56
57
|
stats() {
|
|
57
58
|
return {
|
|
@@ -34,7 +34,7 @@ const cachingPlugin = async (fastify, opts = {}) => {
|
|
|
34
34
|
if (rule?.staleWhileRevalidate) parts.push(`stale-while-revalidate=${rule.staleWhileRevalidate}`);
|
|
35
35
|
return parts.join(", ");
|
|
36
36
|
}
|
|
37
|
-
fastify.addHook("
|
|
37
|
+
fastify.addHook("preSerialization", async (request, reply, payload) => {
|
|
38
38
|
const url = request.url;
|
|
39
39
|
if (exclude.some((p) => url.startsWith(p))) return payload;
|
|
40
40
|
const method = request.method.toUpperCase();
|
|
@@ -48,13 +48,18 @@ const cachingPlugin = async (fastify, opts = {}) => {
|
|
|
48
48
|
const rule = findRule(url);
|
|
49
49
|
reply.header("cache-control", buildCacheControl(rule));
|
|
50
50
|
}
|
|
51
|
-
if (etag && payload) {
|
|
52
|
-
|
|
51
|
+
if (etag && payload != null) {
|
|
52
|
+
let body;
|
|
53
|
+
if (typeof payload === "string") body = payload;
|
|
54
|
+
else if (Buffer.isBuffer(payload)) body = payload.toString("utf-8");
|
|
55
|
+
else body = JSON.stringify(payload);
|
|
56
|
+
const tag = `"${fnv1a(body)}"`;
|
|
53
57
|
reply.header("etag", tag);
|
|
54
58
|
if (conditional) {
|
|
55
59
|
const ifNoneMatch = request.headers["if-none-match"];
|
|
56
60
|
if (ifNoneMatch && ifNoneMatch === tag) {
|
|
57
61
|
reply.code(304);
|
|
62
|
+
reply.serializer((p) => typeof p === "string" ? p : "");
|
|
58
63
|
return "";
|
|
59
64
|
}
|
|
60
65
|
}
|