@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,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File locking for BillClaw
|
|
3
|
+
*
|
|
4
|
+
* Provides inter-process file locking to prevent concurrent write conflicts.
|
|
5
|
+
* Uses proper-lockfile for cross-platform file locking.
|
|
6
|
+
*
|
|
7
|
+
* Use cases:
|
|
8
|
+
* - Preventing concurrent sync operations
|
|
9
|
+
* - Protecting credential writes
|
|
10
|
+
* - Coordinating between multiple processes
|
|
11
|
+
*/
|
|
12
|
+
// Lockfile module (lazy loaded)
|
|
13
|
+
let lockfileModule = null;
|
|
14
|
+
/**
|
|
15
|
+
* Initialize lockfile module
|
|
16
|
+
*/
|
|
17
|
+
async function initLockfile() {
|
|
18
|
+
if (!lockfileModule) {
|
|
19
|
+
try {
|
|
20
|
+
lockfileModule = await import("proper-lockfile");
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw new Error("proper-lockfile is not installed. Install it with: npm install proper-lockfile");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return lockfileModule;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Default lock options
|
|
30
|
+
*/
|
|
31
|
+
const DEFAULT_LOCK_OPTIONS = {
|
|
32
|
+
stale: 60 * 1000, // 1 minute
|
|
33
|
+
retries: {
|
|
34
|
+
count: 10,
|
|
35
|
+
min: 100,
|
|
36
|
+
max: 500,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* File lock implementation
|
|
41
|
+
*/
|
|
42
|
+
class FileLock {
|
|
43
|
+
filePath;
|
|
44
|
+
logger;
|
|
45
|
+
released = false;
|
|
46
|
+
constructor(filePath, logger) {
|
|
47
|
+
this.filePath = filePath;
|
|
48
|
+
this.logger = logger;
|
|
49
|
+
}
|
|
50
|
+
async release() {
|
|
51
|
+
if (this.released) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const lockfile = await initLockfile();
|
|
55
|
+
try {
|
|
56
|
+
await lockfile.unlock(this.filePath);
|
|
57
|
+
this.released = true;
|
|
58
|
+
this.logger?.debug?.(`Released lock: ${this.filePath}`);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
this.logger?.error?.(`Failed to release lock: ${this.filePath}`, error);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async isLocked() {
|
|
66
|
+
if (this.released) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const lockfile = await initLockfile();
|
|
70
|
+
return lockfile.check(this.filePath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Acquire a file lock
|
|
75
|
+
*
|
|
76
|
+
* @param filePath - Path to the file to lock
|
|
77
|
+
* @param options - Lock options
|
|
78
|
+
* @returns Lock handle that must be released when done
|
|
79
|
+
*/
|
|
80
|
+
export async function acquireLock(filePath, options = {}) {
|
|
81
|
+
const opts = { ...DEFAULT_LOCK_OPTIONS, ...options };
|
|
82
|
+
const lockfile = await initLockfile();
|
|
83
|
+
try {
|
|
84
|
+
await lockfile.lock(filePath, {
|
|
85
|
+
stale: opts.stale,
|
|
86
|
+
retries: opts.retries
|
|
87
|
+
? {
|
|
88
|
+
retries: opts.retries.count,
|
|
89
|
+
minTimeout: opts.retries.min,
|
|
90
|
+
maxTimeout: opts.retries.max,
|
|
91
|
+
}
|
|
92
|
+
: undefined,
|
|
93
|
+
});
|
|
94
|
+
options.logger?.debug?.(`Acquired lock: ${filePath}`);
|
|
95
|
+
return new FileLock(filePath, options.logger);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
options.logger?.error?.(`Failed to acquire lock: ${filePath}`, error);
|
|
99
|
+
throw new Error(`Failed to acquire lock on ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Execute a function while holding a lock
|
|
104
|
+
*
|
|
105
|
+
* @param filePath - Path to the file to lock
|
|
106
|
+
* @param fn - Function to execute while holding the lock
|
|
107
|
+
* @param options - Lock options
|
|
108
|
+
* @returns Result of the function
|
|
109
|
+
*/
|
|
110
|
+
export async function withLock(filePath, fn, options = {}) {
|
|
111
|
+
const lock = await acquireLock(filePath, options);
|
|
112
|
+
try {
|
|
113
|
+
return await fn();
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await lock.release();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if a file is locked
|
|
121
|
+
*
|
|
122
|
+
* @param filePath - Path to the file to check
|
|
123
|
+
* @returns true if the file is locked
|
|
124
|
+
*/
|
|
125
|
+
export async function isLocked(filePath) {
|
|
126
|
+
const lockfile = await initLockfile();
|
|
127
|
+
return lockfile.check(filePath);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Lock names for common operations
|
|
131
|
+
*/
|
|
132
|
+
export const LockNames = {
|
|
133
|
+
/**
|
|
134
|
+
* Global sync lock (prevents concurrent sync operations)
|
|
135
|
+
*/
|
|
136
|
+
SYNC: ".sync.lock",
|
|
137
|
+
/**
|
|
138
|
+
* Account-specific sync lock
|
|
139
|
+
*/
|
|
140
|
+
accountSync(accountId) {
|
|
141
|
+
return `.sync_${accountId}.lock`;
|
|
142
|
+
},
|
|
143
|
+
/**
|
|
144
|
+
* Credential lock (protects credential writes)
|
|
145
|
+
*/
|
|
146
|
+
CREDENTIALS: ".credentials.lock",
|
|
147
|
+
/**
|
|
148
|
+
* Account-specific credential lock
|
|
149
|
+
*/
|
|
150
|
+
accountCredentials(accountId) {
|
|
151
|
+
return `.credentials_${accountId}.lock`;
|
|
152
|
+
},
|
|
153
|
+
/**
|
|
154
|
+
* Export lock (protects data export operations)
|
|
155
|
+
*/
|
|
156
|
+
EXPORT: ".export.lock",
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=locking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locking.js","sourceRoot":"","sources":["../../src/storage/locking.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming JSON support for BillClaw
|
|
3
|
+
*
|
|
4
|
+
* Provides streaming JSON parsing and generation for large datasets.
|
|
5
|
+
* This keeps memory usage constant regardless of file size.
|
|
6
|
+
*
|
|
7
|
+
* Use cases:
|
|
8
|
+
* - Reading large transaction files without loading entire file
|
|
9
|
+
* - Writing large datasets incrementally
|
|
10
|
+
* - Processing transactions one at a time
|
|
11
|
+
*/
|
|
12
|
+
import type { Transaction } from "./transaction-storage.js";
|
|
13
|
+
import type { Logger } from "../errors/errors.js";
|
|
14
|
+
/**
|
|
15
|
+
* Options for streaming JSON write
|
|
16
|
+
*/
|
|
17
|
+
export interface StreamingWriteOptions {
|
|
18
|
+
batchSize?: number;
|
|
19
|
+
logger?: Logger;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Options for streaming JSON read
|
|
23
|
+
*/
|
|
24
|
+
export interface StreamingReadOptions<T> {
|
|
25
|
+
batchSize?: number;
|
|
26
|
+
filter?: (item: T) => boolean;
|
|
27
|
+
transform?: (item: T) => T;
|
|
28
|
+
logger?: Logger;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Write items to a JSON file incrementally
|
|
32
|
+
*
|
|
33
|
+
* @param filePath - Path to output file
|
|
34
|
+
* @param items - Async iterable of items to write
|
|
35
|
+
* @param options - Write options
|
|
36
|
+
*/
|
|
37
|
+
export declare function writeStreamingJson<T>(filePath: string, items: AsyncIterable<T>, options?: StreamingWriteOptions): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Read items from a JSON file incrementally
|
|
40
|
+
*
|
|
41
|
+
* @param filePath - Path to input file
|
|
42
|
+
* @param options - Read options
|
|
43
|
+
* @returns Async iterable of items
|
|
44
|
+
*/
|
|
45
|
+
export declare function readStreamingJson<T>(filePath: string, options?: StreamingReadOptions<T>): AsyncIterable<T>;
|
|
46
|
+
/**
|
|
47
|
+
* Stream transactions from a file
|
|
48
|
+
*
|
|
49
|
+
* @param filePath - Path to transaction file
|
|
50
|
+
* @param options - Stream options
|
|
51
|
+
* @returns Async iterable of transactions
|
|
52
|
+
*/
|
|
53
|
+
export declare function streamTransactions(filePath: string, options?: StreamingReadOptions<Transaction>): AsyncIterable<Transaction>;
|
|
54
|
+
/**
|
|
55
|
+
* Write transactions to a file incrementally
|
|
56
|
+
*
|
|
57
|
+
* @param filePath - Path to output file
|
|
58
|
+
* @param transactions - Async iterable of transactions
|
|
59
|
+
* @param options - Write options
|
|
60
|
+
*/
|
|
61
|
+
export declare function writeTransactionsStreaming(filePath: string, transactions: AsyncIterable<Transaction>, options?: StreamingWriteOptions): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Map/reduce operations on streaming JSON
|
|
64
|
+
*/
|
|
65
|
+
export declare class StreamingJsonOperations<T> {
|
|
66
|
+
private filePath;
|
|
67
|
+
private logger?;
|
|
68
|
+
constructor(filePath: string, logger?: Logger | undefined);
|
|
69
|
+
/**
|
|
70
|
+
* Map each item through a transformation function
|
|
71
|
+
*/
|
|
72
|
+
map<U>(fn: (item: T) => U): Promise<U[]>;
|
|
73
|
+
/**
|
|
74
|
+
* Filter items
|
|
75
|
+
*/
|
|
76
|
+
filter(fn: (item: T) => boolean): Promise<T[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Reduce items to a single value
|
|
79
|
+
*/
|
|
80
|
+
reduce<U>(fn: (acc: U, item: T) => U, initial: U): Promise<U>;
|
|
81
|
+
/**
|
|
82
|
+
* Count items matching a predicate
|
|
83
|
+
*/
|
|
84
|
+
count(predicate?: (item: T) => boolean): Promise<number>;
|
|
85
|
+
/**
|
|
86
|
+
* Find the first item matching a predicate
|
|
87
|
+
*/
|
|
88
|
+
find(predicate: (item: T) => boolean): Promise<T | null>;
|
|
89
|
+
/**
|
|
90
|
+
* Check if any item matches a predicate
|
|
91
|
+
*/
|
|
92
|
+
some(predicate: (item: T) => boolean): Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Check if all items match a predicate
|
|
95
|
+
*/
|
|
96
|
+
every(predicate: (item: T) => boolean): Promise<boolean>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create streaming operations for a file
|
|
100
|
+
*/
|
|
101
|
+
export declare function createStreamingOperations<T>(filePath: string, logger?: Logger): StreamingJsonOperations<T>;
|
|
102
|
+
//# sourceMappingURL=streaming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../../src/storage/streaming.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming JSON support for BillClaw
|
|
3
|
+
*
|
|
4
|
+
* Provides streaming JSON parsing and generation for large datasets.
|
|
5
|
+
* This keeps memory usage constant regardless of file size.
|
|
6
|
+
*
|
|
7
|
+
* Use cases:
|
|
8
|
+
* - Reading large transaction files without loading entire file
|
|
9
|
+
* - Writing large datasets incrementally
|
|
10
|
+
* - Processing transactions one at a time
|
|
11
|
+
*/
|
|
12
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
13
|
+
import { createInterface } from "node:readline";
|
|
14
|
+
/**
|
|
15
|
+
* Write items to a JSON file incrementally
|
|
16
|
+
*
|
|
17
|
+
* @param filePath - Path to output file
|
|
18
|
+
* @param items - Async iterable of items to write
|
|
19
|
+
* @param options - Write options
|
|
20
|
+
*/
|
|
21
|
+
export async function writeStreamingJson(filePath, items, options = {}) {
|
|
22
|
+
const { batchSize = 100, logger } = options;
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const writeStream = createWriteStream(filePath);
|
|
25
|
+
let first = true;
|
|
26
|
+
let count = 0;
|
|
27
|
+
writeStream.write("[");
|
|
28
|
+
(async () => {
|
|
29
|
+
try {
|
|
30
|
+
for await (const item of items) {
|
|
31
|
+
if (!first) {
|
|
32
|
+
writeStream.write(",");
|
|
33
|
+
}
|
|
34
|
+
writeStream.write(JSON.stringify(item));
|
|
35
|
+
first = false;
|
|
36
|
+
count++;
|
|
37
|
+
if (count % batchSize === 0) {
|
|
38
|
+
logger?.debug?.(`Written ${count} items to ${filePath}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
writeStream.write("]");
|
|
42
|
+
writeStream.end();
|
|
43
|
+
logger?.info?.(`Finished writing ${count} items to ${filePath}`);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
writeStream.destroy();
|
|
47
|
+
reject(error);
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
writeStream.on("finish", resolve);
|
|
51
|
+
writeStream.on("error", reject);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read items from a JSON file incrementally
|
|
56
|
+
*
|
|
57
|
+
* @param filePath - Path to input file
|
|
58
|
+
* @param options - Read options
|
|
59
|
+
* @returns Async iterable of items
|
|
60
|
+
*/
|
|
61
|
+
export async function* readStreamingJson(filePath, options = {}) {
|
|
62
|
+
const { filter, transform, logger } = options;
|
|
63
|
+
let count = 0;
|
|
64
|
+
const fileStream = createReadStream(filePath);
|
|
65
|
+
const rl = createInterface({
|
|
66
|
+
input: fileStream,
|
|
67
|
+
crlfDelay: Infinity,
|
|
68
|
+
});
|
|
69
|
+
let buffer = "";
|
|
70
|
+
for await (const line of rl) {
|
|
71
|
+
buffer += line;
|
|
72
|
+
// Find complete JSON objects
|
|
73
|
+
let braceCount = 0;
|
|
74
|
+
let objStart = -1;
|
|
75
|
+
let inString = false;
|
|
76
|
+
let escapeNext = false;
|
|
77
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
78
|
+
const char = buffer[i];
|
|
79
|
+
if (escapeNext) {
|
|
80
|
+
escapeNext = false;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (char === "\\") {
|
|
84
|
+
escapeNext = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (char === '"') {
|
|
88
|
+
inString = !inString;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (inString) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (char === "{") {
|
|
95
|
+
if (objStart === -1) {
|
|
96
|
+
objStart = i;
|
|
97
|
+
}
|
|
98
|
+
braceCount++;
|
|
99
|
+
}
|
|
100
|
+
else if (char === "}") {
|
|
101
|
+
braceCount--;
|
|
102
|
+
if (braceCount === 0 && objStart !== -1) {
|
|
103
|
+
const jsonStr = buffer.substring(objStart, i + 1);
|
|
104
|
+
buffer = buffer.substring(i + 1).trim();
|
|
105
|
+
try {
|
|
106
|
+
let item = JSON.parse(jsonStr);
|
|
107
|
+
if (transform) {
|
|
108
|
+
item = transform(item);
|
|
109
|
+
}
|
|
110
|
+
if (!filter || filter(item)) {
|
|
111
|
+
count++;
|
|
112
|
+
yield item;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
logger?.error?.("Failed to parse JSON item", error);
|
|
117
|
+
}
|
|
118
|
+
i = -1; // Reset position
|
|
119
|
+
objStart = -1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
logger?.debug?.(`Read ${count} items from ${filePath}`);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Stream transactions from a file
|
|
128
|
+
*
|
|
129
|
+
* @param filePath - Path to transaction file
|
|
130
|
+
* @param options - Stream options
|
|
131
|
+
* @returns Async iterable of transactions
|
|
132
|
+
*/
|
|
133
|
+
export async function* streamTransactions(filePath, options) {
|
|
134
|
+
yield* readStreamingJson(filePath, options);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Write transactions to a file incrementally
|
|
138
|
+
*
|
|
139
|
+
* @param filePath - Path to output file
|
|
140
|
+
* @param transactions - Async iterable of transactions
|
|
141
|
+
* @param options - Write options
|
|
142
|
+
*/
|
|
143
|
+
export async function writeTransactionsStreaming(filePath, transactions, options) {
|
|
144
|
+
await writeStreamingJson(filePath, transactions, options);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Map/reduce operations on streaming JSON
|
|
148
|
+
*/
|
|
149
|
+
export class StreamingJsonOperations {
|
|
150
|
+
filePath;
|
|
151
|
+
logger;
|
|
152
|
+
constructor(filePath, logger) {
|
|
153
|
+
this.filePath = filePath;
|
|
154
|
+
this.logger = logger;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Map each item through a transformation function
|
|
158
|
+
*/
|
|
159
|
+
async map(fn) {
|
|
160
|
+
const results = [];
|
|
161
|
+
for await (const item of readStreamingJson(this.filePath, {
|
|
162
|
+
logger: this.logger,
|
|
163
|
+
})) {
|
|
164
|
+
results.push(fn(item));
|
|
165
|
+
}
|
|
166
|
+
return results;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Filter items
|
|
170
|
+
*/
|
|
171
|
+
async filter(fn) {
|
|
172
|
+
const results = [];
|
|
173
|
+
for await (const item of readStreamingJson(this.filePath, {
|
|
174
|
+
filter: fn,
|
|
175
|
+
logger: this.logger,
|
|
176
|
+
})) {
|
|
177
|
+
results.push(item);
|
|
178
|
+
}
|
|
179
|
+
return results;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Reduce items to a single value
|
|
183
|
+
*/
|
|
184
|
+
async reduce(fn, initial) {
|
|
185
|
+
let acc = initial;
|
|
186
|
+
for await (const item of readStreamingJson(this.filePath, {
|
|
187
|
+
logger: this.logger,
|
|
188
|
+
})) {
|
|
189
|
+
acc = fn(acc, item);
|
|
190
|
+
}
|
|
191
|
+
return acc;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Count items matching a predicate
|
|
195
|
+
*/
|
|
196
|
+
async count(predicate) {
|
|
197
|
+
let count = 0;
|
|
198
|
+
for await (const _item of readStreamingJson(this.filePath, {
|
|
199
|
+
filter: predicate,
|
|
200
|
+
logger: this.logger,
|
|
201
|
+
})) {
|
|
202
|
+
count++;
|
|
203
|
+
}
|
|
204
|
+
return count;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Find the first item matching a predicate
|
|
208
|
+
*/
|
|
209
|
+
async find(predicate) {
|
|
210
|
+
for await (const item of readStreamingJson(this.filePath, {
|
|
211
|
+
logger: this.logger,
|
|
212
|
+
})) {
|
|
213
|
+
if (predicate(item)) {
|
|
214
|
+
return item;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Check if any item matches a predicate
|
|
221
|
+
*/
|
|
222
|
+
async some(predicate) {
|
|
223
|
+
return (await this.find(predicate)) !== null;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Check if all items match a predicate
|
|
227
|
+
*/
|
|
228
|
+
async every(predicate) {
|
|
229
|
+
for await (const item of readStreamingJson(this.filePath, {
|
|
230
|
+
logger: this.logger,
|
|
231
|
+
})) {
|
|
232
|
+
if (!predicate(item)) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Create streaming operations for a file
|
|
241
|
+
*/
|
|
242
|
+
export function createStreamingOperations(filePath, logger) {
|
|
243
|
+
return new StreamingJsonOperations(filePath, logger);
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=streaming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming.js","sourceRoot":"","sources":["../../src/storage/streaming.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local file storage utilities for BillClaw data
|
|
3
|
+
*/
|
|
4
|
+
import type { StorageConfig } from "../models/config.js";
|
|
5
|
+
export interface Transaction {
|
|
6
|
+
transactionId: string;
|
|
7
|
+
accountId: string;
|
|
8
|
+
date: string;
|
|
9
|
+
amount: number;
|
|
10
|
+
currency: string;
|
|
11
|
+
category: string[];
|
|
12
|
+
merchantName: string;
|
|
13
|
+
paymentChannel: string;
|
|
14
|
+
pending: boolean;
|
|
15
|
+
plaidTransactionId: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Sync state for idempotency
|
|
20
|
+
*/
|
|
21
|
+
export interface SyncState {
|
|
22
|
+
syncId: string;
|
|
23
|
+
accountId: string;
|
|
24
|
+
startedAt: string;
|
|
25
|
+
completedAt?: string;
|
|
26
|
+
status: "running" | "completed" | "failed";
|
|
27
|
+
transactionsAdded: number;
|
|
28
|
+
transactionsUpdated: number;
|
|
29
|
+
cursor: string;
|
|
30
|
+
error?: string;
|
|
31
|
+
requiresReauth?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Account registry entry
|
|
35
|
+
*/
|
|
36
|
+
export interface AccountRegistry {
|
|
37
|
+
id: string;
|
|
38
|
+
type: string;
|
|
39
|
+
name: string;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
lastSync?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Global cursor for incremental sync
|
|
45
|
+
*/
|
|
46
|
+
export interface GlobalCursor {
|
|
47
|
+
lastSyncTime: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the base storage directory
|
|
51
|
+
*/
|
|
52
|
+
export declare function getStorageDir(config?: StorageConfig): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Initialize storage directory structure
|
|
55
|
+
*/
|
|
56
|
+
export declare function initializeStorage(config?: StorageConfig): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Read account registry
|
|
59
|
+
*/
|
|
60
|
+
export declare function readAccountRegistry(config?: StorageConfig): Promise<AccountRegistry[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Write account registry
|
|
63
|
+
*/
|
|
64
|
+
export declare function writeAccountRegistry(accounts: AccountRegistry[], config?: StorageConfig): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Read transactions for an account and month
|
|
67
|
+
*/
|
|
68
|
+
export declare function readTransactions(accountId: string, year: number, month: number, config?: StorageConfig): Promise<Transaction[]>;
|
|
69
|
+
/**
|
|
70
|
+
* Write transactions for an account and month
|
|
71
|
+
* Uses atomic write (temp file + rename) for safety
|
|
72
|
+
*/
|
|
73
|
+
export declare function writeTransactions(accountId: string, year: number, month: number, transactions: Transaction[], config?: StorageConfig): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Append transactions to existing month file (with deduplication)
|
|
76
|
+
*/
|
|
77
|
+
export declare function appendTransactions(accountId: string, year: number, month: number, newTransactions: Transaction[], config?: StorageConfig): Promise<{
|
|
78
|
+
added: number;
|
|
79
|
+
updated: number;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Read sync state for an account
|
|
83
|
+
*/
|
|
84
|
+
export declare function readSyncStates(accountId: string, config?: StorageConfig): Promise<SyncState[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Write sync state
|
|
87
|
+
*/
|
|
88
|
+
export declare function writeSyncState(state: SyncState, config?: StorageConfig): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Read global cursor
|
|
91
|
+
*/
|
|
92
|
+
export declare function readGlobalCursor(config?: StorageConfig): Promise<GlobalCursor | null>;
|
|
93
|
+
/**
|
|
94
|
+
* Write global cursor
|
|
95
|
+
*/
|
|
96
|
+
export declare function writeGlobalCursor(cursor: GlobalCursor, config?: StorageConfig): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Deduplicate transactions within a time window (24 hours)
|
|
99
|
+
*/
|
|
100
|
+
export declare function deduplicateTransactions(transactions: Transaction[], windowHours?: number): Transaction[];
|
|
101
|
+
//# sourceMappingURL=transaction-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-storage.d.ts","sourceRoot":"","sources":["../../src/storage/transaction-storage.ts"],"names":[],"mappings":"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"}
|