@firela/billclaw-core 0.1.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/LICENSE +21 -0
- package/README.md +109 -0
- package/dist/billclaw.d.ts +76 -0
- package/dist/billclaw.d.ts.map +1 -0
- package/dist/billclaw.js +205 -0
- package/dist/billclaw.js.map +1 -0
- package/dist/credentials/index.d.ts +8 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +8 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/credentials/keychain.d.ts +92 -0
- package/dist/credentials/keychain.d.ts.map +1 -0
- package/dist/credentials/keychain.js +172 -0
- package/dist/credentials/keychain.js.map +1 -0
- package/dist/credentials/store.d.ts +76 -0
- package/dist/credentials/store.d.ts.map +1 -0
- package/dist/credentials/store.js +144 -0
- package/dist/credentials/store.js.map +1 -0
- package/dist/errors/errors.d.ts +92 -0
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/errors/errors.js +315 -0
- package/dist/errors/errors.js.map +1 -0
- package/dist/errors/index.d.ts +7 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +7 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/exporters/beancount.d.ts +42 -0
- package/dist/exporters/beancount.d.ts.map +1 -0
- package/dist/exporters/beancount.js +141 -0
- package/dist/exporters/beancount.js.map +1 -0
- package/dist/exporters/index.d.ts +8 -0
- package/dist/exporters/index.d.ts.map +1 -0
- package/dist/exporters/index.js +8 -0
- package/dist/exporters/index.js.map +1 -0
- package/dist/exporters/ledger.d.ts +42 -0
- package/dist/exporters/ledger.d.ts.map +1 -0
- package/dist/exporters/ledger.js +139 -0
- package/dist/exporters/ledger.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/models/config.d.ts +552 -0
- package/dist/models/config.d.ts.map +1 -0
- package/dist/models/config.js +168 -0
- package/dist/models/config.js.map +1 -0
- package/dist/models/index.d.ts +7 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +8 -0
- package/dist/models/index.js.map +1 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/types.d.ts +110 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +85 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/security/audit.d.ts +148 -0
- package/dist/security/audit.d.ts.map +1 -0
- package/dist/security/audit.js +286 -0
- package/dist/security/audit.js.map +1 -0
- package/dist/security/index.d.ts +7 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +7 -0
- package/dist/security/index.js.map +1 -0
- package/dist/services/event-emitter.d.ts +171 -0
- package/dist/services/event-emitter.d.ts.map +1 -0
- package/dist/services/event-emitter.js +287 -0
- package/dist/services/event-emitter.js.map +1 -0
- package/dist/services/index.d.ts +8 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +8 -0
- package/dist/services/index.js.map +1 -0
- package/dist/sources/gmail/bill-recognizer.d.ts +71 -0
- package/dist/sources/gmail/bill-recognizer.d.ts.map +1 -0
- package/dist/sources/gmail/bill-recognizer.js +341 -0
- package/dist/sources/gmail/bill-recognizer.js.map +1 -0
- package/dist/sources/gmail/email-parser.d.ts +68 -0
- package/dist/sources/gmail/email-parser.d.ts.map +1 -0
- package/dist/sources/gmail/email-parser.js +238 -0
- package/dist/sources/gmail/email-parser.js.map +1 -0
- package/dist/sources/gmail/gmail-fetch.d.ts +54 -0
- package/dist/sources/gmail/gmail-fetch.d.ts.map +1 -0
- package/dist/sources/gmail/gmail-fetch.js +300 -0
- package/dist/sources/gmail/gmail-fetch.js.map +1 -0
- package/dist/sources/gmail/index.d.ts +7 -0
- package/dist/sources/gmail/index.d.ts.map +1 -0
- package/dist/sources/gmail/index.js +7 -0
- package/dist/sources/gmail/index.js.map +1 -0
- package/dist/sources/index.d.ts +8 -0
- package/dist/sources/index.d.ts.map +1 -0
- package/dist/sources/index.js +8 -0
- package/dist/sources/index.js.map +1 -0
- package/dist/sources/plaid/index.d.ts +7 -0
- package/dist/sources/plaid/index.d.ts.map +1 -0
- package/dist/sources/plaid/index.js +7 -0
- package/dist/sources/plaid/index.js.map +1 -0
- package/dist/sources/plaid/plaid-sync.d.ts +42 -0
- package/dist/sources/plaid/plaid-sync.d.ts.map +1 -0
- package/dist/sources/plaid/plaid-sync.js +182 -0
- package/dist/sources/plaid/plaid-sync.js.map +1 -0
- package/dist/storage/cache.d.ts +134 -0
- package/dist/storage/cache.d.ts.map +1 -0
- package/dist/storage/cache.js +239 -0
- package/dist/storage/cache.js.map +1 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +11 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/indexes.d.ts +136 -0
- package/dist/storage/indexes.d.ts.map +1 -0
- package/dist/storage/indexes.js +294 -0
- package/dist/storage/indexes.js.map +1 -0
- package/dist/storage/locking.d.ts +103 -0
- package/dist/storage/locking.d.ts.map +1 -0
- package/dist/storage/locking.js +158 -0
- package/dist/storage/locking.js.map +1 -0
- package/dist/storage/streaming.d.ts +102 -0
- package/dist/storage/streaming.d.ts.map +1 -0
- package/dist/storage/streaming.js +245 -0
- package/dist/storage/streaming.js.map +1 -0
- package/dist/storage/transaction-storage.d.ts +101 -0
- package/dist/storage/transaction-storage.d.ts.map +1 -0
- package/dist/storage/transaction-storage.js +193 -0
- package/dist/storage/transaction-storage.js.map +1 -0
- package/dist/sync/index.d.ts +7 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +7 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/sync-service.d.ts +42 -0
- package/dist/sync/sync-service.d.ts.map +1 -0
- package/dist/sync/sync-service.js +112 -0
- package/dist/sync/sync-service.js.map +1 -0
- package/dist/test-fixtures.d.ts +38 -0
- package/dist/test-fixtures.d.ts.map +1 -0
- package/dist/test-fixtures.js +137 -0
- package/dist/test-fixtures.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plaid data source for BillClaw - Framework-agnostic Plaid integration
|
|
3
|
+
*/
|
|
4
|
+
import { Configuration, PlaidApi, PlaidEnvironments } from "plaid";
|
|
5
|
+
import { appendTransactions, deduplicateTransactions, readSyncStates, writeGlobalCursor, writeSyncState, } from "../../storage/transaction-storage.js";
|
|
6
|
+
import { emitEvent } from "../../services/event-emitter.js";
|
|
7
|
+
/**
|
|
8
|
+
* Create Plaid API client
|
|
9
|
+
*/
|
|
10
|
+
export function createPlaidClient(config) {
|
|
11
|
+
const plaidEnvMap = {
|
|
12
|
+
sandbox: PlaidEnvironments.sandbox,
|
|
13
|
+
development: PlaidEnvironments.development,
|
|
14
|
+
production: PlaidEnvironments.production,
|
|
15
|
+
};
|
|
16
|
+
const environment = plaidEnvMap[config.environment] || PlaidEnvironments.sandbox;
|
|
17
|
+
const configuration = new Configuration({
|
|
18
|
+
basePath: environment,
|
|
19
|
+
baseOptions: {
|
|
20
|
+
headers: {
|
|
21
|
+
"PLAID-CLIENT-ID": config.clientId,
|
|
22
|
+
"PLAID-SECRET": config.secret,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
return new PlaidApi(configuration);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert Plaid transaction to internal format
|
|
30
|
+
*/
|
|
31
|
+
export function convertTransaction(plaidTxn, accountId) {
|
|
32
|
+
return {
|
|
33
|
+
transactionId: `${accountId}_${plaidTxn.transaction_id}`,
|
|
34
|
+
accountId,
|
|
35
|
+
date: plaidTxn.date,
|
|
36
|
+
amount: Math.round(plaidTxn.amount * 100), // Convert to cents
|
|
37
|
+
currency: plaidTxn.iso_currency_code,
|
|
38
|
+
category: plaidTxn.category || [],
|
|
39
|
+
merchantName: plaidTxn.merchant_name || plaidTxn.name || "Unknown",
|
|
40
|
+
paymentChannel: plaidTxn.payment_channel,
|
|
41
|
+
pending: plaidTxn.pending,
|
|
42
|
+
plaidTransactionId: plaidTxn.transaction_id,
|
|
43
|
+
createdAt: new Date().toISOString(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Sync transactions from a single Plaid account
|
|
48
|
+
*/
|
|
49
|
+
export async function syncPlaidAccount(account, plaidClient, storageConfig, logger, webhooks = []) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
let transactionsAdded = 0;
|
|
52
|
+
let transactionsUpdated = 0;
|
|
53
|
+
let cursor = "";
|
|
54
|
+
let requiresReauth = false;
|
|
55
|
+
const syncId = `sync_${Date.now()}`;
|
|
56
|
+
const syncState = {
|
|
57
|
+
syncId,
|
|
58
|
+
accountId: account.id,
|
|
59
|
+
startedAt: new Date().toISOString(),
|
|
60
|
+
status: "running",
|
|
61
|
+
transactionsAdded: 0,
|
|
62
|
+
transactionsUpdated: 0,
|
|
63
|
+
cursor: "",
|
|
64
|
+
};
|
|
65
|
+
// Emit sync.started event (fire-and-forget)
|
|
66
|
+
emitEvent(logger, webhooks, "sync.started", {
|
|
67
|
+
accountId: account.id,
|
|
68
|
+
syncId,
|
|
69
|
+
}).catch((err) => logger.debug?.(`Event emission failed:`, err));
|
|
70
|
+
try {
|
|
71
|
+
// Get previous sync state for cursor
|
|
72
|
+
const previousSyncs = await readSyncStates(account.id, storageConfig);
|
|
73
|
+
const lastSync = previousSyncs.find((s) => s.status === "completed");
|
|
74
|
+
const lastCursor = lastSync?.cursor || undefined;
|
|
75
|
+
const request = {
|
|
76
|
+
access_token: account.plaidAccessToken,
|
|
77
|
+
cursor: lastCursor,
|
|
78
|
+
count: 500,
|
|
79
|
+
};
|
|
80
|
+
const axiosResponse = await plaidClient.transactionsSync(request);
|
|
81
|
+
const response = axiosResponse.data;
|
|
82
|
+
cursor = response.next_cursor || "";
|
|
83
|
+
const removed = response.removed || [];
|
|
84
|
+
const added = response.added || [];
|
|
85
|
+
logger.info?.(`Plaid sync for ${account.id}: ${added.length} added, ${removed.length} removed`);
|
|
86
|
+
// Convert transactions
|
|
87
|
+
const transactions = added.map((txn) => convertTransaction(txn, account.id));
|
|
88
|
+
// Deduplicate transactions (24h window)
|
|
89
|
+
const deduplicated = deduplicateTransactions(transactions, 24);
|
|
90
|
+
// Group by month for storage
|
|
91
|
+
const byMonth = new Map();
|
|
92
|
+
for (const txn of deduplicated) {
|
|
93
|
+
const date = new Date(txn.date);
|
|
94
|
+
const key = `${date.getFullYear()}-${date.getMonth()}`;
|
|
95
|
+
if (!byMonth.has(key)) {
|
|
96
|
+
byMonth.set(key, []);
|
|
97
|
+
}
|
|
98
|
+
byMonth.get(key).push(txn);
|
|
99
|
+
}
|
|
100
|
+
// Store transactions per month
|
|
101
|
+
for (const [monthKey, monthTransactions] of byMonth.entries()) {
|
|
102
|
+
const [year, month] = monthKey.split("-").map(Number);
|
|
103
|
+
const result = await appendTransactions(account.id, year, month, monthTransactions, storageConfig);
|
|
104
|
+
transactionsAdded += result.added;
|
|
105
|
+
transactionsUpdated += result.updated;
|
|
106
|
+
}
|
|
107
|
+
// Update sync state
|
|
108
|
+
syncState.status = "completed";
|
|
109
|
+
syncState.completedAt = new Date().toISOString();
|
|
110
|
+
syncState.transactionsAdded = transactionsAdded;
|
|
111
|
+
syncState.transactionsUpdated = transactionsUpdated;
|
|
112
|
+
syncState.cursor = cursor;
|
|
113
|
+
logger.info?.(`Sync completed for ${account.id}: ${transactionsAdded} added, ${transactionsUpdated} updated`);
|
|
114
|
+
// Emit sync.completed event (fire-and-forget)
|
|
115
|
+
const syncDuration = Date.now() - new Date(syncState.startedAt).getTime();
|
|
116
|
+
emitEvent(logger, webhooks, "sync.completed", {
|
|
117
|
+
accountId: account.id,
|
|
118
|
+
syncId,
|
|
119
|
+
transactionsAdded,
|
|
120
|
+
transactionsUpdated,
|
|
121
|
+
duration: syncDuration,
|
|
122
|
+
}).catch((err) => logger.debug?.(`Event emission failed:`, err));
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
126
|
+
errors.push(errorMsg);
|
|
127
|
+
syncState.status = "failed";
|
|
128
|
+
syncState.error = errorMsg;
|
|
129
|
+
logger.error?.(`Sync failed for ${account.id}:`, error);
|
|
130
|
+
// Emit sync.failed event (fire-and-forget)
|
|
131
|
+
emitEvent(logger, webhooks, "sync.failed", {
|
|
132
|
+
accountId: account.id,
|
|
133
|
+
syncId,
|
|
134
|
+
error: errorMsg,
|
|
135
|
+
}).catch((err) => logger.debug?.(`Event emission failed:`, err));
|
|
136
|
+
// Check for Plaid-specific errors that require user action
|
|
137
|
+
if (error && typeof error === "object") {
|
|
138
|
+
const plaidError = error;
|
|
139
|
+
const errorCode = plaidError.code || plaidError.error_code;
|
|
140
|
+
// Item login errors - user needs to re-authenticate via Plaid Link
|
|
141
|
+
if (errorCode === "ITEM_LOGIN_REQUIRED" ||
|
|
142
|
+
errorCode === "INVALID_ACCESS_TOKEN" ||
|
|
143
|
+
errorCode === "PRODUCTS_NOT_READY" ||
|
|
144
|
+
plaidError.response?.data?.error_code === "ITEM_LOGIN_REQUIRED") {
|
|
145
|
+
logger.warn?.(`Account ${account.id} requires re-authentication via Plaid Link`);
|
|
146
|
+
syncState.error = `ITEM_LOGIN_REQUIRED: Please re-connect this account via Plaid Link`;
|
|
147
|
+
syncState.requiresReauth = true;
|
|
148
|
+
requiresReauth = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
await writeSyncState(syncState, storageConfig);
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
success: errors.length === 0,
|
|
157
|
+
accountId: account.id,
|
|
158
|
+
transactionsAdded,
|
|
159
|
+
transactionsUpdated,
|
|
160
|
+
cursor,
|
|
161
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
162
|
+
requiresReauth,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Sync multiple Plaid accounts
|
|
167
|
+
*/
|
|
168
|
+
export async function syncPlaidAccounts(accounts, plaidConfig, storageConfig, logger, webhooks = []) {
|
|
169
|
+
if (accounts.length === 0) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
const plaidClient = createPlaidClient(plaidConfig);
|
|
173
|
+
const results = [];
|
|
174
|
+
for (const account of accounts) {
|
|
175
|
+
const result = await syncPlaidAccount(account, plaidClient, storageConfig, logger, webhooks);
|
|
176
|
+
results.push(result);
|
|
177
|
+
}
|
|
178
|
+
// Update global cursor
|
|
179
|
+
await writeGlobalCursor({ lastSyncTime: new Date().toISOString() }, storageConfig);
|
|
180
|
+
return results;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=plaid-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plaid-sync.js","sourceRoot":"","sources":["../../../src/sources/plaid/plaid-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory cache for BillClaw
|
|
3
|
+
*
|
|
4
|
+
* Provides TTL-based caching for frequently accessed data:
|
|
5
|
+
* - Account configurations
|
|
6
|
+
* - Transaction summaries
|
|
7
|
+
* - Sync states
|
|
8
|
+
* - User preferences
|
|
9
|
+
*
|
|
10
|
+
* This improves performance by reducing disk I/O for commonly accessed data.
|
|
11
|
+
*/
|
|
12
|
+
import type { Logger } from "../errors/errors.js";
|
|
13
|
+
/**
|
|
14
|
+
* Cache configuration
|
|
15
|
+
*/
|
|
16
|
+
export interface CacheConfig {
|
|
17
|
+
defaultTtl: number;
|
|
18
|
+
maxSize: number;
|
|
19
|
+
logger?: Logger;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* In-memory cache with TTL support
|
|
23
|
+
*/
|
|
24
|
+
export declare class MemoryCache {
|
|
25
|
+
private cache;
|
|
26
|
+
private config;
|
|
27
|
+
private cleanupTimer?;
|
|
28
|
+
constructor(config?: Partial<CacheConfig>);
|
|
29
|
+
/**
|
|
30
|
+
* Set a value in the cache
|
|
31
|
+
*
|
|
32
|
+
* @param key - Cache key
|
|
33
|
+
* @param value - Value to cache
|
|
34
|
+
* @param ttl - Time to live in milliseconds (uses default if not specified)
|
|
35
|
+
*/
|
|
36
|
+
set<T>(key: string, value: T, ttl?: number): void;
|
|
37
|
+
/**
|
|
38
|
+
* Get a value from the cache
|
|
39
|
+
*
|
|
40
|
+
* @param key - Cache key
|
|
41
|
+
* @returns The cached value, or null if not found or expired
|
|
42
|
+
*/
|
|
43
|
+
get<T>(key: string): T | null;
|
|
44
|
+
/**
|
|
45
|
+
* Check if a key exists and is not expired
|
|
46
|
+
*
|
|
47
|
+
* @param key - Cache key
|
|
48
|
+
* @returns true if the key exists and is not expired
|
|
49
|
+
*/
|
|
50
|
+
has(key: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Delete a value from the cache
|
|
53
|
+
*
|
|
54
|
+
* @param key - Cache key
|
|
55
|
+
* @returns true if the key was deleted
|
|
56
|
+
*/
|
|
57
|
+
delete(key: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Clear all values from the cache
|
|
60
|
+
*/
|
|
61
|
+
clear(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Get the number of entries in the cache
|
|
64
|
+
*/
|
|
65
|
+
size(): number;
|
|
66
|
+
/**
|
|
67
|
+
* Get all cache keys
|
|
68
|
+
*/
|
|
69
|
+
keys(): string[];
|
|
70
|
+
/**
|
|
71
|
+
* Clean up expired entries
|
|
72
|
+
*/
|
|
73
|
+
private cleanupExpired;
|
|
74
|
+
/**
|
|
75
|
+
* Evict the oldest entry (LRU-style eviction)
|
|
76
|
+
*/
|
|
77
|
+
private evictOldest;
|
|
78
|
+
/**
|
|
79
|
+
* Destroy the cache and cleanup timers
|
|
80
|
+
*/
|
|
81
|
+
destroy(): void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Cache key generators for common data types
|
|
85
|
+
*/
|
|
86
|
+
export declare const CacheKeys: {
|
|
87
|
+
/**
|
|
88
|
+
* Account configuration cache key
|
|
89
|
+
*/
|
|
90
|
+
account(accountId: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* Transaction list cache key
|
|
93
|
+
*/
|
|
94
|
+
transactions(accountId: string, year: number, month: number): string;
|
|
95
|
+
/**
|
|
96
|
+
* Sync state cache key
|
|
97
|
+
*/
|
|
98
|
+
syncState(accountId: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* Plaid balance cache key
|
|
101
|
+
*/
|
|
102
|
+
plaidBalance(accountId: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Gmail history cache key
|
|
105
|
+
*/
|
|
106
|
+
gmailHistory(accountId: string): string;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Create a memory cache with the given configuration
|
|
110
|
+
*/
|
|
111
|
+
export declare function createMemoryCache(config?: Partial<CacheConfig>): MemoryCache;
|
|
112
|
+
/**
|
|
113
|
+
* Cached data wrapper - provides memoization for expensive operations
|
|
114
|
+
*/
|
|
115
|
+
export declare class CachedData<T> {
|
|
116
|
+
private cache;
|
|
117
|
+
private key;
|
|
118
|
+
private ttl;
|
|
119
|
+
private fetchFn;
|
|
120
|
+
constructor(cache: MemoryCache, key: string, fetchFn: () => Promise<T>, ttl?: number);
|
|
121
|
+
/**
|
|
122
|
+
* Get the cached value, or fetch if not cached
|
|
123
|
+
*/
|
|
124
|
+
get(): Promise<T>;
|
|
125
|
+
/**
|
|
126
|
+
* Invalidate the cache
|
|
127
|
+
*/
|
|
128
|
+
invalidate(): void;
|
|
129
|
+
/**
|
|
130
|
+
* Set a specific value in the cache
|
|
131
|
+
*/
|
|
132
|
+
set(value: T): void;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/storage/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAUjD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAUD;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAyC;IACtD,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,YAAY,CAAC,CAAgC;gBAEzC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM;IAS7C;;;;;;OAMG;IACH,GAAG,CAAC,CAAC,EAAG,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAclD;;;;;OAKG;IACH,GAAG,CAAC,CAAC,EAAG,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IAkB9B;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAezB;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAK5B;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,IAAI,IAAI,MAAM;IAId;;OAEG;IACH,IAAI,IAAI,MAAM,EAAE;IAIhB;;OAEG;IACH,OAAO,CAAC,cAAc;IAkBtB;;OAEG;IACH,OAAO,CAAC,WAAW;IAiBnB;;OAEG;IACH,OAAO,IAAI,IAAI;CAOhB;AAED;;GAEG;AACH,eAAO,MAAM,SAAS;IACpB;;OAEG;uBACgB,MAAM,GAAG,MAAM;IAIlC;;OAEG;4BACqB,MAAM,QAAQ,MAAM,SAAS,MAAM,GAAG,MAAM;IAIpE;;OAEG;yBACkB,MAAM,GAAG,MAAM;IAIpC;;OAEG;4BACqB,MAAM,GAAG,MAAM;IAIvC;;OAEG;4BACqB,MAAM,GAAG,MAAM;CAGxC,CAAA;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAE5E;AAED;;GAEG;AACH,qBAAa,UAAU,CAAC,CAAC;IACvB,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,GAAG,CAAQ;IACnB,OAAO,CAAC,GAAG,CAAQ;IACnB,OAAO,CAAC,OAAO,CAAkB;gBAG/B,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,GAAG,CAAC,EAAE,MAAM;IAQd;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;IAYvB;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;CAGpB"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory cache for BillClaw
|
|
3
|
+
*
|
|
4
|
+
* Provides TTL-based caching for frequently accessed data:
|
|
5
|
+
* - Account configurations
|
|
6
|
+
* - Transaction summaries
|
|
7
|
+
* - Sync states
|
|
8
|
+
* - User preferences
|
|
9
|
+
*
|
|
10
|
+
* This improves performance by reducing disk I/O for commonly accessed data.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Default cache configuration
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_CACHE_CONFIG = {
|
|
16
|
+
defaultTtl: 5 * 60 * 1000, // 5 minutes
|
|
17
|
+
maxSize: 1000,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* In-memory cache with TTL support
|
|
21
|
+
*/
|
|
22
|
+
export class MemoryCache {
|
|
23
|
+
cache = new Map();
|
|
24
|
+
config;
|
|
25
|
+
cleanupTimer;
|
|
26
|
+
constructor(config = {}) {
|
|
27
|
+
this.config = { ...DEFAULT_CACHE_CONFIG, ...config };
|
|
28
|
+
// Periodically clean up expired entries
|
|
29
|
+
this.cleanupTimer = setInterval(() => {
|
|
30
|
+
this.cleanupExpired();
|
|
31
|
+
}, 60 * 1000); // Every minute
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Set a value in the cache
|
|
35
|
+
*
|
|
36
|
+
* @param key - Cache key
|
|
37
|
+
* @param value - Value to cache
|
|
38
|
+
* @param ttl - Time to live in milliseconds (uses default if not specified)
|
|
39
|
+
*/
|
|
40
|
+
set(key, value, ttl) {
|
|
41
|
+
const expiresAt = Date.now() + (ttl ?? this.config.defaultTtl);
|
|
42
|
+
// Enforce max size by removing oldest entries if needed
|
|
43
|
+
if (this.cache.size >= this.config.maxSize && !this.cache.has(key)) {
|
|
44
|
+
this.evictOldest();
|
|
45
|
+
}
|
|
46
|
+
this.cache.set(key, { value, expiresAt });
|
|
47
|
+
this.config.logger?.debug?.(`Cache set: ${key} (TTL: ${ttl ?? this.config.defaultTtl}ms)`);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get a value from the cache
|
|
51
|
+
*
|
|
52
|
+
* @param key - Cache key
|
|
53
|
+
* @returns The cached value, or null if not found or expired
|
|
54
|
+
*/
|
|
55
|
+
get(key) {
|
|
56
|
+
const entry = this.cache.get(key);
|
|
57
|
+
if (!entry) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
// Check if expired (use >= to handle TTL of 0)
|
|
61
|
+
if (Date.now() >= entry.expiresAt) {
|
|
62
|
+
this.cache.delete(key);
|
|
63
|
+
this.config.logger?.debug?.(`Cache miss (expired): ${key}`);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
this.config.logger?.debug?.(`Cache hit: ${key}`);
|
|
67
|
+
return entry.value;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if a key exists and is not expired
|
|
71
|
+
*
|
|
72
|
+
* @param key - Cache key
|
|
73
|
+
* @returns true if the key exists and is not expired
|
|
74
|
+
*/
|
|
75
|
+
has(key) {
|
|
76
|
+
const entry = this.cache.get(key);
|
|
77
|
+
if (!entry) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (Date.now() >= entry.expiresAt) {
|
|
81
|
+
this.cache.delete(key);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Delete a value from the cache
|
|
88
|
+
*
|
|
89
|
+
* @param key - Cache key
|
|
90
|
+
* @returns true if the key was deleted
|
|
91
|
+
*/
|
|
92
|
+
delete(key) {
|
|
93
|
+
this.config.logger?.debug?.(`Cache delete: ${key}`);
|
|
94
|
+
return this.cache.delete(key);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Clear all values from the cache
|
|
98
|
+
*/
|
|
99
|
+
clear() {
|
|
100
|
+
this.cache.clear();
|
|
101
|
+
this.config.logger?.info?.("Cache cleared");
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get the number of entries in the cache
|
|
105
|
+
*/
|
|
106
|
+
size() {
|
|
107
|
+
return this.cache.size;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get all cache keys
|
|
111
|
+
*/
|
|
112
|
+
keys() {
|
|
113
|
+
return Array.from(this.cache.keys());
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Clean up expired entries
|
|
117
|
+
*/
|
|
118
|
+
cleanupExpired() {
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
let cleaned = 0;
|
|
121
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
122
|
+
if (now > entry.expiresAt) {
|
|
123
|
+
this.cache.delete(key);
|
|
124
|
+
cleaned++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (cleaned > 0) {
|
|
128
|
+
this.config.logger?.debug?.(`Cache cleanup: removed ${cleaned} expired entries`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Evict the oldest entry (LRU-style eviction)
|
|
133
|
+
*/
|
|
134
|
+
evictOldest() {
|
|
135
|
+
let oldestKey = null;
|
|
136
|
+
let oldestTime = Infinity;
|
|
137
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
138
|
+
if (entry.expiresAt < oldestTime) {
|
|
139
|
+
oldestTime = entry.expiresAt;
|
|
140
|
+
oldestKey = key;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (oldestKey) {
|
|
144
|
+
this.cache.delete(oldestKey);
|
|
145
|
+
this.config.logger?.debug?.(`Cache evicted (oldest): ${oldestKey}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Destroy the cache and cleanup timers
|
|
150
|
+
*/
|
|
151
|
+
destroy() {
|
|
152
|
+
if (this.cleanupTimer) {
|
|
153
|
+
clearInterval(this.cleanupTimer);
|
|
154
|
+
this.cleanupTimer = undefined;
|
|
155
|
+
}
|
|
156
|
+
this.clear();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Cache key generators for common data types
|
|
161
|
+
*/
|
|
162
|
+
export const CacheKeys = {
|
|
163
|
+
/**
|
|
164
|
+
* Account configuration cache key
|
|
165
|
+
*/
|
|
166
|
+
account(accountId) {
|
|
167
|
+
return `account:${accountId}`;
|
|
168
|
+
},
|
|
169
|
+
/**
|
|
170
|
+
* Transaction list cache key
|
|
171
|
+
*/
|
|
172
|
+
transactions(accountId, year, month) {
|
|
173
|
+
return `transactions:${accountId}:${year}:${month}`;
|
|
174
|
+
},
|
|
175
|
+
/**
|
|
176
|
+
* Sync state cache key
|
|
177
|
+
*/
|
|
178
|
+
syncState(accountId) {
|
|
179
|
+
return `sync_state:${accountId}`;
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Plaid balance cache key
|
|
183
|
+
*/
|
|
184
|
+
plaidBalance(accountId) {
|
|
185
|
+
return `plaid_balance:${accountId}`;
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* Gmail history cache key
|
|
189
|
+
*/
|
|
190
|
+
gmailHistory(accountId) {
|
|
191
|
+
return `gmail_history:${accountId}`;
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* Create a memory cache with the given configuration
|
|
196
|
+
*/
|
|
197
|
+
export function createMemoryCache(config) {
|
|
198
|
+
return new MemoryCache(config);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Cached data wrapper - provides memoization for expensive operations
|
|
202
|
+
*/
|
|
203
|
+
export class CachedData {
|
|
204
|
+
cache;
|
|
205
|
+
key;
|
|
206
|
+
ttl;
|
|
207
|
+
fetchFn;
|
|
208
|
+
constructor(cache, key, fetchFn, ttl) {
|
|
209
|
+
this.cache = cache;
|
|
210
|
+
this.key = key;
|
|
211
|
+
this.ttl = ttl ?? cache["config"].defaultTtl;
|
|
212
|
+
this.fetchFn = fetchFn;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get the cached value, or fetch if not cached
|
|
216
|
+
*/
|
|
217
|
+
async get() {
|
|
218
|
+
const cached = this.cache.get(this.key);
|
|
219
|
+
if (cached !== null) {
|
|
220
|
+
return cached;
|
|
221
|
+
}
|
|
222
|
+
const value = await this.fetchFn();
|
|
223
|
+
this.cache.set(this.key, value, this.ttl);
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Invalidate the cache
|
|
228
|
+
*/
|
|
229
|
+
invalidate() {
|
|
230
|
+
this.cache.delete(this.key);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Set a specific value in the cache
|
|
234
|
+
*/
|
|
235
|
+
set(value) {
|
|
236
|
+
this.cache.set(this.key, value, this.ttl);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/storage/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage layer for BillClaw
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export * from "./transaction-storage.js";
|
|
7
|
+
export * from "./cache.js";
|
|
8
|
+
export * from "./indexes.js";
|
|
9
|
+
export * from "./streaming.js";
|
|
10
|
+
export * from "./locking.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,0BAA0B,CAAA;AACxC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage layer for BillClaw
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export * from "./transaction-storage.js";
|
|
7
|
+
export * from "./cache.js";
|
|
8
|
+
export * from "./indexes.js";
|
|
9
|
+
export * from "./streaming.js";
|
|
10
|
+
export * from "./locking.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,0BAA0B,CAAA;AACxC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA"}
|