@classytic/arc 1.0.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/LICENSE +21 -0
- package/README.md +900 -0
- package/bin/arc.js +344 -0
- package/dist/adapters/index.d.ts +237 -0
- package/dist/adapters/index.js +668 -0
- package/dist/arcCorePlugin-DTPWXcZN.d.ts +273 -0
- package/dist/audit/index.d.ts +195 -0
- package/dist/audit/index.js +319 -0
- package/dist/auth/index.d.ts +47 -0
- package/dist/auth/index.js +174 -0
- package/dist/cli/commands/docs.d.ts +11 -0
- package/dist/cli/commands/docs.js +474 -0
- package/dist/cli/commands/introspect.d.ts +8 -0
- package/dist/cli/commands/introspect.js +338 -0
- package/dist/cli/index.d.ts +43 -0
- package/dist/cli/index.js +520 -0
- package/dist/createApp-pzUAkzbz.d.ts +77 -0
- package/dist/docs/index.d.ts +166 -0
- package/dist/docs/index.js +650 -0
- package/dist/errors-8WIxGS_6.d.ts +122 -0
- package/dist/events/index.d.ts +117 -0
- package/dist/events/index.js +89 -0
- package/dist/factory/index.d.ts +38 -0
- package/dist/factory/index.js +1664 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +199 -0
- package/dist/idempotency/index.d.ts +323 -0
- package/dist/idempotency/index.js +500 -0
- package/dist/index-DkAW8BXh.d.ts +1302 -0
- package/dist/index.d.ts +331 -0
- package/dist/index.js +4734 -0
- package/dist/migrations/index.d.ts +185 -0
- package/dist/migrations/index.js +274 -0
- package/dist/org/index.d.ts +129 -0
- package/dist/org/index.js +220 -0
- package/dist/permissions/index.d.ts +144 -0
- package/dist/permissions/index.js +100 -0
- package/dist/plugins/index.d.ts +46 -0
- package/dist/plugins/index.js +1069 -0
- package/dist/policies/index.d.ts +398 -0
- package/dist/policies/index.js +196 -0
- package/dist/presets/index.d.ts +336 -0
- package/dist/presets/index.js +382 -0
- package/dist/presets/multiTenant.d.ts +39 -0
- package/dist/presets/multiTenant.js +112 -0
- package/dist/registry/index.d.ts +16 -0
- package/dist/registry/index.js +253 -0
- package/dist/testing/index.d.ts +618 -0
- package/dist/testing/index.js +48032 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +8 -0
- package/dist/types-0IPhH_NR.d.ts +143 -0
- package/dist/types-B99TBmFV.d.ts +76 -0
- package/dist/utils/index.d.ts +655 -0
- package/dist/utils/index.js +905 -0
- package/package.json +227 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ac as HookContext, ad as HookHandler, aH as HookOperation, aG as HookPhase, aI as HookRegistration, H as HookSystem, aJ as HookSystemOptions, aB as afterCreate, aF as afterDelete, aD as afterUpdate, aA as beforeCreate, aE as beforeDelete, aC as beforeUpdate, az as createHookSystem, ab as hookSystem } from '../index-DkAW8BXh.js';
|
|
2
|
+
import 'mongoose';
|
|
3
|
+
import 'fastify';
|
|
4
|
+
import '../types-B99TBmFV.js';
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// src/hooks/HookSystem.ts
|
|
2
|
+
var HookSystem = class {
|
|
3
|
+
hooks;
|
|
4
|
+
logger;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.hooks = /* @__PURE__ */ new Map();
|
|
7
|
+
this.logger = options?.logger ?? { error: (...args) => console.error(...args) };
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Generate hook key
|
|
11
|
+
*/
|
|
12
|
+
getKey(resource, operation, phase) {
|
|
13
|
+
return `${resource}:${operation}:${phase}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Register a hook
|
|
17
|
+
* Supports both object parameter and positional arguments
|
|
18
|
+
*/
|
|
19
|
+
register(resourceOrOptions, operation, phase, handler, priority = 10) {
|
|
20
|
+
let resource;
|
|
21
|
+
let finalOperation;
|
|
22
|
+
let finalPhase;
|
|
23
|
+
let finalHandler;
|
|
24
|
+
let finalPriority;
|
|
25
|
+
if (typeof resourceOrOptions === "object") {
|
|
26
|
+
resource = resourceOrOptions.resource;
|
|
27
|
+
finalOperation = resourceOrOptions.operation;
|
|
28
|
+
finalPhase = resourceOrOptions.phase;
|
|
29
|
+
finalHandler = resourceOrOptions.handler;
|
|
30
|
+
finalPriority = resourceOrOptions.priority ?? 10;
|
|
31
|
+
} else {
|
|
32
|
+
resource = resourceOrOptions;
|
|
33
|
+
finalOperation = operation;
|
|
34
|
+
finalPhase = phase;
|
|
35
|
+
finalHandler = handler;
|
|
36
|
+
finalPriority = priority;
|
|
37
|
+
}
|
|
38
|
+
const key = this.getKey(resource, finalOperation, finalPhase);
|
|
39
|
+
if (!this.hooks.has(key)) {
|
|
40
|
+
this.hooks.set(key, []);
|
|
41
|
+
}
|
|
42
|
+
const registration = {
|
|
43
|
+
resource,
|
|
44
|
+
operation: finalOperation,
|
|
45
|
+
phase: finalPhase,
|
|
46
|
+
handler: finalHandler,
|
|
47
|
+
priority: finalPriority
|
|
48
|
+
};
|
|
49
|
+
const hooks = this.hooks.get(key);
|
|
50
|
+
hooks.push(registration);
|
|
51
|
+
hooks.sort((a, b) => a.priority - b.priority);
|
|
52
|
+
return () => {
|
|
53
|
+
const idx = hooks.indexOf(registration);
|
|
54
|
+
if (idx !== -1) {
|
|
55
|
+
hooks.splice(idx, 1);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Register before hook
|
|
61
|
+
*/
|
|
62
|
+
before(resource, operation, handler, priority = 10) {
|
|
63
|
+
return this.register(resource, operation, "before", handler, priority);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Register after hook
|
|
67
|
+
*/
|
|
68
|
+
after(resource, operation, handler, priority = 10) {
|
|
69
|
+
return this.register(resource, operation, "after", handler, priority);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Execute hooks for a given context
|
|
73
|
+
*/
|
|
74
|
+
async execute(ctx) {
|
|
75
|
+
const key = this.getKey(ctx.resource, ctx.operation, ctx.phase);
|
|
76
|
+
const hooks = this.hooks.get(key) ?? [];
|
|
77
|
+
const wildcardKey = this.getKey("*", ctx.operation, ctx.phase);
|
|
78
|
+
const wildcardHooks = this.hooks.get(wildcardKey) ?? [];
|
|
79
|
+
const allHooks = [...wildcardHooks, ...hooks];
|
|
80
|
+
allHooks.sort((a, b) => a.priority - b.priority);
|
|
81
|
+
let result = ctx.data;
|
|
82
|
+
for (const hook of allHooks) {
|
|
83
|
+
const handlerContext = {
|
|
84
|
+
resource: ctx.resource,
|
|
85
|
+
operation: ctx.operation,
|
|
86
|
+
phase: ctx.phase,
|
|
87
|
+
data: result,
|
|
88
|
+
result: ctx.result,
|
|
89
|
+
user: ctx.user,
|
|
90
|
+
context: ctx.context,
|
|
91
|
+
meta: ctx.meta
|
|
92
|
+
};
|
|
93
|
+
const hookResult = await hook.handler(handlerContext);
|
|
94
|
+
if (hookResult !== void 0 && hookResult !== null) {
|
|
95
|
+
result = hookResult;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Execute before hooks
|
|
102
|
+
*/
|
|
103
|
+
async executeBefore(resource, operation, data, options) {
|
|
104
|
+
const result = await this.execute({
|
|
105
|
+
resource,
|
|
106
|
+
operation,
|
|
107
|
+
phase: "before",
|
|
108
|
+
data,
|
|
109
|
+
user: options?.user,
|
|
110
|
+
context: options?.context,
|
|
111
|
+
meta: options?.meta
|
|
112
|
+
});
|
|
113
|
+
return result ?? data;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Execute after hooks
|
|
117
|
+
* Errors in after hooks are logged but don't fail the request
|
|
118
|
+
*/
|
|
119
|
+
async executeAfter(resource, operation, result, options) {
|
|
120
|
+
try {
|
|
121
|
+
await this.execute({
|
|
122
|
+
resource,
|
|
123
|
+
operation,
|
|
124
|
+
phase: "after",
|
|
125
|
+
result,
|
|
126
|
+
user: options?.user,
|
|
127
|
+
context: options?.context,
|
|
128
|
+
meta: options?.meta
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
this.logger.error(
|
|
132
|
+
`[HookSystem] Error in after hook for ${resource}:${operation}:`,
|
|
133
|
+
error
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get all registered hooks
|
|
139
|
+
*/
|
|
140
|
+
getAll() {
|
|
141
|
+
const all = [];
|
|
142
|
+
for (const hooks of this.hooks.values()) {
|
|
143
|
+
all.push(...hooks);
|
|
144
|
+
}
|
|
145
|
+
return all;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get hooks for a specific resource
|
|
149
|
+
*/
|
|
150
|
+
getForResource(resource) {
|
|
151
|
+
const all = [];
|
|
152
|
+
for (const [key, hooks] of this.hooks.entries()) {
|
|
153
|
+
if (key.startsWith(`${resource}:`)) {
|
|
154
|
+
all.push(...hooks);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return all;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Clear all hooks
|
|
161
|
+
*/
|
|
162
|
+
clear() {
|
|
163
|
+
this.hooks.clear();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Clear hooks for a specific resource
|
|
167
|
+
*/
|
|
168
|
+
clearResource(resource) {
|
|
169
|
+
for (const key of this.hooks.keys()) {
|
|
170
|
+
if (key.startsWith(`${resource}:`)) {
|
|
171
|
+
this.hooks.delete(key);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
function createHookSystem(options) {
|
|
177
|
+
return new HookSystem(options);
|
|
178
|
+
}
|
|
179
|
+
var hookSystem = new HookSystem();
|
|
180
|
+
function beforeCreate(resource, handler, priority = 10) {
|
|
181
|
+
return hookSystem.before(resource, "create", handler, priority);
|
|
182
|
+
}
|
|
183
|
+
function afterCreate(resource, handler, priority = 10) {
|
|
184
|
+
return hookSystem.after(resource, "create", handler, priority);
|
|
185
|
+
}
|
|
186
|
+
function beforeUpdate(resource, handler, priority = 10) {
|
|
187
|
+
return hookSystem.before(resource, "update", handler, priority);
|
|
188
|
+
}
|
|
189
|
+
function afterUpdate(resource, handler, priority = 10) {
|
|
190
|
+
return hookSystem.after(resource, "update", handler, priority);
|
|
191
|
+
}
|
|
192
|
+
function beforeDelete(resource, handler, priority = 10) {
|
|
193
|
+
return hookSystem.before(resource, "delete", handler, priority);
|
|
194
|
+
}
|
|
195
|
+
function afterDelete(resource, handler, priority = 10) {
|
|
196
|
+
return hookSystem.after(resource, "delete", handler, priority);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { HookSystem, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, hookSystem };
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { FastifyPluginAsync } from 'fastify';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Idempotency Store Interface
|
|
5
|
+
*
|
|
6
|
+
* Defines the contract for idempotency key storage backends.
|
|
7
|
+
* Implement this interface for custom stores (Redis, DynamoDB, etc.)
|
|
8
|
+
*/
|
|
9
|
+
interface IdempotencyResult {
|
|
10
|
+
/** The idempotency key */
|
|
11
|
+
key: string;
|
|
12
|
+
/** HTTP status code of the cached response */
|
|
13
|
+
statusCode: number;
|
|
14
|
+
/** Response headers to replay */
|
|
15
|
+
headers: Record<string, string>;
|
|
16
|
+
/** Response body */
|
|
17
|
+
body: unknown;
|
|
18
|
+
/** When this entry was created */
|
|
19
|
+
createdAt: Date;
|
|
20
|
+
/** When this entry expires */
|
|
21
|
+
expiresAt: Date;
|
|
22
|
+
}
|
|
23
|
+
interface IdempotencyLock {
|
|
24
|
+
/** The idempotency key being locked */
|
|
25
|
+
key: string;
|
|
26
|
+
/** Request ID that holds the lock */
|
|
27
|
+
requestId: string;
|
|
28
|
+
/** When the lock was acquired */
|
|
29
|
+
lockedAt: Date;
|
|
30
|
+
/** When the lock expires (auto-release) */
|
|
31
|
+
expiresAt: Date;
|
|
32
|
+
}
|
|
33
|
+
interface IdempotencyStore {
|
|
34
|
+
/** Store name for logging */
|
|
35
|
+
readonly name: string;
|
|
36
|
+
/**
|
|
37
|
+
* Get a cached result for an idempotency key
|
|
38
|
+
* Returns undefined if not found or expired
|
|
39
|
+
*/
|
|
40
|
+
get(key: string): Promise<IdempotencyResult | undefined>;
|
|
41
|
+
/**
|
|
42
|
+
* Store a result for an idempotency key
|
|
43
|
+
* TTL is handled by the store implementation
|
|
44
|
+
*/
|
|
45
|
+
set(key: string, result: Omit<IdempotencyResult, 'key'>): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Try to acquire a lock for processing a key
|
|
48
|
+
* Returns true if lock acquired, false if already locked
|
|
49
|
+
* Used to prevent concurrent processing of the same key
|
|
50
|
+
*/
|
|
51
|
+
tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
|
|
52
|
+
/**
|
|
53
|
+
* Release a lock after processing complete
|
|
54
|
+
*/
|
|
55
|
+
unlock(key: string, requestId: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Check if a key is currently locked
|
|
58
|
+
*/
|
|
59
|
+
isLocked(key: string): Promise<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* Delete a cached result (for manual invalidation)
|
|
62
|
+
*/
|
|
63
|
+
delete(key: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Close the store (cleanup connections)
|
|
66
|
+
*/
|
|
67
|
+
close?(): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Helper to create a result object
|
|
71
|
+
*/
|
|
72
|
+
declare function createIdempotencyResult(statusCode: number, body: unknown, headers: Record<string, string>, ttlMs: number): Omit<IdempotencyResult, 'key'>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Idempotency Plugin
|
|
76
|
+
*
|
|
77
|
+
* Duplicate request protection for mutating operations.
|
|
78
|
+
* Uses idempotency keys to ensure safe retries.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* import { idempotencyPlugin } from '@classytic/arc/idempotency';
|
|
82
|
+
*
|
|
83
|
+
* await fastify.register(idempotencyPlugin, {
|
|
84
|
+
* enabled: true,
|
|
85
|
+
* headerName: 'idempotency-key',
|
|
86
|
+
* ttlMs: 86400000, // 24 hours
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* // Client sends:
|
|
90
|
+
* // POST /api/orders
|
|
91
|
+
* // Idempotency-Key: order-123-abc
|
|
92
|
+
*
|
|
93
|
+
* // If same key sent again within TTL, returns cached response
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
interface IdempotencyPluginOptions {
|
|
97
|
+
/** Enable idempotency (default: false) */
|
|
98
|
+
enabled?: boolean;
|
|
99
|
+
/** Header name for idempotency key (default: 'idempotency-key') */
|
|
100
|
+
headerName?: string;
|
|
101
|
+
/** TTL for cached responses in ms (default: 86400000 = 24h) */
|
|
102
|
+
ttlMs?: number;
|
|
103
|
+
/** Lock timeout in ms (default: 30000 = 30s) */
|
|
104
|
+
lockTimeoutMs?: number;
|
|
105
|
+
/** HTTP methods to apply idempotency to (default: ['POST', 'PUT', 'PATCH']) */
|
|
106
|
+
methods?: string[];
|
|
107
|
+
/** URL patterns to include (regex). If set, only matching URLs use idempotency */
|
|
108
|
+
include?: RegExp[];
|
|
109
|
+
/** URL patterns to exclude (regex). Excluded patterns take precedence */
|
|
110
|
+
exclude?: RegExp[];
|
|
111
|
+
/** Custom store (default: MemoryIdempotencyStore) */
|
|
112
|
+
store?: IdempotencyStore;
|
|
113
|
+
/** Retry-After header value in seconds when request is in-flight (default: 1) */
|
|
114
|
+
retryAfterSeconds?: number;
|
|
115
|
+
}
|
|
116
|
+
declare module 'fastify' {
|
|
117
|
+
interface FastifyRequest {
|
|
118
|
+
/** The idempotency key for this request (if present) */
|
|
119
|
+
idempotencyKey?: string;
|
|
120
|
+
/** Whether this response was replayed from cache */
|
|
121
|
+
idempotencyReplayed?: boolean;
|
|
122
|
+
}
|
|
123
|
+
interface FastifyInstance {
|
|
124
|
+
/** Idempotency utilities */
|
|
125
|
+
idempotency: {
|
|
126
|
+
/** Manually invalidate an idempotency key */
|
|
127
|
+
invalidate: (key: string) => Promise<void>;
|
|
128
|
+
/** Check if a key has a cached response */
|
|
129
|
+
has: (key: string) => Promise<boolean>;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
declare const idempotencyPlugin: FastifyPluginAsync<IdempotencyPluginOptions>;
|
|
134
|
+
declare const _default: FastifyPluginAsync<IdempotencyPluginOptions>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* In-Memory Idempotency Store
|
|
138
|
+
*
|
|
139
|
+
* Default store for development and small deployments.
|
|
140
|
+
* NOT suitable for multi-instance deployments - use Redis or similar.
|
|
141
|
+
*
|
|
142
|
+
* Features:
|
|
143
|
+
* - Automatic TTL expiration
|
|
144
|
+
* - Lock support for concurrent request handling
|
|
145
|
+
* - Periodic cleanup of expired entries
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
interface MemoryIdempotencyStoreOptions {
|
|
149
|
+
/** Default TTL in milliseconds (default: 86400000 = 24h) */
|
|
150
|
+
ttlMs?: number;
|
|
151
|
+
/** Cleanup interval in milliseconds (default: 60000 = 1 min) */
|
|
152
|
+
cleanupIntervalMs?: number;
|
|
153
|
+
/** Maximum entries before oldest are evicted (default: 10000) */
|
|
154
|
+
maxEntries?: number;
|
|
155
|
+
}
|
|
156
|
+
declare class MemoryIdempotencyStore implements IdempotencyStore {
|
|
157
|
+
readonly name = "memory";
|
|
158
|
+
private results;
|
|
159
|
+
private locks;
|
|
160
|
+
private ttlMs;
|
|
161
|
+
private maxEntries;
|
|
162
|
+
private cleanupInterval;
|
|
163
|
+
constructor(options?: MemoryIdempotencyStoreOptions);
|
|
164
|
+
get(key: string): Promise<IdempotencyResult | undefined>;
|
|
165
|
+
set(key: string, result: Omit<IdempotencyResult, 'key'>): Promise<void>;
|
|
166
|
+
tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
|
|
167
|
+
unlock(key: string, requestId: string): Promise<void>;
|
|
168
|
+
isLocked(key: string): Promise<boolean>;
|
|
169
|
+
delete(key: string): Promise<void>;
|
|
170
|
+
close(): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Get current stats (for debugging/monitoring)
|
|
173
|
+
*/
|
|
174
|
+
getStats(): {
|
|
175
|
+
results: number;
|
|
176
|
+
locks: number;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Remove expired entries
|
|
180
|
+
*/
|
|
181
|
+
private cleanup;
|
|
182
|
+
/**
|
|
183
|
+
* Evict oldest entries when at capacity
|
|
184
|
+
*/
|
|
185
|
+
private evictOldest;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Redis Idempotency Store
|
|
190
|
+
*
|
|
191
|
+
* Durable idempotency store using Redis.
|
|
192
|
+
* Suitable for multi-instance deployments.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* import { createClient } from 'redis';
|
|
196
|
+
* import { RedisIdempotencyStore } from '@classytic/arc/idempotency';
|
|
197
|
+
*
|
|
198
|
+
* const redis = createClient({ url: process.env.REDIS_URL });
|
|
199
|
+
* await redis.connect();
|
|
200
|
+
*
|
|
201
|
+
* await fastify.register(idempotencyPlugin, {
|
|
202
|
+
* store: new RedisIdempotencyStore({ client: redis }),
|
|
203
|
+
* });
|
|
204
|
+
*/
|
|
205
|
+
|
|
206
|
+
interface RedisClient {
|
|
207
|
+
get(key: string): Promise<string | null>;
|
|
208
|
+
set(key: string, value: string, options?: {
|
|
209
|
+
EX?: number;
|
|
210
|
+
NX?: boolean;
|
|
211
|
+
}): Promise<string | null>;
|
|
212
|
+
del(key: string | string[]): Promise<number>;
|
|
213
|
+
exists(key: string | string[]): Promise<number>;
|
|
214
|
+
quit?(): Promise<string>;
|
|
215
|
+
disconnect?(): Promise<void>;
|
|
216
|
+
}
|
|
217
|
+
interface RedisIdempotencyStoreOptions {
|
|
218
|
+
/** Redis client instance */
|
|
219
|
+
client: RedisClient;
|
|
220
|
+
/** Key prefix (default: 'idem:') */
|
|
221
|
+
prefix?: string;
|
|
222
|
+
/** Lock key prefix (default: 'idem:lock:') */
|
|
223
|
+
lockPrefix?: string;
|
|
224
|
+
/** Default TTL in ms (default: 86400000 = 24 hours) */
|
|
225
|
+
ttlMs?: number;
|
|
226
|
+
}
|
|
227
|
+
declare class RedisIdempotencyStore implements IdempotencyStore {
|
|
228
|
+
readonly name = "redis";
|
|
229
|
+
private client;
|
|
230
|
+
private prefix;
|
|
231
|
+
private lockPrefix;
|
|
232
|
+
private ttlMs;
|
|
233
|
+
constructor(options: RedisIdempotencyStoreOptions);
|
|
234
|
+
private resultKey;
|
|
235
|
+
private lockKey;
|
|
236
|
+
get(key: string): Promise<IdempotencyResult | undefined>;
|
|
237
|
+
set(key: string, result: Omit<IdempotencyResult, 'key'>): Promise<void>;
|
|
238
|
+
tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
|
|
239
|
+
unlock(key: string, requestId: string): Promise<void>;
|
|
240
|
+
isLocked(key: string): Promise<boolean>;
|
|
241
|
+
delete(key: string): Promise<void>;
|
|
242
|
+
close(): Promise<void>;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* MongoDB Idempotency Store
|
|
247
|
+
*
|
|
248
|
+
* Durable idempotency store using MongoDB.
|
|
249
|
+
* Suitable for multi-instance deployments.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* import mongoose from 'mongoose';
|
|
253
|
+
* import { MongoIdempotencyStore } from '@classytic/arc/idempotency';
|
|
254
|
+
*
|
|
255
|
+
* await fastify.register(idempotencyPlugin, {
|
|
256
|
+
* store: new MongoIdempotencyStore({
|
|
257
|
+
* connection: mongoose.connection,
|
|
258
|
+
* collection: 'idempotency_keys',
|
|
259
|
+
* }),
|
|
260
|
+
* });
|
|
261
|
+
*/
|
|
262
|
+
|
|
263
|
+
interface MongoConnection {
|
|
264
|
+
db: {
|
|
265
|
+
collection(name: string): MongoCollection;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
interface MongoCollection {
|
|
269
|
+
findOne(filter: object): Promise<IdempotencyDocument | null>;
|
|
270
|
+
insertOne(doc: object): Promise<{
|
|
271
|
+
acknowledged: boolean;
|
|
272
|
+
}>;
|
|
273
|
+
updateOne(filter: object, update: object, options?: object): Promise<{
|
|
274
|
+
acknowledged: boolean;
|
|
275
|
+
}>;
|
|
276
|
+
deleteOne(filter: object): Promise<{
|
|
277
|
+
deletedCount: number;
|
|
278
|
+
}>;
|
|
279
|
+
createIndex(spec: object, options?: object): Promise<string>;
|
|
280
|
+
}
|
|
281
|
+
interface IdempotencyDocument {
|
|
282
|
+
_id: string;
|
|
283
|
+
result?: {
|
|
284
|
+
statusCode: number;
|
|
285
|
+
headers: Record<string, string>;
|
|
286
|
+
body: unknown;
|
|
287
|
+
};
|
|
288
|
+
lock?: {
|
|
289
|
+
requestId: string;
|
|
290
|
+
expiresAt: Date;
|
|
291
|
+
};
|
|
292
|
+
createdAt: Date;
|
|
293
|
+
expiresAt: Date;
|
|
294
|
+
}
|
|
295
|
+
interface MongoIdempotencyStoreOptions {
|
|
296
|
+
/** Mongoose connection or MongoDB connection object */
|
|
297
|
+
connection: MongoConnection;
|
|
298
|
+
/** Collection name (default: 'arc_idempotency') */
|
|
299
|
+
collection?: string;
|
|
300
|
+
/** Create TTL index on startup (default: true) */
|
|
301
|
+
createIndex?: boolean;
|
|
302
|
+
/** Default TTL in ms (default: 86400000 = 24 hours) */
|
|
303
|
+
ttlMs?: number;
|
|
304
|
+
}
|
|
305
|
+
declare class MongoIdempotencyStore implements IdempotencyStore {
|
|
306
|
+
readonly name = "mongodb";
|
|
307
|
+
private connection;
|
|
308
|
+
private collectionName;
|
|
309
|
+
private ttlMs;
|
|
310
|
+
private indexCreated;
|
|
311
|
+
constructor(options: MongoIdempotencyStoreOptions);
|
|
312
|
+
private get collection();
|
|
313
|
+
private ensureIndex;
|
|
314
|
+
get(key: string): Promise<IdempotencyResult | undefined>;
|
|
315
|
+
set(key: string, result: Omit<IdempotencyResult, 'key'>): Promise<void>;
|
|
316
|
+
tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
|
|
317
|
+
unlock(key: string, requestId: string): Promise<void>;
|
|
318
|
+
isLocked(key: string): Promise<boolean>;
|
|
319
|
+
delete(key: string): Promise<void>;
|
|
320
|
+
close(): Promise<void>;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export { type IdempotencyLock, type IdempotencyPluginOptions, type IdempotencyResult, type IdempotencyStore, MemoryIdempotencyStore, type MemoryIdempotencyStoreOptions, MongoIdempotencyStore, type MongoIdempotencyStoreOptions, type RedisClient, RedisIdempotencyStore, type RedisIdempotencyStoreOptions, createIdempotencyResult, _default as idempotencyPlugin, idempotencyPlugin as idempotencyPluginFn };
|